openworkflow 0.4.1 → 0.6.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 +43 -345
- package/dist/backend-test/backend.testsuite.d.ts +20 -0
- package/dist/backend-test/backend.testsuite.d.ts.map +1 -0
- package/dist/{core → backend-test}/backend.testsuite.js +191 -59
- package/dist/backend-test/index.d.ts +2 -0
- package/dist/backend-test/index.d.ts.map +1 -0
- package/dist/backend-test/index.js +1 -0
- package/dist/{core/backend.d.ts → backend.d.ts} +7 -5
- package/dist/backend.d.ts.map +1 -0
- package/dist/{core/backend.js → backend.js} +0 -1
- package/dist/backend.testsuite.d.ts +20 -0
- package/dist/backend.testsuite.d.ts.map +1 -0
- package/dist/{core/backend-test-suite.js → backend.testsuite.js} +301 -171
- package/dist/bin/openworkflow.d.ts +3 -0
- package/dist/bin/openworkflow.d.ts.map +1 -0
- package/dist/bin/openworkflow.js +43 -0
- package/dist/chaos.test.d.ts +2 -0
- package/dist/chaos.test.d.ts.map +1 -0
- package/dist/chaos.test.js +88 -0
- package/dist/client.d.ts +141 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/{sdk/sdk.js → client.js} +43 -71
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/{sdk/sdk.test.js → client.test.js} +130 -14
- package/dist/core/duration.d.ts +4 -2
- package/dist/core/duration.d.ts.map +1 -1
- package/dist/core/duration.js +3 -2
- package/dist/core/duration.test.js +0 -1
- package/dist/core/error.d.ts +14 -0
- package/dist/core/error.d.ts.map +1 -0
- package/dist/core/error.js +17 -0
- package/dist/core/error.test.d.ts +2 -0
- package/dist/core/error.test.d.ts.map +1 -0
- package/dist/core/error.test.js +60 -0
- package/dist/core/json.js +0 -1
- package/dist/core/result.d.ts +14 -4
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +10 -1
- package/dist/core/result.test.js +2 -2
- package/dist/core/retry.d.ts +0 -9
- package/dist/core/retry.d.ts.map +1 -1
- package/dist/core/retry.js +0 -15
- package/dist/core/schema.js +0 -1
- package/dist/core/step.d.ts +1 -32
- package/dist/core/step.d.ts.map +1 -1
- package/dist/core/step.js +0 -36
- package/dist/core/step.test.js +1 -75
- package/dist/core/workflow.d.ts +2 -47
- package/dist/core/workflow.d.ts.map +1 -1
- package/dist/core/workflow.js +0 -45
- package/dist/core/workflow.test.js +1 -104
- package/dist/driver.d.ts +116 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +1 -0
- package/dist/{execution/execution.d.ts → execution.d.ts} +4 -26
- package/dist/execution.d.ts.map +1 -0
- package/dist/{execution/execution.js → execution.js} +4 -5
- package/dist/execution.test.d.ts.map +1 -0
- package/dist/{execution/execution.test.js → execution.test.js} +4 -5
- package/dist/factory.d.ts +74 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +72 -0
- package/dist/index.d.ts +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/internal.d.ts +7 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +2 -0
- package/dist/node-sqlite/backend.d.ts +52 -0
- package/dist/node-sqlite/backend.d.ts.map +1 -0
- package/dist/node-sqlite/backend.js +673 -0
- package/dist/node-sqlite/index.d.ts +11 -0
- package/dist/node-sqlite/index.d.ts.map +1 -0
- package/dist/node-sqlite/index.js +7 -0
- package/dist/node-sqlite/sqlite.d.ts +60 -0
- package/dist/node-sqlite/sqlite.d.ts.map +1 -0
- package/dist/{backend-sqlite → node-sqlite}/sqlite.js +20 -3
- package/dist/postgres/backend.d.ts +44 -0
- package/dist/postgres/backend.d.ts.map +1 -0
- package/dist/postgres/backend.js +534 -0
- package/dist/postgres/backend.test.d.ts +2 -0
- package/dist/postgres/backend.test.d.ts.map +1 -0
- package/dist/postgres/backend.test.js +19 -0
- package/dist/postgres/driver.d.ts +81 -0
- package/dist/postgres/driver.d.ts.map +1 -0
- package/dist/postgres/driver.js +63 -0
- package/dist/postgres/index.d.ts +11 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +7 -0
- package/dist/postgres/internal.d.ts +2 -0
- package/dist/postgres/internal.d.ts.map +1 -0
- package/dist/postgres/internal.js +1 -0
- package/dist/postgres/postgres.d.ts +42 -0
- package/dist/postgres/postgres.d.ts.map +1 -0
- package/dist/postgres/postgres.js +233 -0
- package/dist/postgres/postgres.test.d.ts +2 -0
- package/dist/postgres/postgres.test.d.ts.map +1 -0
- package/dist/postgres/postgres.test.js +45 -0
- package/dist/postgres/scripts/db-migrate.d.ts +2 -0
- package/dist/postgres/scripts/db-migrate.d.ts.map +1 -0
- package/dist/postgres/scripts/db-migrate.js +4 -0
- package/dist/postgres/scripts/db-reset.d.ts +2 -0
- package/dist/postgres/scripts/db-reset.d.ts.map +1 -0
- package/dist/postgres/scripts/db-reset.js +5 -0
- package/dist/postgres/scripts/squawk.d.ts +2 -0
- package/dist/postgres/scripts/squawk.d.ts.map +1 -0
- package/dist/postgres/scripts/squawk.js +16 -0
- package/dist/postgres/vitest.global-setup.d.ts +3 -0
- package/dist/postgres/vitest.global-setup.d.ts.map +1 -0
- package/dist/postgres/vitest.global-setup.js +7 -0
- package/dist/postgres.d.ts +2 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +1 -0
- package/dist/registry.d.ts +27 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +48 -0
- package/dist/registry.test.d.ts +2 -0
- package/dist/registry.test.d.ts.map +1 -0
- package/dist/registry.test.js +109 -0
- package/dist/{backend-sqlite → sqlite}/backend.d.ts +8 -4
- package/dist/sqlite/backend.d.ts.map +1 -0
- package/dist/{backend-sqlite → sqlite}/backend.js +35 -9
- package/dist/sqlite/backend.test.d.ts +2 -0
- package/dist/sqlite/backend.test.d.ts.map +1 -0
- package/dist/sqlite/backend.test.js +50 -0
- package/dist/sqlite/driver.d.ts +79 -0
- package/dist/sqlite/driver.d.ts.map +1 -0
- package/dist/sqlite/driver.js +62 -0
- package/dist/sqlite/index.d.ts +13 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +11 -0
- package/dist/sqlite/internal.d.ts +2 -0
- package/dist/sqlite/internal.d.ts.map +1 -0
- package/dist/sqlite/internal.js +1 -0
- package/dist/{backend-sqlite → sqlite}/sqlite.d.ts +18 -2
- package/dist/sqlite/sqlite.d.ts.map +1 -0
- package/dist/sqlite/sqlite.js +246 -0
- package/dist/sqlite/sqlite.test.d.ts +2 -0
- package/dist/sqlite/sqlite.test.d.ts.map +1 -0
- package/dist/sqlite/sqlite.test.js +171 -0
- package/dist/sqlite.d.ts +2 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{worker/worker.d.ts → worker.d.ts} +11 -4
- package/dist/worker.d.ts.map +1 -0
- package/dist/{worker/worker.js → worker.js} +20 -11
- package/dist/{worker/worker.test.d.ts.map → worker.test.d.ts.map} +1 -1
- package/dist/{worker/worker.test.js → worker.test.js} +136 -22
- package/dist/workflow.d.ts +60 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +48 -0
- package/dist/workflow.test.d.ts +2 -0
- package/dist/workflow.test.d.ts.map +1 -0
- package/dist/workflow.test.js +84 -0
- package/package.json +28 -4
- package/dist/backend-sqlite/backend.d.ts.map +0 -1
- package/dist/backend-sqlite/backend.js.map +0 -1
- package/dist/backend-sqlite/index.d.ts +0 -2
- package/dist/backend-sqlite/index.d.ts.map +0 -1
- package/dist/backend-sqlite/index.js +0 -2
- package/dist/backend-sqlite/index.js.map +0 -1
- package/dist/backend-sqlite/sqlite.d.ts.map +0 -1
- package/dist/backend-sqlite/sqlite.js.map +0 -1
- package/dist/config/config.d.ts +0 -102
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/config.js +0 -29
- package/dist/config/config.js.map +0 -1
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -2
- package/dist/config/index.js.map +0 -1
- package/dist/config.d.ts +0 -28
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -41
- package/dist/config.js.map +0 -1
- package/dist/core/backend-test-suite.d.ts +0 -22
- package/dist/core/backend-test-suite.d.ts.map +0 -1
- package/dist/core/backend-test-suite.js.map +0 -1
- package/dist/core/backend.d.ts.map +0 -1
- package/dist/core/backend.js.map +0 -1
- package/dist/core/backend.testsuite.d.ts +0 -21
- package/dist/core/backend.testsuite.d.ts.map +0 -1
- package/dist/core/backend.testsuite.js.map +0 -1
- package/dist/core/duration.js.map +0 -1
- package/dist/core/duration.test.js.map +0 -1
- package/dist/core/json.js.map +0 -1
- package/dist/core/result.js.map +0 -1
- package/dist/core/result.test.js.map +0 -1
- package/dist/core/retry.js.map +0 -1
- package/dist/core/retry.test.d.ts +0 -2
- package/dist/core/retry.test.d.ts.map +0 -1
- package/dist/core/retry.test.js +0 -36
- package/dist/core/retry.test.js.map +0 -1
- package/dist/core/schema.js.map +0 -1
- package/dist/core/step.js.map +0 -1
- package/dist/core/step.test.js.map +0 -1
- package/dist/core/workflow.js.map +0 -1
- package/dist/core/workflow.test.js.map +0 -1
- package/dist/execution/execution.d.ts.map +0 -1
- package/dist/execution/execution.js.map +0 -1
- package/dist/execution/execution.test.d.ts.map +0 -1
- package/dist/execution/execution.test.js.map +0 -1
- package/dist/global.d.ts +0 -62
- package/dist/global.d.ts.map +0 -1
- package/dist/global.js +0 -78
- package/dist/global.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/sdk/sdk.d.ts +0 -182
- package/dist/sdk/sdk.d.ts.map +0 -1
- package/dist/sdk/sdk.js.map +0 -1
- package/dist/sdk/sdk.test.d.ts +0 -2
- package/dist/sdk/sdk.test.d.ts.map +0 -1
- package/dist/sdk/sdk.test.js.map +0 -1
- package/dist/worker/worker.d.ts.map +0 -1
- package/dist/worker/worker.js.map +0 -1
- package/dist/worker/worker.test.js.map +0 -1
- /package/dist/{execution/execution.test.d.ts → execution.test.d.ts} +0 -0
- /package/dist/{worker/worker.test.d.ts → worker.test.d.ts} +0 -0
|
@@ -2,10 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
3
3
|
/**
|
|
4
4
|
* Runs the Backend test suite.
|
|
5
|
-
*
|
|
6
|
-
* This function wraps all the tests that verify a Backend implementation
|
|
7
|
-
* conforms to the Backend interface contract. It uses the setup function to
|
|
8
|
-
* create backend instances and the teardown function to clean them up.
|
|
5
|
+
* @param options - Test suite options
|
|
9
6
|
*/
|
|
10
7
|
export function testBackend(options) {
|
|
11
8
|
const { setup, teardown } = options;
|
|
@@ -90,7 +87,8 @@ export function testBackend(options) {
|
|
|
90
87
|
await sleep(10); // ensure timestamp difference
|
|
91
88
|
const second = await createPendingWorkflowRun(backend);
|
|
92
89
|
const listed = await backend.listWorkflowRuns({});
|
|
93
|
-
|
|
90
|
+
const listedIds = listed.data.map((run) => run.id);
|
|
91
|
+
expect(listedIds).toEqual([second.id, first.id]);
|
|
94
92
|
await teardown(backend);
|
|
95
93
|
});
|
|
96
94
|
test("paginates workflow runs", async () => {
|
|
@@ -103,8 +101,8 @@ export function testBackend(options) {
|
|
|
103
101
|
// p1
|
|
104
102
|
const page1 = await backend.listWorkflowRuns({ limit: 2 });
|
|
105
103
|
expect(page1.data).toHaveLength(2);
|
|
106
|
-
expect(page1.data[0]?.id).toBe(runs[
|
|
107
|
-
expect(page1.data[1]?.id).toBe(runs[
|
|
104
|
+
expect(page1.data[0]?.id).toBe(runs[4]?.id);
|
|
105
|
+
expect(page1.data[1]?.id).toBe(runs[3]?.id);
|
|
108
106
|
expect(page1.pagination.next).not.toBeNull();
|
|
109
107
|
expect(page1.pagination.prev).toBeNull();
|
|
110
108
|
// p2
|
|
@@ -114,7 +112,7 @@ export function testBackend(options) {
|
|
|
114
112
|
});
|
|
115
113
|
expect(page2.data).toHaveLength(2);
|
|
116
114
|
expect(page2.data[0]?.id).toBe(runs[2]?.id);
|
|
117
|
-
expect(page2.data[1]?.id).toBe(runs[
|
|
115
|
+
expect(page2.data[1]?.id).toBe(runs[1]?.id);
|
|
118
116
|
expect(page2.pagination.next).not.toBeNull();
|
|
119
117
|
expect(page2.pagination.prev).not.toBeNull();
|
|
120
118
|
// p3
|
|
@@ -123,7 +121,7 @@ export function testBackend(options) {
|
|
|
123
121
|
after: page2.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
124
122
|
});
|
|
125
123
|
expect(page3.data).toHaveLength(1);
|
|
126
|
-
expect(page3.data[0]?.id).toBe(runs[
|
|
124
|
+
expect(page3.data[0]?.id).toBe(runs[0]?.id);
|
|
127
125
|
expect(page3.pagination.next).toBeNull();
|
|
128
126
|
expect(page3.pagination.prev).not.toBeNull();
|
|
129
127
|
// p2 again
|
|
@@ -133,7 +131,7 @@ export function testBackend(options) {
|
|
|
133
131
|
});
|
|
134
132
|
expect(page2Back.data).toHaveLength(2);
|
|
135
133
|
expect(page2Back.data[0]?.id).toBe(runs[2]?.id);
|
|
136
|
-
expect(page2Back.data[1]?.id).toBe(runs[
|
|
134
|
+
expect(page2Back.data[1]?.id).toBe(runs[1]?.id);
|
|
137
135
|
expect(page2Back.pagination.next).toEqual(page2.pagination.next);
|
|
138
136
|
expect(page2Back.pagination.prev).toEqual(page2.pagination.prev);
|
|
139
137
|
await teardown(backend);
|
|
@@ -146,6 +144,47 @@ export function testBackend(options) {
|
|
|
146
144
|
expect(listed.pagination.prev).toBeNull();
|
|
147
145
|
await teardown(backend);
|
|
148
146
|
});
|
|
147
|
+
test("paginates correctly with id as tiebreaker when multiple items have the same created_at timestamp", async () => {
|
|
148
|
+
const backend = await setup();
|
|
149
|
+
const runs = [];
|
|
150
|
+
for (let i = 0; i < 5; i++) {
|
|
151
|
+
runs.push(await createPendingWorkflowRun(backend));
|
|
152
|
+
}
|
|
153
|
+
runs.sort((a, b) => {
|
|
154
|
+
const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();
|
|
155
|
+
if (timeDiff !== 0)
|
|
156
|
+
return timeDiff;
|
|
157
|
+
return b.id.localeCompare(a.id);
|
|
158
|
+
});
|
|
159
|
+
const page1 = await backend.listWorkflowRuns({ limit: 2 });
|
|
160
|
+
expect(page1.data).toHaveLength(2);
|
|
161
|
+
expect(page1.data[0]?.id).toBe(runs[0]?.id);
|
|
162
|
+
expect(page1.data[1]?.id).toBe(runs[1]?.id);
|
|
163
|
+
expect(page1.pagination.next).not.toBeNull();
|
|
164
|
+
const page2 = await backend.listWorkflowRuns({
|
|
165
|
+
limit: 2,
|
|
166
|
+
after: page1.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
167
|
+
});
|
|
168
|
+
expect(page2.data).toHaveLength(2);
|
|
169
|
+
expect(page2.data[0]?.id).toBe(runs[2]?.id);
|
|
170
|
+
expect(page2.data[1]?.id).toBe(runs[3]?.id);
|
|
171
|
+
expect(page2.pagination.next).not.toBeNull();
|
|
172
|
+
const page3 = await backend.listWorkflowRuns({
|
|
173
|
+
limit: 2,
|
|
174
|
+
after: page2.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
175
|
+
});
|
|
176
|
+
expect(page3.data).toHaveLength(1);
|
|
177
|
+
expect(page3.data[0]?.id).toBe(runs[4]?.id);
|
|
178
|
+
expect(page3.pagination.next).toBeNull();
|
|
179
|
+
const page2Back = await backend.listWorkflowRuns({
|
|
180
|
+
limit: 2,
|
|
181
|
+
before: page3.pagination.prev, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
182
|
+
});
|
|
183
|
+
expect(page2Back.data).toHaveLength(2);
|
|
184
|
+
expect(page2Back.data[0]?.id).toBe(runs[2]?.id);
|
|
185
|
+
expect(page2Back.data[1]?.id).toBe(runs[3]?.id);
|
|
186
|
+
await teardown(backend);
|
|
187
|
+
});
|
|
149
188
|
});
|
|
150
189
|
describe("claimWorkflowRun()", () => {
|
|
151
190
|
// because claims involve timing and leases, we create and teardown a new
|
|
@@ -169,7 +208,7 @@ export function testBackend(options) {
|
|
|
169
208
|
leaseDurationMs: 10,
|
|
170
209
|
});
|
|
171
210
|
expect(blocked).toBeNull();
|
|
172
|
-
await sleep(firstLeaseMs);
|
|
211
|
+
await sleep(firstLeaseMs + 5); // small buffer for timing variability
|
|
173
212
|
const reclaimed = await backend.claimWorkflowRun({
|
|
174
213
|
workerId: secondWorker,
|
|
175
214
|
leaseDurationMs: 10,
|
|
@@ -278,7 +317,7 @@ export function testBackend(options) {
|
|
|
278
317
|
await backend.failWorkflowRun({
|
|
279
318
|
workflowRunId: claimed.id,
|
|
280
319
|
workerId: claimed.workerId ?? "",
|
|
281
|
-
error:
|
|
320
|
+
error: { message: "failed" },
|
|
282
321
|
});
|
|
283
322
|
await expect(backend.sleepWorkflowRun({
|
|
284
323
|
workflowRunId: claimed.id,
|
|
@@ -344,6 +383,7 @@ export function testBackend(options) {
|
|
|
344
383
|
expect(failed.output).toBeNull();
|
|
345
384
|
expect(failed.finishedAt).toBeNull();
|
|
346
385
|
expect(failed.workerId).toBeNull();
|
|
386
|
+
expect(failed.startedAt).toBeNull(); // cleared on failure for retry
|
|
347
387
|
expect(failed.availableAt).not.toBeNull();
|
|
348
388
|
if (!failed.availableAt)
|
|
349
389
|
throw new Error("Expected availableAt");
|
|
@@ -351,7 +391,7 @@ export function testBackend(options) {
|
|
|
351
391
|
expect(delayMs).toBeGreaterThanOrEqual(900); // ~1s with some tolerance
|
|
352
392
|
expect(delayMs).toBeLessThan(1500);
|
|
353
393
|
});
|
|
354
|
-
test("reschedules with increasing backoff on multiple failures
|
|
394
|
+
test("reschedules with increasing backoff on multiple failures", async () => {
|
|
355
395
|
// this test needs isolated namespace
|
|
356
396
|
const backend = await setup();
|
|
357
397
|
await createPendingWorkflowRun(backend);
|
|
@@ -470,6 +510,7 @@ export function testBackend(options) {
|
|
|
470
510
|
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion,
|
|
471
511
|
output: { ok: true },
|
|
472
512
|
});
|
|
513
|
+
await sleep(10); // ensure timestamp difference
|
|
473
514
|
const second = await backend.createStepAttempt({
|
|
474
515
|
workflowRunId: claimed.id,
|
|
475
516
|
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
@@ -481,10 +522,8 @@ export function testBackend(options) {
|
|
|
481
522
|
const listed = await backend.listStepAttempts({
|
|
482
523
|
workflowRunId: claimed.id,
|
|
483
524
|
});
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
second.stepName,
|
|
487
|
-
]);
|
|
525
|
+
const listedStepNames = listed.data.map((step) => step.stepName);
|
|
526
|
+
expect(listedStepNames).toEqual([first.stepName, second.stepName]);
|
|
488
527
|
});
|
|
489
528
|
test("paginates step attempts", async () => {
|
|
490
529
|
const claimed = await createClaimedWorkflowRun(backend);
|
|
@@ -570,23 +609,6 @@ export function testBackend(options) {
|
|
|
570
609
|
expect(listed.pagination.prev).toBeNull();
|
|
571
610
|
});
|
|
572
611
|
});
|
|
573
|
-
describe("getStepAttempt() duplicate", () => {
|
|
574
|
-
test("returns a persisted step attempt", async () => {
|
|
575
|
-
const claimed = await createClaimedWorkflowRun(backend);
|
|
576
|
-
const created = await backend.createStepAttempt({
|
|
577
|
-
workflowRunId: claimed.id,
|
|
578
|
-
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
579
|
-
stepName: randomUUID(),
|
|
580
|
-
kind: "function",
|
|
581
|
-
config: {},
|
|
582
|
-
context: null,
|
|
583
|
-
});
|
|
584
|
-
const got = await backend.getStepAttempt({
|
|
585
|
-
stepAttemptId: created.id,
|
|
586
|
-
});
|
|
587
|
-
expect(got).toEqual(created);
|
|
588
|
-
});
|
|
589
|
-
});
|
|
590
612
|
describe("completeStepAttempt()", () => {
|
|
591
613
|
test("marks running step attempts as completed", async () => {
|
|
592
614
|
const claimed = await createClaimedWorkflowRun(backend);
|
|
@@ -617,6 +639,50 @@ export function testBackend(options) {
|
|
|
617
639
|
expect(fetched?.error).toBeNull();
|
|
618
640
|
expect(fetched?.finishedAt).not.toBeNull();
|
|
619
641
|
});
|
|
642
|
+
test("throws when workflow is not running", async () => {
|
|
643
|
+
const backend = await setup();
|
|
644
|
+
await createPendingWorkflowRun(backend);
|
|
645
|
+
// create a step attempt by first claiming the workflow
|
|
646
|
+
const claimed = await backend.claimWorkflowRun({
|
|
647
|
+
workerId: randomUUID(),
|
|
648
|
+
leaseDurationMs: 100,
|
|
649
|
+
});
|
|
650
|
+
if (!claimed)
|
|
651
|
+
throw new Error("Failed to claim workflow run");
|
|
652
|
+
const stepAttempt = await backend.createStepAttempt({
|
|
653
|
+
workflowRunId: claimed.id,
|
|
654
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
655
|
+
stepName: randomUUID(),
|
|
656
|
+
kind: "function",
|
|
657
|
+
config: {},
|
|
658
|
+
context: null,
|
|
659
|
+
});
|
|
660
|
+
// complete the workflow so it's no longer running
|
|
661
|
+
await backend.completeWorkflowRun({
|
|
662
|
+
workflowRunId: claimed.id,
|
|
663
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
664
|
+
output: null,
|
|
665
|
+
});
|
|
666
|
+
// try to complete the step attempt
|
|
667
|
+
await expect(backend.completeStepAttempt({
|
|
668
|
+
workflowRunId: claimed.id,
|
|
669
|
+
stepAttemptId: stepAttempt.id,
|
|
670
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
671
|
+
output: { foo: "bar" },
|
|
672
|
+
})).rejects.toThrow("Failed to mark step attempt completed");
|
|
673
|
+
await teardown(backend);
|
|
674
|
+
});
|
|
675
|
+
test("throws when step attempt does not exist", async () => {
|
|
676
|
+
const backend = await setup();
|
|
677
|
+
const claimed = await createClaimedWorkflowRun(backend);
|
|
678
|
+
await expect(backend.completeStepAttempt({
|
|
679
|
+
workflowRunId: claimed.id,
|
|
680
|
+
stepAttemptId: randomUUID(),
|
|
681
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
682
|
+
output: { foo: "bar" },
|
|
683
|
+
})).rejects.toThrow("Failed to mark step attempt completed");
|
|
684
|
+
await teardown(backend);
|
|
685
|
+
});
|
|
620
686
|
});
|
|
621
687
|
describe("failStepAttempt()", () => {
|
|
622
688
|
test("marks running step attempts as failed", async () => {
|
|
@@ -648,6 +714,50 @@ export function testBackend(options) {
|
|
|
648
714
|
expect(fetched?.output).toBeNull();
|
|
649
715
|
expect(fetched?.finishedAt).not.toBeNull();
|
|
650
716
|
});
|
|
717
|
+
test("throws when workflow is not running", async () => {
|
|
718
|
+
const backend = await setup();
|
|
719
|
+
await createPendingWorkflowRun(backend);
|
|
720
|
+
// create a step attempt by first claiming the workflow
|
|
721
|
+
const claimed = await backend.claimWorkflowRun({
|
|
722
|
+
workerId: randomUUID(),
|
|
723
|
+
leaseDurationMs: 100,
|
|
724
|
+
});
|
|
725
|
+
if (!claimed)
|
|
726
|
+
throw new Error("Failed to claim workflow run");
|
|
727
|
+
const stepAttempt = await backend.createStepAttempt({
|
|
728
|
+
workflowRunId: claimed.id,
|
|
729
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
730
|
+
stepName: randomUUID(),
|
|
731
|
+
kind: "function",
|
|
732
|
+
config: {},
|
|
733
|
+
context: null,
|
|
734
|
+
});
|
|
735
|
+
// complete the workflow so it's no longer running
|
|
736
|
+
await backend.completeWorkflowRun({
|
|
737
|
+
workflowRunId: claimed.id,
|
|
738
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
739
|
+
output: null,
|
|
740
|
+
});
|
|
741
|
+
// try to fail the step attempt
|
|
742
|
+
await expect(backend.failStepAttempt({
|
|
743
|
+
workflowRunId: claimed.id,
|
|
744
|
+
stepAttemptId: stepAttempt.id,
|
|
745
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
746
|
+
error: { message: "nope" },
|
|
747
|
+
})).rejects.toThrow("Failed to mark step attempt failed");
|
|
748
|
+
await teardown(backend);
|
|
749
|
+
});
|
|
750
|
+
test("throws when step attempt does not exist", async () => {
|
|
751
|
+
const backend = await setup();
|
|
752
|
+
const claimed = await createClaimedWorkflowRun(backend);
|
|
753
|
+
await expect(backend.failStepAttempt({
|
|
754
|
+
workflowRunId: claimed.id,
|
|
755
|
+
stepAttemptId: randomUUID(),
|
|
756
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
757
|
+
error: { message: "nope" },
|
|
758
|
+
})).rejects.toThrow("Failed to mark step attempt failed");
|
|
759
|
+
await teardown(backend);
|
|
760
|
+
});
|
|
651
761
|
});
|
|
652
762
|
describe("deadline_at", () => {
|
|
653
763
|
test("creates a workflow run with a deadline", async () => {
|
|
@@ -744,6 +854,7 @@ export function testBackend(options) {
|
|
|
744
854
|
expect(failed.status).toBe("failed");
|
|
745
855
|
expect(failed.availableAt).toBeNull();
|
|
746
856
|
expect(failed.finishedAt).not.toBeNull();
|
|
857
|
+
expect(failed.startedAt).toBeNull(); // cleared on permanent failure
|
|
747
858
|
await teardown(backend);
|
|
748
859
|
});
|
|
749
860
|
test("reschedules failed workflow runs if retry would complete before deadline", async () => {
|
|
@@ -916,43 +1027,64 @@ export function testBackend(options) {
|
|
|
916
1027
|
await teardown(backend);
|
|
917
1028
|
});
|
|
918
1029
|
});
|
|
919
|
-
// Helper function for creating workflow runs that uses the shared backend
|
|
920
|
-
async function createPendingWorkflowRun(b) {
|
|
921
|
-
return await b.createWorkflowRun({
|
|
922
|
-
workflowName: randomUUID(),
|
|
923
|
-
version: null,
|
|
924
|
-
idempotencyKey: null,
|
|
925
|
-
input: null,
|
|
926
|
-
config: {},
|
|
927
|
-
context: null,
|
|
928
|
-
availableAt: null,
|
|
929
|
-
deadlineAt: null,
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
async function createClaimedWorkflowRun(b) {
|
|
933
|
-
await createPendingWorkflowRun(b);
|
|
934
|
-
const claimed = await b.claimWorkflowRun({
|
|
935
|
-
workerId: randomUUID(),
|
|
936
|
-
leaseDurationMs: 100,
|
|
937
|
-
});
|
|
938
|
-
if (!claimed)
|
|
939
|
-
throw new Error("Failed to claim workflow run");
|
|
940
|
-
return claimed;
|
|
941
|
-
}
|
|
942
1030
|
});
|
|
943
1031
|
}
|
|
944
|
-
|
|
1032
|
+
/**
|
|
1033
|
+
* Create a pending workflow run for tests.
|
|
1034
|
+
* @param b - Backend
|
|
1035
|
+
* @returns Created workflow run
|
|
1036
|
+
*/
|
|
1037
|
+
async function createPendingWorkflowRun(b) {
|
|
1038
|
+
return await b.createWorkflowRun({
|
|
1039
|
+
workflowName: randomUUID(),
|
|
1040
|
+
version: null,
|
|
1041
|
+
idempotencyKey: null,
|
|
1042
|
+
input: null,
|
|
1043
|
+
config: {},
|
|
1044
|
+
context: null,
|
|
1045
|
+
availableAt: null,
|
|
1046
|
+
deadlineAt: null,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Create and claim a workflow run for tests.
|
|
1051
|
+
* @param b - Backend
|
|
1052
|
+
* @returns Claimed workflow run
|
|
1053
|
+
*/
|
|
1054
|
+
async function createClaimedWorkflowRun(b) {
|
|
1055
|
+
await createPendingWorkflowRun(b);
|
|
1056
|
+
const claimed = await b.claimWorkflowRun({
|
|
1057
|
+
workerId: randomUUID(),
|
|
1058
|
+
leaseDurationMs: 100,
|
|
1059
|
+
});
|
|
1060
|
+
if (!claimed)
|
|
1061
|
+
throw new Error("Failed to claim workflow run");
|
|
1062
|
+
return claimed;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get delta in seconds from now.
|
|
1066
|
+
* @param date - Date to compare
|
|
1067
|
+
* @returns Delta in seconds
|
|
1068
|
+
*/
|
|
945
1069
|
function deltaSeconds(date) {
|
|
946
1070
|
if (!date)
|
|
947
1071
|
return Infinity;
|
|
948
1072
|
return Math.abs((Date.now() - date.getTime()) / 1000);
|
|
949
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Create a Date one year in the future.
|
|
1076
|
+
* @returns Future Date
|
|
1077
|
+
*/
|
|
950
1078
|
function newDateInOneYear() {
|
|
951
1079
|
const d = new Date();
|
|
952
1080
|
d.setFullYear(d.getFullYear() + 1);
|
|
953
1081
|
return d;
|
|
954
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Sleep for a given duration.
|
|
1085
|
+
* @param ms - Milliseconds to sleep
|
|
1086
|
+
* @returns Promise resolved after sleeping
|
|
1087
|
+
*/
|
|
955
1088
|
function sleep(ms) {
|
|
956
1089
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
957
1090
|
}
|
|
958
|
-
//# sourceMappingURL=backend.testsuite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../backend-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { testBackend } from "./backend.testsuite.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
1
|
+
import type { SerializedError } from "./core/error.js";
|
|
2
|
+
import { JsonValue } from "./core/json.js";
|
|
3
|
+
import type { StepAttempt, StepAttemptContext, StepKind } from "./core/step.js";
|
|
4
|
+
import type { WorkflowRun } from "./core/workflow.js";
|
|
4
5
|
export declare const DEFAULT_NAMESPACE_ID = "default";
|
|
5
6
|
/**
|
|
6
7
|
* Backend is the interface for backend providers to implement.
|
|
@@ -20,6 +21,7 @@ export interface Backend {
|
|
|
20
21
|
listStepAttempts(params: Readonly<ListStepAttemptsParams>): Promise<PaginatedResponse<StepAttempt>>;
|
|
21
22
|
completeStepAttempt(params: Readonly<CompleteStepAttemptParams>): Promise<StepAttempt>;
|
|
22
23
|
failStepAttempt(params: Readonly<FailStepAttemptParams>): Promise<StepAttempt>;
|
|
24
|
+
stop(): Promise<void>;
|
|
23
25
|
}
|
|
24
26
|
export interface CreateWorkflowRunParams {
|
|
25
27
|
workflowName: string;
|
|
@@ -57,7 +59,7 @@ export interface CompleteWorkflowRunParams {
|
|
|
57
59
|
export interface FailWorkflowRunParams {
|
|
58
60
|
workflowRunId: string;
|
|
59
61
|
workerId: string;
|
|
60
|
-
error:
|
|
62
|
+
error: SerializedError;
|
|
61
63
|
}
|
|
62
64
|
export interface CancelWorkflowRunParams {
|
|
63
65
|
workflowRunId: string;
|
|
@@ -86,7 +88,7 @@ export interface FailStepAttemptParams {
|
|
|
86
88
|
workflowRunId: string;
|
|
87
89
|
stepAttemptId: string;
|
|
88
90
|
workerId: string;
|
|
89
|
-
error:
|
|
91
|
+
error: SerializedError;
|
|
90
92
|
}
|
|
91
93
|
export interface PaginationOptions {
|
|
92
94
|
limit?: number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Backend } from "./backend.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the Backend test suite.
|
|
4
|
+
*/
|
|
5
|
+
export interface TestBackendOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new isolated Backend instance.
|
|
8
|
+
*/
|
|
9
|
+
setup: () => Promise<Backend>;
|
|
10
|
+
/**
|
|
11
|
+
* Cleans up a Backend instance.
|
|
12
|
+
*/
|
|
13
|
+
teardown: (backend: Backend) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Runs the Backend test suite.
|
|
17
|
+
* @param options - Test suite options
|
|
18
|
+
*/
|
|
19
|
+
export declare function testBackend(options: TestBackendOptions): void;
|
|
20
|
+
//# sourceMappingURL=backend.testsuite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.testsuite.d.ts","sourceRoot":"","sources":["../backend.testsuite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAM5C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAgtC7D"}
|