@btravstack/temporal-worker 0.16.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 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
- return (options) => {
103
- const record = options.unit ?? {};
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 under {@link UNIT_SCOPE},
122
- * where each piece's own wrapper turns it into the typed `context.unit` record
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
- const settle = (0, _btravstack_core.observe)(observers, {
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
- return host.run(metaFor(), (scope) => unit === void 0 ? next() : scope.fork(unit, [[ActivityInputPort, invocation.input]]).flatMap((forked) => next({ context: { [UNIT_SCOPE]: forked } }))).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
143
- outcome: "error",
144
- cause: failure.tag === "Err" ? failure.error : failure.cause
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
@@ -252,10 +213,7 @@ const temporal = (options) => {
252
213
  })),
253
214
  release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
254
215
  }),
255
- _btravstack_di.Provider.member(_btravstack_core.Observers)({
256
- inject: {},
257
- value: _btravstack_core.noObserver
258
- }),
216
+ _btravstack_core.noObserverMember,
259
217
  (0, _btravstack_di.Provider)(TemporalRuntime)({
260
218
  inject: {
261
219
  connection: TemporalConnection,
@@ -383,19 +341,7 @@ const TemporalModule = (name) => (options) => {
383
341
  * and two slices claiming one key are di's duplicate-provider defect at build.
384
342
  */
385
343
  const TemporalActivities = (contract) => {
386
- const build = (0, _btravstack_di.Provider)(TemporalActivitiesPort);
387
- const compose = (pieces) => build({
388
- inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(27), piece.port])),
389
- sync: (services) => services
390
- });
391
- const whole = (options) => {
392
- const record = options.unit ?? {};
393
- return build({
394
- inject: options.inject,
395
- sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
396
- });
397
- };
398
- return ((first) => Array.isArray(first) ? compose(first) : whole(first));
344
+ return (0, _btravstack_core.composeByPrefix)(TemporalActivitiesPort, WORKFLOW_ACTIVITIES_PREFIX, withUnit);
399
345
  };
400
346
  //#endregion
401
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
- * A module `unit.activity` may bind, as the upper bound `temporal()`
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
- * A module `unit.activity` may bind, as the upper bound `temporal()`
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
- return (options) => {
102
- const record = options.unit ?? {};
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 under {@link UNIT_SCOPE},
121
- * where each piece's own wrapper turns it into the typed `context.unit` record
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
- const settle = observe(observers, {
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
- return host.run(metaFor(), (scope) => unit === void 0 ? next() : scope.fork(unit, [[ActivityInputPort, invocation.input]]).flatMap((forked) => next({ context: { [UNIT_SCOPE]: forked } }))).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
142
- outcome: "error",
143
- cause: failure.tag === "Err" ? failure.error : failure.cause
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
@@ -251,10 +212,7 @@ const temporal = (options) => {
251
212
  })),
252
213
  release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
253
214
  }),
254
- Provider.member(Observers)({
255
- inject: {},
256
- value: noObserver
257
- }),
215
+ noObserverMember,
258
216
  Provider(TemporalRuntime)({
259
217
  inject: {
260
218
  connection: TemporalConnection,
@@ -382,19 +340,7 @@ const TemporalModule = (name) => (options) => {
382
340
  * and two slices claiming one key are di's duplicate-provider defect at build.
383
341
  */
384
342
  const TemporalActivities = (contract) => {
385
- const build = Provider(TemporalActivitiesPort);
386
- const compose = (pieces) => build({
387
- inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(27), piece.port])),
388
- sync: (services) => services
389
- });
390
- const whole = (options) => {
391
- const record = options.unit ?? {};
392
- return build({
393
- inject: options.inject,
394
- sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
395
- });
396
- };
397
- return ((first) => Array.isArray(first) ? compose(first) : whole(first));
343
+ return composeByPrefix(TemporalActivitiesPort, WORKFLOW_ACTIVITIES_PREFIX, withUnit);
398
344
  };
399
345
  //#endregion
400
346
  export { ActivityInput, TemporalActivities, TemporalConfig, TemporalConnection, TemporalModule, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities, temporal };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/temporal-worker",
3
- "version": "0.16.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.16.0",
58
- "@btravstack/core": "0.16.0",
59
- "@btravstack/di": "0.16.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.16.0",
61
+ "@btravstack/testing": "0.17.0",
62
62
  "@btravstack/tsconfig": "0.4.0",
63
- "@temporal-contract/client": "8.0.0-beta.9",
64
- "@temporal-contract/contract": "8.0.0-beta.9",
65
- "@temporal-contract/worker": "8.0.0-beta.9",
66
- "@temporalio/activity": "1.23.0",
67
- "@temporalio/client": "1.23.0",
68
- "@temporalio/common": "1.23.0",
69
- "@temporalio/worker": "1.23.0",
70
- "@temporalio/workflow": "1.23.0",
71
- "@types/node": "26.4.1",
72
- "@unthrown/vitest": "5.8.0",
73
- "@vitest/coverage-v8": "4.1.11",
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.8.0",
77
- "vitest": "4.1.11",
78
- "zod": "4.5.4"
76
+ "unthrown": "5.12.0",
77
+ "vitest": "5.0.3",
78
+ "zod": "4.6.5"
79
79
  },
80
80
  "peerDependencies": {
81
- "@btravstack/config": "^0.16.0",
82
- "@btravstack/core": "^0.16.0",
83
- "@btravstack/di": "^0.16.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",