@btravstack/core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/index.cjs +70 -8
- package/dist/index.d.cts +72 -33
- package/dist/index.d.mts +72 -33
- package/dist/index.mjs +70 -9
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -68,8 +68,8 @@ const ticker: Runtime<typeof Greeter> = {
|
|
|
68
68
|
// and stays out of it. A timer has nowhere to return one, so it observes
|
|
69
69
|
// it instead; dropping it would hide the work's `Err` *and* a `Defect`.
|
|
70
70
|
void host
|
|
71
|
-
.run({ kind: "tick", id: `${Date.now()}` }, (
|
|
72
|
-
signal.aborted ? Ok("") : Ok(ctx.get(Greeter).greet("world")),
|
|
71
|
+
.run({ kind: "tick", id: `${Date.now()}` }, (unit, signal) =>
|
|
72
|
+
signal.aborted ? Ok("") : Ok(unit.ctx.get(Greeter).greet("world")),
|
|
73
73
|
)
|
|
74
74
|
.tapFailure((failure) => {
|
|
75
75
|
process.stderr.write(`${JSON.stringify({ tick: failure.tag })}\n`);
|
|
@@ -112,6 +112,22 @@ and turns the outcome into a process exit code — one call, and the whole of a
|
|
|
112
112
|
`TickerApp` that does not export `Greeter` is a type error at the call, and so
|
|
113
113
|
is a module that exports no runtime port at all.
|
|
114
114
|
|
|
115
|
+
## For a runtime author
|
|
116
|
+
|
|
117
|
+
Two helpers sit beside the `Runtime` contract, because every transport needs
|
|
118
|
+
them and none should own a private copy:
|
|
119
|
+
|
|
120
|
+
- **`releasedBy(signal, running)`** — `running`, but no later than the kernel's
|
|
121
|
+
drain deadline, for a `Serving.drain` whose work settles on somebody else's
|
|
122
|
+
clock.
|
|
123
|
+
- **`traceIdOfTraceparent(header)`** — the trace-id field of a W3C
|
|
124
|
+
`traceparent`, and nothing else of it: the parent's span id is dropped, since
|
|
125
|
+
`UnitMeta.traceId` is a correlation id rather than a span context. Answers
|
|
126
|
+
`undefined` for anything the specification calls invalid — a malformed
|
|
127
|
+
header, an all-zero trace id, an all-zero parent id, the reserved version
|
|
128
|
+
`ff` — so your runtime falls back to its own id instead of adopting one that
|
|
129
|
+
means nothing. A higher version's extra fields are read rather than refused.
|
|
130
|
+
|
|
115
131
|
## What you get
|
|
116
132
|
|
|
117
133
|
- **A drain that survives Kubernetes.** On SIGTERM: readiness flips false, the
|
|
@@ -129,8 +145,9 @@ is a module that exports no runtime port at all.
|
|
|
129
145
|
bad value is a `ConfigInvalid` naming every fault at once, and exit `78`.
|
|
130
146
|
- **Teardown on every path**, with failing finalisers in
|
|
131
147
|
`ExitReport.teardownErrors`, never masking the exit reason.
|
|
132
|
-
- **A per-unit scope no handler manages** —
|
|
133
|
-
`
|
|
148
|
+
- **A per-unit scope no handler manages** — the runtime calls
|
|
149
|
+
`unit.fork(module, seed)` from inside `host.run`'s work callback, and the
|
|
150
|
+
kernel closes the scope when the unit settles.
|
|
134
151
|
- **An ambient record, not an ambient container** — `currentUnit()` reads
|
|
135
152
|
`{ unitId, traceId, tenantId, signal }`; services never travel
|
|
136
153
|
there. `signal` is the very `AbortSignal` the unit's work callback is handed,
|
package/dist/index.cjs
CHANGED
|
@@ -206,6 +206,39 @@ var RuntimeStartFailed = class extends (0, unthrown.TaggedError)("RuntimeStartFa
|
|
|
206
206
|
*/
|
|
207
207
|
const RuntimePort = (0, _btravstack_di.Port)("Runtime");
|
|
208
208
|
/**
|
|
209
|
+
* The trace id inside a W3C `traceparent` header, and nothing else of it.
|
|
210
|
+
*
|
|
211
|
+
* @remarks
|
|
212
|
+
* The parent's **span id is dropped**, deliberately: `UnitMeta.traceId` is a
|
|
213
|
+
* correlation id rather than a span context, and half-carrying one would
|
|
214
|
+
* suggest a parent-child link nothing here maintains.
|
|
215
|
+
*
|
|
216
|
+
* Three things the specification calls invalid are refused like a malformed
|
|
217
|
+
* header, because adopting one would replace a runtime's own usable id with a
|
|
218
|
+
* value that means nothing: an **all-zero trace id**, an **all-zero parent
|
|
219
|
+
* id** (the header is well-formed and names no span), and **version `ff`**,
|
|
220
|
+
* which is reserved and never a version a caller may send.
|
|
221
|
+
*
|
|
222
|
+
* It is here rather than in a runtime because **every** transport carrying an
|
|
223
|
+
* inbound trace needs the same answer, and two copies of a parser is two
|
|
224
|
+
* places for the all-zero rule to be forgotten. A runtime pairs it with the
|
|
225
|
+
* adopt-only-a-non-blank-inbound-id rule its own headers need:
|
|
226
|
+
* `UnitMeta.traceId` defaults to `meta.id` when it is nullish and `""` is not,
|
|
227
|
+
* so an empty header would hand a caller's every unit the same blank id.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* const parent = request.headers["traceparent"];
|
|
232
|
+
* const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
const traceIdOfTraceparent = (header) => {
|
|
236
|
+
const [, version, traceId, parentId, extra] = /^(?!ff)([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-[\da-f]{2}(-.*)?$/.exec(header.trim()) ?? [];
|
|
237
|
+
if (traceId === void 0 || parentId === void 0) return void 0;
|
|
238
|
+
if (version === "00" && extra !== void 0) return void 0;
|
|
239
|
+
return /^0+$/.test(traceId) || /^0+$/.test(parentId) ? void 0 : traceId;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
209
242
|
* `running`, but no later than the kernel's drain deadline — the primitive a
|
|
210
243
|
* `Serving.drain` needs when the work it awaits settles on somebody else's
|
|
211
244
|
* clock (Temporal's `shutdownForceTime`, a broker library's close) and so
|
|
@@ -580,15 +613,43 @@ const start = (module, options = {}) => {
|
|
|
580
613
|
runtimeName = runtime.name;
|
|
581
614
|
healthChecks = ctx.get(HealthChecks);
|
|
582
615
|
const runtimeCtx = ctx;
|
|
583
|
-
const unit = options.unit;
|
|
584
616
|
const run = (meta, work) => registry.run(meta, (signal) => {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
617
|
+
const settled = Promise.withResolvers();
|
|
618
|
+
let closing;
|
|
619
|
+
let hasSettled = false;
|
|
620
|
+
const fork = (module, seed) => {
|
|
621
|
+
if (hasSettled) return (0, unthrown.fromSafePromise)(Promise.reject(/* @__PURE__ */ new Error("a unit forks after it has already settled")));
|
|
622
|
+
if (closing !== void 0) return (0, unthrown.fromSafePromise)(Promise.reject(/* @__PURE__ */ new Error("a unit forks its scope once")));
|
|
623
|
+
const ready = Promise.withResolvers();
|
|
624
|
+
closing = _btravstack_di.Module.forkScope(ctx, module, (forked) => {
|
|
625
|
+
ready.resolve(forked);
|
|
626
|
+
return (0, unthrown.fromSafePromise)(settled.promise);
|
|
627
|
+
}, {
|
|
628
|
+
seed,
|
|
629
|
+
onTeardownError: (port, cause) => emit({
|
|
630
|
+
type: "teardownError",
|
|
631
|
+
port,
|
|
632
|
+
cause
|
|
633
|
+
})
|
|
634
|
+
}).recoverDefect((cause) => {
|
|
635
|
+
ready.reject(cause);
|
|
636
|
+
return (0, unthrown.Ok)();
|
|
637
|
+
}).get();
|
|
638
|
+
return (0, unthrown.fromSafePromise)(ready.promise);
|
|
639
|
+
};
|
|
640
|
+
const outcome = (async () => {
|
|
641
|
+
try {
|
|
642
|
+
return await work({
|
|
643
|
+
ctx: runtimeCtx,
|
|
644
|
+
fork
|
|
645
|
+
}, signal);
|
|
646
|
+
} finally {
|
|
647
|
+
hasSettled = true;
|
|
648
|
+
settled.resolve();
|
|
649
|
+
if (closing !== void 0) await closing;
|
|
650
|
+
}
|
|
651
|
+
})();
|
|
652
|
+
return (0, unthrown.fromSafePromise)(outcome).flatMap((result) => result);
|
|
592
653
|
});
|
|
593
654
|
const host = {
|
|
594
655
|
ctx: runtimeCtx,
|
|
@@ -709,3 +770,4 @@ exports.runMain = runMain;
|
|
|
709
770
|
exports.start = start;
|
|
710
771
|
exports.stderrSink = stderrSink;
|
|
711
772
|
exports.systemClock = systemClock;
|
|
773
|
+
exports.traceIdOfTraceparent = traceIdOfTraceparent;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncResult, Result } from "unthrown";
|
|
2
|
-
import { AnyPort, Context, Module, PortClass, PortInstance, Scope, ServiceOf } from "@btravstack/di";
|
|
2
|
+
import { AnyPort, Context, DependencyGate, Module, PortClass, PortInstance, Scope, SeedEntry, ServiceOf } from "@btravstack/di";
|
|
3
3
|
import { Env, Environment } from "@btravstack/config";
|
|
4
4
|
//#region src/instrumentation.d.ts
|
|
5
5
|
/**
|
|
@@ -372,11 +372,28 @@ declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
|
|
|
372
372
|
* `UnitMeta.id` must be unique per unit unless a `traceId` is supplied — see
|
|
373
373
|
* {@link UnitMeta}.
|
|
374
374
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
375
|
+
* A runtime forks the unit's scope itself, through `UnitHost.fork`, at the
|
|
376
|
+
* moment it holds the unit's input; the kernel closes that scope when the
|
|
377
|
+
* unit settles, after the response is flushed. Work that subscribes to an
|
|
378
|
+
* event after a `fork` must first check whether it already fired.
|
|
378
379
|
*/
|
|
379
|
-
type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (
|
|
380
|
+
type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (unit: UnitHost<Resolves>, signal: AbortSignal) => ReturnType<UnitWork<T, E>>) => AsyncResult<T, E>;
|
|
381
|
+
/**
|
|
382
|
+
* What the kernel hands a unit's work: the application context, and the one
|
|
383
|
+
* way to open the unit's own scope.
|
|
384
|
+
*
|
|
385
|
+
* `fork` builds `module` over the application context plus `seed` and hands
|
|
386
|
+
* the forked context back. The scope is torn down when the unit closes —
|
|
387
|
+
* inside the registry's unit, so the unit is not counted closed until its
|
|
388
|
+
* finalisers have run, and inside the unit's ambient record, so a teardown
|
|
389
|
+
* log line carries the unit's ids. A construction failure rides the unit's
|
|
390
|
+
* defect path. A unit forks once; a second call is a defect, and so is a call
|
|
391
|
+
* made after the unit has settled — nothing awaits that scope's teardown.
|
|
392
|
+
*/
|
|
393
|
+
type UnitHost<Resolves extends AnyPort> = {
|
|
394
|
+
readonly ctx: Context<InstanceType<Resolves>>;
|
|
395
|
+
readonly fork: <UnitX, N, Seeded extends AnyPort = never>(module: Module<UnitX, never, N> & DependencyGate<Exclude<N, InstanceType<Resolves> | InstanceType<Seeded> | Scope>>, seed: readonly SeedEntry<Seeded>[]) => AsyncResult<Context<InstanceType<Resolves> | UnitX | InstanceType<Seeded>>, never>;
|
|
396
|
+
};
|
|
380
397
|
/**
|
|
381
398
|
* What a runtime is handed at `start`: the application services **and** the
|
|
382
399
|
* kernel's {@link RunUnit}. Handing it a bare `Context` would leave every
|
|
@@ -388,8 +405,8 @@ type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (ctx: Cont
|
|
|
388
405
|
* {@link RunUnit}), and `UnitMeta.id` must be unique per unit unless a
|
|
389
406
|
* `traceId` is supplied (see {@link UnitMeta}).
|
|
390
407
|
*
|
|
391
|
-
* `ctx` is the **application** context: a port a
|
|
392
|
-
*
|
|
408
|
+
* `ctx` is the **application** context: a port a unit's `fork` module provides
|
|
409
|
+
* exists only in the `Context` `fork` hands back, so a runtime naming it in
|
|
393
410
|
* `resolves` is rejected at `start`'s call site.
|
|
394
411
|
*/
|
|
395
412
|
type RuntimeHost<Resolves extends AnyPort> = {
|
|
@@ -430,8 +447,40 @@ declare const RuntimePort: PortClass<"Runtime">;
|
|
|
430
447
|
type RuntimeInstance = InstanceType<PortClass<"Runtime">>;
|
|
431
448
|
/** The `Runtime<Resolves, Info>` a module exports, or `never` when it exports none. */
|
|
432
449
|
type RuntimeOf<X> = ServiceOf<Extract<X, RuntimeInstance>>;
|
|
433
|
-
type RuntimeResolvesOf<X> = RuntimeOf<X> extends
|
|
434
|
-
|
|
450
|
+
type RuntimeResolvesOf<X> = RuntimeOf<X> extends {
|
|
451
|
+
readonly resolves: readonly (infer Resolves extends AnyPort)[];
|
|
452
|
+
} ? Resolves : never;
|
|
453
|
+
type RuntimeInfoOf<X> = RuntimeOf<X> extends {
|
|
454
|
+
readonly start: (host: never) => AsyncResult<Serving<infer Info>, RuntimeStartFailed>;
|
|
455
|
+
} ? Info : never;
|
|
456
|
+
/**
|
|
457
|
+
* The trace id inside a W3C `traceparent` header, and nothing else of it.
|
|
458
|
+
*
|
|
459
|
+
* @remarks
|
|
460
|
+
* The parent's **span id is dropped**, deliberately: `UnitMeta.traceId` is a
|
|
461
|
+
* correlation id rather than a span context, and half-carrying one would
|
|
462
|
+
* suggest a parent-child link nothing here maintains.
|
|
463
|
+
*
|
|
464
|
+
* Three things the specification calls invalid are refused like a malformed
|
|
465
|
+
* header, because adopting one would replace a runtime's own usable id with a
|
|
466
|
+
* value that means nothing: an **all-zero trace id**, an **all-zero parent
|
|
467
|
+
* id** (the header is well-formed and names no span), and **version `ff`**,
|
|
468
|
+
* which is reserved and never a version a caller may send.
|
|
469
|
+
*
|
|
470
|
+
* It is here rather than in a runtime because **every** transport carrying an
|
|
471
|
+
* inbound trace needs the same answer, and two copies of a parser is two
|
|
472
|
+
* places for the all-zero rule to be forgotten. A runtime pairs it with the
|
|
473
|
+
* adopt-only-a-non-blank-inbound-id rule its own headers need:
|
|
474
|
+
* `UnitMeta.traceId` defaults to `meta.id` when it is nullish and `""` is not,
|
|
475
|
+
* so an empty header would hand a caller's every unit the same blank id.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```ts
|
|
479
|
+
* const parent = request.headers["traceparent"];
|
|
480
|
+
* const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
declare const traceIdOfTraceparent: (header: string) => string | undefined;
|
|
435
484
|
/**
|
|
436
485
|
* `running`, but no later than the kernel's drain deadline — the primitive a
|
|
437
486
|
* `Serving.drain` needs when the work it awaits settles on somebody else's
|
|
@@ -527,26 +576,13 @@ type ExitReport = {
|
|
|
527
576
|
readonly teardownErrors: readonly TeardownError[];
|
|
528
577
|
readonly uptimeMs: number;
|
|
529
578
|
};
|
|
530
|
-
type StartOptions
|
|
579
|
+
type StartOptions = {
|
|
531
580
|
/**
|
|
532
581
|
* The environment the graph is configured from, provided to it as the `Env`
|
|
533
582
|
* port and read for the kernel's own `PROBE_PORT`. Defaults to
|
|
534
583
|
* `process.env`.
|
|
535
584
|
*/
|
|
536
585
|
readonly env?: Environment;
|
|
537
|
-
/**
|
|
538
|
-
* A module forked around **every unit** — a unit being one bounded piece of
|
|
539
|
-
* work, whatever the transport calls it: an HTTP request, a Temporal
|
|
540
|
-
* activity, an AMQP delivery. Built as the unit opens, torn down as it
|
|
541
|
-
* closes — while the unit's ambient record is still open — reading anything
|
|
542
|
-
* the application context carries.
|
|
543
|
-
*
|
|
544
|
-
* A failing unit finaliser is reported as a `teardownError` event and never
|
|
545
|
-
* in `ExitReport.teardownErrors`. With this option the unit's work runs only
|
|
546
|
-
* once the fork is built, so a runtime that subscribes to an event from
|
|
547
|
-
* inside its work must be ready for it to have already fired.
|
|
548
|
-
*/
|
|
549
|
-
readonly unit?: Module<UnitX, never, UnitNeeds>;
|
|
550
586
|
readonly clock?: Clock;
|
|
551
587
|
readonly signals?: boolean;
|
|
552
588
|
/**
|
|
@@ -599,15 +635,18 @@ type RunningApp<E, Info = never> = {
|
|
|
599
635
|
/**
|
|
600
636
|
* The phantom marker `start`, `runMain` and `Boot` all intersect onto their
|
|
601
637
|
* `module` parameter: `unknown` — and invisible — when nothing the module
|
|
602
|
-
* needs is unprovided, it exports a runtime
|
|
603
|
-
* runtime resolves
|
|
604
|
-
*
|
|
605
|
-
* not match.
|
|
638
|
+
* needs is unprovided, it exports a runtime and its exports cover what that
|
|
639
|
+
* runtime resolves; a diagnostic otherwise, printed at the call site as the
|
|
640
|
+
* parameter type the argument did not match.
|
|
606
641
|
*
|
|
607
642
|
* `unknown` is the satisfied arm because intersecting it leaves the module
|
|
608
643
|
* type untouched. A runtime's `resolves` is checked against the module's
|
|
609
|
-
* exports only
|
|
610
|
-
*
|
|
644
|
+
* exports only: `RuntimeHost.ctx` is the application context, so a port only
|
|
645
|
+
* a `fork` provides would resolve to nothing there. A `fork`'s own needs are
|
|
646
|
+
* not this marker's business at all — a `fork` module is forked over the
|
|
647
|
+
* application context, so its needs are exactly what a starter's own `needs`
|
|
648
|
+
* channel already asks the composition root to supply, and di's ordinary
|
|
649
|
+
* `UNSATISFIED DEPENDENCIES` gate is what refuses a root that does not.
|
|
611
650
|
*
|
|
612
651
|
* **Unmet needs are checked FIRST, and answered in di's own words rather than
|
|
613
652
|
* a sentence of the kernel's.** A root that forgot `provides: [router]` used
|
|
@@ -626,10 +665,10 @@ type RunningApp<E, Info = never> = {
|
|
|
626
665
|
* name is reached.
|
|
627
666
|
*/
|
|
628
667
|
type PortIdOf<P> = P extends PortInstance<infer Id, infer _Service> ? Id : P;
|
|
629
|
-
type StartGate<X,
|
|
668
|
+
type StartGate<X, N = never> = [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" : {
|
|
630
669
|
readonly "UNSATISFIED DEPENDENCIES — nothing provides": PortIdOf<Exclude<N, Scope | Env>>;
|
|
631
670
|
};
|
|
632
|
-
declare const start: <X, E, N
|
|
671
|
+
declare const start: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions) => RunningApp<E, RuntimeInfoOf<X>>;
|
|
633
672
|
//#endregion
|
|
634
673
|
//#region src/run-main.d.ts
|
|
635
674
|
/**
|
|
@@ -664,6 +703,6 @@ declare const start: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<
|
|
|
664
703
|
* await runMain(OrderApi);
|
|
665
704
|
* ```
|
|
666
705
|
*/
|
|
667
|
-
declare const runMain: <X, E, N
|
|
706
|
+
declare const runMain: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions, exit?: (code: number) => void) => Promise<void>;
|
|
668
707
|
//#endregion
|
|
669
|
-
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, Observers, type Operation, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Settle, type Settled, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
|
708
|
+
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, Observers, type Operation, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Settle, type Settled, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitHost, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock, traceIdOfTraceparent };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyPort, Context, Module, PortClass, PortInstance, Scope, ServiceOf } from "@btravstack/di";
|
|
1
|
+
import { AnyPort, Context, DependencyGate, Module, PortClass, PortInstance, Scope, SeedEntry, ServiceOf } from "@btravstack/di";
|
|
2
2
|
import { AsyncResult, Result } from "unthrown";
|
|
3
3
|
import { Env, Environment } from "@btravstack/config";
|
|
4
4
|
//#region src/instrumentation.d.ts
|
|
@@ -372,11 +372,28 @@ declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
|
|
|
372
372
|
* `UnitMeta.id` must be unique per unit unless a `traceId` is supplied — see
|
|
373
373
|
* {@link UnitMeta}.
|
|
374
374
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
375
|
+
* A runtime forks the unit's scope itself, through `UnitHost.fork`, at the
|
|
376
|
+
* moment it holds the unit's input; the kernel closes that scope when the
|
|
377
|
+
* unit settles, after the response is flushed. Work that subscribes to an
|
|
378
|
+
* event after a `fork` must first check whether it already fired.
|
|
378
379
|
*/
|
|
379
|
-
type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (
|
|
380
|
+
type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (unit: UnitHost<Resolves>, signal: AbortSignal) => ReturnType<UnitWork<T, E>>) => AsyncResult<T, E>;
|
|
381
|
+
/**
|
|
382
|
+
* What the kernel hands a unit's work: the application context, and the one
|
|
383
|
+
* way to open the unit's own scope.
|
|
384
|
+
*
|
|
385
|
+
* `fork` builds `module` over the application context plus `seed` and hands
|
|
386
|
+
* the forked context back. The scope is torn down when the unit closes —
|
|
387
|
+
* inside the registry's unit, so the unit is not counted closed until its
|
|
388
|
+
* finalisers have run, and inside the unit's ambient record, so a teardown
|
|
389
|
+
* log line carries the unit's ids. A construction failure rides the unit's
|
|
390
|
+
* defect path. A unit forks once; a second call is a defect, and so is a call
|
|
391
|
+
* made after the unit has settled — nothing awaits that scope's teardown.
|
|
392
|
+
*/
|
|
393
|
+
type UnitHost<Resolves extends AnyPort> = {
|
|
394
|
+
readonly ctx: Context<InstanceType<Resolves>>;
|
|
395
|
+
readonly fork: <UnitX, N, Seeded extends AnyPort = never>(module: Module<UnitX, never, N> & DependencyGate<Exclude<N, InstanceType<Resolves> | InstanceType<Seeded> | Scope>>, seed: readonly SeedEntry<Seeded>[]) => AsyncResult<Context<InstanceType<Resolves> | UnitX | InstanceType<Seeded>>, never>;
|
|
396
|
+
};
|
|
380
397
|
/**
|
|
381
398
|
* What a runtime is handed at `start`: the application services **and** the
|
|
382
399
|
* kernel's {@link RunUnit}. Handing it a bare `Context` would leave every
|
|
@@ -388,8 +405,8 @@ type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (ctx: Cont
|
|
|
388
405
|
* {@link RunUnit}), and `UnitMeta.id` must be unique per unit unless a
|
|
389
406
|
* `traceId` is supplied (see {@link UnitMeta}).
|
|
390
407
|
*
|
|
391
|
-
* `ctx` is the **application** context: a port a
|
|
392
|
-
*
|
|
408
|
+
* `ctx` is the **application** context: a port a unit's `fork` module provides
|
|
409
|
+
* exists only in the `Context` `fork` hands back, so a runtime naming it in
|
|
393
410
|
* `resolves` is rejected at `start`'s call site.
|
|
394
411
|
*/
|
|
395
412
|
type RuntimeHost<Resolves extends AnyPort> = {
|
|
@@ -430,8 +447,40 @@ declare const RuntimePort: PortClass<"Runtime">;
|
|
|
430
447
|
type RuntimeInstance = InstanceType<PortClass<"Runtime">>;
|
|
431
448
|
/** The `Runtime<Resolves, Info>` a module exports, or `never` when it exports none. */
|
|
432
449
|
type RuntimeOf<X> = ServiceOf<Extract<X, RuntimeInstance>>;
|
|
433
|
-
type RuntimeResolvesOf<X> = RuntimeOf<X> extends
|
|
434
|
-
|
|
450
|
+
type RuntimeResolvesOf<X> = RuntimeOf<X> extends {
|
|
451
|
+
readonly resolves: readonly (infer Resolves extends AnyPort)[];
|
|
452
|
+
} ? Resolves : never;
|
|
453
|
+
type RuntimeInfoOf<X> = RuntimeOf<X> extends {
|
|
454
|
+
readonly start: (host: never) => AsyncResult<Serving<infer Info>, RuntimeStartFailed>;
|
|
455
|
+
} ? Info : never;
|
|
456
|
+
/**
|
|
457
|
+
* The trace id inside a W3C `traceparent` header, and nothing else of it.
|
|
458
|
+
*
|
|
459
|
+
* @remarks
|
|
460
|
+
* The parent's **span id is dropped**, deliberately: `UnitMeta.traceId` is a
|
|
461
|
+
* correlation id rather than a span context, and half-carrying one would
|
|
462
|
+
* suggest a parent-child link nothing here maintains.
|
|
463
|
+
*
|
|
464
|
+
* Three things the specification calls invalid are refused like a malformed
|
|
465
|
+
* header, because adopting one would replace a runtime's own usable id with a
|
|
466
|
+
* value that means nothing: an **all-zero trace id**, an **all-zero parent
|
|
467
|
+
* id** (the header is well-formed and names no span), and **version `ff`**,
|
|
468
|
+
* which is reserved and never a version a caller may send.
|
|
469
|
+
*
|
|
470
|
+
* It is here rather than in a runtime because **every** transport carrying an
|
|
471
|
+
* inbound trace needs the same answer, and two copies of a parser is two
|
|
472
|
+
* places for the all-zero rule to be forgotten. A runtime pairs it with the
|
|
473
|
+
* adopt-only-a-non-blank-inbound-id rule its own headers need:
|
|
474
|
+
* `UnitMeta.traceId` defaults to `meta.id` when it is nullish and `""` is not,
|
|
475
|
+
* so an empty header would hand a caller's every unit the same blank id.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```ts
|
|
479
|
+
* const parent = request.headers["traceparent"];
|
|
480
|
+
* const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
declare const traceIdOfTraceparent: (header: string) => string | undefined;
|
|
435
484
|
/**
|
|
436
485
|
* `running`, but no later than the kernel's drain deadline — the primitive a
|
|
437
486
|
* `Serving.drain` needs when the work it awaits settles on somebody else's
|
|
@@ -527,26 +576,13 @@ type ExitReport = {
|
|
|
527
576
|
readonly teardownErrors: readonly TeardownError[];
|
|
528
577
|
readonly uptimeMs: number;
|
|
529
578
|
};
|
|
530
|
-
type StartOptions
|
|
579
|
+
type StartOptions = {
|
|
531
580
|
/**
|
|
532
581
|
* The environment the graph is configured from, provided to it as the `Env`
|
|
533
582
|
* port and read for the kernel's own `PROBE_PORT`. Defaults to
|
|
534
583
|
* `process.env`.
|
|
535
584
|
*/
|
|
536
585
|
readonly env?: Environment;
|
|
537
|
-
/**
|
|
538
|
-
* A module forked around **every unit** — a unit being one bounded piece of
|
|
539
|
-
* work, whatever the transport calls it: an HTTP request, a Temporal
|
|
540
|
-
* activity, an AMQP delivery. Built as the unit opens, torn down as it
|
|
541
|
-
* closes — while the unit's ambient record is still open — reading anything
|
|
542
|
-
* the application context carries.
|
|
543
|
-
*
|
|
544
|
-
* A failing unit finaliser is reported as a `teardownError` event and never
|
|
545
|
-
* in `ExitReport.teardownErrors`. With this option the unit's work runs only
|
|
546
|
-
* once the fork is built, so a runtime that subscribes to an event from
|
|
547
|
-
* inside its work must be ready for it to have already fired.
|
|
548
|
-
*/
|
|
549
|
-
readonly unit?: Module<UnitX, never, UnitNeeds>;
|
|
550
586
|
readonly clock?: Clock;
|
|
551
587
|
readonly signals?: boolean;
|
|
552
588
|
/**
|
|
@@ -599,15 +635,18 @@ type RunningApp<E, Info = never> = {
|
|
|
599
635
|
/**
|
|
600
636
|
* The phantom marker `start`, `runMain` and `Boot` all intersect onto their
|
|
601
637
|
* `module` parameter: `unknown` — and invisible — when nothing the module
|
|
602
|
-
* needs is unprovided, it exports a runtime
|
|
603
|
-
* runtime resolves
|
|
604
|
-
*
|
|
605
|
-
* not match.
|
|
638
|
+
* needs is unprovided, it exports a runtime and its exports cover what that
|
|
639
|
+
* runtime resolves; a diagnostic otherwise, printed at the call site as the
|
|
640
|
+
* parameter type the argument did not match.
|
|
606
641
|
*
|
|
607
642
|
* `unknown` is the satisfied arm because intersecting it leaves the module
|
|
608
643
|
* type untouched. A runtime's `resolves` is checked against the module's
|
|
609
|
-
* exports only
|
|
610
|
-
*
|
|
644
|
+
* exports only: `RuntimeHost.ctx` is the application context, so a port only
|
|
645
|
+
* a `fork` provides would resolve to nothing there. A `fork`'s own needs are
|
|
646
|
+
* not this marker's business at all — a `fork` module is forked over the
|
|
647
|
+
* application context, so its needs are exactly what a starter's own `needs`
|
|
648
|
+
* channel already asks the composition root to supply, and di's ordinary
|
|
649
|
+
* `UNSATISFIED DEPENDENCIES` gate is what refuses a root that does not.
|
|
611
650
|
*
|
|
612
651
|
* **Unmet needs are checked FIRST, and answered in di's own words rather than
|
|
613
652
|
* a sentence of the kernel's.** A root that forgot `provides: [router]` used
|
|
@@ -626,10 +665,10 @@ type RunningApp<E, Info = never> = {
|
|
|
626
665
|
* name is reached.
|
|
627
666
|
*/
|
|
628
667
|
type PortIdOf<P> = P extends PortInstance<infer Id, infer _Service> ? Id : P;
|
|
629
|
-
type StartGate<X,
|
|
668
|
+
type StartGate<X, N = never> = [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" : {
|
|
630
669
|
readonly "UNSATISFIED DEPENDENCIES — nothing provides": PortIdOf<Exclude<N, Scope | Env>>;
|
|
631
670
|
};
|
|
632
|
-
declare const start: <X, E, N
|
|
671
|
+
declare const start: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions) => RunningApp<E, RuntimeInfoOf<X>>;
|
|
633
672
|
//#endregion
|
|
634
673
|
//#region src/run-main.d.ts
|
|
635
674
|
/**
|
|
@@ -664,6 +703,6 @@ declare const start: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<
|
|
|
664
703
|
* await runMain(OrderApi);
|
|
665
704
|
* ```
|
|
666
705
|
*/
|
|
667
|
-
declare const runMain: <X, E, N
|
|
706
|
+
declare const runMain: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions, exit?: (code: number) => void) => Promise<void>;
|
|
668
707
|
//#endregion
|
|
669
|
-
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, Observers, type Operation, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Settle, type Settled, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
|
708
|
+
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, Observers, type Operation, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Settle, type Settled, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitHost, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock, traceIdOfTraceparent };
|
package/dist/index.mjs
CHANGED
|
@@ -205,6 +205,39 @@ var RuntimeStartFailed = class extends TaggedError("RuntimeStartFailed") {
|
|
|
205
205
|
*/
|
|
206
206
|
const RuntimePort = Port("Runtime");
|
|
207
207
|
/**
|
|
208
|
+
* The trace id inside a W3C `traceparent` header, and nothing else of it.
|
|
209
|
+
*
|
|
210
|
+
* @remarks
|
|
211
|
+
* The parent's **span id is dropped**, deliberately: `UnitMeta.traceId` is a
|
|
212
|
+
* correlation id rather than a span context, and half-carrying one would
|
|
213
|
+
* suggest a parent-child link nothing here maintains.
|
|
214
|
+
*
|
|
215
|
+
* Three things the specification calls invalid are refused like a malformed
|
|
216
|
+
* header, because adopting one would replace a runtime's own usable id with a
|
|
217
|
+
* value that means nothing: an **all-zero trace id**, an **all-zero parent
|
|
218
|
+
* id** (the header is well-formed and names no span), and **version `ff`**,
|
|
219
|
+
* which is reserved and never a version a caller may send.
|
|
220
|
+
*
|
|
221
|
+
* It is here rather than in a runtime because **every** transport carrying an
|
|
222
|
+
* inbound trace needs the same answer, and two copies of a parser is two
|
|
223
|
+
* places for the all-zero rule to be forgotten. A runtime pairs it with the
|
|
224
|
+
* adopt-only-a-non-blank-inbound-id rule its own headers need:
|
|
225
|
+
* `UnitMeta.traceId` defaults to `meta.id` when it is nullish and `""` is not,
|
|
226
|
+
* so an empty header would hand a caller's every unit the same blank id.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const parent = request.headers["traceparent"];
|
|
231
|
+
* const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
const traceIdOfTraceparent = (header) => {
|
|
235
|
+
const [, version, traceId, parentId, extra] = /^(?!ff)([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-[\da-f]{2}(-.*)?$/.exec(header.trim()) ?? [];
|
|
236
|
+
if (traceId === void 0 || parentId === void 0) return void 0;
|
|
237
|
+
if (version === "00" && extra !== void 0) return void 0;
|
|
238
|
+
return /^0+$/.test(traceId) || /^0+$/.test(parentId) ? void 0 : traceId;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
208
241
|
* `running`, but no later than the kernel's drain deadline — the primitive a
|
|
209
242
|
* `Serving.drain` needs when the work it awaits settles on somebody else's
|
|
210
243
|
* clock (Temporal's `shutdownForceTime`, a broker library's close) and so
|
|
@@ -579,15 +612,43 @@ const start = (module, options = {}) => {
|
|
|
579
612
|
runtimeName = runtime.name;
|
|
580
613
|
healthChecks = ctx.get(HealthChecks);
|
|
581
614
|
const runtimeCtx = ctx;
|
|
582
|
-
const unit = options.unit;
|
|
583
615
|
const run = (meta, work) => registry.run(meta, (signal) => {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
616
|
+
const settled = Promise.withResolvers();
|
|
617
|
+
let closing;
|
|
618
|
+
let hasSettled = false;
|
|
619
|
+
const fork = (module, seed) => {
|
|
620
|
+
if (hasSettled) return fromSafePromise(Promise.reject(/* @__PURE__ */ new Error("a unit forks after it has already settled")));
|
|
621
|
+
if (closing !== void 0) return fromSafePromise(Promise.reject(/* @__PURE__ */ new Error("a unit forks its scope once")));
|
|
622
|
+
const ready = Promise.withResolvers();
|
|
623
|
+
closing = Module.forkScope(ctx, module, (forked) => {
|
|
624
|
+
ready.resolve(forked);
|
|
625
|
+
return fromSafePromise(settled.promise);
|
|
626
|
+
}, {
|
|
627
|
+
seed,
|
|
628
|
+
onTeardownError: (port, cause) => emit({
|
|
629
|
+
type: "teardownError",
|
|
630
|
+
port,
|
|
631
|
+
cause
|
|
632
|
+
})
|
|
633
|
+
}).recoverDefect((cause) => {
|
|
634
|
+
ready.reject(cause);
|
|
635
|
+
return Ok();
|
|
636
|
+
}).get();
|
|
637
|
+
return fromSafePromise(ready.promise);
|
|
638
|
+
};
|
|
639
|
+
const outcome = (async () => {
|
|
640
|
+
try {
|
|
641
|
+
return await work({
|
|
642
|
+
ctx: runtimeCtx,
|
|
643
|
+
fork
|
|
644
|
+
}, signal);
|
|
645
|
+
} finally {
|
|
646
|
+
hasSettled = true;
|
|
647
|
+
settled.resolve();
|
|
648
|
+
if (closing !== void 0) await closing;
|
|
649
|
+
}
|
|
650
|
+
})();
|
|
651
|
+
return fromSafePromise(outcome).flatMap((result) => result);
|
|
591
652
|
});
|
|
592
653
|
const host = {
|
|
593
654
|
ctx: runtimeCtx,
|
|
@@ -688,4 +749,4 @@ const runMain = async (module, options = {}, exit = (code) => {
|
|
|
688
749
|
await awaitExit(start(module, options), exit);
|
|
689
750
|
};
|
|
690
751
|
//#endregion
|
|
691
|
-
export { HealthCheckFailed, HealthChecks, Instrumentations, LEVELS, Logger, Meter, Observers, RuntimePort, RuntimeStartFailed, SPAN_STATUS, Tracer, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
|
752
|
+
export { HealthCheckFailed, HealthChecks, Instrumentations, LEVELS, Logger, Meter, Observers, RuntimePort, RuntimeStartFailed, SPAN_STATUS, Tracer, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock, traceIdOfTraceparent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "The application kernel: boot a di module into a running process, and stop it without losing work",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"application",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"./package.json": "./package.json"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@btravstack/tsconfig": "0.
|
|
48
|
+
"@btravstack/tsconfig": "0.4.0",
|
|
49
49
|
"@types/node": "26.4.0",
|
|
50
50
|
"@unthrown/vitest": "5.7.0",
|
|
51
51
|
"@vitest/coverage-v8": "4.1.11",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"typescript": "7.0.2",
|
|
54
54
|
"unthrown": "5.7.0",
|
|
55
55
|
"vitest": "4.1.11",
|
|
56
|
-
"@btravstack/
|
|
57
|
-
"@btravstack/
|
|
56
|
+
"@btravstack/config": "0.13.0",
|
|
57
|
+
"@btravstack/di": "0.13.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"unthrown": "^5.0.0",
|
|
61
|
-
"@btravstack/config": "^0.
|
|
62
|
-
"@btravstack/di": "^0.
|
|
61
|
+
"@btravstack/config": "^0.13.0",
|
|
62
|
+
"@btravstack/di": "^0.13.0"
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": ">=22"
|