@xtandard/webhooks 0.1.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 (279) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +315 -0
  3. package/bin/xtandard-webhooks.mjs +3 -0
  4. package/dist/basic-BIW3Rvuz.cjs +199 -0
  5. package/dist/basic-BIW3Rvuz.cjs.map +1 -0
  6. package/dist/basic-DKk0Xfuu.mjs +176 -0
  7. package/dist/basic-DKk0Xfuu.mjs.map +1 -0
  8. package/dist/chunk-D7D4PA-g.mjs +13 -0
  9. package/dist/cli.cjs +655 -0
  10. package/dist/cli.cjs.map +1 -0
  11. package/dist/cli.d.cts +42 -0
  12. package/dist/cli.d.mts +42 -0
  13. package/dist/cli.mjs +653 -0
  14. package/dist/cli.mjs.map +1 -0
  15. package/dist/contract-8h-Azxa5.d.cts +71 -0
  16. package/dist/contract-9XpcwcCn.mjs +22 -0
  17. package/dist/contract-9XpcwcCn.mjs.map +1 -0
  18. package/dist/contract-B2d5dNU3.cjs +33 -0
  19. package/dist/contract-B2d5dNU3.cjs.map +1 -0
  20. package/dist/contract-BEhDcd_5.mjs +28 -0
  21. package/dist/contract-BEhDcd_5.mjs.map +1 -0
  22. package/dist/contract-Bf1qguwt.cjs +57 -0
  23. package/dist/contract-Bf1qguwt.cjs.map +1 -0
  24. package/dist/contract-Bnb3fgRJ.d.cts +177 -0
  25. package/dist/contract-C2r2Xzwp.d.mts +46 -0
  26. package/dist/contract-CiPskNvS.d.cts +46 -0
  27. package/dist/contract-DhQ4JjGG.d.mts +71 -0
  28. package/dist/contract-T1kcZNdG.d.mts +177 -0
  29. package/dist/contract-lETlIuXo.d.cts +30 -0
  30. package/dist/contract-lETlIuXo.d.mts +30 -0
  31. package/dist/core-CMpnmI5Q.mjs +1605 -0
  32. package/dist/core-CMpnmI5Q.mjs.map +1 -0
  33. package/dist/core-DT4ppWh8.d.mts +502 -0
  34. package/dist/core-KJawHjFF.d.cts +502 -0
  35. package/dist/core-ZGhH6Vs2.cjs +1790 -0
  36. package/dist/core-ZGhH6Vs2.cjs.map +1 -0
  37. package/dist/core.cjs +8 -0
  38. package/dist/core.d.cts +2 -0
  39. package/dist/core.d.mts +2 -0
  40. package/dist/core.mjs +2 -0
  41. package/dist/create-fetch-handler-BIdk9P30.mjs +1724 -0
  42. package/dist/create-fetch-handler-BIdk9P30.mjs.map +1 -0
  43. package/dist/create-fetch-handler-CmooujQo.cjs +1771 -0
  44. package/dist/create-fetch-handler-CmooujQo.cjs.map +1 -0
  45. package/dist/create-fetch-handler-Dlkhustu.d.cts +162 -0
  46. package/dist/create-fetch-handler-jy3hy5nZ.d.mts +162 -0
  47. package/dist/dispatcher-B0xTEHt1.cjs +212 -0
  48. package/dist/dispatcher-B0xTEHt1.cjs.map +1 -0
  49. package/dist/dispatcher-Coubwrka.mjs +196 -0
  50. package/dist/dispatcher-Coubwrka.mjs.map +1 -0
  51. package/dist/entry-auth-basic.cjs +5 -0
  52. package/dist/entry-auth-basic.d.cts +83 -0
  53. package/dist/entry-auth-basic.d.mts +83 -0
  54. package/dist/entry-auth-basic.mjs +2 -0
  55. package/dist/entry-auth-delegated.cjs +28 -0
  56. package/dist/entry-auth-delegated.cjs.map +1 -0
  57. package/dist/entry-auth-delegated.d.cts +36 -0
  58. package/dist/entry-auth-delegated.d.mts +36 -0
  59. package/dist/entry-auth-delegated.mjs +27 -0
  60. package/dist/entry-auth-delegated.mjs.map +1 -0
  61. package/dist/entry-auth-none.cjs +4 -0
  62. package/dist/entry-auth-none.d.cts +25 -0
  63. package/dist/entry-auth-none.d.mts +25 -0
  64. package/dist/entry-auth-none.mjs +2 -0
  65. package/dist/entry-authorization-delegated.cjs +27 -0
  66. package/dist/entry-authorization-delegated.cjs.map +1 -0
  67. package/dist/entry-authorization-delegated.d.cts +31 -0
  68. package/dist/entry-authorization-delegated.d.mts +31 -0
  69. package/dist/entry-authorization-delegated.mjs +26 -0
  70. package/dist/entry-authorization-delegated.mjs.map +1 -0
  71. package/dist/entry-authorization-none.cjs +3 -0
  72. package/dist/entry-authorization-none.d.cts +18 -0
  73. package/dist/entry-authorization-none.d.mts +18 -0
  74. package/dist/entry-authorization-none.mjs +2 -0
  75. package/dist/entry-authorization-roles.cjs +6 -0
  76. package/dist/entry-authorization-roles.d.cts +65 -0
  77. package/dist/entry-authorization-roles.d.mts +65 -0
  78. package/dist/entry-authorization-roles.mjs +2 -0
  79. package/dist/entry-bun.cjs +24 -0
  80. package/dist/entry-bun.cjs.map +1 -0
  81. package/dist/entry-bun.d.cts +8 -0
  82. package/dist/entry-bun.d.mts +8 -0
  83. package/dist/entry-bun.mjs +23 -0
  84. package/dist/entry-bun.mjs.map +1 -0
  85. package/dist/entry-drizzle-mysql.cjs +20 -0
  86. package/dist/entry-drizzle-mysql.cjs.map +1 -0
  87. package/dist/entry-drizzle-mysql.d.cts +27 -0
  88. package/dist/entry-drizzle-mysql.d.mts +27 -0
  89. package/dist/entry-drizzle-mysql.mjs +19 -0
  90. package/dist/entry-drizzle-mysql.mjs.map +1 -0
  91. package/dist/entry-drizzle-pg.cjs +21 -0
  92. package/dist/entry-drizzle-pg.cjs.map +1 -0
  93. package/dist/entry-drizzle-pg.d.cts +26 -0
  94. package/dist/entry-drizzle-pg.d.mts +26 -0
  95. package/dist/entry-drizzle-pg.mjs +20 -0
  96. package/dist/entry-drizzle-pg.mjs.map +1 -0
  97. package/dist/entry-drizzle-sqlite.cjs +21 -0
  98. package/dist/entry-drizzle-sqlite.cjs.map +1 -0
  99. package/dist/entry-drizzle-sqlite.d.cts +23 -0
  100. package/dist/entry-drizzle-sqlite.d.mts +23 -0
  101. package/dist/entry-drizzle-sqlite.mjs +20 -0
  102. package/dist/entry-drizzle-sqlite.mjs.map +1 -0
  103. package/dist/entry-elysia.cjs +125 -0
  104. package/dist/entry-elysia.cjs.map +1 -0
  105. package/dist/entry-elysia.d.cts +1017 -0
  106. package/dist/entry-elysia.d.mts +1017 -0
  107. package/dist/entry-elysia.mjs +123 -0
  108. package/dist/entry-elysia.mjs.map +1 -0
  109. package/dist/entry-express.cjs +57 -0
  110. package/dist/entry-express.cjs.map +1 -0
  111. package/dist/entry-express.d.cts +15 -0
  112. package/dist/entry-express.d.mts +15 -0
  113. package/dist/entry-express.mjs +56 -0
  114. package/dist/entry-express.mjs.map +1 -0
  115. package/dist/entry-hono.cjs +35 -0
  116. package/dist/entry-hono.cjs.map +1 -0
  117. package/dist/entry-hono.d.cts +16 -0
  118. package/dist/entry-hono.d.mts +16 -0
  119. package/dist/entry-hono.mjs +34 -0
  120. package/dist/entry-hono.mjs.map +1 -0
  121. package/dist/entry-hooks-log.cjs +22 -0
  122. package/dist/entry-hooks-log.cjs.map +1 -0
  123. package/dist/entry-hooks-log.d.cts +23 -0
  124. package/dist/entry-hooks-log.d.mts +23 -0
  125. package/dist/entry-hooks-log.mjs +21 -0
  126. package/dist/entry-hooks-log.mjs.map +1 -0
  127. package/dist/entry-storage-cloudflare-kv.cjs +47 -0
  128. package/dist/entry-storage-cloudflare-kv.cjs.map +1 -0
  129. package/dist/entry-storage-cloudflare-kv.d.cts +42 -0
  130. package/dist/entry-storage-cloudflare-kv.d.mts +42 -0
  131. package/dist/entry-storage-cloudflare-kv.mjs +46 -0
  132. package/dist/entry-storage-cloudflare-kv.mjs.map +1 -0
  133. package/dist/entry-storage-drizzle.cjs +78 -0
  134. package/dist/entry-storage-drizzle.cjs.map +1 -0
  135. package/dist/entry-storage-drizzle.d.cts +30 -0
  136. package/dist/entry-storage-drizzle.d.mts +30 -0
  137. package/dist/entry-storage-drizzle.mjs +77 -0
  138. package/dist/entry-storage-drizzle.mjs.map +1 -0
  139. package/dist/entry-storage-file.cjs +4 -0
  140. package/dist/entry-storage-file.d.cts +30 -0
  141. package/dist/entry-storage-file.d.mts +30 -0
  142. package/dist/entry-storage-file.mjs +2 -0
  143. package/dist/entry-storage-libsql.cjs +3 -0
  144. package/dist/entry-storage-libsql.d.cts +48 -0
  145. package/dist/entry-storage-libsql.d.mts +48 -0
  146. package/dist/entry-storage-libsql.mjs +2 -0
  147. package/dist/entry-storage-memory.cjs +3 -0
  148. package/dist/entry-storage-memory.d.cts +2 -0
  149. package/dist/entry-storage-memory.d.mts +2 -0
  150. package/dist/entry-storage-memory.mjs +2 -0
  151. package/dist/entry-storage-mongodb.cjs +3 -0
  152. package/dist/entry-storage-mongodb.d.cts +55 -0
  153. package/dist/entry-storage-mongodb.d.mts +55 -0
  154. package/dist/entry-storage-mongodb.mjs +2 -0
  155. package/dist/entry-storage-postgres.cjs +3 -0
  156. package/dist/entry-storage-postgres.d.cts +62 -0
  157. package/dist/entry-storage-postgres.d.mts +62 -0
  158. package/dist/entry-storage-postgres.mjs +2 -0
  159. package/dist/entry-storage-redis.cjs +4 -0
  160. package/dist/entry-storage-redis.d.cts +77 -0
  161. package/dist/entry-storage-redis.d.mts +77 -0
  162. package/dist/entry-storage-redis.mjs +2 -0
  163. package/dist/entry-storage-sqlite.cjs +3 -0
  164. package/dist/entry-storage-sqlite.d.cts +36 -0
  165. package/dist/entry-storage-sqlite.d.mts +36 -0
  166. package/dist/entry-storage-sqlite.mjs +2 -0
  167. package/dist/entry-storage-unstorage.cjs +42 -0
  168. package/dist/entry-storage-unstorage.cjs.map +1 -0
  169. package/dist/entry-storage-unstorage.d.cts +29 -0
  170. package/dist/entry-storage-unstorage.d.mts +29 -0
  171. package/dist/entry-storage-unstorage.mjs +41 -0
  172. package/dist/entry-storage-unstorage.mjs.map +1 -0
  173. package/dist/file-COBYZA4Q.cjs +148 -0
  174. package/dist/file-COBYZA4Q.cjs.map +1 -0
  175. package/dist/file-fi02eFHk.mjs +131 -0
  176. package/dist/file-fi02eFHk.mjs.map +1 -0
  177. package/dist/index.cjs +123 -0
  178. package/dist/index.cjs.map +1 -0
  179. package/dist/index.d.cts +368 -0
  180. package/dist/index.d.mts +366 -0
  181. package/dist/index.mjs +61 -0
  182. package/dist/index.mjs.map +1 -0
  183. package/dist/keys-Byyj4quQ.mjs +111 -0
  184. package/dist/keys-Byyj4quQ.mjs.map +1 -0
  185. package/dist/keys-FiKpaVHX.cjs +302 -0
  186. package/dist/keys-FiKpaVHX.cjs.map +1 -0
  187. package/dist/libsql-bpVi0bXN.mjs +113 -0
  188. package/dist/libsql-bpVi0bXN.mjs.map +1 -0
  189. package/dist/libsql-pPJEo1e4.cjs +124 -0
  190. package/dist/libsql-pPJEo1e4.cjs.map +1 -0
  191. package/dist/memory-8Ef-PL5a.cjs +137 -0
  192. package/dist/memory-8Ef-PL5a.cjs.map +1 -0
  193. package/dist/memory-BMsSSwqn.mjs +127 -0
  194. package/dist/memory-BMsSSwqn.mjs.map +1 -0
  195. package/dist/memory-FnMJWCmB.d.cts +28 -0
  196. package/dist/memory-qIvANEs_.d.mts +28 -0
  197. package/dist/mongodb-Cy8yo0uk.cjs +108 -0
  198. package/dist/mongodb-Cy8yo0uk.cjs.map +1 -0
  199. package/dist/mongodb-Ddaq9mml.mjs +97 -0
  200. package/dist/mongodb-Ddaq9mml.mjs.map +1 -0
  201. package/dist/none-BnZtaGNJ.mjs +23 -0
  202. package/dist/none-BnZtaGNJ.mjs.map +1 -0
  203. package/dist/none-CAsxCOWN.cjs +49 -0
  204. package/dist/none-CAsxCOWN.cjs.map +1 -0
  205. package/dist/none-CZVrfnmF.cjs +33 -0
  206. package/dist/none-CZVrfnmF.cjs.map +1 -0
  207. package/dist/none-GhVIoh_s.mjs +33 -0
  208. package/dist/none-GhVIoh_s.mjs.map +1 -0
  209. package/dist/postgres-C8WbchFa.cjs +134 -0
  210. package/dist/postgres-C8WbchFa.cjs.map +1 -0
  211. package/dist/postgres-c3pAhmhr.mjs +123 -0
  212. package/dist/postgres-c3pAhmhr.mjs.map +1 -0
  213. package/dist/react.css +1 -0
  214. package/dist/react.js +31465 -0
  215. package/dist/receiver.cjs +43 -0
  216. package/dist/receiver.cjs.map +1 -0
  217. package/dist/receiver.d.cts +36 -0
  218. package/dist/receiver.d.mts +36 -0
  219. package/dist/receiver.mjs +40 -0
  220. package/dist/receiver.mjs.map +1 -0
  221. package/dist/redis-CFJkuSgB.cjs +270 -0
  222. package/dist/redis-CFJkuSgB.cjs.map +1 -0
  223. package/dist/redis-CvLi0KF7.mjs +254 -0
  224. package/dist/redis-CvLi0KF7.mjs.map +1 -0
  225. package/dist/roles-D0G9XqBq.cjs +128 -0
  226. package/dist/roles-D0G9XqBq.cjs.map +1 -0
  227. package/dist/roles-vp361lTk.mjs +99 -0
  228. package/dist/roles-vp361lTk.mjs.map +1 -0
  229. package/dist/schema-mo__wv4P.d.cts +233 -0
  230. package/dist/schema-mo__wv4P.d.mts +233 -0
  231. package/dist/schema.cjs +13 -0
  232. package/dist/schema.cjs.map +1 -0
  233. package/dist/schema.d.cts +2 -0
  234. package/dist/schema.d.mts +2 -0
  235. package/dist/schema.mjs +11 -0
  236. package/dist/schema.mjs.map +1 -0
  237. package/dist/signing.cjs +162 -0
  238. package/dist/signing.cjs.map +1 -0
  239. package/dist/signing.d.cts +73 -0
  240. package/dist/signing.d.mts +73 -0
  241. package/dist/signing.mjs +156 -0
  242. package/dist/signing.mjs.map +1 -0
  243. package/dist/sqlite-Cmqnrjes.mjs +67 -0
  244. package/dist/sqlite-Cmqnrjes.mjs.map +1 -0
  245. package/dist/sqlite-Dcufk0x3.cjs +78 -0
  246. package/dist/sqlite-Dcufk0x3.cjs.map +1 -0
  247. package/dist/table-Ce3Tzwqs.d.cts +11 -0
  248. package/dist/table-Ce3Tzwqs.d.mts +11 -0
  249. package/dist/testing.cjs +134 -0
  250. package/dist/testing.cjs.map +1 -0
  251. package/dist/testing.d.cts +80 -0
  252. package/dist/testing.d.mts +80 -0
  253. package/dist/testing.mjs +131 -0
  254. package/dist/testing.mjs.map +1 -0
  255. package/dist/types-react/react.d.ts +98 -0
  256. package/dist/types-react/schema.d.ts +229 -0
  257. package/dist/types-react/ui/App.d.ts +22 -0
  258. package/dist/types-react/ui/api.d.ts +97 -0
  259. package/dist/types-react/ui/components/JsonCodeEditor.d.ts +12 -0
  260. package/dist/types-react/ui/components/ThemeToggle.d.ts +2 -0
  261. package/dist/types-react/ui/components/Toast.d.ts +16 -0
  262. package/dist/types-react/ui/components/primitives.d.ts +50 -0
  263. package/dist/types-react/ui/components/ui-bits.d.ts +22 -0
  264. package/dist/types-react/ui/components/webhook-bits.d.ts +51 -0
  265. package/dist/types-react/ui/lib/format.d.ts +39 -0
  266. package/dist/types-react/ui/lib/nav-guard.d.ts +20 -0
  267. package/dist/types-react/ui/lib/utils.d.ts +3 -0
  268. package/dist/types-react/ui/theme.d.ts +12 -0
  269. package/dist/types-react/ui/types.d.ts +80 -0
  270. package/dist/types-react/ui/views/AuditView.d.ts +6 -0
  271. package/dist/types-react/ui/views/DeliveriesView.d.ts +12 -0
  272. package/dist/types-react/ui/views/EndpointsView.d.ts +11 -0
  273. package/dist/types-react/ui/views/EventTypesView.d.ts +11 -0
  274. package/dist/types-react/ui/views/MessagesView.d.ts +10 -0
  275. package/dist/types-react/ui/views/OverviewView.d.ts +12 -0
  276. package/dist/ui/assets/index-B0eoQX2U.css +1 -0
  277. package/dist/ui/assets/index-S5t_CLOe.js +209 -0
  278. package/dist/ui/index.html +14 -0
  279. package/package.json +487 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["/**\n * `xtandard-webhooks` CLI. Operates on the same storage your app/panel uses\n * (configured via the `STORAGE_DRIVER` / `QUEUE_STORAGE_DRIVER` env vars,\n * mirroring the standalone app), so it slots into shell, CI, and split-worker\n * workflows.\n *\n * Commands: `serve` (panel + dispatcher, no Docker), `dispatch` (dispatcher\n * ONLY — the split-worker mode), `init`, `list-apps`, `list-endpoints`,\n * `publish`, `retry`, `verify`, `sign` (build a signed request — the signature\n * playground), and `listen` (a local inspecting receiver — the webhook.site\n * you run yourself).\n *\n * @module\n */\n\nimport type { AuthProvider } from \"./auth/contract.ts\";\nimport type { AuthorizationProvider } from \"./authorization/contract.ts\";\nimport {\n createWebhooksCore,\n type RetentionOptions,\n type RetentionRule,\n type WebhooksCore,\n} from \"./core.ts\";\nimport type { DispatcherOptions } from \"./dispatcher.ts\";\nimport { durationToMs, parseDurationList } from \"./duration.ts\";\nimport type { JsonValue, WebhookDuration } from \"./schema.ts\";\nimport type { WebhooksStorage } from \"./storage/contract.ts\";\n\ntype Driver = \"memory\" | \"file\" | \"redis\" | \"postgres\" | \"mongodb\" | \"sqlite\" | \"libsql\";\n\n/** The two storage roles: control plane (`STORAGE`) and delivery queue (`QUEUE`). */\ntype Role = \"STORAGE\" | \"QUEUE\";\n\nconst env = (key: string, fallback = \"\"): string => process.env[key] ?? fallback;\n\nconst isTruthy = (value: string): boolean => value === \"1\" || value.toLowerCase() === \"true\";\n\n/**\n * The driver env var for a role: `STORAGE_DRIVER` for the control plane,\n * `QUEUE_STORAGE_DRIVER` for the delivery queue.\n */\nconst driverEnv = (role: Role): string =>\n role === \"STORAGE\" ? \"STORAGE_DRIVER\" : \"QUEUE_STORAGE_DRIVER\";\n\n/**\n * Parse a duration env like `\"30d\"`, `\"34h\"`, `\"90m\"`, `\"10s\"` into a\n * {@link WebhookDuration}, warning and ignoring malformed values.\n */\nfunction parseDurationEnv(name: string): WebhookDuration | undefined {\n const raw = env(name);\n if (!raw) return undefined;\n try {\n durationToMs(raw as WebhookDuration); // validate eagerly\n return raw as WebhookDuration;\n } catch {\n process.stderr.write(\n `[xtandard/webhooks] Ignoring ${name}=\"${raw}\" — expected <number><unit> with unit ms|s|m|h|d (e.g. 34h, 30d).\\n`,\n );\n return undefined;\n }\n}\n\n/**\n * Retention from env: `MESSAGE_KEEP_LAST` / `MESSAGE_MAX_AGE` and\n * `AUDIT_KEEP_LAST` / `AUDIT_MAX_AGE`. Returns undefined when none are set.\n */\nfunction retentionFromEnv(): RetentionOptions | undefined {\n const rule = (keepVar: string, ageVar: string): RetentionRule | undefined => {\n const keepRaw = env(keepVar);\n const keepLast = keepRaw && /^\\d+$/.test(keepRaw) ? Number(keepRaw) : undefined;\n if (keepRaw && keepLast === undefined) {\n process.stderr.write(\n `[xtandard/webhooks] Ignoring ${keepVar}=\"${keepRaw}\" — expected a number.\\n`,\n );\n }\n const maxAge = parseDurationEnv(ageVar);\n if (keepLast === undefined && !maxAge) return undefined;\n return {\n ...(keepLast !== undefined ? { keepLast } : {}),\n ...(maxAge !== undefined ? { maxAge } : {}),\n };\n };\n const messages = rule(\"MESSAGE_KEEP_LAST\", \"MESSAGE_MAX_AGE\");\n const audit = rule(\"AUDIT_KEEP_LAST\", \"AUDIT_MAX_AGE\");\n if (!messages && !audit) return undefined;\n return {\n ...(messages ? { messages } : {}),\n ...(audit ? { audit } : {}),\n };\n}\n\n/** Dispatcher config from env: `RETRY_SCHEDULE` (comma list, e.g. `0s,5s,5m`). */\nfunction dispatcherOptionsFromEnv(): DispatcherOptions {\n const raw = env(\"RETRY_SCHEDULE\");\n if (!raw) return {};\n try {\n return { retrySchedule: parseDurationList(raw) };\n } catch {\n process.stderr.write(\n `[xtandard/webhooks] Ignoring RETRY_SCHEDULE=\"${raw}\" — expected a comma list of durations (e.g. \"0s,5s,5m,30m,2h,5h,10h\").\\n`,\n );\n return {};\n }\n}\n\n/** Whether the in-process dispatcher is enabled (`DISPATCHER=0|false` disables). */\nfunction dispatcherEnabled(): boolean {\n const raw = env(\"DISPATCHER\");\n if (!raw) return true;\n return !(raw === \"0\" || raw.toLowerCase() === \"false\");\n}\n\n/**\n * Build one storage role from env. `STORAGE` holds the control plane\n * (applications, event types, endpoints, messages, audit); `QUEUE` holds\n * deliveries + the due index and defaults to the same store (see\n * {@link buildQueueStorage}).\n */\nasync function buildStorage(role: Role): Promise<WebhooksStorage> {\n const driver = (env(driverEnv(role), \"file\") as Driver) || \"file\";\n const r = role.toLowerCase();\n switch (driver) {\n case \"redis\": {\n const { createRedisStorage } = await import(\"./storage/redis.ts\");\n return createRedisStorage({\n url: env(\"REDIS_URL\", \"redis://localhost:6379\"),\n prefix: env(`${role}_PREFIX`, `xtandard:webhooks:${r}`),\n });\n }\n case \"postgres\": {\n const { createPostgresStorage } = await import(\"./storage/postgres.ts\");\n return createPostgresStorage({\n connectionString:\n env(\"DATABASE_URL\") || env(\"POSTGRES_URL\", \"postgres://localhost:5432/postgres\"),\n table: env(`${role}_PG_TABLE`, `xtandard_webhooks_${r}`),\n });\n }\n case \"mongodb\": {\n const { createMongoStorage } = await import(\"./storage/mongodb.ts\");\n return createMongoStorage({\n url: env(\"MONGO_URL\", \"mongodb://localhost:27017\"),\n dbName: env(\"MONGO_DB\", \"xtandard_webhooks\"),\n collectionName: env(`${role}_MONGO_COLLECTION`, `webhooks_${r}`),\n });\n }\n case \"sqlite\": {\n // Requires running the CLI under Bun (`bunx xtandard-webhooks …`).\n const { createSqliteStorage } = await import(\"./storage/sqlite.ts\");\n return createSqliteStorage({\n path: env(`${role}_SQLITE_PATH`, `./.webhooks/${r}.sqlite`),\n });\n }\n case \"libsql\": {\n const { createLibsqlStorage } = await import(\"./storage/libsql.ts\");\n const authToken = env(\"LIBSQL_AUTH_TOKEN\");\n return createLibsqlStorage({\n url: env(`${role}_LIBSQL_URL`) || env(\"LIBSQL_URL\", `file:./.webhooks/${r}.db`),\n ...(authToken ? { authToken } : {}),\n });\n }\n case \"memory\": {\n const { createMemoryStorage } = await import(\"./storage/memory.ts\");\n return createMemoryStorage();\n }\n case \"file\":\n default: {\n const { createFileStorage } = await import(\"./storage/file.ts\");\n return createFileStorage({ dir: env(`${role}_FILE_DIR`, `./.webhooks/${r}`) });\n }\n }\n}\n\n/**\n * The queue store, or `undefined` to share the control-plane store. Only built\n * when `QUEUE_STORAGE_DRIVER` is explicitly set (split-plane deployments:\n * control data in Postgres, queue in Redis).\n */\nasync function buildQueueStorage(): Promise<WebhooksStorage | undefined> {\n if (!env(\"QUEUE_STORAGE_DRIVER\")) return undefined;\n return buildStorage(\"QUEUE\");\n}\n\n/**\n * A human-readable, log-safe description of where a storage role persists data\n * — for the `serve`/`dispatch` startup banner. File/SQLite paths are resolved\n * to absolute so \"where did my webhooks go?\" is obvious; connection-string\n * drivers print only the driver name (never the URL, which may carry\n * credentials).\n */\nasync function describeStorage(role: Role): Promise<string> {\n const { resolve } = await import(\"node:path\");\n const driver = (env(driverEnv(role), \"file\") as Driver) || \"file\";\n const r = role.toLowerCase();\n switch (driver) {\n case \"file\":\n return `file → ${resolve(env(`${role}_FILE_DIR`, `./.webhooks/${r}`))}`;\n case \"sqlite\":\n return `sqlite → ${resolve(env(`${role}_SQLITE_PATH`, `./.webhooks/${r}.sqlite`))}`;\n case \"memory\":\n return \"memory (ephemeral — not persisted)\";\n default:\n return driver;\n }\n}\n\n/** Build the auth + authorization providers from env (mirrors the standalone app). */\nasync function buildAuth(): Promise<{ auth: AuthProvider; authorization: AuthorizationProvider }> {\n const mode = env(\"AUTH_MODE\", \"none\");\n if (mode === \"basic\") {\n const [{ basicAuth }, { rolesAuthorization }] = await Promise.all([\n import(\"./auth/basic.ts\"),\n import(\"./authorization/roles.ts\"),\n ]);\n const passwordHash = env(\"AUTH_PASSWORD_HASH\");\n const password = env(\"AUTH_PASSWORD\");\n if (!passwordHash && !password) {\n process.stderr.write(\n \"[xtandard/webhooks] AUTH_MODE=basic but neither AUTH_PASSWORD_HASH nor AUTH_PASSWORD is set.\\n\",\n );\n }\n return {\n auth: basicAuth({\n users: [\n {\n username: env(\"AUTH_USERNAME\", \"admin\"),\n ...(passwordHash ? { passwordHash } : {}),\n ...(password ? { password } : {}),\n roles: [\"admin\"],\n },\n ],\n }),\n authorization: rolesAuthorization({}),\n };\n }\n const [{ noAuth }, { noAuthorization }] = await Promise.all([\n import(\"./auth/none.ts\"),\n import(\"./authorization/none.ts\"),\n ]);\n return { auth: noAuth(), authorization: noAuthorization() };\n}\n\ntype FetchHandler = (request: Request) => Response | Promise<Response>;\n\n/**\n * Serve a web-standard fetch handler under whatever runtime the CLI runs on:\n * `Bun.serve` under `bunx`, a `node:http` bridge under `npx`/Node. Resolves only\n * once the server is listening; the process then stays alive on the open socket.\n */\nasync function startServer(port: number, fetch: FetchHandler): Promise<void> {\n const bun = (globalThis as { Bun?: { serve: (options: unknown) => unknown } }).Bun;\n if (bun) {\n bun.serve({ port, fetch });\n return;\n }\n const { createServer } = await import(\"node:http\");\n const server = createServer((req, res) => {\n void (async () => {\n try {\n const chunks: Buffer[] = [];\n for await (const c of req) chunks.push(c as Buffer);\n const method = req.method ?? \"GET\";\n const headers = new Headers();\n for (const [k, v] of Object.entries(req.headers)) {\n if (v === undefined) continue;\n headers.set(k, Array.isArray(v) ? v.join(\", \") : v);\n }\n const host = req.headers.host ?? `localhost:${port}`;\n const url = `http://${host}${req.url ?? \"/\"}`;\n const hasBody = method !== \"GET\" && method !== \"HEAD\" && chunks.length > 0;\n const request = new Request(url, {\n method,\n headers,\n body: hasBody ? Buffer.concat(chunks) : undefined,\n });\n const response = await fetch(request);\n res.statusCode = response.status;\n response.headers.forEach((value, key) => res.setHeader(key, value));\n res.end(Buffer.from(await response.arrayBuffer()));\n } catch (err) {\n res.statusCode = 500;\n res.end(`Internal error: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n });\n await new Promise<void>((resolve) => server.listen(port, resolve));\n}\n\n/** Verification outcome shown by `listen` for each captured request. */\nexport type InboundVerification =\n | { state: \"unchecked\" } // no --secret given\n | { state: \"ok\" }\n | { state: \"failed\"; reason: string };\n\n/**\n * Format one captured inbound webhook for the terminal — the `listen` command's\n * pretty-printer. Pure and side-effect-free so it can be unit-tested.\n */\nexport function formatInboundWebhook(input: {\n index: number;\n method: string;\n path: string;\n headers: Record<string, string>;\n body: string;\n verification: InboundVerification;\n at: string;\n}): string {\n const wh = (name: string) => input.headers[name] ?? input.headers[name.toLowerCase()] ?? \"—\";\n const badge =\n input.verification.state === \"ok\"\n ? \"signature: VERIFIED\"\n : input.verification.state === \"failed\"\n ? `signature: FAILED (${input.verification.reason})`\n : \"signature: not checked (pass --secret to verify)\";\n let prettyBody = input.body;\n try {\n prettyBody = JSON.stringify(JSON.parse(input.body), null, 2);\n } catch {\n // leave non-JSON bodies as-is\n }\n return [\n `\\n── #${input.index} ${input.method} ${input.path} ${input.at} ──`,\n `webhook-id: ${wh(\"webhook-id\")}`,\n `webhook-timestamp: ${wh(\"webhook-timestamp\")}`,\n `webhook-signature: ${wh(\"webhook-signature\")}`,\n badge,\n \"body:\",\n prettyBody,\n \"\",\n ].join(\"\\n\");\n}\n\n/** Minimal flag/value argv parser: `--key value` and `--flag`. */\nfunction parseArgs(argv: string[]): { _: string[]; flags: Record<string, string | boolean> } {\n const _: string[] = [];\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!;\n if (a.startsWith(\"--\")) {\n const key = a.slice(2);\n const next = argv[i + 1];\n if (next === undefined || next.startsWith(\"--\")) flags[key] = true;\n else {\n flags[key] = next;\n i++;\n }\n } else _.push(a);\n }\n return { _, flags };\n}\n\n/** A core over the env-configured storage (dispatcher NOT started). */\nasync function makeCore(): Promise<WebhooksCore> {\n const [storage, queueStorage] = await Promise.all([buildStorage(\"STORAGE\"), buildQueueStorage()]);\n const retention = retentionFromEnv();\n return createWebhooksCore({\n storage,\n ...(queueStorage ? { queueStorage } : {}),\n ...(retention ? { retention } : {}),\n dispatcher: dispatcherOptionsFromEnv(),\n });\n}\n\n/** Read this package's version from the nearest package.json (dist or src). */\nasync function pkgVersion(): Promise<string> {\n try {\n const { readFile } = await import(\"node:fs/promises\");\n const { fileURLToPath } = await import(\"node:url\");\n const pkg = JSON.parse(\n await readFile(fileURLToPath(new URL(\"../package.json\", import.meta.url)), \"utf8\"),\n ) as { version?: string };\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction helpText(version: string): string {\n return `xtandard-webhooks v${version} — self-hosted, Standard Webhooks-compliant outbound webhook control plane\n\nUsage:\n xtandard-webhooks <command> [options] # the binary, after: npm i -g @xtandard/webhooks\n npx @xtandard/webhooks <command> # without installing (or: bunx @xtandard/webhooks …)\n\nCommands:\n serve [--port <n>] Run the admin panel + API + delivery dispatcher (no Docker).\n dispatch [--port <n>] Run the delivery dispatcher ONLY (split-worker mode).\n With --port/$PORT: also serves GET /healthcheck.\n init [--app <key>] Create an application + an example event type.\n list-apps List applications.\n list-endpoints --app <key> List an application's endpoints.\n publish --app <key> --type <event> --data '<json>' [--idempotency-key <k>]\n Publish a message (ingest from shell/CI).\n retry --app <key> --delivery <id>\n Re-queue a dead-lettered delivery.\n verify --secret <whsec_…> --payload-file <path> --headers-file <path>\n Verify a captured webhook (receiver-side debugging):\n reads the raw payload + a JSON headers object,\n prints the envelope, exits 1 on failure.\n sign --secret <whsec_…> --data '<json>' [--id <msg_…>] [--timestamp <unix>] [--url <target>]\n Build a signed Standard Webhooks request (the\n signature playground): prints the headers + body,\n and a ready-to-run curl when --url is given.\n listen [--port <n>] [--secret <whsec_…>] [--status <code>]\n Run a local inspecting receiver (the webhook.site\n you host yourself): pretty-prints every incoming\n webhook; with --secret, verifies the signature.\n\nGlobal options:\n -h, --help Show this help.\n -v, --version Print the version.\n\n\\`serve\\` / \\`dispatch\\` options:\n --port <n> Port to listen on (default: $PORT or 3000).\n\nEnvironment variables\n Storage (control plane STORAGE_; delivery queue QUEUE_, defaults to the same store):\n STORAGE_DRIVER, QUEUE_STORAGE_DRIVER\n memory | file | redis | postgres | mongodb | sqlite | libsql\n (CLI default: file · Docker default: memory)\n REDIS_URL redis://localhost:6379 (driver: redis)\n STORAGE_PREFIX, QUEUE_PREFIX key namespace (driver: redis)\n DATABASE_URL | POSTGRES_URL postgres://… (driver: postgres)\n STORAGE_PG_TABLE, QUEUE_PG_TABLE (driver: postgres)\n MONGO_URL, MONGO_DB, STORAGE_MONGO_COLLECTION, QUEUE_MONGO_COLLECTION (driver: mongodb)\n STORAGE_FILE_DIR, QUEUE_FILE_DIR default ./.webhooks/{storage,queue} (driver: file)\n STORAGE_SQLITE_PATH, QUEUE_SQLITE_PATH (driver: sqlite, Bun only)\n LIBSQL_URL | STORAGE_LIBSQL_URL, QUEUE_LIBSQL_URL, LIBSQL_AUTH_TOKEN (driver: libsql)\n\n Server (\\`serve\\` / standalone):\n PORT 3000 Port to listen on (or --port).\n BASE_PATH \"\" URL prefix, e.g. \"/webhooks\".\n TITLE Navbar wordmark.\n LOGO_URL Logo image URL.\n READONLY 1|true Block all mutating operations.\n AUTH_MODE none|basic Authentication mode (default none).\n AUTH_USERNAME admin Username for basic auth.\n AUTH_PASSWORD_HASH scrypt hash (preferred; see docs/AUTH.md).\n AUTH_PASSWORD Plaintext password (dev only).\n PORTAL_SECRET Enables portal-token access (whpt_… bearer tokens).\n\n Delivery (\\`serve\\` / \\`dispatch\\` / standalone):\n DISPATCHER 1 0|false disables the in-process dispatcher\n (host publishes only; a split worker delivers).\n RETRY_SCHEDULE Comma list of retry delays, e.g. \"0s,5s,5m,30m,2h,5h,10h\".\n\n Retention (pruned opportunistically after publishes; kept if EITHER rule keeps it;\n messages with a non-terminal delivery are always kept):\n MESSAGE_KEEP_LAST Keep at most the N most recent messages per app.\n MESSAGE_MAX_AGE 30d Keep messages newer than this (ms|s|m|h|d, e.g. 34h).\n AUDIT_KEEP_LAST Keep at most the N most recent audit entries.\n AUDIT_MAX_AGE 90d Keep audit entries newer than this.\n\nExamples:\n # Quick local panel (file storage, no auth):\n npx @xtandard/webhooks serve --port 3004\n\n # Production-ish: Redis storage + basic auth:\n PORT=4000 AUTH_MODE=basic AUTH_USERNAME=admin AUTH_PASSWORD=secret \\\\\n STORAGE_DRIVER=redis REDIS_URL=redis://localhost:6379 \\\\\n npx @xtandard/webhooks serve\n\n # Split planes: Postgres control plane + Redis queue:\n STORAGE_DRIVER=postgres DATABASE_URL=postgres://localhost:5432/webhooks \\\\\n QUEUE_STORAGE_DRIVER=redis REDIS_URL=redis://localhost:6379 \\\\\n npx @xtandard/webhooks serve\n\n # Split worker: the web process publishes only (DISPATCHER=0); this delivers:\n STORAGE_DRIVER=redis REDIS_URL=redis://localhost:6379 \\\\\n npx @xtandard/webhooks dispatch\n\n # Publish from CI:\n xtandard-webhooks publish --app acme --type invoice.paid --data '{\"invoiceId\":\"inv_1\"}'\n\n # Debug a captured webhook on the receiving side:\n xtandard-webhooks verify --secret whsec_… --payload-file body.json --headers-file headers.json\n\n # Inspect webhooks locally (point an endpoint at http://localhost:4000):\n xtandard-webhooks listen --port 4000 --secret whsec_…\n\n # Build + fire a signed request by hand (the signature playground):\n xtandard-webhooks sign --secret whsec_… --data '{\"hi\":1}' --url http://localhost:4000\n\nReceivers verify with @xtandard/webhooks/receiver — or any Standard Webhooks\nlibrary. Docs: https://github.com/xantiagoma/xtandard-webhooks\n`;\n}\n\n/** Entry point. Returns the process exit code. */\nexport async function run(argv: string[]): Promise<number> {\n const { _, flags } = parseArgs(argv);\n const command = _[0];\n\n // `--version` (bare) / `-v` / `version` → print version. Only the boolean form\n // is treated as \"print version\" so a future `--version <value>` flag can coexist.\n if (flags.version === true || argv.includes(\"-v\") || command === \"version\") {\n process.stdout.write(`${await pkgVersion()}\\n`);\n return 0;\n }\n\n const wantsHelp = Boolean(flags.help) || argv.includes(\"-h\") || command === \"help\";\n if (!command || wantsHelp) {\n process.stdout.write(helpText(await pkgVersion()));\n // Explicit help request → success; bare invocation with no command → usage error.\n return wantsHelp ? 0 : 1;\n }\n\n try {\n switch (command) {\n case \"init\": {\n const core = await makeCore();\n const app = typeof flags.app === \"string\" ? flags.app : env(\"APP\", \"default\");\n const existing = await core.getApplication(app);\n if (!existing) await core.createApplication({ key: app });\n await core.upsertEventType({\n name: \"example.ping\",\n description: \"Example event type created by `xtandard-webhooks init`.\",\n });\n process.stdout.write(\n `Initialized application \"${app}\" with event type \"example.ping\".\\n` +\n `Add endpoints in the panel (xtandard-webhooks serve), then:\\n` +\n ` xtandard-webhooks publish --app ${app} --type example.ping --data '{\"hello\":\"world\"}'\\n`,\n );\n return 0;\n }\n case \"list-apps\": {\n const core = await makeCore();\n const apps = await core.listApplications();\n if (apps.length === 0) process.stdout.write(\"No applications.\\n\");\n for (const app of apps) {\n process.stdout.write(`${app.key}${app.name ? ` ${app.name}` : \"\"}\\n`);\n }\n return 0;\n }\n case \"list-endpoints\": {\n if (typeof flags.app !== \"string\") {\n process.stderr.write(\"Usage: xtandard-webhooks list-endpoints --app <key>\\n\");\n return 1;\n }\n const core = await makeCore();\n const endpoints = await core.listEndpoints(flags.app);\n if (endpoints.length === 0) process.stdout.write(\"No endpoints.\\n\");\n for (const endpoint of endpoints) {\n const subscriptions = endpoint.eventTypes?.length\n ? endpoint.eventTypes.join(\",\")\n : \"all events\";\n process.stdout.write(\n `${endpoint.disabled ? \"○\" : \"●\"} ${endpoint.id} ${endpoint.url} [${subscriptions}]\\n`,\n );\n }\n return 0;\n }\n case \"publish\": {\n if (typeof flags.app !== \"string\" || typeof flags.type !== \"string\") {\n process.stderr.write(\n \"Usage: xtandard-webhooks publish --app <key> --type <event> --data '<json>' [--idempotency-key <k>]\\n\",\n );\n return 1;\n }\n let payload: JsonValue = {};\n if (typeof flags.data === \"string\") {\n try {\n payload = JSON.parse(flags.data) as JsonValue;\n } catch {\n process.stderr.write(\"Invalid --data JSON.\\n\");\n return 1;\n }\n }\n const core = await makeCore();\n const idempotencyKey =\n typeof flags[\"idempotency-key\"] === \"string\" ? flags[\"idempotency-key\"] : undefined;\n const result = await core.publish(flags.app, {\n eventType: flags.type,\n payload,\n ...(idempotencyKey !== undefined ? { idempotencyKey } : {}),\n });\n process.stdout.write(\n `${result.deduplicated ? \"Deduplicated\" : \"Published\"} ${result.message.id} → ` +\n `${result.deliveries.length} deliver${result.deliveries.length === 1 ? \"y\" : \"ies\"} queued.\\n`,\n );\n return 0;\n }\n case \"retry\": {\n if (typeof flags.app !== \"string\" || typeof flags.delivery !== \"string\") {\n process.stderr.write(\"Usage: xtandard-webhooks retry --app <key> --delivery <id>\\n\");\n return 1;\n }\n const core = await makeCore();\n const delivery = await core.retryDelivery(flags.app, flags.delivery);\n process.stdout.write(`Re-queued delivery ${delivery.id} (status: ${delivery.status}).\\n`);\n return 0;\n }\n case \"verify\": {\n if (\n typeof flags.secret !== \"string\" ||\n typeof flags[\"payload-file\"] !== \"string\" ||\n typeof flags[\"headers-file\"] !== \"string\"\n ) {\n process.stderr.write(\n \"Usage: xtandard-webhooks verify --secret <whsec_…> --payload-file <path> --headers-file <path>\\n\",\n );\n return 1;\n }\n const { readFile } = await import(\"node:fs/promises\");\n const payload = await readFile(flags[\"payload-file\"], \"utf8\");\n let headers: Record<string, string>;\n try {\n headers = JSON.parse(await readFile(flags[\"headers-file\"], \"utf8\")) as Record<\n string,\n string\n >;\n } catch {\n process.stderr.write(\"Invalid --headers-file: expected a JSON object of headers.\\n\");\n return 1;\n }\n const { verify, WebhookVerificationError } = await import(\"./signing.ts\");\n try {\n const envelope = await verify({ payload, headers, secret: flags.secret });\n process.stdout.write(`Signature OK.\\n${JSON.stringify(envelope, null, 2)}\\n`);\n return 0;\n } catch (err) {\n if (err instanceof WebhookVerificationError) {\n process.stderr.write(`Verification FAILED: ${err.message}\\n`);\n return 1;\n }\n throw err;\n }\n }\n case \"sign\": {\n // The signature playground: build a fully signed Standard Webhooks\n // request from a secret + payload, so you can curl it at a receiver or\n // paste it into https://www.standardwebhooks.com/simulate.\n if (typeof flags.secret !== \"string\" || typeof flags.data !== \"string\") {\n process.stderr.write(\n \"Usage: xtandard-webhooks sign --secret <whsec_…> --data '<json>' [--id <msg_…>] [--timestamp <unix-seconds>] [--url <post-target>]\\n\",\n );\n return 1;\n }\n // Validate the payload is JSON, but sign the exact bytes given.\n try {\n JSON.parse(flags.data);\n } catch {\n process.stderr.write(\"Invalid --data: expected a JSON string.\\n\");\n return 1;\n }\n const { newId } = await import(\"./id.ts\");\n const { sign } = await import(\"./signing.ts\");\n const id = typeof flags.id === \"string\" ? flags.id : newId(\"msg\");\n const timestamp =\n typeof flags.timestamp === \"string\"\n ? Number(flags.timestamp)\n : Math.floor(Date.now() / 1000);\n const signature = await sign(flags.secret, id, timestamp, flags.data);\n const headerLines = [\n `webhook-id: ${id}`,\n `webhook-timestamp: ${timestamp}`,\n `webhook-signature: ${signature}`,\n ];\n process.stdout.write(`${headerLines.join(\"\\n\")}\\n\\n${flags.data}\\n`);\n if (typeof flags.url === \"string\") {\n const curl = [\n `curl -X POST ${flags.url} \\\\`,\n ` -H 'content-type: application/json' \\\\`,\n ` -H 'webhook-id: ${id}' \\\\`,\n ` -H 'webhook-timestamp: ${timestamp}' \\\\`,\n ` -H 'webhook-signature: ${signature}' \\\\`,\n ` -d '${flags.data.replace(/'/g, \"'\\\\''\")}'`,\n ].join(\"\\n\");\n process.stdout.write(`\\n${curl}\\n`);\n }\n return 0;\n }\n case \"listen\": {\n // A local inspecting receiver — the webhook.site you run yourself.\n // Prints every incoming request; with --secret it verifies the\n // signature and shows a VERIFIED/FAILED badge.\n const port = Number((flags.port as string) || env(\"PORT\", \"4000\"));\n const secret = typeof flags.secret === \"string\" ? flags.secret : undefined;\n const status = typeof flags.status === \"string\" ? Number(flags.status) : 200;\n const { verify, WebhookVerificationError } = await import(\"./signing.ts\");\n let count = 0;\n\n await startServer(port, async (request) => {\n const url = new URL(request.url);\n if (url.pathname === \"/healthcheck\") {\n return new Response(JSON.stringify({ status: \"ok\" }), {\n headers: { \"content-type\": \"application/json\" },\n });\n }\n const body = await request.text();\n const headers: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headers[key] = value;\n });\n let verification: InboundVerification = { state: \"unchecked\" };\n if (secret) {\n try {\n await verify({ payload: body, headers, secret });\n verification = { state: \"ok\" };\n } catch (err) {\n verification = {\n state: \"failed\",\n reason: err instanceof WebhookVerificationError ? err.message : String(err),\n };\n }\n }\n count += 1;\n process.stdout.write(\n formatInboundWebhook({\n index: count,\n method: request.method,\n path: url.pathname,\n headers,\n body,\n verification,\n at: new Date().toISOString(),\n }),\n );\n // A signature failure answers 401 so senders exercise their retry path;\n // otherwise echo the configured status (default 200).\n if (verification.state === \"failed\")\n return new Response(\"invalid signature\", { status: 401 });\n return new Response(\"ok\", { status });\n });\n process.stdout.write(\n `[xtandard/webhooks] listening for webhooks on http://localhost:${port}` +\n `${secret ? \" (verifying signatures)\" : \" (not verifying — pass --secret)\"}\\n`,\n );\n return await new Promise<number>(() => {});\n }\n case \"dispatch\": {\n const core = await makeCore();\n const { createDispatcher } = await import(\"./dispatcher.ts\");\n const dispatcher = createDispatcher(core); // merges core.options.dispatcher (RETRY_SCHEDULE)\n dispatcher.start();\n\n const [storageDesc, queueDesc] = await Promise.all([\n describeStorage(\"STORAGE\"),\n env(\"QUEUE_STORAGE_DRIVER\") ? describeStorage(\"QUEUE\") : Promise.resolve(\"(same store)\"),\n ]);\n process.stdout.write(`[xtandard/webhooks] storage: ${storageDesc}\\n`);\n process.stdout.write(`[xtandard/webhooks] queue: ${queueDesc}\\n`);\n\n // Optional minimal healthcheck server — only when a port was asked for.\n const portRaw = typeof flags.port === \"string\" ? flags.port : env(\"PORT\");\n if (portRaw) {\n const port = Number(portRaw);\n await startServer(port, (request) => {\n const url = new URL(request.url);\n if (url.pathname === \"/healthcheck\") {\n return new Response(JSON.stringify({ status: \"ok\", dispatcher: \"running\" }), {\n headers: { \"content-type\": \"application/json\" },\n });\n }\n return new Response(\"Not Found\", { status: 404 });\n });\n process.stdout.write(\n `[xtandard/webhooks] dispatcher running; healthcheck on http://localhost:${port}/healthcheck\\n`,\n );\n } else {\n // Dispatcher timers are unref()ed by design; hold the event loop open.\n setInterval(() => {}, 2 ** 30);\n process.stdout.write(\"[xtandard/webhooks] dispatcher running.\\n\");\n }\n // The worker owns the process now; never resolve so the bin doesn't exit.\n return await new Promise<number>(() => {});\n }\n case \"serve\": {\n const port = Number((flags.port as string) || env(\"PORT\", \"3000\"));\n const basePath = env(\"BASE_PATH\", \"\");\n const title = env(\"TITLE\", \"@xtandard/webhooks\");\n const logoUrl = env(\"LOGO_URL\");\n const readonly = isTruthy(env(\"READONLY\"));\n const authMode = env(\"AUTH_MODE\", \"none\");\n const portalSecret = env(\"PORTAL_SECRET\");\n\n const { createFetchHandler } = await import(\"./server/create-fetch-handler.ts\");\n const [storage, queueStorage] = await Promise.all([\n buildStorage(\"STORAGE\"),\n buildQueueStorage(),\n ]);\n const { auth, authorization } = await buildAuth();\n\n if (authMode === \"none\") {\n process.stderr.write(\n \"[xtandard/webhooks] AUTH_MODE=none — do NOT expose this publicly without authentication.\\n\",\n );\n }\n\n const retention = retentionFromEnv();\n const panel = createFetchHandler({\n storage,\n ...(queueStorage ? { queueStorage } : {}),\n basePath,\n title,\n ...(logoUrl ? { logoUrl } : {}),\n readonly,\n auth,\n authorization,\n ...(portalSecret ? { portal: { secret: portalSecret } } : {}),\n ...(retention ? { retention } : {}),\n dispatcher: dispatcherEnabled() ? dispatcherOptionsFromEnv() : false,\n });\n\n const normalizedBase =\n basePath && basePath !== \"/\"\n ? basePath.startsWith(\"/\")\n ? basePath\n : `/${basePath}`\n : \"\";\n\n const handler: FetchHandler = (request) => {\n const url = new URL(request.url);\n if (url.pathname === \"/healthcheck\" || url.pathname === `${normalizedBase}/healthcheck`) {\n return new Response(JSON.stringify({ status: \"ok\", title }), {\n headers: { \"content-type\": \"application/json\" },\n });\n }\n return panel.fetch(request);\n };\n\n const [storageDesc, queueDesc] = await Promise.all([\n describeStorage(\"STORAGE\"),\n env(\"QUEUE_STORAGE_DRIVER\") ? describeStorage(\"QUEUE\") : Promise.resolve(\"(same store)\"),\n ]);\n process.stdout.write(`[xtandard/webhooks] storage: ${storageDesc}\\n`);\n process.stdout.write(`[xtandard/webhooks] queue: ${queueDesc}\\n`);\n process.stdout.write(\n `[xtandard/webhooks] dispatcher: ${panel.dispatcher ? \"running\" : \"disabled (DISPATCHER=0)\"}\\n`,\n );\n\n await startServer(port, handler);\n process.stdout.write(\n `[xtandard/webhooks] listening on http://localhost:${port}${normalizedBase || \"/\"}\\n`,\n );\n // The server owns the process now; never resolve so the bin doesn't exit.\n return await new Promise<number>(() => {});\n }\n default:\n process.stderr.write(`Unknown command: ${command}\\n\\n${helpText(await pkgVersion())}`);\n return 1;\n }\n } catch (err) {\n process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\\n`);\n return 1;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { o as Delivery } from "./schema-mo__wv4P.cjs";
2
+
3
+ //#region src/storage/contract.d.ts
4
+ /** The minimal key/value contract every storage backend must satisfy. */
5
+ interface WebhooksStorage {
6
+ /** Read a value, or `null` if absent. */
7
+ getItem<T>(key: string): Promise<T | null>;
8
+ /** Write a value (overwriting any existing). */
9
+ setItem<T>(key: string, value: T): Promise<void>;
10
+ /** Delete a key (no-op if absent). */
11
+ removeItem(key: string): Promise<void>;
12
+ /** List all keys beginning with `prefix`. */
13
+ getKeys(prefix: string): Promise<string[]>;
14
+ }
15
+ /** A storage change event delivered to {@link WatchableWebhooksStorage.watch} callbacks. */
16
+ interface StorageChangeEvent {
17
+ type: "update" | "remove";
18
+ key: string;
19
+ }
20
+ /** Storage that can push change notifications (e.g. Redis pub/sub, fs.watch). */
21
+ interface WatchableWebhooksStorage extends WebhooksStorage {
22
+ /**
23
+ * Subscribe to changes under `prefix`. Resolves to an unsubscribe function.
24
+ */
25
+ watch(prefix: string, callback: (event: StorageChangeEvent) => void): Promise<() => void>;
26
+ }
27
+ /** Storage that supports atomic multi-key transactions. */
28
+ interface TransactionalWebhooksStorage extends WebhooksStorage {
29
+ transaction<T>(callback: (tx: WebhooksStorage) => Promise<T>): Promise<T>;
30
+ }
31
+ /** Storage that supports optimistic concurrency via compare-and-swap. */
32
+ interface CompareAndSwapWebhooksStorage extends WebhooksStorage {
33
+ compareAndSwap<T>(input: {
34
+ key: string;
35
+ expected: T | null;
36
+ next: T;
37
+ }): Promise<boolean>;
38
+ }
39
+ /**
40
+ * Storage that can natively claim due deliveries (sorted-set / `SKIP LOCKED`
41
+ * class backends). When present, the dispatcher delegates claiming entirely to
42
+ * the adapter instead of scanning the generic due index.
43
+ *
44
+ * The claim contract: atomically transition each returned delivery to
45
+ * `status: "delivering"` with `leaseUntil = now + leaseMs`, reposition its due
46
+ * entry at the lease expiry (so a crashed claimer's work re-surfaces), and
47
+ * never return the same delivery to two concurrent claimers.
48
+ */
49
+ interface DeliveryQueueStorage extends WebhooksStorage {
50
+ claimDue(input: {
51
+ now: string;
52
+ limit: number;
53
+ leaseMs: number;
54
+ }): Promise<Delivery[]>;
55
+ }
56
+ /** Runtime feature-detection: does this storage implement `watch`? */
57
+ declare function isWatchable(storage: WebhooksStorage): storage is WatchableWebhooksStorage;
58
+ /** Runtime feature-detection: does this storage implement `transaction`? */
59
+ declare function isTransactional(storage: WebhooksStorage): storage is TransactionalWebhooksStorage;
60
+ /** Runtime feature-detection: does this storage implement `compareAndSwap`? */
61
+ declare function isCompareAndSwap(storage: WebhooksStorage): storage is CompareAndSwapWebhooksStorage;
62
+ /** Runtime feature-detection: does this storage implement `claimDue`? */
63
+ declare function hasDeliveryQueue(storage: WebhooksStorage): storage is DeliveryQueueStorage;
64
+ /**
65
+ * Helper for adapters whose subpath requires an optional peer dependency. Throws
66
+ * a clear, actionable error when the peer is missing.
67
+ */
68
+ declare function requirePeer(name: string, subpath: string): never;
69
+ //#endregion
70
+ export { WatchableWebhooksStorage as a, isCompareAndSwap as c, requirePeer as d, TransactionalWebhooksStorage as i, isTransactional as l, DeliveryQueueStorage as n, WebhooksStorage as o, StorageChangeEvent as r, hasDeliveryQueue as s, CompareAndSwapWebhooksStorage as t, isWatchable as u };
71
+ //# sourceMappingURL=contract-8h-Azxa5.d.cts.map
@@ -0,0 +1,22 @@
1
+ //#region src/authorization/contract.ts
2
+ /** Actions that mutate state — blocked in readonly mode. */
3
+ const MUTATING_ACTIONS = new Set([
4
+ "application:create",
5
+ "application:update",
6
+ "application:delete",
7
+ "event-type:create",
8
+ "event-type:update",
9
+ "event-type:delete",
10
+ "endpoint:create",
11
+ "endpoint:update",
12
+ "endpoint:delete",
13
+ "endpoint:rotate-secret",
14
+ "message:publish",
15
+ "delivery:retry"
16
+ ]);
17
+ /** True if the action mutates state. */
18
+ const isMutatingAction = (action) => MUTATING_ACTIONS.has(action);
19
+ //#endregion
20
+ export { isMutatingAction as n, MUTATING_ACTIONS as t };
21
+
22
+ //# sourceMappingURL=contract-9XpcwcCn.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-9XpcwcCn.mjs","names":[],"sources":["../src/authorization/contract.ts"],"sourcesContent":["/**\n * Authorization contract. Answers: \"Can this principal perform this action on\n * this resource?\"\n *\n * Every mutating admin API route consults the {@link AuthorizationProvider}.\n * Ships `none`/`roles`/`delegated` implementations.\n *\n * @module\n */\n\nimport type { Principal } from \"../auth/contract.ts\";\n\n/** The full set of authorizable actions. */\nexport type WebhooksAction =\n | \"application:read\"\n | \"application:create\"\n | \"application:update\"\n | \"application:delete\"\n | \"event-type:read\"\n | \"event-type:create\"\n | \"event-type:update\"\n | \"event-type:delete\"\n | \"endpoint:read\"\n | \"endpoint:create\"\n | \"endpoint:update\"\n | \"endpoint:delete\"\n | \"endpoint:rotate-secret\"\n | \"endpoint:read-secret\"\n | \"message:read\"\n | \"message:publish\"\n | \"delivery:read\"\n | \"delivery:retry\"\n | \"audit:read\";\n\n/** The resource an action targets. */\nexport type WebhooksResource =\n | { type: \"application\"; applicationKey: string }\n | { type: \"event-type\"; name: string }\n | { type: \"endpoint\"; applicationKey: string; endpointId: string }\n | { type: \"message\"; applicationKey: string; messageId?: string }\n | { type: \"delivery\"; applicationKey: string; deliveryId?: string }\n | { type: \"audit\"; applicationKey?: string };\n\n/** Input passed to {@link AuthorizationProvider.authorize}. */\nexport interface AuthorizeInput {\n principal: Principal | null;\n action: WebhooksAction;\n resource: WebhooksResource;\n request: Request;\n}\n\n/** Decides whether an action is permitted. */\nexport interface AuthorizationProvider {\n authorize(input: AuthorizeInput): Promise<boolean>;\n}\n\n/** Actions that mutate state — blocked in readonly mode. */\nexport const MUTATING_ACTIONS: ReadonlySet<WebhooksAction> = new Set<WebhooksAction>([\n \"application:create\",\n \"application:update\",\n \"application:delete\",\n \"event-type:create\",\n \"event-type:update\",\n \"event-type:delete\",\n \"endpoint:create\",\n \"endpoint:update\",\n \"endpoint:delete\",\n \"endpoint:rotate-secret\",\n \"message:publish\",\n \"delivery:retry\",\n]);\n\n/** True if the action mutates state. */\nexport const isMutatingAction = (action: WebhooksAction): boolean => MUTATING_ACTIONS.has(action);\n"],"mappings":";;AAyDA,MAAa,mBAAgD,IAAI,IAAoB;CACnF;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAa,oBAAoB,WAAoC,iBAAiB,IAAI,MAAM"}
@@ -0,0 +1,33 @@
1
+ //#region src/authorization/contract.ts
2
+ /** Actions that mutate state — blocked in readonly mode. */
3
+ const MUTATING_ACTIONS = new Set([
4
+ "application:create",
5
+ "application:update",
6
+ "application:delete",
7
+ "event-type:create",
8
+ "event-type:update",
9
+ "event-type:delete",
10
+ "endpoint:create",
11
+ "endpoint:update",
12
+ "endpoint:delete",
13
+ "endpoint:rotate-secret",
14
+ "message:publish",
15
+ "delivery:retry"
16
+ ]);
17
+ /** True if the action mutates state. */
18
+ const isMutatingAction = (action) => MUTATING_ACTIONS.has(action);
19
+ //#endregion
20
+ Object.defineProperty(exports, "MUTATING_ACTIONS", {
21
+ enumerable: true,
22
+ get: function() {
23
+ return MUTATING_ACTIONS;
24
+ }
25
+ });
26
+ Object.defineProperty(exports, "isMutatingAction", {
27
+ enumerable: true,
28
+ get: function() {
29
+ return isMutatingAction;
30
+ }
31
+ });
32
+
33
+ //# sourceMappingURL=contract-B2d5dNU3.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-B2d5dNU3.cjs","names":[],"sources":["../src/authorization/contract.ts"],"sourcesContent":["/**\n * Authorization contract. Answers: \"Can this principal perform this action on\n * this resource?\"\n *\n * Every mutating admin API route consults the {@link AuthorizationProvider}.\n * Ships `none`/`roles`/`delegated` implementations.\n *\n * @module\n */\n\nimport type { Principal } from \"../auth/contract.ts\";\n\n/** The full set of authorizable actions. */\nexport type WebhooksAction =\n | \"application:read\"\n | \"application:create\"\n | \"application:update\"\n | \"application:delete\"\n | \"event-type:read\"\n | \"event-type:create\"\n | \"event-type:update\"\n | \"event-type:delete\"\n | \"endpoint:read\"\n | \"endpoint:create\"\n | \"endpoint:update\"\n | \"endpoint:delete\"\n | \"endpoint:rotate-secret\"\n | \"endpoint:read-secret\"\n | \"message:read\"\n | \"message:publish\"\n | \"delivery:read\"\n | \"delivery:retry\"\n | \"audit:read\";\n\n/** The resource an action targets. */\nexport type WebhooksResource =\n | { type: \"application\"; applicationKey: string }\n | { type: \"event-type\"; name: string }\n | { type: \"endpoint\"; applicationKey: string; endpointId: string }\n | { type: \"message\"; applicationKey: string; messageId?: string }\n | { type: \"delivery\"; applicationKey: string; deliveryId?: string }\n | { type: \"audit\"; applicationKey?: string };\n\n/** Input passed to {@link AuthorizationProvider.authorize}. */\nexport interface AuthorizeInput {\n principal: Principal | null;\n action: WebhooksAction;\n resource: WebhooksResource;\n request: Request;\n}\n\n/** Decides whether an action is permitted. */\nexport interface AuthorizationProvider {\n authorize(input: AuthorizeInput): Promise<boolean>;\n}\n\n/** Actions that mutate state — blocked in readonly mode. */\nexport const MUTATING_ACTIONS: ReadonlySet<WebhooksAction> = new Set<WebhooksAction>([\n \"application:create\",\n \"application:update\",\n \"application:delete\",\n \"event-type:create\",\n \"event-type:update\",\n \"event-type:delete\",\n \"endpoint:create\",\n \"endpoint:update\",\n \"endpoint:delete\",\n \"endpoint:rotate-secret\",\n \"message:publish\",\n \"delivery:retry\",\n]);\n\n/** True if the action mutates state. */\nexport const isMutatingAction = (action: WebhooksAction): boolean => MUTATING_ACTIONS.has(action);\n"],"mappings":";;AAyDA,MAAa,mBAAgD,IAAI,IAAoB;CACnF;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAa,oBAAoB,WAAoC,iBAAiB,IAAI,MAAM"}
@@ -0,0 +1,28 @@
1
+ //#region src/storage/contract.ts
2
+ /** Runtime feature-detection: does this storage implement `watch`? */
3
+ function isWatchable(storage) {
4
+ return typeof storage.watch === "function";
5
+ }
6
+ /** Runtime feature-detection: does this storage implement `transaction`? */
7
+ function isTransactional(storage) {
8
+ return typeof storage.transaction === "function";
9
+ }
10
+ /** Runtime feature-detection: does this storage implement `compareAndSwap`? */
11
+ function isCompareAndSwap(storage) {
12
+ return typeof storage.compareAndSwap === "function";
13
+ }
14
+ /** Runtime feature-detection: does this storage implement `claimDue`? */
15
+ function hasDeliveryQueue(storage) {
16
+ return typeof storage.claimDue === "function";
17
+ }
18
+ /**
19
+ * Helper for adapters whose subpath requires an optional peer dependency. Throws
20
+ * a clear, actionable error when the peer is missing.
21
+ */
22
+ function requirePeer(name, subpath) {
23
+ throw new Error(`@xtandard/webhooks/${subpath} requires the "${name}" package. Install it with: bun add ${name}`);
24
+ }
25
+ //#endregion
26
+ export { requirePeer as a, isWatchable as i, isCompareAndSwap as n, isTransactional as r, hasDeliveryQueue as t };
27
+
28
+ //# sourceMappingURL=contract-BEhDcd_5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-BEhDcd_5.mjs","names":[],"sources":["../src/storage/contract.ts"],"sourcesContent":["/**\n * Storage contracts. The base {@link WebhooksStorage} is intentionally tiny —\n * four async methods — so users can bring their own backend. Optional\n * capabilities (watch, transactions, compare-and-swap, native delivery-queue\n * claiming) are separate interfaces that adapters may implement and the core\n * feature-detects.\n *\n * @module\n */\n\nimport type { Delivery } from \"../schema.ts\";\n\n/** The minimal key/value contract every storage backend must satisfy. */\nexport interface WebhooksStorage {\n /** Read a value, or `null` if absent. */\n getItem<T>(key: string): Promise<T | null>;\n /** Write a value (overwriting any existing). */\n setItem<T>(key: string, value: T): Promise<void>;\n /** Delete a key (no-op if absent). */\n removeItem(key: string): Promise<void>;\n /** List all keys beginning with `prefix`. */\n getKeys(prefix: string): Promise<string[]>;\n}\n\n/** A storage change event delivered to {@link WatchableWebhooksStorage.watch} callbacks. */\nexport interface StorageChangeEvent {\n type: \"update\" | \"remove\";\n key: string;\n}\n\n/** Storage that can push change notifications (e.g. Redis pub/sub, fs.watch). */\nexport interface WatchableWebhooksStorage extends WebhooksStorage {\n /**\n * Subscribe to changes under `prefix`. Resolves to an unsubscribe function.\n */\n watch(prefix: string, callback: (event: StorageChangeEvent) => void): Promise<() => void>;\n}\n\n/** Storage that supports atomic multi-key transactions. */\nexport interface TransactionalWebhooksStorage extends WebhooksStorage {\n transaction<T>(callback: (tx: WebhooksStorage) => Promise<T>): Promise<T>;\n}\n\n/** Storage that supports optimistic concurrency via compare-and-swap. */\nexport interface CompareAndSwapWebhooksStorage extends WebhooksStorage {\n compareAndSwap<T>(input: { key: string; expected: T | null; next: T }): Promise<boolean>;\n}\n\n/**\n * Storage that can natively claim due deliveries (sorted-set / `SKIP LOCKED`\n * class backends). When present, the dispatcher delegates claiming entirely to\n * the adapter instead of scanning the generic due index.\n *\n * The claim contract: atomically transition each returned delivery to\n * `status: \"delivering\"` with `leaseUntil = now + leaseMs`, reposition its due\n * entry at the lease expiry (so a crashed claimer's work re-surfaces), and\n * never return the same delivery to two concurrent claimers.\n */\nexport interface DeliveryQueueStorage extends WebhooksStorage {\n claimDue(input: { now: string; limit: number; leaseMs: number }): Promise<Delivery[]>;\n}\n\n/** Runtime feature-detection: does this storage implement `watch`? */\nexport function isWatchable(storage: WebhooksStorage): storage is WatchableWebhooksStorage {\n return typeof (storage as Partial<WatchableWebhooksStorage>).watch === \"function\";\n}\n\n/** Runtime feature-detection: does this storage implement `transaction`? */\nexport function isTransactional(storage: WebhooksStorage): storage is TransactionalWebhooksStorage {\n return typeof (storage as Partial<TransactionalWebhooksStorage>).transaction === \"function\";\n}\n\n/** Runtime feature-detection: does this storage implement `compareAndSwap`? */\nexport function isCompareAndSwap(\n storage: WebhooksStorage,\n): storage is CompareAndSwapWebhooksStorage {\n return typeof (storage as Partial<CompareAndSwapWebhooksStorage>).compareAndSwap === \"function\";\n}\n\n/** Runtime feature-detection: does this storage implement `claimDue`? */\nexport function hasDeliveryQueue(storage: WebhooksStorage): storage is DeliveryQueueStorage {\n return typeof (storage as Partial<DeliveryQueueStorage>).claimDue === \"function\";\n}\n\n/**\n * Helper for adapters whose subpath requires an optional peer dependency. Throws\n * a clear, actionable error when the peer is missing.\n */\nexport function requirePeer(name: string, subpath: string): never {\n throw new Error(\n `@xtandard/webhooks/${subpath} requires the \"${name}\" package. Install it with: bun add ${name}`,\n );\n}\n"],"mappings":";;AA+DA,SAAgB,YAAY,SAA+D;CACzF,OAAO,OAAQ,QAA8C,UAAU;AACzE;;AAGA,SAAgB,gBAAgB,SAAmE;CACjG,OAAO,OAAQ,QAAkD,gBAAgB;AACnF;;AAGA,SAAgB,iBACd,SAC0C;CAC1C,OAAO,OAAQ,QAAmD,mBAAmB;AACvF;;AAGA,SAAgB,iBAAiB,SAA2D;CAC1F,OAAO,OAAQ,QAA0C,aAAa;AACxE;;;;;AAMA,SAAgB,YAAY,MAAc,SAAwB;CAChE,MAAM,IAAI,MACR,sBAAsB,QAAQ,iBAAiB,KAAK,sCAAsC,MAC5F;AACF"}
@@ -0,0 +1,57 @@
1
+ //#region src/storage/contract.ts
2
+ /** Runtime feature-detection: does this storage implement `watch`? */
3
+ function isWatchable(storage) {
4
+ return typeof storage.watch === "function";
5
+ }
6
+ /** Runtime feature-detection: does this storage implement `transaction`? */
7
+ function isTransactional(storage) {
8
+ return typeof storage.transaction === "function";
9
+ }
10
+ /** Runtime feature-detection: does this storage implement `compareAndSwap`? */
11
+ function isCompareAndSwap(storage) {
12
+ return typeof storage.compareAndSwap === "function";
13
+ }
14
+ /** Runtime feature-detection: does this storage implement `claimDue`? */
15
+ function hasDeliveryQueue(storage) {
16
+ return typeof storage.claimDue === "function";
17
+ }
18
+ /**
19
+ * Helper for adapters whose subpath requires an optional peer dependency. Throws
20
+ * a clear, actionable error when the peer is missing.
21
+ */
22
+ function requirePeer(name, subpath) {
23
+ throw new Error(`@xtandard/webhooks/${subpath} requires the "${name}" package. Install it with: bun add ${name}`);
24
+ }
25
+ //#endregion
26
+ Object.defineProperty(exports, "hasDeliveryQueue", {
27
+ enumerable: true,
28
+ get: function() {
29
+ return hasDeliveryQueue;
30
+ }
31
+ });
32
+ Object.defineProperty(exports, "isCompareAndSwap", {
33
+ enumerable: true,
34
+ get: function() {
35
+ return isCompareAndSwap;
36
+ }
37
+ });
38
+ Object.defineProperty(exports, "isTransactional", {
39
+ enumerable: true,
40
+ get: function() {
41
+ return isTransactional;
42
+ }
43
+ });
44
+ Object.defineProperty(exports, "isWatchable", {
45
+ enumerable: true,
46
+ get: function() {
47
+ return isWatchable;
48
+ }
49
+ });
50
+ Object.defineProperty(exports, "requirePeer", {
51
+ enumerable: true,
52
+ get: function() {
53
+ return requirePeer;
54
+ }
55
+ });
56
+
57
+ //# sourceMappingURL=contract-Bf1qguwt.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-Bf1qguwt.cjs","names":[],"sources":["../src/storage/contract.ts"],"sourcesContent":["/**\n * Storage contracts. The base {@link WebhooksStorage} is intentionally tiny —\n * four async methods — so users can bring their own backend. Optional\n * capabilities (watch, transactions, compare-and-swap, native delivery-queue\n * claiming) are separate interfaces that adapters may implement and the core\n * feature-detects.\n *\n * @module\n */\n\nimport type { Delivery } from \"../schema.ts\";\n\n/** The minimal key/value contract every storage backend must satisfy. */\nexport interface WebhooksStorage {\n /** Read a value, or `null` if absent. */\n getItem<T>(key: string): Promise<T | null>;\n /** Write a value (overwriting any existing). */\n setItem<T>(key: string, value: T): Promise<void>;\n /** Delete a key (no-op if absent). */\n removeItem(key: string): Promise<void>;\n /** List all keys beginning with `prefix`. */\n getKeys(prefix: string): Promise<string[]>;\n}\n\n/** A storage change event delivered to {@link WatchableWebhooksStorage.watch} callbacks. */\nexport interface StorageChangeEvent {\n type: \"update\" | \"remove\";\n key: string;\n}\n\n/** Storage that can push change notifications (e.g. Redis pub/sub, fs.watch). */\nexport interface WatchableWebhooksStorage extends WebhooksStorage {\n /**\n * Subscribe to changes under `prefix`. Resolves to an unsubscribe function.\n */\n watch(prefix: string, callback: (event: StorageChangeEvent) => void): Promise<() => void>;\n}\n\n/** Storage that supports atomic multi-key transactions. */\nexport interface TransactionalWebhooksStorage extends WebhooksStorage {\n transaction<T>(callback: (tx: WebhooksStorage) => Promise<T>): Promise<T>;\n}\n\n/** Storage that supports optimistic concurrency via compare-and-swap. */\nexport interface CompareAndSwapWebhooksStorage extends WebhooksStorage {\n compareAndSwap<T>(input: { key: string; expected: T | null; next: T }): Promise<boolean>;\n}\n\n/**\n * Storage that can natively claim due deliveries (sorted-set / `SKIP LOCKED`\n * class backends). When present, the dispatcher delegates claiming entirely to\n * the adapter instead of scanning the generic due index.\n *\n * The claim contract: atomically transition each returned delivery to\n * `status: \"delivering\"` with `leaseUntil = now + leaseMs`, reposition its due\n * entry at the lease expiry (so a crashed claimer's work re-surfaces), and\n * never return the same delivery to two concurrent claimers.\n */\nexport interface DeliveryQueueStorage extends WebhooksStorage {\n claimDue(input: { now: string; limit: number; leaseMs: number }): Promise<Delivery[]>;\n}\n\n/** Runtime feature-detection: does this storage implement `watch`? */\nexport function isWatchable(storage: WebhooksStorage): storage is WatchableWebhooksStorage {\n return typeof (storage as Partial<WatchableWebhooksStorage>).watch === \"function\";\n}\n\n/** Runtime feature-detection: does this storage implement `transaction`? */\nexport function isTransactional(storage: WebhooksStorage): storage is TransactionalWebhooksStorage {\n return typeof (storage as Partial<TransactionalWebhooksStorage>).transaction === \"function\";\n}\n\n/** Runtime feature-detection: does this storage implement `compareAndSwap`? */\nexport function isCompareAndSwap(\n storage: WebhooksStorage,\n): storage is CompareAndSwapWebhooksStorage {\n return typeof (storage as Partial<CompareAndSwapWebhooksStorage>).compareAndSwap === \"function\";\n}\n\n/** Runtime feature-detection: does this storage implement `claimDue`? */\nexport function hasDeliveryQueue(storage: WebhooksStorage): storage is DeliveryQueueStorage {\n return typeof (storage as Partial<DeliveryQueueStorage>).claimDue === \"function\";\n}\n\n/**\n * Helper for adapters whose subpath requires an optional peer dependency. Throws\n * a clear, actionable error when the peer is missing.\n */\nexport function requirePeer(name: string, subpath: string): never {\n throw new Error(\n `@xtandard/webhooks/${subpath} requires the \"${name}\" package. Install it with: bun add ${name}`,\n );\n}\n"],"mappings":";;AA+DA,SAAgB,YAAY,SAA+D;CACzF,OAAO,OAAQ,QAA8C,UAAU;AACzE;;AAGA,SAAgB,gBAAgB,SAAmE;CACjG,OAAO,OAAQ,QAAkD,gBAAgB;AACnF;;AAGA,SAAgB,iBACd,SAC0C;CAC1C,OAAO,OAAQ,QAAmD,mBAAmB;AACvF;;AAGA,SAAgB,iBAAiB,SAA2D;CAC1F,OAAO,OAAQ,QAA0C,aAAa;AACxE;;;;;AAMA,SAAgB,YAAY,MAAc,SAAwB;CAChE,MAAM,IAAI,MACR,sBAAsB,QAAQ,iBAAiB,KAAK,sCAAsC,MAC5F;AACF"}
@@ -0,0 +1,177 @@
1
+ import { a as AuditEntry, f as EventType, l as Endpoint, m as Message, n as Application, o as Delivery, p as JsonValue, s as DeliveryAttempt, t as Actor } from "./schema-mo__wv4P.cjs";
2
+
3
+ //#region src/hooks/contract.d.ts
4
+ /**
5
+ * Event delivered to {@link WebhooksHooks.before} — the *proposed* mutation,
6
+ * before it commits. Throw from the handler to deny it.
7
+ */
8
+ type BeforeEvent = {
9
+ type: "application.create" | "application.update";
10
+ application: Application;
11
+ actor: Actor | null;
12
+ } | {
13
+ type: "application.delete";
14
+ applicationKey: string;
15
+ actor: Actor | null;
16
+ } | {
17
+ type: "event-type.upsert";
18
+ eventType: EventType;
19
+ actor: Actor | null;
20
+ } | {
21
+ type: "event-type.delete";
22
+ name: string;
23
+ actor: Actor | null;
24
+ } | {
25
+ type: "endpoint.create" | "endpoint.update";
26
+ applicationKey: string;
27
+ endpoint: Endpoint;
28
+ actor: Actor | null;
29
+ } | {
30
+ type: "endpoint.delete" | "endpoint.rotate-secret" | "endpoint.disable" | "endpoint.enable";
31
+ applicationKey: string;
32
+ endpointId: string;
33
+ actor: Actor | null;
34
+ } | {
35
+ /** The host's approval/quota gate for the publish hot path. */type: "message.publish";
36
+ applicationKey: string;
37
+ eventType: string;
38
+ payload: JsonValue;
39
+ idempotencyKey?: string;
40
+ actor: Actor | null;
41
+ } | {
42
+ type: "delivery.retry";
43
+ applicationKey: string;
44
+ deliveryId: string;
45
+ actor: Actor | null;
46
+ } | {
47
+ type: "endpoint.recover";
48
+ applicationKey: string;
49
+ endpointId: string;
50
+ since: string;
51
+ actor: Actor | null;
52
+ };
53
+ /**
54
+ * Event delivered to {@link WebhooksHooks.after} — the *committed* mutation.
55
+ * Carries the resulting state in full: for destructive events this is the last
56
+ * chance to offload the payload before it is gone from storage.
57
+ */
58
+ type AfterEvent = {
59
+ type: "application.created" | "application.updated";
60
+ application: Application;
61
+ at: string;
62
+ } | {
63
+ /** Carries the deleted application — everything under it is already gone. */type: "application.deleted";
64
+ applicationKey: string;
65
+ application: Application;
66
+ at: string;
67
+ } | {
68
+ type: "event-type.upserted";
69
+ eventType: EventType;
70
+ at: string;
71
+ } | {
72
+ type: "event-type.deleted";
73
+ name: string;
74
+ eventType: EventType;
75
+ at: string;
76
+ } | {
77
+ type: "endpoint.created" | "endpoint.updated" | "endpoint.deleted" | "endpoint.secret-rotated" | "endpoint.disabled" | "endpoint.enabled" /** Disabled by the failure policy, not an operator (see dispatcher `autoDisable`). */ | "endpoint.auto-disabled";
78
+ applicationKey: string;
79
+ endpoint: Endpoint;
80
+ at: string;
81
+ } | {
82
+ type: "message.published";
83
+ applicationKey: string;
84
+ message: Message;
85
+ deliveryIds: string[];
86
+ at: string;
87
+ } | {
88
+ type: "delivery.succeeded";
89
+ applicationKey: string;
90
+ delivery: Delivery;
91
+ attempt: DeliveryAttempt;
92
+ at: string;
93
+ } | {
94
+ /** Dead-letter offload point: the retry schedule is exhausted. */type: "delivery.exhausted";
95
+ applicationKey: string;
96
+ delivery: Delivery;
97
+ attempts: DeliveryAttempt[];
98
+ at: string;
99
+ } | {
100
+ /** Messages removed by retention. Carries the **full messages** — offload now or lose them. */type: "message.pruned";
101
+ applicationKey: string;
102
+ messages: Message[];
103
+ at: string;
104
+ } | {
105
+ /** Audit entries removed by retention (oldest-first). */type: "audit.pruned";
106
+ applicationKey?: string;
107
+ entries: AuditEntry[];
108
+ at: string;
109
+ };
110
+ /** The discriminant strings of {@link BeforeEvent} / {@link AfterEvent}. */
111
+ type BeforeEventType = BeforeEvent["type"];
112
+ type AfterEventType = AfterEvent["type"];
113
+ /**
114
+ * Thrown from a {@link WebhooksHooks.before} handler to **deny** a mutation with
115
+ * a clean HTTP status (default `403`). Any thrown error denies the mutation,
116
+ * but a plain `Error` maps to `500` at the API layer (treated as an unexpected
117
+ * bug); throw this to signal a deliberate policy rejection (`403`, or a custom
118
+ * `status` such as `409`/`422`).
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * before(event) {
123
+ * if (event.type === "message.publish" && overQuota(event.applicationKey)) {
124
+ * throw new HookDeniedError("Monthly webhook quota exceeded.", { status: 429 });
125
+ * }
126
+ * }
127
+ * ```
128
+ */
129
+ declare class HookDeniedError extends Error {
130
+ /** HTTP status the API layer should respond with. Default `403`. */
131
+ readonly status: number;
132
+ constructor(message: string, options?: {
133
+ status?: number;
134
+ cause?: unknown;
135
+ });
136
+ }
137
+ /**
138
+ * A control-plane hook. Implement `before`, `after`, or both. Pass one — or an
139
+ * array — to {@link ../core.createWebhooksCore} via `hooks`.
140
+ */
141
+ interface WebhooksHooks {
142
+ /**
143
+ * Runs before a mutation commits. **Throw to deny** (the error propagates to
144
+ * the caller; its message is the reason). Return/resolve to allow. Must not
145
+ * mutate the event payload.
146
+ */
147
+ before?(event: BeforeEvent): void | Promise<void>;
148
+ /**
149
+ * Runs after a mutation commits. For side effects only. Errors are isolated
150
+ * and reported via `onHookError` — they never fail the (already committed)
151
+ * operation.
152
+ */
153
+ after?(event: AfterEvent): void | Promise<void>;
154
+ }
155
+ /** Accepts a single hook, an array, or nothing. */
156
+ type WebhooksHooksInput = WebhooksHooks | readonly WebhooksHooks[] | undefined;
157
+ /** Reports an error thrown by an `after` hook. */
158
+ type HookErrorReporter = (error: unknown, event: AfterEvent) => void;
159
+ /** Normalize the `hooks` option into a flat array (empty when unset). */
160
+ declare function normalizeHooks(input: WebhooksHooksInput): WebhooksHooks[];
161
+ /**
162
+ * Run every `before` hook sequentially, in order. The first hook to throw
163
+ * aborts: the error propagates to the caller (denying the mutation) and no
164
+ * later hook runs. A no-op when there are no `before` hooks.
165
+ */
166
+ declare function runBefore(hooks: WebhooksHooks[], event: BeforeEvent): Promise<void>;
167
+ /**
168
+ * Run every `after` hook, isolating failures. The mutation has already
169
+ * committed, so a throwing hook must not fail the operation — its error is
170
+ * routed to `onError` and swallowed. Remaining hooks still run.
171
+ */
172
+ declare function runAfter(hooks: WebhooksHooks[], event: AfterEvent, onError: HookErrorReporter): Promise<void>;
173
+ /** Default `after`-hook error reporter: warn, but never throw. */
174
+ declare const defaultHookErrorReporter: HookErrorReporter;
175
+ //#endregion
176
+ export { HookDeniedError as a, WebhooksHooksInput as c, runAfter as d, runBefore as f, BeforeEventType as i, defaultHookErrorReporter as l, AfterEventType as n, HookErrorReporter as o, BeforeEvent as r, WebhooksHooks as s, AfterEvent as t, normalizeHooks as u };
177
+ //# sourceMappingURL=contract-Bnb3fgRJ.d.cts.map
@@ -0,0 +1,46 @@
1
+ import { n as Principal } from "./contract-lETlIuXo.mjs";
2
+
3
+ //#region src/authorization/contract.d.ts
4
+ /** The full set of authorizable actions. */
5
+ type WebhooksAction = "application:read" | "application:create" | "application:update" | "application:delete" | "event-type:read" | "event-type:create" | "event-type:update" | "event-type:delete" | "endpoint:read" | "endpoint:create" | "endpoint:update" | "endpoint:delete" | "endpoint:rotate-secret" | "endpoint:read-secret" | "message:read" | "message:publish" | "delivery:read" | "delivery:retry" | "audit:read";
6
+ /** The resource an action targets. */
7
+ type WebhooksResource = {
8
+ type: "application";
9
+ applicationKey: string;
10
+ } | {
11
+ type: "event-type";
12
+ name: string;
13
+ } | {
14
+ type: "endpoint";
15
+ applicationKey: string;
16
+ endpointId: string;
17
+ } | {
18
+ type: "message";
19
+ applicationKey: string;
20
+ messageId?: string;
21
+ } | {
22
+ type: "delivery";
23
+ applicationKey: string;
24
+ deliveryId?: string;
25
+ } | {
26
+ type: "audit";
27
+ applicationKey?: string;
28
+ };
29
+ /** Input passed to {@link AuthorizationProvider.authorize}. */
30
+ interface AuthorizeInput {
31
+ principal: Principal | null;
32
+ action: WebhooksAction;
33
+ resource: WebhooksResource;
34
+ request: Request;
35
+ }
36
+ /** Decides whether an action is permitted. */
37
+ interface AuthorizationProvider {
38
+ authorize(input: AuthorizeInput): Promise<boolean>;
39
+ }
40
+ /** Actions that mutate state — blocked in readonly mode. */
41
+ declare const MUTATING_ACTIONS: ReadonlySet<WebhooksAction>;
42
+ /** True if the action mutates state. */
43
+ declare const isMutatingAction: (action: WebhooksAction) => boolean;
44
+ //#endregion
45
+ export { WebhooksResource as a, WebhooksAction as i, AuthorizeInput as n, isMutatingAction as o, MUTATING_ACTIONS as r, AuthorizationProvider as t };
46
+ //# sourceMappingURL=contract-C2r2Xzwp.d.mts.map