reqon-dsl 0.2.0 → 0.3.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 +22 -0
- package/dist/ast/nodes.d.ts +83 -4
- package/dist/ast/nodes.js +14 -0
- package/dist/auth/circuit-breaker.js +7 -6
- package/dist/auth/rate-limiter.d.ts +4 -0
- package/dist/auth/rate-limiter.js +9 -16
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +84 -4
- package/dist/config/constants.d.ts +141 -0
- package/dist/config/constants.js +128 -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 +88 -0
- package/dist/control/server.js +238 -0
- package/dist/control/types.d.ts +55 -0
- package/dist/control/types.js +7 -0
- package/dist/debug/cli-debugger.d.ts +17 -0
- package/dist/debug/cli-debugger.js +180 -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/index.d.ts +21 -1
- package/dist/index.js +33 -2
- package/dist/interpreter/context.d.ts +14 -0
- package/dist/interpreter/context.js +15 -0
- package/dist/interpreter/evaluator.d.ts +63 -1
- package/dist/interpreter/evaluator.js +186 -39
- package/dist/interpreter/executor.d.ts +70 -14
- package/dist/interpreter/executor.js +503 -174
- package/dist/interpreter/fetch-handler.d.ts +9 -0
- package/dist/interpreter/fetch-handler.js +133 -24
- package/dist/interpreter/http.d.ts +5 -0
- package/dist/interpreter/http.js +26 -12
- package/dist/interpreter/index.d.ts +3 -1
- package/dist/interpreter/index.js +2 -0
- package/dist/interpreter/pagination.d.ts +11 -2
- package/dist/interpreter/pagination.js +95 -31
- 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 +13 -0
- package/dist/interpreter/step-handlers/for-handler.js +71 -4
- 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 +43 -16
- 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 +11 -1
- package/dist/interpreter/step-handlers/store-handler.js +45 -13
- 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 +4 -2
- package/dist/interpreter/step-handlers/webhook-handler.d.ts +3 -0
- package/dist/interpreter/step-handlers/webhook-handler.js +18 -2
- package/dist/interpreter/store-manager.d.ts +46 -0
- package/dist/interpreter/store-manager.js +66 -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/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.js +451 -0
- package/dist/oas/index.d.ts +2 -0
- package/dist/oas/index.js +1 -0
- package/dist/oas/mock-generator.d.ts +12 -0
- package/dist/oas/mock-generator.js +187 -0
- package/dist/observability/events.d.ts +244 -0
- package/dist/observability/events.js +90 -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 +259 -0
- package/dist/observability/otel.d.ts +135 -0
- package/dist/observability/otel.js +386 -0
- package/dist/parser/action-parser.d.ts +105 -0
- package/dist/parser/action-parser.js +645 -0
- package/dist/parser/expressions.d.ts +13 -0
- package/dist/parser/expressions.js +72 -2
- package/dist/parser/fetch-parser.d.ts +27 -0
- package/dist/parser/fetch-parser.js +269 -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 +14 -0
- package/dist/pause/index.js +11 -0
- package/dist/pause/manager.d.ts +118 -0
- package/dist/pause/manager.js +245 -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 +156 -0
- package/dist/plugin.d.ts +9 -12
- package/dist/plugin.js +10 -13
- package/dist/stores/factory.d.ts +1 -1
- package/dist/stores/factory.js +3 -2
- package/dist/stores/file.d.ts +26 -0
- package/dist/stores/file.js +64 -10
- 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 +11 -0
- package/dist/stores/types.d.ts +17 -0
- package/dist/stores/types.js +12 -0
- package/dist/trace/index.d.ts +16 -0
- package/dist/trace/index.js +12 -0
- package/dist/trace/recorder.d.ts +71 -0
- package/dist/trace/recorder.js +144 -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 +69 -0
- package/dist/trace/store.js +225 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/type-guards.d.ts +58 -0
- package/dist/utils/type-guards.js +92 -0
- package/dist/webhook/server.js +7 -6
- package/package.json +55 -6
- 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/src/stores/postgrest.ts
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import type { StoreAdapter, StoreFilter } from './types.js';
|
|
2
|
-
|
|
3
|
-
export interface PostgRESTOptions {
|
|
4
|
-
/** Base URL for the PostgREST API (e.g., https://xxx.supabase.co/rest/v1) */
|
|
5
|
-
url: string;
|
|
6
|
-
/** API key for authentication */
|
|
7
|
-
apiKey: string;
|
|
8
|
-
/** Table name */
|
|
9
|
-
table: string;
|
|
10
|
-
/** Primary key column (default: 'id') */
|
|
11
|
-
primaryKey?: string;
|
|
12
|
-
/** Optional schema (for Supabase, typically 'public') */
|
|
13
|
-
schema?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* PostgREST-compatible store adapter.
|
|
18
|
-
* Works with Supabase, standalone PostgREST, or any PostgREST-compatible API.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* const store = new PostgRESTStore({
|
|
23
|
-
* url: 'https://xxx.supabase.co/rest/v1',
|
|
24
|
-
* apiKey: process.env.SUPABASE_ANON_KEY,
|
|
25
|
-
* table: 'users',
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export class PostgRESTStore implements StoreAdapter {
|
|
30
|
-
private baseUrl: string;
|
|
31
|
-
private headers: Record<string, string>;
|
|
32
|
-
private primaryKey: string;
|
|
33
|
-
|
|
34
|
-
constructor(private options: PostgRESTOptions) {
|
|
35
|
-
// Normalize URL (remove trailing slash)
|
|
36
|
-
const url = options.url.replace(/\/$/, '');
|
|
37
|
-
this.baseUrl = `${url}/${options.table}`;
|
|
38
|
-
this.primaryKey = options.primaryKey ?? 'id';
|
|
39
|
-
|
|
40
|
-
this.headers = {
|
|
41
|
-
'Content-Type': 'application/json',
|
|
42
|
-
'apikey': options.apiKey,
|
|
43
|
-
'Authorization': `Bearer ${options.apiKey}`,
|
|
44
|
-
'Prefer': 'return=representation',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
if (options.schema) {
|
|
48
|
-
this.headers['Accept-Profile'] = options.schema;
|
|
49
|
-
this.headers['Content-Profile'] = options.schema;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async get(key: string): Promise<Record<string, unknown> | null> {
|
|
54
|
-
const url = `${this.baseUrl}?${this.primaryKey}=eq.${encodeURIComponent(key)}&limit=1`;
|
|
55
|
-
|
|
56
|
-
const response = await fetch(url, {
|
|
57
|
-
method: 'GET',
|
|
58
|
-
headers: this.headers,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new PostgRESTError(`Failed to get record: ${response.statusText}`, response.status);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const data = await response.json();
|
|
66
|
-
return Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async set(key: string, value: Record<string, unknown>): Promise<void> {
|
|
70
|
-
// Upsert using PostgREST's on_conflict resolution
|
|
71
|
-
const record = { ...value, [this.primaryKey]: key };
|
|
72
|
-
|
|
73
|
-
const response = await fetch(this.baseUrl, {
|
|
74
|
-
method: 'POST',
|
|
75
|
-
headers: {
|
|
76
|
-
...this.headers,
|
|
77
|
-
'Prefer': 'resolution=merge-duplicates,return=representation',
|
|
78
|
-
},
|
|
79
|
-
body: JSON.stringify(record),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (!response.ok) {
|
|
83
|
-
const error = await this.parseError(response);
|
|
84
|
-
throw new PostgRESTError(`Failed to set record: ${error}`, response.status);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async update(key: string, value: Partial<Record<string, unknown>>): Promise<void> {
|
|
89
|
-
const url = `${this.baseUrl}?${this.primaryKey}=eq.${encodeURIComponent(key)}`;
|
|
90
|
-
|
|
91
|
-
const response = await fetch(url, {
|
|
92
|
-
method: 'PATCH',
|
|
93
|
-
headers: this.headers,
|
|
94
|
-
body: JSON.stringify(value),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (!response.ok) {
|
|
98
|
-
const error = await this.parseError(response);
|
|
99
|
-
throw new PostgRESTError(`Failed to update record: ${error}`, response.status);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async delete(key: string): Promise<void> {
|
|
104
|
-
const url = `${this.baseUrl}?${this.primaryKey}=eq.${encodeURIComponent(key)}`;
|
|
105
|
-
|
|
106
|
-
const response = await fetch(url, {
|
|
107
|
-
method: 'DELETE',
|
|
108
|
-
headers: this.headers,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (!response.ok) {
|
|
112
|
-
const error = await this.parseError(response);
|
|
113
|
-
throw new PostgRESTError(`Failed to delete record: ${error}`, response.status);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async list(filter?: StoreFilter): Promise<Record<string, unknown>[]> {
|
|
118
|
-
const params = new URLSearchParams();
|
|
119
|
-
|
|
120
|
-
// Apply where clause
|
|
121
|
-
if (filter?.where) {
|
|
122
|
-
for (const [field, value] of Object.entries(filter.where)) {
|
|
123
|
-
if (value === null) {
|
|
124
|
-
params.append(field, 'is.null');
|
|
125
|
-
} else if (typeof value === 'string') {
|
|
126
|
-
params.append(field, `eq.${value}`);
|
|
127
|
-
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
128
|
-
params.append(field, `eq.${value}`);
|
|
129
|
-
} else {
|
|
130
|
-
// For complex values, try JSON
|
|
131
|
-
params.append(field, `eq.${JSON.stringify(value)}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Apply pagination
|
|
137
|
-
if (filter?.limit) {
|
|
138
|
-
params.append('limit', String(filter.limit));
|
|
139
|
-
}
|
|
140
|
-
if (filter?.offset) {
|
|
141
|
-
params.append('offset', String(filter.offset));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const queryString = params.toString();
|
|
145
|
-
const url = queryString ? `${this.baseUrl}?${queryString}` : this.baseUrl;
|
|
146
|
-
|
|
147
|
-
const response = await fetch(url, {
|
|
148
|
-
method: 'GET',
|
|
149
|
-
headers: this.headers,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (!response.ok) {
|
|
153
|
-
const error = await this.parseError(response);
|
|
154
|
-
throw new PostgRESTError(`Failed to list records: ${error}`, response.status);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return response.json();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async clear(): Promise<void> {
|
|
161
|
-
// Delete all records - PostgREST requires a filter, so we use a always-true condition
|
|
162
|
-
// This deletes where primary key is not null (i.e., all records)
|
|
163
|
-
const url = `${this.baseUrl}?${this.primaryKey}=not.is.null`;
|
|
164
|
-
|
|
165
|
-
const response = await fetch(url, {
|
|
166
|
-
method: 'DELETE',
|
|
167
|
-
headers: this.headers,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
const error = await this.parseError(response);
|
|
172
|
-
throw new PostgRESTError(`Failed to clear records: ${error}`, response.status);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Bulk insert records (more efficient than individual sets)
|
|
178
|
-
*/
|
|
179
|
-
async bulkInsert(records: Record<string, unknown>[]): Promise<void> {
|
|
180
|
-
if (records.length === 0) return;
|
|
181
|
-
|
|
182
|
-
const response = await fetch(this.baseUrl, {
|
|
183
|
-
method: 'POST',
|
|
184
|
-
headers: {
|
|
185
|
-
...this.headers,
|
|
186
|
-
'Prefer': 'resolution=merge-duplicates',
|
|
187
|
-
},
|
|
188
|
-
body: JSON.stringify(records),
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
if (!response.ok) {
|
|
192
|
-
const error = await this.parseError(response);
|
|
193
|
-
throw new PostgRESTError(`Failed to bulk insert: ${error}`, response.status);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Count records matching a filter
|
|
199
|
-
*/
|
|
200
|
-
async count(filter?: StoreFilter): Promise<number> {
|
|
201
|
-
const params = new URLSearchParams();
|
|
202
|
-
params.append('select', 'count');
|
|
203
|
-
|
|
204
|
-
if (filter?.where) {
|
|
205
|
-
for (const [field, value] of Object.entries(filter.where)) {
|
|
206
|
-
if (value === null) {
|
|
207
|
-
params.append(field, 'is.null');
|
|
208
|
-
} else {
|
|
209
|
-
params.append(field, `eq.${value}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const url = `${this.baseUrl}?${params.toString()}`;
|
|
215
|
-
|
|
216
|
-
const response = await fetch(url, {
|
|
217
|
-
method: 'GET',
|
|
218
|
-
headers: {
|
|
219
|
-
...this.headers,
|
|
220
|
-
'Prefer': 'count=exact',
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (!response.ok) {
|
|
225
|
-
throw new PostgRESTError(`Failed to count records: ${response.statusText}`, response.status);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Count is in the Content-Range header: "0-24/100"
|
|
229
|
-
const range = response.headers.get('Content-Range');
|
|
230
|
-
if (range) {
|
|
231
|
-
const match = range.match(/\/(\d+|\*)/);
|
|
232
|
-
if (match && match[1] !== '*') {
|
|
233
|
-
return parseInt(match[1], 10);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Fallback: count the results
|
|
238
|
-
const data = await response.json();
|
|
239
|
-
return Array.isArray(data) ? data.length : 0;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private async parseError(response: Response): Promise<string> {
|
|
243
|
-
try {
|
|
244
|
-
const body = await response.json();
|
|
245
|
-
return body.message || body.error || response.statusText;
|
|
246
|
-
} catch {
|
|
247
|
-
return response.statusText;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Error class for PostgREST operations
|
|
254
|
-
*/
|
|
255
|
-
export class PostgRESTError extends Error {
|
|
256
|
-
constructor(
|
|
257
|
-
message: string,
|
|
258
|
-
public statusCode: number
|
|
259
|
-
) {
|
|
260
|
-
super(message);
|
|
261
|
-
this.name = 'PostgRESTError';
|
|
262
|
-
}
|
|
263
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { rmSync, existsSync } from 'node:fs';
|
|
3
|
-
import { FileStore } from './file.js';
|
|
4
|
-
import { createStore, resolveStoreType } from './factory.js';
|
|
5
|
-
|
|
6
|
-
const TEST_DIR = '.reqon-test-data';
|
|
7
|
-
|
|
8
|
-
describe('FileStore', () => {
|
|
9
|
-
let store: FileStore;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Clean up test directory
|
|
13
|
-
if (existsSync(TEST_DIR)) {
|
|
14
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
15
|
-
}
|
|
16
|
-
store = new FileStore('test-store', { baseDir: TEST_DIR });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
// Clean up after tests
|
|
21
|
-
if (existsSync(TEST_DIR)) {
|
|
22
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('creates data directory and .gitignore', async () => {
|
|
27
|
-
// Trigger initialization by calling any store method
|
|
28
|
-
await store.get('nonexistent');
|
|
29
|
-
expect(existsSync(TEST_DIR)).toBe(true);
|
|
30
|
-
expect(existsSync(`${TEST_DIR}/.gitignore`)).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('stores and retrieves data', async () => {
|
|
34
|
-
await store.set('item-1', { id: '1', name: 'Test Item' });
|
|
35
|
-
const retrieved = await store.get('item-1');
|
|
36
|
-
|
|
37
|
-
expect(retrieved).toEqual({ id: '1', name: 'Test Item' });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('persists data to disk', async () => {
|
|
41
|
-
await store.set('item-1', { id: '1', name: 'Persisted' });
|
|
42
|
-
|
|
43
|
-
// Create new store instance pointing to same file
|
|
44
|
-
const newStore = new FileStore('test-store', { baseDir: TEST_DIR });
|
|
45
|
-
const retrieved = await newStore.get('item-1');
|
|
46
|
-
|
|
47
|
-
expect(retrieved).toEqual({ id: '1', name: 'Persisted' });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('returns null for missing keys', async () => {
|
|
51
|
-
const result = await store.get('nonexistent');
|
|
52
|
-
expect(result).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('updates existing records', async () => {
|
|
56
|
-
await store.set('item-1', { id: '1', name: 'Original', count: 0 });
|
|
57
|
-
await store.update('item-1', { count: 5 });
|
|
58
|
-
|
|
59
|
-
const retrieved = await store.get('item-1');
|
|
60
|
-
expect(retrieved).toEqual({ id: '1', name: 'Original', count: 5 });
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('deletes records', async () => {
|
|
64
|
-
await store.set('item-1', { id: '1' });
|
|
65
|
-
await store.delete('item-1');
|
|
66
|
-
|
|
67
|
-
const result = await store.get('item-1');
|
|
68
|
-
expect(result).toBeNull();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('lists all records', async () => {
|
|
72
|
-
await store.set('a', { id: 'a', type: 'x' });
|
|
73
|
-
await store.set('b', { id: 'b', type: 'y' });
|
|
74
|
-
await store.set('c', { id: 'c', type: 'x' });
|
|
75
|
-
|
|
76
|
-
const all = await store.list();
|
|
77
|
-
expect(all).toHaveLength(3);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('filters records with where clause', async () => {
|
|
81
|
-
await store.set('a', { id: 'a', type: 'x' });
|
|
82
|
-
await store.set('b', { id: 'b', type: 'y' });
|
|
83
|
-
await store.set('c', { id: 'c', type: 'x' });
|
|
84
|
-
|
|
85
|
-
const filtered = await store.list({ where: { type: 'x' } });
|
|
86
|
-
expect(filtered).toHaveLength(2);
|
|
87
|
-
expect(filtered.every((r) => r.type === 'x')).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('clears all records', async () => {
|
|
91
|
-
await store.set('a', { id: 'a' });
|
|
92
|
-
await store.set('b', { id: 'b' });
|
|
93
|
-
await store.clear();
|
|
94
|
-
|
|
95
|
-
const all = await store.list();
|
|
96
|
-
expect(all).toHaveLength(0);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('supports batch mode with flush', async () => {
|
|
100
|
-
const batchStore = new FileStore('batch-test', {
|
|
101
|
-
baseDir: TEST_DIR,
|
|
102
|
-
persist: 'batch',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
await batchStore.set('item-1', { id: '1' });
|
|
106
|
-
await batchStore.set('item-2', { id: '2' });
|
|
107
|
-
|
|
108
|
-
// Before flush, new instance won't see changes
|
|
109
|
-
const beforeFlush = new FileStore('batch-test', { baseDir: TEST_DIR });
|
|
110
|
-
expect(await beforeFlush.get('item-1')).toBeNull();
|
|
111
|
-
|
|
112
|
-
// After flush, changes are persisted
|
|
113
|
-
batchStore.flush();
|
|
114
|
-
const afterFlush = new FileStore('batch-test', { baseDir: TEST_DIR });
|
|
115
|
-
expect(await afterFlush.get('item-1')).toEqual({ id: '1' });
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('createStore factory', () => {
|
|
120
|
-
beforeEach(() => {
|
|
121
|
-
if (existsSync(TEST_DIR)) {
|
|
122
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
afterEach(() => {
|
|
127
|
-
if (existsSync(TEST_DIR)) {
|
|
128
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('creates memory store', async () => {
|
|
133
|
-
const store = createStore({ type: 'memory', name: 'test' });
|
|
134
|
-
await store.set('key', { value: 1 });
|
|
135
|
-
expect(await store.get('key')).toEqual({ value: 1 });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('creates file store', async () => {
|
|
139
|
-
const store = createStore({
|
|
140
|
-
type: 'file',
|
|
141
|
-
name: 'test',
|
|
142
|
-
baseDir: TEST_DIR,
|
|
143
|
-
});
|
|
144
|
-
await store.set('key', { value: 1 });
|
|
145
|
-
|
|
146
|
-
// Verify persistence
|
|
147
|
-
const newStore = createStore({
|
|
148
|
-
type: 'file',
|
|
149
|
-
name: 'test',
|
|
150
|
-
baseDir: TEST_DIR,
|
|
151
|
-
});
|
|
152
|
-
expect(await newStore.get('key')).toEqual({ value: 1 });
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('falls back to file for sql in dev mode', async () => {
|
|
156
|
-
const store = createStore({
|
|
157
|
-
type: 'sql',
|
|
158
|
-
name: 'test',
|
|
159
|
-
baseDir: TEST_DIR,
|
|
160
|
-
});
|
|
161
|
-
await store.set('key', { value: 1 });
|
|
162
|
-
expect(await store.get('key')).toEqual({ value: 1 });
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('falls back to file for nosql in dev mode', async () => {
|
|
166
|
-
const store = createStore({
|
|
167
|
-
type: 'nosql',
|
|
168
|
-
name: 'test',
|
|
169
|
-
baseDir: TEST_DIR,
|
|
170
|
-
});
|
|
171
|
-
await store.set('key', { value: 1 });
|
|
172
|
-
expect(await store.get('key')).toEqual({ value: 1 });
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe('resolveStoreType', () => {
|
|
177
|
-
it('keeps memory as memory', () => {
|
|
178
|
-
expect(resolveStoreType('memory', true)).toBe('memory');
|
|
179
|
-
expect(resolveStoreType('memory', false)).toBe('memory');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('maps sql to file in dev mode', () => {
|
|
183
|
-
expect(resolveStoreType('sql', true)).toBe('file');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('maps nosql to file in dev mode', () => {
|
|
187
|
-
expect(resolveStoreType('nosql', true)).toBe('file');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('keeps sql as sql in production mode', () => {
|
|
191
|
-
expect(resolveStoreType('sql', false)).toBe('sql');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('keeps nosql as nosql in production mode', () => {
|
|
195
|
-
expect(resolveStoreType('nosql', false)).toBe('nosql');
|
|
196
|
-
});
|
|
197
|
-
});
|
package/src/stores/types.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
export interface StoreAdapter {
|
|
2
|
-
get(key: string): Promise<Record<string, unknown> | null>;
|
|
3
|
-
set(key: string, value: Record<string, unknown>): Promise<void>;
|
|
4
|
-
/** Bulk set multiple records at once - more efficient than individual sets */
|
|
5
|
-
bulkSet?(records: Array<{ key: string; value: Record<string, unknown> }>): Promise<void>;
|
|
6
|
-
update(key: string, value: Partial<Record<string, unknown>>): Promise<void>;
|
|
7
|
-
delete(key: string): Promise<void>;
|
|
8
|
-
list(filter?: StoreFilter): Promise<Record<string, unknown>[]>;
|
|
9
|
-
count(filter?: StoreFilter): Promise<number>;
|
|
10
|
-
clear(): Promise<void>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface StoreFilter {
|
|
14
|
-
where?: Record<string, unknown>;
|
|
15
|
-
limit?: number;
|
|
16
|
-
offset?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface StoreConfig {
|
|
20
|
-
type: 'nosql' | 'sql' | 'memory' | 'file';
|
|
21
|
-
target: string;
|
|
22
|
-
connection?: string;
|
|
23
|
-
/** For file stores: 'json' or 'csv' */
|
|
24
|
-
format?: 'json' | 'csv';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Apply filter criteria to a list of records.
|
|
29
|
-
* Handles where clause, offset, and limit.
|
|
30
|
-
*/
|
|
31
|
-
export function applyStoreFilter<T extends Record<string, unknown>>(
|
|
32
|
-
records: T[],
|
|
33
|
-
filter?: StoreFilter
|
|
34
|
-
): T[] {
|
|
35
|
-
if (!filter) return records;
|
|
36
|
-
|
|
37
|
-
let results = records;
|
|
38
|
-
|
|
39
|
-
if (filter.where) {
|
|
40
|
-
const whereClause = filter.where;
|
|
41
|
-
results = results.filter((item) => {
|
|
42
|
-
for (const [key, value] of Object.entries(whereClause)) {
|
|
43
|
-
if (item[key] !== value) return false;
|
|
44
|
-
}
|
|
45
|
-
return true;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (filter.offset) {
|
|
50
|
-
results = results.slice(filter.offset);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (filter.limit) {
|
|
54
|
-
results = results.slice(0, filter.limit);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return results;
|
|
58
|
-
}
|
package/src/sync/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
SyncCheckpoint,
|
|
3
|
-
SyncState,
|
|
4
|
-
SinceResolution,
|
|
5
|
-
SinceDateFormat,
|
|
6
|
-
} from './state.js';
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
generateCheckpointKey,
|
|
10
|
-
formatSinceDate,
|
|
11
|
-
parseSinceDate,
|
|
12
|
-
EPOCH,
|
|
13
|
-
} from './state.js';
|
|
14
|
-
|
|
15
|
-
export type { SyncStore } from './store.js';
|
|
16
|
-
export { FileSyncStore, MemorySyncStore } from './store.js';
|
package/src/sync/state.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sync State - Tracks last sync timestamps for incremental sync
|
|
3
|
-
*
|
|
4
|
-
* Enables "only fetch changed since last run" patterns:
|
|
5
|
-
* - get "/invoices" { since: lastSync }
|
|
6
|
-
* - call Xero.getInvoices { since: lastSync("invoices") }
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Sync checkpoint - records when a sync completed
|
|
11
|
-
*/
|
|
12
|
-
export interface SyncCheckpoint {
|
|
13
|
-
/** Unique key (e.g., "Xero:getInvoices" or "invoices") */
|
|
14
|
-
key: string;
|
|
15
|
-
/** Last successful sync timestamp */
|
|
16
|
-
syncedAt: Date;
|
|
17
|
-
/** Number of records fetched in last sync */
|
|
18
|
-
recordCount?: number;
|
|
19
|
-
/** Optional cursor for cursor-based pagination resume */
|
|
20
|
-
cursor?: string;
|
|
21
|
-
/** Mission that performed the sync */
|
|
22
|
-
mission?: string;
|
|
23
|
-
/** Execution ID that performed the sync */
|
|
24
|
-
executionId?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Sync state for a mission/source
|
|
29
|
-
*/
|
|
30
|
-
export interface SyncState {
|
|
31
|
-
/** Mission name */
|
|
32
|
-
mission: string;
|
|
33
|
-
/** Source name */
|
|
34
|
-
source: string;
|
|
35
|
-
/** Checkpoints by key */
|
|
36
|
-
checkpoints: Map<string, SyncCheckpoint>;
|
|
37
|
-
/** When this state was last updated */
|
|
38
|
-
updatedAt: Date;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Options for resolving a "since" value
|
|
43
|
-
*/
|
|
44
|
-
export interface SinceResolution {
|
|
45
|
-
/** The resolved timestamp */
|
|
46
|
-
timestamp: Date;
|
|
47
|
-
/** Whether this is a fresh sync (no previous checkpoint) */
|
|
48
|
-
isFreshSync: boolean;
|
|
49
|
-
/** The checkpoint key used */
|
|
50
|
-
key: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Generate a checkpoint key for a fetch operation
|
|
55
|
-
*/
|
|
56
|
-
export function generateCheckpointKey(
|
|
57
|
-
source: string,
|
|
58
|
-
operationId?: string,
|
|
59
|
-
endpoint?: string
|
|
60
|
-
): string {
|
|
61
|
-
if (operationId) {
|
|
62
|
-
return `${source}:${operationId}`;
|
|
63
|
-
}
|
|
64
|
-
if (endpoint) {
|
|
65
|
-
// Normalize endpoint (remove query params, trailing slashes)
|
|
66
|
-
const normalized = endpoint.split('?')[0].replace(/\/+$/, '');
|
|
67
|
-
return `${source}:${normalized}`;
|
|
68
|
-
}
|
|
69
|
-
return source;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Default "since" value for fresh syncs
|
|
74
|
-
* Uses Unix epoch (1970-01-01) to fetch all historical data
|
|
75
|
-
*/
|
|
76
|
-
export const EPOCH = new Date(0);
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Common date formats for API "since" parameters
|
|
80
|
-
*/
|
|
81
|
-
export type SinceDateFormat = 'iso' | 'unix' | 'unix-ms' | 'date-only';
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Format a date for use in API requests
|
|
85
|
-
*/
|
|
86
|
-
export function formatSinceDate(date: Date, format: SinceDateFormat = 'iso'): string {
|
|
87
|
-
switch (format) {
|
|
88
|
-
case 'iso':
|
|
89
|
-
return date.toISOString();
|
|
90
|
-
case 'unix':
|
|
91
|
-
return Math.floor(date.getTime() / 1000).toString();
|
|
92
|
-
case 'unix-ms':
|
|
93
|
-
return date.getTime().toString();
|
|
94
|
-
case 'date-only':
|
|
95
|
-
return date.toISOString().split('T')[0];
|
|
96
|
-
default:
|
|
97
|
-
return date.toISOString();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Parse a date from various API response formats
|
|
103
|
-
*/
|
|
104
|
-
export function parseSinceDate(value: unknown): Date | null {
|
|
105
|
-
if (value instanceof Date) {
|
|
106
|
-
return value;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (typeof value === 'string') {
|
|
110
|
-
const parsed = new Date(value);
|
|
111
|
-
if (!isNaN(parsed.getTime())) {
|
|
112
|
-
return parsed;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (typeof value === 'number') {
|
|
117
|
-
// Assume Unix timestamp in seconds if < year 3000 in seconds
|
|
118
|
-
if (value < 32503680000) {
|
|
119
|
-
return new Date(value * 1000);
|
|
120
|
-
}
|
|
121
|
-
// Otherwise assume milliseconds
|
|
122
|
-
return new Date(value);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return null;
|
|
126
|
-
}
|