@nest-batch/core 0.2.0 → 0.2.2

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 (85) hide show
  1. package/README.md +7 -5
  2. package/dist/src/adapters/in-process.adapter.d.ts +16 -13
  3. package/dist/src/adapters/in-process.adapter.d.ts.map +1 -1
  4. package/dist/src/adapters/in-process.adapter.js +2 -0
  5. package/dist/src/adapters/in-process.adapter.js.map +1 -1
  6. package/dist/src/compiler/definition-compiler.d.ts.map +1 -1
  7. package/dist/src/compiler/definition-compiler.js +3 -0
  8. package/dist/src/compiler/definition-compiler.js.map +1 -1
  9. package/dist/src/core/ir/listener-definition.d.ts +2 -0
  10. package/dist/src/core/ir/listener-definition.d.ts.map +1 -1
  11. package/dist/src/core/item/interfaces.d.ts +14 -4
  12. package/dist/src/core/item/interfaces.d.ts.map +1 -1
  13. package/dist/src/decorators/listener.decorators.d.ts +4 -3
  14. package/dist/src/decorators/listener.decorators.d.ts.map +1 -1
  15. package/dist/src/decorators/listener.decorators.js +6 -3
  16. package/dist/src/decorators/listener.decorators.js.map +1 -1
  17. package/dist/src/execution/chunk-step-executor.d.ts +7 -1
  18. package/dist/src/execution/chunk-step-executor.d.ts.map +1 -1
  19. package/dist/src/execution/chunk-step-executor.js +104 -13
  20. package/dist/src/execution/chunk-step-executor.js.map +1 -1
  21. package/dist/src/execution/in-process-schedule.d.ts +25 -0
  22. package/dist/src/execution/in-process-schedule.d.ts.map +1 -0
  23. package/dist/src/execution/in-process-schedule.js +129 -0
  24. package/dist/src/execution/in-process-schedule.js.map +1 -0
  25. package/dist/src/execution/index.d.ts +1 -0
  26. package/dist/src/execution/index.d.ts.map +1 -1
  27. package/dist/src/execution/index.js +1 -0
  28. package/dist/src/execution/index.js.map +1 -1
  29. package/dist/src/execution/job-executor.d.ts.map +1 -1
  30. package/dist/src/execution/job-executor.js +14 -8
  31. package/dist/src/execution/job-executor.js.map +1 -1
  32. package/dist/src/execution/listener-invoker.d.ts +25 -9
  33. package/dist/src/execution/listener-invoker.d.ts.map +1 -1
  34. package/dist/src/execution/listener-invoker.js +70 -14
  35. package/dist/src/execution/listener-invoker.js.map +1 -1
  36. package/dist/src/execution/tasklet-step-executor.d.ts +4 -1
  37. package/dist/src/execution/tasklet-step-executor.d.ts.map +1 -1
  38. package/dist/src/execution/tasklet-step-executor.js +20 -16
  39. package/dist/src/execution/tasklet-step-executor.js.map +1 -1
  40. package/dist/src/explorer/batch-explorer.d.ts +2 -1
  41. package/dist/src/explorer/batch-explorer.d.ts.map +1 -1
  42. package/dist/src/explorer/batch-explorer.js +3 -0
  43. package/dist/src/explorer/batch-explorer.js.map +1 -1
  44. package/dist/src/index.d.ts +1 -0
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +1 -0
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/module/batch-schedule-registry.d.ts +13 -14
  49. package/dist/src/module/batch-schedule-registry.d.ts.map +1 -1
  50. package/dist/src/module/batch-schedule-registry.js +0 -0
  51. package/dist/src/module/batch-schedule-registry.js.map +1 -1
  52. package/dist/src/module/nest-batch.module.d.ts +4 -3
  53. package/dist/src/module/nest-batch.module.d.ts.map +1 -1
  54. package/dist/src/module/nest-batch.module.js +3 -2
  55. package/dist/src/module/nest-batch.module.js.map +1 -1
  56. package/dist/src/module/tokens.d.ts +5 -6
  57. package/dist/src/module/tokens.d.ts.map +1 -1
  58. package/dist/src/module/tokens.js.map +1 -1
  59. package/dist/src/partition-helpers.d.ts +3 -3
  60. package/dist/src/partition-helpers.d.ts.map +1 -1
  61. package/dist/src/partition-helpers.js +3 -3
  62. package/dist/src/partition-helpers.js.map +1 -1
  63. package/dist/src/scheduling/batch-scheduled.d.ts +9 -11
  64. package/dist/src/scheduling/batch-scheduled.d.ts.map +1 -1
  65. package/dist/src/scheduling/batch-scheduled.js +12 -20
  66. package/dist/src/scheduling/batch-scheduled.js.map +1 -1
  67. package/package.json +4 -1
  68. package/src/adapters/in-process.adapter.ts +18 -13
  69. package/src/compiler/definition-compiler.ts +12 -5
  70. package/src/core/ir/listener-definition.ts +2 -0
  71. package/src/core/item/interfaces.ts +15 -4
  72. package/src/decorators/listener.decorators.ts +11 -13
  73. package/src/execution/chunk-step-executor.ts +212 -18
  74. package/src/execution/in-process-schedule.ts +143 -0
  75. package/src/execution/index.ts +1 -0
  76. package/src/execution/job-executor.ts +30 -21
  77. package/src/execution/listener-invoker.ts +105 -27
  78. package/src/execution/tasklet-step-executor.ts +40 -16
  79. package/src/explorer/batch-explorer.ts +10 -4
  80. package/src/index.ts +1 -0
  81. package/src/module/batch-schedule-registry.ts +0 -0
  82. package/src/module/nest-batch.module.ts +21 -42
  83. package/src/module/tokens.ts +8 -15
  84. package/src/partition-helpers.ts +13 -17
  85. package/src/scheduling/batch-scheduled.ts +22 -32
@@ -27,10 +27,7 @@ import {
27
27
  import { FlowEvaluator } from '../flow/flow-evaluator';
28
28
  import { BATCH_SCHEDULED_OPTIONS } from '../decorators/constants';
29
29
  import type { BatchScheduledMetadata } from '../scheduling/batch-scheduled';
30
- import {
31
- BatchScheduleRegistry,
32
- type BatchScheduleEntry,
33
- } from './batch-schedule-registry';
30
+ import { BatchScheduleRegistry, type BatchScheduleEntry } from './batch-schedule-registry';
34
31
  import {
35
32
  BATCH_SCHEDULE_REGISTRY,
36
33
  JOB_REPOSITORY_TOKEN,
@@ -127,9 +124,7 @@ export interface NestBatchModuleOptions {
127
124
  */
128
125
  export interface NestBatchModuleAsyncOptions {
129
126
  imports?: DynamicModule['imports'];
130
- useFactory: (
131
- ...args: unknown[]
132
- ) => Promise<BatchAdaptersConfig> | BatchAdaptersConfig;
127
+ useFactory: (...args: unknown[]) => Promise<BatchAdaptersConfig> | BatchAdaptersConfig;
133
128
  inject?: readonly unknown[];
134
129
  }
135
130
 
@@ -167,9 +162,10 @@ const OPTIONS_FACTORY: symbol = Symbol.for('@nest-batch/core/OPTIONS_FACTORY');
167
162
  *
168
163
  * The bootstrapper also walks every discovered job for
169
164
  * `@BatchScheduled` metadata and registers the corresponding entries
170
- * into the `BatchScheduleRegistry` so the (future) runtime scheduler
171
- * has a single, stable place to read them from. Today, the registry is
172
- * metadata-only no timers are installed.
165
+ * into the `BatchScheduleRegistry` so scheduler adapters have a single,
166
+ * stable place to read them from. Core itself remains metadata-only —
167
+ * runtime adapters install timers and bridge schedule fires into job
168
+ * launches.
173
169
  */
174
170
  @Injectable()
175
171
  export class BatchBootstrapper implements OnApplicationBootstrap {
@@ -191,9 +187,7 @@ export class BatchBootstrapper implements OnApplicationBootstrap {
191
187
  this.registry.register(def);
192
188
  this.logger.log(`Registered job "${jobId}"`);
193
189
  } catch (err) {
194
- this.logger.error(
195
- `Failed to register job "${jobId}": ${(err as Error).message}`,
196
- );
190
+ this.logger.error(`Failed to register job "${jobId}": ${(err as Error).message}`);
197
191
  throw err;
198
192
  }
199
193
  }
@@ -211,13 +205,13 @@ export class BatchBootstrapper implements OnApplicationBootstrap {
211
205
  for (const name of this.allMethodNames(prototype)) {
212
206
  const fn = prototype[name];
213
207
  if (typeof fn !== 'function') continue;
214
- const meta = Reflect.getMetadata(
215
- BATCH_SCHEDULED_OPTIONS,
216
- fn,
217
- ) as BatchScheduledMetadata | undefined;
208
+ const meta = Reflect.getMetadata(BATCH_SCHEDULED_OPTIONS, fn) as
209
+ | BatchScheduledMetadata
210
+ | undefined;
218
211
  if (!meta) continue;
219
212
  const entry: BatchScheduleEntry = {
220
213
  jobId,
214
+ scheduleName: meta.options.name,
221
215
  methodName: name,
222
216
  cron: meta.cron,
223
217
  timezone: meta.options.timezone,
@@ -229,11 +223,12 @@ export class BatchBootstrapper implements OnApplicationBootstrap {
229
223
  try {
230
224
  this.scheduleRegistry.register(entry);
231
225
  this.logger.log(
232
- `Registered schedule for job "${jobId}"::${name} (cron="${meta.cron}", tz="${meta.options.timezone}")`,
226
+ `Registered schedule for job "${jobId}"::${meta.options.name} ` +
227
+ `(method="${name}", cron="${meta.cron}", tz="${meta.options.timezone}")`,
233
228
  );
234
229
  } catch (err) {
235
230
  this.logger.error(
236
- `Failed to register schedule for job "${jobId}"::${name}: ${
231
+ `Failed to register schedule for job "${jobId}"::${meta.options.name}: ${
237
232
  (err as Error).message
238
233
  }`,
239
234
  );
@@ -303,19 +298,13 @@ export class NestBatchModule {
303
298
 
304
299
  const hasJobRepositoryToken = providers.some(
305
300
  (p) =>
306
- typeof p === 'object' &&
307
- p !== null &&
308
- 'provide' in p &&
309
- p.provide === JOB_REPOSITORY_TOKEN,
301
+ typeof p === 'object' && p !== null && 'provide' in p && p.provide === JOB_REPOSITORY_TOKEN,
310
302
  );
311
303
 
312
304
  const hasJobRepositoryClass = providers.some(
313
305
  (p) =>
314
306
  p === JobRepository ||
315
- (typeof p === 'object' &&
316
- p !== null &&
317
- 'provide' in p &&
318
- p.provide === JobRepository),
307
+ (typeof p === 'object' && p !== null && 'provide' in p && p.provide === JobRepository),
319
308
  );
320
309
 
321
310
  if (hasJobRepositoryToken && !hasJobRepositoryClass) {
@@ -336,10 +325,7 @@ export class NestBatchModule {
336
325
  const hasTransactionManagerClass = providers.some(
337
326
  (p) =>
338
327
  p === TransactionManager ||
339
- (typeof p === 'object' &&
340
- p !== null &&
341
- 'provide' in p &&
342
- p.provide === TransactionManager),
328
+ (typeof p === 'object' && p !== null && 'provide' in p && p.provide === TransactionManager),
343
329
  );
344
330
 
345
331
  if (hasTransactionManagerToken && !hasTransactionManagerClass) {
@@ -449,11 +435,7 @@ export class NestBatchModule {
449
435
  return {
450
436
  module: NestBatchModule,
451
437
  global: true,
452
- imports: [
453
- adapters.persistence.module,
454
- adapters.transport.module,
455
- DiscoveryModule,
456
- ],
438
+ imports: [adapters.persistence.module, adapters.transport.module, DiscoveryModule],
457
439
  providers: [
458
440
  // Core classes (discovery + compile + register).
459
441
  JobRegistry,
@@ -556,9 +538,7 @@ export class NestBatchModule {
556
538
  const factoryResult = useFactory(...injectValues);
557
539
 
558
540
  if (factoryResult instanceof Promise) {
559
- return factoryResult.then((adapters) =>
560
- NestBatchModule.buildAsyncModule(options, adapters),
561
- );
541
+ return factoryResult.then((adapters) => NestBatchModule.buildAsyncModule(options, adapters));
562
542
  }
563
543
 
564
544
  return NestBatchModule.buildAsyncModule(options, factoryResult);
@@ -599,9 +579,8 @@ export class NestBatchModule {
599
579
  // read the resolved config by its stable symbol.
600
580
  const optionsProvider: Provider = {
601
581
  provide: MODULE_OPTIONS_TOKEN,
602
- useFactory: (
603
- fromFactory: BatchAdaptersConfig | undefined,
604
- ): BatchAdaptersConfig | undefined => fromFactory,
582
+ useFactory: (fromFactory: BatchAdaptersConfig | undefined): BatchAdaptersConfig | undefined =>
583
+ fromFactory,
605
584
  inject: [OPTIONS_FACTORY],
606
585
  };
607
586
 
@@ -34,9 +34,7 @@ import { EXECUTION_STRATEGY } from '../execution/execution-strategy';
34
34
  * does NOT ship a default binding because the choice of persistence
35
35
  * backend is the host's decision.
36
36
  */
37
- export const JOB_REPOSITORY_TOKEN: symbol = Symbol.for(
38
- '@nest-batch/core/JOB_REPOSITORY',
39
- );
37
+ export const JOB_REPOSITORY_TOKEN: symbol = Symbol.for('@nest-batch/core/JOB_REPOSITORY');
40
38
 
41
39
  /**
42
40
  * Injection token for the `TransactionManager` implementation.
@@ -45,20 +43,17 @@ export const JOB_REPOSITORY_TOKEN: symbol = Symbol.for(
45
43
  * `JobRepository` implementation is expected to participate in the same
46
44
  * transaction (e.g. share the same `EntityManager` / `DataSource`).
47
45
  */
48
- export const TRANSACTION_MANAGER_TOKEN: symbol = Symbol.for(
49
- '@nest-batch/core/TRANSACTION_MANAGER',
50
- );
46
+ export const TRANSACTION_MANAGER_TOKEN: symbol = Symbol.for('@nest-batch/core/TRANSACTION_MANAGER');
51
47
 
52
48
  /**
53
49
  * Injection token for the `BatchScheduleRegistry` provider.
54
50
  *
55
51
  * The `BatchExplorer` populates this registry with `@BatchScheduled`
56
- * metadata it discovers on `@Jobable` classes. The future runtime
57
- * scheduler (the `@nest-batch/bullmq` cron strategy, or a sibling
58
- * scheduling package) reads from this registry to install the actual
59
- * timers. Keeping the registry as a stable token means adapters can
60
- * inject it (for introspection / health checks) without depending on
61
- * the explorer's internal state.
52
+ * metadata it discovers on `@Jobable` classes. Scheduler adapters read
53
+ * from this registry to install the actual timers or external schedules.
54
+ * Keeping the registry as a stable token means adapters can inject it
55
+ * (for introspection / health checks) without depending on the explorer's
56
+ * internal state.
62
57
  */
63
58
  export const BATCH_SCHEDULE_REGISTRY: symbol = Symbol.for(
64
59
  '@nest-batch/core/BATCH_SCHEDULE_REGISTRY',
@@ -78,9 +73,7 @@ export const BATCH_SCHEDULE_REGISTRY: symbol = Symbol.for(
78
73
  * T1 type-contract refactor — hosts that need the options bag should
79
74
  * inject `MODULE_OPTIONS_TOKEN` instead.
80
75
  */
81
- export const MODULE_OPTIONS_TOKEN: symbol = Symbol.for(
82
- '@nest-batch/core/MODULE_OPTIONS',
83
- );
76
+ export const MODULE_OPTIONS_TOKEN: symbol = Symbol.for('@nest-batch/core/MODULE_OPTIONS');
84
77
 
85
78
  /**
86
79
  * Polymorphic execution strategy token.
@@ -4,7 +4,7 @@
4
4
  * This module is the single source of truth for partition validation
5
5
  * and the default partition-range shape. It is deliberately
6
6
  * dependency-light: no `@nest-batch/bullmq`, no `@nest-batch/kafka`,
7
- * no ORMs, no cron — verified by
7
+ * no ORMs — verified by
8
8
  * `packages/core/tests/core/boundary/no-forbidden-imports.test.ts`.
9
9
  *
10
10
  * The helpers are consumed by:
@@ -14,10 +14,10 @@
14
14
  * (cross-checks the resolved `JobDefinition`),
15
15
  * - `packages/core/src/execution/in-process-execution-strategy.ts`
16
16
  * (the in-process adapter's partition guard),
17
- * - `packages/bullmq/src/bullmq-runtime.service.ts` (the BullMQ
17
+ * - `packages/bullmq/src/bullmq-runtime.ts` (the BullMQ
18
18
  * strategy's enqueue fan-out + the worker's `partitionIndex`
19
19
  * enforcement),
20
- * - `packages/kafka/src/kafka-runtime.service.ts` (the Kafka
20
+ * - `packages/kafka/src/kafka-runtime.ts` (the Kafka
21
21
  * mirror — T9).
22
22
  *
23
23
  * Pinned by:
@@ -48,7 +48,10 @@ export const INVALID_PARTITION_INDEX = 'INVALID_PARTITION_INDEX';
48
48
  */
49
49
  export class InvalidPartitionsError extends Error {
50
50
  readonly code: string;
51
- constructor(message: string, public readonly details?: unknown) {
51
+ constructor(
52
+ message: string,
53
+ public readonly details?: unknown,
54
+ ) {
52
55
  super(message);
53
56
  this.name = 'InvalidPartitionsError';
54
57
  this.code = 'INVALID_PARTITIONS';
@@ -109,10 +112,10 @@ export function defaultRange(
109
112
  total: number,
110
113
  ): readonly [from: number, to: number] {
111
114
  if (!Number.isInteger(i) || i < 0 || i >= n) {
112
- throw new InvalidPartitionsError(
113
- `defaultRange: partition index ${i} out of range [0, ${n})`,
114
- { i, n },
115
- );
115
+ throw new InvalidPartitionsError(`defaultRange: partition index ${i} out of range [0, ${n})`, {
116
+ i,
117
+ n,
118
+ });
116
119
  }
117
120
  if (!Number.isInteger(n) || n <= 0) {
118
121
  throw new InvalidPartitionsError(`defaultRange: count ${n} must be a positive integer`, { n });
@@ -140,21 +143,14 @@ export function defaultRange(
140
143
  * surfaces it as `FAILED` with the invariant violation in the
141
144
  * `exitMessage`).
142
145
  */
143
- export function enforcePartitionIndex(
144
- partitionIndex: number | undefined,
145
- count: number,
146
- ): void {
146
+ export function enforcePartitionIndex(partitionIndex: number | undefined, count: number): void {
147
147
  if (partitionIndex === undefined) {
148
148
  throw new InvalidPartitionsError(
149
149
  `partitionIndex is required for a partitioned step (count=${count})`,
150
150
  { count },
151
151
  );
152
152
  }
153
- if (
154
- !Number.isInteger(partitionIndex) ||
155
- partitionIndex < 0 ||
156
- partitionIndex >= count
157
- ) {
153
+ if (!Number.isInteger(partitionIndex) || partitionIndex < 0 || partitionIndex >= count) {
158
154
  throw new InvalidPartitionsError(
159
155
  `partitionIndex ${partitionIndex} is out of range [0, ${count})`,
160
156
  { partitionIndex, count },
@@ -12,11 +12,10 @@ export const BATCH_SCHEDULED_OPTIONS = SCHEDULED_KEY;
12
12
  * - `'queue'` — buffer the new tick and start it after the current one ends.
13
13
  * - `'parallel'` — start the new tick alongside the current one.
14
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.
15
+ * The active scheduler adapter reads this value off the stored metadata
16
+ * and applies the policy at dispatch time. The decorator itself MUST NOT
17
+ * silently default the policy to `'skip'` on the user's behalf — leaving
18
+ * it `undefined` here is the contract: the runtime applies the default.
20
19
  */
21
20
  export type BatchOverlapPolicy = 'skip' | 'queue' | 'parallel';
22
21
 
@@ -45,9 +44,8 @@ export interface BatchScheduledOptions {
45
44
 
46
45
  /**
47
46
  * 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.
47
+ * the decorated method function. Scheduler adapters read this verbatim
48
+ * to register the job with the underlying scheduler.
51
49
  *
52
50
  * Note: `inert` lives at the top level (not inside `options`) on
53
51
  * purpose. It is a *runtime* flag captured at decoration time from
@@ -70,25 +68,17 @@ export interface BatchScheduledMetadata {
70
68
  * to `descriptor.value`), so `Reflect.getMetadata(KEY, Job.prototype.run)`
71
69
  * returns the stored shape.
72
70
  *
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).
71
+ * The decorator validates cron/timezone inputs, stores metadata, and
72
+ * captures inert mode. `process.env.BATCH_SCHEDULED_DISABLE` is read at
73
+ * decoration time and stamped onto the stored shape. The decorator does
74
+ * NOT install any timer, interval, or scheduler registration at decoration
75
+ * time; runtime scheduler adapters honor the metadata when they later
76
+ * walk the `BatchScheduleRegistry`.
77
+ *
78
+ * The decorator is metadata-only by design it does NOT install
79
+ * timers or import the runtime cron engine. The in-process transport
80
+ * owns that dependency because it is the layer that turns metadata
81
+ * into actual launches.
92
82
  */
93
83
  // ---------------------------------------------------------------------------
94
84
  // Validation helpers
@@ -117,8 +107,8 @@ const CRON_MAX_FIELDS = 6;
117
107
  * a single whitespace run.
118
108
  *
119
109
  * 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`.
110
+ * passes the regex. The active scheduler adapter is the layer that
111
+ * handles semantic validation with its scheduler backend.
122
112
  * The shape check exists so the decorator fails fast on
123
113
  * unambiguously-wrong input (empty string, too few / too many
124
114
  * fields, literal English words).
@@ -189,9 +179,9 @@ function assertValidTimezone(tz: string): void {
189
179
  * includes the invalid value and the failure reason so the error
190
180
  * is greppable in logs and pinned in test assertions.
191
181
  *
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.
182
+ * Exported from the module barrel so adapter packages can
183
+ * `instanceof`-check it without reaching into the decorator's internal
184
+ * helper.
195
185
  */
196
186
  export class InvalidBatchScheduledCronError extends Error {
197
187
  readonly cron: string;