@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,594 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import type { ChunkStepDefinition, ReaderRef, ProcessorRef, WriterRef } from '../core/ir';
3
+ import { RefKind } from '../core/ir';
4
+ import type { ItemReader, ItemProcessor, ItemStream, ItemWriter } from '../core/item';
5
+ import type { ExecutionContext, JobRepository } from '../core/repository';
6
+ import type { TransactionManager } from '../core/transaction';
7
+ import { StepStatus } from '../core/status';
8
+ import type { SkipPolicy, SkipContext } from '../policies/skip-policy';
9
+ import type { RetryPolicy, RetryContext } from '../policies/retry-policy';
10
+ import { compileSkipPolicy } from '../policies/skip-policy';
11
+ import { compileRetryPolicy } from '../policies/retry-policy';
12
+ import { SkipLimitExceededError, RetryLimitExceededError } from '../core/errors';
13
+ import { enforcePartitionIndex } from '../partition-helpers';
14
+ import type { ResolverMap } from './listener-invoker';
15
+ import { ListenerInvoker } from './listener-invoker';
16
+ import { resolveProviderToken, type ProviderResolvers } from './ref-resolver';
17
+
18
+ export interface ChunkExecutionContext {
19
+ jobExecutionId: string;
20
+ /** Step execution id, used to scope the chunk-progress checkpoint in the
21
+ * step's ExecutionContext (saved as `{ lastChunkIndex }`). */
22
+ stepExecutionId: string;
23
+ jobRepository: JobRepository;
24
+ transactionManager: TransactionManager;
25
+ listenerInvoker: ListenerInvoker;
26
+ /** Map of resolved reader/processor/writer functions by name. */
27
+ resolvers: Map<string, (...args: unknown[]) => unknown | Promise<unknown>>;
28
+ jobExecutionId2: string; // unique key for listener resolver namespacing
29
+ /** Optional skip listener resolver map. Keys follow the `on-skip:<kind>:<name>` shape. */
30
+ skipListenerResolvers?: ResolverMap;
31
+ /**
32
+ * Optional map of provider-token id → already-resolved provider instance.
33
+ * Populated by the JobExecutor (or test fixtures) so that
34
+ * `RefKind.ProviderToken` refs on the reader/processor/writer slots can
35
+ * be looked up without coupling the executor to the Nest DI container.
36
+ */
37
+ providerResolvers?: ProviderResolvers;
38
+ /**
39
+ * When set, the executor skips any chunk whose 0-based index is less
40
+ * than or equal to this value. The reader is still advanced by
41
+ * `chunkSize` calls per skipped chunk (so the data stream position
42
+ * stays correct), but no read/process/write happens and `commitCount`
43
+ * is not incremented. The checkpoint value is loaded from the prior
44
+ * FAILED step execution's ExecutionContext by `JobExecutor`.
45
+ */
46
+ resumeFromChunkIndex?: number;
47
+ /**
48
+ * Partition ordinal for this chunk execution. Set by the transport
49
+ * (BullMQ / Kafka) when the step declares `partitions.count >= 2`;
50
+ * `undefined` means "non-partitioned" (the 0.1.0 path). The executor
51
+ * uses this together with `partitionCount` and the step's
52
+ * `partitions.range` to bound the read loop to the partition's
53
+ * `[from, to)` range — see `packages/core/src/partition-helpers.ts`
54
+ * and `docs/RELEASE-0.2.0.md §6`.
55
+ */
56
+ partitionIndex?: number;
57
+ /**
58
+ * Mirror of the step's `partitions.count`. Carried on the context
59
+ * (rather than read from the step) so the executor does not have to
60
+ * reason about which step is being executed when it consults the
61
+ * partition range.
62
+ */
63
+ partitionCount?: number;
64
+ }
65
+
66
+ export interface ChunkExecutionResult {
67
+ status: StepStatus;
68
+ exitCode: string;
69
+ exitMessage: string;
70
+ readCount: number;
71
+ writeCount: number;
72
+ skipCount: number;
73
+ commitCount: number;
74
+ }
75
+
76
+ /** Phase tag used by skip/retry policies. Mirrors the union in policy modules. */
77
+ type Phase = 'read' | 'process' | 'write';
78
+
79
+ /**
80
+ * Outcome of a single per-phase attempt after skip/retry consultation.
81
+ * - `ok` — the operation succeeded; `value` is the op's return value.
82
+ * - `skipped` — the error was skippable (and within budget); the op was
83
+ * abandoned, the on-skip listener was invoked, and `value` is
84
+ * `undefined`.
85
+ */
86
+ type PhaseResult<T> = { kind: 'ok'; value: T } | { kind: 'skipped' };
87
+
88
+ /**
89
+ * Options for `runPhase`. `getSkipCount` is a closure over the executor's
90
+ * live `skipCount` accumulator so the policy's budget check is consistent
91
+ * with the accounting in the outer loop.
92
+ */
93
+ interface RunPhaseOptions {
94
+ phase: Phase;
95
+ item: unknown;
96
+ skipPolicy: SkipPolicy | null;
97
+ retryPolicy: RetryPolicy | null;
98
+ skipLimit: number;
99
+ retryLimit: number;
100
+ /** Live read of the executor's skipCount accumulator. */
101
+ getSkipCount: () => number;
102
+ /** Invoked when an error is actually skipped (within budget). The caller
103
+ * is responsible for incrementing its own skipCount here. */
104
+ onSkip: (err: unknown) => Promise<void>;
105
+ }
106
+
107
+ function isItemStream(value: unknown): value is ItemStream {
108
+ if (value === null || typeof value !== 'object') return false;
109
+ const stream = value as Partial<ItemStream>;
110
+ return (
111
+ typeof stream.open === 'function' &&
112
+ typeof stream.update === 'function' &&
113
+ typeof stream.close === 'function'
114
+ );
115
+ }
116
+
117
+ function withLastChunkIndex(ctx: ExecutionContext, chunkIndex: number): ExecutionContext {
118
+ const current = ctx.data;
119
+ const data =
120
+ current !== null && typeof current === 'object' && !Array.isArray(current)
121
+ ? { ...(current as Record<string, unknown>), lastChunkIndex: chunkIndex }
122
+ : { lastChunkIndex: chunkIndex };
123
+
124
+ return {
125
+ data: data as ExecutionContext['data'],
126
+ version: ctx.version,
127
+ };
128
+ }
129
+
130
+ @Injectable()
131
+ export class ChunkStepExecutor {
132
+ async execute(
133
+ step: ChunkStepDefinition,
134
+ context: ChunkExecutionContext,
135
+ ): Promise<ChunkExecutionResult> {
136
+ const skipPolicy = step.skipPolicy ? compileSkipPolicy(step.skipPolicy) : null;
137
+ const retryPolicy = step.retryPolicy ? compileRetryPolicy(step.retryPolicy) : null;
138
+ const skipResolvers: ResolverMap = context.skipListenerResolvers ?? new Map();
139
+
140
+ const skipLimit = step.skipPolicy?.limit ?? 0;
141
+ const retryLimit = step.retryPolicy?.limit ?? 0;
142
+
143
+ // Partition routing (T8): when the transport attached a
144
+ // `partitionIndex` / `partitionCount` pair to this execution, the
145
+ // step's `partitions.range` (or, when absent, the runtime
146
+ // "read until EOF" default) bounds the chunk loop. We resolve the
147
+ // range once up-front so the hot loop does not re-invoke the
148
+ // user's resolver.
149
+ //
150
+ // The resolve helper throws when the index is out of range; we
151
+ // surface the violation as a FAILED step (caught by the outer
152
+ // try/catch) rather than a propagated throw, matching the
153
+ // chunk executor's "errors become FAILED step" contract.
154
+ let partition: { from: number; to: number } | null = null;
155
+ if (context.partitionIndex !== undefined && context.partitionCount !== undefined) {
156
+ // Defensive: enforce the index range here too. The runtime
157
+ // service validates at enqueue time, but the chunk executor is
158
+ // also a public surface (tests drive it directly), so a
159
+ // double-check costs almost nothing and pins the contract.
160
+ enforcePartitionIndex(context.partitionIndex, context.partitionCount);
161
+ if (step.partitions?.range === undefined) {
162
+ // No `range` resolver: each partition reads from the start of
163
+ // the input. This is the documented "default" behaviour in
164
+ // `docs/RELEASE-0.2.0.md §6.1` — the host's reader is
165
+ // responsible for not over-processing. We still cap the read
166
+ // loop to the partition's "share" via a sentinel that the
167
+ // hot loop checks.
168
+ partition = null;
169
+ } else {
170
+ const range = step.partitions.range(context.partitionIndex, context.partitionCount);
171
+ const [from, to] = range;
172
+ if (from < 0 || to < from) {
173
+ throw new Error(
174
+ `[ChunkStepExecutor] partitions.range(${context.partitionIndex}, ` +
175
+ `${context.partitionCount}) returned invalid [${from}, ${to})`,
176
+ );
177
+ }
178
+ partition = { from, to };
179
+ }
180
+ }
181
+
182
+ let readCount = 0;
183
+ let writeCount = 0;
184
+ let skipCount = 0;
185
+ let commitCount = 0;
186
+ // 0-based index of the chunk currently being assembled. Used by the
187
+ // restart path to decide which chunks to skip and where to record
188
+ // the last-committed checkpoint.
189
+ let chunkIndex = 0;
190
+ let openedStreams: ItemStream[] = [];
191
+ let streamContext: ExecutionContext | null = null;
192
+
193
+ try {
194
+ // Resolve inside the try block so a missing provider-token ref
195
+ // surfaces as FAILED/{exitMessage: <err>}, matching the tasklet
196
+ // executor's contract — not as a propagated throw.
197
+ const reader = this.resolveReader(step.reader, context);
198
+ const processor = step.processor ? this.resolveProcessor(step.processor, context) : null;
199
+ const writer = this.resolveWriter(step.writer, context);
200
+
201
+ const streams: ItemStream[] = [];
202
+ if (isItemStream(reader)) streams.push(reader);
203
+ if (isItemStream(writer)) streams.push(writer);
204
+ streamContext = await context.jobRepository.getExecutionContext({
205
+ stepExecutionId: context.stepExecutionId,
206
+ });
207
+ for (const stream of streams) {
208
+ await stream.open(streamContext);
209
+ openedStreams.push(stream);
210
+ }
211
+
212
+ // Outer loop: keep reading chunks until reader returns null
213
+ // eslint-disable-next-line no-constant-condition
214
+ while (true) {
215
+ // ---- SKIP PHASE (restart-only): drain up to chunkSize items so
216
+ // the reader advances past already-committed chunks, but skip
217
+ // the read/process/write pipeline entirely. commitCount is not
218
+ // incremented (those chunks were already counted in the prior
219
+ // run) and no checkpoint is written (the prior run owns it).
220
+ if (
221
+ context.resumeFromChunkIndex !== undefined &&
222
+ chunkIndex <= context.resumeFromChunkIndex
223
+ ) {
224
+ let drained = 0;
225
+ // Cap the restart skip-drain by the partition's remaining
226
+ // budget too (T8). A restart on a partitioned step is rare
227
+ // but possible — without the cap, the skip-drain could
228
+ // pull items the partition's range is not allowed to
229
+ // touch, leaving the chunk pipeline reading the wrong
230
+ // slice on the next iteration.
231
+ const skipCap =
232
+ partition !== null
233
+ ? Math.max(0, partition.to - partition.from - readCount)
234
+ : step.chunkSize;
235
+ if (skipCap === 0) break;
236
+ for (let i = 0; i < skipCap; i++) {
237
+ const item = await reader.read();
238
+ if (item == null) break;
239
+ drained += 1;
240
+ }
241
+ if (drained === 0) break; // EOF reached while skipping
242
+ chunkIndex += 1;
243
+ continue;
244
+ }
245
+
246
+ const items: unknown[] = [];
247
+ let eof = false;
248
+
249
+ // ---- PARTITION CAP (T8) ----
250
+ // When the transport attached a partition's [from, to) range,
251
+ // bound this chunk's read loop so the partition never
252
+ // overshoots. `readCount` is the running count of items the
253
+ // partition has read so far; `partition.to - partition.from`
254
+ // is the partition's total budget. The cap is `min(chunkSize,
255
+ // remaining)` and `0` is a valid "no more items" cap (we
256
+ // exit the outer loop on the next iteration).
257
+ const remaining =
258
+ partition !== null ? Math.max(0, partition.to - partition.from - readCount) : step.chunkSize;
259
+ if (remaining === 0) {
260
+ // Partition is fully drained. Exit the outer loop so the
261
+ // chunk executor returns COMPLETED with the partition's
262
+ // commitCount + readCount + writeCount.
263
+ break;
264
+ }
265
+
266
+ // ---- READ PHASE: per-item retry+skip ----
267
+ for (let i = 0; i < remaining && !eof; i++) {
268
+ const r = await this.runPhase<unknown>(() => reader.read(), {
269
+ phase: 'read',
270
+ item: null,
271
+ skipPolicy,
272
+ retryPolicy,
273
+ skipLimit,
274
+ retryLimit,
275
+ getSkipCount: () => skipCount,
276
+ onSkip: async (err) => {
277
+ skipCount += 1;
278
+ await context.listenerInvoker.invokeOnSkipRead(skipResolvers, err, null);
279
+ },
280
+ });
281
+ if (r.kind === 'skipped') continue;
282
+ if (r.value == null) {
283
+ // Natural EOF from the reader
284
+ eof = true;
285
+ break;
286
+ }
287
+ items.push(r.value);
288
+ readCount += 1;
289
+ }
290
+ if (items.length === 0) break; // EOF (either before first read or after skips)
291
+
292
+ // ---- PROCESS PHASE: per-item retry+skip ----
293
+ const processed: unknown[] = [];
294
+ for (const item of items) {
295
+ if (!processor) {
296
+ processed.push(item);
297
+ continue;
298
+ }
299
+ const r = await this.runPhase<unknown>(() => processor.process(item), {
300
+ phase: 'process',
301
+ item,
302
+ skipPolicy,
303
+ retryPolicy,
304
+ skipLimit,
305
+ retryLimit,
306
+ getSkipCount: () => skipCount,
307
+ onSkip: async (err) => {
308
+ skipCount += 1;
309
+ await context.listenerInvoker.invokeOnSkipProcess(skipResolvers, item, err);
310
+ },
311
+ });
312
+ if (r.kind === 'skipped') continue;
313
+ if (r.value !== null && r.value !== undefined) {
314
+ processed.push(r.value);
315
+ } else {
316
+ // Processor filtered the item out (returned null/undefined) — not a
317
+ // skip-policy skip, but counts as a skip for accounting parity.
318
+ skipCount += 1;
319
+ }
320
+ }
321
+
322
+ // ---- WRITE PHASE: per-chunk retry+skip, wrapped in transaction ----
323
+ if (processed.length > 0) {
324
+ const r = await this.runPhase<{ written: number; skipped: number } | void>(
325
+ () =>
326
+ context.transactionManager.withTransaction(async () => {
327
+ return writer.write(processed);
328
+ }),
329
+ {
330
+ phase: 'write',
331
+ item: processed,
332
+ skipPolicy,
333
+ retryPolicy,
334
+ skipLimit,
335
+ retryLimit,
336
+ getSkipCount: () => skipCount,
337
+ onSkip: async (err) => {
338
+ skipCount += 1;
339
+ await context.listenerInvoker.invokeOnSkipWrite(skipResolvers, processed, err);
340
+ },
341
+ },
342
+ );
343
+ if (r.kind === 'ok') {
344
+ if (r.value) {
345
+ writeCount += r.value.written;
346
+ skipCount += r.value.skipped;
347
+ } else {
348
+ writeCount += processed.length;
349
+ }
350
+ }
351
+ // If the write was skipped, writeCount is not incremented and the
352
+ // chunk is still considered "committed" (commitCount++) so that
353
+ // progress tracking reflects that we moved past it.
354
+ }
355
+
356
+ // Persist the last-committed-chunk checkpoint in the step's
357
+ // ExecutionContext. The next restart will read this value and
358
+ // skip every chunk with index ≤ `lastChunkIndex`. The save is
359
+ // intentionally outside the per-chunk transaction: the
360
+ // checkpoint reflects "we successfully moved past this chunk",
361
+ // and we only reach this line after the write completed
362
+ // (either OK or skipped — both are forward progress).
363
+ streamContext = withLastChunkIndex(streamContext, chunkIndex);
364
+ streamContext = await this.updateStreams(openedStreams, streamContext);
365
+ await context.jobRepository.saveExecutionContext(
366
+ { stepExecutionId: context.stepExecutionId },
367
+ streamContext,
368
+ );
369
+ streamContext = await context.jobRepository.getExecutionContext({
370
+ stepExecutionId: context.stepExecutionId,
371
+ });
372
+
373
+ commitCount += 1;
374
+ chunkIndex += 1;
375
+ }
376
+
377
+ const streamsToClose = openedStreams;
378
+ openedStreams = [];
379
+ await this.closeStreams(streamsToClose);
380
+
381
+ return {
382
+ status: StepStatus.COMPLETED,
383
+ exitCode: 'COMPLETED',
384
+ exitMessage: '',
385
+ readCount,
386
+ writeCount,
387
+ skipCount,
388
+ commitCount,
389
+ };
390
+ } catch (err) {
391
+ let finalError = err;
392
+ const streamsToClose = openedStreams;
393
+ openedStreams = [];
394
+ try {
395
+ await this.closeStreams(streamsToClose);
396
+ } catch (closeErr) {
397
+ finalError = closeErr;
398
+ }
399
+ return {
400
+ status: StepStatus.FAILED,
401
+ exitCode: 'FAILED',
402
+ exitMessage: finalError instanceof Error ? finalError.message : String(finalError),
403
+ readCount,
404
+ writeCount,
405
+ skipCount,
406
+ commitCount,
407
+ };
408
+ }
409
+ }
410
+
411
+ private async updateStreams(
412
+ streams: readonly ItemStream[],
413
+ ctx: ExecutionContext,
414
+ ): Promise<ExecutionContext> {
415
+ let next = ctx;
416
+ for (const stream of streams) {
417
+ const updated = await stream.update(next);
418
+ if (updated !== undefined) {
419
+ next = updated;
420
+ }
421
+ }
422
+ return next;
423
+ }
424
+
425
+ private async closeStreams(streams: readonly ItemStream[]): Promise<void> {
426
+ for (let i = streams.length - 1; i >= 0; i--) {
427
+ await streams[i]!.close();
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Run a single per-phase operation (one read, one process, or one write) with
433
+ * skip/retry policy consultation. Throws when the operation cannot be
434
+ * skipped (either non-skippable error or skip-limit exceeded) or when
435
+ * retries are exhausted.
436
+ *
437
+ * The contract on budget accounting:
438
+ * - The caller's `skipCount` accumulator is consulted *before* a skip is
439
+ * honored. If the post-skip count would exceed the limit, the error
440
+ * becomes a `SkipLimitExceededError` instead of a skip.
441
+ * - The caller's `onSkip` callback is responsible for incrementing the
442
+ * skipCount; it runs only when a skip is actually honored.
443
+ */
444
+ private async runPhase<T>(
445
+ op: () => Promise<T>,
446
+ options: RunPhaseOptions,
447
+ ): Promise<PhaseResult<T>> {
448
+ let attempt = 1;
449
+ // Outer safety cap: when a retry policy exists, allow many iterations;
450
+ // the policy's `canRetry` is the actual gate. When no retry policy,
451
+ // exactly one attempt.
452
+ const outerCap = options.retryPolicy ? 999 : 1;
453
+
454
+ while (attempt <= outerCap) {
455
+ try {
456
+ const value = await op();
457
+ return { kind: 'ok', value };
458
+ } catch (err) {
459
+ // 1) Skip consultation: is this error skippable, and is there budget?
460
+ if (options.skipPolicy) {
461
+ // Use the policy's `shouldSkip` with `skipCount: 0` to get a pure
462
+ // membership check (the policy's own budget gate is bypassed).
463
+ // We apply the budget ourselves with the caller's live accounting.
464
+ const membership: SkipContext = {
465
+ item: options.item,
466
+ phase: options.phase,
467
+ skipCount: 0,
468
+ skipLimit: options.skipLimit,
469
+ };
470
+ if (options.skipPolicy.shouldSkip(err, membership)) {
471
+ // Error is in the skippable list. Now check the budget: would
472
+ // honoring this skip exceed the limit?
473
+ const projected = options.getSkipCount() + 1;
474
+ if (projected > options.skipLimit) {
475
+ throw new SkipLimitExceededError(options.skipLimit);
476
+ }
477
+ await options.onSkip(err);
478
+ return { kind: 'skipped' };
479
+ }
480
+ // Not in the skippable list — fall through to retry/throw.
481
+ }
482
+
483
+ // 2) Retry consultation
484
+ if (options.retryPolicy) {
485
+ const retryCtx: RetryContext = {
486
+ item: options.item,
487
+ phase: options.phase,
488
+ attempt,
489
+ retryLimit: options.retryLimit,
490
+ };
491
+ if (options.retryPolicy.canRetry(err, retryCtx)) {
492
+ const ms = options.retryPolicy.backoffMs(attempt);
493
+ if (ms > 0) await new Promise((r) => setTimeout(r, ms));
494
+ attempt += 1;
495
+ continue;
496
+ }
497
+ // canRetry returned false. Distinguish "exhausted" from "not
498
+ // retryable" by re-checking membership with an effectively
499
+ // infinite budget.
500
+ if (attempt > options.retryLimit) {
501
+ const isRetryable = options.retryPolicy.canRetry(err, {
502
+ item: options.item,
503
+ phase: options.phase,
504
+ attempt: 1,
505
+ retryLimit: Number.POSITIVE_INFINITY,
506
+ });
507
+ if (isRetryable) {
508
+ throw new RetryLimitExceededError(options.retryLimit);
509
+ }
510
+ }
511
+ }
512
+
513
+ // 3) Neither skippable nor retryable: re-throw the original error.
514
+ throw err;
515
+ }
516
+ }
517
+
518
+ // Defensive: the outer cap should never be reached when a retry policy
519
+ // gates us, but if no retry policy exists and the very first attempt
520
+ // somehow re-entered the loop, fall through with a clear failure.
521
+ throw new Error(
522
+ `ChunkStepExecutor: phase "${options.phase}" exhausted attempts without a retry policy`,
523
+ );
524
+ }
525
+
526
+ private resolveReader(ref: ReaderRef, context: ChunkExecutionContext): ItemReader {
527
+ if (ref.kind === RefKind.BuilderLambda && ref.fn) {
528
+ const result = ref.fn();
529
+ if (typeof result === 'function') {
530
+ return { read: result as ItemReader['read'] };
531
+ }
532
+ if (result !== null && typeof result === 'object' && typeof (result as ItemReader).read === 'function') {
533
+ return result as ItemReader;
534
+ }
535
+ return { read: ref.fn as ItemReader['read'] };
536
+ }
537
+ if (ref.kind === RefKind.ProviderToken) {
538
+ return resolveProviderToken<ItemReader>('reader', ref, context.providerResolvers);
539
+ }
540
+ if (ref.kind === RefKind.Method && ref.classToken && ref.methodName) {
541
+ const key = `${context.jobExecutionId2}::reader::${ref.classToken}::${ref.methodName}`;
542
+ const fn = context.resolvers.get(key);
543
+ if (!fn) throw new Error(`Reader not resolved: ${ref.classToken}.${ref.methodName}`);
544
+ return { read: fn as ItemReader['read'] };
545
+ }
546
+ throw new Error(`Unsupported reader ref kind: ${ref.kind}`);
547
+ }
548
+
549
+ private resolveProcessor(ref: ProcessorRef, context: ChunkExecutionContext): ItemProcessor {
550
+ if (ref.kind === RefKind.BuilderLambda && ref.fn) {
551
+ const result = ref.fn();
552
+ if (typeof result === 'function') {
553
+ return { process: result as ItemProcessor['process'] };
554
+ }
555
+ if (result !== null && typeof result === 'object' && typeof (result as ItemProcessor).process === 'function') {
556
+ return result as ItemProcessor;
557
+ }
558
+ return { process: ref.fn as ItemProcessor['process'] };
559
+ }
560
+ if (ref.kind === RefKind.ProviderToken) {
561
+ return resolveProviderToken<ItemProcessor>('processor', ref, context.providerResolvers);
562
+ }
563
+ if (ref.kind === RefKind.Method && ref.classToken && ref.methodName) {
564
+ const key = `${context.jobExecutionId2}::processor::${ref.classToken}::${ref.methodName}`;
565
+ const fn = context.resolvers.get(key);
566
+ if (!fn) throw new Error(`Processor not resolved: ${ref.classToken}.${ref.methodName}`);
567
+ return { process: fn as ItemProcessor['process'] };
568
+ }
569
+ throw new Error(`Unsupported processor ref kind: ${ref.kind}`);
570
+ }
571
+
572
+ private resolveWriter(ref: WriterRef, context: ChunkExecutionContext): ItemWriter {
573
+ if (ref.kind === RefKind.BuilderLambda && ref.fn) {
574
+ const result = ref.fn();
575
+ if (typeof result === 'function') {
576
+ return { write: result as ItemWriter['write'] };
577
+ }
578
+ if (result !== null && typeof result === 'object' && typeof (result as ItemWriter).write === 'function') {
579
+ return result as ItemWriter;
580
+ }
581
+ return { write: ref.fn as ItemWriter['write'] };
582
+ }
583
+ if (ref.kind === RefKind.ProviderToken) {
584
+ return resolveProviderToken<ItemWriter>('writer', ref, context.providerResolvers);
585
+ }
586
+ if (ref.kind === RefKind.Method && ref.classToken && ref.methodName) {
587
+ const key = `${context.jobExecutionId2}::writer::${ref.classToken}::${ref.methodName}`;
588
+ const fn = context.resolvers.get(key);
589
+ if (!fn) throw new Error(`Writer not resolved: ${ref.classToken}.${ref.methodName}`);
590
+ return { write: fn as ItemWriter['write'] };
591
+ }
592
+ throw new Error(`Unsupported writer ref kind: ${ref.kind}`);
593
+ }
594
+ }