@usehelical/workflows 0.0.1-alpha.15 → 0.0.1-alpha.16
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 +661 -0
- package/README.md +0 -111
- package/dist/api.d.ts +45 -0
- package/dist/{workflows.js → api.js} +27 -152
- package/dist/api.js.map +1 -0
- package/dist/chunk-HRDI7EOY.js +441 -0
- package/dist/chunk-HRDI7EOY.js.map +1 -0
- package/dist/index.d.ts +165 -17
- package/dist/index.js +633 -178
- package/dist/index.js.map +1 -1
- package/dist/state-DygxhGtl.d.ts +49 -0
- package/package.json +5 -4
- package/dist/chunk-TZSPUZQ3.js +0 -759
- package/dist/chunk-TZSPUZQ3.js.map +0 -1
- package/dist/run-BDlJdILv.d.ts +0 -77
- package/dist/workflows.d.ts +0 -66
- package/dist/workflows.js.map +0 -1
package/README.md
CHANGED
|
@@ -10,114 +10,3 @@ Simple, typesafe durable workflows without bundler magic
|
|
|
10
10
|
- simple & typesafe api
|
|
11
11
|
- minimal deployment complexity (just postgres)
|
|
12
12
|
- low latency (making it suitable for user-facing ai workflows)
|
|
13
|
-
|
|
14
|
-
# Getting started
|
|
15
|
-
|
|
16
|
-
## Writing workflows
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import * as wf from 'helical-workflows/workflow';
|
|
20
|
-
|
|
21
|
-
async function indexDocument(url: string) {
|
|
22
|
-
// idempotent retryable durable steps
|
|
23
|
-
const document = await wf.runStep(async () => fetchDocument(url), { maxRetries: 5 });
|
|
24
|
-
const parsedDocument = await wf.runStep(async () => parseDocument(document));
|
|
25
|
-
await wf.runStep(async () => upsertIntoDb(parsedDocument));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const indexDocumentWorkflow = defineWorkflow(indexDocument);
|
|
29
|
-
|
|
30
|
-
const indexingQueue = defineQueue();
|
|
31
|
-
|
|
32
|
-
async function processDocumentBatch(urls: string[]) {
|
|
33
|
-
const handles = [];
|
|
34
|
-
|
|
35
|
-
// enqueue all documents for indexing
|
|
36
|
-
for (const url of urls) {
|
|
37
|
-
const runHandle = await wf.enqueueWorkflow(indexingQueue, indexDocument, [url]);
|
|
38
|
-
handles.push(runHandle);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let successfulCount = 0;
|
|
42
|
-
let failedCount = 0;
|
|
43
|
-
|
|
44
|
-
// wait for all documents to be indexed
|
|
45
|
-
for (const handle of handles) {
|
|
46
|
-
try {
|
|
47
|
-
await handle.result();
|
|
48
|
-
successfulCount++;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
failedCount++;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { successfulCount, failedCount };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const processDocumentBatchWorkflow = defineWorkflow(processDocumentBatch);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Running a workflow
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
// create instance and register workflows and queues
|
|
64
|
-
const { runWorkflow } = createInstance({
|
|
65
|
-
workflows: { indexDocumentWorkflow, processDocumentBatchWorkflow },
|
|
66
|
-
queues: { indexingQueue },
|
|
67
|
-
options: { connectionString: 'dummy' },
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const urls = ['a', 'b', 'c'];
|
|
71
|
-
|
|
72
|
-
const run = await runWorkflow(processDocumentBatchWorkflow, urls);
|
|
73
|
-
|
|
74
|
-
console.log(await run.result());
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
# Development roadmap
|
|
78
|
-
|
|
79
|
-
## Messages
|
|
80
|
-
|
|
81
|
-
Messages are a way of communicating with a workflow from the outside. This can be useful for human-in-the-loop workflows or for other signals. Messages are persisted in the database and atomically consumed adhering to once-and-only-once semantics.
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
const approvalMessage = defineMessage<ApprovalMessage>('approval');
|
|
85
|
-
|
|
86
|
-
async function myWorkflowFunction() {
|
|
87
|
-
await wf.runStep(() => stageForApproval());
|
|
88
|
-
await wf.receiveMessage(approvalMessage);
|
|
89
|
-
await wf.runStep(() => publish());
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## State
|
|
94
|
-
|
|
95
|
-
Workflow state is a way for workflows to expose state to the outside. The benefit of this state approach in comparison to other request handler approaches like in Temporal is that the workflow doesn’t have to be executing in order for the state to be retrievable. This makes it possible to retrieve state even if a workflow is already cancelled or finished.
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
async function myWorkflowFunction() {
|
|
99
|
-
await wf.setState({ progress: 50 });
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Schedules
|
|
104
|
-
|
|
105
|
-
Allow a workflow to be scheduled…
|
|
106
|
-
|
|
107
|
-
## Streams
|
|
108
|
-
|
|
109
|
-
Streams allow for publishing streaming data from workflows to the outside the primary use case for this is realtime streaming of LLM responses. I want to take a different approach to other workflow systems here and treat streams as side effects inside of workflows and not as durable outputs. Since LLM streaming responses can be quite quick persisting the token stream into the database is costly. My hypothesis is that stream persistence is not needed since resuming LLM streaming responses require [special handling](<[https://platform.claude.com/docs/en/build-with-claude/streaming#error-recovery](https://platform.claude.com/docs/en/build-with-claude/streaming#error-recovery)>) and in most cases it makes more sense to just regenerate the response. Usually non-idempotent side effects in workflows are prohibited to achieve durability guarantees. But in this case the correctness of the workflow doesn’t depend on the stream the stream is just a user experience optimisation.
|
|
110
|
-
|
|
111
|
-
## Workflow suspension
|
|
112
|
-
|
|
113
|
-
When implementing very long running workflows (days, months…) the workflow would still be the whole time in memory idling waiting for instance for a message or for a sleep() event. Workflow suspension would allow for workflows to be suspended when waiting and then replayed and continued when the event arrives. This happens at the cost of latency (since the workflow has to be replayed to reach its original state again after has been suspended) which is why I want to make this an optional feature.
|
|
114
|
-
|
|
115
|
-
## Workflow version management
|
|
116
|
-
|
|
117
|
-
A tricky topic. In production systems it is possible that workflows with old versions are still Pending or are Queued. I don’t know yet what is the best way to handle this but I’m leaning towards workflow versions that can be manually specified using a versionId or could be auto generated by creating a hash from the workflow code.
|
|
118
|
-
|
|
119
|
-
## Chaos tests
|
|
120
|
-
|
|
121
|
-
## OTEL Integration
|
|
122
|
-
|
|
123
|
-
## CLI & Observability UI
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { S as StateDefinition, M as MessageDefinition } from './state-DygxhGtl.js';
|
|
2
|
+
export { Q as QueueDefinition, b as QueueOptions, c as QueueRateLimit, d as QueueSignature, R as RunStatus, T as TERMINAL_STATES, W as WorkflowDefinition, e as WorkflowFunction, a as WorkflowSignature, f as defineMessage, g as defineQueue, h as defineState, i as defineWorkflow } from './state-DygxhGtl.js';
|
|
3
|
+
|
|
4
|
+
type RetryConfig = {
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
retryDelay?: number;
|
|
7
|
+
backOffRate?: number;
|
|
8
|
+
};
|
|
9
|
+
type RunStepOptions = RetryConfig & {
|
|
10
|
+
name?: string;
|
|
11
|
+
};
|
|
12
|
+
declare function runStep<TReturn>(stepFn: () => Promise<TReturn>, options?: RunStepOptions): Promise<TReturn | (TReturn extends void ? void : TReturn)>;
|
|
13
|
+
declare function executeStepWithRetries<TReturn>(stepName: string, fn: () => Promise<TReturn>, retryConfig: RetryConfig): Promise<TReturn>;
|
|
14
|
+
|
|
15
|
+
declare function setState<T = unknown>(state: StateDefinition<T> | string, value: T): Promise<void>;
|
|
16
|
+
|
|
17
|
+
type ReceiveMessageOptions = {
|
|
18
|
+
timeout?: number;
|
|
19
|
+
};
|
|
20
|
+
declare function receiveMessage<T>(message: MessageDefinition<T> | string, options?: ReceiveMessageOptions): Promise<T>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generates a stable, unique step ID based on the current run ID and sequence ID.
|
|
24
|
+
* This ID is deterministic and will remain the same across retries, making it
|
|
25
|
+
* suitable for use as an idempotency key with third-party systems.
|
|
26
|
+
*
|
|
27
|
+
* The step ID is a SHA-256 hash of the run ID and sequence ID, formatted as a
|
|
28
|
+
* hex string for easy use with external APIs.
|
|
29
|
+
*
|
|
30
|
+
* @returns A unique, stable identifier for the current step execution
|
|
31
|
+
*/
|
|
32
|
+
declare function getStepId(): string;
|
|
33
|
+
|
|
34
|
+
declare function getAbortSignal(): AbortSignal;
|
|
35
|
+
|
|
36
|
+
declare function getRunId(): string;
|
|
37
|
+
|
|
38
|
+
declare function sleep(ms: number): Promise<void>;
|
|
39
|
+
declare function cancellableSleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
40
|
+
|
|
41
|
+
declare function randomUUID(): Promise<string>;
|
|
42
|
+
|
|
43
|
+
declare function now(): Promise<number>;
|
|
44
|
+
|
|
45
|
+
export { MessageDefinition, type ReceiveMessageOptions, type RetryConfig, StateDefinition, cancellableSleep, executeStepWithRetries, getAbortSignal, getRunId, getStepId, now, randomUUID, receiveMessage, runStep, setState, sleep };
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { getExecutionContext, returnOrThrowOperationResult, executeAndRecordOperation, FatalError, MaxRetriesExceededError, sleep,
|
|
2
|
-
export {
|
|
1
|
+
import { getExecutionContext, returnOrThrowOperationResult, executeAndRecordOperation, FatalError, MaxRetriesExceededError, sleep, withDbRetry, serialize, OperationTimedOutError, serializeError, deserialize, RunCancelledError } from './chunk-HRDI7EOY.js';
|
|
2
|
+
export { TERMINAL_STATES, defineWorkflow } from './chunk-HRDI7EOY.js';
|
|
3
3
|
import { sql } from 'kysely';
|
|
4
|
-
import
|
|
4
|
+
import crypto, { createHash } from 'crypto';
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// src/api/message.ts
|
|
7
7
|
function defineMessage(name) {
|
|
8
8
|
return { name };
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
function defineQueue(options = {}) {
|
|
13
|
-
return
|
|
11
|
+
// src/api/queue.ts
|
|
12
|
+
function defineQueue(name, options = {}) {
|
|
13
|
+
return { name, ...options };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// src/api/state.ts
|
|
17
17
|
function defineState(name) {
|
|
18
18
|
return {
|
|
19
19
|
name
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// src/api/steps/run-step.ts
|
|
24
24
|
async function runStep(stepFn, options = {}) {
|
|
25
25
|
const { maxRetries, retryDelay, backOffRate } = options;
|
|
26
26
|
const { operationManager } = getExecutionContext();
|
|
@@ -33,6 +33,11 @@ async function runStep(stepFn, options = {}) {
|
|
|
33
33
|
return await executeStepWithRetries(stepName, stepFn, { maxRetries, retryDelay, backOffRate });
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
+
var ErrorThatShouldNeverHappen = class extends Error {
|
|
37
|
+
constructor() {
|
|
38
|
+
super("This error should never happen");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
36
41
|
async function executeStepWithRetries(stepName, fn, retryConfig) {
|
|
37
42
|
const maxRetries = retryConfig.maxRetries ?? 0;
|
|
38
43
|
const retryDelay = retryConfig.retryDelay ?? 0;
|
|
@@ -57,10 +62,10 @@ async function executeStepWithRetries(stepName, fn, retryConfig) {
|
|
|
57
62
|
await sleep(delay);
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
|
-
throw new ErrorThatShouldNeverHappen(
|
|
65
|
+
throw new ErrorThatShouldNeverHappen();
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
//
|
|
68
|
+
// src/internal/db/commands/insert-state.ts
|
|
64
69
|
async function insertState(tx, options) {
|
|
65
70
|
await tx.insertInto("state").values({
|
|
66
71
|
run_id: options.runId,
|
|
@@ -79,7 +84,7 @@ async function insertState(tx, options) {
|
|
|
79
84
|
}).execute();
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
//
|
|
87
|
+
// src/api/steps/set-state.ts
|
|
83
88
|
var SET_STATE_OPERATION_NAME = "workflow::state::set";
|
|
84
89
|
async function setState(state, value) {
|
|
85
90
|
const stateKey = typeof state === "string" ? state : state.name;
|
|
@@ -116,7 +121,7 @@ async function readAndDeleteMessage(tx, runId, messageType) {
|
|
|
116
121
|
} : void 0;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
//
|
|
124
|
+
// src/internal/with-durable-deadline.ts
|
|
120
125
|
async function withDurableDeadline(timeoutMs, operationName, fn) {
|
|
121
126
|
if (!timeoutMs) {
|
|
122
127
|
return await fn(void 0);
|
|
@@ -135,7 +140,7 @@ async function withDurableDeadline(timeoutMs, operationName, fn) {
|
|
|
135
140
|
return await fn(deadlineMs);
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
//
|
|
143
|
+
// src/api/steps/receive-message.ts
|
|
139
144
|
var RECEIVE_MESSAGE_OPERATION_NAME = "receiveMessage";
|
|
140
145
|
var RECEIVE_MESSAGE_DURABLE_DEADLINE_OPERATION_NAME = "receiveMessageDurableDeadline";
|
|
141
146
|
var MessageNotAvailableError = class extends Error {
|
|
@@ -229,18 +234,18 @@ function getStepId() {
|
|
|
229
234
|
return hash.digest("hex");
|
|
230
235
|
}
|
|
231
236
|
|
|
232
|
-
//
|
|
237
|
+
// src/api/steps/helpers/get-abort-signal.ts
|
|
233
238
|
function getAbortSignal() {
|
|
234
239
|
return getExecutionContext().abortSignal;
|
|
235
240
|
}
|
|
236
241
|
|
|
237
|
-
//
|
|
242
|
+
// src/api/steps/helpers/get-run-id.ts
|
|
238
243
|
function getRunId() {
|
|
239
244
|
const { runId } = getExecutionContext();
|
|
240
245
|
return runId;
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
//
|
|
248
|
+
// src/api/steps/sleep.ts
|
|
244
249
|
var SLEEP_OPERATION_NAME = "_helical::sleep";
|
|
245
250
|
async function sleep2(ms) {
|
|
246
251
|
const { abortSignal } = getExecutionContext();
|
|
@@ -281,111 +286,12 @@ async function randomUUID() {
|
|
|
281
286
|
return existingResult.result;
|
|
282
287
|
} else {
|
|
283
288
|
return await executeAndRecordOperation(operationManager, "randomUUID", async () => {
|
|
284
|
-
return
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
async function runWorkflow(wf, args = [], options = {}) {
|
|
289
|
-
const ctx = getExecutionContext();
|
|
290
|
-
const { operationManager, runPath, workflowRegistry, db, executorId } = ctx;
|
|
291
|
-
const workflow = typeof wf === "string" ? workflowRegistry.getByName(wf) : workflowRegistry.getByWorkflowDefinition(wf);
|
|
292
|
-
if (!workflow) {
|
|
293
|
-
throw new WorkflowNotFoundError("Workflow not found");
|
|
294
|
-
}
|
|
295
|
-
const op = operationManager.getOperationResult();
|
|
296
|
-
if (op) {
|
|
297
|
-
if (op.error) {
|
|
298
|
-
throw deserializeError(op.error);
|
|
299
|
-
}
|
|
300
|
-
const newRun2 = deserialize(op.result);
|
|
301
|
-
return createRunHandle(ctx, newRun2.runId);
|
|
302
|
-
}
|
|
303
|
-
const newRun = await executeAndRecordOperation(operationManager, "runWorkflow", async () => {
|
|
304
|
-
const newRunId = options.id ?? crypto2.randomUUID();
|
|
305
|
-
const newRun2 = {
|
|
306
|
-
runId: newRunId,
|
|
307
|
-
runPath: [...runPath, newRunId],
|
|
308
|
-
workflowName: workflow.name
|
|
309
|
-
};
|
|
310
|
-
withDbRetry(async () => {
|
|
311
|
-
return await insertPendingRun(db, {
|
|
312
|
-
...newRun2,
|
|
313
|
-
path: newRun2.runPath,
|
|
314
|
-
inputs: serialize(args),
|
|
315
|
-
executorId
|
|
316
|
-
});
|
|
289
|
+
return crypto.randomUUID();
|
|
317
290
|
});
|
|
318
|
-
return newRun2;
|
|
319
|
-
});
|
|
320
|
-
await executeWorkflow(ctx, {
|
|
321
|
-
runId: newRun.runId,
|
|
322
|
-
runPath: newRun.runPath,
|
|
323
|
-
workflowName: newRun.workflowName,
|
|
324
|
-
fn: workflow.fn,
|
|
325
|
-
args,
|
|
326
|
-
options
|
|
327
|
-
});
|
|
328
|
-
return createRunHandle(ctx, newRun.runId);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// core/steps/send-message.ts
|
|
332
|
-
var SEND_MESSAGE_OPERATION_NAME = "workflow::message::send";
|
|
333
|
-
async function sendMessage(target, name, data) {
|
|
334
|
-
const { operationManager, db } = getExecutionContext();
|
|
335
|
-
const destinationWorkflowId = typeof target === "string" ? target : target.id;
|
|
336
|
-
const messageType = typeof name === "string" ? name : name.name;
|
|
337
|
-
const prevOp = operationManager.getOperationResult();
|
|
338
|
-
if (prevOp) {
|
|
339
|
-
return returnOrThrowOperationResult(prevOp);
|
|
340
|
-
}
|
|
341
|
-
const seqId = operationManager.reserveSequenceId();
|
|
342
|
-
const serializedData = serialize(data);
|
|
343
|
-
await withDbRetry(async () => {
|
|
344
|
-
return await db.transaction().execute(async (tx) => {
|
|
345
|
-
await insertMessage(tx, { destinationWorkflowId, messageType, data: serializedData });
|
|
346
|
-
await operationManager.recordResult(SEND_MESSAGE_OPERATION_NAME, seqId, null, tx);
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// core/steps/queue-workflow.ts
|
|
352
|
-
async function queueWorkflow(queue, wf, args, options) {
|
|
353
|
-
const ctx = getExecutionContext();
|
|
354
|
-
const { db, operationManager, queueRegistry, workflowRegistry, runPath, executorId } = ctx;
|
|
355
|
-
const workflowName = typeof wf === "string" ? wf : workflowRegistry.getByWorkflowDefinition(wf)?.name;
|
|
356
|
-
if (!workflowName) {
|
|
357
|
-
throw new WorkflowNotFoundError("Workflow name not specified");
|
|
358
|
-
}
|
|
359
|
-
const queueName = typeof queue === "string" ? queue : queueRegistry.getByQueueEntry(queue)?.name;
|
|
360
|
-
if (!queueName) {
|
|
361
|
-
throw new QueueNotFoundError("Queue name not specified");
|
|
362
291
|
}
|
|
363
|
-
const op = operationManager.getOperationResult();
|
|
364
|
-
if (op) {
|
|
365
|
-
if (op.error) {
|
|
366
|
-
throw deserializeError(op.error);
|
|
367
|
-
}
|
|
368
|
-
const newRunId2 = deserialize(op.result);
|
|
369
|
-
return createRunHandle(ctx, newRunId2);
|
|
370
|
-
}
|
|
371
|
-
const newRunId = await executeAndRecordOperation(operationManager, "queueWorkflow", async () => {
|
|
372
|
-
const newRunId2 = options?.id ?? crypto.randomUUID();
|
|
373
|
-
const { runId } = await enqueueRun(db, {
|
|
374
|
-
runId: newRunId2,
|
|
375
|
-
path: [...runPath, newRunId2],
|
|
376
|
-
inputs: serialize(args),
|
|
377
|
-
executorId,
|
|
378
|
-
workflowName,
|
|
379
|
-
queueName,
|
|
380
|
-
timeout: options?.timeout,
|
|
381
|
-
deadline: options?.deadline
|
|
382
|
-
});
|
|
383
|
-
return runId;
|
|
384
|
-
});
|
|
385
|
-
return createRunHandle(ctx, newRunId);
|
|
386
292
|
}
|
|
387
293
|
|
|
388
|
-
//
|
|
294
|
+
// src/api/steps/now.ts
|
|
389
295
|
async function now() {
|
|
390
296
|
const { operationManager } = getExecutionContext();
|
|
391
297
|
const op = operationManager.getOperationResult();
|
|
@@ -398,37 +304,6 @@ async function now() {
|
|
|
398
304
|
}
|
|
399
305
|
}
|
|
400
306
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const stateKey = typeof key === "string" ? key : key.name;
|
|
405
|
-
const targetId = typeof target === "string" ? target : target.id;
|
|
406
|
-
const op = operationManager.getOperationResult();
|
|
407
|
-
if (op) {
|
|
408
|
-
return returnOrThrowOperationResult(op);
|
|
409
|
-
}
|
|
410
|
-
const seqId = operationManager.reserveSequenceId();
|
|
411
|
-
while (true) {
|
|
412
|
-
try {
|
|
413
|
-
return await withDbRetry(async () => {
|
|
414
|
-
const state = await getState(db, targetId, stateKey);
|
|
415
|
-
if (!state) {
|
|
416
|
-
throw new StateNotAvailableError();
|
|
417
|
-
}
|
|
418
|
-
await operationManager.recordResult("getState", seqId, state);
|
|
419
|
-
return deserialize(state);
|
|
420
|
-
});
|
|
421
|
-
} catch (error) {
|
|
422
|
-
if (error instanceof StateNotAvailableError) {
|
|
423
|
-
await waitForStateNotification(stateEventBus, targetId, stateKey);
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
await operationManager.recordError("getState", seqId, serializeError(error));
|
|
427
|
-
throw error;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export { cancellableSleep, defineMessage, defineQueue, defineState, executeStepWithRetries, getAbortSignal, getRunId, getState2 as getState, getStepId, now, queueWorkflow, randomUUID, receiveMessage, runStep, runWorkflow, sendMessage, setState, sleep2 as sleep };
|
|
433
|
-
//# sourceMappingURL=workflows.js.map
|
|
434
|
-
//# sourceMappingURL=workflows.js.map
|
|
307
|
+
export { cancellableSleep, defineMessage, defineQueue, defineState, executeStepWithRetries, getAbortSignal, getRunId, getStepId, now, randomUUID, receiveMessage, runStep, setState, sleep2 as sleep };
|
|
308
|
+
//# sourceMappingURL=api.js.map
|
|
309
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/message.ts","../src/api/queue.ts","../src/api/state.ts","../src/api/steps/run-step.ts","../src/internal/db/commands/insert-state.ts","../src/api/steps/set-state.ts","../src/internal/db/commands/read-and-delete-message.ts","../src/internal/with-durable-deadline.ts","../src/api/steps/receive-message.ts","../src/api/steps/helpers/get-step-id.ts","../src/api/steps/helpers/get-abort-signal.ts","../src/api/steps/helpers/get-run-id.ts","../src/api/steps/sleep.ts","../src/api/steps/random-uuid.ts","../src/api/steps/now.ts"],"names":["sleep"],"mappings":"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","file":"api.js","sourcesContent":["export type MessageDefinition<T> = {\n name: string;\n __data?: T;\n};\n\nexport function defineMessage<T>(name: string): MessageDefinition<T> {\n return { name } as MessageDefinition<T>;\n}\n","export type QueueRateLimit = {\n limitPerPeriod: number;\n period: number;\n};\n\nexport type QueueOptions = {\n workerConcurrency?: number;\n concurrency?: number;\n rateLimit?: QueueRateLimit;\n priorityEnabled?: boolean;\n partitioningEnabled?: boolean;\n};\n\nexport type QueueDefinition = QueueOptions & { name: string };\n\nexport type QueueSignature = {\n name: string;\n};\n\nexport function defineQueue(name: string, options: QueueOptions = {}): QueueDefinition {\n return { name, ...options };\n}\n","export interface StateDefinition<T> {\n name: string;\n data?: T;\n}\n\nexport function defineState<T>(name: string) {\n return {\n name,\n } as StateDefinition<T>;\n}\n","import { FatalError, MaxRetriesExceededError } from '@internal/errors';\nimport { getExecutionContext } from '@internal/context/execution-context';\nimport { sleep } from '@internal/utils/sleep';\nimport {\n executeAndRecordOperation,\n returnOrThrowOperationResult,\n} from '@internal/context/operation-manager';\n\nexport type RetryConfig = {\n maxRetries?: number;\n retryDelay?: number;\n backOffRate?: number;\n};\n\ntype RunStepOptions = RetryConfig & {\n name?: string;\n};\n\nexport async function runStep<TReturn>(\n stepFn: () => Promise<TReturn>,\n options: RunStepOptions = {},\n) {\n const { maxRetries, retryDelay, backOffRate } = options;\n const { operationManager } = getExecutionContext();\n const stepName = options.name || stepFn.name || '<unknown>';\n\n const op = operationManager.getOperationResult();\n if (op) {\n return returnOrThrowOperationResult<TReturn>(op);\n }\n return await executeAndRecordOperation(operationManager, stepName, async () => {\n return await executeStepWithRetries(stepName, stepFn, { maxRetries, retryDelay, backOffRate });\n });\n}\n\nclass ErrorThatShouldNeverHappen extends Error {\n constructor() {\n super('This error should never happen');\n }\n}\n\nexport async function executeStepWithRetries<TReturn>(\n stepName: string,\n fn: () => Promise<TReturn>,\n retryConfig: RetryConfig,\n): Promise<TReturn> {\n const maxRetries = retryConfig.maxRetries ?? 0;\n const retryDelay = retryConfig.retryDelay ?? 0;\n const backOffRate = retryConfig.backOffRate ?? 1;\n\n const attemptErrors: Error[] = [];\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n attemptErrors.push(err);\n if (err instanceof FatalError) {\n throw err;\n }\n if (maxRetries === 0) {\n throw err;\n }\n if (attempt >= maxRetries) {\n throw new MaxRetriesExceededError(stepName, maxRetries, attemptErrors);\n }\n const delay = retryDelay * Math.pow(backOffRate, attempt);\n await sleep(delay);\n }\n }\n throw new ErrorThatShouldNeverHappen();\n}\n","import { Database, Transaction } from '../db';\n\ntype InsertStateOptions = {\n runId: string;\n key: string;\n value: string;\n sequenceId: number;\n};\n\nexport async function insertState(tx: Transaction | Database, options: InsertStateOptions) {\n await tx\n .insertInto('state')\n .values({\n run_id: options.runId,\n key: options.key,\n value: options.value,\n })\n .onConflict((oc) =>\n oc.column('run_id').column('key').doUpdateSet({\n value: options.value,\n }),\n )\n .execute();\n\n await tx\n .insertInto('state_history')\n .values({\n run_id: options.runId,\n sequence_id: options.sequenceId,\n key: options.key,\n value: options.value,\n })\n .execute();\n}\n","import { withDbRetry } from '@internal/db/retry';\nimport { getExecutionContext } from '@internal/context/execution-context';\nimport { returnOrThrowOperationResult } from '@internal/context/operation-manager';\nimport { insertState } from '@internal/db/commands/insert-state';\nimport { serialize } from '@internal/utils/serialization';\nimport { StateDefinition } from '@api/state';\n\nconst SET_STATE_OPERATION_NAME = 'workflow::state::set';\n\nexport async function setState<T = unknown>(state: StateDefinition<T> | string, value: T) {\n const stateKey = typeof state === 'string' ? state : state.name;\n const { operationManager, runId, db } = getExecutionContext();\n\n const op = operationManager.getOperationResult();\n if (op) {\n return returnOrThrowOperationResult<void>(op);\n }\n\n const seqId = operationManager.reserveSequenceId();\n\n await withDbRetry(async () => {\n return await db.transaction().execute(async (tx) => {\n await insertState(tx, { runId, key: stateKey, value: serialize(value), sequenceId: seqId });\n await operationManager.recordResult(SET_STATE_OPERATION_NAME, seqId, null, tx);\n });\n });\n}\n","import { sql } from 'kysely';\nimport { Transaction } from '../db';\n\ntype Message = {\n id: string;\n payload?: string;\n type?: string;\n};\n\nexport async function readAndDeleteMessage(\n tx: Transaction,\n runId: string,\n messageType?: string,\n): Promise<Message | undefined> {\n const results = await sql<Message>`\n DELETE FROM messages\n WHERE id IN (\n SELECT id FROM messages\n WHERE destination_run_id = ${runId}\n ${messageType !== undefined ? sql`AND type = ${messageType}` : sql``}\n ORDER BY created_at_epoch_ms ASC\n LIMIT 1\n )\n RETURNING id, payload, type\n `.execute(tx);\n\n const result = results.rows[0];\n\n return result\n ? {\n id: result.id,\n payload: result.payload ?? undefined,\n type: result.type ?? undefined,\n }\n : undefined;\n}\n","import { getExecutionContext } from './context/execution-context';\nimport { executeAndRecordOperation } from './context/operation-manager';\n\nexport async function withDurableDeadline<T>(\n timeoutMs: number | undefined,\n operationName: string,\n fn: (deadlineMs: number | undefined) => Promise<T>,\n): Promise<T> {\n if (!timeoutMs) {\n return await fn(undefined);\n }\n\n const { operationManager } = getExecutionContext();\n\n let deadlineMs: number;\n const op = operationManager.getOperationResult();\n if (op) {\n deadlineMs = Number(op.result);\n } else {\n deadlineMs = Date.now() + timeoutMs;\n await executeAndRecordOperation(operationManager, operationName, async () => {\n return deadlineMs;\n });\n }\n\n return await fn(deadlineMs);\n}\n","import { withDbRetry } from '@internal/db/retry';\nimport { OperationTimedOutError } from '@internal/errors';\nimport { MessageEventBus } from '@internal/events/message-event-bus';\nimport { getExecutionContext } from '@internal/context/execution-context';\nimport { returnOrThrowOperationResult } from '@internal/context/operation-manager';\nimport { readAndDeleteMessage } from '@internal/db/commands/read-and-delete-message';\nimport { deserialize, serialize, serializeError } from '@internal/utils/serialization';\nimport { withDurableDeadline } from '@internal/with-durable-deadline';\nimport { MessageDefinition } from '@api/message';\n\nconst RECEIVE_MESSAGE_OPERATION_NAME = 'receiveMessage';\n\nconst RECEIVE_MESSAGE_DURABLE_DEADLINE_OPERATION_NAME = 'receiveMessageDurableDeadline';\n\nclass MessageNotAvailableError extends Error {}\n\nexport type ReceiveMessageOptions = {\n timeout?: number;\n};\n\nexport async function receiveMessage<T>(\n message: MessageDefinition<T> | string,\n options?: ReceiveMessageOptions,\n): Promise<T> {\n const messageType = typeof message === 'string' ? message : message.name;\n return await withDurableDeadline(\n options?.timeout,\n RECEIVE_MESSAGE_DURABLE_DEADLINE_OPERATION_NAME,\n async (deadlineMs) => {\n return await receiveMessageWithDeadline(messageType, deadlineMs);\n },\n );\n}\n\nasync function receiveMessageWithDeadline<T>(\n messageType: string,\n deadlineMs: number | undefined,\n): Promise<T> {\n const { runId, operationManager, messageEventBus, db } = getExecutionContext();\n\n const op = operationManager.getOperationResult();\n if (op) {\n return returnOrThrowOperationResult<T>(op) as T;\n }\n\n const seqId = operationManager.reserveSequenceId();\n\n while (true) {\n // Check if timeout expired\n if (deadlineMs && Date.now() >= deadlineMs) {\n const error = new OperationTimedOutError('receiveMessage');\n await operationManager.recordError(\n RECEIVE_MESSAGE_OPERATION_NAME,\n seqId,\n serializeError(error),\n );\n throw error;\n }\n\n try {\n return await withDbRetry(async () => {\n return await db.transaction().execute(async (tx) => {\n const msg = await readAndDeleteMessage(tx, runId, messageType);\n if (!msg) {\n throw new MessageNotAvailableError();\n }\n await operationManager.recordResult(\n RECEIVE_MESSAGE_OPERATION_NAME,\n seqId,\n serialize(msg.payload),\n tx,\n );\n return deserialize(msg.payload!) as T;\n });\n });\n } catch (error) {\n if (error instanceof MessageNotAvailableError) {\n const remainingMs = deadlineMs ? deadlineMs - Date.now() : undefined;\n await waitForMessageNotification(messageEventBus, runId, messageType, remainingMs);\n continue;\n }\n // Record and re-throw other errors\n await operationManager.recordError(\n RECEIVE_MESSAGE_OPERATION_NAME,\n seqId,\n serializeError(error as Error),\n );\n throw error;\n }\n }\n}\n\nasync function waitForMessageNotification(\n messageEventBus: MessageEventBus,\n runId: string,\n messageType: string,\n timeoutMs?: number,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n let timeoutId: NodeJS.Timeout | undefined;\n\n if (timeoutMs !== undefined) {\n if (timeoutMs <= 0) {\n reject(new OperationTimedOutError('receiveMessageDurableDeadline'));\n return;\n }\n\n timeoutId = setTimeout(() => {\n unsubscribe();\n reject(new OperationTimedOutError('waitForMessageNotification'));\n }, timeoutMs);\n }\n\n const unsubscribe = messageEventBus.subscribe(runId, messageType, () => {\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n resolve();\n });\n });\n}\n","import { getExecutionContext } from '@internal/context/execution-context';\nimport { createHash } from 'node:crypto';\n\n/**\n * Generates a stable, unique step ID based on the current run ID and sequence ID.\n * This ID is deterministic and will remain the same across retries, making it\n * suitable for use as an idempotency key with third-party systems.\n *\n * The step ID is a SHA-256 hash of the run ID and sequence ID, formatted as a\n * hex string for easy use with external APIs.\n *\n * @returns A unique, stable identifier for the current step execution\n */\nexport function getStepId(): string {\n const ctx = getExecutionContext();\n const sequenceId = ctx.operationManager.getActiveSequenceId();\n\n if (sequenceId === null) {\n throw new Error('getStepId() can only be called from within a step function');\n }\n\n const hash = createHash('sha256');\n hash.update(`${ctx.runId}:${sequenceId}`);\n return hash.digest('hex');\n}\n","import { getExecutionContext } from '@internal/context/execution-context';\n\nexport function getAbortSignal() {\n return getExecutionContext().abortSignal;\n}\n","import { getExecutionContext } from '@internal/context/execution-context';\n\nexport function getRunId() {\n const { runId } = getExecutionContext();\n return runId;\n}\n","import { RunCancelledError } from '@internal/errors';\nimport { getExecutionContext } from '@internal/context/execution-context';\nimport { withDurableDeadline } from '@internal/with-durable-deadline';\n\nconst SLEEP_OPERATION_NAME = '_helical::sleep';\n\nexport async function sleep(ms: number) {\n const { abortSignal } = getExecutionContext();\n return await withDurableDeadline(ms, SLEEP_OPERATION_NAME, async (deadlineMs) => {\n const remainingMs = deadlineMs! - Date.now();\n try {\n await cancellableSleep(remainingMs, abortSignal);\n } catch {\n throw new RunCancelledError();\n }\n });\n}\n\nexport function cancellableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Sleep aborted'));\n return;\n }\n\n const timeoutId = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n\n const onAbort = () => {\n cleanup();\n reject(new Error('Sleep aborted'));\n };\n\n const cleanup = () => {\n clearTimeout(timeoutId);\n signal?.removeEventListener('abort', onAbort);\n };\n\n signal?.addEventListener('abort', onAbort);\n });\n}\n","import crypto from 'node:crypto';\nimport { getExecutionContext } from '@internal/context/execution-context';\nimport { executeAndRecordOperation } from '@internal/context/operation-manager';\n\nexport async function randomUUID() {\n const { operationManager } = getExecutionContext();\n const existingResult = operationManager.getOperationResult();\n if (existingResult) {\n return existingResult.result as string;\n } else {\n return await executeAndRecordOperation(operationManager, 'randomUUID', async () => {\n return crypto.randomUUID();\n });\n }\n}\n","import { getExecutionContext } from '@internal/context/execution-context';\nimport { executeAndRecordOperation } from '@internal/context/operation-manager';\n\nexport async function now() {\n const { operationManager } = getExecutionContext();\n const op = operationManager.getOperationResult();\n if (op) {\n return Number(op.result);\n } else {\n return await executeAndRecordOperation(operationManager, 'now', async () => {\n return Date.now();\n });\n }\n}\n"]}
|