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