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.
Files changed (220) hide show
  1. package/README.md +43 -345
  2. package/dist/backend-test/backend.testsuite.d.ts +20 -0
  3. package/dist/backend-test/backend.testsuite.d.ts.map +1 -0
  4. package/dist/{core → backend-test}/backend.testsuite.js +191 -59
  5. package/dist/backend-test/index.d.ts +2 -0
  6. package/dist/backend-test/index.d.ts.map +1 -0
  7. package/dist/backend-test/index.js +1 -0
  8. package/dist/{core/backend.d.ts → backend.d.ts} +7 -5
  9. package/dist/backend.d.ts.map +1 -0
  10. package/dist/{core/backend.js → backend.js} +0 -1
  11. package/dist/backend.testsuite.d.ts +20 -0
  12. package/dist/backend.testsuite.d.ts.map +1 -0
  13. package/dist/{core/backend-test-suite.js → backend.testsuite.js} +301 -171
  14. package/dist/bin/openworkflow.d.ts +3 -0
  15. package/dist/bin/openworkflow.d.ts.map +1 -0
  16. package/dist/bin/openworkflow.js +43 -0
  17. package/dist/chaos.test.d.ts +2 -0
  18. package/dist/chaos.test.d.ts.map +1 -0
  19. package/dist/chaos.test.js +88 -0
  20. package/dist/client.d.ts +141 -0
  21. package/dist/client.d.ts.map +1 -0
  22. package/dist/{sdk/sdk.js → client.js} +43 -71
  23. package/dist/client.test.d.ts +2 -0
  24. package/dist/client.test.d.ts.map +1 -0
  25. package/dist/{sdk/sdk.test.js → client.test.js} +130 -14
  26. package/dist/core/duration.d.ts +4 -2
  27. package/dist/core/duration.d.ts.map +1 -1
  28. package/dist/core/duration.js +3 -2
  29. package/dist/core/duration.test.js +0 -1
  30. package/dist/core/error.d.ts +14 -0
  31. package/dist/core/error.d.ts.map +1 -0
  32. package/dist/core/error.js +17 -0
  33. package/dist/core/error.test.d.ts +2 -0
  34. package/dist/core/error.test.d.ts.map +1 -0
  35. package/dist/core/error.test.js +60 -0
  36. package/dist/core/json.js +0 -1
  37. package/dist/core/result.d.ts +14 -4
  38. package/dist/core/result.d.ts.map +1 -1
  39. package/dist/core/result.js +10 -1
  40. package/dist/core/result.test.js +2 -2
  41. package/dist/core/retry.d.ts +0 -9
  42. package/dist/core/retry.d.ts.map +1 -1
  43. package/dist/core/retry.js +0 -15
  44. package/dist/core/schema.js +0 -1
  45. package/dist/core/step.d.ts +1 -32
  46. package/dist/core/step.d.ts.map +1 -1
  47. package/dist/core/step.js +0 -36
  48. package/dist/core/step.test.js +1 -75
  49. package/dist/core/workflow.d.ts +2 -47
  50. package/dist/core/workflow.d.ts.map +1 -1
  51. package/dist/core/workflow.js +0 -45
  52. package/dist/core/workflow.test.js +1 -104
  53. package/dist/driver.d.ts +116 -0
  54. package/dist/driver.d.ts.map +1 -0
  55. package/dist/driver.js +1 -0
  56. package/dist/{execution/execution.d.ts → execution.d.ts} +4 -26
  57. package/dist/execution.d.ts.map +1 -0
  58. package/dist/{execution/execution.js → execution.js} +4 -5
  59. package/dist/execution.test.d.ts.map +1 -0
  60. package/dist/{execution/execution.test.js → execution.test.js} +4 -5
  61. package/dist/factory.d.ts +74 -0
  62. package/dist/factory.d.ts.map +1 -0
  63. package/dist/factory.js +72 -0
  64. package/dist/index.d.ts +6 -9
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +4 -5
  67. package/dist/internal.d.ts +7 -0
  68. package/dist/internal.d.ts.map +1 -0
  69. package/dist/internal.js +2 -0
  70. package/dist/node-sqlite/backend.d.ts +52 -0
  71. package/dist/node-sqlite/backend.d.ts.map +1 -0
  72. package/dist/node-sqlite/backend.js +673 -0
  73. package/dist/node-sqlite/index.d.ts +11 -0
  74. package/dist/node-sqlite/index.d.ts.map +1 -0
  75. package/dist/node-sqlite/index.js +7 -0
  76. package/dist/node-sqlite/sqlite.d.ts +60 -0
  77. package/dist/node-sqlite/sqlite.d.ts.map +1 -0
  78. package/dist/{backend-sqlite → node-sqlite}/sqlite.js +20 -3
  79. package/dist/postgres/backend.d.ts +44 -0
  80. package/dist/postgres/backend.d.ts.map +1 -0
  81. package/dist/postgres/backend.js +534 -0
  82. package/dist/postgres/backend.test.d.ts +2 -0
  83. package/dist/postgres/backend.test.d.ts.map +1 -0
  84. package/dist/postgres/backend.test.js +19 -0
  85. package/dist/postgres/driver.d.ts +81 -0
  86. package/dist/postgres/driver.d.ts.map +1 -0
  87. package/dist/postgres/driver.js +63 -0
  88. package/dist/postgres/index.d.ts +11 -0
  89. package/dist/postgres/index.d.ts.map +1 -0
  90. package/dist/postgres/index.js +7 -0
  91. package/dist/postgres/internal.d.ts +2 -0
  92. package/dist/postgres/internal.d.ts.map +1 -0
  93. package/dist/postgres/internal.js +1 -0
  94. package/dist/postgres/postgres.d.ts +42 -0
  95. package/dist/postgres/postgres.d.ts.map +1 -0
  96. package/dist/postgres/postgres.js +233 -0
  97. package/dist/postgres/postgres.test.d.ts +2 -0
  98. package/dist/postgres/postgres.test.d.ts.map +1 -0
  99. package/dist/postgres/postgres.test.js +45 -0
  100. package/dist/postgres/scripts/db-migrate.d.ts +2 -0
  101. package/dist/postgres/scripts/db-migrate.d.ts.map +1 -0
  102. package/dist/postgres/scripts/db-migrate.js +4 -0
  103. package/dist/postgres/scripts/db-reset.d.ts +2 -0
  104. package/dist/postgres/scripts/db-reset.d.ts.map +1 -0
  105. package/dist/postgres/scripts/db-reset.js +5 -0
  106. package/dist/postgres/scripts/squawk.d.ts +2 -0
  107. package/dist/postgres/scripts/squawk.d.ts.map +1 -0
  108. package/dist/postgres/scripts/squawk.js +16 -0
  109. package/dist/postgres/vitest.global-setup.d.ts +3 -0
  110. package/dist/postgres/vitest.global-setup.d.ts.map +1 -0
  111. package/dist/postgres/vitest.global-setup.js +7 -0
  112. package/dist/postgres.d.ts +2 -0
  113. package/dist/postgres.d.ts.map +1 -0
  114. package/dist/postgres.js +1 -0
  115. package/dist/registry.d.ts +27 -0
  116. package/dist/registry.d.ts.map +1 -0
  117. package/dist/registry.js +48 -0
  118. package/dist/registry.test.d.ts +2 -0
  119. package/dist/registry.test.d.ts.map +1 -0
  120. package/dist/registry.test.js +109 -0
  121. package/dist/{backend-sqlite → sqlite}/backend.d.ts +8 -4
  122. package/dist/sqlite/backend.d.ts.map +1 -0
  123. package/dist/{backend-sqlite → sqlite}/backend.js +35 -9
  124. package/dist/sqlite/backend.test.d.ts +2 -0
  125. package/dist/sqlite/backend.test.d.ts.map +1 -0
  126. package/dist/sqlite/backend.test.js +50 -0
  127. package/dist/sqlite/driver.d.ts +79 -0
  128. package/dist/sqlite/driver.d.ts.map +1 -0
  129. package/dist/sqlite/driver.js +62 -0
  130. package/dist/sqlite/index.d.ts +13 -0
  131. package/dist/sqlite/index.d.ts.map +1 -0
  132. package/dist/sqlite/index.js +11 -0
  133. package/dist/sqlite/internal.d.ts +2 -0
  134. package/dist/sqlite/internal.d.ts.map +1 -0
  135. package/dist/sqlite/internal.js +1 -0
  136. package/dist/{backend-sqlite → sqlite}/sqlite.d.ts +18 -2
  137. package/dist/sqlite/sqlite.d.ts.map +1 -0
  138. package/dist/sqlite/sqlite.js +246 -0
  139. package/dist/sqlite/sqlite.test.d.ts +2 -0
  140. package/dist/sqlite/sqlite.test.d.ts.map +1 -0
  141. package/dist/sqlite/sqlite.test.js +171 -0
  142. package/dist/sqlite.d.ts +2 -0
  143. package/dist/sqlite.d.ts.map +1 -0
  144. package/dist/sqlite.js +1 -0
  145. package/dist/tsconfig.tsbuildinfo +1 -1
  146. package/dist/{worker/worker.d.ts → worker.d.ts} +11 -4
  147. package/dist/worker.d.ts.map +1 -0
  148. package/dist/{worker/worker.js → worker.js} +20 -11
  149. package/dist/{worker/worker.test.d.ts.map → worker.test.d.ts.map} +1 -1
  150. package/dist/{worker/worker.test.js → worker.test.js} +136 -22
  151. package/dist/workflow.d.ts +60 -0
  152. package/dist/workflow.d.ts.map +1 -0
  153. package/dist/workflow.js +48 -0
  154. package/dist/workflow.test.d.ts +2 -0
  155. package/dist/workflow.test.d.ts.map +1 -0
  156. package/dist/workflow.test.js +84 -0
  157. package/package.json +28 -4
  158. package/dist/backend-sqlite/backend.d.ts.map +0 -1
  159. package/dist/backend-sqlite/backend.js.map +0 -1
  160. package/dist/backend-sqlite/index.d.ts +0 -2
  161. package/dist/backend-sqlite/index.d.ts.map +0 -1
  162. package/dist/backend-sqlite/index.js +0 -2
  163. package/dist/backend-sqlite/index.js.map +0 -1
  164. package/dist/backend-sqlite/sqlite.d.ts.map +0 -1
  165. package/dist/backend-sqlite/sqlite.js.map +0 -1
  166. package/dist/config/config.d.ts +0 -102
  167. package/dist/config/config.d.ts.map +0 -1
  168. package/dist/config/config.js +0 -29
  169. package/dist/config/config.js.map +0 -1
  170. package/dist/config/index.d.ts +0 -3
  171. package/dist/config/index.d.ts.map +0 -1
  172. package/dist/config/index.js +0 -2
  173. package/dist/config/index.js.map +0 -1
  174. package/dist/config.d.ts +0 -28
  175. package/dist/config.d.ts.map +0 -1
  176. package/dist/config.js +0 -41
  177. package/dist/config.js.map +0 -1
  178. package/dist/core/backend-test-suite.d.ts +0 -22
  179. package/dist/core/backend-test-suite.d.ts.map +0 -1
  180. package/dist/core/backend-test-suite.js.map +0 -1
  181. package/dist/core/backend.d.ts.map +0 -1
  182. package/dist/core/backend.js.map +0 -1
  183. package/dist/core/backend.testsuite.d.ts +0 -21
  184. package/dist/core/backend.testsuite.d.ts.map +0 -1
  185. package/dist/core/backend.testsuite.js.map +0 -1
  186. package/dist/core/duration.js.map +0 -1
  187. package/dist/core/duration.test.js.map +0 -1
  188. package/dist/core/json.js.map +0 -1
  189. package/dist/core/result.js.map +0 -1
  190. package/dist/core/result.test.js.map +0 -1
  191. package/dist/core/retry.js.map +0 -1
  192. package/dist/core/retry.test.d.ts +0 -2
  193. package/dist/core/retry.test.d.ts.map +0 -1
  194. package/dist/core/retry.test.js +0 -36
  195. package/dist/core/retry.test.js.map +0 -1
  196. package/dist/core/schema.js.map +0 -1
  197. package/dist/core/step.js.map +0 -1
  198. package/dist/core/step.test.js.map +0 -1
  199. package/dist/core/workflow.js.map +0 -1
  200. package/dist/core/workflow.test.js.map +0 -1
  201. package/dist/execution/execution.d.ts.map +0 -1
  202. package/dist/execution/execution.js.map +0 -1
  203. package/dist/execution/execution.test.d.ts.map +0 -1
  204. package/dist/execution/execution.test.js.map +0 -1
  205. package/dist/global.d.ts +0 -62
  206. package/dist/global.d.ts.map +0 -1
  207. package/dist/global.js +0 -78
  208. package/dist/global.js.map +0 -1
  209. package/dist/index.js.map +0 -1
  210. package/dist/sdk/sdk.d.ts +0 -182
  211. package/dist/sdk/sdk.d.ts.map +0 -1
  212. package/dist/sdk/sdk.js.map +0 -1
  213. package/dist/sdk/sdk.test.d.ts +0 -2
  214. package/dist/sdk/sdk.test.d.ts.map +0 -1
  215. package/dist/sdk/sdk.test.js.map +0 -1
  216. package/dist/worker/worker.d.ts.map +0 -1
  217. package/dist/worker/worker.js.map +0 -1
  218. package/dist/worker/worker.test.js.map +0 -1
  219. /package/dist/{execution/execution.test.d.ts → execution.test.d.ts} +0 -0
  220. /package/dist/{worker/worker.test.d.ts → worker.test.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
- import { executeWorkflow } from "../execution/execution.js";
2
- import { getWorkflowKey } from "../sdk/sdk.js";
1
+ import { executeWorkflow } from "./execution.js";
2
+ import { WorkflowRegistry } from "./registry.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
  const DEFAULT_LEASE_DURATION_MS = 30 * 1000; // 30s
5
5
  const DEFAULT_POLL_INTERVAL_MS = 100; // 100ms
@@ -11,22 +11,22 @@ const DEFAULT_CONCURRENCY = 1;
11
11
  export class Worker {
12
12
  backend;
13
13
  workerIds;
14
- registeredWorkflows = new Map();
14
+ registry = new WorkflowRegistry();
15
15
  activeExecutions = new Set();
16
16
  running = false;
17
17
  loopPromise = null;
18
18
  constructor(options) {
19
19
  this.backend = options.backend;
20
+ for (const workflow of options.workflows) {
21
+ this.registry.register(workflow);
22
+ }
20
23
  const concurrency = Math.max(DEFAULT_CONCURRENCY, options.concurrency ?? DEFAULT_CONCURRENCY);
21
24
  // generate worker IDs for every concurrency slot
22
25
  this.workerIds = Array.from({ length: concurrency }, () => randomUUID());
23
- // register workflows
24
- for (const [name, workflow] of options.workflows) {
25
- this.registeredWorkflows.set(name, workflow);
26
- }
27
26
  }
28
27
  /**
29
28
  * Start the worker. It will begin polling for and executing workflows.
29
+ * @returns Promise resolved when started
30
30
  */
31
31
  async start() {
32
32
  if (this.running)
@@ -38,6 +38,7 @@ export class Worker {
38
38
  /**
39
39
  * Stop the worker gracefully. Waits for all active workflow runs to complete
40
40
  * before returning.
41
+ * @returns Promise resolved when stopped
41
42
  */
42
43
  async stop() {
43
44
  this.running = false;
@@ -51,6 +52,7 @@ export class Worker {
51
52
  /**
52
53
  * Processes one round of work claims and execution. Exposed for testing.
53
54
  * Returns the number of workflow runs claimed.
55
+ * @returns Number of workflow runs claimed
54
56
  */
55
57
  async tick() {
56
58
  const availableSlots = this.concurrency - this.activeExecutions.size;
@@ -68,6 +70,7 @@ export class Worker {
68
70
  }
69
71
  /**
70
72
  * Get the configured concurrency limit.
73
+ * @returns Concurrency limit
71
74
  */
72
75
  get concurrency() {
73
76
  return this.workerIds.length;
@@ -92,7 +95,7 @@ export class Worker {
92
95
  }
93
96
  }
94
97
  /*
95
- * Cclaim and process a workflow run for the given worker ID. Do not await the
98
+ * Claim and process a workflow run for the given worker ID. Do not await the
96
99
  * processing here to avoid blocking the caller.
97
100
  * Returns the claimed workflow run, or null if none was available.
98
101
  */
@@ -104,8 +107,7 @@ export class Worker {
104
107
  });
105
108
  if (!workflowRun)
106
109
  return null;
107
- const key = getWorkflowKey(workflowRun.workflowName, workflowRun.version);
108
- const workflow = this.registeredWorkflows.get(key);
110
+ const workflow = this.registry.get(workflowRun.workflowName, workflowRun.version);
109
111
  if (!workflow) {
110
112
  const versionStr = workflowRun.version
111
113
  ? ` (version: ${workflowRun.version})`
@@ -139,6 +141,9 @@ export class Worker {
139
141
  /**
140
142
  * Process a workflow execution, handling heartbeats, step execution, and
141
143
  * marking success or failure.
144
+ * @param execution - Workflow execution
145
+ * @param workflow - Workflow to execute
146
+ * @returns Promise resolved when processing completes
142
147
  */
143
148
  async processExecutionInBackground(execution, workflow) {
144
149
  // start heartbeating
@@ -202,7 +207,11 @@ class WorkflowExecution {
202
207
  }
203
208
  }
204
209
  }
210
+ /**
211
+ * Sleep for a given duration.
212
+ * @param ms - Milliseconds to sleep
213
+ * @returns Promise resolved after sleeping
214
+ */
205
215
  function sleep(ms) {
206
216
  return new Promise((resolve) => setTimeout(resolve, ms));
207
217
  }
208
- //# sourceMappingURL=worker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker.test.d.ts","sourceRoot":"","sources":["../../worker/worker.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"worker.test.d.ts","sourceRoot":"","sources":["../worker.test.ts"],"names":[],"mappings":""}
@@ -1,10 +1,11 @@
1
- import { BackendPostgres } from "../../backend-postgres/backend.js";
2
- import { DEFAULT_DATABASE_URL } from "../../backend-postgres/postgres.js";
3
- import { OpenWorkflow } from "../sdk/sdk.js";
1
+ import { OpenWorkflow } from "./client.js";
2
+ import { BackendPostgres } from "./postgres.js";
3
+ import { DEFAULT_POSTGRES_URL } from "./postgres/postgres.js";
4
+ import { defineWorkflowSpec } from "./workflow.js";
4
5
  import { randomUUID } from "node:crypto";
5
6
  import { describe, expect, test } from "vitest";
6
7
  describe("Worker", () => {
7
- test("passes workflow input to handlers (known slow test)", async () => {
8
+ test("passes workflow input to handlers", async () => {
8
9
  const backend = await createBackend();
9
10
  const client = new OpenWorkflow({ backend });
10
11
  const workflow = client.defineWorkflow({ name: "context" }, ({ input }) => input);
@@ -15,7 +16,7 @@ describe("Worker", () => {
15
16
  const result = await handle.result();
16
17
  expect(result).toEqual(payload);
17
18
  });
18
- test("processes workflow runs to completion (known slow test)", async () => {
19
+ test("processes workflow runs to completion", async () => {
19
20
  const backend = await createBackend();
20
21
  const client = new OpenWorkflow({ backend });
21
22
  const workflow = client.defineWorkflow({ name: "process" }, ({ input }) => input.value * 2);
@@ -25,7 +26,7 @@ describe("Worker", () => {
25
26
  const result = await handle.result();
26
27
  expect(result).toBe(42);
27
28
  });
28
- test("step.run reuses cached results (known slow test)", async () => {
29
+ test("step.run reuses cached results", async () => {
29
30
  const backend = await createBackend();
30
31
  const client = new OpenWorkflow({ backend });
31
32
  let executionCount = 0;
@@ -69,7 +70,7 @@ describe("Worker", () => {
69
70
  expect(updated?.error).toBeDefined();
70
71
  expect(updated?.availableAt).not.toBeNull();
71
72
  });
72
- test("retries failed workflows automatically (known slow test)", async () => {
73
+ test("retries failed workflows automatically", async () => {
73
74
  const backend = await createBackend();
74
75
  const client = new OpenWorkflow({ backend });
75
76
  let attemptCount = 0;
@@ -102,7 +103,7 @@ describe("Worker", () => {
102
103
  const worker = client.newWorker();
103
104
  await worker.tick(); // no runs queued
104
105
  });
105
- test("handles step functions that return undefined (known slow test)", async () => {
106
+ test("handles step functions that return undefined", async () => {
106
107
  const backend = await createBackend();
107
108
  const client = new OpenWorkflow({ backend });
108
109
  const workflow = client.defineWorkflow({ name: "undefined-steps" }, async ({ step }) => {
@@ -120,7 +121,7 @@ describe("Worker", () => {
120
121
  const result = await handle.result();
121
122
  expect(result).toEqual({ success: true });
122
123
  });
123
- test("executes steps synchronously within workflow (known slow test)", async () => {
124
+ test("executes steps synchronously within workflow", async () => {
124
125
  const backend = await createBackend();
125
126
  const client = new OpenWorkflow({ backend });
126
127
  const executionOrder = [];
@@ -144,7 +145,7 @@ describe("Worker", () => {
144
145
  const result = await handle.result();
145
146
  expect(result).toEqual(["start", "step1", "between", "step2", "end"]);
146
147
  });
147
- test("executes parallel steps with Promise.all (known slow test)", async () => {
148
+ test("executes parallel steps with Promise.all", async () => {
148
149
  const backend = await createBackend();
149
150
  const client = new OpenWorkflow({ backend });
150
151
  const executionTimes = {};
@@ -205,7 +206,7 @@ describe("Worker", () => {
205
206
  }
206
207
  expect(completed).toBe(2);
207
208
  });
208
- test("worker starts, processes work, and stops gracefully (known slow test)", async () => {
209
+ test("worker starts, processes work, and stops gracefully", async () => {
209
210
  const backend = await createBackend();
210
211
  const client = new OpenWorkflow({ backend });
211
212
  const workflow = client.defineWorkflow({ name: "lifecycle" }, () => {
@@ -219,7 +220,7 @@ describe("Worker", () => {
219
220
  const result = await handle.result();
220
221
  expect(result).toBe("complete");
221
222
  });
222
- test("recovers from crashes during parallel step execution (known slow test)", async () => {
223
+ test("recovers from crashes during parallel step execution", async () => {
223
224
  const backend = await createBackend();
224
225
  const client = new OpenWorkflow({ backend });
225
226
  let attemptCount = 0;
@@ -254,7 +255,7 @@ describe("Worker", () => {
254
255
  expect(result).toEqual({ a: "a", b: "b", attempts: 2 });
255
256
  expect(attemptCount).toBe(2);
256
257
  });
257
- test("reclaims workflow run when heartbeat stops (known slow test)", async () => {
258
+ test("reclaims workflow run when heartbeat stops", async () => {
258
259
  const backend = await createBackend();
259
260
  const client = new OpenWorkflow({ backend });
260
261
  const workflow = client.defineWorkflow({ name: "heartbeat-test" }, () => "done");
@@ -310,7 +311,7 @@ describe("Worker", () => {
310
311
  expect(claimedAgain).toBe(0);
311
312
  await worker.stop();
312
313
  });
313
- test("worker only sleeps between claims when no work is available (known slow test)", async () => {
314
+ test("worker only sleeps between claims when no work is available", async () => {
314
315
  const backend = await createBackend();
315
316
  const client = new OpenWorkflow({ backend });
316
317
  const workflow = client.defineWorkflow({ name: "adaptive-test" }, async ({ step }) => {
@@ -333,7 +334,7 @@ describe("Worker", () => {
333
334
  // without it (with 100ms sleep between ticks), it would take much longer
334
335
  expect(duration).toBeLessThan(3000); // should complete in under 3 seconds
335
336
  });
336
- test("only failed steps re-execute on retry (known slow test)", async () => {
337
+ test("only failed steps re-execute on retry", async () => {
337
338
  const backend = await createBackend();
338
339
  const client = new OpenWorkflow({ backend });
339
340
  const executionCounts = {
@@ -388,7 +389,7 @@ describe("Worker", () => {
388
389
  c: "c-result",
389
390
  });
390
391
  });
391
- test("step.sleep postpones workflow execution (known slow test)", async () => {
392
+ test("step.sleep postpones workflow execution", async () => {
392
393
  const backend = await createBackend();
393
394
  const client = new OpenWorkflow({ backend });
394
395
  let stepCount = 0;
@@ -494,10 +495,9 @@ describe("Worker", () => {
494
495
  });
495
496
  expect(failed?.status).toBe("pending"); // should be retrying
496
497
  expect(failed?.error).toBeDefined();
497
- // @ts-expect-error - test suite
498
498
  expect(failed?.error?.message).toContain("Invalid duration format");
499
499
  });
500
- test("step.sleep handles multiple sequential sleeps (known slow test)", async () => {
500
+ test("step.sleep handles multiple sequential sleeps", async () => {
501
501
  const backend = await createBackend();
502
502
  const client = new OpenWorkflow({ backend });
503
503
  let executionCount = 0;
@@ -579,7 +579,7 @@ describe("Worker", () => {
579
579
  expect(claimed?.status).toBe("running");
580
580
  expect(claimed?.workerId).toBe("test-worker");
581
581
  });
582
- test("sleep is not skipped when worker crashes after creating sleep step but before marking workflow as sleeping (known slow test)", async () => {
582
+ test("sleep is not skipped when worker crashes after creating sleep step but before marking workflow as sleeping", async () => {
583
583
  const backend = await createBackend();
584
584
  const client = new OpenWorkflow({ backend });
585
585
  let executionCount = 0;
@@ -631,7 +631,7 @@ describe("Worker", () => {
631
631
  const result = await handle.result();
632
632
  expect(result.afterSleepCount).toBe(1);
633
633
  });
634
- test("version enables conditional code paths (known slow test)", async () => {
634
+ test("version enables conditional code paths", async () => {
635
635
  const backend = await createBackend();
636
636
  const client = new OpenWorkflow({ backend });
637
637
  const workflow = client.defineWorkflow({ name: "conditional-workflow", version: "v2" }, async ({ version, step }) => {
@@ -774,13 +774,127 @@ describe("Worker", () => {
774
774
  await handle.cancel();
775
775
  await expect(handle.result()).rejects.toThrow(/Workflow cancel-result was canceled/);
776
776
  });
777
+ describe("version matching", () => {
778
+ test("worker matches workflow runs by version", async () => {
779
+ const backend = await createBackend();
780
+ const client = new OpenWorkflow({ backend });
781
+ client.defineWorkflow({ name: "versioned-workflow", version: "v1" }, async ({ step }) => {
782
+ return await step.run({ name: "compute" }, () => "v1-result");
783
+ });
784
+ client.defineWorkflow({ name: "versioned-workflow", version: "v2" }, async ({ step }) => {
785
+ return await step.run({ name: "compute" }, () => "v2-result");
786
+ });
787
+ const worker = client.newWorker({ concurrency: 2 });
788
+ const v1Spec = defineWorkflowSpec({
789
+ name: "versioned-workflow",
790
+ version: "v1",
791
+ });
792
+ const v2Spec = defineWorkflowSpec({
793
+ name: "versioned-workflow",
794
+ version: "v2",
795
+ });
796
+ const handleV1 = await client.runWorkflow(v1Spec);
797
+ const handleV2 = await client.runWorkflow(v2Spec);
798
+ await worker.tick();
799
+ await sleep(100); // wait for background execution
800
+ const resultV1 = await handleV1.result();
801
+ const resultV2 = await handleV2.result();
802
+ expect(resultV1).toBe("v1-result");
803
+ expect(resultV2).toBe("v2-result");
804
+ });
805
+ test("worker fails workflow run when version is not registered", async () => {
806
+ const backend = await createBackend();
807
+ const client = new OpenWorkflow({ backend });
808
+ client.defineWorkflow({ name: "version-check", version: "v1" }, () => "v1-result");
809
+ const worker = client.newWorker();
810
+ const workflowRun = await backend.createWorkflowRun({
811
+ workflowName: "version-check",
812
+ version: "v2",
813
+ idempotencyKey: null,
814
+ config: {},
815
+ context: null,
816
+ input: null,
817
+ availableAt: null,
818
+ deadlineAt: null,
819
+ });
820
+ await worker.tick();
821
+ const updated = await backend.getWorkflowRun({
822
+ workflowRunId: workflowRun.id,
823
+ });
824
+ expect(updated?.status).toBe("pending");
825
+ expect(updated?.error).toEqual({
826
+ message: 'Workflow "version-check" (version: v2) is not registered',
827
+ });
828
+ });
829
+ test("unversioned workflow does not match versioned run", async () => {
830
+ const backend = await createBackend();
831
+ const client = new OpenWorkflow({ backend });
832
+ client.defineWorkflow({ name: "version-mismatch" }, () => "unversioned-result");
833
+ const worker = client.newWorker();
834
+ const workflowRun = await backend.createWorkflowRun({
835
+ workflowName: "version-mismatch",
836
+ version: "v1",
837
+ idempotencyKey: null,
838
+ config: {},
839
+ context: null,
840
+ input: null,
841
+ availableAt: null,
842
+ deadlineAt: null,
843
+ });
844
+ await worker.tick();
845
+ const updated = await backend.getWorkflowRun({
846
+ workflowRunId: workflowRun.id,
847
+ });
848
+ expect(updated?.status).toBe("pending");
849
+ expect(updated?.error).toEqual({
850
+ message: 'Workflow "version-mismatch" (version: v1) is not registered',
851
+ });
852
+ });
853
+ test("versioned workflow does not match unversioned run", async () => {
854
+ const backend = await createBackend();
855
+ const client = new OpenWorkflow({ backend });
856
+ client.defineWorkflow({ name: "version-required", version: "v1" }, () => "v1-result");
857
+ const worker = client.newWorker();
858
+ const workflowRun = await backend.createWorkflowRun({
859
+ workflowName: "version-required",
860
+ version: null,
861
+ idempotencyKey: null,
862
+ config: {},
863
+ context: null,
864
+ input: null,
865
+ availableAt: null,
866
+ deadlineAt: null,
867
+ });
868
+ await worker.tick();
869
+ const updated = await backend.getWorkflowRun({
870
+ workflowRunId: workflowRun.id,
871
+ });
872
+ expect(updated?.status).toBe("pending");
873
+ expect(updated?.error).toEqual({
874
+ message: 'Workflow "version-required" is not registered',
875
+ });
876
+ });
877
+ test("workflow receives run's version, not registered version", async () => {
878
+ // this test verifies that the version passed to the workflow function
879
+ // is the one from the workflow run, not the registered workflow
880
+ const backend = await createBackend();
881
+ const client = new OpenWorkflow({ backend });
882
+ const workflow = client.defineWorkflow({ name: "version-in-handler", version: "v1" }, async ({ version, step }) => {
883
+ return await step.run({ name: "get-version" }, () => version);
884
+ });
885
+ const worker = client.newWorker();
886
+ const handle = await workflow.run();
887
+ await worker.tick();
888
+ const result = await handle.result();
889
+ expect(result).toBe("v1");
890
+ });
891
+ });
777
892
  });
778
893
  async function createBackend() {
779
- return await BackendPostgres.connect(DEFAULT_DATABASE_URL, {
894
+ return await BackendPostgres.connect(DEFAULT_POSTGRES_URL, {
780
895
  namespaceId: randomUUID(), // unique namespace per test
781
896
  });
782
897
  }
783
898
  function sleep(ms) {
784
899
  return new Promise((resolve) => setTimeout(resolve, ms));
785
900
  }
786
- //# sourceMappingURL=worker.test.js.map
@@ -0,0 +1,60 @@
1
+ import type { StandardSchemaV1 } from "./core/schema.js";
2
+ import { WorkflowFunction } from "./execution.js";
3
+ /**
4
+ * A workflow spec.
5
+ */
6
+ export interface WorkflowSpec<Input, Output, RawInput> {
7
+ /** The name of the workflow. */
8
+ readonly name: string;
9
+ /** The version of the workflow. */
10
+ readonly version?: string;
11
+ /** The schema used to validate inputs. */
12
+ readonly schema?: StandardSchemaV1<RawInput, Input>;
13
+ /** Phantom type carrier - won't exist at runtime. */
14
+ readonly __types?: {
15
+ output: Output;
16
+ };
17
+ }
18
+ /**
19
+ * Define a workflow spec.
20
+ * @param spec - The workflow spec
21
+ * @returns The workflow spec
22
+ */
23
+ export declare function defineWorkflowSpec<Input, Output = unknown, RawInput = Input>(spec: WorkflowSpec<Input, Output, RawInput>): WorkflowSpec<Input, Output, RawInput>;
24
+ /**
25
+ * Define a workflow spec.
26
+ * @param spec - The workflow spec
27
+ * @returns The workflow spec
28
+ * @deprecated use `defineWorkflowSpec` instead
29
+ */
30
+ export declare const declareWorkflow: typeof defineWorkflowSpec;
31
+ /**
32
+ * A workflow spec and implementation.
33
+ */
34
+ export interface Workflow<Input, Output, RawInput> {
35
+ /** The workflow spec. */
36
+ readonly spec: WorkflowSpec<Input, Output, RawInput>;
37
+ /** The workflow implementation function. */
38
+ readonly fn: WorkflowFunction<Input, Output>;
39
+ }
40
+ /**
41
+ * Define a workflow.
42
+ * @param spec - The workflow spec
43
+ * @param fn - The workflow implementation function
44
+ * @returns The workflow
45
+ */
46
+ export declare function defineWorkflow<Input, Output, RawInput = Input>(spec: WorkflowSpec<Input, Output, RawInput>, fn: WorkflowFunction<Input, Output>): Workflow<Input, Output, RawInput>;
47
+ /**
48
+ * Define a workflow.
49
+ * @param spec - The workflow spec
50
+ * @param fn - The workflow implementation function
51
+ * @returns The workflow
52
+ */
53
+ export declare function defineWorkflow<Input, WorkflowFn extends WorkflowFunction<Input, unknown> = WorkflowFunction<Input, unknown>, RawInput = Input>(spec: WorkflowSpec<Input, Awaited<ReturnType<WorkflowFn>>, RawInput>, fn: WorkflowFn): Workflow<Input, Awaited<ReturnType<WorkflowFn>>, RawInput>;
54
+ /**
55
+ * Type guard to check if a value is a Workflow object.
56
+ * @param value - The value to check
57
+ * @returns True if the value is a Workflow
58
+ */
59
+ export declare function isWorkflow(value: unknown): boolean;
60
+ //# sourceMappingURL=workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ;IACnD,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpD,qDAAqD;IACrD,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,QAAQ,GAAG,KAAK,EAC1E,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,GAC1C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAEvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,2BAAqB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ;IAC/C,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACrD,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED;;;;;GAKG;AAIH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,KAAK,EAC5D,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAC3C,EAAE,EAAE,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,GAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAErC;;;;;GAKG;AAGH,wBAAgB,cAAc,CAC5B,KAAK,EACL,UAAU,SAAS,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,gBAAgB,CACpE,KAAK,EACL,OAAO,CACR,EACD,QAAQ,GAAG,KAAK,EAEhB,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,EACpE,EAAE,EAAE,UAAU,GACb,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAmB9D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,WAkBxC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Define a workflow spec.
3
+ * @param spec - The workflow spec
4
+ * @returns The workflow spec
5
+ */
6
+ export function defineWorkflowSpec(spec) {
7
+ return spec;
8
+ }
9
+ /**
10
+ * Define a workflow spec.
11
+ * @param spec - The workflow spec
12
+ * @returns The workflow spec
13
+ * @deprecated use `defineWorkflowSpec` instead
14
+ */
15
+ export const declareWorkflow = defineWorkflowSpec;
16
+ /**
17
+ * Define a workflow.
18
+ * @internal
19
+ * @param spec - The workflow spec
20
+ * @param fn - The workflow implementation function
21
+ * @returns The workflow
22
+ */
23
+ export function defineWorkflow(spec, fn) {
24
+ return {
25
+ spec,
26
+ fn,
27
+ };
28
+ }
29
+ /**
30
+ * Type guard to check if a value is a Workflow object.
31
+ * @param value - The value to check
32
+ * @returns True if the value is a Workflow
33
+ */
34
+ export function isWorkflow(value) {
35
+ if (typeof value !== "object" || value === null) {
36
+ return false;
37
+ }
38
+ const maybeWorkflow = value;
39
+ if (!("spec" in maybeWorkflow) || !("fn" in maybeWorkflow)) {
40
+ return false;
41
+ }
42
+ const { spec, fn } = maybeWorkflow;
43
+ return (typeof spec === "object" &&
44
+ spec !== null &&
45
+ "name" in spec &&
46
+ typeof spec.name === "string" &&
47
+ typeof fn === "function");
48
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workflow.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.test.d.ts","sourceRoot":"","sources":["../workflow.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,84 @@
1
+ import { defineWorkflow, defineWorkflowSpec, isWorkflow } from "./workflow.js";
2
+ import { describe, expect, test } from "vitest";
3
+ describe("defineWorkflowSpec", () => {
4
+ test("returns spec (passthrough)", () => {
5
+ const spec = { name: "test-workflow" };
6
+ const definedSpec = defineWorkflowSpec(spec);
7
+ expect(definedSpec).toStrictEqual(spec);
8
+ });
9
+ });
10
+ describe("defineWorkflow", () => {
11
+ test("returns workflow with spec and fn", () => {
12
+ // eslint-disable-next-line unicorn/consistent-function-scoping
13
+ function fn() {
14
+ return { result: "done" };
15
+ }
16
+ const spec = { name: "test-workflow" };
17
+ const workflow = defineWorkflow(spec, fn);
18
+ expect(workflow).toStrictEqual({
19
+ spec,
20
+ fn,
21
+ });
22
+ });
23
+ });
24
+ describe("isWorkflow", () => {
25
+ test("returns true for valid workflow objects", () => {
26
+ const workflow = defineWorkflow({ name: "test" }, () => "done");
27
+ expect(isWorkflow(workflow)).toBe(true);
28
+ });
29
+ test("returns false for null", () => {
30
+ expect(isWorkflow(null)).toBe(false);
31
+ });
32
+ test("returns false for undefined", () => {
33
+ // eslint-disable-next-line unicorn/no-useless-undefined
34
+ expect(isWorkflow(undefined)).toBe(false);
35
+ });
36
+ test("returns false for primitives", () => {
37
+ expect(isWorkflow("string")).toBe(false);
38
+ expect(isWorkflow(123)).toBe(false);
39
+ expect(isWorkflow(true)).toBe(false);
40
+ });
41
+ test("returns false for objects without spec", () => {
42
+ expect(isWorkflow({ fn: () => "result" })).toBe(false);
43
+ });
44
+ test("returns false for objects without fn", () => {
45
+ expect(isWorkflow({ spec: { name: "test" } })).toBe(false);
46
+ });
47
+ test("returns false for objects with invalid spec", () => {
48
+ expect(isWorkflow({ spec: null, fn: () => "result" })).toBe(false);
49
+ expect(isWorkflow({ spec: "invalid", fn: () => "result" })).toBe(false);
50
+ });
51
+ test("returns false for objects with invalid fn", () => {
52
+ expect(isWorkflow({ spec: { name: "test" }, fn: "not-a-function" })).toBe(false);
53
+ });
54
+ });
55
+ // --- type checks below -------------------------------------------------------
56
+ // they're unused but useful to ensure that the types work as expected for both
57
+ // defineWorkflowSpec and defineWorkflow
58
+ const inferredTypesSpec = defineWorkflowSpec({
59
+ name: "inferred-types",
60
+ });
61
+ defineWorkflow(inferredTypesSpec, async ({ step }) => {
62
+ await step.run({ name: "step-1" }, () => {
63
+ return "success";
64
+ });
65
+ return { result: "done" };
66
+ });
67
+ const explicitInputTypeSpec = defineWorkflowSpec({
68
+ name: "explicit-input-type",
69
+ });
70
+ defineWorkflow(explicitInputTypeSpec, async ({ step }) => {
71
+ await step.run({ name: "step-1" }, () => {
72
+ return "success";
73
+ });
74
+ return { result: "done" };
75
+ });
76
+ const explicitInputAndOutputTypesSpec = defineWorkflowSpec({
77
+ name: "explicit-input-and-output-types",
78
+ });
79
+ defineWorkflow(explicitInputAndOutputTypesSpec, async ({ step }) => {
80
+ await step.run({ name: "step-1" }, () => {
81
+ return "success";
82
+ });
83
+ return { result: "done" };
84
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openworkflow",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Open-source TypeScript framework for building durable, resumable workflows",
5
5
  "keywords": [
6
6
  "durable execution",
@@ -28,10 +28,25 @@
28
28
  "exports": {
29
29
  ".": {
30
30
  "types": "./dist/index.d.ts",
31
- "development": "./index.ts",
32
31
  "default": "./dist/index.js"
32
+ },
33
+ "./internal": {
34
+ "types": "./dist/internal.d.ts",
35
+ "default": "./dist/internal.js"
36
+ },
37
+ "./postgres": {
38
+ "types": "./dist/postgres.d.ts",
39
+ "default": "./dist/postgres.js"
40
+ },
41
+ "./sqlite": {
42
+ "types": "./dist/sqlite.d.ts",
43
+ "default": "./dist/sqlite.js"
33
44
  }
34
45
  },
46
+ "bin": {
47
+ "openworkflow": "./dist/bin/openworkflow.js",
48
+ "ow": "./dist/bin/openworkflow.js"
49
+ },
35
50
  "files": [
36
51
  "dist"
37
52
  ],
@@ -40,10 +55,19 @@
40
55
  "prepublishOnly": "npm run build"
41
56
  },
42
57
  "devDependencies": {
43
- "arktype": "^2.1.28",
58
+ "arktype": "^2.1.29",
44
59
  "valibot": "^1.2.0",
60
+ "vitest": "^4.0.18",
45
61
  "yup": "^1.7.1",
46
- "zod": "^4.1.13"
62
+ "zod": "^4.3.6"
63
+ },
64
+ "peerDependencies": {
65
+ "postgres": ">=3"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "postgres": {
69
+ "optional": true
70
+ }
47
71
  },
48
72
  "engines": {
49
73
  "node": ">=20"
@@ -1 +0,0 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../backend-sqlite/backend.ts"],"names":[],"mappings":"AAaA,OAAO,EAEL,OAAO,EACP,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,4BAA4B,EAC5B,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,WAAW,EACX,WAAW,EAGZ,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD,UAAU,oBAAoB;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC3C,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO;IAKP;;;;OAIG;IACH,MAAM,CAAC,OAAO,CACZ,IAAI,GAAE,MAA8B,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,aAAa;IAgBhB,IAAI,IAAI,IAAI;IAIN,iBAAiB,CACrB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC;IAgDvB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAenE,gBAAgB,CACpB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA0FxB,sBAAsB,CAC1B,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,WAAW,CAAC;IAmCjB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAoCtE,mBAAmB,CACvB,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,WAAW,CAAC;IAyCjB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC;IA+DpE,iBAAiB,CACrB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC;IAuDvB,gBAAgB,CACd,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAgE1C,gBAAgB,CACd,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAmE1C,OAAO,CAAC,wBAAwB;IAyC1B,iBAAiB,CACrB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC;IAwCvB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAenE,mBAAmB,CACvB,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,WAAW,CAAC;IA0DjB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC;CAyD3E"}