Appearance
@znight/core available
The runtime of Znight. It executes the metadata generated by the compiler: no reflection, no runtime type lookup; resolving a singleton is a single Map lookup.
sh
npm install @znight/coreRuns on Node.js 24+ and Bun 1.3+ (ESM only). Decorators require "experimentalDecorators": true.
At a glance
ts
import { Application, Injectable, type OnStart } from '@znight/core'
import { __ZNIGHT_METADATA__ } from './.znight/container.generated.js'
@Injectable()
export class Warmup implements OnStart {
constructor(private readonly cache: Cache) {}
async onStart() {
await this.cache.prime()
}
}
const app = await Application.create({ metadata: __ZNIGHT_METADATA__, plugins: [] })
await app.start()Guides: Dependency injection · Scopes · Lifecycle · Plugins & hooks · AOP
Decorators
All decorators are compile-time markers: each returns a shared no-op function.
| Decorator | Target | Purpose |
|---|---|---|
@Injectable({ scope?, name?, primary? }) | class | Declares a provider (singleton by default) |
@Factory() | class | A singleton whose @Provides methods produce providers |
@Provides({ scope?, name?, primary?, dispose? }) | method of a @Factory | Provides the method's return type (Promise<T> → async) |
@Inject(name) | constructor / @Provides parameter | Injects the provider qualified with name |
@Optional() | constructor / @Provides parameter | Injects undefined when no provider exists |
@Named(name) | class / @Provides method | Qualifier |
@Primary() | class / @Provides method | Preferred candidate |
@Intercept(Interceptor, options?) | method | Wraps calls with an interceptor provider |
Type helpers: Lazy<T>, OnStart, OnReady, OnStop, MethodInterceptor<O>, MethodInvocation.
Application
ts
const app = await Application.create(options)
await app.start()
await app.stop()| Option | Type | Description |
|---|---|---|
metadata | ZnightMetadata | The generated __ZNIGHT_METADATA__ (required). |
plugins | Plugin[] | Runtime features, in order. |
providers | Map / [key, value][] / Record | Values for externals or replacements of compiled providers. Keys: classes or tokens. |
overrides | same as providers | Applied last, highest precedence (tests). Definitions ({ scope, deps, factory }) accepted. |
logger | CoreLogger | Initial logger (console, level info). Replace later with app.setLogger(). |
shutdown | ShutdownOptions | { signals?: ShutdownSignal[] | false, timeoutMs?: 10_000, hookTimeoutMs?: 5_000, process? }; Infinity disables a budget |
init | InitOptions | Forwarded to container.init(), e.g. { only: [UserController] }. |
hot | import.meta.hot | Stops the application when the module is replaced; the HMR runtime waits for the stop. |
| Member | Description |
|---|---|
start() | Providers' onStart → plugins' onStart → onReady hooks; installs signal handlers |
stop() | Idempotent graceful shutdown in three phases (see Lifecycle); callable from a start hook |
get(key) | Shortcut for app.container.get(key) |
state | 'created' | 'starting' | 'ready' | 'stopping' | 'stopped' | 'failed' |
container, hooks, metadata, plugins | The application's parts |
logger, setLogger() | The live application logger and its replacement |
getPlugin(name) | A plugin by name |
Container
Created by the application (app.container), or directly with Container.create(metadata).
| Method | Description |
|---|---|
Container.create(metadata) | Checks the metadata format version (ZNIGHT018) |
register(token, definition) | Runtime provider definition (plugin precedence) |
registerValue(token, value) | Value for a token, typically an external (plugin precedence) |
registerExternal(definition) | Declares a runtime-provided token ({ token, scope?, providedBy?, optional? }) |
provide(key, value) | Value with application precedence (Application.create({ providers })) |
override(key, valueOrDefinition) | Replacement with the highest precedence |
init({ only? }) | Seals, validates and creates singletons (concurrently when independent) |
get(key) / getOptional(key) | Resolve by class, token, alias or unique display name (below) |
has(key) / tokenOf(key) | Lookups |
runInScope(fn, values?) | Runs fn in a new request scope seeded with [key, value] pairs |
openScope(values?) | The same scope without a callback: run(fn, ...args) in it, then close() it |
currentScope() | The active ScopeContext (get, set, has) or undefined |
instances() | Created singletons in resolution order |
dispose() | Disposes singletons in reverse creation order |
Precedence: override() > plugin register()/registerValue() > provide() > compiled. Any registration after init() throws ZNIGHT016.
Built-in tokens (CoreTokens): Container, Application, Hooks and Logger (CoreLogger) are injectable by any provider.
A class that is not a registered key (an abstract base behind an alias, a class supplied by a plugin) is looked up by its name, among the candidates of its own class hierarchy: a candidate of another class with the same name is ignored (a class of yours named Container never resolves the Znight Container), and candidates whose class is unknown (an external value, an implements fake given to override()) count only when nothing of the hierarchy matches. Singletons created on first use (init({ only })) are created outside of any request scope, even when a request uses them first.
Tokens
ts
import { token, type TypedToken } from '@znight/core'
export const ClockToken: TypedToken<Clock> = token<Clock>('@acme/time#Clock')
container.registerValue(ClockToken, systemClock) // checked against Clock
const clock = container.get(ClockToken) // ClockHelpers: displayName(token) ('UserService'), describeToken(token), formatPath(tokens) ('A -> B -> C').
Plugins
A Plugin is an object with a unique name and optional hooks, each receiving a PluginContext (app, container, metadata, hooks, logger, getPlugin(name)):
| Member | When |
|---|---|
register(ctx) | Before container.init(): supply externals (registerValue, register) |
onBootstrap(ctx) | After container.init(): read the metadata, resolve options, register routes, consumers |
onStart, onReady | app.start(): start ingress, then announce readiness |
onStop, onDispose | app.stop(), in reverse plugin order: drain, then release connections |
initRoots({ metadata }) | Not a lifecycle hook: the providers the plugin needs at boot (see below) |
initRoots(context: PluginRootsContext) returns the classes or tokens the plugin resolves at boot although the metadata does not make them entry points: the classes of its options (the global guards of HttpPlugin), or providers it derives from its own section of context.metadata. The application never calls it; tools that restrict the boot with init: { only }, such as TestApp, create these providers at boot with their dependencies. It is called before the application exists, possibly several times: keep it cheap, side-effect free and non-throwing, and return [] for options given as a function of the container.
PluginOptions<O> (options or a function of the container) and resolveOptions(options, container) implement the XxxPlugin.configure(container => options) convention. See Plugins & hooks.
Hooks
Hooks is a typed event bus: on(name, listener), emit(name, event), wrap(name, wrapper), run(name, event, fn), hasListeners(name), hasWrappers(name). The events of the feature packages are defined in core so producers and observers share one type:
| Hook | Event | Produced by |
|---|---|---|
http:request | HttpRequestEvent | @znight/http (one request) |
messaging:message | MessageDeliveryEvent | @znight/messaging (one delivery) |
messaging:publish | MessagePublishEvent | @znight/messaging (one publish) |
schedule:run | ScheduledRunEvent | @znight/schedule (one occurrence of a job: trigger, outcome, error) |
Augment ZnightHooks to declare your own. See Plugins & hooks.
Error code ranges
ZNIGHT001 to ZNIGHT029 belong to the core, the compiler and the features of 0.1 (http, messaging, config, observability). From ZNIGHT030, each first-party package owns a block of ten: ZNIGHT030–039 @znight/openapi, ZNIGHT040–049@znight/schedule (050–069 are reserved). Third-party packages get no ZNIGHT code. Every code is in the error reference.
AOP
MethodInterceptor<O> / MethodInvocation define interceptors; interceptMethod(target, method, chain, options?) and applyInterceptors(target, interceptions, deps) install chains (used by the container, handy in tests). See AOP.
Logging
CoreLogger is the minimal logger interface (debug, info, warn, error, each (message, context?)) used by the core and plugins. createConsoleLogger({ level?, prefix?, console? }), silentLogger and DelegatingLogger are provided; @znight/observability installs a structured pino logger through app.setLogger().
Errors
Every runtime error is a ZnightError with a code, hints and, when relevant, a cause.
| Export | Description |
|---|---|
ZnightError | Base class: code, hints |
InstantiationError | ZNIGHT015: token, path, source, cause |
ZnightAggregateError | Several failures (errors), e.g. ZNIGHT021 |
getSuppressedErrors(error) | Cleanup errors attached to an error |
ErrorCode, ERROR_DESCRIPTIONS | The catalogue shared with the compiler (reference) |
Metadata types
ZnightMetadata, ProviderDefinition, DependencyRef, ExternalDefinition, ZnightExtensionsMetadata, METADATA_VERSION... describe the contract between the compiler and the runtime. They are what custom plugins read and what tests can build by hand. Normative description: architecture §3.
Source
packages/core on GitHub, with its README.