reqon-dsl 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +31 -0
- package/.claude/skills/api-integration.md +125 -0
- package/.claude/skills/database-schema.md +51 -0
- package/.claude/skills/dsl-design.md +80 -0
- package/.claude/skills/property-testing.md +143 -0
- package/.claude/skills/reqon/SKILL.md +44 -0
- package/.claude/skills/reqon/references/examples.md +206 -0
- package/.claude/skills/reqon/references/syntax.md +263 -0
- package/.claude/skills/vscode-extension.md +113 -0
- package/.github/dependabot.yml +32 -0
- package/.github/pull_request_template.md +21 -0
- package/.github/workflows/ci.yml +174 -0
- package/.github/workflows/release.yml +73 -0
- package/CLAUDE.md +72 -0
- package/CONTRIBUTING.md +161 -0
- package/README.md +235 -0
- package/TODO.md +51 -0
- package/dist/ast/index.d.ts +1 -0
- package/dist/ast/index.js +1 -0
- package/dist/ast/nodes.d.ts +237 -0
- package/dist/ast/nodes.js +12 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +255 -0
- package/dist/auth/circuit-breaker.d.ts +115 -0
- package/dist/auth/circuit-breaker.js +267 -0
- package/dist/auth/credentials.d.ts +91 -0
- package/dist/auth/credentials.js +169 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/oauth2-provider.d.ts +41 -0
- package/dist/auth/oauth2-provider.js +131 -0
- package/dist/auth/rate-limiter.d.ts +61 -0
- package/dist/auth/rate-limiter.js +380 -0
- package/dist/auth/token-store.d.ts +30 -0
- package/dist/auth/token-store.js +148 -0
- package/dist/auth/types.d.ts +142 -0
- package/dist/auth/types.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +270 -0
- package/dist/errors/errors.test.d.ts +1 -0
- package/dist/errors/errors.test.js +165 -0
- package/dist/errors/index.d.ts +83 -0
- package/dist/errors/index.js +159 -0
- package/dist/execution/execution.test.d.ts +1 -0
- package/dist/execution/execution.test.js +246 -0
- package/dist/execution/index.d.ts +4 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/state.d.ts +136 -0
- package/dist/execution/state.js +82 -0
- package/dist/execution/store.d.ts +52 -0
- package/dist/execution/store.js +120 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +57 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.js +168 -0
- package/dist/interpreter/context.d.ts +15 -0
- package/dist/interpreter/context.js +29 -0
- package/dist/interpreter/evaluator.d.ts +5 -0
- package/dist/interpreter/evaluator.js +223 -0
- package/dist/interpreter/evaluator.test.d.ts +1 -0
- package/dist/interpreter/evaluator.test.js +512 -0
- package/dist/interpreter/executor.d.ts +131 -0
- package/dist/interpreter/executor.js +663 -0
- package/dist/interpreter/fetch-handler.d.ts +43 -0
- package/dist/interpreter/fetch-handler.js +203 -0
- package/dist/interpreter/http.d.ts +57 -0
- package/dist/interpreter/http.js +210 -0
- package/dist/interpreter/http.test.d.ts +1 -0
- package/dist/interpreter/http.test.js +299 -0
- package/dist/interpreter/index.d.ts +7 -0
- package/dist/interpreter/index.js +7 -0
- package/dist/interpreter/pagination.d.ts +63 -0
- package/dist/interpreter/pagination.js +155 -0
- package/dist/interpreter/progress.test.d.ts +1 -0
- package/dist/interpreter/progress.test.js +216 -0
- package/dist/interpreter/schema-matcher.d.ts +16 -0
- package/dist/interpreter/schema-matcher.js +136 -0
- package/dist/interpreter/schema-matcher.test.d.ts +1 -0
- package/dist/interpreter/schema-matcher.test.js +122 -0
- package/dist/interpreter/signals.d.ts +57 -0
- package/dist/interpreter/signals.js +73 -0
- package/dist/interpreter/step-handlers/for-handler.d.ts +17 -0
- package/dist/interpreter/step-handlers/for-handler.js +51 -0
- package/dist/interpreter/step-handlers/index.d.ts +8 -0
- package/dist/interpreter/step-handlers/index.js +8 -0
- package/dist/interpreter/step-handlers/map-handler.d.ts +10 -0
- package/dist/interpreter/step-handlers/map-handler.js +20 -0
- package/dist/interpreter/step-handlers/match-handler.d.ts +27 -0
- package/dist/interpreter/step-handlers/match-handler.js +61 -0
- package/dist/interpreter/step-handlers/store-handler.d.ts +13 -0
- package/dist/interpreter/step-handlers/store-handler.js +66 -0
- package/dist/interpreter/step-handlers/types.d.ts +15 -0
- package/dist/interpreter/step-handlers/types.js +1 -0
- package/dist/interpreter/step-handlers/validate-handler.d.ts +10 -0
- package/dist/interpreter/step-handlers/validate-handler.js +26 -0
- package/dist/interpreter/step-handlers/webhook-handler.d.ts +36 -0
- package/dist/interpreter/step-handlers/webhook-handler.js +104 -0
- package/dist/lexer/index.d.ts +10 -0
- package/dist/lexer/index.js +12 -0
- package/dist/lexer/lexer.d.ts +24 -0
- package/dist/lexer/lexer.js +264 -0
- package/dist/lexer/lexer.test.d.ts +1 -0
- package/dist/lexer/lexer.test.js +259 -0
- package/dist/lexer/tokens.d.ts +69 -0
- package/dist/lexer/tokens.js +146 -0
- package/dist/loader/index.d.ts +36 -0
- package/dist/loader/index.js +220 -0
- package/dist/loader/loader.test.d.ts +1 -0
- package/dist/loader/loader.test.js +287 -0
- package/dist/oas/index.d.ts +4 -0
- package/dist/oas/index.js +2 -0
- package/dist/oas/loader.d.ts +21 -0
- package/dist/oas/loader.js +82 -0
- package/dist/oas/oas.test.d.ts +1 -0
- package/dist/oas/oas.test.js +218 -0
- package/dist/oas/validator.d.ts +12 -0
- package/dist/oas/validator.js +227 -0
- package/dist/parser/base.d.ts +33 -0
- package/dist/parser/base.js +97 -0
- package/dist/parser/expressions.d.ts +27 -0
- package/dist/parser/expressions.js +248 -0
- package/dist/parser/expressions.test.d.ts +1 -0
- package/dist/parser/expressions.test.js +378 -0
- package/dist/parser/index.d.ts +3 -0
- package/dist/parser/index.js +3 -0
- package/dist/parser/match.test.d.ts +1 -0
- package/dist/parser/match.test.js +254 -0
- package/dist/parser/parser.d.ts +68 -0
- package/dist/parser/parser.js +1229 -0
- package/dist/parser/parser.test.d.ts +1 -0
- package/dist/parser/parser.test.js +333 -0
- package/dist/parser/schedule.test.d.ts +1 -0
- package/dist/parser/schedule.test.js +241 -0
- package/dist/plugin.d.ts +35 -0
- package/dist/plugin.js +68 -0
- package/dist/scheduler/cron-parser.d.ts +32 -0
- package/dist/scheduler/cron-parser.js +198 -0
- package/dist/scheduler/cron-parser.test.d.ts +1 -0
- package/dist/scheduler/cron-parser.test.js +188 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/scheduler.d.ts +81 -0
- package/dist/scheduler/scheduler.js +376 -0
- package/dist/scheduler/types.d.ts +65 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/stores/factory.d.ts +36 -0
- package/dist/stores/factory.js +73 -0
- package/dist/stores/file.d.ts +60 -0
- package/dist/stores/file.js +173 -0
- package/dist/stores/file.test.d.ts +1 -0
- package/dist/stores/file.test.js +165 -0
- package/dist/stores/index.d.ts +6 -0
- package/dist/stores/index.js +5 -0
- package/dist/stores/memory.d.ts +19 -0
- package/dist/stores/memory.js +51 -0
- package/dist/stores/memory.test.d.ts +1 -0
- package/dist/stores/memory.test.js +157 -0
- package/dist/stores/postgrest.d.ts +55 -0
- package/dist/stores/postgrest.js +217 -0
- package/dist/stores/stores.test.d.ts +1 -0
- package/dist/stores/stores.test.js +158 -0
- package/dist/stores/types.d.ts +31 -0
- package/dist/stores/types.js +26 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/state.d.ts +69 -0
- package/dist/sync/state.js +66 -0
- package/dist/sync/store.d.ts +49 -0
- package/dist/sync/store.js +93 -0
- package/dist/sync/sync.test.d.ts +1 -0
- package/dist/sync/sync.test.js +221 -0
- package/dist/utils/async.d.ts +7 -0
- package/dist/utils/async.js +9 -0
- package/dist/utils/file.d.ts +38 -0
- package/dist/utils/file.js +92 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/logger.d.ts +34 -0
- package/dist/utils/logger.js +39 -0
- package/dist/utils/path.d.ts +12 -0
- package/dist/utils/path.js +41 -0
- package/dist/webhook/index.d.ts +8 -0
- package/dist/webhook/index.js +7 -0
- package/dist/webhook/server.d.ts +84 -0
- package/dist/webhook/server.js +319 -0
- package/dist/webhook/store.d.ts +67 -0
- package/dist/webhook/store.js +193 -0
- package/dist/webhook/types.d.ts +88 -0
- package/dist/webhook/types.js +6 -0
- package/docusaurus/README.md +41 -0
- package/docusaurus/docs/advanced/execution-state.md +283 -0
- package/docusaurus/docs/advanced/extending-reqon.md +388 -0
- package/docusaurus/docs/advanced/multi-file-missions.md +250 -0
- package/docusaurus/docs/advanced/parallel-execution.md +353 -0
- package/docusaurus/docs/api-reference.md +443 -0
- package/docusaurus/docs/authentication/api-key.md +339 -0
- package/docusaurus/docs/authentication/basic.md +276 -0
- package/docusaurus/docs/authentication/bearer.md +282 -0
- package/docusaurus/docs/authentication/oauth2.md +317 -0
- package/docusaurus/docs/authentication/overview.md +251 -0
- package/docusaurus/docs/cli.md +229 -0
- package/docusaurus/docs/core-concepts/actions.md +286 -0
- package/docusaurus/docs/core-concepts/missions.md +264 -0
- package/docusaurus/docs/core-concepts/schemas.md +353 -0
- package/docusaurus/docs/core-concepts/sources.md +339 -0
- package/docusaurus/docs/core-concepts/stores.md +332 -0
- package/docusaurus/docs/dsl-syntax/expressions.md +361 -0
- package/docusaurus/docs/dsl-syntax/fetch.md +293 -0
- package/docusaurus/docs/dsl-syntax/for-loops.md +324 -0
- package/docusaurus/docs/dsl-syntax/map.md +345 -0
- package/docusaurus/docs/dsl-syntax/match.md +387 -0
- package/docusaurus/docs/dsl-syntax/pipelines.md +397 -0
- package/docusaurus/docs/dsl-syntax/validate.md +401 -0
- package/docusaurus/docs/error-handling/dead-letter-queues.md +399 -0
- package/docusaurus/docs/error-handling/flow-control.md +337 -0
- package/docusaurus/docs/error-handling/retry-strategies.md +368 -0
- package/docusaurus/docs/examples.md +488 -0
- package/docusaurus/docs/getting-started.md +256 -0
- package/docusaurus/docs/http/circuit-breaker.md +401 -0
- package/docusaurus/docs/http/incremental-sync.md +394 -0
- package/docusaurus/docs/http/pagination.md +361 -0
- package/docusaurus/docs/http/rate-limiting.md +383 -0
- package/docusaurus/docs/http/requests.md +328 -0
- package/docusaurus/docs/http/retry.md +402 -0
- package/docusaurus/docs/intro.md +90 -0
- package/docusaurus/docs/openapi/loading-specs.md +305 -0
- package/docusaurus/docs/openapi/operation-calls.md +314 -0
- package/docusaurus/docs/openapi/overview.md +212 -0
- package/docusaurus/docs/openapi/response-validation.md +344 -0
- package/docusaurus/docs/scheduling/cron.md +305 -0
- package/docusaurus/docs/scheduling/daemon-mode.md +317 -0
- package/docusaurus/docs/scheduling/intervals.md +289 -0
- package/docusaurus/docs/scheduling/overview.md +231 -0
- package/docusaurus/docs/stores/custom-adapters.md +376 -0
- package/docusaurus/docs/stores/file.md +236 -0
- package/docusaurus/docs/stores/memory.md +193 -0
- package/docusaurus/docs/stores/overview.md +274 -0
- package/docusaurus/docs/stores/postgrest.md +316 -0
- package/docusaurus/docusaurus.config.ts +148 -0
- package/docusaurus/package-lock.json +18029 -0
- package/docusaurus/package.json +47 -0
- package/docusaurus/sidebars.ts +155 -0
- package/docusaurus/src/components/HomepageFeatures/index.tsx +105 -0
- package/docusaurus/src/components/HomepageFeatures/styles.module.css +12 -0
- package/docusaurus/src/css/custom.css +169 -0
- package/docusaurus/src/pages/index.module.css +48 -0
- package/docusaurus/src/pages/index.tsx +110 -0
- package/docusaurus/src/pages/markdown-page.md +7 -0
- 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 +10 -0
- package/docusaurus/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docusaurus/static/img/undraw_docusaurus_react.svg +170 -0
- package/docusaurus/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docusaurus/tsconfig.json +8 -0
- package/examples/README.md +112 -0
- package/examples/error-handling/README.md +150 -0
- package/examples/error-handling/payment-processor.vague +287 -0
- package/examples/github-sync/README.md +74 -0
- package/examples/github-sync/fetch-issues.vague +47 -0
- package/examples/github-sync/fetch-prs.vague +40 -0
- package/examples/github-sync/mission.vague +101 -0
- package/examples/github-sync/normalize.vague +70 -0
- package/examples/jsonplaceholder/README.md +28 -0
- package/examples/jsonplaceholder/posts.vague +48 -0
- package/examples/petstore/README.md +35 -0
- package/examples/petstore/openapi.yaml +97 -0
- package/examples/petstore/sync.vague +52 -0
- package/examples/temporal-comparison/README.md +297 -0
- package/examples/temporal-comparison/reconciliation.vague +355 -0
- package/examples/temporal-comparison/temporal/activities/index.ts +8 -0
- package/examples/temporal-comparison/temporal/activities/shipstation.ts +225 -0
- package/examples/temporal-comparison/temporal/activities/shopify.ts +257 -0
- package/examples/temporal-comparison/temporal/activities/storage.ts +198 -0
- package/examples/temporal-comparison/temporal/activities/stripe.ts +169 -0
- package/examples/temporal-comparison/temporal/activities/validation.ts +205 -0
- package/examples/temporal-comparison/temporal/client/schedule.ts +218 -0
- package/examples/temporal-comparison/temporal/config/retry.ts +63 -0
- package/examples/temporal-comparison/temporal/types/index.ts +129 -0
- package/examples/temporal-comparison/temporal/workers/main.ts +130 -0
- package/examples/temporal-comparison/temporal/workflows/orderReconciliation.ts +262 -0
- package/examples/xero/README.md +88 -0
- package/examples/xero/invoices.vague +189 -0
- package/package.json +40 -0
- package/src/api-integration.test.ts +954 -0
- package/src/ast/index.ts +1 -0
- package/src/ast/nodes.ts +310 -0
- package/src/auth/auth.test.ts +326 -0
- package/src/auth/circuit-breaker.test.ts +390 -0
- package/src/auth/circuit-breaker.ts +379 -0
- package/src/auth/credentials.test.ts +273 -0
- package/src/auth/credentials.ts +246 -0
- package/src/auth/index.ts +40 -0
- package/src/auth/oauth2-provider.ts +177 -0
- package/src/auth/rate-limiter.ts +459 -0
- package/src/auth/token-store.ts +177 -0
- package/src/auth/types.ts +159 -0
- package/src/benchmark/e2e.bench.ts +288 -0
- package/src/benchmark/evaluator.bench.ts +331 -0
- package/src/benchmark/fixtures.ts +295 -0
- package/src/benchmark/index.ts +108 -0
- package/src/benchmark/lexer.bench.ts +69 -0
- package/src/benchmark/parser.bench.ts +103 -0
- package/src/benchmark/resilience.bench.ts +193 -0
- package/src/benchmark/store.bench.ts +147 -0
- package/src/benchmark/utils.ts +230 -0
- package/src/cli.ts +313 -0
- package/src/errors/errors.test.ts +234 -0
- package/src/errors/index.ts +223 -0
- package/src/execution/execution.test.ts +307 -0
- package/src/execution/index.ts +21 -0
- package/src/execution/state.ts +207 -0
- package/src/execution/store.ts +188 -0
- package/src/index.ts +169 -0
- package/src/integration.test.ts +192 -0
- package/src/interpreter/context.ts +57 -0
- package/src/interpreter/evaluator.test.ts +796 -0
- package/src/interpreter/evaluator.ts +245 -0
- package/src/interpreter/executor.ts +946 -0
- package/src/interpreter/fetch-handler.ts +302 -0
- package/src/interpreter/http.test.ts +423 -0
- package/src/interpreter/http.ts +308 -0
- package/src/interpreter/index.ts +32 -0
- package/src/interpreter/pagination.ts +207 -0
- package/src/interpreter/progress.test.ts +276 -0
- package/src/interpreter/schema-matcher.test.ts +160 -0
- package/src/interpreter/schema-matcher.ts +168 -0
- package/src/interpreter/signals.ts +73 -0
- package/src/interpreter/step-handlers/for-handler.ts +65 -0
- package/src/interpreter/step-handlers/index.ts +17 -0
- package/src/interpreter/step-handlers/map-handler.ts +24 -0
- package/src/interpreter/step-handlers/match-handler.ts +101 -0
- package/src/interpreter/step-handlers/store-handler.ts +78 -0
- package/src/interpreter/step-handlers/types.ts +17 -0
- package/src/interpreter/step-handlers/validate-handler.ts +30 -0
- package/src/interpreter/step-handlers/webhook-handler.ts +142 -0
- package/src/lexer/index.ts +18 -0
- package/src/lexer/lexer.test.ts +316 -0
- package/src/lexer/tokens.ts +179 -0
- package/src/loader/index.ts +288 -0
- package/src/loader/loader.test.ts +360 -0
- package/src/oas/index.ts +4 -0
- package/src/oas/loader.ts +126 -0
- package/src/oas/oas.test.ts +254 -0
- package/src/oas/validator.ts +299 -0
- package/src/parser/base.ts +124 -0
- package/src/parser/expressions.test.ts +525 -0
- package/src/parser/expressions.ts +314 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/match.test.ts +296 -0
- package/src/parser/parser.test.ts +739 -0
- package/src/parser/parser.ts +1469 -0
- package/src/parser/schedule.test.ts +287 -0
- package/src/parser/webhook.test.ts +248 -0
- package/src/plugin.ts +83 -0
- package/src/scheduler/cron-parser.test.ts +236 -0
- package/src/scheduler/cron-parser.ts +236 -0
- package/src/scheduler/index.ts +10 -0
- package/src/scheduler/scheduler.ts +443 -0
- package/src/scheduler/types.ts +71 -0
- package/src/stores/factory.ts +104 -0
- package/src/stores/file.test.ts +276 -0
- package/src/stores/file.ts +211 -0
- package/src/stores/index.ts +6 -0
- package/src/stores/memory.test.ts +238 -0
- package/src/stores/memory.ts +63 -0
- package/src/stores/postgrest.test.ts +488 -0
- package/src/stores/postgrest.ts +263 -0
- package/src/stores/stores.test.ts +197 -0
- package/src/stores/types.ts +58 -0
- package/src/sync/index.ts +16 -0
- package/src/sync/state.ts +126 -0
- package/src/sync/store.ts +139 -0
- package/src/sync/sync.test.ts +271 -0
- package/src/utils/async.ts +10 -0
- package/src/utils/file.ts +106 -0
- package/src/utils/index.ts +14 -0
- package/src/utils/logger.ts +53 -0
- package/src/utils/path.ts +47 -0
- package/src/webhook/index.ts +15 -0
- package/src/webhook/server.test.ts +253 -0
- package/src/webhook/server.ts +389 -0
- package/src/webhook/store.ts +239 -0
- package/src/webhook/types.ts +93 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +39 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Server
|
|
3
|
+
*
|
|
4
|
+
* HTTP server for receiving webhook callbacks.
|
|
5
|
+
* Supports dynamic registration of webhook endpoints.
|
|
6
|
+
*/
|
|
7
|
+
import type { WebhookServerConfig, WebhookServerCallbacks, WebhookRegistration, WaitResult } from './types.js';
|
|
8
|
+
import type { WebhookStore } from './store.js';
|
|
9
|
+
/**
|
|
10
|
+
* Webhook Server
|
|
11
|
+
*
|
|
12
|
+
* Provides HTTP endpoints for receiving webhook callbacks.
|
|
13
|
+
*/
|
|
14
|
+
export declare class WebhookServer {
|
|
15
|
+
private config;
|
|
16
|
+
private store;
|
|
17
|
+
private callbacks;
|
|
18
|
+
private server?;
|
|
19
|
+
private pendingWaits;
|
|
20
|
+
private cleanupInterval?;
|
|
21
|
+
private running;
|
|
22
|
+
constructor(config?: WebhookServerConfig, store?: WebhookStore, callbacks?: WebhookServerCallbacks);
|
|
23
|
+
/**
|
|
24
|
+
* Start the webhook server
|
|
25
|
+
*/
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Stop the webhook server
|
|
29
|
+
*/
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Register a webhook endpoint
|
|
33
|
+
*/
|
|
34
|
+
register(executionId: string, options?: {
|
|
35
|
+
path?: string;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
expectedEvents?: number;
|
|
38
|
+
filter?: string;
|
|
39
|
+
}): Promise<WebhookRegistration>;
|
|
40
|
+
/**
|
|
41
|
+
* Get the full URL for a webhook endpoint
|
|
42
|
+
*/
|
|
43
|
+
getWebhookUrl(registration: WebhookRegistration): string;
|
|
44
|
+
/**
|
|
45
|
+
* Wait for webhook events
|
|
46
|
+
*/
|
|
47
|
+
waitForEvents(registrationId: string, timeout?: number): Promise<WaitResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Unregister a webhook endpoint
|
|
50
|
+
*/
|
|
51
|
+
unregister(registrationId: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Check if the server is running
|
|
54
|
+
*/
|
|
55
|
+
isRunning(): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Get the server port
|
|
58
|
+
*/
|
|
59
|
+
getPort(): number;
|
|
60
|
+
/**
|
|
61
|
+
* Get the base URL
|
|
62
|
+
*/
|
|
63
|
+
getBaseUrl(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Handle incoming HTTP request
|
|
66
|
+
*/
|
|
67
|
+
private handleRequest;
|
|
68
|
+
/**
|
|
69
|
+
* Read request body
|
|
70
|
+
*/
|
|
71
|
+
private readBody;
|
|
72
|
+
/**
|
|
73
|
+
* Extract headers from request
|
|
74
|
+
*/
|
|
75
|
+
private extractHeaders;
|
|
76
|
+
/**
|
|
77
|
+
* Clean up expired registrations
|
|
78
|
+
*/
|
|
79
|
+
private cleanup;
|
|
80
|
+
/**
|
|
81
|
+
* Log message if verbose mode enabled
|
|
82
|
+
*/
|
|
83
|
+
private log;
|
|
84
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Server
|
|
3
|
+
*
|
|
4
|
+
* HTTP server for receiving webhook callbacks.
|
|
5
|
+
* Supports dynamic registration of webhook endpoints.
|
|
6
|
+
*/
|
|
7
|
+
import { createServer } from 'node:http';
|
|
8
|
+
import { parse as parseUrl } from 'node:url';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { MemoryWebhookStore } from './store.js';
|
|
11
|
+
/**
|
|
12
|
+
* Webhook Server
|
|
13
|
+
*
|
|
14
|
+
* Provides HTTP endpoints for receiving webhook callbacks.
|
|
15
|
+
*/
|
|
16
|
+
export class WebhookServer {
|
|
17
|
+
config;
|
|
18
|
+
store;
|
|
19
|
+
callbacks;
|
|
20
|
+
server;
|
|
21
|
+
pendingWaits = new Map();
|
|
22
|
+
cleanupInterval;
|
|
23
|
+
running = false;
|
|
24
|
+
constructor(config = {}, store, callbacks = {}) {
|
|
25
|
+
this.config = {
|
|
26
|
+
port: config.port ?? 3000,
|
|
27
|
+
host: config.host ?? '0.0.0.0',
|
|
28
|
+
baseUrl: config.baseUrl ?? `http://localhost:${config.port ?? 3000}`,
|
|
29
|
+
defaultTimeout: config.defaultTimeout ?? 300000, // 5 minutes
|
|
30
|
+
verbose: config.verbose ?? false,
|
|
31
|
+
};
|
|
32
|
+
this.store = store ?? new MemoryWebhookStore();
|
|
33
|
+
this.callbacks = callbacks;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Start the webhook server
|
|
37
|
+
*/
|
|
38
|
+
async start() {
|
|
39
|
+
if (this.running)
|
|
40
|
+
return;
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
43
|
+
this.server.on('error', (error) => {
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
47
|
+
this.running = true;
|
|
48
|
+
this.log(`Webhook server listening on ${this.config.host}:${this.config.port}`);
|
|
49
|
+
// Start cleanup interval (every minute)
|
|
50
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Stop the webhook server
|
|
57
|
+
*/
|
|
58
|
+
async stop() {
|
|
59
|
+
if (!this.running)
|
|
60
|
+
return;
|
|
61
|
+
// Clear cleanup interval
|
|
62
|
+
if (this.cleanupInterval) {
|
|
63
|
+
clearInterval(this.cleanupInterval);
|
|
64
|
+
this.cleanupInterval = undefined;
|
|
65
|
+
}
|
|
66
|
+
// Cancel all pending waits
|
|
67
|
+
for (const [id, pending] of this.pendingWaits) {
|
|
68
|
+
clearTimeout(pending.timeoutId);
|
|
69
|
+
pending.resolve({
|
|
70
|
+
success: false,
|
|
71
|
+
events: [],
|
|
72
|
+
error: 'Server shutting down',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
this.pendingWaits.clear();
|
|
76
|
+
// Close server
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
if (this.server) {
|
|
79
|
+
this.server.close(() => {
|
|
80
|
+
this.running = false;
|
|
81
|
+
this.log('Webhook server stopped');
|
|
82
|
+
resolve();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Register a webhook endpoint
|
|
92
|
+
*/
|
|
93
|
+
async register(executionId, options = {}) {
|
|
94
|
+
const id = randomUUID();
|
|
95
|
+
const timeout = options.timeout ?? this.config.defaultTimeout;
|
|
96
|
+
const path = options.path ?? `/webhook/${executionId}/${id}`;
|
|
97
|
+
const registration = {
|
|
98
|
+
id,
|
|
99
|
+
executionId,
|
|
100
|
+
path,
|
|
101
|
+
createdAt: new Date(),
|
|
102
|
+
expiresAt: new Date(Date.now() + timeout),
|
|
103
|
+
expectedEvents: options.expectedEvents ?? 1,
|
|
104
|
+
receivedEvents: 0,
|
|
105
|
+
filter: options.filter,
|
|
106
|
+
};
|
|
107
|
+
await this.store.saveRegistration(registration);
|
|
108
|
+
this.callbacks.onRegistrationCreated?.(registration);
|
|
109
|
+
this.log(`Registered webhook: ${path} (expires: ${registration.expiresAt.toISOString()})`);
|
|
110
|
+
return registration;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the full URL for a webhook endpoint
|
|
114
|
+
*/
|
|
115
|
+
getWebhookUrl(registration) {
|
|
116
|
+
return `${this.config.baseUrl}${registration.path}`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Wait for webhook events
|
|
120
|
+
*/
|
|
121
|
+
async waitForEvents(registrationId, timeout) {
|
|
122
|
+
const registration = await this.store.getRegistration(registrationId);
|
|
123
|
+
if (!registration) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
events: [],
|
|
127
|
+
error: `Registration not found: ${registrationId}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Check if already received enough events
|
|
131
|
+
const events = await this.store.getEvents(registrationId);
|
|
132
|
+
if (events.length >= registration.expectedEvents) {
|
|
133
|
+
return { success: true, events };
|
|
134
|
+
}
|
|
135
|
+
// Wait for more events
|
|
136
|
+
const waitTimeout = timeout ?? (registration.expiresAt.getTime() - Date.now());
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
const timeoutId = setTimeout(() => {
|
|
139
|
+
this.pendingWaits.delete(registrationId);
|
|
140
|
+
this.store.getEvents(registrationId).then((events) => {
|
|
141
|
+
resolve({
|
|
142
|
+
success: events.length >= registration.expectedEvents,
|
|
143
|
+
events,
|
|
144
|
+
timedOut: true,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}, waitTimeout);
|
|
148
|
+
this.pendingWaits.set(registrationId, {
|
|
149
|
+
registrationId,
|
|
150
|
+
resolve,
|
|
151
|
+
timeoutId,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Unregister a webhook endpoint
|
|
157
|
+
*/
|
|
158
|
+
async unregister(registrationId) {
|
|
159
|
+
await this.store.deleteRegistration(registrationId);
|
|
160
|
+
await this.store.deleteEvents(registrationId);
|
|
161
|
+
// Cancel pending wait if any
|
|
162
|
+
const pending = this.pendingWaits.get(registrationId);
|
|
163
|
+
if (pending) {
|
|
164
|
+
clearTimeout(pending.timeoutId);
|
|
165
|
+
this.pendingWaits.delete(registrationId);
|
|
166
|
+
}
|
|
167
|
+
this.log(`Unregistered webhook: ${registrationId}`);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if the server is running
|
|
171
|
+
*/
|
|
172
|
+
isRunning() {
|
|
173
|
+
return this.running;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get the server port
|
|
177
|
+
*/
|
|
178
|
+
getPort() {
|
|
179
|
+
return this.config.port;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the base URL
|
|
183
|
+
*/
|
|
184
|
+
getBaseUrl() {
|
|
185
|
+
return this.config.baseUrl;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Handle incoming HTTP request
|
|
189
|
+
*/
|
|
190
|
+
async handleRequest(req, res) {
|
|
191
|
+
const url = parseUrl(req.url ?? '/', true);
|
|
192
|
+
const path = url.pathname ?? '/';
|
|
193
|
+
// Health check endpoint
|
|
194
|
+
if (path === '/health' || path === '/_health') {
|
|
195
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
196
|
+
res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Find matching registration
|
|
200
|
+
const registration = await this.store.getRegistrationByPath(path);
|
|
201
|
+
if (!registration) {
|
|
202
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
203
|
+
res.end(JSON.stringify({ error: 'Not found', path }));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Check if registration is expired
|
|
207
|
+
if (registration.expiresAt < new Date()) {
|
|
208
|
+
res.writeHead(410, { 'Content-Type': 'application/json' });
|
|
209
|
+
res.end(JSON.stringify({ error: 'Webhook registration expired' }));
|
|
210
|
+
await this.store.deleteRegistration(registration.id);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Parse request body
|
|
214
|
+
let rawBody = '';
|
|
215
|
+
let body = null;
|
|
216
|
+
try {
|
|
217
|
+
rawBody = await this.readBody(req);
|
|
218
|
+
if (rawBody) {
|
|
219
|
+
const contentType = req.headers['content-type'] ?? '';
|
|
220
|
+
if (contentType.includes('application/json')) {
|
|
221
|
+
body = JSON.parse(rawBody);
|
|
222
|
+
}
|
|
223
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
224
|
+
body = Object.fromEntries(new URLSearchParams(rawBody));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
body = rawBody;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
body = rawBody;
|
|
233
|
+
}
|
|
234
|
+
// Create event
|
|
235
|
+
const event = {
|
|
236
|
+
id: randomUUID(),
|
|
237
|
+
registrationId: registration.id,
|
|
238
|
+
receivedAt: new Date(),
|
|
239
|
+
method: req.method ?? 'POST',
|
|
240
|
+
headers: this.extractHeaders(req),
|
|
241
|
+
body,
|
|
242
|
+
rawBody,
|
|
243
|
+
query: url.query,
|
|
244
|
+
};
|
|
245
|
+
// Save event
|
|
246
|
+
await this.store.saveEvent(event);
|
|
247
|
+
registration.receivedEvents++;
|
|
248
|
+
await this.store.saveRegistration(registration);
|
|
249
|
+
this.log(`Webhook received: ${path} (${registration.receivedEvents}/${registration.expectedEvents})`);
|
|
250
|
+
this.callbacks.onWebhookReceived?.(event);
|
|
251
|
+
// Check if all expected events received
|
|
252
|
+
if (registration.receivedEvents >= registration.expectedEvents) {
|
|
253
|
+
const events = await this.store.getEvents(registration.id);
|
|
254
|
+
this.callbacks.onRegistrationComplete?.(registration, events);
|
|
255
|
+
// Resolve pending wait
|
|
256
|
+
const pending = this.pendingWaits.get(registration.id);
|
|
257
|
+
if (pending) {
|
|
258
|
+
clearTimeout(pending.timeoutId);
|
|
259
|
+
this.pendingWaits.delete(registration.id);
|
|
260
|
+
pending.resolve({ success: true, events });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Send response
|
|
264
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
265
|
+
res.end(JSON.stringify({
|
|
266
|
+
success: true,
|
|
267
|
+
eventId: event.id,
|
|
268
|
+
received: registration.receivedEvents,
|
|
269
|
+
expected: registration.expectedEvents,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Read request body
|
|
274
|
+
*/
|
|
275
|
+
readBody(req) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
let data = '';
|
|
278
|
+
req.on('data', (chunk) => {
|
|
279
|
+
data += chunk;
|
|
280
|
+
});
|
|
281
|
+
req.on('end', () => {
|
|
282
|
+
resolve(data);
|
|
283
|
+
});
|
|
284
|
+
req.on('error', reject);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Extract headers from request
|
|
289
|
+
*/
|
|
290
|
+
extractHeaders(req) {
|
|
291
|
+
const headers = {};
|
|
292
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
293
|
+
if (typeof value === 'string') {
|
|
294
|
+
headers[key] = value;
|
|
295
|
+
}
|
|
296
|
+
else if (Array.isArray(value)) {
|
|
297
|
+
headers[key] = value.join(', ');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return headers;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Clean up expired registrations
|
|
304
|
+
*/
|
|
305
|
+
async cleanup() {
|
|
306
|
+
const cleaned = await this.store.cleanupExpired();
|
|
307
|
+
if (cleaned > 0) {
|
|
308
|
+
this.log(`Cleaned up ${cleaned} expired webhook registration(s)`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Log message if verbose mode enabled
|
|
313
|
+
*/
|
|
314
|
+
log(message) {
|
|
315
|
+
if (this.config.verbose) {
|
|
316
|
+
console.log(`[Webhook] ${message}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Store
|
|
3
|
+
*
|
|
4
|
+
* Stores webhook registrations and received events.
|
|
5
|
+
* Supports both in-memory and file-based persistence.
|
|
6
|
+
*/
|
|
7
|
+
import type { WebhookRegistration, WebhookEvent } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Interface for webhook storage
|
|
10
|
+
*/
|
|
11
|
+
export interface WebhookStore {
|
|
12
|
+
/** Save a webhook registration */
|
|
13
|
+
saveRegistration(registration: WebhookRegistration): Promise<void>;
|
|
14
|
+
/** Get a registration by ID */
|
|
15
|
+
getRegistration(id: string): Promise<WebhookRegistration | undefined>;
|
|
16
|
+
/** Get a registration by path */
|
|
17
|
+
getRegistrationByPath(path: string): Promise<WebhookRegistration | undefined>;
|
|
18
|
+
/** Delete a registration */
|
|
19
|
+
deleteRegistration(id: string): Promise<void>;
|
|
20
|
+
/** List all registrations */
|
|
21
|
+
listRegistrations(): Promise<WebhookRegistration[]>;
|
|
22
|
+
/** Save a webhook event */
|
|
23
|
+
saveEvent(event: WebhookEvent): Promise<void>;
|
|
24
|
+
/** Get events for a registration */
|
|
25
|
+
getEvents(registrationId: string): Promise<WebhookEvent[]>;
|
|
26
|
+
/** Delete events for a registration */
|
|
27
|
+
deleteEvents(registrationId: string): Promise<void>;
|
|
28
|
+
/** Clean up expired registrations */
|
|
29
|
+
cleanupExpired(): Promise<number>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* In-memory webhook store
|
|
33
|
+
*/
|
|
34
|
+
export declare class MemoryWebhookStore implements WebhookStore {
|
|
35
|
+
private registrations;
|
|
36
|
+
private events;
|
|
37
|
+
private pathIndex;
|
|
38
|
+
saveRegistration(registration: WebhookRegistration): Promise<void>;
|
|
39
|
+
getRegistration(id: string): Promise<WebhookRegistration | undefined>;
|
|
40
|
+
getRegistrationByPath(path: string): Promise<WebhookRegistration | undefined>;
|
|
41
|
+
deleteRegistration(id: string): Promise<void>;
|
|
42
|
+
listRegistrations(): Promise<WebhookRegistration[]>;
|
|
43
|
+
saveEvent(event: WebhookEvent): Promise<void>;
|
|
44
|
+
getEvents(registrationId: string): Promise<WebhookEvent[]>;
|
|
45
|
+
deleteEvents(registrationId: string): Promise<void>;
|
|
46
|
+
cleanupExpired(): Promise<number>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* File-based webhook store for persistence across restarts
|
|
50
|
+
*/
|
|
51
|
+
export declare class FileWebhookStore implements WebhookStore {
|
|
52
|
+
private baseDir;
|
|
53
|
+
private registrationsDir;
|
|
54
|
+
private eventsDir;
|
|
55
|
+
private initialized;
|
|
56
|
+
constructor(baseDir?: string);
|
|
57
|
+
private ensureInit;
|
|
58
|
+
saveRegistration(registration: WebhookRegistration): Promise<void>;
|
|
59
|
+
getRegistration(id: string): Promise<WebhookRegistration | undefined>;
|
|
60
|
+
getRegistrationByPath(path: string): Promise<WebhookRegistration | undefined>;
|
|
61
|
+
deleteRegistration(id: string): Promise<void>;
|
|
62
|
+
listRegistrations(): Promise<WebhookRegistration[]>;
|
|
63
|
+
saveEvent(event: WebhookEvent): Promise<void>;
|
|
64
|
+
getEvents(registrationId: string): Promise<WebhookEvent[]>;
|
|
65
|
+
deleteEvents(registrationId: string): Promise<void>;
|
|
66
|
+
cleanupExpired(): Promise<number>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Store
|
|
3
|
+
*
|
|
4
|
+
* Stores webhook registrations and received events.
|
|
5
|
+
* Supports both in-memory and file-based persistence.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, readFile, writeFile, readdir, unlink } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
/**
|
|
10
|
+
* In-memory webhook store
|
|
11
|
+
*/
|
|
12
|
+
export class MemoryWebhookStore {
|
|
13
|
+
registrations = new Map();
|
|
14
|
+
events = new Map();
|
|
15
|
+
pathIndex = new Map(); // path -> registrationId
|
|
16
|
+
async saveRegistration(registration) {
|
|
17
|
+
this.registrations.set(registration.id, registration);
|
|
18
|
+
this.pathIndex.set(registration.path, registration.id);
|
|
19
|
+
}
|
|
20
|
+
async getRegistration(id) {
|
|
21
|
+
return this.registrations.get(id);
|
|
22
|
+
}
|
|
23
|
+
async getRegistrationByPath(path) {
|
|
24
|
+
const id = this.pathIndex.get(path);
|
|
25
|
+
if (!id)
|
|
26
|
+
return undefined;
|
|
27
|
+
return this.registrations.get(id);
|
|
28
|
+
}
|
|
29
|
+
async deleteRegistration(id) {
|
|
30
|
+
const reg = this.registrations.get(id);
|
|
31
|
+
if (reg) {
|
|
32
|
+
this.pathIndex.delete(reg.path);
|
|
33
|
+
}
|
|
34
|
+
this.registrations.delete(id);
|
|
35
|
+
}
|
|
36
|
+
async listRegistrations() {
|
|
37
|
+
return Array.from(this.registrations.values());
|
|
38
|
+
}
|
|
39
|
+
async saveEvent(event) {
|
|
40
|
+
const events = this.events.get(event.registrationId) ?? [];
|
|
41
|
+
events.push(event);
|
|
42
|
+
this.events.set(event.registrationId, events);
|
|
43
|
+
}
|
|
44
|
+
async getEvents(registrationId) {
|
|
45
|
+
return this.events.get(registrationId) ?? [];
|
|
46
|
+
}
|
|
47
|
+
async deleteEvents(registrationId) {
|
|
48
|
+
this.events.delete(registrationId);
|
|
49
|
+
}
|
|
50
|
+
async cleanupExpired() {
|
|
51
|
+
const now = new Date();
|
|
52
|
+
let cleaned = 0;
|
|
53
|
+
for (const [id, reg] of this.registrations) {
|
|
54
|
+
if (reg.expiresAt < now) {
|
|
55
|
+
await this.deleteRegistration(id);
|
|
56
|
+
await this.deleteEvents(id);
|
|
57
|
+
cleaned++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return cleaned;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* File-based webhook store for persistence across restarts
|
|
65
|
+
*/
|
|
66
|
+
export class FileWebhookStore {
|
|
67
|
+
baseDir;
|
|
68
|
+
registrationsDir;
|
|
69
|
+
eventsDir;
|
|
70
|
+
initialized = false;
|
|
71
|
+
constructor(baseDir = '.reqon-data/webhooks') {
|
|
72
|
+
this.baseDir = baseDir;
|
|
73
|
+
this.registrationsDir = join(baseDir, 'registrations');
|
|
74
|
+
this.eventsDir = join(baseDir, 'events');
|
|
75
|
+
}
|
|
76
|
+
async ensureInit() {
|
|
77
|
+
if (this.initialized)
|
|
78
|
+
return;
|
|
79
|
+
await mkdir(this.registrationsDir, { recursive: true });
|
|
80
|
+
await mkdir(this.eventsDir, { recursive: true });
|
|
81
|
+
this.initialized = true;
|
|
82
|
+
}
|
|
83
|
+
async saveRegistration(registration) {
|
|
84
|
+
await this.ensureInit();
|
|
85
|
+
const filePath = join(this.registrationsDir, `${registration.id}.json`);
|
|
86
|
+
await writeFile(filePath, JSON.stringify(registration, null, 2));
|
|
87
|
+
}
|
|
88
|
+
async getRegistration(id) {
|
|
89
|
+
await this.ensureInit();
|
|
90
|
+
try {
|
|
91
|
+
const filePath = join(this.registrationsDir, `${id}.json`);
|
|
92
|
+
const content = await readFile(filePath, 'utf-8');
|
|
93
|
+
const data = JSON.parse(content);
|
|
94
|
+
return {
|
|
95
|
+
...data,
|
|
96
|
+
createdAt: new Date(data.createdAt),
|
|
97
|
+
expiresAt: new Date(data.expiresAt),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async getRegistrationByPath(path) {
|
|
105
|
+
const registrations = await this.listRegistrations();
|
|
106
|
+
return registrations.find((r) => r.path === path);
|
|
107
|
+
}
|
|
108
|
+
async deleteRegistration(id) {
|
|
109
|
+
await this.ensureInit();
|
|
110
|
+
try {
|
|
111
|
+
const filePath = join(this.registrationsDir, `${id}.json`);
|
|
112
|
+
await unlink(filePath);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Ignore if file doesn't exist
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async listRegistrations() {
|
|
119
|
+
await this.ensureInit();
|
|
120
|
+
try {
|
|
121
|
+
const files = await readdir(this.registrationsDir);
|
|
122
|
+
const registrations = [];
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
if (!file.endsWith('.json'))
|
|
125
|
+
continue;
|
|
126
|
+
const id = file.replace('.json', '');
|
|
127
|
+
const reg = await this.getRegistration(id);
|
|
128
|
+
if (reg)
|
|
129
|
+
registrations.push(reg);
|
|
130
|
+
}
|
|
131
|
+
return registrations;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async saveEvent(event) {
|
|
138
|
+
await this.ensureInit();
|
|
139
|
+
const eventDir = join(this.eventsDir, event.registrationId);
|
|
140
|
+
await mkdir(eventDir, { recursive: true });
|
|
141
|
+
const filePath = join(eventDir, `${event.id}.json`);
|
|
142
|
+
await writeFile(filePath, JSON.stringify(event, null, 2));
|
|
143
|
+
}
|
|
144
|
+
async getEvents(registrationId) {
|
|
145
|
+
await this.ensureInit();
|
|
146
|
+
try {
|
|
147
|
+
const eventDir = join(this.eventsDir, registrationId);
|
|
148
|
+
const files = await readdir(eventDir);
|
|
149
|
+
const events = [];
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
if (!file.endsWith('.json'))
|
|
152
|
+
continue;
|
|
153
|
+
const filePath = join(eventDir, file);
|
|
154
|
+
const content = await readFile(filePath, 'utf-8');
|
|
155
|
+
const data = JSON.parse(content);
|
|
156
|
+
events.push({
|
|
157
|
+
...data,
|
|
158
|
+
receivedAt: new Date(data.receivedAt),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return events.sort((a, b) => a.receivedAt.getTime() - b.receivedAt.getTime());
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async deleteEvents(registrationId) {
|
|
168
|
+
await this.ensureInit();
|
|
169
|
+
try {
|
|
170
|
+
const eventDir = join(this.eventsDir, registrationId);
|
|
171
|
+
const files = await readdir(eventDir);
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
await unlink(join(eventDir, file));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Ignore if directory doesn't exist
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async cleanupExpired() {
|
|
181
|
+
const now = new Date();
|
|
182
|
+
const registrations = await this.listRegistrations();
|
|
183
|
+
let cleaned = 0;
|
|
184
|
+
for (const reg of registrations) {
|
|
185
|
+
if (reg.expiresAt < now) {
|
|
186
|
+
await this.deleteRegistration(reg.id);
|
|
187
|
+
await this.deleteEvents(reg.id);
|
|
188
|
+
cleaned++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return cleaned;
|
|
192
|
+
}
|
|
193
|
+
}
|