@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,553 @@
1
+ import { Injectable, Inject, Optional, forwardRef, Logger } from '@nestjs/common';
2
+ import type { JobDefinition, ListenerDefinition } from '../core/ir';
3
+ import { RefKind, type ListenerRef } from '../core/ir';
4
+ import { JobRepository, type JobParameters, type JobExecution } from '../core/repository';
5
+ import { TransactionManager } from '../core/transaction';
6
+ import { StepStatus, JobStatus, FlowExecutionStatus } from '../core/status';
7
+ import { JobNotRestartableError } from '../core/errors';
8
+ import { TaskletStepExecutor, type StepExecutionResult } from './tasklet-step-executor';
9
+ import { ChunkStepExecutor, type ChunkExecutionResult } from './chunk-step-executor';
10
+ import {
11
+ ListenerInvoker,
12
+ type ResolverMap,
13
+ type ListenerResolver,
14
+ } from './listener-invoker';
15
+ import { FlowEvaluator } from '../flow/flow-evaluator';
16
+ import {
17
+ BATCH_EVENT,
18
+ NoopBatchObserver,
19
+ type BatchEvent,
20
+ type BatchObserver,
21
+ } from '../observability';
22
+
23
+ /**
24
+ * Result type that covers both tasklet and chunk step outcomes.
25
+ * Structurally compatible with `StepExecutionPatch` so the executor
26
+ * can forward it directly to `updateStepExecution`.
27
+ *
28
+ * The only field chunk has but tasklet doesn't is `commitCount`; for
29
+ * tasklet results it stays `undefined` and `updateStepExecution`
30
+ * happily ignores undefined fields (merge semantics, see repository).
31
+ */
32
+ type StepResult = StepExecutionResult | ChunkExecutionResult;
33
+
34
+ /**
35
+ * JobExecutor — drives a single JobExecution to completion.
36
+ *
37
+ * Flow (per ORACLE verdict 3c):
38
+ * 1. Mark execution as STARTED.
39
+ * 2. `before:job:*` listeners.
40
+ * 3. Loop:
41
+ * a. Look up the current step (jobDef.steps[currentStepId]). If the
42
+ * step is missing, mark the job FAILED with exit code
43
+ * `NO_SUCH_STEP` and break.
44
+ * b. Create a StepExecution, run it (tasklet or chunk), and persist
45
+ * the result via `updateStepExecution`. During the run, the
46
+ * step's own `after-step:*` listeners fire (see
47
+ * `TaskletStepExecutor.execute` step 4 / `ChunkStepExecutor`).
48
+ * Those listeners run BEFORE we evaluate transitions so they get
49
+ * a chance to mutate the result (e.g. flip COMPLETED → FAILED)
50
+ * and the resulting flow routing sees the override.
51
+ * c. Map the (possibly overridden) step status to a
52
+ * `FlowExecutionStatus` and ask the `FlowEvaluator` for the next
53
+ * step. `null` means END.
54
+ * d. If the step FAILED and the evaluator returned `null`
55
+ * (no recovery transition matches), short-circuit the job to
56
+ * FAILED — we must not continue running subsequent steps
57
+ * declared in the graph, because none are reachable.
58
+ * 4. `after:job:*` listeners (with the final status).
59
+ *
60
+ * Out of scope (future tasks):
61
+ * - Concurrency control (Task 38).
62
+ * - `on-error:job:*` listener invocation when the executor itself
63
+ * throws (the catch block can be wired to it in a follow-up).
64
+ */
65
+ @Injectable()
66
+ export class JobExecutor {
67
+ private readonly logger = new Logger(JobExecutor.name);
68
+
69
+ constructor(
70
+ private readonly repository: JobRepository,
71
+ @Inject(forwardRef(() => TransactionManager))
72
+ private readonly transactionManager: TransactionManager,
73
+ private readonly taskletExecutor: TaskletStepExecutor,
74
+ private readonly chunkExecutor: ChunkStepExecutor,
75
+ private readonly listenerInvoker: ListenerInvoker,
76
+ private readonly flowEvaluator: FlowEvaluator,
77
+ @Optional()
78
+ private readonly observer: BatchObserver = new NoopBatchObserver(),
79
+ ) {}
80
+
81
+ /**
82
+ * Execute a JobExecution against its `JobDefinition`. Returns the
83
+ * final, persisted `JobExecution` (status = COMPLETED | FAILED).
84
+ *
85
+ * Restart behavior (per Metis verdict 3b — restartable opt-in, per
86
+ * ORACLE 3b — default-on for persisted repositories):
87
+ * - If `execution.status` is `FAILED` on entry, this is a restart
88
+ * attempt. We require `jobDef.restartable === true`; otherwise we
89
+ * throw `JobNotRestartableError` and leave the execution alone.
90
+ * - For each chunk step, we look up the latest FAILED StepExecution
91
+ * for that step in this job execution. If one exists, we read its
92
+ * ExecutionContext's `lastChunkIndex` checkpoint and pass it to the
93
+ * `ChunkStepExecutor` as `resumeFromChunkIndex`, which then skips
94
+ * chunks ≤ that index. Tasklet steps always re-run from scratch
95
+ * (they have no chunk-level resume granularity in this MVP).
96
+ *
97
+ * Partition routing (T8): the optional third argument carries the
98
+ * `partitionIndex` / `partitionCount` pair the transport attached
99
+ * to the job. When set, the chunk executor bounds the read loop
100
+ * to the partition's range (see `packages/core/src/partition-helpers.ts`).
101
+ */
102
+ async execute(
103
+ execution: JobExecution,
104
+ jobDef: JobDefinition,
105
+ partition?: { partitionIndex?: number; partitionCount?: number },
106
+ ): Promise<JobExecution> {
107
+ // Capture the pre-execute status. For a fresh launch, the launcher
108
+ // created the execution with status STARTING; for a restart, the
109
+ // caller (JobLauncher.run) hands us a terminal execution that can
110
+ // be resumed.
111
+ const isRestart =
112
+ execution.status === JobStatus.FAILED || execution.status === JobStatus.STOPPED;
113
+ if (isRestart && !jobDef.restartable) {
114
+ throw new JobNotRestartableError(jobDef.id);
115
+ }
116
+
117
+ await this.repository.updateJobExecution(execution.id, {
118
+ status: JobStatus.STARTED,
119
+ startTime: new Date(),
120
+ });
121
+
122
+ await this.emit({
123
+ type: BATCH_EVENT.JOB_STARTED,
124
+ timestamp: new Date(),
125
+ jobExecutionId: execution.id,
126
+ data: { jobName: jobDef.id },
127
+ });
128
+
129
+ // Build the full resolver map once. The same map powers both the
130
+ // job-level `invokeBefore` / `invokeAfter` calls below and the
131
+ // step-level resolvers handed to the TaskletStepExecutor (derived
132
+ // by `buildLegacyStepResolvers` into the legacy key shape). Building
133
+ // it once per execution avoids re-walking the IR on every step.
134
+ const jobResolvers = this.buildResolverMap(jobDef);
135
+ const stepResolvers = this.buildLegacyStepResolvers(jobResolvers);
136
+
137
+ await this.listenerInvoker.invokeBefore(jobResolvers, 'job', {
138
+ jobExecutionId: execution.id,
139
+ stepExecutionId: '<job>',
140
+ });
141
+
142
+ // Cache the step order once. `Object.keys` returns insertion order
143
+ // for string keys (per ES2015+), so this is the canonical
144
+ // declaration order — used for the linear fallback below.
145
+ const stepOrder = Object.keys(jobDef.steps);
146
+
147
+ let currentStepId: string | null = jobDef.startStepId;
148
+ let finalStatus: JobStatus = JobStatus.COMPLETED;
149
+ let currentStepExecutionId: string | null = null;
150
+
151
+ try {
152
+ while (currentStepId !== null) {
153
+ const step = jobDef.steps[currentStepId];
154
+ if (!step) {
155
+ await this.repository.updateJobExecution(execution.id, {
156
+ status: JobStatus.FAILED,
157
+ endTime: new Date(),
158
+ exitCode: 'NO_SUCH_STEP',
159
+ exitMessage: `Step "${currentStepId}" not found`,
160
+ });
161
+ finalStatus = JobStatus.FAILED;
162
+ break;
163
+ }
164
+
165
+ // Restart path: if this is a restart and the current step is a
166
+ // chunk step, locate the latest FAILED step execution for the
167
+ // same step name and load its `lastChunkIndex` checkpoint. That
168
+ // value is passed to the chunk executor as `resumeFromChunkIndex`.
169
+ // For tasklet steps (and chunk steps with no prior failure) we
170
+ // leave `resumeFromChunkIndex` undefined — the chunk executor
171
+ // treats undefined as "start from the beginning".
172
+ //
173
+ // Look this up BEFORE createStepExecution so the just-created
174
+ // STARTING step isn't returned as the "latest" entry.
175
+ let resumeFromChunkIndex: number | undefined;
176
+ if (isRestart && step.kind === 'chunk') {
177
+ const priorFailed = await this.repository.findLatestStepExecution(execution.id, step.id);
178
+ if (priorFailed && priorFailed.status === StepStatus.FAILED) {
179
+ resumeFromChunkIndex = await this.getLastCheckpoint(priorFailed.id);
180
+ }
181
+ }
182
+
183
+ const stepExecution = await this.repository.createStepExecution(execution.id, step.id);
184
+ currentStepExecutionId = stepExecution.id;
185
+
186
+ await this.emit({
187
+ type: BATCH_EVENT.STEP_STARTED,
188
+ timestamp: new Date(),
189
+ jobExecutionId: execution.id,
190
+ stepExecutionId: stepExecution.id,
191
+ data: { stepId: step.id, kind: step.kind },
192
+ });
193
+
194
+ let result: StepResult;
195
+ try {
196
+ if (step.kind === 'tasklet') {
197
+ result = await this.taskletExecutor.execute(step, {
198
+ jobExecutionId: execution.id,
199
+ jobRepository: this.repository,
200
+ transactionManager: this.transactionManager,
201
+ listenerInvoker: this.listenerInvoker,
202
+ listenerResolvers: stepResolvers,
203
+ });
204
+ } else {
205
+ // Forward the partition routing (T8) when the transport
206
+ // supplied one. The chunk executor uses the partition
207
+ // index + count + the step's `partitions.range` to bound
208
+ // its read loop. Absent partition args fall through to
209
+ // the 0.1.0 non-partitioned chunk pipeline.
210
+ const partitionArgs =
211
+ partition?.partitionIndex !== undefined && partition?.partitionCount !== undefined
212
+ ? {
213
+ partitionIndex: partition.partitionIndex,
214
+ partitionCount: partition.partitionCount,
215
+ }
216
+ : {};
217
+ result = await this.chunkExecutor.execute(step, {
218
+ jobExecutionId: execution.id,
219
+ stepExecutionId: stepExecution.id,
220
+ jobRepository: this.repository,
221
+ transactionManager: this.transactionManager,
222
+ listenerInvoker: this.listenerInvoker,
223
+ jobExecutionId2: execution.id,
224
+ resolvers: new Map(),
225
+ ...(resumeFromChunkIndex !== undefined ? { resumeFromChunkIndex } : {}),
226
+ ...partitionArgs,
227
+ });
228
+ }
229
+ } catch (stepErr) {
230
+ // The executor itself threw (e.g. resolveReader threw in a
231
+ // chunk step before the executor's own try-catch could catch
232
+ // it). Persist the step as FAILED with the error message and
233
+ // re-raise so the outer handler marks the job FAILED.
234
+ await this.repository.updateStepExecution(stepExecution.id, {
235
+ status: StepStatus.FAILED,
236
+ exitCode: 'FAILED',
237
+ exitMessage: stepErr instanceof Error ? stepErr.message : String(stepErr),
238
+ endTime: new Date(),
239
+ });
240
+ currentStepExecutionId = null;
241
+ throw stepErr;
242
+ }
243
+ currentStepExecutionId = null;
244
+
245
+ await this.repository.updateStepExecution(stepExecution.id, {
246
+ status: result.status,
247
+ ...(result.readCount !== undefined ? { readCount: result.readCount } : {}),
248
+ ...(result.writeCount !== undefined ? { writeCount: result.writeCount } : {}),
249
+ ...(result.skipCount !== undefined ? { skipCount: result.skipCount } : {}),
250
+ ...('commitCount' in result && result.commitCount !== undefined
251
+ ? { commitCount: result.commitCount }
252
+ : {}),
253
+ exitCode: result.exitCode,
254
+ exitMessage: result.exitMessage,
255
+ endTime: new Date(),
256
+ });
257
+
258
+ await this.emit({
259
+ type:
260
+ result.status === StepStatus.COMPLETED
261
+ ? BATCH_EVENT.STEP_COMPLETED
262
+ : result.status === StepStatus.FAILED
263
+ ? BATCH_EVENT.STEP_FAILED
264
+ : BATCH_EVENT.STEP_COMPLETED,
265
+ timestamp: new Date(),
266
+ jobExecutionId: execution.id,
267
+ stepExecutionId: stepExecution.id,
268
+ data: {
269
+ stepId: step.id,
270
+ status: result.status,
271
+ ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
272
+ },
273
+ });
274
+
275
+ // Map StepStatus -> FlowExecutionStatus. Anything other than
276
+ // COMPLETED/FAILED collapses to UNKNOWN → evaluator returns
277
+ // null → flow ends.
278
+ const flowStatus: FlowExecutionStatus =
279
+ result.status === StepStatus.COMPLETED
280
+ ? FlowExecutionStatus.COMPLETED
281
+ : result.status === StepStatus.FAILED
282
+ ? FlowExecutionStatus.FAILED
283
+ : FlowExecutionStatus.UNKNOWN;
284
+ const deciderExitStatus = await this.resolveDeciderExitStatus(
285
+ jobDef,
286
+ currentStepId,
287
+ {
288
+ jobExecution: execution,
289
+ stepId: step.id,
290
+ stepExecutionId: stepExecution.id,
291
+ stepStatus: result.status,
292
+ exitCode: result.exitCode,
293
+ exitMessage: result.exitMessage,
294
+ },
295
+ );
296
+ const flowExitStatus = deciderExitStatus ?? (result.exitCode || flowStatus);
297
+
298
+ let evaluatorResult = await this.flowEvaluator.evaluate(
299
+ jobDef.transitions,
300
+ currentStepId,
301
+ flowExitStatus,
302
+ );
303
+
304
+ // Distinguish "no transition declared" (linear fallback) from
305
+ // "transition declared with toStepId: null" (explicit END).
306
+ // FlowEvaluator returns null for both, so we inspect the
307
+ // transition list directly.
308
+ let hasMatchingTransition = jobDef.transitions.some((t) =>
309
+ this.flowEvaluator.matches(t, currentStepId!, flowExitStatus),
310
+ );
311
+ if (!hasMatchingTransition && flowExitStatus !== flowStatus) {
312
+ evaluatorResult = await this.flowEvaluator.evaluate(
313
+ jobDef.transitions,
314
+ currentStepId,
315
+ flowStatus,
316
+ );
317
+ hasMatchingTransition = jobDef.transitions.some((t) =>
318
+ this.flowEvaluator.matches(t, currentStepId!, flowStatus),
319
+ );
320
+ }
321
+
322
+ let nextStepId: string | null;
323
+ if (hasMatchingTransition) {
324
+ // Explicit transition: respect its target, including null
325
+ // (END). Do not fall through to linear order.
326
+ nextStepId = evaluatorResult;
327
+ } else if (result.status === StepStatus.FAILED) {
328
+ // FAILED with no matching transition → short-circuit. The
329
+ // graph declares no path forward, so the job is FAILED — we
330
+ // must not invent a "next" step.
331
+ await this.repository.updateJobExecution(execution.id, {
332
+ status: JobStatus.FAILED,
333
+ endTime: new Date(),
334
+ exitCode: result.exitCode,
335
+ exitMessage: result.exitMessage,
336
+ });
337
+ finalStatus = JobStatus.FAILED;
338
+ break;
339
+ } else {
340
+ // COMPLETED with no transition → linear fallback to the next
341
+ // step in declaration order. If we're already on the last
342
+ // step, the job ends.
343
+ const currentIdx = stepOrder.indexOf(currentStepId);
344
+ const nextIdx = currentIdx + 1;
345
+ nextStepId = nextIdx < stepOrder.length ? stepOrder[nextIdx]! : null;
346
+ }
347
+
348
+ currentStepId = nextStepId;
349
+ }
350
+
351
+ if (finalStatus === JobStatus.COMPLETED) {
352
+ await this.repository.updateJobExecution(execution.id, {
353
+ status: JobStatus.COMPLETED,
354
+ endTime: new Date(),
355
+ exitCode: 'COMPLETED',
356
+ });
357
+ }
358
+ } catch (err) {
359
+ // Defensive: leave the job FAILED rather than crash the host.
360
+ await this.repository.updateJobExecution(execution.id, {
361
+ status: JobStatus.FAILED,
362
+ endTime: new Date(),
363
+ exitMessage: err instanceof Error ? err.message : String(err),
364
+ });
365
+ finalStatus = JobStatus.FAILED;
366
+ }
367
+
368
+ // `after:job:*` listeners run once the job is in a terminal state.
369
+ // They receive the final status as the second positional argument
370
+ // (the `args` slot in the current API; the legacy builder path used
371
+ // the same shape). The resolver map is the same one built above;
372
+ // we re-use it to avoid a second IR walk.
373
+ await this.listenerInvoker.invokeAfter(
374
+ jobResolvers,
375
+ 'job',
376
+ { jobExecutionId: execution.id, stepExecutionId: '<job>' },
377
+ [{ status: finalStatus }],
378
+ );
379
+
380
+ await this.emit({
381
+ type:
382
+ finalStatus === JobStatus.COMPLETED ? BATCH_EVENT.JOB_COMPLETED : BATCH_EVENT.JOB_FAILED,
383
+ timestamp: new Date(),
384
+ jobExecutionId: execution.id,
385
+ data: { status: finalStatus },
386
+ });
387
+
388
+ return (await this.repository.getJobExecution(execution.id))!;
389
+ }
390
+
391
+ /**
392
+ * Build a listener resolver map for the given job. Walks every
393
+ * `ListenerDefinition` in `jobDef.listeners` (job-level + step-level +
394
+ * chunk-level + item-level + skip-level) and resolves each ref into a
395
+ * callable `ListenerEntry` keyed by `${phase}:${kind}:${name}`.
396
+ *
397
+ * The returned map is consumed by `ListenerInvoker.invokeBefore /
398
+ * invokeAfter / invokeOnError / invokeOnSkip*` (Task 20 API). The legacy
399
+ * step-level methods (`invokeBeforeStep` etc.) consume a derived
400
+ * legacy-shaped map produced by `buildLegacyStepResolvers` — that
401
+ * conversion happens at the call site, not here, so this method stays
402
+ * the single source of truth for the new shape.
403
+ *
404
+ * Ref resolution rules:
405
+ * - `RefKind.BuilderLambda` → use `ref.fn` directly (the compiler
406
+ * pre-binds decorator-discovered methods
407
+ * and the builder API ships bare fns).
408
+ * - `RefKind.Method` → requires the Jobable instance. Until
409
+ * a `ModuleRef` is wired (Task 9+), this
410
+ * branch logs a warning and is skipped.
411
+ * - `RefKind.ProviderToken` → resolved in Task 9 against a
412
+ * pre-built provider map. Skipped here
413
+ * with a warning.
414
+ */
415
+ private buildResolverMap(jobDef: JobDefinition): ResolverMap {
416
+ const resolvers: ResolverMap = new Map();
417
+ let lambdaCounter = 0;
418
+
419
+ for (const def of jobDef.listeners) {
420
+ const fn = this.resolveListenerRef(def);
421
+ if (fn === null) continue;
422
+
423
+ const name = this.resolveListenerName(def.ref, lambdaCounter);
424
+ if (def.ref.kind === RefKind.BuilderLambda) lambdaCounter += 1;
425
+
426
+ const key = `${def.phase}:${def.kind}:${name}`;
427
+ resolvers.set(key, {
428
+ fn,
429
+ ...(def.nonCritical !== undefined ? { nonCritical: def.nonCritical } : {}),
430
+ });
431
+ }
432
+
433
+ return resolvers;
434
+ }
435
+
436
+ /**
437
+ * Resolve a single `ListenerDefinition` to its callable function, or
438
+ * `null` if the ref kind is not yet supported. See `buildResolverMap`
439
+ * for the per-kind resolution contract.
440
+ */
441
+ private resolveListenerRef(def: ListenerDefinition): ((...args: any[]) => any) | null {
442
+ const ref = def.ref;
443
+ switch (ref.kind) {
444
+ case RefKind.BuilderLambda:
445
+ return ref.fn ?? null;
446
+ case RefKind.Method:
447
+ this.logger.warn(
448
+ `JobExecutor: Method-ref listener (classToken=${ref.classToken ?? '<unknown>'}, ` +
449
+ `methodName=${ref.methodName ?? '<unknown>'}) requires a Jobable instance; ` +
450
+ 'this resolution path lands in a follow-up task. Listener skipped.',
451
+ );
452
+ return null;
453
+ case RefKind.ProviderToken:
454
+ this.logger.warn(
455
+ `JobExecutor: ProviderToken-ref listener (token=${ref.token ?? '<empty>'}) ` +
456
+ 'is resolved in Task 9. Listener skipped.',
457
+ );
458
+ return null;
459
+ default: {
460
+ const _exhaustive: never = ref.kind;
461
+ void _exhaustive;
462
+ return null;
463
+ }
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Derive the `name` segment of the resolver key. Method refs carry a
469
+ * `classToken` + `methodName` pair that uniquely identifies the bound
470
+ * method; BuilderLambda refs do not carry a name (the compiler drops
471
+ * the method name when it pre-binds), so we mint a `lambda-N` name
472
+ * from a per-job counter to guarantee uniqueness.
473
+ */
474
+ private resolveListenerName(ref: ListenerRef, lambdaCounter: number): string {
475
+ if (ref.kind === RefKind.Method) {
476
+ return `${ref.classToken ?? '<unknown>'}.${ref.methodName ?? '<unknown>'}`;
477
+ }
478
+ return `lambda-${lambdaCounter}`;
479
+ }
480
+
481
+ /**
482
+ * Derive a legacy `Map<string, ListenerResolver>` from a new
483
+ * `ResolverMap`, containing only the step-level entries with their
484
+ * keys translated from `${phase}:step:${name}` back to the legacy
485
+ * `${phase}-step:${name}` shape. The `nonCritical` flag is dropped
486
+ * (legacy `ListenerResolver` is a bare function with no metadata).
487
+ *
488
+ * This is the bridge the `TaskletStepExecutor` (which still consumes
489
+ * the legacy shape) needs until it migrates to the new API. Kept as
490
+ * a private helper so the conversion logic is in one place.
491
+ */
492
+ private buildLegacyStepResolvers(resolvers: ResolverMap): Map<string, ListenerResolver> {
493
+ const legacy: Map<string, ListenerResolver> = new Map();
494
+ for (const [key, entry] of resolvers.entries()) {
495
+ if (key.startsWith('before:step:')) {
496
+ legacy.set(`before-step:${key.slice('before:step:'.length)}`, entry.fn as ListenerResolver);
497
+ } else if (key.startsWith('after:step:')) {
498
+ legacy.set(`after-step:${key.slice('after:step:'.length)}`, entry.fn as ListenerResolver);
499
+ } else if (key.startsWith('on-error:step:')) {
500
+ legacy.set(`on-step-error:${key.slice('on-error:step:'.length)}`, entry.fn as ListenerResolver);
501
+ }
502
+ }
503
+ return legacy;
504
+ }
505
+
506
+ /**
507
+ * Read the `lastChunkIndex` checkpoint from the step-scoped
508
+ * ExecutionContext for `stepExecutionId`. Returns `undefined` when the
509
+ * step has no recorded checkpoint (e.g., the prior run failed on the
510
+ * very first chunk and never got a chance to write one). The chunk
511
+ * executor treats `undefined` as "no resume; start from the beginning".
512
+ */
513
+ private async getLastCheckpoint(stepExecutionId: string): Promise<number | undefined> {
514
+ const ctx = await this.repository.getExecutionContext({ stepExecutionId });
515
+ if (ctx.data === null || typeof ctx.data !== 'object' || Array.isArray(ctx.data)) {
516
+ return undefined;
517
+ }
518
+ const value = (ctx.data as { lastChunkIndex?: unknown }).lastChunkIndex;
519
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
520
+ }
521
+
522
+ private async resolveDeciderExitStatus(
523
+ jobDef: JobDefinition,
524
+ afterStepId: string,
525
+ context: Parameters<NonNullable<JobDefinition['deciders']>[number]['decide']>[0],
526
+ ): Promise<string | undefined> {
527
+ const decider = (jobDef.deciders ?? []).find((d) => d.afterStepId === afterStepId);
528
+ if (decider === undefined) return undefined;
529
+ const status = await decider.decide(context);
530
+ const trimmed = status.trim();
531
+ return trimmed.length > 0 ? trimmed : undefined;
532
+ }
533
+
534
+ /**
535
+ * Dispatch a BatchEvent to the configured observer. Errors thrown by
536
+ * the observer are swallowed: a failing logger/queue must not crash
537
+ * the executor (the job's persisted state is the source of truth).
538
+ */
539
+ private async emit(event: BatchEvent): Promise<void> {
540
+ try {
541
+ await this.observer.onEvent(event);
542
+ } catch {
543
+ // intentional: observer failures are best-effort and must not
544
+ // affect the executor's own state transitions.
545
+ }
546
+ }
547
+ }
548
+
549
+ // Re-export common types for convenience so callers that import
550
+ // `JobExecutor` don't need a second import for `StepExecutionResult` etc.
551
+ export type { StepExecutionResult } from './tasklet-step-executor';
552
+ export type { ChunkExecutionResult } from './chunk-step-executor';
553
+ export type { JobParameters, JobExecution };
@@ -0,0 +1,73 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ import type { JobDefinition } from '../core/ir';
4
+ import {
5
+ JobExecutionDetails,
6
+ JobExecutionFilter,
7
+ JobInstance,
8
+ JobInstanceFilter,
9
+ JobExecution,
10
+ } from '../core/repository';
11
+ import { JobRepository } from '../core/repository';
12
+ import {
13
+ JobExecutionNotFoundError,
14
+ JobInstanceNotFoundError,
15
+ } from '../core/errors';
16
+ import { JobRegistry } from '../registry/job-registry';
17
+
18
+ @Injectable()
19
+ export class JobExplorer {
20
+ constructor(
21
+ private readonly registry: JobRegistry,
22
+ private readonly repository: JobRepository,
23
+ ) {}
24
+
25
+ listJobs(): JobDefinition[] {
26
+ return this.registry.getAll();
27
+ }
28
+
29
+ async getJobInstance(jobInstanceId: string): Promise<JobInstance> {
30
+ const instance = await this.repository.getJobInstance(jobInstanceId);
31
+ if (instance === null) {
32
+ throw new JobInstanceNotFoundError(jobInstanceId);
33
+ }
34
+ return instance;
35
+ }
36
+
37
+ async listJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {
38
+ return this.repository.findJobInstances(filter);
39
+ }
40
+
41
+ async listJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {
42
+ return this.repository.findJobExecutions(filter);
43
+ }
44
+
45
+ async getJobExecutionDetails(executionId: string): Promise<JobExecutionDetails> {
46
+ const jobExecution = await this.repository.getJobExecution(executionId);
47
+ if (jobExecution === null) {
48
+ throw new JobExecutionNotFoundError(executionId);
49
+ }
50
+
51
+ const jobInstance = await this.repository.getJobInstance(jobExecution.jobInstanceId);
52
+ if (jobInstance === null) {
53
+ throw new JobInstanceNotFoundError(jobExecution.jobInstanceId);
54
+ }
55
+
56
+ const stepExecutions = await this.repository.findStepExecutions(executionId);
57
+ const jobContext = await this.repository.getExecutionContext({ jobExecutionId: executionId });
58
+ const stepContexts = await Promise.all(
59
+ stepExecutions.map(async (step) => ({
60
+ stepExecutionId: step.id,
61
+ context: await this.repository.getExecutionContext({ stepExecutionId: step.id }),
62
+ })),
63
+ );
64
+
65
+ return {
66
+ jobInstance,
67
+ jobExecution,
68
+ stepExecutions,
69
+ jobContext,
70
+ stepContexts,
71
+ };
72
+ }
73
+ }
@@ -0,0 +1,35 @@
1
+ import { createHash } from 'crypto';
2
+ import type { JobParameters } from '../core/repository/types';
3
+
4
+ /**
5
+ * Canonicalize JobParameters into a stable string.
6
+ * - Object keys sorted alphabetically (recursive)
7
+ * - Arrays preserve order (different order = different key)
8
+ * - Date → ISO string
9
+ * - null/undefined → omitted
10
+ * - Number: keep as-is (1 vs 1.0 same via String())
11
+ * - String: as-is (no whitespace trim — callers must normalize)
12
+ */
13
+ function canonicalize(value: unknown): unknown {
14
+ if (value === null || value === undefined) return undefined;
15
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return value;
16
+ if (typeof value === 'bigint') throw new Error('BigInt not supported in job key');
17
+ if (value instanceof Date) return value.toISOString();
18
+ if (Array.isArray(value)) return value.map(canonicalize);
19
+ if (typeof value === 'object') {
20
+ const obj = value as Record<string, unknown>;
21
+ const sorted: Record<string, unknown> = {};
22
+ for (const k of Object.keys(obj).sort()) {
23
+ const v = canonicalize(obj[k]);
24
+ if (v !== undefined) sorted[k] = v;
25
+ }
26
+ return sorted;
27
+ }
28
+ return value;
29
+ }
30
+
31
+ export function canonicalJobKey(params: JobParameters): string {
32
+ const canonical = canonicalize(params);
33
+ const json = JSON.stringify(canonical);
34
+ return createHash('sha256').update(json).digest('hex');
35
+ }