@nest-batch/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/src/adapters/in-process.adapter.d.ts +140 -0
- package/dist/src/adapters/in-process.adapter.d.ts.map +1 -0
- package/dist/src/adapters/in-process.adapter.js +86 -0
- package/dist/src/adapters/in-process.adapter.js.map +1 -0
- package/dist/src/adapters/index.d.ts +18 -0
- package/dist/src/adapters/index.d.ts.map +1 -0
- package/dist/src/adapters/index.js +35 -0
- package/dist/src/adapters/index.js.map +1 -0
- package/dist/src/builder/batch-builder.d.ts +26 -0
- package/dist/src/builder/batch-builder.d.ts.map +1 -0
- package/dist/src/builder/batch-builder.js +25 -0
- package/dist/src/builder/batch-builder.js.map +1 -0
- package/dist/src/builder/flow-builder.d.ts +59 -0
- package/dist/src/builder/flow-builder.d.ts.map +1 -0
- package/dist/src/builder/flow-builder.js +115 -0
- package/dist/src/builder/flow-builder.js.map +1 -0
- package/dist/src/builder/index.d.ts +5 -0
- package/dist/src/builder/index.d.ts.map +1 -0
- package/dist/src/builder/index.js +23 -0
- package/dist/src/builder/index.js.map +1 -0
- package/dist/src/builder/job-builder.d.ts +76 -0
- package/dist/src/builder/job-builder.d.ts.map +1 -0
- package/dist/src/builder/job-builder.js +166 -0
- package/dist/src/builder/job-builder.js.map +1 -0
- package/dist/src/builder/step-builder.d.ts +74 -0
- package/dist/src/builder/step-builder.d.ts.map +1 -0
- package/dist/src/builder/step-builder.js +144 -0
- package/dist/src/builder/step-builder.js.map +1 -0
- package/dist/src/compiler/builder-types.d.ts +20 -0
- package/dist/src/compiler/builder-types.d.ts.map +1 -0
- package/dist/src/compiler/builder-types.js +6 -0
- package/dist/src/compiler/builder-types.js.map +1 -0
- package/dist/src/compiler/definition-compiler.d.ts +99 -0
- package/dist/src/compiler/definition-compiler.d.ts.map +1 -0
- package/dist/src/compiler/definition-compiler.js +257 -0
- package/dist/src/compiler/definition-compiler.js.map +1 -0
- package/dist/src/compiler/index.d.ts +3 -0
- package/dist/src/compiler/index.d.ts.map +1 -0
- package/dist/src/compiler/index.js +21 -0
- package/dist/src/compiler/index.js.map +1 -0
- package/dist/src/core/errors.d.ts +77 -0
- package/dist/src/core/errors.d.ts.map +1 -0
- package/dist/src/core/errors.js +170 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/execution-context/index.d.ts +4 -0
- package/dist/src/core/execution-context/index.d.ts.map +1 -0
- package/dist/src/core/execution-context/index.js +22 -0
- package/dist/src/core/execution-context/index.js.map +1 -0
- package/dist/src/core/execution-context/json-value.d.ts +5 -0
- package/dist/src/core/execution-context/json-value.d.ts.map +1 -0
- package/dist/src/core/execution-context/json-value.js +6 -0
- package/dist/src/core/execution-context/json-value.js.map +1 -0
- package/dist/src/core/execution-context/serializer.d.ts +4 -0
- package/dist/src/core/execution-context/serializer.d.ts.map +1 -0
- package/dist/src/core/execution-context/serializer.js +34 -0
- package/dist/src/core/execution-context/serializer.js.map +1 -0
- package/dist/src/core/execution-context/validator.d.ts +18 -0
- package/dist/src/core/execution-context/validator.d.ts.map +1 -0
- package/dist/src/core/execution-context/validator.js +90 -0
- package/dist/src/core/execution-context/validator.js.map +1 -0
- package/dist/src/core/index.d.ts +8 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +26 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/ir/decider-definition.d.ts +20 -0
- package/dist/src/core/ir/decider-definition.d.ts.map +1 -0
- package/dist/src/core/ir/decider-definition.js +6 -0
- package/dist/src/core/ir/decider-definition.js.map +1 -0
- package/dist/src/core/ir/index.d.ts +8 -0
- package/dist/src/core/ir/index.d.ts.map +1 -0
- package/dist/src/core/ir/index.js +26 -0
- package/dist/src/core/ir/index.js.map +1 -0
- package/dist/src/core/ir/job-definition.d.ts +15 -0
- package/dist/src/core/ir/job-definition.d.ts.map +1 -0
- package/dist/src/core/ir/job-definition.js +6 -0
- package/dist/src/core/ir/job-definition.js.map +1 -0
- package/dist/src/core/ir/listener-definition.d.ts +10 -0
- package/dist/src/core/ir/listener-definition.d.ts.map +1 -0
- package/dist/src/core/ir/listener-definition.js +6 -0
- package/dist/src/core/ir/listener-definition.js.map +1 -0
- package/dist/src/core/ir/policy-config.d.ts +24 -0
- package/dist/src/core/ir/policy-config.d.ts.map +1 -0
- package/dist/src/core/ir/policy-config.js +6 -0
- package/dist/src/core/ir/policy-config.js.map +1 -0
- package/dist/src/core/ir/refs.d.ts +42 -0
- package/dist/src/core/ir/refs.d.ts.map +1 -0
- package/dist/src/core/ir/refs.js +18 -0
- package/dist/src/core/ir/refs.js.map +1 -0
- package/dist/src/core/ir/step-definition.d.ts +59 -0
- package/dist/src/core/ir/step-definition.d.ts.map +1 -0
- package/dist/src/core/ir/step-definition.js +6 -0
- package/dist/src/core/ir/step-definition.js.map +1 -0
- package/dist/src/core/ir/transition-definition.d.ts +8 -0
- package/dist/src/core/ir/transition-definition.d.ts.map +1 -0
- package/dist/src/core/ir/transition-definition.js +6 -0
- package/dist/src/core/ir/transition-definition.js.map +1 -0
- package/dist/src/core/item/index.d.ts +2 -0
- package/dist/src/core/item/index.d.ts.map +1 -0
- package/dist/src/core/item/index.js +20 -0
- package/dist/src/core/item/index.js.map +1 -0
- package/dist/src/core/item/interfaces.d.ts +64 -0
- package/dist/src/core/item/interfaces.d.ts.map +1 -0
- package/dist/src/core/item/interfaces.js +6 -0
- package/dist/src/core/item/interfaces.js.map +1 -0
- package/dist/src/core/repository/index.d.ts +3 -0
- package/dist/src/core/repository/index.d.ts.map +1 -0
- package/dist/src/core/repository/index.js +21 -0
- package/dist/src/core/repository/index.js.map +1 -0
- package/dist/src/core/repository/job-repository.d.ts +60 -0
- package/dist/src/core/repository/job-repository.d.ts.map +1 -0
- package/dist/src/core/repository/job-repository.js +27 -0
- package/dist/src/core/repository/job-repository.js.map +1 -0
- package/dist/src/core/repository/types.d.ts +84 -0
- package/dist/src/core/repository/types.d.ts.map +1 -0
- package/dist/src/core/repository/types.js +6 -0
- package/dist/src/core/repository/types.js.map +1 -0
- package/dist/src/core/status.d.ts +29 -0
- package/dist/src/core/status.d.ts.map +1 -0
- package/dist/src/core/status.js +58 -0
- package/dist/src/core/status.js.map +1 -0
- package/dist/src/core/transaction/index.d.ts +2 -0
- package/dist/src/core/transaction/index.d.ts.map +1 -0
- package/dist/src/core/transaction/index.js +20 -0
- package/dist/src/core/transaction/index.js.map +1 -0
- package/dist/src/core/transaction/transaction-manager.d.ts +8 -0
- package/dist/src/core/transaction/transaction-manager.d.ts.map +1 -0
- package/dist/src/core/transaction/transaction-manager.js +14 -0
- package/dist/src/core/transaction/transaction-manager.js.map +1 -0
- package/dist/src/core/validation/definition-validator.d.ts +46 -0
- package/dist/src/core/validation/definition-validator.d.ts.map +1 -0
- package/dist/src/core/validation/definition-validator.js +177 -0
- package/dist/src/core/validation/definition-validator.js.map +1 -0
- package/dist/src/core/validation/index.d.ts +2 -0
- package/dist/src/core/validation/index.d.ts.map +1 -0
- package/dist/src/core/validation/index.js +20 -0
- package/dist/src/core/validation/index.js.map +1 -0
- package/dist/src/decorators/constants.d.ts +10 -0
- package/dist/src/decorators/constants.d.ts.map +1 -0
- package/dist/src/decorators/constants.js +50 -0
- package/dist/src/decorators/constants.js.map +1 -0
- package/dist/src/decorators/flow.decorator.d.ts +25 -0
- package/dist/src/decorators/flow.decorator.d.ts.map +1 -0
- package/dist/src/decorators/flow.decorator.js +19 -0
- package/dist/src/decorators/flow.decorator.js.map +1 -0
- package/dist/src/decorators/index.d.ts +8 -0
- package/dist/src/decorators/index.d.ts.map +1 -0
- package/dist/src/decorators/index.js +26 -0
- package/dist/src/decorators/index.js.map +1 -0
- package/dist/src/decorators/item.decorators.d.ts +32 -0
- package/dist/src/decorators/item.decorators.d.ts.map +1 -0
- package/dist/src/decorators/item.decorators.js +40 -0
- package/dist/src/decorators/item.decorators.js.map +1 -0
- package/dist/src/decorators/job.decorator.d.ts +11 -0
- package/dist/src/decorators/job.decorator.d.ts.map +1 -0
- package/dist/src/decorators/job.decorator.js +17 -0
- package/dist/src/decorators/job.decorator.js.map +1 -0
- package/dist/src/decorators/listener.decorators.d.ts +56 -0
- package/dist/src/decorators/listener.decorators.d.ts.map +1 -0
- package/dist/src/decorators/listener.decorators.js +157 -0
- package/dist/src/decorators/listener.decorators.js.map +1 -0
- package/dist/src/decorators/step.decorator.d.ts +25 -0
- package/dist/src/decorators/step.decorator.d.ts.map +1 -0
- package/dist/src/decorators/step.decorator.js +21 -0
- package/dist/src/decorators/step.decorator.js.map +1 -0
- package/dist/src/decorators/tasklet.decorator.d.ts +7 -0
- package/dist/src/decorators/tasklet.decorator.d.ts.map +1 -0
- package/dist/src/decorators/tasklet.decorator.js +21 -0
- package/dist/src/decorators/tasklet.decorator.js.map +1 -0
- package/dist/src/execution/batch-worker-runner.d.ts +27 -0
- package/dist/src/execution/batch-worker-runner.d.ts.map +1 -0
- package/dist/src/execution/batch-worker-runner.js +147 -0
- package/dist/src/execution/batch-worker-runner.js.map +1 -0
- package/dist/src/execution/chunk-step-executor.d.ts +86 -0
- package/dist/src/execution/chunk-step-executor.d.ts.map +1 -0
- package/dist/src/execution/chunk-step-executor.js +482 -0
- package/dist/src/execution/chunk-step-executor.js.map +1 -0
- package/dist/src/execution/execution-strategy.d.ts +110 -0
- package/dist/src/execution/execution-strategy.d.ts.map +1 -0
- package/dist/src/execution/execution-strategy.js +13 -0
- package/dist/src/execution/execution-strategy.js.map +1 -0
- package/dist/src/execution/external-task-execution-strategy.d.ts +36 -0
- package/dist/src/execution/external-task-execution-strategy.d.ts.map +1 -0
- package/dist/src/execution/external-task-execution-strategy.js +97 -0
- package/dist/src/execution/external-task-execution-strategy.js.map +1 -0
- package/dist/src/execution/in-process-execution-strategy.d.ts +129 -0
- package/dist/src/execution/in-process-execution-strategy.d.ts.map +1 -0
- package/dist/src/execution/in-process-execution-strategy.js +141 -0
- package/dist/src/execution/in-process-execution-strategy.js.map +1 -0
- package/dist/src/execution/index.d.ts +14 -0
- package/dist/src/execution/index.d.ts.map +1 -0
- package/dist/src/execution/index.js +32 -0
- package/dist/src/execution/index.js.map +1 -0
- package/dist/src/execution/job-executor.d.ts +145 -0
- package/dist/src/execution/job-executor.d.ts.map +1 -0
- package/dist/src/execution/job-executor.js +475 -0
- package/dist/src/execution/job-executor.js.map +1 -0
- package/dist/src/execution/job-explorer.d.ts +15 -0
- package/dist/src/execution/job-explorer.d.ts.map +1 -0
- package/dist/src/execution/job-explorer.js +84 -0
- package/dist/src/execution/job-explorer.js.map +1 -0
- package/dist/src/execution/job-key.d.ts +3 -0
- package/dist/src/execution/job-key.d.ts.map +1 -0
- package/dist/src/execution/job-key.js +43 -0
- package/dist/src/execution/job-key.js.map +1 -0
- package/dist/src/execution/job-launcher.d.ts +75 -0
- package/dist/src/execution/job-launcher.d.ts.map +1 -0
- package/dist/src/execution/job-launcher.js +112 -0
- package/dist/src/execution/job-launcher.js.map +1 -0
- package/dist/src/execution/job-operator.d.ts +22 -0
- package/dist/src/execution/job-operator.d.ts.map +1 -0
- package/dist/src/execution/job-operator.js +125 -0
- package/dist/src/execution/job-operator.js.map +1 -0
- package/dist/src/execution/listener-invoker.d.ts +164 -0
- package/dist/src/execution/listener-invoker.d.ts.map +1 -0
- package/dist/src/execution/listener-invoker.js +246 -0
- package/dist/src/execution/listener-invoker.js.map +1 -0
- package/dist/src/execution/ref-resolver.d.ts +40 -0
- package/dist/src/execution/ref-resolver.d.ts.map +1 -0
- package/dist/src/execution/ref-resolver.js +41 -0
- package/dist/src/execution/ref-resolver.js.map +1 -0
- package/dist/src/execution/tasklet-step-executor.d.ts +79 -0
- package/dist/src/execution/tasklet-step-executor.d.ts.map +1 -0
- package/dist/src/execution/tasklet-step-executor.js +138 -0
- package/dist/src/execution/tasklet-step-executor.js.map +1 -0
- package/dist/src/explorer/batch-explorer.d.ts +138 -0
- package/dist/src/explorer/batch-explorer.d.ts.map +1 -0
- package/dist/src/explorer/batch-explorer.js +167 -0
- package/dist/src/explorer/batch-explorer.js.map +1 -0
- package/dist/src/explorer/index.d.ts +2 -0
- package/dist/src/explorer/index.d.ts.map +1 -0
- package/dist/src/explorer/index.js +20 -0
- package/dist/src/explorer/index.js.map +1 -0
- package/dist/src/flow/flow-evaluator.d.ts +30 -0
- package/dist/src/flow/flow-evaluator.d.ts.map +1 -0
- package/dist/src/flow/flow-evaluator.js +80 -0
- package/dist/src/flow/flow-evaluator.js.map +1 -0
- package/dist/src/flow/index.d.ts +2 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +20 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +90 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/io/checkpoint.d.ts +7 -0
- package/dist/src/io/checkpoint.d.ts.map +1 -0
- package/dist/src/io/checkpoint.js +56 -0
- package/dist/src/io/checkpoint.js.map +1 -0
- package/dist/src/io/database.d.ts +50 -0
- package/dist/src/io/database.d.ts.map +1 -0
- package/dist/src/io/database.js +108 -0
- package/dist/src/io/database.js.map +1 -0
- package/dist/src/io/file-readers.d.ts +54 -0
- package/dist/src/io/file-readers.d.ts.map +1 -0
- package/dist/src/io/file-readers.js +167 -0
- package/dist/src/io/file-readers.js.map +1 -0
- package/dist/src/io/file-writers.d.ts +31 -0
- package/dist/src/io/file-writers.d.ts.map +1 -0
- package/dist/src/io/file-writers.js +80 -0
- package/dist/src/io/file-writers.js.map +1 -0
- package/dist/src/io/index.d.ts +6 -0
- package/dist/src/io/index.d.ts.map +1 -0
- package/dist/src/io/index.js +24 -0
- package/dist/src/io/index.js.map +1 -0
- package/dist/src/io/s3.d.ts +50 -0
- package/dist/src/io/s3.d.ts.map +1 -0
- package/dist/src/io/s3.js +96 -0
- package/dist/src/io/s3.js.map +1 -0
- package/dist/src/listeners/builtin-listeners.d.ts +77 -0
- package/dist/src/listeners/builtin-listeners.d.ts.map +1 -0
- package/dist/src/listeners/builtin-listeners.js +108 -0
- package/dist/src/listeners/builtin-listeners.js.map +1 -0
- package/dist/src/listeners/index.d.ts +8 -0
- package/dist/src/listeners/index.d.ts.map +1 -0
- package/dist/src/listeners/index.js +25 -0
- package/dist/src/listeners/index.js.map +1 -0
- package/dist/src/module/adapter-options.d.ts +39 -0
- package/dist/src/module/adapter-options.d.ts.map +1 -0
- package/dist/src/module/adapter-options.js +34 -0
- package/dist/src/module/adapter-options.js.map +1 -0
- package/dist/src/module/adapter.d.ts +157 -0
- package/dist/src/module/adapter.d.ts.map +1 -0
- package/dist/src/module/adapter.js +80 -0
- package/dist/src/module/adapter.js.map +1 -0
- package/dist/src/module/batch-schedule-registry.d.ts +110 -0
- package/dist/src/module/batch-schedule-registry.d.ts.map +1 -0
- package/dist/src/module/batch-schedule-registry.js +0 -0
- package/dist/src/module/batch-schedule-registry.js.map +1 -0
- package/dist/src/module/index.d.ts +14 -0
- package/dist/src/module/index.d.ts.map +1 -0
- package/dist/src/module/index.js +31 -0
- package/dist/src/module/index.js.map +1 -0
- package/dist/src/module/nest-batch.module.d.ts +236 -0
- package/dist/src/module/nest-batch.module.d.ts.map +1 -0
- package/dist/src/module/nest-batch.module.js +475 -0
- package/dist/src/module/nest-batch.module.js.map +1 -0
- package/dist/src/module/tokens.d.ts +83 -0
- package/dist/src/module/tokens.d.ts.map +1 -0
- package/dist/src/module/tokens.js +58 -0
- package/dist/src/module/tokens.js.map +1 -0
- package/dist/src/observability/event-types.d.ts +55 -0
- package/dist/src/observability/event-types.d.ts.map +1 -0
- package/dist/src/observability/event-types.js +36 -0
- package/dist/src/observability/event-types.js.map +1 -0
- package/dist/src/observability/exporters.d.ts +35 -0
- package/dist/src/observability/exporters.d.ts.map +1 -0
- package/dist/src/observability/exporters.js +93 -0
- package/dist/src/observability/exporters.js.map +1 -0
- package/dist/src/observability/index.d.ts +3 -0
- package/dist/src/observability/index.d.ts.map +1 -0
- package/dist/src/observability/index.js +21 -0
- package/dist/src/observability/index.js.map +1 -0
- package/dist/src/partition-helpers.d.ts +127 -0
- package/dist/src/partition-helpers.d.ts.map +1 -0
- package/dist/src/partition-helpers.js +136 -0
- package/dist/src/partition-helpers.js.map +1 -0
- package/dist/src/policies/backoff.d.ts +3 -0
- package/dist/src/policies/backoff.d.ts.map +1 -0
- package/dist/src/policies/backoff.js +34 -0
- package/dist/src/policies/backoff.js.map +1 -0
- package/dist/src/policies/index.d.ts +4 -0
- package/dist/src/policies/index.d.ts.map +1 -0
- package/dist/src/policies/index.js +22 -0
- package/dist/src/policies/index.js.map +1 -0
- package/dist/src/policies/retry-policy.d.ts +13 -0
- package/dist/src/policies/retry-policy.d.ts.map +1 -0
- package/dist/src/policies/retry-policy.js +55 -0
- package/dist/src/policies/retry-policy.js.map +1 -0
- package/dist/src/policies/skip-policy.d.ts +12 -0
- package/dist/src/policies/skip-policy.d.ts.map +1 -0
- package/dist/src/policies/skip-policy.js +44 -0
- package/dist/src/policies/skip-policy.js.map +1 -0
- package/dist/src/registry/index.d.ts +2 -0
- package/dist/src/registry/index.d.ts.map +1 -0
- package/dist/src/registry/index.js +20 -0
- package/dist/src/registry/index.js.map +1 -0
- package/dist/src/registry/job-registry.d.ts +16 -0
- package/dist/src/registry/job-registry.d.ts.map +1 -0
- package/dist/src/registry/job-registry.js +50 -0
- package/dist/src/registry/job-registry.js.map +1 -0
- package/dist/src/repository/id-generator.d.ts +18 -0
- package/dist/src/repository/id-generator.d.ts.map +1 -0
- package/dist/src/repository/id-generator.js +37 -0
- package/dist/src/repository/id-generator.js.map +1 -0
- package/dist/src/repository/in-memory/in-memory-job-repository.d.ts +49 -0
- package/dist/src/repository/in-memory/in-memory-job-repository.d.ts.map +1 -0
- package/dist/src/repository/in-memory/in-memory-job-repository.js +291 -0
- package/dist/src/repository/in-memory/in-memory-job-repository.js.map +1 -0
- package/dist/src/repository/in-memory/index.d.ts +2 -0
- package/dist/src/repository/in-memory/index.d.ts.map +1 -0
- package/dist/src/repository/in-memory/index.js +20 -0
- package/dist/src/repository/in-memory/index.js.map +1 -0
- package/dist/src/repository/index.d.ts +4 -0
- package/dist/src/repository/index.d.ts.map +1 -0
- package/dist/src/repository/index.js +22 -0
- package/dist/src/repository/index.js.map +1 -0
- package/dist/src/repository/uuid-v7.d.ts +20 -0
- package/dist/src/repository/uuid-v7.d.ts.map +1 -0
- package/dist/src/repository/uuid-v7.js +31 -0
- package/dist/src/repository/uuid-v7.js.map +1 -0
- package/dist/src/scheduling/batch-scheduled.d.ts +87 -0
- package/dist/src/scheduling/batch-scheduled.d.ts.map +1 -0
- package/dist/src/scheduling/batch-scheduled.js +170 -0
- package/dist/src/scheduling/batch-scheduled.js.map +1 -0
- package/dist/src/transaction/in-memory-transaction-manager.d.ts +16 -0
- package/dist/src/transaction/in-memory-transaction-manager.d.ts.map +1 -0
- package/dist/src/transaction/in-memory-transaction-manager.js +33 -0
- package/dist/src/transaction/in-memory-transaction-manager.js.map +1 -0
- package/dist/src/transaction/index.d.ts +2 -0
- package/dist/src/transaction/index.d.ts.map +1 -0
- package/dist/src/transaction/index.js +20 -0
- package/dist/src/transaction/index.js.map +1 -0
- package/dist/tests/contracts/index.d.ts +26 -0
- package/dist/tests/contracts/index.d.ts.map +1 -0
- package/dist/tests/contracts/index.js +37 -0
- package/dist/tests/contracts/index.js.map +1 -0
- package/dist/tests/contracts/job-repository.contract.d.ts +46 -0
- package/dist/tests/contracts/job-repository.contract.d.ts.map +1 -0
- package/dist/tests/contracts/job-repository.contract.js +644 -0
- package/dist/tests/contracts/job-repository.contract.js.map +1 -0
- package/package.json +80 -0
- package/src/adapters/in-process.adapter.ts +182 -0
- package/src/adapters/index.ts +17 -0
- package/src/builder/batch-builder.ts +32 -0
- package/src/builder/flow-builder.ts +141 -0
- package/src/builder/index.ts +4 -0
- package/src/builder/job-builder.ts +206 -0
- package/src/builder/step-builder.ts +190 -0
- package/src/compiler/builder-types.ts +27 -0
- package/src/compiler/definition-compiler.ts +325 -0
- package/src/compiler/index.ts +2 -0
- package/src/core/errors.ts +125 -0
- package/src/core/execution-context/index.ts +3 -0
- package/src/core/execution-context/json-value.ts +3 -0
- package/src/core/execution-context/serializer.ts +21 -0
- package/src/core/execution-context/validator.ts +103 -0
- package/src/core/index.ts +7 -0
- package/src/core/ir/decider-definition.ts +25 -0
- package/src/core/ir/index.ts +7 -0
- package/src/core/ir/job-definition.ts +15 -0
- package/src/core/ir/listener-definition.ts +19 -0
- package/src/core/ir/policy-config.ts +19 -0
- package/src/core/ir/refs.ts +42 -0
- package/src/core/ir/step-definition.ts +62 -0
- package/src/core/ir/transition-definition.ts +9 -0
- package/src/core/item/index.ts +1 -0
- package/src/core/item/interfaces.ts +70 -0
- package/src/core/repository/index.ts +2 -0
- package/src/core/repository/job-repository.ts +100 -0
- package/src/core/repository/types.ts +91 -0
- package/src/core/status.ts +31 -0
- package/src/core/transaction/index.ts +1 -0
- package/src/core/transaction/transaction-manager.ts +8 -0
- package/src/core/validation/definition-validator.ts +215 -0
- package/src/core/validation/index.ts +1 -0
- package/src/decorators/constants.ts +9 -0
- package/src/decorators/flow.decorator.ts +31 -0
- package/src/decorators/index.ts +7 -0
- package/src/decorators/item.decorators.ts +51 -0
- package/src/decorators/job.decorator.ts +16 -0
- package/src/decorators/listener.decorators.ts +142 -0
- package/src/decorators/step.decorator.ts +33 -0
- package/src/decorators/tasklet.decorator.ts +14 -0
- package/src/execution/batch-worker-runner.ts +142 -0
- package/src/execution/chunk-step-executor.ts +594 -0
- package/src/execution/execution-strategy.ts +115 -0
- package/src/execution/external-task-execution-strategy.ts +104 -0
- package/src/execution/in-process-execution-strategy.ts +207 -0
- package/src/execution/index.ts +13 -0
- package/src/execution/job-executor.ts +553 -0
- package/src/execution/job-explorer.ts +73 -0
- package/src/execution/job-key.ts +35 -0
- package/src/execution/job-launcher.ts +132 -0
- package/src/execution/job-operator.ts +127 -0
- package/src/execution/listener-invoker.ts +389 -0
- package/src/execution/ref-resolver.ts +64 -0
- package/src/execution/tasklet-step-executor.ts +182 -0
- package/src/explorer/batch-explorer.ts +251 -0
- package/src/explorer/index.ts +1 -0
- package/src/flow/flow-evaluator.ts +89 -0
- package/src/flow/index.ts +1 -0
- package/src/index.ts +24 -0
- package/src/io/checkpoint.ts +47 -0
- package/src/io/database.ts +114 -0
- package/src/io/file-readers.ts +191 -0
- package/src/io/file-writers.ts +99 -0
- package/src/io/index.ts +5 -0
- package/src/io/s3.ts +117 -0
- package/src/listeners/builtin-listeners.ts +151 -0
- package/src/listeners/index.ts +7 -0
- package/src/module/adapter-options.ts +38 -0
- package/src/module/adapter.ts +160 -0
- package/src/module/batch-schedule-registry.ts +0 -0
- package/src/module/index.ts +13 -0
- package/src/module/nest-batch.module.ts +674 -0
- package/src/module/tokens.ts +95 -0
- package/src/observability/event-types.ts +61 -0
- package/src/observability/exporters.ts +96 -0
- package/src/observability/index.ts +2 -0
- package/src/partition-helpers.ts +204 -0
- package/src/policies/backoff.ts +22 -0
- package/src/policies/index.ts +3 -0
- package/src/policies/retry-policy.ts +57 -0
- package/src/policies/skip-policy.ts +51 -0
- package/src/registry/index.ts +1 -0
- package/src/registry/job-registry.ts +42 -0
- package/src/repository/id-generator.ts +25 -0
- package/src/repository/in-memory/in-memory-job-repository.ts +334 -0
- package/src/repository/in-memory/index.ts +1 -0
- package/src/repository/index.ts +3 -0
- package/src/repository/uuid-v7.ts +40 -0
- package/src/scheduling/batch-scheduled.ts +257 -0
- package/src/transaction/in-memory-transaction-manager.ts +23 -0
- package/src/transaction/index.ts +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
|
|
3
|
+
import { Inject, Injectable, Optional, forwardRef } from '@nestjs/common';
|
|
4
|
+
|
|
5
|
+
import { JobNotFoundError, JobExecutionAlreadyRunningError } from '../core/errors';
|
|
6
|
+
import { JobRepository, type JobParameters, type JobExecution } from '../core/repository';
|
|
7
|
+
import { JobRegistry } from '../registry/job-registry';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
EXECUTION_STRATEGY,
|
|
11
|
+
type IExecutionStrategy,
|
|
12
|
+
} from './execution-strategy';
|
|
13
|
+
import { JobExecutor } from './job-executor';
|
|
14
|
+
import { canonicalJobKey } from './job-key';
|
|
15
|
+
|
|
16
|
+
import type { JobDefinition } from '../core/ir';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* JobLauncher — public entry point for starting a new JobExecution.
|
|
22
|
+
*
|
|
23
|
+
* Flow (pre-strategy, kept unchanged for backwards compat):
|
|
24
|
+
* 1. Look up the `JobDefinition` from the registry. Missing → `JobNotFoundError`.
|
|
25
|
+
* 2. Canonicalize `params` into a stable `jobKey` hash. Object key order,
|
|
26
|
+
* `null/undefined` omission, `Date → ISO` are all normalized so that
|
|
27
|
+
* semantically-identical params yield the same key.
|
|
28
|
+
* 3. `createExecutionAtomic(jobId, jobKey, params)` — idempotent
|
|
29
|
+
* instance get-or-create + `SELECT ... FOR UPDATE SKIP LOCKED` to
|
|
30
|
+
* serialize concurrent launches + running-execution check + insert,
|
|
31
|
+
* all in a single transaction. Throws
|
|
32
|
+
* `JobExecutionAlreadyRunningError` if another launch is in flight
|
|
33
|
+
* or an execution is already STARTING/STARTED for the same instance.
|
|
34
|
+
* 4. Delegate to the injected `IExecutionStrategy` (default:
|
|
35
|
+
* `InProcessExecutionStrategy`, which wraps `JobExecutor.execute`).
|
|
36
|
+
* The lock is released when the createExecutionAtomic transaction
|
|
37
|
+
* commits; the executor itself runs outside the lock (it can be
|
|
38
|
+
* long-running).
|
|
39
|
+
*
|
|
40
|
+
* `allowDuplicateInstances: true` bypasses step 2's dedup by appending a
|
|
41
|
+
* fresh UUID nonce to the canonical key on every call, forcing a new
|
|
42
|
+
* `JobInstance` each time.
|
|
43
|
+
*
|
|
44
|
+
* Polymorphic strategy (Task 11):
|
|
45
|
+
* The launcher is a thin facade — it owns the registry lookup, the
|
|
46
|
+
* canonical `jobKey` derivation, the atomic instance + concurrency
|
|
47
|
+
* lock, and the post-strategy `JobExecution` re-resolution. The
|
|
48
|
+
* actual *how* of running the job (in-process vs. transport) lives
|
|
49
|
+
* behind the `EXECUTION_STRATEGY` token. Sibling packages (e.g.
|
|
50
|
+
* `@nest-batch/bullmq`) override the token with a transport
|
|
51
|
+
* strategy that enqueues work; the launcher never learns about the
|
|
52
|
+
* transport.
|
|
53
|
+
*
|
|
54
|
+
* When no strategy is bound (e.g. direct manual construction in
|
|
55
|
+
* unit tests), the launcher falls back to the original direct
|
|
56
|
+
* `JobExecutor.execute` path. This keeps the legacy
|
|
57
|
+
* `tests/execution/job-launcher.test.ts` working without changes.
|
|
58
|
+
*/
|
|
59
|
+
@Injectable()
|
|
60
|
+
export class JobLauncher {
|
|
61
|
+
constructor(
|
|
62
|
+
private readonly registry: JobRegistry,
|
|
63
|
+
private readonly repository: JobRepository,
|
|
64
|
+
@Inject(forwardRef(() => JobExecutor))
|
|
65
|
+
private readonly jobExecutor: JobExecutor,
|
|
66
|
+
@Optional()
|
|
67
|
+
@Inject(EXECUTION_STRATEGY)
|
|
68
|
+
private readonly strategy?: IExecutionStrategy,
|
|
69
|
+
) {}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Launch a new job execution. Returns the final `JobExecution` after it
|
|
73
|
+
* has finished (status = COMPLETED | FAILED) — or, for transport
|
|
74
|
+
* strategies that return `{ kind: 'enqueued' }`, the latest persisted
|
|
75
|
+
* `JobExecution` (still in `STARTING` / `STARTED`, since the executor
|
|
76
|
+
* has not run on the launcher process).
|
|
77
|
+
*
|
|
78
|
+
* Throws `JobNotFoundError` if `jobId` is not registered.
|
|
79
|
+
* Throws `JobExecutionAlreadyRunningError` if a previous launch of the
|
|
80
|
+
* same `jobName + jobKey` is still in flight.
|
|
81
|
+
*/
|
|
82
|
+
async launch(jobId: string, params: JobParameters = {}): Promise<JobExecution> {
|
|
83
|
+
const jobDef = this.registry.get(jobId); // throws JobNotFoundError on miss
|
|
84
|
+
const canonical = canonicalJobKey(params);
|
|
85
|
+
const jobKey = jobDef.allowDuplicateInstances ? `${canonical}::${randomUUID()}` : canonical;
|
|
86
|
+
|
|
87
|
+
// Atomic get-or-create + lock + check + insert. The repository's
|
|
88
|
+
// implementation uses INSERT ... ON CONFLICT DO NOTHING +
|
|
89
|
+
// FOR UPDATE SKIP LOCKED to serialize concurrent launches.
|
|
90
|
+
const execution = await this.repository.createExecutionAtomic(jobId, jobKey, params);
|
|
91
|
+
|
|
92
|
+
// No strategy bound → fall back to the legacy direct-execute path.
|
|
93
|
+
// This branch preserves `tests/execution/job-launcher.test.ts` (which
|
|
94
|
+
// wires the launcher by hand, no Nest module) and any other
|
|
95
|
+
// direct-construction consumer.
|
|
96
|
+
if (this.strategy === undefined) {
|
|
97
|
+
return this.jobExecutor.execute(execution, jobDef);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The strategy's `LaunchResult` is intentionally discarded: the
|
|
101
|
+
// public `JobLauncher.launch` contract always returns a
|
|
102
|
+
// `JobExecution` (re-resolved from the repository below), and
|
|
103
|
+
// `queueJobId` is for the strategy's own bookkeeping only.
|
|
104
|
+
await this.strategy.launch(jobDef, params, {
|
|
105
|
+
executionId: execution.id,
|
|
106
|
+
jobExecutionId: execution.id,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// For both `completed` and `enqueued` the public `JobLauncher.launch`
|
|
110
|
+
// contract returns a `JobExecution`. Re-resolve the latest persisted
|
|
111
|
+
// row so the caller sees the executor's writes (status, endTime,
|
|
112
|
+
// exitCode) without us hand-merging patches here. If the lookup
|
|
113
|
+
// somehow returns null (e.g. a custom repository that drops rows),
|
|
114
|
+
// fall back to the execution the launcher pre-created.
|
|
115
|
+
const latest = await this.repository.getJobExecution(execution.id);
|
|
116
|
+
return latest ?? execution;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resume an existing `JobExecution` (used by the restart path).
|
|
121
|
+
* For this MVP it just delegates to the executor — the launcher does
|
|
122
|
+
* not yet gate on restartable / concurrency / FAILED-prev-exists rules.
|
|
123
|
+
*
|
|
124
|
+
* Note: `run` bypasses the strategy on purpose. Restart is a
|
|
125
|
+
* recovery path on the in-process execution model; transport
|
|
126
|
+
* strategies that need their own resume semantics can override
|
|
127
|
+
* this method on a subclass or via a future token.
|
|
128
|
+
*/
|
|
129
|
+
async run(execution: JobExecution, jobDef: JobDefinition): Promise<JobExecution> {
|
|
130
|
+
return this.jobExecutor.execute(execution, jobDef);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { Injectable } from '@nestjs/common';
|
|
4
|
+
|
|
5
|
+
import { InvalidJobOperationError } from '../core/errors';
|
|
6
|
+
import {
|
|
7
|
+
JobExecution,
|
|
8
|
+
JobExecutionDetails,
|
|
9
|
+
JobExecutionFilter,
|
|
10
|
+
JobInstance,
|
|
11
|
+
JobInstanceFilter,
|
|
12
|
+
JobParameters,
|
|
13
|
+
JobRepository,
|
|
14
|
+
} from '../core/repository';
|
|
15
|
+
import type { JobDefinition } from '../core/ir';
|
|
16
|
+
import { JobStatus } from '../core/status';
|
|
17
|
+
import { JobRegistry } from '../registry/job-registry';
|
|
18
|
+
|
|
19
|
+
import { JobExplorer } from './job-explorer';
|
|
20
|
+
import { JobLauncher } from './job-launcher';
|
|
21
|
+
|
|
22
|
+
@Injectable()
|
|
23
|
+
export class JobOperator {
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly explorer: JobExplorer,
|
|
26
|
+
private readonly registry: JobRegistry,
|
|
27
|
+
private readonly repository: JobRepository,
|
|
28
|
+
private readonly launcher: JobLauncher,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
listJobs(): JobDefinition[] {
|
|
32
|
+
return this.explorer.listJobs();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
listJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {
|
|
36
|
+
return this.explorer.listJobInstances(filter);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
listJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {
|
|
40
|
+
return this.explorer.listJobExecutions(filter);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getJobExecutionDetails(executionId: string): Promise<JobExecutionDetails> {
|
|
44
|
+
return this.explorer.getJobExecutionDetails(executionId);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async stop(executionId: string): Promise<JobExecutionDetails> {
|
|
48
|
+
const details = await this.explorer.getJobExecutionDetails(executionId);
|
|
49
|
+
const status = details.jobExecution.status;
|
|
50
|
+
|
|
51
|
+
if (status === JobStatus.STOPPED) {
|
|
52
|
+
return details;
|
|
53
|
+
}
|
|
54
|
+
if (!this.isActive(status)) {
|
|
55
|
+
throw new InvalidJobOperationError('stop', `Cannot stop execution in ${status} state`, {
|
|
56
|
+
executionId,
|
|
57
|
+
status,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await this.repository.updateJobExecution(executionId, {
|
|
62
|
+
status: JobStatus.STOPPED,
|
|
63
|
+
endTime: new Date(),
|
|
64
|
+
exitCode: 'STOPPED',
|
|
65
|
+
exitMessage: 'Stopped by JobOperator',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return this.explorer.getJobExecutionDetails(executionId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async abandon(executionId: string): Promise<JobExecutionDetails> {
|
|
72
|
+
const details = await this.explorer.getJobExecutionDetails(executionId);
|
|
73
|
+
const status = details.jobExecution.status;
|
|
74
|
+
|
|
75
|
+
if (status === JobStatus.ABANDONED) {
|
|
76
|
+
return details;
|
|
77
|
+
}
|
|
78
|
+
if (this.isActive(status) || status === JobStatus.COMPLETED) {
|
|
79
|
+
throw new InvalidJobOperationError(
|
|
80
|
+
'abandon',
|
|
81
|
+
`Cannot abandon execution in ${status} state`,
|
|
82
|
+
{ executionId, status },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await this.repository.updateJobExecution(executionId, {
|
|
87
|
+
status: JobStatus.ABANDONED,
|
|
88
|
+
endTime: details.jobExecution.endTime ?? new Date(),
|
|
89
|
+
exitCode: 'ABANDONED',
|
|
90
|
+
exitMessage: 'Abandoned by JobOperator',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return this.explorer.getJobExecutionDetails(executionId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async restart(executionId: string): Promise<JobExecutionDetails> {
|
|
97
|
+
const details = await this.explorer.getJobExecutionDetails(executionId);
|
|
98
|
+
const status = details.jobExecution.status;
|
|
99
|
+
|
|
100
|
+
if (status !== JobStatus.FAILED && status !== JobStatus.STOPPED) {
|
|
101
|
+
throw new InvalidJobOperationError(
|
|
102
|
+
'restart',
|
|
103
|
+
`Cannot restart execution in ${status} state`,
|
|
104
|
+
{ executionId, status },
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const jobDef = this.registry.get(details.jobInstance.jobName);
|
|
109
|
+
const execution = await this.launcher.run(details.jobExecution, jobDef);
|
|
110
|
+
return this.explorer.getJobExecutionDetails(execution.id);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
startNextInstance(jobId: string, params: JobParameters = {}): Promise<JobExecution> {
|
|
114
|
+
return this.launcher.launch(jobId, {
|
|
115
|
+
...params,
|
|
116
|
+
_nestBatchRunId: randomUUID(),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private isActive(status: JobStatus): boolean {
|
|
121
|
+
return (
|
|
122
|
+
status === JobStatus.STARTING ||
|
|
123
|
+
status === JobStatus.STARTED ||
|
|
124
|
+
status === JobStatus.STOPPING
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListenerInvoker — orchestrates lifecycle listeners around step / chunk / job /
|
|
3
|
+
* item / skip execution.
|
|
4
|
+
*
|
|
5
|
+
* Supports the 7 listener kinds declared in `ListenerKind` (job / step / chunk /
|
|
6
|
+
* item-read / item-process / item-write / skip) with proper failure policy:
|
|
7
|
+
*
|
|
8
|
+
* - default → listener throw propagates
|
|
9
|
+
* - `nonCritical: true` → log + continue with the next listener
|
|
10
|
+
*
|
|
11
|
+
* Two resolver-map shapes are supported:
|
|
12
|
+
*
|
|
13
|
+
* 1. Legacy (Task 17) — `Map<string, ListenerResolver>` keyed by
|
|
14
|
+
* `${phase}-${kind}:${name}` (e.g. `before-step:MyListener`). The
|
|
15
|
+
* convenience methods `invokeBeforeStep / invokeAfterStep /
|
|
16
|
+
* invokeOnErrorStep` operate on this shape and remain in place for
|
|
17
|
+
* backward compatibility with the Wave-3 step executor.
|
|
18
|
+
*
|
|
19
|
+
* 2. Current (Task 20) — `Map<string, ListenerEntry>` keyed by
|
|
20
|
+
* `${phase}:${kind}:${name}` (e.g. `before:step:MyListener`,
|
|
21
|
+
* `on-skip:read:MySkipListener`). This is the shape consumed by
|
|
22
|
+
* `invokeBefore / invokeAfter / invokeOnError / invokeOnSkipRead /
|
|
23
|
+
* invokeOnSkipProcess / invokeOnSkipWrite` and is the source of truth for
|
|
24
|
+
* all 7 listener kinds going forward.
|
|
25
|
+
*
|
|
26
|
+
* Registration order is preserved (Map iteration is insertion-ordered in JS).
|
|
27
|
+
*/
|
|
28
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
29
|
+
import type { ExecutionContext } from '../core/repository';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Public types
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Listener function — signature is determined by the listener's kind/phase.
|
|
37
|
+
*
|
|
38
|
+
* Typed as `any[]` rather than `unknown[]` so listeners can be authored with
|
|
39
|
+
* the narrower, kind-specific signatures (e.g. `(ctx) => void`,
|
|
40
|
+
* `(item, ctx) => void`, `(err, item) => void`) without TypeScript rejecting
|
|
41
|
+
* the assignment to `ListenerEntry.fn`. The runtime contract is enforced by
|
|
42
|
+
* the invoker's kind-aware `buildCallArgs`, not by the type system.
|
|
43
|
+
*/
|
|
44
|
+
export type ListenerFn = (...args: any[]) => any;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolver-map entry used by the current (Task 20) API.
|
|
48
|
+
*
|
|
49
|
+
* - `fn` — the actual listener function to invoke
|
|
50
|
+
* - `nonCritical` — when true, failures from this listener are logged and
|
|
51
|
+
* suppressed; otherwise the failure propagates out of the
|
|
52
|
+
* `invoke*` call and aborts the surrounding executor.
|
|
53
|
+
*/
|
|
54
|
+
export interface ListenerEntry {
|
|
55
|
+
fn: ListenerFn;
|
|
56
|
+
nonCritical?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolver map consumed by the current (Task 20) `invoke*` methods. */
|
|
60
|
+
export type ResolverMap = Map<string, ListenerEntry>;
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Legacy types (Task 17 backward compatibility)
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/** Phase prefix constants used by the legacy convenience methods. */
|
|
67
|
+
export const LISTENER_PHASE_PREFIX = {
|
|
68
|
+
BeforeStep: 'before-step:',
|
|
69
|
+
AfterStep: 'after-step:',
|
|
70
|
+
OnStepError: 'on-step-error:',
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
/** Legacy resolver function type — bare callable, no per-entry metadata. */
|
|
74
|
+
export type ListenerResolver = (...args: unknown[]) => unknown | Promise<unknown>;
|
|
75
|
+
|
|
76
|
+
export interface StepListenerContext extends ListenerContext {
|
|
77
|
+
jobExecutionId: string;
|
|
78
|
+
stepExecutionId: string;
|
|
79
|
+
getExecutionContext?: () => Promise<ExecutionContext>;
|
|
80
|
+
saveExecutionContext?: (ctx: ExecutionContext) => Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface StepListenerResult {
|
|
84
|
+
status: string;
|
|
85
|
+
exitCode?: string;
|
|
86
|
+
exitMessage?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Current (Task 20) phase / kind constants
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Phase segment for the current key format `${phase}:${kind}:${name}`.
|
|
95
|
+
*
|
|
96
|
+
* - `before` — `invokeBefore`
|
|
97
|
+
* - `after` — `invokeAfter`
|
|
98
|
+
* - `on-error` — `invokeOnError` (job / step / chunk)
|
|
99
|
+
* - `on-skip` — `invokeOnSkipRead / Process / Write` (the trailing kind
|
|
100
|
+
* segment is one of `read` / `process` / `write`)
|
|
101
|
+
*/
|
|
102
|
+
export const LISTENER_PHASE = {
|
|
103
|
+
Before: 'before',
|
|
104
|
+
After: 'after',
|
|
105
|
+
OnError: 'on-error',
|
|
106
|
+
OnSkip: 'on-skip',
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
/** Phase kinds that share the standard `${phase}:${kind}:${name}` shape. */
|
|
110
|
+
export type LifecyclePhaseKind =
|
|
111
|
+
| 'job'
|
|
112
|
+
| 'step'
|
|
113
|
+
| 'chunk'
|
|
114
|
+
| 'item-read'
|
|
115
|
+
| 'item-process'
|
|
116
|
+
| 'item-write';
|
|
117
|
+
|
|
118
|
+
/** Phase kinds accepted by `invokeOnError` (subset of the lifecycle kinds). */
|
|
119
|
+
export type OnErrorKind = 'job' | 'step' | 'chunk';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sub-kinds for the `on-skip` phase. The resolver key looks like
|
|
123
|
+
* `on-skip:${SkipSubKind}:${name}` — for example `on-skip:read:MySkipListener`.
|
|
124
|
+
*/
|
|
125
|
+
export type SkipSubKind = 'read' | 'process' | 'write';
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Implementation
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
@Injectable()
|
|
132
|
+
export class ListenerInvoker {
|
|
133
|
+
private readonly logger = new Logger(ListenerInvoker.name);
|
|
134
|
+
|
|
135
|
+
// -------------------------------------------------------------------------
|
|
136
|
+
// Current (Task 20) API — supports all 7 listener kinds with failure policy
|
|
137
|
+
// -------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Invoke every `before:<kind>:<name>` resolver, in registration order.
|
|
141
|
+
*
|
|
142
|
+
* Listener signature depends on `<kind>`:
|
|
143
|
+
* - `job` / `chunk` — `fn(ctx)`
|
|
144
|
+
* - `step` — `fn(ctx, result)` (the optional `args` is the result)
|
|
145
|
+
* - `item-read` / `item-process` / `item-write` — `fn(item, ctx)` (the
|
|
146
|
+
* optional `args` is the item, placed in the first position by
|
|
147
|
+
* convention)
|
|
148
|
+
*/
|
|
149
|
+
async invokeBefore(
|
|
150
|
+
resolvers: ResolverMap,
|
|
151
|
+
kind: LifecyclePhaseKind,
|
|
152
|
+
ctx: ListenerContext,
|
|
153
|
+
args?: unknown,
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
const callArgs = this.buildCallArgs(kind, ctx, args);
|
|
156
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.Before}:${kind}:`, callArgs);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Invoke every `after:<kind>:<name>` resolver, in registration order.
|
|
161
|
+
* Same signature rules as `invokeBefore`.
|
|
162
|
+
*/
|
|
163
|
+
async invokeAfter(
|
|
164
|
+
resolvers: ResolverMap,
|
|
165
|
+
kind: LifecyclePhaseKind,
|
|
166
|
+
ctx: ListenerContext,
|
|
167
|
+
args?: unknown,
|
|
168
|
+
): Promise<void> {
|
|
169
|
+
const callArgs = this.buildCallArgs(kind, ctx, args);
|
|
170
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.After}:${kind}:`, callArgs);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Invoke every `on-error:<kind>:<name>` resolver, in registration order.
|
|
175
|
+
* Listener signature is `fn(ctx, err)`.
|
|
176
|
+
*/
|
|
177
|
+
async invokeOnError(
|
|
178
|
+
resolvers: ResolverMap,
|
|
179
|
+
kind: OnErrorKind,
|
|
180
|
+
ctx: ListenerContext,
|
|
181
|
+
err: unknown,
|
|
182
|
+
): Promise<void> {
|
|
183
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnError}:${kind}:`, [ctx, err]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Invoke every `on-skip:read:<name>` resolver. Listener signature: `fn(err, item)`. */
|
|
187
|
+
async invokeOnSkipRead(
|
|
188
|
+
resolvers: ResolverMap,
|
|
189
|
+
err: unknown,
|
|
190
|
+
item: unknown,
|
|
191
|
+
): Promise<void> {
|
|
192
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:read:`, [err, item]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Invoke every `on-skip:process:<name>` resolver. Listener signature: `fn(item, err)`. */
|
|
196
|
+
async invokeOnSkipProcess(
|
|
197
|
+
resolvers: ResolverMap,
|
|
198
|
+
item: unknown,
|
|
199
|
+
err: unknown,
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:process:`, [item, err]);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Invoke every `on-skip:write:<name>` resolver. Listener signature: `fn(items, err)`. */
|
|
205
|
+
async invokeOnSkipWrite(
|
|
206
|
+
resolvers: ResolverMap,
|
|
207
|
+
items: unknown[],
|
|
208
|
+
err: unknown,
|
|
209
|
+
): Promise<void> {
|
|
210
|
+
await this.invokeMatching(resolvers, `${LISTENER_PHASE.OnSkip}:write:`, [items, err]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// -------------------------------------------------------------------------
|
|
214
|
+
// Legacy (Task 17) convenience methods — preserved for backward compat.
|
|
215
|
+
// They now delegate to the current (Task 20) API by converting the legacy
|
|
216
|
+
// `Map<string, ListenerResolver>` shape into a `ResolverMap` and routing
|
|
217
|
+
// through the shared `invokeMatching` internal path. One source of truth
|
|
218
|
+
// for listener dispatch, two public surfaces.
|
|
219
|
+
// -------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Invoke all `before-step:*` resolvers in Map insertion order. Operates on
|
|
223
|
+
* the legacy `Map<string, ListenerResolver>` shape; the current
|
|
224
|
+
* `invokeBefore(resolvers, 'step', ...)` should be preferred for new code.
|
|
225
|
+
*
|
|
226
|
+
* Implementation: convert the legacy `before-step:*` entries into the new
|
|
227
|
+
* `before:step:*` shape and delegate to `invokeBefore` so the dispatch /
|
|
228
|
+
* `nonCritical` policy is shared with the rest of the invoker.
|
|
229
|
+
*/
|
|
230
|
+
async invokeBeforeStep(
|
|
231
|
+
resolvers: Map<string, ListenerResolver>,
|
|
232
|
+
ctx: StepListenerContext,
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
const newResolvers = this.convertLegacyResolvers(
|
|
235
|
+
resolvers,
|
|
236
|
+
LISTENER_PHASE_PREFIX.BeforeStep,
|
|
237
|
+
LISTENER_PHASE.Before,
|
|
238
|
+
);
|
|
239
|
+
await this.invokeBefore(newResolvers, 'step', ctx);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Invoke all `after-step:*` resolvers, receiving the step result as the
|
|
244
|
+
* second argument. Legacy shape; see `invokeAfter(resolvers, 'step', ...)`
|
|
245
|
+
* for the current API.
|
|
246
|
+
*/
|
|
247
|
+
async invokeAfterStep(
|
|
248
|
+
resolvers: Map<string, ListenerResolver>,
|
|
249
|
+
ctx: StepListenerContext,
|
|
250
|
+
result: StepListenerResult,
|
|
251
|
+
): Promise<void> {
|
|
252
|
+
const newResolvers = this.convertLegacyResolvers(
|
|
253
|
+
resolvers,
|
|
254
|
+
LISTENER_PHASE_PREFIX.AfterStep,
|
|
255
|
+
LISTENER_PHASE.After,
|
|
256
|
+
);
|
|
257
|
+
await this.invokeAfter(newResolvers, 'step', ctx, result);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Invoke all `on-step-error:*` resolvers, receiving the thrown error as the
|
|
262
|
+
* second argument. Legacy shape; see `invokeOnError(resolvers, 'step', ...)`
|
|
263
|
+
* for the current API.
|
|
264
|
+
*/
|
|
265
|
+
async invokeOnErrorStep(
|
|
266
|
+
resolvers: Map<string, ListenerResolver>,
|
|
267
|
+
ctx: StepListenerContext,
|
|
268
|
+
err: unknown,
|
|
269
|
+
): Promise<void> {
|
|
270
|
+
const newResolvers = this.convertLegacyResolvers(
|
|
271
|
+
resolvers,
|
|
272
|
+
LISTENER_PHASE_PREFIX.OnStepError,
|
|
273
|
+
LISTENER_PHASE.OnError,
|
|
274
|
+
);
|
|
275
|
+
await this.invokeOnError(newResolvers, 'step', ctx, err);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Translate legacy `before-step:` / `after-step:` / `on-step-error:` keys
|
|
280
|
+
* into the new `${phase}:step:${name}` shape. Only entries whose key
|
|
281
|
+
* starts with the given legacy prefix are forwarded; everything else is
|
|
282
|
+
* silently dropped (matches the prefix-filter semantics of the original
|
|
283
|
+
* legacy loops).
|
|
284
|
+
*
|
|
285
|
+
* `nonCritical` is not representable in the legacy `ListenerResolver`
|
|
286
|
+
* shape (bare function values), so the converted entries always carry
|
|
287
|
+
* `nonCritical: undefined` — i.e. critical propagation. This preserves
|
|
288
|
+
* the legacy contract exactly.
|
|
289
|
+
*/
|
|
290
|
+
private convertLegacyResolvers(
|
|
291
|
+
legacy: Map<string, ListenerResolver>,
|
|
292
|
+
legacyPrefix: string,
|
|
293
|
+
newPhase: string,
|
|
294
|
+
): ResolverMap {
|
|
295
|
+
const out: ResolverMap = new Map();
|
|
296
|
+
for (const [key, fn] of legacy.entries()) {
|
|
297
|
+
if (!key.startsWith(legacyPrefix)) continue;
|
|
298
|
+
const newKey = `${newPhase}:step:${key.slice(legacyPrefix.length)}`;
|
|
299
|
+
out.set(newKey, { fn: fn as ListenerFn });
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// -------------------------------------------------------------------------
|
|
305
|
+
// Internals
|
|
306
|
+
// -------------------------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Compute the positional argument list to forward to a before/after
|
|
310
|
+
* listener, based on the listener's kind.
|
|
311
|
+
*
|
|
312
|
+
* - `job` / `chunk` → `[ctx]`
|
|
313
|
+
* - `step` → `[ctx, args]` (args is the result)
|
|
314
|
+
* - `item-read` /
|
|
315
|
+
* `item-process` /
|
|
316
|
+
* `item-write` → `[args, ctx]` (args is the item, leading position)
|
|
317
|
+
*/
|
|
318
|
+
private buildCallArgs(
|
|
319
|
+
kind: LifecyclePhaseKind,
|
|
320
|
+
ctx: ListenerContext,
|
|
321
|
+
args: unknown,
|
|
322
|
+
): unknown[] {
|
|
323
|
+
switch (kind) {
|
|
324
|
+
case 'item-read':
|
|
325
|
+
case 'item-process':
|
|
326
|
+
case 'item-write':
|
|
327
|
+
return [args, ctx];
|
|
328
|
+
case 'step':
|
|
329
|
+
return [ctx, args];
|
|
330
|
+
case 'job':
|
|
331
|
+
case 'chunk':
|
|
332
|
+
return [ctx];
|
|
333
|
+
default: {
|
|
334
|
+
// exhaustive guard
|
|
335
|
+
const _exhaustive: never = kind;
|
|
336
|
+
void _exhaustive;
|
|
337
|
+
return [ctx];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Iterate the resolver map in insertion order, invoke every entry whose key
|
|
344
|
+
* starts with `prefix`, and apply the failure policy:
|
|
345
|
+
*
|
|
346
|
+
* - if the entry is missing / the function rejects:
|
|
347
|
+
* - `nonCritical: true` → log a warning, swallow, continue
|
|
348
|
+
* - otherwise → re-throw, aborting the surrounding executor
|
|
349
|
+
*/
|
|
350
|
+
private async invokeMatching(
|
|
351
|
+
resolvers: ResolverMap,
|
|
352
|
+
prefix: string,
|
|
353
|
+
args: unknown[],
|
|
354
|
+
): Promise<void> {
|
|
355
|
+
for (const [key, entry] of resolvers.entries()) {
|
|
356
|
+
if (!key.startsWith(prefix)) continue;
|
|
357
|
+
try {
|
|
358
|
+
await entry.fn(...args);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
if (entry.nonCritical) {
|
|
361
|
+
this.logger.warn(
|
|
362
|
+
`[ListenerInvoker] non-critical listener "${key}" failed: ${formatError(err)}`,
|
|
363
|
+
);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// Helpers
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
/** Shared listener-context payload. All fields are optional because the
|
|
377
|
+
* caller (executor) may not always have a stepExecutionId at hand (e.g. for
|
|
378
|
+
* job-level listeners). */
|
|
379
|
+
export interface ListenerContext {
|
|
380
|
+
jobExecutionId: string;
|
|
381
|
+
stepExecutionId?: string;
|
|
382
|
+
stepName?: string;
|
|
383
|
+
/** Arbitrary, executor-supplied metadata (transaction context, etc.). */
|
|
384
|
+
[extra: string]: unknown;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function formatError(err: unknown): string {
|
|
388
|
+
return err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
389
|
+
}
|