@btravstack/core
@btravstack/core
Classes
HealthCheckFailed
Defined in: packages/core/src/health.ts:9
A component could not answer for itself. Modeled rather than thrown, because a health check that throws is a bug in the check; one that fails is the news /healthz exists to carry.
Extends
TaggedErrorInstance<"HealthCheckFailed", {reason:string; }>
Constructors
Constructor
new HealthCheckFailed(args): HealthCheckFailed;Defined in: node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("HealthCheckFailed")<{
readonly reason: string;
}>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
_tag | readonly | "HealthCheckFailed" | TaggedError("HealthCheckFailed")._tag | node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2011 |
cause? | public | unknown | TaggedError("HealthCheckFailed").cause | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24 |
message | public | string | TaggedError("HealthCheckFailed").message | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1075 |
name | public | string | TaggedError("HealthCheckFailed").name | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074 |
reason | readonly | string | TaggedError("HealthCheckFailed").reason | packages/core/src/health.ts:10 |
stack? | public | string | TaggedError("HealthCheckFailed").stack | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076 |
HealthChecks
Defined in: packages/core/src/health.ts:27
The set port every starter contributes to and the kernel reads whole.
A set port rather than a registry the kernel hands out: a starter DECLARES its check the same way it declares anything else, and a starter an application never composed contributes nothing — no registration call to forget, and no order to get wrong.
Extends
PortInstance<"HealthChecks", readonlyHealthCheck[]> &object
Constructors
Constructor
new HealthChecks(): HealthChecks;Defined in: packages/di/dist/index.d.mts:30
Returns
Inherited from
Port.many("HealthChecks")<HealthCheck>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "HealthChecks" | Port.many("HealthChecks").[ID] | packages/di/dist/index.d.mts:12 |
[MANY] | readonly | true | Port.many("HealthChecks").[MANY] | packages/di/dist/index.d.mts:31 |
[SERVICE] | readonly | readonly HealthCheck[] | Port.many("HealthChecks").[SERVICE] | packages/di/dist/index.d.mts:13 |
many | readonly | true | Port.many("HealthChecks").many | packages/di/dist/index.d.mts:34 |
portId | readonly | "HealthChecks" | Port.many("HealthChecks").portId | packages/di/dist/index.d.mts:33 |
Instrumentations
Defined in: packages/core/src/instrumentation.ts:32
The set port a package contributes its instrumentation to.
Nothing collects it unless an OTel SDK is composed, which is the point: a starter DECLARES what it can instrument, and composing @btravstack/observability/otel is what turns the declarations on. A graph without it registers nothing and pays nothing — the Spring Boot starter shape, in one port.
Extends
PortInstance<"Instrumentations", readonlyInstrumentationLoader[]> &object
Constructors
Constructor
new Instrumentations(): Instrumentations;Defined in: packages/di/dist/index.d.mts:30
Returns
Inherited from
Port.many("Instrumentations")<InstrumentationLoader>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "Instrumentations" | Port.many("Instrumentations").[ID] | packages/di/dist/index.d.mts:12 |
[MANY] | readonly | true | Port.many("Instrumentations").[MANY] | packages/di/dist/index.d.mts:31 |
[SERVICE] | readonly | readonly InstrumentationLoader[] | Port.many("Instrumentations").[SERVICE] | packages/di/dist/index.d.mts:13 |
many | readonly | true | HealthChecks.many | packages/di/dist/index.d.mts:34 |
portId | readonly | "Instrumentations" | Port.many("Instrumentations").portId | packages/di/dist/index.d.mts:33 |
Logger
Defined in: packages/core/src/observability.ts:59
The application's logger, as a port — declared here and implemented in @btravstack/observability, because a contract every package may depend on has to be reachable without installing an implementation, and because the correlation an implementation stamps per line is the kernel's own ambient record.
An implementation must not throw: a logger that throws turns an observability problem into an outage. Synchronous void, not an AsyncResult — thesis 6's one deliberate exemption, since a lost line is not a modeled error.
Extends
PortInstance<"Logger",LoggerService>
Constructors
Constructor
new Logger(): Logger;Defined in: packages/di/dist/index.d.mts:16
Returns
Inherited from
Port("Logger")<LoggerService>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "Logger" | Port("Logger").[ID] | packages/di/dist/index.d.mts:12 |
[SERVICE] | readonly | LoggerService | Port("Logger").[SERVICE] | packages/di/dist/index.d.mts:13 |
portId | readonly | "Logger" | Port("Logger").portId | packages/di/dist/index.d.mts:17 |
Meter
Defined in: packages/core/src/observability.ts:115
The application's meter, as a port. See Tracer for why it does not name a vendor.
Extends
PortInstance<"Meter",MeterService>
Constructors
Constructor
new Meter(): Meter;Defined in: packages/di/dist/index.d.mts:16
Returns
Inherited from
Port("Meter")<MeterService>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "Meter" | Port("Meter").[ID] | packages/di/dist/index.d.mts:12 |
[SERVICE] | readonly | MeterService | Port("Meter").[SERVICE] | packages/di/dist/index.d.mts:13 |
portId | readonly | "Meter" | Port("Meter").portId | packages/di/dist/index.d.mts:17 |
Observers
Defined in: packages/core/src/observation.ts:86
The set port a starter contributes its observability to, and reads whole.
Called at the START and answering a finisher, rather than told about a finished operation. That is what lets an observer open a span before the work and end it after — a span reconstructed afterwards from a duration is not the parent of anything that ran inside it.
A set port rather than a Meter in every starter's needs: an observer package an application never composed contributes nothing, and a starter costs a graph no ports to have observability available. That is the difference between "instrumented by default" and "instrumented if you asked for observability", and only the second is free.
A module that reads this port contributes a no-op member of its own, the way otel() does for Instrumentations: a collector depending on a set port NOTHING provides is an unmet dependency, at plan time and in Needs alike. Several no-ops in one graph cost a call each and nothing else.
Extends
PortInstance<"Observers", readonly (operation) =>Settle[]> &object
Constructors
Constructor
new Observers(): Observers;Defined in: packages/di/dist/index.d.mts:30
Returns
Inherited from
Port.many("Observers")<(operation: Operation) => Settle>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "Observers" | Port.many("Observers").[ID] | packages/di/dist/index.d.mts:12 |
[MANY] | readonly | true | Port.many("Observers").[MANY] | packages/di/dist/index.d.mts:31 |
[SERVICE] | readonly | readonly (operation) => Settle[] | Port.many("Observers").[SERVICE] | packages/di/dist/index.d.mts:13 |
many | readonly | true | Port.many("Observers").many | packages/di/dist/index.d.mts:34 |
portId | readonly | "Observers" | Port.many("Observers").portId | packages/di/dist/index.d.mts:33 |
RuntimeStartFailed
Defined in: packages/core/src/runtime.ts:16
Extends
TaggedErrorInstance<"RuntimeStartFailed", {cause:unknown;runtime:string; }>
Constructors
Constructor
new RuntimeStartFailed(args): RuntimeStartFailed;Defined in: node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("RuntimeStartFailed")<{
readonly runtime: string;
readonly cause: unknown;
}>.constructorProperties
| Property | Modifier | Type | Overrides | Inherited from | Defined in |
|---|---|---|---|---|---|
_tag | readonly | "RuntimeStartFailed" | - | TaggedError("RuntimeStartFailed")._tag | node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2011 |
cause | public | unknown | - | HealthCheckFailed.cause | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24 |
message | public | string | TaggedError("RuntimeStartFailed").message | - | packages/core/src/runtime.ts:20 |
name | public | string | - | TaggedError("RuntimeStartFailed").name | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074 |
runtime | readonly | string | - | TaggedError("RuntimeStartFailed").runtime | packages/core/src/runtime.ts:17 |
stack? | public | string | - | TaggedError("RuntimeStartFailed").stack | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076 |
Tracer
Defined in: packages/core/src/observability.ts:89
The application's tracer, as a port — declared without naming OpenTelemetry, because a port typed as a vendor's type points the dependency arrow outwards and makes the whole family install that vendor.
Extends
PortInstance<"Tracer",TracerService>
Constructors
Constructor
new Tracer(): Tracer;Defined in: packages/di/dist/index.d.mts:16
Returns
Inherited from
Port("Tracer")<TracerService>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "Tracer" | Port("Tracer").[ID] | packages/di/dist/index.d.mts:12 |
[SERVICE] | readonly | TracerService | Port("Tracer").[SERVICE] | packages/di/dist/index.d.mts:13 |
portId | readonly | "Tracer" | Port("Tracer").portId | packages/di/dist/index.d.mts:17 |
Type Aliases
Attributes
type Attributes = Readonly<Record<string, string | number | boolean | undefined>>;Defined in: packages/core/src/observability.ts:22
What a line, a span or a measurement carries besides itself: a flat record of scalars.
Flat and scalar on purpose. A nested object is where a field's name stops being stable, and an unknown value is where a logger starts stringifying whatever it is handed — which is how a log call becomes the thing that throws. A failure has a channel of its own, cause.
Clock
type Clock = object;Defined in: packages/core/src/clock.ts:3
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
now | readonly | () => number | packages/core/src/clock.ts:4 |
sleep | readonly | (ms, signal?) => AsyncResult<void, never> | packages/core/src/clock.ts:5 |
ComponentHealth
type ComponentHealth = object;Defined in: packages/core/src/health.ts:29
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
name | readonly | string | - | packages/core/src/health.ts:30 |
reason? | readonly | string | Present only when the component is unhealthy. | packages/core/src/health.ts:33 |
status | readonly | "healthy" | "unhealthy" | - | packages/core/src/health.ts:31 |
Counter
type Counter = object;Defined in: packages/core/src/observability.ts:92
A monotonic count.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
add | readonly | (value, attributes?) => void | packages/core/src/observability.ts:92 |
DrainReport
type DrainReport = object;Defined in: packages/core/src/drain.ts:7
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
abandoned | readonly | number | Units still open at the deadline. The exit-code decision reads this. | packages/core/src/drain.ts:17 |
completed | readonly | number | Units that closed during the drain, counted from a monotonic total. It may exceed inFlightAtStart when in-flight work spawned more units — honest reporting, where inFlightAtStart - abandoned can go negative. | packages/core/src/drain.ts:15 |
inFlightAtStart | readonly | number | Units in flight when the drain began. | packages/core/src/drain.ts:9 |
EventSink
type EventSink = (event) => void;Defined in: packages/core/src/events.ts:46
Parameters
| Parameter | Type |
|---|---|
event | KernelEvent |
Returns
void
ExitReport
type ExitReport = object;Defined in: packages/core/src/start.ts:46
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
abandonedAt? | readonly | "build" | "stop" | Set when the kernel stopped WAITING for a phase that has no deadline of its own — "stop" for a Serving.stop or a finaliser still running at stopTimeoutMs, "build" for a graph abandoned before it ever served. It is "stopped waiting", not "cancelled": nothing here can cancel a finaliser, so a wedged one can still hold the event loop past this report and end in SIGKILL. What the field buys is that the report EXISTS and says which phase ran long — where the alternative was a process stuck in stopping with no event, no code and no exit report at all. | packages/core/src/start.ts:62 |
drain | readonly | DrainReport | undefined | - | packages/core/src/start.ts:48 |
reason | readonly | "signal" | "runtimeStopped" | "uncaught" | - | packages/core/src/start.ts:47 |
teardownErrors | readonly | readonly TeardownError[] | - | packages/core/src/start.ts:49 |
uptimeMs | readonly | number | - | packages/core/src/start.ts:50 |
HealthCheck
type HealthCheck = object;Defined in: packages/core/src/health.ts:14
One contribution: what to call the component, and how to ask it.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
check | readonly | () => AsyncResult<void, HealthCheckFailed> | packages/core/src/health.ts:16 |
name | readonly | string | packages/core/src/health.ts:15 |
HealthReport
type HealthReport = object;Defined in: packages/core/src/health.ts:36
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
components | readonly | readonly ComponentHealth[] | packages/core/src/health.ts:38 |
status | readonly | "healthy" | "unhealthy" | packages/core/src/health.ts:37 |
Histogram
type Histogram = object;Defined in: packages/core/src/observability.ts:95
A distribution of measurements.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
record | readonly | (value, attributes?) => void | packages/core/src/observability.ts:95 |
InstrumentationLoader
type InstrumentationLoader = () => Promise<unknown>;Defined in: packages/core/src/instrumentation.ts:21
One package's offer of an OpenTelemetry instrumentation.
Async, and answers undefined rather than failing, because the package supplying the instrumentation is an OPTIONAL peer. A starter cannot know at composition time whether the consumer installed it, so it contributes the ATTEMPT and the collector drops what did not load — which is what lets @btravstack/prisma declare engine tracing without making every consumer install @prisma/instrumentation. A contributor that wants to say why its load answered nothing logs that itself; it is the one that knows.
The instrumentation is unknown on purpose. Naming OpenTelemetry's Instrumentation would put the vendor in the package every other one peers on — the same reason Tracer and Meter are declared as narrowings rather than as OTel's own types. The collector (@btravstack/observability/otel) is where the vendor is already a dependency, so that is where the cast belongs.
Returns
Promise<unknown>
KernelEvent
type KernelEvent =
| {
type: "building";
}
| {
cause: unknown;
type: "startFailed";
}
| {
info: unknown;
probePort: number | undefined;
runtime: string;
type: "serving";
}
| {
inFlight: number;
type: "draining";
}
| {
report: DrainReport;
type: "drained";
}
| {
afterMs: number | undefined;
phase: "build" | "stop";
type: "stoppedWaiting";
}
| {
type: "stopping";
}
| {
type: "exited";
}
| {
cause: unknown;
port: string;
type: "teardownError";
}
| {
cause: unknown;
type: "uncaught";
};Defined in: packages/core/src/events.ts:3
Union Members
Type Literal
{
type: "building";
}Type Literal
{
cause: unknown;
type: "startFailed";
}Type Literal
{
info: unknown;
probePort: number | undefined;
runtime: string;
type: "serving";
}| Name | Type | Description | Defined in |
|---|---|---|---|
info | unknown | Whatever the runtime published on Serving.info. unknown because the kernel does not know a runtime's Info at the event union — it is read off the module at start's call site, not here — and a sink is serialising it anyway. A generic KernelEvent<Info> would infect EventSink, stderrSink and every adapter for one field none of them reads structurally. | packages/core/src/events.ts:17 |
probePort | number | undefined | The port the kernel's own probe listener bound, undefined when probes are off. Its own field rather than folded into info: the probe server is the kernel's, not the runtime's, so publishing it as something the runtime said would be a small lie. | packages/core/src/events.ts:24 |
runtime | string | - | packages/core/src/events.ts:8 |
type | "serving" | - | packages/core/src/events.ts:7 |
Type Literal
{
inFlight: number;
type: "draining";
}Type Literal
{
report: DrainReport;
type: "drained";
}Type Literal
{
afterMs: number | undefined;
phase: "build" | "stop";
type: "stoppedWaiting";
}| Name | Type | Description | Defined in |
|---|---|---|---|
afterMs | number | undefined | The deadline that expired, or undefined when a second signal cut the wait short. | packages/core/src/events.ts:39 |
phase | "build" | "stop" | "stop" for Serving.stop and the finalisers, "build" for a graph that never served. | packages/core/src/events.ts:37 |
type | "stoppedWaiting" | The kernel stopped WAITING for a phase that has no deadline of its own, and reported anyway. Without it a stop that ran long and a stop that finished are the same two lines on stderr (stopping, exited), which is the silence ExitReport.abandonedAt exists to end. | packages/core/src/events.ts:35 |
Type Literal
{
type: "stopping";
}Type Literal
{
type: "exited";
}Type Literal
{
cause: unknown;
port: string;
type: "teardownError";
}Type Literal
{
cause: unknown;
type: "uncaught";
}Level
type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";Defined in: packages/core/src/observability.ts:8
The severity of one line, and the whole of the set: six levels, ordered, with no caller-defined additions — which is what lets LOG_LEVEL be validated at startup and isEnabled be a comparison rather than a lookup.
LoggerService
type LoggerService = object;Defined in: packages/core/src/observability.ts:33
Every method takes the same three arguments in the same order: (message, attributes?, cause?).
Uniform on purpose. A failure is not a property of severity — an info line reporting a recovered fault carries one too — so every level can take a cause, at the cost of logger.error("boom", undefined, cause) for a failure with nothing else to say.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
debug | readonly | (message, attributes?, cause?) => void | - | packages/core/src/observability.ts:36 |
error | readonly | (message, attributes?, cause?) => void | cause is the failure itself — an Error, an unthrown Err's error, a rejected value. | packages/core/src/observability.ts:40 |
fatal | readonly | (message, attributes?, cause?) => void | - | packages/core/src/observability.ts:41 |
info | readonly | (message, attributes?, cause?) => void | - | packages/core/src/observability.ts:37 |
isEnabled | readonly | (level) => boolean | Whether a line at level would be written — for a payload expensive enough to be worth not building. | packages/core/src/observability.ts:45 |
log | readonly | (level, message, attributes?, cause?) => void | - | packages/core/src/observability.ts:34 |
trace | readonly | (message, attributes?, cause?) => void | - | packages/core/src/observability.ts:35 |
warn | readonly | (message, attributes?, cause?) => void | - | packages/core/src/observability.ts:38 |
with | readonly | (attributes) => LoggerService | A logger carrying attributes on every line it writes, on top of this one's. Never mutates this one. | packages/core/src/observability.ts:43 |
MeterService
type MeterService = object;Defined in: packages/core/src/observability.ts:103
What a Meter does: mint the two instruments a framework package needs — it counts what happened and measures how long it took. A gauge is something an application declares about its own domain, reaching the vendor's meter for it as it would any other adapter.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
createCounter | readonly | (name, options?) => Counter | packages/core/src/observability.ts:104 |
createHistogram | readonly | (name, options?) => Histogram | packages/core/src/observability.ts:108 |
Operation
type Operation = object;Defined in: packages/core/src/observation.ts:19
One operation worth observing, named before it runs.
component is the starter's own word for itself — "http", "cache", "amqp" — and it is what lets an observer mint per-component instrument and span names without knowing anything about the component. name is the operation within it ("get", "unit"), and attributes are the dimensions known before the call.
The dimensions are the contributor's business and their cardinality is its responsibility: a request path or a workflow id here mints a time series per value, which is how a metrics bill becomes the incident.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
attributes | readonly | Attributes | The DIMENSIONS: bounded, and what a metrics observer may put on an instrument. A cache operation, an HTTP method, an activity type. Their cardinality is the contributor's responsibility — a request path or a cache key here mints a time series per value, which is how a metrics bill becomes the incident. That is what Operation.details is for. | packages/core/src/observation.ts:30 |
component | readonly | string | - | packages/core/src/observation.ts:20 |
details? | readonly | Attributes | Unbounded context: a cache key, a mail subject, a URL. It rides spans and log lines, where one more field costs one more field — and never a metric, where it would cost a time series per value. The split is the whole reason an observer can be shared: without it every contributor would have to choose between a useful span and a safe metric. | packages/core/src/observation.ts:39 |
name | readonly | string | - | packages/core/src/observation.ts:21 |
traced? | readonly | boolean | Whether a tracing observer should open a span (default true). false is for a component whose spans already come from somewhere better: @btravstack/prisma says so because @prisma/instrumentation traces at the ENGINE level — the real SQL, the connection acquisition — all of it below what a client-level wrapper can see, so a second span would cost one more per query for strictly less information. Counting and timing still happen. Whether a span is worth opening is the CONTRIBUTOR's knowledge, which is why it rides the operation rather than being configured on the observer. | packages/core/src/observation.ts:52 |
Phase
type Phase = "building" | "starting" | "serving" | "draining" | "stopping" | "exited";Defined in: packages/core/src/phase.ts:1
RunningApp
type RunningApp<E, Info> = object;Defined in: packages/core/src/start.ts:172
Type Parameters
| Type Parameter | Default type |
|---|---|
E | - |
Info | never |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
exited | readonly | AsyncResult<ExitReport, E | RuntimeStartFailed> | - | packages/core/src/start.ts:173 |
phase | readonly | () => Phase | - | packages/core/src/start.ts:176 |
probePort | readonly | () => AsyncResult<number | undefined, never> | The port the probe server actually bound, once the bind attempt has settled; undefined when probes are disabled or the bind failed. | packages/core/src/start.ts:187 |
ready | readonly | () => boolean | The predicate /readyz answers from — serving, and not forced unready by a drain or an uncaught exception — read synchronously, which the probe endpoint is not. | packages/core/src/start.ts:182 |
requestDrain | readonly | () => void | - | packages/core/src/start.ts:175 |
runtimeInfo | readonly | () => AsyncResult<Info | undefined, never> | Whatever the runtime published on Serving.info once it is serving; undefined when it publishes nothing or never reached serving. | packages/core/src/start.ts:192 |
stop | readonly | () => void | - | packages/core/src/start.ts:174 |
Runtime
type Runtime<Resolves, Info> = object;Defined in: packages/core/src/runtime.ts:109
Type Parameters
| Type Parameter | Default type |
|---|---|
Resolves extends AnyPort | never |
Info | never |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
name | readonly | string | packages/core/src/runtime.ts:110 |
resolves | readonly | readonly Resolves[] | packages/core/src/runtime.ts:115 |
start | readonly | (host) => AsyncResult<Serving<Info>, RuntimeStartFailed> | packages/core/src/runtime.ts:116 |
RuntimeHost
type RuntimeHost<Resolves> = object;Defined in: packages/core/src/runtime.ts:88
What a runtime is handed at start: the application services and the kernel's RunUnit. Handing it a bare Context would leave every runtime inventing its own unit tracking — the thing the kernel exists to own.
Remarks
The two contracts a runtime author owes, neither checkable by the kernel: a unit's response must be flushed inside the work callback (see RunUnit), and UnitMeta.id must be unique per unit unless a traceId is supplied (see UnitMeta).
ctx is the application context: a port a unit's fork module provides exists only in the Context fork hands back, so a runtime naming it in resolves is rejected at start's call site.
Type Parameters
| Type Parameter |
|---|
Resolves extends AnyPort |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
ctx | readonly | Context<InstanceType<Resolves>> | packages/core/src/runtime.ts:89 |
run | readonly | RunUnit<Resolves> | packages/core/src/runtime.ts:90 |
RuntimeInfoOf
type RuntimeInfoOf<X> = RuntimeOf<X> extends object ? Info : never;Defined in: packages/core/src/runtime.ts:146
Type Parameters
| Type Parameter |
|---|
X |
RunUnit
type RunUnit<Resolves> = <T, E>(meta, work) => AsyncResult<T, E>;Defined in: packages/core/src/runtime.ts:47
Submit one piece of work as a unit: the kernel counts it towards the drain, hands it an AbortSignal and an ambient record, and gives the work's own Result straight back — mapping that outcome to a transport is the runtime's job, never the kernel's.
Type Parameters
| Type Parameter |
|---|
Resolves extends AnyPort |
Type Parameters
| Type Parameter |
|---|
T |
E |
Parameters
| Parameter | Type |
|---|---|
meta | UnitMeta |
work | (unit, signal) => ReturnType<UnitWork<T, E>> |
Returns
AsyncResult<T, E>
Remarks
Everything the client must receive has to be flushed INSIDE work, never after the returned AsyncResult settles. A unit closes the instant its Result settles, and an idle registry is the drain's permission to call Serving.stop() — so a runtime that writes after resolving is racing the transport being torn down.
UnitMeta.id must be unique per unit unless a traceId is supplied — see UnitMeta.
A runtime forks the unit's scope itself, through UnitHost.fork, at the moment it holds the unit's input; the kernel closes that scope when the unit settles, after the response is flushed. Work that subscribes to an event after a fork must first check whether it already fired.
Serving
type Serving<Info> = object;Defined in: packages/core/src/runtime.ts:101
What a runtime is, once it is up — plus, optionally, what it wants to say about itself, read back through RunningApp.runtimeInfo().
Info is the runtime's own shape rather than a port number: a queue consumer has no port and might publish { queue, prefetch }. It defaults to never, so info is unwritable for a runtime with nothing to publish.
Type Parameters
| Type Parameter | Default type |
|---|---|
Info | never |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
drain | readonly | (signal) => AsyncResult<void, never> | packages/core/src/runtime.ts:104 |
info? | readonly | Info | packages/core/src/runtime.ts:106 |
stop | readonly | () => AsyncResult<void, never> | packages/core/src/runtime.ts:105 |
Settle
type Settle = (settled) => void;Defined in: packages/core/src/observation.ts:65
What an observer answers: a finisher, called exactly once when the operation settles.
Parameters
| Parameter | Type |
|---|---|
settled | Settled |
Returns
void
Settled
type Settled = object;Defined in: packages/core/src/observation.ts:56
How an operation came out, plus whatever was only knowable at the end.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
attributes? | readonly | Attributes | Dimensions known only at the end, merged over the operation's own — an HTTP status, a cache hit or miss. | packages/core/src/observation.ts:59 |
cause? | readonly | unknown | The failure itself, for an observer that writes a line about it. | packages/core/src/observation.ts:61 |
outcome | readonly | "ok" | "error" | - | packages/core/src/observation.ts:57 |
Span
type Span = object;Defined in: packages/core/src/observability.ts:72
One span, narrowed to what a framework package does with it: label it, say how it ended, end it. The numbers are OpenTelemetry's own, so an OTel span satisfies this structurally with no translation in between.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
end | readonly | () => void | packages/core/src/observability.ts:78 |
setAttributes | readonly | (attributes) => unknown | packages/core/src/observability.ts:73 |
setStatus | readonly | (status) => unknown | packages/core/src/observability.ts:74 |
SpanStatusCode
type SpanStatusCode = typeof SPAN_STATUS[keyof typeof SPAN_STATUS];Defined in: packages/core/src/observability.ts:65
The three codes SPAN_STATUS names, as the type a Span takes.
StartGate
type StartGate<X, N> = [Exclude<N, Scope | Env>] extends [never] ? [Extract<X, RuntimeInstance>] extends [never] ? "NO RUNTIME — the module exports no port declared over RuntimePort" : [InstanceType<RuntimeResolvesOf<X>>] extends [X] ? unknown : "UNSATISFIED RUNTIME PORTS — the runtime resolves a port the module does not export" : object;Defined in: packages/core/src/start.ts:229
Type Parameters
| Type Parameter | Default type |
|---|---|
X | - |
N | never |
StartOptions
type StartOptions = object;Defined in: packages/core/src/start.ts:124
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
clock? | readonly | Clock | - | packages/core/src/start.ts:131 |
drainTimeoutMs? | readonly | number | How long in-flight work gets before it is aborted and reported abandoned. Unset, it is bound from DRAIN_TIMEOUT_MS in env (default 20_000). Keep it under the pod's terminationGracePeriodSeconds, which is the reason this one belongs in the environment: the two are set together, in the same manifest. | packages/core/src/start.ts:153 |
env? | readonly | Environment | The environment the graph is configured from, provided to it as the Env port and read for the kernel's own PROBE_PORT. Defaults to process.env. | packages/core/src/start.ts:130 |
onEvent? | readonly | EventSink | - | packages/core/src/start.ts:169 |
preDrainDelayMs? | readonly | number | How long readiness stays false before the runtime is told to stop accepting. Unset, it is bound from PRE_DRAIN_DELAY_MS in env (default 5_000) — the window that covers Kubernetes' eventually-consistent endpoint removal, which is a property of the cluster rather than of the code, so a deployment must be able to set it. | packages/core/src/start.ts:145 |
probes? | readonly | | { port: number; } | false | The probe server's port. Unset, it is bound from PROBE_PORT in env (default 9000); false disables the probe server. | packages/core/src/start.ts:137 |
signals? | readonly | boolean | - | packages/core/src/start.ts:132 |
stopTimeoutMs? | readonly | number | How long the kernel waits for Serving.stop and the application scope's finalisers together before it stops waiting and reports ExitReport.abandonedAt: "stop". Unset, it is bound from STOP_TIMEOUT_MS in env (default 5_000). It exists because beat 3's deadline covers in-flight WORK and nothing covered the teardown: a release that never settles — a socket to a host that stopped answering — left the phase at stopping with no exit report, which is the one artefact the lifecycle exists to produce. The three timings are cumulative against the pod's terminationGracePeriodSeconds, so the defaults sum to 30 s exactly (5_000 + 20_000 + 5_000): raise one and raise the grace period with it. | packages/core/src/start.ts:168 |
TeardownError
type TeardownError = object;Defined in: packages/core/src/start.ts:32
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
cause | readonly | unknown | packages/core/src/start.ts:32 |
port | readonly | string | packages/core/src/start.ts:32 |
TracerService
type TracerService = object;Defined in: packages/core/src/observability.ts:82
What a Tracer does: start a span, by name.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
startSpan | readonly | (name) => Span | packages/core/src/observability.ts:82 |
UnitHost
type UnitHost<Resolves> = object;Defined in: packages/core/src/runtime.ts:64
What the kernel hands a unit's work: the application context, and the one way to open the unit's own scope.
fork builds module over the application context plus seed and hands the forked context back. The scope is torn down when the unit closes — inside the registry's unit, so the unit is not counted closed until its finalisers have run, and inside the unit's ambient record, so a teardown log line carries the unit's ids. A construction failure rides the unit's defect path. A unit forks once; a second call is a defect, and so is a call made after the unit has settled — nothing awaits that scope's teardown.
Type Parameters
| Type Parameter |
|---|
Resolves extends AnyPort |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
ctx | readonly | Context<InstanceType<Resolves>> | packages/core/src/runtime.ts:65 |
fork | readonly | <UnitX, N, Seeded>(module, seed) => AsyncResult<Context<InstanceType<Resolves> | UnitX | InstanceType<Seeded>>, never> | packages/core/src/runtime.ts:66 |
UnitMeta
type UnitMeta = object;Defined in: packages/core/src/units.ts:43
What a runtime says about one piece of work as it submits it. kind is the category ("http", "tick", "job"); id identifies this unit.
Remarks
id must be unique per unit unless a traceId is supplied, because traceId defaults to it. A runtime passing a CATEGORY as the id — a route template such as "POST /orders" — gives every request the same trace id and silently defeats the ambient record. A route template is a kind.
The kernel cannot check this, so uniqueness is the runtime's to guarantee. What it does guarantee is UnitRecord's unitId, minted per unit; traceId is the CORRELATION id, which is why it is the one a runtime may supply — it carries an id from outside the process.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
id | readonly | string | packages/core/src/units.ts:45 |
kind | readonly | string | packages/core/src/units.ts:44 |
tenantId? | readonly | string | packages/core/src/units.ts:47 |
traceId? | readonly | string | packages/core/src/units.ts:46 |
UnitRecord
type UnitRecord = object;Defined in: packages/core/src/units.ts:15
What the kernel opens per unit and currentUnit() reads: a small, fixed record of data about this unit, and never a service.
signal is the same AbortSignal the work callback receives — aborted at the drain deadline, or at once on a path that skips the drain. It is here because the callback is not always where the work is: a middleware-shaped runtime opens the unit around a call whose arguments it does not own. A transport's own cancellation is a different clock, not this one.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
signal | readonly | AbortSignal | packages/core/src/units.ts:19 |
tenantId | readonly | string | undefined | packages/core/src/units.ts:18 |
traceId | readonly | string | packages/core/src/units.ts:17 |
unitId | readonly | string | packages/core/src/units.ts:16 |
UnitRegistry
type UnitRegistry = object;Defined in: packages/core/src/units.ts:55
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
abortAll | readonly | () => void | packages/core/src/units.ts:61 |
awaitIdle | readonly | () => AsyncResult<void, never> | packages/core/src/units.ts:62 |
closed | readonly | () => number | packages/core/src/units.ts:60 |
inFlight | readonly | () => number | packages/core/src/units.ts:57 |
run | readonly | <T, E>(meta, work) => AsyncResult<T, E> | packages/core/src/units.ts:56 |
UnitWork
type UnitWork<T, E> = (signal) =>
| AsyncResult<T, E>
| Promise<Result<T, E>>
| Result<T, E>;Defined in: packages/core/src/units.ts:50
Type Parameters
| Type Parameter |
|---|
T |
E |
Parameters
| Parameter | Type |
|---|---|
signal | AbortSignal |
Returns
| AsyncResult<T, E> | Promise<Result<T, E>> | Result<T, E>
Variables
LEVELS
const LEVELS: readonly Level[];Defined in: packages/core/src/observability.ts:11
The levels in order, least severe first — what isEnabled compares through.
RuntimePort
const RuntimePort: PortClass<"Runtime">;Defined in: packages/core/src/runtime.ts:130
The port the kernel resolves its runtime from. A runtime is a service the module provides, not an option handed to start: a runtime package declares its own port over this one — class HttpRuntime extends RuntimePort<Runtime<never, HttpInfo>> {} — so it is built by di like everything else, and the kernel owns nothing but the lifecycle.
Left generic on purpose: every runtime port is one id at runtime, since a process boots exactly one, while each carries its own Resolves/Info in the type.
SPAN_STATUS
const SPAN_STATUS: object;Defined in: packages/core/src/observability.ts:62
A span's outcome: unset until something says otherwise, then ok or error.
Type Declaration
| Name | Type | Default value | Defined in |
|---|---|---|---|
error | 2 | 2 | packages/core/src/observability.ts:62 |
ok | 1 | 1 | packages/core/src/observability.ts:62 |
unset | 0 | 0 | packages/core/src/observability.ts:62 |
stderrSink
const stderrSink: EventSink;Defined in: packages/core/src/events.ts:70
systemClock
const systemClock: Clock;Defined in: packages/core/src/clock.ts:8
Functions
currentUnit()
function currentUnit(): UnitRecord | undefined;Defined in: packages/core/src/units.ts:26
Returns
UnitRecord | undefined
noObserver()
function noObserver(): Settle;Defined in: packages/core/src/observation.ts:89
The no-op member every reader of Observers contributes, so the set is never empty.
Returns
observe()
function observe(observers, operation): Settle;Defined in: packages/core/src/observation.ts:102
Run every observer around one operation, and hand back the one finisher that settles them all.
Observers are started in order and settled in order, and the finisher is guarded: a second call is dropped. "Called exactly once" is therefore a property of this function rather than a rule each starter re-keeps — which matters because the settling call sites are error paths, where a tap and a tapFailure on the same chain, or a retry, is exactly the shape that fires twice and doubles a failure count.
Parameters
| Parameter | Type |
|---|---|
observers | readonly (operation) => Settle[] |
operation | Operation |
Returns
observed()
function observed<T, E>(
observers,
operation,
call,
settled?
): AsyncResult<T, E>;Defined in: packages/core/src/observation.ts:126
observe, wrapped around one call: every observer is started before call runs and settled from whichever channel it comes back on — ok on success, error carrying the Err or the defect as cause otherwise. The wrapper is transparent to the Result: whatever call answers is what the caller receives.
settled replaces either default where a starter has more to say: ok receives the value (a cache reports a hit or a miss), failure the FailureView (a store reports a missing object as an ordinary answer).
Type Parameters
| Type Parameter |
|---|
T |
E |
Parameters
| Parameter | Type |
|---|---|
observers | readonly (operation) => Settle[] |
operation | Operation |
call | () => AsyncResult<T, E> |
settled | { failure?: (failure) => Settled; ok?: (value) => Settled; } |
settled.failure? | (failure) => Settled |
settled.ok? | (value) => Settled |
Returns
AsyncResult<T, E>
releasedBy()
function releasedBy(signal, running): AsyncResult<void, never>;Defined in: packages/core/src/runtime.ts:212
running, but no later than the kernel's drain deadline — the primitive a Serving.drain needs when the work it awaits settles on somebody else's clock (Temporal's shutdownForceTime, a broker library's close) and so cannot honour signal itself.
The losing branch's Result is dropped, which is the point rather than an oversight: once the deadline wins, the kernel has already moved on and the eventual outcome has no consumer left. What that costs is the runtime's own business — an un-acked AMQP delivery is redelivered, so abandoning one repeats work rather than losing it, while a Temporal activity is retried on another worker.
Deliberately Clock-agnostic: there is no duration here, only a signal, so it behaves identically under @btravstack/testing's fake clock. Racing work against a timeout is a different primitive and belongs on Clock (drain.ts uses clock.sleep for exactly that, so a fake clock can control it) — do not fold the two together.
Parameters
| Parameter | Type |
|---|---|
signal | AbortSignal |
running | AsyncResult<void, never> |
Returns
AsyncResult<void, never>
runHealthChecks()
function runHealthChecks(checks): AsyncResult<HealthReport, never>;Defined in: packages/core/src/health.ts:54
Runs every check and folds the results into one report — the whole app is unhealthy if any single component is.
Each check's failure is recovered into a component line BEFORE allAsync sees it, so a failing dependency cannot short-circuit the others: a report naming one component is worth less than one naming all of them. A check that throws rather than answers is a bug in the check, and it is folded in too: each check is STARTED inside the pipeline, so a synchronous throw and a defecting AsyncResult alike are recovered into an unhealthy line — escaped, the first would reach the kernel's own uncaughtException handler and the second would leave /healthz hanging with nothing written.
Parameters
| Parameter | Type |
|---|---|
checks | readonly HealthCheck[] |
Returns
AsyncResult<HealthReport, never>
runMain()
function runMain<X, E, N>(
module,
options?,
exit?
): Promise<void>;Defined in: packages/core/src/run-main.ts:99
Boot a module and turn its outcome into a process exit code — the front door, and the one sanctioned place this package decides a process's fate. start composed with the wait for exited: use start instead when the RunningApp itself is wanted (a test, an embedder, a dev runner booting two applications — none of which may claim process.exitCode).
exit is injectable and defaults to setting process.exitCode: runMain never calls process.exit(), so pending output is flushed, an embedding host keeps control of its own lifetime, and a test can observe the code without ending the run.
| outcome | code |
|---|---|
| exited cleanly | 0 |
startup failure (a modeled Err) | 1 |
a configuration port that could not be bound (ConfigInvalid, PROBE_PORT included) | 78 |
| drained with work abandoned | 2 |
| exited with teardown errors | 2 |
stopped waiting for the teardown or a build (ExitReport.abandonedAt) | 2 |
| stopped by an uncaught exception or unhandled rejection | 70 |
| a defect | 70 |
The two 70s are the same statement reached through the two channels a bug can take. A crash takes precedence over abandoned work.
Type Parameters
| Type Parameter |
|---|
X |
E |
N |
Parameters
| Parameter | Type |
|---|---|
module | Module<X, E, N> & StartGate<X, N> |
options | StartOptions |
exit | (code) => void |
Returns
Promise<void>
Example
// `OrderApi` imports the application next to `http()` and exports
// `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
await runMain(OrderApi);start()
function start<X, E, N>(module, options?): RunningApp<E, RuntimeInfoOf<X>>;Defined in: packages/core/src/start.ts:237
Type Parameters
| Type Parameter |
|---|
X |
E |
N |
Parameters
| Parameter | Type |
|---|---|
module | Module<X, E, N> & StartGate<X, N> |
options | StartOptions |
Returns
RunningApp<E, RuntimeInfoOf<X>>
traceIdOfTraceparent()
function traceIdOfTraceparent(header): string | undefined;Defined in: packages/core/src/runtime.ts:180
The trace id inside a W3C traceparent header, and nothing else of it.
Parameters
| Parameter | Type |
|---|---|
header | string |
Returns
string | undefined
Remarks
The parent's span id is dropped, deliberately: UnitMeta.traceId is a correlation id rather than a span context, and half-carrying one would suggest a parent-child link nothing here maintains.
Three things the specification calls invalid are refused like a malformed header, because adopting one would replace a runtime's own usable id with a value that means nothing: an all-zero trace id, an all-zero parent id (the header is well-formed and names no span), and version ff, which is reserved and never a version a caller may send.
It is here rather than in a runtime because every transport carrying an inbound trace needs the same answer, and two copies of a parser is two places for the all-zero rule to be forgotten. A runtime pairs it with the adopt-only-a-non-blank-inbound-id rule its own headers need: UnitMeta.traceId defaults to meta.id when it is nullish and "" is not, so an empty header would hand a caller's every unit the same blank id.
Example
const parent = request.headers["traceparent"];
const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;