@btravstack/core 0.32.0 → 0.33.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/dist/index.cjs +38 -2
- package/dist/index.d.cts +11 -7
- package/dist/index.d.mts +11 -7
- package/dist/index.mjs +38 -2
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -455,6 +455,36 @@ const installUncaughtHandlers = (onUncaught) => {
|
|
|
455
455
|
var UnitOverrides = class extends _btravstack_di.Port.many("UnitOverrides") {};
|
|
456
456
|
/** Whether `module`'s tree carries a provider for `portId`. */
|
|
457
457
|
const provides = (module, portId) => module.provides.some((provider) => provider.port.portId === portId) || module.imports.some((imported) => provides(imported, portId));
|
|
458
|
+
/** Every port `module`'s tree provides, by its class. */
|
|
459
|
+
const providedPorts = (module) => [...module.imports.flatMap(providedPorts), ...module.provides.map((provider) => provider.port)];
|
|
460
|
+
/**
|
|
461
|
+
* A check, per unit module, that it provides no port the application scope
|
|
462
|
+
* already provides. A fork builds its whole module tree per unit, so such a
|
|
463
|
+
* provider would shadow the application's instance with a fresh one per unit,
|
|
464
|
+
* released per unit — the application's configuration re-parsed, its clients
|
|
465
|
+
* reopened. A unit's member of the SAME set port is the one exemption, since
|
|
466
|
+
* the fork appends it to the application's members; a set port meeting an
|
|
467
|
+
* ordinary one, or two classes sharing an id, is refused like any other.
|
|
468
|
+
*
|
|
469
|
+
* Answers the refusal rather than throwing it, so the boot can throw it and a
|
|
470
|
+
* fork can reject with it. A module is walked once, however often it forks.
|
|
471
|
+
*/
|
|
472
|
+
const rebuildGuard = (runtime, application) => {
|
|
473
|
+
const owned = new Map(providedPorts(application).map((port) => [port.portId, port]));
|
|
474
|
+
const clean = /* @__PURE__ */ new WeakSet();
|
|
475
|
+
return (module) => {
|
|
476
|
+
if (clean.has(module)) return void 0;
|
|
477
|
+
const rebuilt = providedPorts(module).find((port) => {
|
|
478
|
+
const owner = owned.get(port.portId);
|
|
479
|
+
return owner !== void 0 && !(owner === port && port.many === true);
|
|
480
|
+
});
|
|
481
|
+
if (rebuilt === void 0) {
|
|
482
|
+
clean.add(module);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
return /* @__PURE__ */ new Error(`[core] unit module "${module.name}" of runtime "${runtime}" provides port ${JSON.stringify(rebuilt.portId)}, which the application scope already provides — a unit would rebuild it per unit; remove it from the unit module and let the unit read the application's`);
|
|
486
|
+
};
|
|
487
|
+
};
|
|
458
488
|
/**
|
|
459
489
|
* Each overridden kind's module, mapped to the module the kernel forks in its
|
|
460
490
|
* place. Keyed by module IDENTITY, because `UnitHost.fork` is handed the
|
|
@@ -804,6 +834,11 @@ const start = (module, options = {}) => {
|
|
|
804
834
|
runtimeName = runtime.name;
|
|
805
835
|
healthChecks = ctx.get(HealthChecks);
|
|
806
836
|
const runtimeCtx = ctx;
|
|
837
|
+
const rebuilt = rebuildGuard(runtime.name, root);
|
|
838
|
+
for (const unit of Object.values(runtime.units ?? {})) {
|
|
839
|
+
const refusal = unit === void 0 ? void 0 : rebuilt(unit);
|
|
840
|
+
if (refusal !== void 0) throw refusal;
|
|
841
|
+
}
|
|
807
842
|
const substitutes = unitSubstitutes(runtime.name, runtime.units ?? {}, ctx.get(UnitOverrides));
|
|
808
843
|
const run = (meta, work) => registry.run(meta, (signal) => {
|
|
809
844
|
const settled = Promise.withResolvers();
|
|
@@ -812,6 +847,8 @@ const start = (module, options = {}) => {
|
|
|
812
847
|
const fork = (module, seed) => {
|
|
813
848
|
if (hasSettled) return (0, unthrown.fromSafePromise)(Promise.reject(/* @__PURE__ */ new Error("a unit forks after it has already settled")));
|
|
814
849
|
if (closing !== void 0) return (0, unthrown.fromSafePromise)(Promise.reject(/* @__PURE__ */ new Error("a unit forks its scope once")));
|
|
850
|
+
const refusal = rebuilt(module);
|
|
851
|
+
if (refusal !== void 0) return (0, unthrown.fromSafePromise)(Promise.reject(refusal));
|
|
815
852
|
const ready = Promise.withResolvers();
|
|
816
853
|
closing = _btravstack_di.Module.forkScope(ctx, substitutes.get(module) ?? module, (forked) => {
|
|
817
854
|
ready.resolve(forked);
|
|
@@ -948,8 +985,7 @@ const awaitExit = async (app, exit) => {
|
|
|
948
985
|
*
|
|
949
986
|
* @example
|
|
950
987
|
* ```ts
|
|
951
|
-
* // `OrderApi`
|
|
952
|
-
* // `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
988
|
+
* // `OrderApi` is an `HttpModule`, which exports `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
953
989
|
* await runMain(OrderApi);
|
|
954
990
|
* ```
|
|
955
991
|
*/
|
package/dist/index.d.cts
CHANGED
|
@@ -418,20 +418,25 @@ type AnyUnitModule = Module<never, never, unknown>;
|
|
|
418
418
|
* The needs a bound unit module still owes, or `never` when none is bound.
|
|
419
419
|
* `Scope` is excluded, since nothing can ever provide it — the same exemption
|
|
420
420
|
* `NeedsGate` itself carries — and so is `Seeded`, the port the runtime's own
|
|
421
|
-
* fork seed discharges.
|
|
421
|
+
* fork seed discharges. `Env` is replaced by a refusal nothing provides, so
|
|
422
|
+
* `start` names it: a unit forks per unit of work, so configuration read there
|
|
423
|
+
* would be parsed per request rather than once at boot. Inline rather than a
|
|
424
|
+
* named alias, so a consumer's declaration emit never has to name it.
|
|
422
425
|
*/
|
|
423
|
-
type UnitNeedsOf<Unit, Seeded = never> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | Seeded> : never;
|
|
426
|
+
type UnitNeedsOf<Unit, Seeded = never> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | Seeded | Env> | ([Extract<N, Env>] extends [never] ? never : PortInstance<"UNIT READS CONFIGURATION — Env is application-scoped: parse it in a root provider and inject the result", never>) : never;
|
|
424
427
|
/** What a bound unit module exports — the port instances a piece of that kind may read. */
|
|
425
428
|
type UnitExportsOf<M> = M extends Module<infer X, never, unknown> ? X : never;
|
|
426
429
|
/** A piece's declared `unit:` record, as the services it reads off `context.unit`. */
|
|
427
430
|
type UnitRecordOf<U extends Readonly<Record<string, AnyPort>>> = { readonly [N in keyof U]: ServiceOf<InstanceType<U[N]>>; };
|
|
431
|
+
type UnprovidedBy<Unit, Declared> = [Unit] extends [never] ? NonNullable<Declared> : Unit extends unknown ? Exclude<NonNullable<Declared>, UnitExportsOf<Unit>> : never;
|
|
428
432
|
/**
|
|
429
433
|
* A piece injecting a port the bound unit module does not export. `unknown`
|
|
430
434
|
* when every declared port is covered — including the empty case, so a worker
|
|
431
|
-
* whose pieces declare no `unit:` is gated on nothing.
|
|
435
|
+
* whose pieces declare no `unit:` is gated on nothing. A union of modules is
|
|
436
|
+
* checked module by module.
|
|
432
437
|
*/
|
|
433
|
-
type UnitGate<Unit, Declared> = [
|
|
434
|
-
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export":
|
|
438
|
+
type UnitGate<Unit, Declared> = [UnprovidedBy<Unit, Declared>] extends [never] ? unknown : {
|
|
439
|
+
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export": UnprovidedBy<Unit, Declared>;
|
|
435
440
|
};
|
|
436
441
|
/**
|
|
437
442
|
* One `(helpers, input)` implementation with its piece's declared record put on
|
|
@@ -942,8 +947,7 @@ export declare const start: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>,
|
|
|
942
947
|
*
|
|
943
948
|
* @example
|
|
944
949
|
* ```ts
|
|
945
|
-
* // `OrderApi`
|
|
946
|
-
* // `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
950
|
+
* // `OrderApi` is an `HttpModule`, which exports `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
947
951
|
* await runMain(OrderApi);
|
|
948
952
|
* ```
|
|
949
953
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -418,20 +418,25 @@ type AnyUnitModule = Module<never, never, unknown>;
|
|
|
418
418
|
* The needs a bound unit module still owes, or `never` when none is bound.
|
|
419
419
|
* `Scope` is excluded, since nothing can ever provide it — the same exemption
|
|
420
420
|
* `NeedsGate` itself carries — and so is `Seeded`, the port the runtime's own
|
|
421
|
-
* fork seed discharges.
|
|
421
|
+
* fork seed discharges. `Env` is replaced by a refusal nothing provides, so
|
|
422
|
+
* `start` names it: a unit forks per unit of work, so configuration read there
|
|
423
|
+
* would be parsed per request rather than once at boot. Inline rather than a
|
|
424
|
+
* named alias, so a consumer's declaration emit never has to name it.
|
|
422
425
|
*/
|
|
423
|
-
type UnitNeedsOf<Unit, Seeded = never> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | Seeded> : never;
|
|
426
|
+
type UnitNeedsOf<Unit, Seeded = never> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | Seeded | Env> | ([Extract<N, Env>] extends [never] ? never : PortInstance<"UNIT READS CONFIGURATION — Env is application-scoped: parse it in a root provider and inject the result", never>) : never;
|
|
424
427
|
/** What a bound unit module exports — the port instances a piece of that kind may read. */
|
|
425
428
|
type UnitExportsOf<M> = M extends Module<infer X, never, unknown> ? X : never;
|
|
426
429
|
/** A piece's declared `unit:` record, as the services it reads off `context.unit`. */
|
|
427
430
|
type UnitRecordOf<U extends Readonly<Record<string, AnyPort>>> = { readonly [N in keyof U]: ServiceOf<InstanceType<U[N]>>; };
|
|
431
|
+
type UnprovidedBy<Unit, Declared> = [Unit] extends [never] ? NonNullable<Declared> : Unit extends unknown ? Exclude<NonNullable<Declared>, UnitExportsOf<Unit>> : never;
|
|
428
432
|
/**
|
|
429
433
|
* A piece injecting a port the bound unit module does not export. `unknown`
|
|
430
434
|
* when every declared port is covered — including the empty case, so a worker
|
|
431
|
-
* whose pieces declare no `unit:` is gated on nothing.
|
|
435
|
+
* whose pieces declare no `unit:` is gated on nothing. A union of modules is
|
|
436
|
+
* checked module by module.
|
|
432
437
|
*/
|
|
433
|
-
type UnitGate<Unit, Declared> = [
|
|
434
|
-
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export":
|
|
438
|
+
type UnitGate<Unit, Declared> = [UnprovidedBy<Unit, Declared>] extends [never] ? unknown : {
|
|
439
|
+
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export": UnprovidedBy<Unit, Declared>;
|
|
435
440
|
};
|
|
436
441
|
/**
|
|
437
442
|
* One `(helpers, input)` implementation with its piece's declared record put on
|
|
@@ -942,8 +947,7 @@ export declare const start: <X, E, N>(module: Module<X, E, N> & StartGate<X, N>,
|
|
|
942
947
|
*
|
|
943
948
|
* @example
|
|
944
949
|
* ```ts
|
|
945
|
-
* // `OrderApi`
|
|
946
|
-
* // `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
950
|
+
* // `OrderApi` is an `HttpModule`, which exports `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
947
951
|
* await runMain(OrderApi);
|
|
948
952
|
* ```
|
|
949
953
|
*/
|
package/dist/index.mjs
CHANGED
|
@@ -454,6 +454,36 @@ const installUncaughtHandlers = (onUncaught) => {
|
|
|
454
454
|
var UnitOverrides = class extends Port.many("UnitOverrides") {};
|
|
455
455
|
/** Whether `module`'s tree carries a provider for `portId`. */
|
|
456
456
|
const provides = (module, portId) => module.provides.some((provider) => provider.port.portId === portId) || module.imports.some((imported) => provides(imported, portId));
|
|
457
|
+
/** Every port `module`'s tree provides, by its class. */
|
|
458
|
+
const providedPorts = (module) => [...module.imports.flatMap(providedPorts), ...module.provides.map((provider) => provider.port)];
|
|
459
|
+
/**
|
|
460
|
+
* A check, per unit module, that it provides no port the application scope
|
|
461
|
+
* already provides. A fork builds its whole module tree per unit, so such a
|
|
462
|
+
* provider would shadow the application's instance with a fresh one per unit,
|
|
463
|
+
* released per unit — the application's configuration re-parsed, its clients
|
|
464
|
+
* reopened. A unit's member of the SAME set port is the one exemption, since
|
|
465
|
+
* the fork appends it to the application's members; a set port meeting an
|
|
466
|
+
* ordinary one, or two classes sharing an id, is refused like any other.
|
|
467
|
+
*
|
|
468
|
+
* Answers the refusal rather than throwing it, so the boot can throw it and a
|
|
469
|
+
* fork can reject with it. A module is walked once, however often it forks.
|
|
470
|
+
*/
|
|
471
|
+
const rebuildGuard = (runtime, application) => {
|
|
472
|
+
const owned = new Map(providedPorts(application).map((port) => [port.portId, port]));
|
|
473
|
+
const clean = /* @__PURE__ */ new WeakSet();
|
|
474
|
+
return (module) => {
|
|
475
|
+
if (clean.has(module)) return void 0;
|
|
476
|
+
const rebuilt = providedPorts(module).find((port) => {
|
|
477
|
+
const owner = owned.get(port.portId);
|
|
478
|
+
return owner !== void 0 && !(owner === port && port.many === true);
|
|
479
|
+
});
|
|
480
|
+
if (rebuilt === void 0) {
|
|
481
|
+
clean.add(module);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
return /* @__PURE__ */ new Error(`[core] unit module "${module.name}" of runtime "${runtime}" provides port ${JSON.stringify(rebuilt.portId)}, which the application scope already provides — a unit would rebuild it per unit; remove it from the unit module and let the unit read the application's`);
|
|
485
|
+
};
|
|
486
|
+
};
|
|
457
487
|
/**
|
|
458
488
|
* Each overridden kind's module, mapped to the module the kernel forks in its
|
|
459
489
|
* place. Keyed by module IDENTITY, because `UnitHost.fork` is handed the
|
|
@@ -803,6 +833,11 @@ const start = (module, options = {}) => {
|
|
|
803
833
|
runtimeName = runtime.name;
|
|
804
834
|
healthChecks = ctx.get(HealthChecks);
|
|
805
835
|
const runtimeCtx = ctx;
|
|
836
|
+
const rebuilt = rebuildGuard(runtime.name, root);
|
|
837
|
+
for (const unit of Object.values(runtime.units ?? {})) {
|
|
838
|
+
const refusal = unit === void 0 ? void 0 : rebuilt(unit);
|
|
839
|
+
if (refusal !== void 0) throw refusal;
|
|
840
|
+
}
|
|
806
841
|
const substitutes = unitSubstitutes(runtime.name, runtime.units ?? {}, ctx.get(UnitOverrides));
|
|
807
842
|
const run = (meta, work) => registry.run(meta, (signal) => {
|
|
808
843
|
const settled = Promise.withResolvers();
|
|
@@ -811,6 +846,8 @@ const start = (module, options = {}) => {
|
|
|
811
846
|
const fork = (module, seed) => {
|
|
812
847
|
if (hasSettled) return fromSafePromise(Promise.reject(/* @__PURE__ */ new Error("a unit forks after it has already settled")));
|
|
813
848
|
if (closing !== void 0) return fromSafePromise(Promise.reject(/* @__PURE__ */ new Error("a unit forks its scope once")));
|
|
849
|
+
const refusal = rebuilt(module);
|
|
850
|
+
if (refusal !== void 0) return fromSafePromise(Promise.reject(refusal));
|
|
814
851
|
const ready = Promise.withResolvers();
|
|
815
852
|
closing = Module.forkScope(ctx, substitutes.get(module) ?? module, (forked) => {
|
|
816
853
|
ready.resolve(forked);
|
|
@@ -947,8 +984,7 @@ const awaitExit = async (app, exit) => {
|
|
|
947
984
|
*
|
|
948
985
|
* @example
|
|
949
986
|
* ```ts
|
|
950
|
-
* // `OrderApi`
|
|
951
|
-
* // `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
987
|
+
* // `OrderApi` is an `HttpModule`, which exports `HttpRuntime`. `PORT`, `HOST` and `PROBE_PORT` are read inside the graph.
|
|
952
988
|
* await runMain(OrderApi);
|
|
953
989
|
* ```
|
|
954
990
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.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,8 +45,8 @@
|
|
|
45
45
|
"./package.json": "./package.json"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@btravstack/config": "0.
|
|
49
|
-
"@btravstack/di": "0.
|
|
48
|
+
"@btravstack/config": "0.33.0",
|
|
49
|
+
"@btravstack/di": "0.33.0",
|
|
50
50
|
"@btravstack/tsconfig": "0.4.0",
|
|
51
51
|
"@stryker-mutator/core": "10.0.0",
|
|
52
52
|
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"vitest": "5.0.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
|
-
"@btravstack/config": "^0.
|
|
64
|
-
"@btravstack/di": "^0.
|
|
63
|
+
"@btravstack/config": "^0.33.0",
|
|
64
|
+
"@btravstack/di": "^0.33.0",
|
|
65
65
|
"unthrown": "^5.0.0"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|