@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 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
@@ -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
- _btravstack_di.Provider.member(_btravstack_core.Observers)({
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
- const build = (0, _btravstack_di.Provider)(TemporalActivitiesPort);
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
- * 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
@@ -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
- Provider.member(Observers)({
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
- const build = Provider(TemporalActivitiesPort);
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` is the only field reconciled**, and the rest of an existing
20
- * schedule is left as it stands. Two different reasons, and they are worth
21
- * telling apart:
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
- * - **`args` and the rest of the action are preserved because this cannot
27
- * reconcile them SAFELY.** `create` validates `args` against the workflow's
28
- * input schema; the handle's `update` takes Temporal's own
29
- * `ScheduleUpdateOptions` and validates nothing, so writing them here would
30
- * push unvalidated input at the server through a door the typed client keeps
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. What it fires
36
- * with is whatever it already fired with.
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;
@@ -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` is the only field reconciled**, and the rest of an existing
22
- * schedule is left as it stands. Two different reasons, and they are worth
23
- * telling apart:
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
- * - **`args` and the rest of the action are preserved because this cannot
29
- * reconcile them SAFELY.** `create` validates `args` against the workflow's
30
- * input schema; the handle's `update` takes Temporal's own
31
- * `ScheduleUpdateOptions` and validates nothing, so writing them here would
32
- * push unvalidated input at the server through a door the typed client keeps
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. What it fires
38
- * with is whatever it already fired with.
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", {
@@ -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` is the only field reconciled**, and the rest of an existing
22
- * schedule is left as it stands. Two different reasons, and they are worth
23
- * telling apart:
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
- * - **`args` and the rest of the action are preserved because this cannot
29
- * reconcile them SAFELY.** `create` validates `args` against the workflow's
30
- * input schema; the handle's `update` takes Temporal's own
31
- * `ScheduleUpdateOptions` and validates nothing, so writing them here would
32
- * push unvalidated input at the server through a door the typed client keeps
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. What it fires
38
- * with is whatever it already fired with.
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` is the only field reconciled**, and the rest of an existing
19
- * schedule is left as it stands. Two different reasons, and they are worth
20
- * telling apart:
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
- * - **`args` and the rest of the action are preserved because this cannot
26
- * reconcile them SAFELY.** `create` validates `args` against the workflow's
27
- * input schema; the handle's `update` takes Temporal's own
28
- * `ScheduleUpdateOptions` and validates nothing, so writing them here would
29
- * push unvalidated input at the server through a door the typed client keeps
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. What it fires
35
- * with is whatever it already fired with.
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.15.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.15.0",
58
- "@btravstack/core": "0.15.0",
59
- "@btravstack/di": "0.15.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.15.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.15.0",
82
- "@btravstack/core": "^0.15.0",
83
- "@btravstack/di": "^0.15.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",