@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,553 @@
|
|
|
1
|
+
import { Injectable, Inject, Optional, forwardRef, Logger } from '@nestjs/common';
|
|
2
|
+
import type { JobDefinition, ListenerDefinition } from '../core/ir';
|
|
3
|
+
import { RefKind, type ListenerRef } from '../core/ir';
|
|
4
|
+
import { JobRepository, type JobParameters, type JobExecution } from '../core/repository';
|
|
5
|
+
import { TransactionManager } from '../core/transaction';
|
|
6
|
+
import { StepStatus, JobStatus, FlowExecutionStatus } from '../core/status';
|
|
7
|
+
import { JobNotRestartableError } from '../core/errors';
|
|
8
|
+
import { TaskletStepExecutor, type StepExecutionResult } from './tasklet-step-executor';
|
|
9
|
+
import { ChunkStepExecutor, type ChunkExecutionResult } from './chunk-step-executor';
|
|
10
|
+
import {
|
|
11
|
+
ListenerInvoker,
|
|
12
|
+
type ResolverMap,
|
|
13
|
+
type ListenerResolver,
|
|
14
|
+
} from './listener-invoker';
|
|
15
|
+
import { FlowEvaluator } from '../flow/flow-evaluator';
|
|
16
|
+
import {
|
|
17
|
+
BATCH_EVENT,
|
|
18
|
+
NoopBatchObserver,
|
|
19
|
+
type BatchEvent,
|
|
20
|
+
type BatchObserver,
|
|
21
|
+
} from '../observability';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Result type that covers both tasklet and chunk step outcomes.
|
|
25
|
+
* Structurally compatible with `StepExecutionPatch` so the executor
|
|
26
|
+
* can forward it directly to `updateStepExecution`.
|
|
27
|
+
*
|
|
28
|
+
* The only field chunk has but tasklet doesn't is `commitCount`; for
|
|
29
|
+
* tasklet results it stays `undefined` and `updateStepExecution`
|
|
30
|
+
* happily ignores undefined fields (merge semantics, see repository).
|
|
31
|
+
*/
|
|
32
|
+
type StepResult = StepExecutionResult | ChunkExecutionResult;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* JobExecutor — drives a single JobExecution to completion.
|
|
36
|
+
*
|
|
37
|
+
* Flow (per ORACLE verdict 3c):
|
|
38
|
+
* 1. Mark execution as STARTED.
|
|
39
|
+
* 2. `before:job:*` listeners.
|
|
40
|
+
* 3. Loop:
|
|
41
|
+
* a. Look up the current step (jobDef.steps[currentStepId]). If the
|
|
42
|
+
* step is missing, mark the job FAILED with exit code
|
|
43
|
+
* `NO_SUCH_STEP` and break.
|
|
44
|
+
* b. Create a StepExecution, run it (tasklet or chunk), and persist
|
|
45
|
+
* the result via `updateStepExecution`. During the run, the
|
|
46
|
+
* step's own `after-step:*` listeners fire (see
|
|
47
|
+
* `TaskletStepExecutor.execute` step 4 / `ChunkStepExecutor`).
|
|
48
|
+
* Those listeners run BEFORE we evaluate transitions so they get
|
|
49
|
+
* a chance to mutate the result (e.g. flip COMPLETED → FAILED)
|
|
50
|
+
* and the resulting flow routing sees the override.
|
|
51
|
+
* c. Map the (possibly overridden) step status to a
|
|
52
|
+
* `FlowExecutionStatus` and ask the `FlowEvaluator` for the next
|
|
53
|
+
* step. `null` means END.
|
|
54
|
+
* d. If the step FAILED and the evaluator returned `null`
|
|
55
|
+
* (no recovery transition matches), short-circuit the job to
|
|
56
|
+
* FAILED — we must not continue running subsequent steps
|
|
57
|
+
* declared in the graph, because none are reachable.
|
|
58
|
+
* 4. `after:job:*` listeners (with the final status).
|
|
59
|
+
*
|
|
60
|
+
* Out of scope (future tasks):
|
|
61
|
+
* - Concurrency control (Task 38).
|
|
62
|
+
* - `on-error:job:*` listener invocation when the executor itself
|
|
63
|
+
* throws (the catch block can be wired to it in a follow-up).
|
|
64
|
+
*/
|
|
65
|
+
@Injectable()
|
|
66
|
+
export class JobExecutor {
|
|
67
|
+
private readonly logger = new Logger(JobExecutor.name);
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
private readonly repository: JobRepository,
|
|
71
|
+
@Inject(forwardRef(() => TransactionManager))
|
|
72
|
+
private readonly transactionManager: TransactionManager,
|
|
73
|
+
private readonly taskletExecutor: TaskletStepExecutor,
|
|
74
|
+
private readonly chunkExecutor: ChunkStepExecutor,
|
|
75
|
+
private readonly listenerInvoker: ListenerInvoker,
|
|
76
|
+
private readonly flowEvaluator: FlowEvaluator,
|
|
77
|
+
@Optional()
|
|
78
|
+
private readonly observer: BatchObserver = new NoopBatchObserver(),
|
|
79
|
+
) {}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Execute a JobExecution against its `JobDefinition`. Returns the
|
|
83
|
+
* final, persisted `JobExecution` (status = COMPLETED | FAILED).
|
|
84
|
+
*
|
|
85
|
+
* Restart behavior (per Metis verdict 3b — restartable opt-in, per
|
|
86
|
+
* ORACLE 3b — default-on for persisted repositories):
|
|
87
|
+
* - If `execution.status` is `FAILED` on entry, this is a restart
|
|
88
|
+
* attempt. We require `jobDef.restartable === true`; otherwise we
|
|
89
|
+
* throw `JobNotRestartableError` and leave the execution alone.
|
|
90
|
+
* - For each chunk step, we look up the latest FAILED StepExecution
|
|
91
|
+
* for that step in this job execution. If one exists, we read its
|
|
92
|
+
* ExecutionContext's `lastChunkIndex` checkpoint and pass it to the
|
|
93
|
+
* `ChunkStepExecutor` as `resumeFromChunkIndex`, which then skips
|
|
94
|
+
* chunks ≤ that index. Tasklet steps always re-run from scratch
|
|
95
|
+
* (they have no chunk-level resume granularity in this MVP).
|
|
96
|
+
*
|
|
97
|
+
* Partition routing (T8): the optional third argument carries the
|
|
98
|
+
* `partitionIndex` / `partitionCount` pair the transport attached
|
|
99
|
+
* to the job. When set, the chunk executor bounds the read loop
|
|
100
|
+
* to the partition's range (see `packages/core/src/partition-helpers.ts`).
|
|
101
|
+
*/
|
|
102
|
+
async execute(
|
|
103
|
+
execution: JobExecution,
|
|
104
|
+
jobDef: JobDefinition,
|
|
105
|
+
partition?: { partitionIndex?: number; partitionCount?: number },
|
|
106
|
+
): Promise<JobExecution> {
|
|
107
|
+
// Capture the pre-execute status. For a fresh launch, the launcher
|
|
108
|
+
// created the execution with status STARTING; for a restart, the
|
|
109
|
+
// caller (JobLauncher.run) hands us a terminal execution that can
|
|
110
|
+
// be resumed.
|
|
111
|
+
const isRestart =
|
|
112
|
+
execution.status === JobStatus.FAILED || execution.status === JobStatus.STOPPED;
|
|
113
|
+
if (isRestart && !jobDef.restartable) {
|
|
114
|
+
throw new JobNotRestartableError(jobDef.id);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await this.repository.updateJobExecution(execution.id, {
|
|
118
|
+
status: JobStatus.STARTED,
|
|
119
|
+
startTime: new Date(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await this.emit({
|
|
123
|
+
type: BATCH_EVENT.JOB_STARTED,
|
|
124
|
+
timestamp: new Date(),
|
|
125
|
+
jobExecutionId: execution.id,
|
|
126
|
+
data: { jobName: jobDef.id },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Build the full resolver map once. The same map powers both the
|
|
130
|
+
// job-level `invokeBefore` / `invokeAfter` calls below and the
|
|
131
|
+
// step-level resolvers handed to the TaskletStepExecutor (derived
|
|
132
|
+
// by `buildLegacyStepResolvers` into the legacy key shape). Building
|
|
133
|
+
// it once per execution avoids re-walking the IR on every step.
|
|
134
|
+
const jobResolvers = this.buildResolverMap(jobDef);
|
|
135
|
+
const stepResolvers = this.buildLegacyStepResolvers(jobResolvers);
|
|
136
|
+
|
|
137
|
+
await this.listenerInvoker.invokeBefore(jobResolvers, 'job', {
|
|
138
|
+
jobExecutionId: execution.id,
|
|
139
|
+
stepExecutionId: '<job>',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Cache the step order once. `Object.keys` returns insertion order
|
|
143
|
+
// for string keys (per ES2015+), so this is the canonical
|
|
144
|
+
// declaration order — used for the linear fallback below.
|
|
145
|
+
const stepOrder = Object.keys(jobDef.steps);
|
|
146
|
+
|
|
147
|
+
let currentStepId: string | null = jobDef.startStepId;
|
|
148
|
+
let finalStatus: JobStatus = JobStatus.COMPLETED;
|
|
149
|
+
let currentStepExecutionId: string | null = null;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
while (currentStepId !== null) {
|
|
153
|
+
const step = jobDef.steps[currentStepId];
|
|
154
|
+
if (!step) {
|
|
155
|
+
await this.repository.updateJobExecution(execution.id, {
|
|
156
|
+
status: JobStatus.FAILED,
|
|
157
|
+
endTime: new Date(),
|
|
158
|
+
exitCode: 'NO_SUCH_STEP',
|
|
159
|
+
exitMessage: `Step "${currentStepId}" not found`,
|
|
160
|
+
});
|
|
161
|
+
finalStatus = JobStatus.FAILED;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Restart path: if this is a restart and the current step is a
|
|
166
|
+
// chunk step, locate the latest FAILED step execution for the
|
|
167
|
+
// same step name and load its `lastChunkIndex` checkpoint. That
|
|
168
|
+
// value is passed to the chunk executor as `resumeFromChunkIndex`.
|
|
169
|
+
// For tasklet steps (and chunk steps with no prior failure) we
|
|
170
|
+
// leave `resumeFromChunkIndex` undefined — the chunk executor
|
|
171
|
+
// treats undefined as "start from the beginning".
|
|
172
|
+
//
|
|
173
|
+
// Look this up BEFORE createStepExecution so the just-created
|
|
174
|
+
// STARTING step isn't returned as the "latest" entry.
|
|
175
|
+
let resumeFromChunkIndex: number | undefined;
|
|
176
|
+
if (isRestart && step.kind === 'chunk') {
|
|
177
|
+
const priorFailed = await this.repository.findLatestStepExecution(execution.id, step.id);
|
|
178
|
+
if (priorFailed && priorFailed.status === StepStatus.FAILED) {
|
|
179
|
+
resumeFromChunkIndex = await this.getLastCheckpoint(priorFailed.id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const stepExecution = await this.repository.createStepExecution(execution.id, step.id);
|
|
184
|
+
currentStepExecutionId = stepExecution.id;
|
|
185
|
+
|
|
186
|
+
await this.emit({
|
|
187
|
+
type: BATCH_EVENT.STEP_STARTED,
|
|
188
|
+
timestamp: new Date(),
|
|
189
|
+
jobExecutionId: execution.id,
|
|
190
|
+
stepExecutionId: stepExecution.id,
|
|
191
|
+
data: { stepId: step.id, kind: step.kind },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let result: StepResult;
|
|
195
|
+
try {
|
|
196
|
+
if (step.kind === 'tasklet') {
|
|
197
|
+
result = await this.taskletExecutor.execute(step, {
|
|
198
|
+
jobExecutionId: execution.id,
|
|
199
|
+
jobRepository: this.repository,
|
|
200
|
+
transactionManager: this.transactionManager,
|
|
201
|
+
listenerInvoker: this.listenerInvoker,
|
|
202
|
+
listenerResolvers: stepResolvers,
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
// Forward the partition routing (T8) when the transport
|
|
206
|
+
// supplied one. The chunk executor uses the partition
|
|
207
|
+
// index + count + the step's `partitions.range` to bound
|
|
208
|
+
// its read loop. Absent partition args fall through to
|
|
209
|
+
// the 0.1.0 non-partitioned chunk pipeline.
|
|
210
|
+
const partitionArgs =
|
|
211
|
+
partition?.partitionIndex !== undefined && partition?.partitionCount !== undefined
|
|
212
|
+
? {
|
|
213
|
+
partitionIndex: partition.partitionIndex,
|
|
214
|
+
partitionCount: partition.partitionCount,
|
|
215
|
+
}
|
|
216
|
+
: {};
|
|
217
|
+
result = await this.chunkExecutor.execute(step, {
|
|
218
|
+
jobExecutionId: execution.id,
|
|
219
|
+
stepExecutionId: stepExecution.id,
|
|
220
|
+
jobRepository: this.repository,
|
|
221
|
+
transactionManager: this.transactionManager,
|
|
222
|
+
listenerInvoker: this.listenerInvoker,
|
|
223
|
+
jobExecutionId2: execution.id,
|
|
224
|
+
resolvers: new Map(),
|
|
225
|
+
...(resumeFromChunkIndex !== undefined ? { resumeFromChunkIndex } : {}),
|
|
226
|
+
...partitionArgs,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
} catch (stepErr) {
|
|
230
|
+
// The executor itself threw (e.g. resolveReader threw in a
|
|
231
|
+
// chunk step before the executor's own try-catch could catch
|
|
232
|
+
// it). Persist the step as FAILED with the error message and
|
|
233
|
+
// re-raise so the outer handler marks the job FAILED.
|
|
234
|
+
await this.repository.updateStepExecution(stepExecution.id, {
|
|
235
|
+
status: StepStatus.FAILED,
|
|
236
|
+
exitCode: 'FAILED',
|
|
237
|
+
exitMessage: stepErr instanceof Error ? stepErr.message : String(stepErr),
|
|
238
|
+
endTime: new Date(),
|
|
239
|
+
});
|
|
240
|
+
currentStepExecutionId = null;
|
|
241
|
+
throw stepErr;
|
|
242
|
+
}
|
|
243
|
+
currentStepExecutionId = null;
|
|
244
|
+
|
|
245
|
+
await this.repository.updateStepExecution(stepExecution.id, {
|
|
246
|
+
status: result.status,
|
|
247
|
+
...(result.readCount !== undefined ? { readCount: result.readCount } : {}),
|
|
248
|
+
...(result.writeCount !== undefined ? { writeCount: result.writeCount } : {}),
|
|
249
|
+
...(result.skipCount !== undefined ? { skipCount: result.skipCount } : {}),
|
|
250
|
+
...('commitCount' in result && result.commitCount !== undefined
|
|
251
|
+
? { commitCount: result.commitCount }
|
|
252
|
+
: {}),
|
|
253
|
+
exitCode: result.exitCode,
|
|
254
|
+
exitMessage: result.exitMessage,
|
|
255
|
+
endTime: new Date(),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await this.emit({
|
|
259
|
+
type:
|
|
260
|
+
result.status === StepStatus.COMPLETED
|
|
261
|
+
? BATCH_EVENT.STEP_COMPLETED
|
|
262
|
+
: result.status === StepStatus.FAILED
|
|
263
|
+
? BATCH_EVENT.STEP_FAILED
|
|
264
|
+
: BATCH_EVENT.STEP_COMPLETED,
|
|
265
|
+
timestamp: new Date(),
|
|
266
|
+
jobExecutionId: execution.id,
|
|
267
|
+
stepExecutionId: stepExecution.id,
|
|
268
|
+
data: {
|
|
269
|
+
stepId: step.id,
|
|
270
|
+
status: result.status,
|
|
271
|
+
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Map StepStatus -> FlowExecutionStatus. Anything other than
|
|
276
|
+
// COMPLETED/FAILED collapses to UNKNOWN → evaluator returns
|
|
277
|
+
// null → flow ends.
|
|
278
|
+
const flowStatus: FlowExecutionStatus =
|
|
279
|
+
result.status === StepStatus.COMPLETED
|
|
280
|
+
? FlowExecutionStatus.COMPLETED
|
|
281
|
+
: result.status === StepStatus.FAILED
|
|
282
|
+
? FlowExecutionStatus.FAILED
|
|
283
|
+
: FlowExecutionStatus.UNKNOWN;
|
|
284
|
+
const deciderExitStatus = await this.resolveDeciderExitStatus(
|
|
285
|
+
jobDef,
|
|
286
|
+
currentStepId,
|
|
287
|
+
{
|
|
288
|
+
jobExecution: execution,
|
|
289
|
+
stepId: step.id,
|
|
290
|
+
stepExecutionId: stepExecution.id,
|
|
291
|
+
stepStatus: result.status,
|
|
292
|
+
exitCode: result.exitCode,
|
|
293
|
+
exitMessage: result.exitMessage,
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
const flowExitStatus = deciderExitStatus ?? (result.exitCode || flowStatus);
|
|
297
|
+
|
|
298
|
+
let evaluatorResult = await this.flowEvaluator.evaluate(
|
|
299
|
+
jobDef.transitions,
|
|
300
|
+
currentStepId,
|
|
301
|
+
flowExitStatus,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Distinguish "no transition declared" (linear fallback) from
|
|
305
|
+
// "transition declared with toStepId: null" (explicit END).
|
|
306
|
+
// FlowEvaluator returns null for both, so we inspect the
|
|
307
|
+
// transition list directly.
|
|
308
|
+
let hasMatchingTransition = jobDef.transitions.some((t) =>
|
|
309
|
+
this.flowEvaluator.matches(t, currentStepId!, flowExitStatus),
|
|
310
|
+
);
|
|
311
|
+
if (!hasMatchingTransition && flowExitStatus !== flowStatus) {
|
|
312
|
+
evaluatorResult = await this.flowEvaluator.evaluate(
|
|
313
|
+
jobDef.transitions,
|
|
314
|
+
currentStepId,
|
|
315
|
+
flowStatus,
|
|
316
|
+
);
|
|
317
|
+
hasMatchingTransition = jobDef.transitions.some((t) =>
|
|
318
|
+
this.flowEvaluator.matches(t, currentStepId!, flowStatus),
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let nextStepId: string | null;
|
|
323
|
+
if (hasMatchingTransition) {
|
|
324
|
+
// Explicit transition: respect its target, including null
|
|
325
|
+
// (END). Do not fall through to linear order.
|
|
326
|
+
nextStepId = evaluatorResult;
|
|
327
|
+
} else if (result.status === StepStatus.FAILED) {
|
|
328
|
+
// FAILED with no matching transition → short-circuit. The
|
|
329
|
+
// graph declares no path forward, so the job is FAILED — we
|
|
330
|
+
// must not invent a "next" step.
|
|
331
|
+
await this.repository.updateJobExecution(execution.id, {
|
|
332
|
+
status: JobStatus.FAILED,
|
|
333
|
+
endTime: new Date(),
|
|
334
|
+
exitCode: result.exitCode,
|
|
335
|
+
exitMessage: result.exitMessage,
|
|
336
|
+
});
|
|
337
|
+
finalStatus = JobStatus.FAILED;
|
|
338
|
+
break;
|
|
339
|
+
} else {
|
|
340
|
+
// COMPLETED with no transition → linear fallback to the next
|
|
341
|
+
// step in declaration order. If we're already on the last
|
|
342
|
+
// step, the job ends.
|
|
343
|
+
const currentIdx = stepOrder.indexOf(currentStepId);
|
|
344
|
+
const nextIdx = currentIdx + 1;
|
|
345
|
+
nextStepId = nextIdx < stepOrder.length ? stepOrder[nextIdx]! : null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
currentStepId = nextStepId;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (finalStatus === JobStatus.COMPLETED) {
|
|
352
|
+
await this.repository.updateJobExecution(execution.id, {
|
|
353
|
+
status: JobStatus.COMPLETED,
|
|
354
|
+
endTime: new Date(),
|
|
355
|
+
exitCode: 'COMPLETED',
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
// Defensive: leave the job FAILED rather than crash the host.
|
|
360
|
+
await this.repository.updateJobExecution(execution.id, {
|
|
361
|
+
status: JobStatus.FAILED,
|
|
362
|
+
endTime: new Date(),
|
|
363
|
+
exitMessage: err instanceof Error ? err.message : String(err),
|
|
364
|
+
});
|
|
365
|
+
finalStatus = JobStatus.FAILED;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// `after:job:*` listeners run once the job is in a terminal state.
|
|
369
|
+
// They receive the final status as the second positional argument
|
|
370
|
+
// (the `args` slot in the current API; the legacy builder path used
|
|
371
|
+
// the same shape). The resolver map is the same one built above;
|
|
372
|
+
// we re-use it to avoid a second IR walk.
|
|
373
|
+
await this.listenerInvoker.invokeAfter(
|
|
374
|
+
jobResolvers,
|
|
375
|
+
'job',
|
|
376
|
+
{ jobExecutionId: execution.id, stepExecutionId: '<job>' },
|
|
377
|
+
[{ status: finalStatus }],
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
await this.emit({
|
|
381
|
+
type:
|
|
382
|
+
finalStatus === JobStatus.COMPLETED ? BATCH_EVENT.JOB_COMPLETED : BATCH_EVENT.JOB_FAILED,
|
|
383
|
+
timestamp: new Date(),
|
|
384
|
+
jobExecutionId: execution.id,
|
|
385
|
+
data: { status: finalStatus },
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return (await this.repository.getJobExecution(execution.id))!;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Build a listener resolver map for the given job. Walks every
|
|
393
|
+
* `ListenerDefinition` in `jobDef.listeners` (job-level + step-level +
|
|
394
|
+
* chunk-level + item-level + skip-level) and resolves each ref into a
|
|
395
|
+
* callable `ListenerEntry` keyed by `${phase}:${kind}:${name}`.
|
|
396
|
+
*
|
|
397
|
+
* The returned map is consumed by `ListenerInvoker.invokeBefore /
|
|
398
|
+
* invokeAfter / invokeOnError / invokeOnSkip*` (Task 20 API). The legacy
|
|
399
|
+
* step-level methods (`invokeBeforeStep` etc.) consume a derived
|
|
400
|
+
* legacy-shaped map produced by `buildLegacyStepResolvers` — that
|
|
401
|
+
* conversion happens at the call site, not here, so this method stays
|
|
402
|
+
* the single source of truth for the new shape.
|
|
403
|
+
*
|
|
404
|
+
* Ref resolution rules:
|
|
405
|
+
* - `RefKind.BuilderLambda` → use `ref.fn` directly (the compiler
|
|
406
|
+
* pre-binds decorator-discovered methods
|
|
407
|
+
* and the builder API ships bare fns).
|
|
408
|
+
* - `RefKind.Method` → requires the Jobable instance. Until
|
|
409
|
+
* a `ModuleRef` is wired (Task 9+), this
|
|
410
|
+
* branch logs a warning and is skipped.
|
|
411
|
+
* - `RefKind.ProviderToken` → resolved in Task 9 against a
|
|
412
|
+
* pre-built provider map. Skipped here
|
|
413
|
+
* with a warning.
|
|
414
|
+
*/
|
|
415
|
+
private buildResolverMap(jobDef: JobDefinition): ResolverMap {
|
|
416
|
+
const resolvers: ResolverMap = new Map();
|
|
417
|
+
let lambdaCounter = 0;
|
|
418
|
+
|
|
419
|
+
for (const def of jobDef.listeners) {
|
|
420
|
+
const fn = this.resolveListenerRef(def);
|
|
421
|
+
if (fn === null) continue;
|
|
422
|
+
|
|
423
|
+
const name = this.resolveListenerName(def.ref, lambdaCounter);
|
|
424
|
+
if (def.ref.kind === RefKind.BuilderLambda) lambdaCounter += 1;
|
|
425
|
+
|
|
426
|
+
const key = `${def.phase}:${def.kind}:${name}`;
|
|
427
|
+
resolvers.set(key, {
|
|
428
|
+
fn,
|
|
429
|
+
...(def.nonCritical !== undefined ? { nonCritical: def.nonCritical } : {}),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return resolvers;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Resolve a single `ListenerDefinition` to its callable function, or
|
|
438
|
+
* `null` if the ref kind is not yet supported. See `buildResolverMap`
|
|
439
|
+
* for the per-kind resolution contract.
|
|
440
|
+
*/
|
|
441
|
+
private resolveListenerRef(def: ListenerDefinition): ((...args: any[]) => any) | null {
|
|
442
|
+
const ref = def.ref;
|
|
443
|
+
switch (ref.kind) {
|
|
444
|
+
case RefKind.BuilderLambda:
|
|
445
|
+
return ref.fn ?? null;
|
|
446
|
+
case RefKind.Method:
|
|
447
|
+
this.logger.warn(
|
|
448
|
+
`JobExecutor: Method-ref listener (classToken=${ref.classToken ?? '<unknown>'}, ` +
|
|
449
|
+
`methodName=${ref.methodName ?? '<unknown>'}) requires a Jobable instance; ` +
|
|
450
|
+
'this resolution path lands in a follow-up task. Listener skipped.',
|
|
451
|
+
);
|
|
452
|
+
return null;
|
|
453
|
+
case RefKind.ProviderToken:
|
|
454
|
+
this.logger.warn(
|
|
455
|
+
`JobExecutor: ProviderToken-ref listener (token=${ref.token ?? '<empty>'}) ` +
|
|
456
|
+
'is resolved in Task 9. Listener skipped.',
|
|
457
|
+
);
|
|
458
|
+
return null;
|
|
459
|
+
default: {
|
|
460
|
+
const _exhaustive: never = ref.kind;
|
|
461
|
+
void _exhaustive;
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Derive the `name` segment of the resolver key. Method refs carry a
|
|
469
|
+
* `classToken` + `methodName` pair that uniquely identifies the bound
|
|
470
|
+
* method; BuilderLambda refs do not carry a name (the compiler drops
|
|
471
|
+
* the method name when it pre-binds), so we mint a `lambda-N` name
|
|
472
|
+
* from a per-job counter to guarantee uniqueness.
|
|
473
|
+
*/
|
|
474
|
+
private resolveListenerName(ref: ListenerRef, lambdaCounter: number): string {
|
|
475
|
+
if (ref.kind === RefKind.Method) {
|
|
476
|
+
return `${ref.classToken ?? '<unknown>'}.${ref.methodName ?? '<unknown>'}`;
|
|
477
|
+
}
|
|
478
|
+
return `lambda-${lambdaCounter}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Derive a legacy `Map<string, ListenerResolver>` from a new
|
|
483
|
+
* `ResolverMap`, containing only the step-level entries with their
|
|
484
|
+
* keys translated from `${phase}:step:${name}` back to the legacy
|
|
485
|
+
* `${phase}-step:${name}` shape. The `nonCritical` flag is dropped
|
|
486
|
+
* (legacy `ListenerResolver` is a bare function with no metadata).
|
|
487
|
+
*
|
|
488
|
+
* This is the bridge the `TaskletStepExecutor` (which still consumes
|
|
489
|
+
* the legacy shape) needs until it migrates to the new API. Kept as
|
|
490
|
+
* a private helper so the conversion logic is in one place.
|
|
491
|
+
*/
|
|
492
|
+
private buildLegacyStepResolvers(resolvers: ResolverMap): Map<string, ListenerResolver> {
|
|
493
|
+
const legacy: Map<string, ListenerResolver> = new Map();
|
|
494
|
+
for (const [key, entry] of resolvers.entries()) {
|
|
495
|
+
if (key.startsWith('before:step:')) {
|
|
496
|
+
legacy.set(`before-step:${key.slice('before:step:'.length)}`, entry.fn as ListenerResolver);
|
|
497
|
+
} else if (key.startsWith('after:step:')) {
|
|
498
|
+
legacy.set(`after-step:${key.slice('after:step:'.length)}`, entry.fn as ListenerResolver);
|
|
499
|
+
} else if (key.startsWith('on-error:step:')) {
|
|
500
|
+
legacy.set(`on-step-error:${key.slice('on-error:step:'.length)}`, entry.fn as ListenerResolver);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return legacy;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Read the `lastChunkIndex` checkpoint from the step-scoped
|
|
508
|
+
* ExecutionContext for `stepExecutionId`. Returns `undefined` when the
|
|
509
|
+
* step has no recorded checkpoint (e.g., the prior run failed on the
|
|
510
|
+
* very first chunk and never got a chance to write one). The chunk
|
|
511
|
+
* executor treats `undefined` as "no resume; start from the beginning".
|
|
512
|
+
*/
|
|
513
|
+
private async getLastCheckpoint(stepExecutionId: string): Promise<number | undefined> {
|
|
514
|
+
const ctx = await this.repository.getExecutionContext({ stepExecutionId });
|
|
515
|
+
if (ctx.data === null || typeof ctx.data !== 'object' || Array.isArray(ctx.data)) {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
const value = (ctx.data as { lastChunkIndex?: unknown }).lastChunkIndex;
|
|
519
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private async resolveDeciderExitStatus(
|
|
523
|
+
jobDef: JobDefinition,
|
|
524
|
+
afterStepId: string,
|
|
525
|
+
context: Parameters<NonNullable<JobDefinition['deciders']>[number]['decide']>[0],
|
|
526
|
+
): Promise<string | undefined> {
|
|
527
|
+
const decider = (jobDef.deciders ?? []).find((d) => d.afterStepId === afterStepId);
|
|
528
|
+
if (decider === undefined) return undefined;
|
|
529
|
+
const status = await decider.decide(context);
|
|
530
|
+
const trimmed = status.trim();
|
|
531
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Dispatch a BatchEvent to the configured observer. Errors thrown by
|
|
536
|
+
* the observer are swallowed: a failing logger/queue must not crash
|
|
537
|
+
* the executor (the job's persisted state is the source of truth).
|
|
538
|
+
*/
|
|
539
|
+
private async emit(event: BatchEvent): Promise<void> {
|
|
540
|
+
try {
|
|
541
|
+
await this.observer.onEvent(event);
|
|
542
|
+
} catch {
|
|
543
|
+
// intentional: observer failures are best-effort and must not
|
|
544
|
+
// affect the executor's own state transitions.
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Re-export common types for convenience so callers that import
|
|
550
|
+
// `JobExecutor` don't need a second import for `StepExecutionResult` etc.
|
|
551
|
+
export type { StepExecutionResult } from './tasklet-step-executor';
|
|
552
|
+
export type { ChunkExecutionResult } from './chunk-step-executor';
|
|
553
|
+
export type { JobParameters, JobExecution };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import type { JobDefinition } from '../core/ir';
|
|
4
|
+
import {
|
|
5
|
+
JobExecutionDetails,
|
|
6
|
+
JobExecutionFilter,
|
|
7
|
+
JobInstance,
|
|
8
|
+
JobInstanceFilter,
|
|
9
|
+
JobExecution,
|
|
10
|
+
} from '../core/repository';
|
|
11
|
+
import { JobRepository } from '../core/repository';
|
|
12
|
+
import {
|
|
13
|
+
JobExecutionNotFoundError,
|
|
14
|
+
JobInstanceNotFoundError,
|
|
15
|
+
} from '../core/errors';
|
|
16
|
+
import { JobRegistry } from '../registry/job-registry';
|
|
17
|
+
|
|
18
|
+
@Injectable()
|
|
19
|
+
export class JobExplorer {
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly registry: JobRegistry,
|
|
22
|
+
private readonly repository: JobRepository,
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
listJobs(): JobDefinition[] {
|
|
26
|
+
return this.registry.getAll();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getJobInstance(jobInstanceId: string): Promise<JobInstance> {
|
|
30
|
+
const instance = await this.repository.getJobInstance(jobInstanceId);
|
|
31
|
+
if (instance === null) {
|
|
32
|
+
throw new JobInstanceNotFoundError(jobInstanceId);
|
|
33
|
+
}
|
|
34
|
+
return instance;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async listJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {
|
|
38
|
+
return this.repository.findJobInstances(filter);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async listJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {
|
|
42
|
+
return this.repository.findJobExecutions(filter);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getJobExecutionDetails(executionId: string): Promise<JobExecutionDetails> {
|
|
46
|
+
const jobExecution = await this.repository.getJobExecution(executionId);
|
|
47
|
+
if (jobExecution === null) {
|
|
48
|
+
throw new JobExecutionNotFoundError(executionId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const jobInstance = await this.repository.getJobInstance(jobExecution.jobInstanceId);
|
|
52
|
+
if (jobInstance === null) {
|
|
53
|
+
throw new JobInstanceNotFoundError(jobExecution.jobInstanceId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const stepExecutions = await this.repository.findStepExecutions(executionId);
|
|
57
|
+
const jobContext = await this.repository.getExecutionContext({ jobExecutionId: executionId });
|
|
58
|
+
const stepContexts = await Promise.all(
|
|
59
|
+
stepExecutions.map(async (step) => ({
|
|
60
|
+
stepExecutionId: step.id,
|
|
61
|
+
context: await this.repository.getExecutionContext({ stepExecutionId: step.id }),
|
|
62
|
+
})),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
jobInstance,
|
|
67
|
+
jobExecution,
|
|
68
|
+
stepExecutions,
|
|
69
|
+
jobContext,
|
|
70
|
+
stepContexts,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import type { JobParameters } from '../core/repository/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonicalize JobParameters into a stable string.
|
|
6
|
+
* - Object keys sorted alphabetically (recursive)
|
|
7
|
+
* - Arrays preserve order (different order = different key)
|
|
8
|
+
* - Date → ISO string
|
|
9
|
+
* - null/undefined → omitted
|
|
10
|
+
* - Number: keep as-is (1 vs 1.0 same via String())
|
|
11
|
+
* - String: as-is (no whitespace trim — callers must normalize)
|
|
12
|
+
*/
|
|
13
|
+
function canonicalize(value: unknown): unknown {
|
|
14
|
+
if (value === null || value === undefined) return undefined;
|
|
15
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return value;
|
|
16
|
+
if (typeof value === 'bigint') throw new Error('BigInt not supported in job key');
|
|
17
|
+
if (value instanceof Date) return value.toISOString();
|
|
18
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
19
|
+
if (typeof value === 'object') {
|
|
20
|
+
const obj = value as Record<string, unknown>;
|
|
21
|
+
const sorted: Record<string, unknown> = {};
|
|
22
|
+
for (const k of Object.keys(obj).sort()) {
|
|
23
|
+
const v = canonicalize(obj[k]);
|
|
24
|
+
if (v !== undefined) sorted[k] = v;
|
|
25
|
+
}
|
|
26
|
+
return sorted;
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function canonicalJobKey(params: JobParameters): string {
|
|
32
|
+
const canonical = canonicalize(params);
|
|
33
|
+
const json = JSON.stringify(canonical);
|
|
34
|
+
return createHash('sha256').update(json).digest('hex');
|
|
35
|
+
}
|