@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,334 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { JobRepository } from '@nest-batch/core';
3
+ import type {
4
+ JobInstance,
5
+ JobExecution,
6
+ JobExecutionPatch,
7
+ JobParameters,
8
+ StepExecution,
9
+ StepExecutionPatch,
10
+ ExecutionContext,
11
+ ExecutionScope,
12
+ JobInstanceFilter,
13
+ JobExecutionFilter,
14
+ } from '@nest-batch/core';
15
+ import { JobStatus, StepStatus } from '@nest-batch/core';
16
+ import { JobExecutionAlreadyRunningError } from '@nest-batch/core';
17
+ import { assertJsonSerializable } from '@nest-batch/core';
18
+ import type { IdGenerator } from '../id-generator';
19
+ import { UuidIdGenerator } from '../id-generator';
20
+
21
+ function deepClone<T>(value: T): T {
22
+ if (value === null || typeof value !== 'object') return value;
23
+ if (value instanceof Date) return new Date(value.getTime()) as unknown as T;
24
+ if (Array.isArray(value)) return value.map((v) => deepClone(v)) as unknown as T;
25
+ const out: Record<string, unknown> = {};
26
+ for (const k of Object.keys(value as Record<string, unknown>)) {
27
+ out[k] = deepClone((value as Record<string, unknown>)[k]);
28
+ }
29
+ return out as T;
30
+ }
31
+
32
+ interface InMemoryState {
33
+ instances: Map<string, JobInstance>; // key: instanceId
34
+ instancesByKey: Map<string, JobInstance>; // key: `${name}::${jobKey}`
35
+ executions: Map<string, JobExecution>;
36
+ stepExecutions: Map<string, StepExecution>;
37
+ contexts: Map<string, ExecutionContext>; // key: serialized ExecutionScope
38
+ }
39
+
40
+ function scopeKey(scope: ExecutionScope): string {
41
+ if ('jobExecutionId' in scope) return `job::${scope.jobExecutionId}`;
42
+ return `step::${scope.stepExecutionId}`;
43
+ }
44
+
45
+ /**
46
+ * In-memory JobRepository with real-repo semantics:
47
+ * - deterministic IDs (configurable via IdGenerator)
48
+ * - deep clone on reads/writes to prevent mutation leaks
49
+ * - async signatures
50
+ * - uniqueness on (jobName, jobKey)
51
+ * - getOrCreateJobInstance / createJobExecution / getRunningJobExecution
52
+ * share a single promise-chain lock, so the check-then-create sequence
53
+ * used by JobLauncher is race-safe
54
+ *
55
+ * restartable: false by default (per Metis directive: in-memory repo is non-restartable
56
+ * because contexts are lost on process restart).
57
+ */
58
+ @Injectable()
59
+ export class InMemoryJobRepository extends JobRepository {
60
+ private readonly state: InMemoryState = {
61
+ instances: new Map(),
62
+ instancesByKey: new Map(),
63
+ executions: new Map(),
64
+ stepExecutions: new Map(),
65
+ contexts: new Map(),
66
+ };
67
+ /** Promise-chain lock to serialize getOrCreateJobInstance calls. */
68
+ private lock: Promise<unknown> = Promise.resolve();
69
+
70
+ constructor(private readonly idGen: IdGenerator = new UuidIdGenerator()) {
71
+ super();
72
+ }
73
+
74
+ private async withLock<T>(fn: () => Promise<T>): Promise<T> {
75
+ const prev = this.lock;
76
+ let release!: () => void;
77
+ this.lock = new Promise<void>((resolve) => {
78
+ release = resolve;
79
+ });
80
+ try {
81
+ await prev;
82
+ return await fn();
83
+ } finally {
84
+ release();
85
+ }
86
+ }
87
+
88
+ async getOrCreateJobInstance(name: string, jobKey: string): Promise<JobInstance> {
89
+ return this.withLock(async () => {
90
+ const key = `${name}::${jobKey}`;
91
+ const existing = this.state.instancesByKey.get(key);
92
+ if (existing) return deepClone(existing);
93
+ const inst: JobInstance = {
94
+ id: this.idGen.next(),
95
+ jobName: name,
96
+ jobKey,
97
+ createdAt: new Date(),
98
+ };
99
+ this.state.instances.set(inst.id, inst);
100
+ this.state.instancesByKey.set(key, inst);
101
+ return deepClone(inst);
102
+ });
103
+ }
104
+
105
+ async createJobExecution(jobInstanceId: string, params: JobParameters): Promise<JobExecution> {
106
+ return this.withLock(async () => {
107
+ const exec: JobExecution = {
108
+ id: this.idGen.next(),
109
+ jobInstanceId,
110
+ status: JobStatus.STARTING,
111
+ startTime: null,
112
+ endTime: null,
113
+ exitCode: '',
114
+ exitMessage: '',
115
+ params: deepClone(params),
116
+ };
117
+ this.state.executions.set(exec.id, exec);
118
+ return deepClone(exec);
119
+ });
120
+ }
121
+
122
+ async createExecutionAtomic(
123
+ name: string,
124
+ jobKey: string,
125
+ params: JobParameters,
126
+ ): Promise<JobExecution> {
127
+ return this.withLock(async () => {
128
+ const key = `${name}::${jobKey}`;
129
+ let instance = this.state.instancesByKey.get(key);
130
+ if (!instance) {
131
+ instance = {
132
+ id: this.idGen.next(),
133
+ jobName: name,
134
+ jobKey,
135
+ createdAt: new Date(),
136
+ };
137
+ this.state.instances.set(instance.id, instance);
138
+ this.state.instancesByKey.set(key, instance);
139
+ }
140
+ for (const exec of this.state.executions.values()) {
141
+ if (
142
+ exec.jobInstanceId === instance.id &&
143
+ (exec.status === JobStatus.STARTING || exec.status === JobStatus.STARTED)
144
+ ) {
145
+ throw new JobExecutionAlreadyRunningError(exec.id);
146
+ }
147
+ }
148
+ const exec: JobExecution = {
149
+ id: this.idGen.next(),
150
+ jobInstanceId: instance.id,
151
+ status: JobStatus.STARTING,
152
+ startTime: null,
153
+ endTime: null,
154
+ exitCode: '',
155
+ exitMessage: '',
156
+ params: deepClone(params),
157
+ };
158
+ this.state.executions.set(exec.id, exec);
159
+ return deepClone(exec);
160
+ });
161
+ }
162
+
163
+ async getRunningJobExecution(jobInstanceId: string): Promise<JobExecution | null> {
164
+ return this.withLock(async () => {
165
+ for (const exec of this.state.executions.values()) {
166
+ if (
167
+ exec.jobInstanceId === jobInstanceId &&
168
+ (exec.status === JobStatus.STARTING || exec.status === JobStatus.STARTED)
169
+ ) {
170
+ return deepClone(exec);
171
+ }
172
+ }
173
+ return null;
174
+ });
175
+ }
176
+
177
+ async updateJobExecution(executionId: string, patch: JobExecutionPatch): Promise<void> {
178
+ const cur = this.state.executions.get(executionId);
179
+ if (!cur) throw new Error(`JobExecution not found: ${executionId}`);
180
+ const next: JobExecution = {
181
+ ...cur,
182
+ ...patch,
183
+ startTime: patch.startTime === undefined ? cur.startTime : patch.startTime,
184
+ endTime: patch.endTime === undefined ? cur.endTime : patch.endTime,
185
+ };
186
+ this.state.executions.set(executionId, next);
187
+ }
188
+
189
+ async getJobExecution(executionId: string): Promise<JobExecution | null> {
190
+ const e = this.state.executions.get(executionId);
191
+ return e ? deepClone(e) : null;
192
+ }
193
+
194
+ override async getJobInstance(jobInstanceId: string): Promise<JobInstance | null> {
195
+ const instance = this.state.instances.get(jobInstanceId);
196
+ return instance ? deepClone(instance) : null;
197
+ }
198
+
199
+ override async findJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {
200
+ const rows: JobInstance[] = [];
201
+ for (const instance of this.state.instances.values()) {
202
+ if (filter.jobName !== undefined && instance.jobName !== filter.jobName) continue;
203
+ if (filter.jobKey !== undefined && instance.jobKey !== filter.jobKey) continue;
204
+ rows.push(deepClone(instance));
205
+ }
206
+ return rows;
207
+ }
208
+
209
+ override async findJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {
210
+ let statuses: Set<JobStatus> | undefined;
211
+ if (filter.status !== undefined) {
212
+ const statusFilter = filter.status;
213
+ const statusList: readonly JobStatus[] = Array.isArray(statusFilter)
214
+ ? statusFilter
215
+ : [statusFilter];
216
+ statuses = new Set<JobStatus>(statusList);
217
+ }
218
+
219
+ const rows: JobExecution[] = [];
220
+ for (const execution of this.state.executions.values()) {
221
+ if (
222
+ filter.jobInstanceId !== undefined &&
223
+ execution.jobInstanceId !== filter.jobInstanceId
224
+ ) {
225
+ continue;
226
+ }
227
+ if (statuses !== undefined && !statuses.has(execution.status)) continue;
228
+ if (
229
+ filter.startedAfter !== undefined &&
230
+ (execution.startTime === null || execution.startTime < filter.startedAfter)
231
+ ) {
232
+ continue;
233
+ }
234
+ if (
235
+ filter.startedBefore !== undefined &&
236
+ (execution.startTime === null || execution.startTime > filter.startedBefore)
237
+ ) {
238
+ continue;
239
+ }
240
+ rows.push(deepClone(execution));
241
+ }
242
+ return rows;
243
+ }
244
+
245
+ async createStepExecution(jobExecutionId: string, stepName: string): Promise<StepExecution> {
246
+ const step: StepExecution = {
247
+ id: this.idGen.next(),
248
+ jobExecutionId,
249
+ stepName,
250
+ status: StepStatus.STARTING,
251
+ readCount: 0,
252
+ writeCount: 0,
253
+ skipCount: 0,
254
+ rollbackCount: 0,
255
+ commitCount: 0,
256
+ startTime: null,
257
+ endTime: null,
258
+ exitCode: '',
259
+ exitMessage: '',
260
+ };
261
+ this.state.stepExecutions.set(step.id, step);
262
+ return deepClone(step);
263
+ }
264
+
265
+ async updateStepExecution(stepExecutionId: string, patch: StepExecutionPatch): Promise<void> {
266
+ const cur = this.state.stepExecutions.get(stepExecutionId);
267
+ if (!cur) throw new Error(`StepExecution not found: ${stepExecutionId}`);
268
+ const next: StepExecution = { ...cur, ...patch };
269
+ this.state.stepExecutions.set(stepExecutionId, next);
270
+ }
271
+
272
+ async getStepExecution(stepExecutionId: string): Promise<StepExecution | null> {
273
+ const s = this.state.stepExecutions.get(stepExecutionId);
274
+ return s ? deepClone(s) : null;
275
+ }
276
+
277
+ override async findStepExecutions(jobExecutionId: string): Promise<StepExecution[]> {
278
+ const rows: StepExecution[] = [];
279
+ for (const step of this.state.stepExecutions.values()) {
280
+ if (step.jobExecutionId === jobExecutionId) {
281
+ rows.push(deepClone(step));
282
+ }
283
+ }
284
+ return rows;
285
+ }
286
+
287
+ async getExecutionContext(scope: ExecutionScope): Promise<ExecutionContext> {
288
+ const ctx = this.state.contexts.get(scopeKey(scope));
289
+ if (ctx) return { data: deepClone(ctx.data), version: ctx.version };
290
+ return { data: null, version: 0 };
291
+ }
292
+
293
+ async saveExecutionContext(
294
+ scope: ExecutionScope,
295
+ ctx: ExecutionContext,
296
+ version?: number,
297
+ ): Promise<void> {
298
+ // Validate JSON-serializability (per ExecutionContext contract)
299
+ assertJsonSerializable(ctx.data);
300
+ const current = this.state.contexts.get(scopeKey(scope));
301
+ const nextVersion = version !== undefined ? version : (current?.version ?? 0) + 1;
302
+ this.state.contexts.set(scopeKey(scope), {
303
+ data: deepClone(ctx.data),
304
+ version: nextVersion,
305
+ });
306
+ }
307
+
308
+ /**
309
+ * Returns the most recently created StepExecution for the given
310
+ * (jobExecutionId, stepName) pair, or `null` if none exists. Insertion
311
+ * order over `Map` is stable (ES2015+), so a reverse scan picks the
312
+ * latest entry that matches the filter. The restart path filters the
313
+ * result further by status (FAILED) at the call site.
314
+ */
315
+ async findLatestStepExecution(
316
+ jobExecutionId: string,
317
+ stepName: string,
318
+ ): Promise<StepExecution | null> {
319
+ let latest: StepExecution | null = null;
320
+ for (const step of this.state.stepExecutions.values()) {
321
+ if (step.jobExecutionId === jobExecutionId && step.stepName === stepName) {
322
+ if (!latest) {
323
+ latest = step;
324
+ }
325
+ // No monotonic timestamp on StepExecution itself; rely on Map
326
+ // insertion order: later-created entries override earlier ones.
327
+ latest = step;
328
+ }
329
+ }
330
+ return latest ? deepClone(latest) : null;
331
+ }
332
+ }
333
+
334
+ export const RESTARTABLE_DEFAULT_INMEMORY = false;
@@ -0,0 +1 @@
1
+ export * from './in-memory-job-repository';
@@ -0,0 +1,3 @@
1
+ export * from './id-generator';
2
+ export * from './uuid-v7';
3
+ export * from './in-memory';
@@ -0,0 +1,40 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ /**
4
+ * UUID v7 generator — time-sortable IDs (first 48 bits are unix time ms).
5
+ *
6
+ * Suitable for execution IDs (JobExecution, StepExecution) where
7
+ * chronological ordering is useful — e.g., grouping recent runs in a
8
+ * log dashboard or in a B-tree keyed persistence layer.
9
+ *
10
+ * Layout per RFC 9562 §5.7:
11
+ * xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
12
+ * ^ts hi ^ts lo ^rand ^rand ^rand
13
+ * where:
14
+ * - positions 0-11 = 48-bit unix-ms timestamp (sortable)
15
+ * - position 14 = version nibble '7'
16
+ * - position 19 = variant nibble ∈ {8, 9, a, b} (top 2 bits = 10)
17
+ * - remaining 62 = random bits from `crypto.randomBytes(10)`
18
+ */
19
+ export class UuidV7IdGenerator {
20
+ next(): string {
21
+ // 48-bit timestamp in hex (12 chars) — Date.now() is bounded to ~2^41
22
+ // today, so padStart keeps it stable-width for predictable sorting.
23
+ const ts = Date.now().toString(16).padStart(12, '0');
24
+
25
+ // 80 random bits (10 bytes). We use byte 0's top nibble as the
26
+ // variant and place it explicitly in group 4; the remaining 9
27
+ // bytes (18 hex chars) fill the rest of the random slots.
28
+ const rand = randomBytes(10);
29
+
30
+ // RFC 9562 §4.1 variant: top 2 bits of the variant nibble must be `10`.
31
+ // `& 0x3f` clears the top 2 bits, then `| 0x80` sets them to `10`,
32
+ // yielding a value in [0x80, 0xbf] whose hex first digit ∈ {8,9,a,b}.
33
+ const variantNibble = ((((rand[0] ?? 0) & 0x3f) | 0x80).toString(16) as string)[0];
34
+
35
+ // 18 hex chars of pure random from the remaining 9 bytes.
36
+ const rest = rand.subarray(1).toString('hex');
37
+
38
+ return `${ts.slice(0, 8)}-${ts.slice(8, 12)}-7${rest.slice(0, 3)}-${variantNibble}${rest.slice(3, 6)}-${rest.slice(6, 18)}`;
39
+ }
40
+ }
@@ -0,0 +1,257 @@
1
+ import 'reflect-metadata';
2
+ import { SetMetadata } from '@nestjs/common';
3
+
4
+ import { BATCH_SCHEDULED_OPTIONS as SCHEDULED_KEY } from '../decorators/constants';
5
+
6
+ export const BATCH_SCHEDULED_OPTIONS = SCHEDULED_KEY;
7
+
8
+ /**
9
+ * Overlap policies for cron-scheduled jobs:
10
+ *
11
+ * - `'skip'` — drop the new tick if the previous run is still in flight.
12
+ * - `'queue'` — buffer the new tick and start it after the current one ends.
13
+ * - `'parallel'` — start the new tick alongside the current one.
14
+ *
15
+ * The runtime scheduler (the future `@nest-batch/bullmq` cron strategy)
16
+ * reads this value off the stored metadata and applies the policy at
17
+ * dispatch time. The decorator itself MUST NOT silently default the
18
+ * policy to `'skip'` on the user's behalf — leaving it `undefined` here
19
+ * is the contract: the runtime applies the default.
20
+ */
21
+ export type BatchOverlapPolicy = 'skip' | 'queue' | 'parallel';
22
+
23
+ /**
24
+ * Decorator-facing options for `@BatchScheduled`.
25
+ *
26
+ * - `name` — required, unique per job (used as the scheduling key).
27
+ * - `timezone` — required, IANA zone (e.g. `'UTC'`, `'Asia/Seoul'`).
28
+ * - `overlap` — optional, see `BatchOverlapPolicy`. Default applied at
29
+ * runtime, never silently here.
30
+ * - `startAt` — optional, absolute lower bound. Preserved by reference.
31
+ * - `endAt` — optional, absolute upper bound. Preserved by reference.
32
+ * - `inert` — optional, hints the runtime scheduler to skip actual
33
+ * registration. The decorator stamps this by reading
34
+ * `process.env.BATCH_SCHEDULED_DISABLE` at decoration time
35
+ * so the runtime never has to re-evaluate the env.
36
+ */
37
+ export interface BatchScheduledOptions {
38
+ name: string;
39
+ timezone: string;
40
+ overlap?: BatchOverlapPolicy;
41
+ startAt?: Date;
42
+ endAt?: Date;
43
+ inert?: boolean;
44
+ }
45
+
46
+ /**
47
+ * The shape stored under the `BATCH_SCHEDULED_OPTIONS` metadata key on
48
+ * the decorated method function. The runtime scheduler (the future
49
+ * `@nest-batch/bullmq` cron strategy) reads this verbatim to register
50
+ * the job with the underlying scheduler.
51
+ *
52
+ * Note: `inert` lives at the top level (not inside `options`) on
53
+ * purpose. It is a *runtime* flag captured at decoration time from
54
+ * `process.env.BATCH_SCHEDULED_DISABLE`; it is not a user-facing
55
+ * knob in `BatchScheduledOptions`. (The `inert?: boolean` slot on
56
+ * `BatchScheduledOptions` is the user-facing counterpart — see the
57
+ * GREEN half of Task 13 to wire it up.)
58
+ */
59
+ export interface BatchScheduledMetadata {
60
+ cron: string;
61
+ options: BatchScheduledOptions;
62
+ inert: boolean;
63
+ }
64
+
65
+ /**
66
+ * `@BatchScheduled` — cron decorator.
67
+ *
68
+ * Stamps `BATCH_SCHEDULED_OPTIONS` metadata onto the decorated method's
69
+ * function reference (via `@nestjs/common`'s `SetMetadata`, which writes
70
+ * to `descriptor.value`), so `Reflect.getMetadata(KEY, Job.prototype.run)`
71
+ * returns the stored shape.
72
+ *
73
+ * This is the TDD-RED half of Task 13. It deliberately implements only
74
+ * the metadata-storing contract — i.e. the 10 happy-path assertions in
75
+ * `tests/scheduling/batch-scheduled.test.ts` (sections A + B). The
76
+ * 7 negative-path assertions in sections C + D are the GREEN half
77
+ * of the contract and land in the next task:
78
+ *
79
+ * 1. Cron expression shape validation (5/6 fields, no literal words).
80
+ * 2. IANA timezone validation (rejects unknown / empty / whitespace).
81
+ *
82
+ * The inert-mode flag (section E) is wired up here because it is also
83
+ * a pure metadata capture — `process.env.BATCH_SCHEDULED_DISABLE` is
84
+ * read at decoration time and stamped onto the stored shape. The
85
+ * decorator does NOT install any timer, interval, or scheduler
86
+ * registration at decoration time; `inert` is a hint the future
87
+ * runtime scheduler honors when it later walks the class.
88
+ *
89
+ * The decorator is metadata-only by design — it does NOT depend on
90
+ * `cron` (the boundary test from Task 2 still passes — no `cron`
91
+ * import appears in core).
92
+ */
93
+ // ---------------------------------------------------------------------------
94
+ // Validation helpers
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Minimum + maximum number of whitespace-separated fields a valid
99
+ * cron expression can have. `cronstrue` / Linux
100
+ * `crontab(5)` all agree on 5 (minute, hour, day-of-month, month,
101
+ * day-of-week); Quartz-style extensions add a leading seconds field
102
+ * for 6.
103
+ */
104
+ const CRON_MIN_FIELDS = 5;
105
+ const CRON_MAX_FIELDS = 6;
106
+
107
+ /**
108
+ * Cron-shape check.
109
+ *
110
+ * Accepts:
111
+ * - 5 fields: `minute hour dom month dow` (Linux crontab style)
112
+ * - 6 fields: `second minute hour dom month dow` (extended cron style)
113
+ *
114
+ * Each field is `\S+` (one or more non-whitespace tokens, no empty
115
+ * fields). The trailing `\S+$` is the final field; the leading
116
+ * `(\S+\s+){4,5}` captures the preceding 4 or 5 fields separated by
117
+ * a single whitespace run.
118
+ *
119
+ * This is a shape check, not a semantic one — `99 99 99 99 99` still
120
+ * passes the regex. The runtime scheduler (in `@nest-batch/bullmq`)
121
+ * is the layer that handles semantic validation via `cron-parser`.
122
+ * The shape check exists so the decorator fails fast on
123
+ * unambiguously-wrong input (empty string, too few / too many
124
+ * fields, literal English words).
125
+ */
126
+ const CRON_SHAPE = /^(\S+\s+){4,5}\S+$/;
127
+
128
+ /**
129
+ * IANA timezone validation.
130
+ *
131
+ * The platform's `Intl.DateTimeFormat` is the canonical, no-dep
132
+ * source of truth for valid IANA zone identifiers: it throws a
133
+ * `RangeError` for unknown zones. We use it inside a `try/catch` and
134
+ * normalise the result to a boolean so the decorator's failure mode
135
+ * is a deterministic `Error` (not a `RangeError` leaking out).
136
+ *
137
+ * Reference: https://tc39.es/ecma402/#sec-intl-datetimeformat-constructor
138
+ */
139
+ function isValidIanaTimezone(tz: string): boolean {
140
+ try {
141
+ new Intl.DateTimeFormat(undefined, { timeZone: tz });
142
+ return true;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Validate a cron expression. Throws a deterministic
150
+ * `InvalidBatchScheduledCronError` whose `.message` embeds the
151
+ * invalid value verbatim so log lines and test assertions can pin
152
+ * the error to a specific input.
153
+ */
154
+ function assertValidCron(cronExpression: string): void {
155
+ // Type guard: a non-string value would have already broken
156
+ // SetMetadata, but checking here keeps the helper standalone.
157
+ if (typeof cronExpression !== 'string' || cronExpression.length === 0) {
158
+ throw new InvalidBatchScheduledCronError(cronExpression, 'empty');
159
+ }
160
+ if (!CRON_SHAPE.test(cronExpression.trim())) {
161
+ const fieldCount = cronExpression.trim().split(/\s+/).length;
162
+ const reason =
163
+ fieldCount < CRON_MIN_FIELDS
164
+ ? `fewer than ${CRON_MIN_FIELDS} fields`
165
+ : fieldCount > CRON_MAX_FIELDS
166
+ ? `more than ${CRON_MAX_FIELDS} fields`
167
+ : 'not a valid cron expression';
168
+ throw new InvalidBatchScheduledCronError(cronExpression, reason);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Validate a timezone string. Throws a deterministic
174
+ * `InvalidBatchScheduledTimezoneError` whose `.message` embeds the
175
+ * invalid value verbatim.
176
+ */
177
+ function assertValidTimezone(tz: string): void {
178
+ if (typeof tz !== 'string' || tz.trim().length === 0) {
179
+ throw new InvalidBatchScheduledTimezoneError(tz, 'empty');
180
+ }
181
+ if (!isValidIanaTimezone(tz)) {
182
+ throw new InvalidBatchScheduledTimezoneError(tz, 'not a valid IANA timezone');
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Thrown by `@BatchScheduled` when the cron expression fails the
188
+ * shape check (5/6 fields, non-whitespace tokens). The `.message`
189
+ * includes the invalid value and the failure reason so the error
190
+ * is greppable in logs and pinned in test assertions.
191
+ *
192
+ * Exported from the module barrel so adapter packages (e.g. the
193
+ * BullMQ runtime) can `instanceof`-check it without reaching into
194
+ * the decorator's internal helper.
195
+ */
196
+ export class InvalidBatchScheduledCronError extends Error {
197
+ readonly cron: string;
198
+ readonly reason: string;
199
+
200
+ constructor(cron: string, reason: string) {
201
+ super(
202
+ `[BatchScheduled] invalid cron expression "${cron}": ${reason}. ` +
203
+ `Expected ${CRON_MIN_FIELDS} (Linux crontab) or ${CRON_MAX_FIELDS} (extended cron) ` +
204
+ `whitespace-separated fields.`,
205
+ );
206
+ this.name = 'InvalidBatchScheduledCronError';
207
+ this.cron = cron;
208
+ this.reason = reason;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Thrown by `@BatchScheduled` when the IANA timezone fails the
214
+ * platform check. The `.message` embeds the invalid value and the
215
+ * failure reason.
216
+ *
217
+ * Exported from the module barrel so adapter packages can
218
+ * `instanceof`-check it without reaching into the decorator's
219
+ * internal helper.
220
+ */
221
+ export class InvalidBatchScheduledTimezoneError extends Error {
222
+ readonly timezone: string;
223
+ readonly reason: string;
224
+
225
+ constructor(timezone: string, reason: string) {
226
+ super(
227
+ `[BatchScheduled] invalid timezone "${timezone}": ${reason}. ` +
228
+ `Expected a valid IANA zone identifier (e.g. "UTC", "Asia/Seoul", "America/New_York").`,
229
+ );
230
+ this.name = 'InvalidBatchScheduledTimezoneError';
231
+ this.timezone = timezone;
232
+ this.reason = reason;
233
+ }
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Decorator
238
+ // ---------------------------------------------------------------------------
239
+
240
+ export function BatchScheduled(
241
+ cronExpression: string,
242
+ options: BatchScheduledOptions,
243
+ ): MethodDecorator {
244
+ // 1. Validate inputs at decoration time. The decorator is
245
+ // synchronous and metadata-only; the validation runs BEFORE
246
+ // `SetMetadata(...)` is called so a bad input never reaches
247
+ // the registry.
248
+ assertValidCron(cronExpression);
249
+ assertValidTimezone(options.timezone);
250
+
251
+ const meta: BatchScheduledMetadata = {
252
+ cron: cronExpression,
253
+ options,
254
+ inert: process.env.BATCH_SCHEDULED_DISABLE === '1',
255
+ };
256
+ return SetMetadata(BATCH_SCHEDULED_OPTIONS, meta);
257
+ }
@@ -0,0 +1,23 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { randomUUID } from 'crypto';
3
+ import { TransactionContext, TransactionManager } from '../core/transaction/transaction-manager';
4
+
5
+ export interface InMemoryTransactionContext extends TransactionContext {
6
+ readonly isActive: true;
7
+ readonly id: string;
8
+ }
9
+
10
+ /**
11
+ * No-op TransactionManager for the in-memory adapter and for tests.
12
+ * Calls `fn(ctx)` directly without any real transactional semantics.
13
+ *
14
+ * For real DB transactions, use a MikroORM/SQL adapter implementation
15
+ * (e.g., `MikroORMTransactionManager`).
16
+ */
17
+ @Injectable()
18
+ export class InMemoryTransactionManager extends TransactionManager {
19
+ async withTransaction<T>(fn: (ctx: InMemoryTransactionContext) => Promise<T>): Promise<T> {
20
+ const ctx: InMemoryTransactionContext = { isActive: true, id: randomUUID() };
21
+ return fn(ctx);
22
+ }
23
+ }
@@ -0,0 +1 @@
1
+ export * from './in-memory-transaction-manager';