Skip to content

@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
ts
new HealthCheckFailed(args): HealthCheckFailed;

Defined in: node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2034

Parameters
ParameterType
argsobject & object
Returns

HealthCheckFailed

Inherited from
ts
TaggedError("HealthCheckFailed")<{
  readonly reason: string;
}>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
_tagreadonly"HealthCheckFailed"TaggedError("HealthCheckFailed")._tagnode_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2011
cause?publicunknownTaggedError("HealthCheckFailed").causenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24
messagepublicstringTaggedError("HealthCheckFailed").messagenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1075
namepublicstringTaggedError("HealthCheckFailed").namenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074
reasonreadonlystringTaggedError("HealthCheckFailed").reasonpackages/core/src/health.ts:10
stack?publicstringTaggedError("HealthCheckFailed").stacknode_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", readonly HealthCheck[]> & object

Constructors

Constructor
ts
new HealthChecks(): HealthChecks;

Defined in: packages/di/dist/index.d.mts:30

Returns

HealthChecks

Inherited from
ts
Port.many("HealthChecks")<HealthCheck>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"HealthChecks"Port.many("HealthChecks").[ID]packages/di/dist/index.d.mts:12
[MANY]readonlytruePort.many("HealthChecks").[MANY]packages/di/dist/index.d.mts:31
[SERVICE]readonlyreadonly HealthCheck[]Port.many("HealthChecks").[SERVICE]packages/di/dist/index.d.mts:13
manyreadonlytruePort.many("HealthChecks").manypackages/di/dist/index.d.mts:34
portIdreadonly"HealthChecks"Port.many("HealthChecks").portIdpackages/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

Constructors

Constructor
ts
new Instrumentations(): Instrumentations;

Defined in: packages/di/dist/index.d.mts:30

Returns

Instrumentations

Inherited from
ts
Port.many("Instrumentations")<InstrumentationLoader>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"Instrumentations"Port.many("Instrumentations").[ID]packages/di/dist/index.d.mts:12
[MANY]readonlytruePort.many("Instrumentations").[MANY]packages/di/dist/index.d.mts:31
[SERVICE]readonlyreadonly InstrumentationLoader[]Port.many("Instrumentations").[SERVICE]packages/di/dist/index.d.mts:13
manyreadonlytrueHealthChecks.manypackages/di/dist/index.d.mts:34
portIdreadonly"Instrumentations"Port.many("Instrumentations").portIdpackages/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

Constructors

Constructor
ts
new Logger(): Logger;

Defined in: packages/di/dist/index.d.mts:16

Returns

Logger

Inherited from
ts
Port("Logger")<LoggerService>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"Logger"Port("Logger").[ID]packages/di/dist/index.d.mts:12
[SERVICE]readonlyLoggerServicePort("Logger").[SERVICE]packages/di/dist/index.d.mts:13
portIdreadonly"Logger"Port("Logger").portIdpackages/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

Constructors

Constructor
ts
new Meter(): Meter;

Defined in: packages/di/dist/index.d.mts:16

Returns

Meter

Inherited from
ts
Port("Meter")<MeterService>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"Meter"Port("Meter").[ID]packages/di/dist/index.d.mts:12
[SERVICE]readonlyMeterServicePort("Meter").[SERVICE]packages/di/dist/index.d.mts:13
portIdreadonly"Meter"Port("Meter").portIdpackages/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
ts
new Observers(): Observers;

Defined in: packages/di/dist/index.d.mts:30

Returns

Observers

Inherited from
ts
Port.many("Observers")<(operation: Operation) => Settle>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"Observers"Port.many("Observers").[ID]packages/di/dist/index.d.mts:12
[MANY]readonlytruePort.many("Observers").[MANY]packages/di/dist/index.d.mts:31
[SERVICE]readonlyreadonly (operation) => Settle[]Port.many("Observers").[SERVICE]packages/di/dist/index.d.mts:13
manyreadonlytruePort.many("Observers").manypackages/di/dist/index.d.mts:34
portIdreadonly"Observers"Port.many("Observers").portIdpackages/di/dist/index.d.mts:33

RuntimeStartFailed

Defined in: packages/core/src/runtime.ts:16

Extends

  • TaggedErrorInstance<"RuntimeStartFailed", { cause: unknown; runtime: string; }>

Constructors

Constructor
ts
new RuntimeStartFailed(args): RuntimeStartFailed;

Defined in: node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2034

Parameters
ParameterType
argsobject & object
Returns

RuntimeStartFailed

Inherited from
ts
TaggedError("RuntimeStartFailed")<{
  readonly runtime: string;
  readonly cause: unknown;
}>.constructor

Properties

PropertyModifierTypeOverridesInherited fromDefined in
_tagreadonly"RuntimeStartFailed"-TaggedError("RuntimeStartFailed")._tagnode_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2011
causepublicunknown-HealthCheckFailed.causenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24
messagepublicstringTaggedError("RuntimeStartFailed").message-packages/core/src/runtime.ts:20
namepublicstring-TaggedError("RuntimeStartFailed").namenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074
runtimereadonlystring-TaggedError("RuntimeStartFailed").runtimepackages/core/src/runtime.ts:17
stack?publicstring-TaggedError("RuntimeStartFailed").stacknode_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

Constructors

Constructor
ts
new Tracer(): Tracer;

Defined in: packages/di/dist/index.d.mts:16

Returns

Tracer

Inherited from
ts
Port("Tracer")<TracerService>.constructor

Properties

PropertyModifierTypeInherited fromDefined in
[ID]readonly"Tracer"Port("Tracer").[ID]packages/di/dist/index.d.mts:12
[SERVICE]readonlyTracerServicePort("Tracer").[SERVICE]packages/di/dist/index.d.mts:13
portIdreadonly"Tracer"Port("Tracer").portIdpackages/di/dist/index.d.mts:17

Type Aliases

Attributes

ts
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

ts
type Clock = object;

Defined in: packages/core/src/clock.ts:3

Properties

PropertyModifierTypeDefined in
nowreadonly() => numberpackages/core/src/clock.ts:4
sleepreadonly(ms, signal?) => AsyncResult<void, never>packages/core/src/clock.ts:5

ComponentHealth

ts
type ComponentHealth = object;

Defined in: packages/core/src/health.ts:29

Properties

PropertyModifierTypeDescriptionDefined in
namereadonlystring-packages/core/src/health.ts:30
reason?readonlystringPresent only when the component is unhealthy.packages/core/src/health.ts:33
statusreadonly"healthy" | "unhealthy"-packages/core/src/health.ts:31

Counter

ts
type Counter = object;

Defined in: packages/core/src/observability.ts:92

A monotonic count.

Properties

PropertyModifierTypeDefined in
addreadonly(value, attributes?) => voidpackages/core/src/observability.ts:92

DrainReport

ts
type DrainReport = object;

Defined in: packages/core/src/drain.ts:7

Properties

PropertyModifierTypeDescriptionDefined in
abandonedreadonlynumberUnits still open at the deadline. The exit-code decision reads this.packages/core/src/drain.ts:17
completedreadonlynumberUnits 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
inFlightAtStartreadonlynumberUnits in flight when the drain began.packages/core/src/drain.ts:9

EventSink

ts
type EventSink = (event) => void;

Defined in: packages/core/src/events.ts:46

Parameters

ParameterType
eventKernelEvent

Returns

void


ExitReport

ts
type ExitReport = object;

Defined in: packages/core/src/start.ts:46

Properties

PropertyModifierTypeDescriptionDefined 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
drainreadonlyDrainReport | undefined-packages/core/src/start.ts:48
reasonreadonly"signal" | "runtimeStopped" | "uncaught"-packages/core/src/start.ts:47
teardownErrorsreadonlyreadonly TeardownError[]-packages/core/src/start.ts:49
uptimeMsreadonlynumber-packages/core/src/start.ts:50

HealthCheck

ts
type HealthCheck = object;

Defined in: packages/core/src/health.ts:14

One contribution: what to call the component, and how to ask it.

Properties

PropertyModifierTypeDefined in
checkreadonly() => AsyncResult<void, HealthCheckFailed>packages/core/src/health.ts:16
namereadonlystringpackages/core/src/health.ts:15

HealthReport

ts
type HealthReport = object;

Defined in: packages/core/src/health.ts:36

Properties

PropertyModifierTypeDefined in
componentsreadonlyreadonly ComponentHealth[]packages/core/src/health.ts:38
statusreadonly"healthy" | "unhealthy"packages/core/src/health.ts:37

Histogram

ts
type Histogram = object;

Defined in: packages/core/src/observability.ts:95

A distribution of measurements.

Properties

PropertyModifierTypeDefined in
recordreadonly(value, attributes?) => voidpackages/core/src/observability.ts:95

InstrumentationLoader

ts
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

ts
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
ts
{
  type: "building";
}

Type Literal
ts
{
  cause: unknown;
  type: "startFailed";
}

Type Literal
ts
{
  info: unknown;
  probePort: number | undefined;
  runtime: string;
  type: "serving";
}
NameTypeDescriptionDefined in
infounknownWhatever 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
probePortnumber | undefinedThe 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
runtimestring-packages/core/src/events.ts:8
type"serving"-packages/core/src/events.ts:7

Type Literal
ts
{
  inFlight: number;
  type: "draining";
}

Type Literal
ts
{
  report: DrainReport;
  type: "drained";
}

Type Literal
ts
{
  afterMs: number | undefined;
  phase: "build" | "stop";
  type: "stoppedWaiting";
}
NameTypeDescriptionDefined in
afterMsnumber | undefinedThe 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
ts
{
  type: "stopping";
}

Type Literal
ts
{
  type: "exited";
}

Type Literal
ts
{
  cause: unknown;
  port: string;
  type: "teardownError";
}

Type Literal
ts
{
  cause: unknown;
  type: "uncaught";
}

Level

ts
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

ts
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

PropertyModifierTypeDescriptionDefined in
debugreadonly(message, attributes?, cause?) => void-packages/core/src/observability.ts:36
errorreadonly(message, attributes?, cause?) => voidcause is the failure itself — an Error, an unthrown Err's error, a rejected value.packages/core/src/observability.ts:40
fatalreadonly(message, attributes?, cause?) => void-packages/core/src/observability.ts:41
inforeadonly(message, attributes?, cause?) => void-packages/core/src/observability.ts:37
isEnabledreadonly(level) => booleanWhether a line at level would be written — for a payload expensive enough to be worth not building.packages/core/src/observability.ts:45
logreadonly(level, message, attributes?, cause?) => void-packages/core/src/observability.ts:34
tracereadonly(message, attributes?, cause?) => void-packages/core/src/observability.ts:35
warnreadonly(message, attributes?, cause?) => void-packages/core/src/observability.ts:38
withreadonly(attributes) => LoggerServiceA 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

ts
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

PropertyModifierTypeDefined in
createCounterreadonly(name, options?) => Counterpackages/core/src/observability.ts:104
createHistogramreadonly(name, options?) => Histogrampackages/core/src/observability.ts:108

Operation

ts
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

PropertyModifierTypeDescriptionDefined in
attributesreadonlyAttributesThe 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
componentreadonlystring-packages/core/src/observation.ts:20
details?readonlyAttributesUnbounded 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
namereadonlystring-packages/core/src/observation.ts:21
traced?readonlybooleanWhether 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

ts
type Phase = "building" | "starting" | "serving" | "draining" | "stopping" | "exited";

Defined in: packages/core/src/phase.ts:1


RunningApp

ts
type RunningApp<E, Info> = object;

Defined in: packages/core/src/start.ts:172

Type Parameters

Type ParameterDefault type
E-
Infonever

Properties

PropertyModifierTypeDescriptionDefined in
exitedreadonlyAsyncResult<ExitReport, E | RuntimeStartFailed>-packages/core/src/start.ts:173
phasereadonly() => Phase-packages/core/src/start.ts:176
probePortreadonly() => 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
readyreadonly() => booleanThe 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
requestDrainreadonly() => void-packages/core/src/start.ts:175
runtimeInforeadonly() => 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
stopreadonly() => void-packages/core/src/start.ts:174

Runtime

ts
type Runtime<Resolves, Info> = object;

Defined in: packages/core/src/runtime.ts:109

Type Parameters

Type ParameterDefault type
Resolves extends AnyPortnever
Infonever

Properties

PropertyModifierTypeDefined in
namereadonlystringpackages/core/src/runtime.ts:110
resolvesreadonlyreadonly Resolves[]packages/core/src/runtime.ts:115
startreadonly(host) => AsyncResult<Serving<Info>, RuntimeStartFailed>packages/core/src/runtime.ts:116

RuntimeHost

ts
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

PropertyModifierTypeDefined in
ctxreadonlyContext<InstanceType<Resolves>>packages/core/src/runtime.ts:89
runreadonlyRunUnit<Resolves>packages/core/src/runtime.ts:90

RuntimeInfoOf

ts
type RuntimeInfoOf<X> = RuntimeOf<X> extends object ? Info : never;

Defined in: packages/core/src/runtime.ts:146

Type Parameters

Type Parameter
X

RunUnit

ts
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

ParameterType
metaUnitMeta
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

ts
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 ParameterDefault type
Infonever

Properties

PropertyModifierTypeDefined in
drainreadonly(signal) => AsyncResult<void, never>packages/core/src/runtime.ts:104
info?readonlyInfopackages/core/src/runtime.ts:106
stopreadonly() => AsyncResult<void, never>packages/core/src/runtime.ts:105

Settle

ts
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

ParameterType
settledSettled

Returns

void


Settled

ts
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

PropertyModifierTypeDescriptionDefined in
attributes?readonlyAttributesDimensions 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?readonlyunknownThe failure itself, for an observer that writes a line about it.packages/core/src/observation.ts:61
outcomereadonly"ok" | "error"-packages/core/src/observation.ts:57

Span

ts
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

PropertyModifierTypeDefined in
endreadonly() => voidpackages/core/src/observability.ts:78
setAttributesreadonly(attributes) => unknownpackages/core/src/observability.ts:73
setStatusreadonly(status) => unknownpackages/core/src/observability.ts:74

SpanStatusCode

ts
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

ts
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 ParameterDefault type
X-
Nnever

StartOptions

ts
type StartOptions = object;

Defined in: packages/core/src/start.ts:124

Properties

PropertyModifierTypeDescriptionDefined in
clock?readonlyClock-packages/core/src/start.ts:131
drainTimeoutMs?readonlynumberHow 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?readonlyEnvironmentThe 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?readonlyEventSink-packages/core/src/start.ts:169
preDrainDelayMs?readonlynumberHow 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; } | falseThe 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?readonlyboolean-packages/core/src/start.ts:132
stopTimeoutMs?readonlynumberHow 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

ts
type TeardownError = object;

Defined in: packages/core/src/start.ts:32

Properties

PropertyModifierTypeDefined in
causereadonlyunknownpackages/core/src/start.ts:32
portreadonlystringpackages/core/src/start.ts:32

TracerService

ts
type TracerService = object;

Defined in: packages/core/src/observability.ts:82

What a Tracer does: start a span, by name.

Properties

PropertyModifierTypeDefined in
startSpanreadonly(name) => Spanpackages/core/src/observability.ts:82

UnitHost

ts
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

PropertyModifierTypeDefined in
ctxreadonlyContext<InstanceType<Resolves>>packages/core/src/runtime.ts:65
forkreadonly<UnitX, N, Seeded>(module, seed) => AsyncResult<Context<InstanceType<Resolves> | UnitX | InstanceType<Seeded>>, never>packages/core/src/runtime.ts:66

UnitMeta

ts
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

PropertyModifierTypeDefined in
idreadonlystringpackages/core/src/units.ts:45
kindreadonlystringpackages/core/src/units.ts:44
tenantId?readonlystringpackages/core/src/units.ts:47
traceId?readonlystringpackages/core/src/units.ts:46

UnitRecord

ts
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

PropertyModifierTypeDefined in
signalreadonlyAbortSignalpackages/core/src/units.ts:19
tenantIdreadonlystring | undefinedpackages/core/src/units.ts:18
traceIdreadonlystringpackages/core/src/units.ts:17
unitIdreadonlystringpackages/core/src/units.ts:16

UnitRegistry

ts
type UnitRegistry = object;

Defined in: packages/core/src/units.ts:55

Properties

PropertyModifierTypeDefined in
abortAllreadonly() => voidpackages/core/src/units.ts:61
awaitIdlereadonly() => AsyncResult<void, never>packages/core/src/units.ts:62
closedreadonly() => numberpackages/core/src/units.ts:60
inFlightreadonly() => numberpackages/core/src/units.ts:57
runreadonly<T, E>(meta, work) => AsyncResult<T, E>packages/core/src/units.ts:56

UnitWork

ts
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

ParameterType
signalAbortSignal

Returns

| AsyncResult<T, E> | Promise<Result<T, E>> | Result<T, E>

Variables

LEVELS

ts
const LEVELS: readonly Level[];

Defined in: packages/core/src/observability.ts:11

The levels in order, least severe first — what isEnabled compares through.


RuntimePort

ts
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

ts
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

NameTypeDefault valueDefined in
error22packages/core/src/observability.ts:62
ok11packages/core/src/observability.ts:62
unset00packages/core/src/observability.ts:62

stderrSink

ts
const stderrSink: EventSink;

Defined in: packages/core/src/events.ts:70


systemClock

ts
const systemClock: Clock;

Defined in: packages/core/src/clock.ts:8

Functions

currentUnit()

ts
function currentUnit(): UnitRecord | undefined;

Defined in: packages/core/src/units.ts:26

Returns

UnitRecord | undefined


noObserver()

ts
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

Settle


observe()

ts
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

ParameterType
observersreadonly (operation) => Settle[]
operationOperation

Returns

Settle


observed()

ts
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

ParameterType
observersreadonly (operation) => Settle[]
operationOperation
call() => AsyncResult<T, E>
settled{ failure?: (failure) => Settled; ok?: (value) => Settled; }
settled.failure?(failure) => Settled
settled.ok?(value) => Settled

Returns

AsyncResult<T, E>


releasedBy()

ts
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

ParameterType
signalAbortSignal
runningAsyncResult<void, never>

Returns

AsyncResult<void, never>


runHealthChecks()

ts
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

ParameterType
checksreadonly HealthCheck[]

Returns

AsyncResult<HealthReport, never>


runMain()

ts
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.

outcomecode
exited cleanly0
startup failure (a modeled Err)1
a configuration port that could not be bound (ConfigInvalid, PROBE_PORT included)78
drained with work abandoned2
exited with teardown errors2
stopped waiting for the teardown or a build (ExitReport.abandonedAt)2
stopped by an uncaught exception or unhandled rejection70
a defect70

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

ParameterType
moduleModule<X, E, N> & StartGate<X, N>
optionsStartOptions
exit(code) => void

Returns

Promise<void>

Example

ts
// `OrderApi` imports the application next to `http()` and exports
// `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
await runMain(OrderApi);

start()

ts
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

ParameterType
moduleModule<X, E, N> & StartGate<X, N>
optionsStartOptions

Returns

RunningApp<E, RuntimeInfoOf<X>>


traceIdOfTraceparent()

ts
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

ParameterType
headerstring

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

ts
const parent = request.headers["traceparent"];
const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;

Released under the MIT License.