@btravstack/temporal-worker 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benoit TRAVERS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @btravstack/temporal-worker
2
+
3
+ > The **worker half** of Temporal for [`@btravstack/core`](../core) โ€” starting
4
+ > workflows is a client's job, and belongs to `@temporal-contract/client`. One module
5
+ > providing the runtime, its configuration and its connection; one unit per
6
+ > activity attempt; and a drain that releases the kernel at the **kernel's**
7
+ > deadline, not Temporal's `shutdownForceTime`.
8
+
9
+ ๐Ÿ“– **[Documentation](https://btravstack.github.io/btravstack/how-to/run-a-temporal-worker)** ยท
10
+ [Reference](https://btravstack.github.io/btravstack/reference/temporal-worker) ยท
11
+ [API Reference](https://btravstack.github.io/btravstack/api/temporal-worker/)
12
+
13
+ ```sh
14
+ pnpm add @btravstack/temporal-worker @btravstack/core @btravstack/config @btravstack/di unthrown \
15
+ @temporalio/worker @temporalio/activity @temporalio/common \
16
+ @temporal-contract/worker @temporal-contract/contract
17
+ ```
18
+
19
+ All nine are peer dependencies โ€” install them. Node `>=20`.
20
+
21
+ ## A worked example
22
+
23
+ <!-- doctest: prelude
24
+ import { defineActivity, defineContract, defineWorkflow } from "@temporal-contract/contract";
25
+ import { workflowsPathFromURL } from "@temporal-contract/worker/worker";
26
+ import { Module } from "@btravstack/di";
27
+ import { PlaceOrder, OrderRepository, Outbox, OrderApplicationModule } from "@btravstack/example-order-application";
28
+ import { OrderPersistenceModule } from "@btravstack/example-order-infrastructure";
29
+ import { TenantId } from "@btravstack/example-order-domain";
30
+ import { observability } from "@btravstack/observability";
31
+ import { z } from "zod";
32
+
33
+ // The contract this README's worker serves โ€” one workflow, one activity, one
34
+ // modeled contract error.
35
+ const contract = defineContract({
36
+ taskQueue: "orders",
37
+ workflows: {
38
+ placeOrder: defineWorkflow({
39
+ input: z.object({ tenantId: z.uuidv7(), orderId: z.uuidv7(), quantity: z.number() }),
40
+ output: z.object({ id: z.uuidv7() }),
41
+ idempotency: "allow-duplicate",
42
+ activities: {
43
+ place: defineActivity({
44
+ input: z.object({ tenantId: z.uuidv7(), orderId: z.uuidv7(), quantity: z.number() }),
45
+ output: z.object({ id: z.uuidv7() }),
46
+ errors: {
47
+ OrderAlreadyPlaced: { data: z.object({ id: z.uuidv7() }), nonRetryable: true },
48
+ InvalidOrderId: { data: z.object({ id: z.string() }), nonRetryable: true },
49
+ InvalidQuantity: { data: z.object({ id: z.uuidv7() }), nonRetryable: true },
50
+ },
51
+ activityOptions: { startToCloseTimeout: "30 seconds" },
52
+ }),
53
+ },
54
+ }),
55
+ },
56
+ });
57
+
58
+ const AppModule = Module("App")({
59
+ imports: [OrderApplicationModule, OrderPersistenceModule, observability()],
60
+ exports: [PlaceOrder, OrderRepository, Outbox, Logger],
61
+ });
62
+ -->
63
+
64
+ ```ts
65
+ import { runMain, Logger } from "@btravstack/core";
66
+ import {
67
+ TemporalActivities,
68
+ TemporalModule,
69
+ } from "@btravstack/temporal-worker";
70
+ import { P } from "unthrown";
71
+
72
+ // The application's half: its activities, as a service built from the use
73
+ // cases it declares โ€” closures over them, no context read at call time โ€” on
74
+ // the starter's own activities port, typed by the contract (a worker serves
75
+ // one activities record, so there is nothing to name).
76
+ const orderActivities = TemporalActivities(contract)(
77
+ { place: PlaceOrder },
78
+ {
79
+ sync: ({ place }) => ({
80
+ placeOrder: {
81
+ place: (args, { errors }) =>
82
+ place
83
+ .execute(TenantId(args.tenantId), args.orderId, args.quantity)
84
+ .mapErrCases((matcher) =>
85
+ matcher
86
+ .with(P.tag("DuplicateOrder"), (error) =>
87
+ errors.OrderAlreadyPlaced({ id: error.id }),
88
+ )
89
+ .with(P.tag("InvalidOrderId"), (error) =>
90
+ errors.InvalidOrderId({ id: error.id }),
91
+ )
92
+ .with(P.tag("InvalidQuantity"), (error) =>
93
+ errors.InvalidQuantity({ id: error.id }),
94
+ ),
95
+ ),
96
+ },
97
+ }),
98
+ },
99
+ );
100
+
101
+ // The composition root: a di module, plus the contract, the activities
102
+ // provider and the workflow source โ€” and nothing else to know.
103
+ const OrderWorker = TemporalModule("OrderWorker")({
104
+ contract,
105
+ activities: orderActivities,
106
+ workflows: {
107
+ workflowsPath: workflowsPathFromURL(import.meta.url, "./workflows.js"),
108
+ },
109
+ imports: [AppModule],
110
+ });
111
+
112
+ await runMain(OrderWorker);
113
+ ```
114
+
115
+ `TemporalModule` imports the starter โ€” `TemporalRuntime`, `TemporalConfig`
116
+ bound from `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`) and
117
+ `TEMPORAL_NAMESPACE` (default `default`), `TemporalConnection` opened as a
118
+ resource of the graph and closed on every exit path โ€” provides the activities
119
+ and exports the runtime port. A service that will not answer is a modeled
120
+ `TemporalUnreachable`, exit `1`; the starter calls `declareActivitiesHandler`
121
+ itself, inside its own error qualifier, so a contract it cannot satisfy is a
122
+ startup `Err`, not a defect. `runtimeInfo()` reads `{ taskQueue, namespace }`
123
+ back once the worker is polling.
124
+
125
+ A worker polling for several workflows can be several slices instead of one
126
+ record: `TemporalWorkflowActivities(contract, key)({ name: Dep }, arm)` mints a
127
+ provider for ONE workflow's activities (or a contract-global activity),
128
+ typed by the key alone, and `TemporalActivities(contract)([...])` composes an
129
+ array of them into the same activities provider `TemporalModule` takes โ€” the
130
+ array must cover every key the contract declares, and each piece's own port
131
+ must still be discharged (`provides`), since the composed provider's deps are
132
+ the pieces' ports, not what they close over. Two slices claiming one key are
133
+ di's duplicate-provider defect at build, which is the point: a workflow's
134
+ activities belong to exactly one slice.
135
+
136
+ ## Options
137
+
138
+ `TemporalModule(name)({...})` takes `temporal()`'s options plus `activities`
139
+ and the module lists (`imports`, `provides`, `exports`, `needs`). The
140
+ `temporal()` primitive does not take `activities` as an option โ€” it **needs**
141
+ the activities provider on its own port, which is how the composition root
142
+ supplies it:
143
+
144
+ | Option | What it is |
145
+ | ------------- | ------------------------------------------------------------------------------------------------------ |
146
+ | `contract` | the `temporal-contract` contract; the task queue this worker polls is read off it |
147
+ | `activities` | the activities provider โ€” what `TemporalActivities(contract)(...)` returns for this contract |
148
+ | `workflows` | where the sandbox's code comes from: `{ workflowsPath }` or `{ workflowBundle }` |
149
+ | `address` | pins the service instead of reading `TEMPORAL_ADDRESS` |
150
+ | `namespace` | pins the namespace instead of reading `TEMPORAL_NAMESPACE` |
151
+ | `gracePeriod` | Temporal's `shutdownGraceTime`, a `Duration` (default `"10 seconds"`) |
152
+ | `forceAfter` | Temporal's `shutdownForceTime`, a `Duration` (default `"15 seconds"`) โ€” keep it under `drainTimeoutMs` |
153
+
154
+ The full table โ€” required/optional, defaults, and the reasoning โ€” lives on
155
+ [the reference page](https://btravstack.github.io/btravstack/reference/temporal-worker),
156
+ which is this list's one detailed home.
157
+
158
+ ## The drain
159
+
160
+ `worker.shutdown()` stops polling at once, but `run()` resolves only when the
161
+ last in-flight activity has finished โ€” on Temporal's own `shutdownForceTime`.
162
+ `Serving.drain(signal)` races that wait against the kernel's deadline and
163
+ returns at whichever comes first: the kernel gets its thread back on time, the
164
+ work is reported `abandoned`, and the worker keeps winding down on Temporal's
165
+ clock until the process exits. `@temporalio/worker` exposes no public forced
166
+ shutdown, so "stop waiting" is the escalation. `Result` โ†’ activity failure is
167
+ `declareActivitiesHandler`'s and is deliberately not duplicated here. The rest
168
+ is on the [documentation site](https://btravstack.github.io/btravstack/reference/temporal-worker).
169
+
170
+ ## License
171
+
172
+ [MIT](./LICENSE) ยฉ Benoit TRAVERS
package/dist/index.cjs ADDED
@@ -0,0 +1,261 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _btravstack_di = require("@btravstack/di");
3
+ let _btravstack_config = require("@btravstack/config");
4
+ let _btravstack_core = require("@btravstack/core");
5
+ let _temporal_contract_worker_activity = require("@temporal-contract/worker/activity");
6
+ let _temporalio_worker = require("@temporalio/worker");
7
+ let unthrown = require("unthrown");
8
+ let _temporalio_activity = require("@temporalio/activity");
9
+ //#region src/activity-units.ts
10
+ /**
11
+ * Open one kernel unit per activity attempt. It injects nothing โ€” `next()`
12
+ * unchanged โ€” so the ambient `currentUnit()` record is what an adapter reads.
13
+ *
14
+ * **That includes the kernel's per-unit `AbortSignal`**, and it is the only
15
+ * route to it: an activity has no parameter to receive one through. Temporal's
16
+ * own `Context.current().cancellationSignal` is a DIFFERENT clock, so the two
17
+ * are honoured together rather than one standing in for the other.
18
+ *
19
+ * No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
20
+ * the mapping from a settled `Result` to an activity failure.
21
+ */
22
+ const activityUnits = (host) => (_invocation, next) => host.run(metaFor(), () => next());
23
+ /**
24
+ * `UnitMeta.id` must be unique per unit, and a workflow id is **not** one: an
25
+ * activity is retried under the same execution. A task token identifies one
26
+ * attempt, so its uniqueness is Temporal's guarantee rather than ours.
27
+ *
28
+ * The workflow id becomes the `traceId` โ€” minted outside this process and steady
29
+ * across every retry, so all attempts join up in a log. An activity with no
30
+ * workflow falls back to the activity id.
31
+ */
32
+ const metaFor = () => {
33
+ const info = (0, _temporalio_activity.activityInfo)();
34
+ return {
35
+ kind: "activity",
36
+ id: info.base64TaskToken,
37
+ traceId: info.workflowExecution?.workflowId ?? info.activityId
38
+ };
39
+ };
40
+ //#endregion
41
+ //#region src/temporal-runtime.ts
42
+ /**
43
+ * Where the Temporal service is, as a service: `temporal()` binds it from the
44
+ * environment โ€” `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`) and
45
+ * `TEMPORAL_NAMESPACE` (default `default`) โ€” unless pinned, and anything else
46
+ * in the graph may read it.
47
+ */
48
+ var TemporalConfig = class extends (0, _btravstack_di.Port)("TemporalConfig") {};
49
+ /**
50
+ * The connection, as a resource of the graph: di opens it with the scope and
51
+ * closes it on every exit path, startup failure included.
52
+ */
53
+ var TemporalConnection = class extends (0, _btravstack_di.Port)("TemporalConnection") {};
54
+ /**
55
+ * The service at `TemporalConfig.address` did not answer. Modeled rather than
56
+ * left a defect because an operator *can* act on it โ€” the address is wrong or
57
+ * the service is down, and neither is a bug in this code โ€” so `runMain` exits
58
+ * `1`, a startup `Err`, not the `70` a defect earns.
59
+ */
60
+ var TemporalUnreachable = class extends (0, unthrown.TaggedError)("TemporalUnreachable") {
61
+ message = `the Temporal service at ${this.address} did not answer`;
62
+ };
63
+ /** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
64
+ var TemporalRuntime = class extends _btravstack_core.RuntimePort {};
65
+ /**
66
+ * The activities' port โ€” one id, the starter's own, which an application never
67
+ * names. Generic at the value level (one `Port(...)` call, so no duplicate-id
68
+ * warning however many contracts instantiate it) and fixed per contract at the
69
+ * type level through `ActivitiesPortOf<C>`, so a provider built for one contract
70
+ * cannot be handed to a module declaring another. Exported for this package's
71
+ * tests, not from `index.ts`.
72
+ */
73
+ const TemporalActivitiesPort = (0, _btravstack_di.Port)("TemporalActivities");
74
+ const DEFAULT_GRACE = "10 seconds";
75
+ const DEFAULT_FORCE = "15 seconds";
76
+ /**
77
+ * The Temporal starter: a module providing the runtime, its configuration
78
+ * (bound from `TEMPORAL_ADDRESS` / `TEMPORAL_NAMESPACE` unless pinned) and the
79
+ * connection (a resource opened with the scope and closed with it; a service
80
+ * that will not answer is a modeled `TemporalUnreachable`). Import it next to
81
+ * the application, export `TemporalRuntime`, provide the activities โ€” the
82
+ * activities port is a need of this module.
83
+ *
84
+ * With both configuration fields pinned the module reads nothing from the
85
+ * environment; pin only one and the other still comes from it.
86
+ */
87
+ const temporal = (options) => {
88
+ const { address, namespace } = options;
89
+ const activities = TemporalActivitiesPort;
90
+ const config = address !== void 0 && namespace !== void 0 ? (0, _btravstack_di.Provider)(TemporalConfig)({ value: {
91
+ address,
92
+ namespace
93
+ } }) : _btravstack_config.Config.provider(TemporalConfig)(_btravstack_config.Config.object({
94
+ address: _btravstack_config.Config.pinned(address, _btravstack_config.Config.string("TEMPORAL_ADDRESS", { default: "127.0.0.1:7233" })),
95
+ namespace: _btravstack_config.Config.pinned(namespace, _btravstack_config.Config.string("TEMPORAL_NAMESPACE", { default: "default" }))
96
+ }));
97
+ return (0, _btravstack_di.Module)("Temporal")({
98
+ needs: [_btravstack_config.Env, activities],
99
+ provides: [
100
+ config,
101
+ (0, _btravstack_di.Provider)(TemporalConnection)({ config: TemporalConfig }, {
102
+ acquire: ({ config: bound }) => (0, unthrown.fromPromise)(_temporalio_worker.NativeConnection.connect({ address: bound.address }), (cause) => new TemporalUnreachable({
103
+ address: bound.address,
104
+ cause
105
+ })),
106
+ release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
107
+ }),
108
+ (0, _btravstack_di.Provider)(TemporalRuntime)({
109
+ connection: TemporalConnection,
110
+ config: TemporalConfig,
111
+ activities
112
+ }, { sync: ({ connection, config: bound, activities: impls }) => ({
113
+ name: "temporal",
114
+ resolves: [],
115
+ start: (host) => createWorker(host, connection, bound, impls, options)
116
+ }) })
117
+ ],
118
+ exports: [
119
+ TemporalRuntime,
120
+ TemporalConfig,
121
+ TemporalConnection
122
+ ]
123
+ });
124
+ };
125
+ const startFailed = (cause) => new _btravstack_core.RuntimeStartFailed({
126
+ runtime: "temporal",
127
+ cause
128
+ });
129
+ const heldByWorker = (cause) => cause instanceof Error && cause.name === "IllegalStateError";
130
+ const createWorker = (host, connection, config, activities, options) => {
131
+ const { taskQueue } = options.contract;
132
+ const { namespace } = config;
133
+ return (0, unthrown.fromThrowable)(() => (0, _temporal_contract_worker_activity.declareActivitiesHandler)({
134
+ contract: options.contract,
135
+ middleware: activityUnits(host),
136
+ activities
137
+ }), startFailed)().toAsync().flatMap((handler) => (0, unthrown.fromPromise)(_temporalio_worker.Worker.create({
138
+ connection,
139
+ namespace,
140
+ taskQueue,
141
+ ...options.workflows,
142
+ activities: handler,
143
+ shutdownGraceTime: options.gracePeriod ?? DEFAULT_GRACE,
144
+ shutdownForceTime: options.forceAfter ?? DEFAULT_FORCE
145
+ }), startFailed)).map((worker) => poll(worker, taskQueue, namespace));
146
+ };
147
+ const poll = (worker, taskQueue, namespace) => {
148
+ const running = (0, unthrown.fromSafePromise)(worker.run());
149
+ const stopPolling = () => {
150
+ if (worker.getState() === "RUNNING") worker.shutdown();
151
+ };
152
+ let deadline;
153
+ const stopped = () => deadline === void 0 ? running : (0, _btravstack_core.releasedBy)(deadline, running);
154
+ return {
155
+ info: {
156
+ taskQueue,
157
+ namespace
158
+ },
159
+ drain: (signal) => {
160
+ deadline = signal;
161
+ stopPolling();
162
+ return stopped();
163
+ },
164
+ stop: () => {
165
+ stopPolling();
166
+ return stopped();
167
+ }
168
+ };
169
+ };
170
+ //#endregion
171
+ //#region src/workflow-activities.ts
172
+ /** The prefix a piece's port id carries; the composing form strips it to recover the key. */
173
+ const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
174
+ /**
175
+ * One workflow's activities, as a provider on a port of its own.
176
+ *
177
+ * A worker that polls one queue for several workflows is several pieces, each
178
+ * declaring the services its own activities call;
179
+ * `TemporalActivities(contract)([...])` composes them. `contract` is read for
180
+ * its TYPE only, and types both `key` and the record โ€” so an activity the
181
+ * workflow does not declare is a compile error here rather than at startup.
182
+ *
183
+ * `key` is any top-level key of the activities record, which includes a
184
+ * contract-global activity as well as a workflow; the name is imprecise in that
185
+ * one case deliberately, since narrowing it would lock such a contract out of
186
+ * the split.
187
+ *
188
+ * There is no name to give: the key IS the port's name.
189
+ */
190
+ const TemporalWorkflowActivities = (contract, key) => {
191
+ const port = class extends (0, _btravstack_di.Port)(`${WORKFLOW_ACTIVITIES_PREFIX}${key}`) {};
192
+ return (0, _btravstack_di.Provider)(port);
193
+ };
194
+ //#endregion
195
+ //#region src/temporal-module.ts
196
+ /**
197
+ * `Module(name)({...})` for a Temporal worker deployment: everything a di module
198
+ * takes, plus the contract, the activities provider and the workflow source. The
199
+ * sugar imports the starter, provides the activities and exports
200
+ * `TemporalRuntime`, handing back exactly the module `Module(...)` would have
201
+ * declared over the augmented tuples.
202
+ *
203
+ * ```ts
204
+ * export const OrderTemporalWorker = TemporalModule("OrderTemporalWorker")({
205
+ * contract: orderContract,
206
+ * activities: orderActivities,
207
+ * workflows: { workflowsPath: workflowsPathFromURL(import.meta.url, "./workflows.js") },
208
+ * imports: [OrderApplicationModule, OrderPersistenceModule, FulfillmentModule],
209
+ * });
210
+ * await runMain(OrderTemporalWorker);
211
+ * ```
212
+ */
213
+ const TemporalModule = (name) => (options) => {
214
+ const { contract, activities, workflows, address, namespace, gracePeriod, forceAfter } = options;
215
+ const imports = options.imports ?? [];
216
+ const provides = options.provides ?? [];
217
+ const exports = options.exports ?? [];
218
+ const starter = temporal({
219
+ contract,
220
+ workflows,
221
+ ...address === void 0 ? {} : { address },
222
+ ...namespace === void 0 ? {} : { namespace },
223
+ ...gracePeriod === void 0 ? {} : { gracePeriod },
224
+ ...forceAfter === void 0 ? {} : { forceAfter }
225
+ });
226
+ return (0, _btravstack_di.Module)(name)({
227
+ imports: [...imports, starter],
228
+ provides: [activities, ...provides],
229
+ exports: [TemporalRuntime, ...exports],
230
+ needs: options.needs ?? []
231
+ });
232
+ };
233
+ /**
234
+ * The activities as a provider, from the contract. Three call forms, one port.
235
+ *
236
+ * ```ts
237
+ * TemporalActivities(orderContract)({ place: PlaceOrder }, { sync: ({ place }) => ({ fulfillOrder: { โ€ฆ } }) })
238
+ * TemporalActivities(orderContract)([fulfillOrder, chargeOrder])
239
+ * ```
240
+ *
241
+ * The first two are di's own `Provider(port)` on the starter's activities port
242
+ * typed for the contract. The third takes the pieces
243
+ * `TemporalWorkflowActivities(contract, key)` builds: they are the provider's
244
+ * deps, keyed by the contract key each piece's port id carries, so the services
245
+ * record IS the activities record. Every key must be covered, and two slices
246
+ * claiming one key are di's duplicate-provider defect at build.
247
+ */
248
+ const TemporalActivities = (contract) => {
249
+ const build = (0, _btravstack_di.Provider)(TemporalActivitiesPort);
250
+ const compose = (pieces) => build(Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(27), piece.port])), { sync: (services) => services });
251
+ return ((first, second) => second === void 0 && Array.isArray(first) ? compose(first) : build(first, second));
252
+ };
253
+ //#endregion
254
+ exports.TemporalActivities = TemporalActivities;
255
+ exports.TemporalConfig = TemporalConfig;
256
+ exports.TemporalConnection = TemporalConnection;
257
+ exports.TemporalModule = TemporalModule;
258
+ exports.TemporalRuntime = TemporalRuntime;
259
+ exports.TemporalUnreachable = TemporalUnreachable;
260
+ exports.TemporalWorkflowActivities = TemporalWorkflowActivities;
261
+ exports.temporal = temporal;
@@ -0,0 +1,219 @@
1
+ import { ConfigInvalid, Env } from "@btravstack/config";
2
+ import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope } from "@btravstack/di";
3
+ import { ContractDefinition } from "@temporal-contract/contract";
4
+ import { Duration } from "@temporalio/common";
5
+ import { Runtime, RuntimePort } from "@btravstack/core";
6
+ import { DeclareActivitiesHandlerOptions } from "@temporal-contract/worker/activity";
7
+ import { NativeConnection, WorkflowBundleWithSourceMap } from "@temporalio/worker";
8
+ //#region src/temporal-runtime.d.ts
9
+ /** What the worker publishes once it is polling, read back through `RunningApp.runtimeInfo()`. */
10
+ type TemporalInfo = {
11
+ readonly taskQueue: string;
12
+ readonly namespace: string;
13
+ };
14
+ /**
15
+ * Where the workflow sandbox's code comes from. Two arms because the two
16
+ * callers genuinely differ: a process points at the module and lets Temporal
17
+ * bundle it, while a spec hands over a bundle it built and memoised once โ€”
18
+ * bundling per test is the most expensive thing a suite does.
19
+ */
20
+ type WorkflowSource = {
21
+ readonly workflowsPath: string;
22
+ } | {
23
+ readonly workflowBundle: WorkflowBundleWithSourceMap;
24
+ };
25
+ declare const TemporalConfig_base: import("@btravstack/di").PortClass<"TemporalConfig">;
26
+ /**
27
+ * Where the Temporal service is, as a service: `temporal()` binds it from the
28
+ * environment โ€” `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`) and
29
+ * `TEMPORAL_NAMESPACE` (default `default`) โ€” unless pinned, and anything else
30
+ * in the graph may read it.
31
+ */
32
+ declare class TemporalConfig extends TemporalConfig_base<{
33
+ readonly address: string;
34
+ readonly namespace: string;
35
+ }> {}
36
+ declare const TemporalConnection_base: import("@btravstack/di").PortClass<"TemporalConnection">;
37
+ /**
38
+ * The connection, as a resource of the graph: di opens it with the scope and
39
+ * closes it on every exit path, startup failure included.
40
+ */
41
+ declare class TemporalConnection extends TemporalConnection_base<NativeConnection> {}
42
+ declare const TemporalUnreachable_base: import("unthrown").TaggedErrorConstructor<"TemporalUnreachable">;
43
+ /**
44
+ * The service at `TemporalConfig.address` did not answer. Modeled rather than
45
+ * left a defect because an operator *can* act on it โ€” the address is wrong or
46
+ * the service is down, and neither is a bug in this code โ€” so `runMain` exits
47
+ * `1`, a startup `Err`, not the `70` a defect earns.
48
+ */
49
+ declare class TemporalUnreachable extends TemporalUnreachable_base<{
50
+ readonly address: string;
51
+ readonly cause: unknown;
52
+ }> {
53
+ message: string;
54
+ }
55
+ /** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
56
+ declare class TemporalRuntime extends RuntimePort<Runtime<never, TemporalInfo>> {}
57
+ /** The activity implementations `declareActivitiesHandler` takes for `C`, with no injected context. */
58
+ type ActivitiesOf<C extends ContractDefinition> = DeclareActivitiesHandlerOptions<C>["activities"];
59
+ /** The activities port class, typed for `C`: what `TemporalActivities(contract)(โ€ฆ).port` is. */
60
+ type ActivitiesPortOf<C extends ContractDefinition> = PortClassOf<"TemporalActivities", ActivitiesOf<C>>;
61
+ /** The activities port's instance for `C` โ€” the module's one need. */
62
+ type ActivitiesInstanceOf<C extends ContractDefinition> = PortInstance<"TemporalActivities", ActivitiesOf<C>>;
63
+ type TemporalOptions<C extends ContractDefinition> = {
64
+ /**
65
+ * The contract; the task queue this worker polls is read off it, and the
66
+ * activities port is typed by it. The starter calls
67
+ * `declareActivitiesHandler` itself, with its unit middleware in place.
68
+ */
69
+ readonly contract: C;
70
+ readonly workflows: WorkflowSource;
71
+ /** Pins `TemporalConfig.address` instead of reading `TEMPORAL_ADDRESS`. */
72
+ readonly address?: string;
73
+ /** Pins `TemporalConfig.namespace` instead of reading `TEMPORAL_NAMESPACE`. */
74
+ readonly namespace?: string;
75
+ /** Temporal's `shutdownForceTime`. Default `15 seconds`. Keep it at or below the kernel's `drainTimeoutMs`. */
76
+ readonly forceAfter?: Duration;
77
+ /** Temporal's `shutdownGraceTime`. Default `10 seconds`. */
78
+ readonly gracePeriod?: Duration;
79
+ };
80
+ type Provided = TemporalRuntime | TemporalConfig | TemporalConnection;
81
+ /**
82
+ * The Temporal starter: a module providing the runtime, its configuration
83
+ * (bound from `TEMPORAL_ADDRESS` / `TEMPORAL_NAMESPACE` unless pinned) and the
84
+ * connection (a resource opened with the scope and closed with it; a service
85
+ * that will not answer is a modeled `TemporalUnreachable`). Import it next to
86
+ * the application, export `TemporalRuntime`, provide the activities โ€” the
87
+ * activities port is a need of this module.
88
+ *
89
+ * With both configuration fields pinned the module reads nothing from the
90
+ * environment; pin only one and the other still comes from it.
91
+ */
92
+ declare const temporal: <C extends ContractDefinition>(options: TemporalOptions<C>) => Module<Provided, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C>>;
93
+ //#endregion
94
+ //#region src/workflow-activities.d.ts
95
+ /**
96
+ * `ActivitiesOf<C>` is a `NoInfer`-wrapped conditional TypeScript refuses to
97
+ * index by a generic key. Routing it through `infer` resolves it to a plain
98
+ * object type first, which IS indexable.
99
+ */
100
+ type ActivitiesRecordOf<C extends ContractDefinition> = ActivitiesOf<C> extends (infer Resolved) ? Resolved : never;
101
+ /** The top-level keys of `C`'s activities record: a workflow that declares activities, or a contract-global activity. */
102
+ type ActivitiesKeyOf<C extends ContractDefinition> = keyof ActivitiesRecordOf<C> & string;
103
+ /** The prefix a piece's port id carries; the composing form strips it to recover the key. */
104
+ declare const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
105
+ /**
106
+ * The port one piece targets. Its id carries the key, which is what makes two
107
+ * slices claiming one workflow di's duplicate-provider defect rather than a
108
+ * silent merge.
109
+ */
110
+ type WorkflowActivitiesPortOf<C extends ContractDefinition, K extends ActivitiesKeyOf<C>> = PortClassOf<`${typeof WORKFLOW_ACTIVITIES_PREFIX}${K}`, ActivitiesRecordOf<C>[K]>;
111
+ /**
112
+ * One workflow's activities, as a provider on a port of its own.
113
+ *
114
+ * A worker that polls one queue for several workflows is several pieces, each
115
+ * declaring the services its own activities call;
116
+ * `TemporalActivities(contract)([...])` composes them. `contract` is read for
117
+ * its TYPE only, and types both `key` and the record โ€” so an activity the
118
+ * workflow does not declare is a compile error here rather than at startup.
119
+ *
120
+ * `key` is any top-level key of the activities record, which includes a
121
+ * contract-global activity as well as a workflow; the name is imprecise in that
122
+ * one case deliberately, since narrowing it would lock such a contract out of
123
+ * the split.
124
+ *
125
+ * There is no name to give: the key IS the port's name.
126
+ */
127
+ declare const TemporalWorkflowActivities: <C extends ContractDefinition, const K extends ActivitiesKeyOf<C>>(contract: C, key: K) => ReturnType<typeof Provider<WorkflowActivitiesPortOf<C, K>>>;
128
+ //#endregion
129
+ //#region src/temporal-module.d.ts
130
+ /** The starter's own module, as the sugar adds it to the application's imports. */
131
+ type TemporalStarter<C extends ContractDefinition> = Module<TemporalRuntime | TemporalConfig | TemporalConnection, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C>>;
132
+ /** The application's imports plus the starter โ€” the tuple `Module(name)` is handed. */
133
+ type Imports<I extends readonly AnyModule[], C extends ContractDefinition> = readonly [...I, TemporalStarter<C>];
134
+ /** The activities provider plus the application's own โ€” the tuple `Module(name)` is handed. */
135
+ type Provides<P extends readonly AnyProvider[], C extends ContractDefinition, ActivitiesError, ActivitiesNeeds> = readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P];
136
+ type TemporalModuleOptions<C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, C>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[], N extends readonly AnyPort[]> = {
137
+ /** The `temporal-contract` contract; the task queue this worker polls is read off it. */
138
+ readonly contract: C;
139
+ /** The application's activity implementations โ€” what `TemporalActivities(contract)(โ€ฆ)` returns. */
140
+ readonly activities: Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>;
141
+ readonly workflows: WorkflowSource;
142
+ /** Pins `TemporalConfig.address` instead of reading `TEMPORAL_ADDRESS`. */
143
+ readonly address?: string;
144
+ /** Pins `TemporalConfig.namespace` instead of reading `TEMPORAL_NAMESPACE`. */
145
+ readonly namespace?: string;
146
+ /** Temporal's `shutdownGraceTime`. Default `10 seconds`. */
147
+ readonly gracePeriod?: Duration;
148
+ /** Temporal's `shutdownForceTime`. Default `15 seconds`. Keep it at or below the kernel's `drainTimeoutMs`. */
149
+ readonly forceAfter?: Duration;
150
+ readonly imports?: I;
151
+ readonly provides?: P;
152
+ /** The application's own exports; `TemporalRuntime` is added, since `start` resolves it. */
153
+ readonly exports?: X;
154
+ /**
155
+ * What this root's OWN providers expect from outside. di's gate is re-stated
156
+ * over the augmented tuples below, so forgetting one is an error at THIS call.
157
+ */
158
+ readonly needs?: N;
159
+ } & NeedsGate<Imports<I, C>, Provides<P, C, ActivitiesError, ActivitiesNeeds>, N>;
160
+ /**
161
+ * `Module(name)({...})` for a Temporal worker deployment: everything a di module
162
+ * takes, plus the contract, the activities provider and the workflow source. The
163
+ * sugar imports the starter, provides the activities and exports
164
+ * `TemporalRuntime`, handing back exactly the module `Module(...)` would have
165
+ * declared over the augmented tuples.
166
+ *
167
+ * ```ts
168
+ * export const OrderTemporalWorker = TemporalModule("OrderTemporalWorker")({
169
+ * contract: orderContract,
170
+ * activities: orderActivities,
171
+ * workflows: { workflowsPath: workflowsPathFromURL(import.meta.url, "./workflows.js") },
172
+ * imports: [OrderApplicationModule, OrderPersistenceModule, FulfillmentModule],
173
+ * });
174
+ * await runMain(OrderTemporalWorker);
175
+ * ```
176
+ */
177
+ declare const TemporalModule: <const Name extends string>(name: Name) => <C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, C>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[] = [], const N extends readonly AnyPort[] = []>(options: TemporalModuleOptions<C, ActivitiesError, ActivitiesNeeds, I, P, X, N>) => Module<(Extract<(readonly [typeof TemporalRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof TemporalRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof TemporalRuntime, ...X])[number] extends (infer E_1) ? E_1 extends AnyPort ? InstanceType<E_1> : E_1 extends AnyProvider ? InstanceType<E_1["port"]> : never : never), ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[1] | ((readonly [...I, TemporalStarter<C>])[number] extends (infer T_2) ? T_2 extends (readonly [...I, TemporalStarter<C>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[2], ((readonly [...I, TemporalStarter<C>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]> | Exclude<((readonly [...I, TemporalStarter<C>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((readonly [...I, TemporalStarter<C>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]>>;
178
+ /** One piece of the activities record โ€” what `TemporalWorkflowActivities(contract, key)(โ€ฆ)` returns. */
179
+ type PieceOf<C extends ContractDefinition> = { readonly [K in ActivitiesKeyOf<C>]: {
180
+ readonly port: WorkflowActivitiesPortOf<C, K>;
181
+ }; }[ActivitiesKeyOf<C>];
182
+ /** The key a piece carries, read back off its port id. */
183
+ type KeyOfPiece<P> = P extends {
184
+ readonly port: {
185
+ readonly portId: `${typeof WORKFLOW_ACTIVITIES_PREFIX}${infer K}`;
186
+ };
187
+ } ? K : never;
188
+ /** The record's top-level keys no piece in `T` covers. */
189
+ type Uncovered<C extends ContractDefinition, T extends readonly PieceOf<C>[]> = Exclude<ActivitiesKeyOf<C>, KeyOfPiece<T[number]>>;
190
+ /**
191
+ * The composing arm. Declared LAST in the intersection below on purpose:
192
+ * TypeScript reports the last overload's failure, so a non-covering array is
193
+ * refused against the `"UNCOVERED ACTIVITIES โ€” โ€ฆ"` marker rather than degrading
194
+ * to di's `Qualification`, which names nothing.
195
+ *
196
+ * The marker is a sentence rather than a bare label because it prints last,
197
+ * after the caller's own several-hundred-character piece type.
198
+ */
199
+ type Compose<C extends ContractDefinition> = <const T extends readonly PieceOf<C>[]>(pieces: [Uncovered<C, T>] extends [never] ? T : readonly ["UNCOVERED ACTIVITIES โ€” the contract declares a workflow this array does not cover", Uncovered<C, T>]) => Provider<ActivitiesInstanceOf<C>, never, InstanceType<T[number]["port"]>> & {
200
+ readonly port: ActivitiesPortOf<C>;
201
+ };
202
+ /**
203
+ * The activities as a provider, from the contract. Three call forms, one port.
204
+ *
205
+ * ```ts
206
+ * TemporalActivities(orderContract)({ place: PlaceOrder }, { sync: ({ place }) => ({ fulfillOrder: { โ€ฆ } }) })
207
+ * TemporalActivities(orderContract)([fulfillOrder, chargeOrder])
208
+ * ```
209
+ *
210
+ * The first two are di's own `Provider(port)` on the starter's activities port
211
+ * typed for the contract. The third takes the pieces
212
+ * `TemporalWorkflowActivities(contract, key)` builds: they are the provider's
213
+ * deps, keyed by the contract key each piece's port id carries, so the services
214
+ * record IS the activities record. Every key must be covered, and two slices
215
+ * claiming one key are di's duplicate-provider defect at build.
216
+ */
217
+ declare const TemporalActivities: <C extends ContractDefinition>(contract: C) => ReturnType<typeof Provider<ActivitiesPortOf<C>>> & Compose<C>;
218
+ //#endregion
219
+ export { type ActivitiesInstanceOf, type ActivitiesPortOf, TemporalActivities, TemporalConfig, TemporalConnection, type TemporalInfo, TemporalModule, type TemporalModuleOptions, type TemporalOptions, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities, type WorkflowActivitiesPortOf, type WorkflowSource, temporal };
@@ -0,0 +1,219 @@
1
+ import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope } from "@btravstack/di";
2
+ import { ConfigInvalid, Env } from "@btravstack/config";
3
+ import { Runtime, RuntimePort } from "@btravstack/core";
4
+ import { DeclareActivitiesHandlerOptions } from "@temporal-contract/worker/activity";
5
+ import { NativeConnection, WorkflowBundleWithSourceMap } from "@temporalio/worker";
6
+ import { ContractDefinition } from "@temporal-contract/contract";
7
+ import { Duration } from "@temporalio/common";
8
+ //#region src/temporal-runtime.d.ts
9
+ /** What the worker publishes once it is polling, read back through `RunningApp.runtimeInfo()`. */
10
+ type TemporalInfo = {
11
+ readonly taskQueue: string;
12
+ readonly namespace: string;
13
+ };
14
+ /**
15
+ * Where the workflow sandbox's code comes from. Two arms because the two
16
+ * callers genuinely differ: a process points at the module and lets Temporal
17
+ * bundle it, while a spec hands over a bundle it built and memoised once โ€”
18
+ * bundling per test is the most expensive thing a suite does.
19
+ */
20
+ type WorkflowSource = {
21
+ readonly workflowsPath: string;
22
+ } | {
23
+ readonly workflowBundle: WorkflowBundleWithSourceMap;
24
+ };
25
+ declare const TemporalConfig_base: import("@btravstack/di").PortClass<"TemporalConfig">;
26
+ /**
27
+ * Where the Temporal service is, as a service: `temporal()` binds it from the
28
+ * environment โ€” `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`) and
29
+ * `TEMPORAL_NAMESPACE` (default `default`) โ€” unless pinned, and anything else
30
+ * in the graph may read it.
31
+ */
32
+ declare class TemporalConfig extends TemporalConfig_base<{
33
+ readonly address: string;
34
+ readonly namespace: string;
35
+ }> {}
36
+ declare const TemporalConnection_base: import("@btravstack/di").PortClass<"TemporalConnection">;
37
+ /**
38
+ * The connection, as a resource of the graph: di opens it with the scope and
39
+ * closes it on every exit path, startup failure included.
40
+ */
41
+ declare class TemporalConnection extends TemporalConnection_base<NativeConnection> {}
42
+ declare const TemporalUnreachable_base: import("unthrown").TaggedErrorConstructor<"TemporalUnreachable">;
43
+ /**
44
+ * The service at `TemporalConfig.address` did not answer. Modeled rather than
45
+ * left a defect because an operator *can* act on it โ€” the address is wrong or
46
+ * the service is down, and neither is a bug in this code โ€” so `runMain` exits
47
+ * `1`, a startup `Err`, not the `70` a defect earns.
48
+ */
49
+ declare class TemporalUnreachable extends TemporalUnreachable_base<{
50
+ readonly address: string;
51
+ readonly cause: unknown;
52
+ }> {
53
+ message: string;
54
+ }
55
+ /** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
56
+ declare class TemporalRuntime extends RuntimePort<Runtime<never, TemporalInfo>> {}
57
+ /** The activity implementations `declareActivitiesHandler` takes for `C`, with no injected context. */
58
+ type ActivitiesOf<C extends ContractDefinition> = DeclareActivitiesHandlerOptions<C>["activities"];
59
+ /** The activities port class, typed for `C`: what `TemporalActivities(contract)(โ€ฆ).port` is. */
60
+ type ActivitiesPortOf<C extends ContractDefinition> = PortClassOf<"TemporalActivities", ActivitiesOf<C>>;
61
+ /** The activities port's instance for `C` โ€” the module's one need. */
62
+ type ActivitiesInstanceOf<C extends ContractDefinition> = PortInstance<"TemporalActivities", ActivitiesOf<C>>;
63
+ type TemporalOptions<C extends ContractDefinition> = {
64
+ /**
65
+ * The contract; the task queue this worker polls is read off it, and the
66
+ * activities port is typed by it. The starter calls
67
+ * `declareActivitiesHandler` itself, with its unit middleware in place.
68
+ */
69
+ readonly contract: C;
70
+ readonly workflows: WorkflowSource;
71
+ /** Pins `TemporalConfig.address` instead of reading `TEMPORAL_ADDRESS`. */
72
+ readonly address?: string;
73
+ /** Pins `TemporalConfig.namespace` instead of reading `TEMPORAL_NAMESPACE`. */
74
+ readonly namespace?: string;
75
+ /** Temporal's `shutdownForceTime`. Default `15 seconds`. Keep it at or below the kernel's `drainTimeoutMs`. */
76
+ readonly forceAfter?: Duration;
77
+ /** Temporal's `shutdownGraceTime`. Default `10 seconds`. */
78
+ readonly gracePeriod?: Duration;
79
+ };
80
+ type Provided = TemporalRuntime | TemporalConfig | TemporalConnection;
81
+ /**
82
+ * The Temporal starter: a module providing the runtime, its configuration
83
+ * (bound from `TEMPORAL_ADDRESS` / `TEMPORAL_NAMESPACE` unless pinned) and the
84
+ * connection (a resource opened with the scope and closed with it; a service
85
+ * that will not answer is a modeled `TemporalUnreachable`). Import it next to
86
+ * the application, export `TemporalRuntime`, provide the activities โ€” the
87
+ * activities port is a need of this module.
88
+ *
89
+ * With both configuration fields pinned the module reads nothing from the
90
+ * environment; pin only one and the other still comes from it.
91
+ */
92
+ declare const temporal: <C extends ContractDefinition>(options: TemporalOptions<C>) => Module<Provided, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C>>;
93
+ //#endregion
94
+ //#region src/workflow-activities.d.ts
95
+ /**
96
+ * `ActivitiesOf<C>` is a `NoInfer`-wrapped conditional TypeScript refuses to
97
+ * index by a generic key. Routing it through `infer` resolves it to a plain
98
+ * object type first, which IS indexable.
99
+ */
100
+ type ActivitiesRecordOf<C extends ContractDefinition> = ActivitiesOf<C> extends (infer Resolved) ? Resolved : never;
101
+ /** The top-level keys of `C`'s activities record: a workflow that declares activities, or a contract-global activity. */
102
+ type ActivitiesKeyOf<C extends ContractDefinition> = keyof ActivitiesRecordOf<C> & string;
103
+ /** The prefix a piece's port id carries; the composing form strips it to recover the key. */
104
+ declare const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
105
+ /**
106
+ * The port one piece targets. Its id carries the key, which is what makes two
107
+ * slices claiming one workflow di's duplicate-provider defect rather than a
108
+ * silent merge.
109
+ */
110
+ type WorkflowActivitiesPortOf<C extends ContractDefinition, K extends ActivitiesKeyOf<C>> = PortClassOf<`${typeof WORKFLOW_ACTIVITIES_PREFIX}${K}`, ActivitiesRecordOf<C>[K]>;
111
+ /**
112
+ * One workflow's activities, as a provider on a port of its own.
113
+ *
114
+ * A worker that polls one queue for several workflows is several pieces, each
115
+ * declaring the services its own activities call;
116
+ * `TemporalActivities(contract)([...])` composes them. `contract` is read for
117
+ * its TYPE only, and types both `key` and the record โ€” so an activity the
118
+ * workflow does not declare is a compile error here rather than at startup.
119
+ *
120
+ * `key` is any top-level key of the activities record, which includes a
121
+ * contract-global activity as well as a workflow; the name is imprecise in that
122
+ * one case deliberately, since narrowing it would lock such a contract out of
123
+ * the split.
124
+ *
125
+ * There is no name to give: the key IS the port's name.
126
+ */
127
+ declare const TemporalWorkflowActivities: <C extends ContractDefinition, const K extends ActivitiesKeyOf<C>>(contract: C, key: K) => ReturnType<typeof Provider<WorkflowActivitiesPortOf<C, K>>>;
128
+ //#endregion
129
+ //#region src/temporal-module.d.ts
130
+ /** The starter's own module, as the sugar adds it to the application's imports. */
131
+ type TemporalStarter<C extends ContractDefinition> = Module<TemporalRuntime | TemporalConfig | TemporalConnection, ConfigInvalid | TemporalUnreachable, Env | Scope | ActivitiesInstanceOf<C>>;
132
+ /** The application's imports plus the starter โ€” the tuple `Module(name)` is handed. */
133
+ type Imports<I extends readonly AnyModule[], C extends ContractDefinition> = readonly [...I, TemporalStarter<C>];
134
+ /** The activities provider plus the application's own โ€” the tuple `Module(name)` is handed. */
135
+ type Provides<P extends readonly AnyProvider[], C extends ContractDefinition, ActivitiesError, ActivitiesNeeds> = readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P];
136
+ type TemporalModuleOptions<C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, C>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[], N extends readonly AnyPort[]> = {
137
+ /** The `temporal-contract` contract; the task queue this worker polls is read off it. */
138
+ readonly contract: C;
139
+ /** The application's activity implementations โ€” what `TemporalActivities(contract)(โ€ฆ)` returns. */
140
+ readonly activities: Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>;
141
+ readonly workflows: WorkflowSource;
142
+ /** Pins `TemporalConfig.address` instead of reading `TEMPORAL_ADDRESS`. */
143
+ readonly address?: string;
144
+ /** Pins `TemporalConfig.namespace` instead of reading `TEMPORAL_NAMESPACE`. */
145
+ readonly namespace?: string;
146
+ /** Temporal's `shutdownGraceTime`. Default `10 seconds`. */
147
+ readonly gracePeriod?: Duration;
148
+ /** Temporal's `shutdownForceTime`. Default `15 seconds`. Keep it at or below the kernel's `drainTimeoutMs`. */
149
+ readonly forceAfter?: Duration;
150
+ readonly imports?: I;
151
+ readonly provides?: P;
152
+ /** The application's own exports; `TemporalRuntime` is added, since `start` resolves it. */
153
+ readonly exports?: X;
154
+ /**
155
+ * What this root's OWN providers expect from outside. di's gate is re-stated
156
+ * over the augmented tuples below, so forgetting one is an error at THIS call.
157
+ */
158
+ readonly needs?: N;
159
+ } & NeedsGate<Imports<I, C>, Provides<P, C, ActivitiesError, ActivitiesNeeds>, N>;
160
+ /**
161
+ * `Module(name)({...})` for a Temporal worker deployment: everything a di module
162
+ * takes, plus the contract, the activities provider and the workflow source. The
163
+ * sugar imports the starter, provides the activities and exports
164
+ * `TemporalRuntime`, handing back exactly the module `Module(...)` would have
165
+ * declared over the augmented tuples.
166
+ *
167
+ * ```ts
168
+ * export const OrderTemporalWorker = TemporalModule("OrderTemporalWorker")({
169
+ * contract: orderContract,
170
+ * activities: orderActivities,
171
+ * workflows: { workflowsPath: workflowsPathFromURL(import.meta.url, "./workflows.js") },
172
+ * imports: [OrderApplicationModule, OrderPersistenceModule, FulfillmentModule],
173
+ * });
174
+ * await runMain(OrderTemporalWorker);
175
+ * ```
176
+ */
177
+ declare const TemporalModule: <const Name extends string>(name: Name) => <C extends ContractDefinition, ActivitiesError, ActivitiesNeeds, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, C>, Provides<P, C, ActivitiesError, ActivitiesNeeds>>[] = [], const N extends readonly AnyPort[] = []>(options: TemporalModuleOptions<C, ActivitiesError, ActivitiesNeeds, I, P, X, N>) => Module<(Extract<(readonly [typeof TemporalRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof TemporalRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof TemporalRuntime, ...X])[number] extends (infer E_1) ? E_1 extends AnyPort ? InstanceType<E_1> : E_1 extends AnyProvider ? InstanceType<E_1["port"]> : never : never), ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_1 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[1] | ((readonly [...I, TemporalStarter<C>])[number] extends (infer T_2) ? T_2 extends (readonly [...I, TemporalStarter<C>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[2], ((readonly [...I, TemporalStarter<C>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]> | Exclude<((readonly [...I, TemporalStarter<C>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((readonly [...I, TemporalStarter<C>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, TemporalStarter<C>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<ActivitiesInstanceOf<C>, ActivitiesError, ActivitiesNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]>>;
178
+ /** One piece of the activities record โ€” what `TemporalWorkflowActivities(contract, key)(โ€ฆ)` returns. */
179
+ type PieceOf<C extends ContractDefinition> = { readonly [K in ActivitiesKeyOf<C>]: {
180
+ readonly port: WorkflowActivitiesPortOf<C, K>;
181
+ }; }[ActivitiesKeyOf<C>];
182
+ /** The key a piece carries, read back off its port id. */
183
+ type KeyOfPiece<P> = P extends {
184
+ readonly port: {
185
+ readonly portId: `${typeof WORKFLOW_ACTIVITIES_PREFIX}${infer K}`;
186
+ };
187
+ } ? K : never;
188
+ /** The record's top-level keys no piece in `T` covers. */
189
+ type Uncovered<C extends ContractDefinition, T extends readonly PieceOf<C>[]> = Exclude<ActivitiesKeyOf<C>, KeyOfPiece<T[number]>>;
190
+ /**
191
+ * The composing arm. Declared LAST in the intersection below on purpose:
192
+ * TypeScript reports the last overload's failure, so a non-covering array is
193
+ * refused against the `"UNCOVERED ACTIVITIES โ€” โ€ฆ"` marker rather than degrading
194
+ * to di's `Qualification`, which names nothing.
195
+ *
196
+ * The marker is a sentence rather than a bare label because it prints last,
197
+ * after the caller's own several-hundred-character piece type.
198
+ */
199
+ type Compose<C extends ContractDefinition> = <const T extends readonly PieceOf<C>[]>(pieces: [Uncovered<C, T>] extends [never] ? T : readonly ["UNCOVERED ACTIVITIES โ€” the contract declares a workflow this array does not cover", Uncovered<C, T>]) => Provider<ActivitiesInstanceOf<C>, never, InstanceType<T[number]["port"]>> & {
200
+ readonly port: ActivitiesPortOf<C>;
201
+ };
202
+ /**
203
+ * The activities as a provider, from the contract. Three call forms, one port.
204
+ *
205
+ * ```ts
206
+ * TemporalActivities(orderContract)({ place: PlaceOrder }, { sync: ({ place }) => ({ fulfillOrder: { โ€ฆ } }) })
207
+ * TemporalActivities(orderContract)([fulfillOrder, chargeOrder])
208
+ * ```
209
+ *
210
+ * The first two are di's own `Provider(port)` on the starter's activities port
211
+ * typed for the contract. The third takes the pieces
212
+ * `TemporalWorkflowActivities(contract, key)` builds: they are the provider's
213
+ * deps, keyed by the contract key each piece's port id carries, so the services
214
+ * record IS the activities record. Every key must be covered, and two slices
215
+ * claiming one key are di's duplicate-provider defect at build.
216
+ */
217
+ declare const TemporalActivities: <C extends ContractDefinition>(contract: C) => ReturnType<typeof Provider<ActivitiesPortOf<C>>> & Compose<C>;
218
+ //#endregion
219
+ export { type ActivitiesInstanceOf, type ActivitiesPortOf, TemporalActivities, TemporalConfig, TemporalConnection, type TemporalInfo, TemporalModule, type TemporalModuleOptions, type TemporalOptions, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities, type WorkflowActivitiesPortOf, type WorkflowSource, temporal };
package/dist/index.mjs ADDED
@@ -0,0 +1,253 @@
1
+ import { Module, Port, Provider } from "@btravstack/di";
2
+ import { Config, Env } from "@btravstack/config";
3
+ import { RuntimePort, RuntimeStartFailed, releasedBy } from "@btravstack/core";
4
+ import { declareActivitiesHandler } from "@temporal-contract/worker/activity";
5
+ import { NativeConnection, Worker } from "@temporalio/worker";
6
+ import { TaggedError, fromPromise, fromSafePromise, fromThrowable } from "unthrown";
7
+ import { activityInfo } from "@temporalio/activity";
8
+ //#region src/activity-units.ts
9
+ /**
10
+ * Open one kernel unit per activity attempt. It injects nothing โ€” `next()`
11
+ * unchanged โ€” so the ambient `currentUnit()` record is what an adapter reads.
12
+ *
13
+ * **That includes the kernel's per-unit `AbortSignal`**, and it is the only
14
+ * route to it: an activity has no parameter to receive one through. Temporal's
15
+ * own `Context.current().cancellationSignal` is a DIFFERENT clock, so the two
16
+ * are honoured together rather than one standing in for the other.
17
+ *
18
+ * No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
19
+ * the mapping from a settled `Result` to an activity failure.
20
+ */
21
+ const activityUnits = (host) => (_invocation, next) => host.run(metaFor(), () => next());
22
+ /**
23
+ * `UnitMeta.id` must be unique per unit, and a workflow id is **not** one: an
24
+ * activity is retried under the same execution. A task token identifies one
25
+ * attempt, so its uniqueness is Temporal's guarantee rather than ours.
26
+ *
27
+ * The workflow id becomes the `traceId` โ€” minted outside this process and steady
28
+ * across every retry, so all attempts join up in a log. An activity with no
29
+ * workflow falls back to the activity id.
30
+ */
31
+ const metaFor = () => {
32
+ const info = activityInfo();
33
+ return {
34
+ kind: "activity",
35
+ id: info.base64TaskToken,
36
+ traceId: info.workflowExecution?.workflowId ?? info.activityId
37
+ };
38
+ };
39
+ //#endregion
40
+ //#region src/temporal-runtime.ts
41
+ /**
42
+ * Where the Temporal service is, as a service: `temporal()` binds it from the
43
+ * environment โ€” `TEMPORAL_ADDRESS` (default `127.0.0.1:7233`) and
44
+ * `TEMPORAL_NAMESPACE` (default `default`) โ€” unless pinned, and anything else
45
+ * in the graph may read it.
46
+ */
47
+ var TemporalConfig = class extends Port("TemporalConfig") {};
48
+ /**
49
+ * The connection, as a resource of the graph: di opens it with the scope and
50
+ * closes it on every exit path, startup failure included.
51
+ */
52
+ var TemporalConnection = class extends Port("TemporalConnection") {};
53
+ /**
54
+ * The service at `TemporalConfig.address` did not answer. Modeled rather than
55
+ * left a defect because an operator *can* act on it โ€” the address is wrong or
56
+ * the service is down, and neither is a bug in this code โ€” so `runMain` exits
57
+ * `1`, a startup `Err`, not the `70` a defect earns.
58
+ */
59
+ var TemporalUnreachable = class extends TaggedError("TemporalUnreachable") {
60
+ message = `the Temporal service at ${this.address} did not answer`;
61
+ };
62
+ /** The runtime's port: what `temporal()` provides, and what the module `start` boots must export. */
63
+ var TemporalRuntime = class extends RuntimePort {};
64
+ /**
65
+ * The activities' port โ€” one id, the starter's own, which an application never
66
+ * names. Generic at the value level (one `Port(...)` call, so no duplicate-id
67
+ * warning however many contracts instantiate it) and fixed per contract at the
68
+ * type level through `ActivitiesPortOf<C>`, so a provider built for one contract
69
+ * cannot be handed to a module declaring another. Exported for this package's
70
+ * tests, not from `index.ts`.
71
+ */
72
+ const TemporalActivitiesPort = Port("TemporalActivities");
73
+ const DEFAULT_GRACE = "10 seconds";
74
+ const DEFAULT_FORCE = "15 seconds";
75
+ /**
76
+ * The Temporal starter: a module providing the runtime, its configuration
77
+ * (bound from `TEMPORAL_ADDRESS` / `TEMPORAL_NAMESPACE` unless pinned) and the
78
+ * connection (a resource opened with the scope and closed with it; a service
79
+ * that will not answer is a modeled `TemporalUnreachable`). Import it next to
80
+ * the application, export `TemporalRuntime`, provide the activities โ€” the
81
+ * activities port is a need of this module.
82
+ *
83
+ * With both configuration fields pinned the module reads nothing from the
84
+ * environment; pin only one and the other still comes from it.
85
+ */
86
+ const temporal = (options) => {
87
+ const { address, namespace } = options;
88
+ const activities = TemporalActivitiesPort;
89
+ const config = address !== void 0 && namespace !== void 0 ? Provider(TemporalConfig)({ value: {
90
+ address,
91
+ namespace
92
+ } }) : Config.provider(TemporalConfig)(Config.object({
93
+ address: Config.pinned(address, Config.string("TEMPORAL_ADDRESS", { default: "127.0.0.1:7233" })),
94
+ namespace: Config.pinned(namespace, Config.string("TEMPORAL_NAMESPACE", { default: "default" }))
95
+ }));
96
+ return Module("Temporal")({
97
+ needs: [Env, activities],
98
+ provides: [
99
+ config,
100
+ Provider(TemporalConnection)({ config: TemporalConfig }, {
101
+ acquire: ({ config: bound }) => fromPromise(NativeConnection.connect({ address: bound.address }), (cause) => new TemporalUnreachable({
102
+ address: bound.address,
103
+ cause
104
+ })),
105
+ release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
106
+ }),
107
+ Provider(TemporalRuntime)({
108
+ connection: TemporalConnection,
109
+ config: TemporalConfig,
110
+ activities
111
+ }, { sync: ({ connection, config: bound, activities: impls }) => ({
112
+ name: "temporal",
113
+ resolves: [],
114
+ start: (host) => createWorker(host, connection, bound, impls, options)
115
+ }) })
116
+ ],
117
+ exports: [
118
+ TemporalRuntime,
119
+ TemporalConfig,
120
+ TemporalConnection
121
+ ]
122
+ });
123
+ };
124
+ const startFailed = (cause) => new RuntimeStartFailed({
125
+ runtime: "temporal",
126
+ cause
127
+ });
128
+ const heldByWorker = (cause) => cause instanceof Error && cause.name === "IllegalStateError";
129
+ const createWorker = (host, connection, config, activities, options) => {
130
+ const { taskQueue } = options.contract;
131
+ const { namespace } = config;
132
+ return fromThrowable(() => declareActivitiesHandler({
133
+ contract: options.contract,
134
+ middleware: activityUnits(host),
135
+ activities
136
+ }), startFailed)().toAsync().flatMap((handler) => fromPromise(Worker.create({
137
+ connection,
138
+ namespace,
139
+ taskQueue,
140
+ ...options.workflows,
141
+ activities: handler,
142
+ shutdownGraceTime: options.gracePeriod ?? DEFAULT_GRACE,
143
+ shutdownForceTime: options.forceAfter ?? DEFAULT_FORCE
144
+ }), startFailed)).map((worker) => poll(worker, taskQueue, namespace));
145
+ };
146
+ const poll = (worker, taskQueue, namespace) => {
147
+ const running = fromSafePromise(worker.run());
148
+ const stopPolling = () => {
149
+ if (worker.getState() === "RUNNING") worker.shutdown();
150
+ };
151
+ let deadline;
152
+ const stopped = () => deadline === void 0 ? running : releasedBy(deadline, running);
153
+ return {
154
+ info: {
155
+ taskQueue,
156
+ namespace
157
+ },
158
+ drain: (signal) => {
159
+ deadline = signal;
160
+ stopPolling();
161
+ return stopped();
162
+ },
163
+ stop: () => {
164
+ stopPolling();
165
+ return stopped();
166
+ }
167
+ };
168
+ };
169
+ //#endregion
170
+ //#region src/workflow-activities.ts
171
+ /** The prefix a piece's port id carries; the composing form strips it to recover the key. */
172
+ const WORKFLOW_ACTIVITIES_PREFIX = "TemporalWorkflowActivities:";
173
+ /**
174
+ * One workflow's activities, as a provider on a port of its own.
175
+ *
176
+ * A worker that polls one queue for several workflows is several pieces, each
177
+ * declaring the services its own activities call;
178
+ * `TemporalActivities(contract)([...])` composes them. `contract` is read for
179
+ * its TYPE only, and types both `key` and the record โ€” so an activity the
180
+ * workflow does not declare is a compile error here rather than at startup.
181
+ *
182
+ * `key` is any top-level key of the activities record, which includes a
183
+ * contract-global activity as well as a workflow; the name is imprecise in that
184
+ * one case deliberately, since narrowing it would lock such a contract out of
185
+ * the split.
186
+ *
187
+ * There is no name to give: the key IS the port's name.
188
+ */
189
+ const TemporalWorkflowActivities = (contract, key) => {
190
+ const port = class extends Port(`${WORKFLOW_ACTIVITIES_PREFIX}${key}`) {};
191
+ return Provider(port);
192
+ };
193
+ //#endregion
194
+ //#region src/temporal-module.ts
195
+ /**
196
+ * `Module(name)({...})` for a Temporal worker deployment: everything a di module
197
+ * takes, plus the contract, the activities provider and the workflow source. The
198
+ * sugar imports the starter, provides the activities and exports
199
+ * `TemporalRuntime`, handing back exactly the module `Module(...)` would have
200
+ * declared over the augmented tuples.
201
+ *
202
+ * ```ts
203
+ * export const OrderTemporalWorker = TemporalModule("OrderTemporalWorker")({
204
+ * contract: orderContract,
205
+ * activities: orderActivities,
206
+ * workflows: { workflowsPath: workflowsPathFromURL(import.meta.url, "./workflows.js") },
207
+ * imports: [OrderApplicationModule, OrderPersistenceModule, FulfillmentModule],
208
+ * });
209
+ * await runMain(OrderTemporalWorker);
210
+ * ```
211
+ */
212
+ const TemporalModule = (name) => (options) => {
213
+ const { contract, activities, workflows, address, namespace, gracePeriod, forceAfter } = options;
214
+ const imports = options.imports ?? [];
215
+ const provides = options.provides ?? [];
216
+ const exports = options.exports ?? [];
217
+ const starter = temporal({
218
+ contract,
219
+ workflows,
220
+ ...address === void 0 ? {} : { address },
221
+ ...namespace === void 0 ? {} : { namespace },
222
+ ...gracePeriod === void 0 ? {} : { gracePeriod },
223
+ ...forceAfter === void 0 ? {} : { forceAfter }
224
+ });
225
+ return Module(name)({
226
+ imports: [...imports, starter],
227
+ provides: [activities, ...provides],
228
+ exports: [TemporalRuntime, ...exports],
229
+ needs: options.needs ?? []
230
+ });
231
+ };
232
+ /**
233
+ * The activities as a provider, from the contract. Three call forms, one port.
234
+ *
235
+ * ```ts
236
+ * TemporalActivities(orderContract)({ place: PlaceOrder }, { sync: ({ place }) => ({ fulfillOrder: { โ€ฆ } }) })
237
+ * TemporalActivities(orderContract)([fulfillOrder, chargeOrder])
238
+ * ```
239
+ *
240
+ * The first two are di's own `Provider(port)` on the starter's activities port
241
+ * typed for the contract. The third takes the pieces
242
+ * `TemporalWorkflowActivities(contract, key)` builds: they are the provider's
243
+ * deps, keyed by the contract key each piece's port id carries, so the services
244
+ * record IS the activities record. Every key must be covered, and two slices
245
+ * claiming one key are di's duplicate-provider defect at build.
246
+ */
247
+ const TemporalActivities = (contract) => {
248
+ const build = Provider(TemporalActivitiesPort);
249
+ const compose = (pieces) => build(Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(27), piece.port])), { sync: (services) => services });
250
+ return ((first, second) => second === void 0 && Array.isArray(first) ? compose(first) : build(first, second));
251
+ };
252
+ //#endregion
253
+ export { TemporalActivities, TemporalConfig, TemporalConnection, TemporalModule, TemporalRuntime, TemporalUnreachable, TemporalWorkflowActivities, temporal };
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@btravstack/temporal-worker",
3
+ "version": "0.3.0",
4
+ "description": "The Temporal worker runtime for @btravstack/core: one unit per activity attempt, and a drain that honours the kernel's deadline",
5
+ "keywords": [
6
+ "graceful-shutdown",
7
+ "lifecycle",
8
+ "runtime",
9
+ "temporal",
10
+ "typescript",
11
+ "unthrown",
12
+ "worker"
13
+ ],
14
+ "homepage": "https://github.com/btravstack/btravstack#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/btravstack/btravstack/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Benoit TRAVERS <benoit.travers.fr@gmail.com>",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/btravstack/btravstack.git",
23
+ "directory": "packages/temporal-worker"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "main": "./dist/index.cjs",
31
+ "module": "./dist/index.mjs",
32
+ "types": "./dist/index.d.cts",
33
+ "exports": {
34
+ ".": {
35
+ "import": {
36
+ "types": "./dist/index.d.mts",
37
+ "default": "./dist/index.mjs"
38
+ },
39
+ "require": {
40
+ "types": "./dist/index.d.cts",
41
+ "default": "./dist/index.cjs"
42
+ }
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "devDependencies": {
47
+ "@btravstack/tsconfig": "0.2.0",
48
+ "@temporal-contract/contract": "8.0.0-beta.5",
49
+ "@temporal-contract/worker": "8.0.0-beta.5",
50
+ "@temporalio/activity": "1.22.0",
51
+ "@temporalio/client": "1.22.0",
52
+ "@temporalio/common": "1.22.0",
53
+ "@temporalio/worker": "1.22.0",
54
+ "@temporalio/workflow": "1.22.0",
55
+ "@types/node": "26.2.0",
56
+ "@unthrown/vitest": "5.7.0",
57
+ "@vitest/coverage-v8": "4.1.11",
58
+ "tsdown": "0.22.14",
59
+ "typescript": "7.0.2",
60
+ "unthrown": "5.7.0",
61
+ "vitest": "4.1.11",
62
+ "zod": "4.4.3",
63
+ "@btravstack/core": "0.3.0",
64
+ "@btravstack/di": "0.3.0",
65
+ "@btravstack/testing": "0.3.0",
66
+ "@btravstack/config": "0.3.0",
67
+ "@btravstack/internal-test-infra": "0.0.0"
68
+ },
69
+ "peerDependencies": {
70
+ "@temporal-contract/contract": "^8.0.0-beta",
71
+ "@temporal-contract/worker": "^8.0.0-beta",
72
+ "@temporalio/activity": "^1.22.0",
73
+ "@temporalio/common": "^1.22.0",
74
+ "@temporalio/worker": "^1.22.0",
75
+ "unthrown": "^5.0.0",
76
+ "@btravstack/config": "^0.3.0",
77
+ "@btravstack/di": "^0.3.0",
78
+ "@btravstack/core": "^0.3.0"
79
+ },
80
+ "engines": {
81
+ "node": ">=20"
82
+ },
83
+ "scripts": {
84
+ "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
85
+ "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
86
+ "test": "vitest run --coverage",
87
+ "test:types": "tsc --noEmit -p tsconfig.test-d.json",
88
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json"
89
+ }
90
+ }