@aws/durable-execution-sdk-js 0.0.1 → 1.0.1

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 (284) hide show
  1. package/README.md +365 -0
  2. package/dist/index.mjs +4628 -0
  3. package/dist/index.mjs.map +1 -0
  4. package/dist-cjs/index.js +4646 -0
  5. package/dist-cjs/index.js.map +1 -0
  6. package/dist-types/context/durable-context/durable-context.d.ts +70 -0
  7. package/dist-types/context/durable-context/durable-context.d.ts.map +1 -0
  8. package/dist-types/context/durable-context/durable-context.integration.test.d.ts +2 -0
  9. package/dist-types/context/durable-context/durable-context.integration.test.d.ts.map +1 -0
  10. package/dist-types/context/durable-context/durable-context.unit.test.d.ts +2 -0
  11. package/dist-types/context/durable-context/durable-context.unit.test.d.ts.map +1 -0
  12. package/dist-types/context/durable-context/logger-mode-aware.test.d.ts +2 -0
  13. package/dist-types/context/durable-context/logger-mode-aware.test.d.ts.map +1 -0
  14. package/dist-types/context/durable-context/logger-property.test.d.ts +2 -0
  15. package/dist-types/context/durable-context/logger-property.test.d.ts.map +1 -0
  16. package/dist-types/context/durable-context/mode-management/mode-management.d.ts +13 -0
  17. package/dist-types/context/durable-context/mode-management/mode-management.d.ts.map +1 -0
  18. package/dist-types/context/durable-context/mode-management/mode-management.test.d.ts +2 -0
  19. package/dist-types/context/durable-context/mode-management/mode-management.test.d.ts.map +1 -0
  20. package/dist-types/context/execution-context/execution-context.d.ts +9 -0
  21. package/dist-types/context/execution-context/execution-context.d.ts.map +1 -0
  22. package/dist-types/context/execution-context/execution-context.test.d.ts +2 -0
  23. package/dist-types/context/execution-context/execution-context.test.d.ts.map +1 -0
  24. package/dist-types/durable-execution-api-client/durable-execution-api-client-caching.test.d.ts +2 -0
  25. package/dist-types/durable-execution-api-client/durable-execution-api-client-caching.test.d.ts.map +1 -0
  26. package/dist-types/durable-execution-api-client/durable-execution-api-client.d.ts +29 -0
  27. package/dist-types/durable-execution-api-client/durable-execution-api-client.d.ts.map +1 -0
  28. package/dist-types/durable-execution-api-client/durable-execution-api-client.test.d.ts +2 -0
  29. package/dist-types/durable-execution-api-client/durable-execution-api-client.test.d.ts.map +1 -0
  30. package/dist-types/errors/callback-error/callback-error.test.d.ts +2 -0
  31. package/dist-types/errors/callback-error/callback-error.test.d.ts.map +1 -0
  32. package/dist-types/errors/checkpoint-errors/checkpoint-errors.d.ts +21 -0
  33. package/dist-types/errors/checkpoint-errors/checkpoint-errors.d.ts.map +1 -0
  34. package/dist-types/errors/checkpoint-errors/checkpoint-errors.test.d.ts +2 -0
  35. package/dist-types/errors/checkpoint-errors/checkpoint-errors.test.d.ts.map +1 -0
  36. package/dist-types/errors/durable-error/durable-error-coverage.test.d.ts +2 -0
  37. package/dist-types/errors/durable-error/durable-error-coverage.test.d.ts.map +1 -0
  38. package/dist-types/errors/durable-error/durable-error.d.ts +61 -0
  39. package/dist-types/errors/durable-error/durable-error.d.ts.map +1 -0
  40. package/dist-types/errors/durable-error/durable-error.test.d.ts +2 -0
  41. package/dist-types/errors/durable-error/durable-error.test.d.ts.map +1 -0
  42. package/dist-types/errors/durable-error/error-determinism.integration.test.d.ts +2 -0
  43. package/dist-types/errors/durable-error/error-determinism.integration.test.d.ts.map +1 -0
  44. package/dist-types/errors/non-deterministic-error/non-deterministic-error.d.ts +10 -0
  45. package/dist-types/errors/non-deterministic-error/non-deterministic-error.d.ts.map +1 -0
  46. package/dist-types/errors/serdes-errors/serdes-errors.d.ts +27 -0
  47. package/dist-types/errors/serdes-errors/serdes-errors.d.ts.map +1 -0
  48. package/dist-types/errors/serdes-errors/serdes-errors.test.d.ts +2 -0
  49. package/dist-types/errors/serdes-errors/serdes-errors.test.d.ts.map +1 -0
  50. package/dist-types/errors/step-errors/step-errors.d.ts +9 -0
  51. package/dist-types/errors/step-errors/step-errors.d.ts.map +1 -0
  52. package/dist-types/errors/unrecoverable-error/unrecoverable-error.d.ts +41 -0
  53. package/dist-types/errors/unrecoverable-error/unrecoverable-error.d.ts.map +1 -0
  54. package/dist-types/errors/unrecoverable-error/unrecoverable-error.test.d.ts +2 -0
  55. package/dist-types/errors/unrecoverable-error/unrecoverable-error.test.d.ts.map +1 -0
  56. package/dist-types/handlers/callback-handler/callback-promise.d.ts +5 -0
  57. package/dist-types/handlers/callback-handler/callback-promise.d.ts.map +1 -0
  58. package/dist-types/handlers/callback-handler/callback-promise.test.d.ts +2 -0
  59. package/dist-types/handlers/callback-handler/callback-promise.test.d.ts.map +1 -0
  60. package/dist-types/handlers/callback-handler/callback.d.ts +6 -0
  61. package/dist-types/handlers/callback-handler/callback.d.ts.map +1 -0
  62. package/dist-types/handlers/callback-handler/callback.test.d.ts +2 -0
  63. package/dist-types/handlers/callback-handler/callback.test.d.ts.map +1 -0
  64. package/dist-types/handlers/concurrent-execution-handler/batch-result.d.ts +35 -0
  65. package/dist-types/handlers/concurrent-execution-handler/batch-result.d.ts.map +1 -0
  66. package/dist-types/handlers/concurrent-execution-handler/batch-result.test.d.ts +2 -0
  67. package/dist-types/handlers/concurrent-execution-handler/batch-result.test.d.ts.map +1 -0
  68. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler-two-phase.test.d.ts +2 -0
  69. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler-two-phase.test.d.ts.map +1 -0
  70. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.d.ts +13 -0
  71. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.d.ts.map +1 -0
  72. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.integration.test.d.ts +2 -0
  73. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.integration.test.d.ts.map +1 -0
  74. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.replay.test.d.ts +2 -0
  75. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.replay.test.d.ts.map +1 -0
  76. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.test.d.ts +2 -0
  77. package/dist-types/handlers/concurrent-execution-handler/concurrent-execution-handler.test.d.ts.map +1 -0
  78. package/dist-types/handlers/invoke-handler/invoke-handler-two-phase.test.d.ts +2 -0
  79. package/dist-types/handlers/invoke-handler/invoke-handler-two-phase.test.d.ts.map +1 -0
  80. package/dist-types/handlers/invoke-handler/invoke-handler.d.ts +7 -0
  81. package/dist-types/handlers/invoke-handler/invoke-handler.d.ts.map +1 -0
  82. package/dist-types/handlers/invoke-handler/invoke-handler.test.d.ts +2 -0
  83. package/dist-types/handlers/invoke-handler/invoke-handler.test.d.ts.map +1 -0
  84. package/dist-types/handlers/map-handler/map-handler-two-phase.test.d.ts +2 -0
  85. package/dist-types/handlers/map-handler/map-handler-two-phase.test.d.ts.map +1 -0
  86. package/dist-types/handlers/map-handler/map-handler.d.ts +3 -0
  87. package/dist-types/handlers/map-handler/map-handler.d.ts.map +1 -0
  88. package/dist-types/handlers/map-handler/map-handler.test.d.ts +2 -0
  89. package/dist-types/handlers/map-handler/map-handler.test.d.ts.map +1 -0
  90. package/dist-types/handlers/parallel-handler/parallel-handler-two-phase.test.d.ts +2 -0
  91. package/dist-types/handlers/parallel-handler/parallel-handler-two-phase.test.d.ts.map +1 -0
  92. package/dist-types/handlers/parallel-handler/parallel-handler.d.ts +3 -0
  93. package/dist-types/handlers/parallel-handler/parallel-handler.d.ts.map +1 -0
  94. package/dist-types/handlers/parallel-handler/parallel-handler.test.d.ts +2 -0
  95. package/dist-types/handlers/parallel-handler/parallel-handler.test.d.ts.map +1 -0
  96. package/dist-types/handlers/promise-handler/promise-handler.d.ts +8 -0
  97. package/dist-types/handlers/promise-handler/promise-handler.d.ts.map +1 -0
  98. package/dist-types/handlers/promise-handler/promise-handler.test.d.ts +2 -0
  99. package/dist-types/handlers/promise-handler/promise-handler.test.d.ts.map +1 -0
  100. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-handler-two-phase.test.d.ts +2 -0
  101. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-handler-two-phase.test.d.ts.map +1 -0
  102. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-handler.d.ts +10 -0
  103. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-handler.d.ts.map +1 -0
  104. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-handler.test.d.ts +2 -0
  105. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-handler.test.d.ts.map +1 -0
  106. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-integration.test.d.ts +2 -0
  107. package/dist-types/handlers/run-in-child-context-handler/run-in-child-context-integration.test.d.ts.map +1 -0
  108. package/dist-types/handlers/step-handler/step-handler-two-phase.test.d.ts +2 -0
  109. package/dist-types/handlers/step-handler/step-handler-two-phase.test.d.ts.map +1 -0
  110. package/dist-types/handlers/step-handler/step-handler.d.ts +6 -0
  111. package/dist-types/handlers/step-handler/step-handler.d.ts.map +1 -0
  112. package/dist-types/handlers/step-handler/step-handler.test.d.ts +2 -0
  113. package/dist-types/handlers/step-handler/step-handler.test.d.ts.map +1 -0
  114. package/dist-types/handlers/step-handler/step-handler.timing.test.d.ts +2 -0
  115. package/dist-types/handlers/step-handler/step-handler.timing.test.d.ts.map +1 -0
  116. package/dist-types/handlers/wait-for-callback-handler/wait-for-callback-handler.d.ts +3 -0
  117. package/dist-types/handlers/wait-for-callback-handler/wait-for-callback-handler.d.ts.map +1 -0
  118. package/dist-types/handlers/wait-for-callback-handler/wait-for-callback-handler.test.d.ts +2 -0
  119. package/dist-types/handlers/wait-for-callback-handler/wait-for-callback-handler.test.d.ts.map +1 -0
  120. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler-two-phase.test.d.ts +2 -0
  121. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler-two-phase.test.d.ts.map +1 -0
  122. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler.d.ts +5 -0
  123. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler.d.ts.map +1 -0
  124. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler.test.d.ts +2 -0
  125. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler.test.d.ts.map +1 -0
  126. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler.timing.test.d.ts +2 -0
  127. package/dist-types/handlers/wait-for-condition-handler/wait-for-condition-handler.timing.test.d.ts.map +1 -0
  128. package/dist-types/handlers/wait-handler/wait-handler-comparison.test.d.ts +2 -0
  129. package/dist-types/handlers/wait-handler/wait-handler-comparison.test.d.ts.map +1 -0
  130. package/dist-types/handlers/wait-handler/wait-handler-two-phase.test.d.ts +2 -0
  131. package/dist-types/handlers/wait-handler/wait-handler-two-phase.test.d.ts.map +1 -0
  132. package/dist-types/handlers/wait-handler/wait-handler.d.ts +8 -0
  133. package/dist-types/handlers/wait-handler/wait-handler.d.ts.map +1 -0
  134. package/dist-types/handlers/wait-handler/wait-handler.test.d.ts +2 -0
  135. package/dist-types/handlers/wait-handler/wait-handler.test.d.ts.map +1 -0
  136. package/dist-types/index.d.ts +12 -0
  137. package/dist-types/index.d.ts.map +1 -0
  138. package/dist-types/run-durable.d.ts +2 -0
  139. package/dist-types/run-durable.d.ts.map +1 -0
  140. package/dist-types/termination-manager/termination-manager-checkpoint.test.d.ts +2 -0
  141. package/dist-types/termination-manager/termination-manager-checkpoint.test.d.ts.map +1 -0
  142. package/dist-types/termination-manager/termination-manager.d.ts +15 -0
  143. package/dist-types/termination-manager/termination-manager.d.ts.map +1 -0
  144. package/dist-types/termination-manager/termination-manager.test.d.ts +2 -0
  145. package/dist-types/termination-manager/termination-manager.test.d.ts.map +1 -0
  146. package/dist-types/termination-manager/types.d.ts +26 -0
  147. package/dist-types/termination-manager/types.d.ts.map +1 -0
  148. package/dist-types/testing/create-test-checkpoint-manager.d.ts +5 -0
  149. package/dist-types/testing/create-test-checkpoint-manager.d.ts.map +1 -0
  150. package/dist-types/testing/create-test-durable-context.d.ts +32 -0
  151. package/dist-types/testing/create-test-durable-context.d.ts.map +1 -0
  152. package/dist-types/testing/mock-batch-result.d.ts +26 -0
  153. package/dist-types/testing/mock-batch-result.d.ts.map +1 -0
  154. package/dist-types/testing/mock-checkpoint-manager.d.ts +20 -0
  155. package/dist-types/testing/mock-checkpoint-manager.d.ts.map +1 -0
  156. package/dist-types/testing/mock-checkpoint.d.ts +13 -0
  157. package/dist-types/testing/mock-checkpoint.d.ts.map +1 -0
  158. package/dist-types/testing/mock-context.d.ts +10 -0
  159. package/dist-types/testing/mock-context.d.ts.map +1 -0
  160. package/dist-types/testing/test-constants.d.ts +59 -0
  161. package/dist-types/testing/test-constants.d.ts.map +1 -0
  162. package/dist-types/types/batch.d.ts +185 -0
  163. package/dist-types/types/batch.d.ts.map +1 -0
  164. package/dist-types/types/callback.d.ts +48 -0
  165. package/dist-types/types/callback.d.ts.map +1 -0
  166. package/dist-types/types/child-context.d.ts +24 -0
  167. package/dist-types/types/child-context.d.ts.map +1 -0
  168. package/dist-types/types/core.d.ts +313 -0
  169. package/dist-types/types/core.d.ts.map +1 -0
  170. package/dist-types/types/durable-context.d.ts +669 -0
  171. package/dist-types/types/durable-context.d.ts.map +1 -0
  172. package/dist-types/types/durable-execution.d.ts +192 -0
  173. package/dist-types/types/durable-execution.d.ts.map +1 -0
  174. package/dist-types/types/durable-logger.d.ts +69 -0
  175. package/dist-types/types/durable-logger.d.ts.map +1 -0
  176. package/dist-types/types/durable-promise.d.ts +80 -0
  177. package/dist-types/types/durable-promise.d.ts.map +1 -0
  178. package/dist-types/types/index.d.ts +15 -0
  179. package/dist-types/types/index.d.ts.map +1 -0
  180. package/dist-types/types/invoke.d.ts +12 -0
  181. package/dist-types/types/invoke.d.ts.map +1 -0
  182. package/dist-types/types/logger.d.ts +63 -0
  183. package/dist-types/types/logger.d.ts.map +1 -0
  184. package/dist-types/types/operation-lifecycle-state.d.ts +27 -0
  185. package/dist-types/types/operation-lifecycle-state.d.ts.map +1 -0
  186. package/dist-types/types/operation-lifecycle.d.ts +27 -0
  187. package/dist-types/types/operation-lifecycle.d.ts.map +1 -0
  188. package/dist-types/types/step.d.ts +76 -0
  189. package/dist-types/types/step.d.ts.map +1 -0
  190. package/dist-types/types/wait-condition.d.ts +46 -0
  191. package/dist-types/types/wait-condition.d.ts.map +1 -0
  192. package/dist-types/utils/checkpoint/checkpoint-ancestor.test.d.ts +2 -0
  193. package/dist-types/utils/checkpoint/checkpoint-ancestor.test.d.ts.map +1 -0
  194. package/dist-types/utils/checkpoint/checkpoint-central-termination.test.d.ts +2 -0
  195. package/dist-types/utils/checkpoint/checkpoint-central-termination.test.d.ts.map +1 -0
  196. package/dist-types/utils/checkpoint/checkpoint-error-classification.test.d.ts +2 -0
  197. package/dist-types/utils/checkpoint/checkpoint-error-classification.test.d.ts.map +1 -0
  198. package/dist-types/utils/checkpoint/checkpoint-helper.d.ts +47 -0
  199. package/dist-types/utils/checkpoint/checkpoint-helper.d.ts.map +1 -0
  200. package/dist-types/utils/checkpoint/checkpoint-integration.test.d.ts +2 -0
  201. package/dist-types/utils/checkpoint/checkpoint-integration.test.d.ts.map +1 -0
  202. package/dist-types/utils/checkpoint/checkpoint-manager.d.ts +78 -0
  203. package/dist-types/utils/checkpoint/checkpoint-manager.d.ts.map +1 -0
  204. package/dist-types/utils/checkpoint/checkpoint-queue-completion.test.d.ts +2 -0
  205. package/dist-types/utils/checkpoint/checkpoint-queue-completion.test.d.ts.map +1 -0
  206. package/dist-types/utils/checkpoint/checkpoint-stepdata-update.test.d.ts +2 -0
  207. package/dist-types/utils/checkpoint/checkpoint-stepdata-update.test.d.ts.map +1 -0
  208. package/dist-types/utils/checkpoint/checkpoint-termination.test.d.ts +2 -0
  209. package/dist-types/utils/checkpoint/checkpoint-termination.test.d.ts.map +1 -0
  210. package/dist-types/utils/checkpoint/checkpoint.test.d.ts +2 -0
  211. package/dist-types/utils/checkpoint/checkpoint.test.d.ts.map +1 -0
  212. package/dist-types/utils/constants/constants.d.ts +9 -0
  213. package/dist-types/utils/constants/constants.d.ts.map +1 -0
  214. package/dist-types/utils/context-tracker/context-tracker.d.ts +13 -0
  215. package/dist-types/utils/context-tracker/context-tracker.d.ts.map +1 -0
  216. package/dist-types/utils/context-tracker/context-tracker.test.d.ts +2 -0
  217. package/dist-types/utils/context-tracker/context-tracker.test.d.ts.map +1 -0
  218. package/dist-types/utils/durable-execution-invocation-input/durable-execution-invocation-input.d.ts +22 -0
  219. package/dist-types/utils/durable-execution-invocation-input/durable-execution-invocation-input.d.ts.map +1 -0
  220. package/dist-types/utils/durable-execution-invocation-input/durable-execution-invocation-input.test.d.ts +2 -0
  221. package/dist-types/utils/durable-execution-invocation-input/durable-execution-invocation-input.test.d.ts.map +1 -0
  222. package/dist-types/utils/duration/duration.d.ts +8 -0
  223. package/dist-types/utils/duration/duration.d.ts.map +1 -0
  224. package/dist-types/utils/duration/duration.test.d.ts +2 -0
  225. package/dist-types/utils/duration/duration.test.d.ts.map +1 -0
  226. package/dist-types/utils/error-object/error-object-coverage.test.d.ts +2 -0
  227. package/dist-types/utils/error-object/error-object-coverage.test.d.ts.map +1 -0
  228. package/dist-types/utils/error-object/error-object.d.ts +3 -0
  229. package/dist-types/utils/error-object/error-object.d.ts.map +1 -0
  230. package/dist-types/utils/error-object/error-object.test.d.ts +2 -0
  231. package/dist-types/utils/error-object/error-object.test.d.ts.map +1 -0
  232. package/dist-types/utils/logger/default-logger.d.ts +51 -0
  233. package/dist-types/utils/logger/default-logger.d.ts.map +1 -0
  234. package/dist-types/utils/logger/default-logger.test.d.ts +2 -0
  235. package/dist-types/utils/logger/default-logger.test.d.ts.map +1 -0
  236. package/dist-types/utils/logger/logger.d.ts +2 -0
  237. package/dist-types/utils/logger/logger.d.ts.map +1 -0
  238. package/dist-types/utils/logger/logger.test.d.ts +2 -0
  239. package/dist-types/utils/logger/logger.test.d.ts.map +1 -0
  240. package/dist-types/utils/logger/structured-logger-integration.test.d.ts +2 -0
  241. package/dist-types/utils/logger/structured-logger-integration.test.d.ts.map +1 -0
  242. package/dist-types/utils/replay-validation/replay-validation.d.ts +8 -0
  243. package/dist-types/utils/replay-validation/replay-validation.d.ts.map +1 -0
  244. package/dist-types/utils/replay-validation/replay-validation.test.d.ts +2 -0
  245. package/dist-types/utils/replay-validation/replay-validation.test.d.ts.map +1 -0
  246. package/dist-types/utils/retry/retry-config/index.d.ts +167 -0
  247. package/dist-types/utils/retry/retry-config/index.d.ts.map +1 -0
  248. package/dist-types/utils/retry/retry-config/index.test.d.ts +2 -0
  249. package/dist-types/utils/retry/retry-config/index.test.d.ts.map +1 -0
  250. package/dist-types/utils/retry/retry-presets/retry-presets.d.ts +35 -0
  251. package/dist-types/utils/retry/retry-presets/retry-presets.d.ts.map +1 -0
  252. package/dist-types/utils/safe-stringify/safe-stringify.d.ts +2 -0
  253. package/dist-types/utils/safe-stringify/safe-stringify.d.ts.map +1 -0
  254. package/dist-types/utils/safe-stringify/safe-stringify.test.d.ts +2 -0
  255. package/dist-types/utils/safe-stringify/safe-stringify.test.d.ts.map +1 -0
  256. package/dist-types/utils/serdes/serdes.d.ts +152 -0
  257. package/dist-types/utils/serdes/serdes.d.ts.map +1 -0
  258. package/dist-types/utils/serdes/serdes.test.d.ts +2 -0
  259. package/dist-types/utils/serdes/serdes.test.d.ts.map +1 -0
  260. package/dist-types/utils/step-id-utils/step-id-utils.d.ts +16 -0
  261. package/dist-types/utils/step-id-utils/step-id-utils.d.ts.map +1 -0
  262. package/dist-types/utils/step-id-utils/step-id-utils.test.d.ts +2 -0
  263. package/dist-types/utils/step-id-utils/step-id-utils.test.d.ts.map +1 -0
  264. package/dist-types/utils/summary-generators/summary-generators.d.ts +10 -0
  265. package/dist-types/utils/summary-generators/summary-generators.d.ts.map +1 -0
  266. package/dist-types/utils/summary-generators/summary-generators.test.d.ts +2 -0
  267. package/dist-types/utils/summary-generators/summary-generators.test.d.ts.map +1 -0
  268. package/dist-types/utils/termination-helper/termination-deferral.test.d.ts +2 -0
  269. package/dist-types/utils/termination-helper/termination-deferral.test.d.ts.map +1 -0
  270. package/dist-types/utils/termination-helper/termination-helper.d.ts +11 -0
  271. package/dist-types/utils/termination-helper/termination-helper.d.ts.map +1 -0
  272. package/dist-types/utils/termination-helper/termination-helper.test.d.ts +2 -0
  273. package/dist-types/utils/termination-helper/termination-helper.test.d.ts.map +1 -0
  274. package/dist-types/utils/wait-strategy/wait-strategy-config.d.ts +19 -0
  275. package/dist-types/utils/wait-strategy/wait-strategy-config.d.ts.map +1 -0
  276. package/dist-types/utils/wait-strategy/wait-strategy-config.test.d.ts +2 -0
  277. package/dist-types/utils/wait-strategy/wait-strategy-config.test.d.ts.map +1 -0
  278. package/dist-types/with-durable-execution-queue-completion.test.d.ts +2 -0
  279. package/dist-types/with-durable-execution-queue-completion.test.d.ts.map +1 -0
  280. package/dist-types/with-durable-execution.d.ts +75 -0
  281. package/dist-types/with-durable-execution.d.ts.map +1 -0
  282. package/dist-types/with-durable-execution.test.d.ts +2 -0
  283. package/dist-types/with-durable-execution.test.d.ts.map +1 -0
  284. package/package.json +69 -3
@@ -0,0 +1,4646 @@
1
+ 'use strict';
2
+
3
+ var clientLambda = require('@aws-sdk/client-lambda');
4
+ var events = require('events');
5
+ var async_hooks = require('async_hooks');
6
+ var crypto = require('crypto');
7
+ var node_console = require('node:console');
8
+ var util = require('node:util');
9
+
10
+ /**
11
+ * @internal
12
+ */
13
+ var DurableExecutionMode;
14
+ (function (DurableExecutionMode) {
15
+ DurableExecutionMode["ExecutionMode"] = "ExecutionMode";
16
+ DurableExecutionMode["ReplayMode"] = "ReplayMode";
17
+ DurableExecutionMode["ReplaySucceededContext"] = "ReplaySucceededContext";
18
+ })(DurableExecutionMode || (DurableExecutionMode = {}));
19
+ /**
20
+ * Status enumeration for durable execution invocation results.
21
+ *
22
+ * This enum defines the possible outcomes of a durable execution invocation,
23
+ * indicating whether the execution completed successfully, failed, or is
24
+ * continuing asynchronously.
25
+ *
26
+ * The status determines how the AWS durable execution service will handle
27
+ * the execution:
28
+ * - SUCCEEDED: Execution completed successfully with a final result
29
+ * - FAILED: Execution failed with an error that cannot be retried
30
+ * - PENDING: Execution is continuing and will be resumed later (checkpointed)
31
+ *
32
+ * @public
33
+ */
34
+ exports.InvocationStatus = void 0;
35
+ (function (InvocationStatus) {
36
+ /**
37
+ * The durable execution completed successfully.
38
+ *
39
+ * This status indicates:
40
+ * - A final result is available (if any)
41
+ * - No further invocations are needed
42
+ * - The execution has reached its natural completion
43
+ */
44
+ InvocationStatus["SUCCEEDED"] = "SUCCEEDED";
45
+ /**
46
+ * The durable execution failed with an unrecoverable error.
47
+ *
48
+ * This status indicates:
49
+ * - An error occurred that cannot be automatically retried
50
+ * - Error details are provided in the response
51
+ * - No further invocations will occur
52
+ */
53
+ InvocationStatus["FAILED"] = "FAILED";
54
+ /**
55
+ * The durable execution is continuing asynchronously.
56
+ *
57
+ * This status indicates:
58
+ * - Execution was checkpointed and will resume later
59
+ * - Common scenarios: waiting for callbacks, retries, wait operations
60
+ * - The function may terminate while execution continues
61
+ * - Future invocations will resume from the checkpoint
62
+ */
63
+ InvocationStatus["PENDING"] = "PENDING";
64
+ })(exports.InvocationStatus || (exports.InvocationStatus = {}));
65
+ /**
66
+ * Operation subtype enumeration for categorizing different types of durable operations.
67
+ *
68
+ * This enum provides fine-grained classification of durable operations beyond the
69
+ * basic operation types. Subtypes enable improved observability for specific
70
+ * operation patterns.
71
+ *
72
+ * Each subtype corresponds to a specific durable context method or execution pattern.
73
+ *
74
+ * @public
75
+ */
76
+ exports.OperationSubType = void 0;
77
+ (function (OperationSubType) {
78
+ /**
79
+ * A durable step operation (`context.step`).
80
+ *
81
+ * Represents atomic operations with automatic retry and checkpointing.
82
+ * Steps are the fundamental building blocks of durable executions.
83
+ */
84
+ OperationSubType["STEP"] = "Step";
85
+ /**
86
+ * A wait operation (`context.wait`).
87
+ *
88
+ * Represents time-based delays that pause execution for a specified duration.
89
+ * Waits allow long-running workflows without keeping invocations active.
90
+ */
91
+ OperationSubType["WAIT"] = "Wait";
92
+ /**
93
+ * A callback creation operation (`context.createCallback`).
94
+ *
95
+ * Represents the creation of a callback that external systems can complete.
96
+ * Used for human-in-the-loop workflows and external system integration.
97
+ */
98
+ OperationSubType["CALLBACK"] = "Callback";
99
+ /**
100
+ * A child context operation (`context.runInChildContext`).
101
+ *
102
+ * Represents execution within an isolated child context with its own
103
+ * step counter and state tracking. Used for grouping related operations.
104
+ */
105
+ OperationSubType["RUN_IN_CHILD_CONTEXT"] = "RunInChildContext";
106
+ /**
107
+ * A map operation (`context.map`).
108
+ *
109
+ * Represents parallel processing of an array of items with concurrency control
110
+ * and completion policies. Each map operation coordinates multiple iterations.
111
+ */
112
+ OperationSubType["MAP"] = "Map";
113
+ /**
114
+ * An individual iteration within a map operation.
115
+ *
116
+ * Represents the processing of a single item within a `context.map` call.
117
+ * Each iteration runs in its own child context with isolated state.
118
+ */
119
+ OperationSubType["MAP_ITERATION"] = "MapIteration";
120
+ /**
121
+ * A parallel execution operation (`context.parallel`).
122
+ *
123
+ * Represents concurrent execution of multiple branches with optional
124
+ * concurrency control and completion policies.
125
+ */
126
+ OperationSubType["PARALLEL"] = "Parallel";
127
+ /**
128
+ * An individual branch within a parallel operation.
129
+ *
130
+ * Represents a single branch of execution within a `context.parallel` call.
131
+ * Each branch runs in its own child context with isolated state.
132
+ */
133
+ OperationSubType["PARALLEL_BRANCH"] = "ParallelBranch";
134
+ /**
135
+ * A wait for callback operation (`context.waitForCallback`).
136
+ *
137
+ * Represents waiting for an external system to complete a callback,
138
+ * combining callback creation with submission logic.
139
+ */
140
+ OperationSubType["WAIT_FOR_CALLBACK"] = "WaitForCallback";
141
+ /**
142
+ * A wait for condition operation (`context.waitForCondition`).
143
+ *
144
+ * Represents periodic checking of a condition until it's met,
145
+ * with configurable polling intervals and wait strategies.
146
+ */
147
+ OperationSubType["WAIT_FOR_CONDITION"] = "WaitForCondition";
148
+ /**
149
+ * A chained invocation operation (`context.invoke`).
150
+ *
151
+ * Represents calling another durable function with input parameters
152
+ * and waiting for its completion. Used for function composition and workflows.
153
+ */
154
+ OperationSubType["CHAINED_INVOKE"] = "ChainedInvoke";
155
+ })(exports.OperationSubType || (exports.OperationSubType = {}));
156
+
157
+ /**
158
+ * Log level supported by the durable logger
159
+ * @public
160
+ */
161
+ var DurableLogLevel;
162
+ (function (DurableLogLevel) {
163
+ DurableLogLevel["INFO"] = "INFO";
164
+ DurableLogLevel["WARN"] = "WARN";
165
+ DurableLogLevel["ERROR"] = "ERROR";
166
+ DurableLogLevel["DEBUG"] = "DEBUG";
167
+ })(DurableLogLevel || (DurableLogLevel = {}));
168
+
169
+ /**
170
+ * @public
171
+ */
172
+ exports.StepSemantics = void 0;
173
+ (function (StepSemantics) {
174
+ StepSemantics["AtMostOncePerRetry"] = "AT_MOST_ONCE_PER_RETRY";
175
+ StepSemantics["AtLeastOncePerRetry"] = "AT_LEAST_ONCE_PER_RETRY";
176
+ })(exports.StepSemantics || (exports.StepSemantics = {}));
177
+ /**
178
+ * Jitter strategy for retry delays to prevent thundering herd. Jitter reduces simultaneous retry attempts
179
+ * by spreading retries out over a randomized delay interval.
180
+ *
181
+ * @public
182
+ */
183
+ exports.JitterStrategy = void 0;
184
+ (function (JitterStrategy) {
185
+ /** No jitter - use exact calculated delay */
186
+ JitterStrategy["NONE"] = "NONE";
187
+ /** Full jitter - random delay between 0 and calculated delay */
188
+ JitterStrategy["FULL"] = "FULL";
189
+ /** Half jitter - random delay between 50% and 100% of calculated delay */
190
+ JitterStrategy["HALF"] = "HALF";
191
+ })(exports.JitterStrategy || (exports.JitterStrategy = {}));
192
+
193
+ /**
194
+ * The status of a batch item
195
+ * @public
196
+ */
197
+ exports.BatchItemStatus = void 0;
198
+ (function (BatchItemStatus) {
199
+ BatchItemStatus["SUCCEEDED"] = "SUCCEEDED";
200
+ BatchItemStatus["FAILED"] = "FAILED";
201
+ BatchItemStatus["STARTED"] = "STARTED";
202
+ })(exports.BatchItemStatus || (exports.BatchItemStatus = {}));
203
+
204
+ /**
205
+ * A promise that defers execution until it's awaited or .then/.catch/.finally is called
206
+ *
207
+ * @public
208
+ */
209
+ class DurablePromise {
210
+ /**
211
+ * The actual promise instance, created only when execution begins.
212
+ * Starts as null and remains null until the DurablePromise is first awaited
213
+ * or chained (.then/.catch/.finally). Once created, it holds the running
214
+ * promise returned by the _executor function.
215
+ *
216
+ * Example lifecycle:
217
+ * ```typescript
218
+ * const dp = new DurablePromise(() => fetch('/api')); // _promise = null
219
+ * console.log(dp.isExecuted); // false
220
+ *
221
+ * const result = await dp; // NOW _promise = fetch('/api') promise
222
+ * console.log(dp.isExecuted); // true
223
+ * ```
224
+ *
225
+ * This lazy initialization prevents the executor from running until needed.
226
+ */
227
+ _promise = null;
228
+ /**
229
+ * Function that contains the deferred execution logic.
230
+ * This function is NOT called when the DurablePromise is created - it's only
231
+ * executed when the promise is first awaited or chained (.then/.catch/.finally).
232
+ *
233
+ * Example:
234
+ * ```typescript
235
+ * const durablePromise = new DurablePromise(async () => {
236
+ * console.log("This runs ONLY when awaited, not when created");
237
+ * return await someAsyncOperation();
238
+ * });
239
+ *
240
+ * // At this point, nothing has executed yet
241
+ * console.log("Promise created but not executed");
242
+ *
243
+ * // NOW the executor function runs
244
+ * const result = await durablePromise;
245
+ * ```
246
+ */
247
+ _executor;
248
+ /** Flag indicating whether the promise has been executed (awaited or chained) */
249
+ _isExecuted = false;
250
+ /**
251
+ * Creates a new DurablePromise
252
+ * @param executor - Function containing the deferred execution logic
253
+ */
254
+ constructor(executor) {
255
+ this._executor = executor;
256
+ }
257
+ /**
258
+ * Ensures the promise is executed, creating the actual promise if needed
259
+ * @returns The underlying promise instance
260
+ */
261
+ ensureExecution() {
262
+ if (!this._promise) {
263
+ this._isExecuted = true;
264
+ // Execute the promise
265
+ this._promise = this._executor();
266
+ }
267
+ return this._promise;
268
+ }
269
+ /**
270
+ * Attaches callbacks for the resolution and/or rejection of the Promise
271
+ * Triggers execution if not already started
272
+ */
273
+ then(onfulfilled, onrejected) {
274
+ return this.ensureExecution().then(onfulfilled, onrejected);
275
+ }
276
+ /**
277
+ * Attaches a callback for only the rejection of the Promise
278
+ * Triggers execution if not already started
279
+ */
280
+ catch(onrejected) {
281
+ return this.ensureExecution().catch(onrejected);
282
+ }
283
+ /**
284
+ * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected)
285
+ * Triggers execution if not already started
286
+ */
287
+ finally(onfinally) {
288
+ return this.ensureExecution().finally(onfinally);
289
+ }
290
+ /** Returns the string tag for the promise type */
291
+ get [Symbol.toStringTag]() {
292
+ return "DurablePromise";
293
+ }
294
+ /**
295
+ * Check if the promise has been executed (awaited or had .then/.catch/.finally called)
296
+ * @returns true if execution has started, false otherwise
297
+ */
298
+ get isExecuted() {
299
+ return this._isExecuted;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Represents the lifecycle state of an operation in the durable execution system.
305
+ * This is distinct from AWS SDK's OperationStatus (PENDING, SUCCEEDED, FAILED).
306
+ */
307
+ var OperationLifecycleState;
308
+ (function (OperationLifecycleState) {
309
+ /**
310
+ * Operation is currently executing user code (step function, waitForCondition check)
311
+ */
312
+ OperationLifecycleState["EXECUTING"] = "EXECUTING";
313
+ /**
314
+ * Operation is waiting for retry timer to expire before re-executing user code
315
+ */
316
+ OperationLifecycleState["RETRY_WAITING"] = "RETRY_WAITING";
317
+ /**
318
+ * Operation is waiting for external event (timer, callback, invoke) but not awaited yet (phase 1)
319
+ */
320
+ OperationLifecycleState["IDLE_NOT_AWAITED"] = "IDLE_NOT_AWAITED";
321
+ /**
322
+ * Operation is waiting for external event and has been awaited (phase 2)
323
+ */
324
+ OperationLifecycleState["IDLE_AWAITED"] = "IDLE_AWAITED";
325
+ /**
326
+ * Operation has completed (success or permanent failure)
327
+ */
328
+ OperationLifecycleState["COMPLETED"] = "COMPLETED";
329
+ })(OperationLifecycleState || (OperationLifecycleState = {}));
330
+
331
+ /**
332
+ * Converts a Duration object to total seconds
333
+ * @param duration - Duration object with at least one time unit specified
334
+ * @returns Total duration in seconds
335
+ */
336
+ function durationToSeconds(duration) {
337
+ const days = "days" in duration ? (duration.days ?? 0) : 0;
338
+ const hours = "hours" in duration ? (duration.hours ?? 0) : 0;
339
+ const minutes = "minutes" in duration ? (duration.minutes ?? 0) : 0;
340
+ const seconds = "seconds" in duration ? (duration.seconds ?? 0) : 0;
341
+ return days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds;
342
+ }
343
+
344
+ /**
345
+ * Terminates execution for unrecoverable errors and returns a never-resolving promise
346
+ * @param context - The execution context containing the termination manager
347
+ * @param error - The unrecoverable error that caused termination
348
+ * @param stepIdentifier - The step name or ID for error messaging
349
+ * @returns A never-resolving promise
350
+ */
351
+ function terminateForUnrecoverableError(context, error, stepIdentifier) {
352
+ context.terminationManager.terminate({
353
+ reason: error.terminationReason,
354
+ message: `Unrecoverable error in step ${stepIdentifier}: ${error.message}`,
355
+ });
356
+ return new Promise(() => { }); // Never-resolving promise
357
+ }
358
+
359
+ const safeStringify = (data) => {
360
+ try {
361
+ const seen = new WeakSet();
362
+ return JSON.stringify(data, (key, value) => {
363
+ if (typeof value === "object" && value !== null) {
364
+ if (seen.has(value))
365
+ return "[Circular]";
366
+ seen.add(value);
367
+ // Handle Error objects by extracting their properties
368
+ if (value instanceof Error) {
369
+ return {
370
+ ...value,
371
+ name: value.name,
372
+ message: value.message,
373
+ stack: value.stack,
374
+ };
375
+ }
376
+ }
377
+ return value;
378
+ }, 2);
379
+ }
380
+ catch {
381
+ return "[Unable to stringify]";
382
+ }
383
+ };
384
+
385
+ /* eslint-disable no-console */
386
+ const log = (emoji, message, data) => {
387
+ if (process.env.DURABLE_VERBOSE_MODE === "true") {
388
+ console.debug(`${emoji} ${message}`, data ? safeStringify(data) : "");
389
+ }
390
+ };
391
+
392
+ const DEFAULT_CONFIG$1 = {
393
+ maxAttempts: 3,
394
+ initialDelay: { seconds: 5 },
395
+ maxDelay: { minutes: 5 },
396
+ backoffRate: 2,
397
+ jitter: exports.JitterStrategy.FULL,
398
+ retryableErrors: [/.*/], // By default, retry all errors
399
+ retryableErrorTypes: [],
400
+ };
401
+ const applyJitter$1 = (delay, strategy) => {
402
+ switch (strategy) {
403
+ case exports.JitterStrategy.NONE:
404
+ return delay;
405
+ case exports.JitterStrategy.FULL:
406
+ // Random between 0 and delay
407
+ return Math.random() * delay;
408
+ case exports.JitterStrategy.HALF:
409
+ // Random between delay/2 and delay
410
+ return delay / 2 + Math.random() * (delay / 2);
411
+ default:
412
+ return delay;
413
+ }
414
+ };
415
+ /**
416
+ * Creates a retry strategy function with exponential backoff and configurable jitter
417
+ *
418
+ * @param config - Configuration options for the retry strategy
419
+ * @returns A function that determines whether to retry and calculates delay based on error and attempt count
420
+ *
421
+ * @remarks
422
+ * The returned function takes an error and attempt count, and returns a {@link RetryDecision} indicating
423
+ * whether to retry and the delay before the next attempt.
424
+ *
425
+ * **Delay Calculation:**
426
+ * - Base delay = `initialDelay × backoffRate^(attemptsMade - 1)`
427
+ * - Capped at `maxDelay`
428
+ * - Jitter applied based on `jitter` strategy
429
+ * - Final delay rounded to nearest second, minimum 1 second
430
+ *
431
+ * **Error Filtering:**
432
+ * - If neither `retryableErrors` nor `retryableErrorTypes` is specified: all errors are retried
433
+ * - If either is specified: only matching errors are retried
434
+ * - If both are specified: errors matching either criteria are retried (OR logic)
435
+ *
436
+ * @example
437
+ * ```typescript
438
+ * // Basic usage with defaults (retry all errors, 3 attempts, exponential backoff)
439
+ * const defaultRetry = createRetryStrategy();
440
+ *
441
+ * // Custom retry with more attempts and specific delays
442
+ * const customRetry = createRetryStrategy({
443
+ * maxAttempts: 5,
444
+ * initialDelay: { seconds: 10 },
445
+ * maxDelay: { seconds: 60 },
446
+ * backoffRate: 2,
447
+ * jitter: JitterStrategy.HALF
448
+ * });
449
+ *
450
+ * // Retry only specific error types
451
+ * class TimeoutError extends Error {}
452
+ * const typeBasedRetry = createRetryStrategy({
453
+ * retryableErrorTypes: [TimeoutError]
454
+ * });
455
+ *
456
+ * // Retry only errors matching message patterns
457
+ * const patternBasedRetry = createRetryStrategy({
458
+ * retryableErrors: [/timeout/i, /connection/i, "rate limit"]
459
+ * });
460
+ *
461
+ * // Combine error types and patterns
462
+ * const combinedRetry = createRetryStrategy({
463
+ * retryableErrorTypes: [TimeoutError],
464
+ * retryableErrors: [/network/i]
465
+ * });
466
+ *
467
+ * // Use in step configuration
468
+ * await context.step('api-call', async () => {
469
+ * return await callExternalAPI();
470
+ * }, { retryStrategy: customRetry });
471
+ * ```
472
+ *
473
+ * @see {@link RetryStrategyConfig} for configuration options
474
+ * @see {@link JitterStrategy} for jitter strategies
475
+ * @see {@link RetryDecision} for return type
476
+ *
477
+ * @public
478
+ */
479
+ const createRetryStrategy = (config = {}) => {
480
+ // Only apply default retryableErrors if user didn't specify either filter
481
+ const shouldUseDefaultErrors = config.retryableErrors === undefined &&
482
+ config.retryableErrorTypes === undefined;
483
+ const finalConfig = {
484
+ ...DEFAULT_CONFIG$1,
485
+ ...config,
486
+ retryableErrors: config.retryableErrors ?? (shouldUseDefaultErrors ? [/.*/] : []),
487
+ };
488
+ return (error, attemptsMade) => {
489
+ // Check if we've exceeded max attempts
490
+ if (attemptsMade >= finalConfig.maxAttempts) {
491
+ return { shouldRetry: false };
492
+ }
493
+ // Check if error is retryable based on error message
494
+ const isRetryableErrorMessage = finalConfig.retryableErrors.some((pattern) => {
495
+ if (pattern instanceof RegExp) {
496
+ return pattern.test(error.message);
497
+ }
498
+ return error.message.includes(pattern);
499
+ });
500
+ // Check if error is retryable based on error type
501
+ const isRetryableErrorType = finalConfig.retryableErrorTypes.some((ErrorType) => error instanceof ErrorType);
502
+ if (!isRetryableErrorMessage && !isRetryableErrorType) {
503
+ return { shouldRetry: false };
504
+ }
505
+ // Calculate delay with exponential backoff
506
+ const initialDelaySeconds = durationToSeconds(finalConfig.initialDelay);
507
+ const maxDelaySeconds = durationToSeconds(finalConfig.maxDelay);
508
+ const baseDelay = Math.min(initialDelaySeconds * Math.pow(finalConfig.backoffRate, attemptsMade - 1), maxDelaySeconds);
509
+ // Apply jitter
510
+ const delayWithJitter = applyJitter$1(baseDelay, finalConfig.jitter);
511
+ // Ensure delay is an integer >= 1
512
+ const finalDelay = Math.max(1, Math.round(delayWithJitter));
513
+ return { shouldRetry: true, delay: { seconds: finalDelay } };
514
+ };
515
+ };
516
+
517
+ /**
518
+ * Pre-configured retry strategies for common use cases
519
+ * @example
520
+ * ```typescript
521
+ * // Use default retry preset (3 attempts with exponential backoff)
522
+ * await context.step('my-step', async () => {
523
+ * return await someOperation();
524
+ * }, { retryStrategy: retryPresets.default });
525
+ *
526
+ * // Use no-retry preset (fail immediately on error)
527
+ * await context.step('critical-step', async () => {
528
+ * return await criticalOperation();
529
+ * }, { retryStrategy: retryPresets.noRetry });
530
+ * ```
531
+ *
532
+ * @public
533
+ */
534
+ const retryPresets = {
535
+ /**
536
+ * Default retry strategy with exponential backoff
537
+ * - 6 total attempts (1 initial + 5 retries)
538
+ * - Initial delay: 5 seconds
539
+ * - Max delay: 60 seconds
540
+ * - Backoff rate: 2x
541
+ * - Jitter: FULL (randomizes delay between 0 and calculated delay)
542
+ * - Total max wait time less than 150 seconds (2:30)
543
+ */
544
+ default: createRetryStrategy({
545
+ maxAttempts: 6,
546
+ initialDelay: { seconds: 5 },
547
+ maxDelay: { seconds: 60 },
548
+ backoffRate: 2,
549
+ jitter: exports.JitterStrategy.FULL,
550
+ }),
551
+ /**
552
+ * No retry strategy - fails immediately on first error
553
+ * - 1 total attempt (no retries)
554
+ */
555
+ noRetry: createRetryStrategy({
556
+ maxAttempts: 1,
557
+ }),
558
+ };
559
+
560
+ /**
561
+ * Error thrown when a step with AT_MOST_ONCE_PER_RETRY semantics was started but interrupted
562
+ * before completion.
563
+ * @public
564
+ */
565
+ class StepInterruptedError extends Error {
566
+ constructor(_stepId, _stepName) {
567
+ super(`The step execution process was initiated but failed to reach completion due to an interruption.`);
568
+ this.name = "StepInterruptedError";
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Base class for all durable operation errors
574
+ * @public
575
+ */
576
+ class DurableOperationError extends Error {
577
+ cause;
578
+ errorData;
579
+ stackTrace;
580
+ constructor(message, cause, errorData) {
581
+ super(message);
582
+ this.name = this.constructor.name;
583
+ this.cause = cause;
584
+ this.errorData = errorData;
585
+ }
586
+ /**
587
+ * Create DurableOperationError from ErrorObject (for reconstruction during replay)
588
+ */
589
+ static fromErrorObject(errorObject) {
590
+ const cause = new Error(errorObject.ErrorMessage);
591
+ cause.name = errorObject.ErrorType || "Error";
592
+ cause.stack = errorObject.StackTrace?.join("\n");
593
+ // Determine error type and create appropriate instance
594
+ switch (errorObject.ErrorType) {
595
+ case "StepError":
596
+ return new StepError(errorObject.ErrorMessage || "Step failed", cause, errorObject.ErrorData);
597
+ case "CallbackError":
598
+ return new CallbackError(errorObject.ErrorMessage || "Callback failed", cause, errorObject.ErrorData);
599
+ case "InvokeError":
600
+ return new InvokeError(errorObject.ErrorMessage || "Invoke failed", cause, errorObject.ErrorData);
601
+ case "ChildContextError":
602
+ return new ChildContextError(errorObject.ErrorMessage || "Child context failed", cause, errorObject.ErrorData);
603
+ case "WaitForConditionError":
604
+ return new WaitForConditionError(errorObject.ErrorMessage || "Wait for condition failed", cause, errorObject.ErrorData);
605
+ default:
606
+ return new StepError(errorObject.ErrorMessage || "Unknown error", cause, errorObject.ErrorData);
607
+ }
608
+ }
609
+ /**
610
+ * Convert to ErrorObject for serialization
611
+ */
612
+ toErrorObject() {
613
+ return {
614
+ ErrorType: this.errorType,
615
+ ErrorMessage: this.message,
616
+ ErrorData: this.errorData,
617
+ StackTrace: undefined,
618
+ };
619
+ }
620
+ }
621
+ /**
622
+ * Error thrown when a step operation fails
623
+ * @public
624
+ */
625
+ class StepError extends DurableOperationError {
626
+ errorType = "StepError";
627
+ constructor(message, cause, errorData) {
628
+ super(message || "Step failed", cause, errorData);
629
+ }
630
+ }
631
+ /**
632
+ * Error thrown when a callback operation fails
633
+ * @public
634
+ */
635
+ class CallbackError extends DurableOperationError {
636
+ errorType = "CallbackError";
637
+ constructor(message, cause, errorData) {
638
+ super(message || "Callback failed", cause, errorData);
639
+ }
640
+ }
641
+ /**
642
+ * Error thrown when an invoke operation fails
643
+ * @public
644
+ */
645
+ class InvokeError extends DurableOperationError {
646
+ errorType = "InvokeError";
647
+ constructor(message, cause, errorData) {
648
+ super(message || "Invoke failed", cause, errorData);
649
+ }
650
+ }
651
+ /**
652
+ * Error thrown when a child context operation fails
653
+ * @public
654
+ */
655
+ class ChildContextError extends DurableOperationError {
656
+ errorType = "ChildContextError";
657
+ constructor(message, cause, errorData) {
658
+ super(message || "Child context failed", cause, errorData);
659
+ }
660
+ }
661
+ /**
662
+ * Error thrown when a wait for condition operation fails
663
+ * @public
664
+ */
665
+ class WaitForConditionError extends DurableOperationError {
666
+ errorType = "WaitForConditionError";
667
+ constructor(message, cause, errorData) {
668
+ super(message || "Wait for condition failed", cause, errorData);
669
+ }
670
+ }
671
+
672
+ /**
673
+ * Default Serdes implementation using JSON.stringify and JSON.parse
674
+ * Wrapped in Promise.resolve() to maintain async interface compatibility
675
+ * Ignores context parameter since it uses inline JSON serialization
676
+ *
677
+ * Note: Uses 'any' type intentionally as this is a generic serializer that must
678
+ * handle arbitrary JavaScript values. JSON.stringify/parse work with any type,
679
+ * and using more restrictive types would break compatibility with the generic
680
+ * Serdes<T> interface when T can be any type.
681
+ *
682
+ * @public
683
+ */
684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
685
+ const defaultSerdes = {
686
+ serialize: async (
687
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
688
+ value, _context) => value !== undefined ? JSON.stringify(value) : undefined,
689
+ deserialize: async (data, _context) => (data !== undefined ? JSON.parse(data) : undefined),
690
+ };
691
+ /**
692
+ * Creates a Serdes for a specific class that preserves the class type. This implementation
693
+ * is a basic class wrapper and does not support any complex class structures. If you need
694
+ * custom serialization, it is recommended to create your own custom serdes.
695
+ *
696
+ * @param cls - The class constructor (must have no required parameters)
697
+ * @returns A Serdes that maintains the class type during serialization/deserialization
698
+ *
699
+ * @example
700
+ * ```typescript
701
+ * class User {
702
+ * name: string = "";
703
+ * age: number = 0;
704
+ *
705
+ * greet() {
706
+ * return `Hello, ${this.name}`;
707
+ * }
708
+ * }
709
+ *
710
+ * const userSerdes = createClassSerdes(User);
711
+ *
712
+ * // In a durable function:
713
+ * const user = await context.step("create-user", async () => {
714
+ * const u = new User();
715
+ * u.name = "Alice";
716
+ * u.age = 30;
717
+ * return u;
718
+ * }, { serdes: userSerdes });
719
+ *
720
+ * console.log(user.greet()); // "Hello, Alice" - methods are preserved
721
+ * ```
722
+ *
723
+ * Limitations:
724
+ * - Class instances becomes plain objects and loses all class information
725
+ * - Constructor must have no parameters
726
+ * - Constructor side-effects will re-run during deserialization
727
+ * - Private fields (#field) cannot be serialized
728
+ * - Getters/setters are not preserved
729
+ * - Nested class instances lose their prototype
730
+ *
731
+ * For classes with Date properties, use createClassSerdesWithDates instead.
732
+ *
733
+ * @beta
734
+ */
735
+ function createClassSerdes(cls) {
736
+ return {
737
+ serialize: async (value, _context) => value !== undefined ? JSON.stringify(value) : undefined,
738
+ deserialize: async (data, _context) => data !== undefined
739
+ ? Object.assign(new cls(), JSON.parse(data))
740
+ : undefined,
741
+ };
742
+ }
743
+ /**
744
+ * Creates a custom Serdes for a class with special handling for Date properties. This implementation
745
+ * is a basic class wrapper and does not support any complex class structures. If you need
746
+ * custom serialization, it is recommended to create your own custom serdes.
747
+ *
748
+ * @param cls - The class constructor (must have no required parameters)
749
+ * @param dateProps - Array of property paths that should be converted to Date objects (supports nested paths like "metadata.createdAt")
750
+ * @returns A Serdes that maintains the class type and converts specified properties to Date objects
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * class Article {
755
+ * title: string = "";
756
+ * createdAt: Date = new Date();
757
+ * metadata: {
758
+ * publishedAt: Date;
759
+ * updatedAt: Date;
760
+ * } = {
761
+ * publishedAt: new Date(),
762
+ * updatedAt: new Date()
763
+ * };
764
+ *
765
+ * getAge() {
766
+ * return Date.now() - this.createdAt.getTime();
767
+ * }
768
+ * }
769
+ *
770
+ * const articleSerdes = createClassSerdesWithDates(Article, [
771
+ * "createdAt",
772
+ * "metadata.publishedAt",
773
+ * "metadata.updatedAt"
774
+ * ]);
775
+ *
776
+ * // In a durable function:
777
+ * const article = await context.step("create-article", async () => {
778
+ * const a = new Article();
779
+ * a.title = "My Article";
780
+ * return a;
781
+ * }, { serdes: articleSerdes });
782
+ *
783
+ * console.log(article.getAge()); // Works! Dates are properly restored
784
+ * ```
785
+ *
786
+ * Limitations:
787
+ * - Class instances becomes plain objects and loses all class information
788
+ * - Constructor must have no parameters
789
+ * - Constructor side-effects will re-run during deserialization
790
+ * - Private fields (#field) cannot be serialized
791
+ * - Getters/setters are not preserved
792
+ * - Nested class instances lose their prototype
793
+ *
794
+ * For classes with Date properties, use createClassSerdesWithDates instead.
795
+ *
796
+ * @beta
797
+ */
798
+ function createClassSerdesWithDates(cls, dateProps) {
799
+ return {
800
+ serialize: async (value, _context) => value !== undefined ? JSON.stringify(value) : undefined,
801
+ deserialize: async (data, _context) => {
802
+ if (data === undefined) {
803
+ return undefined;
804
+ }
805
+ const parsed = JSON.parse(data);
806
+ const instance = new cls();
807
+ // Copy all properties from parsed object to the new instance
808
+ Object.assign(instance, parsed);
809
+ // Convert date strings back to Date objects (supports nested paths)
810
+ for (const prop of dateProps) {
811
+ const parts = prop.split(".");
812
+ let obj = instance;
813
+ // Navigate to parent of target property
814
+ for (let i = 0; i < parts.length - 1; i++) {
815
+ const next = obj[parts[i]];
816
+ if (!next || typeof next !== "object")
817
+ break;
818
+ obj = next;
819
+ }
820
+ // Convert to Date if path exists
821
+ const lastKey = parts[parts.length - 1];
822
+ if (obj[lastKey]) {
823
+ obj[lastKey] = new Date(obj[lastKey]);
824
+ }
825
+ }
826
+ return instance;
827
+ },
828
+ };
829
+ }
830
+
831
+ var TerminationReason;
832
+ (function (TerminationReason) {
833
+ // Default termination reason
834
+ TerminationReason["OPERATION_TERMINATED"] = "OPERATION_TERMINATED";
835
+ // Retry-related reasons
836
+ TerminationReason["RETRY_SCHEDULED"] = "RETRY_SCHEDULED";
837
+ TerminationReason["RETRY_INTERRUPTED_STEP"] = "RETRY_INTERRUPTED_STEP";
838
+ // Wait-related reasons
839
+ TerminationReason["WAIT_SCHEDULED"] = "WAIT_SCHEDULED";
840
+ // Callback-related reasons
841
+ TerminationReason["CALLBACK_PENDING"] = "CALLBACK_PENDING";
842
+ // Error-related reasons
843
+ TerminationReason["CHECKPOINT_FAILED"] = "CHECKPOINT_FAILED";
844
+ TerminationReason["SERDES_FAILED"] = "SERDES_FAILED";
845
+ TerminationReason["CONTEXT_VALIDATION_ERROR"] = "CONTEXT_VALIDATION_ERROR";
846
+ // Custom reason
847
+ TerminationReason["CUSTOM"] = "CUSTOM";
848
+ })(TerminationReason || (TerminationReason = {}));
849
+
850
+ /**
851
+ * Base class for all unrecoverable errors
852
+ * Any error that inherits from this class indicates a fatal condition
853
+ */
854
+ class UnrecoverableError extends Error {
855
+ originalError;
856
+ isUnrecoverable = true;
857
+ constructor(message, originalError) {
858
+ super(message);
859
+ this.originalError = originalError;
860
+ this.name = this.constructor.name;
861
+ // Preserve the original stack trace if available
862
+ if (originalError?.stack) {
863
+ this.stack = `${this.stack}\nCaused by: ${originalError.stack}`;
864
+ }
865
+ }
866
+ }
867
+ /**
868
+ * Base class for errors that make the entire execution unrecoverable
869
+ * These errors indicate that the execution cannot continue and should be terminated completely
870
+ */
871
+ class UnrecoverableExecutionError extends UnrecoverableError {
872
+ isUnrecoverableExecution = true;
873
+ constructor(message, originalError) {
874
+ super(`[Unrecoverable Execution] ${message}`, originalError);
875
+ }
876
+ }
877
+ /**
878
+ * Base class for errors that make the current invocation unrecoverable
879
+ * These errors indicate that the current Lambda invocation should be terminated,
880
+ * but the execution might be able to continue with a new invocation
881
+ */
882
+ class UnrecoverableInvocationError extends UnrecoverableError {
883
+ isUnrecoverableInvocation = true;
884
+ constructor(message, originalError) {
885
+ super(`[Unrecoverable Invocation] ${message}`, originalError);
886
+ }
887
+ }
888
+ /**
889
+ * Type guard to check if an error is any kind of unrecoverable error
890
+ */
891
+ function isUnrecoverableError(error) {
892
+ return (error instanceof Error &&
893
+ "isUnrecoverable" in error &&
894
+ error.isUnrecoverable === true);
895
+ }
896
+ /**
897
+ * Type guard to check if an error is an unrecoverable invocation error
898
+ */
899
+ function isUnrecoverableInvocationError(error) {
900
+ return (error instanceof Error &&
901
+ "isUnrecoverableInvocation" in error &&
902
+ error.isUnrecoverableInvocation === true);
903
+ }
904
+
905
+ /**
906
+ * Error thrown when serdes operation fails and terminates Lambda invocation
907
+ * This is used by withDurableExecution to terminate the Lambda when serdes fails
908
+ */
909
+ class SerdesFailedError extends UnrecoverableInvocationError {
910
+ terminationReason = TerminationReason.SERDES_FAILED;
911
+ constructor(message, originalError) {
912
+ super(message || "Serdes operation failed", originalError);
913
+ }
914
+ }
915
+ /**
916
+ * Utility function to safely execute serialization with proper error handling
917
+ * Instead of throwing unrecoverable errors, this directly terminates execution
918
+ */
919
+ async function safeSerialize(serdes, value, stepId, stepName, terminationManager, durableExecutionArn) {
920
+ try {
921
+ const context = {
922
+ entityId: stepId,
923
+ durableExecutionArn,
924
+ };
925
+ return await serdes.serialize(value, context);
926
+ }
927
+ catch (error) {
928
+ const message = `Serialization failed for step ${stepName ? `"${stepName}" ` : ""}(${stepId}): ${error instanceof Error ? error.message : "Unknown serialization error"}`;
929
+ log("💥", "Serialization failed - terminating execution:", {
930
+ stepId,
931
+ stepName,
932
+ error: error instanceof Error ? error.message : String(error),
933
+ });
934
+ terminationManager.terminate({
935
+ reason: TerminationReason.SERDES_FAILED,
936
+ message: message,
937
+ });
938
+ // Return a never-resolving promise to ensure the execution doesn't continue
939
+ return new Promise(() => { });
940
+ }
941
+ }
942
+ /**
943
+ * Utility function to safely execute deserialization with proper error handling
944
+ * Instead of throwing unrecoverable errors, this directly terminates execution
945
+ */
946
+ async function safeDeserialize(serdes, data, stepId, stepName, terminationManager, durableExecutionArn) {
947
+ try {
948
+ const context = {
949
+ entityId: stepId,
950
+ durableExecutionArn,
951
+ };
952
+ return await serdes.deserialize(data, context);
953
+ }
954
+ catch (error) {
955
+ const message = `Deserialization failed for step ${stepName ? `"${stepName}" ` : ""}(${stepId}): ${error instanceof Error ? error.message : "Unknown deserialization error"}`;
956
+ log("💥", "Deserialization failed - terminating execution:", {
957
+ stepId,
958
+ stepName,
959
+ error: error instanceof Error ? error.message : String(error),
960
+ });
961
+ terminationManager.terminate({
962
+ reason: TerminationReason.SERDES_FAILED,
963
+ message: message,
964
+ });
965
+ // Return a never-resolving promise to ensure the execution doesn't continue
966
+ return new Promise(() => { });
967
+ }
968
+ }
969
+
970
+ const asyncLocalStorage = new async_hooks.AsyncLocalStorage();
971
+ const getActiveContext = () => {
972
+ return asyncLocalStorage.getStore();
973
+ };
974
+ const runWithContext = (contextId, parentId, fn, attempt, durableExecutionMode) => {
975
+ return asyncLocalStorage.run({ contextId, parentId, attempt, durableExecutionMode }, fn);
976
+ };
977
+ const validateContextUsage = (operationContextId, operationName, terminationManager) => {
978
+ const contextId = operationContextId || "root";
979
+ const activeContext = getActiveContext();
980
+ if (!activeContext) {
981
+ return;
982
+ }
983
+ if (activeContext.contextId !== contextId) {
984
+ const errorMessage = `Context usage error in "${operationName}": You are using a parent or sibling context instead of the current child context. Expected context ID: "${activeContext.contextId}", but got: "${operationContextId}". When inside runInChildContext(), you must use the child context parameter, not the parent context.`;
985
+ terminationManager.terminate({
986
+ reason: TerminationReason.CONTEXT_VALIDATION_ERROR,
987
+ message: errorMessage,
988
+ error: new Error(errorMessage),
989
+ });
990
+ // Only call termination manager, don't throw or return promise
991
+ }
992
+ };
993
+
994
+ function isErrorLike(obj) {
995
+ return (obj instanceof Error ||
996
+ (obj != null &&
997
+ typeof obj === "object" &&
998
+ "message" in obj &&
999
+ "name" in obj));
1000
+ }
1001
+ function createErrorObjectFromError(error, data) {
1002
+ if (error instanceof DurableOperationError) {
1003
+ // Use DurableOperationError's built-in serialization
1004
+ const errorObject = error.toErrorObject();
1005
+ return errorObject;
1006
+ }
1007
+ if (isErrorLike(error)) {
1008
+ return {
1009
+ ErrorData: data,
1010
+ ErrorMessage: error.message,
1011
+ ErrorType: error.name,
1012
+ StackTrace: undefined,
1013
+ };
1014
+ }
1015
+ return {
1016
+ ErrorData: data,
1017
+ ErrorMessage: "Unknown error",
1018
+ };
1019
+ }
1020
+
1021
+ /**
1022
+ * Error thrown when non-deterministic code is detected during replay
1023
+ */
1024
+ class NonDeterministicExecutionError extends UnrecoverableExecutionError {
1025
+ terminationReason = TerminationReason.CUSTOM;
1026
+ constructor(message) {
1027
+ super(message);
1028
+ this.name = "NonDeterministicExecutionError";
1029
+ }
1030
+ }
1031
+
1032
+ const validateReplayConsistency = (stepId, currentOperation, checkpointData, context) => {
1033
+ // Skip validation if no checkpoint data exists or if Type is undefined (first execution)
1034
+ if (!checkpointData || !checkpointData.Type) {
1035
+ return;
1036
+ }
1037
+ // Validate operation type
1038
+ if (checkpointData.Type !== currentOperation.type) {
1039
+ const error = new NonDeterministicExecutionError(`Non-deterministic execution detected: Operation type mismatch for step "${stepId}". ` +
1040
+ `Expected type "${checkpointData.Type}", but got "${currentOperation.type}". ` +
1041
+ `This indicates non-deterministic control flow in your workflow code.`);
1042
+ terminateForUnrecoverableError(context, error, stepId);
1043
+ }
1044
+ // Validate operation name (including undefined)
1045
+ if (checkpointData.Name !== currentOperation.name) {
1046
+ const error = new NonDeterministicExecutionError(`Non-deterministic execution detected: Operation name mismatch for step "${stepId}". ` +
1047
+ `Expected name "${checkpointData.Name ?? "undefined"}", but got "${currentOperation.name ?? "undefined"}". ` +
1048
+ `This indicates non-deterministic control flow in your workflow code.`);
1049
+ terminateForUnrecoverableError(context, error, stepId);
1050
+ }
1051
+ // Validate operation subtype
1052
+ if (checkpointData.SubType !== currentOperation.subType) {
1053
+ const error = new NonDeterministicExecutionError(`Non-deterministic execution detected: Operation subtype mismatch for step "${stepId}". ` +
1054
+ `Expected subtype "${checkpointData.SubType}", but got "${currentOperation.subType}". ` +
1055
+ `This indicates non-deterministic control flow in your workflow code.`);
1056
+ terminateForUnrecoverableError(context, error, stepId);
1057
+ }
1058
+ };
1059
+
1060
+ const createStepHandler = (context, checkpoint, parentContext, createStepId, logger, parentId) => {
1061
+ return (nameOrFn, fnOrOptions, maybeOptions) => {
1062
+ let name;
1063
+ let fn;
1064
+ let options;
1065
+ if (typeof nameOrFn === "string" || nameOrFn === undefined) {
1066
+ name = nameOrFn;
1067
+ fn = fnOrOptions;
1068
+ options = maybeOptions;
1069
+ }
1070
+ else {
1071
+ fn = nameOrFn;
1072
+ options = fnOrOptions;
1073
+ }
1074
+ const stepId = createStepId();
1075
+ const semantics = options?.semantics || exports.StepSemantics.AtLeastOncePerRetry;
1076
+ const serdes = options?.serdes || defaultSerdes;
1077
+ // Phase 1: Execute step
1078
+ const phase1Promise = (async () => {
1079
+ let stepData = context.getStepData(stepId);
1080
+ validateReplayConsistency(stepId, { type: clientLambda.OperationType.STEP, name, subType: exports.OperationSubType.STEP }, stepData, context);
1081
+ // Check if already completed
1082
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1083
+ log("⏭️", "Step already completed:", { stepId });
1084
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1085
+ metadata: {
1086
+ stepId,
1087
+ name,
1088
+ type: clientLambda.OperationType.STEP,
1089
+ subType: exports.OperationSubType.STEP,
1090
+ parentId,
1091
+ },
1092
+ });
1093
+ return await safeDeserialize(serdes, stepData.StepDetails?.Result, stepId, name, context.terminationManager, context.durableExecutionArn);
1094
+ }
1095
+ // Check if already failed
1096
+ if (stepData?.Status === clientLambda.OperationStatus.FAILED) {
1097
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1098
+ metadata: {
1099
+ stepId,
1100
+ name,
1101
+ type: clientLambda.OperationType.STEP,
1102
+ subType: exports.OperationSubType.STEP,
1103
+ parentId,
1104
+ },
1105
+ });
1106
+ if (stepData.StepDetails?.Error) {
1107
+ throw DurableOperationError.fromErrorObject(stepData.StepDetails.Error);
1108
+ }
1109
+ throw new StepError("Unknown error");
1110
+ }
1111
+ // Check if pending retry
1112
+ if (stepData?.Status === clientLambda.OperationStatus.PENDING) {
1113
+ checkpoint.markOperationState(stepId, OperationLifecycleState.RETRY_WAITING, {
1114
+ metadata: {
1115
+ stepId,
1116
+ name,
1117
+ type: clientLambda.OperationType.STEP,
1118
+ subType: exports.OperationSubType.STEP,
1119
+ parentId,
1120
+ },
1121
+ endTimestamp: stepData.StepDetails?.NextAttemptTimestamp,
1122
+ });
1123
+ return (async () => {
1124
+ await checkpoint.waitForRetryTimer(stepId);
1125
+ stepData = context.getStepData(stepId);
1126
+ return await executeStepLogic();
1127
+ })();
1128
+ }
1129
+ // Check for interrupted step with AT_MOST_ONCE_PER_RETRY
1130
+ if (stepData?.Status === clientLambda.OperationStatus.STARTED &&
1131
+ semantics === exports.StepSemantics.AtMostOncePerRetry) {
1132
+ const error = new StepInterruptedError(stepId, name);
1133
+ const currentAttempt = (stepData.StepDetails?.Attempt || 0) + 1;
1134
+ const retryDecision = options?.retryStrategy?.(error, currentAttempt) ??
1135
+ retryPresets.default(error, currentAttempt);
1136
+ if (!retryDecision.shouldRetry) {
1137
+ await checkpoint.checkpoint(stepId, {
1138
+ Id: stepId,
1139
+ ParentId: parentId,
1140
+ Action: clientLambda.OperationAction.FAIL,
1141
+ SubType: exports.OperationSubType.STEP,
1142
+ Type: clientLambda.OperationType.STEP,
1143
+ Error: createErrorObjectFromError(error),
1144
+ Name: name,
1145
+ });
1146
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1147
+ throw DurableOperationError.fromErrorObject(createErrorObjectFromError(error));
1148
+ }
1149
+ await checkpoint.checkpoint(stepId, {
1150
+ Id: stepId,
1151
+ ParentId: parentId,
1152
+ Action: clientLambda.OperationAction.RETRY,
1153
+ SubType: exports.OperationSubType.STEP,
1154
+ Type: clientLambda.OperationType.STEP,
1155
+ Error: createErrorObjectFromError(error),
1156
+ Name: name,
1157
+ StepOptions: {
1158
+ NextAttemptDelaySeconds: retryDecision.delay
1159
+ ? durationToSeconds(retryDecision.delay)
1160
+ : 1,
1161
+ },
1162
+ });
1163
+ checkpoint.markOperationState(stepId, OperationLifecycleState.RETRY_WAITING, {
1164
+ metadata: {
1165
+ stepId,
1166
+ name,
1167
+ type: clientLambda.OperationType.STEP,
1168
+ subType: exports.OperationSubType.STEP,
1169
+ parentId,
1170
+ },
1171
+ endTimestamp: context.getStepData(stepId)?.StepDetails?.NextAttemptTimestamp,
1172
+ });
1173
+ return (async () => {
1174
+ await checkpoint.waitForRetryTimer(stepId);
1175
+ stepData = context.getStepData(stepId);
1176
+ return await executeStepLogic();
1177
+ })();
1178
+ }
1179
+ return await executeStepLogic();
1180
+ async function executeStepLogic() {
1181
+ stepData = context.getStepData(stepId);
1182
+ if (stepData?.Status !== clientLambda.OperationStatus.STARTED) {
1183
+ if (semantics === exports.StepSemantics.AtMostOncePerRetry) {
1184
+ await checkpoint.checkpoint(stepId, {
1185
+ Id: stepId,
1186
+ ParentId: parentId,
1187
+ Action: clientLambda.OperationAction.START,
1188
+ SubType: exports.OperationSubType.STEP,
1189
+ Type: clientLambda.OperationType.STEP,
1190
+ Name: name,
1191
+ });
1192
+ }
1193
+ else {
1194
+ checkpoint.checkpoint(stepId, {
1195
+ Id: stepId,
1196
+ ParentId: parentId,
1197
+ Action: clientLambda.OperationAction.START,
1198
+ SubType: exports.OperationSubType.STEP,
1199
+ Type: clientLambda.OperationType.STEP,
1200
+ Name: name,
1201
+ });
1202
+ }
1203
+ }
1204
+ try {
1205
+ stepData = context.getStepData(stepId);
1206
+ const currentAttempt = stepData?.StepDetails?.Attempt || 0;
1207
+ const stepContext = { logger };
1208
+ // Mark operation as EXECUTING
1209
+ checkpoint.markOperationState(stepId, OperationLifecycleState.EXECUTING, {
1210
+ metadata: {
1211
+ stepId,
1212
+ name,
1213
+ type: clientLambda.OperationType.STEP,
1214
+ subType: exports.OperationSubType.STEP,
1215
+ parentId,
1216
+ },
1217
+ });
1218
+ let result;
1219
+ result = await runWithContext(stepId, parentId, () => fn(stepContext), currentAttempt + 1, DurableExecutionMode.ExecutionMode);
1220
+ const serializedResult = await safeSerialize(serdes, result, stepId, name, context.terminationManager, context.durableExecutionArn);
1221
+ await checkpoint.checkpoint(stepId, {
1222
+ Id: stepId,
1223
+ ParentId: parentId,
1224
+ Action: clientLambda.OperationAction.SUCCEED,
1225
+ SubType: exports.OperationSubType.STEP,
1226
+ Type: clientLambda.OperationType.STEP,
1227
+ Payload: serializedResult,
1228
+ Name: name,
1229
+ });
1230
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1231
+ return await safeDeserialize(serdes, serializedResult, stepId, name, context.terminationManager, context.durableExecutionArn);
1232
+ }
1233
+ catch (error) {
1234
+ if (isUnrecoverableError(error)) {
1235
+ return terminateForUnrecoverableError(context, error, name || stepId);
1236
+ }
1237
+ stepData = context.getStepData(stepId);
1238
+ const currentAttempt = (stepData?.StepDetails?.Attempt || 0) + 1;
1239
+ const retryDecision = options?.retryStrategy?.(error instanceof Error ? error : new Error("Unknown Error"), currentAttempt) ??
1240
+ retryPresets.default(error instanceof Error ? error : new Error("Unknown Error"), currentAttempt);
1241
+ if (!retryDecision.shouldRetry) {
1242
+ await checkpoint.checkpoint(stepId, {
1243
+ Id: stepId,
1244
+ ParentId: parentId,
1245
+ Action: clientLambda.OperationAction.FAIL,
1246
+ SubType: exports.OperationSubType.STEP,
1247
+ Type: clientLambda.OperationType.STEP,
1248
+ Error: createErrorObjectFromError(error),
1249
+ Name: name,
1250
+ });
1251
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1252
+ throw DurableOperationError.fromErrorObject(createErrorObjectFromError(error));
1253
+ }
1254
+ await checkpoint.checkpoint(stepId, {
1255
+ Id: stepId,
1256
+ ParentId: parentId,
1257
+ Action: clientLambda.OperationAction.RETRY,
1258
+ SubType: exports.OperationSubType.STEP,
1259
+ Type: clientLambda.OperationType.STEP,
1260
+ Error: createErrorObjectFromError(error),
1261
+ Name: name,
1262
+ StepOptions: {
1263
+ NextAttemptDelaySeconds: retryDecision.delay
1264
+ ? durationToSeconds(retryDecision.delay)
1265
+ : 1,
1266
+ },
1267
+ });
1268
+ checkpoint.markOperationState(stepId, OperationLifecycleState.RETRY_WAITING, {
1269
+ metadata: {
1270
+ stepId,
1271
+ name,
1272
+ type: clientLambda.OperationType.STEP,
1273
+ subType: exports.OperationSubType.STEP,
1274
+ parentId,
1275
+ },
1276
+ endTimestamp: context.getStepData(stepId)?.StepDetails?.NextAttemptTimestamp,
1277
+ });
1278
+ await checkpoint.waitForRetryTimer(stepId);
1279
+ return await executeStepLogic();
1280
+ }
1281
+ }
1282
+ })();
1283
+ phase1Promise.catch(() => { });
1284
+ return new DurablePromise(async () => {
1285
+ checkpoint.markOperationAwaited(stepId);
1286
+ return await phase1Promise;
1287
+ });
1288
+ };
1289
+ };
1290
+
1291
+ const createInvokeHandler = (context, checkpoint, createStepId, parentId, checkAndUpdateReplayMode) => {
1292
+ function invokeHandler(nameOrFuncId, funcIdOrInput, inputOrConfig, maybeConfig) {
1293
+ const isNameFirst = typeof funcIdOrInput === "string";
1294
+ const name = isNameFirst ? nameOrFuncId : undefined;
1295
+ const funcId = isNameFirst ? funcIdOrInput : nameOrFuncId;
1296
+ const input = isNameFirst
1297
+ ? inputOrConfig
1298
+ : funcIdOrInput;
1299
+ const config = isNameFirst
1300
+ ? maybeConfig
1301
+ : inputOrConfig;
1302
+ const stepId = createStepId();
1303
+ // Phase 1: Start invoke operation
1304
+ let isCompleted = false;
1305
+ const phase1Promise = (async () => {
1306
+ log("🔗", "Invoke phase 1:", { stepId, name: name || funcId });
1307
+ let stepData = context.getStepData(stepId);
1308
+ // Validate replay consistency
1309
+ validateReplayConsistency(stepId, {
1310
+ type: clientLambda.OperationType.CHAINED_INVOKE,
1311
+ name,
1312
+ subType: exports.OperationSubType.CHAINED_INVOKE,
1313
+ }, stepData, context);
1314
+ // Check if already completed
1315
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1316
+ log("⏭️", "Invoke already completed:", { stepId });
1317
+ checkAndUpdateReplayMode?.();
1318
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1319
+ metadata: {
1320
+ stepId,
1321
+ name,
1322
+ type: clientLambda.OperationType.CHAINED_INVOKE,
1323
+ subType: exports.OperationSubType.CHAINED_INVOKE,
1324
+ parentId,
1325
+ },
1326
+ });
1327
+ isCompleted = true;
1328
+ return;
1329
+ }
1330
+ // Check if already failed
1331
+ if (stepData?.Status === clientLambda.OperationStatus.FAILED ||
1332
+ stepData?.Status === clientLambda.OperationStatus.TIMED_OUT ||
1333
+ stepData?.Status === clientLambda.OperationStatus.STOPPED) {
1334
+ log("❌", "Invoke already failed:", { stepId });
1335
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1336
+ metadata: {
1337
+ stepId,
1338
+ name,
1339
+ type: clientLambda.OperationType.CHAINED_INVOKE,
1340
+ subType: exports.OperationSubType.CHAINED_INVOKE,
1341
+ parentId,
1342
+ },
1343
+ });
1344
+ isCompleted = true;
1345
+ return;
1346
+ }
1347
+ // Start invoke if not already started
1348
+ if (!stepData) {
1349
+ const serializedPayload = await safeSerialize(config?.payloadSerdes || defaultSerdes, input, stepId, name, context.terminationManager, context.durableExecutionArn);
1350
+ await checkpoint.checkpoint(stepId, {
1351
+ Id: stepId,
1352
+ ParentId: parentId,
1353
+ Action: clientLambda.OperationAction.START,
1354
+ SubType: exports.OperationSubType.CHAINED_INVOKE,
1355
+ Type: clientLambda.OperationType.CHAINED_INVOKE,
1356
+ Name: name,
1357
+ Payload: serializedPayload,
1358
+ ChainedInvokeOptions: {
1359
+ FunctionName: funcId,
1360
+ },
1361
+ });
1362
+ }
1363
+ // Mark as IDLE_NOT_AWAITED
1364
+ checkpoint.markOperationState(stepId, OperationLifecycleState.IDLE_NOT_AWAITED, {
1365
+ metadata: {
1366
+ stepId,
1367
+ name,
1368
+ type: clientLambda.OperationType.CHAINED_INVOKE,
1369
+ subType: exports.OperationSubType.CHAINED_INVOKE,
1370
+ parentId,
1371
+ },
1372
+ });
1373
+ log("✅", "Invoke phase 1 complete:", { stepId });
1374
+ })();
1375
+ phase1Promise.catch(() => { });
1376
+ // Phase 2: Wait for completion
1377
+ return new DurablePromise(async () => {
1378
+ await phase1Promise;
1379
+ if (isCompleted) {
1380
+ const stepData = context.getStepData(stepId);
1381
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1382
+ const invokeDetails = stepData.ChainedInvokeDetails;
1383
+ return await safeDeserialize(config?.resultSerdes || defaultSerdes, invokeDetails?.Result, stepId, name, context.terminationManager, context.durableExecutionArn);
1384
+ }
1385
+ // Handle failure
1386
+ const invokeDetails = stepData?.ChainedInvokeDetails;
1387
+ if (invokeDetails?.Error) {
1388
+ throw new InvokeError(invokeDetails.Error.ErrorMessage || "Invoke failed", invokeDetails.Error.ErrorMessage
1389
+ ? new Error(invokeDetails.Error.ErrorMessage)
1390
+ : undefined, invokeDetails.Error.ErrorData);
1391
+ }
1392
+ else {
1393
+ throw new InvokeError("Invoke failed");
1394
+ }
1395
+ }
1396
+ log("🔗", "Invoke phase 2:", { stepId });
1397
+ checkpoint.markOperationAwaited(stepId);
1398
+ await checkpoint.waitForStatusChange(stepId);
1399
+ const stepData = context.getStepData(stepId);
1400
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1401
+ log("✅", "Invoke completed:", { stepId });
1402
+ checkAndUpdateReplayMode?.();
1403
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1404
+ const invokeDetails = stepData.ChainedInvokeDetails;
1405
+ return await safeDeserialize(config?.resultSerdes || defaultSerdes, invokeDetails?.Result, stepId, name, context.terminationManager, context.durableExecutionArn);
1406
+ }
1407
+ // Handle failure
1408
+ log("❌", "Invoke failed:", { stepId, status: stepData?.Status });
1409
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1410
+ const invokeDetails = stepData?.ChainedInvokeDetails;
1411
+ if (invokeDetails?.Error) {
1412
+ throw new InvokeError(invokeDetails.Error.ErrorMessage || "Invoke failed", invokeDetails.Error.ErrorMessage
1413
+ ? new Error(invokeDetails.Error.ErrorMessage)
1414
+ : undefined, invokeDetails.Error.ErrorData);
1415
+ }
1416
+ else {
1417
+ throw new InvokeError("Invoke failed");
1418
+ }
1419
+ });
1420
+ }
1421
+ return invokeHandler;
1422
+ };
1423
+
1424
+ // Checkpoint size limit in bytes (256KB)
1425
+ const CHECKPOINT_SIZE_LIMIT = 256 * 1024;
1426
+ const determineChildReplayMode = (context, stepId) => {
1427
+ const stepData = context.getStepData(stepId);
1428
+ if (!stepData) {
1429
+ return DurableExecutionMode.ExecutionMode;
1430
+ }
1431
+ if (stepData.Status === clientLambda.OperationStatus.SUCCEEDED &&
1432
+ stepData.ContextDetails?.ReplayChildren) {
1433
+ return DurableExecutionMode.ReplaySucceededContext;
1434
+ }
1435
+ if (stepData.Status === clientLambda.OperationStatus.SUCCEEDED ||
1436
+ stepData.Status === clientLambda.OperationStatus.FAILED) {
1437
+ return DurableExecutionMode.ReplayMode;
1438
+ }
1439
+ return DurableExecutionMode.ExecutionMode;
1440
+ };
1441
+ const createRunInChildContextHandler = (context, checkpoint, parentContext, createStepId, getParentLogger, createChildContext, parentId) => {
1442
+ return (nameOrFn, fnOrOptions, maybeOptions) => {
1443
+ let name;
1444
+ let fn;
1445
+ let options;
1446
+ if (typeof nameOrFn === "string" || nameOrFn === undefined) {
1447
+ name = nameOrFn;
1448
+ fn = fnOrOptions;
1449
+ options = maybeOptions;
1450
+ }
1451
+ else {
1452
+ fn = nameOrFn;
1453
+ options = fnOrOptions;
1454
+ }
1455
+ const entityId = createStepId();
1456
+ log("🔄", "Running child context:", {
1457
+ entityId,
1458
+ name,
1459
+ });
1460
+ const stepData = context.getStepData(entityId);
1461
+ // Validate replay consistency
1462
+ validateReplayConsistency(entityId, {
1463
+ type: clientLambda.OperationType.CONTEXT,
1464
+ name,
1465
+ subType: options?.subType ||
1466
+ exports.OperationSubType.RUN_IN_CHILD_CONTEXT,
1467
+ }, stepData, context);
1468
+ // Two-phase execution: Phase 1 starts immediately, Phase 2 returns result when awaited
1469
+ let phase1Result;
1470
+ let phase1Error;
1471
+ // Phase 1: Start execution immediately and capture result/error
1472
+ const phase1Promise = (async () => {
1473
+ const currentStepData = context.getStepData(entityId);
1474
+ // If already completed, return cached result
1475
+ if (currentStepData?.Status === clientLambda.OperationStatus.SUCCEEDED ||
1476
+ currentStepData?.Status === clientLambda.OperationStatus.FAILED) {
1477
+ // Mark this run-in-child-context as finished to prevent descendant operations
1478
+ checkpoint.markAncestorFinished(entityId);
1479
+ return handleCompletedChildContext(context, parentContext, entityId, name, fn, options, getParentLogger, createChildContext);
1480
+ }
1481
+ // Execute if not completed
1482
+ return executeChildContext(context, checkpoint, parentContext, entityId, name, fn, options, getParentLogger, createChildContext, parentId);
1483
+ })()
1484
+ .then((result) => {
1485
+ phase1Result = result;
1486
+ })
1487
+ .catch((error) => {
1488
+ phase1Error = error;
1489
+ });
1490
+ // Phase 2: Return DurablePromise that returns Phase 1 result when awaited
1491
+ return new DurablePromise(async () => {
1492
+ await phase1Promise;
1493
+ if (phase1Error !== undefined) {
1494
+ throw phase1Error;
1495
+ }
1496
+ return phase1Result;
1497
+ });
1498
+ };
1499
+ };
1500
+ const handleCompletedChildContext = async (context, parentContext, entityId, stepName, fn, options, getParentLogger, createChildContext) => {
1501
+ const serdes = options?.serdes || defaultSerdes;
1502
+ const stepData = context.getStepData(entityId);
1503
+ const result = stepData?.ContextDetails?.Result;
1504
+ // Handle failed child context
1505
+ if (stepData?.Status === clientLambda.OperationStatus.FAILED) {
1506
+ if (stepData.ContextDetails?.Error) {
1507
+ const originalError = DurableOperationError.fromErrorObject(stepData.ContextDetails.Error);
1508
+ throw new ChildContextError(originalError.message, originalError);
1509
+ }
1510
+ else {
1511
+ throw new ChildContextError("Child context failed");
1512
+ }
1513
+ }
1514
+ // Check if we need to replay children due to large payload
1515
+ if (stepData?.ContextDetails?.ReplayChildren) {
1516
+ log("🔄", "ReplayChildren mode: Re-executing child context due to large payload:", { entityId, stepName });
1517
+ // Re-execute the child context to reconstruct the result
1518
+ const durableChildContext = createChildContext(context, parentContext, DurableExecutionMode.ReplaySucceededContext, getParentLogger(), entityId, undefined, entityId);
1519
+ return await runWithContext(entityId, entityId, () => fn(durableChildContext));
1520
+ }
1521
+ log("⏭️", "Child context already finished, returning cached result:", {
1522
+ entityId,
1523
+ });
1524
+ return await safeDeserialize(serdes, result, entityId, stepName, context.terminationManager, context.durableExecutionArn);
1525
+ };
1526
+ const executeChildContext = async (context, checkpoint, parentContext, entityId, name, fn, options, getParentLogger, createChildContext, parentId) => {
1527
+ const serdes = options?.serdes || defaultSerdes;
1528
+ // Checkpoint at start if not already started (fire-and-forget for performance)
1529
+ if (context.getStepData(entityId) === undefined) {
1530
+ const subType = options?.subType || exports.OperationSubType.RUN_IN_CHILD_CONTEXT;
1531
+ checkpoint.checkpoint(entityId, {
1532
+ Id: entityId,
1533
+ ParentId: parentId,
1534
+ Action: clientLambda.OperationAction.START,
1535
+ SubType: subType,
1536
+ Type: clientLambda.OperationType.CONTEXT,
1537
+ Name: name,
1538
+ });
1539
+ }
1540
+ const childReplayMode = determineChildReplayMode(context, entityId);
1541
+ // Create a child context with the entity ID as prefix
1542
+ const durableChildContext = createChildContext(context, parentContext, childReplayMode, getParentLogger(), entityId, undefined, entityId);
1543
+ try {
1544
+ // Execute the child context function with context tracking
1545
+ const result = await runWithContext(entityId, parentId, () => fn(durableChildContext), undefined, childReplayMode);
1546
+ // Serialize the result for consistency
1547
+ const serializedResult = await safeSerialize(serdes, result, entityId, name, context.terminationManager, context.durableExecutionArn);
1548
+ // Check if payload is too large for adaptive mode
1549
+ let payloadToCheckpoint = serializedResult;
1550
+ let replayChildren = false;
1551
+ if (serializedResult &&
1552
+ Buffer.byteLength(serializedResult, "utf8") > CHECKPOINT_SIZE_LIMIT) {
1553
+ replayChildren = true;
1554
+ // Use summary generator if provided, otherwise use empty string
1555
+ if (options?.summaryGenerator) {
1556
+ payloadToCheckpoint = options.summaryGenerator(result);
1557
+ }
1558
+ else {
1559
+ payloadToCheckpoint = "";
1560
+ }
1561
+ log("📦", "Large payload detected, using ReplayChildren mode:", {
1562
+ entityId,
1563
+ name,
1564
+ payloadSize: Buffer.byteLength(serializedResult, "utf8"),
1565
+ limit: CHECKPOINT_SIZE_LIMIT,
1566
+ });
1567
+ }
1568
+ // Mark this run-in-child-context as finished to prevent descendant operations
1569
+ checkpoint.markAncestorFinished(entityId);
1570
+ const subType = options?.subType || exports.OperationSubType.RUN_IN_CHILD_CONTEXT;
1571
+ checkpoint.checkpoint(entityId, {
1572
+ Id: entityId,
1573
+ ParentId: parentId,
1574
+ Action: clientLambda.OperationAction.SUCCEED,
1575
+ SubType: subType,
1576
+ Type: clientLambda.OperationType.CONTEXT,
1577
+ Payload: payloadToCheckpoint,
1578
+ ContextOptions: replayChildren ? { ReplayChildren: true } : undefined,
1579
+ Name: name,
1580
+ });
1581
+ log("✅", "Child context completed successfully:", {
1582
+ entityId,
1583
+ name,
1584
+ });
1585
+ return result;
1586
+ }
1587
+ catch (error) {
1588
+ log("❌", "Child context failed:", {
1589
+ entityId,
1590
+ name,
1591
+ error,
1592
+ });
1593
+ // Mark this run-in-child-context as finished to prevent descendant operations
1594
+ checkpoint.markAncestorFinished(entityId);
1595
+ // Always checkpoint failures
1596
+ const subType = options?.subType || exports.OperationSubType.RUN_IN_CHILD_CONTEXT;
1597
+ checkpoint.checkpoint(entityId, {
1598
+ Id: entityId,
1599
+ ParentId: parentId,
1600
+ Action: clientLambda.OperationAction.FAIL,
1601
+ SubType: subType,
1602
+ Type: clientLambda.OperationType.CONTEXT,
1603
+ Error: createErrorObjectFromError(error),
1604
+ Name: name,
1605
+ });
1606
+ // Reconstruct error from ErrorObject for deterministic behavior
1607
+ const errorObject = createErrorObjectFromError(error);
1608
+ const reconstructedError = DurableOperationError.fromErrorObject(errorObject);
1609
+ throw new ChildContextError(reconstructedError.message, reconstructedError);
1610
+ }
1611
+ };
1612
+
1613
+ const createWaitHandler = (context, checkpoint, createStepId, parentId, checkAndUpdateReplayMode) => {
1614
+ function waitHandler(nameOrDuration, duration) {
1615
+ const isNameFirst = typeof nameOrDuration === "string";
1616
+ const actualName = isNameFirst ? nameOrDuration : undefined;
1617
+ const actualDuration = isNameFirst ? duration : nameOrDuration;
1618
+ const actualSeconds = durationToSeconds(actualDuration);
1619
+ const stepId = createStepId();
1620
+ // Phase 1: Start wait operation
1621
+ let isCompleted = false;
1622
+ const phase1Promise = (async () => {
1623
+ log("⏲️", "Wait phase 1:", {
1624
+ stepId,
1625
+ name: actualName,
1626
+ seconds: actualSeconds,
1627
+ });
1628
+ let stepData = context.getStepData(stepId);
1629
+ // Validate replay consistency
1630
+ validateReplayConsistency(stepId, {
1631
+ type: clientLambda.OperationType.WAIT,
1632
+ name: actualName,
1633
+ subType: exports.OperationSubType.WAIT,
1634
+ }, stepData, context);
1635
+ // Check if already completed
1636
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1637
+ log("⏭️", "Wait already completed:", { stepId });
1638
+ checkAndUpdateReplayMode?.();
1639
+ // Mark as completed
1640
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1641
+ metadata: {
1642
+ stepId,
1643
+ name: actualName,
1644
+ type: clientLambda.OperationType.WAIT,
1645
+ subType: exports.OperationSubType.WAIT,
1646
+ parentId,
1647
+ },
1648
+ });
1649
+ isCompleted = true;
1650
+ return;
1651
+ }
1652
+ // Start wait if not already started
1653
+ if (!stepData) {
1654
+ await checkpoint.checkpoint(stepId, {
1655
+ Id: stepId,
1656
+ ParentId: parentId,
1657
+ Action: clientLambda.OperationAction.START,
1658
+ SubType: exports.OperationSubType.WAIT,
1659
+ Type: clientLambda.OperationType.WAIT,
1660
+ Name: actualName,
1661
+ WaitOptions: {
1662
+ WaitSeconds: actualSeconds,
1663
+ },
1664
+ });
1665
+ }
1666
+ // Refresh stepData after checkpoint
1667
+ stepData = context.getStepData(stepId);
1668
+ // Mark as IDLE_NOT_AWAITED (phase 1 complete, not awaited yet)
1669
+ checkpoint.markOperationState(stepId, OperationLifecycleState.IDLE_NOT_AWAITED, {
1670
+ metadata: {
1671
+ stepId,
1672
+ name: actualName,
1673
+ type: clientLambda.OperationType.WAIT,
1674
+ subType: exports.OperationSubType.WAIT,
1675
+ parentId,
1676
+ },
1677
+ endTimestamp: stepData?.WaitDetails?.ScheduledEndTimestamp,
1678
+ });
1679
+ log("✅", "Wait phase 1 complete:", { stepId });
1680
+ })();
1681
+ // Prevent unhandled rejection
1682
+ phase1Promise.catch(() => { });
1683
+ // Phase 2: Wait for completion
1684
+ return new DurablePromise(async () => {
1685
+ // Wait for phase 1
1686
+ await phase1Promise;
1687
+ // If already completed in phase 1, skip phase 2
1688
+ if (isCompleted) {
1689
+ return;
1690
+ }
1691
+ log("⏲️", "Wait phase 2:", { stepId });
1692
+ // Mark as awaited
1693
+ checkpoint.markOperationAwaited(stepId);
1694
+ // Wait for status change
1695
+ await checkpoint.waitForStatusChange(stepId);
1696
+ // Check final status
1697
+ const stepData = context.getStepData(stepId);
1698
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1699
+ log("✅", "Wait completed:", { stepId });
1700
+ checkAndUpdateReplayMode?.();
1701
+ // Mark as completed
1702
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1703
+ return;
1704
+ }
1705
+ // Should not reach here, but handle gracefully
1706
+ log("⚠️", "Wait ended with unexpected status:", {
1707
+ stepId,
1708
+ status: stepData?.Status,
1709
+ });
1710
+ });
1711
+ }
1712
+ return waitHandler;
1713
+ };
1714
+
1715
+ const createWaitForConditionHandler = (context, checkpoint, createStepId, logger, parentId) => {
1716
+ return (nameOrCheck, checkOrConfig, maybeConfig) => {
1717
+ let name;
1718
+ let check;
1719
+ let config;
1720
+ if (typeof nameOrCheck === "string" || nameOrCheck === undefined) {
1721
+ name = nameOrCheck;
1722
+ check = checkOrConfig;
1723
+ config = maybeConfig;
1724
+ }
1725
+ else {
1726
+ check = nameOrCheck;
1727
+ config = checkOrConfig;
1728
+ }
1729
+ if (!config?.waitStrategy || config.initialState === undefined) {
1730
+ throw new Error("waitForCondition requires config with waitStrategy and initialState");
1731
+ }
1732
+ const stepId = createStepId();
1733
+ const serdes = config.serdes || defaultSerdes;
1734
+ const phase1Promise = (async () => {
1735
+ let stepData = context.getStepData(stepId);
1736
+ // Check if already completed
1737
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1738
+ log("⏭️", "WaitForCondition already completed:", { stepId });
1739
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1740
+ metadata: {
1741
+ stepId,
1742
+ name,
1743
+ type: clientLambda.OperationType.STEP,
1744
+ subType: exports.OperationSubType.WAIT_FOR_CONDITION,
1745
+ parentId,
1746
+ },
1747
+ });
1748
+ return await safeDeserialize(serdes, stepData.StepDetails?.Result, stepId, name, context.terminationManager, context.durableExecutionArn);
1749
+ }
1750
+ // Check if already failed
1751
+ if (stepData?.Status === clientLambda.OperationStatus.FAILED) {
1752
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1753
+ metadata: {
1754
+ stepId,
1755
+ name,
1756
+ type: clientLambda.OperationType.STEP,
1757
+ subType: exports.OperationSubType.WAIT_FOR_CONDITION,
1758
+ parentId,
1759
+ },
1760
+ });
1761
+ if (stepData.StepDetails?.Error) {
1762
+ throw DurableOperationError.fromErrorObject(stepData.StepDetails.Error);
1763
+ }
1764
+ throw new WaitForConditionError("waitForCondition failed");
1765
+ }
1766
+ // Check if pending retry
1767
+ if (stepData?.Status === clientLambda.OperationStatus.PENDING) {
1768
+ checkpoint.markOperationState(stepId, OperationLifecycleState.RETRY_WAITING, {
1769
+ metadata: {
1770
+ stepId,
1771
+ name,
1772
+ type: clientLambda.OperationType.STEP,
1773
+ subType: exports.OperationSubType.WAIT_FOR_CONDITION,
1774
+ parentId,
1775
+ },
1776
+ endTimestamp: stepData.StepDetails?.NextAttemptTimestamp,
1777
+ });
1778
+ return (async () => {
1779
+ await checkpoint.waitForRetryTimer(stepId);
1780
+ stepData = context.getStepData(stepId);
1781
+ return await executeCheckLogic();
1782
+ })();
1783
+ }
1784
+ return await executeCheckLogic();
1785
+ async function executeCheckLogic() {
1786
+ stepData = context.getStepData(stepId);
1787
+ // Get current state
1788
+ let currentState;
1789
+ if (stepData?.Status === clientLambda.OperationStatus.STARTED ||
1790
+ stepData?.Status === clientLambda.OperationStatus.READY) {
1791
+ const checkpointData = stepData.StepDetails?.Result;
1792
+ if (checkpointData) {
1793
+ try {
1794
+ const serdesContext = {
1795
+ entityId: stepId,
1796
+ durableExecutionArn: context.durableExecutionArn,
1797
+ };
1798
+ currentState = await serdes.deserialize(checkpointData, serdesContext);
1799
+ }
1800
+ catch {
1801
+ currentState = config.initialState;
1802
+ }
1803
+ }
1804
+ else {
1805
+ currentState = config.initialState;
1806
+ }
1807
+ }
1808
+ else {
1809
+ currentState = config.initialState;
1810
+ }
1811
+ const currentAttempt = (stepData?.StepDetails?.Attempt ?? 0) + 1;
1812
+ // Checkpoint START if not already started
1813
+ if (stepData?.Status !== clientLambda.OperationStatus.STARTED) {
1814
+ checkpoint.checkpoint(stepId, {
1815
+ Id: stepId,
1816
+ ParentId: parentId,
1817
+ Action: clientLambda.OperationAction.START,
1818
+ SubType: exports.OperationSubType.WAIT_FOR_CONDITION,
1819
+ Type: clientLambda.OperationType.STEP,
1820
+ Name: name,
1821
+ });
1822
+ }
1823
+ try {
1824
+ const waitForConditionContext = {
1825
+ logger,
1826
+ };
1827
+ // Mark operation as EXECUTING
1828
+ checkpoint.markOperationState(stepId, OperationLifecycleState.EXECUTING, {
1829
+ metadata: {
1830
+ stepId,
1831
+ name,
1832
+ type: clientLambda.OperationType.STEP,
1833
+ subType: exports.OperationSubType.WAIT_FOR_CONDITION,
1834
+ parentId,
1835
+ },
1836
+ });
1837
+ const newState = await runWithContext(stepId, parentId, () => check(currentState, waitForConditionContext), currentAttempt, DurableExecutionMode.ExecutionMode);
1838
+ const serializedState = await safeSerialize(serdes, newState, stepId, name, context.terminationManager, context.durableExecutionArn);
1839
+ const deserializedState = await safeDeserialize(serdes, serializedState, stepId, name, context.terminationManager, context.durableExecutionArn);
1840
+ const decision = config.waitStrategy(deserializedState, currentAttempt);
1841
+ if (!decision.shouldContinue) {
1842
+ await checkpoint.checkpoint(stepId, {
1843
+ Id: stepId,
1844
+ ParentId: parentId,
1845
+ Action: clientLambda.OperationAction.SUCCEED,
1846
+ SubType: exports.OperationSubType.WAIT_FOR_CONDITION,
1847
+ Type: clientLambda.OperationType.STEP,
1848
+ Payload: serializedState,
1849
+ Name: name,
1850
+ });
1851
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1852
+ return deserializedState;
1853
+ }
1854
+ await checkpoint.checkpoint(stepId, {
1855
+ Id: stepId,
1856
+ ParentId: parentId,
1857
+ Action: clientLambda.OperationAction.RETRY,
1858
+ SubType: exports.OperationSubType.WAIT_FOR_CONDITION,
1859
+ Type: clientLambda.OperationType.STEP,
1860
+ Payload: serializedState,
1861
+ Name: name,
1862
+ StepOptions: {
1863
+ NextAttemptDelaySeconds: durationToSeconds(decision.delay),
1864
+ },
1865
+ });
1866
+ checkpoint.markOperationState(stepId, OperationLifecycleState.RETRY_WAITING, {
1867
+ metadata: {
1868
+ stepId,
1869
+ name,
1870
+ type: clientLambda.OperationType.STEP,
1871
+ subType: exports.OperationSubType.WAIT_FOR_CONDITION,
1872
+ parentId,
1873
+ },
1874
+ endTimestamp: context.getStepData(stepId)?.StepDetails?.NextAttemptTimestamp,
1875
+ });
1876
+ await checkpoint.waitForRetryTimer(stepId);
1877
+ return await executeCheckLogic();
1878
+ }
1879
+ catch (error) {
1880
+ await checkpoint.checkpoint(stepId, {
1881
+ Id: stepId,
1882
+ ParentId: parentId,
1883
+ Action: clientLambda.OperationAction.FAIL,
1884
+ SubType: exports.OperationSubType.WAIT_FOR_CONDITION,
1885
+ Type: clientLambda.OperationType.STEP,
1886
+ Error: createErrorObjectFromError(error),
1887
+ Name: name,
1888
+ });
1889
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1890
+ throw DurableOperationError.fromErrorObject(createErrorObjectFromError(error));
1891
+ }
1892
+ }
1893
+ })();
1894
+ phase1Promise.catch(() => { });
1895
+ return new DurablePromise(async () => {
1896
+ checkpoint.markOperationAwaited(stepId);
1897
+ return await phase1Promise;
1898
+ });
1899
+ };
1900
+ };
1901
+
1902
+ const createCallbackPromise = (context, checkpoint, stepId, stepName, serdes, checkAndUpdateReplayMode) => {
1903
+ return new DurablePromise(async () => {
1904
+ log("🔄", "Callback promise phase 2:", { stepId, stepName });
1905
+ checkpoint.markOperationAwaited(stepId);
1906
+ await checkpoint.waitForStatusChange(stepId);
1907
+ const stepData = context.getStepData(stepId);
1908
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1909
+ log("✅", "Callback completed:", { stepId });
1910
+ checkAndUpdateReplayMode();
1911
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1912
+ const callbackData = stepData.CallbackDetails;
1913
+ if (!callbackData) {
1914
+ throw new CallbackError(`No callback data found for completed callback: ${stepId}`);
1915
+ }
1916
+ const result = await safeDeserialize(serdes, callbackData.Result, stepId, stepName, context.terminationManager, context.durableExecutionArn);
1917
+ return result;
1918
+ }
1919
+ // Handle failure
1920
+ log("❌", "Callback failed:", { stepId, status: stepData?.Status });
1921
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED);
1922
+ const callbackData = stepData?.CallbackDetails;
1923
+ const error = callbackData?.Error;
1924
+ if (error) {
1925
+ const cause = new Error(error.ErrorMessage);
1926
+ cause.name = error.ErrorType || "Error";
1927
+ cause.stack = error.StackTrace?.join("\n");
1928
+ throw new CallbackError(error.ErrorMessage || "Callback failed", cause, error.ErrorData);
1929
+ }
1930
+ throw new CallbackError("Callback failed");
1931
+ });
1932
+ };
1933
+
1934
+ const createPassThroughSerdes = () => ({
1935
+ serialize: async (value) => value,
1936
+ deserialize: async (data) => data,
1937
+ });
1938
+ const createCallback = (context, checkpoint, createStepId, checkAndUpdateReplayMode, parentId) => {
1939
+ return (nameOrConfig, maybeConfig) => {
1940
+ let name;
1941
+ let config;
1942
+ if (typeof nameOrConfig === "string" || nameOrConfig === undefined) {
1943
+ name = nameOrConfig;
1944
+ config = maybeConfig;
1945
+ }
1946
+ else {
1947
+ config = nameOrConfig;
1948
+ }
1949
+ const stepId = createStepId();
1950
+ const serdes = config?.serdes || createPassThroughSerdes();
1951
+ // Phase 1: Setup and checkpoint
1952
+ let isCompleted = false;
1953
+ const phase1Promise = (async () => {
1954
+ log("📞", "Callback phase 1:", { stepId, name });
1955
+ let stepData = context.getStepData(stepId);
1956
+ // Validate replay consistency
1957
+ validateReplayConsistency(stepId, {
1958
+ type: clientLambda.OperationType.CALLBACK,
1959
+ name,
1960
+ subType: exports.OperationSubType.CALLBACK,
1961
+ }, stepData, context);
1962
+ // Check if already completed
1963
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
1964
+ log("⏭️", "Callback already completed:", { stepId });
1965
+ checkAndUpdateReplayMode();
1966
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1967
+ metadata: {
1968
+ stepId,
1969
+ name,
1970
+ type: clientLambda.OperationType.CALLBACK,
1971
+ subType: exports.OperationSubType.CALLBACK,
1972
+ parentId,
1973
+ },
1974
+ });
1975
+ isCompleted = true;
1976
+ return;
1977
+ }
1978
+ // Check if already failed
1979
+ if (stepData?.Status === clientLambda.OperationStatus.FAILED ||
1980
+ stepData?.Status === clientLambda.OperationStatus.TIMED_OUT) {
1981
+ log("❌", "Callback already failed:", { stepId });
1982
+ checkpoint.markOperationState(stepId, OperationLifecycleState.COMPLETED, {
1983
+ metadata: {
1984
+ stepId,
1985
+ name,
1986
+ type: clientLambda.OperationType.CALLBACK,
1987
+ subType: exports.OperationSubType.CALLBACK,
1988
+ parentId,
1989
+ },
1990
+ });
1991
+ isCompleted = true;
1992
+ return;
1993
+ }
1994
+ // Start callback if not already started
1995
+ if (!stepData) {
1996
+ await checkpoint.checkpoint(stepId, {
1997
+ Id: stepId,
1998
+ ParentId: parentId,
1999
+ Action: "START",
2000
+ SubType: exports.OperationSubType.CALLBACK,
2001
+ Type: clientLambda.OperationType.CALLBACK,
2002
+ Name: name,
2003
+ CallbackOptions: {
2004
+ TimeoutSeconds: config?.timeout
2005
+ ? durationToSeconds(config.timeout)
2006
+ : undefined,
2007
+ HeartbeatTimeoutSeconds: config?.heartbeatTimeout
2008
+ ? durationToSeconds(config.heartbeatTimeout)
2009
+ : undefined,
2010
+ },
2011
+ });
2012
+ // Refresh stepData after checkpoint
2013
+ stepData = context.getStepData(stepId);
2014
+ }
2015
+ // Mark as IDLE_NOT_AWAITED
2016
+ checkpoint.markOperationState(stepId, OperationLifecycleState.IDLE_NOT_AWAITED, {
2017
+ metadata: {
2018
+ stepId,
2019
+ name,
2020
+ type: clientLambda.OperationType.CALLBACK,
2021
+ subType: exports.OperationSubType.CALLBACK,
2022
+ parentId,
2023
+ },
2024
+ });
2025
+ log("✅", "Callback phase 1 complete:", { stepId });
2026
+ })();
2027
+ phase1Promise.catch(() => { });
2028
+ // Phase 2: Handle results and create callback promise
2029
+ return new DurablePromise(async () => {
2030
+ await phase1Promise;
2031
+ if (isCompleted) {
2032
+ const stepData = context.getStepData(stepId);
2033
+ const callbackData = stepData?.CallbackDetails;
2034
+ if (!callbackData?.CallbackId) {
2035
+ throw new CallbackError(`No callback ID found for callback: ${stepId}`);
2036
+ }
2037
+ if (stepData?.Status === clientLambda.OperationStatus.SUCCEEDED) {
2038
+ const deserializedResult = await safeDeserialize(serdes, callbackData.Result, stepId, name, context.terminationManager, context.durableExecutionArn);
2039
+ const resolvedPromise = new DurablePromise(async () => deserializedResult);
2040
+ return [resolvedPromise, callbackData.CallbackId];
2041
+ }
2042
+ // Handle failure
2043
+ const error = stepData?.CallbackDetails?.Error;
2044
+ const callbackError = error
2045
+ ? (() => {
2046
+ const cause = new Error(error.ErrorMessage);
2047
+ cause.name = error.ErrorType || "Error";
2048
+ cause.stack = error.StackTrace?.join("\n");
2049
+ return new CallbackError(error.ErrorMessage || "Callback failed", cause, error.ErrorData);
2050
+ })()
2051
+ : new CallbackError("Callback failed");
2052
+ const rejectedPromise = new DurablePromise(async () => {
2053
+ throw callbackError;
2054
+ });
2055
+ return [rejectedPromise, callbackData.CallbackId];
2056
+ }
2057
+ log("📞", "Callback phase 2:", { stepId });
2058
+ const stepData = context.getStepData(stepId);
2059
+ const callbackData = stepData?.CallbackDetails;
2060
+ if (!callbackData?.CallbackId) {
2061
+ throw new CallbackError(`No callback ID found for started callback: ${stepId}`);
2062
+ }
2063
+ const callbackId = callbackData.CallbackId;
2064
+ const callbackPromise = createCallbackPromise(context, checkpoint, stepId, name, serdes, checkAndUpdateReplayMode);
2065
+ log("✅", "Callback created:", { stepId, name, callbackId });
2066
+ return [callbackPromise, callbackId];
2067
+ });
2068
+ };
2069
+ };
2070
+
2071
+ const createWaitForCallbackHandler = (context, getNextStepId, runInChildContext) => {
2072
+ return (nameOrSubmitter, submitterOrConfig, maybeConfig) => {
2073
+ let name;
2074
+ let submitter;
2075
+ let config;
2076
+ // Parse the overloaded parameters - validation errors thrown here are async
2077
+ if (typeof nameOrSubmitter === "string" || nameOrSubmitter === undefined) {
2078
+ // Case: waitForCallback("name", submitterFunc, config?) or waitForCallback(undefined, submitterFunc, config?)
2079
+ name = nameOrSubmitter;
2080
+ if (typeof submitterOrConfig === "function") {
2081
+ submitter = submitterOrConfig;
2082
+ config = maybeConfig;
2083
+ }
2084
+ else {
2085
+ return new DurablePromise(() => Promise.reject(new Error("waitForCallback requires a submitter function when name is provided")));
2086
+ }
2087
+ }
2088
+ else if (typeof nameOrSubmitter === "function") {
2089
+ // Case: waitForCallback(submitterFunc, config?)
2090
+ submitter = nameOrSubmitter;
2091
+ config = submitterOrConfig;
2092
+ }
2093
+ else {
2094
+ return new DurablePromise(() => Promise.reject(new Error("waitForCallback requires a submitter function")));
2095
+ }
2096
+ // Two-phase execution: Phase 1 starts immediately, Phase 2 returns result when awaited
2097
+ // Phase 1: Start execution immediately and capture result/error
2098
+ const phase1Promise = (async () => {
2099
+ log("📞", "WaitForCallback requested:", {
2100
+ name,
2101
+ hasSubmitter: !!submitter,
2102
+ config,
2103
+ });
2104
+ // Use runInChildContext to ensure proper ID generation and isolation
2105
+ const childFunction = async (childCtx) => {
2106
+ // Convert WaitForCallbackConfig to CreateCallbackConfig
2107
+ const createCallbackConfig = config
2108
+ ? {
2109
+ timeout: config.timeout,
2110
+ heartbeatTimeout: config.heartbeatTimeout,
2111
+ }
2112
+ : undefined;
2113
+ // Create callback and get the promise + callbackId
2114
+ const [callbackPromise, callbackId] = await childCtx.createCallback(createCallbackConfig);
2115
+ log("🆔", "Callback created:", {
2116
+ callbackId,
2117
+ name,
2118
+ });
2119
+ // Execute the submitter step (submitter is now mandatory)
2120
+ await childCtx.step(async (stepContext) => {
2121
+ // Use the step's built-in logger instead of creating a new one
2122
+ const callbackContext = {
2123
+ logger: stepContext.logger,
2124
+ };
2125
+ log("📤", "Executing submitter:", {
2126
+ callbackId,
2127
+ name,
2128
+ });
2129
+ await submitter(callbackId, callbackContext);
2130
+ log("✅", "Submitter completed:", {
2131
+ callbackId,
2132
+ name,
2133
+ });
2134
+ }, config?.retryStrategy
2135
+ ? { retryStrategy: config.retryStrategy }
2136
+ : undefined);
2137
+ log("⏳", "Waiting for callback completion:", {
2138
+ callbackId,
2139
+ name,
2140
+ });
2141
+ // Return just the callback promise result
2142
+ return await callbackPromise;
2143
+ };
2144
+ const stepId = getNextStepId();
2145
+ return {
2146
+ result: await runInChildContext(name, childFunction, {
2147
+ subType: exports.OperationSubType.WAIT_FOR_CALLBACK,
2148
+ }),
2149
+ stepId,
2150
+ };
2151
+ })();
2152
+ // Attach catch handler to prevent unhandled promise rejections
2153
+ // The error will still be thrown when the DurablePromise is awaited
2154
+ phase1Promise.catch(() => { });
2155
+ // Phase 2: Return DurablePromise that returns Phase 1 result when awaited
2156
+ return new DurablePromise(async () => {
2157
+ const { result, stepId } = await phase1Promise;
2158
+ // Always deserialize the result since it's a string
2159
+ return (await safeDeserialize(config?.serdes ?? createPassThroughSerdes(), result, stepId, name, context.terminationManager, context.durableExecutionArn));
2160
+ });
2161
+ };
2162
+ };
2163
+
2164
+ /**
2165
+ * Creates a predefined summary generator for parallel operations
2166
+ */
2167
+ const createParallelSummaryGenerator = () => (result) => {
2168
+ return JSON.stringify({
2169
+ type: "ParallelResult",
2170
+ totalCount: result.totalCount,
2171
+ successCount: result.successCount,
2172
+ failureCount: result.failureCount,
2173
+ startedCount: result.startedCount,
2174
+ completionReason: result.completionReason,
2175
+ status: result.status,
2176
+ });
2177
+ };
2178
+ /**
2179
+ * Creates a predefined summary generator for map operations
2180
+ */
2181
+ const createMapSummaryGenerator = () => (result) => {
2182
+ return JSON.stringify({
2183
+ type: "MapResult",
2184
+ totalCount: result.totalCount,
2185
+ successCount: result.successCount,
2186
+ failureCount: result.failureCount,
2187
+ completionReason: result.completionReason,
2188
+ status: result.status,
2189
+ });
2190
+ };
2191
+
2192
+ const createMapHandler = (context, executeConcurrently) => {
2193
+ return (nameOrItems, itemsOrMapFunc, mapFuncOrConfig, maybeConfig) => {
2194
+ // Phase 1: Parse parameters and start execution immediately
2195
+ const phase1Promise = (async () => {
2196
+ let name;
2197
+ let items;
2198
+ let mapFunc;
2199
+ let config;
2200
+ // Parse overloaded parameters
2201
+ if (typeof nameOrItems === "string" || nameOrItems === undefined) {
2202
+ // Case: map(name, items, mapFunc, config?)
2203
+ name = nameOrItems;
2204
+ items = itemsOrMapFunc;
2205
+ mapFunc = mapFuncOrConfig;
2206
+ config = maybeConfig;
2207
+ }
2208
+ else {
2209
+ // Case: map(items, mapFunc, config?)
2210
+ items = nameOrItems;
2211
+ mapFunc = itemsOrMapFunc;
2212
+ config = mapFuncOrConfig;
2213
+ }
2214
+ log("🗺️", "Starting map operation:", {
2215
+ name,
2216
+ itemCount: items.length,
2217
+ maxConcurrency: config?.maxConcurrency,
2218
+ });
2219
+ // Validate inputs
2220
+ if (!Array.isArray(items)) {
2221
+ throw new Error("Map operation requires an array of items");
2222
+ }
2223
+ if (typeof mapFunc !== "function") {
2224
+ throw new Error("Map operation requires a function to process items");
2225
+ }
2226
+ // Convert to concurrent execution items
2227
+ const executionItems = items.map((item, index) => ({
2228
+ id: `map-item-${index}`,
2229
+ data: item,
2230
+ index,
2231
+ name: config?.itemNamer ? config.itemNamer(item, index) : undefined,
2232
+ }));
2233
+ // Create executor that calls mapFunc
2234
+ const executor = async (executionItem, childContext) => mapFunc(childContext, executionItem.data, executionItem.index, items);
2235
+ const result = await executeConcurrently(name, executionItems, executor, {
2236
+ maxConcurrency: config?.maxConcurrency,
2237
+ topLevelSubType: exports.OperationSubType.MAP,
2238
+ iterationSubType: exports.OperationSubType.MAP_ITERATION,
2239
+ summaryGenerator: createMapSummaryGenerator(),
2240
+ completionConfig: config?.completionConfig,
2241
+ serdes: config?.serdes,
2242
+ itemSerdes: config?.itemSerdes,
2243
+ });
2244
+ log("🗺️", "Map operation completed successfully:", {
2245
+ resultCount: result.totalCount,
2246
+ });
2247
+ return result;
2248
+ })();
2249
+ // Attach catch handler to prevent unhandled promise rejections
2250
+ // The error will still be thrown when the DurablePromise is awaited
2251
+ phase1Promise.catch(() => { });
2252
+ // Phase 2: Return DurablePromise that returns Phase 1 result when awaited
2253
+ return new DurablePromise(async () => {
2254
+ return await phase1Promise;
2255
+ });
2256
+ };
2257
+ };
2258
+
2259
+ const createParallelHandler = (context, executeConcurrently) => {
2260
+ return (nameOrBranches, branchesOrConfig, maybeConfig) => {
2261
+ // Phase 1: Parse parameters and start execution immediately
2262
+ const phase1Promise = (async () => {
2263
+ let name;
2264
+ let branches;
2265
+ let config;
2266
+ // Parse overloaded parameters
2267
+ if (typeof nameOrBranches === "string" || nameOrBranches === undefined) {
2268
+ // Case: parallel(name, branches, config?)
2269
+ name = nameOrBranches;
2270
+ branches = branchesOrConfig;
2271
+ config = maybeConfig;
2272
+ }
2273
+ else {
2274
+ // Case: parallel(branches, config?)
2275
+ branches = nameOrBranches;
2276
+ config = branchesOrConfig;
2277
+ }
2278
+ // Validate inputs
2279
+ if (!Array.isArray(branches)) {
2280
+ throw new Error("Parallel operation requires an array of branch functions");
2281
+ }
2282
+ log("🔀", "Starting parallel operation:", {
2283
+ name,
2284
+ branchCount: branches.length,
2285
+ maxConcurrency: config?.maxConcurrency,
2286
+ });
2287
+ if (branches.some((branch) => typeof branch !== "function" &&
2288
+ (typeof branch !== "object" || typeof branch.func !== "function"))) {
2289
+ throw new Error("All branches must be functions or NamedParallelBranch objects");
2290
+ }
2291
+ // Convert to concurrent execution items
2292
+ const executionItems = branches.map((branch, index) => {
2293
+ const isNamedBranch = typeof branch === "object" && "func" in branch;
2294
+ const func = isNamedBranch ? branch.func : branch;
2295
+ const branchName = isNamedBranch ? branch.name : undefined;
2296
+ return {
2297
+ id: `parallel-branch-${index}`,
2298
+ data: func,
2299
+ index,
2300
+ name: branchName,
2301
+ };
2302
+ });
2303
+ // Create executor that calls the branch function
2304
+ const executor = async (executionItem, childContext) => {
2305
+ log("🔀", "Processing parallel branch:", {
2306
+ index: executionItem.index,
2307
+ });
2308
+ const result = await executionItem.data(childContext);
2309
+ log("✅", "Parallel branch completed:", {
2310
+ index: executionItem.index,
2311
+ result,
2312
+ });
2313
+ return result;
2314
+ };
2315
+ const result = await executeConcurrently(name, executionItems, executor, {
2316
+ maxConcurrency: config?.maxConcurrency,
2317
+ topLevelSubType: exports.OperationSubType.PARALLEL,
2318
+ iterationSubType: exports.OperationSubType.PARALLEL_BRANCH,
2319
+ summaryGenerator: createParallelSummaryGenerator(),
2320
+ completionConfig: config?.completionConfig,
2321
+ serdes: config?.serdes,
2322
+ itemSerdes: config?.itemSerdes,
2323
+ });
2324
+ log("🔀", "Parallel operation completed successfully:", {
2325
+ resultCount: result.totalCount,
2326
+ });
2327
+ return result;
2328
+ })();
2329
+ // Attach catch handler to prevent unhandled promise rejections
2330
+ // The error will still be thrown when the DurablePromise is awaited
2331
+ phase1Promise.catch(() => { });
2332
+ // Phase 2: Return DurablePromise that returns Phase 1 result when awaited
2333
+ return new DurablePromise(async () => {
2334
+ return await phase1Promise;
2335
+ });
2336
+ };
2337
+ };
2338
+
2339
+ // Minimal error decoration for Promise.allSettled results
2340
+ function decorateErrors(value) {
2341
+ return value.map((item) => {
2342
+ if (item && item.status === "rejected" && item.reason instanceof Error) {
2343
+ return {
2344
+ ...item,
2345
+ reason: {
2346
+ message: item.reason.message,
2347
+ name: item.reason.name,
2348
+ stack: item.reason.stack,
2349
+ },
2350
+ };
2351
+ }
2352
+ return item;
2353
+ });
2354
+ }
2355
+ // Error restoration for Promise.allSettled results
2356
+ function restoreErrors(value) {
2357
+ return value.map((item) => {
2358
+ if (item &&
2359
+ item.status === "rejected" &&
2360
+ item.reason &&
2361
+ typeof item.reason === "object" &&
2362
+ item.reason.message) {
2363
+ const error = new Error(item.reason.message);
2364
+ error.name = item.reason.name || "Error";
2365
+ if (item.reason.stack)
2366
+ error.stack = item.reason.stack;
2367
+ return {
2368
+ ...item,
2369
+ reason: error,
2370
+ };
2371
+ }
2372
+ return item;
2373
+ });
2374
+ }
2375
+ // Custom serdes for promise results with error handling
2376
+ function createErrorAwareSerdes() {
2377
+ return {
2378
+ serialize: async (value, _context) => value !== undefined ? JSON.stringify(decorateErrors(value)) : undefined,
2379
+ deserialize: async (data, _context) => data !== undefined
2380
+ ? restoreErrors(JSON.parse(data))
2381
+ : undefined,
2382
+ };
2383
+ }
2384
+ // No-retry strategy for promise combinators
2385
+ const stepConfig = {
2386
+ retryStrategy: () => ({
2387
+ shouldRetry: false,
2388
+ }),
2389
+ };
2390
+ const createPromiseHandler = (step) => {
2391
+ const parseParams = (nameOrPromises, maybePromises) => {
2392
+ if (typeof nameOrPromises === "string" || nameOrPromises === undefined) {
2393
+ return { name: nameOrPromises, promises: maybePromises };
2394
+ }
2395
+ return { name: undefined, promises: nameOrPromises };
2396
+ };
2397
+ const all = (nameOrPromises, maybePromises) => {
2398
+ return new DurablePromise(async () => {
2399
+ const { name, promises } = parseParams(nameOrPromises, maybePromises);
2400
+ // Wrap Promise.all execution in a step for persistence
2401
+ return await step(name, () => Promise.all(promises), stepConfig);
2402
+ });
2403
+ };
2404
+ const allSettled = (nameOrPromises, maybePromises) => {
2405
+ return new DurablePromise(async () => {
2406
+ const { name, promises } = parseParams(nameOrPromises, maybePromises);
2407
+ // Wrap Promise.allSettled execution in a step for persistence
2408
+ return await step(name, () => Promise.allSettled(promises), {
2409
+ ...stepConfig,
2410
+ serdes: createErrorAwareSerdes(),
2411
+ });
2412
+ });
2413
+ };
2414
+ const any = (nameOrPromises, maybePromises) => {
2415
+ return new DurablePromise(async () => {
2416
+ const { name, promises } = parseParams(nameOrPromises, maybePromises);
2417
+ // Wrap Promise.any execution in a step for persistence
2418
+ return await step(name, () => Promise.any(promises), stepConfig);
2419
+ });
2420
+ };
2421
+ const race = (nameOrPromises, maybePromises) => {
2422
+ return new DurablePromise(async () => {
2423
+ const { name, promises } = parseParams(nameOrPromises, maybePromises);
2424
+ // Wrap Promise.race execution in a step for persistence
2425
+ return await step(name, () => Promise.race(promises), stepConfig);
2426
+ });
2427
+ };
2428
+ return {
2429
+ all,
2430
+ allSettled,
2431
+ any,
2432
+ race,
2433
+ };
2434
+ };
2435
+
2436
+ class BatchResultImpl {
2437
+ all;
2438
+ completionReason;
2439
+ constructor(all, completionReason) {
2440
+ this.all = all;
2441
+ this.completionReason = completionReason;
2442
+ }
2443
+ succeeded() {
2444
+ return this.all.filter((item) => item.status === exports.BatchItemStatus.SUCCEEDED && item.result !== undefined);
2445
+ }
2446
+ failed() {
2447
+ return this.all.filter((item) => item.status === exports.BatchItemStatus.FAILED && item.error !== undefined);
2448
+ }
2449
+ started() {
2450
+ return this.all.filter((item) => item.status === exports.BatchItemStatus.STARTED);
2451
+ }
2452
+ get status() {
2453
+ return this.hasFailure ? exports.BatchItemStatus.FAILED : exports.BatchItemStatus.SUCCEEDED;
2454
+ }
2455
+ get hasFailure() {
2456
+ return this.all.some((item) => item.status === exports.BatchItemStatus.FAILED);
2457
+ }
2458
+ throwIfError() {
2459
+ const firstError = this.all.find((item) => item.status === exports.BatchItemStatus.FAILED)?.error;
2460
+ if (firstError) {
2461
+ throw firstError;
2462
+ }
2463
+ }
2464
+ getResults() {
2465
+ return this.succeeded().map((item) => item.result);
2466
+ }
2467
+ getErrors() {
2468
+ return this.failed().map((item) => item.error);
2469
+ }
2470
+ get successCount() {
2471
+ return this.all.filter((item) => item.status === exports.BatchItemStatus.SUCCEEDED)
2472
+ .length;
2473
+ }
2474
+ get failureCount() {
2475
+ return this.all.filter((item) => item.status === exports.BatchItemStatus.FAILED)
2476
+ .length;
2477
+ }
2478
+ get startedCount() {
2479
+ return this.all.filter((item) => item.status === exports.BatchItemStatus.STARTED)
2480
+ .length;
2481
+ }
2482
+ get totalCount() {
2483
+ return this.all.length;
2484
+ }
2485
+ }
2486
+ /**
2487
+ * Restores methods to deserialized BatchResult data
2488
+ */
2489
+ function restoreBatchResult(data) {
2490
+ if (data &&
2491
+ typeof data === "object" &&
2492
+ "all" in data &&
2493
+ Array.isArray(data.all)) {
2494
+ const serializedData = data;
2495
+ // Restore Error objects
2496
+ const restoredItems = serializedData.all.map((item) => ({
2497
+ ...item,
2498
+ result: item.result,
2499
+ error: item.error
2500
+ ? DurableOperationError.fromErrorObject(item.error)
2501
+ : undefined,
2502
+ }));
2503
+ return new BatchResultImpl(restoredItems, serializedData.completionReason);
2504
+ }
2505
+ return new BatchResultImpl([], "ALL_COMPLETED");
2506
+ }
2507
+
2508
+ class ConcurrencyController {
2509
+ operationName;
2510
+ skipNextOperation;
2511
+ constructor(operationName, skipNextOperation) {
2512
+ this.operationName = operationName;
2513
+ this.skipNextOperation = skipNextOperation;
2514
+ }
2515
+ isChildEntityCompleted(executionContext, parentEntityId, completedCount) {
2516
+ const childEntityId = `${parentEntityId}-${completedCount + 1}`;
2517
+ const childStepData = executionContext.getStepData(childEntityId);
2518
+ return !!(childStepData &&
2519
+ (childStepData.Status === clientLambda.OperationStatus.SUCCEEDED ||
2520
+ childStepData.Status === clientLambda.OperationStatus.FAILED));
2521
+ }
2522
+ getCompletionReason(failureCount, successCount, completedCount, items, config) {
2523
+ // Check tolerance first, before checking if all completed
2524
+ const completion = config.completionConfig;
2525
+ // Handle fail-fast behavior (no completion config or empty completion config)
2526
+ if (!completion) {
2527
+ if (failureCount > 0)
2528
+ return "FAILURE_TOLERANCE_EXCEEDED";
2529
+ }
2530
+ else {
2531
+ const hasAnyCompletionCriteria = Object.values(completion).some((value) => value !== undefined);
2532
+ if (!hasAnyCompletionCriteria) {
2533
+ if (failureCount > 0)
2534
+ return "FAILURE_TOLERANCE_EXCEEDED";
2535
+ }
2536
+ else {
2537
+ // Check specific tolerance thresholds
2538
+ if (completion.toleratedFailureCount !== undefined &&
2539
+ failureCount > completion.toleratedFailureCount) {
2540
+ return "FAILURE_TOLERANCE_EXCEEDED";
2541
+ }
2542
+ if (completion.toleratedFailurePercentage !== undefined) {
2543
+ const failurePercentage = (failureCount / items.length) * 100;
2544
+ if (failurePercentage > completion.toleratedFailurePercentage) {
2545
+ return "FAILURE_TOLERANCE_EXCEEDED";
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ // Check other completion reasons
2551
+ if (completedCount === items.length)
2552
+ return "ALL_COMPLETED";
2553
+ if (config.completionConfig?.minSuccessful !== undefined &&
2554
+ successCount >= config.completionConfig.minSuccessful)
2555
+ return "MIN_SUCCESSFUL_REACHED";
2556
+ return "ALL_COMPLETED";
2557
+ }
2558
+ async executeItems(items, executor, parentContext, config, durableExecutionMode = DurableExecutionMode.ExecutionMode, entityId, executionContext) {
2559
+ // In replay mode, we're reconstructing the result from child contexts
2560
+ if (durableExecutionMode === DurableExecutionMode.ReplaySucceededContext) {
2561
+ log("🔄", `Replay mode: Reconstructing ${this.operationName} result:`, {
2562
+ itemCount: items.length,
2563
+ });
2564
+ // Try to get the target count from step data
2565
+ let targetTotalCount;
2566
+ if (entityId && executionContext) {
2567
+ const stepData = executionContext.getStepData(entityId);
2568
+ const summaryPayload = stepData?.ContextDetails?.Result;
2569
+ if (summaryPayload) {
2570
+ try {
2571
+ const serdes = config.serdes || defaultSerdes;
2572
+ const parsedSummary = await serdes.deserialize(summaryPayload, {
2573
+ entityId: entityId,
2574
+ durableExecutionArn: executionContext.durableExecutionArn,
2575
+ });
2576
+ if (parsedSummary &&
2577
+ typeof parsedSummary === "object" &&
2578
+ "totalCount" in parsedSummary) {
2579
+ // Read totalCount directly from summary metadata
2580
+ targetTotalCount = parsedSummary.totalCount;
2581
+ log("📊", "Found initial execution count:", {
2582
+ targetTotalCount,
2583
+ });
2584
+ }
2585
+ }
2586
+ catch (error) {
2587
+ log("⚠️", "Could not parse initial result summary:", error);
2588
+ }
2589
+ }
2590
+ }
2591
+ // If we have target count and required context, use optimized replay; otherwise fallback to concurrent execution
2592
+ if (targetTotalCount !== undefined && entityId && executionContext) {
2593
+ return await this.replayItems(items, executor, parentContext, config, targetTotalCount, executionContext, entityId);
2594
+ }
2595
+ else {
2596
+ log("⚠️", "No valid target count or context found, falling back to concurrent execution");
2597
+ }
2598
+ }
2599
+ // First-time execution or fallback: use normal concurrent execution logic
2600
+ return await this.executeItemsConcurrently(items, executor, parentContext, config);
2601
+ }
2602
+ async replayItems(items, executor, parentContext, config, targetTotalCount, executionContext, parentEntityId) {
2603
+ const resultItems = [];
2604
+ log("🔄", `Replaying ${items.length} items sequentially`, {
2605
+ targetTotalCount,
2606
+ });
2607
+ let completedCount = 0;
2608
+ let stepCounter = 0;
2609
+ // Replay items sequentially until we reach the target count
2610
+ for (const item of items) {
2611
+ // Stop if we've replayed all items that completed in initial execution
2612
+ if (completedCount >= targetTotalCount) {
2613
+ log("✅", "Reached target count, stopping replay", {
2614
+ completedCount,
2615
+ targetTotalCount,
2616
+ });
2617
+ break;
2618
+ }
2619
+ // Calculate the child entity ID that runInChildContext will create
2620
+ // It uses the parent's next step ID, which is parentEntityId-{counter}
2621
+ const childEntityId = `${parentEntityId}-${stepCounter + 1}`;
2622
+ if (!this.isChildEntityCompleted(executionContext, parentEntityId, stepCounter)) {
2623
+ log("⏭️", `Skipping incomplete item:`, {
2624
+ index: item.index,
2625
+ itemId: item.id,
2626
+ childEntityId,
2627
+ });
2628
+ // Increment step counter to maintain consistency
2629
+ this.skipNextOperation();
2630
+ stepCounter++;
2631
+ continue;
2632
+ }
2633
+ try {
2634
+ const result = await parentContext.runInChildContext(item.name || item.id, (childContext) => executor(item, childContext), { subType: config.iterationSubType, serdes: config.itemSerdes });
2635
+ resultItems.push({
2636
+ result,
2637
+ index: item.index,
2638
+ status: exports.BatchItemStatus.SUCCEEDED,
2639
+ });
2640
+ completedCount++;
2641
+ stepCounter++;
2642
+ log("✅", `Replayed ${this.operationName} item:`, {
2643
+ index: item.index,
2644
+ itemId: item.id,
2645
+ completedCount,
2646
+ });
2647
+ }
2648
+ catch (error) {
2649
+ const err = error instanceof ChildContextError
2650
+ ? error
2651
+ : new ChildContextError(error instanceof Error ? error.message : String(error), error instanceof Error ? error : undefined);
2652
+ resultItems.push({
2653
+ error: err,
2654
+ index: item.index,
2655
+ status: exports.BatchItemStatus.FAILED,
2656
+ });
2657
+ completedCount++;
2658
+ stepCounter++;
2659
+ log("❌", `Replay failed for ${this.operationName} item:`, {
2660
+ index: item.index,
2661
+ itemId: item.id,
2662
+ error: err.message,
2663
+ completedCount,
2664
+ });
2665
+ }
2666
+ }
2667
+ log("🎉", `${this.operationName} replay completed:`, {
2668
+ completedCount,
2669
+ totalCount: resultItems.length,
2670
+ });
2671
+ const successCount = resultItems.filter((item) => item.status === exports.BatchItemStatus.SUCCEEDED).length;
2672
+ const failureCount = completedCount - successCount;
2673
+ return new BatchResultImpl(resultItems, this.getCompletionReason(failureCount, successCount, completedCount, items, config));
2674
+ }
2675
+ async executeItemsConcurrently(items, executor, parentContext, config) {
2676
+ const maxConcurrency = config.maxConcurrency || Infinity;
2677
+ const resultItems = new Array(items.length);
2678
+ const startedItems = new Set();
2679
+ let activeCount = 0;
2680
+ let currentIndex = 0;
2681
+ let completedCount = 0;
2682
+ let successCount = 0;
2683
+ let failureCount = 0;
2684
+ log("🚀", `Starting ${this.operationName} with concurrency control:`, {
2685
+ itemCount: items.length,
2686
+ maxConcurrency,
2687
+ });
2688
+ return new Promise((resolve) => {
2689
+ const shouldContinue = () => {
2690
+ const completion = config.completionConfig;
2691
+ if (!completion)
2692
+ return failureCount === 0;
2693
+ // Default to fail-fast when no completion criteria are defined
2694
+ const hasAnyCompletionCriteria = Object.values(completion).some((value) => value !== undefined);
2695
+ if (!hasAnyCompletionCriteria) {
2696
+ return failureCount === 0;
2697
+ }
2698
+ if (completion.toleratedFailureCount !== undefined &&
2699
+ failureCount > completion.toleratedFailureCount)
2700
+ return false;
2701
+ if (completion.toleratedFailurePercentage !== undefined) {
2702
+ const failurePercentage = (failureCount / items.length) * 100;
2703
+ if (failurePercentage > completion.toleratedFailurePercentage)
2704
+ return false;
2705
+ }
2706
+ return true;
2707
+ };
2708
+ const isComplete = () => {
2709
+ // Always complete when all items are done (matches BatchResult inference)
2710
+ if (completedCount === items.length) {
2711
+ return true;
2712
+ }
2713
+ const completion = config.completionConfig;
2714
+ if (completion?.minSuccessful !== undefined &&
2715
+ successCount >= completion.minSuccessful) {
2716
+ return true;
2717
+ }
2718
+ return false;
2719
+ };
2720
+ const getCompletionReason = (failureCount) => {
2721
+ return this.getCompletionReason(failureCount, successCount, completedCount, items, config);
2722
+ };
2723
+ const tryStartNext = () => {
2724
+ while (activeCount < maxConcurrency &&
2725
+ currentIndex < items.length &&
2726
+ shouldContinue()) {
2727
+ const index = currentIndex++;
2728
+ const item = items[index];
2729
+ startedItems.add(index);
2730
+ activeCount++;
2731
+ // Set STARTED status immediately in result array
2732
+ resultItems[index] = { index, status: exports.BatchItemStatus.STARTED };
2733
+ log("▶️", `Starting ${this.operationName} item:`, {
2734
+ index,
2735
+ itemId: item.id,
2736
+ itemName: item.name,
2737
+ });
2738
+ parentContext
2739
+ .runInChildContext(item.name || item.id, (childContext) => executor(item, childContext), { subType: config.iterationSubType, serdes: config.itemSerdes })
2740
+ .then((result) => {
2741
+ resultItems[index] = {
2742
+ result,
2743
+ index,
2744
+ status: exports.BatchItemStatus.SUCCEEDED,
2745
+ };
2746
+ successCount++;
2747
+ log("✅", `${this.operationName} item completed:`, {
2748
+ index,
2749
+ itemId: item.id,
2750
+ itemName: item.name,
2751
+ });
2752
+ onComplete();
2753
+ }, (error) => {
2754
+ const err = error instanceof ChildContextError
2755
+ ? error
2756
+ : new ChildContextError(error instanceof Error ? error.message : String(error), error instanceof Error ? error : undefined);
2757
+ resultItems[index] = {
2758
+ error: err,
2759
+ index,
2760
+ status: exports.BatchItemStatus.FAILED,
2761
+ };
2762
+ failureCount++;
2763
+ log("❌", `${this.operationName} item failed:`, {
2764
+ index,
2765
+ itemId: item.id,
2766
+ itemName: item.name,
2767
+ error: err.message,
2768
+ });
2769
+ onComplete();
2770
+ });
2771
+ }
2772
+ };
2773
+ const onComplete = () => {
2774
+ activeCount--;
2775
+ completedCount++;
2776
+ if (isComplete() || !shouldContinue()) {
2777
+ // Convert sparse array to dense array - items are already in correct order by index
2778
+ // Include all items that were started (have a value in resultItems)
2779
+ // Create shallow copy to prevent mutations from affecting the returned result
2780
+ const finalBatchItems = [];
2781
+ for (let i = 0; i < resultItems.length; i++) {
2782
+ if (resultItems[i] !== undefined) {
2783
+ finalBatchItems.push({ ...resultItems[i] });
2784
+ }
2785
+ }
2786
+ log("🎉", `${this.operationName} completed:`, {
2787
+ successCount,
2788
+ failureCount,
2789
+ startedCount: finalBatchItems.filter((item) => item.status === exports.BatchItemStatus.STARTED).length,
2790
+ totalCount: finalBatchItems.length,
2791
+ });
2792
+ const result = new BatchResultImpl(finalBatchItems, getCompletionReason(failureCount));
2793
+ resolve(result);
2794
+ }
2795
+ else {
2796
+ tryStartNext();
2797
+ }
2798
+ };
2799
+ tryStartNext();
2800
+ });
2801
+ }
2802
+ }
2803
+ const createConcurrentExecutionHandler = (context, runInChildContext, skipNextOperation) => {
2804
+ return (nameOrItems, itemsOrExecutor, executorOrConfig, maybeConfig) => {
2805
+ // Phase 1: Start execution immediately
2806
+ const phase1Promise = (async () => {
2807
+ let name;
2808
+ let items;
2809
+ let executor;
2810
+ let config;
2811
+ if (typeof nameOrItems === "string" || nameOrItems === undefined) {
2812
+ name = nameOrItems;
2813
+ items = itemsOrExecutor;
2814
+ executor = executorOrConfig;
2815
+ config = maybeConfig;
2816
+ }
2817
+ else {
2818
+ items = nameOrItems;
2819
+ executor = itemsOrExecutor;
2820
+ config = executorOrConfig;
2821
+ }
2822
+ log("🔄", "Starting concurrent execution:", {
2823
+ name,
2824
+ itemCount: items.length,
2825
+ maxConcurrency: config?.maxConcurrency,
2826
+ });
2827
+ if (!Array.isArray(items)) {
2828
+ throw new Error("Concurrent execution requires an array of items");
2829
+ }
2830
+ if (typeof executor !== "function") {
2831
+ throw new Error("Concurrent execution requires an executor function");
2832
+ }
2833
+ if (config?.maxConcurrency !== undefined &&
2834
+ config.maxConcurrency !== null &&
2835
+ config.maxConcurrency <= 0) {
2836
+ throw new Error(`Invalid maxConcurrency: ${config.maxConcurrency}. Must be a positive number or undefined for unlimited concurrency.`);
2837
+ }
2838
+ const executeOperation = async (executionContext) => {
2839
+ const concurrencyController = new ConcurrencyController("concurrent-execution", skipNextOperation);
2840
+ // Access durableExecutionMode from the context - it's set by runInChildContext
2841
+ // based on determineChildReplayMode logic
2842
+ const durableExecutionMode = executionContext.durableExecutionMode;
2843
+ // Get the entity ID (step prefix) from the child context
2844
+ const entityId = executionContext._stepPrefix;
2845
+ log("🔄", "Concurrent execution mode:", {
2846
+ mode: durableExecutionMode,
2847
+ itemCount: items.length,
2848
+ entityId,
2849
+ });
2850
+ return await concurrencyController.executeItems(items, executor, executionContext, config || {}, durableExecutionMode, entityId, context);
2851
+ };
2852
+ const result = await runInChildContext(name, executeOperation, {
2853
+ subType: config?.topLevelSubType,
2854
+ summaryGenerator: config?.summaryGenerator,
2855
+ serdes: config?.serdes,
2856
+ });
2857
+ // Restore BatchResult methods if the result came from deserialized data
2858
+ if (result &&
2859
+ typeof result === "object" &&
2860
+ "all" in result &&
2861
+ Array.isArray(result.all)) {
2862
+ return restoreBatchResult(result);
2863
+ }
2864
+ return result;
2865
+ })();
2866
+ // Attach catch handler to prevent unhandled promise rejections
2867
+ // The error will still be thrown when the DurablePromise is awaited
2868
+ phase1Promise.catch(() => { });
2869
+ // Phase 2: Return DurablePromise that returns Phase 1 result when awaited
2870
+ return new DurablePromise(async () => {
2871
+ return await phase1Promise;
2872
+ });
2873
+ };
2874
+ };
2875
+
2876
+ class ModeManagement {
2877
+ captureExecutionState;
2878
+ checkAndUpdateReplayMode;
2879
+ checkForNonResolvingPromise;
2880
+ getDurableExecutionMode;
2881
+ setDurableExecutionMode;
2882
+ constructor(captureExecutionState, checkAndUpdateReplayMode, checkForNonResolvingPromise, getDurableExecutionMode, setDurableExecutionMode) {
2883
+ this.captureExecutionState = captureExecutionState;
2884
+ this.checkAndUpdateReplayMode = checkAndUpdateReplayMode;
2885
+ this.checkForNonResolvingPromise = checkForNonResolvingPromise;
2886
+ this.getDurableExecutionMode = getDurableExecutionMode;
2887
+ this.setDurableExecutionMode = setDurableExecutionMode;
2888
+ }
2889
+ withModeManagement(operation) {
2890
+ const shouldSwitchToExecutionMode = this.captureExecutionState();
2891
+ this.checkAndUpdateReplayMode();
2892
+ const nonResolvingPromise = this.checkForNonResolvingPromise();
2893
+ if (nonResolvingPromise)
2894
+ return nonResolvingPromise;
2895
+ try {
2896
+ return operation();
2897
+ }
2898
+ finally {
2899
+ if (shouldSwitchToExecutionMode) {
2900
+ this.setDurableExecutionMode(DurableExecutionMode.ExecutionMode);
2901
+ }
2902
+ }
2903
+ }
2904
+ withDurableModeManagement(operation) {
2905
+ const shouldSwitchToExecutionMode = this.captureExecutionState();
2906
+ this.checkAndUpdateReplayMode();
2907
+ const nonResolvingPromise = this.checkForNonResolvingPromise();
2908
+ if (nonResolvingPromise) {
2909
+ return new DurablePromise(async () => {
2910
+ await nonResolvingPromise;
2911
+ // This will never be reached
2912
+ throw new Error("Unreachable code");
2913
+ });
2914
+ }
2915
+ try {
2916
+ return operation();
2917
+ }
2918
+ finally {
2919
+ if (shouldSwitchToExecutionMode) {
2920
+ this.setDurableExecutionMode(DurableExecutionMode.ExecutionMode);
2921
+ }
2922
+ }
2923
+ }
2924
+ }
2925
+
2926
+ const HASH_LENGTH = 16;
2927
+ /**
2928
+ * Creates an MD5 hash of the input string for better performance than SHA-256
2929
+ * @param input - The string to hash
2930
+ * @returns The truncated hexadecimal hash string
2931
+ */
2932
+ const hashId = (input) => {
2933
+ return crypto.createHash("md5")
2934
+ .update(input)
2935
+ .digest("hex")
2936
+ .substring(0, HASH_LENGTH);
2937
+ };
2938
+ /**
2939
+ * Helper function to get step data using the original stepId
2940
+ * This function handles the hashing internally so callers don't need to worry about it
2941
+ * @param stepData - The stepData record from context
2942
+ * @param stepId - The original stepId (will be hashed internally)
2943
+ * @returns The operation data or undefined if not found
2944
+ */
2945
+ const getStepData = (stepData, stepId) => {
2946
+ const hashedId = hashId(stepId);
2947
+ return stepData[hashedId];
2948
+ };
2949
+
2950
+ class DurableContextImpl {
2951
+ executionContext;
2952
+ lambdaContext;
2953
+ _stepPrefix;
2954
+ _stepCounter = 0;
2955
+ durableLogger;
2956
+ modeAwareLoggingEnabled = true;
2957
+ checkpoint;
2958
+ durableExecutionMode;
2959
+ _parentId;
2960
+ modeManagement;
2961
+ durableExecution;
2962
+ logger;
2963
+ constructor(executionContext, lambdaContext, durableExecutionMode, inheritedLogger, stepPrefix, durableExecution, parentId) {
2964
+ this.executionContext = executionContext;
2965
+ this.lambdaContext = lambdaContext;
2966
+ this._stepPrefix = stepPrefix;
2967
+ this._parentId = parentId;
2968
+ this.durableExecution = durableExecution;
2969
+ this.durableLogger = inheritedLogger;
2970
+ this.durableLogger.configureDurableLoggingContext?.(this.getDurableLoggingContext());
2971
+ this.logger = this.createModeAwareLogger(inheritedLogger);
2972
+ this.durableExecutionMode = durableExecutionMode;
2973
+ this.checkpoint = durableExecution.checkpointManager;
2974
+ this.modeManagement = new ModeManagement(this.captureExecutionState.bind(this), this.checkAndUpdateReplayMode.bind(this), this.checkForNonResolvingPromise.bind(this), () => this.durableExecutionMode, (mode) => {
2975
+ this.durableExecutionMode = mode;
2976
+ });
2977
+ }
2978
+ getDurableLoggingContext() {
2979
+ return {
2980
+ getDurableLogData: () => {
2981
+ const activeContext = getActiveContext();
2982
+ const result = {
2983
+ executionArn: this.executionContext.durableExecutionArn,
2984
+ requestId: this.executionContext.requestId,
2985
+ tenantId: this.executionContext.tenantId,
2986
+ operationId: !activeContext || activeContext?.contextId === "root"
2987
+ ? undefined
2988
+ : hashId(activeContext.contextId),
2989
+ };
2990
+ if (activeContext?.attempt !== undefined) {
2991
+ result.attempt = activeContext.attempt;
2992
+ }
2993
+ return result;
2994
+ },
2995
+ };
2996
+ }
2997
+ shouldLog() {
2998
+ const activeContext = getActiveContext();
2999
+ if (!this.modeAwareLoggingEnabled || !activeContext) {
3000
+ return true;
3001
+ }
3002
+ if (activeContext.contextId === "root") {
3003
+ return this.durableExecutionMode === DurableExecutionMode.ExecutionMode;
3004
+ }
3005
+ return (activeContext.durableExecutionMode === DurableExecutionMode.ExecutionMode);
3006
+ }
3007
+ createModeAwareLogger(logger) {
3008
+ const durableContextLogger = {
3009
+ warn: (...args) => {
3010
+ if (this.shouldLog()) {
3011
+ return logger.warn(...args);
3012
+ }
3013
+ },
3014
+ debug: (...args) => {
3015
+ if (this.shouldLog()) {
3016
+ return logger.debug(...args);
3017
+ }
3018
+ },
3019
+ info: (...args) => {
3020
+ if (this.shouldLog()) {
3021
+ return logger.info(...args);
3022
+ }
3023
+ },
3024
+ error: (...args) => {
3025
+ if (this.shouldLog()) {
3026
+ return logger.error(...args);
3027
+ }
3028
+ },
3029
+ };
3030
+ if ("log" in logger) {
3031
+ durableContextLogger.log = (level, ...args) => {
3032
+ if (this.shouldLog()) {
3033
+ return logger.log?.(level, ...args);
3034
+ }
3035
+ };
3036
+ }
3037
+ return durableContextLogger;
3038
+ }
3039
+ createStepId() {
3040
+ this._stepCounter++;
3041
+ return this._stepPrefix
3042
+ ? `${this._stepPrefix}-${this._stepCounter}`
3043
+ : `${this._stepCounter}`;
3044
+ }
3045
+ getNextStepId() {
3046
+ const nextCounter = this._stepCounter + 1;
3047
+ return this._stepPrefix
3048
+ ? `${this._stepPrefix}-${nextCounter}`
3049
+ : `${nextCounter}`;
3050
+ }
3051
+ /**
3052
+ * Skips the next operation by incrementing the step counter.
3053
+ * Used internally by concurrent execution handler during replay to skip incomplete items.
3054
+ * @internal
3055
+ */
3056
+ skipNextOperation() {
3057
+ this._stepCounter++;
3058
+ }
3059
+ checkAndUpdateReplayMode() {
3060
+ if (this.durableExecutionMode === DurableExecutionMode.ReplayMode) {
3061
+ const nextStepId = this.getNextStepId();
3062
+ const nextStepData = this.executionContext.getStepData(nextStepId);
3063
+ if (!nextStepData) {
3064
+ this.durableExecutionMode = DurableExecutionMode.ExecutionMode;
3065
+ }
3066
+ }
3067
+ }
3068
+ captureExecutionState() {
3069
+ const wasInReplayMode = this.durableExecutionMode === DurableExecutionMode.ReplayMode;
3070
+ const nextStepId = this.getNextStepId();
3071
+ const stepData = this.executionContext.getStepData(nextStepId);
3072
+ const wasNotFinished = !!(stepData &&
3073
+ stepData.Status !== clientLambda.OperationStatus.SUCCEEDED &&
3074
+ stepData.Status !== clientLambda.OperationStatus.FAILED);
3075
+ return wasInReplayMode && wasNotFinished;
3076
+ }
3077
+ checkForNonResolvingPromise() {
3078
+ if (this.durableExecutionMode === DurableExecutionMode.ReplaySucceededContext) {
3079
+ const nextStepId = this.getNextStepId();
3080
+ const nextStepData = this.executionContext.getStepData(nextStepId);
3081
+ if (nextStepData &&
3082
+ nextStepData.Status !== clientLambda.OperationStatus.SUCCEEDED &&
3083
+ nextStepData.Status !== clientLambda.OperationStatus.FAILED) {
3084
+ return new Promise(() => { }); // Non-resolving promise
3085
+ }
3086
+ }
3087
+ return null;
3088
+ }
3089
+ withModeManagement(operation) {
3090
+ return this.modeManagement.withModeManagement(operation);
3091
+ }
3092
+ withDurableModeManagement(operation) {
3093
+ return this.modeManagement.withDurableModeManagement(operation);
3094
+ }
3095
+ step(nameOrFn, fnOrOptions, maybeOptions) {
3096
+ validateContextUsage(this._stepPrefix, "step", this.executionContext.terminationManager);
3097
+ return this.withDurableModeManagement(() => {
3098
+ const stepHandler = createStepHandler(this.executionContext, this.checkpoint, this.lambdaContext, this.createStepId.bind(this), this.durableLogger, this._parentId);
3099
+ return stepHandler(nameOrFn, fnOrOptions, maybeOptions);
3100
+ });
3101
+ }
3102
+ invoke(nameOrFuncId, funcIdOrInput, inputOrConfig, maybeConfig) {
3103
+ validateContextUsage(this._stepPrefix, "invoke", this.executionContext.terminationManager);
3104
+ return this.withDurableModeManagement(() => {
3105
+ const invokeHandler = createInvokeHandler(this.executionContext, this.checkpoint, this.createStepId.bind(this), this._parentId, this.checkAndUpdateReplayMode.bind(this));
3106
+ return invokeHandler(...[
3107
+ nameOrFuncId,
3108
+ funcIdOrInput,
3109
+ inputOrConfig,
3110
+ maybeConfig,
3111
+ ]);
3112
+ });
3113
+ }
3114
+ runInChildContext(nameOrFn, fnOrOptions, maybeOptions) {
3115
+ validateContextUsage(this._stepPrefix, "runInChildContext", this.executionContext.terminationManager);
3116
+ return this.withDurableModeManagement(() => {
3117
+ const blockHandler = createRunInChildContextHandler(this.executionContext, this.checkpoint, this.lambdaContext, this.createStepId.bind(this), () => this.durableLogger,
3118
+ // Adapter function to maintain compatibility
3119
+ (executionContext, parentContext, durableExecutionMode, inheritedLogger, stepPrefix, _checkpointToken, parentId) => createDurableContext(executionContext, parentContext, durableExecutionMode, inheritedLogger, stepPrefix, this.durableExecution, parentId), this._parentId);
3120
+ return blockHandler(nameOrFn, fnOrOptions, maybeOptions);
3121
+ });
3122
+ }
3123
+ wait(nameOrDuration, maybeDuration) {
3124
+ validateContextUsage(this._stepPrefix, "wait", this.executionContext.terminationManager);
3125
+ return this.withDurableModeManagement(() => {
3126
+ const waitHandler = createWaitHandler(this.executionContext, this.checkpoint, this.createStepId.bind(this), this._parentId, this.checkAndUpdateReplayMode.bind(this));
3127
+ return typeof nameOrDuration === "string"
3128
+ ? waitHandler(nameOrDuration, maybeDuration)
3129
+ : waitHandler(nameOrDuration);
3130
+ });
3131
+ }
3132
+ /**
3133
+ * Configure logger behavior for this context
3134
+ *
3135
+ * This method allows partial configuration - only the properties provided will be updated.
3136
+ * For example, calling configureLogger(\{ modeAware: false \}) will only change the modeAware
3137
+ * setting without affecting any previously configured custom logger.
3138
+ *
3139
+ * @param config - Logger configuration options including customLogger and modeAware settings (default: modeAware=true)
3140
+ * @example
3141
+ * // Set custom logger and enable mode-aware logging
3142
+ * context.configureLogger(\{ customLogger: myLogger, modeAware: true \});
3143
+ *
3144
+ * // Later, disable mode-aware logging without changing the custom logger
3145
+ * context.configureLogger(\{ modeAware: false \});
3146
+ */
3147
+ configureLogger(config) {
3148
+ if (config.customLogger !== undefined) {
3149
+ this.durableLogger = config.customLogger;
3150
+ this.durableLogger.configureDurableLoggingContext?.(this.getDurableLoggingContext());
3151
+ this.logger = this.createModeAwareLogger(this.durableLogger);
3152
+ }
3153
+ if (config.modeAware !== undefined) {
3154
+ this.modeAwareLoggingEnabled = config.modeAware;
3155
+ }
3156
+ }
3157
+ createCallback(nameOrConfig, maybeConfig) {
3158
+ validateContextUsage(this._stepPrefix, "createCallback", this.executionContext.terminationManager);
3159
+ return this.withDurableModeManagement(() => {
3160
+ const callbackFactory = createCallback(this.executionContext, this.checkpoint, this.createStepId.bind(this), this.checkAndUpdateReplayMode.bind(this), this._parentId);
3161
+ return callbackFactory(nameOrConfig, maybeConfig);
3162
+ });
3163
+ }
3164
+ waitForCallback(nameOrSubmitter, submitterOrConfig, maybeConfig) {
3165
+ validateContextUsage(this._stepPrefix, "waitForCallback", this.executionContext.terminationManager);
3166
+ return this.withDurableModeManagement(() => {
3167
+ const waitForCallbackHandler = createWaitForCallbackHandler(this.executionContext, this.getNextStepId.bind(this), this.runInChildContext.bind(this));
3168
+ return waitForCallbackHandler(nameOrSubmitter, submitterOrConfig, maybeConfig);
3169
+ });
3170
+ }
3171
+ waitForCondition(nameOrCheckFunc, checkFuncOrConfig, maybeConfig) {
3172
+ validateContextUsage(this._stepPrefix, "waitForCondition", this.executionContext.terminationManager);
3173
+ return this.withDurableModeManagement(() => {
3174
+ const waitForConditionHandler = createWaitForConditionHandler(this.executionContext, this.checkpoint, this.createStepId.bind(this), this.durableLogger, this._parentId);
3175
+ return typeof nameOrCheckFunc === "string" ||
3176
+ nameOrCheckFunc === undefined
3177
+ ? waitForConditionHandler(nameOrCheckFunc, checkFuncOrConfig, maybeConfig)
3178
+ : waitForConditionHandler(nameOrCheckFunc, checkFuncOrConfig);
3179
+ });
3180
+ }
3181
+ map(nameOrItems, itemsOrMapFunc, mapFuncOrConfig, maybeConfig) {
3182
+ validateContextUsage(this._stepPrefix, "map", this.executionContext.terminationManager);
3183
+ return this.withDurableModeManagement(() => {
3184
+ const mapHandler = createMapHandler(this.executionContext, this._executeConcurrently.bind(this));
3185
+ return mapHandler(nameOrItems, itemsOrMapFunc, mapFuncOrConfig, maybeConfig);
3186
+ });
3187
+ }
3188
+ parallel(nameOrBranches, branchesOrConfig, maybeConfig) {
3189
+ validateContextUsage(this._stepPrefix, "parallel", this.executionContext.terminationManager);
3190
+ return this.withDurableModeManagement(() => {
3191
+ const parallelHandler = createParallelHandler(this.executionContext, this._executeConcurrently.bind(this));
3192
+ return parallelHandler(nameOrBranches, branchesOrConfig, maybeConfig);
3193
+ });
3194
+ }
3195
+ _executeConcurrently(nameOrItems, itemsOrExecutor, executorOrConfig, maybeConfig) {
3196
+ validateContextUsage(this._stepPrefix, "_executeConcurrently", this.executionContext.terminationManager);
3197
+ return this.withDurableModeManagement(() => {
3198
+ const concurrentExecutionHandler = createConcurrentExecutionHandler(this.executionContext, this.runInChildContext.bind(this), this.skipNextOperation.bind(this));
3199
+ const promise = concurrentExecutionHandler(nameOrItems, itemsOrExecutor, executorOrConfig, maybeConfig);
3200
+ // Prevent unhandled promise rejections
3201
+ promise?.catch(() => { });
3202
+ return promise;
3203
+ });
3204
+ }
3205
+ get promise() {
3206
+ return createPromiseHandler(this.step.bind(this));
3207
+ }
3208
+ }
3209
+ const createDurableContext = (executionContext, parentContext, durableExecutionMode, inheritedLogger, stepPrefix, durableExecution, parentId) => {
3210
+ return new DurableContextImpl(executionContext, parentContext, durableExecutionMode, inheritedLogger, stepPrefix, durableExecution, parentId);
3211
+ };
3212
+
3213
+ /**
3214
+ * Error thrown when a checkpoint operation fails due to invocation-level issues
3215
+ * (e.g., 5xx errors, invalid checkpoint token)
3216
+ * This will terminate the current Lambda invocation, but the execution can continue with a new invocation
3217
+ */
3218
+ class CheckpointUnrecoverableInvocationError extends UnrecoverableInvocationError {
3219
+ terminationReason = TerminationReason.CHECKPOINT_FAILED;
3220
+ constructor(message, originalError) {
3221
+ super(message || "Checkpoint operation failed", originalError);
3222
+ }
3223
+ }
3224
+ /**
3225
+ * Error thrown when a checkpoint operation fails due to execution-level issues
3226
+ * (e.g., 4xx errors other than invalid checkpoint token)
3227
+ * This will terminate the entire execution and cannot be recovered
3228
+ */
3229
+ class CheckpointUnrecoverableExecutionError extends UnrecoverableExecutionError {
3230
+ terminationReason = TerminationReason.CHECKPOINT_FAILED;
3231
+ constructor(message, originalError) {
3232
+ super(message || "Checkpoint operation failed", originalError);
3233
+ }
3234
+ }
3235
+
3236
+ const STEP_DATA_UPDATED_EVENT = "stepDataUpdated";
3237
+ class CheckpointManager {
3238
+ durableExecutionArn;
3239
+ stepData;
3240
+ storage;
3241
+ terminationManager;
3242
+ stepDataEmitter;
3243
+ logger;
3244
+ finishedAncestors;
3245
+ queue = [];
3246
+ isProcessing = false;
3247
+ currentTaskToken;
3248
+ forceCheckpointPromises = [];
3249
+ queueCompletionResolver = null;
3250
+ MAX_PAYLOAD_SIZE = 750 * 1024; // 750KB in bytes
3251
+ isTerminating = false;
3252
+ static textEncoder = new TextEncoder();
3253
+ // Operation lifecycle tracking
3254
+ operations = new Map();
3255
+ // Termination cooldown
3256
+ terminationTimer = null;
3257
+ terminationReason = null;
3258
+ TERMINATION_COOLDOWN_MS = 50;
3259
+ constructor(durableExecutionArn, stepData, storage, terminationManager, initialTaskToken, stepDataEmitter, logger, finishedAncestors) {
3260
+ this.durableExecutionArn = durableExecutionArn;
3261
+ this.stepData = stepData;
3262
+ this.storage = storage;
3263
+ this.terminationManager = terminationManager;
3264
+ this.stepDataEmitter = stepDataEmitter;
3265
+ this.logger = logger;
3266
+ this.finishedAncestors = finishedAncestors;
3267
+ this.currentTaskToken = initialTaskToken;
3268
+ }
3269
+ setTerminating() {
3270
+ this.isTerminating = true;
3271
+ log("🛑", "Checkpoint manager marked as terminating");
3272
+ }
3273
+ /**
3274
+ * Mark an ancestor as finished (for run-in-child-context operations)
3275
+ */
3276
+ markAncestorFinished(stepId) {
3277
+ this.finishedAncestors.add(stepId);
3278
+ }
3279
+ /**
3280
+ * Extract parent ID from hierarchical stepId (e.g., "1-2-3" -\> "1-2")
3281
+ */
3282
+ getParentId(stepId) {
3283
+ const lastDashIndex = stepId.lastIndexOf("-");
3284
+ return lastDashIndex > 0 ? stepId.substring(0, lastDashIndex) : undefined;
3285
+ }
3286
+ /**
3287
+ * Checks if any ancestor of the given stepId is finished
3288
+ * Only applies to operations that are descendants of run-in-child-context operations
3289
+ */
3290
+ hasFinishedAncestor(stepId) {
3291
+ // Only use getParentId to avoid mixing hashed and original stepIds
3292
+ let currentParentId = this.getParentId(stepId);
3293
+ while (currentParentId) {
3294
+ // Check if this ancestor is finished
3295
+ if (this.finishedAncestors.has(currentParentId)) {
3296
+ return true;
3297
+ }
3298
+ // Move up to the next ancestor using hierarchical stepId
3299
+ currentParentId = this.getParentId(currentParentId);
3300
+ }
3301
+ return false;
3302
+ }
3303
+ async forceCheckpoint() {
3304
+ if (this.isTerminating) {
3305
+ log("⚠️", "Force checkpoint skipped - termination in progress");
3306
+ return new Promise(() => { }); // Never resolves during termination
3307
+ }
3308
+ return new Promise((resolve, reject) => {
3309
+ this.forceCheckpointPromises.push({ resolve, reject });
3310
+ if (!this.isProcessing) {
3311
+ setImmediate(() => {
3312
+ this.processQueue();
3313
+ });
3314
+ }
3315
+ });
3316
+ }
3317
+ async waitForQueueCompletion() {
3318
+ if (this.queue.length === 0 && !this.isProcessing) {
3319
+ return;
3320
+ }
3321
+ return new Promise((resolve) => {
3322
+ this.queueCompletionResolver = resolve;
3323
+ });
3324
+ }
3325
+ clearQueue() {
3326
+ // Silently clear queue - we're terminating so no need to reject promises
3327
+ this.queue = [];
3328
+ this.forceCheckpointPromises = [];
3329
+ // Resolve any waiting queue completion promises since we're clearing
3330
+ this.notifyQueueCompletion();
3331
+ }
3332
+ // Alias for backward compatibility with Checkpoint interface
3333
+ async force() {
3334
+ return this.forceCheckpoint();
3335
+ }
3336
+ async checkpoint(stepId, data) {
3337
+ if (this.isTerminating) {
3338
+ log("⚠️", "Checkpoint skipped - termination in progress:", { stepId });
3339
+ return new Promise(() => { }); // Never resolves during termination
3340
+ }
3341
+ // Check if any ancestor is finished - if so, don't queue and don't resolve
3342
+ if (this.hasFinishedAncestor(stepId)) {
3343
+ log("⚠️", "Checkpoint skipped - ancestor already finished:", { stepId });
3344
+ return new Promise(() => { }); // Never resolves when ancestor is finished
3345
+ }
3346
+ return new Promise((resolve, reject) => {
3347
+ const queuedItem = {
3348
+ stepId,
3349
+ data,
3350
+ resolve: () => {
3351
+ resolve();
3352
+ },
3353
+ reject: (error) => {
3354
+ reject(error);
3355
+ },
3356
+ };
3357
+ this.queue.push(queuedItem);
3358
+ log("📥", "Checkpoint queued:", {
3359
+ stepId,
3360
+ queueLength: this.queue.length,
3361
+ isProcessing: this.isProcessing,
3362
+ });
3363
+ if (!this.isProcessing) {
3364
+ setImmediate(() => {
3365
+ this.processQueue();
3366
+ });
3367
+ }
3368
+ });
3369
+ }
3370
+ classifyCheckpointError(error) {
3371
+ const originalError = error instanceof Error ? error : new Error(String(error));
3372
+ const awsError = error;
3373
+ const statusCode = awsError.$metadata?.httpStatusCode;
3374
+ const errorName = awsError.name;
3375
+ const errorMessage = awsError.message || originalError.message;
3376
+ log("🔍", "Classifying checkpoint error:", {
3377
+ statusCode,
3378
+ errorName,
3379
+ errorMessage,
3380
+ });
3381
+ if (statusCode &&
3382
+ statusCode >= 400 &&
3383
+ statusCode < 500 &&
3384
+ errorName === "InvalidParameterValueException" &&
3385
+ errorMessage.startsWith("Invalid Checkpoint Token")) {
3386
+ return new CheckpointUnrecoverableInvocationError(`Checkpoint failed: ${errorMessage}`, originalError);
3387
+ }
3388
+ if (statusCode &&
3389
+ statusCode >= 400 &&
3390
+ statusCode < 500 &&
3391
+ statusCode !== 429) {
3392
+ return new CheckpointUnrecoverableExecutionError(`Checkpoint failed: ${errorMessage}`, originalError);
3393
+ }
3394
+ return new CheckpointUnrecoverableInvocationError(`Checkpoint failed: ${errorMessage}`, originalError);
3395
+ }
3396
+ async processQueue() {
3397
+ if (this.isProcessing) {
3398
+ return;
3399
+ }
3400
+ const hasQueuedItems = this.queue.length > 0;
3401
+ const hasForceRequests = this.forceCheckpointPromises.length > 0;
3402
+ if (!hasQueuedItems && !hasForceRequests) {
3403
+ return;
3404
+ }
3405
+ this.isProcessing = true;
3406
+ const batch = [];
3407
+ const baseSize = this.currentTaskToken.length + 100;
3408
+ let currentSize = baseSize;
3409
+ while (this.queue.length > 0) {
3410
+ const nextItem = this.queue[0];
3411
+ const itemSize = CheckpointManager.textEncoder.encode(JSON.stringify(nextItem)).length;
3412
+ if (currentSize + itemSize > this.MAX_PAYLOAD_SIZE && batch.length > 0) {
3413
+ break;
3414
+ }
3415
+ this.queue.shift();
3416
+ batch.push(nextItem);
3417
+ currentSize += itemSize;
3418
+ }
3419
+ log("🔄", "Processing checkpoint batch:", {
3420
+ batchSize: batch.length,
3421
+ remainingInQueue: this.queue.length,
3422
+ estimatedSize: currentSize,
3423
+ maxSize: this.MAX_PAYLOAD_SIZE,
3424
+ });
3425
+ try {
3426
+ if (batch.length > 0 || this.forceCheckpointPromises.length > 0) {
3427
+ await this.processBatch(batch);
3428
+ }
3429
+ batch.forEach((item) => {
3430
+ item.resolve();
3431
+ });
3432
+ const forcePromises = this.forceCheckpointPromises.splice(0);
3433
+ forcePromises.forEach((promise) => {
3434
+ promise.resolve();
3435
+ });
3436
+ log("✅", "Checkpoint batch processed successfully:", {
3437
+ batchSize: batch.length,
3438
+ forceRequests: forcePromises.length,
3439
+ newTaskToken: this.currentTaskToken,
3440
+ });
3441
+ }
3442
+ catch (error) {
3443
+ log("❌", "Checkpoint batch failed:", {
3444
+ batchSize: batch.length,
3445
+ error,
3446
+ });
3447
+ const checkpointError = this.classifyCheckpointError(error);
3448
+ // Clear remaining queue silently - we're terminating
3449
+ this.clearQueue();
3450
+ this.terminationManager.terminate({
3451
+ reason: TerminationReason.CHECKPOINT_FAILED,
3452
+ message: checkpointError.message,
3453
+ error: checkpointError,
3454
+ });
3455
+ }
3456
+ finally {
3457
+ this.isProcessing = false;
3458
+ if (this.queue.length > 0) {
3459
+ setImmediate(() => {
3460
+ this.processQueue();
3461
+ });
3462
+ }
3463
+ else {
3464
+ // Queue is empty and processing is done - notify all waiting promises
3465
+ this.notifyQueueCompletion();
3466
+ }
3467
+ }
3468
+ }
3469
+ notifyQueueCompletion() {
3470
+ if (this.queueCompletionResolver) {
3471
+ this.queueCompletionResolver();
3472
+ this.queueCompletionResolver = null;
3473
+ }
3474
+ }
3475
+ async processBatch(batch) {
3476
+ const updates = batch.map((item) => {
3477
+ const hashedStepId = hashId(item.stepId);
3478
+ const update = {
3479
+ Type: item.data.Type || "STEP",
3480
+ Action: item.data.Action || "START",
3481
+ ...item.data,
3482
+ Id: hashedStepId,
3483
+ ...(item.data.ParentId && { ParentId: hashId(item.data.ParentId) }),
3484
+ };
3485
+ return update;
3486
+ });
3487
+ const checkpointData = {
3488
+ DurableExecutionArn: this.durableExecutionArn,
3489
+ CheckpointToken: this.currentTaskToken,
3490
+ Updates: updates,
3491
+ };
3492
+ log("⏺️", "Creating checkpoint batch:", {
3493
+ batchSize: updates.length,
3494
+ checkpointToken: this.currentTaskToken,
3495
+ updates: updates.map((u) => ({
3496
+ Id: u.Id,
3497
+ Action: u.Action,
3498
+ Type: u.Type,
3499
+ })),
3500
+ });
3501
+ const response = await this.storage.checkpoint(checkpointData, this.logger);
3502
+ if (response.CheckpointToken) {
3503
+ this.currentTaskToken = response.CheckpointToken;
3504
+ }
3505
+ if (response.NewExecutionState?.Operations) {
3506
+ this.updateStepDataFromCheckpointResponse(response.NewExecutionState.Operations);
3507
+ }
3508
+ }
3509
+ updateStepDataFromCheckpointResponse(operations) {
3510
+ log("🔄", "Updating stepData from checkpoint response:", {
3511
+ operationCount: operations.length,
3512
+ operationIds: operations.map((op) => op.Id).filter(Boolean),
3513
+ });
3514
+ operations.forEach((operation) => {
3515
+ if (operation.Id) {
3516
+ // Check if status changed
3517
+ const oldStatus = this.stepData[operation.Id]?.Status;
3518
+ const newStatus = operation.Status;
3519
+ this.stepData[operation.Id] = operation;
3520
+ log("📝", "Updated stepData entry:", operation);
3521
+ this.stepDataEmitter.emit(STEP_DATA_UPDATED_EVENT, operation.Id);
3522
+ // If status changed and we have a waiting promise, resolve it
3523
+ if (oldStatus !== newStatus) {
3524
+ this.resolveWaitingOperation(operation.Id);
3525
+ }
3526
+ }
3527
+ });
3528
+ log("✅", "StepData update completed:", {
3529
+ totalStepDataEntries: Object.keys(this.stepData).length,
3530
+ });
3531
+ }
3532
+ resolveWaitingOperation(hashedStepId) {
3533
+ // Find operation by hashed ID in our operations map
3534
+ for (const [stepId, op] of this.operations.entries()) {
3535
+ if (hashId(stepId) === hashedStepId && op.resolver) {
3536
+ log("✅", `Resolving waiting operation ${stepId} due to status change`);
3537
+ op.resolver();
3538
+ op.resolver = undefined;
3539
+ if (op.timer) {
3540
+ clearTimeout(op.timer);
3541
+ op.timer = undefined;
3542
+ }
3543
+ break;
3544
+ }
3545
+ }
3546
+ }
3547
+ getQueueStatus() {
3548
+ return {
3549
+ queueLength: this.queue.length,
3550
+ isProcessing: this.isProcessing,
3551
+ };
3552
+ }
3553
+ // ===== New Lifecycle & Termination Methods =====
3554
+ markOperationState(stepId, state, options) {
3555
+ let op = this.operations.get(stepId);
3556
+ if (!op) {
3557
+ // First call - create operation
3558
+ if (!options?.metadata) {
3559
+ throw new Error(`metadata required on first call for ${stepId}`);
3560
+ }
3561
+ op = {
3562
+ stepId,
3563
+ state,
3564
+ metadata: options.metadata,
3565
+ endTimestamp: options.endTimestamp,
3566
+ };
3567
+ this.operations.set(stepId, op);
3568
+ }
3569
+ else {
3570
+ // Update existing operation
3571
+ op.state = state;
3572
+ if (options?.endTimestamp !== undefined) {
3573
+ op.endTimestamp = options.endTimestamp;
3574
+ }
3575
+ }
3576
+ // Cleanup if transitioning to COMPLETED
3577
+ if (state === OperationLifecycleState.COMPLETED) {
3578
+ this.cleanupOperation(stepId);
3579
+ }
3580
+ // Check if we should terminate
3581
+ // Don't check for IDLE_NOT_AWAITED - operation might be awaited later or intentionally not awaited
3582
+ if (state !== OperationLifecycleState.IDLE_NOT_AWAITED) {
3583
+ this.checkAndTerminate();
3584
+ }
3585
+ }
3586
+ waitForRetryTimer(stepId) {
3587
+ const op = this.operations.get(stepId);
3588
+ if (!op) {
3589
+ throw new Error(`Operation ${stepId} not found`);
3590
+ }
3591
+ if (op.state !== OperationLifecycleState.RETRY_WAITING) {
3592
+ throw new Error(`Operation ${stepId} must be in RETRY_WAITING state, got ${op.state}`);
3593
+ }
3594
+ // Start timer with polling
3595
+ this.startTimerWithPolling(stepId, op.endTimestamp);
3596
+ // Return promise that resolves when status changes
3597
+ return new Promise((resolve) => {
3598
+ op.resolver = resolve;
3599
+ });
3600
+ }
3601
+ waitForStatusChange(stepId) {
3602
+ const op = this.operations.get(stepId);
3603
+ if (!op) {
3604
+ throw new Error(`Operation ${stepId} not found`);
3605
+ }
3606
+ if (op.state !== OperationLifecycleState.IDLE_AWAITED) {
3607
+ throw new Error(`Operation ${stepId} must be in IDLE_AWAITED state, got ${op.state}`);
3608
+ }
3609
+ // Start timer with polling
3610
+ this.startTimerWithPolling(stepId, op.endTimestamp);
3611
+ // Return promise that resolves when status changes
3612
+ return new Promise((resolve) => {
3613
+ op.resolver = resolve;
3614
+ });
3615
+ }
3616
+ markOperationAwaited(stepId) {
3617
+ const op = this.operations.get(stepId);
3618
+ if (!op) {
3619
+ log("⚠️", `Cannot mark operation as awaited: ${stepId} not found`);
3620
+ return;
3621
+ }
3622
+ // Transition IDLE_NOT_AWAITED → IDLE_AWAITED
3623
+ if (op.state === OperationLifecycleState.IDLE_NOT_AWAITED) {
3624
+ op.state = OperationLifecycleState.IDLE_AWAITED;
3625
+ log("📍", `Operation marked as awaited: ${stepId}`);
3626
+ // Check if we should terminate now that operation is awaited
3627
+ this.checkAndTerminate();
3628
+ }
3629
+ }
3630
+ getOperationState(stepId) {
3631
+ return this.operations.get(stepId)?.state;
3632
+ }
3633
+ getAllOperations() {
3634
+ return new Map(this.operations);
3635
+ }
3636
+ // ===== Private Helper Methods =====
3637
+ cleanupOperation(stepId) {
3638
+ const op = this.operations.get(stepId);
3639
+ if (!op)
3640
+ return;
3641
+ // Clear timer
3642
+ if (op.timer) {
3643
+ clearTimeout(op.timer);
3644
+ op.timer = undefined;
3645
+ }
3646
+ // Clear resolver
3647
+ op.resolver = undefined;
3648
+ }
3649
+ cleanupAllOperations() {
3650
+ for (const op of this.operations.values()) {
3651
+ if (op.timer) {
3652
+ clearTimeout(op.timer);
3653
+ op.timer = undefined;
3654
+ }
3655
+ op.resolver = undefined;
3656
+ }
3657
+ }
3658
+ checkAndTerminate() {
3659
+ // Rule 1: Can't terminate if checkpoint queue is not empty
3660
+ if (this.queue.length > 0) {
3661
+ this.abortTermination();
3662
+ return;
3663
+ }
3664
+ // Rule 2: Can't terminate if checkpoint is currently processing
3665
+ if (this.isProcessing) {
3666
+ this.abortTermination();
3667
+ return;
3668
+ }
3669
+ // Rule 3: Can't terminate if there are pending force checkpoint promises
3670
+ if (this.forceCheckpointPromises.length > 0) {
3671
+ this.abortTermination();
3672
+ return;
3673
+ }
3674
+ const allOps = Array.from(this.operations.values());
3675
+ // Rule 4: Can't terminate if any operation is EXECUTING
3676
+ const hasExecuting = allOps.some((op) => op.state === OperationLifecycleState.EXECUTING);
3677
+ if (hasExecuting) {
3678
+ this.abortTermination();
3679
+ return;
3680
+ }
3681
+ // Rule 5: Clean up operations whose ancestors are complete or pending completion
3682
+ for (const op of allOps) {
3683
+ if (op.state === OperationLifecycleState.RETRY_WAITING ||
3684
+ op.state === OperationLifecycleState.IDLE_NOT_AWAITED ||
3685
+ op.state === OperationLifecycleState.IDLE_AWAITED) {
3686
+ // Use the original stepId from metadata, not the potentially hashed op.stepId
3687
+ const originalStepId = op.metadata.stepId;
3688
+ if (this.hasFinishedAncestor(originalStepId)) {
3689
+ log("🧹", `Cleaning up operation with completed ancestor: ${originalStepId}`);
3690
+ this.cleanupOperation(op.stepId);
3691
+ this.operations.delete(op.stepId);
3692
+ }
3693
+ }
3694
+ }
3695
+ // Re-check operations after cleanup
3696
+ const remainingOps = Array.from(this.operations.values());
3697
+ // Determine if we should terminate
3698
+ const hasWaiting = remainingOps.some((op) => op.state === OperationLifecycleState.RETRY_WAITING ||
3699
+ op.state === OperationLifecycleState.IDLE_NOT_AWAITED ||
3700
+ op.state === OperationLifecycleState.IDLE_AWAITED);
3701
+ if (hasWaiting) {
3702
+ const reason = this.determineTerminationReason(remainingOps);
3703
+ this.scheduleTermination(reason);
3704
+ }
3705
+ else {
3706
+ this.abortTermination();
3707
+ }
3708
+ }
3709
+ abortTermination() {
3710
+ if (this.terminationTimer) {
3711
+ clearTimeout(this.terminationTimer);
3712
+ this.terminationTimer = null;
3713
+ this.terminationReason = null;
3714
+ log("🔄", "Termination aborted - conditions changed");
3715
+ }
3716
+ }
3717
+ scheduleTermination(reason) {
3718
+ // If already scheduled with same reason, don't reschedule
3719
+ if (this.terminationTimer && this.terminationReason === reason) {
3720
+ return;
3721
+ }
3722
+ // Clear any existing timer
3723
+ this.abortTermination();
3724
+ // Schedule new termination
3725
+ this.terminationReason = reason;
3726
+ log("⏱️", "Scheduling termination", {
3727
+ reason,
3728
+ cooldownMs: this.TERMINATION_COOLDOWN_MS,
3729
+ });
3730
+ this.terminationTimer = setTimeout(() => {
3731
+ this.executeTermination(reason);
3732
+ }, this.TERMINATION_COOLDOWN_MS);
3733
+ }
3734
+ executeTermination(reason) {
3735
+ log("🛑", "Executing termination after cooldown", { reason });
3736
+ // Clear timer
3737
+ this.terminationTimer = null;
3738
+ this.terminationReason = null;
3739
+ // Cleanup all operations before terminating
3740
+ this.cleanupAllOperations();
3741
+ // Call termination manager directly
3742
+ this.terminationManager.terminate({ reason });
3743
+ }
3744
+ determineTerminationReason(ops) {
3745
+ // Priority: RETRY_SCHEDULED > WAIT_SCHEDULED > CALLBACK_PENDING
3746
+ if (ops.some((op) => op.state === OperationLifecycleState.RETRY_WAITING &&
3747
+ op.metadata.subType === "Step")) {
3748
+ return TerminationReason.RETRY_SCHEDULED;
3749
+ }
3750
+ if (ops.some((op) => (op.state === OperationLifecycleState.IDLE_NOT_AWAITED ||
3751
+ op.state === OperationLifecycleState.IDLE_AWAITED) &&
3752
+ op.metadata.subType === "Wait")) {
3753
+ return TerminationReason.WAIT_SCHEDULED;
3754
+ }
3755
+ return TerminationReason.CALLBACK_PENDING;
3756
+ }
3757
+ startTimerWithPolling(stepId, endTimestamp) {
3758
+ const op = this.operations.get(stepId);
3759
+ if (!op)
3760
+ return;
3761
+ let delay;
3762
+ if (endTimestamp) {
3763
+ // Ensure endTimestamp is a Date object
3764
+ const timestamp = endTimestamp instanceof Date ? endTimestamp : new Date(endTimestamp);
3765
+ // Wait until endTimestamp
3766
+ delay = Math.max(0, timestamp.getTime() - Date.now());
3767
+ }
3768
+ else {
3769
+ // No timestamp, start polling immediately (1 second delay)
3770
+ delay = 1000;
3771
+ }
3772
+ // Initialize poll count and start time for this operation
3773
+ if (!op.pollCount) {
3774
+ op.pollCount = 0;
3775
+ op.pollStartTime = Date.now();
3776
+ }
3777
+ op.timer = setTimeout(() => {
3778
+ this.forceRefreshAndCheckStatus(stepId);
3779
+ }, delay);
3780
+ }
3781
+ async forceRefreshAndCheckStatus(stepId) {
3782
+ const op = this.operations.get(stepId);
3783
+ if (!op)
3784
+ return;
3785
+ // Check if we've exceeded max polling duration (15 minutes)
3786
+ const MAX_POLL_DURATION_MS = 15 * 60 * 1000; // 15 minutes
3787
+ if (op.pollStartTime &&
3788
+ Date.now() - op.pollStartTime > MAX_POLL_DURATION_MS) {
3789
+ // Stop polling after 15 minutes to prevent indefinite resource consumption.
3790
+ // We don't resolve or reject the promise because the handler cannot continue
3791
+ // without a status change. The execution will remain suspended until the
3792
+ // operation completes or the Lambda times out.
3793
+ log("⏱️", `Max polling duration (15 min) exceeded for ${stepId}, stopping poll`);
3794
+ if (op.timer) {
3795
+ clearTimeout(op.timer);
3796
+ op.timer = undefined;
3797
+ }
3798
+ return;
3799
+ }
3800
+ // Get old status before refresh
3801
+ const oldStatus = this.stepData[hashId(stepId)]?.Status;
3802
+ // Force checkpoint to refresh state from backend
3803
+ await this.forceCheckpoint();
3804
+ // Get new status after refresh
3805
+ const newStatus = this.stepData[hashId(stepId)]?.Status;
3806
+ // Check if status changed
3807
+ if (newStatus !== oldStatus) {
3808
+ // Status changed, resolve the waiting promise
3809
+ log("✅", `Status changed for ${stepId}: ${oldStatus} → ${newStatus}`);
3810
+ op.resolver?.();
3811
+ op.resolver = undefined;
3812
+ // Clear timer
3813
+ if (op.timer) {
3814
+ clearTimeout(op.timer);
3815
+ op.timer = undefined;
3816
+ }
3817
+ }
3818
+ else {
3819
+ // Status not changed yet, poll again with incremental backoff
3820
+ // Start at 1s, increase by 1s each poll, max 10s
3821
+ op.pollCount = (op.pollCount || 0) + 1;
3822
+ const nextDelay = Math.min(op.pollCount * 1000, 10000);
3823
+ op.timer = setTimeout(() => {
3824
+ this.forceRefreshAndCheckStatus(stepId);
3825
+ }, nextDelay);
3826
+ }
3827
+ }
3828
+ }
3829
+
3830
+ /*
3831
+ Second Approach (Promise-based):
3832
+ Pros:
3833
+ - Simpler implementation
3834
+ - Single promise instance for the entire lifecycle
3835
+ - More memory efficient as it doesn't create new promises on each getTerminationPromise() call
3836
+ - More predictable behavior since there's only one resolution path
3837
+ Cons:
3838
+ - Less flexible as it doesn't support multiple listeners
3839
+ - Once the promise is resolved, it stays resolved (can't be reset)
3840
+ - Doesn't leverage Node.js's event system
3841
+ */
3842
+ class TerminationManager extends events.EventEmitter {
3843
+ isTerminated = false;
3844
+ terminationDetails;
3845
+ resolveTermination;
3846
+ terminationPromise;
3847
+ setCheckpointTerminating;
3848
+ constructor(setCheckpointTerminating) {
3849
+ super();
3850
+ this.setCheckpointTerminating = setCheckpointTerminating;
3851
+ // Create the promise immediately during construction
3852
+ this.terminationPromise = new Promise((resolve) => {
3853
+ this.resolveTermination = resolve;
3854
+ });
3855
+ }
3856
+ setCheckpointTerminatingCallback(callback) {
3857
+ this.setCheckpointTerminating = callback;
3858
+ }
3859
+ terminate(options = {}) {
3860
+ if (this.isTerminated)
3861
+ return;
3862
+ // Set checkpoint termination flag before any other termination logic
3863
+ this.setCheckpointTerminating?.();
3864
+ this.isTerminated = true;
3865
+ this.terminationDetails = {
3866
+ reason: options.reason ?? TerminationReason.OPERATION_TERMINATED,
3867
+ message: options.message ?? "Operation terminated",
3868
+ error: options.error,
3869
+ cleanup: options.cleanup,
3870
+ };
3871
+ // Instead of emitting an event, resolve the promise
3872
+ if (this.resolveTermination) {
3873
+ this.handleTermination(this.terminationDetails).then(this.resolveTermination);
3874
+ }
3875
+ }
3876
+ getTerminationPromise() {
3877
+ return this.terminationPromise;
3878
+ }
3879
+ async handleTermination(details) {
3880
+ if (details.cleanup) {
3881
+ try {
3882
+ await details.cleanup();
3883
+ }
3884
+ catch { }
3885
+ }
3886
+ return {
3887
+ reason: details.reason,
3888
+ message: details.message,
3889
+ error: details.error,
3890
+ };
3891
+ }
3892
+ }
3893
+
3894
+ // The logic from this file is based on the NodeJS RIC LogPatch functionality for parity with standard Lambda functions. We should always
3895
+ // align the default behaviour of how logs are emitted to match the RIC logging behaviour for consistency.
3896
+ // For custom logic, users can implement their own logger to log data differently.
3897
+ // See: https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/962ed28eefbc052389c4de4366b1c0c49ee08a13/src/LogPatch.js
3898
+ /**
3899
+ * JSON.stringify replacer function for Error objects.
3900
+ * Based on AWS Lambda Runtime Interface Client LogPatch functionality.
3901
+ * Transforms Error instances into serializable objects with structured error information,
3902
+ * emulating the default Node.js console behavior in Lambda environments.
3903
+ *
3904
+ * @param _key - The property key (unused in this replacer)
3905
+ * @param value - The value being stringified
3906
+ * @returns The original value, or a structured error object for Error instances
3907
+ */
3908
+ function jsonErrorReplacer(_key, value) {
3909
+ if (value instanceof Error) {
3910
+ return Object.assign({
3911
+ errorType: value?.constructor?.name ?? "UnknownError",
3912
+ errorMessage: value.message,
3913
+ stackTrace: typeof value.stack === "string"
3914
+ ? value.stack.split("\n")
3915
+ : value.stack,
3916
+ }, value);
3917
+ }
3918
+ return value;
3919
+ }
3920
+ /**
3921
+ * Formats durable log data into structured JSON string output.
3922
+ * Emulates AWS Lambda Runtime Interface Client's formatJsonMessage functionality
3923
+ * to provide consistent logging format with standard Lambda functions.
3924
+ *
3925
+ * The function handles two main scenarios:
3926
+ * 1. Single parameter: Attempts to stringify directly, falls back to util.format on error
3927
+ * 2. Multiple parameters: Uses util.format to create message, extracts error details if present
3928
+ *
3929
+ * This approach mirrors the RIC's behavior of:
3930
+ * - Using util.format for message formatting (same as console.log)
3931
+ * - Handling circular references gracefully with fallback formatting
3932
+ * - Extracting structured error information when Error objects are present
3933
+ * - Including optional tenantId when available
3934
+ *
3935
+ * @param level - The log level for this message
3936
+ * @param logData - Durable execution context data (requestId, executionArn, etc.)
3937
+ * @param messageParams - Variable number of message parameters to log
3938
+ * @returns JSON string representation of the structured log entry
3939
+ */
3940
+ function formatDurableLogData(level, logData, ...messageParams) {
3941
+ const result = {
3942
+ requestId: logData.requestId,
3943
+ timestamp: new Date().toISOString(),
3944
+ level: level.toUpperCase(),
3945
+ executionArn: logData.executionArn,
3946
+ };
3947
+ const tenantId = logData.tenantId;
3948
+ if (tenantId != undefined && tenantId != null) {
3949
+ result.tenantId = tenantId;
3950
+ }
3951
+ if (logData.operationId !== undefined) {
3952
+ result.operationId = logData.operationId;
3953
+ }
3954
+ if (logData.attempt !== undefined) {
3955
+ result.attempt = logData.attempt;
3956
+ }
3957
+ if (messageParams.length === 1) {
3958
+ result.message = messageParams[0];
3959
+ try {
3960
+ return JSON.stringify(result, jsonErrorReplacer);
3961
+ }
3962
+ catch (_) {
3963
+ result.message = util.format(result.message);
3964
+ return JSON.stringify(result);
3965
+ }
3966
+ }
3967
+ result.message = util.format(...messageParams);
3968
+ for (const param of messageParams) {
3969
+ if (param instanceof Error) {
3970
+ result.errorType = param?.constructor?.name ?? "UnknownError";
3971
+ result.errorMessage = param.message;
3972
+ result.stackTrace =
3973
+ typeof param.stack === "string" ? param.stack.split("\n") : [];
3974
+ break;
3975
+ }
3976
+ }
3977
+ return JSON.stringify(result);
3978
+ }
3979
+ /**
3980
+ * Default logger class that outputs structured logs to console.
3981
+ *
3982
+ * This logger emulates the AWS Lambda Runtime Interface Client (RIC) console patching
3983
+ * behavior to maintain parity with standard Lambda function logging while providing
3984
+ * structured output suitable for durable execution contexts.
3985
+ *
3986
+ * Key RIC behavior emulation:
3987
+ * - Respects AWS_LAMBDA_LOG_LEVEL environment variable for log filtering
3988
+ * - Uses priority-based level filtering (DEBUG=2, INFO=3, WARN=4, ERROR=5)
3989
+ * - Outputs structured JSON with timestamp, requestId, executionArn, and other metadata
3990
+ * - Handles Error objects with structured error information extraction
3991
+ * - Uses Node.js Console instance for proper stdout/stderr routing
3992
+ * - Applies util.format for message formatting (same as console.log behavior)
3993
+ *
3994
+ * Individual logger methods (info, error, warn, debug) are dynamically enabled/disabled
3995
+ * based on the configured log level, defaulting to no-op functions when disabled.
3996
+ * This mirrors how RIC patches console methods conditionally.
3997
+ */
3998
+ class DefaultLogger {
3999
+ consoleLogger;
4000
+ durableLoggingContext = undefined;
4001
+ executionContext;
4002
+ noOpLog = () => { };
4003
+ constructor(executionContext) {
4004
+ this.executionContext = executionContext;
4005
+ // Override the RIC logger to provide custom attributes on the structured log output
4006
+ this.consoleLogger = new node_console.Console({
4007
+ stdout: process.stdout,
4008
+ stderr: process.stderr,
4009
+ });
4010
+ // Initialize methods with no-op and then configure based on log level
4011
+ this.info = this.noOpLog;
4012
+ this.error = this.noOpLog;
4013
+ this.warn = this.noOpLog;
4014
+ this.debug = this.noOpLog;
4015
+ this.configureLogLevel();
4016
+ }
4017
+ configureLogLevel() {
4018
+ const levels = {
4019
+ DEBUG: { name: "DEBUG", priority: 2 },
4020
+ INFO: { name: "INFO", priority: 3 },
4021
+ WARN: { name: "WARN", priority: 4 },
4022
+ ERROR: { name: "ERROR", priority: 5 },
4023
+ // Not implemented yet. Can be implemented later
4024
+ // TRACE: { name: "TRACE", priority: 1 },
4025
+ // FATAL: { name: "FATAL", priority: 6 },
4026
+ };
4027
+ const logLevelEnvVariable = process.env["AWS_LAMBDA_LOG_LEVEL"]?.toUpperCase();
4028
+ // Default to DEBUG level when env var is invalid/missing
4029
+ const lambdaLogLevel = logLevelEnvVariable && logLevelEnvVariable in levels
4030
+ ? levels[logLevelEnvVariable]
4031
+ : levels.DEBUG;
4032
+ // Enable methods based on priority: higher priority = more restrictive
4033
+ // e.g., if WARN is set (priority 4), only WARN and ERROR methods are enabled
4034
+ if (levels.DEBUG.priority >= lambdaLogLevel.priority) {
4035
+ this.debug = (message, ...optionalParams) => {
4036
+ const loggingContext = this.ensureDurableLoggingContext();
4037
+ const params = message !== undefined ? [message, ...optionalParams] : optionalParams;
4038
+ this.consoleLogger.debug(formatDurableLogData(DurableLogLevel.DEBUG, loggingContext.getDurableLogData(), ...params));
4039
+ };
4040
+ }
4041
+ if (levels.INFO.priority >= lambdaLogLevel.priority) {
4042
+ this.info = (message, ...optionalParams) => {
4043
+ const loggingContext = this.ensureDurableLoggingContext();
4044
+ const params = message !== undefined ? [message, ...optionalParams] : optionalParams;
4045
+ this.consoleLogger.info(formatDurableLogData(DurableLogLevel.INFO, loggingContext.getDurableLogData(), ...params));
4046
+ };
4047
+ }
4048
+ if (levels.WARN.priority >= lambdaLogLevel.priority) {
4049
+ this.warn = (message, ...optionalParams) => {
4050
+ const loggingContext = this.ensureDurableLoggingContext();
4051
+ const params = message !== undefined ? [message, ...optionalParams] : optionalParams;
4052
+ this.consoleLogger.warn(formatDurableLogData(DurableLogLevel.WARN, loggingContext.getDurableLogData(), ...params));
4053
+ };
4054
+ }
4055
+ if (levels.ERROR.priority >= lambdaLogLevel.priority) {
4056
+ this.error = (message, ...optionalParams) => {
4057
+ const loggingContext = this.ensureDurableLoggingContext();
4058
+ const params = message !== undefined ? [message, ...optionalParams] : optionalParams;
4059
+ this.consoleLogger.error(formatDurableLogData(DurableLogLevel.ERROR, loggingContext.getDurableLogData(), ...params));
4060
+ };
4061
+ }
4062
+ }
4063
+ ensureDurableLoggingContext() {
4064
+ const context = this.executionContext;
4065
+ if (!this.durableLoggingContext && !context) {
4066
+ throw new Error("DurableLoggingContext is not configured. Please call configureDurableLoggingContext before logging.");
4067
+ }
4068
+ if (this.durableLoggingContext) {
4069
+ return this.durableLoggingContext;
4070
+ }
4071
+ if (!context) {
4072
+ throw new Error("Execution context is not provided.");
4073
+ }
4074
+ return {
4075
+ getDurableLogData: () => {
4076
+ return {
4077
+ requestId: context.requestId,
4078
+ executionArn: context.durableExecutionArn,
4079
+ tenantId: context.tenantId,
4080
+ };
4081
+ },
4082
+ };
4083
+ }
4084
+ log(level, message, ...optionalParams) {
4085
+ switch (level) {
4086
+ case DurableLogLevel.DEBUG:
4087
+ this.debug(message, ...optionalParams);
4088
+ break;
4089
+ case DurableLogLevel.INFO:
4090
+ this.info(message, ...optionalParams);
4091
+ break;
4092
+ case DurableLogLevel.WARN:
4093
+ this.warn(message, ...optionalParams);
4094
+ break;
4095
+ case DurableLogLevel.ERROR:
4096
+ this.error(message, ...optionalParams);
4097
+ break;
4098
+ default:
4099
+ this.info(message, ...optionalParams);
4100
+ break;
4101
+ }
4102
+ }
4103
+ // These method signatures will be set dynamically in configureLogLevel()
4104
+ info;
4105
+ error;
4106
+ warn;
4107
+ debug;
4108
+ configureDurableLoggingContext(durableLoggingContext) {
4109
+ this.durableLoggingContext = durableLoggingContext;
4110
+ }
4111
+ }
4112
+ /**
4113
+ * Creates a default logger that outputs structured logs to console.
4114
+ *
4115
+ * @param executionContext - Optional execution context for logging
4116
+ * @returns DefaultLogger instance
4117
+ */
4118
+ const createDefaultLogger = (executionContext) => {
4119
+ return new DefaultLogger(executionContext);
4120
+ };
4121
+
4122
+ let defaultLambdaClient;
4123
+ /**
4124
+ * Durable execution client which uses an API-based LambdaClient
4125
+ * with built-in error logging. By default, the Lambda client will
4126
+ * have custom timeouts set.
4127
+ *
4128
+ * @public
4129
+ */
4130
+ class DurableExecutionApiClient {
4131
+ client;
4132
+ constructor(client) {
4133
+ if (!client) {
4134
+ if (!defaultLambdaClient) {
4135
+ defaultLambdaClient = new clientLambda.LambdaClient({
4136
+ requestHandler: {
4137
+ connectionTimeout: 5000,
4138
+ socketTimeout: 50000,
4139
+ requestTimeout: 55000,
4140
+ throwOnRequestTimeout: true,
4141
+ },
4142
+ });
4143
+ }
4144
+ this.client = defaultLambdaClient;
4145
+ }
4146
+ else {
4147
+ this.client = client;
4148
+ }
4149
+ }
4150
+ /**
4151
+ * Gets operation state data from the durable execution
4152
+ * @param params - The GetDurableExecutionState request
4153
+ * @param logger - Optional developer logger for error reporting
4154
+ * @returns Response with operations data
4155
+ */
4156
+ async getExecutionState(params, logger) {
4157
+ try {
4158
+ const response = await this.client.send(new clientLambda.GetDurableExecutionStateCommand({
4159
+ DurableExecutionArn: params.DurableExecutionArn,
4160
+ CheckpointToken: params.CheckpointToken,
4161
+ Marker: params.Marker,
4162
+ MaxItems: params.MaxItems,
4163
+ }));
4164
+ return response;
4165
+ }
4166
+ catch (error) {
4167
+ // Internal debug logging
4168
+ log("❌", "GetDurableExecutionState failed", {
4169
+ error,
4170
+ requestId: error?.$metadata
4171
+ ?.requestId,
4172
+ DurableExecutionArn: params.DurableExecutionArn,
4173
+ CheckpointToken: params.CheckpointToken,
4174
+ Marker: params.Marker,
4175
+ });
4176
+ // Developer logging if logger provided
4177
+ if (logger) {
4178
+ logger.error("Failed to get durable execution state", error, {
4179
+ requestId: error
4180
+ ?.$metadata?.requestId,
4181
+ });
4182
+ }
4183
+ throw error;
4184
+ }
4185
+ }
4186
+ /**
4187
+ * Checkpoints the durable execution with operation updates
4188
+ * @param params - The checkpoint request
4189
+ * @param logger - Optional developer logger for error reporting
4190
+ * @returns Checkpoint response
4191
+ */
4192
+ async checkpoint(params, logger) {
4193
+ try {
4194
+ const response = await this.client.send(new clientLambda.CheckpointDurableExecutionCommand({
4195
+ DurableExecutionArn: params.DurableExecutionArn,
4196
+ CheckpointToken: params.CheckpointToken,
4197
+ ClientToken: params.ClientToken,
4198
+ Updates: params.Updates,
4199
+ }));
4200
+ return response;
4201
+ }
4202
+ catch (error) {
4203
+ // Internal debug logging
4204
+ log("❌", "CheckpointDurableExecution failed", {
4205
+ error,
4206
+ requestId: error?.$metadata
4207
+ ?.requestId,
4208
+ DurableExecutionArn: params.DurableExecutionArn,
4209
+ CheckpointToken: params.CheckpointToken,
4210
+ ClientToken: params.ClientToken,
4211
+ });
4212
+ // Developer logging if logger provided
4213
+ if (logger) {
4214
+ logger.error("Failed to checkpoint durable execution", error, {
4215
+ requestId: error
4216
+ ?.$metadata?.requestId,
4217
+ });
4218
+ }
4219
+ throw error;
4220
+ }
4221
+ }
4222
+ }
4223
+
4224
+ /**
4225
+ * Custom DurableExecutionInvocationInput which uses a custom durable
4226
+ * execution client instead of the API-based LambdaClient.
4227
+ *
4228
+ * @internal
4229
+ */
4230
+ class DurableExecutionInvocationInputWithClient {
4231
+ durableExecutionClient;
4232
+ InitialExecutionState;
4233
+ DurableExecutionArn;
4234
+ CheckpointToken;
4235
+ constructor(params, durableExecutionClient) {
4236
+ this.durableExecutionClient = durableExecutionClient;
4237
+ this.InitialExecutionState = params.InitialExecutionState;
4238
+ this.DurableExecutionArn = params.DurableExecutionArn;
4239
+ this.CheckpointToken = params.CheckpointToken;
4240
+ }
4241
+ static isInstance(event) {
4242
+ if (event instanceof DurableExecutionInvocationInputWithClient) {
4243
+ return true;
4244
+ }
4245
+ return !!(typeof event === "object" &&
4246
+ event &&
4247
+ event.toString() ===
4248
+ "[object DurableExecutionInvocationInputWithClient]" &&
4249
+ "durableExecutionClient" in event &&
4250
+ event.constructor.name === "DurableExecutionInvocationInputWithClient");
4251
+ }
4252
+ get [Symbol.toStringTag]() {
4253
+ return "DurableExecutionInvocationInputWithClient";
4254
+ }
4255
+ }
4256
+
4257
+ const initializeExecutionContext = async (event, context, lambdaClient) => {
4258
+ log("🔵", "Initializing durable function with event:", event);
4259
+ log("📍", "Function Input:", event);
4260
+ const checkpointToken = event.CheckpointToken;
4261
+ const durableExecutionArn = event.DurableExecutionArn;
4262
+ const durableExecutionClient =
4263
+ // Allow passing arbitrary durable clients if the input is a custom class
4264
+ DurableExecutionInvocationInputWithClient.isInstance(event)
4265
+ ? event.durableExecutionClient
4266
+ : new DurableExecutionApiClient(lambdaClient);
4267
+ // Create logger for initialization errors using existing logger factory
4268
+ const initLogger = createDefaultLogger({
4269
+ durableExecutionArn,
4270
+ requestId: context.awsRequestId,
4271
+ tenantId: context.tenantId,
4272
+ });
4273
+ const operationsArray = [...(event.InitialExecutionState.Operations || [])];
4274
+ let nextMarker = event.InitialExecutionState.NextMarker;
4275
+ while (nextMarker) {
4276
+ const response = await durableExecutionClient.getExecutionState({
4277
+ CheckpointToken: checkpointToken,
4278
+ Marker: nextMarker,
4279
+ DurableExecutionArn: durableExecutionArn,
4280
+ MaxItems: 1000,
4281
+ }, initLogger);
4282
+ operationsArray.push(...(response.Operations || []));
4283
+ nextMarker = response.NextMarker || "";
4284
+ }
4285
+ // Determine replay mode based on operations array length
4286
+ const durableExecutionMode = operationsArray.length > 1
4287
+ ? DurableExecutionMode.ReplayMode
4288
+ : DurableExecutionMode.ExecutionMode;
4289
+ log("📝", "Operations:", operationsArray);
4290
+ const stepData = operationsArray.reduce((acc, operation) => {
4291
+ if (operation.Id) {
4292
+ // The stepData received from backend has Id and ParentId as hash, so no need to hash it again
4293
+ acc[operation.Id] = operation;
4294
+ }
4295
+ return acc;
4296
+ }, {});
4297
+ log("📝", "Loaded step data:", stepData);
4298
+ return {
4299
+ executionContext: {
4300
+ durableExecutionClient,
4301
+ _stepData: stepData,
4302
+ terminationManager: new TerminationManager(),
4303
+ durableExecutionArn,
4304
+ pendingCompletions: new Set(),
4305
+ getStepData(stepId) {
4306
+ return getStepData(stepData, stepId);
4307
+ },
4308
+ tenantId: context.tenantId,
4309
+ requestId: context.awsRequestId,
4310
+ },
4311
+ durableExecutionMode,
4312
+ checkpointToken,
4313
+ };
4314
+ };
4315
+
4316
+ // Lambda response size limit is 6MB
4317
+ const LAMBDA_RESPONSE_SIZE_LIMIT = 6 * 1024 * 1024 - 50; // 6MB in bytes, minus 50 bytes for envelope
4318
+ async function runHandler(event, context, executionContext, durableExecutionMode, checkpointToken, handler) {
4319
+ // Create checkpoint manager and step data emitter
4320
+ const stepDataEmitter = new events.EventEmitter();
4321
+ const checkpointManager = new CheckpointManager(executionContext.durableExecutionArn, executionContext._stepData, executionContext.durableExecutionClient, executionContext.terminationManager, checkpointToken, stepDataEmitter, createDefaultLogger(executionContext), new Set());
4322
+ // Set the checkpoint terminating callback on the termination manager
4323
+ executionContext.terminationManager.setCheckpointTerminatingCallback(() => {
4324
+ checkpointManager.setTerminating();
4325
+ });
4326
+ const durableExecution = {
4327
+ checkpointManager,
4328
+ stepDataEmitter,
4329
+ setTerminating: () => checkpointManager.setTerminating(),
4330
+ };
4331
+ const durableContext = createDurableContext(executionContext, context, durableExecutionMode,
4332
+ // Default logger may not have the same type as Logger, but we should always provide a default logger even if the user overrides it
4333
+ createDefaultLogger(), undefined, durableExecution);
4334
+ // Extract customerHandlerEvent from the original event
4335
+ const initialExecutionEvent = event.InitialExecutionState.Operations?.[0];
4336
+ const customerHandlerEvent = JSON.parse(initialExecutionEvent?.ExecutionDetails?.InputPayload ?? "{}");
4337
+ try {
4338
+ log("🎯", `Starting handler execution, handler event: ${customerHandlerEvent}`);
4339
+ let handlerPromiseResolved = false;
4340
+ let terminationPromiseResolved = false;
4341
+ const handlerPromise = runWithContext("root", undefined, () => handler(customerHandlerEvent, durableContext)).then((result) => {
4342
+ handlerPromiseResolved = true;
4343
+ log("🏆", "Handler promise resolved first!");
4344
+ return ["handler", result];
4345
+ });
4346
+ const terminationPromise = executionContext.terminationManager
4347
+ .getTerminationPromise()
4348
+ .then((result) => {
4349
+ terminationPromiseResolved = true;
4350
+ log("💥", "Termination promise resolved first!");
4351
+ // Set checkpoint manager as terminating when termination starts
4352
+ durableExecution.setTerminating();
4353
+ return ["termination", result];
4354
+ });
4355
+ // Set up a timeout to log the state of promises after a short delay
4356
+ setTimeout(() => {
4357
+ log("⏱️", "Promise race status check:", {
4358
+ handlerResolved: handlerPromiseResolved,
4359
+ terminationResolved: terminationPromiseResolved,
4360
+ });
4361
+ }, 500);
4362
+ const [resultType, result] = await Promise.race([
4363
+ handlerPromise,
4364
+ terminationPromise,
4365
+ ]);
4366
+ log("🏁", "Promise race completed with:", {
4367
+ resultType,
4368
+ });
4369
+ // Wait for all pending checkpoints to complete
4370
+ try {
4371
+ await durableExecution.checkpointManager.waitForQueueCompletion();
4372
+ log("✅", "All pending checkpoints completed");
4373
+ }
4374
+ catch (error) {
4375
+ log("⚠️", "Error waiting for checkpoint completion:", error);
4376
+ }
4377
+ // If termination was due to checkpoint failure, throw the appropriate error
4378
+ if (resultType === "termination" &&
4379
+ result.reason === TerminationReason.CHECKPOINT_FAILED) {
4380
+ log("🛑", "Checkpoint failed - handling termination");
4381
+ // checkpoint.ts always provides classified error
4382
+ throw result.error;
4383
+ }
4384
+ // If termination was due to serdes failure, throw an error to terminate the Lambda
4385
+ if (resultType === "termination" &&
4386
+ result.reason === TerminationReason.SERDES_FAILED) {
4387
+ log("🛑", "Serdes failed - terminating Lambda execution");
4388
+ throw new SerdesFailedError(result.message);
4389
+ }
4390
+ // If termination was due to context validation error, return FAILED
4391
+ if (resultType === "termination" &&
4392
+ result.reason === TerminationReason.CONTEXT_VALIDATION_ERROR) {
4393
+ log("🛑", "Context validation error - returning FAILED status");
4394
+ return {
4395
+ Status: exports.InvocationStatus.FAILED,
4396
+ Error: createErrorObjectFromError(result.error || new Error(result.message)),
4397
+ };
4398
+ }
4399
+ if (resultType === "termination") {
4400
+ log("🛑", "Returning termination response");
4401
+ return {
4402
+ Status: exports.InvocationStatus.PENDING,
4403
+ };
4404
+ }
4405
+ log("✅", "Returning normal completion response");
4406
+ // Stringify the result once to avoid multiple JSON.stringify calls
4407
+ const serializedResult = JSON.stringify(result);
4408
+ const serializedSize = new TextEncoder().encode(serializedResult).length;
4409
+ // Check if the response size exceeds the Lambda limit
4410
+ // Note: JSON.stringify(undefined) returns undefined, so we need to handle that case
4411
+ if (serializedResult && serializedSize > LAMBDA_RESPONSE_SIZE_LIMIT) {
4412
+ log("📦", `Response size (${serializedSize} bytes) exceeds Lambda limit (${LAMBDA_RESPONSE_SIZE_LIMIT} bytes). Checkpointing result.`);
4413
+ // Create a checkpoint to save the large result
4414
+ const stepId = `execution-result-${Date.now()}`;
4415
+ try {
4416
+ await durableExecution.checkpointManager.checkpoint(stepId, {
4417
+ Id: stepId,
4418
+ Action: "SUCCEED",
4419
+ Type: clientLambda.OperationType.EXECUTION,
4420
+ Payload: serializedResult, // Reuse the already serialized result
4421
+ });
4422
+ log("✅", "Large result successfully checkpointed");
4423
+ // Wait for any pending checkpoints to complete before returning
4424
+ try {
4425
+ await durableExecution.checkpointManager.waitForQueueCompletion();
4426
+ }
4427
+ catch (waitError) {
4428
+ log("⚠️", "Error waiting for checkpoint queue completion:", waitError);
4429
+ // Continue anyway - the checkpoint will be retried on next invocation
4430
+ }
4431
+ // Return a response indicating the result was checkpointed
4432
+ return {
4433
+ Status: exports.InvocationStatus.SUCCEEDED,
4434
+ Result: "",
4435
+ };
4436
+ }
4437
+ catch (checkpointError) {
4438
+ log("❌", "Failed to checkpoint large result:", checkpointError);
4439
+ // Re-throw - checkpoint.ts always classifies errors before terminating
4440
+ throw checkpointError;
4441
+ }
4442
+ }
4443
+ // If response size is acceptable, return the response
4444
+ // Wait for any pending checkpoints to complete before returning
4445
+ try {
4446
+ await durableExecution.checkpointManager.waitForQueueCompletion();
4447
+ }
4448
+ catch (waitError) {
4449
+ log("⚠️", "Error waiting for checkpoint queue completion:", waitError);
4450
+ // Continue anyway - the checkpoint will be retried on next invocation
4451
+ }
4452
+ return {
4453
+ Status: exports.InvocationStatus.SUCCEEDED,
4454
+ Result: serializedResult,
4455
+ };
4456
+ }
4457
+ catch (error) {
4458
+ log("❌", "Handler threw an error:", error);
4459
+ // Check if this is an unrecoverable invocation error (includes checkpoint invocation failures)
4460
+ if (isUnrecoverableInvocationError(error)) {
4461
+ log("🛑", "Unrecoverable invocation error - terminating Lambda execution");
4462
+ throw error; // Re-throw the error to terminate Lambda execution
4463
+ }
4464
+ // Wait for any pending checkpoints to complete before returning error
4465
+ try {
4466
+ await durableExecution.checkpointManager.waitForQueueCompletion();
4467
+ }
4468
+ catch (waitError) {
4469
+ log("⚠️", "Error waiting for checkpoint queue completion:", waitError);
4470
+ // Continue anyway - the checkpoint will be retried on next invocation
4471
+ }
4472
+ return {
4473
+ Status: exports.InvocationStatus.FAILED,
4474
+ Error: createErrorObjectFromError(error),
4475
+ };
4476
+ }
4477
+ }
4478
+ /**
4479
+ * Validates that the event is a proper durable execution input
4480
+ */
4481
+ function validateDurableExecutionEvent(event) {
4482
+ try {
4483
+ const eventObj = event;
4484
+ if (!eventObj?.DurableExecutionArn || !eventObj?.CheckpointToken) {
4485
+ throw new Error("Missing required durable execution fields");
4486
+ }
4487
+ }
4488
+ catch {
4489
+ const msg = `Unexpected payload provided to start the durable execution.
4490
+ Check your resource configurations to confirm the durability is set.`;
4491
+ throw new Error(msg);
4492
+ }
4493
+ }
4494
+ /**
4495
+ * Wraps a durable handler function to create a handler with automatic state persistence,
4496
+ * retry logic, and workflow orchestration capabilities.
4497
+ *
4498
+ * This function transforms your durable handler into a function that integrates
4499
+ * with the AWS Durable Execution service. The wrapped handler automatically manages execution state
4500
+ * and checkpointing.
4501
+ *
4502
+ * @typeParam TEvent - The type of the input event your handler expects (defaults to any)
4503
+ * @typeParam TResult - The type of the result your handler returns (defaults to any)
4504
+ * @typeParam TLogger - The type of custom logger implementation (defaults to DurableLogger)
4505
+ *
4506
+ * @param handler - Your durable handler function that uses the DurableContext for operations
4507
+ * @param config - Optional configuration for custom advanced settings
4508
+ *
4509
+ * @returns A handler function that automatically manages durability
4510
+ *
4511
+ * @example
4512
+ * **Basic Usage:**
4513
+ * ```typescript
4514
+ * import { withDurableExecution, DurableExecutionHandler } from '@aws/durable-execution-sdk-js';
4515
+ *
4516
+ * const durableHandler: DurableExecutionHandler<MyEvent, MyResult> = async (event, context) => {
4517
+ * // Execute durable operations with automatic retry and checkpointing
4518
+ * const userData = await context.step("fetch-user", async () =>
4519
+ * fetchUserFromDB(event.userId)
4520
+ * );
4521
+ *
4522
+ * // Wait for external approval
4523
+ * const approval = await context.waitForCallback("user-approval", async (callbackId) => {
4524
+ * await sendApprovalEmail(callbackId, userData);
4525
+ * });
4526
+ *
4527
+ * // Process in parallel
4528
+ * const results = await context.parallel("process-data", [
4529
+ * async (ctx) => ctx.step("validate", () => validateData(userData)),
4530
+ * async (ctx) => ctx.step("transform", () => transformData(userData))
4531
+ * ]);
4532
+ *
4533
+ * return { success: true, results };
4534
+ * };
4535
+ *
4536
+ * export const handler = withDurableExecution(durableHandler);
4537
+ * ```
4538
+ *
4539
+ * @example
4540
+ * **With Custom Configuration:**
4541
+ * ```typescript
4542
+ * import { LambdaClient } from '@aws-sdk/client-lambda';
4543
+ *
4544
+ * const customClient = new LambdaClient({
4545
+ * region: 'us-west-2',
4546
+ * maxAttempts: 5
4547
+ * });
4548
+ *
4549
+ * export const handler = withDurableExecution(durableHandler, {
4550
+ * client: customClient
4551
+ * });
4552
+ * ```
4553
+ *
4554
+ * @example
4555
+ * **Passed Directly to the Handler:**
4556
+ * ```typescript
4557
+ * export const handler = withDurableExecution(async (event, context) => {
4558
+ * const result = await context.step(async () => processEvent(event));
4559
+ * return result;
4560
+ * });
4561
+ * ```
4562
+ *
4563
+ * @public
4564
+ */
4565
+ const withDurableExecution = (handler, config) => {
4566
+ return async (event, context) => {
4567
+ validateDurableExecutionEvent(event);
4568
+ const { executionContext, durableExecutionMode, checkpointToken } = await initializeExecutionContext(event, context, config?.client);
4569
+ let response = null;
4570
+ try {
4571
+ response = await runHandler(event, context, executionContext, durableExecutionMode, checkpointToken, handler);
4572
+ return response;
4573
+ }
4574
+ catch (err) {
4575
+ throw err;
4576
+ }
4577
+ };
4578
+ };
4579
+
4580
+ const DEFAULT_CONFIG = {
4581
+ maxAttempts: 60,
4582
+ initialDelay: { seconds: 5 },
4583
+ maxDelay: { seconds: 300 }, // 5 minutes
4584
+ backoffRate: 1.5,
4585
+ jitter: exports.JitterStrategy.FULL,
4586
+ timeoutSeconds: undefined, // No timeout by default
4587
+ };
4588
+ const applyJitter = (delay, strategy) => {
4589
+ switch (strategy) {
4590
+ case exports.JitterStrategy.NONE:
4591
+ return delay;
4592
+ case exports.JitterStrategy.FULL:
4593
+ // Random between 0 and delay
4594
+ return Math.random() * delay;
4595
+ case exports.JitterStrategy.HALF:
4596
+ // Random between delay/2 and delay
4597
+ return delay / 2 + Math.random() * (delay / 2);
4598
+ }
4599
+ };
4600
+ /**
4601
+ * @public
4602
+ */
4603
+ const createWaitStrategy = (config) => {
4604
+ const finalConfig = {
4605
+ ...DEFAULT_CONFIG,
4606
+ ...config,
4607
+ };
4608
+ return (result, attemptsMade) => {
4609
+ // Check if condition is met
4610
+ if (!finalConfig.shouldContinuePolling(result)) {
4611
+ return { shouldContinue: false };
4612
+ }
4613
+ // Check if we've exceeded max attempts
4614
+ if (attemptsMade >= finalConfig.maxAttempts) {
4615
+ throw new Error(`waitForCondition exceeded maximum attempts (${finalConfig.maxAttempts})`);
4616
+ }
4617
+ // Calculate delay with exponential backoff
4618
+ const initialDelaySeconds = durationToSeconds(finalConfig.initialDelay);
4619
+ const maxDelaySeconds = durationToSeconds(finalConfig.maxDelay);
4620
+ const baseDelay = Math.min(initialDelaySeconds * Math.pow(finalConfig.backoffRate, attemptsMade - 1), maxDelaySeconds);
4621
+ // Apply jitter
4622
+ const delayWithJitter = applyJitter(baseDelay, finalConfig.jitter);
4623
+ // Ensure delay is an integer >= 1
4624
+ const finalDelay = Math.max(1, Math.round(delayWithJitter));
4625
+ return { shouldContinue: true, delay: { seconds: finalDelay } };
4626
+ };
4627
+ };
4628
+
4629
+ exports.CallbackError = CallbackError;
4630
+ exports.ChildContextError = ChildContextError;
4631
+ exports.DurableExecutionApiClient = DurableExecutionApiClient;
4632
+ exports.DurableExecutionInvocationInputWithClient = DurableExecutionInvocationInputWithClient;
4633
+ exports.DurableOperationError = DurableOperationError;
4634
+ exports.DurablePromise = DurablePromise;
4635
+ exports.InvokeError = InvokeError;
4636
+ exports.StepError = StepError;
4637
+ exports.StepInterruptedError = StepInterruptedError;
4638
+ exports.WaitForConditionError = WaitForConditionError;
4639
+ exports.createClassSerdes = createClassSerdes;
4640
+ exports.createClassSerdesWithDates = createClassSerdesWithDates;
4641
+ exports.createRetryStrategy = createRetryStrategy;
4642
+ exports.createWaitStrategy = createWaitStrategy;
4643
+ exports.defaultSerdes = defaultSerdes;
4644
+ exports.retryPresets = retryPresets;
4645
+ exports.withDurableExecution = withDurableExecution;
4646
+ //# sourceMappingURL=index.js.map