reqon-dsl 0.2.0 → 0.4.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/CHANGELOG.md +7 -0
- package/README.md +45 -3
- package/dist/ast/nodes.d.ts +91 -4
- package/dist/ast/nodes.js +14 -0
- package/dist/auth/circuit-breaker.d.ts +11 -0
- package/dist/auth/circuit-breaker.js +90 -18
- package/dist/auth/credentials.d.ts +6 -1
- package/dist/auth/credentials.js +12 -4
- package/dist/auth/oauth2-provider.js +13 -3
- package/dist/auth/rate-limiter.d.ts +12 -1
- package/dist/auth/rate-limiter.js +39 -26
- package/dist/auth/token-store.js +8 -1
- package/dist/cli.d.ts +24 -1
- package/dist/cli.js +149 -10
- package/dist/config/constants.d.ts +152 -0
- package/dist/config/constants.js +139 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +4 -0
- package/dist/control/index.d.ts +2 -0
- package/dist/control/index.js +1 -0
- package/dist/control/server.d.ts +105 -0
- package/dist/control/server.js +315 -0
- package/dist/control/types.d.ts +61 -0
- package/dist/control/types.js +7 -0
- package/dist/debug/cli-debugger.d.ts +17 -0
- package/dist/debug/cli-debugger.js +185 -0
- package/dist/debug/controller.d.ts +94 -0
- package/dist/debug/controller.js +45 -0
- package/dist/debug/index.d.ts +6 -0
- package/dist/debug/index.js +5 -0
- package/dist/errors/index.d.ts +67 -0
- package/dist/errors/index.js +89 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/state.d.ts +24 -0
- package/dist/execution/store.js +2 -2
- package/dist/execution-log/events.d.ts +125 -0
- package/dist/execution-log/events.js +17 -0
- package/dist/execution-log/fold.d.ts +38 -0
- package/dist/execution-log/fold.js +54 -0
- package/dist/execution-log/index.d.ts +18 -0
- package/dist/execution-log/index.js +6 -0
- package/dist/execution-log/postgres-store.d.ts +36 -0
- package/dist/execution-log/postgres-store.js +108 -0
- package/dist/execution-log/resume.d.ts +11 -0
- package/dist/execution-log/resume.js +5 -0
- package/dist/execution-log/sqlite-store.d.ts +16 -0
- package/dist/execution-log/sqlite-store.js +101 -0
- package/dist/execution-log/store.d.ts +72 -0
- package/dist/execution-log/store.js +182 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +35 -3
- package/dist/interpreter/context.d.ts +29 -0
- package/dist/interpreter/context.js +18 -0
- package/dist/interpreter/evaluator.d.ts +63 -1
- package/dist/interpreter/evaluator.js +219 -42
- package/dist/interpreter/executor.d.ts +132 -14
- package/dist/interpreter/executor.js +883 -178
- package/dist/interpreter/fetch-handler.d.ts +48 -1
- package/dist/interpreter/fetch-handler.js +216 -38
- package/dist/interpreter/http.d.ts +34 -0
- package/dist/interpreter/http.js +203 -28
- package/dist/interpreter/index.d.ts +5 -3
- package/dist/interpreter/index.js +4 -2
- package/dist/interpreter/pagination.d.ts +12 -3
- package/dist/interpreter/pagination.js +102 -32
- package/dist/interpreter/signals.d.ts +8 -0
- package/dist/interpreter/signals.js +12 -0
- package/dist/interpreter/source-manager.d.ts +75 -0
- package/dist/interpreter/source-manager.js +157 -0
- package/dist/interpreter/step-handlers/apply-handler.d.ts +29 -0
- package/dist/interpreter/step-handlers/apply-handler.js +79 -0
- package/dist/interpreter/step-handlers/for-handler.d.ts +16 -0
- package/dist/interpreter/step-handlers/for-handler.js +89 -7
- package/dist/interpreter/step-handlers/index.d.ts +4 -2
- package/dist/interpreter/step-handlers/index.js +4 -2
- package/dist/interpreter/step-handlers/match-handler.d.ts +9 -0
- package/dist/interpreter/step-handlers/match-handler.js +47 -17
- package/dist/interpreter/step-handlers/pause-handler.d.ts +52 -0
- package/dist/interpreter/step-handlers/pause-handler.js +87 -0
- package/dist/interpreter/step-handlers/store-handler.d.ts +17 -1
- package/dist/interpreter/step-handlers/store-handler.js +61 -20
- package/dist/interpreter/step-handlers/types.d.ts +3 -0
- package/dist/interpreter/step-handlers/validate-handler.d.ts +2 -1
- package/dist/interpreter/step-handlers/validate-handler.js +7 -2
- package/dist/interpreter/step-handlers/webhook-handler.d.ts +4 -0
- package/dist/interpreter/step-handlers/webhook-handler.js +31 -5
- package/dist/interpreter/store-manager.d.ts +46 -0
- package/dist/interpreter/store-manager.js +70 -0
- package/dist/lexer/index.d.ts +11 -4
- package/dist/lexer/index.js +11 -4
- package/dist/lexer/tokens.d.ts +17 -1
- package/dist/lexer/tokens.js +36 -0
- package/dist/loader/index.js +5 -8
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/sandbox.d.ts +41 -0
- package/dist/mcp/sandbox.js +76 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.js +504 -0
- package/dist/oas/index.d.ts +2 -0
- package/dist/oas/index.js +1 -0
- package/dist/oas/loader.d.ts +13 -1
- package/dist/oas/loader.js +25 -3
- package/dist/oas/mock-generator.d.ts +12 -0
- package/dist/oas/mock-generator.js +196 -0
- package/dist/oas/validator.js +45 -5
- package/dist/observability/events.d.ts +248 -0
- package/dist/observability/events.js +85 -0
- package/dist/observability/index.d.ts +15 -0
- package/dist/observability/index.js +12 -0
- package/dist/observability/logger.d.ts +106 -0
- package/dist/observability/logger.js +266 -0
- package/dist/observability/otel.d.ts +143 -0
- package/dist/observability/otel.js +421 -0
- package/dist/parser/action-parser.d.ts +105 -0
- package/dist/parser/action-parser.js +645 -0
- package/dist/parser/base.d.ts +7 -0
- package/dist/parser/base.js +11 -0
- package/dist/parser/expressions.d.ts +14 -0
- package/dist/parser/expressions.js +89 -6
- package/dist/parser/fetch-parser.d.ts +27 -0
- package/dist/parser/fetch-parser.js +280 -0
- package/dist/parser/index.d.ts +17 -0
- package/dist/parser/index.js +17 -0
- package/dist/parser/parser.d.ts +44 -46
- package/dist/parser/parser.js +122 -1070
- package/dist/parser/pipeline-parser.d.ts +12 -0
- package/dist/parser/pipeline-parser.js +52 -0
- package/dist/parser/schedule-parser.d.ts +7 -0
- package/dist/parser/schedule-parser.js +137 -0
- package/dist/parser/source-parser.d.ts +9 -0
- package/dist/parser/source-parser.js +151 -0
- package/dist/pause/index.d.ts +15 -0
- package/dist/pause/index.js +12 -0
- package/dist/pause/log-store.d.ts +33 -0
- package/dist/pause/log-store.js +98 -0
- package/dist/pause/manager.d.ts +130 -0
- package/dist/pause/manager.js +294 -0
- package/dist/pause/state.d.ts +93 -0
- package/dist/pause/state.js +103 -0
- package/dist/pause/store.d.ts +61 -0
- package/dist/pause/store.js +158 -0
- package/dist/plugin.d.ts +9 -12
- package/dist/plugin.js +10 -13
- package/dist/scheduler/cron-parser.d.ts +10 -3
- package/dist/scheduler/cron-parser.js +227 -48
- package/dist/scheduler/scheduler.js +56 -22
- package/dist/stores/factory.d.ts +7 -1
- package/dist/stores/factory.js +14 -3
- package/dist/stores/file.d.ts +26 -0
- package/dist/stores/file.js +67 -21
- package/dist/stores/index.d.ts +16 -1
- package/dist/stores/index.js +16 -1
- package/dist/stores/memory.d.ts +4 -0
- package/dist/stores/memory.js +8 -6
- package/dist/stores/postgrest.d.ts +28 -0
- package/dist/stores/postgrest.js +84 -37
- package/dist/stores/types.d.ts +17 -0
- package/dist/stores/types.js +12 -0
- package/dist/sync/index.d.ts +3 -2
- package/dist/sync/index.js +2 -1
- package/dist/sync/log-store.d.ts +30 -0
- package/dist/sync/log-store.js +45 -0
- package/dist/sync/store.js +1 -1
- package/dist/trace/index.d.ts +18 -0
- package/dist/trace/index.js +13 -0
- package/dist/trace/log-view.d.ts +57 -0
- package/dist/trace/log-view.js +76 -0
- package/dist/trace/recorder.d.ts +75 -0
- package/dist/trace/recorder.js +157 -0
- package/dist/trace/replay.d.ts +132 -0
- package/dist/trace/replay.js +264 -0
- package/dist/trace/state.d.ts +102 -0
- package/dist/trace/state.js +86 -0
- package/dist/trace/store.d.ts +75 -0
- package/dist/trace/store.js +250 -0
- package/dist/utils/deep-merge.d.ts +10 -0
- package/dist/utils/deep-merge.js +23 -0
- package/dist/utils/file.d.ts +13 -4
- package/dist/utils/file.js +70 -12
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/long-timeout.d.ts +19 -0
- package/dist/utils/long-timeout.js +33 -0
- package/dist/utils/path.d.ts +22 -1
- package/dist/utils/path.js +46 -1
- package/dist/utils/redact.d.ts +22 -0
- package/dist/utils/redact.js +42 -0
- package/dist/utils/type-guards.d.ts +58 -0
- package/dist/utils/type-guards.js +92 -0
- package/dist/webhook/server.d.ts +9 -0
- package/dist/webhook/server.js +122 -36
- package/dist/webhook/types.d.ts +9 -1
- package/package.json +76 -9
- package/.claude/settings.local.json +0 -31
- package/.claude/skills/api-integration.md +0 -125
- package/.claude/skills/database-schema.md +0 -51
- package/.claude/skills/dsl-design.md +0 -80
- package/.claude/skills/property-testing.md +0 -143
- package/.claude/skills/reqon/SKILL.md +0 -44
- package/.claude/skills/reqon/references/examples.md +0 -206
- package/.claude/skills/reqon/references/syntax.md +0 -263
- package/.claude/skills/vscode-extension.md +0 -113
- package/.github/dependabot.yml +0 -32
- package/.github/pull_request_template.md +0 -21
- package/.github/workflows/ci.yml +0 -174
- package/.github/workflows/release.yml +0 -73
- package/CLAUDE.md +0 -72
- package/CONTRIBUTING.md +0 -161
- package/TODO.md +0 -51
- package/dist/auth/auth.test.d.ts +0 -1
- package/dist/auth/auth.test.js +0 -255
- package/dist/errors/errors.test.d.ts +0 -1
- package/dist/errors/errors.test.js +0 -165
- package/dist/execution/execution.test.d.ts +0 -1
- package/dist/execution/execution.test.js +0 -246
- package/dist/integration.test.d.ts +0 -1
- package/dist/integration.test.js +0 -168
- package/dist/interpreter/evaluator.test.d.ts +0 -1
- package/dist/interpreter/evaluator.test.js +0 -512
- package/dist/interpreter/http.test.d.ts +0 -1
- package/dist/interpreter/http.test.js +0 -299
- package/dist/interpreter/progress.test.d.ts +0 -1
- package/dist/interpreter/progress.test.js +0 -216
- package/dist/interpreter/schema-matcher.test.d.ts +0 -1
- package/dist/interpreter/schema-matcher.test.js +0 -122
- package/dist/lexer/lexer.d.ts +0 -24
- package/dist/lexer/lexer.js +0 -264
- package/dist/lexer/lexer.test.d.ts +0 -1
- package/dist/lexer/lexer.test.js +0 -259
- package/dist/loader/loader.test.d.ts +0 -1
- package/dist/loader/loader.test.js +0 -287
- package/dist/oas/oas.test.d.ts +0 -1
- package/dist/oas/oas.test.js +0 -218
- package/dist/parser/expressions.test.d.ts +0 -1
- package/dist/parser/expressions.test.js +0 -378
- package/dist/parser/match.test.d.ts +0 -1
- package/dist/parser/match.test.js +0 -254
- package/dist/parser/parser.test.d.ts +0 -1
- package/dist/parser/parser.test.js +0 -333
- package/dist/parser/schedule.test.d.ts +0 -1
- package/dist/parser/schedule.test.js +0 -241
- package/dist/scheduler/cron-parser.test.d.ts +0 -1
- package/dist/scheduler/cron-parser.test.js +0 -188
- package/dist/stores/file.test.d.ts +0 -1
- package/dist/stores/file.test.js +0 -165
- package/dist/stores/memory.test.d.ts +0 -1
- package/dist/stores/memory.test.js +0 -157
- package/dist/stores/stores.test.d.ts +0 -1
- package/dist/stores/stores.test.js +0 -158
- package/dist/sync/sync.test.d.ts +0 -1
- package/dist/sync/sync.test.js +0 -221
- package/docusaurus/README.md +0 -41
- package/docusaurus/docs/advanced/execution-state.md +0 -283
- package/docusaurus/docs/advanced/extending-reqon.md +0 -388
- package/docusaurus/docs/advanced/multi-file-missions.md +0 -250
- package/docusaurus/docs/advanced/parallel-execution.md +0 -353
- package/docusaurus/docs/api-reference.md +0 -443
- package/docusaurus/docs/authentication/api-key.md +0 -339
- package/docusaurus/docs/authentication/basic.md +0 -276
- package/docusaurus/docs/authentication/bearer.md +0 -282
- package/docusaurus/docs/authentication/oauth2.md +0 -317
- package/docusaurus/docs/authentication/overview.md +0 -251
- package/docusaurus/docs/cli.md +0 -229
- package/docusaurus/docs/core-concepts/actions.md +0 -286
- package/docusaurus/docs/core-concepts/missions.md +0 -264
- package/docusaurus/docs/core-concepts/schemas.md +0 -353
- package/docusaurus/docs/core-concepts/sources.md +0 -339
- package/docusaurus/docs/core-concepts/stores.md +0 -332
- package/docusaurus/docs/dsl-syntax/expressions.md +0 -361
- package/docusaurus/docs/dsl-syntax/fetch.md +0 -293
- package/docusaurus/docs/dsl-syntax/for-loops.md +0 -324
- package/docusaurus/docs/dsl-syntax/map.md +0 -345
- package/docusaurus/docs/dsl-syntax/match.md +0 -387
- package/docusaurus/docs/dsl-syntax/pipelines.md +0 -397
- package/docusaurus/docs/dsl-syntax/validate.md +0 -401
- package/docusaurus/docs/error-handling/dead-letter-queues.md +0 -399
- package/docusaurus/docs/error-handling/flow-control.md +0 -337
- package/docusaurus/docs/error-handling/retry-strategies.md +0 -368
- package/docusaurus/docs/examples.md +0 -488
- package/docusaurus/docs/getting-started.md +0 -256
- package/docusaurus/docs/http/circuit-breaker.md +0 -401
- package/docusaurus/docs/http/incremental-sync.md +0 -394
- package/docusaurus/docs/http/pagination.md +0 -361
- package/docusaurus/docs/http/rate-limiting.md +0 -383
- package/docusaurus/docs/http/requests.md +0 -328
- package/docusaurus/docs/http/retry.md +0 -402
- package/docusaurus/docs/intro.md +0 -90
- package/docusaurus/docs/openapi/loading-specs.md +0 -305
- package/docusaurus/docs/openapi/operation-calls.md +0 -314
- package/docusaurus/docs/openapi/overview.md +0 -212
- package/docusaurus/docs/openapi/response-validation.md +0 -344
- package/docusaurus/docs/scheduling/cron.md +0 -305
- package/docusaurus/docs/scheduling/daemon-mode.md +0 -317
- package/docusaurus/docs/scheduling/intervals.md +0 -289
- package/docusaurus/docs/scheduling/overview.md +0 -231
- package/docusaurus/docs/stores/custom-adapters.md +0 -376
- package/docusaurus/docs/stores/file.md +0 -236
- package/docusaurus/docs/stores/memory.md +0 -193
- package/docusaurus/docs/stores/overview.md +0 -274
- package/docusaurus/docs/stores/postgrest.md +0 -316
- package/docusaurus/docusaurus.config.ts +0 -148
- package/docusaurus/package-lock.json +0 -18029
- package/docusaurus/package.json +0 -47
- package/docusaurus/sidebars.ts +0 -155
- package/docusaurus/src/components/HomepageFeatures/index.tsx +0 -105
- package/docusaurus/src/components/HomepageFeatures/styles.module.css +0 -12
- package/docusaurus/src/css/custom.css +0 -169
- package/docusaurus/src/pages/index.module.css +0 -48
- package/docusaurus/src/pages/index.tsx +0 -110
- package/docusaurus/src/pages/markdown-page.md +0 -7
- package/docusaurus/static/.nojekyll +0 -0
- package/docusaurus/static/img/docusaurus-social-card.jpg +0 -0
- package/docusaurus/static/img/docusaurus.png +0 -0
- package/docusaurus/static/img/favicon.ico +0 -0
- package/docusaurus/static/img/logo.svg +0 -10
- package/docusaurus/static/img/undraw_docusaurus_mountain.svg +0 -171
- package/docusaurus/static/img/undraw_docusaurus_react.svg +0 -170
- package/docusaurus/static/img/undraw_docusaurus_tree.svg +0 -40
- package/docusaurus/tsconfig.json +0 -8
- package/examples/README.md +0 -112
- package/examples/error-handling/README.md +0 -150
- package/examples/error-handling/payment-processor.vague +0 -287
- package/examples/github-sync/README.md +0 -74
- package/examples/github-sync/fetch-issues.vague +0 -47
- package/examples/github-sync/fetch-prs.vague +0 -40
- package/examples/github-sync/mission.vague +0 -101
- package/examples/github-sync/normalize.vague +0 -70
- package/examples/jsonplaceholder/README.md +0 -28
- package/examples/jsonplaceholder/posts.vague +0 -48
- package/examples/petstore/README.md +0 -35
- package/examples/petstore/openapi.yaml +0 -97
- package/examples/petstore/sync.vague +0 -52
- package/examples/temporal-comparison/README.md +0 -297
- package/examples/temporal-comparison/reconciliation.vague +0 -355
- package/examples/temporal-comparison/temporal/activities/index.ts +0 -8
- package/examples/temporal-comparison/temporal/activities/shipstation.ts +0 -225
- package/examples/temporal-comparison/temporal/activities/shopify.ts +0 -257
- package/examples/temporal-comparison/temporal/activities/storage.ts +0 -198
- package/examples/temporal-comparison/temporal/activities/stripe.ts +0 -169
- package/examples/temporal-comparison/temporal/activities/validation.ts +0 -205
- package/examples/temporal-comparison/temporal/client/schedule.ts +0 -218
- package/examples/temporal-comparison/temporal/config/retry.ts +0 -63
- package/examples/temporal-comparison/temporal/types/index.ts +0 -129
- package/examples/temporal-comparison/temporal/workers/main.ts +0 -130
- package/examples/temporal-comparison/temporal/workflows/orderReconciliation.ts +0 -262
- package/examples/xero/README.md +0 -88
- package/examples/xero/invoices.vague +0 -189
- package/src/api-integration.test.ts +0 -954
- package/src/ast/index.ts +0 -1
- package/src/ast/nodes.ts +0 -310
- package/src/auth/auth.test.ts +0 -326
- package/src/auth/circuit-breaker.test.ts +0 -390
- package/src/auth/circuit-breaker.ts +0 -379
- package/src/auth/credentials.test.ts +0 -273
- package/src/auth/credentials.ts +0 -246
- package/src/auth/index.ts +0 -40
- package/src/auth/oauth2-provider.ts +0 -177
- package/src/auth/rate-limiter.ts +0 -459
- package/src/auth/token-store.ts +0 -177
- package/src/auth/types.ts +0 -159
- package/src/benchmark/e2e.bench.ts +0 -288
- package/src/benchmark/evaluator.bench.ts +0 -331
- package/src/benchmark/fixtures.ts +0 -295
- package/src/benchmark/index.ts +0 -108
- package/src/benchmark/lexer.bench.ts +0 -69
- package/src/benchmark/parser.bench.ts +0 -103
- package/src/benchmark/resilience.bench.ts +0 -193
- package/src/benchmark/store.bench.ts +0 -147
- package/src/benchmark/utils.ts +0 -230
- package/src/cli.ts +0 -313
- package/src/errors/errors.test.ts +0 -234
- package/src/errors/index.ts +0 -223
- package/src/execution/execution.test.ts +0 -307
- package/src/execution/index.ts +0 -21
- package/src/execution/state.ts +0 -207
- package/src/execution/store.ts +0 -188
- package/src/index.ts +0 -169
- package/src/integration.test.ts +0 -192
- package/src/interpreter/context.ts +0 -57
- package/src/interpreter/evaluator.test.ts +0 -796
- package/src/interpreter/evaluator.ts +0 -245
- package/src/interpreter/executor.ts +0 -946
- package/src/interpreter/fetch-handler.ts +0 -302
- package/src/interpreter/http.test.ts +0 -423
- package/src/interpreter/http.ts +0 -308
- package/src/interpreter/index.ts +0 -32
- package/src/interpreter/pagination.ts +0 -207
- package/src/interpreter/progress.test.ts +0 -276
- package/src/interpreter/schema-matcher.test.ts +0 -160
- package/src/interpreter/schema-matcher.ts +0 -168
- package/src/interpreter/signals.ts +0 -73
- package/src/interpreter/step-handlers/for-handler.ts +0 -65
- package/src/interpreter/step-handlers/index.ts +0 -17
- package/src/interpreter/step-handlers/map-handler.ts +0 -24
- package/src/interpreter/step-handlers/match-handler.ts +0 -101
- package/src/interpreter/step-handlers/store-handler.ts +0 -78
- package/src/interpreter/step-handlers/types.ts +0 -17
- package/src/interpreter/step-handlers/validate-handler.ts +0 -30
- package/src/interpreter/step-handlers/webhook-handler.ts +0 -142
- package/src/lexer/index.ts +0 -18
- package/src/lexer/lexer.test.ts +0 -316
- package/src/lexer/tokens.ts +0 -179
- package/src/loader/index.ts +0 -288
- package/src/loader/loader.test.ts +0 -360
- package/src/oas/index.ts +0 -4
- package/src/oas/loader.ts +0 -126
- package/src/oas/oas.test.ts +0 -254
- package/src/oas/validator.ts +0 -299
- package/src/parser/base.ts +0 -124
- package/src/parser/expressions.test.ts +0 -525
- package/src/parser/expressions.ts +0 -314
- package/src/parser/index.ts +0 -3
- package/src/parser/match.test.ts +0 -296
- package/src/parser/parser.test.ts +0 -739
- package/src/parser/parser.ts +0 -1469
- package/src/parser/schedule.test.ts +0 -287
- package/src/parser/webhook.test.ts +0 -248
- package/src/plugin.ts +0 -83
- package/src/scheduler/cron-parser.test.ts +0 -236
- package/src/scheduler/cron-parser.ts +0 -236
- package/src/scheduler/index.ts +0 -10
- package/src/scheduler/scheduler.ts +0 -443
- package/src/scheduler/types.ts +0 -71
- package/src/stores/factory.ts +0 -104
- package/src/stores/file.test.ts +0 -276
- package/src/stores/file.ts +0 -211
- package/src/stores/index.ts +0 -6
- package/src/stores/memory.test.ts +0 -238
- package/src/stores/memory.ts +0 -63
- package/src/stores/postgrest.test.ts +0 -488
- package/src/stores/postgrest.ts +0 -263
- package/src/stores/stores.test.ts +0 -197
- package/src/stores/types.ts +0 -58
- package/src/sync/index.ts +0 -16
- package/src/sync/state.ts +0 -126
- package/src/sync/store.ts +0 -139
- package/src/sync/sync.test.ts +0 -271
- package/src/utils/async.ts +0 -10
- package/src/utils/file.ts +0 -106
- package/src/utils/index.ts +0 -14
- package/src/utils/logger.ts +0 -53
- package/src/utils/path.ts +0 -47
- package/src/webhook/index.ts +0 -15
- package/src/webhook/server.test.ts +0 -253
- package/src/webhook/server.ts +0 -389
- package/src/webhook/store.ts +0 -239
- package/src/webhook/types.ts +0 -93
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -39
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution event log — the durable, append-only record of a mission run.
|
|
3
|
+
*
|
|
4
|
+
* Every step's start, completion, and side effect is appended as it happens.
|
|
5
|
+
* Resume becomes "replay the log and fold to last state" (see {@link ./fold}),
|
|
6
|
+
* and trace/time-travel, audit, and idempotent resume all derive from this one
|
|
7
|
+
* structure. Recorded values (timestamps, effect ids, outputs) are read back on
|
|
8
|
+
* replay rather than re-derived, so replay is deterministic.
|
|
9
|
+
*/
|
|
10
|
+
export type ExecutionEventType = 'mission.started' | 'step.started' | 'step.completed' | 'effect.applied' | 'page.completed' | 'checkpoint.advanced' | 'pause.created' | 'pause.resumed' | 'mission.completed' | 'mission.failed';
|
|
11
|
+
interface BaseEvent {
|
|
12
|
+
/** The run this event belongs to. */
|
|
13
|
+
executionId: string;
|
|
14
|
+
/** Event kind (discriminant). */
|
|
15
|
+
type: ExecutionEventType;
|
|
16
|
+
}
|
|
17
|
+
export interface MissionStartedEvent extends BaseEvent {
|
|
18
|
+
type: 'mission.started';
|
|
19
|
+
mission: string;
|
|
20
|
+
}
|
|
21
|
+
export interface StepStartedEvent extends BaseEvent {
|
|
22
|
+
type: 'step.started';
|
|
23
|
+
stepId: string;
|
|
24
|
+
action: string;
|
|
25
|
+
stepType: string;
|
|
26
|
+
attempt: number;
|
|
27
|
+
}
|
|
28
|
+
export interface StepCompletedEvent extends BaseEvent {
|
|
29
|
+
type: 'step.completed';
|
|
30
|
+
stepId: string;
|
|
31
|
+
attempt: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A side effect (a fetch or a store write) was applied. The {@link effectId}
|
|
35
|
+
* is the stable identity used to skip already-applied effects on replay.
|
|
36
|
+
*/
|
|
37
|
+
export interface EffectAppliedEvent extends BaseEvent {
|
|
38
|
+
type: 'effect.applied';
|
|
39
|
+
stepId: string;
|
|
40
|
+
attempt: number;
|
|
41
|
+
effectType: 'fetch' | 'store';
|
|
42
|
+
effectId: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A page of a resumable (backfill) paginated fetch finished — its data is
|
|
46
|
+
* fetched and persisted. Carries the position to resume from: the next page
|
|
47
|
+
* index and/or cursor. `done` marks the natural end of pagination (the API had
|
|
48
|
+
* no more), distinct from stopping early on a per-run item cap, so a resume
|
|
49
|
+
* knows whether to continue or skip.
|
|
50
|
+
*/
|
|
51
|
+
export interface PageCompletedEvent extends BaseEvent {
|
|
52
|
+
type: 'page.completed';
|
|
53
|
+
stepId: string;
|
|
54
|
+
/** Zero-based index of the next page to fetch on resume. */
|
|
55
|
+
page: number;
|
|
56
|
+
/** Cursor to resume from (cursor-based pagination). */
|
|
57
|
+
cursor?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Records *fetched* on this page. NOTE: this is the fetched count, not the
|
|
60
|
+
* persisted count — the page's data is still only in memory when this event is
|
|
61
|
+
* recorded; the downstream store step persists it afterwards. A crash between
|
|
62
|
+
* this event and the store's `effect.applied` advances the resume cursor past a
|
|
63
|
+
* page whose data was never stored. See the known crash-window note in
|
|
64
|
+
* CODE_REVIEW.md (C2); a full fix defers this advance until after persistence.
|
|
65
|
+
*/
|
|
66
|
+
recordCount?: number;
|
|
67
|
+
/** True once pagination has reached its natural end. */
|
|
68
|
+
done: boolean;
|
|
69
|
+
}
|
|
70
|
+
export interface CheckpointAdvancedEvent extends BaseEvent {
|
|
71
|
+
type: 'checkpoint.advanced';
|
|
72
|
+
key: string;
|
|
73
|
+
syncedAt: string;
|
|
74
|
+
/** Records fetched in the sync that advanced this checkpoint. */
|
|
75
|
+
recordCount?: number;
|
|
76
|
+
/** Opaque pagination cursor to resume from (cursor-based incremental sync). */
|
|
77
|
+
cursor?: string;
|
|
78
|
+
/** Mission that advanced the checkpoint (lets a shared log filter by mission). */
|
|
79
|
+
mission?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface PauseCreatedEvent extends BaseEvent {
|
|
82
|
+
type: 'pause.created';
|
|
83
|
+
pauseId: string;
|
|
84
|
+
/**
|
|
85
|
+
* The full durable pause state (expiry, resume triggers, captured checkpoint).
|
|
86
|
+
* Carried in the log so a paused run — and the timer/webhook it waits on — can
|
|
87
|
+
* be reconstructed from the log alone, no separate pause file. Opaque to the
|
|
88
|
+
* log layer (typed `unknown` to avoid coupling it to the pause domain).
|
|
89
|
+
*/
|
|
90
|
+
pause?: unknown;
|
|
91
|
+
}
|
|
92
|
+
export interface PauseResumedEvent extends BaseEvent {
|
|
93
|
+
type: 'pause.resumed';
|
|
94
|
+
pauseId: string;
|
|
95
|
+
resumedBy: string;
|
|
96
|
+
/** Terminal status of the pause: resumed (default), cancelled, or expired. */
|
|
97
|
+
status?: 'resumed' | 'cancelled' | 'expired';
|
|
98
|
+
/** Recorded resume time (ISO 8601). */
|
|
99
|
+
resumedAt?: string;
|
|
100
|
+
/** Payload delivered by a webhook resume. */
|
|
101
|
+
webhookPayload?: unknown;
|
|
102
|
+
}
|
|
103
|
+
export interface MissionCompletedEvent extends BaseEvent {
|
|
104
|
+
type: 'mission.completed';
|
|
105
|
+
}
|
|
106
|
+
export interface MissionFailedEvent extends BaseEvent {
|
|
107
|
+
type: 'mission.failed';
|
|
108
|
+
error: string;
|
|
109
|
+
}
|
|
110
|
+
/** An event as appended by a caller (the log assigns seq + timestamp). */
|
|
111
|
+
export type ExecutionEvent = MissionStartedEvent | StepStartedEvent | StepCompletedEvent | EffectAppliedEvent | PageCompletedEvent | CheckpointAdvancedEvent | PauseCreatedEvent | PauseResumedEvent | MissionCompletedEvent | MissionFailedEvent;
|
|
112
|
+
/** A persisted event: the appended event plus the log's assigned metadata. */
|
|
113
|
+
export type StoredEvent = ExecutionEvent & {
|
|
114
|
+
/** Monotonic sequence within the execution, starting at 0. */
|
|
115
|
+
seq: number;
|
|
116
|
+
/** Recorded wall-clock time (ISO 8601); read back on replay, never re-derived. */
|
|
117
|
+
at: string;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Stable identity for a side effect: `(executionId, stepId, attempt, effectType)`
|
|
121
|
+
* plus a caller-supplied discriminator (e.g. the request signature or record
|
|
122
|
+
* key). Replay uses this to skip effects already recorded as applied.
|
|
123
|
+
*/
|
|
124
|
+
export declare function effectId(executionId: string, stepId: string, attempt: number, effectType: 'fetch' | 'store', discriminator: string): string;
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution event log — the durable, append-only record of a mission run.
|
|
3
|
+
*
|
|
4
|
+
* Every step's start, completion, and side effect is appended as it happens.
|
|
5
|
+
* Resume becomes "replay the log and fold to last state" (see {@link ./fold}),
|
|
6
|
+
* and trace/time-travel, audit, and idempotent resume all derive from this one
|
|
7
|
+
* structure. Recorded values (timestamps, effect ids, outputs) are read back on
|
|
8
|
+
* replay rather than re-derived, so replay is deterministic.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Stable identity for a side effect: `(executionId, stepId, attempt, effectType)`
|
|
12
|
+
* plus a caller-supplied discriminator (e.g. the request signature or record
|
|
13
|
+
* key). Replay uses this to skip effects already recorded as applied.
|
|
14
|
+
*/
|
|
15
|
+
export function effectId(executionId, stepId, attempt, effectType, discriminator) {
|
|
16
|
+
return `${executionId}::${stepId}::${attempt}::${effectType}::${discriminator}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fold an execution event log down to current state.
|
|
3
|
+
*
|
|
4
|
+
* This is the heart of replay-based resume: replaying the log and folding it
|
|
5
|
+
* yields the exact state the run was in, including which effects already
|
|
6
|
+
* applied (so replay skips them) and which pause it is waiting on.
|
|
7
|
+
*/
|
|
8
|
+
import type { StoredEvent } from './events.js';
|
|
9
|
+
export type FoldedStatus = 'pending' | 'running' | 'paused' | 'completed' | 'failed';
|
|
10
|
+
export interface FoldedState {
|
|
11
|
+
status: FoldedStatus;
|
|
12
|
+
/** Sequence of the last event folded (-1 for an empty log). */
|
|
13
|
+
lastSeq: number;
|
|
14
|
+
/** Step ids that have completed (replay skips re-running them). */
|
|
15
|
+
completedSteps: Set<string>;
|
|
16
|
+
/** Effect ids already applied (replay skips re-applying them). */
|
|
17
|
+
appliedEffects: Set<string>;
|
|
18
|
+
/** Latest synced-at per checkpoint key. */
|
|
19
|
+
checkpoints: Map<string, string>;
|
|
20
|
+
/** Resume position per backfill step id (last persisted page + cursor). */
|
|
21
|
+
pageProgress: Map<string, {
|
|
22
|
+
page: number;
|
|
23
|
+
cursor?: string;
|
|
24
|
+
done: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
/** The pause this run is currently waiting on, if any. */
|
|
27
|
+
pendingPauseId?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The last pause marked resumed in the log. A resume trigger (webhook/timeout)
|
|
30
|
+
* can record `pause.resumed` before the executor re-runs, so this lets the
|
|
31
|
+
* executor recognise it must continue past that pause rather than create a new
|
|
32
|
+
* one. Cleared once the run completes or fails.
|
|
33
|
+
*/
|
|
34
|
+
resumedPauseId?: string;
|
|
35
|
+
/** Error message if the run failed. */
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function foldLog(events: StoredEvent[]): FoldedState;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function foldLog(events) {
|
|
2
|
+
const state = {
|
|
3
|
+
status: 'pending',
|
|
4
|
+
lastSeq: -1,
|
|
5
|
+
completedSteps: new Set(),
|
|
6
|
+
appliedEffects: new Set(),
|
|
7
|
+
checkpoints: new Map(),
|
|
8
|
+
pageProgress: new Map(),
|
|
9
|
+
};
|
|
10
|
+
for (const event of events) {
|
|
11
|
+
state.lastSeq = event.seq;
|
|
12
|
+
switch (event.type) {
|
|
13
|
+
case 'mission.started':
|
|
14
|
+
state.status = 'running';
|
|
15
|
+
break;
|
|
16
|
+
case 'step.completed':
|
|
17
|
+
state.completedSteps.add(event.stepId);
|
|
18
|
+
break;
|
|
19
|
+
case 'effect.applied':
|
|
20
|
+
state.appliedEffects.add(event.effectId);
|
|
21
|
+
break;
|
|
22
|
+
case 'page.completed':
|
|
23
|
+
state.pageProgress.set(event.stepId, {
|
|
24
|
+
page: event.page,
|
|
25
|
+
cursor: event.cursor,
|
|
26
|
+
done: event.done,
|
|
27
|
+
});
|
|
28
|
+
break;
|
|
29
|
+
case 'checkpoint.advanced':
|
|
30
|
+
state.checkpoints.set(event.key, event.syncedAt);
|
|
31
|
+
break;
|
|
32
|
+
case 'pause.created':
|
|
33
|
+
state.status = 'paused';
|
|
34
|
+
state.pendingPauseId = event.pauseId;
|
|
35
|
+
break;
|
|
36
|
+
case 'pause.resumed':
|
|
37
|
+
state.status = 'running';
|
|
38
|
+
state.pendingPauseId = undefined;
|
|
39
|
+
state.resumedPauseId = event.pauseId;
|
|
40
|
+
break;
|
|
41
|
+
case 'mission.completed':
|
|
42
|
+
state.status = 'completed';
|
|
43
|
+
state.resumedPauseId = undefined;
|
|
44
|
+
break;
|
|
45
|
+
case 'mission.failed':
|
|
46
|
+
state.status = 'failed';
|
|
47
|
+
state.error = event.error;
|
|
48
|
+
state.resumedPauseId = undefined;
|
|
49
|
+
break;
|
|
50
|
+
// step.started carries no folded state on its own.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution event log — the append-only foundation for durable execution.
|
|
3
|
+
*
|
|
4
|
+
* A running mission is modelled as an ordered log of events; resume is replay +
|
|
5
|
+
* fold (see {@link foldLog}), and idempotent effects fall out of recording an
|
|
6
|
+
* `effect.applied` event per side effect. This module is the seam the rest of
|
|
7
|
+
* the durable-execution work (transactional backend, exactly-once effects,
|
|
8
|
+
* trace/sync/pause as log views) builds on.
|
|
9
|
+
*/
|
|
10
|
+
export type { ExecutionEvent, StoredEvent, ExecutionEventType, MissionStartedEvent, StepStartedEvent, StepCompletedEvent, EffectAppliedEvent, CheckpointAdvancedEvent, PauseCreatedEvent, PauseResumedEvent, MissionCompletedEvent, MissionFailedEvent, } from './events.js';
|
|
11
|
+
export { effectId } from './events.js';
|
|
12
|
+
export type { ExecutionLogStore, CheckpointRecord } from './store.js';
|
|
13
|
+
export { MemoryExecutionLog, FileExecutionLog, reduceCheckpoints } from './store.js';
|
|
14
|
+
export { SqliteExecutionLog } from './sqlite-store.js';
|
|
15
|
+
export { PostgresExecutionLog, type PostgresExecutionLogOptions } from './postgres-store.js';
|
|
16
|
+
export type { FoldedState, FoldedStatus } from './fold.js';
|
|
17
|
+
export { foldLog } from './fold.js';
|
|
18
|
+
export { loadState } from './resume.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { effectId } from './events.js';
|
|
2
|
+
export { MemoryExecutionLog, FileExecutionLog, reduceCheckpoints } from './store.js';
|
|
3
|
+
export { SqliteExecutionLog } from './sqlite-store.js';
|
|
4
|
+
export { PostgresExecutionLog } from './postgres-store.js';
|
|
5
|
+
export { foldLog } from './fold.js';
|
|
6
|
+
export { loadState } from './resume.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres-backed execution log — a transactional, multi-node durable backend.
|
|
3
|
+
*
|
|
4
|
+
* Like the SQLite backend, `pg` is an *optional* peer dependency imported lazily
|
|
5
|
+
* via a non-literal specifier, so the core package carries no database driver
|
|
6
|
+
* and a missing one yields a clear, actionable error.
|
|
7
|
+
*
|
|
8
|
+
* Multi-writer safety: `seq` is assigned inside the INSERT under a
|
|
9
|
+
* `(execution_id, seq)` primary key. If two appenders race and compute the same
|
|
10
|
+
* next seq, one wins and the other gets a unique-violation (SQLSTATE 23505) and
|
|
11
|
+
* retries against the now-higher max — so concurrent appenders always land on
|
|
12
|
+
* distinct, contiguous sequence numbers.
|
|
13
|
+
*/
|
|
14
|
+
import type { ExecutionEvent, StoredEvent } from './events.js';
|
|
15
|
+
import type { CheckpointRecord, ExecutionLogStore } from './store.js';
|
|
16
|
+
export interface PostgresExecutionLogOptions {
|
|
17
|
+
/** Table to store events in. Default `reqon_execution_events`. */
|
|
18
|
+
table?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class PostgresExecutionLog implements ExecutionLogStore {
|
|
21
|
+
private connectionString;
|
|
22
|
+
private pool?;
|
|
23
|
+
private ready?;
|
|
24
|
+
private readonly table;
|
|
25
|
+
constructor(connectionString: string, options?: PostgresExecutionLogOptions);
|
|
26
|
+
private ensure;
|
|
27
|
+
private init;
|
|
28
|
+
append(event: ExecutionEvent): Promise<StoredEvent>;
|
|
29
|
+
read(executionId: string): Promise<StoredEvent[]>;
|
|
30
|
+
listCheckpoints(mission?: string): Promise<CheckpointRecord[]>;
|
|
31
|
+
listPauses(): Promise<StoredEvent[]>;
|
|
32
|
+
/** Drop all rows. Intended for test setup, not production use. */
|
|
33
|
+
reset(): Promise<void>;
|
|
34
|
+
/** Release the connection pool. */
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { reduceCheckpoints } from './store.js';
|
|
2
|
+
const UNIQUE_VIOLATION = '23505';
|
|
3
|
+
const MAX_APPEND_ATTEMPTS = 8;
|
|
4
|
+
export class PostgresExecutionLog {
|
|
5
|
+
connectionString;
|
|
6
|
+
pool;
|
|
7
|
+
ready;
|
|
8
|
+
table;
|
|
9
|
+
constructor(connectionString, options = {}) {
|
|
10
|
+
this.connectionString = connectionString;
|
|
11
|
+
this.table = options.table ?? 'reqon_execution_events';
|
|
12
|
+
// The table name is interpolated into SQL (identifiers can't be bound), so
|
|
13
|
+
// constrain it to a safe identifier to keep that interpolation injection-free.
|
|
14
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(this.table)) {
|
|
15
|
+
throw new Error(`Invalid table name: ${this.table}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async ensure() {
|
|
19
|
+
if (this.pool)
|
|
20
|
+
return this.pool;
|
|
21
|
+
if (!this.ready)
|
|
22
|
+
this.ready = this.init();
|
|
23
|
+
return this.ready;
|
|
24
|
+
}
|
|
25
|
+
async init() {
|
|
26
|
+
let Pool;
|
|
27
|
+
try {
|
|
28
|
+
// Non-literal specifier: keeps the driver an optional dependency with no
|
|
29
|
+
// compile-time type coupling (no @types/pg required).
|
|
30
|
+
const specifier = 'pg';
|
|
31
|
+
const mod = (await import(specifier));
|
|
32
|
+
const ctor = mod.Pool ?? mod.default?.Pool;
|
|
33
|
+
if (!ctor)
|
|
34
|
+
throw new Error('pg.Pool not found');
|
|
35
|
+
Pool = ctor;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error("PostgresExecutionLog requires the optional peer dependency 'pg'. " +
|
|
39
|
+
'Install it with: npm install pg');
|
|
40
|
+
}
|
|
41
|
+
const pool = new Pool({ connectionString: this.connectionString });
|
|
42
|
+
await pool.query(`CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
43
|
+
execution_id text NOT NULL,
|
|
44
|
+
seq integer NOT NULL,
|
|
45
|
+
at text NOT NULL,
|
|
46
|
+
data jsonb NOT NULL,
|
|
47
|
+
PRIMARY KEY (execution_id, seq)
|
|
48
|
+
)`);
|
|
49
|
+
this.pool = pool;
|
|
50
|
+
return pool;
|
|
51
|
+
}
|
|
52
|
+
async append(event) {
|
|
53
|
+
const pool = await this.ensure();
|
|
54
|
+
const at = new Date().toISOString();
|
|
55
|
+
const data = JSON.stringify(event);
|
|
56
|
+
for (let attempt = 0; attempt < MAX_APPEND_ATTEMPTS; attempt++) {
|
|
57
|
+
try {
|
|
58
|
+
const res = await pool.query(`INSERT INTO ${this.table} (execution_id, seq, at, data)
|
|
59
|
+
VALUES (
|
|
60
|
+
$1,
|
|
61
|
+
(SELECT COALESCE(MAX(seq) + 1, 0) FROM ${this.table} WHERE execution_id = $1),
|
|
62
|
+
$2,
|
|
63
|
+
$3::jsonb
|
|
64
|
+
)
|
|
65
|
+
RETURNING seq`, [event.executionId, at, data]);
|
|
66
|
+
return { ...event, seq: res.rows[0].seq, at };
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// A concurrent appender claimed this seq first; retry against the new max.
|
|
70
|
+
if (err.code === UNIQUE_VIOLATION)
|
|
71
|
+
continue;
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`append failed after ${MAX_APPEND_ATTEMPTS} attempts for execution ${event.executionId}`);
|
|
76
|
+
}
|
|
77
|
+
async read(executionId) {
|
|
78
|
+
const pool = await this.ensure();
|
|
79
|
+
const res = await pool.query(`SELECT seq, at, data FROM ${this.table} WHERE execution_id = $1 ORDER BY seq ASC`, [executionId]);
|
|
80
|
+
// jsonb comes back already parsed; overlay the assigned seq + recorded time.
|
|
81
|
+
return res.rows.map((r) => ({ ...r.data, seq: r.seq, at: r.at }));
|
|
82
|
+
}
|
|
83
|
+
async listCheckpoints(mission) {
|
|
84
|
+
const pool = await this.ensure();
|
|
85
|
+
const res = await pool.query(`SELECT seq, at, data FROM ${this.table} WHERE data->>'type' = 'checkpoint.advanced'`);
|
|
86
|
+
const events = res.rows.map((r) => ({ ...r.data, seq: r.seq, at: r.at }));
|
|
87
|
+
return reduceCheckpoints(events, mission);
|
|
88
|
+
}
|
|
89
|
+
async listPauses() {
|
|
90
|
+
const pool = await this.ensure();
|
|
91
|
+
const res = await pool.query(`SELECT seq, at, data FROM ${this.table}
|
|
92
|
+
WHERE data->>'type' IN ('pause.created', 'pause.resumed')`);
|
|
93
|
+
return res.rows.map((r) => ({ ...r.data, seq: r.seq, at: r.at }));
|
|
94
|
+
}
|
|
95
|
+
/** Drop all rows. Intended for test setup, not production use. */
|
|
96
|
+
async reset() {
|
|
97
|
+
const pool = await this.ensure();
|
|
98
|
+
await pool.query(`TRUNCATE ${this.table}`);
|
|
99
|
+
}
|
|
100
|
+
/** Release the connection pool. */
|
|
101
|
+
async close() {
|
|
102
|
+
if (this.pool) {
|
|
103
|
+
await this.pool.end();
|
|
104
|
+
this.pool = undefined;
|
|
105
|
+
this.ready = undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume helpers: load the current folded state of a run from its log.
|
|
3
|
+
*
|
|
4
|
+
* Resume is "read the log, fold it, continue" — never a second execution. A
|
|
5
|
+
* replaying caller checks {@link FoldedState.appliedEffects} before re-running
|
|
6
|
+
* a side effect, so an effect recorded before a crash is skipped on resume.
|
|
7
|
+
*/
|
|
8
|
+
import type { ExecutionLogStore } from './store.js';
|
|
9
|
+
import { type FoldedState } from './fold.js';
|
|
10
|
+
/** Read and fold an execution's log into its current state. */
|
|
11
|
+
export declare function loadState(store: ExecutionLogStore, executionId: string): Promise<FoldedState>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ExecutionEvent, StoredEvent } from './events.js';
|
|
2
|
+
import type { CheckpointRecord, ExecutionLogStore } from './store.js';
|
|
3
|
+
export declare class SqliteExecutionLog implements ExecutionLogStore {
|
|
4
|
+
private path;
|
|
5
|
+
private db?;
|
|
6
|
+
private insertStmt?;
|
|
7
|
+
private readStmt?;
|
|
8
|
+
private checkpointStmt?;
|
|
9
|
+
private pauseStmt?;
|
|
10
|
+
constructor(path?: string);
|
|
11
|
+
private ensureDb;
|
|
12
|
+
append(event: ExecutionEvent): Promise<StoredEvent>;
|
|
13
|
+
read(executionId: string): Promise<StoredEvent[]>;
|
|
14
|
+
listCheckpoints(mission?: string): Promise<CheckpointRecord[]>;
|
|
15
|
+
listPauses(): Promise<StoredEvent[]>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed execution log — durable, transactional state for single-process
|
|
3
|
+
* self-hosting (the "SQLite-of-durable-execution" lane).
|
|
4
|
+
*
|
|
5
|
+
* The `better-sqlite3` driver is an *optional* peer dependency: only installed
|
|
6
|
+
* by users who opt into the SQLite backend. It is imported lazily so the core
|
|
7
|
+
* package carries no native dependency, and a missing driver yields a clear,
|
|
8
|
+
* actionable error rather than an opaque module-resolution failure.
|
|
9
|
+
*
|
|
10
|
+
* Durability: WAL mode + `synchronous = NORMAL` gives fsync-backed commits that
|
|
11
|
+
* survive a process crash, and `seq` is assigned atomically inside the INSERT
|
|
12
|
+
* (guarded by a `(execution_id, seq)` primary key) so concurrent appenders
|
|
13
|
+
* cannot collide on a sequence number.
|
|
14
|
+
*/
|
|
15
|
+
import { mkdir } from 'node:fs/promises';
|
|
16
|
+
import { dirname } from 'node:path';
|
|
17
|
+
import { reduceCheckpoints } from './store.js';
|
|
18
|
+
export class SqliteExecutionLog {
|
|
19
|
+
path;
|
|
20
|
+
db;
|
|
21
|
+
insertStmt;
|
|
22
|
+
readStmt;
|
|
23
|
+
checkpointStmt;
|
|
24
|
+
pauseStmt;
|
|
25
|
+
constructor(path = '.reqon-data/execution-log.sqlite') {
|
|
26
|
+
this.path = path;
|
|
27
|
+
}
|
|
28
|
+
async ensureDb() {
|
|
29
|
+
if (this.db)
|
|
30
|
+
return this.db;
|
|
31
|
+
if (this.path !== ':memory:') {
|
|
32
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
let Database;
|
|
35
|
+
try {
|
|
36
|
+
// Non-literal specifier: keeps the driver an optional dependency with no
|
|
37
|
+
// compile-time type coupling (no @types/better-sqlite3 required).
|
|
38
|
+
const specifier = 'better-sqlite3';
|
|
39
|
+
const mod = (await import(specifier));
|
|
40
|
+
Database = mod.default;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error("SqliteExecutionLog requires the optional peer dependency 'better-sqlite3'. " +
|
|
44
|
+
'Install it with: npm install better-sqlite3');
|
|
45
|
+
}
|
|
46
|
+
const db = new Database(this.path);
|
|
47
|
+
db.pragma('journal_mode = WAL');
|
|
48
|
+
db.pragma('synchronous = NORMAL');
|
|
49
|
+
db.exec(`CREATE TABLE IF NOT EXISTS events (
|
|
50
|
+
execution_id TEXT NOT NULL,
|
|
51
|
+
seq INTEGER NOT NULL,
|
|
52
|
+
at TEXT NOT NULL,
|
|
53
|
+
data TEXT NOT NULL,
|
|
54
|
+
PRIMARY KEY (execution_id, seq)
|
|
55
|
+
)`);
|
|
56
|
+
// seq is computed inside the INSERT so it is assigned atomically, and
|
|
57
|
+
// RETURNING hands it back without a second round-trip.
|
|
58
|
+
this.insertStmt = db.prepare(`INSERT INTO events (execution_id, seq, at, data)
|
|
59
|
+
VALUES (
|
|
60
|
+
@id,
|
|
61
|
+
(SELECT COALESCE(MAX(seq) + 1, 0) FROM events WHERE execution_id = @id),
|
|
62
|
+
@at,
|
|
63
|
+
@data
|
|
64
|
+
)
|
|
65
|
+
RETURNING seq`);
|
|
66
|
+
this.readStmt = db.prepare(`SELECT seq, at, data FROM events WHERE execution_id = @id ORDER BY seq ASC`);
|
|
67
|
+
this.checkpointStmt = db.prepare(`SELECT execution_id, seq, at, data FROM events
|
|
68
|
+
WHERE json_extract(data, '$.type') = 'checkpoint.advanced'`);
|
|
69
|
+
this.pauseStmt = db.prepare(`SELECT execution_id, seq, at, data FROM events
|
|
70
|
+
WHERE json_extract(data, '$.type') IN ('pause.created', 'pause.resumed')`);
|
|
71
|
+
this.db = db;
|
|
72
|
+
return db;
|
|
73
|
+
}
|
|
74
|
+
async append(event) {
|
|
75
|
+
await this.ensureDb();
|
|
76
|
+
const at = new Date().toISOString();
|
|
77
|
+
const { executionId } = event;
|
|
78
|
+
const row = this.insertStmt.get({
|
|
79
|
+
id: executionId,
|
|
80
|
+
at,
|
|
81
|
+
data: JSON.stringify(event),
|
|
82
|
+
});
|
|
83
|
+
return { ...event, seq: row.seq, at };
|
|
84
|
+
}
|
|
85
|
+
async read(executionId) {
|
|
86
|
+
await this.ensureDb();
|
|
87
|
+
const rows = this.readStmt.all({ id: executionId });
|
|
88
|
+
return rows.map((r) => ({ ...JSON.parse(r.data), seq: r.seq, at: r.at }));
|
|
89
|
+
}
|
|
90
|
+
async listCheckpoints(mission) {
|
|
91
|
+
await this.ensureDb();
|
|
92
|
+
const rows = this.checkpointStmt.all({});
|
|
93
|
+
const events = rows.map((r) => ({ ...JSON.parse(r.data), seq: r.seq, at: r.at }));
|
|
94
|
+
return reduceCheckpoints(events, mission);
|
|
95
|
+
}
|
|
96
|
+
async listPauses() {
|
|
97
|
+
await this.ensureDb();
|
|
98
|
+
const rows = this.pauseStmt.all({});
|
|
99
|
+
return rows.map((r) => ({ ...JSON.parse(r.data), seq: r.seq, at: r.at }));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ExecutionEvent, StoredEvent } from './events.js';
|
|
2
|
+
/**
|
|
3
|
+
* The latest sync checkpoint for a key, materialised across executions — the
|
|
4
|
+
* read model behind log-backed incremental sync. Derived from the most recent
|
|
5
|
+
* `checkpoint.advanced` event for each key.
|
|
6
|
+
*/
|
|
7
|
+
export interface CheckpointRecord {
|
|
8
|
+
key: string;
|
|
9
|
+
syncedAt: string;
|
|
10
|
+
recordCount?: number;
|
|
11
|
+
cursor?: string;
|
|
12
|
+
mission?: string;
|
|
13
|
+
/** The execution that advanced this checkpoint. */
|
|
14
|
+
executionId: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ExecutionLogStore {
|
|
17
|
+
/** Append one event; returns it with its assigned seq and recorded timestamp. */
|
|
18
|
+
append(event: ExecutionEvent): Promise<StoredEvent>;
|
|
19
|
+
/** Read all events for an execution, in append order. */
|
|
20
|
+
read(executionId: string): Promise<StoredEvent[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Latest checkpoint per key across all executions in this store (optionally
|
|
23
|
+
* filtered by mission). The read model for log-backed incremental sync.
|
|
24
|
+
*/
|
|
25
|
+
listCheckpoints(mission?: string): Promise<CheckpointRecord[]>;
|
|
26
|
+
/**
|
|
27
|
+
* All pause lifecycle events (pause.created / pause.resumed) across every
|
|
28
|
+
* execution in this store, in no guaranteed order. The raw input the
|
|
29
|
+
* log-backed pause store folds into pause state.
|
|
30
|
+
*/
|
|
31
|
+
listPauses(): Promise<StoredEvent[]>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fold `checkpoint.advanced` events into the latest checkpoint per key. "Latest"
|
|
35
|
+
* is the furthest-advanced sync: ordered by `syncedAt` (so a checkpoint never
|
|
36
|
+
* moves backwards across runs — incremental sync stays monotonic), ties broken
|
|
37
|
+
* by the recording time `at` then `seq`. Shared by every store backend so the
|
|
38
|
+
* read-model semantics stay identical across them.
|
|
39
|
+
*/
|
|
40
|
+
export declare function reduceCheckpoints(events: StoredEvent[], mission?: string): CheckpointRecord[];
|
|
41
|
+
/** In-memory execution log — for tests and ephemeral runs. */
|
|
42
|
+
export declare class MemoryExecutionLog implements ExecutionLogStore {
|
|
43
|
+
private events;
|
|
44
|
+
append(event: ExecutionEvent): Promise<StoredEvent>;
|
|
45
|
+
read(executionId: string): Promise<StoredEvent[]>;
|
|
46
|
+
listCheckpoints(mission?: string): Promise<CheckpointRecord[]>;
|
|
47
|
+
listPauses(): Promise<StoredEvent[]>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Durable execution log: one append-only JSON-lines file per execution.
|
|
51
|
+
*
|
|
52
|
+
* Appends are O_APPEND single-line writes, so a crash mid-write can at worst
|
|
53
|
+
* leave a torn final line, which {@link read} skips. The log therefore survives
|
|
54
|
+
* a process restart — a resumed run reads its prior events back. Dev-grade: no
|
|
55
|
+
* atomic seq assignment or locking. For a transactional single-process backend
|
|
56
|
+
* use {@link SqliteExecutionLog}; Postgres (multi-node) is still to come.
|
|
57
|
+
*/
|
|
58
|
+
export declare class FileExecutionLog implements ExecutionLogStore {
|
|
59
|
+
private dir;
|
|
60
|
+
/** Cached next-seq per execution, lazily initialised from disk. */
|
|
61
|
+
private counts;
|
|
62
|
+
constructor(dir?: string);
|
|
63
|
+
private pathFor;
|
|
64
|
+
append(event: ExecutionEvent): Promise<StoredEvent>;
|
|
65
|
+
read(executionId: string): Promise<StoredEvent[]>;
|
|
66
|
+
listCheckpoints(mission?: string): Promise<CheckpointRecord[]>;
|
|
67
|
+
listPauses(): Promise<StoredEvent[]>;
|
|
68
|
+
/** Read and parse every execution's events in this directory. */
|
|
69
|
+
private readAll;
|
|
70
|
+
private readRaw;
|
|
71
|
+
private parse;
|
|
72
|
+
}
|