@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
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/database/pubsub.ts"],"sourcesContent":["import assert from \"assert\";\nimport type { Knex } from \"knex\";\nimport { err, ok, type Result } from \"../core/result\";\n\nexport type OnSubscribed = (result: Result<string | null>) => void | Promise<void>;\n\nexport class PostgresPubSub {\n private _destroyed = false;\n private _onClosed: () => Promise<void>;\n private _listeners = new Map<string, Set<OnSubscribed>>();\n\n // biome-ignore lint/suspicious/noExplicitAny: Knex exposes a connection as any\n private _connection: any | null = null;\n\n private constructor(private readonly knex: Knex) {\n // Re-connect to the database when the connection is closed and not destroyed manually\n this._onClosed = (async () => {\n if (this._destroyed) {\n return;\n }\n\n await this.connect();\n }).bind(this);\n }\n\n get destroyed() {\n return this._destroyed;\n }\n\n // acquire new raw connection and set up listeners\n async connect() {\n const connection = await this.knex.client.acquireRawConnection();\n connection.on(\"close\", this._onClosed);\n connection.on(\n \"notification\",\n async ({ channel, payload: rawPayload }: { channel: string; payload: unknown }) => {\n const payload =\n typeof rawPayload === \"string\" && rawPayload.length !== 0 ? rawPayload : null;\n const listeners = this._listeners.get(channel);\n if (!listeners) {\n return;\n }\n\n const result = ok(payload);\n await Promise.allSettled(\n Array.from(listeners.values()).map((listener) => Promise.resolve(listener(result))),\n );\n },\n );\n connection.on(\"error\", async (error: Error) => {\n const result = err(error);\n await Promise.allSettled(\n Array.from(this._listeners.values())\n .flatMap((listeners) => Array.from(listeners))\n .map((listener) => Promise.resolve(listener(result))),\n );\n });\n\n for (const channel of this._listeners.keys()) {\n connection.query(`LISTEN ${channel}`);\n }\n\n this._connection = connection;\n }\n\n // destroy the listener and close the connection, do not destroy the knex connection\n async destroy() {\n if (this._destroyed) {\n return;\n }\n try {\n this._connection.off(\"close\", this._onClosed);\n await this.knex.client.destroyRawConnection(this._connection);\n } finally {\n this._destroyed = true;\n }\n }\n\n // create a new listener and connect to the database\n static async create(knex: Knex) {\n const listener = new PostgresPubSub(knex);\n await listener.connect();\n return listener;\n }\n\n // add a new listener to the channel\n listenEvent(channel: string, callback: OnSubscribed) {\n if (!this._listeners.has(channel)) {\n this._connection?.query(`LISTEN ${channel}`);\n this._listeners.set(channel, new Set<OnSubscribed>().add(callback));\n return;\n }\n\n const listeners = this._listeners.get(channel);\n assert(listeners, \"Listener channel not found\");\n listeners.add(callback);\n }\n}\n"],"names":["assert","err","ok","PostgresPubSub","_destroyed","_onClosed","_listeners","Map","_connection","knex","connect","bind","destroyed","connection","client","acquireRawConnection","on","channel","payload","rawPayload","length","listeners","get","result","Promise","allSettled","Array","from","values","map","listener","resolve","error","flatMap","keys","query","destroy","off","destroyRawConnection","create","listenEvent","callback","has","set","Set","add"],"mappings":"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"}
1
+ {"version":3,"file":"pubsub.js","names":["knex: Knex"],"sources":["../../src/database/pubsub.ts"],"sourcesContent":["import assert from \"assert\";\n\nimport { type Knex } from \"knex\";\n\nimport { err, ok } from \"../core/result\";\nimport { type Result } from \"../core/result\";\n\nexport type OnSubscribed = (result: Result<string | null>) => void | Promise<void>;\n\nexport class PostgresPubSub {\n private _destroyed = false;\n private _connecting = false;\n private _onClosed: () => Promise<void>;\n private _onNotification: (msg: { channel: string; payload: unknown }) => Promise<void>;\n private _onError: (error: Error) => Promise<void>;\n private _listeners = new Map<string, Set<OnSubscribed>>();\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- Knex exposes a connection as any\n private _connection: any | null = null;\n\n private constructor(private readonly knex: Knex) {\n // Re-connect to the database when the connection is closed and not destroyed manually\n this._onClosed = (async () => {\n if (this._destroyed) {\n return;\n }\n\n await this.connect();\n }).bind(this);\n\n this._onNotification = (async ({\n channel,\n payload: rawPayload,\n }: {\n channel: string;\n payload: unknown;\n }) => {\n const payload = typeof rawPayload === \"string\" && rawPayload.length !== 0 ? rawPayload : null;\n const listeners = this._listeners.get(channel);\n if (!listeners) {\n return;\n }\n\n const result = ok(payload);\n await Promise.allSettled(\n Array.from(listeners.values()).map((listener) => Promise.resolve(listener(result))),\n );\n }).bind(this);\n\n this._onError = (async (error: Error) => {\n const result = err(error);\n await Promise.allSettled(\n Array.from(this._listeners.values())\n .flatMap((listeners) => Array.from(listeners))\n .map((listener) => Promise.resolve(listener(result))),\n );\n }).bind(this);\n }\n\n get destroyed() {\n return this._destroyed;\n }\n\n // acquire new raw connection and set up listeners\n async connect() {\n // 동시 재연결 시도로 인한 연결 누수를 방지합니다.\n if (this._connecting) return;\n this._connecting = true;\n\n try {\n const connection = await this.knex.client.acquireRawConnection();\n connection.on(\"close\", this._onClosed);\n connection.on(\"notification\", this._onNotification);\n connection.on(\"error\", this._onError);\n\n for (const channel of this._listeners.keys()) {\n connection.query(`LISTEN ${channel}`);\n }\n\n this._connection = connection;\n } finally {\n this._connecting = false;\n }\n }\n\n // destroy the listener and close the connection, do not destroy the knex connection\n async destroy() {\n if (this._destroyed) {\n return;\n }\n try {\n this._connection.off(\"close\", this._onClosed);\n this._connection.off(\"notification\", this._onNotification);\n this._connection.off(\"error\", this._onError);\n await this.knex.client.destroyRawConnection(this._connection);\n } finally {\n this._destroyed = true;\n }\n }\n\n // create a new listener and connect to the database\n static async create(knex: Knex) {\n const listener = new PostgresPubSub(knex);\n await listener.connect();\n return listener;\n }\n\n // add a new listener to the channel\n listenEvent(channel: string, callback: OnSubscribed) {\n if (!this._listeners.has(channel)) {\n this._connection?.query(`LISTEN ${channel}`);\n this._listeners.set(channel, new Set<OnSubscribed>().add(callback));\n return;\n }\n\n const listeners = this._listeners.get(channel);\n assert(listeners, \"Listener channel not found\");\n listeners.add(callback);\n }\n}\n"],"mappings":";;;;AASA,IAAa,iBAAb,MAAa,eAAe;CAC1B,AAAQ,aAAa;CACrB,AAAQ,cAAc;CACtB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,6BAAa,IAAI,KAAgC;CAGzD,AAAQ,cAA0B;CAElC,AAAQ,YAAY,AAAiBA,MAAY;EAAZ;AAEnC,OAAK,aAAa,YAAY;AAC5B,OAAI,KAAK,WACP;AAGF,SAAM,KAAK,SAAS;KACnB,KAAK,KAAK;AAEb,OAAK,mBAAmB,OAAO,EAC7B,SACA,SAAS,iBAIL;GACJ,MAAM,UAAU,OAAO,eAAe,YAAY,WAAW,WAAW,IAAI,aAAa;GACzF,MAAM,YAAY,KAAK,WAAW,IAAI,QAAQ;AAC9C,OAAI,CAAC,UACH;GAGF,MAAM,SAAS,GAAG,QAAQ;AAC1B,SAAM,QAAQ,WACZ,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC,KAAK,aAAa,QAAQ,QAAQ,SAAS,OAAO,CAAC,CAAC,CACpF;KACA,KAAK,KAAK;AAEb,OAAK,YAAY,OAAO,UAAiB;GACvC,MAAM,SAAS,IAAI,MAAM;AACzB,SAAM,QAAQ,WACZ,MAAM,KAAK,KAAK,WAAW,QAAQ,CAAC,CACjC,SAAS,cAAc,MAAM,KAAK,UAAU,CAAC,CAC7C,KAAK,aAAa,QAAQ,QAAQ,SAAS,OAAO,CAAC,CAAC,CACxD;KACA,KAAK,KAAK;;CAGf,IAAI,YAAY;AACd,SAAO,KAAK;;CAId,MAAM,UAAU;AAEd,MAAI,KAAK,YAAa;AACtB,OAAK,cAAc;AAEnB,MAAI;GACF,MAAM,aAAa,MAAM,KAAK,KAAK,OAAO,sBAAsB;AAChE,cAAW,GAAG,SAAS,KAAK,UAAU;AACtC,cAAW,GAAG,gBAAgB,KAAK,gBAAgB;AACnD,cAAW,GAAG,SAAS,KAAK,SAAS;AAErC,QAAK,MAAM,WAAW,KAAK,WAAW,MAAM,CAC1C,YAAW,MAAM,UAAU,UAAU;AAGvC,QAAK,cAAc;YACX;AACR,QAAK,cAAc;;;CAKvB,MAAM,UAAU;AACd,MAAI,KAAK,WACP;AAEF,MAAI;AACF,QAAK,YAAY,IAAI,SAAS,KAAK,UAAU;AAC7C,QAAK,YAAY,IAAI,gBAAgB,KAAK,gBAAgB;AAC1D,QAAK,YAAY,IAAI,SAAS,KAAK,SAAS;AAC5C,SAAM,KAAK,KAAK,OAAO,qBAAqB,KAAK,YAAY;YACrD;AACR,QAAK,aAAa;;;CAKtB,aAAa,OAAO,MAAY;EAC9B,MAAM,WAAW,IAAI,eAAe,KAAK;AACzC,QAAM,SAAS,SAAS;AACxB,SAAO;;CAIT,YAAY,SAAiB,UAAwB;AACnD,MAAI,CAAC,KAAK,WAAW,IAAI,QAAQ,EAAE;AACjC,QAAK,aAAa,MAAM,UAAU,UAAU;AAC5C,QAAK,WAAW,IAAI,0BAAS,IAAI,KAAmB,EAAC,IAAI,SAAS,CAAC;AACnE;;EAGF,MAAM,YAAY,KAAK,WAAW,IAAI,QAAQ;AAC9C,SAAO,WAAW,6BAA6B;AAC/C,YAAU,IAAI,SAAS"}
@@ -1,84 +1,45 @@
1
- import type { Backend } from "./backend";
2
- import type { DurationString } from "./core/duration";
3
- import { type RetryPolicy } from "./core/retry";
4
- import type { StepAttempt } from "./core/step";
5
- import type { WorkflowRun } from "./core/workflow";
1
+ import { DurationString } from "./core/duration.js";
2
+
3
+ //#region src/execution.d.ts
4
+
6
5
  /**
7
6
  * Config for an individual step defined with `step.run()`.
8
7
  */
9
- export interface StepFunctionConfig {
10
- /**
11
- * The name of the step.
12
- */
13
- name: string;
8
+ interface StepFunctionConfig {
9
+ /**
10
+ * The name of the step.
11
+ */
12
+ name: string;
14
13
  }
15
14
  /**
16
15
  * Represents the API for defining steps within a workflow. Used within a
17
16
  * workflow handler to define steps by calling `step.run()`.
18
17
  */
19
- export interface StepApi {
20
- run<Output>(config: Readonly<StepFunctionConfig>, fn: StepFunction<Output>): Promise<Output>;
21
- sleep(name: string, duration: DurationString): Promise<void>;
18
+ interface StepApi {
19
+ run<Output>(config: Readonly<StepFunctionConfig>, fn: StepFunction<Output>): Promise<Output>;
20
+ sleep(name: string, duration: DurationString): Promise<void>;
22
21
  }
23
22
  /**
24
23
  * The step definition (defined by the user) that executes user code. Can return
25
24
  * undefined (e.g., when using `return;`) which will be converted to null.
26
25
  */
27
- export type StepFunction<Output> = () => Promise<Output | undefined> | Output | undefined;
26
+ type StepFunction<Output> = () => Promise<Output | undefined> | Output | undefined;
28
27
  /**
29
28
  * Params passed to a workflow function for the user to use when defining steps.
30
29
  */
31
- export interface WorkflowFunctionParams<Input> {
32
- input: Input;
33
- step: StepApi;
34
- version: string | null;
30
+ interface WorkflowFunctionParams<Input> {
31
+ input: Input;
32
+ step: StepApi;
33
+ version: string | null;
35
34
  }
36
35
  /**
37
36
  * The workflow definition's function (defined by the user) that the user uses
38
37
  * to define the workflow's steps.
39
38
  */
40
- export type WorkflowFunction<Input, Output> = (params: Readonly<WorkflowFunctionParams<Input>>) => Promise<Output> | Output;
39
+ type WorkflowFunction<Input, Output> = (params: Readonly<WorkflowFunctionParams<Input>>) => Promise<Output> | Output;
41
40
  /**
42
41
  * Configures the options for a StepExecutor.
43
42
  */
44
- export interface StepExecutorOptions {
45
- backend: Backend;
46
- workflowRunId: string;
47
- workerId: string;
48
- attempts: StepAttempt[];
49
- }
50
- /**
51
- * Replays prior step attempts and persists new ones while memoizing
52
- * deterministic step outputs.
53
- */
54
- export declare class StepExecutor implements StepApi {
55
- private readonly backend;
56
- private readonly workflowRunId;
57
- private readonly workerId;
58
- private cache;
59
- constructor(options: Readonly<StepExecutorOptions>);
60
- run<Output>(config: Readonly<StepFunctionConfig>, fn: StepFunction<Output>): Promise<Output>;
61
- sleep(name: string, duration: DurationString): Promise<void>;
62
- }
63
- /**
64
- * Parameters for the workflow execution use case.
65
- */
66
- export interface ExecuteWorkflowParams {
67
- backend: Backend;
68
- workflowRun: WorkflowRun;
69
- workflowFn: WorkflowFunction<unknown, unknown>;
70
- workflowVersion: string | null;
71
- workerId: string;
72
- retryPolicy?: RetryPolicy;
73
- }
74
- /**
75
- * Execute a workflow run. This is the core application use case that handles:
76
- * - Loading step history
77
- * - Handling sleeping steps
78
- * - Creating the step executor
79
- * - Executing the workflow function
80
- * - Completing, failing, or sleeping the workflow run based on the outcome
81
- * @param params - The execution parameters
82
- */
83
- export declare function executeWorkflow(params: Readonly<ExecuteWorkflowParams>): Promise<void>;
43
+ //#endregion
44
+ export { StepApi, WorkflowFunction };
84
45
  //# sourceMappingURL=execution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAoB,MAAM,aAAa,CAAC;AASjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7F,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,MAAM,IAAI,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,KAAK;IAC3C,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,CAAC,KAAK,EAAE,MAAM,IAAI,CAC5C,MAAM,EAAE,QAAQ,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,KAC5C,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAgB9B;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,YAAa,YAAW,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,KAAK,CAAmB;gBAEpB,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC;IAQ5C,GAAG,CAAC,MAAM,EACd,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,EAAE,EAAE,YAAY,CAAC,MAAM,CAAC,GACvB,OAAO,CAAC,MAAM,CAAC;IAgDZ,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CA2BnE;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAyG5F"}
1
+ {"version":3,"file":"execution.d.ts","names":[],"sources":["../src/execution.ts"],"sourcesContent":[],"mappings":";;;;;;;AAgCqE,UAZpD,kBAAA,CAYoD;;;;MACrC,EAAA,MAAA;;;AAOhC;;;AAAyC,UATxB,OAAA,CASwB;KAA8B,CAAA,MAAA,CAAA,CAAA,MAAA,EARjD,QAQiD,CARxC,kBAQwC,CAAA,EAAA,EAAA,EARf,YAQe,CARF,MAQE,CAAA,CAAA,EARQ,OAQR,CARgB,MAQhB,CAAA;EAAM,KAAA,CAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAP7C,cAO6C,CAAA,EAP5B,OAO4B,CAAA,IAAA,CAAA;AAK7E;;;;;AAUY,KAfA,YAegB,CAAA,MAAA,CAAA,GAAA,GAAA,GAfa,OAeb,CAfqB,MAerB,GAAA,SAAA,CAAA,GAf2C,MAe3C,GAAA,SAAA;;;;AAClB,UAXO,sBAWP,CAAA,KAAA,CAAA,CAAA;OACG,EAXJ,KAWI;MAAR,EAVG,OAUH;SAAkB,EAAA,MAAA,GAAA,IAAA;;;;;;KAFX,2CACF,SAAS,uBAAuB,YACrC,QAAQ,UAAU"}
package/dist/execution.js CHANGED
@@ -1,196 +1,183 @@
1
- import { serializeError } from "./core/error.js";
2
1
  import { isDynamicRetryPolicy } from "./core/retry.js";
2
+ import { serializeError } from "./core/error.js";
3
3
  import { addToStepAttemptCache, calculateSleepResumeAt, createSleepContext, createStepAttemptCacheFromAttempts, getCachedStepAttempt, normalizeStepOutput } from "./core/step.js";
4
+
5
+ //#region src/execution.ts
4
6
  /**
5
- * Signal thrown when a workflow needs to sleep. Contains the time when the
6
- * workflow should resume.
7
- */ class SleepSignal extends Error {
8
- resumeAt;
9
- constructor(resumeAt){
10
- super("SleepSignal");
11
- this.name = "SleepSignal";
12
- this.resumeAt = resumeAt;
13
- }
14
- }
7
+ * Signal thrown when a workflow needs to sleep. Contains the time when the
8
+ * workflow should resume.
9
+ */
10
+ var SleepSignal = class extends Error {
11
+ resumeAt;
12
+ constructor(resumeAt) {
13
+ super("SleepSignal");
14
+ this.name = "SleepSignal";
15
+ this.resumeAt = resumeAt;
16
+ }
17
+ };
15
18
  /**
16
- * Replays prior step attempts and persists new ones while memoizing
17
- * deterministic step outputs.
18
- */ export class StepExecutor {
19
- backend;
20
- workflowRunId;
21
- workerId;
22
- cache;
23
- constructor(options){
24
- this.backend = options.backend;
25
- this.workflowRunId = options.workflowRunId;
26
- this.workerId = options.workerId;
27
- this.cache = createStepAttemptCacheFromAttempts(options.attempts);
28
- }
29
- async run(config, fn) {
30
- const { name } = config;
31
- // return cached result if available
32
- const existingAttempt = getCachedStepAttempt(this.cache, name);
33
- if (existingAttempt) {
34
- return existingAttempt.output;
35
- }
36
- // not in cache, create new step attempt
37
- const attempt = await this.backend.createStepAttempt({
38
- workflowRunId: this.workflowRunId,
39
- workerId: this.workerId,
40
- stepName: name,
41
- kind: "function",
42
- config: {},
43
- context: null
44
- });
45
- try {
46
- // execute step function
47
- const result = await fn();
48
- const output = normalizeStepOutput(result);
49
- // mark success
50
- const savedAttempt = await this.backend.completeStepAttempt({
51
- workflowRunId: this.workflowRunId,
52
- stepAttemptId: attempt.id,
53
- workerId: this.workerId,
54
- output
55
- });
56
- // cache result
57
- this.cache = addToStepAttemptCache(this.cache, savedAttempt);
58
- return savedAttempt.output;
59
- } catch (error) {
60
- // mark failure
61
- await this.backend.failStepAttempt({
62
- workflowRunId: this.workflowRunId,
63
- stepAttemptId: attempt.id,
64
- workerId: this.workerId,
65
- error: serializeError(error)
66
- });
67
- throw error;
68
- }
69
- }
70
- async sleep(name, duration) {
71
- // return cached result if this sleep already completed
72
- const existingAttempt = getCachedStepAttempt(this.cache, name);
73
- if (existingAttempt) return;
74
- // create new step attempt for the sleep
75
- const result = calculateSleepResumeAt(duration);
76
- if (!result.ok) {
77
- throw result.error;
78
- }
79
- const resumeAt = result.value;
80
- const context = createSleepContext(resumeAt);
81
- await this.backend.createStepAttempt({
82
- workflowRunId: this.workflowRunId,
83
- workerId: this.workerId,
84
- stepName: name,
85
- kind: "sleep",
86
- config: {},
87
- context
88
- });
89
- // throw sleep signal to trigger postponement
90
- // we do not mark the step as completed here; it will be updated
91
- // when the workflow resumes
92
- throw new SleepSignal(resumeAt);
93
- }
94
- }
19
+ * 외부에서 workflow 상태가 변경되었을 실행을 안전하게 중단하기 위한 에러입니다.
20
+ */
21
+ var WorkflowAbortedError = class extends Error {
22
+ constructor() {
23
+ super("Workflow execution aborted");
24
+ this.name = "WorkflowAbortedError";
25
+ }
26
+ };
27
+ /**
28
+ * Replays prior step attempts and persists new ones while memoizing
29
+ * deterministic step outputs.
30
+ */
31
+ var StepExecutor = class {
32
+ backend;
33
+ workflowRunId;
34
+ workerId;
35
+ signal;
36
+ cache;
37
+ constructor(options) {
38
+ this.backend = options.backend;
39
+ this.workflowRunId = options.workflowRunId;
40
+ this.workerId = options.workerId;
41
+ this.signal = options.signal;
42
+ this.cache = createStepAttemptCacheFromAttempts(options.attempts);
43
+ }
44
+ async run(config, fn) {
45
+ const { name } = config;
46
+ if (this.signal?.aborted) throw new WorkflowAbortedError();
47
+ const existingAttempt = getCachedStepAttempt(this.cache, name);
48
+ if (existingAttempt) return existingAttempt.output;
49
+ const attempt = await this.backend.createStepAttempt({
50
+ workflowRunId: this.workflowRunId,
51
+ workerId: this.workerId,
52
+ stepName: name,
53
+ kind: "function",
54
+ config: {},
55
+ context: null
56
+ });
57
+ try {
58
+ const output = normalizeStepOutput(await fn());
59
+ const savedAttempt = await this.backend.completeStepAttempt({
60
+ workflowRunId: this.workflowRunId,
61
+ stepAttemptId: attempt.id,
62
+ workerId: this.workerId,
63
+ output
64
+ });
65
+ if (!savedAttempt) throw new WorkflowAbortedError();
66
+ this.cache = addToStepAttemptCache(this.cache, savedAttempt);
67
+ return savedAttempt.output;
68
+ } catch (error) {
69
+ if (!await this.backend.failStepAttempt({
70
+ workflowRunId: this.workflowRunId,
71
+ stepAttemptId: attempt.id,
72
+ workerId: this.workerId,
73
+ error: serializeError(error)
74
+ })) throw new WorkflowAbortedError();
75
+ throw error;
76
+ }
77
+ }
78
+ async sleep(name, duration) {
79
+ if (this.signal?.aborted) throw new WorkflowAbortedError();
80
+ if (getCachedStepAttempt(this.cache, name)) return;
81
+ const result = calculateSleepResumeAt(duration);
82
+ if (!result.ok) throw result.error;
83
+ const resumeAt = result.value;
84
+ const context = createSleepContext(resumeAt);
85
+ await this.backend.createStepAttempt({
86
+ workflowRunId: this.workflowRunId,
87
+ workerId: this.workerId,
88
+ stepName: name,
89
+ kind: "sleep",
90
+ config: {},
91
+ context
92
+ });
93
+ throw new SleepSignal(resumeAt);
94
+ }
95
+ };
95
96
  /**
96
- * Execute a workflow run. This is the core application use case that handles:
97
- * - Loading step history
98
- * - Handling sleeping steps
99
- * - Creating the step executor
100
- * - Executing the workflow function
101
- * - Completing, failing, or sleeping the workflow run based on the outcome
102
- * @param params - The execution parameters
103
- */ export async function executeWorkflow(params) {
104
- const { backend, workflowRun, workflowFn, workflowVersion, workerId, retryPolicy } = params;
105
- try {
106
- // load all pages of step history
107
- const attempts = [];
108
- let cursor;
109
- do {
110
- const response = await backend.listStepAttempts({
111
- workflowRunId: workflowRun.id,
112
- ...cursor ? {
113
- after: cursor
114
- } : {},
115
- limit: 1000
116
- });
117
- attempts.push(...response.data);
118
- cursor = response.pagination.next ?? undefined;
119
- }while (cursor)
120
- // mark any sleep steps as completed if their sleep duration has elapsed,
121
- // or rethrow SleepSignal if still sleeping
122
- for(let i = 0; i < attempts.length; i++){
123
- const attempt = attempts[i];
124
- if (!attempt) continue;
125
- if (attempt.status === "running" && attempt.kind === "sleep" && attempt.context?.kind === "sleep") {
126
- const now = Date.now();
127
- const resumeAt = new Date(attempt.context.resumeAt);
128
- const resumeAtMs = resumeAt.getTime();
129
- if (now < resumeAtMs) {
130
- // sleep duration HAS NOT elapsed yet, throw signal to put workflow
131
- // back to sleep
132
- throw new SleepSignal(resumeAt);
133
- }
134
- // sleep duration HAS elapsed, mark the step as completed and continue
135
- const completed = await backend.completeStepAttempt({
136
- workflowRunId: workflowRun.id,
137
- stepAttemptId: attempt.id,
138
- workerId,
139
- output: null
140
- });
141
- // update cache w/ completed attempt
142
- attempts[i] = completed;
143
- }
144
- }
145
- // create step executor
146
- const executor = new StepExecutor({
147
- backend,
148
- workflowRunId: workflowRun.id,
149
- workerId,
150
- attempts
151
- });
152
- // execute workflow
153
- const output = await workflowFn({
154
- input: workflowRun.input,
155
- step: executor,
156
- version: workflowVersion
157
- });
158
- // mark success
159
- await backend.completeWorkflowRun({
160
- workflowRunId: workflowRun.id,
161
- workerId,
162
- output: output ?? null
163
- });
164
- } catch (error) {
165
- // handle sleep signal by setting workflow to sleeping status
166
- if (error instanceof SleepSignal) {
167
- await backend.sleepWorkflowRun({
168
- workflowRunId: workflowRun.id,
169
- workerId,
170
- availableAt: error.resumeAt
171
- });
172
- return;
173
- }
174
- // claimWorkflowRun에서 이미 attempts가 증가된 상태입니다.
175
- let forceComplete = false;
176
- let customDelayMs;
177
- if (retryPolicy && isDynamicRetryPolicy(retryPolicy)) {
178
- const serializedError = serializeError(error);
179
- const decision = retryPolicy.shouldRetry(serializedError, workflowRun.attempts ?? 1);
180
- if (!decision.shouldRetry) {
181
- forceComplete = true;
182
- } else {
183
- customDelayMs = decision.delayMs;
184
- }
185
- }
186
- await backend.failWorkflowRun({
187
- workflowRunId: workflowRun.id,
188
- workerId,
189
- error: serializeError(error),
190
- forceComplete,
191
- customDelayMs
192
- });
193
- }
97
+ * Execute a workflow run. This is the core application use case that handles:
98
+ * - Loading step history
99
+ * - Handling sleeping steps
100
+ * - Creating the step executor
101
+ * - Executing the workflow function
102
+ * - Completing, failing, or sleeping the workflow run based on the outcome
103
+ * @param params - The execution parameters
104
+ */
105
+ async function executeWorkflow(params) {
106
+ const { backend, workflowRun, workflowFn, workflowVersion, workerId, retryPolicy, signal } = params;
107
+ try {
108
+ const attempts = [];
109
+ let cursor;
110
+ do {
111
+ const response = await backend.listStepAttempts({
112
+ workflowRunId: workflowRun.id,
113
+ ...cursor ? { after: cursor } : {},
114
+ limit: 1e3
115
+ });
116
+ attempts.push(...response.data);
117
+ cursor = response.pagination.next ?? void 0;
118
+ } while (cursor);
119
+ for (let i = 0; i < attempts.length; i++) {
120
+ const attempt = attempts[i];
121
+ if (!attempt) continue;
122
+ if (attempt.status === "running" && attempt.kind === "sleep" && attempt.context?.kind === "sleep") {
123
+ const now = Date.now();
124
+ const resumeAt = new Date(attempt.context.resumeAt);
125
+ if (now < resumeAt.getTime()) throw new SleepSignal(resumeAt);
126
+ const completed = await backend.completeStepAttempt({
127
+ workflowRunId: workflowRun.id,
128
+ stepAttemptId: attempt.id,
129
+ workerId,
130
+ output: null
131
+ });
132
+ if (!completed) throw new WorkflowAbortedError();
133
+ attempts[i] = completed;
134
+ }
135
+ }
136
+ const executor = new StepExecutor({
137
+ backend,
138
+ workflowRunId: workflowRun.id,
139
+ workerId,
140
+ attempts,
141
+ signal
142
+ });
143
+ const output = await workflowFn({
144
+ input: workflowRun.input,
145
+ step: executor,
146
+ version: workflowVersion
147
+ });
148
+ await backend.completeWorkflowRun({
149
+ workflowRunId: workflowRun.id,
150
+ workerId,
151
+ output: output ?? null
152
+ });
153
+ } catch (error) {
154
+ if (error instanceof SleepSignal) {
155
+ await backend.sleepWorkflowRun({
156
+ workflowRunId: workflowRun.id,
157
+ workerId,
158
+ availableAt: error.resumeAt
159
+ });
160
+ return;
161
+ }
162
+ if (error instanceof WorkflowAbortedError || signal?.aborted) return;
163
+ let forceComplete = false;
164
+ let customDelayMs;
165
+ if (retryPolicy && isDynamicRetryPolicy(retryPolicy)) {
166
+ const serializedError = serializeError(error);
167
+ const decision = retryPolicy.shouldRetry(serializedError, workflowRun.attempts ?? 1);
168
+ if (!decision.shouldRetry) forceComplete = true;
169
+ else customDelayMs = decision.delayMs;
170
+ }
171
+ await backend.failWorkflowRun({
172
+ workflowRunId: workflowRun.id,
173
+ workerId,
174
+ error: serializeError(error),
175
+ forceComplete,
176
+ customDelayMs
177
+ });
178
+ }
194
179
  }
195
180
 
181
+ //#endregion
182
+ export { executeWorkflow };
196
183
  //# sourceMappingURL=execution.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/execution.ts"],"sourcesContent":["import type { Backend } from \"./backend\";\nimport type { DurationString } from \"./core/duration\";\nimport { serializeError } from \"./core/error\";\nimport type { JsonValue } from \"./core/json\";\nimport { isDynamicRetryPolicy, type RetryPolicy } from \"./core/retry\";\nimport type { StepAttempt, StepAttemptCache } from \"./core/step\";\nimport {\n addToStepAttemptCache,\n calculateSleepResumeAt,\n createSleepContext,\n createStepAttemptCacheFromAttempts,\n getCachedStepAttempt,\n normalizeStepOutput,\n} from \"./core/step\";\nimport type { WorkflowRun } from \"./core/workflow\";\n\n/**\n * Config for an individual step defined with `step.run()`.\n */\nexport interface StepFunctionConfig {\n /**\n * The name of the step.\n */\n name: string;\n}\n\n/**\n * Represents the API for defining steps within a workflow. Used within a\n * workflow handler to define steps by calling `step.run()`.\n */\nexport interface StepApi {\n run<Output>(config: Readonly<StepFunctionConfig>, fn: StepFunction<Output>): Promise<Output>;\n sleep(name: string, duration: DurationString): Promise<void>;\n}\n\n/**\n * The step definition (defined by the user) that executes user code. Can return\n * undefined (e.g., when using `return;`) which will be converted to null.\n */\nexport type StepFunction<Output> = () => Promise<Output | undefined> | Output | undefined;\n\n/**\n * Params passed to a workflow function for the user to use when defining steps.\n */\nexport interface WorkflowFunctionParams<Input> {\n input: Input;\n step: StepApi;\n version: string | null;\n}\n\n/**\n * The workflow definition's function (defined by the user) that the user uses\n * to define the workflow's steps.\n */\nexport type WorkflowFunction<Input, Output> = (\n params: Readonly<WorkflowFunctionParams<Input>>,\n) => Promise<Output> | Output;\n\n/**\n * Signal thrown when a workflow needs to sleep. Contains the time when the\n * workflow should resume.\n */\nclass SleepSignal extends Error {\n readonly resumeAt: Date;\n\n constructor(resumeAt: Readonly<Date>) {\n super(\"SleepSignal\");\n this.name = \"SleepSignal\";\n this.resumeAt = resumeAt;\n }\n}\n\n/**\n * Configures the options for a StepExecutor.\n */\nexport interface StepExecutorOptions {\n backend: Backend;\n workflowRunId: string;\n workerId: string;\n attempts: StepAttempt[];\n}\n\n/**\n * Replays prior step attempts and persists new ones while memoizing\n * deterministic step outputs.\n */\nexport class StepExecutor implements StepApi {\n private readonly backend: Backend;\n private readonly workflowRunId: string;\n private readonly workerId: string;\n private cache: StepAttemptCache;\n\n constructor(options: Readonly<StepExecutorOptions>) {\n this.backend = options.backend;\n this.workflowRunId = options.workflowRunId;\n this.workerId = options.workerId;\n\n this.cache = createStepAttemptCacheFromAttempts(options.attempts);\n }\n\n async run<Output>(\n config: Readonly<StepFunctionConfig>,\n fn: StepFunction<Output>,\n ): Promise<Output> {\n const { name } = config;\n\n // return cached result if available\n const existingAttempt = getCachedStepAttempt(this.cache, name);\n if (existingAttempt) {\n return existingAttempt.output as Output;\n }\n\n // not in cache, create new step attempt\n const attempt = await this.backend.createStepAttempt({\n workflowRunId: this.workflowRunId,\n workerId: this.workerId,\n stepName: name,\n kind: \"function\",\n config: {},\n context: null,\n });\n\n try {\n // execute step function\n const result = await fn();\n const output = normalizeStepOutput(result);\n\n // mark success\n const savedAttempt = await this.backend.completeStepAttempt({\n workflowRunId: this.workflowRunId,\n stepAttemptId: attempt.id,\n workerId: this.workerId,\n output,\n });\n\n // cache result\n this.cache = addToStepAttemptCache(this.cache, savedAttempt);\n\n return savedAttempt.output as Output;\n } catch (error) {\n // mark failure\n await this.backend.failStepAttempt({\n workflowRunId: this.workflowRunId,\n stepAttemptId: attempt.id,\n workerId: this.workerId,\n error: serializeError(error),\n });\n throw error;\n }\n }\n\n async sleep(name: string, duration: DurationString): Promise<void> {\n // return cached result if this sleep already completed\n const existingAttempt = getCachedStepAttempt(this.cache, name);\n if (existingAttempt) return;\n\n // create new step attempt for the sleep\n const result = calculateSleepResumeAt(duration);\n if (!result.ok) {\n throw result.error;\n }\n const resumeAt = result.value;\n const context = createSleepContext(resumeAt);\n\n await this.backend.createStepAttempt({\n workflowRunId: this.workflowRunId,\n workerId: this.workerId,\n stepName: name,\n kind: \"sleep\",\n config: {},\n context,\n });\n\n // throw sleep signal to trigger postponement\n // we do not mark the step as completed here; it will be updated\n // when the workflow resumes\n throw new SleepSignal(resumeAt);\n }\n}\n\n/**\n * Parameters for the workflow execution use case.\n */\nexport interface ExecuteWorkflowParams {\n backend: Backend;\n workflowRun: WorkflowRun;\n workflowFn: WorkflowFunction<unknown, unknown>;\n workflowVersion: string | null;\n workerId: string;\n retryPolicy?: RetryPolicy;\n}\n\n/**\n * Execute a workflow run. This is the core application use case that handles:\n * - Loading step history\n * - Handling sleeping steps\n * - Creating the step executor\n * - Executing the workflow function\n * - Completing, failing, or sleeping the workflow run based on the outcome\n * @param params - The execution parameters\n */\nexport async function executeWorkflow(params: Readonly<ExecuteWorkflowParams>): Promise<void> {\n const { backend, workflowRun, workflowFn, workflowVersion, workerId, retryPolicy } = params;\n\n try {\n // load all pages of step history\n const attempts: StepAttempt[] = [];\n let cursor: string | undefined;\n do {\n const response = await backend.listStepAttempts({\n workflowRunId: workflowRun.id,\n ...(cursor ? { after: cursor } : {}),\n limit: 1000,\n });\n attempts.push(...response.data);\n cursor = response.pagination.next ?? undefined;\n } while (cursor);\n\n // mark any sleep steps as completed if their sleep duration has elapsed,\n // or rethrow SleepSignal if still sleeping\n for (let i = 0; i < attempts.length; i++) {\n const attempt = attempts[i];\n if (!attempt) continue;\n\n if (\n attempt.status === \"running\" &&\n attempt.kind === \"sleep\" &&\n attempt.context?.kind === \"sleep\"\n ) {\n const now = Date.now();\n const resumeAt = new Date(attempt.context.resumeAt);\n const resumeAtMs = resumeAt.getTime();\n\n if (now < resumeAtMs) {\n // sleep duration HAS NOT elapsed yet, throw signal to put workflow\n // back to sleep\n throw new SleepSignal(resumeAt);\n }\n\n // sleep duration HAS elapsed, mark the step as completed and continue\n const completed = await backend.completeStepAttempt({\n workflowRunId: workflowRun.id,\n stepAttemptId: attempt.id,\n workerId,\n output: null,\n });\n\n // update cache w/ completed attempt\n attempts[i] = completed;\n }\n }\n\n // create step executor\n const executor = new StepExecutor({\n backend,\n workflowRunId: workflowRun.id,\n workerId,\n attempts,\n });\n\n // execute workflow\n const output = await workflowFn({\n input: workflowRun.input as unknown,\n step: executor,\n version: workflowVersion,\n });\n\n // mark success\n await backend.completeWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n output: (output ?? null) as JsonValue,\n });\n } catch (error) {\n // handle sleep signal by setting workflow to sleeping status\n if (error instanceof SleepSignal) {\n await backend.sleepWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n availableAt: error.resumeAt,\n });\n\n return;\n }\n\n // claimWorkflowRun에서 이미 attempts가 증가된 상태입니다.\n let forceComplete = false;\n let customDelayMs: number | undefined;\n if (retryPolicy && isDynamicRetryPolicy(retryPolicy)) {\n const serializedError = serializeError(error);\n const decision = retryPolicy.shouldRetry(serializedError, workflowRun.attempts ?? 1);\n if (!decision.shouldRetry) {\n forceComplete = true;\n } else {\n customDelayMs = decision.delayMs;\n }\n }\n\n await backend.failWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n error: serializeError(error),\n forceComplete,\n customDelayMs,\n });\n }\n}\n"],"names":["serializeError","isDynamicRetryPolicy","addToStepAttemptCache","calculateSleepResumeAt","createSleepContext","createStepAttemptCacheFromAttempts","getCachedStepAttempt","normalizeStepOutput","SleepSignal","Error","resumeAt","name","StepExecutor","backend","workflowRunId","workerId","cache","options","attempts","run","config","fn","existingAttempt","output","attempt","createStepAttempt","stepName","kind","context","result","savedAttempt","completeStepAttempt","stepAttemptId","id","error","failStepAttempt","sleep","duration","ok","value","executeWorkflow","params","workflowRun","workflowFn","workflowVersion","retryPolicy","cursor","response","listStepAttempts","after","limit","push","data","pagination","next","undefined","i","length","status","now","Date","resumeAtMs","getTime","completed","executor","input","step","version","completeWorkflowRun","sleepWorkflowRun","availableAt","forceComplete","customDelayMs","serializedError","decision","shouldRetry","delayMs","failWorkflowRun"],"mappings":"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"}
1
+ {"version":3,"file":"execution.js","names":["attempts: StepAttempt[]","cursor: string | undefined","customDelayMs: number | undefined"],"sources":["../src/execution.ts"],"sourcesContent":["import { type Backend } from \"./backend\";\nimport { type DurationString } from \"./core/duration\";\nimport { serializeError } from \"./core/error\";\nimport { type JsonValue } from \"./core/json\";\nimport { isDynamicRetryPolicy } from \"./core/retry\";\nimport { type RetryPolicy } from \"./core/retry\";\nimport { type StepAttempt, type StepAttemptCache } from \"./core/step\";\nimport {\n addToStepAttemptCache,\n calculateSleepResumeAt,\n createSleepContext,\n createStepAttemptCacheFromAttempts,\n getCachedStepAttempt,\n normalizeStepOutput,\n} from \"./core/step\";\nimport { type WorkflowRun } from \"./core/workflow\";\n\n/**\n * Config for an individual step defined with `step.run()`.\n */\nexport interface StepFunctionConfig {\n /**\n * The name of the step.\n */\n name: string;\n}\n\n/**\n * Represents the API for defining steps within a workflow. Used within a\n * workflow handler to define steps by calling `step.run()`.\n */\nexport interface StepApi {\n run<Output>(config: Readonly<StepFunctionConfig>, fn: StepFunction<Output>): Promise<Output>;\n sleep(name: string, duration: DurationString): Promise<void>;\n}\n\n/**\n * The step definition (defined by the user) that executes user code. Can return\n * undefined (e.g., when using `return;`) which will be converted to null.\n */\nexport type StepFunction<Output> = () => Promise<Output | undefined> | Output | undefined;\n\n/**\n * Params passed to a workflow function for the user to use when defining steps.\n */\nexport interface WorkflowFunctionParams<Input> {\n input: Input;\n step: StepApi;\n version: string | null;\n}\n\n/**\n * The workflow definition's function (defined by the user) that the user uses\n * to define the workflow's steps.\n */\nexport type WorkflowFunction<Input, Output> = (\n params: Readonly<WorkflowFunctionParams<Input>>,\n) => Promise<Output> | Output;\n\n/**\n * Signal thrown when a workflow needs to sleep. Contains the time when the\n * workflow should resume.\n */\nclass SleepSignal extends Error {\n readonly resumeAt: Date;\n\n constructor(resumeAt: Readonly<Date>) {\n super(\"SleepSignal\");\n this.name = \"SleepSignal\";\n this.resumeAt = resumeAt;\n }\n}\n\n/**\n * 외부에서 workflow 상태가 변경되었을 때 실행을 안전하게 중단하기 위한 에러입니다.\n */\nclass WorkflowAbortedError extends Error {\n constructor() {\n super(\"Workflow execution aborted\");\n this.name = \"WorkflowAbortedError\";\n }\n}\n\n/**\n * Configures the options for a StepExecutor.\n */\nexport interface StepExecutorOptions {\n backend: Backend;\n workflowRunId: string;\n workerId: string;\n attempts: StepAttempt[];\n signal?: AbortSignal;\n}\n\n/**\n * Replays prior step attempts and persists new ones while memoizing\n * deterministic step outputs.\n */\nexport class StepExecutor implements StepApi {\n private readonly backend: Backend;\n private readonly workflowRunId: string;\n private readonly workerId: string;\n private readonly signal?: AbortSignal;\n private cache: StepAttemptCache;\n\n constructor(options: Readonly<StepExecutorOptions>) {\n this.backend = options.backend;\n this.workflowRunId = options.workflowRunId;\n this.workerId = options.workerId;\n this.signal = options.signal;\n\n this.cache = createStepAttemptCacheFromAttempts(options.attempts);\n }\n\n async run<Output>(\n config: Readonly<StepFunctionConfig>,\n fn: StepFunction<Output>,\n ): Promise<Output> {\n const { name } = config;\n if (this.signal?.aborted) {\n throw new WorkflowAbortedError();\n }\n\n // return cached result if available\n const existingAttempt = getCachedStepAttempt(this.cache, name);\n if (existingAttempt) {\n return existingAttempt.output as Output;\n }\n\n // not in cache, create new step attempt\n const attempt = await this.backend.createStepAttempt({\n workflowRunId: this.workflowRunId,\n workerId: this.workerId,\n stepName: name,\n kind: \"function\",\n config: {},\n context: null,\n });\n\n try {\n // execute step function\n const result = await fn();\n const output = normalizeStepOutput(result);\n\n // mark success — null이면 외부에서 워크플로우 상태가 변경된 것입니다(pause/cancel).\n const savedAttempt = await this.backend.completeStepAttempt({\n workflowRunId: this.workflowRunId,\n stepAttemptId: attempt.id,\n workerId: this.workerId,\n output,\n });\n if (!savedAttempt) {\n throw new WorkflowAbortedError();\n }\n\n // cache result\n this.cache = addToStepAttemptCache(this.cache, savedAttempt);\n\n return savedAttempt.output as Output;\n } catch (error) {\n // mark failure — null이면 외부에서 워크플로우 상태가 변경된 것입니다(pause/cancel).\n const failed = await this.backend.failStepAttempt({\n workflowRunId: this.workflowRunId,\n stepAttemptId: attempt.id,\n workerId: this.workerId,\n error: serializeError(error),\n });\n if (!failed) {\n throw new WorkflowAbortedError();\n }\n\n throw error;\n }\n }\n\n async sleep(name: string, duration: DurationString): Promise<void> {\n if (this.signal?.aborted) {\n throw new WorkflowAbortedError();\n }\n\n // return cached result if this sleep already completed\n const existingAttempt = getCachedStepAttempt(this.cache, name);\n if (existingAttempt) return;\n\n // create new step attempt for the sleep\n const result = calculateSleepResumeAt(duration);\n if (!result.ok) {\n throw result.error;\n }\n const resumeAt = result.value;\n const context = createSleepContext(resumeAt);\n\n await this.backend.createStepAttempt({\n workflowRunId: this.workflowRunId,\n workerId: this.workerId,\n stepName: name,\n kind: \"sleep\",\n config: {},\n context,\n });\n\n // throw sleep signal to trigger postponement\n // we do not mark the step as completed here; it will be updated\n // when the workflow resumes\n throw new SleepSignal(resumeAt);\n }\n}\n\n/**\n * Parameters for the workflow execution use case.\n */\nexport interface ExecuteWorkflowParams {\n backend: Backend;\n workflowRun: WorkflowRun;\n workflowFn: WorkflowFunction<unknown, unknown>;\n workflowVersion: string | null;\n workerId: string;\n retryPolicy?: RetryPolicy;\n signal?: AbortSignal;\n}\n\n/**\n * Execute a workflow run. This is the core application use case that handles:\n * - Loading step history\n * - Handling sleeping steps\n * - Creating the step executor\n * - Executing the workflow function\n * - Completing, failing, or sleeping the workflow run based on the outcome\n * @param params - The execution parameters\n */\nexport async function executeWorkflow(params: Readonly<ExecuteWorkflowParams>): Promise<void> {\n const { backend, workflowRun, workflowFn, workflowVersion, workerId, retryPolicy, signal } =\n params;\n\n try {\n // load all pages of step history\n const attempts: StepAttempt[] = [];\n let cursor: string | undefined;\n do {\n const response = await backend.listStepAttempts({\n workflowRunId: workflowRun.id,\n ...(cursor ? { after: cursor } : {}),\n limit: 1000,\n });\n attempts.push(...response.data);\n cursor = response.pagination.next ?? undefined;\n } while (cursor);\n\n // mark any sleep steps as completed if their sleep duration has elapsed,\n // or rethrow SleepSignal if still sleeping\n for (let i = 0; i < attempts.length; i++) {\n const attempt = attempts[i];\n if (!attempt) continue;\n\n if (\n attempt.status === \"running\" &&\n attempt.kind === \"sleep\" &&\n attempt.context?.kind === \"sleep\"\n ) {\n const now = Date.now();\n const resumeAt = new Date(attempt.context.resumeAt);\n const resumeAtMs = resumeAt.getTime();\n\n if (now < resumeAtMs) {\n // sleep duration HAS NOT elapsed yet, throw signal to put workflow\n // back to sleep\n throw new SleepSignal(resumeAt);\n }\n\n // sleep duration HAS elapsed, mark the step as completed and continue\n const completed = await backend.completeStepAttempt({\n workflowRunId: workflowRun.id,\n stepAttemptId: attempt.id,\n workerId,\n output: null,\n });\n if (!completed) {\n throw new WorkflowAbortedError();\n }\n\n // update cache w/ completed attempt\n attempts[i] = completed;\n }\n }\n\n // create step executor\n const executor = new StepExecutor({\n backend,\n workflowRunId: workflowRun.id,\n workerId,\n attempts,\n signal,\n });\n\n // execute workflow\n const output = await workflowFn({\n input: workflowRun.input as unknown,\n step: executor,\n version: workflowVersion,\n });\n\n // mark success\n await backend.completeWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n output: (output ?? null) as JsonValue,\n });\n } catch (error) {\n // handle sleep signal by setting workflow to sleeping status\n if (error instanceof SleepSignal) {\n await backend.sleepWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n availableAt: error.resumeAt,\n });\n\n return;\n }\n\n // heartbeat 실패로 abort된 경우, failWorkflowRun을 호출하지 않고 조용히 종료합니다.\n if (error instanceof WorkflowAbortedError || signal?.aborted) {\n return;\n }\n\n // claimWorkflowRun에서 이미 attempts가 증가된 상태입니다.\n let forceComplete = false;\n let customDelayMs: number | undefined;\n if (retryPolicy && isDynamicRetryPolicy(retryPolicy)) {\n const serializedError = serializeError(error);\n const decision = retryPolicy.shouldRetry(serializedError, workflowRun.attempts ?? 1);\n if (!decision.shouldRetry) {\n forceComplete = true;\n } else {\n customDelayMs = decision.delayMs;\n }\n }\n\n await backend.failWorkflowRun({\n workflowRunId: workflowRun.id,\n workerId,\n error: serializeError(error),\n forceComplete,\n customDelayMs,\n });\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,5 @@
1
- export type { OpenWorkflowOptions } from "./client";
2
- export { declareWorkflow, OpenWorkflow } from "./client";
3
- export type { OpenWorkflowConfig, WorkerConfig } from "./config";
4
- export { defineConfig } from "./config";
5
- export { BackendPostgres } from "./database/backend";
6
- export type { WorkerOptions } from "./worker";
7
- export { Worker } from "./worker";
8
- //# sourceMappingURL=index.d.ts.map
1
+ import { Worker, WorkerOptions } from "./worker.js";
2
+ import { OpenWorkflow, OpenWorkflowOptions, declareWorkflow } from "./client.js";
3
+ import { OpenWorkflowConfig, WorkerConfig, defineConfig } from "./config.js";
4
+ import { BackendPostgres } from "./database/backend.js";
5
+ export { BackendPostgres, OpenWorkflow, type OpenWorkflowConfig, type OpenWorkflowOptions, Worker, type WorkerConfig, type WorkerOptions, declareWorkflow, defineConfig };