@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,366 @@
1
+ import { _ as WebhookEnvelope, a as AuditEntry, c as DeliveryStatus, d as EndpointSecret, f as EventType, g as WebhookDuration, h as SCHEMA_VERSION, i as AuditAction, l as Endpoint, m as Message, n as Application, o as Delivery, p as JsonValue, r as AttemptTrigger, s as DeliveryAttempt, t as Actor, u as EndpointDisabledReason, v as isTerminalDeliveryStatus } from "./schema-mo__wv4P.mjs";
2
+ import { A as BuildSignedRequestInput, C as createDispatcher, D as emitDelivery, E as DeliveryListener, F as attemptDelivery, I as buildSignedRequest, M as DEFAULT_RESPONSE_BODY_LIMIT, N as SignedRequest, O as AttemptDeliveryInput, P as activeSecrets, S as DispatcherOptions, T as DeliveryEvent, _ as WebhooksCore, a as ListDeliveriesOptions, b as DEFAULT_RETRY_SCHEDULE, c as PayloadTooLargeError, d as ReadonlyError, f as RecordAttemptInput, g as SendExampleResult, h as RetentionRule, i as IdempotencyConflictError, j as DEFAULT_ATTEMPT_TIMEOUT_MS, k as AttemptOutcome, l as PublishInput, m as RetentionOptions, n as ClaimInput, o as ListMessagesOptions, p as RecoverResult, r as ConflictError, s as NotFoundError, t as ActorOptions, u as PublishResult, v as WebhooksCoreOptions, w as DeliveryErrorReporter, x as Dispatcher, y as createWebhooksCore } from "./core-DT4ppWh8.mjs";
3
+ import { a as WatchableWebhooksStorage, c as isCompareAndSwap, d as requirePeer, i as TransactionalWebhooksStorage, l as isTransactional, n as DeliveryQueueStorage, o as WebhooksStorage, r as StorageChangeEvent, s as hasDeliveryQueue, t as CompareAndSwapWebhooksStorage, u as isWatchable } from "./contract-DhQ4JjGG.mjs";
4
+ import { a as HookDeniedError, c as WebhooksHooksInput, d as runAfter, f as runBefore, i as BeforeEventType, l as defaultHookErrorReporter, n as AfterEventType, o as HookErrorReporter, r as BeforeEvent, s as WebhooksHooks, t as AfterEvent, u as normalizeHooks } from "./contract-T1kcZNdG.mjs";
5
+ import { n as Principal, t as AuthProvider } from "./contract-lETlIuXo.mjs";
6
+ import { a as WebhooksResource, i as WebhooksAction, n as AuthorizeInput, o as isMutatingAction, r as MUTATING_ACTIONS, t as AuthorizationProvider } from "./contract-C2r2Xzwp.mjs";
7
+ import { a as WebhooksPortalOptions, i as DEFAULT_PORTAL_ACTIONS, n as WebhooksPanelOptions, o as WebhooksCorsOptions, r as createFetchHandler, t as CreateFetchHandlerResult } from "./create-fetch-handler-jy3hy5nZ.mjs";
8
+ import { SECRET_PREFIX, VerifyInput, WebhookVerificationError, generateSecret, sign, signatureHeader, verify } from "./signing.mjs";
9
+
10
+ //#region src/version.d.ts
11
+ /**
12
+ * Package version, used for the default dispatcher `user-agent`. Bumped by the
13
+ * release flow alongside `package.json`.
14
+ *
15
+ * @module
16
+ */
17
+ /** The published package version. */
18
+ declare const VERSION = "0.1.0";
19
+ declare namespace keys_d_exports {
20
+ export { DueEntry, RESERVED_APPLICATION_KEYS, ROOT, applicationMetaKey, applicationPrefix, applicationsKey, attemptKey, attemptsPrefix, auditLogKey, byEndpointKey, byEndpointPrefix, byMessageKey, byMessagePrefix, deliveriesPrefix, deliveryKey, dueKey, duePrefix, endpointKey, endpointsKey, eventTypeKey, eventTypesKey, globalAuditLogKey, idempotencyKey, lastSegment, messageKey, messagesPrefix, parseDueKey };
21
+ }
22
+ /**
23
+ * Storage key layout for `@xtandard/webhooks`.
24
+ *
25
+ * Keys are namespaced by application so a single storage backend can host the
26
+ * whole control plane. Pure string helpers — no dependencies.
27
+ *
28
+ * ```txt
29
+ * whk/applications -> string[] index
30
+ * whk/{app}/metadata -> Application
31
+ * whk/event-types -> string[] index (global)
32
+ * whk/event-types/{name} -> EventType
33
+ * whk/{app}/endpoints -> string[] index
34
+ * whk/{app}/endpoints/{id} -> Endpoint
35
+ * whk/{app}/messages/{id} -> Message
36
+ * whk/{app}/idempotency/{key} -> message id
37
+ * whk/{app}/deliveries/{id} -> Delivery
38
+ * whk/{app}/attempts/{deliveryId}/{n} -> DeliveryAttempt (n zero-padded)
39
+ * whk/{app}/due/{dueAtMillis}~{deliveryId} -> DueEntry (13-digit zero-padded millis)
40
+ * whk/{app}/by-message/{messageId}/{deliveryId} -> 1 (reverse index)
41
+ * whk/{app}/by-endpoint/{endpointId}/{deliveryId} -> 1 (reverse index)
42
+ * whk/{app}/audit-log -> AuditEntry[] (single ordered array)
43
+ * ```
44
+ *
45
+ * The due index is the dispatcher's generic work queue: 13-digit zero-padded
46
+ * milliseconds make lexicographic key order chronological, so a sorted
47
+ * `getKeys(duePrefix(app))` scan yields deliveries in due order.
48
+ *
49
+ * @module
50
+ */
51
+ /** Root namespace segment for all keys. */
52
+ declare const ROOT = "whk";
53
+ /**
54
+ * Application keys that would collide with global index keys under the shared
55
+ * root. Rejected by validation.
56
+ */
57
+ declare const RESERVED_APPLICATION_KEYS: readonly ["applications", "event-types", "audit-log"];
58
+ /** Global audit log (event-type actions, which are not application-scoped). */
59
+ declare const globalAuditLogKey: () => string;
60
+ /** Index key listing all known application keys. */
61
+ declare const applicationsKey: () => string;
62
+ /** Metadata for a single application. */
63
+ declare const applicationMetaKey: (applicationKey: string) => string;
64
+ /** Index key listing all event type names (global catalog). */
65
+ declare const eventTypesKey: () => string;
66
+ /** A single event type record. */
67
+ declare const eventTypeKey: (name: string) => string;
68
+ /** Index key listing an application's endpoint ids. */
69
+ declare const endpointsKey: (applicationKey: string) => string;
70
+ /** A single endpoint record. */
71
+ declare const endpointKey: (applicationKey: string, endpointId: string) => string;
72
+ /** Prefix under which an application's messages live. */
73
+ declare const messagesPrefix: (applicationKey: string) => string;
74
+ /** A single message record. */
75
+ declare const messageKey: (applicationKey: string, messageId: string) => string;
76
+ /** Maps a caller-supplied idempotency key to the message id it produced. */
77
+ declare const idempotencyKey: (applicationKey: string, key: string) => string;
78
+ /** Prefix under which an application's deliveries live. */
79
+ declare const deliveriesPrefix: (applicationKey: string) => string;
80
+ /** A single delivery record. */
81
+ declare const deliveryKey: (applicationKey: string, deliveryId: string) => string;
82
+ /** Prefix under which one delivery's attempts live. */
83
+ declare const attemptsPrefix: (applicationKey: string, deliveryId: string) => string;
84
+ /**
85
+ * A single attempt record. The attempt number is zero-padded to four digits so
86
+ * lexicographic key order equals attempt order.
87
+ */
88
+ declare const attemptKey: (applicationKey: string, deliveryId: string, attemptNumber: number) => string;
89
+ /** Prefix of an application's due index (the dispatcher's work queue). */
90
+ declare const duePrefix: (applicationKey: string) => string;
91
+ /** Value stored at a due-index key. */
92
+ interface DueEntry {
93
+ app: string;
94
+ deliveryId: string;
95
+ }
96
+ /**
97
+ * A due-index entry: 13-digit zero-padded epoch milliseconds, then the delivery
98
+ * id, `~`-separated. Sorting keys lexicographically sorts entries chronologically.
99
+ */
100
+ declare const dueKey: (applicationKey: string, dueAtMillis: number, deliveryId: string) => string;
101
+ /** Parse a due-index key back into its due-time and delivery id. */
102
+ declare const parseDueKey: (key: string) => {
103
+ dueAtMillis: number;
104
+ deliveryId: string;
105
+ } | null;
106
+ /** Prefix of the message → deliveries reverse index. */
107
+ declare const byMessagePrefix: (applicationKey: string, messageId: string) => string;
108
+ /** One entry in the message → deliveries reverse index. */
109
+ declare const byMessageKey: (applicationKey: string, messageId: string, deliveryId: string) => string;
110
+ /** Prefix of the endpoint → deliveries reverse index. */
111
+ declare const byEndpointPrefix: (applicationKey: string, endpointId: string) => string;
112
+ /** One entry in the endpoint → deliveries reverse index. */
113
+ declare const byEndpointKey: (applicationKey: string, endpointId: string, deliveryId: string) => string;
114
+ /** Append-only audit log for an application, stored as an ordered `AuditEntry[]`. */
115
+ declare const auditLogKey: (applicationKey: string) => string;
116
+ /** Prefix under which everything belonging to one application lives. */
117
+ declare const applicationPrefix: (applicationKey: string) => string;
118
+ /** Extract the trailing segment (endpoint id / message id / …) from a key. */
119
+ declare const lastSegment: (key: string) => string;
120
+ //#endregion
121
+ //#region src/id.d.ts
122
+ /**
123
+ * Entity id generation: `{prefix}_{22-char base62}`. Zero dependencies — base62
124
+ * over `crypto.getRandomValues`, no ULID. 22 base62 characters encode ~131 bits;
125
+ * ids are generated from 16 random bytes (128 bits) and left-padded to a fixed
126
+ * length so ids are uniform and lexicographically well-behaved.
127
+ *
128
+ * @module
129
+ */
130
+ /** Prefixes for the four generated entity id kinds. */
131
+ type IdPrefix = "msg" | "ep" | "dlv" | "atp";
132
+ /**
133
+ * Generate a new entity id, e.g. `newId("msg")` → `"msg_0uK9…"` (22-char base62
134
+ * suffix).
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * import { newId } from "@xtandard/webhooks";
139
+ *
140
+ * const id = newId("ep"); // "ep_…"
141
+ * ```
142
+ */
143
+ declare function newId(prefix: IdPrefix): string;
144
+ /** Regex an id of the given prefix must match. */
145
+ declare function idPattern(prefix: IdPrefix): RegExp;
146
+ //#endregion
147
+ //#region src/duration.d.ts
148
+ /**
149
+ * Convert a {@link WebhookDuration} (`5000`, `"5s"`, `"30m"`, `"2h"`, `"5d"`)
150
+ * to milliseconds. Throws on malformed strings or negative numbers.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { durationToMs } from "@xtandard/webhooks";
155
+ *
156
+ * durationToMs("5m"); // 300000
157
+ * durationToMs(250); // 250
158
+ * ```
159
+ */
160
+ declare function durationToMs(duration: WebhookDuration): number;
161
+ /**
162
+ * Parse a comma-separated duration list (the `RETRY_SCHEDULE` env var format,
163
+ * e.g. `"0s,5s,5m,30m,2h,5h,10h"`) into a {@link WebhookDuration} array.
164
+ */
165
+ declare function parseDurationList(input: string): WebhookDuration[];
166
+ //#endregion
167
+ //#region src/storage/watch.d.ts
168
+ /**
169
+ * A change source. Called with a `notify` callback — wire your source to invoke
170
+ * it (optionally with the changed key) on every change — and return an
171
+ * unsubscribe function (sync or async).
172
+ */
173
+ type WatchSubscribe = (notify: (key?: string) => void) => (() => void | Promise<void>) | Promise<() => void | Promise<void>>;
174
+ /**
175
+ * Wrap a storage so it implements {@link WatchableWebhooksStorage} using
176
+ * `subscribe` as the change source. The wrapper delegates all reads/writes to
177
+ * `storage` and only adds `watch`; keys not under the watched `prefix` are
178
+ * filtered out. A notification without a key is delivered as a change to the
179
+ * prefix itself (enough to trigger a refresh).
180
+ */
181
+ declare function withWatch<S extends WebhooksStorage>(storage: S, subscribe: WatchSubscribe): S & WatchableWebhooksStorage;
182
+ /**
183
+ * A dedicated notification client for {@link pgListenNotify} — satisfied by a
184
+ * `pg` `Client`. `Pool` won't do (it rotates connections); use a `Client` you
185
+ * `connect()`.
186
+ */
187
+ interface PgNotificationClient {
188
+ query(sql: string): Promise<unknown>;
189
+ on(event: "notification", listener: (msg: {
190
+ channel: string;
191
+ payload?: string;
192
+ }) => void): void;
193
+ removeListener(event: "notification", listener: (msg: {
194
+ channel: string;
195
+ payload?: string;
196
+ }) => void): void;
197
+ }
198
+ /**
199
+ * A {@link WatchSubscribe} backed by Postgres `LISTEN`/`NOTIFY`. Pair with
200
+ * {@link withWatch} for the postgres or drizzle-over-pg adapters. Your migration
201
+ * owns the trigger that `pg_notify`s `channel` with the changed key as payload;
202
+ * this only `LISTEN`s.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { Client } from "pg";
207
+ * const listener = new Client({ connectionString: process.env.DATABASE_URL });
208
+ * await listener.connect();
209
+ * const storage = withWatch(base, pgListenNotify(listener, "xtandard_webhooks"));
210
+ * ```
211
+ */
212
+ declare function pgListenNotify(client: PgNotificationClient, channel?: string): WatchSubscribe;
213
+ //#endregion
214
+ //#region src/validation.d.ts
215
+ /**
216
+ * Runtime validation for control-plane inputs, built on `valibot`.
217
+ *
218
+ * This is the **admin path** only — `publish()` performs its own minimal
219
+ * checks and the wire/receiver path never imports this module, so `valibot`
220
+ * stays off the hot paths. Validation combines structural parsing (valibot)
221
+ * with semantic checks (URL policy, reserved header names, reserved keys).
222
+ *
223
+ * @module
224
+ */
225
+ /** Allowed characters for application keys and event type names. */
226
+ declare const KEY_REGEX: RegExp;
227
+ /**
228
+ * Header names owned by the Standard Webhooks wire contract. Endpoints may not
229
+ * override them via static headers.
230
+ */
231
+ declare const RESERVED_HEADERS: readonly ["webhook-id", "webhook-timestamp", "webhook-signature"];
232
+ /** A single validation problem with a dotted path into the offending data. */
233
+ interface ValidationIssue {
234
+ path: string;
235
+ message: string;
236
+ }
237
+ /** Result of the `validate*` functions. */
238
+ interface ValidationResult {
239
+ valid: boolean;
240
+ errors: ValidationIssue[];
241
+ }
242
+ /** Raised by {@link assertValid} when an input fails validation. Maps to HTTP 422. */
243
+ declare class ValidationError extends Error {
244
+ readonly errors: ValidationIssue[];
245
+ constructor(errors: ValidationIssue[]);
246
+ }
247
+ /** Throw a {@link ValidationError} when `result` is invalid. */
248
+ declare function assertValid(result: ValidationResult): void;
249
+ /**
250
+ * Validate an {@link Application}: structure + reserved-key check.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * import { validateApplication } from "@xtandard/webhooks";
255
+ *
256
+ * const result = validateApplication({ key: "acme" });
257
+ * // result.valid === true
258
+ * ```
259
+ */
260
+ declare function validateApplication(input: unknown, basePath?: string): ValidationResult;
261
+ /** Validate an {@link EventType}: structural only (the name regex carries the semantics). */
262
+ declare function validateEventType(input: unknown, basePath?: string): ValidationResult;
263
+ /** Options that relax/extend the endpoint URL policy. */
264
+ interface UrlPolicyOptions {
265
+ /** Allow `http:` for non-localhost hosts (dev only; default `false`). */
266
+ allowInsecureUrls?: boolean;
267
+ /** Extra host-supplied gate; return `false` to reject (e.g. an SSRF denylist). */
268
+ urlPolicy?: (url: string) => boolean;
269
+ }
270
+ /**
271
+ * Validate an endpoint destination URL: parseable, `https:` (or `http:` for
272
+ * localhost, or anywhere when `allowInsecureUrls`), no embedded credentials,
273
+ * and passing the host's optional `urlPolicy` gate.
274
+ */
275
+ declare function validateEndpointUrl(url: string, options?: UrlPolicyOptions, basePath?: string): ValidationResult;
276
+ /**
277
+ * Validate an {@link Endpoint} input: structure, URL policy, and static-header
278
+ * restrictions (the Standard Webhooks headers are reserved).
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * import { validateEndpoint } from "@xtandard/webhooks";
283
+ *
284
+ * const result = validateEndpoint({ url: "https://api.example.com/hooks" });
285
+ * // result.valid === true
286
+ * ```
287
+ */
288
+ declare function validateEndpoint(input: unknown, options?: UrlPolicyOptions, basePath?: string): ValidationResult;
289
+ //#endregion
290
+ //#region src/portal.d.ts
291
+ /** Prefix of every portal token. */
292
+ declare const PORTAL_TOKEN_PREFIX = "whpt_";
293
+ /**
294
+ * Thrown by {@link verifyPortalToken} for any invalid token — malformed,
295
+ * bad signature, or expired. Maps to HTTP 401 at the API layer.
296
+ */
297
+ declare class PortalTokenError extends Error {
298
+ constructor(message: string);
299
+ }
300
+ /** Options for {@link createPortalToken}. */
301
+ interface PortalTokenOptions {
302
+ /** Token lifetime. Default `"7d"`. */
303
+ expiresIn?: WebhookDuration;
304
+ }
305
+ /**
306
+ * Mint a portal token granting access to `applicationKey` until `expiresIn`
307
+ * elapses (default 7 days). Call this **server-side** — anyone holding the
308
+ * portal secret can mint tokens for any application.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * import { createPortalToken } from "@xtandard/webhooks";
313
+ *
314
+ * // In the host app's session-guarded route:
315
+ * const token = await createPortalToken(process.env.PORTAL_SECRET!, "acme", {
316
+ * expiresIn: "1h",
317
+ * });
318
+ * // Hand `token` to the frontend for <WebhooksPortal token={token} />.
319
+ * ```
320
+ */
321
+ declare function createPortalToken(secret: string, applicationKey: string, options?: PortalTokenOptions): Promise<string>;
322
+ /**
323
+ * Verify a portal token: format, signature (constant time), then expiry.
324
+ * Returns the granted application key on success; throws
325
+ * {@link PortalTokenError} otherwise.
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * import { verifyPortalToken } from "@xtandard/webhooks";
330
+ *
331
+ * const { applicationKey } = await verifyPortalToken(secret, token);
332
+ * ```
333
+ */
334
+ declare function verifyPortalToken(secret: string, token: string): Promise<{
335
+ applicationKey: string;
336
+ }>;
337
+ //#endregion
338
+ //#region src/server/openapi.d.ts
339
+ /**
340
+ * OpenAPI 3.1 description of the admin JSON API.
341
+ *
342
+ * Exposed two ways, mirroring the flags panel:
343
+ * - served at `GET {basePath}/api/openapi.json` for standalone tools (Scalar,
344
+ * Swagger UI, Postman, codegen);
345
+ * - returned by `createFetchHandler(...).openapi()` so you can MERGE it into
346
+ * your host app's OpenAPI document (e.g. Elysia `@elysiajs/openapi`
347
+ * `references`, or Hono's OpenAPI).
348
+ *
349
+ * Hand-authored — no generation dependency (flags precedent).
350
+ *
351
+ * @module
352
+ */
353
+ /** Options for {@link buildOpenApiDocument}. */
354
+ interface OpenApiOptions {
355
+ /** Mount prefix used in the `servers` url (e.g. `"/webhooks"`). */
356
+ basePath?: string;
357
+ /** Document title. */
358
+ title?: string;
359
+ /** Document version (defaults to the package's API version). */
360
+ version?: string;
361
+ }
362
+ /** Build the OpenAPI 3.1 document for the admin API. Pure — safe to call anywhere. */
363
+ declare function buildOpenApiDocument(options?: OpenApiOptions): Record<string, unknown>;
364
+ //#endregion
365
+ export { Actor, ActorOptions, type AfterEvent, type AfterEventType, Application, type AttemptDeliveryInput, type AttemptOutcome, AttemptTrigger, AuditAction, AuditEntry, type AuthProvider, type AuthorizationProvider, type AuthorizeInput, type BeforeEvent, type BeforeEventType, type BuildSignedRequestInput, ClaimInput, type CompareAndSwapWebhooksStorage, ConflictError, type CreateFetchHandlerResult, DEFAULT_ATTEMPT_TIMEOUT_MS, DEFAULT_PORTAL_ACTIONS, DEFAULT_RESPONSE_BODY_LIMIT, DEFAULT_RETRY_SCHEDULE, Delivery, DeliveryAttempt, type DeliveryErrorReporter, type DeliveryEvent, type DeliveryListener, type DeliveryQueueStorage, DeliveryStatus, type Dispatcher, type DispatcherOptions, Endpoint, EndpointDisabledReason, EndpointSecret, EventType, HookDeniedError, type HookErrorReporter, type IdPrefix, IdempotencyConflictError, JsonValue, KEY_REGEX, ListDeliveriesOptions, ListMessagesOptions, MUTATING_ACTIONS, Message, NotFoundError, type OpenApiOptions, PORTAL_TOKEN_PREFIX, PayloadTooLargeError, type PgNotificationClient, PortalTokenError, type PortalTokenOptions, type Principal, PublishInput, PublishResult, RESERVED_HEADERS, ReadonlyError, RecordAttemptInput, RecoverResult, RetentionOptions, RetentionRule, SCHEMA_VERSION, SECRET_PREFIX, SendExampleResult, type SignedRequest, type StorageChangeEvent, type TransactionalWebhooksStorage, type UrlPolicyOptions, VERSION, ValidationError, type ValidationIssue, type ValidationResult, type VerifyInput, type WatchSubscribe, type WatchableWebhooksStorage, WebhookDuration, WebhookEnvelope, WebhookVerificationError, type WebhooksAction, WebhooksCore, WebhooksCoreOptions, type WebhooksCorsOptions, type WebhooksHooks, type WebhooksHooksInput, type WebhooksPanelOptions, type WebhooksPortalOptions, type WebhooksResource, type WebhooksStorage, activeSecrets, assertValid, attemptDelivery, buildOpenApiDocument, buildSignedRequest, createDispatcher, createFetchHandler, createPortalToken, createWebhooksCore, defaultHookErrorReporter, durationToMs, emitDelivery, generateSecret, hasDeliveryQueue, idPattern, isCompareAndSwap, isMutatingAction, isTerminalDeliveryStatus, isTransactional, isWatchable, keys_d_exports as keys, newId, normalizeHooks, parseDurationList, pgListenNotify, requirePeer, runAfter, runBefore, sign, signatureHeader, validateApplication, validateEndpoint, validateEndpointUrl, validateEventType, verify, verifyPortalToken, withWatch };
366
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,61 @@
1
+ import { SCHEMA_VERSION, isTerminalDeliveryStatus } from "./schema.mjs";
2
+ import { SECRET_PREFIX, WebhookVerificationError, generateSecret, sign, signatureHeader, verify } from "./signing.mjs";
3
+ import { A as attemptDelivery, C as newId, D as DEFAULT_ATTEMPT_TIMEOUT_MS, E as emitDelivery, O as DEFAULT_RESPONSE_BODY_LIMIT, T as parseDurationList, _ as normalizeHooks, a as ReadonlyError, b as VERSION, c as RESERVED_HEADERS, d as validateApplication, f as validateEndpoint, g as defaultHookErrorReporter, h as HookDeniedError, i as PayloadTooLargeError, j as buildSignedRequest, k as activeSecrets, l as ValidationError, m as validateEventType, n as IdempotencyConflictError, o as createWebhooksCore, p as validateEndpointUrl, r as NotFoundError, s as KEY_REGEX, t as ConflictError, u as assertValid, v as runAfter, w as durationToMs, x as idPattern, y as runBefore } from "./core-CMpnmI5Q.mjs";
4
+ import { x as keys_exports } from "./keys-Byyj4quQ.mjs";
5
+ import { a as requirePeer, i as isWatchable, n as isCompareAndSwap, r as isTransactional, t as hasDeliveryQueue } from "./contract-BEhDcd_5.mjs";
6
+ import { n as createDispatcher, t as DEFAULT_RETRY_SCHEDULE } from "./dispatcher-Coubwrka.mjs";
7
+ import { n as isMutatingAction, t as MUTATING_ACTIONS } from "./contract-9XpcwcCn.mjs";
8
+ import { a as PORTAL_TOKEN_PREFIX, c as verifyPortalToken, i as buildOpenApiDocument, o as PortalTokenError, r as DEFAULT_PORTAL_ACTIONS, s as createPortalToken, t as createFetchHandler } from "./create-fetch-handler-BIdk9P30.mjs";
9
+ //#region src/storage/watch.ts
10
+ /**
11
+ * Wrap a storage so it implements {@link WatchableWebhooksStorage} using
12
+ * `subscribe` as the change source. The wrapper delegates all reads/writes to
13
+ * `storage` and only adds `watch`; keys not under the watched `prefix` are
14
+ * filtered out. A notification without a key is delivered as a change to the
15
+ * prefix itself (enough to trigger a refresh).
16
+ */
17
+ function withWatch(storage, subscribe) {
18
+ const watchable = Object.create(storage);
19
+ watchable.watch = (prefix, callback) => {
20
+ const notify = (key) => {
21
+ if (key !== void 0 && key !== "" && !key.startsWith(prefix)) return;
22
+ callback({
23
+ type: "update",
24
+ key: key && key !== "" ? key : prefix
25
+ });
26
+ };
27
+ return Promise.resolve(subscribe(notify));
28
+ };
29
+ return watchable;
30
+ }
31
+ /**
32
+ * A {@link WatchSubscribe} backed by Postgres `LISTEN`/`NOTIFY`. Pair with
33
+ * {@link withWatch} for the postgres or drizzle-over-pg adapters. Your migration
34
+ * owns the trigger that `pg_notify`s `channel` with the changed key as payload;
35
+ * this only `LISTEN`s.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { Client } from "pg";
40
+ * const listener = new Client({ connectionString: process.env.DATABASE_URL });
41
+ * await listener.connect();
42
+ * const storage = withWatch(base, pgListenNotify(listener, "xtandard_webhooks"));
43
+ * ```
44
+ */
45
+ function pgListenNotify(client, channel = "xtandard_webhooks") {
46
+ return async (notify) => {
47
+ const listener = (msg) => {
48
+ if (msg.channel === channel) notify(msg.payload);
49
+ };
50
+ client.on("notification", listener);
51
+ await client.query(`LISTEN "${channel.replace(/"/g, "\"\"")}"`);
52
+ return async () => {
53
+ client.removeListener("notification", listener);
54
+ await client.query(`UNLISTEN "${channel.replace(/"/g, "\"\"")}"`);
55
+ };
56
+ };
57
+ }
58
+ //#endregion
59
+ export { ConflictError, DEFAULT_ATTEMPT_TIMEOUT_MS, DEFAULT_PORTAL_ACTIONS, DEFAULT_RESPONSE_BODY_LIMIT, DEFAULT_RETRY_SCHEDULE, HookDeniedError, IdempotencyConflictError, KEY_REGEX, MUTATING_ACTIONS, NotFoundError, PORTAL_TOKEN_PREFIX, PayloadTooLargeError, PortalTokenError, RESERVED_HEADERS, ReadonlyError, SCHEMA_VERSION, SECRET_PREFIX, VERSION, ValidationError, WebhookVerificationError, activeSecrets, assertValid, attemptDelivery, buildOpenApiDocument, buildSignedRequest, createDispatcher, createFetchHandler, createPortalToken, createWebhooksCore, defaultHookErrorReporter, durationToMs, emitDelivery, generateSecret, hasDeliveryQueue, idPattern, isCompareAndSwap, isMutatingAction, isTerminalDeliveryStatus, isTransactional, isWatchable, keys_exports as keys, newId, normalizeHooks, parseDurationList, pgListenNotify, requirePeer, runAfter, runBefore, sign, signatureHeader, validateApplication, validateEndpoint, validateEndpointUrl, validateEventType, verify, verifyPortalToken, withWatch };
60
+
61
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/storage/watch.ts"],"sourcesContent":["/**\n * Composable change-notifications. `watch` is **orthogonal** to which storage\n * you use, so rather than bake it into each adapter, {@link withWatch} wraps\n * *any* {@link WebhooksStorage} with a {@link WatchSubscribe} source you provide\n * — Postgres `LISTEN`/`NOTIFY`, Redis pub/sub, an ORM after-write hook, a\n * websocket, an `EventEmitter`, etc.\n *\n * This gives `watch` to adapters that don't implement it themselves (postgres,\n * drizzle, mongodb, unstorage, cloudflare-kv, …), driven by whatever change\n * signal your infrastructure already has.\n *\n * @example\n * ```ts\n * import { withWatch } from \"@xtandard/webhooks\";\n * import { createDrizzleStorage } from \"@xtandard/webhooks/storage/drizzle\";\n *\n * const storage = withWatch(createDrizzleStorage({ db, table }), (notify) => {\n * const sub = redis.subscribe(\"webhook-changes\", (m) => notify(m.key));\n * return () => sub.unsubscribe();\n * });\n * ```\n *\n * @module\n */\n\nimport type { StorageChangeEvent, WatchableWebhooksStorage, WebhooksStorage } from \"./contract.ts\";\n\n/**\n * A change source. Called with a `notify` callback — wire your source to invoke\n * it (optionally with the changed key) on every change — and return an\n * unsubscribe function (sync or async).\n */\nexport type WatchSubscribe = (\n notify: (key?: string) => void,\n) => (() => void | Promise<void>) | Promise<() => void | Promise<void>>;\n\n/**\n * Wrap a storage so it implements {@link WatchableWebhooksStorage} using\n * `subscribe` as the change source. The wrapper delegates all reads/writes to\n * `storage` and only adds `watch`; keys not under the watched `prefix` are\n * filtered out. A notification without a key is delivered as a change to the\n * prefix itself (enough to trigger a refresh).\n */\nexport function withWatch<S extends WebhooksStorage>(\n storage: S,\n subscribe: WatchSubscribe,\n): S & WatchableWebhooksStorage {\n const watchable = Object.create(storage) as S & WatchableWebhooksStorage;\n watchable.watch = (prefix, callback) => {\n const notify = (key?: string): void => {\n if (key !== undefined && key !== \"\" && !key.startsWith(prefix)) return;\n callback({\n type: \"update\",\n key: key && key !== \"\" ? key : prefix,\n } satisfies StorageChangeEvent);\n };\n return Promise.resolve(subscribe(notify));\n };\n return watchable;\n}\n\n/**\n * A dedicated notification client for {@link pgListenNotify} — satisfied by a\n * `pg` `Client`. `Pool` won't do (it rotates connections); use a `Client` you\n * `connect()`.\n */\nexport interface PgNotificationClient {\n query(sql: string): Promise<unknown>;\n on(event: \"notification\", listener: (msg: { channel: string; payload?: string }) => void): void;\n removeListener(\n event: \"notification\",\n listener: (msg: { channel: string; payload?: string }) => void,\n ): void;\n}\n\n/**\n * A {@link WatchSubscribe} backed by Postgres `LISTEN`/`NOTIFY`. Pair with\n * {@link withWatch} for the postgres or drizzle-over-pg adapters. Your migration\n * owns the trigger that `pg_notify`s `channel` with the changed key as payload;\n * this only `LISTEN`s.\n *\n * @example\n * ```ts\n * import { Client } from \"pg\";\n * const listener = new Client({ connectionString: process.env.DATABASE_URL });\n * await listener.connect();\n * const storage = withWatch(base, pgListenNotify(listener, \"xtandard_webhooks\"));\n * ```\n */\nexport function pgListenNotify(\n client: PgNotificationClient,\n channel = \"xtandard_webhooks\",\n): WatchSubscribe {\n return async (notify) => {\n const listener = (msg: { channel: string; payload?: string }): void => {\n if (msg.channel === channel) notify(msg.payload);\n };\n client.on(\"notification\", listener);\n await client.query(`LISTEN \"${channel.replace(/\"/g, '\"\"')}\"`);\n return async () => {\n client.removeListener(\"notification\", listener);\n await client.query(`UNLISTEN \"${channel.replace(/\"/g, '\"\"')}\"`);\n };\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2CA,SAAgB,UACd,SACA,WAC8B;CAC9B,MAAM,YAAY,OAAO,OAAO,OAAO;CACvC,UAAU,SAAS,QAAQ,aAAa;EACtC,MAAM,UAAU,QAAuB;GACrC,IAAI,QAAQ,KAAA,KAAa,QAAQ,MAAM,CAAC,IAAI,WAAW,MAAM,GAAG;GAChE,SAAS;IACP,MAAM;IACN,KAAK,OAAO,QAAQ,KAAK,MAAM;GACjC,CAA8B;EAChC;EACA,OAAO,QAAQ,QAAQ,UAAU,MAAM,CAAC;CAC1C;CACA,OAAO;AACT;;;;;;;;;;;;;;;AA8BA,SAAgB,eACd,QACA,UAAU,qBACM;CAChB,OAAO,OAAO,WAAW;EACvB,MAAM,YAAY,QAAqD;GACrE,IAAI,IAAI,YAAY,SAAS,OAAO,IAAI,OAAO;EACjD;EACA,OAAO,GAAG,gBAAgB,QAAQ;EAClC,MAAM,OAAO,MAAM,WAAW,QAAQ,QAAQ,MAAM,MAAI,EAAE,EAAE;EAC5D,OAAO,YAAY;GACjB,OAAO,eAAe,gBAAgB,QAAQ;GAC9C,MAAM,OAAO,MAAM,aAAa,QAAQ,QAAQ,MAAM,MAAI,EAAE,EAAE;EAChE;CACF;AACF"}
@@ -0,0 +1,111 @@
1
+ import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
+ //#region src/keys.ts
3
+ var keys_exports = /* @__PURE__ */ __exportAll({
4
+ RESERVED_APPLICATION_KEYS: () => RESERVED_APPLICATION_KEYS,
5
+ ROOT: () => "whk",
6
+ applicationMetaKey: () => applicationMetaKey,
7
+ applicationPrefix: () => applicationPrefix,
8
+ applicationsKey: () => applicationsKey,
9
+ attemptKey: () => attemptKey,
10
+ attemptsPrefix: () => attemptsPrefix,
11
+ auditLogKey: () => auditLogKey,
12
+ byEndpointKey: () => byEndpointKey,
13
+ byEndpointPrefix: () => byEndpointPrefix,
14
+ byMessageKey: () => byMessageKey,
15
+ byMessagePrefix: () => byMessagePrefix,
16
+ deliveriesPrefix: () => deliveriesPrefix,
17
+ deliveryKey: () => deliveryKey,
18
+ dueKey: () => dueKey,
19
+ duePrefix: () => duePrefix,
20
+ endpointKey: () => endpointKey,
21
+ endpointsKey: () => endpointsKey,
22
+ eventTypeKey: () => eventTypeKey,
23
+ eventTypesKey: () => eventTypesKey,
24
+ globalAuditLogKey: () => globalAuditLogKey,
25
+ idempotencyKey: () => idempotencyKey,
26
+ lastSegment: () => lastSegment,
27
+ messageKey: () => messageKey,
28
+ messagesPrefix: () => messagesPrefix,
29
+ parseDueKey: () => parseDueKey
30
+ });
31
+ /**
32
+ * Application keys that would collide with global index keys under the shared
33
+ * root. Rejected by validation.
34
+ */
35
+ const RESERVED_APPLICATION_KEYS = [
36
+ "applications",
37
+ "event-types",
38
+ "audit-log"
39
+ ];
40
+ /** Global audit log (event-type actions, which are not application-scoped). */
41
+ const globalAuditLogKey = () => `whk/audit-log`;
42
+ /** Index key listing all known application keys. */
43
+ const applicationsKey = () => `whk/applications`;
44
+ /** Metadata for a single application. */
45
+ const applicationMetaKey = (applicationKey) => `whk/${applicationKey}/metadata`;
46
+ /** Index key listing all event type names (global catalog). */
47
+ const eventTypesKey = () => `whk/event-types`;
48
+ /** A single event type record. */
49
+ const eventTypeKey = (name) => `whk/event-types/${name}`;
50
+ /** Index key listing an application's endpoint ids. */
51
+ const endpointsKey = (applicationKey) => `whk/${applicationKey}/endpoints`;
52
+ /** A single endpoint record. */
53
+ const endpointKey = (applicationKey, endpointId) => `whk/${applicationKey}/endpoints/${endpointId}`;
54
+ /** Prefix under which an application's messages live. */
55
+ const messagesPrefix = (applicationKey) => `whk/${applicationKey}/messages/`;
56
+ /** A single message record. */
57
+ const messageKey = (applicationKey, messageId) => `${messagesPrefix(applicationKey)}${messageId}`;
58
+ /** Maps a caller-supplied idempotency key to the message id it produced. */
59
+ const idempotencyKey = (applicationKey, key) => `whk/${applicationKey}/idempotency/${key}`;
60
+ /** Prefix under which an application's deliveries live. */
61
+ const deliveriesPrefix = (applicationKey) => `whk/${applicationKey}/deliveries/`;
62
+ /** A single delivery record. */
63
+ const deliveryKey = (applicationKey, deliveryId) => `${deliveriesPrefix(applicationKey)}${deliveryId}`;
64
+ /** Prefix under which one delivery's attempts live. */
65
+ const attemptsPrefix = (applicationKey, deliveryId) => `whk/${applicationKey}/attempts/${deliveryId}/`;
66
+ /**
67
+ * A single attempt record. The attempt number is zero-padded to four digits so
68
+ * lexicographic key order equals attempt order.
69
+ */
70
+ const attemptKey = (applicationKey, deliveryId, attemptNumber) => `${attemptsPrefix(applicationKey, deliveryId)}${String(attemptNumber).padStart(4, "0")}`;
71
+ /** Prefix of an application's due index (the dispatcher's work queue). */
72
+ const duePrefix = (applicationKey) => `whk/${applicationKey}/due/`;
73
+ /**
74
+ * A due-index entry: 13-digit zero-padded epoch milliseconds, then the delivery
75
+ * id, `~`-separated. Sorting keys lexicographically sorts entries chronologically.
76
+ */
77
+ const dueKey = (applicationKey, dueAtMillis, deliveryId) => `${duePrefix(applicationKey)}${String(dueAtMillis).padStart(13, "0")}~${deliveryId}`;
78
+ /** Parse a due-index key back into its due-time and delivery id. */
79
+ const parseDueKey = (key) => {
80
+ const last = lastSegment(key);
81
+ const sep = last.indexOf("~");
82
+ if (sep === -1) return null;
83
+ const millis = Number(last.slice(0, sep));
84
+ const deliveryId = last.slice(sep + 1);
85
+ if (!Number.isFinite(millis) || deliveryId.length === 0) return null;
86
+ return {
87
+ dueAtMillis: millis,
88
+ deliveryId
89
+ };
90
+ };
91
+ /** Prefix of the message → deliveries reverse index. */
92
+ const byMessagePrefix = (applicationKey, messageId) => `whk/${applicationKey}/by-message/${messageId}/`;
93
+ /** One entry in the message → deliveries reverse index. */
94
+ const byMessageKey = (applicationKey, messageId, deliveryId) => `${byMessagePrefix(applicationKey, messageId)}${deliveryId}`;
95
+ /** Prefix of the endpoint → deliveries reverse index. */
96
+ const byEndpointPrefix = (applicationKey, endpointId) => `whk/${applicationKey}/by-endpoint/${endpointId}/`;
97
+ /** One entry in the endpoint → deliveries reverse index. */
98
+ const byEndpointKey = (applicationKey, endpointId, deliveryId) => `${byEndpointPrefix(applicationKey, endpointId)}${deliveryId}`;
99
+ /** Append-only audit log for an application, stored as an ordered `AuditEntry[]`. */
100
+ const auditLogKey = (applicationKey) => `whk/${applicationKey}/audit-log`;
101
+ /** Prefix under which everything belonging to one application lives. */
102
+ const applicationPrefix = (applicationKey) => `whk/${applicationKey}/`;
103
+ /** Extract the trailing segment (endpoint id / message id / …) from a key. */
104
+ const lastSegment = (key) => {
105
+ const parts = key.split("/");
106
+ return parts[parts.length - 1] ?? "";
107
+ };
108
+ //#endregion
109
+ export { messageKey as C, lastSegment as S, parseDueKey as T, eventTypeKey as _, attemptKey as a, idempotencyKey as b, byEndpointKey as c, byMessagePrefix as d, deliveriesPrefix as f, endpointsKey as g, endpointKey as h, applicationsKey as i, byEndpointPrefix as l, dueKey as m, applicationMetaKey as n, attemptsPrefix as o, deliveryKey as p, applicationPrefix as r, auditLogKey as s, RESERVED_APPLICATION_KEYS as t, byMessageKey as u, eventTypesKey as v, messagesPrefix as w, keys_exports as x, globalAuditLogKey as y };
110
+
111
+ //# sourceMappingURL=keys-Byyj4quQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys-Byyj4quQ.mjs","names":[],"sources":["../src/keys.ts"],"sourcesContent":["/**\n * Storage key layout for `@xtandard/webhooks`.\n *\n * Keys are namespaced by application so a single storage backend can host the\n * whole control plane. Pure string helpers — no dependencies.\n *\n * ```txt\n * whk/applications -> string[] index\n * whk/{app}/metadata -> Application\n * whk/event-types -> string[] index (global)\n * whk/event-types/{name} -> EventType\n * whk/{app}/endpoints -> string[] index\n * whk/{app}/endpoints/{id} -> Endpoint\n * whk/{app}/messages/{id} -> Message\n * whk/{app}/idempotency/{key} -> message id\n * whk/{app}/deliveries/{id} -> Delivery\n * whk/{app}/attempts/{deliveryId}/{n} -> DeliveryAttempt (n zero-padded)\n * whk/{app}/due/{dueAtMillis}~{deliveryId} -> DueEntry (13-digit zero-padded millis)\n * whk/{app}/by-message/{messageId}/{deliveryId} -> 1 (reverse index)\n * whk/{app}/by-endpoint/{endpointId}/{deliveryId} -> 1 (reverse index)\n * whk/{app}/audit-log -> AuditEntry[] (single ordered array)\n * ```\n *\n * The due index is the dispatcher's generic work queue: 13-digit zero-padded\n * milliseconds make lexicographic key order chronological, so a sorted\n * `getKeys(duePrefix(app))` scan yields deliveries in due order.\n *\n * @module\n */\n\n/** Root namespace segment for all keys. */\nexport const ROOT = \"whk\";\n\n/**\n * Application keys that would collide with global index keys under the shared\n * root. Rejected by validation.\n */\nexport const RESERVED_APPLICATION_KEYS = [\"applications\", \"event-types\", \"audit-log\"] as const;\n\n/** Global audit log (event-type actions, which are not application-scoped). */\nexport const globalAuditLogKey = () => `${ROOT}/audit-log`;\n\n/** Index key listing all known application keys. */\nexport const applicationsKey = () => `${ROOT}/applications`;\n\n/** Metadata for a single application. */\nexport const applicationMetaKey = (applicationKey: string) => `${ROOT}/${applicationKey}/metadata`;\n\n/** Index key listing all event type names (global catalog). */\nexport const eventTypesKey = () => `${ROOT}/event-types`;\n\n/** A single event type record. */\nexport const eventTypeKey = (name: string) => `${ROOT}/event-types/${name}`;\n\n/** Index key listing an application's endpoint ids. */\nexport const endpointsKey = (applicationKey: string) => `${ROOT}/${applicationKey}/endpoints`;\n\n/** A single endpoint record. */\nexport const endpointKey = (applicationKey: string, endpointId: string) =>\n `${ROOT}/${applicationKey}/endpoints/${endpointId}`;\n\n/** Prefix under which an application's messages live. */\nexport const messagesPrefix = (applicationKey: string) => `${ROOT}/${applicationKey}/messages/`;\n\n/** A single message record. */\nexport const messageKey = (applicationKey: string, messageId: string) =>\n `${messagesPrefix(applicationKey)}${messageId}`;\n\n/** Maps a caller-supplied idempotency key to the message id it produced. */\nexport const idempotencyKey = (applicationKey: string, key: string) =>\n `${ROOT}/${applicationKey}/idempotency/${key}`;\n\n/** Prefix under which an application's deliveries live. */\nexport const deliveriesPrefix = (applicationKey: string) => `${ROOT}/${applicationKey}/deliveries/`;\n\n/** A single delivery record. */\nexport const deliveryKey = (applicationKey: string, deliveryId: string) =>\n `${deliveriesPrefix(applicationKey)}${deliveryId}`;\n\n/** Prefix under which one delivery's attempts live. */\nexport const attemptsPrefix = (applicationKey: string, deliveryId: string) =>\n `${ROOT}/${applicationKey}/attempts/${deliveryId}/`;\n\n/**\n * A single attempt record. The attempt number is zero-padded to four digits so\n * lexicographic key order equals attempt order.\n */\nexport const attemptKey = (applicationKey: string, deliveryId: string, attemptNumber: number) =>\n `${attemptsPrefix(applicationKey, deliveryId)}${String(attemptNumber).padStart(4, \"0\")}`;\n\n/** Prefix of an application's due index (the dispatcher's work queue). */\nexport const duePrefix = (applicationKey: string) => `${ROOT}/${applicationKey}/due/`;\n\n/** Value stored at a due-index key. */\nexport interface DueEntry {\n app: string;\n deliveryId: string;\n}\n\n/**\n * A due-index entry: 13-digit zero-padded epoch milliseconds, then the delivery\n * id, `~`-separated. Sorting keys lexicographically sorts entries chronologically.\n */\nexport const dueKey = (applicationKey: string, dueAtMillis: number, deliveryId: string) =>\n `${duePrefix(applicationKey)}${String(dueAtMillis).padStart(13, \"0\")}~${deliveryId}`;\n\n/** Parse a due-index key back into its due-time and delivery id. */\nexport const parseDueKey = (key: string): { dueAtMillis: number; deliveryId: string } | null => {\n const last = lastSegment(key);\n const sep = last.indexOf(\"~\");\n if (sep === -1) return null;\n const millis = Number(last.slice(0, sep));\n const deliveryId = last.slice(sep + 1);\n if (!Number.isFinite(millis) || deliveryId.length === 0) return null;\n return { dueAtMillis: millis, deliveryId };\n};\n\n/** Prefix of the message → deliveries reverse index. */\nexport const byMessagePrefix = (applicationKey: string, messageId: string) =>\n `${ROOT}/${applicationKey}/by-message/${messageId}/`;\n\n/** One entry in the message → deliveries reverse index. */\nexport const byMessageKey = (applicationKey: string, messageId: string, deliveryId: string) =>\n `${byMessagePrefix(applicationKey, messageId)}${deliveryId}`;\n\n/** Prefix of the endpoint → deliveries reverse index. */\nexport const byEndpointPrefix = (applicationKey: string, endpointId: string) =>\n `${ROOT}/${applicationKey}/by-endpoint/${endpointId}/`;\n\n/** One entry in the endpoint → deliveries reverse index. */\nexport const byEndpointKey = (applicationKey: string, endpointId: string, deliveryId: string) =>\n `${byEndpointPrefix(applicationKey, endpointId)}${deliveryId}`;\n\n/** Append-only audit log for an application, stored as an ordered `AuditEntry[]`. */\nexport const auditLogKey = (applicationKey: string) => `${ROOT}/${applicationKey}/audit-log`;\n\n/** Prefix under which everything belonging to one application lives. */\nexport const applicationPrefix = (applicationKey: string) => `${ROOT}/${applicationKey}/`;\n\n/** Extract the trailing segment (endpoint id / message id / …) from a key. */\nexport const lastSegment = (key: string): string => {\n const parts = key.split(\"/\");\n return parts[parts.length - 1] ?? \"\";\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,4BAA4B;CAAC;CAAgB;CAAe;AAAW;;AAGpF,MAAa,0BAA0B;;AAGvC,MAAa,wBAAwB;;AAGrC,MAAa,sBAAsB,mBAA2B,OAAW,eAAe;;AAGxF,MAAa,sBAAsB;;AAGnC,MAAa,gBAAgB,SAAiB,mBAAuB;;AAGrE,MAAa,gBAAgB,mBAA2B,OAAW,eAAe;;AAGlF,MAAa,eAAe,gBAAwB,eAClD,OAAW,eAAe,aAAa;;AAGzC,MAAa,kBAAkB,mBAA2B,OAAW,eAAe;;AAGpF,MAAa,cAAc,gBAAwB,cACjD,GAAG,eAAe,cAAc,IAAI;;AAGtC,MAAa,kBAAkB,gBAAwB,QACrD,OAAW,eAAe,eAAe;;AAG3C,MAAa,oBAAoB,mBAA2B,OAAW,eAAe;;AAGtF,MAAa,eAAe,gBAAwB,eAClD,GAAG,iBAAiB,cAAc,IAAI;;AAGxC,MAAa,kBAAkB,gBAAwB,eACrD,OAAW,eAAe,YAAY,WAAW;;;;;AAMnD,MAAa,cAAc,gBAAwB,YAAoB,kBACrE,GAAG,eAAe,gBAAgB,UAAU,IAAI,OAAO,aAAa,EAAE,SAAS,GAAG,GAAG;;AAGvF,MAAa,aAAa,mBAA2B,OAAW,eAAe;;;;;AAY/E,MAAa,UAAU,gBAAwB,aAAqB,eAClE,GAAG,UAAU,cAAc,IAAI,OAAO,WAAW,EAAE,SAAS,IAAI,GAAG,EAAE,GAAG;;AAG1E,MAAa,eAAe,QAAoE;CAC9F,MAAM,OAAO,YAAY,GAAG;CAC5B,MAAM,MAAM,KAAK,QAAQ,GAAG;CAC5B,IAAI,QAAQ,IAAI,OAAO;CACvB,MAAM,SAAS,OAAO,KAAK,MAAM,GAAG,GAAG,CAAC;CACxC,MAAM,aAAa,KAAK,MAAM,MAAM,CAAC;CACrC,IAAI,CAAC,OAAO,SAAS,MAAM,KAAK,WAAW,WAAW,GAAG,OAAO;CAChE,OAAO;EAAE,aAAa;EAAQ;CAAW;AAC3C;;AAGA,MAAa,mBAAmB,gBAAwB,cACtD,OAAW,eAAe,cAAc,UAAU;;AAGpD,MAAa,gBAAgB,gBAAwB,WAAmB,eACtE,GAAG,gBAAgB,gBAAgB,SAAS,IAAI;;AAGlD,MAAa,oBAAoB,gBAAwB,eACvD,OAAW,eAAe,eAAe,WAAW;;AAGtD,MAAa,iBAAiB,gBAAwB,YAAoB,eACxE,GAAG,iBAAiB,gBAAgB,UAAU,IAAI;;AAGpD,MAAa,eAAe,mBAA2B,OAAW,eAAe;;AAGjF,MAAa,qBAAqB,mBAA2B,OAAW,eAAe;;AAGvF,MAAa,eAAe,QAAwB;CAClD,MAAM,QAAQ,IAAI,MAAM,GAAG;CAC3B,OAAO,MAAM,MAAM,SAAS,MAAM;AACpC"}