@btravstack/temporal-worker 0.31.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/README.md CHANGED
@@ -161,7 +161,7 @@ startup `Err`, not a defect. `runtimeInfo()` reads `{ taskQueue, namespace }`
161
161
  back once the worker is polling.
162
162
 
163
163
  A worker polling for several workflows can be several slices instead of one
164
- record: `TemporalWorkflowActivities(contract, key)({ inject: { name: Dep }, unit, sync })` mints a
164
+ record: `TemporalWorkflowActivities(contract, key)({ inject: { name: Dep }, unit, fork, sync })` mints a
165
165
  provider for ONE workflow's activities (or a contract-global activity),
166
166
  typed by the key alone, and `TemporalActivities(contract)([...])` composes an
167
167
  array of them into the same activities provider `TemporalModule` takes — the
@@ -184,6 +184,14 @@ slicing first. The root is checked against what was declared either way: bind a
184
184
  module that does not export one of those ports and `TemporalModule` refuses it,
185
185
  naming the port.
186
186
 
187
+ A piece whose activities carry a different scope from the rest names its own
188
+ module on `fork`: its attempts fork THAT module, seeded the same way, instead of
189
+ `unit.activity` — a listing activity with no tenant in its input beside a
190
+ fan-out whose activities each act for one. The piece's `unit` is then checked
191
+ against its own module at the piece's call, the module's unmet needs become the
192
+ root's (named by `start`), and an override reaches it under the module's name:
193
+ `overridden(root, providers, { unit: { TenantUnit: [...] } })`.
194
+
187
195
  Its own test suite needs a **Docker daemon**: the specs run against the shared
188
196
  `temporalio/auto-setup` container `internal/test-infra` starts once per machine and every
189
197
  workspace reuses. Nothing else here does.
@@ -198,13 +206,21 @@ subpath. Why scheduled work costs a cluster here, and when it should not be
198
206
  this stack at all:
199
207
  [Run something on a schedule](https://btravstack.github.io/btravstack/how-to/run-something-on-a-schedule).
200
208
 
209
+ ## Testing
210
+
211
+ `@btravstack/temporal-worker/testing` runs a worker's specs against one real
212
+ Temporal server per test run: `temporalServer` is the Vitest `globalSetup`
213
+ (the entry's default export), and `temporalEnvironment(prefix)` a file-scoped
214
+ fixture answering `{ TEMPORAL_ADDRESS, TEMPORAL_NAMESPACE }` for a namespace of
215
+ the spec file's own. It needs Docker, and `vitest` and `testcontainers` as
216
+ optional peers. See
217
+ [Test a Temporal worker](https://btravstack.github.io/btravstack/how-to/test-a-temporal-worker).
218
+
201
219
  ## Options
202
220
 
203
- `TemporalModule(name)({...})` takes `temporal()`'s options plus `activities`
204
- and the module lists (`imports`, `provides`, `exports`, `needs`). The
205
- `temporal()` primitive does not take `activities` as an option — it **needs**
206
- the activities provider on its own port, which is how the composition root
207
- supplies it:
221
+ `TemporalModule(name)({...})` takes the starter's options plus `activities`
222
+ and the module lists (`imports`, `provides`, `exports`, `needs`). It is the
223
+ only way to compose the starter, which is not exported:
208
224
 
209
225
  | Option | What it is |
210
226
  | ------------- | ----------------------------------------------------------------------------------------------------------------------- |
@@ -233,6 +249,10 @@ shutdown, so "stop waiting" is the escalation. `Result` → activity failure is
233
249
  `declareActivitiesHandler`'s and is deliberately not duplicated here. The rest
234
250
  is on the [documentation site](https://btravstack.github.io/btravstack/reference/temporal-worker).
235
251
 
252
+ `TemporalModule` automatically exports publicly available `Logger` and `HealthChecks`
253
+ ports, including whole-module re-exports and providers declared at the root.
254
+ It keeps private infrastructure hidden and business exports explicit.
255
+
236
256
  ## License
237
257
 
238
258
  [MIT](./LICENSE) © Benoit TRAVERS
package/dist/index.d.mts CHANGED
@@ -59,11 +59,15 @@ declare const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
59
59
  * silent merge.
60
60
  */
61
61
  type WorkflowActivitiesPortOf<C extends ContractDefinition, K extends ActivitiesKeyOf<C>> = PortClassOf<`${typeof WORKFLOW_ACTIVITIES_PREFIX}${K}`, ActivitiesRecordOf<C>[K]>;
62
- /** What a minted piece returns. */
63
- type MintedActivities<C extends ContractDefinition, K extends ActivitiesKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<WorkflowActivitiesPortOf<C, K>>, never, N> & {
62
+ /**
63
+ * What a minted piece returns. A piece naming its own `fork` owes that
64
+ * module's unmet needs, and leaves nothing for the root's `unit.activity` to
65
+ * export — its declared ports were checked against its own module at the call.
66
+ */
67
+ type MintedActivities<C extends ContractDefinition, K extends ActivitiesKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>, M extends AnyUnitModule | undefined> = Provider<InstanceType<WorkflowActivitiesPortOf<C, K>>, never, N | UnitNeedsOf$1<M>> & {
64
68
  readonly port: WorkflowActivitiesPortOf<C, K>;
65
69
  /** Phantom: the ports this piece injects, which the root's `unit.activity` must export. */
66
- readonly _declared?: InstanceType<U[keyof U]>;
70
+ readonly _declared?: undefined extends M ? InstanceType<U[keyof U]> : never;
67
71
  };
68
72
  /** The activities `sync` hands back, typed by the record THIS piece declared. */
69
73
  type ScopedActivitiesOf<C extends ContractDefinition, K extends ActivitiesKeyOf<C>, U extends Readonly<Record<string, AnyPort>>> = ActivitiesRecordOf<C, {
@@ -90,15 +94,24 @@ type ScopedActivitiesOf<C extends ContractDefinition, K extends ActivitiesKeyOf<
90
94
  * `unit` declares the ports the activities read off `context.unit`, resolved
91
95
  * out of the fork the attempt opened; the root's `unit.activity` module must
92
96
  * export every one of them.
97
+ *
98
+ * `fork` names the unit module THESE activities fork per attempt instead of
99
+ * the root's `unit.activity` — seeded with the validated input on
100
+ * `ActivityInput(contract)` the same way — for a worker whose activities do
101
+ * not all carry the same scope: a listing activity with no tenant in its
102
+ * input beside a fan-out whose activities each act for one. The ports `unit`
103
+ * declares are then checked against this module, here.
93
104
  */
94
- export declare const TemporalWorkflowActivities: <C extends ContractDefinition, const K extends ActivitiesKeyOf<C>>(contract: C, key: K) => <const D extends Readonly<Record<string, AnyPort>> = Record<never, never>, const U extends Readonly<Record<string, AnyPort>> = Record<never, never>>(options: {
105
+ export declare const TemporalWorkflowActivities: <C extends ContractDefinition, const K extends ActivitiesKeyOf<C>>(contract: C, key: K) => <const D extends Readonly<Record<string, AnyPort>> = Record<never, never>, const U extends Readonly<Record<string, AnyPort>> = Record<never, never>, M extends AnyUnitModule | undefined = undefined>(options: {
95
106
  readonly inject?: D;
96
107
  /** The unit-scoped ports these activities read off `context.unit`. */
97
108
  readonly unit?: U;
109
+ /** The unit module these activities fork per attempt, in place of the root's `unit.activity`. */
110
+ readonly fork?: M;
98
111
  readonly sync: (services: { readonly [N in keyof D]: ServiceOf<InstanceType<D[N]>>; }) => ScopedActivitiesOf<C, K, U>;
99
112
  } & ([keyof D] extends [never] ? unknown : {
100
113
  readonly inject: D;
101
- })) => MintedActivities<C, K, InstanceType<D[keyof D]>, U>;
114
+ }) & ([Exclude<M, undefined>] extends [never] ? unknown : UnitGate<Exclude<M, undefined>, InstanceType<U[keyof U]>>)) => MintedActivities<C, K, InstanceType<D[keyof D]>, U, M>;
102
115
  //#endregion
103
116
  //#region src/temporal-runtime.d.ts
104
117
  /** What the worker publishes once it is polling, read back through `RunningApp.runtimeInfo()`. */
@@ -119,7 +132,7 @@ type WorkflowSource = {
119
132
  };
120
133
  declare const TemporalConfig_base: import("@btravstack/di").PortClass<"TemporalConfig">;
121
134
  /**
122
- * Where the Temporal service is, as a service: `temporal()` binds it from the
135
+ * Where the Temporal service is, as a service: the starter binds it from the
123
136
  * environment — `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`),
124
137
  * `TEMPORAL_NAMESPACE` (default `default`) and the shutdown budgets below —
125
138
  * unless pinned, and anything else in the graph may read it.
@@ -151,7 +164,7 @@ export declare class TemporalUnreachable extends TemporalUnreachable_base<{
151
164
  }> {
152
165
  message: string;
153
166
  }
154
- /** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
167
+ /** The runtime's port: what the starter provides, and what `TemporalModule` exports for `start` to boot. */
155
168
  export declare class TemporalRuntime extends RuntimePort<Runtime<never, TemporalInfo>> {}
156
169
  /** The needs a bound `unit.activity` module still owes, less the input the fork's seed discharges. */
157
170
  type UnitNeedsOf$1<Unit> = UnitNeedsOf<Unit, ActivityInputInstance>;
@@ -167,7 +180,7 @@ type ActivitiesPortOf<C extends ContractDefinition> = PortClassOf<"TemporalActiv
167
180
  /** The activities port's instance for `C` — the module's one need. */
168
181
  type ActivitiesInstanceOf<C extends ContractDefinition> = PortInstance<"TemporalActivities", ActivitiesOf<C>>;
169
182
  /**
170
- * What a Temporal deployment tunes, shared verbatim by `temporal()` and
183
+ * What a Temporal deployment tunes, shared verbatim by the starter and
171
184
  * `TemporalModule` — spelled once so the two cannot drift, which is what a
172
185
  * second copy of an option list always eventually does.
173
186
  */
@@ -210,19 +223,6 @@ type TemporalOptions<C extends ContractDefinition, Unit extends AnyUnitModule |
210
223
  readonly contract: C;
211
224
  readonly workflows: WorkflowSource;
212
225
  };
213
- type Provided = TemporalRuntime | TemporalConfig | TemporalConnection;
214
- /**
215
- * The Temporal starter: a module providing the runtime, its configuration
216
- * (bound from the `TEMPORAL_*` variables unless pinned) and the connection (a
217
- * resource opened with the scope and closed with it; a service that will not
218
- * answer is a modeled `TemporalUnreachable`). Import it next to the
219
- * application, export `TemporalRuntime`, provide the activities — the
220
- * activities port is a need of this module.
221
- *
222
- * Each configuration field is pinned on its own: a pinned field reads nothing
223
- * from the environment, and a field left unpinned still does.
224
- */
225
- export declare const temporal: <C extends ContractDefinition, Unit extends AnyUnitModule | undefined = undefined>(options: TemporalOptions<C, Unit>) => Module<Provided, ConfigInvalid | TemporalUnreachable, EnvReading<never, "TEMPORAL_ADDRESS" | "TEMPORAL_NAMESPACE" | "TEMPORAL_GRACE_PERIOD_MS" | "TEMPORAL_FORCE_AFTER_MS"> | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf$1<Unit>>;
226
226
  //#endregion
227
227
  //#region src/temporal-module.d.ts
228
228
  /** The starter's own module, as the sugar adds it to the application's imports. */
@@ -231,7 +231,7 @@ type TemporalStarter<C extends ContractDefinition, Unit extends AnyUnitModule |
231
231
  type Imports<I extends readonly AnyModule[], C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = readonly [...I, TemporalStarter<C, Unit>];
232
232
  /** The activities provider plus the application's own — the tuple `Module(name)` is handed. */
233
233
  type Provides<P extends readonly AnyProvider[], C extends ContractDefinition, ActivitiesError, ActivitiesNeeds> = readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P];
234
- type TemporalModuleOptions<C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, Declared, Unit extends AnyUnitModule | undefined, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, C, Unit>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[], N extends readonly AnyPort[]> = TemporalTuning<Unit> & {
234
+ type TemporalModuleOptions<C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, Declared, Unit extends AnyUnitModule | undefined, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, C, Unit>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[], N extends readonly AnyPort[] | undefined> = TemporalTuning<Unit> & {
235
235
  /** The `temporal-contract` contract; the task queue this worker polls is read off it. */
236
236
  readonly contract: C;
237
237
  readonly workflows: WorkflowSource;
@@ -246,7 +246,7 @@ type TemporalModuleOptions<C extends ContractDefinition, ActivitiesError, Activi
246
246
  };
247
247
  readonly imports?: I;
248
248
  readonly provides?: P;
249
- /** The application's own exports; `TemporalRuntime` is added, since `start` resolves it. */
249
+ /** The application's own exports; `TemporalRuntime` and visible kernel infrastructure are added. */
250
250
  readonly exports?: X;
251
251
  /**
252
252
  * What this root's OWN providers expect from outside. di's gate is re-stated
@@ -271,7 +271,7 @@ type TemporalModuleOptions<C extends ContractDefinition, ActivitiesError, Activi
271
271
  * await runMain(OrderTemporalWorker);
272
272
  * ```
273
273
  */
274
- export declare const TemporalModule: <const Name extends string>(name: Name) => <C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, Declared = never, Unit extends AnyUnitModule | undefined = undefined, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, C, Unit>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[] = [], const N extends readonly AnyPort[] = []>(options: TemporalModuleOptions<C, ActivitiesError, ActivitiesNeeds, Declared, Unit, I, P, X, N>) => Module<(Extract<(readonly [typeof TemporalRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof TemporalRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof TemporalRuntime, ...X])[number] extends (infer E_1) ? E_1 extends AnyPort ? InstanceType<E_1> : E_1 extends AnyProvider ? InstanceType<E_1["port"]> : never : never), ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[1] | ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_2) ? T_2 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[2], ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]> | Exclude<((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]>>;
274
+ export declare const TemporalModule: <const Name extends string>(name: Name) => <C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, Declared = never, Unit extends AnyUnitModule | undefined = undefined, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, C, Unit>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[] = [], const N extends readonly AnyPort[] | undefined = undefined>(options: TemporalModuleOptions<C, ActivitiesError, ActivitiesNeeds, Declared, Unit, I, P, X, N>) => Module<(Extract<(readonly [typeof TemporalRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T) ? T extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T) ? T extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number], AnyModule> extends (infer T_2) ? T_2 extends Extract<(readonly [typeof TemporalRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T) ? T extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T) ? T extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number], AnyModule> ? T_2 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [typeof TemporalRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T) ? T extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T) ? T extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number] extends (infer E_1) ? E_1 extends AnyPort ? InstanceType<E_1> : E_1 extends AnyProvider ? InstanceType<E_1["port"]> : never : never), ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[1] | ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_4 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[1], Exclude<((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[2], ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]> | Exclude<((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[2], ((readonly [...I, TemporalStarter<C, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, TemporalStarter<C, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]>>;
275
275
  /** One piece of the activities record — what `TemporalWorkflowActivities(contract, key)(…)` returns. */
276
276
  type PieceOf<C extends ContractDefinition> = { readonly [K in ActivitiesKeyOf<C>]: {
277
277
  readonly port: WorkflowActivitiesPortOf<C, K>;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Observers, RuntimePort, RuntimeStartFailed, composeByPrefix, dispatchUnit, mintPiece, noObserverMember, releasedBy, withUnitRecord } from "@btravstack/core";
1
+ import { Observers, RuntimePort, RuntimeStartFailed, composeByPrefix, dispatchUnit, infrastructureExports, mintPiece, noObserverMember, releasedBy, withUnitRecord } from "@btravstack/core";
2
2
  import { Module, Port, Provider } from "@btravstack/di";
3
3
  import { Config, Env } from "@btravstack/config";
4
4
  import { declareActivitiesHandler } from "@temporal-contract/worker/activity";
@@ -14,6 +14,21 @@ import { activityInfo } from "@temporalio/activity";
14
14
  * constructs it, so an attempt costs one record and one context object.
15
15
  */
16
16
  const withUnit = (record, entry) => typeof entry === "function" ? withUnitRecord(record, entry) : Object.fromEntries(Object.entries(entry).map(([name, implementation]) => [name, withUnitRecord(record, implementation)]));
17
+ const PIECE_UNIT = Symbol("@btravstack/temporal-worker/piece-unit");
18
+ const withFork = (entry, fork) => fork === void 0 ? entry : Object.defineProperty(entry, PIECE_UNIT, { value: fork });
19
+ const forkOf = (entry) => entry?.[PIECE_UNIT];
20
+ const unitsOf = (activity, activities) => {
21
+ const units = /* @__PURE__ */ new Map([["activity", activity]]);
22
+ for (const [key, entry] of Object.entries(activities)) {
23
+ const fork = forkOf(entry);
24
+ if (fork === void 0) continue;
25
+ if (fork === activity) throw new Error(`[temporal-worker] the "${key}" piece forks "${fork.name}", the module \`unit.activity\` already binds — drop the piece's \`fork\`, which adds nothing`);
26
+ const bound = units.get(fork.name);
27
+ if (bound !== void 0 && bound !== fork) throw new Error(`[temporal-worker] two unit modules named "${fork.name}" — an override keyed by that name could not tell them apart; rename one`);
28
+ units.set(fork.name, fork);
29
+ }
30
+ return Object.fromEntries(units);
31
+ };
17
32
  //#endregion
18
33
  //#region src/workflow-activities.ts
19
34
  /** One id, declared once, so no contract instantiating it warns about a duplicate. */
@@ -60,17 +75,24 @@ const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
60
75
  * `unit` declares the ports the activities read off `context.unit`, resolved
61
76
  * out of the fork the attempt opened; the root's `unit.activity` module must
62
77
  * export every one of them.
78
+ *
79
+ * `fork` names the unit module THESE activities fork per attempt instead of
80
+ * the root's `unit.activity` — seeded with the validated input on
81
+ * `ActivityInput(contract)` the same way — for a worker whose activities do
82
+ * not all carry the same scope: a listing activity with no tenant in its
83
+ * input beside a fan-out whose activities each act for one. The ports `unit`
84
+ * declares are then checked against this module, here.
63
85
  */
64
86
  const TemporalWorkflowActivities = (contract, key) => {
65
87
  const port = class extends Port(`${WORKFLOW_ACTIVITIES_PREFIX}${key}`) {};
66
- const mint = mintPiece(port, withUnit);
67
- return (options) => mint(options);
88
+ return (options) => mintPiece(port, (record, entry) => withFork(withUnit(record, entry), options.fork))(options);
68
89
  };
69
90
  //#endregion
70
91
  //#region src/activity-units.ts
71
92
  /**
72
- * Open one kernel unit per activity attempt, forking `unit` — when one is
73
- * bound — after the activity is invoked, before it runs; the fork is torn down
93
+ * Open one kernel unit per activity attempt, forking the module `unitFor`
94
+ * answers for the attempt's contract key — the piece's own, else the
95
+ * worker-wide one, when either is bound — after the activity is invoked, before it runs; the fork is torn down
74
96
  * when the unit closes. It is seeded with the validated input on
75
97
  * `ActivityInput(contract)`, so a unit module derives a tenant from the
76
98
  * invocation rather than reading one off an ambient record. With no `unit`
@@ -91,7 +113,7 @@ const TemporalWorkflowActivities = (contract, key) => {
91
113
  * No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
92
114
  * the mapping from a settled `Result` to an activity failure.
93
115
  */
94
- const activityUnits = (host, observers, unit) => (invocation, next) => dispatchUnit({
116
+ const activityUnits = (host, observers, unitFor) => (invocation, next) => dispatchUnit({
95
117
  host,
96
118
  observers,
97
119
  operation: {
@@ -100,7 +122,7 @@ const activityUnits = (host, observers, unit) => (invocation, next) => dispatchU
100
122
  attributes: { activity: activityInfo().activityType }
101
123
  },
102
124
  meta: metaFor(),
103
- unit,
125
+ unit: unitFor(invocation.workflowName ?? invocation.activityName),
104
126
  seed: [ActivityInputPort, invocation.input],
105
127
  next
106
128
  });
@@ -134,7 +156,7 @@ const metaFor = () => {
134
156
  //#endregion
135
157
  //#region src/temporal-runtime.ts
136
158
  /**
137
- * Where the Temporal service is, as a service: `temporal()` binds it from the
159
+ * Where the Temporal service is, as a service: the starter binds it from the
138
160
  * environment — `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`),
139
161
  * `TEMPORAL_NAMESPACE` (default `default`) and the shutdown budgets below —
140
162
  * unless pinned, and anything else in the graph may read it.
@@ -154,7 +176,7 @@ var TemporalConnection = class extends Port("TemporalConnection") {};
154
176
  var TemporalUnreachable = class extends TaggedError("TemporalUnreachable") {
155
177
  message = `the Temporal service at ${this.address} did not answer`;
156
178
  };
157
- /** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
179
+ /** The runtime's port: what the starter provides, and what `TemporalModule` exports for `start` to boot. */
158
180
  var TemporalRuntime = class extends RuntimePort {};
159
181
  /**
160
182
  * The activities' port — one id, the starter's own, which an application never
@@ -228,7 +250,7 @@ const temporal = (options) => {
228
250
  sync: ({ connection, config: bound, activities: impls, observers }) => ({
229
251
  name: "temporal",
230
252
  resolves: [],
231
- units: { activity: options.unit?.activity },
253
+ units: unitsOf(options.unit?.activity, impls),
232
254
  start: (host) => createWorker(host, connection, bound, impls, options, observers)
233
255
  })
234
256
  })
@@ -250,7 +272,7 @@ const createWorker = (host, connection, config, activities, options, observers)
250
272
  const { namespace } = config;
251
273
  return fromThrowable(() => declareActivitiesHandler({
252
274
  contract: options.contract,
253
- middleware: activityUnits(host, observers, options.unit?.activity),
275
+ middleware: activityUnits(host, observers, (key) => forkOf(activities[key]) ?? options.unit?.activity),
254
276
  activities
255
277
  }), startFailed)().toAsync().flatMap((handler) => fromPromise(Worker.create({
256
278
  connection,
@@ -324,10 +346,16 @@ const TemporalModule = (name) => (options) => {
324
346
  const provides = options.provides ?? [];
325
347
  const exports = options.exports ?? [];
326
348
  const starter = temporal(options);
349
+ const rootImports = [...imports, starter];
350
+ const rootProvides = [activities, ...provides];
327
351
  return Module(name)({
328
- imports: [...imports, starter],
329
- provides: [activities, ...provides],
330
- exports: [TemporalRuntime, ...exports],
352
+ imports: rootImports,
353
+ provides: rootProvides,
354
+ exports: [
355
+ TemporalRuntime,
356
+ ...infrastructureExports(rootImports, rootProvides),
357
+ ...exports
358
+ ],
331
359
  needs: options.needs ?? []
332
360
  });
333
361
  };
@@ -350,4 +378,4 @@ const TemporalActivities = (contract) => {
350
378
  return composeByPrefix(TemporalActivitiesPort, WORKFLOW_ACTIVITIES_PREFIX, withUnit);
351
379
  };
352
380
  //#endregion
353
- export { ActivityInput, TemporalActivities, TemporalConfig, TemporalConnection, TemporalModule, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities, temporal };
381
+ export { ActivityInput, TemporalActivities, TemporalConfig, TemporalConnection, TemporalModule, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities };
@@ -0,0 +1,64 @@
1
+ import { TestProject } from "vitest/node";
2
+ //#region src/testing.d.ts
3
+ declare module "vitest" {
4
+ interface ProvidedContext {
5
+ __BTRAVSTACK_TEMPORAL_ADDRESS__: string;
6
+ }
7
+ }
8
+ /** Options for {@link temporalServer}. */
9
+ export type TemporalServerOptions = {
10
+ /**
11
+ * How long each container may take to report healthy, in milliseconds.
12
+ * `temporalio/auto-setup` creates its schemas on first boot, which a loaded
13
+ * Docker host can stretch well past a minute.
14
+ *
15
+ * @defaultValue `300_000`
16
+ */
17
+ readonly startupTimeoutMs?: number;
18
+ };
19
+ /** The environment a spec spreads into `start`'s `env`: the server, and a namespace of the spec file's own. */
20
+ export type TemporalEnvironment = {
21
+ readonly TEMPORAL_ADDRESS: string;
22
+ readonly TEMPORAL_NAMESPACE: string;
23
+ };
24
+ /**
25
+ * A Vitest `globalSetup`: one PostgreSQL and one `temporalio/auto-setup`
26
+ * container for the whole run, started through `testcontainers` and stopped
27
+ * when the run ends. The package's `/testing` entry default-exports it with
28
+ * the default options, so `globalSetup: ["@btravstack/temporal-worker/testing"]`
29
+ * is the whole configuration.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // temporal-setup.ts, for a host that needs a longer budget
34
+ * import { temporalServer } from "@btravstack/temporal-worker/testing";
35
+ *
36
+ * export default temporalServer({ startupTimeoutMs: 600_000 });
37
+ * ```
38
+ */
39
+ export declare const temporalServer: ({ startupTimeoutMs }?: TemporalServerOptions) => ({ provide }: TestProject) => Promise<() => Promise<void>>;
40
+ declare const _default: ({ provide }: TestProject) => Promise<() => Promise<void>>;
41
+ /**
42
+ * A file-scoped Vitest fixture answering the {@link TemporalEnvironment} of a
43
+ * namespace registered for the spec file, on the server {@link temporalServer}
44
+ * started. The namespace is the isolation boundary: the starter reads its task
45
+ * queue off the contract, so two spec files polling the same queue are kept
46
+ * apart by namespace, and nothing is cleaned up — the run's end disposes.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * export const it = test.extend<{ temporal: TemporalEnvironment; boot: Boot }>({
51
+ * temporal: temporalEnvironment("orders"),
52
+ * boot: bootFixture(),
53
+ * });
54
+ *
55
+ * it("fulfils", async ({ temporal, boot }) => {
56
+ * const app = boot(OrderWorker, { env: { ...temporal } });
57
+ * });
58
+ * ```
59
+ */
60
+ export declare const temporalEnvironment: (prefix?: string) => [(context: object, use: (environment: TemporalEnvironment) => Promise<void>) => Promise<void>, {
61
+ scope: "file";
62
+ }];
63
+ //#endregion
64
+ export { _default as default };
@@ -0,0 +1,126 @@
1
+ import { msToTs } from "@temporalio/common";
2
+ import { NativeConnection } from "@temporalio/worker";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region src/testing.ts
5
+ const POSTGRES_IMAGE = "postgres:18.1";
6
+ const TEMPORAL_IMAGE = "temporalio/auto-setup:1.29.1";
7
+ const NAMESPACE_READY_MS = 6e4;
8
+ /**
9
+ * A Vitest `globalSetup`: one PostgreSQL and one `temporalio/auto-setup`
10
+ * container for the whole run, started through `testcontainers` and stopped
11
+ * when the run ends. The package's `/testing` entry default-exports it with
12
+ * the default options, so `globalSetup: ["@btravstack/temporal-worker/testing"]`
13
+ * is the whole configuration.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // temporal-setup.ts, for a host that needs a longer budget
18
+ * import { temporalServer } from "@btravstack/temporal-worker/testing";
19
+ *
20
+ * export default temporalServer({ startupTimeoutMs: 600_000 });
21
+ * ```
22
+ */
23
+ const temporalServer = ({ startupTimeoutMs = 3e5 } = {}) => async ({ provide }) => {
24
+ const { GenericContainer, Network, Wait } = await import("testcontainers");
25
+ const healthCheck = {
26
+ interval: 1e3,
27
+ retries: Math.ceil(startupTimeoutMs / 1e3),
28
+ startPeriod: startupTimeoutMs,
29
+ timeout: 5e3
30
+ };
31
+ const password = randomUUID();
32
+ const started = [];
33
+ const stopAll = async () => {
34
+ const failures = [];
35
+ for (const resource of started.toReversed()) await resource.stop().then(void 0, (failure) => failures.push(failure));
36
+ return failures;
37
+ };
38
+ const teardown = async () => {
39
+ const failures = await stopAll();
40
+ if (failures.length > 0) throw new AggregateError(failures, "[temporal-worker] the Temporal test server did not stop cleanly");
41
+ };
42
+ try {
43
+ const network = await new Network().start();
44
+ started.push(network);
45
+ started.push(await new GenericContainer(POSTGRES_IMAGE).withNetwork(network).withNetworkAliases("postgres").withEnvironment({
46
+ POSTGRES_DB: "temporal",
47
+ POSTGRES_USER: "temporal",
48
+ POSTGRES_PASSWORD: password
49
+ }).withHealthCheck({
50
+ test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U temporal"],
51
+ ...healthCheck
52
+ }).withWaitStrategy(Wait.forHealthCheck()).withStartupTimeout(startupTimeoutMs).start());
53
+ const temporal = await new GenericContainer(TEMPORAL_IMAGE).withNetwork(network).withExposedPorts(7233).withEnvironment({
54
+ DB: "postgres12",
55
+ DB_PORT: "5432",
56
+ POSTGRES_SEEDS: "postgres",
57
+ POSTGRES_USER: "temporal",
58
+ POSTGRES_PWD: password,
59
+ BIND_ON_IP: "0.0.0.0",
60
+ TEMPORAL_BROADCAST_ADDRESS: "127.0.0.1"
61
+ }).withHealthCheck({
62
+ test: ["CMD-SHELL", "tctl --address 127.0.0.1:7233 workflow list"],
63
+ ...healthCheck
64
+ }).withWaitStrategy(Wait.forHealthCheck()).withStartupTimeout(startupTimeoutMs).start();
65
+ started.push(temporal);
66
+ provide("__BTRAVSTACK_TEMPORAL_ADDRESS__", `${temporal.getHost()}:${temporal.getMappedPort(7233)}`);
67
+ return teardown;
68
+ } catch (cause) {
69
+ const failures = await stopAll();
70
+ throw failures.length === 0 ? cause : new AggregateError([cause, ...failures], "[temporal-worker] the Temporal test server failed to start, and what had started did not stop cleanly");
71
+ }
72
+ };
73
+ var testing_default = temporalServer();
74
+ /**
75
+ * Registering a namespace is not the same as being able to use one: each
76
+ * Temporal service refreshes its namespace registry on its own interval, so a
77
+ * namespace-scoped read is retried until it answers, and its last error is
78
+ * what a namespace that never becomes usable fails with.
79
+ */
80
+ const registerNamespace = async (address, prefix) => {
81
+ const { vi } = await import("vitest");
82
+ const namespace = `${prefix}-${randomUUID()}`;
83
+ const connection = await NativeConnection.connect({ address });
84
+ try {
85
+ await connection.workflowService.registerNamespace({
86
+ namespace,
87
+ workflowExecutionRetentionPeriod: msToTs("1 day")
88
+ });
89
+ await vi.waitFor(() => connection.workflowService.countWorkflowExecutions({ namespace }), {
90
+ timeout: NAMESPACE_READY_MS,
91
+ interval: 250
92
+ });
93
+ return namespace;
94
+ } finally {
95
+ await connection.close();
96
+ }
97
+ };
98
+ /**
99
+ * A file-scoped Vitest fixture answering the {@link TemporalEnvironment} of a
100
+ * namespace registered for the spec file, on the server {@link temporalServer}
101
+ * started. The namespace is the isolation boundary: the starter reads its task
102
+ * queue off the contract, so two spec files polling the same queue are kept
103
+ * apart by namespace, and nothing is cleaned up — the run's end disposes.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * export const it = test.extend<{ temporal: TemporalEnvironment; boot: Boot }>({
108
+ * temporal: temporalEnvironment("orders"),
109
+ * boot: bootFixture(),
110
+ * });
111
+ *
112
+ * it("fulfils", async ({ temporal, boot }) => {
113
+ * const app = boot(OrderWorker, { env: { ...temporal } });
114
+ * });
115
+ * ```
116
+ */
117
+ const temporalEnvironment = (prefix = "spec") => [async ({}, use) => {
118
+ const { inject } = await import("vitest");
119
+ const address = inject("__BTRAVSTACK_TEMPORAL_ADDRESS__");
120
+ await use({
121
+ TEMPORAL_ADDRESS: address,
122
+ TEMPORAL_NAMESPACE: await registerNamespace(address, prefix)
123
+ });
124
+ }, { scope: "file" }];
125
+ //#endregion
126
+ export { testing_default as default, temporalEnvironment, temporalServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/temporal-worker",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "description": "The Temporal worker runtime for @btravstack/core: one unit per activity attempt, and a drain that honours the kernel's deadline",
5
5
  "keywords": [
6
6
  "graceful-shutdown",
@@ -43,14 +43,20 @@
43
43
  "types": "./dist/schedule.d.mts",
44
44
  "default": "./dist/schedule.mjs"
45
45
  }
46
+ },
47
+ "./testing": {
48
+ "import": {
49
+ "types": "./dist/testing.d.mts",
50
+ "default": "./dist/testing.mjs"
51
+ }
46
52
  }
47
53
  },
48
54
  "devDependencies": {
49
- "@btravstack/config": "0.31.0",
50
- "@btravstack/core": "0.31.0",
51
- "@btravstack/di": "0.31.0",
55
+ "@btravstack/config": "0.33.0",
56
+ "@btravstack/core": "0.33.0",
57
+ "@btravstack/di": "0.33.0",
52
58
  "@btravstack/internal-test-infra": "0.0.0",
53
- "@btravstack/testing": "0.31.0",
59
+ "@btravstack/testing": "0.33.0",
54
60
  "@btravstack/tsconfig": "0.4.0",
55
61
  "@temporal-contract/client": "8.0.0-beta.11",
56
62
  "@temporal-contract/contract": "8.0.0-beta.11",
@@ -63,6 +69,7 @@
63
69
  "@types/node": "26.6.4",
64
70
  "@unthrown/vitest": "5.12.1",
65
71
  "@vitest/coverage-v8": "5.0.3",
72
+ "testcontainers": "12.2.0",
66
73
  "tsdown": "0.23.0",
67
74
  "typescript": "7.0.2",
68
75
  "typescript-consumer": "npm:typescript@5.9.3",
@@ -71,28 +78,36 @@
71
78
  "zod": "4.6.5"
72
79
  },
73
80
  "peerDependencies": {
74
- "@btravstack/config": "^0.31.0",
75
- "@btravstack/core": "^0.31.0",
76
- "@btravstack/di": "^0.31.0",
81
+ "@btravstack/config": "^0.33.0",
82
+ "@btravstack/core": "^0.33.0",
83
+ "@btravstack/di": "^0.33.0",
77
84
  "@temporal-contract/client": "^8.0.0-beta.9",
78
85
  "@temporal-contract/contract": "^8.0.0-beta.9",
79
86
  "@temporal-contract/worker": "^8.0.0-beta.9",
80
87
  "@temporalio/activity": "^1.22.0",
81
88
  "@temporalio/common": "^1.22.0",
82
89
  "@temporalio/worker": "^1.22.0",
83
- "unthrown": "^5.7.0"
90
+ "testcontainers": "^12.0.0",
91
+ "unthrown": "^5.7.0",
92
+ "vitest": ">=3.2.0"
84
93
  },
85
94
  "peerDependenciesMeta": {
86
95
  "@temporal-contract/client": {
87
96
  "optional": true
97
+ },
98
+ "testcontainers": {
99
+ "optional": true
100
+ },
101
+ "vitest": {
102
+ "optional": true
88
103
  }
89
104
  },
90
105
  "engines": {
91
106
  "node": ">=22.22"
92
107
  },
93
108
  "scripts": {
94
- "build": "tsdown src/index.ts src/schedule.ts --format esm --dts --clean",
95
- "dev": "tsdown src/index.ts src/schedule.ts --format esm --dts --watch",
109
+ "build": "tsdown src/index.ts src/schedule.ts src/testing.ts --format esm --dts --clean",
110
+ "dev": "tsdown src/index.ts src/schedule.ts src/testing.ts --format esm --dts --watch",
96
111
  "test": "vitest run --coverage",
97
112
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json && node ./node_modules/typescript-consumer/bin/tsc --noEmit -p tsconfig.test-d.json"
98
113
  }