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,126 @@
1
+ import SwaggerParser from '@apidevtools/swagger-parser';
2
+ import type { OpenAPI, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
3
+
4
+ export type OpenAPISpec = OpenAPIV3.Document | OpenAPIV3_1.Document;
5
+
6
+ export interface OASOperation {
7
+ operationId: string;
8
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
9
+ path: string;
10
+ parameters?: OpenAPIV3.ParameterObject[];
11
+ requestBody?: OpenAPIV3.RequestBodyObject;
12
+ responses?: OpenAPIV3.ResponsesObject;
13
+ security?: OpenAPIV3.SecurityRequirementObject[];
14
+ }
15
+
16
+ export interface OASSource {
17
+ spec: OpenAPISpec;
18
+ baseUrl: string;
19
+ operations: Map<string, OASOperation>;
20
+ schemas: Map<string, OpenAPIV3.SchemaObject>;
21
+ }
22
+
23
+ // Cache loaded specs to avoid re-parsing
24
+ const specCache = new Map<string, OASSource>();
25
+
26
+ export async function loadOAS(specPath: string, forceReload = false): Promise<OASSource> {
27
+ if (!forceReload && specCache.has(specPath)) {
28
+ return specCache.get(specPath)!;
29
+ }
30
+
31
+ const api = await SwaggerParser.dereference(specPath) as OpenAPISpec;
32
+
33
+ const baseUrl = extractBaseUrl(api);
34
+ const operations = extractOperations(api);
35
+ const schemas = extractSchemas(api);
36
+
37
+ const source: OASSource = {
38
+ spec: api,
39
+ baseUrl,
40
+ operations,
41
+ schemas,
42
+ };
43
+
44
+ specCache.set(specPath, source);
45
+ return source;
46
+ }
47
+
48
+ function extractBaseUrl(spec: OpenAPISpec): string {
49
+ if (spec.servers && spec.servers.length > 0) {
50
+ return spec.servers[0].url;
51
+ }
52
+ return '';
53
+ }
54
+
55
+ function extractOperations(spec: OpenAPISpec): Map<string, OASOperation> {
56
+ const operations = new Map<string, OASOperation>();
57
+
58
+ if (!spec.paths) return operations;
59
+
60
+ for (const [path, pathItem] of Object.entries(spec.paths)) {
61
+ if (!pathItem) continue;
62
+
63
+ const methods = ['get', 'post', 'put', 'patch', 'delete'] as const;
64
+
65
+ for (const method of methods) {
66
+ const operation = pathItem[method] as OpenAPIV3.OperationObject | undefined;
67
+ if (!operation?.operationId) continue;
68
+
69
+ operations.set(operation.operationId, {
70
+ operationId: operation.operationId,
71
+ method: method.toUpperCase() as OASOperation['method'],
72
+ path,
73
+ parameters: operation.parameters as OpenAPIV3.ParameterObject[],
74
+ requestBody: operation.requestBody as OpenAPIV3.RequestBodyObject,
75
+ responses: operation.responses as OpenAPIV3.ResponsesObject,
76
+ security: operation.security,
77
+ });
78
+ }
79
+ }
80
+
81
+ return operations;
82
+ }
83
+
84
+ function extractSchemas(spec: OpenAPISpec): Map<string, OpenAPIV3.SchemaObject> {
85
+ const schemas = new Map<string, OpenAPIV3.SchemaObject>();
86
+
87
+ const components = spec.components;
88
+ if (!components?.schemas) return schemas;
89
+
90
+ for (const [name, schema] of Object.entries(components.schemas)) {
91
+ schemas.set(name, schema as OpenAPIV3.SchemaObject);
92
+ }
93
+
94
+ return schemas;
95
+ }
96
+
97
+ export function resolveOperation(source: OASSource, operationId: string): OASOperation {
98
+ const operation = source.operations.get(operationId);
99
+ if (!operation) {
100
+ const available = Array.from(source.operations.keys()).slice(0, 5).join(', ');
101
+ throw new Error(
102
+ `Operation '${operationId}' not found in OAS spec. Available: ${available}...`
103
+ );
104
+ }
105
+ return operation;
106
+ }
107
+
108
+ export function getResponseSchema(
109
+ source: OASSource,
110
+ operationId: string,
111
+ statusCode = '200'
112
+ ): OpenAPIV3.SchemaObject | undefined {
113
+ const operation = resolveOperation(source, operationId);
114
+
115
+ const response = operation.responses?.[statusCode] as OpenAPIV3.ResponseObject | undefined;
116
+ if (!response?.content) return undefined;
117
+
118
+ const jsonContent = response.content['application/json'];
119
+ if (!jsonContent?.schema) return undefined;
120
+
121
+ return jsonContent.schema as OpenAPIV3.SchemaObject;
122
+ }
123
+
124
+ export function clearCache(): void {
125
+ specCache.clear();
126
+ }
@@ -0,0 +1,254 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { loadOAS, resolveOperation, getResponseSchema, clearCache } from './loader.js';
3
+ import { validateResponse } from './validator.js';
4
+ import { ReqonLexer } from '../lexer/index.js';
5
+ import { ReqonParser } from '../parser/parser.js';
6
+
7
+ describe('OAS Loader', () => {
8
+ beforeAll(() => {
9
+ clearCache();
10
+ });
11
+
12
+ it('loads and parses an OpenAPI spec', async () => {
13
+ const source = await loadOAS('./examples/petstore/openapi.yaml');
14
+
15
+ expect(source.baseUrl).toBe('https://api.petstore.example.com/v1');
16
+ expect(source.operations.size).toBe(3);
17
+ expect(source.schemas.size).toBe(2);
18
+ });
19
+
20
+ it('resolves operations by operationId', async () => {
21
+ const source = await loadOAS('./examples/petstore/openapi.yaml');
22
+
23
+ const listPets = resolveOperation(source, 'listPets');
24
+ expect(listPets.method).toBe('GET');
25
+ expect(listPets.path).toBe('/pets');
26
+
27
+ const getPet = resolveOperation(source, 'getPet');
28
+ expect(getPet.method).toBe('GET');
29
+ expect(getPet.path).toBe('/pets/{petId}');
30
+
31
+ const createPet = resolveOperation(source, 'createPet');
32
+ expect(createPet.method).toBe('POST');
33
+ expect(createPet.path).toBe('/pets');
34
+ });
35
+
36
+ it('throws for unknown operationId', async () => {
37
+ const source = await loadOAS('./examples/petstore/openapi.yaml');
38
+
39
+ expect(() => resolveOperation(source, 'unknownOp')).toThrow(/not found/);
40
+ });
41
+
42
+ it('extracts response schemas', async () => {
43
+ const source = await loadOAS('./examples/petstore/openapi.yaml');
44
+
45
+ const schema = getResponseSchema(source, 'getPet');
46
+ expect(schema).toBeDefined();
47
+ expect(schema?.type).toBe('object');
48
+ expect(schema?.properties).toHaveProperty('id');
49
+ expect(schema?.properties).toHaveProperty('name');
50
+ });
51
+ });
52
+
53
+ describe('OAS Validator', () => {
54
+ it('validates a valid response', () => {
55
+ const schema = {
56
+ type: 'object' as const,
57
+ required: ['id', 'name'],
58
+ properties: {
59
+ id: { type: 'string' as const },
60
+ name: { type: 'string' as const },
61
+ age: { type: 'integer' as const, minimum: 0 },
62
+ },
63
+ };
64
+
65
+ const data = { id: '123', name: 'Fluffy', age: 3 };
66
+ const result = validateResponse(data, schema);
67
+
68
+ expect(result.valid).toBe(true);
69
+ expect(result.errors).toHaveLength(0);
70
+ });
71
+
72
+ it('catches missing required properties', () => {
73
+ const schema = {
74
+ type: 'object' as const,
75
+ required: ['id', 'name'],
76
+ properties: {
77
+ id: { type: 'string' as const },
78
+ name: { type: 'string' as const },
79
+ },
80
+ };
81
+
82
+ const data = { id: '123' }; // missing 'name'
83
+ const result = validateResponse(data, schema);
84
+
85
+ expect(result.valid).toBe(false);
86
+ expect(result.errors.some(e => e.path === 'name')).toBe(true);
87
+ });
88
+
89
+ it('catches type mismatches', () => {
90
+ const schema = {
91
+ type: 'object' as const,
92
+ properties: {
93
+ age: { type: 'integer' as const },
94
+ },
95
+ };
96
+
97
+ const data = { age: 'not a number' };
98
+ const result = validateResponse(data, schema);
99
+
100
+ expect(result.valid).toBe(false);
101
+ expect(result.errors[0].path).toBe('age');
102
+ });
103
+
104
+ it('validates arrays', () => {
105
+ const schema = {
106
+ type: 'array' as const,
107
+ items: {
108
+ type: 'object' as const,
109
+ required: ['id'],
110
+ properties: {
111
+ id: { type: 'string' as const },
112
+ },
113
+ },
114
+ };
115
+
116
+ const validData = [{ id: '1' }, { id: '2' }];
117
+ expect(validateResponse(validData, schema).valid).toBe(true);
118
+
119
+ const invalidData = [{ id: '1' }, { noId: true }];
120
+ const result = validateResponse(invalidData, schema);
121
+ expect(result.valid).toBe(false);
122
+ expect(result.errors[0].path).toBe('[1].id');
123
+ });
124
+ });
125
+
126
+ describe('OAS Parser Integration', () => {
127
+ function parse(source: string) {
128
+ const lexer = new ReqonLexer(source);
129
+ const tokens = lexer.tokenize();
130
+ const parser = new ReqonParser(tokens);
131
+ return parser.parse();
132
+ }
133
+
134
+ it('parses source with OAS spec path', () => {
135
+ const source = `
136
+ mission TestOAS {
137
+ source Petstore from "./examples/petstore.yaml" {
138
+ auth: bearer
139
+ }
140
+
141
+ store pets: memory("pets")
142
+
143
+ action FetchPets {
144
+ call Petstore.listPets
145
+
146
+ store response.pets -> pets {
147
+ key: .id
148
+ }
149
+ }
150
+
151
+ run FetchPets
152
+ }
153
+ `;
154
+
155
+ const program = parse(source);
156
+ expect(program.type).toBe('ReqonProgram');
157
+
158
+ const mission = program.statements[0];
159
+ if (mission.type === 'MissionDefinition') {
160
+ expect(mission.sources[0].specPath).toBe('./examples/petstore.yaml');
161
+ expect(mission.sources[0].config.base).toBeUndefined();
162
+
163
+ const action = mission.actions[0];
164
+ const fetchStep = action.steps[0];
165
+ if (fetchStep.type === 'FetchStep') {
166
+ expect(fetchStep.operationRef).toBeDefined();
167
+ expect(fetchStep.operationRef?.source).toBe('Petstore');
168
+ expect(fetchStep.operationRef?.operationId).toBe('listPets');
169
+ expect(fetchStep.method).toBeUndefined();
170
+ expect(fetchStep.path).toBeUndefined();
171
+ }
172
+ }
173
+ });
174
+
175
+ it('parses source with OAS and explicit base URL override', () => {
176
+ const source = `
177
+ mission TestOAS {
178
+ source Petstore from "./examples/petstore.yaml" {
179
+ auth: bearer,
180
+ base: "https://staging.petstore.com/v1"
181
+ }
182
+
183
+ store pets: memory("pets")
184
+
185
+ action Fetch {
186
+ get "/custom-path"
187
+ store response -> pets { key: .id }
188
+ }
189
+
190
+ run Fetch
191
+ }
192
+ `;
193
+
194
+ const program = parse(source);
195
+ const mission = program.statements[0];
196
+ if (mission.type === 'MissionDefinition') {
197
+ expect(mission.sources[0].specPath).toBe('./examples/petstore.yaml');
198
+ expect(mission.sources[0].config.base).toBe('https://staging.petstore.com/v1');
199
+ }
200
+ });
201
+
202
+ it('supports both OAS call and traditional HTTP methods in same mission', () => {
203
+ const source = `
204
+ mission MixedFetch {
205
+ source Petstore from "./examples/petstore.yaml" {
206
+ auth: bearer
207
+ }
208
+
209
+ source Legacy {
210
+ auth: bearer,
211
+ base: "https://legacy.api.com"
212
+ }
213
+
214
+ store pets: memory("pets")
215
+
216
+ action FetchFromOAS {
217
+ call Petstore.listPets
218
+ store response.pets -> pets { key: .id }
219
+ }
220
+
221
+ action FetchFromLegacy {
222
+ get "/old-endpoint" {
223
+ source: Legacy
224
+ }
225
+ store response -> pets { key: .id }
226
+ }
227
+
228
+ run FetchFromOAS then FetchFromLegacy
229
+ }
230
+ `;
231
+
232
+ const program = parse(source);
233
+ const mission = program.statements[0];
234
+ if (mission.type === 'MissionDefinition') {
235
+ expect(mission.sources).toHaveLength(2);
236
+
237
+ const oasAction = mission.actions[0];
238
+ const legacyAction = mission.actions[1];
239
+
240
+ const oasFetch = oasAction.steps[0];
241
+ const legacyFetch = legacyAction.steps[0];
242
+
243
+ if (oasFetch.type === 'FetchStep' && legacyFetch.type === 'FetchStep') {
244
+ // OAS call
245
+ expect(oasFetch.operationRef).toBeDefined();
246
+ expect(oasFetch.method).toBeUndefined();
247
+
248
+ // Traditional HTTP method
249
+ expect(legacyFetch.operationRef).toBeUndefined();
250
+ expect(legacyFetch.method).toBe('GET');
251
+ }
252
+ }
253
+ });
254
+ });
@@ -0,0 +1,299 @@
1
+ import type { OpenAPIV3 } from 'openapi-types';
2
+
3
+ type ArraySchemaObject = OpenAPIV3.ArraySchemaObject;
4
+
5
+ export interface ValidationResult {
6
+ valid: boolean;
7
+ errors: ValidationError[];
8
+ }
9
+
10
+ export interface ValidationError {
11
+ path: string;
12
+ message: string;
13
+ expected?: string;
14
+ actual?: string;
15
+ }
16
+
17
+ export function validateResponse(
18
+ data: unknown,
19
+ schema: OpenAPIV3.SchemaObject,
20
+ path = ''
21
+ ): ValidationResult {
22
+ const errors: ValidationError[] = [];
23
+
24
+ validateValue(data, schema, path, errors);
25
+
26
+ return {
27
+ valid: errors.length === 0,
28
+ errors,
29
+ };
30
+ }
31
+
32
+ function validateValue(
33
+ value: unknown,
34
+ schema: OpenAPIV3.SchemaObject,
35
+ path: string,
36
+ errors: ValidationError[]
37
+ ): void {
38
+ // Handle nullable
39
+ if (value === null) {
40
+ if (schema.nullable) return;
41
+ errors.push({ path, message: 'Value is null but schema is not nullable' });
42
+ return;
43
+ }
44
+
45
+ // Handle undefined/missing
46
+ if (value === undefined) {
47
+ // Required check happens at object level
48
+ return;
49
+ }
50
+
51
+ // Check type
52
+ const schemaType = schema.type;
53
+
54
+ switch (schemaType) {
55
+ case 'string':
56
+ validateString(value, schema, path, errors);
57
+ break;
58
+ case 'number':
59
+ case 'integer':
60
+ validateNumber(value, schema, path, errors);
61
+ break;
62
+ case 'boolean':
63
+ validateBoolean(value, path, errors);
64
+ break;
65
+ case 'array':
66
+ validateArray(value, schema, path, errors);
67
+ break;
68
+ case 'object':
69
+ validateObject(value, schema, path, errors);
70
+ break;
71
+ default:
72
+ // No type specified, or unknown type - allow anything
73
+ break;
74
+ }
75
+
76
+ // Check enum
77
+ if (schema.enum && !schema.enum.includes(value)) {
78
+ errors.push({
79
+ path,
80
+ message: `Value not in enum`,
81
+ expected: schema.enum.join(' | '),
82
+ actual: String(value),
83
+ });
84
+ }
85
+ }
86
+
87
+ function validateString(
88
+ value: unknown,
89
+ schema: OpenAPIV3.SchemaObject,
90
+ path: string,
91
+ errors: ValidationError[]
92
+ ): void {
93
+ if (typeof value !== 'string') {
94
+ errors.push({
95
+ path,
96
+ message: 'Expected string',
97
+ expected: 'string',
98
+ actual: typeof value,
99
+ });
100
+ return;
101
+ }
102
+
103
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
104
+ errors.push({
105
+ path,
106
+ message: `String too short`,
107
+ expected: `>= ${schema.minLength} chars`,
108
+ actual: `${value.length} chars`,
109
+ });
110
+ }
111
+
112
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
113
+ errors.push({
114
+ path,
115
+ message: `String too long`,
116
+ expected: `<= ${schema.maxLength} chars`,
117
+ actual: `${value.length} chars`,
118
+ });
119
+ }
120
+
121
+ if (schema.pattern) {
122
+ const regex = new RegExp(schema.pattern);
123
+ if (!regex.test(value)) {
124
+ errors.push({
125
+ path,
126
+ message: `String does not match pattern`,
127
+ expected: schema.pattern,
128
+ actual: value,
129
+ });
130
+ }
131
+ }
132
+ }
133
+
134
+ function validateNumber(
135
+ value: unknown,
136
+ schema: OpenAPIV3.SchemaObject,
137
+ path: string,
138
+ errors: ValidationError[]
139
+ ): void {
140
+ if (typeof value !== 'number') {
141
+ errors.push({
142
+ path,
143
+ message: 'Expected number',
144
+ expected: schema.type ?? 'number',
145
+ actual: typeof value,
146
+ });
147
+ return;
148
+ }
149
+
150
+ if (schema.type === 'integer' && !Number.isInteger(value)) {
151
+ errors.push({
152
+ path,
153
+ message: 'Expected integer',
154
+ expected: 'integer',
155
+ actual: String(value),
156
+ });
157
+ }
158
+
159
+ if (schema.minimum !== undefined && value < schema.minimum) {
160
+ errors.push({
161
+ path,
162
+ message: `Number below minimum`,
163
+ expected: `>= ${schema.minimum}`,
164
+ actual: String(value),
165
+ });
166
+ }
167
+
168
+ if (schema.maximum !== undefined && value > schema.maximum) {
169
+ errors.push({
170
+ path,
171
+ message: `Number above maximum`,
172
+ expected: `<= ${schema.maximum}`,
173
+ actual: String(value),
174
+ });
175
+ }
176
+ }
177
+
178
+ function validateBoolean(
179
+ value: unknown,
180
+ path: string,
181
+ errors: ValidationError[]
182
+ ): void {
183
+ if (typeof value !== 'boolean') {
184
+ errors.push({
185
+ path,
186
+ message: 'Expected boolean',
187
+ expected: 'boolean',
188
+ actual: typeof value,
189
+ });
190
+ }
191
+ }
192
+
193
+ function validateArray(
194
+ value: unknown,
195
+ schema: OpenAPIV3.SchemaObject,
196
+ path: string,
197
+ errors: ValidationError[]
198
+ ): void {
199
+ if (!Array.isArray(value)) {
200
+ errors.push({
201
+ path,
202
+ message: 'Expected array',
203
+ expected: 'array',
204
+ actual: typeof value,
205
+ });
206
+ return;
207
+ }
208
+
209
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
210
+ errors.push({
211
+ path,
212
+ message: `Array too short`,
213
+ expected: `>= ${schema.minItems} items`,
214
+ actual: `${value.length} items`,
215
+ });
216
+ }
217
+
218
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
219
+ errors.push({
220
+ path,
221
+ message: `Array too long`,
222
+ expected: `<= ${schema.maxItems} items`,
223
+ actual: `${value.length} items`,
224
+ });
225
+ }
226
+
227
+ // Validate items
228
+ const arraySchema = schema as OpenAPIV3.ArraySchemaObject;
229
+ if (arraySchema.items) {
230
+ const itemSchema = arraySchema.items as OpenAPIV3.SchemaObject;
231
+ value.forEach((item, index) => {
232
+ validateValue(item, itemSchema, `${path}[${index}]`, errors);
233
+ });
234
+ }
235
+ }
236
+
237
+ function validateObject(
238
+ value: unknown,
239
+ schema: OpenAPIV3.SchemaObject,
240
+ path: string,
241
+ errors: ValidationError[]
242
+ ): void {
243
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
244
+ errors.push({
245
+ path,
246
+ message: 'Expected object',
247
+ expected: 'object',
248
+ actual: Array.isArray(value) ? 'array' : typeof value,
249
+ });
250
+ return;
251
+ }
252
+
253
+ const obj = value as Record<string, unknown>;
254
+
255
+ // Check required properties
256
+ if (schema.required) {
257
+ for (const prop of schema.required) {
258
+ if (!(prop in obj)) {
259
+ errors.push({
260
+ path: path ? `${path}.${prop}` : prop,
261
+ message: `Missing required property`,
262
+ });
263
+ }
264
+ }
265
+ }
266
+
267
+ // Validate properties
268
+ const properties = schema.properties as Record<string, OpenAPIV3.SchemaObject> | undefined;
269
+ if (properties) {
270
+ for (const [key, propSchema] of Object.entries(properties)) {
271
+ if (key in obj) {
272
+ const propPath = path ? `${path}.${key}` : key;
273
+ validateValue(obj[key], propSchema, propPath, errors);
274
+ }
275
+ }
276
+ }
277
+
278
+ // Handle additionalProperties
279
+ if (schema.additionalProperties === false) {
280
+ const allowedKeys = new Set(Object.keys(properties ?? {}));
281
+ for (const key of Object.keys(obj)) {
282
+ if (!allowedKeys.has(key)) {
283
+ errors.push({
284
+ path: path ? `${path}.${key}` : key,
285
+ message: `Unexpected property`,
286
+ });
287
+ }
288
+ }
289
+ } else if (typeof schema.additionalProperties === 'object') {
290
+ const additionalSchema = schema.additionalProperties as OpenAPIV3.SchemaObject;
291
+ const allowedKeys = new Set(Object.keys(properties ?? {}));
292
+ for (const [key, val] of Object.entries(obj)) {
293
+ if (!allowedKeys.has(key)) {
294
+ const propPath = path ? `${path}.${key}` : key;
295
+ validateValue(val, additionalSchema, propPath, errors);
296
+ }
297
+ }
298
+ }
299
+ }