@btravstack/core 0.12.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 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
  /**
@@ -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> = {
@@ -430,8 +447,12 @@ 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 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
  *
@@ -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
+ 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
+ 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, 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
- * 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> = {
@@ -430,8 +447,12 @@ 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 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
  *
@@ -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
+ 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
+ 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, 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
@@ -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.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.2.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/config": "0.12.0",
57
- "@btravstack/di": "0.12.0"
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.12.0",
62
- "@btravstack/di": "^0.12.0"
61
+ "@btravstack/config": "^0.13.0",
62
+ "@btravstack/di": "^0.13.0"
63
63
  },
64
64
  "engines": {
65
65
  "node": ">=22"