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
package/dist/interpreter/http.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { parseRateLimitHeaders } from '../auth/rate-limiter.js';
|
|
2
2
|
import { CircuitBreakerError } from '../auth/circuit-breaker.js';
|
|
3
3
|
import { sleep } from '../utils/async.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import { HTTP_RETRY_DEFAULTS } from '../config/index.js';
|
|
5
|
+
import { FetchError } from '../errors/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Parse a `Retry-After` header into a delay in ms, clamped to `maxDelayMs`.
|
|
8
|
+
* The header may be delta-seconds (`120`) or an HTTP-date; a date in the past or
|
|
9
|
+
* an unparseable value yields 0 and `undefined` respectively. Clamping stops a
|
|
10
|
+
* hostile/broken server from pinning the client for hours, and the date branch
|
|
11
|
+
* stops `parseInt` from turning a date into `NaN` → `sleep(NaN)` → a tight loop.
|
|
12
|
+
*/
|
|
13
|
+
export function parseRetryAfterMs(value, maxDelayMs) {
|
|
14
|
+
if (!value)
|
|
15
|
+
return undefined;
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
const seconds = Number(trimmed);
|
|
18
|
+
let ms;
|
|
19
|
+
if (trimmed !== '' && Number.isFinite(seconds)) {
|
|
20
|
+
ms = seconds * 1000;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const when = Date.parse(trimmed);
|
|
24
|
+
if (Number.isNaN(when))
|
|
25
|
+
return undefined;
|
|
26
|
+
ms = when - Date.now();
|
|
27
|
+
}
|
|
28
|
+
return Math.min(Math.max(ms, 0), maxDelayMs);
|
|
29
|
+
}
|
|
30
|
+
/** Maximum buffered response body size (10 MiB) before the request is rejected. */
|
|
31
|
+
const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
32
|
+
/** HTTP methods that are safe to retry automatically (idempotent per RFC 7231). */
|
|
33
|
+
const IDEMPOTENT_METHODS = new Set(['GET', 'PUT', 'DELETE']);
|
|
11
34
|
export class HttpClient {
|
|
12
35
|
config;
|
|
13
36
|
constructor(config) {
|
|
@@ -15,17 +38,29 @@ export class HttpClient {
|
|
|
15
38
|
}
|
|
16
39
|
async request(req, retry) {
|
|
17
40
|
const url = this.buildUrl(req.path, req.query);
|
|
18
|
-
const
|
|
41
|
+
const requestHeaders = { ...req.headers };
|
|
42
|
+
if (req.idempotencyKey) {
|
|
43
|
+
requestHeaders['Idempotency-Key'] = req.idempotencyKey;
|
|
44
|
+
}
|
|
45
|
+
const headers = await this.buildHeaders(requestHeaders);
|
|
19
46
|
const fetchOptions = {
|
|
20
47
|
method: req.method,
|
|
21
48
|
headers,
|
|
22
49
|
body: req.body ? JSON.stringify(req.body) : undefined,
|
|
23
50
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
51
|
+
// Auto-retry only idempotent verbs, or any verb carrying an idempotency key.
|
|
52
|
+
// A blind retry of POST/PATCH can re-send a write the server already
|
|
53
|
+
// committed (timeout / dropped socket after commit), duplicating data.
|
|
54
|
+
const retriable = IDEMPOTENT_METHODS.has(req.method) || Boolean(req.idempotencyKey);
|
|
55
|
+
const maxAttempts = retry?.maxAttempts ?? HTTP_RETRY_DEFAULTS.MAX_ATTEMPTS;
|
|
56
|
+
const backoff = retry?.backoff ?? HTTP_RETRY_DEFAULTS.BACKOFF;
|
|
57
|
+
const initialDelay = retry?.initialDelay ?? HTTP_RETRY_DEFAULTS.INITIAL_DELAY_MS;
|
|
58
|
+
const maxDelay = retry?.maxDelay ?? HTTP_RETRY_DEFAULTS.MAX_DELAY_MS;
|
|
59
|
+
const timeout = retry?.timeout ?? this.config.timeout ?? HTTP_RETRY_DEFAULTS.TIMEOUT_MS;
|
|
28
60
|
let lastError = null;
|
|
61
|
+
// Refresh the token at most once per request to avoid burning a fresh
|
|
62
|
+
// rotating refresh token on every 401 retry attempt.
|
|
63
|
+
let hasRefreshed = false;
|
|
29
64
|
// Check circuit breaker before attempting requests
|
|
30
65
|
if (this.config.circuitBreaker && this.config.sourceName) {
|
|
31
66
|
// This will throw CircuitBreakerError if circuit is open
|
|
@@ -37,14 +72,16 @@ export class HttpClient {
|
|
|
37
72
|
if (attempt > 1 && this.config.circuitBreaker && this.config.sourceName) {
|
|
38
73
|
if (!this.config.circuitBreaker.canProceed(this.config.sourceName, req.path)) {
|
|
39
74
|
// Circuit opened during retries, fail fast
|
|
40
|
-
throw new CircuitBreakerError(this.config.sourceName, req.path, this.config.circuitBreaker
|
|
75
|
+
throw new CircuitBreakerError(this.config.sourceName, req.path, this.config.circuitBreaker
|
|
76
|
+
.getStatus(this.config.sourceName, req.path)
|
|
77
|
+
.nextAttemptTime?.getTime() ?? 0 - Date.now());
|
|
41
78
|
}
|
|
42
79
|
}
|
|
43
80
|
// Wait for rate limit capacity if we have a rate limiter
|
|
44
81
|
if (this.config.rateLimiter && this.config.sourceName) {
|
|
45
82
|
await this.config.rateLimiter.waitForCapacity(this.config.sourceName, req.path);
|
|
46
83
|
}
|
|
47
|
-
const response = await
|
|
84
|
+
const response = await this.fetchWithTimeout(url, fetchOptions, timeout, req.method);
|
|
48
85
|
// Extract and record rate limit info from response headers
|
|
49
86
|
const responseHeaders = {};
|
|
50
87
|
response.headers.forEach((value, key) => {
|
|
@@ -54,17 +91,19 @@ export class HttpClient {
|
|
|
54
91
|
const rateLimitInfo = parseRateLimitHeaders(responseHeaders);
|
|
55
92
|
// Add retry-after from 429 responses
|
|
56
93
|
if (response.status === 429) {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
rateLimitInfo.retryAfter =
|
|
94
|
+
const ms = parseRetryAfterMs(response.headers.get('Retry-After'), maxDelay);
|
|
95
|
+
if (ms !== undefined) {
|
|
96
|
+
rateLimitInfo.retryAfter = Math.ceil(ms / 1000);
|
|
60
97
|
}
|
|
61
98
|
}
|
|
62
99
|
this.config.rateLimiter.recordResponse(this.config.sourceName, rateLimitInfo, req.path);
|
|
63
100
|
}
|
|
64
|
-
// Handle rate limiting
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
// Handle rate limiting: retry only while attempts remain. A 429 on the
|
|
102
|
+
// final attempt falls through to the >=400 handler below, which throws a
|
|
103
|
+
// FetchError carrying status 429 — not the generic "all retries" error.
|
|
104
|
+
if (response.status === 429 && retriable && attempt < maxAttempts) {
|
|
105
|
+
const delay = parseRetryAfterMs(response.headers.get('Retry-After'), maxDelay) ??
|
|
106
|
+
this.calculateDelay(attempt, backoff, initialDelay, maxDelay);
|
|
68
107
|
await sleep(delay);
|
|
69
108
|
continue;
|
|
70
109
|
}
|
|
@@ -74,21 +113,35 @@ export class HttpClient {
|
|
|
74
113
|
if (this.config.circuitBreaker && this.config.sourceName) {
|
|
75
114
|
this.config.circuitBreaker.recordFailure(this.config.sourceName, req.path, response.status);
|
|
76
115
|
}
|
|
77
|
-
if (attempt < maxAttempts) {
|
|
116
|
+
if (retriable && attempt < maxAttempts) {
|
|
78
117
|
const delay = this.calculateDelay(attempt, backoff, initialDelay, maxDelay);
|
|
79
118
|
await sleep(delay);
|
|
80
119
|
continue;
|
|
81
120
|
}
|
|
121
|
+
// Non-idempotent without an idempotency key: do not re-send a write
|
|
122
|
+
// that the server may have already committed. Return the 5xx instead.
|
|
82
123
|
}
|
|
83
|
-
// Handle 401 - try token refresh
|
|
84
|
-
if (response.status === 401 &&
|
|
124
|
+
// Handle 401 - try token refresh (at most once per request)
|
|
125
|
+
if (response.status === 401 &&
|
|
126
|
+
this.config.auth?.refreshToken &&
|
|
127
|
+
!hasRefreshed &&
|
|
128
|
+
attempt < maxAttempts) {
|
|
129
|
+
hasRefreshed = true;
|
|
85
130
|
await this.config.auth.refreshToken();
|
|
86
|
-
// Rebuild headers with new token
|
|
87
|
-
const newHeaders = await this.buildHeaders(
|
|
131
|
+
// Rebuild headers with new token (preserving the idempotency key)
|
|
132
|
+
const newHeaders = await this.buildHeaders(requestHeaders);
|
|
88
133
|
fetchOptions.headers = newHeaders;
|
|
89
134
|
continue;
|
|
90
135
|
}
|
|
91
|
-
|
|
136
|
+
// Any remaining non-2xx/3xx response is an error: a 4xx (other than the
|
|
137
|
+
// 429/401-refresh cases handled above) or a 5xx that exhausted retries.
|
|
138
|
+
// Returning it as `data` would let map/store persist an API error body.
|
|
139
|
+
if (response.status >= 400) {
|
|
140
|
+
const snippet = await this.safeReadSnippet(response);
|
|
141
|
+
throw new FetchError(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ''}` +
|
|
142
|
+
(snippet ? `: ${snippet}` : ''), { url, method: req.method, statusCode: response.status });
|
|
143
|
+
}
|
|
144
|
+
const data = await this.parseResponseBody(response, url, req.method);
|
|
92
145
|
// Record success in circuit breaker
|
|
93
146
|
if (this.config.circuitBreaker && this.config.sourceName && response.status < 500) {
|
|
94
147
|
this.config.circuitBreaker.recordSuccess(this.config.sourceName, req.path);
|
|
@@ -105,10 +158,21 @@ export class HttpClient {
|
|
|
105
158
|
if (error instanceof CircuitBreakerError) {
|
|
106
159
|
throw error;
|
|
107
160
|
}
|
|
161
|
+
// HTTP-status errors (4xx, exhausted 5xx) and body parse/size errors
|
|
162
|
+
// are definitive — don't burn retries re-fetching them.
|
|
163
|
+
if (error instanceof FetchError && error.statusCode !== undefined) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
108
166
|
// Record network errors in circuit breaker
|
|
109
167
|
if (this.config.circuitBreaker && this.config.sourceName) {
|
|
110
168
|
this.config.circuitBreaker.recordFailure(this.config.sourceName, req.path, undefined, true);
|
|
111
169
|
}
|
|
170
|
+
// A network error on a non-idempotent write is ambiguous: the request
|
|
171
|
+
// may have reached the server and committed before the socket dropped.
|
|
172
|
+
// Surface the error rather than blindly re-sending a duplicate write.
|
|
173
|
+
if (!retriable) {
|
|
174
|
+
throw lastError;
|
|
175
|
+
}
|
|
112
176
|
if (attempt < maxAttempts) {
|
|
113
177
|
const delay = this.calculateDelay(attempt, backoff, initialDelay, maxDelay);
|
|
114
178
|
await sleep(delay);
|
|
@@ -117,6 +181,100 @@ export class HttpClient {
|
|
|
117
181
|
}
|
|
118
182
|
throw lastError ?? new Error('Request failed after all retries');
|
|
119
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Run a fetch with a per-attempt timeout. Aborts the request (freeing the
|
|
186
|
+
* connection and rate-limiter slot) if it exceeds `timeoutMs`, surfacing a
|
|
187
|
+
* retryable FetchError rather than hanging forever.
|
|
188
|
+
*/
|
|
189
|
+
async fetchWithTimeout(url, options, timeoutMs, method) {
|
|
190
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
191
|
+
return fetch(url, options);
|
|
192
|
+
}
|
|
193
|
+
const controller = new AbortController();
|
|
194
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
195
|
+
try {
|
|
196
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error.name === 'AbortError') {
|
|
200
|
+
throw new FetchError(`Request timed out after ${timeoutMs}ms`, {
|
|
201
|
+
url,
|
|
202
|
+
method,
|
|
203
|
+
cause: error,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Parse a successful (2xx) response body. Handles empty/204 responses,
|
|
214
|
+
* returns non-JSON content as raw text, and caps the buffered size.
|
|
215
|
+
*/
|
|
216
|
+
async parseResponseBody(response, url, method) {
|
|
217
|
+
// No-content responses have no body to parse.
|
|
218
|
+
if (response.status === 204 || response.status === 205) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const text = await this.readCappedText(response, url, method);
|
|
222
|
+
if (text.trim() === '') {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
226
|
+
const looksJson = contentType === '' || contentType.includes('json');
|
|
227
|
+
try {
|
|
228
|
+
return JSON.parse(text);
|
|
229
|
+
}
|
|
230
|
+
catch (parseError) {
|
|
231
|
+
// A non-JSON content-type (text/html, text/plain, …) is returned as-is
|
|
232
|
+
// rather than throwing — only fail when the body claimed to be JSON.
|
|
233
|
+
if (!looksJson) {
|
|
234
|
+
return text;
|
|
235
|
+
}
|
|
236
|
+
throw new FetchError(`Failed to parse JSON response (content-type '${contentType}'): ${parseError.message}`, { url, method, statusCode: response.status, cause: parseError });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Read a response body to text, rejecting once it exceeds MAX_RESPONSE_BYTES. */
|
|
240
|
+
async readCappedText(response, url, method) {
|
|
241
|
+
const body = response.body;
|
|
242
|
+
if (!body) {
|
|
243
|
+
return await response.text();
|
|
244
|
+
}
|
|
245
|
+
const reader = body.getReader();
|
|
246
|
+
const chunks = [];
|
|
247
|
+
let size = 0;
|
|
248
|
+
for (;;) {
|
|
249
|
+
const { done, value } = await reader.read();
|
|
250
|
+
if (done)
|
|
251
|
+
break;
|
|
252
|
+
if (value) {
|
|
253
|
+
size += value.byteLength;
|
|
254
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
255
|
+
await reader.cancel();
|
|
256
|
+
throw new FetchError(`Response body exceeds ${MAX_RESPONSE_BYTES} bytes`, {
|
|
257
|
+
url,
|
|
258
|
+
method,
|
|
259
|
+
statusCode: response.status,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
chunks.push(Buffer.from(value));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
266
|
+
}
|
|
267
|
+
/** Read a short snippet of a body for an error message (best-effort). */
|
|
268
|
+
async safeReadSnippet(response) {
|
|
269
|
+
try {
|
|
270
|
+
const text = await response.text();
|
|
271
|
+
const trimmed = text.trim();
|
|
272
|
+
return trimmed.length > 200 ? `${trimmed.slice(0, 200)}…` : trimmed;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return '';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
120
278
|
buildUrl(path, query) {
|
|
121
279
|
const base = this.config.baseUrl.replace(/\/$/, '');
|
|
122
280
|
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
|
@@ -176,6 +334,8 @@ export class OAuth2AuthProvider {
|
|
|
176
334
|
tokenEndpoint;
|
|
177
335
|
clientId;
|
|
178
336
|
clientSecret;
|
|
337
|
+
/** Single-flight guard: coalesces concurrent refreshes into one in-flight request */
|
|
338
|
+
refreshPromise = null;
|
|
179
339
|
constructor(config) {
|
|
180
340
|
this.accessToken = config.accessToken;
|
|
181
341
|
this.refreshTokenValue = config.refreshToken;
|
|
@@ -187,6 +347,21 @@ export class OAuth2AuthProvider {
|
|
|
187
347
|
return this.accessToken;
|
|
188
348
|
}
|
|
189
349
|
async refreshToken() {
|
|
350
|
+
// Deduplicate concurrent refresh requests. With rotating refresh tokens,
|
|
351
|
+
// letting many in-flight requests each POST to the token endpoint would
|
|
352
|
+
// 400 invalid_grant all but one and kill the session.
|
|
353
|
+
if (this.refreshPromise) {
|
|
354
|
+
return this.refreshPromise;
|
|
355
|
+
}
|
|
356
|
+
this.refreshPromise = this.doRefresh();
|
|
357
|
+
try {
|
|
358
|
+
return await this.refreshPromise;
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
this.refreshPromise = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async doRefresh() {
|
|
190
365
|
if (!this.refreshTokenValue || !this.tokenEndpoint) {
|
|
191
366
|
throw new Error('Cannot refresh token: missing refresh token or endpoint');
|
|
192
367
|
}
|
|
@@ -200,7 +375,7 @@ export class OAuth2AuthProvider {
|
|
|
200
375
|
client_secret: this.clientSecret ?? '',
|
|
201
376
|
}),
|
|
202
377
|
});
|
|
203
|
-
const data = await response.json();
|
|
378
|
+
const data = (await response.json());
|
|
204
379
|
this.accessToken = data.access_token;
|
|
205
380
|
if (data.refresh_token) {
|
|
206
381
|
this.refreshTokenValue = data.refresh_token;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { MissionExecutor, type ExecutionResult, type ExecutionError, type ExecutorConfig, type ProgressCallbacks, type ExecutionStartEvent, type ExecutionCompleteEvent, type StageStartEvent, type StageCompleteEvent, } from './executor.js';
|
|
2
|
-
export { HttpClient, BearerAuthProvider, OAuth2AuthProvider, type HttpClientConfig, type AuthProvider } from './http.js';
|
|
3
|
-
export {
|
|
1
|
+
export { MissionExecutor, type ExecutionResult, type ExecutionError, type ExecutorConfig, type ProgressCallbacks, type ExecutionStartEvent, type ExecutionCompleteEvent, type StageStartEvent, type StageCompleteEvent, type AuthConfig, } from './executor.js';
|
|
2
|
+
export { HttpClient, BearerAuthProvider, OAuth2AuthProvider, type HttpClientConfig, type AuthProvider, } from './http.js';
|
|
3
|
+
export { SourceManager, type SourceManagerConfig, type SourceManagerDeps, } from './source-manager.js';
|
|
4
|
+
export { StoreManager, type StoreManagerConfig } from './store-manager.js';
|
|
5
|
+
export { createContext, childContext, getVariable, setVariable, type ExecutionContext, } from './context.js';
|
|
4
6
|
export { evaluate, evaluateToString, interpolatePath } from './evaluator.js';
|
|
5
7
|
export { FetchHandler, type FetchHandlerDeps, type FetchResult } from './fetch-handler.js';
|
|
6
8
|
export { type PaginationStrategy, type PaginationContext, type PageResult, createPaginationStrategy, OffsetPaginationStrategy, PageNumberPaginationStrategy, CursorPaginationStrategy, } from './pagination.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { MissionExecutor, } from './executor.js';
|
|
2
|
-
export { HttpClient, BearerAuthProvider, OAuth2AuthProvider } from './http.js';
|
|
3
|
-
export {
|
|
2
|
+
export { HttpClient, BearerAuthProvider, OAuth2AuthProvider, } from './http.js';
|
|
3
|
+
export { SourceManager, } from './source-manager.js';
|
|
4
|
+
export { StoreManager } from './store-manager.js';
|
|
5
|
+
export { createContext, childContext, getVariable, setVariable, } from './context.js';
|
|
4
6
|
export { evaluate, evaluateToString, interpolatePath } from './evaluator.js';
|
|
5
7
|
export { FetchHandler } from './fetch-handler.js';
|
|
6
8
|
export { createPaginationStrategy, OffsetPaginationStrategy, PageNumberPaginationStrategy, CursorPaginationStrategy, } from './pagination.js';
|
|
@@ -23,8 +23,13 @@ export interface PaginationStrategy {
|
|
|
23
23
|
buildQuery(ctx: PaginationContext): Record<string, string>;
|
|
24
24
|
/** Extract results and determine if more pages exist */
|
|
25
25
|
extractResults(response: unknown, ctx: PaginationContext): PageResult;
|
|
26
|
+
/** Clear any cached state (for reuse across different responses) */
|
|
27
|
+
clearCache?(): void;
|
|
26
28
|
}
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Use strategy.clearCache() instead. Kept for backward compatibility.
|
|
31
|
+
* Note: This now only clears the global compatibility cache, not instance caches.
|
|
32
|
+
*/
|
|
28
33
|
export declare function clearPaginationCache(): void;
|
|
29
34
|
/**
|
|
30
35
|
* Offset-based pagination (e.g., offset=100, offset=200)
|
|
@@ -32,9 +37,11 @@ export declare function clearPaginationCache(): void;
|
|
|
32
37
|
export declare class OffsetPaginationStrategy implements PaginationStrategy {
|
|
33
38
|
private config;
|
|
34
39
|
private cacheKey;
|
|
40
|
+
private cache;
|
|
35
41
|
constructor(config: PaginationConfig);
|
|
36
42
|
buildQuery(ctx: PaginationContext): Record<string, string>;
|
|
37
43
|
extractResults(response: unknown, ctx: PaginationContext): PageResult;
|
|
44
|
+
clearCache(): void;
|
|
38
45
|
}
|
|
39
46
|
/**
|
|
40
47
|
* Page number pagination (e.g., page=1, page=2)
|
|
@@ -42,20 +49,22 @@ export declare class OffsetPaginationStrategy implements PaginationStrategy {
|
|
|
42
49
|
export declare class PageNumberPaginationStrategy implements PaginationStrategy {
|
|
43
50
|
private config;
|
|
44
51
|
private cacheKey;
|
|
52
|
+
private cache;
|
|
45
53
|
constructor(config: PaginationConfig);
|
|
46
54
|
buildQuery(ctx: PaginationContext): Record<string, string>;
|
|
47
55
|
extractResults(response: unknown, ctx: PaginationContext): PageResult;
|
|
56
|
+
clearCache(): void;
|
|
48
57
|
}
|
|
49
58
|
/**
|
|
50
59
|
* Cursor-based pagination (e.g., cursor=abc123)
|
|
51
60
|
*/
|
|
52
61
|
export declare class CursorPaginationStrategy implements PaginationStrategy {
|
|
53
62
|
private config;
|
|
54
|
-
private cacheKey;
|
|
55
63
|
private cachedArrayField;
|
|
56
64
|
constructor(config: PaginationConfig);
|
|
57
65
|
buildQuery(ctx: PaginationContext): Record<string, string>;
|
|
58
|
-
extractResults(response: unknown,
|
|
66
|
+
extractResults(response: unknown, _ctx: PaginationContext): PageResult;
|
|
67
|
+
clearCache(): void;
|
|
59
68
|
}
|
|
60
69
|
/**
|
|
61
70
|
* Create the appropriate pagination strategy based on config
|
|
@@ -1,40 +1,91 @@
|
|
|
1
1
|
import { extractNestedValue } from '../utils/path.js';
|
|
2
|
-
/**
|
|
3
|
-
const
|
|
2
|
+
/** Default TTL for cache entries (5 minutes) */
|
|
3
|
+
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
4
|
+
/** Maximum number of cache entries before cleanup */
|
|
5
|
+
const MAX_CACHE_ENTRIES = 100;
|
|
6
|
+
/**
|
|
7
|
+
* Instance-level cache for array field discovery.
|
|
8
|
+
* Each pagination strategy instance has its own cache to avoid global state pollution.
|
|
9
|
+
*/
|
|
10
|
+
class ArrayFieldCache {
|
|
11
|
+
cache = new Map();
|
|
12
|
+
ttlMs;
|
|
13
|
+
constructor(ttlMs = DEFAULT_CACHE_TTL_MS) {
|
|
14
|
+
this.ttlMs = ttlMs;
|
|
15
|
+
}
|
|
16
|
+
get(key) {
|
|
17
|
+
const entry = this.cache.get(key);
|
|
18
|
+
if (!entry) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
// Check if expired
|
|
22
|
+
if (Date.now() > entry.expiresAt) {
|
|
23
|
+
this.cache.delete(key);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return entry.value;
|
|
27
|
+
}
|
|
28
|
+
set(key, value) {
|
|
29
|
+
// Cleanup if cache is getting too large
|
|
30
|
+
if (this.cache.size >= MAX_CACHE_ENTRIES) {
|
|
31
|
+
this.cleanup();
|
|
32
|
+
}
|
|
33
|
+
this.cache.set(key, {
|
|
34
|
+
value,
|
|
35
|
+
expiresAt: Date.now() + this.ttlMs,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
clear() {
|
|
39
|
+
this.cache.clear();
|
|
40
|
+
}
|
|
41
|
+
cleanup() {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
for (const [key, entry] of this.cache) {
|
|
44
|
+
if (now > entry.expiresAt) {
|
|
45
|
+
this.cache.delete(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// If still too large, remove oldest half
|
|
49
|
+
if (this.cache.size >= MAX_CACHE_ENTRIES) {
|
|
50
|
+
const entries = Array.from(this.cache.entries());
|
|
51
|
+
entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
52
|
+
const toRemove = entries.slice(0, Math.floor(entries.length / 2));
|
|
53
|
+
for (const [key] of toRemove) {
|
|
54
|
+
this.cache.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
4
59
|
/**
|
|
5
60
|
* Extract items array from response and determine if more pages exist
|
|
6
61
|
* Shared utility for offset and page-based pagination strategies
|
|
7
|
-
* Caches the discovered array field for subsequent pages
|
|
8
62
|
*/
|
|
9
|
-
function extractItemsFromResponse(response, pageSize, cacheKey) {
|
|
63
|
+
function extractItemsFromResponse(response, pageSize, cacheKey, cache) {
|
|
10
64
|
if (!response || typeof response !== 'object') {
|
|
11
65
|
return { items: [], hasMore: false };
|
|
12
66
|
}
|
|
13
67
|
const data = response;
|
|
14
|
-
// Check cache first
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (cachedField
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
};
|
|
27
|
-
}
|
|
68
|
+
// Check cache first
|
|
69
|
+
const cachedField = cache.get(cacheKey);
|
|
70
|
+
if (cachedField !== undefined) {
|
|
71
|
+
if (cachedField === null) {
|
|
72
|
+
return { items: [], hasMore: false };
|
|
73
|
+
}
|
|
74
|
+
const items = data[cachedField];
|
|
75
|
+
if (Array.isArray(items)) {
|
|
76
|
+
return {
|
|
77
|
+
items,
|
|
78
|
+
hasMore: items.length >= pageSize,
|
|
79
|
+
};
|
|
28
80
|
}
|
|
81
|
+
// Cached field no longer valid, clear it
|
|
82
|
+
cache.clear();
|
|
29
83
|
}
|
|
30
84
|
// Search for array field
|
|
31
85
|
for (const key of Object.keys(data)) {
|
|
32
86
|
if (Array.isArray(data[key])) {
|
|
33
87
|
const items = data[key];
|
|
34
|
-
|
|
35
|
-
if (cacheKey) {
|
|
36
|
-
arrayFieldCache.set(cacheKey, key);
|
|
37
|
-
}
|
|
88
|
+
cache.set(cacheKey, key);
|
|
38
89
|
return {
|
|
39
90
|
items,
|
|
40
91
|
hasMore: items.length >= pageSize,
|
|
@@ -42,14 +93,16 @@ function extractItemsFromResponse(response, pageSize, cacheKey) {
|
|
|
42
93
|
}
|
|
43
94
|
}
|
|
44
95
|
// Cache negative result
|
|
45
|
-
|
|
46
|
-
arrayFieldCache.set(cacheKey, null);
|
|
47
|
-
}
|
|
96
|
+
cache.set(cacheKey, null);
|
|
48
97
|
return { items: [], hasMore: false };
|
|
49
98
|
}
|
|
50
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* @deprecated Use strategy.clearCache() instead. Kept for backward compatibility.
|
|
101
|
+
* Note: This now only clears the global compatibility cache, not instance caches.
|
|
102
|
+
*/
|
|
51
103
|
export function clearPaginationCache() {
|
|
52
|
-
|
|
104
|
+
// No-op - caches are now instance-level
|
|
105
|
+
// Individual strategies should call clearCache() if needed
|
|
53
106
|
}
|
|
54
107
|
/**
|
|
55
108
|
* Offset-based pagination (e.g., offset=100, offset=200)
|
|
@@ -57,9 +110,11 @@ export function clearPaginationCache() {
|
|
|
57
110
|
export class OffsetPaginationStrategy {
|
|
58
111
|
config;
|
|
59
112
|
cacheKey;
|
|
113
|
+
cache;
|
|
60
114
|
constructor(config) {
|
|
61
115
|
this.config = config;
|
|
62
116
|
this.cacheKey = `offset:${config.param}`;
|
|
117
|
+
this.cache = new ArrayFieldCache();
|
|
63
118
|
}
|
|
64
119
|
buildQuery(ctx) {
|
|
65
120
|
return {
|
|
@@ -67,7 +122,10 @@ export class OffsetPaginationStrategy {
|
|
|
67
122
|
};
|
|
68
123
|
}
|
|
69
124
|
extractResults(response, ctx) {
|
|
70
|
-
return extractItemsFromResponse(response, ctx.pageSize, this.cacheKey);
|
|
125
|
+
return extractItemsFromResponse(response, ctx.pageSize, this.cacheKey, this.cache);
|
|
126
|
+
}
|
|
127
|
+
clearCache() {
|
|
128
|
+
this.cache.clear();
|
|
71
129
|
}
|
|
72
130
|
}
|
|
73
131
|
/**
|
|
@@ -76,9 +134,11 @@ export class OffsetPaginationStrategy {
|
|
|
76
134
|
export class PageNumberPaginationStrategy {
|
|
77
135
|
config;
|
|
78
136
|
cacheKey;
|
|
137
|
+
cache;
|
|
79
138
|
constructor(config) {
|
|
80
139
|
this.config = config;
|
|
81
140
|
this.cacheKey = `page:${config.param}`;
|
|
141
|
+
this.cache = new ArrayFieldCache();
|
|
82
142
|
}
|
|
83
143
|
buildQuery(ctx) {
|
|
84
144
|
return {
|
|
@@ -86,7 +146,10 @@ export class PageNumberPaginationStrategy {
|
|
|
86
146
|
};
|
|
87
147
|
}
|
|
88
148
|
extractResults(response, ctx) {
|
|
89
|
-
return extractItemsFromResponse(response, ctx.pageSize, this.cacheKey);
|
|
149
|
+
return extractItemsFromResponse(response, ctx.pageSize, this.cacheKey, this.cache);
|
|
150
|
+
}
|
|
151
|
+
clearCache() {
|
|
152
|
+
this.cache.clear();
|
|
90
153
|
}
|
|
91
154
|
}
|
|
92
155
|
/**
|
|
@@ -94,11 +157,9 @@ export class PageNumberPaginationStrategy {
|
|
|
94
157
|
*/
|
|
95
158
|
export class CursorPaginationStrategy {
|
|
96
159
|
config;
|
|
97
|
-
cacheKey;
|
|
98
160
|
cachedArrayField = null;
|
|
99
161
|
constructor(config) {
|
|
100
162
|
this.config = config;
|
|
101
|
-
this.cacheKey = `cursor:${config.param}`;
|
|
102
163
|
}
|
|
103
164
|
buildQuery(ctx) {
|
|
104
165
|
if (ctx.cursor) {
|
|
@@ -106,7 +167,7 @@ export class CursorPaginationStrategy {
|
|
|
106
167
|
}
|
|
107
168
|
return {};
|
|
108
169
|
}
|
|
109
|
-
extractResults(response,
|
|
170
|
+
extractResults(response, _ctx) {
|
|
110
171
|
if (!response || typeof response !== 'object') {
|
|
111
172
|
return { items: [], hasMore: false };
|
|
112
173
|
}
|
|
@@ -137,11 +198,20 @@ export class CursorPaginationStrategy {
|
|
|
137
198
|
nextCursor,
|
|
138
199
|
};
|
|
139
200
|
}
|
|
201
|
+
clearCache() {
|
|
202
|
+
this.cachedArrayField = null;
|
|
203
|
+
}
|
|
140
204
|
}
|
|
141
205
|
/**
|
|
142
206
|
* Create the appropriate pagination strategy based on config
|
|
143
207
|
*/
|
|
144
208
|
export function createPaginationStrategy(config) {
|
|
209
|
+
// A non-positive page size makes every page request the same offset and makes
|
|
210
|
+
// the `items.length >= pageSize` termination test always true, so pagination
|
|
211
|
+
// re-fetches page one until it hits the page cap. Reject it up front.
|
|
212
|
+
if (!Number.isFinite(config.pageSize) || config.pageSize < 1) {
|
|
213
|
+
throw new Error(`Pagination page size must be a positive integer, got ${config.pageSize}`);
|
|
214
|
+
}
|
|
145
215
|
switch (config.type) {
|
|
146
216
|
case 'offset':
|
|
147
217
|
return new OffsetPaginationStrategy(config);
|
|
@@ -55,3 +55,11 @@ export declare class NoMatchError extends Error {
|
|
|
55
55
|
export declare class AbortError extends Error {
|
|
56
56
|
constructor(message?: string);
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Signal thrown when execution is paused by external control or pause step
|
|
60
|
+
*/
|
|
61
|
+
export declare class PauseSignal extends Error {
|
|
62
|
+
/** Pause ID if created by a pause step */
|
|
63
|
+
pauseId?: string;
|
|
64
|
+
constructor(pauseId?: string);
|
|
65
|
+
}
|
|
@@ -71,3 +71,15 @@ export class AbortError extends Error {
|
|
|
71
71
|
this.name = 'AbortError';
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Signal thrown when execution is paused by external control or pause step
|
|
76
|
+
*/
|
|
77
|
+
export class PauseSignal extends Error {
|
|
78
|
+
/** Pause ID if created by a pause step */
|
|
79
|
+
pauseId;
|
|
80
|
+
constructor(pauseId) {
|
|
81
|
+
super(pauseId ? `Execution paused (${pauseId})` : 'Execution paused');
|
|
82
|
+
this.name = 'PauseSignal';
|
|
83
|
+
this.pauseId = pauseId;
|
|
84
|
+
}
|
|
85
|
+
}
|