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,43 @@
1
+ import type { FetchStep } from '../ast/nodes.js';
2
+ import type { ExecutionContext } from './context.js';
3
+ import type { OASSource } from '../oas/index.js';
4
+ import { type SyncStore } from '../sync/index.js';
5
+ export interface FetchHandlerDeps {
6
+ ctx: ExecutionContext;
7
+ oasSources: Map<string, OASSource>;
8
+ sourceConfigs: Map<string, {
9
+ config: {
10
+ validateResponses?: boolean;
11
+ };
12
+ }>;
13
+ syncStore?: SyncStore;
14
+ missionName?: string;
15
+ executionId?: string;
16
+ dryRun?: boolean;
17
+ log: (message: string) => void;
18
+ }
19
+ export interface FetchResult {
20
+ data: unknown;
21
+ checkpointKey?: string;
22
+ }
23
+ /**
24
+ * Handles HTTP fetch operations including pagination and incremental sync.
25
+ * Extracted from MissionExecutor for better separation of concerns.
26
+ */
27
+ export declare class FetchHandler {
28
+ private deps;
29
+ constructor(deps: FetchHandlerDeps);
30
+ /**
31
+ * Execute a fetch step, handling OAS resolution, pagination, and sync checkpoints.
32
+ */
33
+ execute(step: FetchStep): Promise<FetchResult>;
34
+ /**
35
+ * Record a sync checkpoint after successful fetch.
36
+ */
37
+ recordCheckpoint(key: string, step: FetchStep, data: unknown): Promise<void>;
38
+ private resolveFetchTarget;
39
+ private resolveSinceParams;
40
+ private executePaginated;
41
+ private countRecords;
42
+ private validateOASResponse;
43
+ }
@@ -0,0 +1,203 @@
1
+ import { evaluate, interpolatePath } from './evaluator.js';
2
+ import { resolveOperation, getResponseSchema, validateResponse } from '../oas/index.js';
3
+ import { generateCheckpointKey, formatSinceDate } from '../sync/index.js';
4
+ import { extractNestedValue } from '../utils/path.js';
5
+ import { createPaginationStrategy } from './pagination.js';
6
+ /** Maximum pages to fetch to prevent infinite loops */
7
+ const MAX_PAGINATION_PAGES = 100;
8
+ /**
9
+ * Handles HTTP fetch operations including pagination and incremental sync.
10
+ * Extracted from MissionExecutor for better separation of concerns.
11
+ */
12
+ export class FetchHandler {
13
+ deps;
14
+ constructor(deps) {
15
+ this.deps = deps;
16
+ }
17
+ /**
18
+ * Execute a fetch step, handling OAS resolution, pagination, and sync checkpoints.
19
+ */
20
+ async execute(step) {
21
+ const resolved = this.resolveFetchTarget(step);
22
+ const client = this.deps.ctx.sources.get(resolved.sourceName);
23
+ if (!client) {
24
+ throw new Error(`Source not found: ${resolved.sourceName}`);
25
+ }
26
+ // Resolve "since" query parameter for incremental sync
27
+ const { query: sinceQuery, checkpointKey } = await this.resolveSinceParams(step, resolved.sourceName, resolved.operationId, resolved.path);
28
+ if (this.deps.dryRun) {
29
+ this.deps.log('(dry run - skipping actual request)');
30
+ return { data: { dryRun: true }, checkpointKey };
31
+ }
32
+ // Execute with or without pagination
33
+ let data;
34
+ if (step.paginate) {
35
+ data = await this.executePaginated(step, client, resolved.path, resolved.method, resolved.sourceName, resolved.operationId, sinceQuery);
36
+ }
37
+ else {
38
+ const response = await client.request({
39
+ method: resolved.method,
40
+ path: resolved.path,
41
+ query: Object.keys(sinceQuery).length > 0 ? sinceQuery : undefined,
42
+ body: step.body ? evaluate(step.body, this.deps.ctx) : undefined,
43
+ }, step.retry);
44
+ await this.validateOASResponse(resolved.sourceName, resolved.operationId, response.data);
45
+ data = response.data;
46
+ }
47
+ return { data, checkpointKey };
48
+ }
49
+ /**
50
+ * Record a sync checkpoint after successful fetch.
51
+ */
52
+ async recordCheckpoint(key, step, data) {
53
+ if (!this.deps.syncStore)
54
+ return;
55
+ let syncedAt = new Date();
56
+ // If updateFrom is specified, extract the timestamp from response
57
+ if (step.since?.updateFrom && data && typeof data === 'object') {
58
+ const extracted = extractNestedValue(data, step.since.updateFrom);
59
+ if (extracted instanceof Date) {
60
+ syncedAt = extracted;
61
+ }
62
+ else if (typeof extracted === 'string' || typeof extracted === 'number') {
63
+ const parsed = new Date(extracted);
64
+ if (!isNaN(parsed.getTime())) {
65
+ syncedAt = parsed;
66
+ }
67
+ }
68
+ }
69
+ // Count records if response is an array
70
+ const recordCount = this.countRecords(data);
71
+ await this.deps.syncStore.recordSync({
72
+ key,
73
+ syncedAt,
74
+ recordCount,
75
+ mission: this.deps.missionName,
76
+ executionId: this.deps.executionId,
77
+ });
78
+ this.deps.log(`Recorded sync checkpoint: ${key} at ${syncedAt.toISOString()}`);
79
+ }
80
+ resolveFetchTarget(step) {
81
+ if (step.operationRef) {
82
+ // OAS-style: resolve from operationId
83
+ const sourceName = step.operationRef.source;
84
+ const operationId = step.operationRef.operationId;
85
+ const oasSource = this.deps.oasSources.get(sourceName);
86
+ if (!oasSource) {
87
+ throw new Error(`Source '${sourceName}' does not have an OAS spec. Use 'source ${sourceName} from "spec.yaml"'`);
88
+ }
89
+ const operation = resolveOperation(oasSource, operationId);
90
+ const path = interpolatePath(operation.path, this.deps.ctx);
91
+ this.deps.log(`Fetching: ${sourceName}.${operationId} -> ${operation.method} ${path}`);
92
+ return { sourceName, method: operation.method, path, operationId };
93
+ }
94
+ // Traditional: explicit method + path
95
+ const sourceName = step.source ?? this.deps.ctx.sources.keys().next().value;
96
+ const method = step.method;
97
+ let path;
98
+ if (step.path.type === 'Literal' && step.path.dataType === 'string') {
99
+ path = interpolatePath(step.path.value, this.deps.ctx);
100
+ }
101
+ else {
102
+ path = String(evaluate(step.path, this.deps.ctx));
103
+ }
104
+ this.deps.log(`Fetching: ${method} ${path}`);
105
+ return { sourceName, method, path };
106
+ }
107
+ async resolveSinceParams(step, sourceName, operationId, path) {
108
+ const query = {};
109
+ if (!step.since || !this.deps.syncStore) {
110
+ return { query };
111
+ }
112
+ const checkpointKey = step.since.key ?? generateCheckpointKey(sourceName, operationId, path);
113
+ if (step.since.type === 'lastSync') {
114
+ const lastSync = await this.deps.syncStore.getLastSync(checkpointKey);
115
+ const paramName = step.since.param ?? 'since';
116
+ const format = step.since.format ?? 'iso';
117
+ query[paramName] = formatSinceDate(lastSync, format);
118
+ this.deps.log(`Incremental sync: ${paramName}=${query[paramName]} (key: ${checkpointKey})`);
119
+ }
120
+ else if (step.since.type === 'expression' && step.since.expression) {
121
+ const value = evaluate(step.since.expression, this.deps.ctx);
122
+ const paramName = step.since.param ?? 'since';
123
+ query[paramName] = String(value);
124
+ }
125
+ return { query, checkpointKey };
126
+ }
127
+ async executePaginated(step, client, basePath, method, sourceName, operationId, sinceQuery = {}) {
128
+ const allResults = [];
129
+ const paginate = step.paginate;
130
+ const strategy = createPaginationStrategy(paginate);
131
+ const ctx = {
132
+ page: 0,
133
+ pageSize: paginate.pageSize,
134
+ };
135
+ let hasMore = true;
136
+ while (hasMore) {
137
+ // Build query with pagination params
138
+ const paginationQuery = strategy.buildQuery(ctx);
139
+ const query = { ...sinceQuery, ...paginationQuery };
140
+ this.deps.log(`Fetching page ${ctx.page + 1}...`);
141
+ const response = await client.request({ method, path: basePath, query }, step.retry);
142
+ await this.validateOASResponse(sourceName, operationId, response.data);
143
+ // Temporarily set response for until condition evaluation
144
+ this.deps.ctx.response = response.data;
145
+ // Check until condition
146
+ if (step.until) {
147
+ const shouldStop = evaluate(step.until, this.deps.ctx);
148
+ if (shouldStop) {
149
+ break;
150
+ }
151
+ }
152
+ // Extract results using strategy
153
+ const pageResult = strategy.extractResults(response.data, ctx);
154
+ allResults.push(...pageResult.items);
155
+ hasMore = pageResult.hasMore;
156
+ // Update cursor for next iteration
157
+ if (pageResult.nextCursor) {
158
+ ctx.cursor = pageResult.nextCursor;
159
+ }
160
+ ctx.page++;
161
+ // Safety limit
162
+ if (ctx.page >= MAX_PAGINATION_PAGES) {
163
+ this.deps.log(`Warning: pagination limit (${MAX_PAGINATION_PAGES}) reached`);
164
+ hasMore = false;
165
+ }
166
+ }
167
+ this.deps.log(`Fetched ${allResults.length} total items`);
168
+ return allResults;
169
+ }
170
+ countRecords(data) {
171
+ if (Array.isArray(data)) {
172
+ return data.length;
173
+ }
174
+ if (data && typeof data === 'object') {
175
+ for (const val of Object.values(data)) {
176
+ if (Array.isArray(val)) {
177
+ return val.length;
178
+ }
179
+ }
180
+ }
181
+ return undefined;
182
+ }
183
+ async validateOASResponse(sourceName, operationId, data) {
184
+ if (!operationId)
185
+ return;
186
+ const sourceConfig = this.deps.sourceConfigs.get(sourceName);
187
+ if (!sourceConfig?.config.validateResponses)
188
+ return;
189
+ const oasSource = this.deps.oasSources.get(sourceName);
190
+ if (!oasSource)
191
+ return;
192
+ const schema = getResponseSchema(oasSource, operationId);
193
+ if (!schema) {
194
+ this.deps.log(`No response schema found for ${operationId}`);
195
+ return;
196
+ }
197
+ const result = validateResponse(data, schema);
198
+ if (!result.valid) {
199
+ const errorMessages = result.errors.map((e) => ` ${e.path}: ${e.message}`).join('\n');
200
+ this.deps.log(`Response validation warnings for ${operationId}:\n${errorMessages}`);
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,57 @@
1
+ import type { RetryConfig } from '../ast/nodes.js';
2
+ import type { RateLimiter } from '../auth/types.js';
3
+ import { CircuitBreaker } from '../auth/circuit-breaker.js';
4
+ export interface HttpClientConfig {
5
+ baseUrl: string;
6
+ headers?: Record<string, string>;
7
+ auth?: AuthProvider;
8
+ rateLimiter?: RateLimiter;
9
+ circuitBreaker?: CircuitBreaker;
10
+ /** Source name for rate limit and circuit breaker tracking */
11
+ sourceName?: string;
12
+ }
13
+ export interface AuthProvider {
14
+ getToken(): Promise<string>;
15
+ refreshToken?(): Promise<string>;
16
+ }
17
+ export interface HttpRequest {
18
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
19
+ path: string;
20
+ body?: unknown;
21
+ headers?: Record<string, string>;
22
+ query?: Record<string, string>;
23
+ }
24
+ export interface HttpResponse<T = unknown> {
25
+ status: number;
26
+ data: T;
27
+ headers: Record<string, string>;
28
+ }
29
+ export declare class HttpClient {
30
+ private config;
31
+ constructor(config: HttpClientConfig);
32
+ request<T = unknown>(req: HttpRequest, retry?: RetryConfig): Promise<HttpResponse<T>>;
33
+ private buildUrl;
34
+ private buildHeaders;
35
+ private calculateDelay;
36
+ }
37
+ export declare class BearerAuthProvider implements AuthProvider {
38
+ private token;
39
+ constructor(token: string);
40
+ getToken(): Promise<string>;
41
+ }
42
+ export declare class OAuth2AuthProvider implements AuthProvider {
43
+ private accessToken;
44
+ private refreshTokenValue?;
45
+ private tokenEndpoint?;
46
+ private clientId?;
47
+ private clientSecret?;
48
+ constructor(config: {
49
+ accessToken: string;
50
+ refreshToken?: string;
51
+ tokenEndpoint?: string;
52
+ clientId?: string;
53
+ clientSecret?: string;
54
+ });
55
+ getToken(): Promise<string>;
56
+ refreshToken(): Promise<string>;
57
+ }
@@ -0,0 +1,210 @@
1
+ import { parseRateLimitHeaders } from '../auth/rate-limiter.js';
2
+ import { CircuitBreakerError } from '../auth/circuit-breaker.js';
3
+ import { sleep } from '../utils/async.js';
4
+ /** Default retry configuration values */
5
+ const DEFAULT_RETRY = {
6
+ MAX_ATTEMPTS: 3,
7
+ INITIAL_DELAY_MS: 1000,
8
+ MAX_DELAY_MS: 30000,
9
+ BACKOFF: 'exponential',
10
+ };
11
+ export class HttpClient {
12
+ config;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ async request(req, retry) {
17
+ const url = this.buildUrl(req.path, req.query);
18
+ const headers = await this.buildHeaders(req.headers);
19
+ const fetchOptions = {
20
+ method: req.method,
21
+ headers,
22
+ body: req.body ? JSON.stringify(req.body) : undefined,
23
+ };
24
+ const maxAttempts = retry?.maxAttempts ?? DEFAULT_RETRY.MAX_ATTEMPTS;
25
+ const backoff = retry?.backoff ?? DEFAULT_RETRY.BACKOFF;
26
+ const initialDelay = retry?.initialDelay ?? DEFAULT_RETRY.INITIAL_DELAY_MS;
27
+ const maxDelay = retry?.maxDelay ?? DEFAULT_RETRY.MAX_DELAY_MS;
28
+ let lastError = null;
29
+ // Check circuit breaker before attempting requests
30
+ if (this.config.circuitBreaker && this.config.sourceName) {
31
+ // This will throw CircuitBreakerError if circuit is open
32
+ this.config.circuitBreaker.ensureCanProceed(this.config.sourceName, req.path);
33
+ }
34
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
35
+ try {
36
+ // Re-check circuit breaker on retries (state may have changed)
37
+ if (attempt > 1 && this.config.circuitBreaker && this.config.sourceName) {
38
+ if (!this.config.circuitBreaker.canProceed(this.config.sourceName, req.path)) {
39
+ // Circuit opened during retries, fail fast
40
+ throw new CircuitBreakerError(this.config.sourceName, req.path, this.config.circuitBreaker.getStatus(this.config.sourceName, req.path).nextAttemptTime?.getTime() ?? 0 - Date.now());
41
+ }
42
+ }
43
+ // Wait for rate limit capacity if we have a rate limiter
44
+ if (this.config.rateLimiter && this.config.sourceName) {
45
+ await this.config.rateLimiter.waitForCapacity(this.config.sourceName, req.path);
46
+ }
47
+ const response = await fetch(url, fetchOptions);
48
+ // Extract and record rate limit info from response headers
49
+ const responseHeaders = {};
50
+ response.headers.forEach((value, key) => {
51
+ responseHeaders[key] = value;
52
+ });
53
+ if (this.config.rateLimiter && this.config.sourceName) {
54
+ const rateLimitInfo = parseRateLimitHeaders(responseHeaders);
55
+ // Add retry-after from 429 responses
56
+ if (response.status === 429) {
57
+ const retryAfter = response.headers.get('Retry-After');
58
+ if (retryAfter) {
59
+ rateLimitInfo.retryAfter = parseInt(retryAfter, 10);
60
+ }
61
+ }
62
+ this.config.rateLimiter.recordResponse(this.config.sourceName, rateLimitInfo, req.path);
63
+ }
64
+ // Handle rate limiting
65
+ if (response.status === 429) {
66
+ const retryAfter = response.headers.get('Retry-After');
67
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : this.calculateDelay(attempt, backoff, initialDelay, maxDelay);
68
+ await sleep(delay);
69
+ continue;
70
+ }
71
+ // Handle server errors with retry
72
+ if (response.status >= 500) {
73
+ // Record failure in circuit breaker
74
+ if (this.config.circuitBreaker && this.config.sourceName) {
75
+ this.config.circuitBreaker.recordFailure(this.config.sourceName, req.path, response.status);
76
+ }
77
+ if (attempt < maxAttempts) {
78
+ const delay = this.calculateDelay(attempt, backoff, initialDelay, maxDelay);
79
+ await sleep(delay);
80
+ continue;
81
+ }
82
+ }
83
+ // Handle 401 - try token refresh
84
+ if (response.status === 401 && this.config.auth?.refreshToken && attempt < maxAttempts) {
85
+ await this.config.auth.refreshToken();
86
+ // Rebuild headers with new token
87
+ const newHeaders = await this.buildHeaders(req.headers);
88
+ fetchOptions.headers = newHeaders;
89
+ continue;
90
+ }
91
+ const data = await response.json();
92
+ // Record success in circuit breaker
93
+ if (this.config.circuitBreaker && this.config.sourceName && response.status < 500) {
94
+ this.config.circuitBreaker.recordSuccess(this.config.sourceName, req.path);
95
+ }
96
+ return {
97
+ status: response.status,
98
+ data,
99
+ headers: responseHeaders,
100
+ };
101
+ }
102
+ catch (error) {
103
+ lastError = error;
104
+ // Re-throw circuit breaker errors immediately
105
+ if (error instanceof CircuitBreakerError) {
106
+ throw error;
107
+ }
108
+ // Record network errors in circuit breaker
109
+ if (this.config.circuitBreaker && this.config.sourceName) {
110
+ this.config.circuitBreaker.recordFailure(this.config.sourceName, req.path, undefined, true);
111
+ }
112
+ if (attempt < maxAttempts) {
113
+ const delay = this.calculateDelay(attempt, backoff, initialDelay, maxDelay);
114
+ await sleep(delay);
115
+ }
116
+ }
117
+ }
118
+ throw lastError ?? new Error('Request failed after all retries');
119
+ }
120
+ buildUrl(path, query) {
121
+ const base = this.config.baseUrl.replace(/\/$/, '');
122
+ const cleanPath = path.startsWith('/') ? path : `/${path}`;
123
+ let url = `${base}${cleanPath}`;
124
+ if (query && Object.keys(query).length > 0) {
125
+ const params = new URLSearchParams(query);
126
+ url += `?${params.toString()}`;
127
+ }
128
+ return url;
129
+ }
130
+ async buildHeaders(requestHeaders) {
131
+ const headers = {
132
+ 'Content-Type': 'application/json',
133
+ Accept: 'application/json',
134
+ ...this.config.headers,
135
+ ...requestHeaders,
136
+ };
137
+ if (this.config.auth) {
138
+ const token = await this.config.auth.getToken();
139
+ headers['Authorization'] = `Bearer ${token}`;
140
+ }
141
+ return headers;
142
+ }
143
+ calculateDelay(attempt, backoff, initialDelay, maxDelay) {
144
+ let delay;
145
+ switch (backoff) {
146
+ case 'exponential':
147
+ delay = initialDelay * Math.pow(2, attempt - 1);
148
+ break;
149
+ case 'linear':
150
+ delay = initialDelay * attempt;
151
+ break;
152
+ case 'constant':
153
+ default:
154
+ delay = initialDelay;
155
+ }
156
+ // Add jitter (±10%)
157
+ const jitter = delay * 0.1 * (Math.random() * 2 - 1);
158
+ delay = Math.min(delay + jitter, maxDelay);
159
+ return Math.round(delay);
160
+ }
161
+ }
162
+ // Simple token-based auth provider
163
+ export class BearerAuthProvider {
164
+ token;
165
+ constructor(token) {
166
+ this.token = token;
167
+ }
168
+ async getToken() {
169
+ return this.token;
170
+ }
171
+ }
172
+ // OAuth2 auth provider (simplified)
173
+ export class OAuth2AuthProvider {
174
+ accessToken;
175
+ refreshTokenValue;
176
+ tokenEndpoint;
177
+ clientId;
178
+ clientSecret;
179
+ constructor(config) {
180
+ this.accessToken = config.accessToken;
181
+ this.refreshTokenValue = config.refreshToken;
182
+ this.tokenEndpoint = config.tokenEndpoint;
183
+ this.clientId = config.clientId;
184
+ this.clientSecret = config.clientSecret;
185
+ }
186
+ async getToken() {
187
+ return this.accessToken;
188
+ }
189
+ async refreshToken() {
190
+ if (!this.refreshTokenValue || !this.tokenEndpoint) {
191
+ throw new Error('Cannot refresh token: missing refresh token or endpoint');
192
+ }
193
+ const response = await fetch(this.tokenEndpoint, {
194
+ method: 'POST',
195
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
196
+ body: new URLSearchParams({
197
+ grant_type: 'refresh_token',
198
+ refresh_token: this.refreshTokenValue,
199
+ client_id: this.clientId ?? '',
200
+ client_secret: this.clientSecret ?? '',
201
+ }),
202
+ });
203
+ const data = await response.json();
204
+ this.accessToken = data.access_token;
205
+ if (data.refresh_token) {
206
+ this.refreshTokenValue = data.refresh_token;
207
+ }
208
+ return this.accessToken;
209
+ }
210
+ }
@@ -0,0 +1 @@
1
+ export {};