@btravstack/temporal-worker 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/README.md +22 -6
- package/dist/index.d.mts +23 -23
- package/dist/index.mjs +33 -11
- package/dist/testing.d.mts +64 -0
- package/dist/testing.mjs +126 -0
- package/package.json +26 -11
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
|
|
204
|
-
and the module lists (`imports`, `provides`, `exports`, `needs`).
|
|
205
|
-
|
|
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
|
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
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
|
-
/**
|
|
63
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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;
|
|
@@ -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[] =
|
|
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
|
@@ -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
|
-
|
|
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 `
|
|
73
|
-
*
|
|
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,
|
|
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:
|
|
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
|
|
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:
|
|
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,
|
|
@@ -356,4 +378,4 @@ const TemporalActivities = (contract) => {
|
|
|
356
378
|
return composeByPrefix(TemporalActivitiesPort, WORKFLOW_ACTIVITIES_PREFIX, withUnit);
|
|
357
379
|
};
|
|
358
380
|
//#endregion
|
|
359
|
-
export { ActivityInput, TemporalActivities, TemporalConfig, TemporalConnection, TemporalModule, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities
|
|
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 };
|
package/dist/testing.mjs
ADDED
|
@@ -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.
|
|
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.
|
|
50
|
-
"@btravstack/core": "0.
|
|
51
|
-
"@btravstack/di": "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.
|
|
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.
|
|
75
|
-
"@btravstack/core": "^0.
|
|
76
|
-
"@btravstack/di": "^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
|
-
"
|
|
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
|
}
|