@nest-batch/core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +368 -0
  3. package/dist/src/adapters/in-process.adapter.d.ts +140 -0
  4. package/dist/src/adapters/in-process.adapter.d.ts.map +1 -0
  5. package/dist/src/adapters/in-process.adapter.js +86 -0
  6. package/dist/src/adapters/in-process.adapter.js.map +1 -0
  7. package/dist/src/adapters/index.d.ts +18 -0
  8. package/dist/src/adapters/index.d.ts.map +1 -0
  9. package/dist/src/adapters/index.js +35 -0
  10. package/dist/src/adapters/index.js.map +1 -0
  11. package/dist/src/builder/batch-builder.d.ts +26 -0
  12. package/dist/src/builder/batch-builder.d.ts.map +1 -0
  13. package/dist/src/builder/batch-builder.js +25 -0
  14. package/dist/src/builder/batch-builder.js.map +1 -0
  15. package/dist/src/builder/flow-builder.d.ts +59 -0
  16. package/dist/src/builder/flow-builder.d.ts.map +1 -0
  17. package/dist/src/builder/flow-builder.js +115 -0
  18. package/dist/src/builder/flow-builder.js.map +1 -0
  19. package/dist/src/builder/index.d.ts +5 -0
  20. package/dist/src/builder/index.d.ts.map +1 -0
  21. package/dist/src/builder/index.js +23 -0
  22. package/dist/src/builder/index.js.map +1 -0
  23. package/dist/src/builder/job-builder.d.ts +76 -0
  24. package/dist/src/builder/job-builder.d.ts.map +1 -0
  25. package/dist/src/builder/job-builder.js +166 -0
  26. package/dist/src/builder/job-builder.js.map +1 -0
  27. package/dist/src/builder/step-builder.d.ts +74 -0
  28. package/dist/src/builder/step-builder.d.ts.map +1 -0
  29. package/dist/src/builder/step-builder.js +144 -0
  30. package/dist/src/builder/step-builder.js.map +1 -0
  31. package/dist/src/compiler/builder-types.d.ts +20 -0
  32. package/dist/src/compiler/builder-types.d.ts.map +1 -0
  33. package/dist/src/compiler/builder-types.js +6 -0
  34. package/dist/src/compiler/builder-types.js.map +1 -0
  35. package/dist/src/compiler/definition-compiler.d.ts +99 -0
  36. package/dist/src/compiler/definition-compiler.d.ts.map +1 -0
  37. package/dist/src/compiler/definition-compiler.js +257 -0
  38. package/dist/src/compiler/definition-compiler.js.map +1 -0
  39. package/dist/src/compiler/index.d.ts +3 -0
  40. package/dist/src/compiler/index.d.ts.map +1 -0
  41. package/dist/src/compiler/index.js +21 -0
  42. package/dist/src/compiler/index.js.map +1 -0
  43. package/dist/src/core/errors.d.ts +77 -0
  44. package/dist/src/core/errors.d.ts.map +1 -0
  45. package/dist/src/core/errors.js +170 -0
  46. package/dist/src/core/errors.js.map +1 -0
  47. package/dist/src/core/execution-context/index.d.ts +4 -0
  48. package/dist/src/core/execution-context/index.d.ts.map +1 -0
  49. package/dist/src/core/execution-context/index.js +22 -0
  50. package/dist/src/core/execution-context/index.js.map +1 -0
  51. package/dist/src/core/execution-context/json-value.d.ts +5 -0
  52. package/dist/src/core/execution-context/json-value.d.ts.map +1 -0
  53. package/dist/src/core/execution-context/json-value.js +6 -0
  54. package/dist/src/core/execution-context/json-value.js.map +1 -0
  55. package/dist/src/core/execution-context/serializer.d.ts +4 -0
  56. package/dist/src/core/execution-context/serializer.d.ts.map +1 -0
  57. package/dist/src/core/execution-context/serializer.js +34 -0
  58. package/dist/src/core/execution-context/serializer.js.map +1 -0
  59. package/dist/src/core/execution-context/validator.d.ts +18 -0
  60. package/dist/src/core/execution-context/validator.d.ts.map +1 -0
  61. package/dist/src/core/execution-context/validator.js +90 -0
  62. package/dist/src/core/execution-context/validator.js.map +1 -0
  63. package/dist/src/core/index.d.ts +8 -0
  64. package/dist/src/core/index.d.ts.map +1 -0
  65. package/dist/src/core/index.js +26 -0
  66. package/dist/src/core/index.js.map +1 -0
  67. package/dist/src/core/ir/decider-definition.d.ts +20 -0
  68. package/dist/src/core/ir/decider-definition.d.ts.map +1 -0
  69. package/dist/src/core/ir/decider-definition.js +6 -0
  70. package/dist/src/core/ir/decider-definition.js.map +1 -0
  71. package/dist/src/core/ir/index.d.ts +8 -0
  72. package/dist/src/core/ir/index.d.ts.map +1 -0
  73. package/dist/src/core/ir/index.js +26 -0
  74. package/dist/src/core/ir/index.js.map +1 -0
  75. package/dist/src/core/ir/job-definition.d.ts +15 -0
  76. package/dist/src/core/ir/job-definition.d.ts.map +1 -0
  77. package/dist/src/core/ir/job-definition.js +6 -0
  78. package/dist/src/core/ir/job-definition.js.map +1 -0
  79. package/dist/src/core/ir/listener-definition.d.ts +10 -0
  80. package/dist/src/core/ir/listener-definition.d.ts.map +1 -0
  81. package/dist/src/core/ir/listener-definition.js +6 -0
  82. package/dist/src/core/ir/listener-definition.js.map +1 -0
  83. package/dist/src/core/ir/policy-config.d.ts +24 -0
  84. package/dist/src/core/ir/policy-config.d.ts.map +1 -0
  85. package/dist/src/core/ir/policy-config.js +6 -0
  86. package/dist/src/core/ir/policy-config.js.map +1 -0
  87. package/dist/src/core/ir/refs.d.ts +42 -0
  88. package/dist/src/core/ir/refs.d.ts.map +1 -0
  89. package/dist/src/core/ir/refs.js +18 -0
  90. package/dist/src/core/ir/refs.js.map +1 -0
  91. package/dist/src/core/ir/step-definition.d.ts +59 -0
  92. package/dist/src/core/ir/step-definition.d.ts.map +1 -0
  93. package/dist/src/core/ir/step-definition.js +6 -0
  94. package/dist/src/core/ir/step-definition.js.map +1 -0
  95. package/dist/src/core/ir/transition-definition.d.ts +8 -0
  96. package/dist/src/core/ir/transition-definition.d.ts.map +1 -0
  97. package/dist/src/core/ir/transition-definition.js +6 -0
  98. package/dist/src/core/ir/transition-definition.js.map +1 -0
  99. package/dist/src/core/item/index.d.ts +2 -0
  100. package/dist/src/core/item/index.d.ts.map +1 -0
  101. package/dist/src/core/item/index.js +20 -0
  102. package/dist/src/core/item/index.js.map +1 -0
  103. package/dist/src/core/item/interfaces.d.ts +64 -0
  104. package/dist/src/core/item/interfaces.d.ts.map +1 -0
  105. package/dist/src/core/item/interfaces.js +6 -0
  106. package/dist/src/core/item/interfaces.js.map +1 -0
  107. package/dist/src/core/repository/index.d.ts +3 -0
  108. package/dist/src/core/repository/index.d.ts.map +1 -0
  109. package/dist/src/core/repository/index.js +21 -0
  110. package/dist/src/core/repository/index.js.map +1 -0
  111. package/dist/src/core/repository/job-repository.d.ts +60 -0
  112. package/dist/src/core/repository/job-repository.d.ts.map +1 -0
  113. package/dist/src/core/repository/job-repository.js +27 -0
  114. package/dist/src/core/repository/job-repository.js.map +1 -0
  115. package/dist/src/core/repository/types.d.ts +84 -0
  116. package/dist/src/core/repository/types.d.ts.map +1 -0
  117. package/dist/src/core/repository/types.js +6 -0
  118. package/dist/src/core/repository/types.js.map +1 -0
  119. package/dist/src/core/status.d.ts +29 -0
  120. package/dist/src/core/status.d.ts.map +1 -0
  121. package/dist/src/core/status.js +58 -0
  122. package/dist/src/core/status.js.map +1 -0
  123. package/dist/src/core/transaction/index.d.ts +2 -0
  124. package/dist/src/core/transaction/index.d.ts.map +1 -0
  125. package/dist/src/core/transaction/index.js +20 -0
  126. package/dist/src/core/transaction/index.js.map +1 -0
  127. package/dist/src/core/transaction/transaction-manager.d.ts +8 -0
  128. package/dist/src/core/transaction/transaction-manager.d.ts.map +1 -0
  129. package/dist/src/core/transaction/transaction-manager.js +14 -0
  130. package/dist/src/core/transaction/transaction-manager.js.map +1 -0
  131. package/dist/src/core/validation/definition-validator.d.ts +46 -0
  132. package/dist/src/core/validation/definition-validator.d.ts.map +1 -0
  133. package/dist/src/core/validation/definition-validator.js +177 -0
  134. package/dist/src/core/validation/definition-validator.js.map +1 -0
  135. package/dist/src/core/validation/index.d.ts +2 -0
  136. package/dist/src/core/validation/index.d.ts.map +1 -0
  137. package/dist/src/core/validation/index.js +20 -0
  138. package/dist/src/core/validation/index.js.map +1 -0
  139. package/dist/src/decorators/constants.d.ts +10 -0
  140. package/dist/src/decorators/constants.d.ts.map +1 -0
  141. package/dist/src/decorators/constants.js +50 -0
  142. package/dist/src/decorators/constants.js.map +1 -0
  143. package/dist/src/decorators/flow.decorator.d.ts +25 -0
  144. package/dist/src/decorators/flow.decorator.d.ts.map +1 -0
  145. package/dist/src/decorators/flow.decorator.js +19 -0
  146. package/dist/src/decorators/flow.decorator.js.map +1 -0
  147. package/dist/src/decorators/index.d.ts +8 -0
  148. package/dist/src/decorators/index.d.ts.map +1 -0
  149. package/dist/src/decorators/index.js +26 -0
  150. package/dist/src/decorators/index.js.map +1 -0
  151. package/dist/src/decorators/item.decorators.d.ts +32 -0
  152. package/dist/src/decorators/item.decorators.d.ts.map +1 -0
  153. package/dist/src/decorators/item.decorators.js +40 -0
  154. package/dist/src/decorators/item.decorators.js.map +1 -0
  155. package/dist/src/decorators/job.decorator.d.ts +11 -0
  156. package/dist/src/decorators/job.decorator.d.ts.map +1 -0
  157. package/dist/src/decorators/job.decorator.js +17 -0
  158. package/dist/src/decorators/job.decorator.js.map +1 -0
  159. package/dist/src/decorators/listener.decorators.d.ts +56 -0
  160. package/dist/src/decorators/listener.decorators.d.ts.map +1 -0
  161. package/dist/src/decorators/listener.decorators.js +157 -0
  162. package/dist/src/decorators/listener.decorators.js.map +1 -0
  163. package/dist/src/decorators/step.decorator.d.ts +25 -0
  164. package/dist/src/decorators/step.decorator.d.ts.map +1 -0
  165. package/dist/src/decorators/step.decorator.js +21 -0
  166. package/dist/src/decorators/step.decorator.js.map +1 -0
  167. package/dist/src/decorators/tasklet.decorator.d.ts +7 -0
  168. package/dist/src/decorators/tasklet.decorator.d.ts.map +1 -0
  169. package/dist/src/decorators/tasklet.decorator.js +21 -0
  170. package/dist/src/decorators/tasklet.decorator.js.map +1 -0
  171. package/dist/src/execution/batch-worker-runner.d.ts +27 -0
  172. package/dist/src/execution/batch-worker-runner.d.ts.map +1 -0
  173. package/dist/src/execution/batch-worker-runner.js +147 -0
  174. package/dist/src/execution/batch-worker-runner.js.map +1 -0
  175. package/dist/src/execution/chunk-step-executor.d.ts +86 -0
  176. package/dist/src/execution/chunk-step-executor.d.ts.map +1 -0
  177. package/dist/src/execution/chunk-step-executor.js +482 -0
  178. package/dist/src/execution/chunk-step-executor.js.map +1 -0
  179. package/dist/src/execution/execution-strategy.d.ts +110 -0
  180. package/dist/src/execution/execution-strategy.d.ts.map +1 -0
  181. package/dist/src/execution/execution-strategy.js +13 -0
  182. package/dist/src/execution/execution-strategy.js.map +1 -0
  183. package/dist/src/execution/external-task-execution-strategy.d.ts +36 -0
  184. package/dist/src/execution/external-task-execution-strategy.d.ts.map +1 -0
  185. package/dist/src/execution/external-task-execution-strategy.js +97 -0
  186. package/dist/src/execution/external-task-execution-strategy.js.map +1 -0
  187. package/dist/src/execution/in-process-execution-strategy.d.ts +129 -0
  188. package/dist/src/execution/in-process-execution-strategy.d.ts.map +1 -0
  189. package/dist/src/execution/in-process-execution-strategy.js +141 -0
  190. package/dist/src/execution/in-process-execution-strategy.js.map +1 -0
  191. package/dist/src/execution/index.d.ts +14 -0
  192. package/dist/src/execution/index.d.ts.map +1 -0
  193. package/dist/src/execution/index.js +32 -0
  194. package/dist/src/execution/index.js.map +1 -0
  195. package/dist/src/execution/job-executor.d.ts +145 -0
  196. package/dist/src/execution/job-executor.d.ts.map +1 -0
  197. package/dist/src/execution/job-executor.js +475 -0
  198. package/dist/src/execution/job-executor.js.map +1 -0
  199. package/dist/src/execution/job-explorer.d.ts +15 -0
  200. package/dist/src/execution/job-explorer.d.ts.map +1 -0
  201. package/dist/src/execution/job-explorer.js +84 -0
  202. package/dist/src/execution/job-explorer.js.map +1 -0
  203. package/dist/src/execution/job-key.d.ts +3 -0
  204. package/dist/src/execution/job-key.d.ts.map +1 -0
  205. package/dist/src/execution/job-key.js +43 -0
  206. package/dist/src/execution/job-key.js.map +1 -0
  207. package/dist/src/execution/job-launcher.d.ts +75 -0
  208. package/dist/src/execution/job-launcher.d.ts.map +1 -0
  209. package/dist/src/execution/job-launcher.js +112 -0
  210. package/dist/src/execution/job-launcher.js.map +1 -0
  211. package/dist/src/execution/job-operator.d.ts +22 -0
  212. package/dist/src/execution/job-operator.d.ts.map +1 -0
  213. package/dist/src/execution/job-operator.js +125 -0
  214. package/dist/src/execution/job-operator.js.map +1 -0
  215. package/dist/src/execution/listener-invoker.d.ts +164 -0
  216. package/dist/src/execution/listener-invoker.d.ts.map +1 -0
  217. package/dist/src/execution/listener-invoker.js +246 -0
  218. package/dist/src/execution/listener-invoker.js.map +1 -0
  219. package/dist/src/execution/ref-resolver.d.ts +40 -0
  220. package/dist/src/execution/ref-resolver.d.ts.map +1 -0
  221. package/dist/src/execution/ref-resolver.js +41 -0
  222. package/dist/src/execution/ref-resolver.js.map +1 -0
  223. package/dist/src/execution/tasklet-step-executor.d.ts +79 -0
  224. package/dist/src/execution/tasklet-step-executor.d.ts.map +1 -0
  225. package/dist/src/execution/tasklet-step-executor.js +138 -0
  226. package/dist/src/execution/tasklet-step-executor.js.map +1 -0
  227. package/dist/src/explorer/batch-explorer.d.ts +138 -0
  228. package/dist/src/explorer/batch-explorer.d.ts.map +1 -0
  229. package/dist/src/explorer/batch-explorer.js +167 -0
  230. package/dist/src/explorer/batch-explorer.js.map +1 -0
  231. package/dist/src/explorer/index.d.ts +2 -0
  232. package/dist/src/explorer/index.d.ts.map +1 -0
  233. package/dist/src/explorer/index.js +20 -0
  234. package/dist/src/explorer/index.js.map +1 -0
  235. package/dist/src/flow/flow-evaluator.d.ts +30 -0
  236. package/dist/src/flow/flow-evaluator.d.ts.map +1 -0
  237. package/dist/src/flow/flow-evaluator.js +80 -0
  238. package/dist/src/flow/flow-evaluator.js.map +1 -0
  239. package/dist/src/flow/index.d.ts +2 -0
  240. package/dist/src/flow/index.d.ts.map +1 -0
  241. package/dist/src/flow/index.js +20 -0
  242. package/dist/src/flow/index.js.map +1 -0
  243. package/dist/src/index.d.ts +18 -0
  244. package/dist/src/index.d.ts.map +1 -0
  245. package/dist/src/index.js +90 -0
  246. package/dist/src/index.js.map +1 -0
  247. package/dist/src/io/checkpoint.d.ts +7 -0
  248. package/dist/src/io/checkpoint.d.ts.map +1 -0
  249. package/dist/src/io/checkpoint.js +56 -0
  250. package/dist/src/io/checkpoint.js.map +1 -0
  251. package/dist/src/io/database.d.ts +50 -0
  252. package/dist/src/io/database.d.ts.map +1 -0
  253. package/dist/src/io/database.js +108 -0
  254. package/dist/src/io/database.js.map +1 -0
  255. package/dist/src/io/file-readers.d.ts +54 -0
  256. package/dist/src/io/file-readers.d.ts.map +1 -0
  257. package/dist/src/io/file-readers.js +167 -0
  258. package/dist/src/io/file-readers.js.map +1 -0
  259. package/dist/src/io/file-writers.d.ts +31 -0
  260. package/dist/src/io/file-writers.d.ts.map +1 -0
  261. package/dist/src/io/file-writers.js +80 -0
  262. package/dist/src/io/file-writers.js.map +1 -0
  263. package/dist/src/io/index.d.ts +6 -0
  264. package/dist/src/io/index.d.ts.map +1 -0
  265. package/dist/src/io/index.js +24 -0
  266. package/dist/src/io/index.js.map +1 -0
  267. package/dist/src/io/s3.d.ts +50 -0
  268. package/dist/src/io/s3.d.ts.map +1 -0
  269. package/dist/src/io/s3.js +96 -0
  270. package/dist/src/io/s3.js.map +1 -0
  271. package/dist/src/listeners/builtin-listeners.d.ts +77 -0
  272. package/dist/src/listeners/builtin-listeners.d.ts.map +1 -0
  273. package/dist/src/listeners/builtin-listeners.js +108 -0
  274. package/dist/src/listeners/builtin-listeners.js.map +1 -0
  275. package/dist/src/listeners/index.d.ts +8 -0
  276. package/dist/src/listeners/index.d.ts.map +1 -0
  277. package/dist/src/listeners/index.js +25 -0
  278. package/dist/src/listeners/index.js.map +1 -0
  279. package/dist/src/module/adapter-options.d.ts +39 -0
  280. package/dist/src/module/adapter-options.d.ts.map +1 -0
  281. package/dist/src/module/adapter-options.js +34 -0
  282. package/dist/src/module/adapter-options.js.map +1 -0
  283. package/dist/src/module/adapter.d.ts +157 -0
  284. package/dist/src/module/adapter.d.ts.map +1 -0
  285. package/dist/src/module/adapter.js +80 -0
  286. package/dist/src/module/adapter.js.map +1 -0
  287. package/dist/src/module/batch-schedule-registry.d.ts +110 -0
  288. package/dist/src/module/batch-schedule-registry.d.ts.map +1 -0
  289. package/dist/src/module/batch-schedule-registry.js +0 -0
  290. package/dist/src/module/batch-schedule-registry.js.map +1 -0
  291. package/dist/src/module/index.d.ts +14 -0
  292. package/dist/src/module/index.d.ts.map +1 -0
  293. package/dist/src/module/index.js +31 -0
  294. package/dist/src/module/index.js.map +1 -0
  295. package/dist/src/module/nest-batch.module.d.ts +236 -0
  296. package/dist/src/module/nest-batch.module.d.ts.map +1 -0
  297. package/dist/src/module/nest-batch.module.js +475 -0
  298. package/dist/src/module/nest-batch.module.js.map +1 -0
  299. package/dist/src/module/tokens.d.ts +83 -0
  300. package/dist/src/module/tokens.d.ts.map +1 -0
  301. package/dist/src/module/tokens.js +58 -0
  302. package/dist/src/module/tokens.js.map +1 -0
  303. package/dist/src/observability/event-types.d.ts +55 -0
  304. package/dist/src/observability/event-types.d.ts.map +1 -0
  305. package/dist/src/observability/event-types.js +36 -0
  306. package/dist/src/observability/event-types.js.map +1 -0
  307. package/dist/src/observability/exporters.d.ts +35 -0
  308. package/dist/src/observability/exporters.d.ts.map +1 -0
  309. package/dist/src/observability/exporters.js +93 -0
  310. package/dist/src/observability/exporters.js.map +1 -0
  311. package/dist/src/observability/index.d.ts +3 -0
  312. package/dist/src/observability/index.d.ts.map +1 -0
  313. package/dist/src/observability/index.js +21 -0
  314. package/dist/src/observability/index.js.map +1 -0
  315. package/dist/src/partition-helpers.d.ts +127 -0
  316. package/dist/src/partition-helpers.d.ts.map +1 -0
  317. package/dist/src/partition-helpers.js +136 -0
  318. package/dist/src/partition-helpers.js.map +1 -0
  319. package/dist/src/policies/backoff.d.ts +3 -0
  320. package/dist/src/policies/backoff.d.ts.map +1 -0
  321. package/dist/src/policies/backoff.js +34 -0
  322. package/dist/src/policies/backoff.js.map +1 -0
  323. package/dist/src/policies/index.d.ts +4 -0
  324. package/dist/src/policies/index.d.ts.map +1 -0
  325. package/dist/src/policies/index.js +22 -0
  326. package/dist/src/policies/index.js.map +1 -0
  327. package/dist/src/policies/retry-policy.d.ts +13 -0
  328. package/dist/src/policies/retry-policy.d.ts.map +1 -0
  329. package/dist/src/policies/retry-policy.js +55 -0
  330. package/dist/src/policies/retry-policy.js.map +1 -0
  331. package/dist/src/policies/skip-policy.d.ts +12 -0
  332. package/dist/src/policies/skip-policy.d.ts.map +1 -0
  333. package/dist/src/policies/skip-policy.js +44 -0
  334. package/dist/src/policies/skip-policy.js.map +1 -0
  335. package/dist/src/registry/index.d.ts +2 -0
  336. package/dist/src/registry/index.d.ts.map +1 -0
  337. package/dist/src/registry/index.js +20 -0
  338. package/dist/src/registry/index.js.map +1 -0
  339. package/dist/src/registry/job-registry.d.ts +16 -0
  340. package/dist/src/registry/job-registry.d.ts.map +1 -0
  341. package/dist/src/registry/job-registry.js +50 -0
  342. package/dist/src/registry/job-registry.js.map +1 -0
  343. package/dist/src/repository/id-generator.d.ts +18 -0
  344. package/dist/src/repository/id-generator.d.ts.map +1 -0
  345. package/dist/src/repository/id-generator.js +37 -0
  346. package/dist/src/repository/id-generator.js.map +1 -0
  347. package/dist/src/repository/in-memory/in-memory-job-repository.d.ts +49 -0
  348. package/dist/src/repository/in-memory/in-memory-job-repository.d.ts.map +1 -0
  349. package/dist/src/repository/in-memory/in-memory-job-repository.js +291 -0
  350. package/dist/src/repository/in-memory/in-memory-job-repository.js.map +1 -0
  351. package/dist/src/repository/in-memory/index.d.ts +2 -0
  352. package/dist/src/repository/in-memory/index.d.ts.map +1 -0
  353. package/dist/src/repository/in-memory/index.js +20 -0
  354. package/dist/src/repository/in-memory/index.js.map +1 -0
  355. package/dist/src/repository/index.d.ts +4 -0
  356. package/dist/src/repository/index.d.ts.map +1 -0
  357. package/dist/src/repository/index.js +22 -0
  358. package/dist/src/repository/index.js.map +1 -0
  359. package/dist/src/repository/uuid-v7.d.ts +20 -0
  360. package/dist/src/repository/uuid-v7.d.ts.map +1 -0
  361. package/dist/src/repository/uuid-v7.js +31 -0
  362. package/dist/src/repository/uuid-v7.js.map +1 -0
  363. package/dist/src/scheduling/batch-scheduled.d.ts +87 -0
  364. package/dist/src/scheduling/batch-scheduled.d.ts.map +1 -0
  365. package/dist/src/scheduling/batch-scheduled.js +170 -0
  366. package/dist/src/scheduling/batch-scheduled.js.map +1 -0
  367. package/dist/src/transaction/in-memory-transaction-manager.d.ts +16 -0
  368. package/dist/src/transaction/in-memory-transaction-manager.d.ts.map +1 -0
  369. package/dist/src/transaction/in-memory-transaction-manager.js +33 -0
  370. package/dist/src/transaction/in-memory-transaction-manager.js.map +1 -0
  371. package/dist/src/transaction/index.d.ts +2 -0
  372. package/dist/src/transaction/index.d.ts.map +1 -0
  373. package/dist/src/transaction/index.js +20 -0
  374. package/dist/src/transaction/index.js.map +1 -0
  375. package/dist/tests/contracts/index.d.ts +26 -0
  376. package/dist/tests/contracts/index.d.ts.map +1 -0
  377. package/dist/tests/contracts/index.js +37 -0
  378. package/dist/tests/contracts/index.js.map +1 -0
  379. package/dist/tests/contracts/job-repository.contract.d.ts +46 -0
  380. package/dist/tests/contracts/job-repository.contract.d.ts.map +1 -0
  381. package/dist/tests/contracts/job-repository.contract.js +644 -0
  382. package/dist/tests/contracts/job-repository.contract.js.map +1 -0
  383. package/package.json +80 -0
  384. package/src/adapters/in-process.adapter.ts +182 -0
  385. package/src/adapters/index.ts +17 -0
  386. package/src/builder/batch-builder.ts +32 -0
  387. package/src/builder/flow-builder.ts +141 -0
  388. package/src/builder/index.ts +4 -0
  389. package/src/builder/job-builder.ts +206 -0
  390. package/src/builder/step-builder.ts +190 -0
  391. package/src/compiler/builder-types.ts +27 -0
  392. package/src/compiler/definition-compiler.ts +325 -0
  393. package/src/compiler/index.ts +2 -0
  394. package/src/core/errors.ts +125 -0
  395. package/src/core/execution-context/index.ts +3 -0
  396. package/src/core/execution-context/json-value.ts +3 -0
  397. package/src/core/execution-context/serializer.ts +21 -0
  398. package/src/core/execution-context/validator.ts +103 -0
  399. package/src/core/index.ts +7 -0
  400. package/src/core/ir/decider-definition.ts +25 -0
  401. package/src/core/ir/index.ts +7 -0
  402. package/src/core/ir/job-definition.ts +15 -0
  403. package/src/core/ir/listener-definition.ts +19 -0
  404. package/src/core/ir/policy-config.ts +19 -0
  405. package/src/core/ir/refs.ts +42 -0
  406. package/src/core/ir/step-definition.ts +62 -0
  407. package/src/core/ir/transition-definition.ts +9 -0
  408. package/src/core/item/index.ts +1 -0
  409. package/src/core/item/interfaces.ts +70 -0
  410. package/src/core/repository/index.ts +2 -0
  411. package/src/core/repository/job-repository.ts +100 -0
  412. package/src/core/repository/types.ts +91 -0
  413. package/src/core/status.ts +31 -0
  414. package/src/core/transaction/index.ts +1 -0
  415. package/src/core/transaction/transaction-manager.ts +8 -0
  416. package/src/core/validation/definition-validator.ts +215 -0
  417. package/src/core/validation/index.ts +1 -0
  418. package/src/decorators/constants.ts +9 -0
  419. package/src/decorators/flow.decorator.ts +31 -0
  420. package/src/decorators/index.ts +7 -0
  421. package/src/decorators/item.decorators.ts +51 -0
  422. package/src/decorators/job.decorator.ts +16 -0
  423. package/src/decorators/listener.decorators.ts +142 -0
  424. package/src/decorators/step.decorator.ts +33 -0
  425. package/src/decorators/tasklet.decorator.ts +14 -0
  426. package/src/execution/batch-worker-runner.ts +142 -0
  427. package/src/execution/chunk-step-executor.ts +594 -0
  428. package/src/execution/execution-strategy.ts +115 -0
  429. package/src/execution/external-task-execution-strategy.ts +104 -0
  430. package/src/execution/in-process-execution-strategy.ts +207 -0
  431. package/src/execution/index.ts +13 -0
  432. package/src/execution/job-executor.ts +553 -0
  433. package/src/execution/job-explorer.ts +73 -0
  434. package/src/execution/job-key.ts +35 -0
  435. package/src/execution/job-launcher.ts +132 -0
  436. package/src/execution/job-operator.ts +127 -0
  437. package/src/execution/listener-invoker.ts +389 -0
  438. package/src/execution/ref-resolver.ts +64 -0
  439. package/src/execution/tasklet-step-executor.ts +182 -0
  440. package/src/explorer/batch-explorer.ts +251 -0
  441. package/src/explorer/index.ts +1 -0
  442. package/src/flow/flow-evaluator.ts +89 -0
  443. package/src/flow/index.ts +1 -0
  444. package/src/index.ts +24 -0
  445. package/src/io/checkpoint.ts +47 -0
  446. package/src/io/database.ts +114 -0
  447. package/src/io/file-readers.ts +191 -0
  448. package/src/io/file-writers.ts +99 -0
  449. package/src/io/index.ts +5 -0
  450. package/src/io/s3.ts +117 -0
  451. package/src/listeners/builtin-listeners.ts +151 -0
  452. package/src/listeners/index.ts +7 -0
  453. package/src/module/adapter-options.ts +38 -0
  454. package/src/module/adapter.ts +160 -0
  455. package/src/module/batch-schedule-registry.ts +0 -0
  456. package/src/module/index.ts +13 -0
  457. package/src/module/nest-batch.module.ts +674 -0
  458. package/src/module/tokens.ts +95 -0
  459. package/src/observability/event-types.ts +61 -0
  460. package/src/observability/exporters.ts +96 -0
  461. package/src/observability/index.ts +2 -0
  462. package/src/partition-helpers.ts +204 -0
  463. package/src/policies/backoff.ts +22 -0
  464. package/src/policies/index.ts +3 -0
  465. package/src/policies/retry-policy.ts +57 -0
  466. package/src/policies/skip-policy.ts +51 -0
  467. package/src/registry/index.ts +1 -0
  468. package/src/registry/job-registry.ts +42 -0
  469. package/src/repository/id-generator.ts +25 -0
  470. package/src/repository/in-memory/in-memory-job-repository.ts +334 -0
  471. package/src/repository/in-memory/index.ts +1 -0
  472. package/src/repository/index.ts +3 -0
  473. package/src/repository/uuid-v7.ts +40 -0
  474. package/src/scheduling/batch-scheduled.ts +257 -0
  475. package/src/transaction/in-memory-transaction-manager.ts +23 -0
  476. package/src/transaction/index.ts +1 -0
@@ -0,0 +1,132 @@
1
+ import { randomUUID } from 'crypto';
2
+
3
+ import { Inject, Injectable, Optional, forwardRef } from '@nestjs/common';
4
+
5
+ import { JobNotFoundError, JobExecutionAlreadyRunningError } from '../core/errors';
6
+ import { JobRepository, type JobParameters, type JobExecution } from '../core/repository';
7
+ import { JobRegistry } from '../registry/job-registry';
8
+
9
+ import {
10
+ EXECUTION_STRATEGY,
11
+ type IExecutionStrategy,
12
+ } from './execution-strategy';
13
+ import { JobExecutor } from './job-executor';
14
+ import { canonicalJobKey } from './job-key';
15
+
16
+ import type { JobDefinition } from '../core/ir';
17
+
18
+
19
+
20
+ /**
21
+ * JobLauncher — public entry point for starting a new JobExecution.
22
+ *
23
+ * Flow (pre-strategy, kept unchanged for backwards compat):
24
+ * 1. Look up the `JobDefinition` from the registry. Missing → `JobNotFoundError`.
25
+ * 2. Canonicalize `params` into a stable `jobKey` hash. Object key order,
26
+ * `null/undefined` omission, `Date → ISO` are all normalized so that
27
+ * semantically-identical params yield the same key.
28
+ * 3. `createExecutionAtomic(jobId, jobKey, params)` — idempotent
29
+ * instance get-or-create + `SELECT ... FOR UPDATE SKIP LOCKED` to
30
+ * serialize concurrent launches + running-execution check + insert,
31
+ * all in a single transaction. Throws
32
+ * `JobExecutionAlreadyRunningError` if another launch is in flight
33
+ * or an execution is already STARTING/STARTED for the same instance.
34
+ * 4. Delegate to the injected `IExecutionStrategy` (default:
35
+ * `InProcessExecutionStrategy`, which wraps `JobExecutor.execute`).
36
+ * The lock is released when the createExecutionAtomic transaction
37
+ * commits; the executor itself runs outside the lock (it can be
38
+ * long-running).
39
+ *
40
+ * `allowDuplicateInstances: true` bypasses step 2's dedup by appending a
41
+ * fresh UUID nonce to the canonical key on every call, forcing a new
42
+ * `JobInstance` each time.
43
+ *
44
+ * Polymorphic strategy (Task 11):
45
+ * The launcher is a thin facade — it owns the registry lookup, the
46
+ * canonical `jobKey` derivation, the atomic instance + concurrency
47
+ * lock, and the post-strategy `JobExecution` re-resolution. The
48
+ * actual *how* of running the job (in-process vs. transport) lives
49
+ * behind the `EXECUTION_STRATEGY` token. Sibling packages (e.g.
50
+ * `@nest-batch/bullmq`) override the token with a transport
51
+ * strategy that enqueues work; the launcher never learns about the
52
+ * transport.
53
+ *
54
+ * When no strategy is bound (e.g. direct manual construction in
55
+ * unit tests), the launcher falls back to the original direct
56
+ * `JobExecutor.execute` path. This keeps the legacy
57
+ * `tests/execution/job-launcher.test.ts` working without changes.
58
+ */
59
+ @Injectable()
60
+ export class JobLauncher {
61
+ constructor(
62
+ private readonly registry: JobRegistry,
63
+ private readonly repository: JobRepository,
64
+ @Inject(forwardRef(() => JobExecutor))
65
+ private readonly jobExecutor: JobExecutor,
66
+ @Optional()
67
+ @Inject(EXECUTION_STRATEGY)
68
+ private readonly strategy?: IExecutionStrategy,
69
+ ) {}
70
+
71
+ /**
72
+ * Launch a new job execution. Returns the final `JobExecution` after it
73
+ * has finished (status = COMPLETED | FAILED) — or, for transport
74
+ * strategies that return `{ kind: 'enqueued' }`, the latest persisted
75
+ * `JobExecution` (still in `STARTING` / `STARTED`, since the executor
76
+ * has not run on the launcher process).
77
+ *
78
+ * Throws `JobNotFoundError` if `jobId` is not registered.
79
+ * Throws `JobExecutionAlreadyRunningError` if a previous launch of the
80
+ * same `jobName + jobKey` is still in flight.
81
+ */
82
+ async launch(jobId: string, params: JobParameters = {}): Promise<JobExecution> {
83
+ const jobDef = this.registry.get(jobId); // throws JobNotFoundError on miss
84
+ const canonical = canonicalJobKey(params);
85
+ const jobKey = jobDef.allowDuplicateInstances ? `${canonical}::${randomUUID()}` : canonical;
86
+
87
+ // Atomic get-or-create + lock + check + insert. The repository's
88
+ // implementation uses INSERT ... ON CONFLICT DO NOTHING +
89
+ // FOR UPDATE SKIP LOCKED to serialize concurrent launches.
90
+ const execution = await this.repository.createExecutionAtomic(jobId, jobKey, params);
91
+
92
+ // No strategy bound → fall back to the legacy direct-execute path.
93
+ // This branch preserves `tests/execution/job-launcher.test.ts` (which
94
+ // wires the launcher by hand, no Nest module) and any other
95
+ // direct-construction consumer.
96
+ if (this.strategy === undefined) {
97
+ return this.jobExecutor.execute(execution, jobDef);
98
+ }
99
+
100
+ // The strategy's `LaunchResult` is intentionally discarded: the
101
+ // public `JobLauncher.launch` contract always returns a
102
+ // `JobExecution` (re-resolved from the repository below), and
103
+ // `queueJobId` is for the strategy's own bookkeeping only.
104
+ await this.strategy.launch(jobDef, params, {
105
+ executionId: execution.id,
106
+ jobExecutionId: execution.id,
107
+ });
108
+
109
+ // For both `completed` and `enqueued` the public `JobLauncher.launch`
110
+ // contract returns a `JobExecution`. Re-resolve the latest persisted
111
+ // row so the caller sees the executor's writes (status, endTime,
112
+ // exitCode) without us hand-merging patches here. If the lookup
113
+ // somehow returns null (e.g. a custom repository that drops rows),
114
+ // fall back to the execution the launcher pre-created.
115
+ const latest = await this.repository.getJobExecution(execution.id);
116
+ return latest ?? execution;
117
+ }
118
+
119
+ /**
120
+ * Resume an existing `JobExecution` (used by the restart path).
121
+ * For this MVP it just delegates to the executor — the launcher does
122
+ * not yet gate on restartable / concurrency / FAILED-prev-exists rules.
123
+ *
124
+ * Note: `run` bypasses the strategy on purpose. Restart is a
125
+ * recovery path on the in-process execution model; transport
126
+ * strategies that need their own resume semantics can override
127
+ * this method on a subclass or via a future token.
128
+ */
129
+ async run(execution: JobExecution, jobDef: JobDefinition): Promise<JobExecution> {
130
+ return this.jobExecutor.execute(execution, jobDef);
131
+ }
132
+ }
@@ -0,0 +1,127 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ import { Injectable } from '@nestjs/common';
4
+
5
+ import { InvalidJobOperationError } from '../core/errors';
6
+ import {
7
+ JobExecution,
8
+ JobExecutionDetails,
9
+ JobExecutionFilter,
10
+ JobInstance,
11
+ JobInstanceFilter,
12
+ JobParameters,
13
+ JobRepository,
14
+ } from '../core/repository';
15
+ import type { JobDefinition } from '../core/ir';
16
+ import { JobStatus } from '../core/status';
17
+ import { JobRegistry } from '../registry/job-registry';
18
+
19
+ import { JobExplorer } from './job-explorer';
20
+ import { JobLauncher } from './job-launcher';
21
+
22
+ @Injectable()
23
+ export class JobOperator {
24
+ constructor(
25
+ private readonly explorer: JobExplorer,
26
+ private readonly registry: JobRegistry,
27
+ private readonly repository: JobRepository,
28
+ private readonly launcher: JobLauncher,
29
+ ) {}
30
+
31
+ listJobs(): JobDefinition[] {
32
+ return this.explorer.listJobs();
33
+ }
34
+
35
+ listJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {
36
+ return this.explorer.listJobInstances(filter);
37
+ }
38
+
39
+ listJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {
40
+ return this.explorer.listJobExecutions(filter);
41
+ }
42
+
43
+ getJobExecutionDetails(executionId: string): Promise<JobExecutionDetails> {
44
+ return this.explorer.getJobExecutionDetails(executionId);
45
+ }
46
+
47
+ async stop(executionId: string): Promise<JobExecutionDetails> {
48
+ const details = await this.explorer.getJobExecutionDetails(executionId);
49
+ const status = details.jobExecution.status;
50
+
51
+ if (status === JobStatus.STOPPED) {
52
+ return details;
53
+ }
54
+ if (!this.isActive(status)) {
55
+ throw new InvalidJobOperationError('stop', `Cannot stop execution in ${status} state`, {
56
+ executionId,
57
+ status,
58
+ });
59
+ }
60
+
61
+ await this.repository.updateJobExecution(executionId, {
62
+ status: JobStatus.STOPPED,
63
+ endTime: new Date(),
64
+ exitCode: 'STOPPED',
65
+ exitMessage: 'Stopped by JobOperator',
66
+ });
67
+
68
+ return this.explorer.getJobExecutionDetails(executionId);
69
+ }
70
+
71
+ async abandon(executionId: string): Promise<JobExecutionDetails> {
72
+ const details = await this.explorer.getJobExecutionDetails(executionId);
73
+ const status = details.jobExecution.status;
74
+
75
+ if (status === JobStatus.ABANDONED) {
76
+ return details;
77
+ }
78
+ if (this.isActive(status) || status === JobStatus.COMPLETED) {
79
+ throw new InvalidJobOperationError(
80
+ 'abandon',
81
+ `Cannot abandon execution in ${status} state`,
82
+ { executionId, status },
83
+ );
84
+ }
85
+
86
+ await this.repository.updateJobExecution(executionId, {
87
+ status: JobStatus.ABANDONED,
88
+ endTime: details.jobExecution.endTime ?? new Date(),
89
+ exitCode: 'ABANDONED',
90
+ exitMessage: 'Abandoned by JobOperator',
91
+ });
92
+
93
+ return this.explorer.getJobExecutionDetails(executionId);
94
+ }
95
+
96
+ async restart(executionId: string): Promise<JobExecutionDetails> {
97
+ const details = await this.explorer.getJobExecutionDetails(executionId);
98
+ const status = details.jobExecution.status;
99
+
100
+ if (status !== JobStatus.FAILED && status !== JobStatus.STOPPED) {
101
+ throw new InvalidJobOperationError(
102
+ 'restart',
103
+ `Cannot restart execution in ${status} state`,
104
+ { executionId, status },
105
+ );
106
+ }
107
+
108
+ const jobDef = this.registry.get(details.jobInstance.jobName);
109
+ const execution = await this.launcher.run(details.jobExecution, jobDef);
110
+ return this.explorer.getJobExecutionDetails(execution.id);
111
+ }
112
+
113
+ startNextInstance(jobId: string, params: JobParameters = {}): Promise<JobExecution> {
114
+ return this.launcher.launch(jobId, {
115
+ ...params,
116
+ _nestBatchRunId: randomUUID(),
117
+ });
118
+ }
119
+
120
+ private isActive(status: JobStatus): boolean {
121
+ return (
122
+ status === JobStatus.STARTING ||
123
+ status === JobStatus.STARTED ||
124
+ status === JobStatus.STOPPING
125
+ );
126
+ }
127
+ }
@@ -0,0 +1,389 @@
1
+ /**
2
+ * ListenerInvoker — orchestrates lifecycle listeners around step / chunk / job /
3
+ * item / skip execution.
4
+ *
5
+ * Supports the 7 listener kinds declared in `ListenerKind` (job / step / chunk /
6
+ * item-read / item-process / item-write / skip) with proper failure policy:
7
+ *
8
+ * - default → listener throw propagates
9
+ * - `nonCritical: true` → log + continue with the next listener
10
+ *
11
+ * Two resolver-map shapes are supported:
12
+ *
13
+ * 1. Legacy (Task 17) — `Map<string, ListenerResolver>` keyed by
14
+ * `${phase}-${kind}:${name}` (e.g. `before-step:MyListener`). The
15
+ * convenience methods `invokeBeforeStep / invokeAfterStep /
16
+ * invokeOnErrorStep` operate on this shape and remain in place for
17
+ * backward compatibility with the Wave-3 step executor.
18
+ *
19
+ * 2. Current (Task 20) — `Map<string, ListenerEntry>` keyed by
20
+ * `${phase}:${kind}:${name}` (e.g. `before:step:MyListener`,
21
+ * `on-skip:read:MySkipListener`). This is the shape consumed by
22
+ * `invokeBefore / invokeAfter / invokeOnError / invokeOnSkipRead /
23
+ * invokeOnSkipProcess / invokeOnSkipWrite` and is the source of truth for
24
+ * all 7 listener kinds going forward.
25
+ *
26
+ * Registration order is preserved (Map iteration is insertion-ordered in JS).
27
+ */
28
+ import { Injectable, Logger } from '@nestjs/common';
29
+ import type { ExecutionContext } from '../core/repository';
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Public types
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Listener function — signature is determined by the listener's kind/phase.
37
+ *
38
+ * Typed as `any[]` rather than `unknown[]` so listeners can be authored with
39
+ * the narrower, kind-specific signatures (e.g. `(ctx) => void`,
40
+ * `(item, ctx) => void`, `(err, item) => void`) without TypeScript rejecting
41
+ * the assignment to `ListenerEntry.fn`. The runtime contract is enforced by
42
+ * the invoker's kind-aware `buildCallArgs`, not by the type system.
43
+ */
44
+ export type ListenerFn = (...args: any[]) => any;
45
+
46
+ /**
47
+ * Resolver-map entry used by the current (Task 20) API.
48
+ *
49
+ * - `fn` — the actual listener function to invoke
50
+ * - `nonCritical` — when true, failures from this listener are logged and
51
+ * suppressed; otherwise the failure propagates out of the
52
+ * `invoke*` call and aborts the surrounding executor.
53
+ */
54
+ export interface ListenerEntry {
55
+ fn: ListenerFn;
56
+ nonCritical?: boolean;
57
+ }
58
+
59
+ /** Resolver map consumed by the current (Task 20) `invoke*` methods. */
60
+ export type ResolverMap = Map<string, ListenerEntry>;
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Legacy types (Task 17 backward compatibility)
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /** Phase prefix constants used by the legacy convenience methods. */
67
+ export const LISTENER_PHASE_PREFIX = {
68
+ BeforeStep: 'before-step:',
69
+ AfterStep: 'after-step:',
70
+ OnStepError: 'on-step-error:',
71
+ } as const;
72
+
73
+ /** Legacy resolver function type — bare callable, no per-entry metadata. */
74
+ export type ListenerResolver = (...args: unknown[]) => unknown | Promise<unknown>;
75
+
76
+ export interface StepListenerContext extends ListenerContext {
77
+ jobExecutionId: string;
78
+ stepExecutionId: string;
79
+ getExecutionContext?: () => Promise<ExecutionContext>;
80
+ saveExecutionContext?: (ctx: ExecutionContext) => Promise<void>;
81
+ }
82
+
83
+ export interface StepListenerResult {
84
+ status: string;
85
+ exitCode?: string;
86
+ exitMessage?: string;
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Current (Task 20) phase / kind constants
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Phase segment for the current key format `${phase}:${kind}:${name}`.
95
+ *
96
+ * - `before` — `invokeBefore`
97
+ * - `after` — `invokeAfter`
98
+ * - `on-error` — `invokeOnError` (job / step / chunk)
99
+ * - `on-skip` — `invokeOnSkipRead / Process / Write` (the trailing kind
100
+ * segment is one of `read` / `process` / `write`)
101
+ */
102
+ export const LISTENER_PHASE = {
103
+ Before: 'before',
104
+ After: 'after',
105
+ OnError: 'on-error',
106
+ OnSkip: 'on-skip',
107
+ } as const;
108
+
109
+ /** Phase kinds that share the standard `${phase}:${kind}:${name}` shape. */
110
+ export type LifecyclePhaseKind =
111
+ | 'job'
112
+ | 'step'
113
+ | 'chunk'
114
+ | 'item-read'
115
+ | 'item-process'
116
+ | 'item-write';
117
+
118
+ /** Phase kinds accepted by `invokeOnError` (subset of the lifecycle kinds). */
119
+ export type OnErrorKind = 'job' | 'step' | 'chunk';
120
+
121
+ /**
122
+ * Sub-kinds for the `on-skip` phase. The resolver key looks like
123
+ * `on-skip:${SkipSubKind}:${name}` — for example `on-skip:read:MySkipListener`.
124
+ */
125
+ export type SkipSubKind = 'read' | 'process' | 'write';
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Implementation
129
+ // ---------------------------------------------------------------------------
130
+
131
+ @Injectable()
132
+ export class ListenerInvoker {
133
+ private readonly logger = new Logger(ListenerInvoker.name);
134
+
135
+ // -------------------------------------------------------------------------
136
+ // Current (Task 20) API — supports all 7 listener kinds with failure policy
137
+ // -------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Invoke every `before:<kind>:<name>` resolver, in registration order.
141
+ *
142
+ * Listener signature depends on `<kind>`:
143
+ * - `job` / `chunk` — `fn(ctx)`
144
+ * - `step` — `fn(ctx, result)` (the optional `args` is the result)
145
+ * - `item-read` / `item-process` / `item-write` — `fn(item, ctx)` (the
146
+ * optional `args` is the item, placed in the first position by
147
+ * convention)
148
+ */
149
+ async invokeBefore(
150
+ resolvers: ResolverMap,
151
+ kind: LifecyclePhaseKind,
152
+ ctx: ListenerContext,
153
+ args?: unknown,
154
+ ): Promise<void> {
155
+ const callArgs = this.buildCallArgs(kind, ctx, args);
156
+ await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:${kind}:`, callArgs);
157
+ }
158
+
159
+ /**
160
+ * Invoke every `after:<kind>:<name>` resolver, in registration order.
161
+ * Same signature rules as `invokeBefore`.
162
+ */
163
+ async invokeAfter(
164
+ resolvers: ResolverMap,
165
+ kind: LifecyclePhaseKind,
166
+ ctx: ListenerContext,
167
+ args?: unknown,
168
+ ): Promise<void> {
169
+ const callArgs = this.buildCallArgs(kind, ctx, args);
170
+ await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:${kind}:`, callArgs);
171
+ }
172
+
173
+ /**
174
+ * Invoke every `on-error:<kind>:<name>` resolver, in registration order.
175
+ * Listener signature is `fn(ctx, err)`.
176
+ */
177
+ async invokeOnError(
178
+ resolvers: ResolverMap,
179
+ kind: OnErrorKind,
180
+ ctx: ListenerContext,
181
+ err: unknown,
182
+ ): Promise<void> {
183
+ await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:${kind}:`, [ctx, err]);
184
+ }
185
+
186
+ /** Invoke every `on-skip:read:<name>` resolver. Listener signature: `fn(err, item)`. */
187
+ async invokeOnSkipRead(
188
+ resolvers: ResolverMap,
189
+ err: unknown,
190
+ item: unknown,
191
+ ): Promise<void> {
192
+ await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:read:`, [err, item]);
193
+ }
194
+
195
+ /** Invoke every `on-skip:process:<name>` resolver. Listener signature: `fn(item, err)`. */
196
+ async invokeOnSkipProcess(
197
+ resolvers: ResolverMap,
198
+ item: unknown,
199
+ err: unknown,
200
+ ): Promise<void> {
201
+ await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:process:`, [item, err]);
202
+ }
203
+
204
+ /** Invoke every `on-skip:write:<name>` resolver. Listener signature: `fn(items, err)`. */
205
+ async invokeOnSkipWrite(
206
+ resolvers: ResolverMap,
207
+ items: unknown[],
208
+ err: unknown,
209
+ ): Promise<void> {
210
+ await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:write:`, [items, err]);
211
+ }
212
+
213
+ // -------------------------------------------------------------------------
214
+ // Legacy (Task 17) convenience methods — preserved for backward compat.
215
+ // They now delegate to the current (Task 20) API by converting the legacy
216
+ // `Map<string, ListenerResolver>` shape into a `ResolverMap` and routing
217
+ // through the shared `invokeMatching` internal path. One source of truth
218
+ // for listener dispatch, two public surfaces.
219
+ // -------------------------------------------------------------------------
220
+
221
+ /**
222
+ * Invoke all `before-step:*` resolvers in Map insertion order. Operates on
223
+ * the legacy `Map<string, ListenerResolver>` shape; the current
224
+ * `invokeBefore(resolvers, 'step', ...)` should be preferred for new code.
225
+ *
226
+ * Implementation: convert the legacy `before-step:*` entries into the new
227
+ * `before:step:*` shape and delegate to `invokeBefore` so the dispatch /
228
+ * `nonCritical` policy is shared with the rest of the invoker.
229
+ */
230
+ async invokeBeforeStep(
231
+ resolvers: Map<string, ListenerResolver>,
232
+ ctx: StepListenerContext,
233
+ ): Promise<void> {
234
+ const newResolvers = this.convertLegacyResolvers(
235
+ resolvers,
236
+ LISTENER_PHASE_PREFIX.BeforeStep,
237
+ LISTENER_PHASE.Before,
238
+ );
239
+ await this.invokeBefore(newResolvers, 'step', ctx);
240
+ }
241
+
242
+ /**
243
+ * Invoke all `after-step:*` resolvers, receiving the step result as the
244
+ * second argument. Legacy shape; see `invokeAfter(resolvers, 'step', ...)`
245
+ * for the current API.
246
+ */
247
+ async invokeAfterStep(
248
+ resolvers: Map<string, ListenerResolver>,
249
+ ctx: StepListenerContext,
250
+ result: StepListenerResult,
251
+ ): Promise<void> {
252
+ const newResolvers = this.convertLegacyResolvers(
253
+ resolvers,
254
+ LISTENER_PHASE_PREFIX.AfterStep,
255
+ LISTENER_PHASE.After,
256
+ );
257
+ await this.invokeAfter(newResolvers, 'step', ctx, result);
258
+ }
259
+
260
+ /**
261
+ * Invoke all `on-step-error:*` resolvers, receiving the thrown error as the
262
+ * second argument. Legacy shape; see `invokeOnError(resolvers, 'step', ...)`
263
+ * for the current API.
264
+ */
265
+ async invokeOnErrorStep(
266
+ resolvers: Map<string, ListenerResolver>,
267
+ ctx: StepListenerContext,
268
+ err: unknown,
269
+ ): Promise<void> {
270
+ const newResolvers = this.convertLegacyResolvers(
271
+ resolvers,
272
+ LISTENER_PHASE_PREFIX.OnStepError,
273
+ LISTENER_PHASE.OnError,
274
+ );
275
+ await this.invokeOnError(newResolvers, 'step', ctx, err);
276
+ }
277
+
278
+ /**
279
+ * Translate legacy `before-step:` / `after-step:` / `on-step-error:` keys
280
+ * into the new `${phase}:step:${name}` shape. Only entries whose key
281
+ * starts with the given legacy prefix are forwarded; everything else is
282
+ * silently dropped (matches the prefix-filter semantics of the original
283
+ * legacy loops).
284
+ *
285
+ * `nonCritical` is not representable in the legacy `ListenerResolver`
286
+ * shape (bare function values), so the converted entries always carry
287
+ * `nonCritical: undefined` — i.e. critical propagation. This preserves
288
+ * the legacy contract exactly.
289
+ */
290
+ private convertLegacyResolvers(
291
+ legacy: Map<string, ListenerResolver>,
292
+ legacyPrefix: string,
293
+ newPhase: string,
294
+ ): ResolverMap {
295
+ const out: ResolverMap = new Map();
296
+ for (const [key, fn] of legacy.entries()) {
297
+ if (!key.startsWith(legacyPrefix)) continue;
298
+ const newKey = `${newPhase}:step:${key.slice(legacyPrefix.length)}`;
299
+ out.set(newKey, { fn: fn as ListenerFn });
300
+ }
301
+ return out;
302
+ }
303
+
304
+ // -------------------------------------------------------------------------
305
+ // Internals
306
+ // -------------------------------------------------------------------------
307
+
308
+ /**
309
+ * Compute the positional argument list to forward to a before/after
310
+ * listener, based on the listener's kind.
311
+ *
312
+ * - `job` / `chunk` → `[ctx]`
313
+ * - `step` → `[ctx, args]` (args is the result)
314
+ * - `item-read` /
315
+ * `item-process` /
316
+ * `item-write` → `[args, ctx]` (args is the item, leading position)
317
+ */
318
+ private buildCallArgs(
319
+ kind: LifecyclePhaseKind,
320
+ ctx: ListenerContext,
321
+ args: unknown,
322
+ ): unknown[] {
323
+ switch (kind) {
324
+ case 'item-read':
325
+ case 'item-process':
326
+ case 'item-write':
327
+ return [args, ctx];
328
+ case 'step':
329
+ return [ctx, args];
330
+ case 'job':
331
+ case 'chunk':
332
+ return [ctx];
333
+ default: {
334
+ // exhaustive guard
335
+ const _exhaustive: never = kind;
336
+ void _exhaustive;
337
+ return [ctx];
338
+ }
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Iterate the resolver map in insertion order, invoke every entry whose key
344
+ * starts with `prefix`, and apply the failure policy:
345
+ *
346
+ * - if the entry is missing / the function rejects:
347
+ * - `nonCritical: true` → log a warning, swallow, continue
348
+ * - otherwise → re-throw, aborting the surrounding executor
349
+ */
350
+ private async invokeMatching(
351
+ resolvers: ResolverMap,
352
+ prefix: string,
353
+ args: unknown[],
354
+ ): Promise<void> {
355
+ for (const [key, entry] of resolvers.entries()) {
356
+ if (!key.startsWith(prefix)) continue;
357
+ try {
358
+ await entry.fn(...args);
359
+ } catch (err) {
360
+ if (entry.nonCritical) {
361
+ this.logger.warn(
362
+ `[ListenerInvoker] non-critical listener "${key}" failed: ${formatError(err)}`,
363
+ );
364
+ continue;
365
+ }
366
+ throw err;
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // Helpers
374
+ // ---------------------------------------------------------------------------
375
+
376
+ /** Shared listener-context payload. All fields are optional because the
377
+ * caller (executor) may not always have a stepExecutionId at hand (e.g. for
378
+ * job-level listeners). */
379
+ export interface ListenerContext {
380
+ jobExecutionId: string;
381
+ stepExecutionId?: string;
382
+ stepName?: string;
383
+ /** Arbitrary, executor-supplied metadata (transaction context, etc.). */
384
+ [extra: string]: unknown;
385
+ }
386
+
387
+ function formatError(err: unknown): string {
388
+ return err instanceof Error ? `${err.name}: ${err.message}` : String(err);
389
+ }