@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,334 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { JobRepository } from '@nest-batch/core';
|
|
3
|
+
import type {
|
|
4
|
+
JobInstance,
|
|
5
|
+
JobExecution,
|
|
6
|
+
JobExecutionPatch,
|
|
7
|
+
JobParameters,
|
|
8
|
+
StepExecution,
|
|
9
|
+
StepExecutionPatch,
|
|
10
|
+
ExecutionContext,
|
|
11
|
+
ExecutionScope,
|
|
12
|
+
JobInstanceFilter,
|
|
13
|
+
JobExecutionFilter,
|
|
14
|
+
} from '@nest-batch/core';
|
|
15
|
+
import { JobStatus, StepStatus } from '@nest-batch/core';
|
|
16
|
+
import { JobExecutionAlreadyRunningError } from '@nest-batch/core';
|
|
17
|
+
import { assertJsonSerializable } from '@nest-batch/core';
|
|
18
|
+
import type { IdGenerator } from '../id-generator';
|
|
19
|
+
import { UuidIdGenerator } from '../id-generator';
|
|
20
|
+
|
|
21
|
+
function deepClone<T>(value: T): T {
|
|
22
|
+
if (value === null || typeof value !== 'object') return value;
|
|
23
|
+
if (value instanceof Date) return new Date(value.getTime()) as unknown as T;
|
|
24
|
+
if (Array.isArray(value)) return value.map((v) => deepClone(v)) as unknown as T;
|
|
25
|
+
const out: Record<string, unknown> = {};
|
|
26
|
+
for (const k of Object.keys(value as Record<string, unknown>)) {
|
|
27
|
+
out[k] = deepClone((value as Record<string, unknown>)[k]);
|
|
28
|
+
}
|
|
29
|
+
return out as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface InMemoryState {
|
|
33
|
+
instances: Map<string, JobInstance>; // key: instanceId
|
|
34
|
+
instancesByKey: Map<string, JobInstance>; // key: `${name}::${jobKey}`
|
|
35
|
+
executions: Map<string, JobExecution>;
|
|
36
|
+
stepExecutions: Map<string, StepExecution>;
|
|
37
|
+
contexts: Map<string, ExecutionContext>; // key: serialized ExecutionScope
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function scopeKey(scope: ExecutionScope): string {
|
|
41
|
+
if ('jobExecutionId' in scope) return `job::${scope.jobExecutionId}`;
|
|
42
|
+
return `step::${scope.stepExecutionId}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* In-memory JobRepository with real-repo semantics:
|
|
47
|
+
* - deterministic IDs (configurable via IdGenerator)
|
|
48
|
+
* - deep clone on reads/writes to prevent mutation leaks
|
|
49
|
+
* - async signatures
|
|
50
|
+
* - uniqueness on (jobName, jobKey)
|
|
51
|
+
* - getOrCreateJobInstance / createJobExecution / getRunningJobExecution
|
|
52
|
+
* share a single promise-chain lock, so the check-then-create sequence
|
|
53
|
+
* used by JobLauncher is race-safe
|
|
54
|
+
*
|
|
55
|
+
* restartable: false by default (per Metis directive: in-memory repo is non-restartable
|
|
56
|
+
* because contexts are lost on process restart).
|
|
57
|
+
*/
|
|
58
|
+
@Injectable()
|
|
59
|
+
export class InMemoryJobRepository extends JobRepository {
|
|
60
|
+
private readonly state: InMemoryState = {
|
|
61
|
+
instances: new Map(),
|
|
62
|
+
instancesByKey: new Map(),
|
|
63
|
+
executions: new Map(),
|
|
64
|
+
stepExecutions: new Map(),
|
|
65
|
+
contexts: new Map(),
|
|
66
|
+
};
|
|
67
|
+
/** Promise-chain lock to serialize getOrCreateJobInstance calls. */
|
|
68
|
+
private lock: Promise<unknown> = Promise.resolve();
|
|
69
|
+
|
|
70
|
+
constructor(private readonly idGen: IdGenerator = new UuidIdGenerator()) {
|
|
71
|
+
super();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async withLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
75
|
+
const prev = this.lock;
|
|
76
|
+
let release!: () => void;
|
|
77
|
+
this.lock = new Promise<void>((resolve) => {
|
|
78
|
+
release = resolve;
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
await prev;
|
|
82
|
+
return await fn();
|
|
83
|
+
} finally {
|
|
84
|
+
release();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getOrCreateJobInstance(name: string, jobKey: string): Promise<JobInstance> {
|
|
89
|
+
return this.withLock(async () => {
|
|
90
|
+
const key = `${name}::${jobKey}`;
|
|
91
|
+
const existing = this.state.instancesByKey.get(key);
|
|
92
|
+
if (existing) return deepClone(existing);
|
|
93
|
+
const inst: JobInstance = {
|
|
94
|
+
id: this.idGen.next(),
|
|
95
|
+
jobName: name,
|
|
96
|
+
jobKey,
|
|
97
|
+
createdAt: new Date(),
|
|
98
|
+
};
|
|
99
|
+
this.state.instances.set(inst.id, inst);
|
|
100
|
+
this.state.instancesByKey.set(key, inst);
|
|
101
|
+
return deepClone(inst);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async createJobExecution(jobInstanceId: string, params: JobParameters): Promise<JobExecution> {
|
|
106
|
+
return this.withLock(async () => {
|
|
107
|
+
const exec: JobExecution = {
|
|
108
|
+
id: this.idGen.next(),
|
|
109
|
+
jobInstanceId,
|
|
110
|
+
status: JobStatus.STARTING,
|
|
111
|
+
startTime: null,
|
|
112
|
+
endTime: null,
|
|
113
|
+
exitCode: '',
|
|
114
|
+
exitMessage: '',
|
|
115
|
+
params: deepClone(params),
|
|
116
|
+
};
|
|
117
|
+
this.state.executions.set(exec.id, exec);
|
|
118
|
+
return deepClone(exec);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async createExecutionAtomic(
|
|
123
|
+
name: string,
|
|
124
|
+
jobKey: string,
|
|
125
|
+
params: JobParameters,
|
|
126
|
+
): Promise<JobExecution> {
|
|
127
|
+
return this.withLock(async () => {
|
|
128
|
+
const key = `${name}::${jobKey}`;
|
|
129
|
+
let instance = this.state.instancesByKey.get(key);
|
|
130
|
+
if (!instance) {
|
|
131
|
+
instance = {
|
|
132
|
+
id: this.idGen.next(),
|
|
133
|
+
jobName: name,
|
|
134
|
+
jobKey,
|
|
135
|
+
createdAt: new Date(),
|
|
136
|
+
};
|
|
137
|
+
this.state.instances.set(instance.id, instance);
|
|
138
|
+
this.state.instancesByKey.set(key, instance);
|
|
139
|
+
}
|
|
140
|
+
for (const exec of this.state.executions.values()) {
|
|
141
|
+
if (
|
|
142
|
+
exec.jobInstanceId === instance.id &&
|
|
143
|
+
(exec.status === JobStatus.STARTING || exec.status === JobStatus.STARTED)
|
|
144
|
+
) {
|
|
145
|
+
throw new JobExecutionAlreadyRunningError(exec.id);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const exec: JobExecution = {
|
|
149
|
+
id: this.idGen.next(),
|
|
150
|
+
jobInstanceId: instance.id,
|
|
151
|
+
status: JobStatus.STARTING,
|
|
152
|
+
startTime: null,
|
|
153
|
+
endTime: null,
|
|
154
|
+
exitCode: '',
|
|
155
|
+
exitMessage: '',
|
|
156
|
+
params: deepClone(params),
|
|
157
|
+
};
|
|
158
|
+
this.state.executions.set(exec.id, exec);
|
|
159
|
+
return deepClone(exec);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async getRunningJobExecution(jobInstanceId: string): Promise<JobExecution | null> {
|
|
164
|
+
return this.withLock(async () => {
|
|
165
|
+
for (const exec of this.state.executions.values()) {
|
|
166
|
+
if (
|
|
167
|
+
exec.jobInstanceId === jobInstanceId &&
|
|
168
|
+
(exec.status === JobStatus.STARTING || exec.status === JobStatus.STARTED)
|
|
169
|
+
) {
|
|
170
|
+
return deepClone(exec);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async updateJobExecution(executionId: string, patch: JobExecutionPatch): Promise<void> {
|
|
178
|
+
const cur = this.state.executions.get(executionId);
|
|
179
|
+
if (!cur) throw new Error(`JobExecution not found: ${executionId}`);
|
|
180
|
+
const next: JobExecution = {
|
|
181
|
+
...cur,
|
|
182
|
+
...patch,
|
|
183
|
+
startTime: patch.startTime === undefined ? cur.startTime : patch.startTime,
|
|
184
|
+
endTime: patch.endTime === undefined ? cur.endTime : patch.endTime,
|
|
185
|
+
};
|
|
186
|
+
this.state.executions.set(executionId, next);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async getJobExecution(executionId: string): Promise<JobExecution | null> {
|
|
190
|
+
const e = this.state.executions.get(executionId);
|
|
191
|
+
return e ? deepClone(e) : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
override async getJobInstance(jobInstanceId: string): Promise<JobInstance | null> {
|
|
195
|
+
const instance = this.state.instances.get(jobInstanceId);
|
|
196
|
+
return instance ? deepClone(instance) : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
override async findJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {
|
|
200
|
+
const rows: JobInstance[] = [];
|
|
201
|
+
for (const instance of this.state.instances.values()) {
|
|
202
|
+
if (filter.jobName !== undefined && instance.jobName !== filter.jobName) continue;
|
|
203
|
+
if (filter.jobKey !== undefined && instance.jobKey !== filter.jobKey) continue;
|
|
204
|
+
rows.push(deepClone(instance));
|
|
205
|
+
}
|
|
206
|
+
return rows;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
override async findJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {
|
|
210
|
+
let statuses: Set<JobStatus> | undefined;
|
|
211
|
+
if (filter.status !== undefined) {
|
|
212
|
+
const statusFilter = filter.status;
|
|
213
|
+
const statusList: readonly JobStatus[] = Array.isArray(statusFilter)
|
|
214
|
+
? statusFilter
|
|
215
|
+
: [statusFilter];
|
|
216
|
+
statuses = new Set<JobStatus>(statusList);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const rows: JobExecution[] = [];
|
|
220
|
+
for (const execution of this.state.executions.values()) {
|
|
221
|
+
if (
|
|
222
|
+
filter.jobInstanceId !== undefined &&
|
|
223
|
+
execution.jobInstanceId !== filter.jobInstanceId
|
|
224
|
+
) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (statuses !== undefined && !statuses.has(execution.status)) continue;
|
|
228
|
+
if (
|
|
229
|
+
filter.startedAfter !== undefined &&
|
|
230
|
+
(execution.startTime === null || execution.startTime < filter.startedAfter)
|
|
231
|
+
) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (
|
|
235
|
+
filter.startedBefore !== undefined &&
|
|
236
|
+
(execution.startTime === null || execution.startTime > filter.startedBefore)
|
|
237
|
+
) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
rows.push(deepClone(execution));
|
|
241
|
+
}
|
|
242
|
+
return rows;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async createStepExecution(jobExecutionId: string, stepName: string): Promise<StepExecution> {
|
|
246
|
+
const step: StepExecution = {
|
|
247
|
+
id: this.idGen.next(),
|
|
248
|
+
jobExecutionId,
|
|
249
|
+
stepName,
|
|
250
|
+
status: StepStatus.STARTING,
|
|
251
|
+
readCount: 0,
|
|
252
|
+
writeCount: 0,
|
|
253
|
+
skipCount: 0,
|
|
254
|
+
rollbackCount: 0,
|
|
255
|
+
commitCount: 0,
|
|
256
|
+
startTime: null,
|
|
257
|
+
endTime: null,
|
|
258
|
+
exitCode: '',
|
|
259
|
+
exitMessage: '',
|
|
260
|
+
};
|
|
261
|
+
this.state.stepExecutions.set(step.id, step);
|
|
262
|
+
return deepClone(step);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async updateStepExecution(stepExecutionId: string, patch: StepExecutionPatch): Promise<void> {
|
|
266
|
+
const cur = this.state.stepExecutions.get(stepExecutionId);
|
|
267
|
+
if (!cur) throw new Error(`StepExecution not found: ${stepExecutionId}`);
|
|
268
|
+
const next: StepExecution = { ...cur, ...patch };
|
|
269
|
+
this.state.stepExecutions.set(stepExecutionId, next);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async getStepExecution(stepExecutionId: string): Promise<StepExecution | null> {
|
|
273
|
+
const s = this.state.stepExecutions.get(stepExecutionId);
|
|
274
|
+
return s ? deepClone(s) : null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
override async findStepExecutions(jobExecutionId: string): Promise<StepExecution[]> {
|
|
278
|
+
const rows: StepExecution[] = [];
|
|
279
|
+
for (const step of this.state.stepExecutions.values()) {
|
|
280
|
+
if (step.jobExecutionId === jobExecutionId) {
|
|
281
|
+
rows.push(deepClone(step));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return rows;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async getExecutionContext(scope: ExecutionScope): Promise<ExecutionContext> {
|
|
288
|
+
const ctx = this.state.contexts.get(scopeKey(scope));
|
|
289
|
+
if (ctx) return { data: deepClone(ctx.data), version: ctx.version };
|
|
290
|
+
return { data: null, version: 0 };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async saveExecutionContext(
|
|
294
|
+
scope: ExecutionScope,
|
|
295
|
+
ctx: ExecutionContext,
|
|
296
|
+
version?: number,
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
// Validate JSON-serializability (per ExecutionContext contract)
|
|
299
|
+
assertJsonSerializable(ctx.data);
|
|
300
|
+
const current = this.state.contexts.get(scopeKey(scope));
|
|
301
|
+
const nextVersion = version !== undefined ? version : (current?.version ?? 0) + 1;
|
|
302
|
+
this.state.contexts.set(scopeKey(scope), {
|
|
303
|
+
data: deepClone(ctx.data),
|
|
304
|
+
version: nextVersion,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Returns the most recently created StepExecution for the given
|
|
310
|
+
* (jobExecutionId, stepName) pair, or `null` if none exists. Insertion
|
|
311
|
+
* order over `Map` is stable (ES2015+), so a reverse scan picks the
|
|
312
|
+
* latest entry that matches the filter. The restart path filters the
|
|
313
|
+
* result further by status (FAILED) at the call site.
|
|
314
|
+
*/
|
|
315
|
+
async findLatestStepExecution(
|
|
316
|
+
jobExecutionId: string,
|
|
317
|
+
stepName: string,
|
|
318
|
+
): Promise<StepExecution | null> {
|
|
319
|
+
let latest: StepExecution | null = null;
|
|
320
|
+
for (const step of this.state.stepExecutions.values()) {
|
|
321
|
+
if (step.jobExecutionId === jobExecutionId && step.stepName === stepName) {
|
|
322
|
+
if (!latest) {
|
|
323
|
+
latest = step;
|
|
324
|
+
}
|
|
325
|
+
// No monotonic timestamp on StepExecution itself; rely on Map
|
|
326
|
+
// insertion order: later-created entries override earlier ones.
|
|
327
|
+
latest = step;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return latest ? deepClone(latest) : null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const RESTARTABLE_DEFAULT_INMEMORY = false;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './in-memory-job-repository';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UUID v7 generator — time-sortable IDs (first 48 bits are unix time ms).
|
|
5
|
+
*
|
|
6
|
+
* Suitable for execution IDs (JobExecution, StepExecution) where
|
|
7
|
+
* chronological ordering is useful — e.g., grouping recent runs in a
|
|
8
|
+
* log dashboard or in a B-tree keyed persistence layer.
|
|
9
|
+
*
|
|
10
|
+
* Layout per RFC 9562 §5.7:
|
|
11
|
+
* xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
|
|
12
|
+
* ^ts hi ^ts lo ^rand ^rand ^rand
|
|
13
|
+
* where:
|
|
14
|
+
* - positions 0-11 = 48-bit unix-ms timestamp (sortable)
|
|
15
|
+
* - position 14 = version nibble '7'
|
|
16
|
+
* - position 19 = variant nibble ∈ {8, 9, a, b} (top 2 bits = 10)
|
|
17
|
+
* - remaining 62 = random bits from `crypto.randomBytes(10)`
|
|
18
|
+
*/
|
|
19
|
+
export class UuidV7IdGenerator {
|
|
20
|
+
next(): string {
|
|
21
|
+
// 48-bit timestamp in hex (12 chars) — Date.now() is bounded to ~2^41
|
|
22
|
+
// today, so padStart keeps it stable-width for predictable sorting.
|
|
23
|
+
const ts = Date.now().toString(16).padStart(12, '0');
|
|
24
|
+
|
|
25
|
+
// 80 random bits (10 bytes). We use byte 0's top nibble as the
|
|
26
|
+
// variant and place it explicitly in group 4; the remaining 9
|
|
27
|
+
// bytes (18 hex chars) fill the rest of the random slots.
|
|
28
|
+
const rand = randomBytes(10);
|
|
29
|
+
|
|
30
|
+
// RFC 9562 §4.1 variant: top 2 bits of the variant nibble must be `10`.
|
|
31
|
+
// `& 0x3f` clears the top 2 bits, then `| 0x80` sets them to `10`,
|
|
32
|
+
// yielding a value in [0x80, 0xbf] whose hex first digit ∈ {8,9,a,b}.
|
|
33
|
+
const variantNibble = ((((rand[0] ?? 0) & 0x3f) | 0x80).toString(16) as string)[0];
|
|
34
|
+
|
|
35
|
+
// 18 hex chars of pure random from the remaining 9 bytes.
|
|
36
|
+
const rest = rand.subarray(1).toString('hex');
|
|
37
|
+
|
|
38
|
+
return `${ts.slice(0, 8)}-${ts.slice(8, 12)}-7${rest.slice(0, 3)}-${variantNibble}${rest.slice(3, 6)}-${rest.slice(6, 18)}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { SetMetadata } from '@nestjs/common';
|
|
3
|
+
|
|
4
|
+
import { BATCH_SCHEDULED_OPTIONS as SCHEDULED_KEY } from '../decorators/constants';
|
|
5
|
+
|
|
6
|
+
export const BATCH_SCHEDULED_OPTIONS = SCHEDULED_KEY;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Overlap policies for cron-scheduled jobs:
|
|
10
|
+
*
|
|
11
|
+
* - `'skip'` — drop the new tick if the previous run is still in flight.
|
|
12
|
+
* - `'queue'` — buffer the new tick and start it after the current one ends.
|
|
13
|
+
* - `'parallel'` — start the new tick alongside the current one.
|
|
14
|
+
*
|
|
15
|
+
* The runtime scheduler (the future `@nest-batch/bullmq` cron strategy)
|
|
16
|
+
* reads this value off the stored metadata and applies the policy at
|
|
17
|
+
* dispatch time. The decorator itself MUST NOT silently default the
|
|
18
|
+
* policy to `'skip'` on the user's behalf — leaving it `undefined` here
|
|
19
|
+
* is the contract: the runtime applies the default.
|
|
20
|
+
*/
|
|
21
|
+
export type BatchOverlapPolicy = 'skip' | 'queue' | 'parallel';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Decorator-facing options for `@BatchScheduled`.
|
|
25
|
+
*
|
|
26
|
+
* - `name` — required, unique per job (used as the scheduling key).
|
|
27
|
+
* - `timezone` — required, IANA zone (e.g. `'UTC'`, `'Asia/Seoul'`).
|
|
28
|
+
* - `overlap` — optional, see `BatchOverlapPolicy`. Default applied at
|
|
29
|
+
* runtime, never silently here.
|
|
30
|
+
* - `startAt` — optional, absolute lower bound. Preserved by reference.
|
|
31
|
+
* - `endAt` — optional, absolute upper bound. Preserved by reference.
|
|
32
|
+
* - `inert` — optional, hints the runtime scheduler to skip actual
|
|
33
|
+
* registration. The decorator stamps this by reading
|
|
34
|
+
* `process.env.BATCH_SCHEDULED_DISABLE` at decoration time
|
|
35
|
+
* so the runtime never has to re-evaluate the env.
|
|
36
|
+
*/
|
|
37
|
+
export interface BatchScheduledOptions {
|
|
38
|
+
name: string;
|
|
39
|
+
timezone: string;
|
|
40
|
+
overlap?: BatchOverlapPolicy;
|
|
41
|
+
startAt?: Date;
|
|
42
|
+
endAt?: Date;
|
|
43
|
+
inert?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The shape stored under the `BATCH_SCHEDULED_OPTIONS` metadata key on
|
|
48
|
+
* the decorated method function. The runtime scheduler (the future
|
|
49
|
+
* `@nest-batch/bullmq` cron strategy) reads this verbatim to register
|
|
50
|
+
* the job with the underlying scheduler.
|
|
51
|
+
*
|
|
52
|
+
* Note: `inert` lives at the top level (not inside `options`) on
|
|
53
|
+
* purpose. It is a *runtime* flag captured at decoration time from
|
|
54
|
+
* `process.env.BATCH_SCHEDULED_DISABLE`; it is not a user-facing
|
|
55
|
+
* knob in `BatchScheduledOptions`. (The `inert?: boolean` slot on
|
|
56
|
+
* `BatchScheduledOptions` is the user-facing counterpart — see the
|
|
57
|
+
* GREEN half of Task 13 to wire it up.)
|
|
58
|
+
*/
|
|
59
|
+
export interface BatchScheduledMetadata {
|
|
60
|
+
cron: string;
|
|
61
|
+
options: BatchScheduledOptions;
|
|
62
|
+
inert: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `@BatchScheduled` — cron decorator.
|
|
67
|
+
*
|
|
68
|
+
* Stamps `BATCH_SCHEDULED_OPTIONS` metadata onto the decorated method's
|
|
69
|
+
* function reference (via `@nestjs/common`'s `SetMetadata`, which writes
|
|
70
|
+
* to `descriptor.value`), so `Reflect.getMetadata(KEY, Job.prototype.run)`
|
|
71
|
+
* returns the stored shape.
|
|
72
|
+
*
|
|
73
|
+
* This is the TDD-RED half of Task 13. It deliberately implements only
|
|
74
|
+
* the metadata-storing contract — i.e. the 10 happy-path assertions in
|
|
75
|
+
* `tests/scheduling/batch-scheduled.test.ts` (sections A + B). The
|
|
76
|
+
* 7 negative-path assertions in sections C + D are the GREEN half
|
|
77
|
+
* of the contract and land in the next task:
|
|
78
|
+
*
|
|
79
|
+
* 1. Cron expression shape validation (5/6 fields, no literal words).
|
|
80
|
+
* 2. IANA timezone validation (rejects unknown / empty / whitespace).
|
|
81
|
+
*
|
|
82
|
+
* The inert-mode flag (section E) is wired up here because it is also
|
|
83
|
+
* a pure metadata capture — `process.env.BATCH_SCHEDULED_DISABLE` is
|
|
84
|
+
* read at decoration time and stamped onto the stored shape. The
|
|
85
|
+
* decorator does NOT install any timer, interval, or scheduler
|
|
86
|
+
* registration at decoration time; `inert` is a hint the future
|
|
87
|
+
* runtime scheduler honors when it later walks the class.
|
|
88
|
+
*
|
|
89
|
+
* The decorator is metadata-only by design — it does NOT depend on
|
|
90
|
+
* `cron` (the boundary test from Task 2 still passes — no `cron`
|
|
91
|
+
* import appears in core).
|
|
92
|
+
*/
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Validation helpers
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Minimum + maximum number of whitespace-separated fields a valid
|
|
99
|
+
* cron expression can have. `cronstrue` / Linux
|
|
100
|
+
* `crontab(5)` all agree on 5 (minute, hour, day-of-month, month,
|
|
101
|
+
* day-of-week); Quartz-style extensions add a leading seconds field
|
|
102
|
+
* for 6.
|
|
103
|
+
*/
|
|
104
|
+
const CRON_MIN_FIELDS = 5;
|
|
105
|
+
const CRON_MAX_FIELDS = 6;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Cron-shape check.
|
|
109
|
+
*
|
|
110
|
+
* Accepts:
|
|
111
|
+
* - 5 fields: `minute hour dom month dow` (Linux crontab style)
|
|
112
|
+
* - 6 fields: `second minute hour dom month dow` (extended cron style)
|
|
113
|
+
*
|
|
114
|
+
* Each field is `\S+` (one or more non-whitespace tokens, no empty
|
|
115
|
+
* fields). The trailing `\S+$` is the final field; the leading
|
|
116
|
+
* `(\S+\s+){4,5}` captures the preceding 4 or 5 fields separated by
|
|
117
|
+
* a single whitespace run.
|
|
118
|
+
*
|
|
119
|
+
* This is a shape check, not a semantic one — `99 99 99 99 99` still
|
|
120
|
+
* passes the regex. The runtime scheduler (in `@nest-batch/bullmq`)
|
|
121
|
+
* is the layer that handles semantic validation via `cron-parser`.
|
|
122
|
+
* The shape check exists so the decorator fails fast on
|
|
123
|
+
* unambiguously-wrong input (empty string, too few / too many
|
|
124
|
+
* fields, literal English words).
|
|
125
|
+
*/
|
|
126
|
+
const CRON_SHAPE = /^(\S+\s+){4,5}\S+$/;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* IANA timezone validation.
|
|
130
|
+
*
|
|
131
|
+
* The platform's `Intl.DateTimeFormat` is the canonical, no-dep
|
|
132
|
+
* source of truth for valid IANA zone identifiers: it throws a
|
|
133
|
+
* `RangeError` for unknown zones. We use it inside a `try/catch` and
|
|
134
|
+
* normalise the result to a boolean so the decorator's failure mode
|
|
135
|
+
* is a deterministic `Error` (not a `RangeError` leaking out).
|
|
136
|
+
*
|
|
137
|
+
* Reference: https://tc39.es/ecma402/#sec-intl-datetimeformat-constructor
|
|
138
|
+
*/
|
|
139
|
+
function isValidIanaTimezone(tz: string): boolean {
|
|
140
|
+
try {
|
|
141
|
+
new Intl.DateTimeFormat(undefined, { timeZone: tz });
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate a cron expression. Throws a deterministic
|
|
150
|
+
* `InvalidBatchScheduledCronError` whose `.message` embeds the
|
|
151
|
+
* invalid value verbatim so log lines and test assertions can pin
|
|
152
|
+
* the error to a specific input.
|
|
153
|
+
*/
|
|
154
|
+
function assertValidCron(cronExpression: string): void {
|
|
155
|
+
// Type guard: a non-string value would have already broken
|
|
156
|
+
// SetMetadata, but checking here keeps the helper standalone.
|
|
157
|
+
if (typeof cronExpression !== 'string' || cronExpression.length === 0) {
|
|
158
|
+
throw new InvalidBatchScheduledCronError(cronExpression, 'empty');
|
|
159
|
+
}
|
|
160
|
+
if (!CRON_SHAPE.test(cronExpression.trim())) {
|
|
161
|
+
const fieldCount = cronExpression.trim().split(/\s+/).length;
|
|
162
|
+
const reason =
|
|
163
|
+
fieldCount < CRON_MIN_FIELDS
|
|
164
|
+
? `fewer than ${CRON_MIN_FIELDS} fields`
|
|
165
|
+
: fieldCount > CRON_MAX_FIELDS
|
|
166
|
+
? `more than ${CRON_MAX_FIELDS} fields`
|
|
167
|
+
: 'not a valid cron expression';
|
|
168
|
+
throw new InvalidBatchScheduledCronError(cronExpression, reason);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate a timezone string. Throws a deterministic
|
|
174
|
+
* `InvalidBatchScheduledTimezoneError` whose `.message` embeds the
|
|
175
|
+
* invalid value verbatim.
|
|
176
|
+
*/
|
|
177
|
+
function assertValidTimezone(tz: string): void {
|
|
178
|
+
if (typeof tz !== 'string' || tz.trim().length === 0) {
|
|
179
|
+
throw new InvalidBatchScheduledTimezoneError(tz, 'empty');
|
|
180
|
+
}
|
|
181
|
+
if (!isValidIanaTimezone(tz)) {
|
|
182
|
+
throw new InvalidBatchScheduledTimezoneError(tz, 'not a valid IANA timezone');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Thrown by `@BatchScheduled` when the cron expression fails the
|
|
188
|
+
* shape check (5/6 fields, non-whitespace tokens). The `.message`
|
|
189
|
+
* includes the invalid value and the failure reason so the error
|
|
190
|
+
* is greppable in logs and pinned in test assertions.
|
|
191
|
+
*
|
|
192
|
+
* Exported from the module barrel so adapter packages (e.g. the
|
|
193
|
+
* BullMQ runtime) can `instanceof`-check it without reaching into
|
|
194
|
+
* the decorator's internal helper.
|
|
195
|
+
*/
|
|
196
|
+
export class InvalidBatchScheduledCronError extends Error {
|
|
197
|
+
readonly cron: string;
|
|
198
|
+
readonly reason: string;
|
|
199
|
+
|
|
200
|
+
constructor(cron: string, reason: string) {
|
|
201
|
+
super(
|
|
202
|
+
`[BatchScheduled] invalid cron expression "${cron}": ${reason}. ` +
|
|
203
|
+
`Expected ${CRON_MIN_FIELDS} (Linux crontab) or ${CRON_MAX_FIELDS} (extended cron) ` +
|
|
204
|
+
`whitespace-separated fields.`,
|
|
205
|
+
);
|
|
206
|
+
this.name = 'InvalidBatchScheduledCronError';
|
|
207
|
+
this.cron = cron;
|
|
208
|
+
this.reason = reason;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Thrown by `@BatchScheduled` when the IANA timezone fails the
|
|
214
|
+
* platform check. The `.message` embeds the invalid value and the
|
|
215
|
+
* failure reason.
|
|
216
|
+
*
|
|
217
|
+
* Exported from the module barrel so adapter packages can
|
|
218
|
+
* `instanceof`-check it without reaching into the decorator's
|
|
219
|
+
* internal helper.
|
|
220
|
+
*/
|
|
221
|
+
export class InvalidBatchScheduledTimezoneError extends Error {
|
|
222
|
+
readonly timezone: string;
|
|
223
|
+
readonly reason: string;
|
|
224
|
+
|
|
225
|
+
constructor(timezone: string, reason: string) {
|
|
226
|
+
super(
|
|
227
|
+
`[BatchScheduled] invalid timezone "${timezone}": ${reason}. ` +
|
|
228
|
+
`Expected a valid IANA zone identifier (e.g. "UTC", "Asia/Seoul", "America/New_York").`,
|
|
229
|
+
);
|
|
230
|
+
this.name = 'InvalidBatchScheduledTimezoneError';
|
|
231
|
+
this.timezone = timezone;
|
|
232
|
+
this.reason = reason;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Decorator
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
export function BatchScheduled(
|
|
241
|
+
cronExpression: string,
|
|
242
|
+
options: BatchScheduledOptions,
|
|
243
|
+
): MethodDecorator {
|
|
244
|
+
// 1. Validate inputs at decoration time. The decorator is
|
|
245
|
+
// synchronous and metadata-only; the validation runs BEFORE
|
|
246
|
+
// `SetMetadata(...)` is called so a bad input never reaches
|
|
247
|
+
// the registry.
|
|
248
|
+
assertValidCron(cronExpression);
|
|
249
|
+
assertValidTimezone(options.timezone);
|
|
250
|
+
|
|
251
|
+
const meta: BatchScheduledMetadata = {
|
|
252
|
+
cron: cronExpression,
|
|
253
|
+
options,
|
|
254
|
+
inert: process.env.BATCH_SCHEDULED_DISABLE === '1',
|
|
255
|
+
};
|
|
256
|
+
return SetMetadata(BATCH_SCHEDULED_OPTIONS, meta);
|
|
257
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { TransactionContext, TransactionManager } from '../core/transaction/transaction-manager';
|
|
4
|
+
|
|
5
|
+
export interface InMemoryTransactionContext extends TransactionContext {
|
|
6
|
+
readonly isActive: true;
|
|
7
|
+
readonly id: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* No-op TransactionManager for the in-memory adapter and for tests.
|
|
12
|
+
* Calls `fn(ctx)` directly without any real transactional semantics.
|
|
13
|
+
*
|
|
14
|
+
* For real DB transactions, use a MikroORM/SQL adapter implementation
|
|
15
|
+
* (e.g., `MikroORMTransactionManager`).
|
|
16
|
+
*/
|
|
17
|
+
@Injectable()
|
|
18
|
+
export class InMemoryTransactionManager extends TransactionManager {
|
|
19
|
+
async withTransaction<T>(fn: (ctx: InMemoryTransactionContext) => Promise<T>): Promise<T> {
|
|
20
|
+
const ctx: InMemoryTransactionContext = { isActive: true, id: randomUUID() };
|
|
21
|
+
return fn(ctx);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './in-memory-transaction-manager';
|