@btravstack/testing 0.18.0 → 0.20.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
@@ -100,6 +100,9 @@ answers the very instances the running graph holds once it is built — after
100
100
  tree stopped providing is a `WiringDefect` ("nothing to override"), which is
101
101
  what keeps a test root from drifting silently away from the real one — the
102
102
  drift that four hand-maintained parallel roots had already produced here.
103
+ `overridden(module, [providers], { unit: { user: [providers] } })` reaches
104
+ inside a unit module the root binds, by kind, with the same drift gate at
105
+ boot.
103
106
  - **`createFakeClock(start?)`** — a `Clock` for `StartOptions.clock` whose
104
107
  time moves only on `advance(ms)`.
105
108
 
package/dist/index.cjs CHANGED
@@ -114,6 +114,14 @@ const createFakeClock = (start = 0) => {
114
114
  * tree no longer provides is a `WiringDefect` ("nothing to override"), which is
115
115
  * what turns fixture drift into a loud failure.
116
116
  *
117
+ * `options.unit` reaches inside the unit modules the root binds, keyed by the
118
+ * kind the root binds them under (`{ user: [...] }`, `{ message: [...] }`):
119
+ * a unit module is forked per unit, after the root is built, so a root-level
120
+ * override cannot see its providers. The kernel applies these at boot, and a
121
+ * kind the runtime binds no module for, a port that kind's module does not
122
+ * provide, or two overrides for one port in one kind is a defect there — the
123
+ * same drift gate, at the same moment.
124
+ *
117
125
  * It replaces ONE provider, never a subsystem: the replaced provider's siblings
118
126
  * still construct, so swapping a whole adapter stack remains a different module
119
127
  * composed in its place.
@@ -123,9 +131,12 @@ const createFakeClock = (start = 0) => {
123
131
  * root to re-export internals to be overridable. They are checked at build
124
132
  * instead, by `plan`'s missing-provider defect.
125
133
  */
126
- const overridden = (module, overrides) => (0, _btravstack_di.Module)("Overridden")({
134
+ const overridden = (module, overrides, options = {}) => (0, _btravstack_di.Module)("Overridden")({
127
135
  imports: [module],
128
- provides: overrides.map((override) => (0, _btravstack_di.overrideProvider)(override)),
136
+ provides: [...overrides.map((override) => (0, _btravstack_di.overrideProvider)(override)), _btravstack_di.Provider.member(_btravstack_core.UnitOverrides)({
137
+ inject: {},
138
+ value: Object.fromEntries(Object.entries(options.unit ?? {}).map(([kind, providers]) => [kind, providers.map((provider) => (0, _btravstack_di.overrideProvider)(provider))]))
139
+ })],
129
140
  exports: [module]
130
141
  });
131
142
  //#endregion
@@ -176,6 +187,7 @@ const testRuntime = (name = "test", options = {}) => {
176
187
  exports: [TestRuntimePort]
177
188
  }),
178
189
  resolves: [],
190
+ units: { test: options.unit },
179
191
  start: (h) => {
180
192
  host = h;
181
193
  accepting = true;
package/dist/index.d.cts CHANGED
@@ -68,9 +68,11 @@ export declare const createFakeClock: (start?: number) => FakeClock;
68
68
  //#endregion
69
69
  //#region src/overridden.d.ts
70
70
  /** One override's error channel, unioned across the tuple by `overridden`. */
71
- type ErrorsOf<O extends readonly AnyProviderFor[]> = O[number] extends Provider<never, infer E, unknown> ? E : never;
71
+ type ErrorsOf<O extends readonly AnyProviderFor[]> = O[number] extends (infer P) ? P extends Provider<never, infer E, unknown> ? E : never : never;
72
72
  /** The widest provider `overridden` accepts — variance makes every real one assignable. */
73
73
  type AnyProviderFor = Provider<never, unknown, unknown>;
74
+ /** A unit module's provider: a fork's error channel is `never`, so its overrides' must be too. */
75
+ type UnitProviderFor = Provider<never, never, unknown>;
74
76
  /**
75
77
  * The real composition root, with named providers substituted — the testing half
76
78
  * of "swapping an adapter is composing a different module", for the seam
@@ -83,6 +85,14 @@ type AnyProviderFor = Provider<never, unknown, unknown>;
83
85
  * tree no longer provides is a `WiringDefect` ("nothing to override"), which is
84
86
  * what turns fixture drift into a loud failure.
85
87
  *
88
+ * `options.unit` reaches inside the unit modules the root binds, keyed by the
89
+ * kind the root binds them under (`{ user: [...] }`, `{ message: [...] }`):
90
+ * a unit module is forked per unit, after the root is built, so a root-level
91
+ * override cannot see its providers. The kernel applies these at boot, and a
92
+ * kind the runtime binds no module for, a port that kind's module does not
93
+ * provide, or two overrides for one port in one kind is a defect there — the
94
+ * same drift gate, at the same moment.
95
+ *
86
96
  * It replaces ONE provider, never a subsystem: the replaced provider's siblings
87
97
  * still construct, so swapping a whole adapter stack remains a different module
88
98
  * composed in its place.
@@ -92,7 +102,9 @@ type AnyProviderFor = Provider<never, unknown, unknown>;
92
102
  * root to re-export internals to be overridable. They are checked at build
93
103
  * instead, by `plan`'s missing-provider defect.
94
104
  */
95
- export declare const overridden: <X, E, N, const O extends readonly AnyProviderFor[]>(module: Module<X, E, N>, overrides: O) => Module<X, E | ErrorsOf<O>, N>;
105
+ export declare const overridden: <X, E, N, const O extends readonly AnyProviderFor[]>(module: Module<X, E, N>, overrides: O, options?: {
106
+ readonly unit?: Readonly<Record<string, readonly UnitProviderFor[]>>;
107
+ }) => Module<X, E | ErrorsOf<O>, N>;
96
108
  //#endregion
97
109
  //#region src/tapped.d.ts
98
110
  /** The services behind `ports`, in order — what `tapped(...).services()` answers once the graph is built. */
package/dist/index.d.mts CHANGED
@@ -68,9 +68,11 @@ export declare const createFakeClock: (start?: number) => FakeClock;
68
68
  //#endregion
69
69
  //#region src/overridden.d.ts
70
70
  /** One override's error channel, unioned across the tuple by `overridden`. */
71
- type ErrorsOf<O extends readonly AnyProviderFor[]> = O[number] extends Provider<never, infer E, unknown> ? E : never;
71
+ type ErrorsOf<O extends readonly AnyProviderFor[]> = O[number] extends (infer P) ? P extends Provider<never, infer E, unknown> ? E : never : never;
72
72
  /** The widest provider `overridden` accepts — variance makes every real one assignable. */
73
73
  type AnyProviderFor = Provider<never, unknown, unknown>;
74
+ /** A unit module's provider: a fork's error channel is `never`, so its overrides' must be too. */
75
+ type UnitProviderFor = Provider<never, never, unknown>;
74
76
  /**
75
77
  * The real composition root, with named providers substituted — the testing half
76
78
  * of "swapping an adapter is composing a different module", for the seam
@@ -83,6 +85,14 @@ type AnyProviderFor = Provider<never, unknown, unknown>;
83
85
  * tree no longer provides is a `WiringDefect` ("nothing to override"), which is
84
86
  * what turns fixture drift into a loud failure.
85
87
  *
88
+ * `options.unit` reaches inside the unit modules the root binds, keyed by the
89
+ * kind the root binds them under (`{ user: [...] }`, `{ message: [...] }`):
90
+ * a unit module is forked per unit, after the root is built, so a root-level
91
+ * override cannot see its providers. The kernel applies these at boot, and a
92
+ * kind the runtime binds no module for, a port that kind's module does not
93
+ * provide, or two overrides for one port in one kind is a defect there — the
94
+ * same drift gate, at the same moment.
95
+ *
86
96
  * It replaces ONE provider, never a subsystem: the replaced provider's siblings
87
97
  * still construct, so swapping a whole adapter stack remains a different module
88
98
  * composed in its place.
@@ -92,7 +102,9 @@ type AnyProviderFor = Provider<never, unknown, unknown>;
92
102
  * root to re-export internals to be overridable. They are checked at build
93
103
  * instead, by `plan`'s missing-provider defect.
94
104
  */
95
- export declare const overridden: <X, E, N, const O extends readonly AnyProviderFor[]>(module: Module<X, E, N>, overrides: O) => Module<X, E | ErrorsOf<O>, N>;
105
+ export declare const overridden: <X, E, N, const O extends readonly AnyProviderFor[]>(module: Module<X, E, N>, overrides: O, options?: {
106
+ readonly unit?: Readonly<Record<string, readonly UnitProviderFor[]>>;
107
+ }) => Module<X, E | ErrorsOf<O>, N>;
96
108
  //#endregion
97
109
  //#region src/tapped.d.ts
98
110
  /** The services behind `ports`, in order — what `tapped(...).services()` answers once the graph is built. */
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { RuntimePort, start } from "@btravstack/core";
1
+ import { RuntimePort, UnitOverrides, start } from "@btravstack/core";
2
2
  import { setTimeout } from "node:timers/promises";
3
3
  import { OkAsync, fromSafePromise } from "unthrown";
4
4
  import { Module, Port, Provider, overrideProvider } from "@btravstack/di";
@@ -113,6 +113,14 @@ const createFakeClock = (start = 0) => {
113
113
  * tree no longer provides is a `WiringDefect` ("nothing to override"), which is
114
114
  * what turns fixture drift into a loud failure.
115
115
  *
116
+ * `options.unit` reaches inside the unit modules the root binds, keyed by the
117
+ * kind the root binds them under (`{ user: [...] }`, `{ message: [...] }`):
118
+ * a unit module is forked per unit, after the root is built, so a root-level
119
+ * override cannot see its providers. The kernel applies these at boot, and a
120
+ * kind the runtime binds no module for, a port that kind's module does not
121
+ * provide, or two overrides for one port in one kind is a defect there — the
122
+ * same drift gate, at the same moment.
123
+ *
116
124
  * It replaces ONE provider, never a subsystem: the replaced provider's siblings
117
125
  * still construct, so swapping a whole adapter stack remains a different module
118
126
  * composed in its place.
@@ -122,9 +130,12 @@ const createFakeClock = (start = 0) => {
122
130
  * root to re-export internals to be overridable. They are checked at build
123
131
  * instead, by `plan`'s missing-provider defect.
124
132
  */
125
- const overridden = (module, overrides) => Module("Overridden")({
133
+ const overridden = (module, overrides, options = {}) => Module("Overridden")({
126
134
  imports: [module],
127
- provides: overrides.map((override) => overrideProvider(override)),
135
+ provides: [...overrides.map((override) => overrideProvider(override)), Provider.member(UnitOverrides)({
136
+ inject: {},
137
+ value: Object.fromEntries(Object.entries(options.unit ?? {}).map(([kind, providers]) => [kind, providers.map((provider) => overrideProvider(provider))]))
138
+ })],
128
139
  exports: [module]
129
140
  });
130
141
  //#endregion
@@ -175,6 +186,7 @@ const testRuntime = (name = "test", options = {}) => {
175
186
  exports: [TestRuntimePort]
176
187
  }),
177
188
  resolves: [],
189
+ units: { test: options.unit },
178
190
  start: (h) => {
179
191
  host = h;
180
192
  accepting = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/testing",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Test the applications @btravstack/core boots: an in-memory runtime, a fake clock, a boot fixture that stops what it started, and a tap into a running graph",
5
5
  "keywords": [
6
6
  "dependency-injection",
@@ -54,9 +54,9 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@btravstack/config": "0.18.0",
58
- "@btravstack/core": "0.18.0",
59
- "@btravstack/di": "0.18.0",
57
+ "@btravstack/config": "0.20.0",
58
+ "@btravstack/core": "0.20.0",
59
+ "@btravstack/di": "0.20.0",
60
60
  "@btravstack/tsconfig": "0.4.0",
61
61
  "@types/node": "26.6.4",
62
62
  "@unthrown/vitest": "5.12.0",
@@ -68,9 +68,9 @@
68
68
  "vitest": "5.0.3"
69
69
  },
70
70
  "peerDependencies": {
71
- "@btravstack/config": "^0.18.0",
72
- "@btravstack/core": "^0.18.0",
73
- "@btravstack/di": "^0.18.0",
71
+ "@btravstack/config": "^0.20.0",
72
+ "@btravstack/core": "^0.20.0",
73
+ "@btravstack/di": "^0.20.0",
74
74
  "jose": "^6",
75
75
  "unthrown": "^5.0.0"
76
76
  },