@proompteng/temporal-bun-sdk 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -544
- package/dist/agent-readiness.json +46 -0
- package/dist/production-readiness.json +93 -0
- package/dist/src/bin/lint-workflows-command.d.ts.map +1 -1
- package/dist/src/bin/lint-workflows-command.js +24 -2
- package/dist/src/bin/lint-workflows-command.js.map +1 -1
- package/dist/src/bin/temporal-bun.d.ts.map +1 -1
- package/dist/src/bin/temporal-bun.js +20 -5
- package/dist/src/bin/temporal-bun.js.map +1 -1
- package/dist/src/otel/sdk-trace.d.ts.map +1 -1
- package/dist/src/otel/sdk-trace.js +5 -0
- package/dist/src/otel/sdk-trace.js.map +1 -1
- package/dist/src/workflow/executor.d.ts.map +1 -1
- package/dist/src/workflow/executor.js +41 -6
- package/dist/src/workflow/executor.js.map +1 -1
- package/dist/src/workflow/guards.d.ts.map +1 -1
- package/dist/src/workflow/guards.js +15 -0
- package/dist/src/workflow/guards.js.map +1 -1
- package/docs/agent-adoption-guide.md +61 -0
- package/docs/feature-matrix.md +31 -0
- package/docs/production-design.md +589 -0
- package/docs/production-readiness-implementation-plan.md +370 -0
- package/docs/release-runbook.md +48 -0
- package/docs/support-policy.md +76 -0
- package/docs/temporal-ci-cluster-requirement.md +36 -0
- package/docs/workflow-updates.md +107 -0
- package/package.json +21 -17
package/README.md
CHANGED
|
@@ -1,572 +1,108 @@
|
|
|
1
1
|
# `@proompteng/temporal-bun-sdk`
|
|
2
2
|
|
|
3
|
-
Run Temporal workers and clients on Bun
|
|
3
|
+
Run Temporal workers and clients on Bun.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Docs: <https://docs.proompteng.ai/docs/temporal-bun-sdk>
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Quickstart
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
1. Scaffold a new project in a clean directory:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
cd /tmp
|
|
15
|
-
bunx @proompteng/temporal-bun-sdk init hello-worker
|
|
16
|
-
cd hello-worker
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
2. Repair the generated SDK version for the current npm release:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
bun add @proompteng/temporal-bun-sdk@0.7.0
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
3. Start Temporal locally:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
temporal server start-dev --headless
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
4. Create a `.env` file:
|
|
32
|
-
|
|
33
|
-
```env
|
|
34
|
-
TEMPORAL_ADDRESS=127.0.0.1:7233
|
|
35
|
-
TEMPORAL_NAMESPACE=default
|
|
36
|
-
TEMPORAL_TASK_QUEUE=hello-bun
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
5. Run the worker:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
bun run dev
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
6. In another shell, start the example workflow:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
temporal workflow start \
|
|
49
|
-
--task-queue hello-bun \
|
|
50
|
-
--workflow-type helloWorkflow \
|
|
51
|
-
--input '"Codex"'
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
You should see the worker pick up the workflow and complete it.
|
|
55
|
-
|
|
56
|
-
## Install into an existing Bun project
|
|
9
|
+
Run this outside another Bun workspace:
|
|
57
10
|
|
|
58
11
|
```bash
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
For Temporal Cloud, TLS, Docker, replay, and observability, use the full guide:
|
|
63
|
-
<https://docs.proompteng.ai/docs/temporal-bun-sdk>
|
|
64
|
-
|
|
65
|
-
## Why teams adopt it
|
|
66
|
-
|
|
67
|
-
- **Bun-native runtime** – run workers, activities, and clients directly on Bun.
|
|
68
|
-
- **Production path included** – TLS, Temporal Cloud, Docker packaging, retries, observability, and replay tooling are built in.
|
|
69
|
-
- **Typed workflow surface** – workflows, queries, updates, signals, schedules, and Cloud/Operator RPCs are exposed with typed helpers.
|
|
70
|
-
- **No native bridge required** – the runtime is pure TypeScript and uses generated protobuf stubs.
|
|
71
|
-
- **CLI included** – `temporal-bun` scaffolds workers, validates config with `doctor`, builds Docker images, and replays histories.
|
|
72
|
-
|
|
73
|
-
## Prerequisites
|
|
74
|
-
|
|
75
|
-
- **Bun ≥ 1.3.10** – required for the runtime and CLI.
|
|
76
|
-
- **Temporal CLI ≥ 1.4** – optional, but useful for spinning up a local dev server.
|
|
77
|
-
|
|
78
|
-
## Production features
|
|
79
|
-
|
|
80
|
-
- `loadTemporalConfig()` with typed env parsing for local, self-hosted, and Temporal Cloud setups.
|
|
81
|
-
- TLS, API key, and insecure-dev toggles with early configuration validation.
|
|
82
|
-
- `temporal-bun doctor` to validate config and exporters before rollout.
|
|
83
|
-
- Replay tooling to verify determinism against stored histories.
|
|
84
|
-
- Docker helpers for Bun worker packaging.
|
|
85
|
-
- Testing helpers for local and time-skipping environments.
|
|
86
|
-
- Observability hooks for logs, metrics, tracing, and interceptors.
|
|
87
|
-
|
|
88
|
-
## Useful links
|
|
89
|
-
|
|
90
|
-
- Docs: <https://docs.proompteng.ai/docs/temporal-bun-sdk>
|
|
91
|
-
- Example app: <https://github.com/proompteng/lab/tree/main/packages/temporal-bun-sdk-example>
|
|
92
|
-
- Package issues: <https://github.com/proompteng/lab/issues>
|
|
93
|
-
|
|
94
|
-
## Workflow updates
|
|
95
|
-
|
|
96
|
-
### Registering update handlers
|
|
97
|
-
|
|
98
|
-
Workflows can now declare strongly typed update handlers with the new `defineWorkflowUpdates` helper and register them inside the workflow definition. Update handlers run inside the normal determinism guard and support optional Effect Schema validators:
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
import { defineWorkflow, defineWorkflowUpdates } from '@proompteng/temporal-bun-sdk/workflow'
|
|
102
|
-
import * as Schema from 'effect/Schema'
|
|
103
|
-
|
|
104
|
-
const counterUpdates = defineWorkflowUpdates([
|
|
105
|
-
{
|
|
106
|
-
name: 'setCounter',
|
|
107
|
-
input: Schema.Number,
|
|
108
|
-
handler: async ({ updates }, value: number) => updates.registerDefault(async () => value),
|
|
109
|
-
},
|
|
110
|
-
])
|
|
111
|
-
|
|
112
|
-
export const counterWorkflow = defineWorkflow(
|
|
113
|
-
'counterWorkflow',
|
|
114
|
-
Schema.Number,
|
|
115
|
-
({ input, updates }) => {
|
|
116
|
-
let count = input
|
|
117
|
-
|
|
118
|
-
updates.register(counterUpdates[0], async (_ctx, value) => {
|
|
119
|
-
count = value
|
|
120
|
-
return count
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
return Effect.sync(() => count)
|
|
124
|
-
},
|
|
125
|
-
{ updates: counterUpdates },
|
|
126
|
-
)
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
The workflow runtime automatically polls `PollWorkflowTaskQueueResponse.messages`, routes update requests through the scheduler, and records lifecycle events (admitted/accepted/completed) in determinism snapshots so sticky caches stay healthy on replay.
|
|
130
|
-
|
|
131
|
-
## Workflow queries
|
|
132
|
-
|
|
133
|
-
- Register query handlers with `defineWorkflowQueries` and wire them inside the workflow handler. The worker runtime now detects legacy query-only tasks (`response.query`) and answers them via `RespondQueryTaskCompleted`, so CLI/SDK queries return immediately even when workflow tasks are blocked.
|
|
134
|
-
- Query evaluation runs in a **read-only** mode: no new commands, timers, randomness, or continue-as-new requests are allowed. Violations surface as `WorkflowQueryViolationError` and are returned to the caller.
|
|
135
|
-
- The client supports `QueryRejectCondition` through `temporalCallOptions({ queryRejectCondition: QueryRejectCondition.NOT_OPEN })`; query results are decoded with the configured data converter.
|
|
136
|
-
- Metrics: `temporal_worker_query_started_total`, `temporal_worker_query_completed_total`, `temporal_worker_query_failed_total`, and latency histogram `temporal_worker_query_latency_ms`.
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
import { defineWorkflow, defineWorkflowQueries } from '@proompteng/temporal-bun-sdk/workflow'
|
|
140
|
-
import { QueryRejectCondition, temporalCallOptions } from '@proompteng/temporal-bun-sdk/client'
|
|
141
|
-
|
|
142
|
-
const queries = defineWorkflowQueries({
|
|
143
|
-
status: {
|
|
144
|
-
input: Schema.Struct({}),
|
|
145
|
-
output: Schema.String,
|
|
146
|
-
},
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
export const statusWorkflow = defineWorkflow({
|
|
150
|
-
name: 'statusWorkflow',
|
|
151
|
-
queries,
|
|
152
|
-
handler: ({ queries: registry }) =>
|
|
153
|
-
Effect.gen(function* () {
|
|
154
|
-
let current = 'booting'
|
|
155
|
-
yield* registry.register(queries.status, () => Effect.sync(() => current))
|
|
156
|
-
current = 'ready'
|
|
157
|
-
return current
|
|
158
|
-
}),
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// client side
|
|
162
|
-
const value = await client.queryWorkflow(
|
|
163
|
-
handle,
|
|
164
|
-
'status',
|
|
165
|
-
temporalCallOptions({
|
|
166
|
-
queryRejectCondition: QueryRejectCondition.NONE,
|
|
167
|
-
}),
|
|
168
|
-
)
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Invoking workflow updates
|
|
172
|
-
|
|
173
|
-
The client surface exposes `workflow.update`, `workflow.awaitUpdate`, `workflow.cancelUpdate`, and `workflow.getUpdateHandle` helpers on the `TemporalWorkflowClient`:
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
const updateResult = await client.workflow.update(handle, {
|
|
177
|
-
updateName: 'setCounter',
|
|
178
|
-
args: [42],
|
|
179
|
-
waitForStage: 'completed', // 'admitted' | 'accepted' | 'completed'
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
if (updateResult.outcome?.status === 'success') {
|
|
183
|
-
console.log('Counter set to', updateResult.outcome.result)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// await a handle later, perhaps from another service
|
|
187
|
-
const handle = client.workflow.getUpdateHandle(handle, updateResult.handle.updateId)
|
|
188
|
-
// defaults to waiting for completion when waitForStage is omitted
|
|
189
|
-
await client.workflow.awaitUpdate(handle)
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Each update call is idempotent (via `updateId`) and accepts the same `temporalCallOptions` as other Workflow APIs so you can override headers, retries, or timeouts per request.
|
|
193
|
-
|
|
194
|
-
## WorkflowService client resilience
|
|
12
|
+
bunx @proompteng/temporal-bun-sdk init hello-worker
|
|
13
|
+
cd hello-worker
|
|
14
|
+
bun install
|
|
195
15
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
-
|
|
200
|
-
|
|
201
|
-
```ts
|
|
202
|
-
import { temporalCallOptions } from '@proompteng/temporal-bun-sdk'
|
|
203
|
-
|
|
204
|
-
await client.queryWorkflow(
|
|
205
|
-
handle,
|
|
206
|
-
'currentState',
|
|
207
|
-
{ kind: 'snapshot' },
|
|
208
|
-
temporalCallOptions({
|
|
209
|
-
timeoutMs: 15_000,
|
|
210
|
-
headers: { 'x-trace-id': traceId },
|
|
211
|
-
}),
|
|
212
|
-
)
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
- **Telemetry-friendly interceptors** – the default interceptor builder injects namespace/identity headers, logs RPC attempts, and records latency/error counters with your configured metrics registry/exporter. Provide `interceptors` when creating a client to append custom tracing or auth middleware.
|
|
216
|
-
- **Memo & search attribute helpers** – `client.memo.encode/decode` and `client.searchAttributes.encode/decode` reuse the client’s `DataConverter`, making it trivial to prepare `Memo`/`SearchAttributes` payloads for raw WorkflowService requests.
|
|
217
|
-
- **TLS validation & handshake errors** – TLS buffers are validated up front (missing files, invalid PEMs, and mismatched cert/key pairs throw `TemporalTlsConfigurationError`). Runtime handshake failures raise `TemporalTlsHandshakeError` with remediation hints (verify CA bundles, check `TEMPORAL_TLS_SERVER_NAME`, or use `TEMPORAL_ALLOW_INSECURE=1` in trusted dev clusters).
|
|
218
|
-
|
|
219
|
-
## Workflow surface
|
|
220
|
-
|
|
221
|
-
Workflow handlers receive a rich context that captures command intents deterministically. Alongside `input` you now get:
|
|
222
|
-
|
|
223
|
-
- `activities.schedule(type, args, options)` – buffers a `SCHEDULE_ACTIVITY_TASK` command. Activity IDs default to `activity-{n}` and inherit the workflow task queue unless overridden.
|
|
224
|
-
- `timers.start({ timeoutMs })` – emits a `START_TIMER` command and returns a handle for bookkeeping.
|
|
225
|
-
- `childWorkflows.start(type, args, options)` – records `START_CHILD_WORKFLOW_EXECUTION` with optional `workflowId`, retry policy, and task queue overrides.
|
|
226
|
-
- `signals.signal(name, args, options)` – queues `SIGNAL_EXTERNAL_WORKFLOW_EXECUTION` for another run or child.
|
|
227
|
-
- `signals.on(handle, handler)` – consumes the next delivery for `handle` and runs `handler` with the decoded payload + metadata (throws `WorkflowBlockedError` when history hasn’t delivered the signal yet).
|
|
228
|
-
- `signals.waitFor(handle)` / `signals.drain(handle)` – await a single or all buffered deliveries for a handle and return the decoded payload plus the originating history metadata (event id, workflow task completion id, and identity).
|
|
229
|
-
- `continueAsNew(options)` – records a `CONTINUE_AS_NEW_WORKFLOW_EXECUTION` command and short-circuits the current run.
|
|
230
|
-
- `determinism.now()` / `determinism.random()` – shims for wall clock time and randomness that log values into the replay ledger so replays must produce identical sequences.
|
|
231
|
-
- `queries.register(handle, resolver)` – exposes a deterministic query handler that the worker will invoke whenever Temporal issues a query task; handlers run under the determinism guard and must not mutate workflow state.
|
|
232
|
-
- `queries.resolve(handle, input)` – synchronously evaluate a registered query inside the workflow (useful for composing queries from smaller resolvers or for unit tests).
|
|
233
|
-
|
|
234
|
-
Signals and queries declare their schema via helper builders so handlers operate on typed payloads. The object form of `defineWorkflow` accepts the handle sets and surfaces them from the workflow definition for reuse:
|
|
235
|
-
|
|
236
|
-
```ts
|
|
237
|
-
const signals = defineWorkflowSignals({
|
|
238
|
-
unblock: Schema.String,
|
|
239
|
-
finish: Schema.Struct({}),
|
|
240
|
-
})
|
|
241
|
-
const queries = defineWorkflowQueries({
|
|
242
|
-
state: {
|
|
243
|
-
input: Schema.Struct({}),
|
|
244
|
-
output: Schema.Struct({ message: Schema.String }),
|
|
245
|
-
},
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
export const signalDrivenWorkflow = defineWorkflow({
|
|
249
|
-
name: 'signalDrivenWorkflow',
|
|
250
|
-
signals,
|
|
251
|
-
queries,
|
|
252
|
-
handler: ({ signals, queries }) =>
|
|
253
|
-
Effect.gen(function* () {
|
|
254
|
-
let current = 'waiting'
|
|
255
|
-
yield* queries.register(queries.state, () => Effect.sync(() => ({ message: current })))
|
|
256
|
-
const delivery = yield* signals.waitFor(signals.unblock)
|
|
257
|
-
current = delivery.payload
|
|
258
|
-
yield* signals.waitFor(signals.finish)
|
|
259
|
-
return current
|
|
260
|
-
}),
|
|
261
|
-
})
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Example:
|
|
265
|
-
|
|
266
|
-
```ts
|
|
267
|
-
import { Effect } from 'effect'
|
|
268
|
-
import * as Schema from 'effect/Schema'
|
|
269
|
-
import { defineWorkflow } from '@proompteng/temporal-bun-sdk/workflow'
|
|
270
|
-
|
|
271
|
-
export const workflows = [
|
|
272
|
-
defineWorkflow('greet', Schema.Array(Schema.String), ({ input, activities, timers, determinism }) => {
|
|
273
|
-
const [name = 'Temporal'] = input
|
|
274
|
-
return Effect.flatMap(activities.schedule('recordGreeting', [name]), () =>
|
|
275
|
-
Effect.flatMap(timers.start({ timeoutMs: 1_000 }), () =>
|
|
276
|
-
Effect.sync(() => {
|
|
277
|
-
const iso = new Date(determinism.now()).toISOString()
|
|
278
|
-
return `Greeting enqueued for ${name} at ${iso}`
|
|
279
|
-
}),
|
|
280
|
-
),
|
|
281
|
-
)
|
|
282
|
-
}),
|
|
283
|
-
]
|
|
16
|
+
cat > .env <<'EOF'
|
|
17
|
+
TEMPORAL_ADDRESS=127.0.0.1:7233
|
|
18
|
+
TEMPORAL_NAMESPACE=default
|
|
19
|
+
TEMPORAL_TASK_QUEUE=hello-bun
|
|
20
|
+
EOF
|
|
284
21
|
```
|
|
285
22
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
## Activity lifecycle
|
|
289
|
-
|
|
290
|
-
Activities run with an ambient `ActivityContext` (available through `currentActivityContext()`) so you can send heartbeats, observe cancellation, and inspect attempt metadata:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
import { currentActivityContext } from '@proompteng/temporal-bun-sdk/worker'
|
|
23
|
+
Start Temporal:
|
|
294
24
|
|
|
295
|
-
export const ingestLargeFile = async (chunks: Iterable<string>) => {
|
|
296
|
-
const ctx = currentActivityContext()
|
|
297
|
-
let processed = 0
|
|
298
|
-
for (const chunk of chunks) {
|
|
299
|
-
// do expensive work
|
|
300
|
-
await writeChunk(chunk)
|
|
301
|
-
processed += 1
|
|
302
|
-
await ctx?.heartbeat({ processed })
|
|
303
|
-
ctx?.throwIfCancelled()
|
|
304
|
-
}
|
|
305
|
-
return processed
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
- `activityContext.heartbeat(...details)` is throttled by `TEMPORAL_ACTIVITY_HEARTBEAT_INTERVAL_MS` and retried with exponential backoff (tunable via `TEMPORAL_ACTIVITY_HEARTBEAT_RPC_TIMEOUT_MS`) so transient RPC failures do not kill the attempt.
|
|
310
|
-
- The latest heartbeat payload automatically flows into cancellation/failure responses so Temporal UI surfaces helpful metadata.
|
|
311
|
-
- WorkerRuntime honours workflow `retry` policies locally: `maximumAttempts`, `initialInterval`, `maximumInterval`, `backoffCoefficient`, and `nonRetryableErrorTypes` all apply before a terminal failure is reported back to the server. Errors flagged as non-retryable (`error.nonRetryable = true` or matching the configured type list) short-circuit the retry loop.
|
|
312
|
-
- Activity attempts that exhaust the policy are reported as `nonRetryable` failures so the Temporal service does not perform redundant retries.
|
|
313
|
-
|
|
314
|
-
## Integration harness
|
|
315
|
-
|
|
316
|
-
The SDK ships an integration harness that wraps the Temporal CLI dev server scripts. It can execute workflows through `temporal workflow execute`/`temporal workflow show`, turn the emitted history into `HistoryEvent[]`, and feed the ingestion pipeline. To use it locally:
|
|
317
|
-
|
|
318
|
-
1. Install the [Temporal CLI](https://github.com/temporalio/cli) and ensure the `temporal` binary is on your `PATH` (or set `TEMPORAL_CLI_PATH`).
|
|
319
|
-
2. Start the dev server with `bun scripts/start-temporal-cli.ts` or let the harness do it for you.
|
|
320
|
-
3. Run the integration suite:
|
|
321
|
-
```bash
|
|
322
|
-
bun test tests/integration/history-replay.test.ts
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
If the CLI is missing the tests log a skip and continue so the rest of the suite still passes. The harness lives in `tests/integration/harness.ts` and exposes helpers for bespoke scenarios if you need to extend coverage.
|
|
326
|
-
|
|
327
|
-
## Worker load & performance tests
|
|
328
|
-
|
|
329
|
-
The TBS-003 load harness lives under `tests/integration/load/**` and uses CPU-heavy workflows, I/O-heavy activities, and the worker runtime's file metrics exporter to guard throughput, poll latency, and sticky cache hit ratios. There are two supported entry points:
|
|
330
|
-
|
|
331
|
-
1. **Bun test suite** (reuses the Temporal CLI harness):
|
|
332
|
-
```bash
|
|
333
|
-
cd packages/temporal-bun-sdk && TEMPORAL_INTEGRATION_TESTS=1 bun test tests/integration/worker-load.test.ts
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
````
|
|
337
|
-
2. **Developer-friendly CLI runner** (wraps the same scenario without `bun test`):
|
|
338
25
|
```bash
|
|
339
|
-
|
|
340
|
-
````
|
|
341
|
-
|
|
342
|
-
Both commands start the Temporal CLI dev server (unless `TEMPORAL_TEST_SERVER=1` is set), run the worker load scenario, and write artefacts to `.artifacts/worker-load/`:
|
|
343
|
-
|
|
344
|
-
- `metrics.jsonl` – raw JSONL stream from the file metrics exporter.
|
|
345
|
-
- `report.json` – human-readable summary (config, peak concurrency, throughput, latency percentiles, sticky cache hit ratio).
|
|
346
|
-
- `temporal-cli.log` – CLI stdout/stderr captured during the run.
|
|
347
|
-
|
|
348
|
-
The default profile now submits 36 workflows (roughly a 2:1 mix of CPU-heavy to IO-heavy types) with worker poller concurrency of 10/14 (workflow/activity) and a 100-second workflow deadline. That keeps the CI run comfortably under two minutes while actually saturating the scheduler. The Bun test wrapper adds a 15-second safety buffer on top of `TEMPORAL_LOAD_TEST_TIMEOUT_MS + TEMPORAL_LOAD_TEST_METRICS_FLUSH_MS`, so keep that env var in sync with any threshold tweaks.
|
|
349
|
-
|
|
350
|
-
### Tuning the load harness
|
|
351
|
-
|
|
352
|
-
The harness is driven by env vars (also exposed as CLI flags via `--workflows`, `--workflow-concurrency`, `--activity-concurrency`, and `--timeout` when calling `bun run test:load`). Key overrides:
|
|
353
|
-
|
|
354
|
-
- `TEMPORAL_LOAD_TEST_WORKFLOWS` – total workflows to submit.
|
|
355
|
-
- `TEMPORAL_LOAD_TEST_WORKFLOW_CONCURRENCY` / `TEMPORAL_LOAD_TEST_ACTIVITY_CONCURRENCY` – poller counts / concurrency targets.
|
|
356
|
-
- `TEMPORAL_LOAD_TEST_WORKFLOW_POLL_P95_MS` / `TEMPORAL_LOAD_TEST_ACTIVITY_POLL_P95_MS` – max allowed P95 poll latency.
|
|
357
|
-
- `TEMPORAL_LOAD_TEST_STICKY_MIN_RATIO` – minimum sticky cache hit ratio before the suite fails.
|
|
358
|
-
- `TEMPORAL_LOAD_TEST_TIMEOUT_MS` – overall completion timeout per batch.
|
|
359
|
-
- `TEMPORAL_LOAD_TEST_ACTIVITY_BURSTS`, `TEMPORAL_LOAD_TEST_COMPUTE_ITERATIONS`, `TEMPORAL_LOAD_TEST_ACTIVITY_DELAY_MS`, `TEMPORAL_LOAD_TEST_ACTIVITY_PAYLOAD_BYTES` – fine-tune CPU/IO stress.
|
|
360
|
-
|
|
361
|
-
CI runs `cd packages/temporal-bun-sdk && bun run test:load` inside `.github/workflows/temporal-bun-sdk.yml` and uploads `.artifacts/worker-load/{metrics.jsonl,report.json,temporal-cli.log}` so reviewers can inspect regressions. When running locally on slower hardware, lower the concurrency knobs or bump the latency thresholds to avoid spurious failures.
|
|
362
|
-
|
|
363
|
-
## Effect service integration
|
|
364
|
-
|
|
365
|
-
```ts
|
|
366
|
-
import { Effect, Layer } from 'effect'
|
|
367
|
-
import { WorkerService } from '@proompteng/temporal-bun-sdk/worker'
|
|
368
|
-
|
|
369
|
-
const workerLayer = WorkerService({
|
|
370
|
-
workflowsPath: new URL('./workflows/index.ts', import.meta.url).pathname,
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
const program = Effect.gen(function* () {
|
|
374
|
-
const { run } = yield* WorkerService
|
|
375
|
-
yield* run
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
await Effect.provide(program, workerLayer)
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
Define workflows with the Effect runtime:
|
|
382
|
-
|
|
383
|
-
```ts
|
|
384
|
-
import { Effect } from 'effect'
|
|
385
|
-
import { defineWorkflow } from '@proompteng/temporal-bun-sdk/workflow'
|
|
386
|
-
|
|
387
|
-
export const workflows = [
|
|
388
|
-
defineWorkflow('helloWorkflow', ({ input }) =>
|
|
389
|
-
Effect.sync(() => {
|
|
390
|
-
const [name] = Array.isArray(input) ? input : []
|
|
391
|
-
return typeof name === 'string' && name.length > 0 ? `Hello, ${name}!` : 'Hello, Temporal!'
|
|
392
|
-
}),
|
|
393
|
-
),
|
|
394
|
-
]
|
|
26
|
+
temporal server start-dev --headless
|
|
395
27
|
```
|
|
396
28
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
```ts
|
|
400
|
-
import { createWorker } from '@proompteng/temporal-bun-sdk/worker'
|
|
401
|
-
import * as activities from './activities'
|
|
402
|
-
|
|
403
|
-
const { worker } = await createWorker({
|
|
404
|
-
activities,
|
|
405
|
-
workflowsPath: new URL('./workflows/index.ts', import.meta.url).pathname,
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
await worker.run()
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### Layer-based worker bootstrap
|
|
412
|
-
|
|
413
|
-
```ts
|
|
414
|
-
import { runWorkerApp } from '@proompteng/temporal-bun-sdk/runtime/worker-app'
|
|
415
|
-
import * as activities from './activities'
|
|
416
|
-
|
|
417
|
-
await runWorkerApp({
|
|
418
|
-
config: {
|
|
419
|
-
defaults: {
|
|
420
|
-
address: '127.0.0.1:7233',
|
|
421
|
-
namespace: 'staging',
|
|
422
|
-
taskQueue: 'staging-worker',
|
|
423
|
-
},
|
|
424
|
-
},
|
|
425
|
-
worker: {
|
|
426
|
-
activities,
|
|
427
|
-
workflowsPath: new URL('./workflows/index.ts', import.meta.url).pathname,
|
|
428
|
-
},
|
|
429
|
-
})
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
### CLI layer helpers
|
|
433
|
-
|
|
434
|
-
Use `runTemporalCliEffect` to run ad-hoc Effect programs (doctor checks, replay helpers,
|
|
435
|
-
tests) against the same layered config/logging/workflow stack without hand-loading env vars:
|
|
436
|
-
|
|
437
|
-
```ts
|
|
438
|
-
import { Effect } from 'effect'
|
|
439
|
-
import { runTemporalCliEffect } from '@proompteng/temporal-bun-sdk/runtime/cli-layer'
|
|
440
|
-
import { TemporalConfigService } from '@proompteng/temporal-bun-sdk/runtime/effect-layers'
|
|
441
|
-
|
|
442
|
-
await runTemporalCliEffect(
|
|
443
|
-
Effect.gen(function* () {
|
|
444
|
-
const config = yield* TemporalConfigService
|
|
445
|
-
console.log('Active namespace:', config.namespace)
|
|
446
|
-
}),
|
|
447
|
-
{
|
|
448
|
-
config: {
|
|
449
|
-
defaults: {
|
|
450
|
-
address: '127.0.0.1:7233',
|
|
451
|
-
namespace: 'cli-layer',
|
|
452
|
-
taskQueue: 'cli-layer',
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
)
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
## CLI commands
|
|
460
|
-
|
|
461
|
-
```
|
|
462
|
-
temporal-bun init [directory] Scaffold a new worker project
|
|
463
|
-
temporal-bun docker-build Build a Docker image for the current project
|
|
464
|
-
temporal-bun doctor Validate config + observability sinks
|
|
465
|
-
temporal-bun replay Replay workflow histories and diff determinism
|
|
466
|
-
temporal-bun skill Install/list bundled agent skills
|
|
467
|
-
temporal-bun help Show available commands
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
## Bundled agent skills
|
|
471
|
-
|
|
472
|
-
`@proompteng/temporal-bun-sdk` ships with a packaged Temporal skill bundle under `skills/` plus CLI helpers:
|
|
29
|
+
Start the worker:
|
|
473
30
|
|
|
474
31
|
```bash
|
|
475
|
-
|
|
476
|
-
bunx temporal-bun skill list
|
|
477
|
-
|
|
478
|
-
# install default bundled skill into Codex skills directory
|
|
479
|
-
bunx temporal-bun skill install temporal
|
|
480
|
-
|
|
481
|
-
# install into a custom directory
|
|
482
|
-
bunx temporal-bun skill install temporal --to /tmp/my-skills
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
For direct scripting, use the exported API from `@proompteng/temporal-bun-sdk/skills`:
|
|
486
|
-
|
|
487
|
-
```ts
|
|
488
|
-
import { installBundledSkill, listBundledSkills } from '@proompteng/temporal-bun-sdk/skills'
|
|
489
|
-
|
|
490
|
-
const skills = await listBundledSkills()
|
|
491
|
-
await installBundledSkill({
|
|
492
|
-
skillName: skills[0]?.name ?? 'temporal',
|
|
493
|
-
destinationDir: '/tmp/skills',
|
|
494
|
-
force: true,
|
|
495
|
-
})
|
|
32
|
+
bun run dev
|
|
496
33
|
```
|
|
497
34
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
`temporal-bun replay` ingests workflow histories from JSON files or live
|
|
501
|
-
executions, reuses the existing determinism ingestion pipeline, and exits with a
|
|
502
|
-
non-zero status when mismatches surface (`0` success, `2` nondeterminism, `1`
|
|
503
|
-
configuration or IO failures).
|
|
504
|
-
|
|
505
|
-
- **History files** – accept either raw `temporal workflow show --history --output
|
|
506
|
-
json` output or fixture-style envelopes containing `history` + `info`.
|
|
507
|
-
- **Live executions** – pass `--execution <workflowId/runId>` and the command
|
|
508
|
-
shells out to the Temporal CLI (`--source cli`) or the WorkflowService RPC API
|
|
509
|
-
(`--source service`). `--source auto` (default) tries the CLI first and falls
|
|
510
|
-
back to WorkflowService when the binary is missing.
|
|
511
|
-
- **Observability** – the command composes `runTemporalCliEffect` layers (config,
|
|
512
|
-
observability, WorkflowService) before running and increments
|
|
513
|
-
`temporal_bun_replay_runs_total` / `temporal_bun_replay_mismatches_total`.
|
|
514
|
-
- **Troubleshooting** – surface the mismatching command index, event ids, and
|
|
515
|
-
workflow-task metadata on stdout plus a JSON summary when `--json` is set.
|
|
516
|
-
|
|
517
|
-
Examples:
|
|
35
|
+
Start a workflow in another shell:
|
|
518
36
|
|
|
519
37
|
```bash
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
--
|
|
523
|
-
--
|
|
524
|
-
--json
|
|
525
|
-
|
|
526
|
-
# Diff a live execution using the Temporal CLI harness
|
|
527
|
-
TEMPORAL_ADDRESS=127.0.0.1:7233 TEMPORAL_NAMESPACE=temporal-bun-integration \
|
|
528
|
-
bunx temporal-bun replay \
|
|
529
|
-
--execution workflow-id/run-id \
|
|
530
|
-
--workflow-type integrationWorkflow \
|
|
531
|
-
--namespace temporal-bun-integration \
|
|
532
|
-
--source cli
|
|
533
|
-
|
|
534
|
-
# Force WorkflowService RPCs (no CLI installed)
|
|
535
|
-
bunx temporal-bun replay \
|
|
536
|
-
--execution workflow-id/run-id \
|
|
537
|
-
--workflow-type integrationWorkflow \
|
|
538
|
-
--namespace temporal-bun-integration \
|
|
539
|
-
--source service
|
|
38
|
+
temporal workflow start \
|
|
39
|
+
--task-queue hello-bun \
|
|
40
|
+
--type helloWorkflow \
|
|
41
|
+
--input '"Codex"'
|
|
540
42
|
```
|
|
541
43
|
|
|
542
|
-
|
|
543
|
-
your `PATH`. TLS, API key, and namespace overrides rely on the same environment
|
|
544
|
-
variables consumed by worker/client code (`TEMPORAL_ADDRESS`,
|
|
545
|
-
`TEMPORAL_NAMESPACE`, `TEMPORAL_TLS_*`, etc.).
|
|
546
|
-
|
|
547
|
-
## Data conversion
|
|
548
|
-
|
|
549
|
-
`createDefaultDataConverter()` encodes payloads as JSON. Implement `DataConverter` if you need a custom codec or encryption layer:
|
|
550
|
-
|
|
551
|
-
```ts
|
|
552
|
-
import { createDefaultDataConverter } from '@proompteng/temporal-bun-sdk/common/payloads'
|
|
44
|
+
## Add to an existing Bun project
|
|
553
45
|
|
|
554
|
-
|
|
46
|
+
```bash
|
|
47
|
+
bun add @proompteng/temporal-bun-sdk
|
|
555
48
|
```
|
|
556
49
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
50
|
+
## Strict mode
|
|
51
|
+
|
|
52
|
+
The generated worker uses `workflowGuards: 'warn'` so local setup works with
|
|
53
|
+
`temporal server start-dev`.
|
|
54
|
+
|
|
55
|
+
If you want strict mode, set `workflowGuards: 'strict'` in your worker and
|
|
56
|
+
configure worker versioning and build IDs.
|
|
57
|
+
|
|
58
|
+
## What is included
|
|
59
|
+
|
|
60
|
+
- Bun worker and client runtime
|
|
61
|
+
- Config loader for local, self-hosted, and Temporal Cloud setups
|
|
62
|
+
- TLS and API key support
|
|
63
|
+
- Docker build helper
|
|
64
|
+
- Replay tooling
|
|
65
|
+
- `temporal-bun` CLI for scaffolding and diagnostics
|
|
66
|
+
|
|
67
|
+
## Production readiness
|
|
68
|
+
|
|
69
|
+
This package is a Bun-native Temporal worker/client SDK, not a wrapper around the
|
|
70
|
+
official Node.js worker runtime. The worker path does not depend on
|
|
71
|
+
`@temporalio/worker`, Node-API native modules, `process.dlopen()`, or
|
|
72
|
+
`worker_threads`.
|
|
73
|
+
|
|
74
|
+
Release and deployment gates cover the production concerns that matter for
|
|
75
|
+
Temporal workers:
|
|
76
|
+
|
|
77
|
+
- deterministic workflow guards and real-history replay fixtures,
|
|
78
|
+
- activity heartbeats, retries, cancellation, and failure conversion,
|
|
79
|
+
- sticky-cache healing, build-id routing, graceful shutdown, and worker metrics,
|
|
80
|
+
- Temporal CLI integration tests and worker load/perf checks in CI,
|
|
81
|
+
- `bun run verify:production` asserts that published assets stay pure
|
|
82
|
+
Bun/TypeScript with no native bridge or official Node worker dependency path,
|
|
83
|
+
- `dist/production-readiness.json` and `dist/agent-readiness.json` are generated
|
|
84
|
+
before packing so agents can inspect release evidence mechanically,
|
|
85
|
+
- npm trusted publishing with provenance,
|
|
86
|
+
- deployed usage from `services/jangar` through `createWorker()` and
|
|
87
|
+
`createTemporalClient()`.
|
|
88
|
+
|
|
89
|
+
The remaining tradeoff is support ownership: this is a community/company SDK,
|
|
90
|
+
not the official Temporal TypeScript SDK. Choose it when you want Bun as the
|
|
91
|
+
worker runtime and are willing to validate your workflows with replay and load
|
|
92
|
+
gates. Choose the official SDK when the requirement is official Temporal support
|
|
93
|
+
on Node.js.
|
|
94
|
+
|
|
95
|
+
## Docs
|
|
96
|
+
|
|
97
|
+
- Main guide: <https://docs.proompteng.ai/docs/temporal-bun-sdk>
|
|
98
|
+
- Temporal Cloud and TLS: <https://docs.proompteng.ai/docs/temporal-bun-sdk-cloud-tls>
|
|
99
|
+
- Bun SDK vs official TypeScript SDK: <https://docs.proompteng.ai/docs/temporal-bun-sdk-comparison>
|
|
100
|
+
- Production readiness plan: `docs/production-readiness-implementation-plan.md`
|
|
101
|
+
- Feature matrix: `docs/feature-matrix.md`
|
|
102
|
+
- Support policy: `docs/support-policy.md`
|
|
103
|
+
- Agent adoption guide: `docs/agent-adoption-guide.md`
|
|
104
|
+
- Example app: <https://github.com/proompteng/lab/tree/main/packages/temporal-bun-sdk-example>
|
|
105
|
+
- Issues: <https://github.com/proompteng/lab/issues>
|
|
570
106
|
|
|
571
107
|
## License
|
|
572
108
|
|