@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.
- package/README.md +7 -5
- package/dist/src/adapters/in-process.adapter.d.ts +16 -13
- package/dist/src/adapters/in-process.adapter.d.ts.map +1 -1
- package/dist/src/adapters/in-process.adapter.js +2 -0
- package/dist/src/adapters/in-process.adapter.js.map +1 -1
- package/dist/src/compiler/definition-compiler.d.ts.map +1 -1
- package/dist/src/compiler/definition-compiler.js +3 -0
- package/dist/src/compiler/definition-compiler.js.map +1 -1
- package/dist/src/core/ir/listener-definition.d.ts +2 -0
- package/dist/src/core/ir/listener-definition.d.ts.map +1 -1
- package/dist/src/core/item/interfaces.d.ts +14 -4
- package/dist/src/core/item/interfaces.d.ts.map +1 -1
- package/dist/src/decorators/listener.decorators.d.ts +4 -3
- package/dist/src/decorators/listener.decorators.d.ts.map +1 -1
- package/dist/src/decorators/listener.decorators.js +6 -3
- package/dist/src/decorators/listener.decorators.js.map +1 -1
- package/dist/src/execution/chunk-step-executor.d.ts +7 -1
- package/dist/src/execution/chunk-step-executor.d.ts.map +1 -1
- package/dist/src/execution/chunk-step-executor.js +104 -13
- package/dist/src/execution/chunk-step-executor.js.map +1 -1
- package/dist/src/execution/in-process-schedule.d.ts +25 -0
- package/dist/src/execution/in-process-schedule.d.ts.map +1 -0
- package/dist/src/execution/in-process-schedule.js +129 -0
- package/dist/src/execution/in-process-schedule.js.map +1 -0
- package/dist/src/execution/index.d.ts +1 -0
- package/dist/src/execution/index.d.ts.map +1 -1
- package/dist/src/execution/index.js +1 -0
- package/dist/src/execution/index.js.map +1 -1
- package/dist/src/execution/job-executor.d.ts.map +1 -1
- package/dist/src/execution/job-executor.js +14 -8
- package/dist/src/execution/job-executor.js.map +1 -1
- package/dist/src/execution/listener-invoker.d.ts +25 -9
- package/dist/src/execution/listener-invoker.d.ts.map +1 -1
- package/dist/src/execution/listener-invoker.js +70 -14
- package/dist/src/execution/listener-invoker.js.map +1 -1
- package/dist/src/execution/tasklet-step-executor.d.ts +4 -1
- package/dist/src/execution/tasklet-step-executor.d.ts.map +1 -1
- package/dist/src/execution/tasklet-step-executor.js +20 -16
- package/dist/src/execution/tasklet-step-executor.js.map +1 -1
- package/dist/src/explorer/batch-explorer.d.ts +2 -1
- package/dist/src/explorer/batch-explorer.d.ts.map +1 -1
- package/dist/src/explorer/batch-explorer.js +3 -0
- package/dist/src/explorer/batch-explorer.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/module/batch-schedule-registry.d.ts +13 -14
- package/dist/src/module/batch-schedule-registry.d.ts.map +1 -1
- package/dist/src/module/batch-schedule-registry.js +0 -0
- package/dist/src/module/batch-schedule-registry.js.map +1 -1
- package/dist/src/module/nest-batch.module.d.ts +4 -3
- package/dist/src/module/nest-batch.module.d.ts.map +1 -1
- package/dist/src/module/nest-batch.module.js +3 -2
- package/dist/src/module/nest-batch.module.js.map +1 -1
- package/dist/src/module/tokens.d.ts +5 -6
- package/dist/src/module/tokens.d.ts.map +1 -1
- package/dist/src/module/tokens.js.map +1 -1
- package/dist/src/partition-helpers.d.ts +3 -3
- package/dist/src/partition-helpers.d.ts.map +1 -1
- package/dist/src/partition-helpers.js +3 -3
- package/dist/src/partition-helpers.js.map +1 -1
- package/dist/src/scheduling/batch-scheduled.d.ts +9 -11
- package/dist/src/scheduling/batch-scheduled.d.ts.map +1 -1
- package/dist/src/scheduling/batch-scheduled.js +12 -20
- package/dist/src/scheduling/batch-scheduled.js.map +1 -1
- package/package.json +4 -1
- package/src/adapters/in-process.adapter.ts +18 -13
- package/src/compiler/definition-compiler.ts +12 -5
- package/src/core/ir/listener-definition.ts +2 -0
- package/src/core/item/interfaces.ts +15 -4
- package/src/decorators/listener.decorators.ts +11 -13
- package/src/execution/chunk-step-executor.ts +212 -18
- package/src/execution/in-process-schedule.ts +143 -0
- package/src/execution/index.ts +1 -0
- package/src/execution/job-executor.ts +30 -21
- package/src/execution/listener-invoker.ts +105 -27
- package/src/execution/tasklet-step-executor.ts +40 -16
- package/src/explorer/batch-explorer.ts +10 -4
- package/src/index.ts +1 -0
- package/src/module/batch-schedule-registry.ts +0 -0
- package/src/module/nest-batch.module.ts +21 -42
- package/src/module/tokens.ts +8 -15
- package/src/partition-helpers.ts +13 -17
- package/src/scheduling/batch-scheduled.ts +22 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/execution/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/execution/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iCAAiC,CAAC;AAChD,cAAc,uBAAuB,CAAC"}
|
|
@@ -15,6 +15,7 @@ _export_star(require("./job-key"), exports);
|
|
|
15
15
|
_export_star(require("./execution-strategy"), exports);
|
|
16
16
|
_export_star(require("./ref-resolver"), exports);
|
|
17
17
|
_export_star(require("./in-process-execution-strategy"), exports);
|
|
18
|
+
_export_star(require("./in-process-schedule"), exports);
|
|
18
19
|
function _export_star(from, to) {
|
|
19
20
|
Object.keys(from).forEach(function(k) {
|
|
20
21
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/execution/index.ts"],"sourcesContent":["export * from './listener-invoker';\nexport * from './tasklet-step-executor';\nexport * from './chunk-step-executor';\nexport * from './job-executor';\nexport * from './job-launcher';\nexport * from './job-explorer';\nexport * from './job-operator';\nexport * from './batch-worker-runner';\nexport * from './external-task-execution-strategy';\nexport * from './job-key';\nexport * from './execution-strategy';\nexport * from './ref-resolver';\nexport * from './in-process-execution-strategy';\n"],"names":[],"mappings":";;;;qBAAc;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA"}
|
|
1
|
+
{"version":3,"sources":["../../../src/execution/index.ts"],"sourcesContent":["export * from './listener-invoker';\nexport * from './tasklet-step-executor';\nexport * from './chunk-step-executor';\nexport * from './job-executor';\nexport * from './job-launcher';\nexport * from './job-explorer';\nexport * from './job-operator';\nexport * from './batch-worker-runner';\nexport * from './external-task-execution-strategy';\nexport * from './job-key';\nexport * from './execution-strategy';\nexport * from './ref-resolver';\nexport * from './in-process-execution-strategy';\nexport * from './in-process-schedule';\n"],"names":[],"mappings":";;;;qBAAc;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA;qBACA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-executor.d.ts","sourceRoot":"","sources":["../../../src/execution/job-executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAsB,MAAM,YAAY,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGzD,OAAO,EAAE,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAA6B,MAAM,uBAAuB,CAAC;AACrF,OAAO,
|
|
1
|
+
{"version":3,"file":"job-executor.d.ts","sourceRoot":"","sources":["../../../src/execution/job-executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAsB,MAAM,YAAY,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGzD,OAAO,EAAE,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAA6B,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,eAAe,EAA2C,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAa1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBACa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAE3B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,UAAU,EAAE,aAAa,EAEzB,kBAAkB,EAAE,kBAAkB,EACtC,eAAe,EAAE,mBAAmB,EACpC,aAAa,EAAE,iBAAiB,EAChC,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,EAE5B,QAAQ,GAAE,aAAuC;IAGpE;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,OAAO,CACX,SAAS,EAAE,YAAY,EACvB,MAAM,EAAE,aAAa,EACrB,SAAS,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAC/D,OAAO,CAAC,YAAY,CAAC;IAoSxB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,wBAAwB;IAiBhC;;;;;;OAMG;YACW,iBAAiB;YASjB,wBAAwB;IAYtC;;;;OAIG;YACW,IAAI;CAQnB;AAID,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -101,7 +101,8 @@ let JobExecutor = class JobExecutor {
|
|
|
101
101
|
const stepResolvers = this.buildLegacyStepResolvers(jobResolvers);
|
|
102
102
|
await this.listenerInvoker.invokeBefore(jobResolvers, 'job', {
|
|
103
103
|
jobExecutionId: execution.id,
|
|
104
|
-
stepExecutionId: '<job>'
|
|
104
|
+
stepExecutionId: '<job>',
|
|
105
|
+
jobParameters: execution.params
|
|
105
106
|
});
|
|
106
107
|
// Cache the step order once. `Object.keys` returns insertion order
|
|
107
108
|
// for string keys (per ES2015+), so this is the canonical
|
|
@@ -157,6 +158,9 @@ let JobExecutor = class JobExecutor {
|
|
|
157
158
|
if (step.kind === 'tasklet') {
|
|
158
159
|
result = await this.taskletExecutor.execute(step, {
|
|
159
160
|
jobExecutionId: execution.id,
|
|
161
|
+
stepExecutionId: stepExecution.id,
|
|
162
|
+
stepName: step.id,
|
|
163
|
+
jobParameters: execution.params,
|
|
160
164
|
jobRepository: this.repository,
|
|
161
165
|
transactionManager: this.transactionManager,
|
|
162
166
|
listenerInvoker: this.listenerInvoker,
|
|
@@ -175,9 +179,12 @@ let JobExecutor = class JobExecutor {
|
|
|
175
179
|
result = await this.chunkExecutor.execute(step, {
|
|
176
180
|
jobExecutionId: execution.id,
|
|
177
181
|
stepExecutionId: stepExecution.id,
|
|
182
|
+
stepName: step.id,
|
|
183
|
+
jobParameters: execution.params,
|
|
178
184
|
jobRepository: this.repository,
|
|
179
185
|
transactionManager: this.transactionManager,
|
|
180
186
|
listenerInvoker: this.listenerInvoker,
|
|
187
|
+
listenerResolvers: jobResolvers,
|
|
181
188
|
jobExecutionId2: execution.id,
|
|
182
189
|
resolvers: new Map(),
|
|
183
190
|
...resumeFromChunkIndex !== undefined ? {
|
|
@@ -305,12 +312,11 @@ let JobExecutor = class JobExecutor {
|
|
|
305
312
|
// we re-use it to avoid a second IR walk.
|
|
306
313
|
await this.listenerInvoker.invokeAfter(jobResolvers, 'job', {
|
|
307
314
|
jobExecutionId: execution.id,
|
|
308
|
-
stepExecutionId: '<job>'
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
]);
|
|
315
|
+
stepExecutionId: '<job>',
|
|
316
|
+
jobParameters: execution.params
|
|
317
|
+
}, {
|
|
318
|
+
status: finalStatus
|
|
319
|
+
});
|
|
314
320
|
await this.emit({
|
|
315
321
|
type: finalStatus === _status.JobStatus.COMPLETED ? _observability.BATCH_EVENT.JOB_COMPLETED : _observability.BATCH_EVENT.JOB_FAILED,
|
|
316
322
|
timestamp: new Date(),
|
|
@@ -352,7 +358,7 @@ let JobExecutor = class JobExecutor {
|
|
|
352
358
|
if (fn === null) continue;
|
|
353
359
|
const name = this.resolveListenerName(def.ref, lambdaCounter);
|
|
354
360
|
if (def.ref.kind === _ir.RefKind.BuilderLambda) lambdaCounter += 1;
|
|
355
|
-
const key = `${def.phase}:${def.kind}:${name}`;
|
|
361
|
+
const key = def.kind === 'skip' && def.skipKind !== undefined ? `on-skip:${def.skipKind}:${name}` : `${def.phase}:${def.kind}:${name}`;
|
|
356
362
|
resolvers.set(key, {
|
|
357
363
|
fn,
|
|
358
364
|
...def.nonCritical !== undefined ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/execution/job-executor.ts"],"sourcesContent":["import { Injectable, Inject, Optional, forwardRef, Logger } from '@nestjs/common';\nimport type { JobDefinition, ListenerDefinition } from '../core/ir';\nimport { RefKind, type ListenerRef } from '../core/ir';\nimport { JobRepository, type JobParameters, type JobExecution } from '../core/repository';\nimport { TransactionManager } from '../core/transaction';\nimport { StepStatus, JobStatus, FlowExecutionStatus } from '../core/status';\nimport { JobNotRestartableError } from '../core/errors';\nimport { TaskletStepExecutor, type StepExecutionResult } from './tasklet-step-executor';\nimport { ChunkStepExecutor, type ChunkExecutionResult } from './chunk-step-executor';\nimport {\n ListenerInvoker,\n type ResolverMap,\n type ListenerResolver,\n} from './listener-invoker';\nimport { FlowEvaluator } from '../flow/flow-evaluator';\nimport {\n BATCH_EVENT,\n NoopBatchObserver,\n type BatchEvent,\n type BatchObserver,\n} from '../observability';\n\n/**\n * Result type that covers both tasklet and chunk step outcomes.\n * Structurally compatible with `StepExecutionPatch` so the executor\n * can forward it directly to `updateStepExecution`.\n *\n * The only field chunk has but tasklet doesn't is `commitCount`; for\n * tasklet results it stays `undefined` and `updateStepExecution`\n * happily ignores undefined fields (merge semantics, see repository).\n */\ntype StepResult = StepExecutionResult | ChunkExecutionResult;\n\n/**\n * JobExecutor — drives a single JobExecution to completion.\n *\n * Flow (per ORACLE verdict 3c):\n * 1. Mark execution as STARTED.\n * 2. `before:job:*` listeners.\n * 3. Loop:\n * a. Look up the current step (jobDef.steps[currentStepId]). If the\n * step is missing, mark the job FAILED with exit code\n * `NO_SUCH_STEP` and break.\n * b. Create a StepExecution, run it (tasklet or chunk), and persist\n * the result via `updateStepExecution`. During the run, the\n * step's own `after-step:*` listeners fire (see\n * `TaskletStepExecutor.execute` step 4 / `ChunkStepExecutor`).\n * Those listeners run BEFORE we evaluate transitions so they get\n * a chance to mutate the result (e.g. flip COMPLETED → FAILED)\n * and the resulting flow routing sees the override.\n * c. Map the (possibly overridden) step status to a\n * `FlowExecutionStatus` and ask the `FlowEvaluator` for the next\n * step. `null` means END.\n * d. If the step FAILED and the evaluator returned `null`\n * (no recovery transition matches), short-circuit the job to\n * FAILED — we must not continue running subsequent steps\n * declared in the graph, because none are reachable.\n * 4. `after:job:*` listeners (with the final status).\n *\n * Out of scope (future tasks):\n * - Concurrency control (Task 38).\n * - `on-error:job:*` listener invocation when the executor itself\n * throws (the catch block can be wired to it in a follow-up).\n */\n@Injectable()\nexport class JobExecutor {\n private readonly logger = new Logger(JobExecutor.name);\n\n constructor(\n private readonly repository: JobRepository,\n @Inject(forwardRef(() => TransactionManager))\n private readonly transactionManager: TransactionManager,\n private readonly taskletExecutor: TaskletStepExecutor,\n private readonly chunkExecutor: ChunkStepExecutor,\n private readonly listenerInvoker: ListenerInvoker,\n private readonly flowEvaluator: FlowEvaluator,\n @Optional()\n private readonly observer: BatchObserver = new NoopBatchObserver(),\n ) {}\n\n /**\n * Execute a JobExecution against its `JobDefinition`. Returns the\n * final, persisted `JobExecution` (status = COMPLETED | FAILED).\n *\n * Restart behavior (per Metis verdict 3b — restartable opt-in, per\n * ORACLE 3b — default-on for persisted repositories):\n * - If `execution.status` is `FAILED` on entry, this is a restart\n * attempt. We require `jobDef.restartable === true`; otherwise we\n * throw `JobNotRestartableError` and leave the execution alone.\n * - For each chunk step, we look up the latest FAILED StepExecution\n * for that step in this job execution. If one exists, we read its\n * ExecutionContext's `lastChunkIndex` checkpoint and pass it to the\n * `ChunkStepExecutor` as `resumeFromChunkIndex`, which then skips\n * chunks ≤ that index. Tasklet steps always re-run from scratch\n * (they have no chunk-level resume granularity in this MVP).\n *\n * Partition routing (T8): the optional third argument carries the\n * `partitionIndex` / `partitionCount` pair the transport attached\n * to the job. When set, the chunk executor bounds the read loop\n * to the partition's range (see `packages/core/src/partition-helpers.ts`).\n */\n async execute(\n execution: JobExecution,\n jobDef: JobDefinition,\n partition?: { partitionIndex?: number; partitionCount?: number },\n ): Promise<JobExecution> {\n // Capture the pre-execute status. For a fresh launch, the launcher\n // created the execution with status STARTING; for a restart, the\n // caller (JobLauncher.run) hands us a terminal execution that can\n // be resumed.\n const isRestart =\n execution.status === JobStatus.FAILED || execution.status === JobStatus.STOPPED;\n if (isRestart && !jobDef.restartable) {\n throw new JobNotRestartableError(jobDef.id);\n }\n\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.STARTED,\n startTime: new Date(),\n });\n\n await this.emit({\n type: BATCH_EVENT.JOB_STARTED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n data: { jobName: jobDef.id },\n });\n\n // Build the full resolver map once. The same map powers both the\n // job-level `invokeBefore` / `invokeAfter` calls below and the\n // step-level resolvers handed to the TaskletStepExecutor (derived\n // by `buildLegacyStepResolvers` into the legacy key shape). Building\n // it once per execution avoids re-walking the IR on every step.\n const jobResolvers = this.buildResolverMap(jobDef);\n const stepResolvers = this.buildLegacyStepResolvers(jobResolvers);\n\n await this.listenerInvoker.invokeBefore(jobResolvers, 'job', {\n jobExecutionId: execution.id,\n stepExecutionId: '<job>',\n });\n\n // Cache the step order once. `Object.keys` returns insertion order\n // for string keys (per ES2015+), so this is the canonical\n // declaration order — used for the linear fallback below.\n const stepOrder = Object.keys(jobDef.steps);\n\n let currentStepId: string | null = jobDef.startStepId;\n let finalStatus: JobStatus = JobStatus.COMPLETED;\n let currentStepExecutionId: string | null = null;\n\n try {\n while (currentStepId !== null) {\n const step = jobDef.steps[currentStepId];\n if (!step) {\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.FAILED,\n endTime: new Date(),\n exitCode: 'NO_SUCH_STEP',\n exitMessage: `Step \"${currentStepId}\" not found`,\n });\n finalStatus = JobStatus.FAILED;\n break;\n }\n\n // Restart path: if this is a restart and the current step is a\n // chunk step, locate the latest FAILED step execution for the\n // same step name and load its `lastChunkIndex` checkpoint. That\n // value is passed to the chunk executor as `resumeFromChunkIndex`.\n // For tasklet steps (and chunk steps with no prior failure) we\n // leave `resumeFromChunkIndex` undefined — the chunk executor\n // treats undefined as \"start from the beginning\".\n //\n // Look this up BEFORE createStepExecution so the just-created\n // STARTING step isn't returned as the \"latest\" entry.\n let resumeFromChunkIndex: number | undefined;\n if (isRestart && step.kind === 'chunk') {\n const priorFailed = await this.repository.findLatestStepExecution(execution.id, step.id);\n if (priorFailed && priorFailed.status === StepStatus.FAILED) {\n resumeFromChunkIndex = await this.getLastCheckpoint(priorFailed.id);\n }\n }\n\n const stepExecution = await this.repository.createStepExecution(execution.id, step.id);\n currentStepExecutionId = stepExecution.id;\n\n await this.emit({\n type: BATCH_EVENT.STEP_STARTED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n data: { stepId: step.id, kind: step.kind },\n });\n\n let result: StepResult;\n try {\n if (step.kind === 'tasklet') {\n result = await this.taskletExecutor.execute(step, {\n jobExecutionId: execution.id,\n jobRepository: this.repository,\n transactionManager: this.transactionManager,\n listenerInvoker: this.listenerInvoker,\n listenerResolvers: stepResolvers,\n });\n } else {\n // Forward the partition routing (T8) when the transport\n // supplied one. The chunk executor uses the partition\n // index + count + the step's `partitions.range` to bound\n // its read loop. Absent partition args fall through to\n // the 0.1.0 non-partitioned chunk pipeline.\n const partitionArgs =\n partition?.partitionIndex !== undefined && partition?.partitionCount !== undefined\n ? {\n partitionIndex: partition.partitionIndex,\n partitionCount: partition.partitionCount,\n }\n : {};\n result = await this.chunkExecutor.execute(step, {\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n jobRepository: this.repository,\n transactionManager: this.transactionManager,\n listenerInvoker: this.listenerInvoker,\n jobExecutionId2: execution.id,\n resolvers: new Map(),\n ...(resumeFromChunkIndex !== undefined ? { resumeFromChunkIndex } : {}),\n ...partitionArgs,\n });\n }\n } catch (stepErr) {\n // The executor itself threw (e.g. resolveReader threw in a\n // chunk step before the executor's own try-catch could catch\n // it). Persist the step as FAILED with the error message and\n // re-raise so the outer handler marks the job FAILED.\n await this.repository.updateStepExecution(stepExecution.id, {\n status: StepStatus.FAILED,\n exitCode: 'FAILED',\n exitMessage: stepErr instanceof Error ? stepErr.message : String(stepErr),\n endTime: new Date(),\n });\n currentStepExecutionId = null;\n throw stepErr;\n }\n currentStepExecutionId = null;\n\n await this.repository.updateStepExecution(stepExecution.id, {\n status: result.status,\n ...(result.readCount !== undefined ? { readCount: result.readCount } : {}),\n ...(result.writeCount !== undefined ? { writeCount: result.writeCount } : {}),\n ...(result.skipCount !== undefined ? { skipCount: result.skipCount } : {}),\n ...('commitCount' in result && result.commitCount !== undefined\n ? { commitCount: result.commitCount }\n : {}),\n exitCode: result.exitCode,\n exitMessage: result.exitMessage,\n endTime: new Date(),\n });\n\n await this.emit({\n type:\n result.status === StepStatus.COMPLETED\n ? BATCH_EVENT.STEP_COMPLETED\n : result.status === StepStatus.FAILED\n ? BATCH_EVENT.STEP_FAILED\n : BATCH_EVENT.STEP_COMPLETED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n data: {\n stepId: step.id,\n status: result.status,\n ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),\n },\n });\n\n // Map StepStatus -> FlowExecutionStatus. Anything other than\n // COMPLETED/FAILED collapses to UNKNOWN → evaluator returns\n // null → flow ends.\n const flowStatus: FlowExecutionStatus =\n result.status === StepStatus.COMPLETED\n ? FlowExecutionStatus.COMPLETED\n : result.status === StepStatus.FAILED\n ? FlowExecutionStatus.FAILED\n : FlowExecutionStatus.UNKNOWN;\n const deciderExitStatus = await this.resolveDeciderExitStatus(\n jobDef,\n currentStepId,\n {\n jobExecution: execution,\n stepId: step.id,\n stepExecutionId: stepExecution.id,\n stepStatus: result.status,\n exitCode: result.exitCode,\n exitMessage: result.exitMessage,\n },\n );\n const flowExitStatus = deciderExitStatus ?? (result.exitCode || flowStatus);\n\n let evaluatorResult = await this.flowEvaluator.evaluate(\n jobDef.transitions,\n currentStepId,\n flowExitStatus,\n );\n\n // Distinguish \"no transition declared\" (linear fallback) from\n // \"transition declared with toStepId: null\" (explicit END).\n // FlowEvaluator returns null for both, so we inspect the\n // transition list directly.\n let hasMatchingTransition = jobDef.transitions.some((t) =>\n this.flowEvaluator.matches(t, currentStepId!, flowExitStatus),\n );\n if (!hasMatchingTransition && flowExitStatus !== flowStatus) {\n evaluatorResult = await this.flowEvaluator.evaluate(\n jobDef.transitions,\n currentStepId,\n flowStatus,\n );\n hasMatchingTransition = jobDef.transitions.some((t) =>\n this.flowEvaluator.matches(t, currentStepId!, flowStatus),\n );\n }\n\n let nextStepId: string | null;\n if (hasMatchingTransition) {\n // Explicit transition: respect its target, including null\n // (END). Do not fall through to linear order.\n nextStepId = evaluatorResult;\n } else if (result.status === StepStatus.FAILED) {\n // FAILED with no matching transition → short-circuit. The\n // graph declares no path forward, so the job is FAILED — we\n // must not invent a \"next\" step.\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.FAILED,\n endTime: new Date(),\n exitCode: result.exitCode,\n exitMessage: result.exitMessage,\n });\n finalStatus = JobStatus.FAILED;\n break;\n } else {\n // COMPLETED with no transition → linear fallback to the next\n // step in declaration order. If we're already on the last\n // step, the job ends.\n const currentIdx = stepOrder.indexOf(currentStepId);\n const nextIdx = currentIdx + 1;\n nextStepId = nextIdx < stepOrder.length ? stepOrder[nextIdx]! : null;\n }\n\n currentStepId = nextStepId;\n }\n\n if (finalStatus === JobStatus.COMPLETED) {\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.COMPLETED,\n endTime: new Date(),\n exitCode: 'COMPLETED',\n });\n }\n } catch (err) {\n // Defensive: leave the job FAILED rather than crash the host.\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.FAILED,\n endTime: new Date(),\n exitMessage: err instanceof Error ? err.message : String(err),\n });\n finalStatus = JobStatus.FAILED;\n }\n\n // `after:job:*` listeners run once the job is in a terminal state.\n // They receive the final status as the second positional argument\n // (the `args` slot in the current API; the legacy builder path used\n // the same shape). The resolver map is the same one built above;\n // we re-use it to avoid a second IR walk.\n await this.listenerInvoker.invokeAfter(\n jobResolvers,\n 'job',\n { jobExecutionId: execution.id, stepExecutionId: '<job>' },\n [{ status: finalStatus }],\n );\n\n await this.emit({\n type:\n finalStatus === JobStatus.COMPLETED ? BATCH_EVENT.JOB_COMPLETED : BATCH_EVENT.JOB_FAILED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n data: { status: finalStatus },\n });\n\n return (await this.repository.getJobExecution(execution.id))!;\n }\n\n /**\n * Build a listener resolver map for the given job. Walks every\n * `ListenerDefinition` in `jobDef.listeners` (job-level + step-level +\n * chunk-level + item-level + skip-level) and resolves each ref into a\n * callable `ListenerEntry` keyed by `${phase}:${kind}:${name}`.\n *\n * The returned map is consumed by `ListenerInvoker.invokeBefore /\n * invokeAfter / invokeOnError / invokeOnSkip*` (Task 20 API). The legacy\n * step-level methods (`invokeBeforeStep` etc.) consume a derived\n * legacy-shaped map produced by `buildLegacyStepResolvers` — that\n * conversion happens at the call site, not here, so this method stays\n * the single source of truth for the new shape.\n *\n * Ref resolution rules:\n * - `RefKind.BuilderLambda` → use `ref.fn` directly (the compiler\n * pre-binds decorator-discovered methods\n * and the builder API ships bare fns).\n * - `RefKind.Method` → requires the Jobable instance. Until\n * a `ModuleRef` is wired (Task 9+), this\n * branch logs a warning and is skipped.\n * - `RefKind.ProviderToken` → resolved in Task 9 against a\n * pre-built provider map. Skipped here\n * with a warning.\n */\n private buildResolverMap(jobDef: JobDefinition): ResolverMap {\n const resolvers: ResolverMap = new Map();\n let lambdaCounter = 0;\n\n for (const def of jobDef.listeners) {\n const fn = this.resolveListenerRef(def);\n if (fn === null) continue;\n\n const name = this.resolveListenerName(def.ref, lambdaCounter);\n if (def.ref.kind === RefKind.BuilderLambda) lambdaCounter += 1;\n\n const key = `${def.phase}:${def.kind}:${name}`;\n resolvers.set(key, {\n fn,\n ...(def.nonCritical !== undefined ? { nonCritical: def.nonCritical } : {}),\n });\n }\n\n return resolvers;\n }\n\n /**\n * Resolve a single `ListenerDefinition` to its callable function, or\n * `null` if the ref kind is not yet supported. See `buildResolverMap`\n * for the per-kind resolution contract.\n */\n private resolveListenerRef(def: ListenerDefinition): ((...args: any[]) => any) | null {\n const ref = def.ref;\n switch (ref.kind) {\n case RefKind.BuilderLambda:\n return ref.fn ?? null;\n case RefKind.Method:\n this.logger.warn(\n `JobExecutor: Method-ref listener (classToken=${ref.classToken ?? '<unknown>'}, ` +\n `methodName=${ref.methodName ?? '<unknown>'}) requires a Jobable instance; ` +\n 'this resolution path lands in a follow-up task. Listener skipped.',\n );\n return null;\n case RefKind.ProviderToken:\n this.logger.warn(\n `JobExecutor: ProviderToken-ref listener (token=${ref.token ?? '<empty>'}) ` +\n 'is resolved in Task 9. Listener skipped.',\n );\n return null;\n default: {\n const _exhaustive: never = ref.kind;\n void _exhaustive;\n return null;\n }\n }\n }\n\n /**\n * Derive the `name` segment of the resolver key. Method refs carry a\n * `classToken` + `methodName` pair that uniquely identifies the bound\n * method; BuilderLambda refs do not carry a name (the compiler drops\n * the method name when it pre-binds), so we mint a `lambda-N` name\n * from a per-job counter to guarantee uniqueness.\n */\n private resolveListenerName(ref: ListenerRef, lambdaCounter: number): string {\n if (ref.kind === RefKind.Method) {\n return `${ref.classToken ?? '<unknown>'}.${ref.methodName ?? '<unknown>'}`;\n }\n return `lambda-${lambdaCounter}`;\n }\n\n /**\n * Derive a legacy `Map<string, ListenerResolver>` from a new\n * `ResolverMap`, containing only the step-level entries with their\n * keys translated from `${phase}:step:${name}` back to the legacy\n * `${phase}-step:${name}` shape. The `nonCritical` flag is dropped\n * (legacy `ListenerResolver` is a bare function with no metadata).\n *\n * This is the bridge the `TaskletStepExecutor` (which still consumes\n * the legacy shape) needs until it migrates to the new API. Kept as\n * a private helper so the conversion logic is in one place.\n */\n private buildLegacyStepResolvers(resolvers: ResolverMap): Map<string, ListenerResolver> {\n const legacy: Map<string, ListenerResolver> = new Map();\n for (const [key, entry] of resolvers.entries()) {\n if (key.startsWith('before:step:')) {\n legacy.set(`before-step:${key.slice('before:step:'.length)}`, entry.fn as ListenerResolver);\n } else if (key.startsWith('after:step:')) {\n legacy.set(`after-step:${key.slice('after:step:'.length)}`, entry.fn as ListenerResolver);\n } else if (key.startsWith('on-error:step:')) {\n legacy.set(`on-step-error:${key.slice('on-error:step:'.length)}`, entry.fn as ListenerResolver);\n }\n }\n return legacy;\n }\n\n /**\n * Read the `lastChunkIndex` checkpoint from the step-scoped\n * ExecutionContext for `stepExecutionId`. Returns `undefined` when the\n * step has no recorded checkpoint (e.g., the prior run failed on the\n * very first chunk and never got a chance to write one). The chunk\n * executor treats `undefined` as \"no resume; start from the beginning\".\n */\n private async getLastCheckpoint(stepExecutionId: string): Promise<number | undefined> {\n const ctx = await this.repository.getExecutionContext({ stepExecutionId });\n if (ctx.data === null || typeof ctx.data !== 'object' || Array.isArray(ctx.data)) {\n return undefined;\n }\n const value = (ctx.data as { lastChunkIndex?: unknown }).lastChunkIndex;\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n }\n\n private async resolveDeciderExitStatus(\n jobDef: JobDefinition,\n afterStepId: string,\n context: Parameters<NonNullable<JobDefinition['deciders']>[number]['decide']>[0],\n ): Promise<string | undefined> {\n const decider = (jobDef.deciders ?? []).find((d) => d.afterStepId === afterStepId);\n if (decider === undefined) return undefined;\n const status = await decider.decide(context);\n const trimmed = status.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n /**\n * Dispatch a BatchEvent to the configured observer. Errors thrown by\n * the observer are swallowed: a failing logger/queue must not crash\n * the executor (the job's persisted state is the source of truth).\n */\n private async emit(event: BatchEvent): Promise<void> {\n try {\n await this.observer.onEvent(event);\n } catch {\n // intentional: observer failures are best-effort and must not\n // affect the executor's own state transitions.\n }\n }\n}\n\n// Re-export common types for convenience so callers that import\n// `JobExecutor` don't need a second import for `StepExecutionResult` etc.\nexport type { StepExecutionResult } from './tasklet-step-executor';\nexport type { ChunkExecutionResult } from './chunk-step-executor';\nexport type { JobParameters, JobExecution };\n"],"names":["JobExecutor","logger","Logger","name","repository","transactionManager","taskletExecutor","chunkExecutor","listenerInvoker","flowEvaluator","observer","NoopBatchObserver","execute","execution","jobDef","partition","isRestart","status","JobStatus","FAILED","STOPPED","restartable","JobNotRestartableError","id","updateJobExecution","STARTED","startTime","Date","emit","type","BATCH_EVENT","JOB_STARTED","timestamp","jobExecutionId","data","jobName","jobResolvers","buildResolverMap","stepResolvers","buildLegacyStepResolvers","invokeBefore","stepExecutionId","stepOrder","Object","keys","steps","currentStepId","startStepId","finalStatus","COMPLETED","currentStepExecutionId","step","endTime","exitCode","exitMessage","resumeFromChunkIndex","kind","priorFailed","findLatestStepExecution","StepStatus","getLastCheckpoint","stepExecution","createStepExecution","STEP_STARTED","stepId","result","jobRepository","listenerResolvers","partitionArgs","partitionIndex","undefined","partitionCount","jobExecutionId2","resolvers","Map","stepErr","updateStepExecution","Error","message","String","readCount","writeCount","skipCount","commitCount","STEP_COMPLETED","STEP_FAILED","flowStatus","FlowExecutionStatus","UNKNOWN","deciderExitStatus","resolveDeciderExitStatus","jobExecution","stepStatus","flowExitStatus","evaluatorResult","evaluate","transitions","hasMatchingTransition","some","t","matches","nextStepId","currentIdx","indexOf","nextIdx","length","err","invokeAfter","JOB_COMPLETED","JOB_FAILED","getJobExecution","lambdaCounter","def","listeners","fn","resolveListenerRef","resolveListenerName","ref","RefKind","BuilderLambda","key","phase","set","nonCritical","Method","warn","classToken","methodName","ProviderToken","token","_exhaustive","legacy","entry","entries","startsWith","slice","ctx","getExecutionContext","Array","isArray","value","lastChunkIndex","Number","isFinite","afterStepId","context","decider","deciders","find","d","decide","trimmed","trim","event","onEvent","TransactionManager"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/execution/job-executor.ts"],"sourcesContent":["import { Injectable, Inject, Optional, forwardRef, Logger } from '@nestjs/common';\nimport type { JobDefinition, ListenerDefinition } from '../core/ir';\nimport { RefKind, type ListenerRef } from '../core/ir';\nimport { JobRepository, type JobParameters, type JobExecution } from '../core/repository';\nimport { TransactionManager } from '../core/transaction';\nimport { StepStatus, JobStatus, FlowExecutionStatus } from '../core/status';\nimport { JobNotRestartableError } from '../core/errors';\nimport { TaskletStepExecutor, type StepExecutionResult } from './tasklet-step-executor';\nimport { ChunkStepExecutor, type ChunkExecutionResult } from './chunk-step-executor';\nimport { ListenerInvoker, type ResolverMap, type ListenerResolver } from './listener-invoker';\nimport { FlowEvaluator } from '../flow/flow-evaluator';\nimport {\n BATCH_EVENT,\n NoopBatchObserver,\n type BatchEvent,\n type BatchObserver,\n} from '../observability';\n\n/**\n * Result type that covers both tasklet and chunk step outcomes.\n * Structurally compatible with `StepExecutionPatch` so the executor\n * can forward it directly to `updateStepExecution`.\n *\n * The only field chunk has but tasklet doesn't is `commitCount`; for\n * tasklet results it stays `undefined` and `updateStepExecution`\n * happily ignores undefined fields (merge semantics, see repository).\n */\ntype StepResult = StepExecutionResult | ChunkExecutionResult;\n\n/**\n * JobExecutor — drives a single JobExecution to completion.\n *\n * Flow (per ORACLE verdict 3c):\n * 1. Mark execution as STARTED.\n * 2. `before:job:*` listeners.\n * 3. Loop:\n * a. Look up the current step (jobDef.steps[currentStepId]). If the\n * step is missing, mark the job FAILED with exit code\n * `NO_SUCH_STEP` and break.\n * b. Create a StepExecution, run it (tasklet or chunk), and persist\n * the result via `updateStepExecution`. During the run, the\n * step's own `after-step:*` listeners fire (see\n * `TaskletStepExecutor.execute` step 4 / `ChunkStepExecutor`).\n * Those listeners run BEFORE we evaluate transitions so they get\n * a chance to mutate the result (e.g. flip COMPLETED → FAILED)\n * and the resulting flow routing sees the override.\n * c. Map the (possibly overridden) step status to a\n * `FlowExecutionStatus` and ask the `FlowEvaluator` for the next\n * step. `null` means END.\n * d. If the step FAILED and the evaluator returned `null`\n * (no recovery transition matches), short-circuit the job to\n * FAILED — we must not continue running subsequent steps\n * declared in the graph, because none are reachable.\n * 4. `after:job:*` listeners (with the final status).\n *\n * Out of scope (future tasks):\n * - Concurrency control (Task 38).\n * - `on-error:job:*` listener invocation when the executor itself\n * throws (the catch block can be wired to it in a follow-up).\n */\n@Injectable()\nexport class JobExecutor {\n private readonly logger = new Logger(JobExecutor.name);\n\n constructor(\n private readonly repository: JobRepository,\n @Inject(forwardRef(() => TransactionManager))\n private readonly transactionManager: TransactionManager,\n private readonly taskletExecutor: TaskletStepExecutor,\n private readonly chunkExecutor: ChunkStepExecutor,\n private readonly listenerInvoker: ListenerInvoker,\n private readonly flowEvaluator: FlowEvaluator,\n @Optional()\n private readonly observer: BatchObserver = new NoopBatchObserver(),\n ) {}\n\n /**\n * Execute a JobExecution against its `JobDefinition`. Returns the\n * final, persisted `JobExecution` (status = COMPLETED | FAILED).\n *\n * Restart behavior (per Metis verdict 3b — restartable opt-in, per\n * ORACLE 3b — default-on for persisted repositories):\n * - If `execution.status` is `FAILED` on entry, this is a restart\n * attempt. We require `jobDef.restartable === true`; otherwise we\n * throw `JobNotRestartableError` and leave the execution alone.\n * - For each chunk step, we look up the latest FAILED StepExecution\n * for that step in this job execution. If one exists, we read its\n * ExecutionContext's `lastChunkIndex` checkpoint and pass it to the\n * `ChunkStepExecutor` as `resumeFromChunkIndex`, which then skips\n * chunks ≤ that index. Tasklet steps always re-run from scratch\n * (they have no chunk-level resume granularity in this MVP).\n *\n * Partition routing (T8): the optional third argument carries the\n * `partitionIndex` / `partitionCount` pair the transport attached\n * to the job. When set, the chunk executor bounds the read loop\n * to the partition's range (see `packages/core/src/partition-helpers.ts`).\n */\n async execute(\n execution: JobExecution,\n jobDef: JobDefinition,\n partition?: { partitionIndex?: number; partitionCount?: number },\n ): Promise<JobExecution> {\n // Capture the pre-execute status. For a fresh launch, the launcher\n // created the execution with status STARTING; for a restart, the\n // caller (JobLauncher.run) hands us a terminal execution that can\n // be resumed.\n const isRestart =\n execution.status === JobStatus.FAILED || execution.status === JobStatus.STOPPED;\n if (isRestart && !jobDef.restartable) {\n throw new JobNotRestartableError(jobDef.id);\n }\n\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.STARTED,\n startTime: new Date(),\n });\n\n await this.emit({\n type: BATCH_EVENT.JOB_STARTED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n data: { jobName: jobDef.id },\n });\n\n // Build the full resolver map once. The same map powers both the\n // job-level `invokeBefore` / `invokeAfter` calls below and the\n // step-level resolvers handed to the TaskletStepExecutor (derived\n // by `buildLegacyStepResolvers` into the legacy key shape). Building\n // it once per execution avoids re-walking the IR on every step.\n const jobResolvers = this.buildResolverMap(jobDef);\n const stepResolvers = this.buildLegacyStepResolvers(jobResolvers);\n\n await this.listenerInvoker.invokeBefore(jobResolvers, 'job', {\n jobExecutionId: execution.id,\n stepExecutionId: '<job>',\n jobParameters: execution.params,\n });\n\n // Cache the step order once. `Object.keys` returns insertion order\n // for string keys (per ES2015+), so this is the canonical\n // declaration order — used for the linear fallback below.\n const stepOrder = Object.keys(jobDef.steps);\n\n let currentStepId: string | null = jobDef.startStepId;\n let finalStatus: JobStatus = JobStatus.COMPLETED;\n let currentStepExecutionId: string | null = null;\n\n try {\n while (currentStepId !== null) {\n const step = jobDef.steps[currentStepId];\n if (!step) {\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.FAILED,\n endTime: new Date(),\n exitCode: 'NO_SUCH_STEP',\n exitMessage: `Step \"${currentStepId}\" not found`,\n });\n finalStatus = JobStatus.FAILED;\n break;\n }\n\n // Restart path: if this is a restart and the current step is a\n // chunk step, locate the latest FAILED step execution for the\n // same step name and load its `lastChunkIndex` checkpoint. That\n // value is passed to the chunk executor as `resumeFromChunkIndex`.\n // For tasklet steps (and chunk steps with no prior failure) we\n // leave `resumeFromChunkIndex` undefined — the chunk executor\n // treats undefined as \"start from the beginning\".\n //\n // Look this up BEFORE createStepExecution so the just-created\n // STARTING step isn't returned as the \"latest\" entry.\n let resumeFromChunkIndex: number | undefined;\n if (isRestart && step.kind === 'chunk') {\n const priorFailed = await this.repository.findLatestStepExecution(execution.id, step.id);\n if (priorFailed && priorFailed.status === StepStatus.FAILED) {\n resumeFromChunkIndex = await this.getLastCheckpoint(priorFailed.id);\n }\n }\n\n const stepExecution = await this.repository.createStepExecution(execution.id, step.id);\n currentStepExecutionId = stepExecution.id;\n\n await this.emit({\n type: BATCH_EVENT.STEP_STARTED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n data: { stepId: step.id, kind: step.kind },\n });\n\n let result: StepResult;\n try {\n if (step.kind === 'tasklet') {\n result = await this.taskletExecutor.execute(step, {\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n stepName: step.id,\n jobParameters: execution.params,\n jobRepository: this.repository,\n transactionManager: this.transactionManager,\n listenerInvoker: this.listenerInvoker,\n listenerResolvers: stepResolvers,\n });\n } else {\n // Forward the partition routing (T8) when the transport\n // supplied one. The chunk executor uses the partition\n // index + count + the step's `partitions.range` to bound\n // its read loop. Absent partition args fall through to\n // the 0.1.0 non-partitioned chunk pipeline.\n const partitionArgs =\n partition?.partitionIndex !== undefined && partition?.partitionCount !== undefined\n ? {\n partitionIndex: partition.partitionIndex,\n partitionCount: partition.partitionCount,\n }\n : {};\n result = await this.chunkExecutor.execute(step, {\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n stepName: step.id,\n jobParameters: execution.params,\n jobRepository: this.repository,\n transactionManager: this.transactionManager,\n listenerInvoker: this.listenerInvoker,\n listenerResolvers: jobResolvers,\n jobExecutionId2: execution.id,\n resolvers: new Map(),\n ...(resumeFromChunkIndex !== undefined ? { resumeFromChunkIndex } : {}),\n ...partitionArgs,\n });\n }\n } catch (stepErr) {\n // The executor itself threw (e.g. resolveReader threw in a\n // chunk step before the executor's own try-catch could catch\n // it). Persist the step as FAILED with the error message and\n // re-raise so the outer handler marks the job FAILED.\n await this.repository.updateStepExecution(stepExecution.id, {\n status: StepStatus.FAILED,\n exitCode: 'FAILED',\n exitMessage: stepErr instanceof Error ? stepErr.message : String(stepErr),\n endTime: new Date(),\n });\n currentStepExecutionId = null;\n throw stepErr;\n }\n currentStepExecutionId = null;\n\n await this.repository.updateStepExecution(stepExecution.id, {\n status: result.status,\n ...(result.readCount !== undefined ? { readCount: result.readCount } : {}),\n ...(result.writeCount !== undefined ? { writeCount: result.writeCount } : {}),\n ...(result.skipCount !== undefined ? { skipCount: result.skipCount } : {}),\n ...('commitCount' in result && result.commitCount !== undefined\n ? { commitCount: result.commitCount }\n : {}),\n exitCode: result.exitCode,\n exitMessage: result.exitMessage,\n endTime: new Date(),\n });\n\n await this.emit({\n type:\n result.status === StepStatus.COMPLETED\n ? BATCH_EVENT.STEP_COMPLETED\n : result.status === StepStatus.FAILED\n ? BATCH_EVENT.STEP_FAILED\n : BATCH_EVENT.STEP_COMPLETED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n stepExecutionId: stepExecution.id,\n data: {\n stepId: step.id,\n status: result.status,\n ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),\n },\n });\n\n // Map StepStatus -> FlowExecutionStatus. Anything other than\n // COMPLETED/FAILED collapses to UNKNOWN → evaluator returns\n // null → flow ends.\n const flowStatus: FlowExecutionStatus =\n result.status === StepStatus.COMPLETED\n ? FlowExecutionStatus.COMPLETED\n : result.status === StepStatus.FAILED\n ? FlowExecutionStatus.FAILED\n : FlowExecutionStatus.UNKNOWN;\n const deciderExitStatus = await this.resolveDeciderExitStatus(jobDef, currentStepId, {\n jobExecution: execution,\n stepId: step.id,\n stepExecutionId: stepExecution.id,\n stepStatus: result.status,\n exitCode: result.exitCode,\n exitMessage: result.exitMessage,\n });\n const flowExitStatus = deciderExitStatus ?? (result.exitCode || flowStatus);\n\n let evaluatorResult = await this.flowEvaluator.evaluate(\n jobDef.transitions,\n currentStepId,\n flowExitStatus,\n );\n\n // Distinguish \"no transition declared\" (linear fallback) from\n // \"transition declared with toStepId: null\" (explicit END).\n // FlowEvaluator returns null for both, so we inspect the\n // transition list directly.\n let hasMatchingTransition = jobDef.transitions.some((t) =>\n this.flowEvaluator.matches(t, currentStepId!, flowExitStatus),\n );\n if (!hasMatchingTransition && flowExitStatus !== flowStatus) {\n evaluatorResult = await this.flowEvaluator.evaluate(\n jobDef.transitions,\n currentStepId,\n flowStatus,\n );\n hasMatchingTransition = jobDef.transitions.some((t) =>\n this.flowEvaluator.matches(t, currentStepId!, flowStatus),\n );\n }\n\n let nextStepId: string | null;\n if (hasMatchingTransition) {\n // Explicit transition: respect its target, including null\n // (END). Do not fall through to linear order.\n nextStepId = evaluatorResult;\n } else if (result.status === StepStatus.FAILED) {\n // FAILED with no matching transition → short-circuit. The\n // graph declares no path forward, so the job is FAILED — we\n // must not invent a \"next\" step.\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.FAILED,\n endTime: new Date(),\n exitCode: result.exitCode,\n exitMessage: result.exitMessage,\n });\n finalStatus = JobStatus.FAILED;\n break;\n } else {\n // COMPLETED with no transition → linear fallback to the next\n // step in declaration order. If we're already on the last\n // step, the job ends.\n const currentIdx = stepOrder.indexOf(currentStepId);\n const nextIdx = currentIdx + 1;\n nextStepId = nextIdx < stepOrder.length ? stepOrder[nextIdx]! : null;\n }\n\n currentStepId = nextStepId;\n }\n\n if (finalStatus === JobStatus.COMPLETED) {\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.COMPLETED,\n endTime: new Date(),\n exitCode: 'COMPLETED',\n });\n }\n } catch (err) {\n // Defensive: leave the job FAILED rather than crash the host.\n await this.repository.updateJobExecution(execution.id, {\n status: JobStatus.FAILED,\n endTime: new Date(),\n exitMessage: err instanceof Error ? err.message : String(err),\n });\n finalStatus = JobStatus.FAILED;\n }\n\n // `after:job:*` listeners run once the job is in a terminal state.\n // They receive the final status as the second positional argument\n // (the `args` slot in the current API; the legacy builder path used\n // the same shape). The resolver map is the same one built above;\n // we re-use it to avoid a second IR walk.\n await this.listenerInvoker.invokeAfter(\n jobResolvers,\n 'job',\n {\n jobExecutionId: execution.id,\n stepExecutionId: '<job>',\n jobParameters: execution.params,\n },\n { status: finalStatus },\n );\n\n await this.emit({\n type:\n finalStatus === JobStatus.COMPLETED ? BATCH_EVENT.JOB_COMPLETED : BATCH_EVENT.JOB_FAILED,\n timestamp: new Date(),\n jobExecutionId: execution.id,\n data: { status: finalStatus },\n });\n\n return (await this.repository.getJobExecution(execution.id))!;\n }\n\n /**\n * Build a listener resolver map for the given job. Walks every\n * `ListenerDefinition` in `jobDef.listeners` (job-level + step-level +\n * chunk-level + item-level + skip-level) and resolves each ref into a\n * callable `ListenerEntry` keyed by `${phase}:${kind}:${name}`.\n *\n * The returned map is consumed by `ListenerInvoker.invokeBefore /\n * invokeAfter / invokeOnError / invokeOnSkip*` (Task 20 API). The legacy\n * step-level methods (`invokeBeforeStep` etc.) consume a derived\n * legacy-shaped map produced by `buildLegacyStepResolvers` — that\n * conversion happens at the call site, not here, so this method stays\n * the single source of truth for the new shape.\n *\n * Ref resolution rules:\n * - `RefKind.BuilderLambda` → use `ref.fn` directly (the compiler\n * pre-binds decorator-discovered methods\n * and the builder API ships bare fns).\n * - `RefKind.Method` → requires the Jobable instance. Until\n * a `ModuleRef` is wired (Task 9+), this\n * branch logs a warning and is skipped.\n * - `RefKind.ProviderToken` → resolved in Task 9 against a\n * pre-built provider map. Skipped here\n * with a warning.\n */\n private buildResolverMap(jobDef: JobDefinition): ResolverMap {\n const resolvers: ResolverMap = new Map();\n let lambdaCounter = 0;\n\n for (const def of jobDef.listeners) {\n const fn = this.resolveListenerRef(def);\n if (fn === null) continue;\n\n const name = this.resolveListenerName(def.ref, lambdaCounter);\n if (def.ref.kind === RefKind.BuilderLambda) lambdaCounter += 1;\n\n const key =\n def.kind === 'skip' && def.skipKind !== undefined\n ? `on-skip:${def.skipKind}:${name}`\n : `${def.phase}:${def.kind}:${name}`;\n resolvers.set(key, {\n fn,\n ...(def.nonCritical !== undefined ? { nonCritical: def.nonCritical } : {}),\n });\n }\n\n return resolvers;\n }\n\n /**\n * Resolve a single `ListenerDefinition` to its callable function, or\n * `null` if the ref kind is not yet supported. See `buildResolverMap`\n * for the per-kind resolution contract.\n */\n private resolveListenerRef(def: ListenerDefinition): ((...args: any[]) => any) | null {\n const ref = def.ref;\n switch (ref.kind) {\n case RefKind.BuilderLambda:\n return ref.fn ?? null;\n case RefKind.Method:\n this.logger.warn(\n `JobExecutor: Method-ref listener (classToken=${ref.classToken ?? '<unknown>'}, ` +\n `methodName=${ref.methodName ?? '<unknown>'}) requires a Jobable instance; ` +\n 'this resolution path lands in a follow-up task. Listener skipped.',\n );\n return null;\n case RefKind.ProviderToken:\n this.logger.warn(\n `JobExecutor: ProviderToken-ref listener (token=${ref.token ?? '<empty>'}) ` +\n 'is resolved in Task 9. Listener skipped.',\n );\n return null;\n default: {\n const _exhaustive: never = ref.kind;\n void _exhaustive;\n return null;\n }\n }\n }\n\n /**\n * Derive the `name` segment of the resolver key. Method refs carry a\n * `classToken` + `methodName` pair that uniquely identifies the bound\n * method; BuilderLambda refs do not carry a name (the compiler drops\n * the method name when it pre-binds), so we mint a `lambda-N` name\n * from a per-job counter to guarantee uniqueness.\n */\n private resolveListenerName(ref: ListenerRef, lambdaCounter: number): string {\n if (ref.kind === RefKind.Method) {\n return `${ref.classToken ?? '<unknown>'}.${ref.methodName ?? '<unknown>'}`;\n }\n return `lambda-${lambdaCounter}`;\n }\n\n /**\n * Derive a legacy `Map<string, ListenerResolver>` from a new\n * `ResolverMap`, containing only the step-level entries with their\n * keys translated from `${phase}:step:${name}` back to the legacy\n * `${phase}-step:${name}` shape. The `nonCritical` flag is dropped\n * (legacy `ListenerResolver` is a bare function with no metadata).\n *\n * This is the bridge the `TaskletStepExecutor` (which still consumes\n * the legacy shape) needs until it migrates to the new API. Kept as\n * a private helper so the conversion logic is in one place.\n */\n private buildLegacyStepResolvers(resolvers: ResolverMap): Map<string, ListenerResolver> {\n const legacy: Map<string, ListenerResolver> = new Map();\n for (const [key, entry] of resolvers.entries()) {\n if (key.startsWith('before:step:')) {\n legacy.set(`before-step:${key.slice('before:step:'.length)}`, entry.fn as ListenerResolver);\n } else if (key.startsWith('after:step:')) {\n legacy.set(`after-step:${key.slice('after:step:'.length)}`, entry.fn as ListenerResolver);\n } else if (key.startsWith('on-error:step:')) {\n legacy.set(\n `on-step-error:${key.slice('on-error:step:'.length)}`,\n entry.fn as ListenerResolver,\n );\n }\n }\n return legacy;\n }\n\n /**\n * Read the `lastChunkIndex` checkpoint from the step-scoped\n * ExecutionContext for `stepExecutionId`. Returns `undefined` when the\n * step has no recorded checkpoint (e.g., the prior run failed on the\n * very first chunk and never got a chance to write one). The chunk\n * executor treats `undefined` as \"no resume; start from the beginning\".\n */\n private async getLastCheckpoint(stepExecutionId: string): Promise<number | undefined> {\n const ctx = await this.repository.getExecutionContext({ stepExecutionId });\n if (ctx.data === null || typeof ctx.data !== 'object' || Array.isArray(ctx.data)) {\n return undefined;\n }\n const value = (ctx.data as { lastChunkIndex?: unknown }).lastChunkIndex;\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n }\n\n private async resolveDeciderExitStatus(\n jobDef: JobDefinition,\n afterStepId: string,\n context: Parameters<NonNullable<JobDefinition['deciders']>[number]['decide']>[0],\n ): Promise<string | undefined> {\n const decider = (jobDef.deciders ?? []).find((d) => d.afterStepId === afterStepId);\n if (decider === undefined) return undefined;\n const status = await decider.decide(context);\n const trimmed = status.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n /**\n * Dispatch a BatchEvent to the configured observer. Errors thrown by\n * the observer are swallowed: a failing logger/queue must not crash\n * the executor (the job's persisted state is the source of truth).\n */\n private async emit(event: BatchEvent): Promise<void> {\n try {\n await this.observer.onEvent(event);\n } catch {\n // intentional: observer failures are best-effort and must not\n // affect the executor's own state transitions.\n }\n }\n}\n\n// Re-export common types for convenience so callers that import\n// `JobExecutor` don't need a second import for `StepExecutionResult` etc.\nexport type { StepExecutionResult } from './tasklet-step-executor';\nexport type { ChunkExecutionResult } from './chunk-step-executor';\nexport type { JobParameters, JobExecution };\n"],"names":["JobExecutor","logger","Logger","name","repository","transactionManager","taskletExecutor","chunkExecutor","listenerInvoker","flowEvaluator","observer","NoopBatchObserver","execute","execution","jobDef","partition","isRestart","status","JobStatus","FAILED","STOPPED","restartable","JobNotRestartableError","id","updateJobExecution","STARTED","startTime","Date","emit","type","BATCH_EVENT","JOB_STARTED","timestamp","jobExecutionId","data","jobName","jobResolvers","buildResolverMap","stepResolvers","buildLegacyStepResolvers","invokeBefore","stepExecutionId","jobParameters","params","stepOrder","Object","keys","steps","currentStepId","startStepId","finalStatus","COMPLETED","currentStepExecutionId","step","endTime","exitCode","exitMessage","resumeFromChunkIndex","kind","priorFailed","findLatestStepExecution","StepStatus","getLastCheckpoint","stepExecution","createStepExecution","STEP_STARTED","stepId","result","stepName","jobRepository","listenerResolvers","partitionArgs","partitionIndex","undefined","partitionCount","jobExecutionId2","resolvers","Map","stepErr","updateStepExecution","Error","message","String","readCount","writeCount","skipCount","commitCount","STEP_COMPLETED","STEP_FAILED","flowStatus","FlowExecutionStatus","UNKNOWN","deciderExitStatus","resolveDeciderExitStatus","jobExecution","stepStatus","flowExitStatus","evaluatorResult","evaluate","transitions","hasMatchingTransition","some","t","matches","nextStepId","currentIdx","indexOf","nextIdx","length","err","invokeAfter","JOB_COMPLETED","JOB_FAILED","getJobExecution","lambdaCounter","def","listeners","fn","resolveListenerRef","resolveListenerName","ref","RefKind","BuilderLambda","key","skipKind","phase","set","nonCritical","Method","warn","classToken","methodName","ProviderToken","token","_exhaustive","legacy","entry","entries","startsWith","slice","ctx","getExecutionContext","Array","isArray","value","lastChunkIndex","Number","isFinite","afterStepId","context","decider","deciders","find","d","decide","trimmed","trim","event","onEvent","TransactionManager"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExecutionContext } from '../core/repository';
|
|
1
|
+
import type { ExecutionContext, JobParameters } from '../core/repository';
|
|
2
2
|
/**
|
|
3
3
|
* Listener function — signature is determined by the listener's kind/phase.
|
|
4
4
|
*
|
|
@@ -65,18 +65,16 @@ export type OnErrorKind = 'job' | 'step' | 'chunk';
|
|
|
65
65
|
* Sub-kinds for the `on-skip` phase. The resolver key looks like
|
|
66
66
|
* `on-skip:${SkipSubKind}:${name}` — for example `on-skip:read:MySkipListener`.
|
|
67
67
|
*/
|
|
68
|
-
export type SkipSubKind = 'read' | 'process' | 'write';
|
|
69
68
|
export declare class ListenerInvoker {
|
|
70
69
|
private readonly logger;
|
|
71
70
|
/**
|
|
72
71
|
* Invoke every `before:<kind>:<name>` resolver, in registration order.
|
|
73
72
|
*
|
|
74
73
|
* Listener signature depends on `<kind>`:
|
|
75
|
-
* - `job` / `chunk`
|
|
76
|
-
* - `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* convention)
|
|
74
|
+
* - `job` / `step` / `chunk` — `fn(ctx, result?)`
|
|
75
|
+
* - `item-read` / `item-process` / `item-write` — `fn(item, ctx)` for the
|
|
76
|
+
* legacy generic path. Prefer the explicit item helpers below for exact
|
|
77
|
+
* read/process/write signatures.
|
|
80
78
|
*/
|
|
81
79
|
invokeBefore(resolvers: ResolverMap, kind: LifecyclePhaseKind, ctx: ListenerContext, args?: unknown): Promise<void>;
|
|
82
80
|
/**
|
|
@@ -89,6 +87,24 @@ export declare class ListenerInvoker {
|
|
|
89
87
|
* Listener signature is `fn(ctx, err)`.
|
|
90
88
|
*/
|
|
91
89
|
invokeOnError(resolvers: ResolverMap, kind: OnErrorKind, ctx: ListenerContext, err: unknown): Promise<void>;
|
|
90
|
+
/** Invoke every `before:item-read:<name>` resolver. Listener signature: `fn(ctx)`. */
|
|
91
|
+
invokeBeforeRead(resolvers: ResolverMap, ctx: ListenerContext): Promise<void>;
|
|
92
|
+
/** Invoke every `after:item-read:<name>` resolver. Listener signature: `fn(item, ctx)`. */
|
|
93
|
+
invokeAfterRead(resolvers: ResolverMap, item: unknown, ctx: ListenerContext): Promise<void>;
|
|
94
|
+
/** Invoke every `on-error:item-read:<name>` resolver. Listener signature: `fn(err, ctx)`. */
|
|
95
|
+
invokeOnReadError(resolvers: ResolverMap, err: unknown, ctx: ListenerContext): Promise<void>;
|
|
96
|
+
/** Invoke every `before:item-process:<name>` resolver. Listener signature: `fn(item, ctx)`. */
|
|
97
|
+
invokeBeforeProcess(resolvers: ResolverMap, item: unknown, ctx: ListenerContext): Promise<void>;
|
|
98
|
+
/** Invoke every `after:item-process:<name>` resolver. Listener signature: `fn(item, result, ctx)`. */
|
|
99
|
+
invokeAfterProcess(resolvers: ResolverMap, item: unknown, result: unknown, ctx: ListenerContext): Promise<void>;
|
|
100
|
+
/** Invoke every `on-error:item-process:<name>` resolver. Listener signature: `fn(item, err, ctx)`. */
|
|
101
|
+
invokeOnProcessError(resolvers: ResolverMap, item: unknown, err: unknown, ctx: ListenerContext): Promise<void>;
|
|
102
|
+
/** Invoke every `before:item-write:<name>` resolver. Listener signature: `fn(items, ctx)`. */
|
|
103
|
+
invokeBeforeWrite(resolvers: ResolverMap, items: unknown[], ctx: ListenerContext): Promise<void>;
|
|
104
|
+
/** Invoke every `after:item-write:<name>` resolver. Listener signature: `fn(items, result, ctx)`. */
|
|
105
|
+
invokeAfterWrite(resolvers: ResolverMap, items: unknown[], result: unknown, ctx: ListenerContext): Promise<void>;
|
|
106
|
+
/** Invoke every `on-error:item-write:<name>` resolver. Listener signature: `fn(items, err, ctx)`. */
|
|
107
|
+
invokeOnWriteError(resolvers: ResolverMap, items: unknown[], err: unknown, ctx: ListenerContext): Promise<void>;
|
|
92
108
|
/** Invoke every `on-skip:read:<name>` resolver. Listener signature: `fn(err, item)`. */
|
|
93
109
|
invokeOnSkipRead(resolvers: ResolverMap, err: unknown, item: unknown): Promise<void>;
|
|
94
110
|
/** Invoke every `on-skip:process:<name>` resolver. Listener signature: `fn(item, err)`. */
|
|
@@ -134,8 +150,7 @@ export declare class ListenerInvoker {
|
|
|
134
150
|
* Compute the positional argument list to forward to a before/after
|
|
135
151
|
* listener, based on the listener's kind.
|
|
136
152
|
*
|
|
137
|
-
* - `job` / `chunk`
|
|
138
|
-
* - `step` → `[ctx, args]` (args is the result)
|
|
153
|
+
* - `job` / `step` / `chunk` → `[ctx]` or `[ctx, args]`
|
|
139
154
|
* - `item-read` /
|
|
140
155
|
* `item-process` /
|
|
141
156
|
* `item-write` → `[args, ctx]` (args is the item, leading position)
|
|
@@ -158,6 +173,7 @@ export interface ListenerContext {
|
|
|
158
173
|
jobExecutionId: string;
|
|
159
174
|
stepExecutionId?: string;
|
|
160
175
|
stepName?: string;
|
|
176
|
+
jobParameters?: JobParameters;
|
|
161
177
|
/** Arbitrary, executor-supplied metadata (transaction context, etc.). */
|
|
162
178
|
[extra: string]: unknown;
|
|
163
179
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener-invoker.d.ts","sourceRoot":"","sources":["../../../src/execution/listener-invoker.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"listener-invoker.d.ts","sourceRoot":"","sources":["../../../src/execution/listener-invoker.ts"],"names":[],"mappings":"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"}
|
|
@@ -72,11 +72,10 @@ let ListenerInvoker = class ListenerInvoker {
|
|
|
72
72
|
* Invoke every `before:<kind>:<name>` resolver, in registration order.
|
|
73
73
|
*
|
|
74
74
|
* Listener signature depends on `<kind>`:
|
|
75
|
-
* - `job` / `chunk`
|
|
76
|
-
* - `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* convention)
|
|
75
|
+
* - `job` / `step` / `chunk` — `fn(ctx, result?)`
|
|
76
|
+
* - `item-read` / `item-process` / `item-write` — `fn(item, ctx)` for the
|
|
77
|
+
* legacy generic path. Prefer the explicit item helpers below for exact
|
|
78
|
+
* read/process/write signatures.
|
|
80
79
|
*/ async invokeBefore(resolvers, kind, ctx, args) {
|
|
81
80
|
const callArgs = this.buildCallArgs(kind, ctx, args);
|
|
82
81
|
await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:${kind}:`, callArgs);
|
|
@@ -97,6 +96,63 @@ let ListenerInvoker = class ListenerInvoker {
|
|
|
97
96
|
err
|
|
98
97
|
]);
|
|
99
98
|
}
|
|
99
|
+
/** Invoke every `before:item-read:<name>` resolver. Listener signature: `fn(ctx)`. */ async invokeBeforeRead(resolvers, ctx) {
|
|
100
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:item-read:`, [
|
|
101
|
+
ctx
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
/** Invoke every `after:item-read:<name>` resolver. Listener signature: `fn(item, ctx)`. */ async invokeAfterRead(resolvers, item, ctx) {
|
|
105
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:item-read:`, [
|
|
106
|
+
item,
|
|
107
|
+
ctx
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
/** Invoke every `on-error:item-read:<name>` resolver. Listener signature: `fn(err, ctx)`. */ async invokeOnReadError(resolvers, err, ctx) {
|
|
111
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:item-read:`, [
|
|
112
|
+
err,
|
|
113
|
+
ctx
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
/** Invoke every `before:item-process:<name>` resolver. Listener signature: `fn(item, ctx)`. */ async invokeBeforeProcess(resolvers, item, ctx) {
|
|
117
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:item-process:`, [
|
|
118
|
+
item,
|
|
119
|
+
ctx
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
/** Invoke every `after:item-process:<name>` resolver. Listener signature: `fn(item, result, ctx)`. */ async invokeAfterProcess(resolvers, item, result, ctx) {
|
|
123
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:item-process:`, [
|
|
124
|
+
item,
|
|
125
|
+
result,
|
|
126
|
+
ctx
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
/** Invoke every `on-error:item-process:<name>` resolver. Listener signature: `fn(item, err, ctx)`. */ async invokeOnProcessError(resolvers, item, err, ctx) {
|
|
130
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:item-process:`, [
|
|
131
|
+
item,
|
|
132
|
+
err,
|
|
133
|
+
ctx
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
/** Invoke every `before:item-write:<name>` resolver. Listener signature: `fn(items, ctx)`. */ async invokeBeforeWrite(resolvers, items, ctx) {
|
|
137
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:item-write:`, [
|
|
138
|
+
items,
|
|
139
|
+
ctx
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
/** Invoke every `after:item-write:<name>` resolver. Listener signature: `fn(items, result, ctx)`. */ async invokeAfterWrite(resolvers, items, result, ctx) {
|
|
143
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:item-write:`, [
|
|
144
|
+
items,
|
|
145
|
+
result,
|
|
146
|
+
ctx
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
/** Invoke every `on-error:item-write:<name>` resolver. Listener signature: `fn(items, err, ctx)`. */ async invokeOnWriteError(resolvers, items, err, ctx) {
|
|
150
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:item-write:`, [
|
|
151
|
+
items,
|
|
152
|
+
err,
|
|
153
|
+
ctx
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
100
156
|
/** Invoke every `on-skip:read:<name>` resolver. Listener signature: `fn(err, item)`. */ async invokeOnSkipRead(resolvers, err, item) {
|
|
101
157
|
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:read:`, [
|
|
102
158
|
err,
|
|
@@ -179,8 +235,7 @@ let ListenerInvoker = class ListenerInvoker {
|
|
|
179
235
|
* Compute the positional argument list to forward to a before/after
|
|
180
236
|
* listener, based on the listener's kind.
|
|
181
237
|
*
|
|
182
|
-
* - `job` / `chunk`
|
|
183
|
-
* - `step` → `[ctx, args]` (args is the result)
|
|
238
|
+
* - `job` / `step` / `chunk` → `[ctx]` or `[ctx, args]`
|
|
184
239
|
* - `item-read` /
|
|
185
240
|
* `item-process` /
|
|
186
241
|
* `item-write` → `[args, ctx]` (args is the item, leading position)
|
|
@@ -189,19 +244,20 @@ let ListenerInvoker = class ListenerInvoker {
|
|
|
189
244
|
case 'item-read':
|
|
190
245
|
case 'item-process':
|
|
191
246
|
case 'item-write':
|
|
192
|
-
return [
|
|
247
|
+
return args === undefined ? [
|
|
248
|
+
ctx
|
|
249
|
+
] : [
|
|
193
250
|
args,
|
|
194
251
|
ctx
|
|
195
252
|
];
|
|
196
|
-
case 'step':
|
|
197
|
-
return [
|
|
198
|
-
ctx,
|
|
199
|
-
args
|
|
200
|
-
];
|
|
201
253
|
case 'job':
|
|
254
|
+
case 'step':
|
|
202
255
|
case 'chunk':
|
|
203
|
-
return [
|
|
256
|
+
return args === undefined ? [
|
|
204
257
|
ctx
|
|
258
|
+
] : [
|
|
259
|
+
ctx,
|
|
260
|
+
args
|
|
205
261
|
];
|
|
206
262
|
default:
|
|
207
263
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/execution/listener-invoker.ts"],"sourcesContent":["/**\n * ListenerInvoker — orchestrates lifecycle listeners around step / chunk / job /\n * item / skip execution.\n *\n * Supports the 7 listener kinds declared in `ListenerKind` (job / step / chunk /\n * item-read / item-process / item-write / skip) with proper failure policy:\n *\n * - default → listener throw propagates\n * - `nonCritical: true` → log + continue with the next listener\n *\n * Two resolver-map shapes are supported:\n *\n * 1. Legacy (Task 17) — `Map<string, ListenerResolver>` keyed by\n * `${phase}-${kind}:${name}` (e.g. `before-step:MyListener`). The\n * convenience methods `invokeBeforeStep / invokeAfterStep /\n * invokeOnErrorStep` operate on this shape and remain in place for\n * backward compatibility with the Wave-3 step executor.\n *\n * 2. Current (Task 20) — `Map<string, ListenerEntry>` keyed by\n * `${phase}:${kind}:${name}` (e.g. `before:step:MyListener`,\n * `on-skip:read:MySkipListener`). This is the shape consumed by\n * `invokeBefore / invokeAfter / invokeOnError / invokeOnSkipRead /\n * invokeOnSkipProcess / invokeOnSkipWrite` and is the source of truth for\n * all 7 listener kinds going forward.\n *\n * Registration order is preserved (Map iteration is insertion-ordered in JS).\n */\nimport { Injectable, Logger } from '@nestjs/common';\nimport type { ExecutionContext } from '../core/repository';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Listener function — signature is determined by the listener's kind/phase.\n *\n * Typed as `any[]` rather than `unknown[]` so listeners can be authored with\n * the narrower, kind-specific signatures (e.g. `(ctx) => void`,\n * `(item, ctx) => void`, `(err, item) => void`) without TypeScript rejecting\n * the assignment to `ListenerEntry.fn`. The runtime contract is enforced by\n * the invoker's kind-aware `buildCallArgs`, not by the type system.\n */\nexport type ListenerFn = (...args: any[]) => any;\n\n/**\n * Resolver-map entry used by the current (Task 20) API.\n *\n * - `fn` — the actual listener function to invoke\n * - `nonCritical` — when true, failures from this listener are logged and\n * suppressed; otherwise the failure propagates out of the\n * `invoke*` call and aborts the surrounding executor.\n */\nexport interface ListenerEntry {\n fn: ListenerFn;\n nonCritical?: boolean;\n}\n\n/** Resolver map consumed by the current (Task 20) `invoke*` methods. */\nexport type ResolverMap = Map<string, ListenerEntry>;\n\n// ---------------------------------------------------------------------------\n// Legacy types (Task 17 backward compatibility)\n// ---------------------------------------------------------------------------\n\n/** Phase prefix constants used by the legacy convenience methods. */\nexport const LISTENER_PHASE_PREFIX = {\n BeforeStep: 'before-step:',\n AfterStep: 'after-step:',\n OnStepError: 'on-step-error:',\n} as const;\n\n/** Legacy resolver function type — bare callable, no per-entry metadata. */\nexport type ListenerResolver = (...args: unknown[]) => unknown | Promise<unknown>;\n\nexport interface StepListenerContext extends ListenerContext {\n jobExecutionId: string;\n stepExecutionId: string;\n getExecutionContext?: () => Promise<ExecutionContext>;\n saveExecutionContext?: (ctx: ExecutionContext) => Promise<void>;\n}\n\nexport interface StepListenerResult {\n status: string;\n exitCode?: string;\n exitMessage?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Current (Task 20) phase / kind constants\n// ---------------------------------------------------------------------------\n\n/**\n * Phase segment for the current key format `${phase}:${kind}:${name}`.\n *\n * - `before` — `invokeBefore`\n * - `after` — `invokeAfter`\n * - `on-error` — `invokeOnError` (job / step / chunk)\n * - `on-skip` — `invokeOnSkipRead / Process / Write` (the trailing kind\n * segment is one of `read` / `process` / `write`)\n */\nexport const LISTENER_PHASE = {\n Before: 'before',\n After: 'after',\n OnError: 'on-error',\n OnSkip: 'on-skip',\n} as const;\n\n/** Phase kinds that share the standard `${phase}:${kind}:${name}` shape. */\nexport type LifecyclePhaseKind =\n | 'job'\n | 'step'\n | 'chunk'\n | 'item-read'\n | 'item-process'\n | 'item-write';\n\n/** Phase kinds accepted by `invokeOnError` (subset of the lifecycle kinds). */\nexport type OnErrorKind = 'job' | 'step' | 'chunk';\n\n/**\n * Sub-kinds for the `on-skip` phase. The resolver key looks like\n * `on-skip:${SkipSubKind}:${name}` — for example `on-skip:read:MySkipListener`.\n */\nexport type SkipSubKind = 'read' | 'process' | 'write';\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n@Injectable()\nexport class ListenerInvoker {\n private readonly logger = new Logger(ListenerInvoker.name);\n\n // -------------------------------------------------------------------------\n // Current (Task 20) API — supports all 7 listener kinds with failure policy\n // -------------------------------------------------------------------------\n\n /**\n * Invoke every `before:<kind>:<name>` resolver, in registration order.\n *\n * Listener signature depends on `<kind>`:\n * - `job` / `chunk` — `fn(ctx)`\n * - `step` — `fn(ctx, result)` (the optional `args` is the result)\n * - `item-read` / `item-process` / `item-write` — `fn(item, ctx)` (the\n * optional `args` is the item, placed in the first position by\n * convention)\n */\n async invokeBefore(\n resolvers: ResolverMap,\n kind: LifecyclePhaseKind,\n ctx: ListenerContext,\n args?: unknown,\n ): Promise<void> {\n const callArgs = this.buildCallArgs(kind, ctx, args);\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:${kind}:`, callArgs);\n }\n\n /**\n * Invoke every `after:<kind>:<name>` resolver, in registration order.\n * Same signature rules as `invokeBefore`.\n */\n async invokeAfter(\n resolvers: ResolverMap,\n kind: LifecyclePhaseKind,\n ctx: ListenerContext,\n args?: unknown,\n ): Promise<void> {\n const callArgs = this.buildCallArgs(kind, ctx, args);\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:${kind}:`, callArgs);\n }\n\n /**\n * Invoke every `on-error:<kind>:<name>` resolver, in registration order.\n * Listener signature is `fn(ctx, err)`.\n */\n async invokeOnError(\n resolvers: ResolverMap,\n kind: OnErrorKind,\n ctx: ListenerContext,\n err: unknown,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:${kind}:`, [ctx, err]);\n }\n\n /** Invoke every `on-skip:read:<name>` resolver. Listener signature: `fn(err, item)`. */\n async invokeOnSkipRead(\n resolvers: ResolverMap,\n err: unknown,\n item: unknown,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:read:`, [err, item]);\n }\n\n /** Invoke every `on-skip:process:<name>` resolver. Listener signature: `fn(item, err)`. */\n async invokeOnSkipProcess(\n resolvers: ResolverMap,\n item: unknown,\n err: unknown,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:process:`, [item, err]);\n }\n\n /** Invoke every `on-skip:write:<name>` resolver. Listener signature: `fn(items, err)`. */\n async invokeOnSkipWrite(\n resolvers: ResolverMap,\n items: unknown[],\n err: unknown,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:write:`, [items, err]);\n }\n\n // -------------------------------------------------------------------------\n // Legacy (Task 17) convenience methods — preserved for backward compat.\n // They now delegate to the current (Task 20) API by converting the legacy\n // `Map<string, ListenerResolver>` shape into a `ResolverMap` and routing\n // through the shared `invokeMatching` internal path. One source of truth\n // for listener dispatch, two public surfaces.\n // -------------------------------------------------------------------------\n\n /**\n * Invoke all `before-step:*` resolvers in Map insertion order. Operates on\n * the legacy `Map<string, ListenerResolver>` shape; the current\n * `invokeBefore(resolvers, 'step', ...)` should be preferred for new code.\n *\n * Implementation: convert the legacy `before-step:*` entries into the new\n * `before:step:*` shape and delegate to `invokeBefore` so the dispatch /\n * `nonCritical` policy is shared with the rest of the invoker.\n */\n async invokeBeforeStep(\n resolvers: Map<string, ListenerResolver>,\n ctx: StepListenerContext,\n ): Promise<void> {\n const newResolvers = this.convertLegacyResolvers(\n resolvers,\n LISTENER_PHASE_PREFIX.BeforeStep,\n LISTENER_PHASE.Before,\n );\n await this.invokeBefore(newResolvers, 'step', ctx);\n }\n\n /**\n * Invoke all `after-step:*` resolvers, receiving the step result as the\n * second argument. Legacy shape; see `invokeAfter(resolvers, 'step', ...)`\n * for the current API.\n */\n async invokeAfterStep(\n resolvers: Map<string, ListenerResolver>,\n ctx: StepListenerContext,\n result: StepListenerResult,\n ): Promise<void> {\n const newResolvers = this.convertLegacyResolvers(\n resolvers,\n LISTENER_PHASE_PREFIX.AfterStep,\n LISTENER_PHASE.After,\n );\n await this.invokeAfter(newResolvers, 'step', ctx, result);\n }\n\n /**\n * Invoke all `on-step-error:*` resolvers, receiving the thrown error as the\n * second argument. Legacy shape; see `invokeOnError(resolvers, 'step', ...)`\n * for the current API.\n */\n async invokeOnErrorStep(\n resolvers: Map<string, ListenerResolver>,\n ctx: StepListenerContext,\n err: unknown,\n ): Promise<void> {\n const newResolvers = this.convertLegacyResolvers(\n resolvers,\n LISTENER_PHASE_PREFIX.OnStepError,\n LISTENER_PHASE.OnError,\n );\n await this.invokeOnError(newResolvers, 'step', ctx, err);\n }\n\n /**\n * Translate legacy `before-step:` / `after-step:` / `on-step-error:` keys\n * into the new `${phase}:step:${name}` shape. Only entries whose key\n * starts with the given legacy prefix are forwarded; everything else is\n * silently dropped (matches the prefix-filter semantics of the original\n * legacy loops).\n *\n * `nonCritical` is not representable in the legacy `ListenerResolver`\n * shape (bare function values), so the converted entries always carry\n * `nonCritical: undefined` — i.e. critical propagation. This preserves\n * the legacy contract exactly.\n */\n private convertLegacyResolvers(\n legacy: Map<string, ListenerResolver>,\n legacyPrefix: string,\n newPhase: string,\n ): ResolverMap {\n const out: ResolverMap = new Map();\n for (const [key, fn] of legacy.entries()) {\n if (!key.startsWith(legacyPrefix)) continue;\n const newKey = `${newPhase}:step:${key.slice(legacyPrefix.length)}`;\n out.set(newKey, { fn: fn as ListenerFn });\n }\n return out;\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /**\n * Compute the positional argument list to forward to a before/after\n * listener, based on the listener's kind.\n *\n * - `job` / `chunk` → `[ctx]`\n * - `step` → `[ctx, args]` (args is the result)\n * - `item-read` /\n * `item-process` /\n * `item-write` → `[args, ctx]` (args is the item, leading position)\n */\n private buildCallArgs(\n kind: LifecyclePhaseKind,\n ctx: ListenerContext,\n args: unknown,\n ): unknown[] {\n switch (kind) {\n case 'item-read':\n case 'item-process':\n case 'item-write':\n return [args, ctx];\n case 'step':\n return [ctx, args];\n case 'job':\n case 'chunk':\n return [ctx];\n default: {\n // exhaustive guard\n const _exhaustive: never = kind;\n void _exhaustive;\n return [ctx];\n }\n }\n }\n\n /**\n * Iterate the resolver map in insertion order, invoke every entry whose key\n * starts with `prefix`, and apply the failure policy:\n *\n * - if the entry is missing / the function rejects:\n * - `nonCritical: true` → log a warning, swallow, continue\n * - otherwise → re-throw, aborting the surrounding executor\n */\n private async invokeMatching(\n resolvers: ResolverMap,\n prefix: string,\n args: unknown[],\n ): Promise<void> {\n for (const [key, entry] of resolvers.entries()) {\n if (!key.startsWith(prefix)) continue;\n try {\n await entry.fn(...args);\n } catch (err) {\n if (entry.nonCritical) {\n this.logger.warn(\n `[ListenerInvoker] non-critical listener \"${key}\" failed: ${formatError(err)}`,\n );\n continue;\n }\n throw err;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Shared listener-context payload. All fields are optional because the\n * caller (executor) may not always have a stepExecutionId at hand (e.g. for\n * job-level listeners). */\nexport interface ListenerContext {\n jobExecutionId: string;\n stepExecutionId?: string;\n stepName?: string;\n /** Arbitrary, executor-supplied metadata (transaction context, etc.). */\n [extra: string]: unknown;\n}\n\nfunction formatError(err: unknown): string {\n return err instanceof Error ? `${err.name}: ${err.message}` : String(err);\n}\n"],"names":["LISTENER_PHASE","LISTENER_PHASE_PREFIX","ListenerInvoker","BeforeStep","AfterStep","OnStepError","Before","After","OnError","OnSkip","logger","Logger","name","invokeBefore","resolvers","kind","ctx","args","callArgs","buildCallArgs","invokeMatching","invokeAfter","invokeOnError","err","invokeOnSkipRead","item","invokeOnSkipProcess","invokeOnSkipWrite","items","invokeBeforeStep","newResolvers","convertLegacyResolvers","invokeAfterStep","result","invokeOnErrorStep","legacy","legacyPrefix","newPhase","out","Map","key","fn","entries","startsWith","newKey","slice","length","set","_exhaustive","prefix","entry","nonCritical","warn","formatError","Error","message","String"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/execution/listener-invoker.ts"],"sourcesContent":["/**\n * ListenerInvoker — orchestrates lifecycle listeners around step / chunk / job /\n * item / skip execution.\n *\n * Supports the 7 listener kinds declared in `ListenerKind` (job / step / chunk /\n * item-read / item-process / item-write / skip) with proper failure policy:\n *\n * - default → listener throw propagates\n * - `nonCritical: true` → log + continue with the next listener\n *\n * Two resolver-map shapes are supported:\n *\n * 1. Legacy (Task 17) — `Map<string, ListenerResolver>` keyed by\n * `${phase}-${kind}:${name}` (e.g. `before-step:MyListener`). The\n * convenience methods `invokeBeforeStep / invokeAfterStep /\n * invokeOnErrorStep` operate on this shape and remain in place for\n * backward compatibility with the Wave-3 step executor.\n *\n * 2. Current (Task 20) — `Map<string, ListenerEntry>` keyed by\n * `${phase}:${kind}:${name}` (e.g. `before:step:MyListener`,\n * `on-skip:read:MySkipListener`). This is the shape consumed by\n * `invokeBefore / invokeAfter / invokeOnError / invokeOnSkipRead /\n * invokeOnSkipProcess / invokeOnSkipWrite` and is the source of truth for\n * all 7 listener kinds going forward.\n *\n * Registration order is preserved (Map iteration is insertion-ordered in JS).\n */\nimport { Injectable, Logger } from '@nestjs/common';\nimport type { ExecutionContext, JobParameters } from '../core/repository';\nimport type { SkipSubKind } from '../core/ir/listener-definition';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Listener function — signature is determined by the listener's kind/phase.\n *\n * Typed as `any[]` rather than `unknown[]` so listeners can be authored with\n * the narrower, kind-specific signatures (e.g. `(ctx) => void`,\n * `(item, ctx) => void`, `(err, item) => void`) without TypeScript rejecting\n * the assignment to `ListenerEntry.fn`. The runtime contract is enforced by\n * the invoker's kind-aware `buildCallArgs`, not by the type system.\n */\nexport type ListenerFn = (...args: any[]) => any;\n\n/**\n * Resolver-map entry used by the current (Task 20) API.\n *\n * - `fn` — the actual listener function to invoke\n * - `nonCritical` — when true, failures from this listener are logged and\n * suppressed; otherwise the failure propagates out of the\n * `invoke*` call and aborts the surrounding executor.\n */\nexport interface ListenerEntry {\n fn: ListenerFn;\n nonCritical?: boolean;\n}\n\n/** Resolver map consumed by the current (Task 20) `invoke*` methods. */\nexport type ResolverMap = Map<string, ListenerEntry>;\n\n// ---------------------------------------------------------------------------\n// Legacy types (Task 17 backward compatibility)\n// ---------------------------------------------------------------------------\n\n/** Phase prefix constants used by the legacy convenience methods. */\nexport const LISTENER_PHASE_PREFIX = {\n BeforeStep: 'before-step:',\n AfterStep: 'after-step:',\n OnStepError: 'on-step-error:',\n} as const;\n\n/** Legacy resolver function type — bare callable, no per-entry metadata. */\nexport type ListenerResolver = (...args: unknown[]) => unknown | Promise<unknown>;\n\nexport interface StepListenerContext extends ListenerContext {\n jobExecutionId: string;\n stepExecutionId: string;\n getExecutionContext?: () => Promise<ExecutionContext>;\n saveExecutionContext?: (ctx: ExecutionContext) => Promise<void>;\n}\n\nexport interface StepListenerResult {\n status: string;\n exitCode?: string;\n exitMessage?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Current (Task 20) phase / kind constants\n// ---------------------------------------------------------------------------\n\n/**\n * Phase segment for the current key format `${phase}:${kind}:${name}`.\n *\n * - `before` — `invokeBefore`\n * - `after` — `invokeAfter`\n * - `on-error` — `invokeOnError` (job / step / chunk)\n * - `on-skip` — `invokeOnSkipRead / Process / Write` (the trailing kind\n * segment is one of `read` / `process` / `write`)\n */\nexport const LISTENER_PHASE = {\n Before: 'before',\n After: 'after',\n OnError: 'on-error',\n OnSkip: 'on-skip',\n} as const;\n\n/** Phase kinds that share the standard `${phase}:${kind}:${name}` shape. */\nexport type LifecyclePhaseKind =\n | 'job'\n | 'step'\n | 'chunk'\n | 'item-read'\n | 'item-process'\n | 'item-write';\n\n/** Phase kinds accepted by `invokeOnError` (subset of the lifecycle kinds). */\nexport type OnErrorKind = 'job' | 'step' | 'chunk';\n\n/**\n * Sub-kinds for the `on-skip` phase. The resolver key looks like\n * `on-skip:${SkipSubKind}:${name}` — for example `on-skip:read:MySkipListener`.\n */\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n@Injectable()\nexport class ListenerInvoker {\n private readonly logger = new Logger(ListenerInvoker.name);\n\n // -------------------------------------------------------------------------\n // Current (Task 20) API — supports all 7 listener kinds with failure policy\n // -------------------------------------------------------------------------\n\n /**\n * Invoke every `before:<kind>:<name>` resolver, in registration order.\n *\n * Listener signature depends on `<kind>`:\n * - `job` / `step` / `chunk` — `fn(ctx, result?)`\n * - `item-read` / `item-process` / `item-write` — `fn(item, ctx)` for the\n * legacy generic path. Prefer the explicit item helpers below for exact\n * read/process/write signatures.\n */\n async invokeBefore(\n resolvers: ResolverMap,\n kind: LifecyclePhaseKind,\n ctx: ListenerContext,\n args?: unknown,\n ): Promise<void> {\n const callArgs = this.buildCallArgs(kind, ctx, args);\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:${kind}:`, callArgs);\n }\n\n /**\n * Invoke every `after:<kind>:<name>` resolver, in registration order.\n * Same signature rules as `invokeBefore`.\n */\n async invokeAfter(\n resolvers: ResolverMap,\n kind: LifecyclePhaseKind,\n ctx: ListenerContext,\n args?: unknown,\n ): Promise<void> {\n const callArgs = this.buildCallArgs(kind, ctx, args);\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:${kind}:`, callArgs);\n }\n\n /**\n * Invoke every `on-error:<kind>:<name>` resolver, in registration order.\n * Listener signature is `fn(ctx, err)`.\n */\n async invokeOnError(\n resolvers: ResolverMap,\n kind: OnErrorKind,\n ctx: ListenerContext,\n err: unknown,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:${kind}:`, [ctx, err]);\n }\n\n /** Invoke every `before:item-read:<name>` resolver. Listener signature: `fn(ctx)`. */\n async invokeBeforeRead(resolvers: ResolverMap, ctx: ListenerContext): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:item-read:`, [ctx]);\n }\n\n /** Invoke every `after:item-read:<name>` resolver. Listener signature: `fn(item, ctx)`. */\n async invokeAfterRead(\n resolvers: ResolverMap,\n item: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:item-read:`, [item, ctx]);\n }\n\n /** Invoke every `on-error:item-read:<name>` resolver. Listener signature: `fn(err, ctx)`. */\n async invokeOnReadError(\n resolvers: ResolverMap,\n err: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:item-read:`, [err, ctx]);\n }\n\n /** Invoke every `before:item-process:<name>` resolver. Listener signature: `fn(item, ctx)`. */\n async invokeBeforeProcess(\n resolvers: ResolverMap,\n item: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:item-process:`, [item, ctx]);\n }\n\n /** Invoke every `after:item-process:<name>` resolver. Listener signature: `fn(item, result, ctx)`. */\n async invokeAfterProcess(\n resolvers: ResolverMap,\n item: unknown,\n result: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:item-process:`, [\n item,\n result,\n ctx,\n ]);\n }\n\n /** Invoke every `on-error:item-process:<name>` resolver. Listener signature: `fn(item, err, ctx)`. */\n async invokeOnProcessError(\n resolvers: ResolverMap,\n item: unknown,\n err: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:item-process:`, [\n item,\n err,\n ctx,\n ]);\n }\n\n /** Invoke every `before:item-write:<name>` resolver. Listener signature: `fn(items, ctx)`. */\n async invokeBeforeWrite(\n resolvers: ResolverMap,\n items: unknown[],\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:item-write:`, [items, ctx]);\n }\n\n /** Invoke every `after:item-write:<name>` resolver. Listener signature: `fn(items, result, ctx)`. */\n async invokeAfterWrite(\n resolvers: ResolverMap,\n items: unknown[],\n result: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:item-write:`, [\n items,\n result,\n ctx,\n ]);\n }\n\n /** Invoke every `on-error:item-write:<name>` resolver. Listener signature: `fn(items, err, ctx)`. */\n async invokeOnWriteError(\n resolvers: ResolverMap,\n items: unknown[],\n err: unknown,\n ctx: ListenerContext,\n ): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:item-write:`, [\n items,\n err,\n ctx,\n ]);\n }\n\n /** Invoke every `on-skip:read:<name>` resolver. Listener signature: `fn(err, item)`. */\n async invokeOnSkipRead(resolvers: ResolverMap, err: unknown, item: unknown): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:read:`, [err, item]);\n }\n\n /** Invoke every `on-skip:process:<name>` resolver. Listener signature: `fn(item, err)`. */\n async invokeOnSkipProcess(resolvers: ResolverMap, item: unknown, err: unknown): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:process:`, [item, err]);\n }\n\n /** Invoke every `on-skip:write:<name>` resolver. Listener signature: `fn(items, err)`. */\n async invokeOnSkipWrite(resolvers: ResolverMap, items: unknown[], err: unknown): Promise<void> {\n await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:write:`, [items, err]);\n }\n\n // -------------------------------------------------------------------------\n // Legacy (Task 17) convenience methods — preserved for backward compat.\n // They now delegate to the current (Task 20) API by converting the legacy\n // `Map<string, ListenerResolver>` shape into a `ResolverMap` and routing\n // through the shared `invokeMatching` internal path. One source of truth\n // for listener dispatch, two public surfaces.\n // -------------------------------------------------------------------------\n\n /**\n * Invoke all `before-step:*` resolvers in Map insertion order. Operates on\n * the legacy `Map<string, ListenerResolver>` shape; the current\n * `invokeBefore(resolvers, 'step', ...)` should be preferred for new code.\n *\n * Implementation: convert the legacy `before-step:*` entries into the new\n * `before:step:*` shape and delegate to `invokeBefore` so the dispatch /\n * `nonCritical` policy is shared with the rest of the invoker.\n */\n async invokeBeforeStep(\n resolvers: Map<string, ListenerResolver>,\n ctx: StepListenerContext,\n ): Promise<void> {\n const newResolvers = this.convertLegacyResolvers(\n resolvers,\n LISTENER_PHASE_PREFIX.BeforeStep,\n LISTENER_PHASE.Before,\n );\n await this.invokeBefore(newResolvers, 'step', ctx);\n }\n\n /**\n * Invoke all `after-step:*` resolvers, receiving the step result as the\n * second argument. Legacy shape; see `invokeAfter(resolvers, 'step', ...)`\n * for the current API.\n */\n async invokeAfterStep(\n resolvers: Map<string, ListenerResolver>,\n ctx: StepListenerContext,\n result: StepListenerResult,\n ): Promise<void> {\n const newResolvers = this.convertLegacyResolvers(\n resolvers,\n LISTENER_PHASE_PREFIX.AfterStep,\n LISTENER_PHASE.After,\n );\n await this.invokeAfter(newResolvers, 'step', ctx, result);\n }\n\n /**\n * Invoke all `on-step-error:*` resolvers, receiving the thrown error as the\n * second argument. Legacy shape; see `invokeOnError(resolvers, 'step', ...)`\n * for the current API.\n */\n async invokeOnErrorStep(\n resolvers: Map<string, ListenerResolver>,\n ctx: StepListenerContext,\n err: unknown,\n ): Promise<void> {\n const newResolvers = this.convertLegacyResolvers(\n resolvers,\n LISTENER_PHASE_PREFIX.OnStepError,\n LISTENER_PHASE.OnError,\n );\n await this.invokeOnError(newResolvers, 'step', ctx, err);\n }\n\n /**\n * Translate legacy `before-step:` / `after-step:` / `on-step-error:` keys\n * into the new `${phase}:step:${name}` shape. Only entries whose key\n * starts with the given legacy prefix are forwarded; everything else is\n * silently dropped (matches the prefix-filter semantics of the original\n * legacy loops).\n *\n * `nonCritical` is not representable in the legacy `ListenerResolver`\n * shape (bare function values), so the converted entries always carry\n * `nonCritical: undefined` — i.e. critical propagation. This preserves\n * the legacy contract exactly.\n */\n private convertLegacyResolvers(\n legacy: Map<string, ListenerResolver>,\n legacyPrefix: string,\n newPhase: string,\n ): ResolverMap {\n const out: ResolverMap = new Map();\n for (const [key, fn] of legacy.entries()) {\n if (!key.startsWith(legacyPrefix)) continue;\n const newKey = `${newPhase}:step:${key.slice(legacyPrefix.length)}`;\n out.set(newKey, { fn: fn as ListenerFn });\n }\n return out;\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /**\n * Compute the positional argument list to forward to a before/after\n * listener, based on the listener's kind.\n *\n * - `job` / `step` / `chunk` → `[ctx]` or `[ctx, args]`\n * - `item-read` /\n * `item-process` /\n * `item-write` → `[args, ctx]` (args is the item, leading position)\n */\n private buildCallArgs(kind: LifecyclePhaseKind, ctx: ListenerContext, args: unknown): unknown[] {\n switch (kind) {\n case 'item-read':\n case 'item-process':\n case 'item-write':\n return args === undefined ? [ctx] : [args, ctx];\n case 'job':\n case 'step':\n case 'chunk':\n return args === undefined ? [ctx] : [ctx, args];\n default: {\n // exhaustive guard\n const _exhaustive: never = kind;\n void _exhaustive;\n return [ctx];\n }\n }\n }\n\n /**\n * Iterate the resolver map in insertion order, invoke every entry whose key\n * starts with `prefix`, and apply the failure policy:\n *\n * - if the entry is missing / the function rejects:\n * - `nonCritical: true` → log a warning, swallow, continue\n * - otherwise → re-throw, aborting the surrounding executor\n */\n private async invokeMatching(\n resolvers: ResolverMap,\n prefix: string,\n args: unknown[],\n ): Promise<void> {\n for (const [key, entry] of resolvers.entries()) {\n if (!key.startsWith(prefix)) continue;\n try {\n await entry.fn(...args);\n } catch (err) {\n if (entry.nonCritical) {\n this.logger.warn(\n `[ListenerInvoker] non-critical listener \"${key}\" failed: ${formatError(err)}`,\n );\n continue;\n }\n throw err;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Shared listener-context payload. All fields are optional because the\n * caller (executor) may not always have a stepExecutionId at hand (e.g. for\n * job-level listeners). */\nexport interface ListenerContext {\n jobExecutionId: string;\n stepExecutionId?: string;\n stepName?: string;\n jobParameters?: JobParameters;\n /** Arbitrary, executor-supplied metadata (transaction context, etc.). */\n [extra: string]: unknown;\n}\n\nfunction formatError(err: unknown): string {\n return err instanceof Error ? `${err.name}: ${err.message}` : String(err);\n}\n"],"names":["LISTENER_PHASE","LISTENER_PHASE_PREFIX","ListenerInvoker","BeforeStep","AfterStep","OnStepError","Before","After","OnError","OnSkip","logger","Logger","name","invokeBefore","resolvers","kind","ctx","args","callArgs","buildCallArgs","invokeMatching","invokeAfter","invokeOnError","err","invokeBeforeRead","invokeAfterRead","item","invokeOnReadError","invokeBeforeProcess","invokeAfterProcess","result","invokeOnProcessError","invokeBeforeWrite","items","invokeAfterWrite","invokeOnWriteError","invokeOnSkipRead","invokeOnSkipProcess","invokeOnSkipWrite","invokeBeforeStep","newResolvers","convertLegacyResolvers","invokeAfterStep","invokeOnErrorStep","legacy","legacyPrefix","newPhase","out","Map","key","fn","entries","startsWith","newKey","slice","length","set","undefined","_exhaustive","prefix","entry","nonCritical","warn","formatError","Error","message","String"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC;;;;;;;;;;;QA4EYA;eAAAA;;QAnCAC;eAAAA;;QA+DAC;eAAAA;;;wBAvGsB;;;;;;;AAwC5B,MAAMD,wBAAwB;IACnCE,YAAY;IACZC,WAAW;IACXC,aAAa;AACf;AA+BO,MAAML,iBAAiB;IAC5BM,QAAQ;IACRC,OAAO;IACPC,SAAS;IACTC,QAAQ;AACV;AAuBO,IAAA,AAAMP,kBAAN,MAAMA;IACMQ,SAAS,IAAIC,cAAM,CAACT,gBAAgBU,IAAI,EAAE;IAE3D,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAE5E;;;;;;;;GAQC,GACD,MAAMC,aACJC,SAAsB,EACtBC,IAAwB,EACxBC,GAAoB,EACpBC,IAAc,EACC;QACf,MAAMC,WAAW,IAAI,CAACC,aAAa,CAACJ,MAAMC,KAAKC;QAC/C,MAAM,IAAI,CAACG,cAAc,CAACN,WAAW,GAAGd,eAAeM,MAAM,CAAC,CAAC,EAAES,KAAK,CAAC,CAAC,EAAEG;IAC5E;IAEA;;;GAGC,GACD,MAAMG,YACJP,SAAsB,EACtBC,IAAwB,EACxBC,GAAoB,EACpBC,IAAc,EACC;QACf,MAAMC,WAAW,IAAI,CAACC,aAAa,CAACJ,MAAMC,KAAKC;QAC/C,MAAM,IAAI,CAACG,cAAc,CAACN,WAAW,GAAGd,eAAeO,KAAK,CAAC,CAAC,EAAEQ,KAAK,CAAC,CAAC,EAAEG;IAC3E;IAEA;;;GAGC,GACD,MAAMI,cACJR,SAAsB,EACtBC,IAAiB,EACjBC,GAAoB,EACpBO,GAAY,EACG;QACf,MAAM,IAAI,CAACH,cAAc,CAACN,WAAW,GAAGd,eAAeQ,OAAO,CAAC,CAAC,EAAEO,KAAK,CAAC,CAAC,EAAE;YAACC;YAAKO;SAAI;IACvF;IAEA,oFAAoF,GACpF,MAAMC,iBAAiBV,SAAsB,EAAEE,GAAoB,EAAiB;QAClF,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeM,MAAM,CAAC,WAAW,CAAC,EAAE;YAACU;SAAI;IACnF;IAEA,yFAAyF,GACzF,MAAMS,gBACJX,SAAsB,EACtBY,IAAa,EACbV,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeO,KAAK,CAAC,WAAW,CAAC,EAAE;YAACmB;YAAMV;SAAI;IACxF;IAEA,2FAA2F,GAC3F,MAAMW,kBACJb,SAAsB,EACtBS,GAAY,EACZP,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeQ,OAAO,CAAC,WAAW,CAAC,EAAE;YAACe;YAAKP;SAAI;IACzF;IAEA,6FAA6F,GAC7F,MAAMY,oBACJd,SAAsB,EACtBY,IAAa,EACbV,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeM,MAAM,CAAC,cAAc,CAAC,EAAE;YAACoB;YAAMV;SAAI;IAC5F;IAEA,oGAAoG,GACpG,MAAMa,mBACJf,SAAsB,EACtBY,IAAa,EACbI,MAAe,EACfd,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeO,KAAK,CAAC,cAAc,CAAC,EAAE;YAC5EmB;YACAI;YACAd;SACD;IACH;IAEA,oGAAoG,GACpG,MAAMe,qBACJjB,SAAsB,EACtBY,IAAa,EACbH,GAAY,EACZP,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeQ,OAAO,CAAC,cAAc,CAAC,EAAE;YAC9EkB;YACAH;YACAP;SACD;IACH;IAEA,4FAA4F,GAC5F,MAAMgB,kBACJlB,SAAsB,EACtBmB,KAAgB,EAChBjB,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeM,MAAM,CAAC,YAAY,CAAC,EAAE;YAAC2B;YAAOjB;SAAI;IAC3F;IAEA,mGAAmG,GACnG,MAAMkB,iBACJpB,SAAsB,EACtBmB,KAAgB,EAChBH,MAAe,EACfd,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeO,KAAK,CAAC,YAAY,CAAC,EAAE;YAC1E0B;YACAH;YACAd;SACD;IACH;IAEA,mGAAmG,GACnG,MAAMmB,mBACJrB,SAAsB,EACtBmB,KAAgB,EAChBV,GAAY,EACZP,GAAoB,EACL;QACf,MAAM,IAAI,CAACI,cAAc,CAACN,WAAW,GAAGd,eAAeQ,OAAO,CAAC,YAAY,CAAC,EAAE;YAC5EyB;YACAV;YACAP;SACD;IACH;IAEA,sFAAsF,GACtF,MAAMoB,iBAAiBtB,SAAsB,EAAES,GAAY,EAAEG,IAAa,EAAiB;QACzF,MAAM,IAAI,CAACN,cAAc,CAACN,WAAW,GAAGd,eAAeS,MAAM,CAAC,MAAM,CAAC,EAAE;YAACc;YAAKG;SAAK;IACpF;IAEA,yFAAyF,GACzF,MAAMW,oBAAoBvB,SAAsB,EAAEY,IAAa,EAAEH,GAAY,EAAiB;QAC5F,MAAM,IAAI,CAACH,cAAc,CAACN,WAAW,GAAGd,eAAeS,MAAM,CAAC,SAAS,CAAC,EAAE;YAACiB;YAAMH;SAAI;IACvF;IAEA,wFAAwF,GACxF,MAAMe,kBAAkBxB,SAAsB,EAAEmB,KAAgB,EAAEV,GAAY,EAAiB;QAC7F,MAAM,IAAI,CAACH,cAAc,CAACN,WAAW,GAAGd,eAAeS,MAAM,CAAC,OAAO,CAAC,EAAE;YAACwB;YAAOV;SAAI;IACtF;IAEA,4EAA4E;IAC5E,wEAAwE;IACxE,0EAA0E;IAC1E,yEAAyE;IACzE,yEAAyE;IACzE,8CAA8C;IAC9C,4EAA4E;IAE5E;;;;;;;;GAQC,GACD,MAAMgB,iBACJzB,SAAwC,EACxCE,GAAwB,EACT;QACf,MAAMwB,eAAe,IAAI,CAACC,sBAAsB,CAC9C3B,WACAb,sBAAsBE,UAAU,EAChCH,eAAeM,MAAM;QAEvB,MAAM,IAAI,CAACO,YAAY,CAAC2B,cAAc,QAAQxB;IAChD;IAEA;;;;GAIC,GACD,MAAM0B,gBACJ5B,SAAwC,EACxCE,GAAwB,EACxBc,MAA0B,EACX;QACf,MAAMU,eAAe,IAAI,CAACC,sBAAsB,CAC9C3B,WACAb,sBAAsBG,SAAS,EAC/BJ,eAAeO,KAAK;QAEtB,MAAM,IAAI,CAACc,WAAW,CAACmB,cAAc,QAAQxB,KAAKc;IACpD;IAEA;;;;GAIC,GACD,MAAMa,kBACJ7B,SAAwC,EACxCE,GAAwB,EACxBO,GAAY,EACG;QACf,MAAMiB,eAAe,IAAI,CAACC,sBAAsB,CAC9C3B,WACAb,sBAAsBI,WAAW,EACjCL,eAAeQ,OAAO;QAExB,MAAM,IAAI,CAACc,aAAa,CAACkB,cAAc,QAAQxB,KAAKO;IACtD;IAEA;;;;;;;;;;;GAWC,GACD,AAAQkB,uBACNG,MAAqC,EACrCC,YAAoB,EACpBC,QAAgB,EACH;QACb,MAAMC,MAAmB,IAAIC;QAC7B,KAAK,MAAM,CAACC,KAAKC,GAAG,IAAIN,OAAOO,OAAO,GAAI;YACxC,IAAI,CAACF,IAAIG,UAAU,CAACP,eAAe;YACnC,MAAMQ,SAAS,GAAGP,SAAS,MAAM,EAAEG,IAAIK,KAAK,CAACT,aAAaU,MAAM,GAAG;YACnER,IAAIS,GAAG,CAACH,QAAQ;gBAAEH,IAAIA;YAAiB;QACzC;QACA,OAAOH;IACT;IAEA,4EAA4E;IAC5E,YAAY;IACZ,4EAA4E;IAE5E;;;;;;;;GAQC,GACD,AAAQ5B,cAAcJ,IAAwB,EAAEC,GAAoB,EAAEC,IAAa,EAAa;QAC9F,OAAQF;YACN,KAAK;YACL,KAAK;YACL,KAAK;gBACH,OAAOE,SAASwC,YAAY;oBAACzC;iBAAI,GAAG;oBAACC;oBAAMD;iBAAI;YACjD,KAAK;YACL,KAAK;YACL,KAAK;gBACH,OAAOC,SAASwC,YAAY;oBAACzC;iBAAI,GAAG;oBAACA;oBAAKC;iBAAK;YACjD;gBAAS;oBACP,mBAAmB;oBACnB,MAAMyC,cAAqB3C;oBAC3B,KAAK2C;oBACL,OAAO;wBAAC1C;qBAAI;gBACd;QACF;IACF;IAEA;;;;;;;GAOC,GACD,MAAcI,eACZN,SAAsB,EACtB6C,MAAc,EACd1C,IAAe,EACA;QACf,KAAK,MAAM,CAACgC,KAAKW,MAAM,IAAI9C,UAAUqC,OAAO,GAAI;YAC9C,IAAI,CAACF,IAAIG,UAAU,CAACO,SAAS;YAC7B,IAAI;gBACF,MAAMC,MAAMV,EAAE,IAAIjC;YACpB,EAAE,OAAOM,KAAK;gBACZ,IAAIqC,MAAMC,WAAW,EAAE;oBACrB,IAAI,CAACnD,MAAM,CAACoD,IAAI,CACd,CAAC,yCAAyC,EAAEb,IAAI,UAAU,EAAEc,YAAYxC,MAAM;oBAEhF;gBACF;gBACA,MAAMA;YACR;QACF;IACF;AACF;;;;AAkBA,SAASwC,YAAYxC,GAAY;IAC/B,OAAOA,eAAeyC,QAAQ,GAAGzC,IAAIX,IAAI,CAAC,EAAE,EAAEW,IAAI0C,OAAO,EAAE,GAAGC,OAAO3C;AACvE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TaskletStepDefinition } from '../core/ir';
|
|
2
|
-
import type { JobRepository, StepExecution, ExecutionScope } from '../core/repository';
|
|
2
|
+
import type { JobRepository, StepExecution, ExecutionScope, JobParameters } from '../core/repository';
|
|
3
3
|
import type { TransactionManager } from '../core/transaction';
|
|
4
4
|
import { StepStatus, JobStatus } from '../core/status';
|
|
5
5
|
import { ListenerInvoker, type ListenerResolver } from './listener-invoker';
|
|
@@ -10,6 +10,9 @@ import { type ProviderResolvers } from './ref-resolver';
|
|
|
10
10
|
*/
|
|
11
11
|
export interface TaskletExecutionContext {
|
|
12
12
|
jobExecutionId: string;
|
|
13
|
+
stepExecutionId?: string;
|
|
14
|
+
stepName?: string;
|
|
15
|
+
jobParameters?: JobParameters;
|
|
13
16
|
jobRepository: JobRepository;
|
|
14
17
|
transactionManager: TransactionManager;
|
|
15
18
|
listenerInvoker: ListenerInvoker;
|