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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ReqonLexer } from '../lexer/index.js';
|
|
3
|
+
import { ReqonParser } from './parser.js';
|
|
4
|
+
describe('ReqonParser', () => {
|
|
5
|
+
function parse(source) {
|
|
6
|
+
const lexer = new ReqonLexer(source);
|
|
7
|
+
const tokens = lexer.tokenize();
|
|
8
|
+
const parser = new ReqonParser(tokens);
|
|
9
|
+
return parser.parse();
|
|
10
|
+
}
|
|
11
|
+
it('parses a simple mission', () => {
|
|
12
|
+
const source = `
|
|
13
|
+
mission TestMission {
|
|
14
|
+
source API {
|
|
15
|
+
auth: bearer,
|
|
16
|
+
base: "https://api.example.com"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
store items: memory("items")
|
|
20
|
+
|
|
21
|
+
action FetchItems {
|
|
22
|
+
fetch GET "/items"
|
|
23
|
+
|
|
24
|
+
store response -> items {
|
|
25
|
+
key: .id
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
run FetchItems
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
const program = parse(source);
|
|
33
|
+
expect(program.type).toBe('ReqonProgram');
|
|
34
|
+
expect(program.statements).toHaveLength(1);
|
|
35
|
+
const mission = program.statements[0];
|
|
36
|
+
expect(mission.type).toBe('MissionDefinition');
|
|
37
|
+
if (mission.type === 'MissionDefinition') {
|
|
38
|
+
expect(mission.name).toBe('TestMission');
|
|
39
|
+
expect(mission.sources).toHaveLength(1);
|
|
40
|
+
expect(mission.stores).toHaveLength(1);
|
|
41
|
+
expect(mission.actions).toHaveLength(1);
|
|
42
|
+
expect(mission.pipeline.stages).toHaveLength(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it('parses fetch with pagination', () => {
|
|
46
|
+
const source = `
|
|
47
|
+
mission PaginatedFetch {
|
|
48
|
+
source API {
|
|
49
|
+
auth: oauth2,
|
|
50
|
+
base: "https://api.example.com"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
store items: memory("items")
|
|
54
|
+
|
|
55
|
+
action FetchAll {
|
|
56
|
+
fetch GET "/items" {
|
|
57
|
+
paginate: offset(page, 50),
|
|
58
|
+
until: response.items.length == 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
store response.items -> items {
|
|
62
|
+
key: .id
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
run FetchAll
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
const program = parse(source);
|
|
70
|
+
const mission = program.statements[0];
|
|
71
|
+
if (mission.type === 'MissionDefinition') {
|
|
72
|
+
const action = mission.actions[0];
|
|
73
|
+
const fetchStep = action.steps[0];
|
|
74
|
+
if (fetchStep.type === 'FetchStep') {
|
|
75
|
+
expect(fetchStep.paginate).toBeDefined();
|
|
76
|
+
expect(fetchStep.paginate?.type).toBe('offset');
|
|
77
|
+
expect(fetchStep.paginate?.pageSize).toBe(50);
|
|
78
|
+
expect(fetchStep.until).toBeDefined();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('parses for loops with conditions', () => {
|
|
83
|
+
const source = `
|
|
84
|
+
mission IterateItems {
|
|
85
|
+
source API {
|
|
86
|
+
auth: bearer,
|
|
87
|
+
base: "https://api.example.com"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
store items: memory("items")
|
|
91
|
+
|
|
92
|
+
action ProcessItems {
|
|
93
|
+
for item in items where .status == "pending" {
|
|
94
|
+
fetch GET "/items/{item.id}"
|
|
95
|
+
|
|
96
|
+
store response -> items {
|
|
97
|
+
key: .id,
|
|
98
|
+
upsert: true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
run ProcessItems
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
const program = parse(source);
|
|
107
|
+
const mission = program.statements[0];
|
|
108
|
+
if (mission.type === 'MissionDefinition') {
|
|
109
|
+
const action = mission.actions[0];
|
|
110
|
+
const forStep = action.steps[0];
|
|
111
|
+
if (forStep.type === 'ForStep') {
|
|
112
|
+
expect(forStep.variable).toBe('item');
|
|
113
|
+
expect(forStep.condition).toBeDefined();
|
|
114
|
+
expect(forStep.steps).toHaveLength(2);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
it('parses map steps with match expressions', () => {
|
|
119
|
+
const source = `
|
|
120
|
+
mission MapItems {
|
|
121
|
+
source API {
|
|
122
|
+
auth: bearer,
|
|
123
|
+
base: "https://api.example.com"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
store raw: memory("raw")
|
|
127
|
+
store normalized: memory("normalized")
|
|
128
|
+
|
|
129
|
+
action Normalize {
|
|
130
|
+
for item in raw {
|
|
131
|
+
map item -> StandardItem {
|
|
132
|
+
id: .external_id,
|
|
133
|
+
status: match .state {
|
|
134
|
+
"active" => "enabled",
|
|
135
|
+
"inactive" => "disabled",
|
|
136
|
+
_ => "unknown"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
store response -> normalized {
|
|
141
|
+
key: .id
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
run Normalize
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
const program = parse(source);
|
|
150
|
+
const mission = program.statements[0];
|
|
151
|
+
if (mission.type === 'MissionDefinition') {
|
|
152
|
+
const action = mission.actions[0];
|
|
153
|
+
const forStep = action.steps[0];
|
|
154
|
+
if (forStep.type === 'ForStep') {
|
|
155
|
+
const mapStep = forStep.steps[0];
|
|
156
|
+
if (mapStep.type === 'MapStep') {
|
|
157
|
+
expect(mapStep.targetSchema).toBe('StandardItem');
|
|
158
|
+
expect(mapStep.mappings).toHaveLength(2);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
it('parses validation steps', () => {
|
|
164
|
+
const source = `
|
|
165
|
+
mission ValidateData {
|
|
166
|
+
source API {
|
|
167
|
+
auth: bearer,
|
|
168
|
+
base: "https://api.example.com"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
store data: memory("data")
|
|
172
|
+
|
|
173
|
+
action Fetch {
|
|
174
|
+
fetch GET "/data"
|
|
175
|
+
|
|
176
|
+
validate response {
|
|
177
|
+
assume length(.items) > 0,
|
|
178
|
+
assume .count > 0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
store response.items -> data {
|
|
182
|
+
key: .id
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
run Fetch
|
|
187
|
+
}
|
|
188
|
+
`;
|
|
189
|
+
const program = parse(source);
|
|
190
|
+
const mission = program.statements[0];
|
|
191
|
+
if (mission.type === 'MissionDefinition') {
|
|
192
|
+
const action = mission.actions[0];
|
|
193
|
+
const validateStep = action.steps[1];
|
|
194
|
+
if (validateStep.type === 'ValidateStep') {
|
|
195
|
+
expect(validateStep.constraints).toHaveLength(2);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
it('parses pipeline with multiple stages', () => {
|
|
200
|
+
const source = `
|
|
201
|
+
mission MultiStage {
|
|
202
|
+
source API {
|
|
203
|
+
auth: bearer,
|
|
204
|
+
base: "https://api.example.com"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
store data: memory("data")
|
|
208
|
+
|
|
209
|
+
action Step1 {
|
|
210
|
+
fetch GET "/step1"
|
|
211
|
+
store response -> data { key: .id }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
action Step2 {
|
|
215
|
+
fetch GET "/step2"
|
|
216
|
+
store response -> data { key: .id }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
action Step3 {
|
|
220
|
+
fetch GET "/step3"
|
|
221
|
+
store response -> data { key: .id }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
run Step1 then Step2 then Step3
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
const program = parse(source);
|
|
228
|
+
const mission = program.statements[0];
|
|
229
|
+
if (mission.type === 'MissionDefinition') {
|
|
230
|
+
expect(mission.pipeline.stages).toHaveLength(3);
|
|
231
|
+
expect(mission.pipeline.stages[0].action).toBe('Step1');
|
|
232
|
+
expect(mission.pipeline.stages[1].action).toBe('Step2');
|
|
233
|
+
expect(mission.pipeline.stages[2].action).toBe('Step3');
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
it('parses parallel stages with bracket syntax', () => {
|
|
237
|
+
const source = `
|
|
238
|
+
mission ParallelSync {
|
|
239
|
+
source API {
|
|
240
|
+
auth: bearer,
|
|
241
|
+
base: "https://api.example.com"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
store orders: memory("orders")
|
|
245
|
+
store payments: memory("payments")
|
|
246
|
+
store reconciled: memory("reconciled")
|
|
247
|
+
|
|
248
|
+
action FetchOrders {
|
|
249
|
+
fetch GET "/orders"
|
|
250
|
+
store response -> orders { key: .id }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
action FetchPayments {
|
|
254
|
+
fetch GET "/payments"
|
|
255
|
+
store response -> payments { key: .id }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
action Reconcile {
|
|
259
|
+
fetch GET "/reconcile"
|
|
260
|
+
store response -> reconciled { key: .id }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
run [FetchOrders, FetchPayments] then Reconcile
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
const program = parse(source);
|
|
267
|
+
const mission = program.statements[0];
|
|
268
|
+
if (mission.type === 'MissionDefinition') {
|
|
269
|
+
expect(mission.pipeline.stages).toHaveLength(2);
|
|
270
|
+
// First stage is parallel
|
|
271
|
+
const parallelStage = mission.pipeline.stages[0];
|
|
272
|
+
expect(parallelStage.actions).toBeDefined();
|
|
273
|
+
expect(parallelStage.actions).toEqual(['FetchOrders', 'FetchPayments']);
|
|
274
|
+
expect(parallelStage.action).toBeUndefined();
|
|
275
|
+
// Second stage is sequential
|
|
276
|
+
const sequentialStage = mission.pipeline.stages[1];
|
|
277
|
+
expect(sequentialStage.action).toBe('Reconcile');
|
|
278
|
+
expect(sequentialStage.actions).toBeUndefined();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
it('parses multiple parallel stages', () => {
|
|
282
|
+
const source = `
|
|
283
|
+
mission ComplexPipeline {
|
|
284
|
+
source API {
|
|
285
|
+
auth: bearer,
|
|
286
|
+
base: "https://api.example.com"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
store data: memory("data")
|
|
290
|
+
|
|
291
|
+
action A { fetch GET "/a" }
|
|
292
|
+
action B { fetch GET "/b" }
|
|
293
|
+
action C { fetch GET "/c" }
|
|
294
|
+
action D { fetch GET "/d" }
|
|
295
|
+
action E { fetch GET "/e" }
|
|
296
|
+
|
|
297
|
+
run A then [B, C, D] then E
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
const program = parse(source);
|
|
301
|
+
const mission = program.statements[0];
|
|
302
|
+
if (mission.type === 'MissionDefinition') {
|
|
303
|
+
expect(mission.pipeline.stages).toHaveLength(3);
|
|
304
|
+
expect(mission.pipeline.stages[0].action).toBe('A');
|
|
305
|
+
expect(mission.pipeline.stages[1].actions).toEqual(['B', 'C', 'D']);
|
|
306
|
+
expect(mission.pipeline.stages[2].action).toBe('E');
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
it('parses parallel stage with three actions', () => {
|
|
310
|
+
const source = `
|
|
311
|
+
mission TripleParallel {
|
|
312
|
+
source API {
|
|
313
|
+
auth: bearer,
|
|
314
|
+
base: "https://api.example.com"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
store data: memory("data")
|
|
318
|
+
|
|
319
|
+
action X { fetch GET "/x" }
|
|
320
|
+
action Y { fetch GET "/y" }
|
|
321
|
+
action Z { fetch GET "/z" }
|
|
322
|
+
|
|
323
|
+
run [X, Y, Z]
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
const program = parse(source);
|
|
327
|
+
const mission = program.statements[0];
|
|
328
|
+
if (mission.type === 'MissionDefinition') {
|
|
329
|
+
expect(mission.pipeline.stages).toHaveLength(1);
|
|
330
|
+
expect(mission.pipeline.stages[0].actions).toEqual(['X', 'Y', 'Z']);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ReqonLexer } from '../lexer/index.js';
|
|
3
|
+
import { ReqonParser } from './parser.js';
|
|
4
|
+
function parseSchedule(source) {
|
|
5
|
+
const lexer = new ReqonLexer(source);
|
|
6
|
+
const tokens = lexer.tokenize();
|
|
7
|
+
const parser = new ReqonParser(tokens);
|
|
8
|
+
const program = parser.parse();
|
|
9
|
+
const mission = program.statements.find((s) => s.type === 'MissionDefinition');
|
|
10
|
+
return mission?.schedule;
|
|
11
|
+
}
|
|
12
|
+
describe('Schedule parsing', () => {
|
|
13
|
+
describe('interval schedules', () => {
|
|
14
|
+
it('should parse schedule: every N hours', () => {
|
|
15
|
+
const source = `
|
|
16
|
+
mission Test {
|
|
17
|
+
schedule: every 6 hours
|
|
18
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
19
|
+
action Sync { fetch GET "/data" }
|
|
20
|
+
run Sync
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
const schedule = parseSchedule(source);
|
|
24
|
+
expect(schedule).toBeDefined();
|
|
25
|
+
expect(schedule.scheduleType).toBe('interval');
|
|
26
|
+
expect(schedule.interval).toEqual({ value: 6, unit: 'hours' });
|
|
27
|
+
});
|
|
28
|
+
it('should parse schedule: every N minutes', () => {
|
|
29
|
+
const source = `
|
|
30
|
+
mission Test {
|
|
31
|
+
schedule: every 30 minutes
|
|
32
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
33
|
+
action Sync { fetch GET "/data" }
|
|
34
|
+
run Sync
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
const schedule = parseSchedule(source);
|
|
38
|
+
expect(schedule).toBeDefined();
|
|
39
|
+
expect(schedule.scheduleType).toBe('interval');
|
|
40
|
+
expect(schedule.interval).toEqual({ value: 30, unit: 'minutes' });
|
|
41
|
+
});
|
|
42
|
+
it('should parse schedule: every N seconds', () => {
|
|
43
|
+
const source = `
|
|
44
|
+
mission Test {
|
|
45
|
+
schedule: every 60 seconds
|
|
46
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
47
|
+
action Sync { fetch GET "/data" }
|
|
48
|
+
run Sync
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
const schedule = parseSchedule(source);
|
|
52
|
+
expect(schedule).toBeDefined();
|
|
53
|
+
expect(schedule.scheduleType).toBe('interval');
|
|
54
|
+
expect(schedule.interval).toEqual({ value: 60, unit: 'seconds' });
|
|
55
|
+
});
|
|
56
|
+
it('should parse schedule: every N days', () => {
|
|
57
|
+
const source = `
|
|
58
|
+
mission Test {
|
|
59
|
+
schedule: every 1 days
|
|
60
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
61
|
+
action Sync { fetch GET "/data" }
|
|
62
|
+
run Sync
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
const schedule = parseSchedule(source);
|
|
66
|
+
expect(schedule).toBeDefined();
|
|
67
|
+
expect(schedule.scheduleType).toBe('interval');
|
|
68
|
+
expect(schedule.interval).toEqual({ value: 1, unit: 'days' });
|
|
69
|
+
});
|
|
70
|
+
it('should parse schedule: every N weeks', () => {
|
|
71
|
+
const source = `
|
|
72
|
+
mission Test {
|
|
73
|
+
schedule: every 2 weeks
|
|
74
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
75
|
+
action Sync { fetch GET "/data" }
|
|
76
|
+
run Sync
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
const schedule = parseSchedule(source);
|
|
80
|
+
expect(schedule).toBeDefined();
|
|
81
|
+
expect(schedule.scheduleType).toBe('interval');
|
|
82
|
+
expect(schedule.interval).toEqual({ value: 2, unit: 'weeks' });
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('cron schedules', () => {
|
|
86
|
+
it('should parse schedule: cron expression', () => {
|
|
87
|
+
const source = `
|
|
88
|
+
mission Test {
|
|
89
|
+
schedule: cron "0 */6 * * *"
|
|
90
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
91
|
+
action Sync { fetch GET "/data" }
|
|
92
|
+
run Sync
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
const schedule = parseSchedule(source);
|
|
96
|
+
expect(schedule).toBeDefined();
|
|
97
|
+
expect(schedule.scheduleType).toBe('cron');
|
|
98
|
+
expect(schedule.cronExpression).toBe('0 */6 * * *');
|
|
99
|
+
});
|
|
100
|
+
it('should parse complex cron expressions', () => {
|
|
101
|
+
const source = `
|
|
102
|
+
mission Test {
|
|
103
|
+
schedule: cron "30 9 15 * 1-5"
|
|
104
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
105
|
+
action Sync { fetch GET "/data" }
|
|
106
|
+
run Sync
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
const schedule = parseSchedule(source);
|
|
110
|
+
expect(schedule).toBeDefined();
|
|
111
|
+
expect(schedule.scheduleType).toBe('cron');
|
|
112
|
+
expect(schedule.cronExpression).toBe('30 9 15 * 1-5');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('one-time schedules', () => {
|
|
116
|
+
it('should parse schedule: at datetime', () => {
|
|
117
|
+
const source = `
|
|
118
|
+
mission Test {
|
|
119
|
+
schedule: at "2025-01-25T15:00:00Z"
|
|
120
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
121
|
+
action Sync { fetch GET "/data" }
|
|
122
|
+
run Sync
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
const schedule = parseSchedule(source);
|
|
126
|
+
expect(schedule).toBeDefined();
|
|
127
|
+
expect(schedule.scheduleType).toBe('once');
|
|
128
|
+
expect(schedule.runAt).toBe('2025-01-25T15:00:00Z');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('schedule options', () => {
|
|
132
|
+
it('should parse schedule with timezone option', () => {
|
|
133
|
+
const source = `
|
|
134
|
+
mission Test {
|
|
135
|
+
schedule: every 6 hours {
|
|
136
|
+
timezone: "America/New_York"
|
|
137
|
+
}
|
|
138
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
139
|
+
action Sync { fetch GET "/data" }
|
|
140
|
+
run Sync
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
const schedule = parseSchedule(source);
|
|
144
|
+
expect(schedule).toBeDefined();
|
|
145
|
+
expect(schedule.timezone).toBe('America/New_York');
|
|
146
|
+
});
|
|
147
|
+
it('should parse schedule with maxConcurrency option', () => {
|
|
148
|
+
const source = `
|
|
149
|
+
mission Test {
|
|
150
|
+
schedule: every 6 hours {
|
|
151
|
+
maxConcurrency: 2
|
|
152
|
+
}
|
|
153
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
154
|
+
action Sync { fetch GET "/data" }
|
|
155
|
+
run Sync
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
const schedule = parseSchedule(source);
|
|
159
|
+
expect(schedule).toBeDefined();
|
|
160
|
+
expect(schedule.maxConcurrency).toBe(2);
|
|
161
|
+
});
|
|
162
|
+
it('should parse schedule with skipIfRunning option', () => {
|
|
163
|
+
const source = `
|
|
164
|
+
mission Test {
|
|
165
|
+
schedule: every 6 hours {
|
|
166
|
+
skipIfRunning: false
|
|
167
|
+
}
|
|
168
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
169
|
+
action Sync { fetch GET "/data" }
|
|
170
|
+
run Sync
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
const schedule = parseSchedule(source);
|
|
174
|
+
expect(schedule).toBeDefined();
|
|
175
|
+
expect(schedule.skipIfRunning).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
it('should parse schedule with retry config', () => {
|
|
178
|
+
const source = `
|
|
179
|
+
mission Test {
|
|
180
|
+
schedule: every 6 hours {
|
|
181
|
+
retry: {
|
|
182
|
+
maxRetries: 5,
|
|
183
|
+
delaySeconds: 120
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
187
|
+
action Sync { fetch GET "/data" }
|
|
188
|
+
run Sync
|
|
189
|
+
}
|
|
190
|
+
`;
|
|
191
|
+
const schedule = parseSchedule(source);
|
|
192
|
+
expect(schedule).toBeDefined();
|
|
193
|
+
expect(schedule.retryOnFailure).toEqual({
|
|
194
|
+
maxRetries: 5,
|
|
195
|
+
delaySeconds: 120,
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
it('should parse schedule with multiple options', () => {
|
|
199
|
+
const source = `
|
|
200
|
+
mission Test {
|
|
201
|
+
schedule: cron "0 9 * * 1-5" {
|
|
202
|
+
timezone: "Europe/London",
|
|
203
|
+
maxConcurrency: 1,
|
|
204
|
+
skipIfRunning: true,
|
|
205
|
+
retry: {
|
|
206
|
+
maxRetries: 3,
|
|
207
|
+
delaySeconds: 60
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
211
|
+
action Sync { fetch GET "/data" }
|
|
212
|
+
run Sync
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
const schedule = parseSchedule(source);
|
|
216
|
+
expect(schedule).toBeDefined();
|
|
217
|
+
expect(schedule.scheduleType).toBe('cron');
|
|
218
|
+
expect(schedule.cronExpression).toBe('0 9 * * 1-5');
|
|
219
|
+
expect(schedule.timezone).toBe('Europe/London');
|
|
220
|
+
expect(schedule.maxConcurrency).toBe(1);
|
|
221
|
+
expect(schedule.skipIfRunning).toBe(true);
|
|
222
|
+
expect(schedule.retryOnFailure).toEqual({
|
|
223
|
+
maxRetries: 3,
|
|
224
|
+
delaySeconds: 60,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
describe('mission without schedule', () => {
|
|
229
|
+
it('should parse mission without schedule', () => {
|
|
230
|
+
const source = `
|
|
231
|
+
mission Test {
|
|
232
|
+
source API { auth: none, base: "http://api.example.com" }
|
|
233
|
+
action Sync { fetch GET "/data" }
|
|
234
|
+
run Sync
|
|
235
|
+
}
|
|
236
|
+
`;
|
|
237
|
+
const schedule = parseSchedule(source);
|
|
238
|
+
expect(schedule).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reqon plugin for Vague.
|
|
3
|
+
*
|
|
4
|
+
* This plugin registers Reqon's keywords and statement parsers with Vague,
|
|
5
|
+
* allowing Vague to parse Reqon syntax when the plugin is registered.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { registerPlugin } from 'vague-lang';
|
|
9
|
+
* import { reqonPlugin } from 'reqon';
|
|
10
|
+
* registerPlugin(reqonPlugin);
|
|
11
|
+
*
|
|
12
|
+
* Or simply import the plugin module to auto-register:
|
|
13
|
+
* import 'reqon/plugin';
|
|
14
|
+
*/
|
|
15
|
+
import { type VaguePlugin } from 'vague-lang';
|
|
16
|
+
/**
|
|
17
|
+
* The Reqon plugin for Vague.
|
|
18
|
+
*
|
|
19
|
+
* Registers all Reqon keywords with Vague's lexer, allowing Vague's
|
|
20
|
+
* lexer to tokenize Reqon source code.
|
|
21
|
+
*/
|
|
22
|
+
export declare const reqonPlugin: VaguePlugin;
|
|
23
|
+
/**
|
|
24
|
+
* Register Reqon with Vague's plugin system.
|
|
25
|
+
* Safe to call multiple times - will only register once.
|
|
26
|
+
*/
|
|
27
|
+
export declare function registerReqonPlugin(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Unregister Reqon from Vague's plugin system.
|
|
30
|
+
*/
|
|
31
|
+
export declare function unregisterReqonPlugin(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Check if Reqon plugin is currently registered.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isReqonPluginRegistered(): boolean;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reqon plugin for Vague.
|
|
3
|
+
*
|
|
4
|
+
* This plugin registers Reqon's keywords and statement parsers with Vague,
|
|
5
|
+
* allowing Vague to parse Reqon syntax when the plugin is registered.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { registerPlugin } from 'vague-lang';
|
|
9
|
+
* import { reqonPlugin } from 'reqon';
|
|
10
|
+
* registerPlugin(reqonPlugin);
|
|
11
|
+
*
|
|
12
|
+
* Or simply import the plugin module to auto-register:
|
|
13
|
+
* import 'reqon/plugin';
|
|
14
|
+
*/
|
|
15
|
+
import { registerPlugin, unregisterPlugin, } from 'vague-lang';
|
|
16
|
+
import { REQON_KEYWORDS } from './lexer/tokens.js';
|
|
17
|
+
/**
|
|
18
|
+
* Convert REQON_KEYWORDS map to PluginKeyword array for Vague plugin system.
|
|
19
|
+
*/
|
|
20
|
+
function buildKeywords() {
|
|
21
|
+
const keywords = [];
|
|
22
|
+
for (const [keyword, tokenType] of Object.entries(REQON_KEYWORDS)) {
|
|
23
|
+
keywords.push({
|
|
24
|
+
keyword,
|
|
25
|
+
tokenType: tokenType,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return keywords;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The Reqon plugin for Vague.
|
|
32
|
+
*
|
|
33
|
+
* Registers all Reqon keywords with Vague's lexer, allowing Vague's
|
|
34
|
+
* lexer to tokenize Reqon source code.
|
|
35
|
+
*/
|
|
36
|
+
export const reqonPlugin = {
|
|
37
|
+
name: 'reqon',
|
|
38
|
+
keywords: buildKeywords(),
|
|
39
|
+
// Statement parsers will be added when we refactor ReqonParser
|
|
40
|
+
};
|
|
41
|
+
let isRegistered = false;
|
|
42
|
+
/**
|
|
43
|
+
* Register Reqon with Vague's plugin system.
|
|
44
|
+
* Safe to call multiple times - will only register once.
|
|
45
|
+
*/
|
|
46
|
+
export function registerReqonPlugin() {
|
|
47
|
+
if (!isRegistered) {
|
|
48
|
+
registerPlugin(reqonPlugin);
|
|
49
|
+
isRegistered = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Unregister Reqon from Vague's plugin system.
|
|
54
|
+
*/
|
|
55
|
+
export function unregisterReqonPlugin() {
|
|
56
|
+
if (isRegistered) {
|
|
57
|
+
unregisterPlugin('reqon');
|
|
58
|
+
isRegistered = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if Reqon plugin is currently registered.
|
|
63
|
+
*/
|
|
64
|
+
export function isReqonPluginRegistered() {
|
|
65
|
+
return isRegistered;
|
|
66
|
+
}
|
|
67
|
+
// Auto-register on import
|
|
68
|
+
registerReqonPlugin();
|