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,84 @@
1
+ /**
2
+ * Webhook Server
3
+ *
4
+ * HTTP server for receiving webhook callbacks.
5
+ * Supports dynamic registration of webhook endpoints.
6
+ */
7
+ import type { WebhookServerConfig, WebhookServerCallbacks, WebhookRegistration, WaitResult } from './types.js';
8
+ import type { WebhookStore } from './store.js';
9
+ /**
10
+ * Webhook Server
11
+ *
12
+ * Provides HTTP endpoints for receiving webhook callbacks.
13
+ */
14
+ export declare class WebhookServer {
15
+ private config;
16
+ private store;
17
+ private callbacks;
18
+ private server?;
19
+ private pendingWaits;
20
+ private cleanupInterval?;
21
+ private running;
22
+ constructor(config?: WebhookServerConfig, store?: WebhookStore, callbacks?: WebhookServerCallbacks);
23
+ /**
24
+ * Start the webhook server
25
+ */
26
+ start(): Promise<void>;
27
+ /**
28
+ * Stop the webhook server
29
+ */
30
+ stop(): Promise<void>;
31
+ /**
32
+ * Register a webhook endpoint
33
+ */
34
+ register(executionId: string, options?: {
35
+ path?: string;
36
+ timeout?: number;
37
+ expectedEvents?: number;
38
+ filter?: string;
39
+ }): Promise<WebhookRegistration>;
40
+ /**
41
+ * Get the full URL for a webhook endpoint
42
+ */
43
+ getWebhookUrl(registration: WebhookRegistration): string;
44
+ /**
45
+ * Wait for webhook events
46
+ */
47
+ waitForEvents(registrationId: string, timeout?: number): Promise<WaitResult>;
48
+ /**
49
+ * Unregister a webhook endpoint
50
+ */
51
+ unregister(registrationId: string): Promise<void>;
52
+ /**
53
+ * Check if the server is running
54
+ */
55
+ isRunning(): boolean;
56
+ /**
57
+ * Get the server port
58
+ */
59
+ getPort(): number;
60
+ /**
61
+ * Get the base URL
62
+ */
63
+ getBaseUrl(): string;
64
+ /**
65
+ * Handle incoming HTTP request
66
+ */
67
+ private handleRequest;
68
+ /**
69
+ * Read request body
70
+ */
71
+ private readBody;
72
+ /**
73
+ * Extract headers from request
74
+ */
75
+ private extractHeaders;
76
+ /**
77
+ * Clean up expired registrations
78
+ */
79
+ private cleanup;
80
+ /**
81
+ * Log message if verbose mode enabled
82
+ */
83
+ private log;
84
+ }
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Webhook Server
3
+ *
4
+ * HTTP server for receiving webhook callbacks.
5
+ * Supports dynamic registration of webhook endpoints.
6
+ */
7
+ import { createServer } from 'node:http';
8
+ import { parse as parseUrl } from 'node:url';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { MemoryWebhookStore } from './store.js';
11
+ /**
12
+ * Webhook Server
13
+ *
14
+ * Provides HTTP endpoints for receiving webhook callbacks.
15
+ */
16
+ export class WebhookServer {
17
+ config;
18
+ store;
19
+ callbacks;
20
+ server;
21
+ pendingWaits = new Map();
22
+ cleanupInterval;
23
+ running = false;
24
+ constructor(config = {}, store, callbacks = {}) {
25
+ this.config = {
26
+ port: config.port ?? 3000,
27
+ host: config.host ?? '0.0.0.0',
28
+ baseUrl: config.baseUrl ?? `http://localhost:${config.port ?? 3000}`,
29
+ defaultTimeout: config.defaultTimeout ?? 300000, // 5 minutes
30
+ verbose: config.verbose ?? false,
31
+ };
32
+ this.store = store ?? new MemoryWebhookStore();
33
+ this.callbacks = callbacks;
34
+ }
35
+ /**
36
+ * Start the webhook server
37
+ */
38
+ async start() {
39
+ if (this.running)
40
+ return;
41
+ return new Promise((resolve, reject) => {
42
+ this.server = createServer((req, res) => this.handleRequest(req, res));
43
+ this.server.on('error', (error) => {
44
+ reject(error);
45
+ });
46
+ this.server.listen(this.config.port, this.config.host, () => {
47
+ this.running = true;
48
+ this.log(`Webhook server listening on ${this.config.host}:${this.config.port}`);
49
+ // Start cleanup interval (every minute)
50
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
51
+ resolve();
52
+ });
53
+ });
54
+ }
55
+ /**
56
+ * Stop the webhook server
57
+ */
58
+ async stop() {
59
+ if (!this.running)
60
+ return;
61
+ // Clear cleanup interval
62
+ if (this.cleanupInterval) {
63
+ clearInterval(this.cleanupInterval);
64
+ this.cleanupInterval = undefined;
65
+ }
66
+ // Cancel all pending waits
67
+ for (const [id, pending] of this.pendingWaits) {
68
+ clearTimeout(pending.timeoutId);
69
+ pending.resolve({
70
+ success: false,
71
+ events: [],
72
+ error: 'Server shutting down',
73
+ });
74
+ }
75
+ this.pendingWaits.clear();
76
+ // Close server
77
+ return new Promise((resolve) => {
78
+ if (this.server) {
79
+ this.server.close(() => {
80
+ this.running = false;
81
+ this.log('Webhook server stopped');
82
+ resolve();
83
+ });
84
+ }
85
+ else {
86
+ resolve();
87
+ }
88
+ });
89
+ }
90
+ /**
91
+ * Register a webhook endpoint
92
+ */
93
+ async register(executionId, options = {}) {
94
+ const id = randomUUID();
95
+ const timeout = options.timeout ?? this.config.defaultTimeout;
96
+ const path = options.path ?? `/webhook/${executionId}/${id}`;
97
+ const registration = {
98
+ id,
99
+ executionId,
100
+ path,
101
+ createdAt: new Date(),
102
+ expiresAt: new Date(Date.now() + timeout),
103
+ expectedEvents: options.expectedEvents ?? 1,
104
+ receivedEvents: 0,
105
+ filter: options.filter,
106
+ };
107
+ await this.store.saveRegistration(registration);
108
+ this.callbacks.onRegistrationCreated?.(registration);
109
+ this.log(`Registered webhook: ${path} (expires: ${registration.expiresAt.toISOString()})`);
110
+ return registration;
111
+ }
112
+ /**
113
+ * Get the full URL for a webhook endpoint
114
+ */
115
+ getWebhookUrl(registration) {
116
+ return `${this.config.baseUrl}${registration.path}`;
117
+ }
118
+ /**
119
+ * Wait for webhook events
120
+ */
121
+ async waitForEvents(registrationId, timeout) {
122
+ const registration = await this.store.getRegistration(registrationId);
123
+ if (!registration) {
124
+ return {
125
+ success: false,
126
+ events: [],
127
+ error: `Registration not found: ${registrationId}`,
128
+ };
129
+ }
130
+ // Check if already received enough events
131
+ const events = await this.store.getEvents(registrationId);
132
+ if (events.length >= registration.expectedEvents) {
133
+ return { success: true, events };
134
+ }
135
+ // Wait for more events
136
+ const waitTimeout = timeout ?? (registration.expiresAt.getTime() - Date.now());
137
+ return new Promise((resolve) => {
138
+ const timeoutId = setTimeout(() => {
139
+ this.pendingWaits.delete(registrationId);
140
+ this.store.getEvents(registrationId).then((events) => {
141
+ resolve({
142
+ success: events.length >= registration.expectedEvents,
143
+ events,
144
+ timedOut: true,
145
+ });
146
+ });
147
+ }, waitTimeout);
148
+ this.pendingWaits.set(registrationId, {
149
+ registrationId,
150
+ resolve,
151
+ timeoutId,
152
+ });
153
+ });
154
+ }
155
+ /**
156
+ * Unregister a webhook endpoint
157
+ */
158
+ async unregister(registrationId) {
159
+ await this.store.deleteRegistration(registrationId);
160
+ await this.store.deleteEvents(registrationId);
161
+ // Cancel pending wait if any
162
+ const pending = this.pendingWaits.get(registrationId);
163
+ if (pending) {
164
+ clearTimeout(pending.timeoutId);
165
+ this.pendingWaits.delete(registrationId);
166
+ }
167
+ this.log(`Unregistered webhook: ${registrationId}`);
168
+ }
169
+ /**
170
+ * Check if the server is running
171
+ */
172
+ isRunning() {
173
+ return this.running;
174
+ }
175
+ /**
176
+ * Get the server port
177
+ */
178
+ getPort() {
179
+ return this.config.port;
180
+ }
181
+ /**
182
+ * Get the base URL
183
+ */
184
+ getBaseUrl() {
185
+ return this.config.baseUrl;
186
+ }
187
+ /**
188
+ * Handle incoming HTTP request
189
+ */
190
+ async handleRequest(req, res) {
191
+ const url = parseUrl(req.url ?? '/', true);
192
+ const path = url.pathname ?? '/';
193
+ // Health check endpoint
194
+ if (path === '/health' || path === '/_health') {
195
+ res.writeHead(200, { 'Content-Type': 'application/json' });
196
+ res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }));
197
+ return;
198
+ }
199
+ // Find matching registration
200
+ const registration = await this.store.getRegistrationByPath(path);
201
+ if (!registration) {
202
+ res.writeHead(404, { 'Content-Type': 'application/json' });
203
+ res.end(JSON.stringify({ error: 'Not found', path }));
204
+ return;
205
+ }
206
+ // Check if registration is expired
207
+ if (registration.expiresAt < new Date()) {
208
+ res.writeHead(410, { 'Content-Type': 'application/json' });
209
+ res.end(JSON.stringify({ error: 'Webhook registration expired' }));
210
+ await this.store.deleteRegistration(registration.id);
211
+ return;
212
+ }
213
+ // Parse request body
214
+ let rawBody = '';
215
+ let body = null;
216
+ try {
217
+ rawBody = await this.readBody(req);
218
+ if (rawBody) {
219
+ const contentType = req.headers['content-type'] ?? '';
220
+ if (contentType.includes('application/json')) {
221
+ body = JSON.parse(rawBody);
222
+ }
223
+ else if (contentType.includes('application/x-www-form-urlencoded')) {
224
+ body = Object.fromEntries(new URLSearchParams(rawBody));
225
+ }
226
+ else {
227
+ body = rawBody;
228
+ }
229
+ }
230
+ }
231
+ catch (error) {
232
+ body = rawBody;
233
+ }
234
+ // Create event
235
+ const event = {
236
+ id: randomUUID(),
237
+ registrationId: registration.id,
238
+ receivedAt: new Date(),
239
+ method: req.method ?? 'POST',
240
+ headers: this.extractHeaders(req),
241
+ body,
242
+ rawBody,
243
+ query: url.query,
244
+ };
245
+ // Save event
246
+ await this.store.saveEvent(event);
247
+ registration.receivedEvents++;
248
+ await this.store.saveRegistration(registration);
249
+ this.log(`Webhook received: ${path} (${registration.receivedEvents}/${registration.expectedEvents})`);
250
+ this.callbacks.onWebhookReceived?.(event);
251
+ // Check if all expected events received
252
+ if (registration.receivedEvents >= registration.expectedEvents) {
253
+ const events = await this.store.getEvents(registration.id);
254
+ this.callbacks.onRegistrationComplete?.(registration, events);
255
+ // Resolve pending wait
256
+ const pending = this.pendingWaits.get(registration.id);
257
+ if (pending) {
258
+ clearTimeout(pending.timeoutId);
259
+ this.pendingWaits.delete(registration.id);
260
+ pending.resolve({ success: true, events });
261
+ }
262
+ }
263
+ // Send response
264
+ res.writeHead(200, { 'Content-Type': 'application/json' });
265
+ res.end(JSON.stringify({
266
+ success: true,
267
+ eventId: event.id,
268
+ received: registration.receivedEvents,
269
+ expected: registration.expectedEvents,
270
+ }));
271
+ }
272
+ /**
273
+ * Read request body
274
+ */
275
+ readBody(req) {
276
+ return new Promise((resolve, reject) => {
277
+ let data = '';
278
+ req.on('data', (chunk) => {
279
+ data += chunk;
280
+ });
281
+ req.on('end', () => {
282
+ resolve(data);
283
+ });
284
+ req.on('error', reject);
285
+ });
286
+ }
287
+ /**
288
+ * Extract headers from request
289
+ */
290
+ extractHeaders(req) {
291
+ const headers = {};
292
+ for (const [key, value] of Object.entries(req.headers)) {
293
+ if (typeof value === 'string') {
294
+ headers[key] = value;
295
+ }
296
+ else if (Array.isArray(value)) {
297
+ headers[key] = value.join(', ');
298
+ }
299
+ }
300
+ return headers;
301
+ }
302
+ /**
303
+ * Clean up expired registrations
304
+ */
305
+ async cleanup() {
306
+ const cleaned = await this.store.cleanupExpired();
307
+ if (cleaned > 0) {
308
+ this.log(`Cleaned up ${cleaned} expired webhook registration(s)`);
309
+ }
310
+ }
311
+ /**
312
+ * Log message if verbose mode enabled
313
+ */
314
+ log(message) {
315
+ if (this.config.verbose) {
316
+ console.log(`[Webhook] ${message}`);
317
+ }
318
+ }
319
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Webhook Store
3
+ *
4
+ * Stores webhook registrations and received events.
5
+ * Supports both in-memory and file-based persistence.
6
+ */
7
+ import type { WebhookRegistration, WebhookEvent } from './types.js';
8
+ /**
9
+ * Interface for webhook storage
10
+ */
11
+ export interface WebhookStore {
12
+ /** Save a webhook registration */
13
+ saveRegistration(registration: WebhookRegistration): Promise<void>;
14
+ /** Get a registration by ID */
15
+ getRegistration(id: string): Promise<WebhookRegistration | undefined>;
16
+ /** Get a registration by path */
17
+ getRegistrationByPath(path: string): Promise<WebhookRegistration | undefined>;
18
+ /** Delete a registration */
19
+ deleteRegistration(id: string): Promise<void>;
20
+ /** List all registrations */
21
+ listRegistrations(): Promise<WebhookRegistration[]>;
22
+ /** Save a webhook event */
23
+ saveEvent(event: WebhookEvent): Promise<void>;
24
+ /** Get events for a registration */
25
+ getEvents(registrationId: string): Promise<WebhookEvent[]>;
26
+ /** Delete events for a registration */
27
+ deleteEvents(registrationId: string): Promise<void>;
28
+ /** Clean up expired registrations */
29
+ cleanupExpired(): Promise<number>;
30
+ }
31
+ /**
32
+ * In-memory webhook store
33
+ */
34
+ export declare class MemoryWebhookStore implements WebhookStore {
35
+ private registrations;
36
+ private events;
37
+ private pathIndex;
38
+ saveRegistration(registration: WebhookRegistration): Promise<void>;
39
+ getRegistration(id: string): Promise<WebhookRegistration | undefined>;
40
+ getRegistrationByPath(path: string): Promise<WebhookRegistration | undefined>;
41
+ deleteRegistration(id: string): Promise<void>;
42
+ listRegistrations(): Promise<WebhookRegistration[]>;
43
+ saveEvent(event: WebhookEvent): Promise<void>;
44
+ getEvents(registrationId: string): Promise<WebhookEvent[]>;
45
+ deleteEvents(registrationId: string): Promise<void>;
46
+ cleanupExpired(): Promise<number>;
47
+ }
48
+ /**
49
+ * File-based webhook store for persistence across restarts
50
+ */
51
+ export declare class FileWebhookStore implements WebhookStore {
52
+ private baseDir;
53
+ private registrationsDir;
54
+ private eventsDir;
55
+ private initialized;
56
+ constructor(baseDir?: string);
57
+ private ensureInit;
58
+ saveRegistration(registration: WebhookRegistration): Promise<void>;
59
+ getRegistration(id: string): Promise<WebhookRegistration | undefined>;
60
+ getRegistrationByPath(path: string): Promise<WebhookRegistration | undefined>;
61
+ deleteRegistration(id: string): Promise<void>;
62
+ listRegistrations(): Promise<WebhookRegistration[]>;
63
+ saveEvent(event: WebhookEvent): Promise<void>;
64
+ getEvents(registrationId: string): Promise<WebhookEvent[]>;
65
+ deleteEvents(registrationId: string): Promise<void>;
66
+ cleanupExpired(): Promise<number>;
67
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Webhook Store
3
+ *
4
+ * Stores webhook registrations and received events.
5
+ * Supports both in-memory and file-based persistence.
6
+ */
7
+ import { mkdir, readFile, writeFile, readdir, unlink } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ /**
10
+ * In-memory webhook store
11
+ */
12
+ export class MemoryWebhookStore {
13
+ registrations = new Map();
14
+ events = new Map();
15
+ pathIndex = new Map(); // path -> registrationId
16
+ async saveRegistration(registration) {
17
+ this.registrations.set(registration.id, registration);
18
+ this.pathIndex.set(registration.path, registration.id);
19
+ }
20
+ async getRegistration(id) {
21
+ return this.registrations.get(id);
22
+ }
23
+ async getRegistrationByPath(path) {
24
+ const id = this.pathIndex.get(path);
25
+ if (!id)
26
+ return undefined;
27
+ return this.registrations.get(id);
28
+ }
29
+ async deleteRegistration(id) {
30
+ const reg = this.registrations.get(id);
31
+ if (reg) {
32
+ this.pathIndex.delete(reg.path);
33
+ }
34
+ this.registrations.delete(id);
35
+ }
36
+ async listRegistrations() {
37
+ return Array.from(this.registrations.values());
38
+ }
39
+ async saveEvent(event) {
40
+ const events = this.events.get(event.registrationId) ?? [];
41
+ events.push(event);
42
+ this.events.set(event.registrationId, events);
43
+ }
44
+ async getEvents(registrationId) {
45
+ return this.events.get(registrationId) ?? [];
46
+ }
47
+ async deleteEvents(registrationId) {
48
+ this.events.delete(registrationId);
49
+ }
50
+ async cleanupExpired() {
51
+ const now = new Date();
52
+ let cleaned = 0;
53
+ for (const [id, reg] of this.registrations) {
54
+ if (reg.expiresAt < now) {
55
+ await this.deleteRegistration(id);
56
+ await this.deleteEvents(id);
57
+ cleaned++;
58
+ }
59
+ }
60
+ return cleaned;
61
+ }
62
+ }
63
+ /**
64
+ * File-based webhook store for persistence across restarts
65
+ */
66
+ export class FileWebhookStore {
67
+ baseDir;
68
+ registrationsDir;
69
+ eventsDir;
70
+ initialized = false;
71
+ constructor(baseDir = '.reqon-data/webhooks') {
72
+ this.baseDir = baseDir;
73
+ this.registrationsDir = join(baseDir, 'registrations');
74
+ this.eventsDir = join(baseDir, 'events');
75
+ }
76
+ async ensureInit() {
77
+ if (this.initialized)
78
+ return;
79
+ await mkdir(this.registrationsDir, { recursive: true });
80
+ await mkdir(this.eventsDir, { recursive: true });
81
+ this.initialized = true;
82
+ }
83
+ async saveRegistration(registration) {
84
+ await this.ensureInit();
85
+ const filePath = join(this.registrationsDir, `${registration.id}.json`);
86
+ await writeFile(filePath, JSON.stringify(registration, null, 2));
87
+ }
88
+ async getRegistration(id) {
89
+ await this.ensureInit();
90
+ try {
91
+ const filePath = join(this.registrationsDir, `${id}.json`);
92
+ const content = await readFile(filePath, 'utf-8');
93
+ const data = JSON.parse(content);
94
+ return {
95
+ ...data,
96
+ createdAt: new Date(data.createdAt),
97
+ expiresAt: new Date(data.expiresAt),
98
+ };
99
+ }
100
+ catch {
101
+ return undefined;
102
+ }
103
+ }
104
+ async getRegistrationByPath(path) {
105
+ const registrations = await this.listRegistrations();
106
+ return registrations.find((r) => r.path === path);
107
+ }
108
+ async deleteRegistration(id) {
109
+ await this.ensureInit();
110
+ try {
111
+ const filePath = join(this.registrationsDir, `${id}.json`);
112
+ await unlink(filePath);
113
+ }
114
+ catch {
115
+ // Ignore if file doesn't exist
116
+ }
117
+ }
118
+ async listRegistrations() {
119
+ await this.ensureInit();
120
+ try {
121
+ const files = await readdir(this.registrationsDir);
122
+ const registrations = [];
123
+ for (const file of files) {
124
+ if (!file.endsWith('.json'))
125
+ continue;
126
+ const id = file.replace('.json', '');
127
+ const reg = await this.getRegistration(id);
128
+ if (reg)
129
+ registrations.push(reg);
130
+ }
131
+ return registrations;
132
+ }
133
+ catch {
134
+ return [];
135
+ }
136
+ }
137
+ async saveEvent(event) {
138
+ await this.ensureInit();
139
+ const eventDir = join(this.eventsDir, event.registrationId);
140
+ await mkdir(eventDir, { recursive: true });
141
+ const filePath = join(eventDir, `${event.id}.json`);
142
+ await writeFile(filePath, JSON.stringify(event, null, 2));
143
+ }
144
+ async getEvents(registrationId) {
145
+ await this.ensureInit();
146
+ try {
147
+ const eventDir = join(this.eventsDir, registrationId);
148
+ const files = await readdir(eventDir);
149
+ const events = [];
150
+ for (const file of files) {
151
+ if (!file.endsWith('.json'))
152
+ continue;
153
+ const filePath = join(eventDir, file);
154
+ const content = await readFile(filePath, 'utf-8');
155
+ const data = JSON.parse(content);
156
+ events.push({
157
+ ...data,
158
+ receivedAt: new Date(data.receivedAt),
159
+ });
160
+ }
161
+ return events.sort((a, b) => a.receivedAt.getTime() - b.receivedAt.getTime());
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ }
167
+ async deleteEvents(registrationId) {
168
+ await this.ensureInit();
169
+ try {
170
+ const eventDir = join(this.eventsDir, registrationId);
171
+ const files = await readdir(eventDir);
172
+ for (const file of files) {
173
+ await unlink(join(eventDir, file));
174
+ }
175
+ }
176
+ catch {
177
+ // Ignore if directory doesn't exist
178
+ }
179
+ }
180
+ async cleanupExpired() {
181
+ const now = new Date();
182
+ const registrations = await this.listRegistrations();
183
+ let cleaned = 0;
184
+ for (const reg of registrations) {
185
+ if (reg.expiresAt < now) {
186
+ await this.deleteRegistration(reg.id);
187
+ await this.deleteEvents(reg.id);
188
+ cleaned++;
189
+ }
190
+ }
191
+ return cleaned;
192
+ }
193
+ }