@btravstack/temporal-worker 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +29 -73
- package/dist/index.d.cts +5 -48
- package/dist/index.d.mts +5 -48
- package/dist/index.mjs +29 -73
- package/dist/schedule.cjs +25 -14
- package/dist/schedule.d.cts +18 -13
- package/dist/schedule.d.mts +18 -13
- package/dist/schedule.mjs +25 -14
- package/package.json +22 -22
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _btravstack_core = require("@btravstack/core");
|
|
2
3
|
let _btravstack_di = require("@btravstack/di");
|
|
3
4
|
let _btravstack_config = require("@btravstack/config");
|
|
4
|
-
let _btravstack_core = require("@btravstack/core");
|
|
5
5
|
let _temporal_contract_worker_activity = require("@temporal-contract/worker/activity");
|
|
6
6
|
let _temporalio_common = require("@temporalio/common");
|
|
7
7
|
let _temporalio_worker = require("@temporalio/worker");
|
|
@@ -9,47 +9,12 @@ let unthrown = require("unthrown");
|
|
|
9
9
|
let _temporalio_activity = require("@temporalio/activity");
|
|
10
10
|
//#region src/unit.ts
|
|
11
11
|
/**
|
|
12
|
-
* Where `activityUnits` leaves the forked context for the piece wrappers to
|
|
13
|
-
* read. A symbol rather than a name: the middleware merges it into the context
|
|
14
|
-
* every activity sees, and a string key would appear on the record an activity
|
|
15
|
-
* written against the `{ inject, sync }` arm destructures.
|
|
16
|
-
*/
|
|
17
|
-
const UNIT_SCOPE = Symbol("@btravstack/temporal-worker/unit-scope");
|
|
18
|
-
/**
|
|
19
|
-
* The declared record, as a getter per name resolved on read out of the fork.
|
|
20
|
-
* Neither writable nor configurable — an activity reads what the fork holds, and
|
|
21
|
-
* cannot reshape the record under the next attempt. With no `unit.activity`
|
|
22
|
-
* bound there is nothing to resolve from, so the record is empty whatever was
|
|
23
|
-
* declared.
|
|
24
|
-
*/
|
|
25
|
-
const unitRecordOf = (forked, record) => {
|
|
26
|
-
const unit = {};
|
|
27
|
-
if (forked === void 0) return unit;
|
|
28
|
-
for (const [name, port] of Object.entries(record)) Object.defineProperty(unit, name, {
|
|
29
|
-
enumerable: true,
|
|
30
|
-
get: () => forked.get(port)
|
|
31
|
-
});
|
|
32
|
-
return unit;
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
12
|
* One entry of the activities record — a contract-global activity's own
|
|
36
13
|
* implementation, or the record of them a workflow key carries — with its
|
|
37
14
|
* piece's declared record put on `context.unit`. Applied once per piece as di
|
|
38
15
|
* constructs it, so an attempt costs one record and one context object.
|
|
39
16
|
*/
|
|
40
|
-
const withUnit = (record, entry) =>
|
|
41
|
-
const wrap = (implementation) => (helpers, input) => {
|
|
42
|
-
const { [UNIT_SCOPE]: forked, ...rest } = helpers.context ?? {};
|
|
43
|
-
return implementation({
|
|
44
|
-
...helpers,
|
|
45
|
-
context: {
|
|
46
|
-
...rest,
|
|
47
|
-
unit: unitRecordOf(forked, record)
|
|
48
|
-
}
|
|
49
|
-
}, input);
|
|
50
|
-
};
|
|
51
|
-
return typeof entry === "function" ? wrap(entry) : Object.fromEntries(Object.entries(entry).map(([name, implementation]) => [name, wrap(implementation)]));
|
|
52
|
-
};
|
|
17
|
+
const withUnit = (record, entry) => typeof entry === "function" ? (0, _btravstack_core.withUnitRecord)(record, entry) : Object.fromEntries(Object.entries(entry).map(([name, implementation]) => [name, (0, _btravstack_core.withUnitRecord)(record, implementation)]));
|
|
53
18
|
//#endregion
|
|
54
19
|
//#region src/workflow-activities.ts
|
|
55
20
|
/** One id, declared once, so no contract instantiating it warns about a duplicate. */
|
|
@@ -99,13 +64,8 @@ const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
|
|
|
99
64
|
*/
|
|
100
65
|
const TemporalWorkflowActivities = (contract, key) => {
|
|
101
66
|
const port = class extends (0, _btravstack_di.Port)(`${WORKFLOW_ACTIVITIES_PREFIX}${key}`) {};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return Object.assign((0, _btravstack_di.Provider)(port)({
|
|
105
|
-
inject: options.inject,
|
|
106
|
-
sync: (services) => withUnit(record, options.sync(services))
|
|
107
|
-
}), { unit: record });
|
|
108
|
-
};
|
|
67
|
+
const mint = (0, _btravstack_core.mintPiece)(port, withUnit);
|
|
68
|
+
return (options) => mint(options);
|
|
109
69
|
};
|
|
110
70
|
//#endregion
|
|
111
71
|
//#region src/activity-units.ts
|
|
@@ -118,9 +78,8 @@ const TemporalWorkflowActivities = (contract, key) => {
|
|
|
118
78
|
* bound, `next()` runs unchanged, so the ambient `currentUnit()` record is what
|
|
119
79
|
* an adapter reads.
|
|
120
80
|
*
|
|
121
|
-
* The forked context rides the invocation's context
|
|
122
|
-
*
|
|
123
|
-
* that piece declared — the records live with the pieces, so a hand-composed
|
|
81
|
+
* The forked context rides the invocation's context, where each piece's own
|
|
82
|
+
* wrapper turns it into the typed `context.unit` record that piece declared — the records live with the pieces, so a hand-composed
|
|
124
83
|
* `temporal()` gets them without threading a second option through the starter,
|
|
125
84
|
* and the middleware never has to map Temporal's FLAT activity name back to the
|
|
126
85
|
* workflow key its piece was minted under.
|
|
@@ -133,17 +92,19 @@ const TemporalWorkflowActivities = (contract, key) => {
|
|
|
133
92
|
* No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
|
|
134
93
|
* the mapping from a settled `Result` to an activity failure.
|
|
135
94
|
*/
|
|
136
|
-
const activityUnits = (host, observers, unit) => (invocation, next) => {
|
|
137
|
-
|
|
95
|
+
const activityUnits = (host, observers, unit) => (invocation, next) => (0, _btravstack_core.dispatchUnit)({
|
|
96
|
+
host,
|
|
97
|
+
observers,
|
|
98
|
+
operation: {
|
|
138
99
|
component: "temporal",
|
|
139
100
|
name: "attempt",
|
|
140
101
|
attributes: { activity: (0, _temporalio_activity.activityInfo)().activityType }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
};
|
|
102
|
+
},
|
|
103
|
+
meta: metaFor(),
|
|
104
|
+
unit,
|
|
105
|
+
seed: [ActivityInputPort, invocation.input],
|
|
106
|
+
next
|
|
107
|
+
});
|
|
147
108
|
/**
|
|
148
109
|
* `UnitMeta.id` must be unique per unit, and a workflow id is **not** one: an
|
|
149
110
|
* activity is retried under the same execution. A task token identifies one
|
|
@@ -151,7 +112,17 @@ const activityUnits = (host, observers, unit) => (invocation, next) => {
|
|
|
151
112
|
*
|
|
152
113
|
* The workflow id becomes the `traceId` — minted outside this process and steady
|
|
153
114
|
* across every retry, so all attempts join up in a log. An activity with no
|
|
154
|
-
* workflow falls back to the activity id
|
|
115
|
+
* workflow falls back to the activity id; `workflowExecution` is optional
|
|
116
|
+
* because Temporal's own `Info` declares it so, "not set if the activity was
|
|
117
|
+
* not started by a Workflow", which a worker polling a task queue never sees.
|
|
118
|
+
*
|
|
119
|
+
* **A REUSED workflow id merges its runs into one trace**, and that is the
|
|
120
|
+
* trade rather than an oversight: Temporal allows an id again once the previous
|
|
121
|
+
* execution closed, so `order-42` run twice is one `traceId` over two runs.
|
|
122
|
+
* `runId` would separate them and is the wrong correlation for this transport —
|
|
123
|
+
* server-minted, opaque, and not what an operator greps for. A deployment that
|
|
124
|
+
* needs per-run traces gives its workflows unique ids, which is what a Schedule
|
|
125
|
+
* already does.
|
|
155
126
|
*/
|
|
156
127
|
const metaFor = () => {
|
|
157
128
|
const info = (0, _temporalio_activity.activityInfo)();
|
|
@@ -242,10 +213,7 @@ const temporal = (options) => {
|
|
|
242
213
|
})),
|
|
243
214
|
release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
|
|
244
215
|
}),
|
|
245
|
-
|
|
246
|
-
inject: {},
|
|
247
|
-
value: _btravstack_core.noObserver
|
|
248
|
-
}),
|
|
216
|
+
_btravstack_core.noObserverMember,
|
|
249
217
|
(0, _btravstack_di.Provider)(TemporalRuntime)({
|
|
250
218
|
inject: {
|
|
251
219
|
connection: TemporalConnection,
|
|
@@ -373,19 +341,7 @@ const TemporalModule = (name) => (options) => {
|
|
|
373
341
|
* and two slices claiming one key are di's duplicate-provider defect at build.
|
|
374
342
|
*/
|
|
375
343
|
const TemporalActivities = (contract) => {
|
|
376
|
-
|
|
377
|
-
const compose = (pieces) => build({
|
|
378
|
-
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(27), piece.port])),
|
|
379
|
-
sync: (services) => services
|
|
380
|
-
});
|
|
381
|
-
const whole = (options) => {
|
|
382
|
-
const record = options.unit ?? {};
|
|
383
|
-
return build({
|
|
384
|
-
inject: options.inject,
|
|
385
|
-
sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
|
|
386
|
-
});
|
|
387
|
-
};
|
|
388
|
-
return ((first) => Array.isArray(first) ? compose(first) : whole(first));
|
|
344
|
+
return (0, _btravstack_core.composeByPrefix)(TemporalActivitiesPort, WORKFLOW_ACTIVITIES_PREFIX, withUnit);
|
|
389
345
|
};
|
|
390
346
|
//#endregion
|
|
391
347
|
exports.ActivityInput = ActivityInput;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
|
2
|
+
import { AnyUnitModule, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
2
3
|
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope, ServiceOf } from "@btravstack/di";
|
|
3
4
|
import { ContractDefinition } from "@temporal-contract/contract";
|
|
4
|
-
import { Runtime, RuntimePort } from "@btravstack/core";
|
|
5
5
|
import { DeclareActivitiesHandlerOptions, EmptyContext } from "@temporal-contract/worker/activity";
|
|
6
6
|
import { Duration } from "@temporalio/common";
|
|
7
7
|
import { NativeConnection, WorkflowBundleWithSourceMap } from "@temporalio/worker";
|
|
8
|
-
//#region src/unit.d.ts
|
|
9
|
-
/** What a bound unit module exports — the port instances an activity of that kind may read. */
|
|
10
|
-
type UnitExportsOf<M> = M extends Module<infer X, never, unknown> ? X : never;
|
|
11
|
-
/** The declared `unit:` record, as the services an activity reads off `context.unit`. */
|
|
12
|
-
type UnitRecordOf<U extends Readonly<Record<string, AnyPort>>> = { readonly [N in keyof U]: ServiceOf<InstanceType<U[N]>>; };
|
|
13
|
-
/**
|
|
14
|
-
* A piece injecting a port the bound `unit.activity` module does not export.
|
|
15
|
-
* `unknown` when every declared port is covered — including the empty case, so
|
|
16
|
-
* a worker whose pieces declare no `unit:` is gated on nothing.
|
|
17
|
-
*/
|
|
18
|
-
type UnitGate<Unit, Declared> = [Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>] extends [never] ? unknown : {
|
|
19
|
-
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export": Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>;
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
8
|
//#region src/workflow-activities.d.ts
|
|
23
9
|
/**
|
|
24
10
|
* `ActivitiesOf<C, …>` is a `NoInfer`-wrapped conditional TypeScript refuses to
|
|
@@ -76,8 +62,6 @@ type WorkflowActivitiesPortOf<C extends ContractDefinition, K extends Activities
|
|
|
76
62
|
/** What a minted piece returns. */
|
|
77
63
|
type MintedActivities<C extends ContractDefinition, K extends ActivitiesKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<WorkflowActivitiesPortOf<C, K>>, never, N> & {
|
|
78
64
|
readonly port: WorkflowActivitiesPortOf<C, K>;
|
|
79
|
-
/** The declared `unit:` record, which the wrapper resolves against. */
|
|
80
|
-
readonly unit: U;
|
|
81
65
|
/** Phantom: the ports this piece injects, which the root's `unit.activity` must export. */
|
|
82
66
|
readonly _declared?: InstanceType<U[keyof U]>;
|
|
83
67
|
};
|
|
@@ -167,22 +151,8 @@ export declare class TemporalUnreachable extends TemporalUnreachable_base<{
|
|
|
167
151
|
}
|
|
168
152
|
/** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
|
|
169
153
|
export declare class TemporalRuntime extends RuntimePort<Runtime<never, TemporalInfo>> {}
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
* constrains its own `Unit` type parameter to. `Module`'s `_exports` channel
|
|
173
|
-
* is contravariant, so `Exports = never` — never `unknown` — is what makes a
|
|
174
|
-
* REAL module's own (necessarily narrower) export type assignable to this
|
|
175
|
-
* bound: `(x: Concrete) => void` is assignable to `(x: never) => void`, not
|
|
176
|
-
* to `(x: unknown) => void`.
|
|
177
|
-
*/
|
|
178
|
-
type AnyUnitModule = Module<never, never, unknown>;
|
|
179
|
-
/**
|
|
180
|
-
* The needs a bound `unit.activity` module still owes, or `never` when none is
|
|
181
|
-
* bound. `Scope` is excluded, since nothing can ever provide it — the same
|
|
182
|
-
* exemption `NeedsGate` itself carries — and so is the activity input, which
|
|
183
|
-
* the fork's own seed discharges.
|
|
184
|
-
*/
|
|
185
|
-
type UnitNeedsOf<Unit> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | ActivityInputInstance> : never;
|
|
154
|
+
/** The needs a bound `unit.activity` module still owes, less the input the fork's seed discharges. */
|
|
155
|
+
type UnitNeedsOf$1<Unit> = UnitNeedsOf<Unit, ActivityInputInstance>;
|
|
186
156
|
/**
|
|
187
157
|
* The activity implementations `declareActivitiesHandler` takes for `C`.
|
|
188
158
|
* `TContext` is what an implementation reads off `helpers.context` — left
|
|
@@ -250,11 +220,11 @@ type Provided = TemporalRuntime | TemporalConfig | TemporalConnection;
|
|
|
250
220
|
* Each configuration field is pinned on its own: a pinned field reads nothing
|
|
251
221
|
* from the environment, and a field left unpinned still does.
|
|
252
222
|
*/
|
|
253
|
-
export declare const temporal: <C extends ContractDefinition, Unit extends AnyUnitModule | undefined = undefined>(options: TemporalOptions<C, Unit>) => Module<Provided, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf<Unit>>;
|
|
223
|
+
export declare const temporal: <C extends ContractDefinition, Unit extends AnyUnitModule | undefined = undefined>(options: TemporalOptions<C, Unit>) => Module<Provided, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf$1<Unit>>;
|
|
254
224
|
//#endregion
|
|
255
225
|
//#region src/temporal-module.d.ts
|
|
256
226
|
/** The starter's own module, as the sugar adds it to the application's imports. */
|
|
257
|
-
type TemporalStarter<C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = Module<TemporalRuntime | TemporalConfig | TemporalConnection, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf<Unit>>;
|
|
227
|
+
type TemporalStarter<C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = Module<TemporalRuntime | TemporalConfig | TemporalConnection, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf$1<Unit>>;
|
|
258
228
|
/** The application's imports plus the starter — the tuple `Module(name)` is handed. */
|
|
259
229
|
type Imports<I extends readonly AnyModule[], C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = readonly [...I, TemporalStarter<C, Unit>];
|
|
260
230
|
/** The activities provider plus the application's own — the tuple `Module(name)` is handed. */
|
|
@@ -318,19 +288,6 @@ type KeyOfPiece<P> = P extends {
|
|
|
318
288
|
} ? K : never;
|
|
319
289
|
/** The record's top-level keys no piece in `T` covers. */
|
|
320
290
|
type Uncovered<C extends ContractDefinition, T extends readonly PieceOf<C>[]> = Exclude<ActivitiesKeyOf<C>, KeyOfPiece<T[number]>>;
|
|
321
|
-
/**
|
|
322
|
-
* A refused array: as long as the array the caller wrote, its head the caller's
|
|
323
|
-
* own elements — which match — and its LAST element the marker paired with what
|
|
324
|
-
* is wrong.
|
|
325
|
-
*
|
|
326
|
-
* TypeScript compares two equal-length tuples element by element, so the extra
|
|
327
|
-
* diagnostic it reports lands on the trailing element and carries both the
|
|
328
|
-
* sentence and the missing key. A fixed two-element tuple named the key only
|
|
329
|
-
* when the array happened to be two elements long; every other arity was a
|
|
330
|
-
* length mismatch, and the developer diffed the contract against the array by
|
|
331
|
-
* hand.
|
|
332
|
-
*/
|
|
333
|
-
type Refuse<T extends readonly unknown[], Marker extends string, Detail> = T extends readonly [...infer Head, unknown] ? readonly [...Head, readonly [Marker, Detail]] : readonly [readonly [Marker, Detail]];
|
|
334
291
|
/**
|
|
335
292
|
* The record arm: the whole activities record from one `sync`, with one `unit:`
|
|
336
293
|
* record shared by every entry in it.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
|
+
import { AnyUnitModule, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
1
2
|
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope, ServiceOf } from "@btravstack/di";
|
|
2
3
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
|
3
|
-
import { Runtime, RuntimePort } from "@btravstack/core";
|
|
4
4
|
import { DeclareActivitiesHandlerOptions, EmptyContext } from "@temporal-contract/worker/activity";
|
|
5
5
|
import { Duration } from "@temporalio/common";
|
|
6
6
|
import { NativeConnection, Worker, WorkflowBundleWithSourceMap } from "@temporalio/worker";
|
|
7
7
|
import { ContractDefinition } from "@temporal-contract/contract";
|
|
8
|
-
//#region src/unit.d.ts
|
|
9
|
-
/** What a bound unit module exports — the port instances an activity of that kind may read. */
|
|
10
|
-
type UnitExportsOf<M> = M extends Module<infer X, never, unknown> ? X : never;
|
|
11
|
-
/** The declared `unit:` record, as the services an activity reads off `context.unit`. */
|
|
12
|
-
type UnitRecordOf<U extends Readonly<Record<string, AnyPort>>> = { readonly [N in keyof U]: ServiceOf<InstanceType<U[N]>>; };
|
|
13
|
-
/**
|
|
14
|
-
* A piece injecting a port the bound `unit.activity` module does not export.
|
|
15
|
-
* `unknown` when every declared port is covered — including the empty case, so
|
|
16
|
-
* a worker whose pieces declare no `unit:` is gated on nothing.
|
|
17
|
-
*/
|
|
18
|
-
type UnitGate<Unit, Declared> = [Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>] extends [never] ? unknown : {
|
|
19
|
-
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export": Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>;
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
8
|
//#region src/workflow-activities.d.ts
|
|
23
9
|
/**
|
|
24
10
|
* `ActivitiesOf<C, …>` is a `NoInfer`-wrapped conditional TypeScript refuses to
|
|
@@ -76,8 +62,6 @@ type WorkflowActivitiesPortOf<C extends ContractDefinition, K extends Activities
|
|
|
76
62
|
/** What a minted piece returns. */
|
|
77
63
|
type MintedActivities<C extends ContractDefinition, K extends ActivitiesKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<WorkflowActivitiesPortOf<C, K>>, never, N> & {
|
|
78
64
|
readonly port: WorkflowActivitiesPortOf<C, K>;
|
|
79
|
-
/** The declared `unit:` record, which the wrapper resolves against. */
|
|
80
|
-
readonly unit: U;
|
|
81
65
|
/** Phantom: the ports this piece injects, which the root's `unit.activity` must export. */
|
|
82
66
|
readonly _declared?: InstanceType<U[keyof U]>;
|
|
83
67
|
};
|
|
@@ -167,22 +151,8 @@ export declare class TemporalUnreachable extends TemporalUnreachable_base<{
|
|
|
167
151
|
}
|
|
168
152
|
/** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
|
|
169
153
|
export declare class TemporalRuntime extends RuntimePort<Runtime<never, TemporalInfo>> {}
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
* constrains its own `Unit` type parameter to. `Module`'s `_exports` channel
|
|
173
|
-
* is contravariant, so `Exports = never` — never `unknown` — is what makes a
|
|
174
|
-
* REAL module's own (necessarily narrower) export type assignable to this
|
|
175
|
-
* bound: `(x: Concrete) => void` is assignable to `(x: never) => void`, not
|
|
176
|
-
* to `(x: unknown) => void`.
|
|
177
|
-
*/
|
|
178
|
-
type AnyUnitModule = Module<never, never, unknown>;
|
|
179
|
-
/**
|
|
180
|
-
* The needs a bound `unit.activity` module still owes, or `never` when none is
|
|
181
|
-
* bound. `Scope` is excluded, since nothing can ever provide it — the same
|
|
182
|
-
* exemption `NeedsGate` itself carries — and so is the activity input, which
|
|
183
|
-
* the fork's own seed discharges.
|
|
184
|
-
*/
|
|
185
|
-
type UnitNeedsOf<Unit> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | ActivityInputInstance> : never;
|
|
154
|
+
/** The needs a bound `unit.activity` module still owes, less the input the fork's seed discharges. */
|
|
155
|
+
type UnitNeedsOf$1<Unit> = UnitNeedsOf<Unit, ActivityInputInstance>;
|
|
186
156
|
/**
|
|
187
157
|
* The activity implementations `declareActivitiesHandler` takes for `C`.
|
|
188
158
|
* `TContext` is what an implementation reads off `helpers.context` — left
|
|
@@ -250,11 +220,11 @@ type Provided = TemporalRuntime | TemporalConfig | TemporalConnection;
|
|
|
250
220
|
* Each configuration field is pinned on its own: a pinned field reads nothing
|
|
251
221
|
* from the environment, and a field left unpinned still does.
|
|
252
222
|
*/
|
|
253
|
-
export declare const temporal: <C extends ContractDefinition, Unit extends AnyUnitModule | undefined = undefined>(options: TemporalOptions<C, Unit>) => Module<Provided, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf<Unit>>;
|
|
223
|
+
export declare const temporal: <C extends ContractDefinition, Unit extends AnyUnitModule | undefined = undefined>(options: TemporalOptions<C, Unit>) => Module<Provided, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf$1<Unit>>;
|
|
254
224
|
//#endregion
|
|
255
225
|
//#region src/temporal-module.d.ts
|
|
256
226
|
/** The starter's own module, as the sugar adds it to the application's imports. */
|
|
257
|
-
type TemporalStarter<C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = Module<TemporalRuntime | TemporalConfig | TemporalConnection, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf<Unit>>;
|
|
227
|
+
type TemporalStarter<C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = Module<TemporalRuntime | TemporalConfig | TemporalConnection, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C> | UnitNeedsOf$1<Unit>>;
|
|
258
228
|
/** The application's imports plus the starter — the tuple `Module(name)` is handed. */
|
|
259
229
|
type Imports<I extends readonly AnyModule[], C extends ContractDefinition, Unit extends AnyUnitModule | undefined> = readonly [...I, TemporalStarter<C, Unit>];
|
|
260
230
|
/** The activities provider plus the application's own — the tuple `Module(name)` is handed. */
|
|
@@ -318,19 +288,6 @@ type KeyOfPiece<P> = P extends {
|
|
|
318
288
|
} ? K : never;
|
|
319
289
|
/** The record's top-level keys no piece in `T` covers. */
|
|
320
290
|
type Uncovered<C extends ContractDefinition, T extends readonly PieceOf<C>[]> = Exclude<ActivitiesKeyOf<C>, KeyOfPiece<T[number]>>;
|
|
321
|
-
/**
|
|
322
|
-
* A refused array: as long as the array the caller wrote, its head the caller's
|
|
323
|
-
* own elements — which match — and its LAST element the marker paired with what
|
|
324
|
-
* is wrong.
|
|
325
|
-
*
|
|
326
|
-
* TypeScript compares two equal-length tuples element by element, so the extra
|
|
327
|
-
* diagnostic it reports lands on the trailing element and carries both the
|
|
328
|
-
* sentence and the missing key. A fixed two-element tuple named the key only
|
|
329
|
-
* when the array happened to be two elements long; every other arity was a
|
|
330
|
-
* length mismatch, and the developer diffed the contract against the array by
|
|
331
|
-
* hand.
|
|
332
|
-
*/
|
|
333
|
-
type Refuse<T extends readonly unknown[], Marker extends string, Detail> = T extends readonly [...infer Head, unknown] ? readonly [...Head, readonly [Marker, Detail]] : readonly [readonly [Marker, Detail]];
|
|
334
291
|
/**
|
|
335
292
|
* The record arm: the whole activities record from one `sync`, with one `unit:`
|
|
336
293
|
* record shared by every entry in it.
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Observers, RuntimePort, RuntimeStartFailed, composeByPrefix, dispatchUnit, mintPiece, noObserverMember, releasedBy, withUnitRecord } from "@btravstack/core";
|
|
1
2
|
import { Module, Port, Provider } from "@btravstack/di";
|
|
2
3
|
import { Config, Env } from "@btravstack/config";
|
|
3
|
-
import { Observers, RuntimePort, RuntimeStartFailed, noObserver, observe, releasedBy } from "@btravstack/core";
|
|
4
4
|
import { declareActivitiesHandler } from "@temporal-contract/worker/activity";
|
|
5
5
|
import { msToNumber } from "@temporalio/common";
|
|
6
6
|
import { NativeConnection, Worker } from "@temporalio/worker";
|
|
@@ -8,47 +8,12 @@ import { Ok, OkAsync, TaggedError, fromPromise, fromSafePromise, fromThrowable }
|
|
|
8
8
|
import { activityInfo } from "@temporalio/activity";
|
|
9
9
|
//#region src/unit.ts
|
|
10
10
|
/**
|
|
11
|
-
* Where `activityUnits` leaves the forked context for the piece wrappers to
|
|
12
|
-
* read. A symbol rather than a name: the middleware merges it into the context
|
|
13
|
-
* every activity sees, and a string key would appear on the record an activity
|
|
14
|
-
* written against the `{ inject, sync }` arm destructures.
|
|
15
|
-
*/
|
|
16
|
-
const UNIT_SCOPE = Symbol("@btravstack/temporal-worker/unit-scope");
|
|
17
|
-
/**
|
|
18
|
-
* The declared record, as a getter per name resolved on read out of the fork.
|
|
19
|
-
* Neither writable nor configurable — an activity reads what the fork holds, and
|
|
20
|
-
* cannot reshape the record under the next attempt. With no `unit.activity`
|
|
21
|
-
* bound there is nothing to resolve from, so the record is empty whatever was
|
|
22
|
-
* declared.
|
|
23
|
-
*/
|
|
24
|
-
const unitRecordOf = (forked, record) => {
|
|
25
|
-
const unit = {};
|
|
26
|
-
if (forked === void 0) return unit;
|
|
27
|
-
for (const [name, port] of Object.entries(record)) Object.defineProperty(unit, name, {
|
|
28
|
-
enumerable: true,
|
|
29
|
-
get: () => forked.get(port)
|
|
30
|
-
});
|
|
31
|
-
return unit;
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
11
|
* One entry of the activities record — a contract-global activity's own
|
|
35
12
|
* implementation, or the record of them a workflow key carries — with its
|
|
36
13
|
* piece's declared record put on `context.unit`. Applied once per piece as di
|
|
37
14
|
* constructs it, so an attempt costs one record and one context object.
|
|
38
15
|
*/
|
|
39
|
-
const withUnit = (record, entry) =>
|
|
40
|
-
const wrap = (implementation) => (helpers, input) => {
|
|
41
|
-
const { [UNIT_SCOPE]: forked, ...rest } = helpers.context ?? {};
|
|
42
|
-
return implementation({
|
|
43
|
-
...helpers,
|
|
44
|
-
context: {
|
|
45
|
-
...rest,
|
|
46
|
-
unit: unitRecordOf(forked, record)
|
|
47
|
-
}
|
|
48
|
-
}, input);
|
|
49
|
-
};
|
|
50
|
-
return typeof entry === "function" ? wrap(entry) : Object.fromEntries(Object.entries(entry).map(([name, implementation]) => [name, wrap(implementation)]));
|
|
51
|
-
};
|
|
16
|
+
const withUnit = (record, entry) => typeof entry === "function" ? withUnitRecord(record, entry) : Object.fromEntries(Object.entries(entry).map(([name, implementation]) => [name, withUnitRecord(record, implementation)]));
|
|
52
17
|
//#endregion
|
|
53
18
|
//#region src/workflow-activities.ts
|
|
54
19
|
/** One id, declared once, so no contract instantiating it warns about a duplicate. */
|
|
@@ -98,13 +63,8 @@ const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
|
|
|
98
63
|
*/
|
|
99
64
|
const TemporalWorkflowActivities = (contract, key) => {
|
|
100
65
|
const port = class extends Port(`${WORKFLOW_ACTIVITIES_PREFIX}${key}`) {};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return Object.assign(Provider(port)({
|
|
104
|
-
inject: options.inject,
|
|
105
|
-
sync: (services) => withUnit(record, options.sync(services))
|
|
106
|
-
}), { unit: record });
|
|
107
|
-
};
|
|
66
|
+
const mint = mintPiece(port, withUnit);
|
|
67
|
+
return (options) => mint(options);
|
|
108
68
|
};
|
|
109
69
|
//#endregion
|
|
110
70
|
//#region src/activity-units.ts
|
|
@@ -117,9 +77,8 @@ const TemporalWorkflowActivities = (contract, key) => {
|
|
|
117
77
|
* bound, `next()` runs unchanged, so the ambient `currentUnit()` record is what
|
|
118
78
|
* an adapter reads.
|
|
119
79
|
*
|
|
120
|
-
* The forked context rides the invocation's context
|
|
121
|
-
*
|
|
122
|
-
* that piece declared — the records live with the pieces, so a hand-composed
|
|
80
|
+
* The forked context rides the invocation's context, where each piece's own
|
|
81
|
+
* wrapper turns it into the typed `context.unit` record that piece declared — the records live with the pieces, so a hand-composed
|
|
123
82
|
* `temporal()` gets them without threading a second option through the starter,
|
|
124
83
|
* and the middleware never has to map Temporal's FLAT activity name back to the
|
|
125
84
|
* workflow key its piece was minted under.
|
|
@@ -132,17 +91,19 @@ const TemporalWorkflowActivities = (contract, key) => {
|
|
|
132
91
|
* No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
|
|
133
92
|
* the mapping from a settled `Result` to an activity failure.
|
|
134
93
|
*/
|
|
135
|
-
const activityUnits = (host, observers, unit) => (invocation, next) => {
|
|
136
|
-
|
|
94
|
+
const activityUnits = (host, observers, unit) => (invocation, next) => dispatchUnit({
|
|
95
|
+
host,
|
|
96
|
+
observers,
|
|
97
|
+
operation: {
|
|
137
98
|
component: "temporal",
|
|
138
99
|
name: "attempt",
|
|
139
100
|
attributes: { activity: activityInfo().activityType }
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
};
|
|
101
|
+
},
|
|
102
|
+
meta: metaFor(),
|
|
103
|
+
unit,
|
|
104
|
+
seed: [ActivityInputPort, invocation.input],
|
|
105
|
+
next
|
|
106
|
+
});
|
|
146
107
|
/**
|
|
147
108
|
* `UnitMeta.id` must be unique per unit, and a workflow id is **not** one: an
|
|
148
109
|
* activity is retried under the same execution. A task token identifies one
|
|
@@ -150,7 +111,17 @@ const activityUnits = (host, observers, unit) => (invocation, next) => {
|
|
|
150
111
|
*
|
|
151
112
|
* The workflow id becomes the `traceId` — minted outside this process and steady
|
|
152
113
|
* across every retry, so all attempts join up in a log. An activity with no
|
|
153
|
-
* workflow falls back to the activity id
|
|
114
|
+
* workflow falls back to the activity id; `workflowExecution` is optional
|
|
115
|
+
* because Temporal's own `Info` declares it so, "not set if the activity was
|
|
116
|
+
* not started by a Workflow", which a worker polling a task queue never sees.
|
|
117
|
+
*
|
|
118
|
+
* **A REUSED workflow id merges its runs into one trace**, and that is the
|
|
119
|
+
* trade rather than an oversight: Temporal allows an id again once the previous
|
|
120
|
+
* execution closed, so `order-42` run twice is one `traceId` over two runs.
|
|
121
|
+
* `runId` would separate them and is the wrong correlation for this transport —
|
|
122
|
+
* server-minted, opaque, and not what an operator greps for. A deployment that
|
|
123
|
+
* needs per-run traces gives its workflows unique ids, which is what a Schedule
|
|
124
|
+
* already does.
|
|
154
125
|
*/
|
|
155
126
|
const metaFor = () => {
|
|
156
127
|
const info = activityInfo();
|
|
@@ -241,10 +212,7 @@ const temporal = (options) => {
|
|
|
241
212
|
})),
|
|
242
213
|
release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
|
|
243
214
|
}),
|
|
244
|
-
|
|
245
|
-
inject: {},
|
|
246
|
-
value: noObserver
|
|
247
|
-
}),
|
|
215
|
+
noObserverMember,
|
|
248
216
|
Provider(TemporalRuntime)({
|
|
249
217
|
inject: {
|
|
250
218
|
connection: TemporalConnection,
|
|
@@ -372,19 +340,7 @@ const TemporalModule = (name) => (options) => {
|
|
|
372
340
|
* and two slices claiming one key are di's duplicate-provider defect at build.
|
|
373
341
|
*/
|
|
374
342
|
const TemporalActivities = (contract) => {
|
|
375
|
-
|
|
376
|
-
const compose = (pieces) => build({
|
|
377
|
-
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(27), piece.port])),
|
|
378
|
-
sync: (services) => services
|
|
379
|
-
});
|
|
380
|
-
const whole = (options) => {
|
|
381
|
-
const record = options.unit ?? {};
|
|
382
|
-
return build({
|
|
383
|
-
inject: options.inject,
|
|
384
|
-
sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
|
|
385
|
-
});
|
|
386
|
-
};
|
|
387
|
-
return ((first) => Array.isArray(first) ? compose(first) : whole(first));
|
|
343
|
+
return composeByPrefix(TemporalActivitiesPort, WORKFLOW_ACTIVITIES_PREFIX, withUnit);
|
|
388
344
|
};
|
|
389
345
|
//#endregion
|
|
390
346
|
export { ActivityInput, TemporalActivities, TemporalConfig, TemporalConnection, TemporalModule, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities, temporal };
|
package/dist/schedule.cjs
CHANGED
|
@@ -16,24 +16,29 @@ let _temporal_contract_client = require("@temporal-contract/client");
|
|
|
16
16
|
* This recovers exactly that one error into an `update`. Every other error
|
|
17
17
|
* stays on the channel, still typed.
|
|
18
18
|
*
|
|
19
|
-
* **`spec
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* **`spec`, the action's `workflowType` and `args`, and `policies` are
|
|
20
|
+
* reconciled**; everything else about an existing schedule is left as it
|
|
21
|
+
* stands.
|
|
22
22
|
*
|
|
23
|
+
* - **`args` and `workflowType` are written, and they are checked on the way
|
|
24
|
+
* through.** The typed handle's `update` validates the returned action's
|
|
25
|
+
* `args` against the named workflow's input schema before anything is
|
|
26
|
+
* persisted, and answers `WorkflowValidationError` — the same error `create`
|
|
27
|
+
* answers, already on this channel. So a deploy that changed what the
|
|
28
|
+
* workflow takes is either written or refused, never reported `"updated"`
|
|
29
|
+
* over a server still holding the old action. `workflowType` rides with them
|
|
30
|
+
* because it is what selects the schema those args are checked against.
|
|
23
31
|
* - **`state` is deliberately preserved.** A schedule an operator paused stays
|
|
24
32
|
* paused across a deploy, because unpausing it is a decision a person made
|
|
25
33
|
* and a deploy is not the place to reverse it.
|
|
26
|
-
* - **`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* shut. A deploy that changes what a schedule RUNS — its args, its workflow,
|
|
32
|
-
* its policies — should say so explicitly: delete the schedule and create it,
|
|
33
|
-
* or reach `getHandle(id).update(...)` directly and own the shape.
|
|
34
|
+
* - **`memo`, `searchAttributes` and the action's own overrides are
|
|
35
|
+
* preserved**, because rebuilding the action wholesale means reproducing
|
|
36
|
+
* `create`'s own assembly here — the task queue off the contract, the search
|
|
37
|
+
* attribute translation, eight optional overrides — which is a copy that
|
|
38
|
+
* drifts. A deploy changing one of those deletes the schedule and creates it.
|
|
34
39
|
*
|
|
35
|
-
* So: after this call the schedule FIRES when the arguments say
|
|
36
|
-
*
|
|
40
|
+
* So: after this call the schedule FIRES when the arguments say, and RUNS what
|
|
41
|
+
* they say.
|
|
37
42
|
*
|
|
38
43
|
* ```ts
|
|
39
44
|
* await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
|
|
@@ -45,7 +50,13 @@ let _temporal_contract_client = require("@temporal-contract/client");
|
|
|
45
50
|
*/
|
|
46
51
|
const ensureSchedule = (schedules, workflow, options) => schedules.create(workflow, options).map(() => "created").flatMapErrCases((matcher) => matcher.with(unthrown.P.tag(_temporal_contract_client.SCHEDULE_ALREADY_EXISTS_ERROR_TAG), () => schedules.getHandle(options.scheduleId).update((previous) => ({
|
|
47
52
|
...previous,
|
|
48
|
-
spec: options.spec
|
|
53
|
+
spec: options.spec,
|
|
54
|
+
action: {
|
|
55
|
+
...previous.action,
|
|
56
|
+
workflowType: workflow,
|
|
57
|
+
args: [options.args]
|
|
58
|
+
},
|
|
59
|
+
...options.policies === void 0 ? {} : { policies: options.policies }
|
|
49
60
|
})).map(() => "updated")).with(unthrown.P.tag("@temporal-contract/WorkflowNotInContractError"), (error) => (0, unthrown.ErrAsync)(error)).with(unthrown.P.tag("@temporal-contract/WorkflowValidationError"), (error) => (0, unthrown.ErrAsync)(error)));
|
|
50
61
|
//#endregion
|
|
51
62
|
exports.ensureSchedule = ensureSchedule;
|
package/dist/schedule.d.cts
CHANGED
|
@@ -18,24 +18,29 @@ export type ScheduleOutcome = "created" | "updated";
|
|
|
18
18
|
* This recovers exactly that one error into an `update`. Every other error
|
|
19
19
|
* stays on the channel, still typed.
|
|
20
20
|
*
|
|
21
|
-
* **`spec
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* **`spec`, the action's `workflowType` and `args`, and `policies` are
|
|
22
|
+
* reconciled**; everything else about an existing schedule is left as it
|
|
23
|
+
* stands.
|
|
24
24
|
*
|
|
25
|
+
* - **`args` and `workflowType` are written, and they are checked on the way
|
|
26
|
+
* through.** The typed handle's `update` validates the returned action's
|
|
27
|
+
* `args` against the named workflow's input schema before anything is
|
|
28
|
+
* persisted, and answers `WorkflowValidationError` — the same error `create`
|
|
29
|
+
* answers, already on this channel. So a deploy that changed what the
|
|
30
|
+
* workflow takes is either written or refused, never reported `"updated"`
|
|
31
|
+
* over a server still holding the old action. `workflowType` rides with them
|
|
32
|
+
* because it is what selects the schema those args are checked against.
|
|
25
33
|
* - **`state` is deliberately preserved.** A schedule an operator paused stays
|
|
26
34
|
* paused across a deploy, because unpausing it is a decision a person made
|
|
27
35
|
* and a deploy is not the place to reverse it.
|
|
28
|
-
* - **`
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* shut. A deploy that changes what a schedule RUNS — its args, its workflow,
|
|
34
|
-
* its policies — should say so explicitly: delete the schedule and create it,
|
|
35
|
-
* or reach `getHandle(id).update(...)` directly and own the shape.
|
|
36
|
+
* - **`memo`, `searchAttributes` and the action's own overrides are
|
|
37
|
+
* preserved**, because rebuilding the action wholesale means reproducing
|
|
38
|
+
* `create`'s own assembly here — the task queue off the contract, the search
|
|
39
|
+
* attribute translation, eight optional overrides — which is a copy that
|
|
40
|
+
* drifts. A deploy changing one of those deletes the schedule and creates it.
|
|
36
41
|
*
|
|
37
|
-
* So: after this call the schedule FIRES when the arguments say
|
|
38
|
-
*
|
|
42
|
+
* So: after this call the schedule FIRES when the arguments say, and RUNS what
|
|
43
|
+
* they say.
|
|
39
44
|
*
|
|
40
45
|
* ```ts
|
|
41
46
|
* await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
|
package/dist/schedule.d.mts
CHANGED
|
@@ -18,24 +18,29 @@ export type ScheduleOutcome = "created" | "updated";
|
|
|
18
18
|
* This recovers exactly that one error into an `update`. Every other error
|
|
19
19
|
* stays on the channel, still typed.
|
|
20
20
|
*
|
|
21
|
-
* **`spec
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* **`spec`, the action's `workflowType` and `args`, and `policies` are
|
|
22
|
+
* reconciled**; everything else about an existing schedule is left as it
|
|
23
|
+
* stands.
|
|
24
24
|
*
|
|
25
|
+
* - **`args` and `workflowType` are written, and they are checked on the way
|
|
26
|
+
* through.** The typed handle's `update` validates the returned action's
|
|
27
|
+
* `args` against the named workflow's input schema before anything is
|
|
28
|
+
* persisted, and answers `WorkflowValidationError` — the same error `create`
|
|
29
|
+
* answers, already on this channel. So a deploy that changed what the
|
|
30
|
+
* workflow takes is either written or refused, never reported `"updated"`
|
|
31
|
+
* over a server still holding the old action. `workflowType` rides with them
|
|
32
|
+
* because it is what selects the schema those args are checked against.
|
|
25
33
|
* - **`state` is deliberately preserved.** A schedule an operator paused stays
|
|
26
34
|
* paused across a deploy, because unpausing it is a decision a person made
|
|
27
35
|
* and a deploy is not the place to reverse it.
|
|
28
|
-
* - **`
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* shut. A deploy that changes what a schedule RUNS — its args, its workflow,
|
|
34
|
-
* its policies — should say so explicitly: delete the schedule and create it,
|
|
35
|
-
* or reach `getHandle(id).update(...)` directly and own the shape.
|
|
36
|
+
* - **`memo`, `searchAttributes` and the action's own overrides are
|
|
37
|
+
* preserved**, because rebuilding the action wholesale means reproducing
|
|
38
|
+
* `create`'s own assembly here — the task queue off the contract, the search
|
|
39
|
+
* attribute translation, eight optional overrides — which is a copy that
|
|
40
|
+
* drifts. A deploy changing one of those deletes the schedule and creates it.
|
|
36
41
|
*
|
|
37
|
-
* So: after this call the schedule FIRES when the arguments say
|
|
38
|
-
*
|
|
42
|
+
* So: after this call the schedule FIRES when the arguments say, and RUNS what
|
|
43
|
+
* they say.
|
|
39
44
|
*
|
|
40
45
|
* ```ts
|
|
41
46
|
* await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
|
package/dist/schedule.mjs
CHANGED
|
@@ -15,24 +15,29 @@ import { SCHEDULE_ALREADY_EXISTS_ERROR_TAG } from "@temporal-contract/client";
|
|
|
15
15
|
* This recovers exactly that one error into an `update`. Every other error
|
|
16
16
|
* stays on the channel, still typed.
|
|
17
17
|
*
|
|
18
|
-
* **`spec
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* **`spec`, the action's `workflowType` and `args`, and `policies` are
|
|
19
|
+
* reconciled**; everything else about an existing schedule is left as it
|
|
20
|
+
* stands.
|
|
21
21
|
*
|
|
22
|
+
* - **`args` and `workflowType` are written, and they are checked on the way
|
|
23
|
+
* through.** The typed handle's `update` validates the returned action's
|
|
24
|
+
* `args` against the named workflow's input schema before anything is
|
|
25
|
+
* persisted, and answers `WorkflowValidationError` — the same error `create`
|
|
26
|
+
* answers, already on this channel. So a deploy that changed what the
|
|
27
|
+
* workflow takes is either written or refused, never reported `"updated"`
|
|
28
|
+
* over a server still holding the old action. `workflowType` rides with them
|
|
29
|
+
* because it is what selects the schema those args are checked against.
|
|
22
30
|
* - **`state` is deliberately preserved.** A schedule an operator paused stays
|
|
23
31
|
* paused across a deploy, because unpausing it is a decision a person made
|
|
24
32
|
* and a deploy is not the place to reverse it.
|
|
25
|
-
* - **`
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* shut. A deploy that changes what a schedule RUNS — its args, its workflow,
|
|
31
|
-
* its policies — should say so explicitly: delete the schedule and create it,
|
|
32
|
-
* or reach `getHandle(id).update(...)` directly and own the shape.
|
|
33
|
+
* - **`memo`, `searchAttributes` and the action's own overrides are
|
|
34
|
+
* preserved**, because rebuilding the action wholesale means reproducing
|
|
35
|
+
* `create`'s own assembly here — the task queue off the contract, the search
|
|
36
|
+
* attribute translation, eight optional overrides — which is a copy that
|
|
37
|
+
* drifts. A deploy changing one of those deletes the schedule and creates it.
|
|
33
38
|
*
|
|
34
|
-
* So: after this call the schedule FIRES when the arguments say
|
|
35
|
-
*
|
|
39
|
+
* So: after this call the schedule FIRES when the arguments say, and RUNS what
|
|
40
|
+
* they say.
|
|
36
41
|
*
|
|
37
42
|
* ```ts
|
|
38
43
|
* await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
|
|
@@ -44,7 +49,13 @@ import { SCHEDULE_ALREADY_EXISTS_ERROR_TAG } from "@temporal-contract/client";
|
|
|
44
49
|
*/
|
|
45
50
|
const ensureSchedule = (schedules, workflow, options) => schedules.create(workflow, options).map(() => "created").flatMapErrCases((matcher) => matcher.with(P.tag(SCHEDULE_ALREADY_EXISTS_ERROR_TAG), () => schedules.getHandle(options.scheduleId).update((previous) => ({
|
|
46
51
|
...previous,
|
|
47
|
-
spec: options.spec
|
|
52
|
+
spec: options.spec,
|
|
53
|
+
action: {
|
|
54
|
+
...previous.action,
|
|
55
|
+
workflowType: workflow,
|
|
56
|
+
args: [options.args]
|
|
57
|
+
},
|
|
58
|
+
...options.policies === void 0 ? {} : { policies: options.policies }
|
|
48
59
|
})).map(() => "updated")).with(P.tag("@temporal-contract/WorkflowNotInContractError"), (error) => ErrAsync(error)).with(P.tag("@temporal-contract/WorkflowValidationError"), (error) => ErrAsync(error)));
|
|
49
60
|
//#endregion
|
|
50
61
|
export { ensureSchedule };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/temporal-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|
|
@@ -54,33 +54,33 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@btravstack/config": "0.
|
|
58
|
-
"@btravstack/core": "0.
|
|
59
|
-
"@btravstack/di": "0.
|
|
57
|
+
"@btravstack/config": "0.17.0",
|
|
58
|
+
"@btravstack/core": "0.17.0",
|
|
59
|
+
"@btravstack/di": "0.17.0",
|
|
60
60
|
"@btravstack/internal-test-infra": "0.0.0",
|
|
61
|
-
"@btravstack/testing": "0.
|
|
61
|
+
"@btravstack/testing": "0.17.0",
|
|
62
62
|
"@btravstack/tsconfig": "0.4.0",
|
|
63
|
-
"@temporal-contract/client": "8.0.0-beta.
|
|
64
|
-
"@temporal-contract/contract": "8.0.0-beta.
|
|
65
|
-
"@temporal-contract/worker": "8.0.0-beta.
|
|
66
|
-
"@temporalio/activity": "1.
|
|
67
|
-
"@temporalio/client": "1.
|
|
68
|
-
"@temporalio/common": "1.
|
|
69
|
-
"@temporalio/worker": "1.
|
|
70
|
-
"@temporalio/workflow": "1.
|
|
71
|
-
"@types/node": "26.4
|
|
72
|
-
"@unthrown/vitest": "5.
|
|
73
|
-
"@vitest/coverage-v8": "
|
|
63
|
+
"@temporal-contract/client": "8.0.0-beta.11",
|
|
64
|
+
"@temporal-contract/contract": "8.0.0-beta.11",
|
|
65
|
+
"@temporal-contract/worker": "8.0.0-beta.11",
|
|
66
|
+
"@temporalio/activity": "1.24.0",
|
|
67
|
+
"@temporalio/client": "1.24.0",
|
|
68
|
+
"@temporalio/common": "1.24.0",
|
|
69
|
+
"@temporalio/worker": "1.24.0",
|
|
70
|
+
"@temporalio/workflow": "1.24.0",
|
|
71
|
+
"@types/node": "26.6.4",
|
|
72
|
+
"@unthrown/vitest": "5.12.0",
|
|
73
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
74
74
|
"tsdown": "0.23.0",
|
|
75
75
|
"typescript": "7.0.2",
|
|
76
|
-
"unthrown": "5.
|
|
77
|
-
"vitest": "
|
|
78
|
-
"zod": "4.5
|
|
76
|
+
"unthrown": "5.12.0",
|
|
77
|
+
"vitest": "5.0.3",
|
|
78
|
+
"zod": "4.6.5"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@btravstack/config": "^0.
|
|
82
|
-
"@btravstack/core": "^0.
|
|
83
|
-
"@btravstack/di": "^0.
|
|
81
|
+
"@btravstack/config": "^0.17.0",
|
|
82
|
+
"@btravstack/core": "^0.17.0",
|
|
83
|
+
"@btravstack/di": "^0.17.0",
|
|
84
84
|
"@temporal-contract/client": "^8.0.0-beta.9",
|
|
85
85
|
"@temporal-contract/contract": "^8.0.0-beta.9",
|
|
86
86
|
"@temporal-contract/worker": "^8.0.0-beta.9",
|