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.
Files changed (388) hide show
  1. package/.claude/settings.local.json +31 -0
  2. package/.claude/skills/api-integration.md +125 -0
  3. package/.claude/skills/database-schema.md +51 -0
  4. package/.claude/skills/dsl-design.md +80 -0
  5. package/.claude/skills/property-testing.md +143 -0
  6. package/.claude/skills/reqon/SKILL.md +44 -0
  7. package/.claude/skills/reqon/references/examples.md +206 -0
  8. package/.claude/skills/reqon/references/syntax.md +263 -0
  9. package/.claude/skills/vscode-extension.md +113 -0
  10. package/.github/dependabot.yml +32 -0
  11. package/.github/pull_request_template.md +21 -0
  12. package/.github/workflows/ci.yml +174 -0
  13. package/.github/workflows/release.yml +73 -0
  14. package/CLAUDE.md +72 -0
  15. package/CONTRIBUTING.md +161 -0
  16. package/README.md +235 -0
  17. package/TODO.md +51 -0
  18. package/dist/ast/index.d.ts +1 -0
  19. package/dist/ast/index.js +1 -0
  20. package/dist/ast/nodes.d.ts +237 -0
  21. package/dist/ast/nodes.js +12 -0
  22. package/dist/auth/auth.test.d.ts +1 -0
  23. package/dist/auth/auth.test.js +255 -0
  24. package/dist/auth/circuit-breaker.d.ts +115 -0
  25. package/dist/auth/circuit-breaker.js +267 -0
  26. package/dist/auth/credentials.d.ts +91 -0
  27. package/dist/auth/credentials.js +169 -0
  28. package/dist/auth/index.d.ts +5 -0
  29. package/dist/auth/index.js +8 -0
  30. package/dist/auth/oauth2-provider.d.ts +41 -0
  31. package/dist/auth/oauth2-provider.js +131 -0
  32. package/dist/auth/rate-limiter.d.ts +61 -0
  33. package/dist/auth/rate-limiter.js +380 -0
  34. package/dist/auth/token-store.d.ts +30 -0
  35. package/dist/auth/token-store.js +148 -0
  36. package/dist/auth/types.d.ts +142 -0
  37. package/dist/auth/types.js +1 -0
  38. package/dist/cli.d.ts +2 -0
  39. package/dist/cli.js +270 -0
  40. package/dist/errors/errors.test.d.ts +1 -0
  41. package/dist/errors/errors.test.js +165 -0
  42. package/dist/errors/index.d.ts +83 -0
  43. package/dist/errors/index.js +159 -0
  44. package/dist/execution/execution.test.d.ts +1 -0
  45. package/dist/execution/execution.test.js +246 -0
  46. package/dist/execution/index.d.ts +4 -0
  47. package/dist/execution/index.js +2 -0
  48. package/dist/execution/state.d.ts +136 -0
  49. package/dist/execution/state.js +82 -0
  50. package/dist/execution/store.d.ts +52 -0
  51. package/dist/execution/store.js +120 -0
  52. package/dist/index.d.ts +27 -0
  53. package/dist/index.js +57 -0
  54. package/dist/integration.test.d.ts +1 -0
  55. package/dist/integration.test.js +168 -0
  56. package/dist/interpreter/context.d.ts +15 -0
  57. package/dist/interpreter/context.js +29 -0
  58. package/dist/interpreter/evaluator.d.ts +5 -0
  59. package/dist/interpreter/evaluator.js +223 -0
  60. package/dist/interpreter/evaluator.test.d.ts +1 -0
  61. package/dist/interpreter/evaluator.test.js +512 -0
  62. package/dist/interpreter/executor.d.ts +131 -0
  63. package/dist/interpreter/executor.js +663 -0
  64. package/dist/interpreter/fetch-handler.d.ts +43 -0
  65. package/dist/interpreter/fetch-handler.js +203 -0
  66. package/dist/interpreter/http.d.ts +57 -0
  67. package/dist/interpreter/http.js +210 -0
  68. package/dist/interpreter/http.test.d.ts +1 -0
  69. package/dist/interpreter/http.test.js +299 -0
  70. package/dist/interpreter/index.d.ts +7 -0
  71. package/dist/interpreter/index.js +7 -0
  72. package/dist/interpreter/pagination.d.ts +63 -0
  73. package/dist/interpreter/pagination.js +155 -0
  74. package/dist/interpreter/progress.test.d.ts +1 -0
  75. package/dist/interpreter/progress.test.js +216 -0
  76. package/dist/interpreter/schema-matcher.d.ts +16 -0
  77. package/dist/interpreter/schema-matcher.js +136 -0
  78. package/dist/interpreter/schema-matcher.test.d.ts +1 -0
  79. package/dist/interpreter/schema-matcher.test.js +122 -0
  80. package/dist/interpreter/signals.d.ts +57 -0
  81. package/dist/interpreter/signals.js +73 -0
  82. package/dist/interpreter/step-handlers/for-handler.d.ts +17 -0
  83. package/dist/interpreter/step-handlers/for-handler.js +51 -0
  84. package/dist/interpreter/step-handlers/index.d.ts +8 -0
  85. package/dist/interpreter/step-handlers/index.js +8 -0
  86. package/dist/interpreter/step-handlers/map-handler.d.ts +10 -0
  87. package/dist/interpreter/step-handlers/map-handler.js +20 -0
  88. package/dist/interpreter/step-handlers/match-handler.d.ts +27 -0
  89. package/dist/interpreter/step-handlers/match-handler.js +61 -0
  90. package/dist/interpreter/step-handlers/store-handler.d.ts +13 -0
  91. package/dist/interpreter/step-handlers/store-handler.js +66 -0
  92. package/dist/interpreter/step-handlers/types.d.ts +15 -0
  93. package/dist/interpreter/step-handlers/types.js +1 -0
  94. package/dist/interpreter/step-handlers/validate-handler.d.ts +10 -0
  95. package/dist/interpreter/step-handlers/validate-handler.js +26 -0
  96. package/dist/interpreter/step-handlers/webhook-handler.d.ts +36 -0
  97. package/dist/interpreter/step-handlers/webhook-handler.js +104 -0
  98. package/dist/lexer/index.d.ts +10 -0
  99. package/dist/lexer/index.js +12 -0
  100. package/dist/lexer/lexer.d.ts +24 -0
  101. package/dist/lexer/lexer.js +264 -0
  102. package/dist/lexer/lexer.test.d.ts +1 -0
  103. package/dist/lexer/lexer.test.js +259 -0
  104. package/dist/lexer/tokens.d.ts +69 -0
  105. package/dist/lexer/tokens.js +146 -0
  106. package/dist/loader/index.d.ts +36 -0
  107. package/dist/loader/index.js +220 -0
  108. package/dist/loader/loader.test.d.ts +1 -0
  109. package/dist/loader/loader.test.js +287 -0
  110. package/dist/oas/index.d.ts +4 -0
  111. package/dist/oas/index.js +2 -0
  112. package/dist/oas/loader.d.ts +21 -0
  113. package/dist/oas/loader.js +82 -0
  114. package/dist/oas/oas.test.d.ts +1 -0
  115. package/dist/oas/oas.test.js +218 -0
  116. package/dist/oas/validator.d.ts +12 -0
  117. package/dist/oas/validator.js +227 -0
  118. package/dist/parser/base.d.ts +33 -0
  119. package/dist/parser/base.js +97 -0
  120. package/dist/parser/expressions.d.ts +27 -0
  121. package/dist/parser/expressions.js +248 -0
  122. package/dist/parser/expressions.test.d.ts +1 -0
  123. package/dist/parser/expressions.test.js +378 -0
  124. package/dist/parser/index.d.ts +3 -0
  125. package/dist/parser/index.js +3 -0
  126. package/dist/parser/match.test.d.ts +1 -0
  127. package/dist/parser/match.test.js +254 -0
  128. package/dist/parser/parser.d.ts +68 -0
  129. package/dist/parser/parser.js +1229 -0
  130. package/dist/parser/parser.test.d.ts +1 -0
  131. package/dist/parser/parser.test.js +333 -0
  132. package/dist/parser/schedule.test.d.ts +1 -0
  133. package/dist/parser/schedule.test.js +241 -0
  134. package/dist/plugin.d.ts +35 -0
  135. package/dist/plugin.js +68 -0
  136. package/dist/scheduler/cron-parser.d.ts +32 -0
  137. package/dist/scheduler/cron-parser.js +198 -0
  138. package/dist/scheduler/cron-parser.test.d.ts +1 -0
  139. package/dist/scheduler/cron-parser.test.js +188 -0
  140. package/dist/scheduler/index.d.ts +3 -0
  141. package/dist/scheduler/index.js +2 -0
  142. package/dist/scheduler/scheduler.d.ts +81 -0
  143. package/dist/scheduler/scheduler.js +376 -0
  144. package/dist/scheduler/types.d.ts +65 -0
  145. package/dist/scheduler/types.js +1 -0
  146. package/dist/stores/factory.d.ts +36 -0
  147. package/dist/stores/factory.js +73 -0
  148. package/dist/stores/file.d.ts +60 -0
  149. package/dist/stores/file.js +173 -0
  150. package/dist/stores/file.test.d.ts +1 -0
  151. package/dist/stores/file.test.js +165 -0
  152. package/dist/stores/index.d.ts +6 -0
  153. package/dist/stores/index.js +5 -0
  154. package/dist/stores/memory.d.ts +19 -0
  155. package/dist/stores/memory.js +51 -0
  156. package/dist/stores/memory.test.d.ts +1 -0
  157. package/dist/stores/memory.test.js +157 -0
  158. package/dist/stores/postgrest.d.ts +55 -0
  159. package/dist/stores/postgrest.js +217 -0
  160. package/dist/stores/stores.test.d.ts +1 -0
  161. package/dist/stores/stores.test.js +158 -0
  162. package/dist/stores/types.d.ts +31 -0
  163. package/dist/stores/types.js +26 -0
  164. package/dist/sync/index.d.ts +4 -0
  165. package/dist/sync/index.js +2 -0
  166. package/dist/sync/state.d.ts +69 -0
  167. package/dist/sync/state.js +66 -0
  168. package/dist/sync/store.d.ts +49 -0
  169. package/dist/sync/store.js +93 -0
  170. package/dist/sync/sync.test.d.ts +1 -0
  171. package/dist/sync/sync.test.js +221 -0
  172. package/dist/utils/async.d.ts +7 -0
  173. package/dist/utils/async.js +9 -0
  174. package/dist/utils/file.d.ts +38 -0
  175. package/dist/utils/file.js +92 -0
  176. package/dist/utils/index.d.ts +4 -0
  177. package/dist/utils/index.js +4 -0
  178. package/dist/utils/logger.d.ts +34 -0
  179. package/dist/utils/logger.js +39 -0
  180. package/dist/utils/path.d.ts +12 -0
  181. package/dist/utils/path.js +41 -0
  182. package/dist/webhook/index.d.ts +8 -0
  183. package/dist/webhook/index.js +7 -0
  184. package/dist/webhook/server.d.ts +84 -0
  185. package/dist/webhook/server.js +319 -0
  186. package/dist/webhook/store.d.ts +67 -0
  187. package/dist/webhook/store.js +193 -0
  188. package/dist/webhook/types.d.ts +88 -0
  189. package/dist/webhook/types.js +6 -0
  190. package/docusaurus/README.md +41 -0
  191. package/docusaurus/docs/advanced/execution-state.md +283 -0
  192. package/docusaurus/docs/advanced/extending-reqon.md +388 -0
  193. package/docusaurus/docs/advanced/multi-file-missions.md +250 -0
  194. package/docusaurus/docs/advanced/parallel-execution.md +353 -0
  195. package/docusaurus/docs/api-reference.md +443 -0
  196. package/docusaurus/docs/authentication/api-key.md +339 -0
  197. package/docusaurus/docs/authentication/basic.md +276 -0
  198. package/docusaurus/docs/authentication/bearer.md +282 -0
  199. package/docusaurus/docs/authentication/oauth2.md +317 -0
  200. package/docusaurus/docs/authentication/overview.md +251 -0
  201. package/docusaurus/docs/cli.md +229 -0
  202. package/docusaurus/docs/core-concepts/actions.md +286 -0
  203. package/docusaurus/docs/core-concepts/missions.md +264 -0
  204. package/docusaurus/docs/core-concepts/schemas.md +353 -0
  205. package/docusaurus/docs/core-concepts/sources.md +339 -0
  206. package/docusaurus/docs/core-concepts/stores.md +332 -0
  207. package/docusaurus/docs/dsl-syntax/expressions.md +361 -0
  208. package/docusaurus/docs/dsl-syntax/fetch.md +293 -0
  209. package/docusaurus/docs/dsl-syntax/for-loops.md +324 -0
  210. package/docusaurus/docs/dsl-syntax/map.md +345 -0
  211. package/docusaurus/docs/dsl-syntax/match.md +387 -0
  212. package/docusaurus/docs/dsl-syntax/pipelines.md +397 -0
  213. package/docusaurus/docs/dsl-syntax/validate.md +401 -0
  214. package/docusaurus/docs/error-handling/dead-letter-queues.md +399 -0
  215. package/docusaurus/docs/error-handling/flow-control.md +337 -0
  216. package/docusaurus/docs/error-handling/retry-strategies.md +368 -0
  217. package/docusaurus/docs/examples.md +488 -0
  218. package/docusaurus/docs/getting-started.md +256 -0
  219. package/docusaurus/docs/http/circuit-breaker.md +401 -0
  220. package/docusaurus/docs/http/incremental-sync.md +394 -0
  221. package/docusaurus/docs/http/pagination.md +361 -0
  222. package/docusaurus/docs/http/rate-limiting.md +383 -0
  223. package/docusaurus/docs/http/requests.md +328 -0
  224. package/docusaurus/docs/http/retry.md +402 -0
  225. package/docusaurus/docs/intro.md +90 -0
  226. package/docusaurus/docs/openapi/loading-specs.md +305 -0
  227. package/docusaurus/docs/openapi/operation-calls.md +314 -0
  228. package/docusaurus/docs/openapi/overview.md +212 -0
  229. package/docusaurus/docs/openapi/response-validation.md +344 -0
  230. package/docusaurus/docs/scheduling/cron.md +305 -0
  231. package/docusaurus/docs/scheduling/daemon-mode.md +317 -0
  232. package/docusaurus/docs/scheduling/intervals.md +289 -0
  233. package/docusaurus/docs/scheduling/overview.md +231 -0
  234. package/docusaurus/docs/stores/custom-adapters.md +376 -0
  235. package/docusaurus/docs/stores/file.md +236 -0
  236. package/docusaurus/docs/stores/memory.md +193 -0
  237. package/docusaurus/docs/stores/overview.md +274 -0
  238. package/docusaurus/docs/stores/postgrest.md +316 -0
  239. package/docusaurus/docusaurus.config.ts +148 -0
  240. package/docusaurus/package-lock.json +18029 -0
  241. package/docusaurus/package.json +47 -0
  242. package/docusaurus/sidebars.ts +155 -0
  243. package/docusaurus/src/components/HomepageFeatures/index.tsx +105 -0
  244. package/docusaurus/src/components/HomepageFeatures/styles.module.css +12 -0
  245. package/docusaurus/src/css/custom.css +169 -0
  246. package/docusaurus/src/pages/index.module.css +48 -0
  247. package/docusaurus/src/pages/index.tsx +110 -0
  248. package/docusaurus/src/pages/markdown-page.md +7 -0
  249. package/docusaurus/static/.nojekyll +0 -0
  250. package/docusaurus/static/img/docusaurus-social-card.jpg +0 -0
  251. package/docusaurus/static/img/docusaurus.png +0 -0
  252. package/docusaurus/static/img/favicon.ico +0 -0
  253. package/docusaurus/static/img/logo.svg +10 -0
  254. package/docusaurus/static/img/undraw_docusaurus_mountain.svg +171 -0
  255. package/docusaurus/static/img/undraw_docusaurus_react.svg +170 -0
  256. package/docusaurus/static/img/undraw_docusaurus_tree.svg +40 -0
  257. package/docusaurus/tsconfig.json +8 -0
  258. package/examples/README.md +112 -0
  259. package/examples/error-handling/README.md +150 -0
  260. package/examples/error-handling/payment-processor.vague +287 -0
  261. package/examples/github-sync/README.md +74 -0
  262. package/examples/github-sync/fetch-issues.vague +47 -0
  263. package/examples/github-sync/fetch-prs.vague +40 -0
  264. package/examples/github-sync/mission.vague +101 -0
  265. package/examples/github-sync/normalize.vague +70 -0
  266. package/examples/jsonplaceholder/README.md +28 -0
  267. package/examples/jsonplaceholder/posts.vague +48 -0
  268. package/examples/petstore/README.md +35 -0
  269. package/examples/petstore/openapi.yaml +97 -0
  270. package/examples/petstore/sync.vague +52 -0
  271. package/examples/temporal-comparison/README.md +297 -0
  272. package/examples/temporal-comparison/reconciliation.vague +355 -0
  273. package/examples/temporal-comparison/temporal/activities/index.ts +8 -0
  274. package/examples/temporal-comparison/temporal/activities/shipstation.ts +225 -0
  275. package/examples/temporal-comparison/temporal/activities/shopify.ts +257 -0
  276. package/examples/temporal-comparison/temporal/activities/storage.ts +198 -0
  277. package/examples/temporal-comparison/temporal/activities/stripe.ts +169 -0
  278. package/examples/temporal-comparison/temporal/activities/validation.ts +205 -0
  279. package/examples/temporal-comparison/temporal/client/schedule.ts +218 -0
  280. package/examples/temporal-comparison/temporal/config/retry.ts +63 -0
  281. package/examples/temporal-comparison/temporal/types/index.ts +129 -0
  282. package/examples/temporal-comparison/temporal/workers/main.ts +130 -0
  283. package/examples/temporal-comparison/temporal/workflows/orderReconciliation.ts +262 -0
  284. package/examples/xero/README.md +88 -0
  285. package/examples/xero/invoices.vague +189 -0
  286. package/package.json +40 -0
  287. package/src/api-integration.test.ts +954 -0
  288. package/src/ast/index.ts +1 -0
  289. package/src/ast/nodes.ts +310 -0
  290. package/src/auth/auth.test.ts +326 -0
  291. package/src/auth/circuit-breaker.test.ts +390 -0
  292. package/src/auth/circuit-breaker.ts +379 -0
  293. package/src/auth/credentials.test.ts +273 -0
  294. package/src/auth/credentials.ts +246 -0
  295. package/src/auth/index.ts +40 -0
  296. package/src/auth/oauth2-provider.ts +177 -0
  297. package/src/auth/rate-limiter.ts +459 -0
  298. package/src/auth/token-store.ts +177 -0
  299. package/src/auth/types.ts +159 -0
  300. package/src/benchmark/e2e.bench.ts +288 -0
  301. package/src/benchmark/evaluator.bench.ts +331 -0
  302. package/src/benchmark/fixtures.ts +295 -0
  303. package/src/benchmark/index.ts +108 -0
  304. package/src/benchmark/lexer.bench.ts +69 -0
  305. package/src/benchmark/parser.bench.ts +103 -0
  306. package/src/benchmark/resilience.bench.ts +193 -0
  307. package/src/benchmark/store.bench.ts +147 -0
  308. package/src/benchmark/utils.ts +230 -0
  309. package/src/cli.ts +313 -0
  310. package/src/errors/errors.test.ts +234 -0
  311. package/src/errors/index.ts +223 -0
  312. package/src/execution/execution.test.ts +307 -0
  313. package/src/execution/index.ts +21 -0
  314. package/src/execution/state.ts +207 -0
  315. package/src/execution/store.ts +188 -0
  316. package/src/index.ts +169 -0
  317. package/src/integration.test.ts +192 -0
  318. package/src/interpreter/context.ts +57 -0
  319. package/src/interpreter/evaluator.test.ts +796 -0
  320. package/src/interpreter/evaluator.ts +245 -0
  321. package/src/interpreter/executor.ts +946 -0
  322. package/src/interpreter/fetch-handler.ts +302 -0
  323. package/src/interpreter/http.test.ts +423 -0
  324. package/src/interpreter/http.ts +308 -0
  325. package/src/interpreter/index.ts +32 -0
  326. package/src/interpreter/pagination.ts +207 -0
  327. package/src/interpreter/progress.test.ts +276 -0
  328. package/src/interpreter/schema-matcher.test.ts +160 -0
  329. package/src/interpreter/schema-matcher.ts +168 -0
  330. package/src/interpreter/signals.ts +73 -0
  331. package/src/interpreter/step-handlers/for-handler.ts +65 -0
  332. package/src/interpreter/step-handlers/index.ts +17 -0
  333. package/src/interpreter/step-handlers/map-handler.ts +24 -0
  334. package/src/interpreter/step-handlers/match-handler.ts +101 -0
  335. package/src/interpreter/step-handlers/store-handler.ts +78 -0
  336. package/src/interpreter/step-handlers/types.ts +17 -0
  337. package/src/interpreter/step-handlers/validate-handler.ts +30 -0
  338. package/src/interpreter/step-handlers/webhook-handler.ts +142 -0
  339. package/src/lexer/index.ts +18 -0
  340. package/src/lexer/lexer.test.ts +316 -0
  341. package/src/lexer/tokens.ts +179 -0
  342. package/src/loader/index.ts +288 -0
  343. package/src/loader/loader.test.ts +360 -0
  344. package/src/oas/index.ts +4 -0
  345. package/src/oas/loader.ts +126 -0
  346. package/src/oas/oas.test.ts +254 -0
  347. package/src/oas/validator.ts +299 -0
  348. package/src/parser/base.ts +124 -0
  349. package/src/parser/expressions.test.ts +525 -0
  350. package/src/parser/expressions.ts +314 -0
  351. package/src/parser/index.ts +3 -0
  352. package/src/parser/match.test.ts +296 -0
  353. package/src/parser/parser.test.ts +739 -0
  354. package/src/parser/parser.ts +1469 -0
  355. package/src/parser/schedule.test.ts +287 -0
  356. package/src/parser/webhook.test.ts +248 -0
  357. package/src/plugin.ts +83 -0
  358. package/src/scheduler/cron-parser.test.ts +236 -0
  359. package/src/scheduler/cron-parser.ts +236 -0
  360. package/src/scheduler/index.ts +10 -0
  361. package/src/scheduler/scheduler.ts +443 -0
  362. package/src/scheduler/types.ts +71 -0
  363. package/src/stores/factory.ts +104 -0
  364. package/src/stores/file.test.ts +276 -0
  365. package/src/stores/file.ts +211 -0
  366. package/src/stores/index.ts +6 -0
  367. package/src/stores/memory.test.ts +238 -0
  368. package/src/stores/memory.ts +63 -0
  369. package/src/stores/postgrest.test.ts +488 -0
  370. package/src/stores/postgrest.ts +263 -0
  371. package/src/stores/stores.test.ts +197 -0
  372. package/src/stores/types.ts +58 -0
  373. package/src/sync/index.ts +16 -0
  374. package/src/sync/state.ts +126 -0
  375. package/src/sync/store.ts +139 -0
  376. package/src/sync/sync.test.ts +271 -0
  377. package/src/utils/async.ts +10 -0
  378. package/src/utils/file.ts +106 -0
  379. package/src/utils/index.ts +14 -0
  380. package/src/utils/logger.ts +53 -0
  381. package/src/utils/path.ts +47 -0
  382. package/src/webhook/index.ts +15 -0
  383. package/src/webhook/server.test.ts +253 -0
  384. package/src/webhook/server.ts +389 -0
  385. package/src/webhook/store.ts +239 -0
  386. package/src/webhook/types.ts +93 -0
  387. package/tsconfig.json +17 -0
  388. package/vitest.config.ts +39 -0
@@ -0,0 +1,314 @@
1
+ import { TokenType, type Expression, type QualifiedName, type MatchArm, type Token } from 'vague-lang';
2
+ import { ReqonTokenType } from '../lexer/tokens.js';
3
+ import { ReqonParserBase } from './base.js';
4
+
5
+ // Extended expression type for Reqon's 'is' type checking
6
+ export interface IsExpression {
7
+ type: 'IsExpression';
8
+ operand: Expression;
9
+ typeCheck: string; // 'array', 'object', 'string', 'number', 'boolean', 'null', 'undefined'
10
+ }
11
+
12
+ export class ReqonExpressionParser extends ReqonParserBase {
13
+ parseExpression(): Expression {
14
+ return this.parseTernary();
15
+ }
16
+
17
+ parseLogicalExpression(): Expression {
18
+ return this.parseOr();
19
+ }
20
+
21
+ private parseTernary(): Expression {
22
+ const condition = this.parseOr();
23
+
24
+ if (this.match(TokenType.QUESTION)) {
25
+ const consequent = this.parseTernary();
26
+ this.consume(TokenType.COLON, "Expected ':' in ternary expression");
27
+ const alternate = this.parseTernary();
28
+ return {
29
+ type: 'TernaryExpression',
30
+ condition,
31
+ consequent,
32
+ alternate,
33
+ };
34
+ }
35
+
36
+ return condition;
37
+ }
38
+
39
+ private parseOr(): Expression {
40
+ let left = this.parseAnd();
41
+
42
+ while (this.match(TokenType.OR)) {
43
+ const right = this.parseAnd();
44
+ left = { type: 'LogicalExpression', operator: 'or', left, right };
45
+ }
46
+
47
+ return left;
48
+ }
49
+
50
+ private parseAnd(): Expression {
51
+ let left = this.parseNot();
52
+
53
+ while (this.match(TokenType.AND)) {
54
+ const right = this.parseNot();
55
+ left = { type: 'LogicalExpression', operator: 'and', left, right };
56
+ }
57
+
58
+ return left;
59
+ }
60
+
61
+ private parseNot(): Expression {
62
+ if (this.match(TokenType.NOT)) {
63
+ const operand = this.parseNot();
64
+ return { type: 'NotExpression', operand };
65
+ }
66
+
67
+ return this.parseSuperposition();
68
+ }
69
+
70
+ private parseSuperposition(): Expression {
71
+ const first = this.parseSuperpositionOption();
72
+
73
+ if (this.check(TokenType.PIPE)) {
74
+ const options = [first];
75
+
76
+ while (this.match(TokenType.PIPE)) {
77
+ options.push(this.parseSuperpositionOption());
78
+ }
79
+
80
+ return { type: 'SuperpositionExpression', options };
81
+ }
82
+
83
+ return first.value;
84
+ }
85
+
86
+ private parseSuperpositionOption(): { weight?: number; value: Expression } {
87
+ const expr = this.parseComparison();
88
+
89
+ if (expr.type === 'Literal' && expr.dataType === 'number' && this.check(TokenType.COLON)) {
90
+ this.advance();
91
+ const value = this.parseComparison();
92
+ return { weight: expr.value as number, value };
93
+ }
94
+
95
+ return { value: expr };
96
+ }
97
+
98
+ parseComparison(): Expression {
99
+ let left = this.parseRange();
100
+
101
+ while (this.checkAny(TokenType.LT, TokenType.GT, TokenType.LTE, TokenType.GTE, TokenType.DOUBLE_EQUALS, ReqonTokenType.NOT_EQUALS)) {
102
+ const token = this.advance();
103
+ // Normalize the operator value for NOT_EQUALS
104
+ const operator = token.type === ReqonTokenType.NOT_EQUALS ? '!=' : token.value;
105
+ const right = this.parseRange();
106
+ left = { type: 'BinaryExpression', operator, left, right };
107
+ }
108
+
109
+ // Check for 'is' type checking: expr is array, expr is string, etc.
110
+ if (this.check(ReqonTokenType.IS)) {
111
+ this.advance(); // consume 'is'
112
+ const typeCheck = this.consume(TokenType.IDENTIFIER, "Expected type name after 'is'").value;
113
+ return { type: 'IsExpression', operand: left, typeCheck } as unknown as Expression;
114
+ }
115
+
116
+ return left;
117
+ }
118
+
119
+ parseRange(): Expression {
120
+ const left = this.parseAdditive();
121
+
122
+ if (this.match(TokenType.DOTDOT)) {
123
+ const right = this.check(TokenType.NUMBER) ? this.parseAdditive() : undefined;
124
+ return { type: 'RangeExpression', min: left, max: right };
125
+ }
126
+
127
+ return left;
128
+ }
129
+
130
+ parseAdditive(): Expression {
131
+ let left = this.parseMultiplicative();
132
+
133
+ while (this.checkAny(TokenType.PLUS, TokenType.MINUS)) {
134
+ const operator = this.advance().value;
135
+ const right = this.parseMultiplicative();
136
+ left = { type: 'BinaryExpression', operator, left, right };
137
+ }
138
+
139
+ return left;
140
+ }
141
+
142
+ private parseMultiplicative(): Expression {
143
+ let left = this.parseUnary();
144
+
145
+ while (this.checkAny(TokenType.STAR, TokenType.SLASH)) {
146
+ const operator = this.advance().value;
147
+ const right = this.parseUnary();
148
+ left = { type: 'BinaryExpression', operator, left, right };
149
+ }
150
+
151
+ return left;
152
+ }
153
+
154
+ private parseUnary(): Expression {
155
+ if (this.match(TokenType.CARET)) {
156
+ const path = this.parseQualifiedName();
157
+ return { type: 'ParentReference', path };
158
+ }
159
+
160
+ if (this.checkAny(TokenType.MINUS, TokenType.PLUS)) {
161
+ const operator = this.advance().value;
162
+ const operand = this.parseUnary();
163
+ return { type: 'UnaryExpression', operator, operand };
164
+ }
165
+
166
+ return this.parseCall();
167
+ }
168
+
169
+ private parseCall(): Expression {
170
+ let expr = this.parsePrimary();
171
+
172
+ while (true) {
173
+ if (this.match(TokenType.LPAREN)) {
174
+ const args: Expression[] = [];
175
+ if (!this.check(TokenType.RPAREN)) {
176
+ do {
177
+ args.push(this.parseExpression());
178
+ } while (this.match(TokenType.COMMA));
179
+ }
180
+ this.consume(TokenType.RPAREN, "Expected ')'");
181
+
182
+ if (expr.type === 'Identifier') {
183
+ expr = { type: 'CallExpression', callee: expr.name, arguments: args };
184
+ } else if (expr.type === 'QualifiedName') {
185
+ expr = { type: 'CallExpression', callee: expr.parts.join('.'), arguments: args };
186
+ }
187
+ } else if (this.match(TokenType.DOT)) {
188
+ const name = this.consume(TokenType.IDENTIFIER, 'Expected property name').value;
189
+ if (expr.type === 'Identifier') {
190
+ expr = { type: 'QualifiedName', parts: [expr.name, name] };
191
+ } else if (expr.type === 'QualifiedName') {
192
+ expr.parts.push(name);
193
+ }
194
+ } else {
195
+ break;
196
+ }
197
+ }
198
+
199
+ return expr;
200
+ }
201
+
202
+ parsePrimary(): Expression {
203
+ // Match expression
204
+ if (this.match(TokenType.MATCH)) {
205
+ return this.parseMatchExpression();
206
+ }
207
+
208
+ // Ordered sequence
209
+ if (this.match(TokenType.LBRACKET)) {
210
+ return this.parseOrderedSequence();
211
+ }
212
+
213
+ // Any of expression
214
+ if (this.match(TokenType.ANY)) {
215
+ this.consume(TokenType.OF, "Expected 'of'");
216
+ const collection = this.parseExpression();
217
+ let condition: Expression | undefined;
218
+ if (this.match(TokenType.WHERE)) {
219
+ condition = this.parseExpression();
220
+ }
221
+ return { type: 'AnyOfExpression', collection, condition };
222
+ }
223
+
224
+ // Parenthesized expression
225
+ if (this.match(TokenType.LPAREN)) {
226
+ const expr = this.parseExpression();
227
+ this.consume(TokenType.RPAREN, "Expected ')'");
228
+ return expr;
229
+ }
230
+
231
+ // Number literal
232
+ if (this.check(TokenType.NUMBER)) {
233
+ const value = parseFloat(this.advance().value);
234
+ return { type: 'Literal', value, dataType: 'number' };
235
+ }
236
+
237
+ // String literal
238
+ if (this.check(TokenType.STRING)) {
239
+ const value = this.advance().value;
240
+ return { type: 'Literal', value, dataType: 'string' };
241
+ }
242
+
243
+ // Null literal
244
+ if (this.match(TokenType.NULL)) {
245
+ return { type: 'Literal', value: null, dataType: 'null' };
246
+ }
247
+
248
+ // Boolean literals
249
+ if (this.match(TokenType.TRUE)) {
250
+ return { type: 'Literal', value: true, dataType: 'boolean' };
251
+ }
252
+ if (this.match(TokenType.FALSE)) {
253
+ return { type: 'Literal', value: false, dataType: 'boolean' };
254
+ }
255
+
256
+ // Identifier (including HTTP method tokens that can be used as identifiers)
257
+ if (this.checkIdentifier()) {
258
+ const name = this.advance().value;
259
+ return { type: 'Identifier', name };
260
+ }
261
+
262
+ // .field shorthand
263
+ if (this.match(TokenType.DOT)) {
264
+ const name = this.consumeIdentifier("Expected field name after '.'").value;
265
+ return { type: 'Identifier', name };
266
+ }
267
+
268
+ throw this.error(`Unexpected token: ${this.peek().value}`);
269
+ }
270
+
271
+ private parseMatchExpression(): Expression {
272
+ const value = this.parsePrimary();
273
+ this.consume(TokenType.LBRACE, "Expected '{'");
274
+
275
+ const arms: MatchArm[] = [];
276
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
277
+ const pattern = this.parseExpression();
278
+ this.consume(TokenType.ARROW, "Expected '=>'");
279
+ const result = this.parseExpression();
280
+ arms.push({ pattern, result });
281
+ this.match(TokenType.COMMA);
282
+ }
283
+
284
+ this.consume(TokenType.RBRACE, "Expected '}'");
285
+ return { type: 'MatchExpression', value, arms };
286
+ }
287
+
288
+ private parseOrderedSequence(): Expression {
289
+ const elements: Expression[] = [];
290
+
291
+ if (this.check(TokenType.RBRACKET)) {
292
+ throw this.error('Ordered sequence cannot be empty');
293
+ }
294
+
295
+ do {
296
+ elements.push(this.parseExpression());
297
+ } while (this.match(TokenType.COMMA));
298
+
299
+ this.consume(TokenType.RBRACKET, "Expected ']'");
300
+
301
+ return { type: 'OrderedSequenceType', elements };
302
+ }
303
+
304
+ parseQualifiedName(): QualifiedName {
305
+ const parts: string[] = [];
306
+ parts.push(this.consume(TokenType.IDENTIFIER, 'Expected identifier').value);
307
+
308
+ while (this.match(TokenType.DOT)) {
309
+ parts.push(this.consume(TokenType.IDENTIFIER, 'Expected identifier').value);
310
+ }
311
+
312
+ return { type: 'QualifiedName', parts };
313
+ }
314
+ }
@@ -0,0 +1,3 @@
1
+ export { ReqonParser } from './parser.js';
2
+ export { ReqonExpressionParser } from './expressions.js';
3
+ export { ReqonParserBase } from './base.js';
@@ -0,0 +1,296 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ReqonLexer } from '../lexer/index.js';
3
+ import { ReqonParser } from './parser.js';
4
+ import type { ActionDefinition, MatchStep } from '../ast/nodes.js';
5
+
6
+ describe('Match Step Parsing', () => {
7
+ function parseAction(source: string): ActionDefinition {
8
+ const lexer = new ReqonLexer(source);
9
+ const tokens = lexer.tokenize();
10
+ const parser = new ReqonParser(tokens, source);
11
+ const program = parser.parse();
12
+
13
+ const action = program.statements.find(
14
+ (s) => s.type === 'ActionDefinition'
15
+ ) as ActionDefinition;
16
+
17
+ if (!action) {
18
+ throw new Error('No action found in program');
19
+ }
20
+
21
+ return action;
22
+ }
23
+
24
+ describe('basic match step', () => {
25
+ it('parses match with single schema and flow directive', () => {
26
+ const action = parseAction(`
27
+ action HandleResponse {
28
+ match response {
29
+ SuccessResponse -> continue
30
+ }
31
+ }
32
+ `);
33
+
34
+ expect(action.steps).toHaveLength(1);
35
+ const matchStep = action.steps[0] as MatchStep;
36
+ expect(matchStep.type).toBe('MatchStep');
37
+ expect(matchStep.arms).toHaveLength(1);
38
+ expect(matchStep.arms[0].schema).toBe('SuccessResponse');
39
+ expect(matchStep.arms[0].flow).toEqual({ type: 'continue' });
40
+ });
41
+
42
+ it('parses match with multiple schemas', () => {
43
+ const action = parseAction(`
44
+ action HandleResponse {
45
+ match response {
46
+ SuccessResponse -> continue,
47
+ NotFoundError -> skip,
48
+ ServerError -> abort "Something went wrong"
49
+ }
50
+ }
51
+ `);
52
+
53
+ expect(action.steps).toHaveLength(1);
54
+ const matchStep = action.steps[0] as MatchStep;
55
+ expect(matchStep.arms).toHaveLength(3);
56
+
57
+ expect(matchStep.arms[0].schema).toBe('SuccessResponse');
58
+ expect(matchStep.arms[0].flow).toEqual({ type: 'continue' });
59
+
60
+ expect(matchStep.arms[1].schema).toBe('NotFoundError');
61
+ expect(matchStep.arms[1].flow).toEqual({ type: 'skip' });
62
+
63
+ expect(matchStep.arms[2].schema).toBe('ServerError');
64
+ expect(matchStep.arms[2].flow).toEqual({
65
+ type: 'abort',
66
+ message: 'Something went wrong',
67
+ });
68
+ });
69
+ });
70
+
71
+ describe('flow directives', () => {
72
+ it('parses continue directive', () => {
73
+ const action = parseAction(`
74
+ action Test {
75
+ match response {
76
+ Schema -> continue
77
+ }
78
+ }
79
+ `);
80
+
81
+ const matchStep = action.steps[0] as MatchStep;
82
+ expect(matchStep.arms[0].flow).toEqual({ type: 'continue' });
83
+ });
84
+
85
+ it('parses skip directive', () => {
86
+ const action = parseAction(`
87
+ action Test {
88
+ match response {
89
+ Schema -> skip
90
+ }
91
+ }
92
+ `);
93
+
94
+ const matchStep = action.steps[0] as MatchStep;
95
+ expect(matchStep.arms[0].flow).toEqual({ type: 'skip' });
96
+ });
97
+
98
+ it('parses abort directive without message', () => {
99
+ const action = parseAction(`
100
+ action Test {
101
+ match response {
102
+ Schema -> abort
103
+ }
104
+ }
105
+ `);
106
+
107
+ const matchStep = action.steps[0] as MatchStep;
108
+ expect(matchStep.arms[0].flow).toEqual({ type: 'abort' });
109
+ });
110
+
111
+ it('parses abort directive with message', () => {
112
+ const action = parseAction(`
113
+ action Test {
114
+ match response {
115
+ Schema -> abort "Error occurred"
116
+ }
117
+ }
118
+ `);
119
+
120
+ const matchStep = action.steps[0] as MatchStep;
121
+ expect(matchStep.arms[0].flow).toEqual({
122
+ type: 'abort',
123
+ message: 'Error occurred',
124
+ });
125
+ });
126
+
127
+ it('parses queue directive without target', () => {
128
+ const action = parseAction(`
129
+ action Test {
130
+ match response {
131
+ Schema -> queue
132
+ }
133
+ }
134
+ `);
135
+
136
+ const matchStep = action.steps[0] as MatchStep;
137
+ expect(matchStep.arms[0].flow).toEqual({ type: 'queue' });
138
+ });
139
+
140
+ it('parses queue directive with target', () => {
141
+ const action = parseAction(`
142
+ action Test {
143
+ match response {
144
+ Schema -> queue deadLetterQueue
145
+ }
146
+ }
147
+ `);
148
+
149
+ const matchStep = action.steps[0] as MatchStep;
150
+ expect(matchStep.arms[0].flow).toEqual({
151
+ type: 'queue',
152
+ target: 'deadLetterQueue',
153
+ });
154
+ });
155
+
156
+ it('parses jump directive without then', () => {
157
+ const action = parseAction(`
158
+ action Test {
159
+ match response {
160
+ Schema -> jump RefreshToken
161
+ }
162
+ }
163
+ `);
164
+
165
+ const matchStep = action.steps[0] as MatchStep;
166
+ expect(matchStep.arms[0].flow).toEqual({
167
+ type: 'jump',
168
+ action: 'RefreshToken',
169
+ });
170
+ });
171
+
172
+ it('parses jump directive with then retry', () => {
173
+ const action = parseAction(`
174
+ action Test {
175
+ match response {
176
+ Schema -> jump RefreshToken then retry
177
+ }
178
+ }
179
+ `);
180
+
181
+ const matchStep = action.steps[0] as MatchStep;
182
+ expect(matchStep.arms[0].flow).toEqual({
183
+ type: 'jump',
184
+ action: 'RefreshToken',
185
+ then: 'retry',
186
+ });
187
+ });
188
+
189
+ it('parses jump directive with then continue', () => {
190
+ const action = parseAction(`
191
+ action Test {
192
+ match response {
193
+ Schema -> jump RefreshToken then continue
194
+ }
195
+ }
196
+ `);
197
+
198
+ const matchStep = action.steps[0] as MatchStep;
199
+ expect(matchStep.arms[0].flow).toEqual({
200
+ type: 'jump',
201
+ action: 'RefreshToken',
202
+ then: 'continue',
203
+ });
204
+ });
205
+
206
+ it('parses retry directive without config', () => {
207
+ const action = parseAction(`
208
+ action Test {
209
+ match response {
210
+ Schema -> retry
211
+ }
212
+ }
213
+ `);
214
+
215
+ const matchStep = action.steps[0] as MatchStep;
216
+ expect(matchStep.arms[0].flow).toEqual({ type: 'retry' });
217
+ });
218
+
219
+ it('parses retry directive with config', () => {
220
+ const action = parseAction(`
221
+ action Test {
222
+ match response {
223
+ Schema -> retry { maxAttempts: 5, backoff: exponential, initialDelay: 1000 }
224
+ }
225
+ }
226
+ `);
227
+
228
+ const matchStep = action.steps[0] as MatchStep;
229
+ expect(matchStep.arms[0].flow?.type).toBe('retry');
230
+ if (matchStep.arms[0].flow?.type === 'retry') {
231
+ expect(matchStep.arms[0].flow.backoff).toEqual({
232
+ maxAttempts: 5,
233
+ backoff: 'exponential',
234
+ initialDelay: 1000,
235
+ });
236
+ }
237
+ });
238
+ });
239
+
240
+ describe('match with steps', () => {
241
+ it('parses match with single inline step', () => {
242
+ const action = parseAction(`
243
+ action Test {
244
+ match response {
245
+ SuccessResponse -> store response -> cache
246
+ }
247
+ }
248
+ `);
249
+
250
+ const matchStep = action.steps[0] as MatchStep;
251
+ expect(matchStep.arms[0].schema).toBe('SuccessResponse');
252
+ expect(matchStep.arms[0].steps).toHaveLength(1);
253
+ expect(matchStep.arms[0].steps![0].type).toBe('StoreStep');
254
+ });
255
+
256
+ it('parses match with step block', () => {
257
+ const action = parseAction(`
258
+ action Test {
259
+ match response {
260
+ SuccessResponse -> {
261
+ store response -> cache,
262
+ store response -> backup
263
+ }
264
+ }
265
+ }
266
+ `);
267
+
268
+ const matchStep = action.steps[0] as MatchStep;
269
+ expect(matchStep.arms[0].schema).toBe('SuccessResponse');
270
+ expect(matchStep.arms[0].steps).toHaveLength(2);
271
+ expect(matchStep.arms[0].steps![0].type).toBe('StoreStep');
272
+ expect(matchStep.arms[0].steps![1].type).toBe('StoreStep');
273
+ });
274
+ });
275
+
276
+ describe('wildcard pattern', () => {
277
+ it('parses wildcard pattern at end', () => {
278
+ const action = parseAction(`
279
+ action Test {
280
+ match response {
281
+ SuccessResponse -> continue,
282
+ _ -> abort "Unexpected response"
283
+ }
284
+ }
285
+ `);
286
+
287
+ const matchStep = action.steps[0] as MatchStep;
288
+ expect(matchStep.arms).toHaveLength(2);
289
+ expect(matchStep.arms[1].schema).toBe('_');
290
+ expect(matchStep.arms[1].flow).toEqual({
291
+ type: 'abort',
292
+ message: 'Unexpected response',
293
+ });
294
+ });
295
+ });
296
+ });