@btravstack/core 0.12.0 → 0.14.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 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()}` }, (ctx, signal) =>
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`);
@@ -145,8 +145,9 @@ them and none should own a private copy:
145
145
  bad value is a `ConfigInvalid` naming every fault at once, and exit `78`.
146
146
  - **Teardown on every path**, with failing finalisers in
147
147
  `ExitReport.teardownErrors`, never masking the exit reason.
148
- - **A per-unit scope no handler manages** — pass a module as
149
- `StartOptions.unit` and the kernel forks it around every unit.
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.
150
151
  - **An ambient record, not an ambient container** — `currentUnit()` reads
151
152
  `{ unitId, traceId, tenantId, signal }`; services never travel
152
153
  there. `signal` is the very `AbortSignal` the unit's work callback is handed,
package/dist/index.cjs CHANGED
@@ -613,15 +613,43 @@ const start = (module, options = {}) => {
613
613
  runtimeName = runtime.name;
614
614
  healthChecks = ctx.get(HealthChecks);
615
615
  const runtimeCtx = ctx;
616
- const unit = options.unit;
617
616
  const run = (meta, work) => registry.run(meta, (signal) => {
618
- if (unit === void 0) return work(runtimeCtx, signal);
619
- const fork = _btravstack_di.Module.forkScope;
620
- return fork(ctx, unit, (forked) => (0, unthrown.fromSafePromise)((async () => await work(forked, signal))()).flatMap((result) => result), { onTeardownError: (port, cause) => emit({
621
- type: "teardownError",
622
- port,
623
- cause
624
- }) });
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);
625
653
  });
626
654
  const host = {
627
655
  ctx: runtimeCtx,
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
  /**
@@ -31,7 +31,7 @@ declare const Instrumentations_base: import("@btravstack/di").ManyPortClass<"Ins
31
31
  * without it registers nothing and pays nothing — the Spring Boot starter
32
32
  * shape, in one port.
33
33
  */
34
- declare class Instrumentations extends Instrumentations_base<InstrumentationLoader> {}
34
+ export declare class Instrumentations extends Instrumentations_base<InstrumentationLoader> {}
35
35
  //#endregion
36
36
  //#region src/health.d.ts
37
37
  declare const HealthCheckFailed_base: import("unthrown").TaggedErrorConstructor<"HealthCheckFailed">;
@@ -40,7 +40,7 @@ declare const HealthCheckFailed_base: import("unthrown").TaggedErrorConstructor<
40
40
  * a health check that throws is a bug in the check; one that fails is the news
41
41
  * `/healthz` exists to carry.
42
42
  */
43
- declare class HealthCheckFailed extends HealthCheckFailed_base<{
43
+ export declare class HealthCheckFailed extends HealthCheckFailed_base<{
44
44
  readonly reason: string;
45
45
  }> {}
46
46
  /** One contribution: what to call the component, and how to ask it. */
@@ -57,7 +57,7 @@ declare const HealthChecks_base: import("@btravstack/di").ManyPortClass<"HealthC
57
57
  * application never composed contributes nothing — no registration call to
58
58
  * forget, and no order to get wrong.
59
59
  */
60
- declare class HealthChecks extends HealthChecks_base<HealthCheck> {}
60
+ export declare class HealthChecks extends HealthChecks_base<HealthCheck> {}
61
61
  type ComponentHealth = {
62
62
  readonly name: string;
63
63
  readonly status: "healthy" | "unhealthy";
@@ -81,7 +81,7 @@ type HealthReport = {
81
81
  * escaped, the first would reach the kernel's own `uncaughtException` handler
82
82
  * and the second would leave `/healthz` hanging with nothing written.
83
83
  */
84
- declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<HealthReport, never>;
84
+ export declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<HealthReport, never>;
85
85
  //#endregion
86
86
  //#region src/observability.d.ts
87
87
  /**
@@ -91,7 +91,7 @@ declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<H
91
91
  */
92
92
  type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
93
93
  /** The levels in order, least severe first — what `isEnabled` compares through. */
94
- declare const LEVELS: readonly Level[];
94
+ export declare const LEVELS: readonly Level[];
95
95
  /**
96
96
  * What a line, a span or a measurement carries besides itself: a flat record of
97
97
  * scalars.
@@ -137,9 +137,9 @@ declare const Logger_base: import("@btravstack/di").PortClass<"Logger">;
137
137
  * problem into an outage. Synchronous `void`, not an `AsyncResult` — thesis 6's
138
138
  * one deliberate exemption, since a lost line is not a modeled error.
139
139
  */
140
- declare class Logger extends Logger_base<LoggerService> {}
140
+ export declare class Logger extends Logger_base<LoggerService> {}
141
141
  /** A span's outcome: unset until something says otherwise, then `ok` or `error`. */
142
- declare const SPAN_STATUS: {
142
+ export declare const SPAN_STATUS: {
143
143
  readonly unset: 0;
144
144
  readonly ok: 1;
145
145
  readonly error: 2;
@@ -169,7 +169,7 @@ declare const Tracer_base: import("@btravstack/di").PortClass<"Tracer">;
169
169
  * because a port typed as a vendor's type points the dependency arrow outwards
170
170
  * and makes the whole family install that vendor.
171
171
  */
172
- declare class Tracer extends Tracer_base<TracerService> {}
172
+ export declare class Tracer extends Tracer_base<TracerService> {}
173
173
  /** A monotonic count. */
174
174
  type Counter = {
175
175
  readonly add: (value: number, attributes?: Attributes) => void;
@@ -196,7 +196,7 @@ type MeterService = {
196
196
  };
197
197
  declare const Meter_base: import("@btravstack/di").PortClass<"Meter">;
198
198
  /** The application's meter, as a port. See {@link Tracer} for why it does not name a vendor. */
199
- declare class Meter extends Meter_base<MeterService> {}
199
+ export declare class Meter extends Meter_base<MeterService> {}
200
200
  //#endregion
201
201
  //#region src/observation.d.ts
202
202
  /**
@@ -277,9 +277,9 @@ declare const Observers_base: import("@btravstack/di").ManyPortClass<"Observers"
277
277
  * NOTHING provides is an unmet dependency, at plan time and in `Needs` alike.
278
278
  * Several no-ops in one graph cost a call each and nothing else.
279
279
  */
280
- declare class Observers extends Observers_base<(operation: Operation) => Settle> {}
280
+ export declare class Observers extends Observers_base<(operation: Operation) => Settle> {}
281
281
  /** The no-op member every reader of {@link Observers} contributes, so the set is never empty. */
282
- declare const noObserver: () => Settle;
282
+ export declare const noObserver: () => Settle;
283
283
  /**
284
284
  * Run every observer around one operation, and hand back the one finisher that
285
285
  * settles them all.
@@ -291,14 +291,14 @@ declare const noObserver: () => Settle;
291
291
  * `tapFailure` on the same chain, or a retry, is exactly the shape that fires
292
292
  * twice and doubles a failure count.
293
293
  */
294
- declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
294
+ export declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
295
295
  //#endregion
296
296
  //#region src/clock.d.ts
297
297
  type Clock = {
298
298
  readonly now: () => number;
299
299
  readonly sleep: (ms: number, signal?: AbortSignal) => AsyncResult<void, never>;
300
300
  };
301
- declare const systemClock: Clock;
301
+ export declare const systemClock: Clock;
302
302
  //#endregion
303
303
  //#region src/units.d.ts
304
304
  /**
@@ -317,7 +317,7 @@ type UnitRecord = {
317
317
  readonly tenantId: string | undefined;
318
318
  readonly signal: AbortSignal;
319
319
  };
320
- declare const currentUnit: () => UnitRecord | undefined;
320
+ export declare const currentUnit: () => UnitRecord | undefined;
321
321
  /**
322
322
  * What a runtime says about one piece of work as it submits it. `kind` is the
323
323
  * category (`"http"`, `"tick"`, `"job"`); `id` identifies **this** unit.
@@ -350,7 +350,7 @@ type UnitRegistry = {
350
350
  //#endregion
351
351
  //#region src/runtime.d.ts
352
352
  declare const RuntimeStartFailed_base: import("unthrown").TaggedErrorConstructor<"RuntimeStartFailed">;
353
- declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
353
+ export declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
354
354
  readonly runtime: string;
355
355
  readonly cause: unknown;
356
356
  }> {
@@ -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
- * With a `StartOptions.unit` module in play, `work` runs only once the fork is
376
- * built. A runtime that subscribes to an event from inside `work` must first
377
- * check whether it has already fired.
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: (ctx: Context<InstanceType<Resolves>>, signal: AbortSignal) => ReturnType<UnitWork<T, E>>) => AsyncResult<T, E>;
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 `StartOptions.unit` module
392
- * provides exists only while a unit is open, so a runtime naming it in
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> = {
@@ -425,13 +442,17 @@ type Runtime<Resolves extends AnyPort = never, Info = never> = {
425
442
  * process boots exactly one, while each carries its own `Resolves`/`Info` in
426
443
  * the type.
427
444
  */
428
- declare const RuntimePort: PortClass<"Runtime">;
445
+ export declare const RuntimePort: PortClass<"Runtime">;
429
446
  /** The instance type every runtime port shares — `Extract` this from a module's exports to find its 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 Runtime<infer Resolves, unknown> ? Resolves : never;
434
- type RuntimeInfoOf<X> = RuntimeOf<X> extends Runtime<AnyPort, infer Info> ? Info : never;
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;
435
456
  /**
436
457
  * The trace id inside a W3C `traceparent` header, and nothing else of it.
437
458
  *
@@ -459,7 +480,7 @@ type RuntimeInfoOf<X> = RuntimeOf<X> extends Runtime<AnyPort, infer Info> ? Info
459
480
  * const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;
460
481
  * ```
461
482
  */
462
- declare const traceIdOfTraceparent: (header: string) => string | undefined;
483
+ export declare const traceIdOfTraceparent: (header: string) => string | undefined;
463
484
  /**
464
485
  * `running`, but no later than the kernel's drain deadline — the primitive a
465
486
  * `Serving.drain` needs when the work it awaits settles on somebody else's
@@ -479,7 +500,7 @@ declare const traceIdOfTraceparent: (header: string) => string | undefined;
479
500
  * (`drain.ts` uses `clock.sleep` for exactly that, so a fake clock can control
480
501
  * it) — do not fold the two together.
481
502
  */
482
- declare const releasedBy: (signal: AbortSignal, running: AsyncResult<void, never>) => AsyncResult<void, never>;
503
+ export declare const releasedBy: (signal: AbortSignal, running: AsyncResult<void, never>) => AsyncResult<void, never>;
483
504
  //#endregion
484
505
  //#region src/drain.d.ts
485
506
  type DrainReport = {
@@ -539,7 +560,7 @@ type KernelEvent = {
539
560
  readonly cause: unknown;
540
561
  };
541
562
  type EventSink = (event: KernelEvent) => void;
542
- declare const stderrSink: EventSink;
563
+ export declare const stderrSink: EventSink;
543
564
  //#endregion
544
565
  //#region src/phase.d.ts
545
566
  type Phase = "building" | "starting" | "serving" | "draining" | "stopping" | "exited";
@@ -555,26 +576,13 @@ type ExitReport = {
555
576
  readonly teardownErrors: readonly TeardownError[];
556
577
  readonly uptimeMs: number;
557
578
  };
558
- type StartOptions<UnitX = never, UnitNeeds = never> = {
579
+ type StartOptions = {
559
580
  /**
560
581
  * The environment the graph is configured from, provided to it as the `Env`
561
582
  * port and read for the kernel's own `PROBE_PORT`. Defaults to
562
583
  * `process.env`.
563
584
  */
564
585
  readonly env?: Environment;
565
- /**
566
- * A module forked around **every unit** — a unit being one bounded piece of
567
- * work, whatever the transport calls it: an HTTP request, a Temporal
568
- * activity, an AMQP delivery. Built as the unit opens, torn down as it
569
- * closes — while the unit's ambient record is still open — reading anything
570
- * the application context carries.
571
- *
572
- * A failing unit finaliser is reported as a `teardownError` event and never
573
- * in `ExitReport.teardownErrors`. With this option the unit's work runs only
574
- * once the fork is built, so a runtime that subscribes to an event from
575
- * inside its work must be ready for it to have already fired.
576
- */
577
- readonly unit?: Module<UnitX, never, UnitNeeds>;
578
586
  readonly clock?: Clock;
579
587
  readonly signals?: boolean;
580
588
  /**
@@ -627,15 +635,18 @@ type RunningApp<E, Info = never> = {
627
635
  /**
628
636
  * The phantom marker `start`, `runMain` and `Boot` all intersect onto their
629
637
  * `module` parameter: `unknown` — and invisible — when nothing the module
630
- * needs is unprovided, it exports a runtime, its exports cover what that
631
- * runtime resolves and they cover the unit module's needs; a diagnostic
632
- * otherwise, printed at the call site as the parameter type the argument did
633
- * 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.
634
641
  *
635
642
  * `unknown` is the satisfied arm because intersecting it leaves the module
636
643
  * type untouched. A runtime's `resolves` is checked against the module's
637
- * exports only, never the unit module's: `RuntimeHost.ctx` is the application
638
- * context, so a unit-only port would resolve to nothing there.
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.
639
650
  *
640
651
  * **Unmet needs are checked FIRST, and answered in di's own words rather than
641
652
  * a sentence of the kernel's.** A root that forgot `provides: [router]` used
@@ -654,10 +665,10 @@ type RunningApp<E, Info = never> = {
654
665
  * name is reached.
655
666
  */
656
667
  type PortIdOf<P> = P extends PortInstance<infer Id, infer _Service> ? Id : P;
657
- type StartGate<X, UnitNeeds = never, 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] ? [Exclude<UnitNeeds, X | Scope | Env>] extends [never] ? unknown : "UNSATISFIED UNIT NEEDS — the unit module needs a port the module does not export" : "UNSATISFIED RUNTIME PORTS — the runtime resolves a port the module does not export" : {
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" : {
658
669
  readonly "UNSATISFIED DEPENDENCIES — nothing provides": PortIdOf<Exclude<N, Scope | Env>>;
659
670
  };
660
- declare const start: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<X, E, N> & StartGate<X, UnitNeeds, N>, options?: StartOptions<UnitX, UnitNeeds>) => RunningApp<E, RuntimeInfoOf<X>>;
671
+ export declare const start: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions) => RunningApp<E, RuntimeInfoOf<X>>;
661
672
  //#endregion
662
673
  //#region src/run-main.d.ts
663
674
  /**
@@ -692,6 +703,6 @@ declare const start: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<
692
703
  * await runMain(OrderApi);
693
704
  * ```
694
705
  */
695
- declare const runMain: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<X, E, N> & StartGate<X, UnitNeeds, N>, options?: StartOptions<UnitX, UnitNeeds>, exit?: (code: number) => void) => Promise<void>;
706
+ export declare const runMain: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions, exit?: (code: number) => void) => Promise<void>;
696
707
  //#endregion
697
- 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, traceIdOfTraceparent };
708
+ export type { Attributes, Clock, ComponentHealth, Counter, DrainReport, EventSink, ExitReport, HealthCheck, HealthReport, Histogram, InstrumentationLoader, KernelEvent, Level, LoggerService, MeterService, Operation, Phase, RunUnit, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, Serving, Settle, Settled, Span, SpanStatusCode, StartGate, StartOptions, TeardownError, TracerService, UnitHost, UnitMeta, UnitRecord, UnitRegistry, UnitWork };
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
@@ -31,7 +31,7 @@ declare const Instrumentations_base: import("@btravstack/di").ManyPortClass<"Ins
31
31
  * without it registers nothing and pays nothing — the Spring Boot starter
32
32
  * shape, in one port.
33
33
  */
34
- declare class Instrumentations extends Instrumentations_base<InstrumentationLoader> {}
34
+ export declare class Instrumentations extends Instrumentations_base<InstrumentationLoader> {}
35
35
  //#endregion
36
36
  //#region src/health.d.ts
37
37
  declare const HealthCheckFailed_base: import("unthrown").TaggedErrorConstructor<"HealthCheckFailed">;
@@ -40,7 +40,7 @@ declare const HealthCheckFailed_base: import("unthrown").TaggedErrorConstructor<
40
40
  * a health check that throws is a bug in the check; one that fails is the news
41
41
  * `/healthz` exists to carry.
42
42
  */
43
- declare class HealthCheckFailed extends HealthCheckFailed_base<{
43
+ export declare class HealthCheckFailed extends HealthCheckFailed_base<{
44
44
  readonly reason: string;
45
45
  }> {}
46
46
  /** One contribution: what to call the component, and how to ask it. */
@@ -57,7 +57,7 @@ declare const HealthChecks_base: import("@btravstack/di").ManyPortClass<"HealthC
57
57
  * application never composed contributes nothing — no registration call to
58
58
  * forget, and no order to get wrong.
59
59
  */
60
- declare class HealthChecks extends HealthChecks_base<HealthCheck> {}
60
+ export declare class HealthChecks extends HealthChecks_base<HealthCheck> {}
61
61
  type ComponentHealth = {
62
62
  readonly name: string;
63
63
  readonly status: "healthy" | "unhealthy";
@@ -81,7 +81,7 @@ type HealthReport = {
81
81
  * escaped, the first would reach the kernel's own `uncaughtException` handler
82
82
  * and the second would leave `/healthz` hanging with nothing written.
83
83
  */
84
- declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<HealthReport, never>;
84
+ export declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<HealthReport, never>;
85
85
  //#endregion
86
86
  //#region src/observability.d.ts
87
87
  /**
@@ -91,7 +91,7 @@ declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<H
91
91
  */
92
92
  type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
93
93
  /** The levels in order, least severe first — what `isEnabled` compares through. */
94
- declare const LEVELS: readonly Level[];
94
+ export declare const LEVELS: readonly Level[];
95
95
  /**
96
96
  * What a line, a span or a measurement carries besides itself: a flat record of
97
97
  * scalars.
@@ -137,9 +137,9 @@ declare const Logger_base: import("@btravstack/di").PortClass<"Logger">;
137
137
  * problem into an outage. Synchronous `void`, not an `AsyncResult` — thesis 6's
138
138
  * one deliberate exemption, since a lost line is not a modeled error.
139
139
  */
140
- declare class Logger extends Logger_base<LoggerService> {}
140
+ export declare class Logger extends Logger_base<LoggerService> {}
141
141
  /** A span's outcome: unset until something says otherwise, then `ok` or `error`. */
142
- declare const SPAN_STATUS: {
142
+ export declare const SPAN_STATUS: {
143
143
  readonly unset: 0;
144
144
  readonly ok: 1;
145
145
  readonly error: 2;
@@ -169,7 +169,7 @@ declare const Tracer_base: import("@btravstack/di").PortClass<"Tracer">;
169
169
  * because a port typed as a vendor's type points the dependency arrow outwards
170
170
  * and makes the whole family install that vendor.
171
171
  */
172
- declare class Tracer extends Tracer_base<TracerService> {}
172
+ export declare class Tracer extends Tracer_base<TracerService> {}
173
173
  /** A monotonic count. */
174
174
  type Counter = {
175
175
  readonly add: (value: number, attributes?: Attributes) => void;
@@ -196,7 +196,7 @@ type MeterService = {
196
196
  };
197
197
  declare const Meter_base: import("@btravstack/di").PortClass<"Meter">;
198
198
  /** The application's meter, as a port. See {@link Tracer} for why it does not name a vendor. */
199
- declare class Meter extends Meter_base<MeterService> {}
199
+ export declare class Meter extends Meter_base<MeterService> {}
200
200
  //#endregion
201
201
  //#region src/observation.d.ts
202
202
  /**
@@ -277,9 +277,9 @@ declare const Observers_base: import("@btravstack/di").ManyPortClass<"Observers"
277
277
  * NOTHING provides is an unmet dependency, at plan time and in `Needs` alike.
278
278
  * Several no-ops in one graph cost a call each and nothing else.
279
279
  */
280
- declare class Observers extends Observers_base<(operation: Operation) => Settle> {}
280
+ export declare class Observers extends Observers_base<(operation: Operation) => Settle> {}
281
281
  /** The no-op member every reader of {@link Observers} contributes, so the set is never empty. */
282
- declare const noObserver: () => Settle;
282
+ export declare const noObserver: () => Settle;
283
283
  /**
284
284
  * Run every observer around one operation, and hand back the one finisher that
285
285
  * settles them all.
@@ -291,14 +291,14 @@ declare const noObserver: () => Settle;
291
291
  * `tapFailure` on the same chain, or a retry, is exactly the shape that fires
292
292
  * twice and doubles a failure count.
293
293
  */
294
- declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
294
+ export declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
295
295
  //#endregion
296
296
  //#region src/clock.d.ts
297
297
  type Clock = {
298
298
  readonly now: () => number;
299
299
  readonly sleep: (ms: number, signal?: AbortSignal) => AsyncResult<void, never>;
300
300
  };
301
- declare const systemClock: Clock;
301
+ export declare const systemClock: Clock;
302
302
  //#endregion
303
303
  //#region src/units.d.ts
304
304
  /**
@@ -317,7 +317,7 @@ type UnitRecord = {
317
317
  readonly tenantId: string | undefined;
318
318
  readonly signal: AbortSignal;
319
319
  };
320
- declare const currentUnit: () => UnitRecord | undefined;
320
+ export declare const currentUnit: () => UnitRecord | undefined;
321
321
  /**
322
322
  * What a runtime says about one piece of work as it submits it. `kind` is the
323
323
  * category (`"http"`, `"tick"`, `"job"`); `id` identifies **this** unit.
@@ -350,7 +350,7 @@ type UnitRegistry = {
350
350
  //#endregion
351
351
  //#region src/runtime.d.ts
352
352
  declare const RuntimeStartFailed_base: import("unthrown").TaggedErrorConstructor<"RuntimeStartFailed">;
353
- declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
353
+ export declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
354
354
  readonly runtime: string;
355
355
  readonly cause: unknown;
356
356
  }> {
@@ -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
- * With a `StartOptions.unit` module in play, `work` runs only once the fork is
376
- * built. A runtime that subscribes to an event from inside `work` must first
377
- * check whether it has already fired.
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: (ctx: Context<InstanceType<Resolves>>, signal: AbortSignal) => ReturnType<UnitWork<T, E>>) => AsyncResult<T, E>;
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 `StartOptions.unit` module
392
- * provides exists only while a unit is open, so a runtime naming it in
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> = {
@@ -425,13 +442,17 @@ type Runtime<Resolves extends AnyPort = never, Info = never> = {
425
442
  * process boots exactly one, while each carries its own `Resolves`/`Info` in
426
443
  * the type.
427
444
  */
428
- declare const RuntimePort: PortClass<"Runtime">;
445
+ export declare const RuntimePort: PortClass<"Runtime">;
429
446
  /** The instance type every runtime port shares — `Extract` this from a module's exports to find its 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 Runtime<infer Resolves, unknown> ? Resolves : never;
434
- type RuntimeInfoOf<X> = RuntimeOf<X> extends Runtime<AnyPort, infer Info> ? Info : never;
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;
435
456
  /**
436
457
  * The trace id inside a W3C `traceparent` header, and nothing else of it.
437
458
  *
@@ -459,7 +480,7 @@ type RuntimeInfoOf<X> = RuntimeOf<X> extends Runtime<AnyPort, infer Info> ? Info
459
480
  * const traceId = typeof parent === "string" ? traceIdOfTraceparent(parent) : undefined;
460
481
  * ```
461
482
  */
462
- declare const traceIdOfTraceparent: (header: string) => string | undefined;
483
+ export declare const traceIdOfTraceparent: (header: string) => string | undefined;
463
484
  /**
464
485
  * `running`, but no later than the kernel's drain deadline — the primitive a
465
486
  * `Serving.drain` needs when the work it awaits settles on somebody else's
@@ -479,7 +500,7 @@ declare const traceIdOfTraceparent: (header: string) => string | undefined;
479
500
  * (`drain.ts` uses `clock.sleep` for exactly that, so a fake clock can control
480
501
  * it) — do not fold the two together.
481
502
  */
482
- declare const releasedBy: (signal: AbortSignal, running: AsyncResult<void, never>) => AsyncResult<void, never>;
503
+ export declare const releasedBy: (signal: AbortSignal, running: AsyncResult<void, never>) => AsyncResult<void, never>;
483
504
  //#endregion
484
505
  //#region src/drain.d.ts
485
506
  type DrainReport = {
@@ -539,7 +560,7 @@ type KernelEvent = {
539
560
  readonly cause: unknown;
540
561
  };
541
562
  type EventSink = (event: KernelEvent) => void;
542
- declare const stderrSink: EventSink;
563
+ export declare const stderrSink: EventSink;
543
564
  //#endregion
544
565
  //#region src/phase.d.ts
545
566
  type Phase = "building" | "starting" | "serving" | "draining" | "stopping" | "exited";
@@ -555,26 +576,13 @@ type ExitReport = {
555
576
  readonly teardownErrors: readonly TeardownError[];
556
577
  readonly uptimeMs: number;
557
578
  };
558
- type StartOptions<UnitX = never, UnitNeeds = never> = {
579
+ type StartOptions = {
559
580
  /**
560
581
  * The environment the graph is configured from, provided to it as the `Env`
561
582
  * port and read for the kernel's own `PROBE_PORT`. Defaults to
562
583
  * `process.env`.
563
584
  */
564
585
  readonly env?: Environment;
565
- /**
566
- * A module forked around **every unit** — a unit being one bounded piece of
567
- * work, whatever the transport calls it: an HTTP request, a Temporal
568
- * activity, an AMQP delivery. Built as the unit opens, torn down as it
569
- * closes — while the unit's ambient record is still open — reading anything
570
- * the application context carries.
571
- *
572
- * A failing unit finaliser is reported as a `teardownError` event and never
573
- * in `ExitReport.teardownErrors`. With this option the unit's work runs only
574
- * once the fork is built, so a runtime that subscribes to an event from
575
- * inside its work must be ready for it to have already fired.
576
- */
577
- readonly unit?: Module<UnitX, never, UnitNeeds>;
578
586
  readonly clock?: Clock;
579
587
  readonly signals?: boolean;
580
588
  /**
@@ -627,15 +635,18 @@ type RunningApp<E, Info = never> = {
627
635
  /**
628
636
  * The phantom marker `start`, `runMain` and `Boot` all intersect onto their
629
637
  * `module` parameter: `unknown` — and invisible — when nothing the module
630
- * needs is unprovided, it exports a runtime, its exports cover what that
631
- * runtime resolves and they cover the unit module's needs; a diagnostic
632
- * otherwise, printed at the call site as the parameter type the argument did
633
- * 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.
634
641
  *
635
642
  * `unknown` is the satisfied arm because intersecting it leaves the module
636
643
  * type untouched. A runtime's `resolves` is checked against the module's
637
- * exports only, never the unit module's: `RuntimeHost.ctx` is the application
638
- * context, so a unit-only port would resolve to nothing there.
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.
639
650
  *
640
651
  * **Unmet needs are checked FIRST, and answered in di's own words rather than
641
652
  * a sentence of the kernel's.** A root that forgot `provides: [router]` used
@@ -654,10 +665,10 @@ type RunningApp<E, Info = never> = {
654
665
  * name is reached.
655
666
  */
656
667
  type PortIdOf<P> = P extends PortInstance<infer Id, infer _Service> ? Id : P;
657
- type StartGate<X, UnitNeeds = never, 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] ? [Exclude<UnitNeeds, X | Scope | Env>] extends [never] ? unknown : "UNSATISFIED UNIT NEEDS — the unit module needs a port the module does not export" : "UNSATISFIED RUNTIME PORTS — the runtime resolves a port the module does not export" : {
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" : {
658
669
  readonly "UNSATISFIED DEPENDENCIES — nothing provides": PortIdOf<Exclude<N, Scope | Env>>;
659
670
  };
660
- declare const start: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<X, E, N> & StartGate<X, UnitNeeds, N>, options?: StartOptions<UnitX, UnitNeeds>) => RunningApp<E, RuntimeInfoOf<X>>;
671
+ export declare const start: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions) => RunningApp<E, RuntimeInfoOf<X>>;
661
672
  //#endregion
662
673
  //#region src/run-main.d.ts
663
674
  /**
@@ -692,6 +703,6 @@ declare const start: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<
692
703
  * await runMain(OrderApi);
693
704
  * ```
694
705
  */
695
- declare const runMain: <X, E, N, UnitX = never, UnitNeeds = never>(module: Module<X, E, N> & StartGate<X, UnitNeeds, N>, options?: StartOptions<UnitX, UnitNeeds>, exit?: (code: number) => void) => Promise<void>;
706
+ export declare const runMain: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>, options?: StartOptions, exit?: (code: number) => void) => Promise<void>;
696
707
  //#endregion
697
- 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, traceIdOfTraceparent };
708
+ export type { Attributes, Clock, ComponentHealth, Counter, DrainReport, EventSink, ExitReport, HealthCheck, HealthReport, Histogram, InstrumentationLoader, KernelEvent, Level, LoggerService, MeterService, Operation, Phase, RunUnit, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, Serving, Settle, Settled, Span, SpanStatusCode, StartGate, StartOptions, TeardownError, TracerService, UnitHost, UnitMeta, UnitRecord, UnitRegistry, UnitWork };
package/dist/index.mjs CHANGED
@@ -612,15 +612,43 @@ const start = (module, options = {}) => {
612
612
  runtimeName = runtime.name;
613
613
  healthChecks = ctx.get(HealthChecks);
614
614
  const runtimeCtx = ctx;
615
- const unit = options.unit;
616
615
  const run = (meta, work) => registry.run(meta, (signal) => {
617
- if (unit === void 0) return work(runtimeCtx, signal);
618
- const fork = Module.forkScope;
619
- return fork(ctx, unit, (forked) => fromSafePromise((async () => await work(forked, signal))()).flatMap((result) => result), { onTeardownError: (port, cause) => emit({
620
- type: "teardownError",
621
- port,
622
- cause
623
- }) });
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);
624
652
  });
625
653
  const host = {
626
654
  ctx: runtimeCtx,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/core",
3
- "version": "0.12.0",
3
+ "version": "0.14.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,21 +45,21 @@
45
45
  "./package.json": "./package.json"
46
46
  },
47
47
  "devDependencies": {
48
- "@btravstack/tsconfig": "0.2.0",
49
- "@types/node": "26.4.0",
50
- "@unthrown/vitest": "5.7.0",
48
+ "@btravstack/tsconfig": "0.4.0",
49
+ "@types/node": "26.4.1",
50
+ "@unthrown/vitest": "5.8.0",
51
51
  "@vitest/coverage-v8": "4.1.11",
52
- "tsdown": "0.22.14",
52
+ "tsdown": "0.23.0",
53
53
  "typescript": "7.0.2",
54
- "unthrown": "5.7.0",
54
+ "unthrown": "5.8.0",
55
55
  "vitest": "4.1.11",
56
- "@btravstack/config": "0.12.0",
57
- "@btravstack/di": "0.12.0"
56
+ "@btravstack/config": "0.14.0",
57
+ "@btravstack/di": "0.14.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "unthrown": "^5.0.0",
61
- "@btravstack/config": "^0.12.0",
62
- "@btravstack/di": "^0.12.0"
61
+ "@btravstack/config": "^0.14.0",
62
+ "@btravstack/di": "^0.14.0"
63
63
  },
64
64
  "engines": {
65
65
  "node": ">=22"