@sonamu-kit/tasks 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/.oxlintrc.json +3 -0
  2. package/AGENTS.md +21 -0
  3. package/dist/backend.d.ts +126 -107
  4. package/dist/backend.d.ts.map +1 -1
  5. package/dist/backend.js +4 -1
  6. package/dist/backend.js.map +1 -1
  7. package/dist/client.d.ts +145 -132
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +219 -213
  10. package/dist/client.js.map +1 -1
  11. package/dist/config.d.ts +15 -8
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +22 -17
  14. package/dist/config.js.map +1 -1
  15. package/dist/core/duration.d.ts +5 -4
  16. package/dist/core/duration.d.ts.map +1 -1
  17. package/dist/core/duration.js +54 -59
  18. package/dist/core/duration.js.map +1 -1
  19. package/dist/core/error.d.ts +10 -7
  20. package/dist/core/error.d.ts.map +1 -1
  21. package/dist/core/error.js +21 -21
  22. package/dist/core/error.js.map +1 -1
  23. package/dist/core/json.d.ts +8 -3
  24. package/dist/core/json.d.ts.map +1 -1
  25. package/dist/core/result.d.ts +10 -14
  26. package/dist/core/result.d.ts.map +1 -1
  27. package/dist/core/result.js +21 -16
  28. package/dist/core/result.js.map +1 -1
  29. package/dist/core/retry.d.ts +37 -31
  30. package/dist/core/retry.d.ts.map +1 -1
  31. package/dist/core/retry.js +44 -51
  32. package/dist/core/retry.js.map +1 -1
  33. package/dist/core/schema.d.ts +57 -53
  34. package/dist/core/schema.d.ts.map +1 -1
  35. package/dist/core/step.d.ts +28 -78
  36. package/dist/core/step.d.ts.map +1 -1
  37. package/dist/core/step.js +53 -63
  38. package/dist/core/step.js.map +1 -1
  39. package/dist/core/workflow.d.ts +33 -61
  40. package/dist/core/workflow.d.ts.map +1 -1
  41. package/dist/core/workflow.js +31 -41
  42. package/dist/core/workflow.js.map +1 -1
  43. package/dist/database/backend.d.ts +53 -46
  44. package/dist/database/backend.d.ts.map +1 -1
  45. package/dist/database/backend.js +544 -577
  46. package/dist/database/backend.js.map +1 -1
  47. package/dist/database/base.js +48 -25
  48. package/dist/database/base.js.map +1 -1
  49. package/dist/database/migrations/20251212000000_0_init.d.ts +10 -0
  50. package/dist/database/migrations/20251212000000_0_init.d.ts.map +1 -0
  51. package/dist/database/migrations/20251212000000_0_init.js +8 -4
  52. package/dist/database/migrations/20251212000000_0_init.js.map +1 -1
  53. package/dist/database/migrations/20251212000000_1_tables.d.ts +10 -0
  54. package/dist/database/migrations/20251212000000_1_tables.d.ts.map +1 -0
  55. package/dist/database/migrations/20251212000000_1_tables.js +81 -83
  56. package/dist/database/migrations/20251212000000_1_tables.js.map +1 -1
  57. package/dist/database/migrations/20251212000000_2_fk.d.ts +10 -0
  58. package/dist/database/migrations/20251212000000_2_fk.d.ts.map +1 -0
  59. package/dist/database/migrations/20251212000000_2_fk.js +20 -43
  60. package/dist/database/migrations/20251212000000_2_fk.js.map +1 -1
  61. package/dist/database/migrations/20251212000000_3_indexes.d.ts +10 -0
  62. package/dist/database/migrations/20251212000000_3_indexes.d.ts.map +1 -0
  63. package/dist/database/migrations/20251212000000_3_indexes.js +88 -102
  64. package/dist/database/migrations/20251212000000_3_indexes.js.map +1 -1
  65. package/dist/database/pubsub.d.ts +7 -16
  66. package/dist/database/pubsub.d.ts.map +1 -1
  67. package/dist/database/pubsub.js +75 -73
  68. package/dist/database/pubsub.js.map +1 -1
  69. package/dist/execution.d.ts +20 -59
  70. package/dist/execution.d.ts.map +1 -1
  71. package/dist/execution.js +175 -188
  72. package/dist/execution.js.map +1 -1
  73. package/dist/index.d.ts +5 -8
  74. package/dist/index.js +5 -5
  75. package/dist/internal.d.ts +12 -13
  76. package/dist/internal.js +4 -4
  77. package/dist/registry.d.ts +33 -27
  78. package/dist/registry.d.ts.map +1 -1
  79. package/dist/registry.js +58 -49
  80. package/dist/registry.js.map +1 -1
  81. package/dist/worker.d.ts +57 -50
  82. package/dist/worker.d.ts.map +1 -1
  83. package/dist/worker.js +194 -199
  84. package/dist/worker.js.map +1 -1
  85. package/dist/workflow.d.ts +26 -30
  86. package/dist/workflow.d.ts.map +1 -1
  87. package/dist/workflow.js +20 -15
  88. package/dist/workflow.js.map +1 -1
  89. package/nodemon.json +1 -1
  90. package/package.json +17 -19
  91. package/src/backend.ts +25 -9
  92. package/src/chaos.test.ts +3 -1
  93. package/src/client.test.ts +2 -0
  94. package/src/client.ts +30 -8
  95. package/src/config.test.ts +1 -0
  96. package/src/config.ts +3 -2
  97. package/src/core/duration.test.ts +2 -1
  98. package/src/core/duration.ts +1 -1
  99. package/src/core/error.test.ts +1 -0
  100. package/src/core/error.ts +1 -1
  101. package/src/core/result.test.ts +1 -0
  102. package/src/core/retry.test.ts +3 -2
  103. package/src/core/retry.ts +1 -1
  104. package/src/core/schema.ts +2 -2
  105. package/src/core/step.test.ts +2 -1
  106. package/src/core/step.ts +4 -3
  107. package/src/core/workflow.test.ts +2 -1
  108. package/src/core/workflow.ts +4 -3
  109. package/src/database/backend.test.ts +1 -0
  110. package/src/database/backend.testsuite.ts +44 -40
  111. package/src/database/backend.ts +207 -25
  112. package/src/database/base.test.ts +41 -0
  113. package/src/database/base.ts +51 -2
  114. package/src/database/migrations/20251212000000_0_init.ts +2 -1
  115. package/src/database/migrations/20251212000000_1_tables.ts +2 -1
  116. package/src/database/migrations/20251212000000_2_fk.ts +2 -1
  117. package/src/database/migrations/20251212000000_3_indexes.ts +2 -1
  118. package/src/database/pubsub.test.ts +6 -3
  119. package/src/database/pubsub.ts +55 -33
  120. package/src/execution.test.ts +2 -0
  121. package/src/execution.ts +49 -10
  122. package/src/internal.ts +15 -15
  123. package/src/practices/01-remote-workflow.ts +1 -0
  124. package/src/registry.test.ts +1 -0
  125. package/src/registry.ts +1 -1
  126. package/src/testing/connection.ts +3 -1
  127. package/src/worker.test.ts +2 -0
  128. package/src/worker.ts +30 -9
  129. package/src/workflow.test.ts +1 -0
  130. package/src/workflow.ts +3 -3
  131. package/templates/openworkflow.config.ts +2 -1
  132. package/tsdown.config.ts +31 -0
  133. package/.swcrc +0 -17
  134. package/dist/chaos.test.d.ts +0 -2
  135. package/dist/chaos.test.d.ts.map +0 -1
  136. package/dist/chaos.test.js +0 -92
  137. package/dist/chaos.test.js.map +0 -1
  138. package/dist/client.test.d.ts +0 -2
  139. package/dist/client.test.d.ts.map +0 -1
  140. package/dist/client.test.js +0 -340
  141. package/dist/client.test.js.map +0 -1
  142. package/dist/config.test.d.ts +0 -2
  143. package/dist/config.test.d.ts.map +0 -1
  144. package/dist/config.test.js +0 -24
  145. package/dist/config.test.js.map +0 -1
  146. package/dist/core/duration.test.d.ts +0 -2
  147. package/dist/core/duration.test.d.ts.map +0 -1
  148. package/dist/core/duration.test.js +0 -265
  149. package/dist/core/duration.test.js.map +0 -1
  150. package/dist/core/error.test.d.ts +0 -2
  151. package/dist/core/error.test.d.ts.map +0 -1
  152. package/dist/core/error.test.js +0 -63
  153. package/dist/core/error.test.js.map +0 -1
  154. package/dist/core/json.js +0 -3
  155. package/dist/core/json.js.map +0 -1
  156. package/dist/core/result.test.d.ts +0 -2
  157. package/dist/core/result.test.d.ts.map +0 -1
  158. package/dist/core/result.test.js +0 -19
  159. package/dist/core/result.test.js.map +0 -1
  160. package/dist/core/retry.test.d.ts +0 -2
  161. package/dist/core/retry.test.d.ts.map +0 -1
  162. package/dist/core/retry.test.js +0 -198
  163. package/dist/core/retry.test.js.map +0 -1
  164. package/dist/core/schema.js +0 -4
  165. package/dist/core/schema.js.map +0 -1
  166. package/dist/core/step.test.d.ts +0 -2
  167. package/dist/core/step.test.d.ts.map +0 -1
  168. package/dist/core/step.test.js +0 -356
  169. package/dist/core/step.test.js.map +0 -1
  170. package/dist/core/workflow.test.d.ts +0 -2
  171. package/dist/core/workflow.test.d.ts.map +0 -1
  172. package/dist/core/workflow.test.js +0 -172
  173. package/dist/core/workflow.test.js.map +0 -1
  174. package/dist/database/backend.test.d.ts +0 -2
  175. package/dist/database/backend.test.d.ts.map +0 -1
  176. package/dist/database/backend.test.js +0 -19
  177. package/dist/database/backend.test.js.map +0 -1
  178. package/dist/database/backend.testsuite.d.ts +0 -20
  179. package/dist/database/backend.testsuite.d.ts.map +0 -1
  180. package/dist/database/backend.testsuite.js +0 -1280
  181. package/dist/database/backend.testsuite.js.map +0 -1
  182. package/dist/database/base.d.ts +0 -12
  183. package/dist/database/base.d.ts.map +0 -1
  184. package/dist/database/pubsub.test.d.ts +0 -2
  185. package/dist/database/pubsub.test.d.ts.map +0 -1
  186. package/dist/database/pubsub.test.js +0 -86
  187. package/dist/database/pubsub.test.js.map +0 -1
  188. package/dist/execution.test.d.ts +0 -2
  189. package/dist/execution.test.d.ts.map +0 -1
  190. package/dist/execution.test.js +0 -662
  191. package/dist/execution.test.js.map +0 -1
  192. package/dist/index.d.ts.map +0 -1
  193. package/dist/index.js.map +0 -1
  194. package/dist/internal.d.ts.map +0 -1
  195. package/dist/internal.js.map +0 -1
  196. package/dist/practices/01-remote-workflow.d.ts +0 -2
  197. package/dist/practices/01-remote-workflow.d.ts.map +0 -1
  198. package/dist/practices/01-remote-workflow.js +0 -70
  199. package/dist/practices/01-remote-workflow.js.map +0 -1
  200. package/dist/registry.test.d.ts +0 -2
  201. package/dist/registry.test.d.ts.map +0 -1
  202. package/dist/registry.test.js +0 -95
  203. package/dist/registry.test.js.map +0 -1
  204. package/dist/testing/connection.d.ts +0 -7
  205. package/dist/testing/connection.d.ts.map +0 -1
  206. package/dist/testing/connection.js +0 -39
  207. package/dist/testing/connection.js.map +0 -1
  208. package/dist/worker.test.d.ts +0 -2
  209. package/dist/worker.test.d.ts.map +0 -1
  210. package/dist/worker.test.js +0 -1164
  211. package/dist/worker.test.js.map +0 -1
  212. package/dist/workflow.test.d.ts +0 -2
  213. package/dist/workflow.test.d.ts.map +0 -1
  214. package/dist/workflow.test.js +0 -73
  215. package/dist/workflow.test.js.map +0 -1
package/dist/worker.js CHANGED
@@ -1,207 +1,202 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { executeWorkflow } from "./execution.js";
3
- const DEFAULT_LEASE_DURATION_MS = 30 * 1000; // 30s
4
- const DEFAULT_POLL_INTERVAL_MS = 100; // 100ms
2
+ import { randomUUID } from "node:crypto";
3
+
4
+ //#region src/worker.ts
5
+ const DEFAULT_LEASE_DURATION_MS = 30 * 1e3;
6
+ const DEFAULT_POLL_INTERVAL_MS = 100;
5
7
  const DEFAULT_CONCURRENCY = 1;
6
8
  /**
7
- * Runs workflows by polling the backend, dispatching runs across a concurrency
8
- * pool, and heartbeating/extending leases.
9
- */ export class Worker {
10
- backend;
11
- workerIds;
12
- registry;
13
- activeExecutions = new Set();
14
- running = false;
15
- loopPromise = null;
16
- usePubSub;
17
- listenDelay;
18
- constructor(options){
19
- this.backend = options.backend;
20
- this.registry = options.registry;
21
- this.usePubSub = options.usePubSub ?? true;
22
- this.listenDelay = options.listenDelay ?? 500;
23
- const concurrency = Math.max(DEFAULT_CONCURRENCY, options.concurrency ?? DEFAULT_CONCURRENCY);
24
- // generate worker IDs for every concurrency slot
25
- this.workerIds = Array.from({
26
- length: concurrency
27
- }, ()=>randomUUID());
28
- }
29
- /**
30
- * Start the worker. It will begin polling for and executing workflows.
31
- * @returns Promise resolved when started
32
- */ async start() {
33
- if (this.running) return;
34
- this.running = true;
35
- this.loopPromise = this.runLoop();
36
- await Promise.resolve();
37
- }
38
- /**
39
- * Stop the worker gracefully. Waits for all active workflow runs to complete
40
- * before returning.
41
- * @returns Promise resolved when stopped
42
- */ async stop() {
43
- this.running = false;
44
- // wait for the poll loop to stop
45
- if (this.loopPromise) await this.loopPromise;
46
- // wait for all active executions to finish
47
- while(this.activeExecutions.size > 0)await sleep(100);
48
- }
49
- /**
50
- * Processes one round of work claims and execution. Exposed for testing.
51
- * Returns the number of workflow runs claimed.
52
- * @returns Number of workflow runs claimed
53
- */ async tick() {
54
- const availableSlots = this.concurrency - this.activeExecutions.size;
55
- if (availableSlots <= 0) return 0;
56
- // claim work for each available slot
57
- const claims = Array.from({
58
- length: availableSlots
59
- }, (_, i)=>{
60
- const availableWorkerId = this.workerIds[i % this.workerIds.length];
61
- return availableWorkerId ? this.claimAndProcessWorkflowRunInBackground(availableWorkerId) : Promise.resolve(null);
62
- });
63
- const claimed = await Promise.all(claims);
64
- return claimed.filter((run)=>run !== null).length;
65
- }
66
- /**
67
- * Get the configured concurrency limit.
68
- * @returns Concurrency limit
69
- */ get concurrency() {
70
- return this.workerIds.length;
71
- }
72
- /*
73
- * Main run loop that continuously ticks while the worker is running.
74
- * Only sleeps when no work was claimed to avoid busy-waiting.
75
- */ async runLoop() {
76
- if (this.usePubSub) {
77
- this.backend.subscribe(async (result)=>{
78
- if (!result.ok) {
79
- return;
80
- }
81
- await sleep(this.listenDelay);
82
- await this.tick();
83
- });
84
- }
85
- while(this.running){
86
- try {
87
- const claimedCount = await this.tick();
88
- // only sleep if we didn't claim any work
89
- if (claimedCount === 0) {
90
- await sleep(DEFAULT_POLL_INTERVAL_MS);
91
- }
92
- } catch (error) {
93
- console.error("Worker tick failed:", error);
94
- await sleep(DEFAULT_POLL_INTERVAL_MS);
95
- }
96
- }
97
- }
98
- /*
99
- * Cclaim and process a workflow run for the given worker ID. Do not await the
100
- * processing here to avoid blocking the caller.
101
- * Returns the claimed workflow run, or null if none was available.
102
- */ async claimAndProcessWorkflowRunInBackground(workerId) {
103
- // claim workflow run
104
- const workflowRun = await this.backend.claimWorkflowRun({
105
- workerId,
106
- leaseDurationMs: DEFAULT_LEASE_DURATION_MS
107
- });
108
- if (!workflowRun) return null;
109
- const workflow = this.registry.get(workflowRun.workflowName, workflowRun.version);
110
- if (!workflow) {
111
- const versionStr = workflowRun.version ? ` (version: ${workflowRun.version})` : "";
112
- await this.backend.failWorkflowRun({
113
- workflowRunId: workflowRun.id,
114
- workerId,
115
- error: {
116
- message: `Workflow "${workflowRun.workflowName}"${versionStr} is not registered`
117
- }
118
- });
119
- return null;
120
- }
121
- // create execution and start processing *async* w/o blocking
122
- const execution = new WorkflowExecution({
123
- backend: this.backend,
124
- workflowRun,
125
- workerId
126
- });
127
- this.activeExecutions.add(execution);
128
- this.processExecutionInBackground(execution, workflow).catch(()=>{
129
- // errors are already handled in processExecution
130
- }).finally(()=>{
131
- execution.stopHeartbeat();
132
- this.activeExecutions.delete(execution);
133
- });
134
- return workflowRun;
135
- }
136
- /**
137
- * Process a workflow execution, handling heartbeats, step execution, and
138
- * marking success or failure.
139
- * @param execution - Workflow execution
140
- * @param workflow - Workflow to execute
141
- * @returns Promise resolved when processing completes
142
- */ async processExecutionInBackground(execution, workflow) {
143
- // start heartbeating
144
- execution.startHeartbeat();
145
- try {
146
- await executeWorkflow({
147
- backend: this.backend,
148
- workflowRun: execution.workflowRun,
149
- workflowFn: workflow.fn,
150
- workflowVersion: execution.workflowRun.version,
151
- workerId: execution.workerId,
152
- retryPolicy: workflow.spec.retryPolicy
153
- });
154
- } catch (error) {
155
- // specifically for unexpected errors in the execution wrapper itself, not
156
- // for business logic errors (those are handled inside executeWorkflow)
157
- console.error(`Critical error during workflow execution for run ${execution.workflowRun.id}:`, error);
158
- }
159
- }
160
- }
9
+ * Runs workflows by polling the backend, dispatching runs across a concurrency
10
+ * pool, and heartbeating/extending leases.
11
+ */
12
+ var Worker = class {
13
+ backend;
14
+ workerIds;
15
+ registry;
16
+ activeExecutions = /* @__PURE__ */ new Set();
17
+ running = false;
18
+ loopPromise = null;
19
+ subscribed = false;
20
+ usePubSub;
21
+ listenDelay;
22
+ constructor(options) {
23
+ this.backend = options.backend;
24
+ this.registry = options.registry;
25
+ this.usePubSub = options.usePubSub ?? true;
26
+ this.listenDelay = options.listenDelay ?? 500;
27
+ const concurrency = Math.max(DEFAULT_CONCURRENCY, options.concurrency ?? DEFAULT_CONCURRENCY);
28
+ this.workerIds = Array.from({ length: concurrency }, () => randomUUID());
29
+ }
30
+ /**
31
+ * Start the worker. It will begin polling for and executing workflows.
32
+ * @returns Promise resolved when started
33
+ */
34
+ async start() {
35
+ if (this.running) return;
36
+ this.running = true;
37
+ this.loopPromise = this.runLoop();
38
+ await Promise.resolve();
39
+ }
40
+ /**
41
+ * Stop the worker gracefully. Waits for all active workflow runs to complete
42
+ * before returning.
43
+ * @returns Promise resolved when stopped
44
+ */
45
+ async stop() {
46
+ this.running = false;
47
+ if (this.loopPromise) await this.loopPromise;
48
+ while (this.activeExecutions.size > 0) await sleep(100);
49
+ this.subscribed = false;
50
+ }
51
+ /**
52
+ * Processes one round of work claims and execution. Exposed for testing.
53
+ * Returns the number of workflow runs claimed.
54
+ * @returns Number of workflow runs claimed
55
+ */
56
+ async tick() {
57
+ const availableSlots = this.concurrency - this.activeExecutions.size;
58
+ if (availableSlots <= 0) return 0;
59
+ const claims = Array.from({ length: availableSlots }, (_, i) => {
60
+ const availableWorkerId = this.workerIds[i % this.workerIds.length];
61
+ return availableWorkerId ? this.claimAndProcessWorkflowRunInBackground(availableWorkerId) : Promise.resolve(null);
62
+ });
63
+ return (await Promise.all(claims)).filter((run) => run !== null).length;
64
+ }
65
+ /**
66
+ * Get the configured concurrency limit.
67
+ * @returns Concurrency limit
68
+ */
69
+ get concurrency() {
70
+ return this.workerIds.length;
71
+ }
72
+ async runLoop() {
73
+ if (this.usePubSub && !this.subscribed) {
74
+ this.subscribed = true;
75
+ this.backend.subscribe(async (result) => {
76
+ if (!result.ok || !this.running) return;
77
+ await sleep(this.listenDelay);
78
+ await this.tick();
79
+ });
80
+ }
81
+ while (this.running) try {
82
+ if (await this.tick() === 0) await sleep(DEFAULT_POLL_INTERVAL_MS);
83
+ } catch (error) {
84
+ console.error("Worker tick failed:", error);
85
+ await sleep(DEFAULT_POLL_INTERVAL_MS);
86
+ }
87
+ }
88
+ async claimAndProcessWorkflowRunInBackground(workerId) {
89
+ const workflowRun = await this.backend.claimWorkflowRun({
90
+ workerId,
91
+ leaseDurationMs: DEFAULT_LEASE_DURATION_MS
92
+ });
93
+ if (!workflowRun) return null;
94
+ const workflow = this.registry.get(workflowRun.workflowName, workflowRun.version);
95
+ if (!workflow) {
96
+ const versionStr = workflowRun.version ? ` (version: ${workflowRun.version})` : "";
97
+ await this.backend.failWorkflowRun({
98
+ workflowRunId: workflowRun.id,
99
+ workerId,
100
+ error: { message: `Workflow "${workflowRun.workflowName}"${versionStr} is not registered` }
101
+ });
102
+ return null;
103
+ }
104
+ const execution = new WorkflowExecution({
105
+ backend: this.backend,
106
+ workflowRun,
107
+ workerId
108
+ });
109
+ this.activeExecutions.add(execution);
110
+ this.processExecutionInBackground(execution, workflow).catch(() => {}).finally(() => {
111
+ execution.stopHeartbeat();
112
+ this.activeExecutions.delete(execution);
113
+ });
114
+ return workflowRun;
115
+ }
116
+ /**
117
+ * Process a workflow execution, handling heartbeats, step execution, and
118
+ * marking success or failure.
119
+ * @param execution - Workflow execution
120
+ * @param workflow - Workflow to execute
121
+ * @returns Promise resolved when processing completes
122
+ */
123
+ async processExecutionInBackground(execution, workflow) {
124
+ execution.startHeartbeat();
125
+ try {
126
+ await executeWorkflow({
127
+ backend: this.backend,
128
+ workflowRun: execution.workflowRun,
129
+ workflowFn: workflow.fn,
130
+ workflowVersion: execution.workflowRun.version,
131
+ workerId: execution.workerId,
132
+ retryPolicy: workflow.spec.retryPolicy,
133
+ signal: execution.signal
134
+ });
135
+ } catch (error) {
136
+ if (execution.signal.aborted) return;
137
+ console.error(`Critical error during workflow execution for run ${execution.workflowRun.id}:`, error);
138
+ }
139
+ }
140
+ };
161
141
  /**
162
- * Tracks a claimed workflow run and maintains its heartbeat lease for the
163
- * worker.
164
- */ class WorkflowExecution {
165
- backend;
166
- workflowRun;
167
- workerId;
168
- heartbeatTimer = null;
169
- constructor(options){
170
- this.backend = options.backend;
171
- this.workflowRun = options.workflowRun;
172
- this.workerId = options.workerId;
173
- }
174
- /**
175
- * Start the heartbeat loop for this execution, heartbeating at half the lease
176
- * duration.
177
- */ startHeartbeat() {
178
- const leaseDurationMs = DEFAULT_LEASE_DURATION_MS;
179
- const heartbeatIntervalMs = leaseDurationMs / 2;
180
- this.heartbeatTimer = setInterval(()=>{
181
- this.backend.extendWorkflowRunLease({
182
- workflowRunId: this.workflowRun.id,
183
- workerId: this.workerId,
184
- leaseDurationMs
185
- }).catch((error)=>{
186
- console.error("Heartbeat failed:", error);
187
- });
188
- }, heartbeatIntervalMs);
189
- }
190
- /**
191
- * Stop the heartbeat loop.
192
- */ stopHeartbeat() {
193
- if (this.heartbeatTimer) {
194
- clearInterval(this.heartbeatTimer);
195
- this.heartbeatTimer = null;
196
- }
197
- }
198
- }
142
+ * Tracks a claimed workflow run and maintains its heartbeat lease for the
143
+ * worker.
144
+ */
145
+ var WorkflowExecution = class {
146
+ backend;
147
+ workflowRun;
148
+ workerId;
149
+ heartbeatTimer = null;
150
+ abortController = new AbortController();
151
+ constructor(options) {
152
+ this.backend = options.backend;
153
+ this.workflowRun = options.workflowRun;
154
+ this.workerId = options.workerId;
155
+ }
156
+ /**
157
+ * 외부에서 abort 여부를 확인할 수 있는 signal입니다.
158
+ * heartbeat 실패 시 abort됩니다.
159
+ */
160
+ get signal() {
161
+ return this.abortController.signal;
162
+ }
163
+ /**
164
+ * Start the heartbeat loop for this execution, heartbeating at half the lease
165
+ * duration.
166
+ */
167
+ startHeartbeat() {
168
+ const leaseDurationMs = DEFAULT_LEASE_DURATION_MS;
169
+ const heartbeatIntervalMs = leaseDurationMs / 2;
170
+ this.heartbeatTimer = setInterval(() => {
171
+ this.backend.extendWorkflowRunLease({
172
+ workflowRunId: this.workflowRun.id,
173
+ workerId: this.workerId,
174
+ leaseDurationMs
175
+ }).catch((_error) => {
176
+ this.abortController.abort();
177
+ this.stopHeartbeat();
178
+ });
179
+ }, heartbeatIntervalMs);
180
+ }
181
+ /**
182
+ * Stop the heartbeat loop.
183
+ */
184
+ stopHeartbeat() {
185
+ if (this.heartbeatTimer) {
186
+ clearInterval(this.heartbeatTimer);
187
+ this.heartbeatTimer = null;
188
+ }
189
+ }
190
+ };
199
191
  /**
200
- * Sleep for a given duration.
201
- * @param ms - Milliseconds to sleep
202
- * @returns Promise resolved after sleeping
203
- */ function sleep(ms) {
204
- return new Promise((resolve)=>setTimeout(resolve, ms));
192
+ * Sleep for a given duration.
193
+ * @param ms - Milliseconds to sleep
194
+ * @returns Promise resolved after sleeping
195
+ */
196
+ function sleep(ms) {
197
+ return new Promise((resolve) => setTimeout(resolve, ms));
205
198
  }
206
199
 
200
+ //#endregion
201
+ export { Worker };
207
202
  //# sourceMappingURL=worker.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Backend } from \"./backend\";\nimport type { WorkflowRun } from \"./core/workflow\";\nimport { executeWorkflow } from \"./execution\";\nimport type { WorkflowRegistry } from \"./registry\";\nimport type { Workflow } from \"./workflow\";\n\nconst DEFAULT_LEASE_DURATION_MS = 30 * 1000; // 30s\nconst DEFAULT_POLL_INTERVAL_MS = 100; // 100ms\nconst DEFAULT_CONCURRENCY = 1;\n\n/**\n * Configures how a Worker polls the backend, leases workflow runs, and\n * registers workflows.\n */\nexport interface WorkerOptions {\n backend: Backend;\n registry: WorkflowRegistry;\n concurrency?: number | undefined;\n usePubSub?: boolean;\n listenDelay?: number;\n}\n\n/**\n * Runs workflows by polling the backend, dispatching runs across a concurrency\n * pool, and heartbeating/extending leases.\n */\nexport class Worker {\n private readonly backend: Backend;\n private readonly workerIds: string[];\n private readonly registry: WorkflowRegistry;\n private readonly activeExecutions = new Set<WorkflowExecution>();\n private running = false;\n private loopPromise: Promise<void> | null = null;\n\n private usePubSub: boolean;\n private listenDelay: number;\n\n constructor(options: WorkerOptions) {\n this.backend = options.backend;\n this.registry = options.registry;\n this.usePubSub = options.usePubSub ?? true;\n this.listenDelay = options.listenDelay ?? 500;\n\n const concurrency = Math.max(DEFAULT_CONCURRENCY, options.concurrency ?? DEFAULT_CONCURRENCY);\n\n // generate worker IDs for every concurrency slot\n this.workerIds = Array.from({ length: concurrency }, () => randomUUID());\n }\n\n /**\n * Start the worker. It will begin polling for and executing workflows.\n * @returns Promise resolved when started\n */\n async start(): Promise<void> {\n if (this.running) return;\n this.running = true;\n this.loopPromise = this.runLoop();\n await Promise.resolve();\n }\n\n /**\n * Stop the worker gracefully. Waits for all active workflow runs to complete\n * before returning.\n * @returns Promise resolved when stopped\n */\n async stop(): Promise<void> {\n this.running = false;\n\n // wait for the poll loop to stop\n if (this.loopPromise) await this.loopPromise;\n\n // wait for all active executions to finish\n while (this.activeExecutions.size > 0) await sleep(100);\n }\n\n /**\n * Processes one round of work claims and execution. Exposed for testing.\n * Returns the number of workflow runs claimed.\n * @returns Number of workflow runs claimed\n */\n async tick(): Promise<number> {\n const availableSlots = this.concurrency - this.activeExecutions.size;\n if (availableSlots <= 0) return 0;\n\n // claim work for each available slot\n const claims = Array.from({ length: availableSlots }, (_, i) => {\n const availableWorkerId = this.workerIds[i % this.workerIds.length];\n return availableWorkerId\n ? this.claimAndProcessWorkflowRunInBackground(availableWorkerId)\n : Promise.resolve(null);\n });\n\n const claimed = await Promise.all(claims);\n return claimed.filter((run) => run !== null).length;\n }\n\n /**\n * Get the configured concurrency limit.\n * @returns Concurrency limit\n */\n private get concurrency(): number {\n return this.workerIds.length;\n }\n\n /*\n * Main run loop that continuously ticks while the worker is running.\n * Only sleeps when no work was claimed to avoid busy-waiting.\n */\n private async runLoop(): Promise<void> {\n if (this.usePubSub) {\n this.backend.subscribe(async (result) => {\n if (!result.ok) {\n return;\n }\n\n await sleep(this.listenDelay);\n await this.tick();\n });\n }\n\n while (this.running) {\n try {\n const claimedCount = await this.tick();\n // only sleep if we didn't claim any work\n if (claimedCount === 0) {\n await sleep(DEFAULT_POLL_INTERVAL_MS);\n }\n } catch (error) {\n console.error(\"Worker tick failed:\", error);\n await sleep(DEFAULT_POLL_INTERVAL_MS);\n }\n }\n }\n\n /*\n * Cclaim and process a workflow run for the given worker ID. Do not await the\n * processing here to avoid blocking the caller.\n * Returns the claimed workflow run, or null if none was available.\n */\n private async claimAndProcessWorkflowRunInBackground(\n workerId: string,\n ): Promise<WorkflowRun | null> {\n // claim workflow run\n const workflowRun = await this.backend.claimWorkflowRun({\n workerId,\n leaseDurationMs: DEFAULT_LEASE_DURATION_MS,\n });\n if (!workflowRun) return null;\n\n const workflow = this.registry.get(workflowRun.workflowName, workflowRun.version);\n if (!workflow) {\n const versionStr = workflowRun.version ? ` (version: ${workflowRun.version})` : \"\";\n await this.backend.failWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n error: {\n message: `Workflow \"${workflowRun.workflowName}\"${versionStr} is not registered`,\n },\n });\n return null;\n }\n\n // create execution and start processing *async* w/o blocking\n const execution = new WorkflowExecution({\n backend: this.backend,\n workflowRun,\n workerId,\n });\n this.activeExecutions.add(execution);\n\n this.processExecutionInBackground(execution, workflow)\n .catch(() => {\n // errors are already handled in processExecution\n })\n .finally(() => {\n execution.stopHeartbeat();\n this.activeExecutions.delete(execution);\n });\n\n return workflowRun;\n }\n\n /**\n * Process a workflow execution, handling heartbeats, step execution, and\n * marking success or failure.\n * @param execution - Workflow execution\n * @param workflow - Workflow to execute\n * @returns Promise resolved when processing completes\n */\n private async processExecutionInBackground(\n execution: WorkflowExecution,\n workflow: Workflow<unknown, unknown, unknown>,\n ): Promise<void> {\n // start heartbeating\n execution.startHeartbeat();\n\n try {\n await executeWorkflow({\n backend: this.backend,\n workflowRun: execution.workflowRun,\n workflowFn: workflow.fn,\n workflowVersion: execution.workflowRun.version,\n workerId: execution.workerId,\n retryPolicy: workflow.spec.retryPolicy,\n });\n } catch (error) {\n // specifically for unexpected errors in the execution wrapper itself, not\n // for business logic errors (those are handled inside executeWorkflow)\n console.error(\n `Critical error during workflow execution for run ${execution.workflowRun.id}:`,\n error,\n );\n }\n }\n}\n\n/**\n * Configures the options for a WorkflowExecution.\n */\ninterface WorkflowExecutionOptions {\n backend: Backend;\n workflowRun: WorkflowRun;\n workerId: string;\n}\n\n/**\n * Tracks a claimed workflow run and maintains its heartbeat lease for the\n * worker.\n */\nclass WorkflowExecution {\n private backend: Backend;\n workflowRun: WorkflowRun;\n workerId: string;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n\n constructor(options: WorkflowExecutionOptions) {\n this.backend = options.backend;\n this.workflowRun = options.workflowRun;\n this.workerId = options.workerId;\n }\n\n /**\n * Start the heartbeat loop for this execution, heartbeating at half the lease\n * duration.\n */\n startHeartbeat(): void {\n const leaseDurationMs = DEFAULT_LEASE_DURATION_MS;\n const heartbeatIntervalMs = leaseDurationMs / 2;\n\n this.heartbeatTimer = setInterval(() => {\n this.backend\n .extendWorkflowRunLease({\n workflowRunId: this.workflowRun.id,\n workerId: this.workerId,\n leaseDurationMs,\n })\n .catch((error: unknown) => {\n console.error(\"Heartbeat failed:\", error);\n });\n }, heartbeatIntervalMs);\n }\n\n /**\n * Stop the heartbeat loop.\n */\n stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n}\n\n/**\n * Sleep for a given duration.\n * @param ms - Milliseconds to sleep\n * @returns Promise resolved after sleeping\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"names":["randomUUID","executeWorkflow","DEFAULT_LEASE_DURATION_MS","DEFAULT_POLL_INTERVAL_MS","DEFAULT_CONCURRENCY","Worker","backend","workerIds","registry","activeExecutions","Set","running","loopPromise","usePubSub","listenDelay","options","concurrency","Math","max","Array","from","length","start","runLoop","Promise","resolve","stop","size","sleep","tick","availableSlots","claims","_","i","availableWorkerId","claimAndProcessWorkflowRunInBackground","claimed","all","filter","run","subscribe","result","ok","claimedCount","error","console","workerId","workflowRun","claimWorkflowRun","leaseDurationMs","workflow","get","workflowName","version","versionStr","failWorkflowRun","workflowRunId","id","message","execution","WorkflowExecution","add","processExecutionInBackground","catch","finally","stopHeartbeat","delete","startHeartbeat","workflowFn","fn","workflowVersion","retryPolicy","spec","heartbeatTimer","heartbeatIntervalMs","setInterval","extendWorkflowRunLease","clearInterval","ms","setTimeout"],"mappings":"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"}
1
+ {"version":3,"file":"worker.js","names":[],"sources":["../src/worker.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport { type Backend } from \"./backend\";\nimport { type WorkflowRun } from \"./core/workflow\";\nimport { executeWorkflow } from \"./execution\";\nimport { type WorkflowRegistry } from \"./registry\";\nimport { type Workflow } from \"./workflow\";\n\nconst DEFAULT_LEASE_DURATION_MS = 30 * 1000; // 30s\nconst DEFAULT_POLL_INTERVAL_MS = 100; // 100ms\nconst DEFAULT_CONCURRENCY = 1;\n\n/**\n * Configures how a Worker polls the backend, leases workflow runs, and\n * registers workflows.\n */\nexport interface WorkerOptions {\n backend: Backend;\n registry: WorkflowRegistry;\n concurrency?: number | undefined;\n usePubSub?: boolean;\n listenDelay?: number;\n}\n\n/**\n * Runs workflows by polling the backend, dispatching runs across a concurrency\n * pool, and heartbeating/extending leases.\n */\nexport class Worker {\n private readonly backend: Backend;\n private readonly workerIds: string[];\n private readonly registry: WorkflowRegistry;\n private readonly activeExecutions = new Set<WorkflowExecution>();\n private running = false;\n private loopPromise: Promise<void> | null = null;\n private subscribed = false;\n\n private usePubSub: boolean;\n private listenDelay: number;\n\n constructor(options: WorkerOptions) {\n this.backend = options.backend;\n this.registry = options.registry;\n this.usePubSub = options.usePubSub ?? true;\n this.listenDelay = options.listenDelay ?? 500;\n\n const concurrency = Math.max(DEFAULT_CONCURRENCY, options.concurrency ?? DEFAULT_CONCURRENCY);\n\n // generate worker IDs for every concurrency slot\n this.workerIds = Array.from({ length: concurrency }, () => randomUUID());\n }\n\n /**\n * Start the worker. It will begin polling for and executing workflows.\n * @returns Promise resolved when started\n */\n async start(): Promise<void> {\n if (this.running) return;\n this.running = true;\n this.loopPromise = this.runLoop();\n await Promise.resolve();\n }\n\n /**\n * Stop the worker gracefully. Waits for all active workflow runs to complete\n * before returning.\n * @returns Promise resolved when stopped\n */\n async stop(): Promise<void> {\n this.running = false;\n\n // wait for the poll loop to stop\n if (this.loopPromise) await this.loopPromise;\n\n // wait for all active executions to finish\n while (this.activeExecutions.size > 0) await sleep(100);\n\n this.subscribed = false;\n }\n\n /**\n * Processes one round of work claims and execution. Exposed for testing.\n * Returns the number of workflow runs claimed.\n * @returns Number of workflow runs claimed\n */\n async tick(): Promise<number> {\n const availableSlots = this.concurrency - this.activeExecutions.size;\n if (availableSlots <= 0) return 0;\n\n // claim work for each available slot\n const claims = Array.from({ length: availableSlots }, (_, i) => {\n const availableWorkerId = this.workerIds[i % this.workerIds.length];\n return availableWorkerId\n ? this.claimAndProcessWorkflowRunInBackground(availableWorkerId)\n : Promise.resolve(null);\n });\n\n const claimed = await Promise.all(claims);\n return claimed.filter((run) => run !== null).length;\n }\n\n /**\n * Get the configured concurrency limit.\n * @returns Concurrency limit\n */\n private get concurrency(): number {\n return this.workerIds.length;\n }\n\n /*\n * Main run loop that continuously ticks while the worker is running.\n * Only sleeps when no work was claimed to avoid busy-waiting.\n */\n private async runLoop(): Promise<void> {\n if (this.usePubSub && !this.subscribed) {\n this.subscribed = true;\n this.backend.subscribe(async (result) => {\n if (!result.ok || !this.running) {\n return;\n }\n\n await sleep(this.listenDelay);\n await this.tick();\n });\n }\n\n while (this.running) {\n try {\n const claimedCount = await this.tick();\n // only sleep if we didn't claim any work\n if (claimedCount === 0) {\n await sleep(DEFAULT_POLL_INTERVAL_MS);\n }\n } catch (error) {\n console.error(\"Worker tick failed:\", error);\n await sleep(DEFAULT_POLL_INTERVAL_MS);\n }\n }\n }\n\n /*\n * Claim and process a workflow run for the given worker ID. Do not await the\n * processing here to avoid blocking the caller.\n * Returns the claimed workflow run, or null if none was available.\n */\n private async claimAndProcessWorkflowRunInBackground(\n workerId: string,\n ): Promise<WorkflowRun | null> {\n // claim workflow run\n const workflowRun = await this.backend.claimWorkflowRun({\n workerId,\n leaseDurationMs: DEFAULT_LEASE_DURATION_MS,\n });\n if (!workflowRun) return null;\n\n const workflow = this.registry.get(workflowRun.workflowName, workflowRun.version);\n if (!workflow) {\n const versionStr = workflowRun.version ? ` (version: ${workflowRun.version})` : \"\";\n await this.backend.failWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n error: {\n message: `Workflow \"${workflowRun.workflowName}\"${versionStr} is not registered`,\n },\n });\n return null;\n }\n\n // create execution and start processing *async* w/o blocking\n const execution = new WorkflowExecution({\n backend: this.backend,\n workflowRun,\n workerId,\n });\n this.activeExecutions.add(execution);\n\n this.processExecutionInBackground(execution, workflow)\n .catch(() => {\n // errors are already handled in processExecution\n })\n .finally(() => {\n execution.stopHeartbeat();\n this.activeExecutions.delete(execution);\n });\n\n return workflowRun;\n }\n\n /**\n * Process a workflow execution, handling heartbeats, step execution, and\n * marking success or failure.\n * @param execution - Workflow execution\n * @param workflow - Workflow to execute\n * @returns Promise resolved when processing completes\n */\n private async processExecutionInBackground(\n execution: WorkflowExecution,\n workflow: Workflow<unknown, unknown, unknown>,\n ): Promise<void> {\n // start heartbeating\n execution.startHeartbeat();\n\n try {\n await executeWorkflow({\n backend: this.backend,\n workflowRun: execution.workflowRun,\n workflowFn: workflow.fn,\n workflowVersion: execution.workflowRun.version,\n workerId: execution.workerId,\n retryPolicy: workflow.spec.retryPolicy,\n signal: execution.signal,\n });\n } catch (error) {\n if (execution.signal.aborted) {\n return;\n }\n\n // specifically for unexpected errors in the execution wrapper itself, not\n // for business logic errors (those are handled inside executeWorkflow)\n console.error(\n `Critical error during workflow execution for run ${execution.workflowRun.id}:`,\n error,\n );\n }\n }\n}\n\n/**\n * Configures the options for a WorkflowExecution.\n */\ninterface WorkflowExecutionOptions {\n backend: Backend;\n workflowRun: WorkflowRun;\n workerId: string;\n}\n\n/**\n * Tracks a claimed workflow run and maintains its heartbeat lease for the\n * worker.\n */\nclass WorkflowExecution {\n private backend: Backend;\n workflowRun: WorkflowRun;\n workerId: string;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private abortController = new AbortController();\n\n constructor(options: WorkflowExecutionOptions) {\n this.backend = options.backend;\n this.workflowRun = options.workflowRun;\n this.workerId = options.workerId;\n }\n\n /**\n * 외부에서 abort 여부를 확인할 수 있는 signal입니다.\n * heartbeat 실패 시 abort됩니다.\n */\n get signal(): AbortSignal {\n return this.abortController.signal;\n }\n\n /**\n * Start the heartbeat loop for this execution, heartbeating at half the lease\n * duration.\n */\n startHeartbeat(): void {\n const leaseDurationMs = DEFAULT_LEASE_DURATION_MS;\n const heartbeatIntervalMs = leaseDurationMs / 2;\n\n this.heartbeatTimer = setInterval(() => {\n this.backend\n .extendWorkflowRunLease({\n workflowRunId: this.workflowRun.id,\n workerId: this.workerId,\n leaseDurationMs,\n })\n .catch((_error: unknown) => {\n // lease 연장 실패에는 heartbeat를 중단하고 abort 신호를 보냅니다.\n this.abortController.abort();\n this.stopHeartbeat();\n });\n }, heartbeatIntervalMs);\n }\n\n /**\n * Stop the heartbeat loop.\n */\n stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n}\n\n/**\n * Sleep for a given duration.\n * @param ms - Milliseconds to sleep\n * @returns Promise resolved after sleeping\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,34 +1,36 @@
1
- import type { RetryPolicy } from "./core/retry";
2
- import type { StandardSchemaV1 } from "./core/schema";
3
- import type { WorkflowFunction } from "./execution";
4
- export interface WorkflowSpec<Input, Output, RawInput> {
5
- /** The name of the workflow. */
6
- readonly name: string;
7
- /** The version of the workflow. */
8
- readonly version?: string;
9
- /** The schema used to validate inputs. */
10
- readonly schema?: StandardSchemaV1<RawInput, Input>;
11
- /** The retry policy for the workflow. */
12
- readonly retryPolicy?: RetryPolicy;
13
- /** Phantom type carrier - won't exist at runtime. */
14
- readonly __types?: {
15
- output: Output;
16
- };
1
+ import { RetryPolicy } from "./core/retry.js";
2
+ import { StandardSchemaV1 } from "./core/schema.js";
3
+ import { WorkflowFunction } from "./execution.js";
4
+
5
+ //#region src/workflow.d.ts
6
+ 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
+ /** The retry policy for the workflow. */
14
+ readonly retryPolicy?: RetryPolicy;
15
+ /** Phantom type carrier - won't exist at runtime. */
16
+ readonly __types?: {
17
+ output: Output;
18
+ };
17
19
  }
18
20
  /**
19
21
  * Define a workflow spec.
20
22
  * @param spec - The workflow spec
21
23
  * @returns The workflow spec
22
24
  */
23
- export declare function defineWorkflowSpec<Input, Output = unknown, RawInput = Input>(spec: WorkflowSpec<Input, Output, RawInput>): WorkflowSpec<Input, Output, RawInput>;
25
+ declare function defineWorkflowSpec<Input, Output = unknown, RawInput = Input>(spec: WorkflowSpec<Input, Output, RawInput>): WorkflowSpec<Input, Output, RawInput>;
24
26
  /**
25
27
  * A workflow spec and implementation.
26
28
  */
27
- export interface Workflow<Input, Output, RawInput> {
28
- /** The workflow spec. */
29
- readonly spec: WorkflowSpec<Input, Output, RawInput>;
30
- /** The workflow implementation function. */
31
- readonly fn: WorkflowFunction<Input, Output>;
29
+ interface Workflow<Input, Output, RawInput> {
30
+ /** The workflow spec. */
31
+ readonly spec: WorkflowSpec<Input, Output, RawInput>;
32
+ /** The workflow implementation function. */
33
+ readonly fn: WorkflowFunction<Input, Output>;
32
34
  }
33
35
  /**
34
36
  * Define a workflow.
@@ -36,12 +38,6 @@ export interface Workflow<Input, Output, RawInput> {
36
38
  * @param fn - The workflow implementation function
37
39
  * @returns The workflow
38
40
  */
39
- export declare function defineWorkflow<Input, Output, RawInput = Input>(spec: WorkflowSpec<Input, Output, RawInput>, fn: WorkflowFunction<Input, Output>): Workflow<Input, Output, RawInput>;
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, 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>;
41
+ //#endregion
42
+ export { Workflow, WorkflowSpec, defineWorkflowSpec };
47
43
  //# sourceMappingURL=workflow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,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,yCAAyC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,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;;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,CAAC,KAAK,EAAE,OAAO,CAAC,EACtF,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"}
1
+ {"version":3,"file":"workflow.d.ts","names":[],"sources":["../src/workflow.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB;;EAAA,SAAA,IAAA,EAAY,MAAA;EAAA;WAMQ,OAAA,CAAA,EAAA,MAAA;;WAAjB,MAAA,CAAA,EAAA,gBAAA,CAAiB,QAAjB,EAA2B,KAA3B,CAAA;;WAKR,WAAA,CAAA,EAHa,WAGb;EAAM;EASF,SAAA,OAAA,CAAA,EAAA;IAAkB,MAAA,EATtB,MASsB;;;;;;;;AAEH,iBAFf,kBAEe,CAAA,KAAA,EAAA,SAAA,OAAA,EAAA,WAFwC,KAExC,CAAA,CAAA,IAAA,EADvB,YACuB,CADV,KACU,EADH,MACG,EADK,QACL,CAAA,CAAA,EAA5B,YAA4B,CAAf,KAAe,EAAR,MAAQ,EAAA,QAAA,CAAA;;;AAO/B;AAAyB,UAAR,QAAQ,CAAA,KAAA,EAAA,MAAA,EAAA,QAAA,CAAA,CAAA;;WAEY,IAAA,EAApB,YAAoB,CAAP,KAAO,EAAA,MAAA,EAAQ,QAAR,CAAA;;WAApB,EAAA,EAEF,gBAFE,CAEe,KAFf,EAEsB,MAFtB,CAAA"}
package/dist/workflow.js CHANGED
@@ -1,21 +1,26 @@
1
+ //#region src/workflow.ts
1
2
  /**
2
- * Define a workflow spec.
3
- * @param spec - The workflow spec
4
- * @returns The workflow spec
5
- */ export function defineWorkflowSpec(spec) {
6
- return spec;
3
+ * Define a workflow spec.
4
+ * @param spec - The workflow spec
5
+ * @returns The workflow spec
6
+ */
7
+ function defineWorkflowSpec(spec) {
8
+ return spec;
7
9
  }
8
10
  /**
9
- * Define a workflow.
10
- * @internal
11
- * @param spec - The workflow spec
12
- * @param fn - The workflow implementation function
13
- * @returns The workflow
14
- */ export function defineWorkflow(spec, fn) {
15
- return {
16
- spec,
17
- fn
18
- };
11
+ * Define a workflow.
12
+ * @internal
13
+ * @param spec - The workflow spec
14
+ * @param fn - The workflow implementation function
15
+ * @returns The workflow
16
+ */
17
+ function defineWorkflow(spec, fn) {
18
+ return {
19
+ spec,
20
+ fn
21
+ };
19
22
  }
20
23
 
24
+ //#endregion
25
+ export { defineWorkflow, defineWorkflowSpec };
21
26
  //# sourceMappingURL=workflow.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/workflow.ts"],"sourcesContent":["import type { RetryPolicy } from \"./core/retry\";\nimport type { StandardSchemaV1 } from \"./core/schema\";\nimport type { WorkflowFunction } from \"./execution\";\n\nexport interface WorkflowSpec<Input, Output, RawInput> {\n /** The name of the workflow. */\n readonly name: string;\n /** The version of the workflow. */\n readonly version?: string;\n /** The schema used to validate inputs. */\n readonly schema?: StandardSchemaV1<RawInput, Input>;\n /** The retry policy for the workflow. */\n readonly retryPolicy?: RetryPolicy;\n /** Phantom type carrier - won't exist at runtime. */\n readonly __types?: {\n output: Output;\n };\n}\n\n/**\n * Define a workflow spec.\n * @param spec - The workflow spec\n * @returns The workflow spec\n */\nexport function defineWorkflowSpec<Input, Output = unknown, RawInput = Input>(\n spec: WorkflowSpec<Input, Output, RawInput>,\n): WorkflowSpec<Input, Output, RawInput> {\n return spec;\n}\n\n/**\n * A workflow spec and implementation.\n */\nexport interface Workflow<Input, Output, RawInput> {\n /** The workflow spec. */\n readonly spec: WorkflowSpec<Input, Output, RawInput>;\n /** The workflow implementation function. */\n readonly fn: WorkflowFunction<Input, Output>;\n}\n\n/**\n * Define a workflow.\n * @param spec - The workflow spec\n * @param fn - The workflow implementation function\n * @returns The workflow\n */\n// Handles:\n// - `defineWorkflow(spec, fn)` (0 generics)\n// - `defineWorkflow<Input, Output>(spec, fn)` (2 generics)\nexport function defineWorkflow<Input, Output, RawInput = Input>(\n spec: WorkflowSpec<Input, Output, RawInput>,\n fn: WorkflowFunction<Input, Output>,\n): Workflow<Input, Output, RawInput>;\n\n/**\n * Define a workflow.\n * @param spec - The workflow spec\n * @param fn - The workflow implementation function\n * @returns The workflow\n */\n// Handles:\n// - `defineWorkflow<Input>(spec, fn)` (1 generic)\nexport function defineWorkflow<\n Input,\n WorkflowFn extends WorkflowFunction<Input, unknown> = WorkflowFunction<Input, unknown>,\n RawInput = Input,\n>(\n spec: WorkflowSpec<Input, Awaited<ReturnType<WorkflowFn>>, RawInput>,\n fn: WorkflowFn,\n): Workflow<Input, Awaited<ReturnType<WorkflowFn>>, RawInput>;\n\n/**\n * Define a workflow.\n * @internal\n * @param spec - The workflow spec\n * @param fn - The workflow implementation function\n * @returns The workflow\n */\nexport function defineWorkflow<Input, Output, RawInput>(\n spec: WorkflowSpec<Input, Output, RawInput>,\n fn: WorkflowFunction<Input, Output>,\n): Workflow<Input, Output, RawInput> {\n return {\n spec,\n fn,\n };\n}\n"],"names":["defineWorkflowSpec","spec","defineWorkflow","fn"],"mappings":"AAmBA;;;;CAIC,GACD,OAAO,SAASA,mBACdC,IAA2C;IAE3C,OAAOA;AACT;AA2CA;;;;;;CAMC,GACD,OAAO,SAASC,eACdD,IAA2C,EAC3CE,EAAmC;IAEnC,OAAO;QACLF;QACAE;IACF;AACF"}
1
+ {"version":3,"file":"workflow.js","names":[],"sources":["../src/workflow.ts"],"sourcesContent":["import { type RetryPolicy } from \"./core/retry\";\nimport { type StandardSchemaV1 } from \"./core/schema\";\nimport { type WorkflowFunction } from \"./execution\";\n\nexport interface WorkflowSpec<Input, Output, RawInput> {\n /** The name of the workflow. */\n readonly name: string;\n /** The version of the workflow. */\n readonly version?: string;\n /** The schema used to validate inputs. */\n readonly schema?: StandardSchemaV1<RawInput, Input>;\n /** The retry policy for the workflow. */\n readonly retryPolicy?: RetryPolicy;\n /** Phantom type carrier - won't exist at runtime. */\n readonly __types?: {\n output: Output;\n };\n}\n\n/**\n * Define a workflow spec.\n * @param spec - The workflow spec\n * @returns The workflow spec\n */\nexport function defineWorkflowSpec<Input, Output = unknown, RawInput = Input>(\n spec: WorkflowSpec<Input, Output, RawInput>,\n): WorkflowSpec<Input, Output, RawInput> {\n return spec;\n}\n\n/**\n * A workflow spec and implementation.\n */\nexport interface Workflow<Input, Output, RawInput> {\n /** The workflow spec. */\n readonly spec: WorkflowSpec<Input, Output, RawInput>;\n /** The workflow implementation function. */\n readonly fn: WorkflowFunction<Input, Output>;\n}\n\n/**\n * Define a workflow.\n * @param spec - The workflow spec\n * @param fn - The workflow implementation function\n * @returns The workflow\n */\n// Handles:\n// - `defineWorkflow(spec, fn)` (0 generics)\n// - `defineWorkflow<Input, Output>(spec, fn)` (2 generics)\nexport function defineWorkflow<Input, Output, RawInput = Input>(\n spec: WorkflowSpec<Input, Output, RawInput>,\n fn: WorkflowFunction<Input, Output>,\n): Workflow<Input, Output, RawInput>;\n\n/**\n * Define a workflow.\n * @param spec - The workflow spec\n * @param fn - The workflow implementation function\n * @returns The workflow\n */\n// Handles:\n// - `defineWorkflow<Input>(spec, fn)` (1 generic)\nexport function defineWorkflow<\n Input,\n WorkflowFn extends WorkflowFunction<Input, unknown> = WorkflowFunction<Input, unknown>,\n RawInput = Input,\n>(\n spec: WorkflowSpec<Input, Awaited<ReturnType<WorkflowFn>>, RawInput>,\n fn: WorkflowFn,\n): Workflow<Input, Awaited<ReturnType<WorkflowFn>>, RawInput>;\n\n/**\n * Define a workflow.\n * @internal\n * @param spec - The workflow spec\n * @param fn - The workflow implementation function\n * @returns The workflow\n */\nexport function defineWorkflow<Input, Output, RawInput>(\n spec: WorkflowSpec<Input, Output, RawInput>,\n fn: WorkflowFunction<Input, Output>,\n): Workflow<Input, Output, RawInput> {\n return {\n spec,\n fn,\n };\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,mBACd,MACuC;AACvC,QAAO;;;;;;;;;AAmDT,SAAgB,eACd,MACA,IACmC;AACnC,QAAO;EACL;EACA;EACD"}
package/nodemon.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "watch": ["src"],
3
3
  "ext": "ts,json",
4
- "exec": "swc src --config-file .swcrc -d dist --strip-leading-paths && tsc --emitDeclarationOnly && node --enable-source-maps dist/index.js",
4
+ "exec": "rm -rf dist && tsdown && node --enable-source-maps dist/index.js",
5
5
  "signal": "SIGUSR2"
6
6
  }