@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,137 @@
1
+ const require_keys = require("./keys-FiKpaVHX.cjs");
2
+ //#region src/storage/memory.ts
3
+ /**
4
+ * In-memory storage adapter. Zero deps. Useful for tests, dev, and the demo.
5
+ * Values are deep-cloned on write and read so callers cannot mutate stored
6
+ * state by reference. Implements every optional capability: `watch`,
7
+ * `compareAndSwap`, and native `claimDue` (the delivery queue).
8
+ *
9
+ * @module
10
+ */
11
+ var memory_exports = /* @__PURE__ */ require_keys.__exportAll({ createMemoryStorage: () => createMemoryStorage });
12
+ const clone = (value) => value === void 0 ? value : structuredClone(value);
13
+ function deepEqual(a, b) {
14
+ if (a === b) return true;
15
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
16
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
17
+ const aKeys = Object.keys(a);
18
+ const bKeys = Object.keys(b);
19
+ if (aKeys.length !== bKeys.length) return false;
20
+ return aKeys.every((k) => deepEqual(a[k], b[k]));
21
+ }
22
+ const DUE_KEY_RE = new RegExp(`^whk/[^/]+/due/`);
23
+ /**
24
+ * Create an in-memory {@link WebhooksStorage} with every optional capability.
25
+ * `watch` fires callbacks on the next microtask after a write/remove;
26
+ * `compareAndSwap` compares deep equality; `claimDue` scans the due index
27
+ * across all applications and claims atomically (single-threaded JS makes the
28
+ * scan-and-write race-free in-process).
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { createMemoryStorage } from "@xtandard/webhooks/storage/memory";
33
+ *
34
+ * const storage = createMemoryStorage();
35
+ * ```
36
+ */
37
+ function createMemoryStorage(options = {}) {
38
+ const map = /* @__PURE__ */ new Map();
39
+ if (options.initial) for (const [k, val] of Object.entries(options.initial)) map.set(k, clone(val));
40
+ const watchers = /* @__PURE__ */ new Set();
41
+ const notify = (event) => {
42
+ for (const w of watchers) if (event.key.startsWith(w.prefix)) queueMicrotask(() => w.cb(event));
43
+ };
44
+ const set = (key, value) => {
45
+ map.set(key, clone(value));
46
+ notify({
47
+ type: "update",
48
+ key
49
+ });
50
+ };
51
+ const remove = (key) => {
52
+ if (map.delete(key)) notify({
53
+ type: "remove",
54
+ key
55
+ });
56
+ };
57
+ return {
58
+ async getItem(key) {
59
+ return map.has(key) ? clone(map.get(key)) : null;
60
+ },
61
+ async setItem(key, value) {
62
+ set(key, value);
63
+ },
64
+ async removeItem(key) {
65
+ remove(key);
66
+ },
67
+ async getKeys(prefix) {
68
+ const out = [];
69
+ for (const k of map.keys()) if (k.startsWith(prefix)) out.push(k);
70
+ return out;
71
+ },
72
+ async watch(prefix, cb) {
73
+ const entry = {
74
+ prefix,
75
+ cb
76
+ };
77
+ watchers.add(entry);
78
+ return () => {
79
+ watchers.delete(entry);
80
+ };
81
+ },
82
+ async compareAndSwap(input) {
83
+ if (!deepEqual(map.has(input.key) ? map.get(input.key) : null, input.expected)) return false;
84
+ set(input.key, input.next);
85
+ return true;
86
+ },
87
+ async claimDue(input) {
88
+ const nowMillis = Date.parse(input.now);
89
+ const due = [];
90
+ for (const k of map.keys()) if (DUE_KEY_RE.test(k)) due.push(k);
91
+ due.sort();
92
+ const claimed = [];
93
+ for (const key of due) {
94
+ if (claimed.length >= input.limit) break;
95
+ const parsed = require_keys.parseDueKey(key);
96
+ if (!parsed || parsed.dueAtMillis > nowMillis) continue;
97
+ const entry = map.get(key);
98
+ if (!entry) continue;
99
+ const dKey = require_keys.deliveryKey(entry.app, entry.deliveryId);
100
+ const delivery = map.get(dKey);
101
+ if (!delivery || delivery.status === "succeeded" || delivery.status === "failed") {
102
+ remove(key);
103
+ continue;
104
+ }
105
+ const leaseExpired = delivery.status === "delivering" && (!delivery.leaseUntil || Date.parse(delivery.leaseUntil) <= nowMillis);
106
+ if (delivery.status !== "pending" && !leaseExpired) continue;
107
+ const leaseUntil = new Date(nowMillis + input.leaseMs).toISOString();
108
+ const next = {
109
+ ...delivery,
110
+ status: "delivering",
111
+ leaseUntil,
112
+ updatedAt: input.now
113
+ };
114
+ set(dKey, next);
115
+ remove(key);
116
+ set(require_keys.dueKey(entry.app, nowMillis + input.leaseMs, entry.deliveryId), entry);
117
+ claimed.push(clone(next));
118
+ }
119
+ return claimed;
120
+ }
121
+ };
122
+ }
123
+ //#endregion
124
+ Object.defineProperty(exports, "createMemoryStorage", {
125
+ enumerable: true,
126
+ get: function() {
127
+ return createMemoryStorage;
128
+ }
129
+ });
130
+ Object.defineProperty(exports, "memory_exports", {
131
+ enumerable: true,
132
+ get: function() {
133
+ return memory_exports;
134
+ }
135
+ });
136
+
137
+ //# sourceMappingURL=memory-8Ef-PL5a.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-8Ef-PL5a.cjs","names":["parseDueKey","deliveryKey","dueKey"],"sources":["../src/storage/memory.ts"],"sourcesContent":["/**\n * In-memory storage adapter. Zero deps. Useful for tests, dev, and the demo.\n * Values are deep-cloned on write and read so callers cannot mutate stored\n * state by reference. Implements every optional capability: `watch`,\n * `compareAndSwap`, and native `claimDue` (the delivery queue).\n *\n * @module\n */\n\nimport { dueKey, deliveryKey, parseDueKey, ROOT, type DueEntry } from \"../keys.ts\";\nimport type { Delivery } from \"../schema.ts\";\nimport type {\n CompareAndSwapWebhooksStorage,\n DeliveryQueueStorage,\n StorageChangeEvent,\n WatchableWebhooksStorage,\n WebhooksStorage,\n} from \"./contract.ts\";\n\n/** Options for {@link createMemoryStorage}. */\nexport interface MemoryStorageOptions {\n /** Optional seed data (key → value). */\n initial?: Record<string, unknown>;\n}\n\n/** The full capability set the memory adapter implements. */\nexport type MemoryWebhooksStorage = WatchableWebhooksStorage &\n CompareAndSwapWebhooksStorage &\n DeliveryQueueStorage;\n\nconst clone = <T>(value: T): T => (value === undefined ? value : (structuredClone(value) as T));\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((k) =>\n deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]),\n );\n}\n\nconst DUE_KEY_RE = new RegExp(`^${ROOT}/[^/]+/due/`);\n\n/**\n * Create an in-memory {@link WebhooksStorage} with every optional capability.\n * `watch` fires callbacks on the next microtask after a write/remove;\n * `compareAndSwap` compares deep equality; `claimDue` scans the due index\n * across all applications and claims atomically (single-threaded JS makes the\n * scan-and-write race-free in-process).\n *\n * @example\n * ```ts\n * import { createMemoryStorage } from \"@xtandard/webhooks/storage/memory\";\n *\n * const storage = createMemoryStorage();\n * ```\n */\nexport function createMemoryStorage(options: MemoryStorageOptions = {}): MemoryWebhooksStorage {\n const map = new Map<string, unknown>();\n if (options.initial) {\n for (const [k, val] of Object.entries(options.initial)) map.set(k, clone(val));\n }\n\n const watchers = new Set<{ prefix: string; cb: (event: StorageChangeEvent) => void }>();\n const notify = (event: StorageChangeEvent) => {\n for (const w of watchers) {\n if (event.key.startsWith(w.prefix)) queueMicrotask(() => w.cb(event));\n }\n };\n\n const set = (key: string, value: unknown) => {\n map.set(key, clone(value));\n notify({ type: \"update\", key });\n };\n const remove = (key: string) => {\n if (map.delete(key)) notify({ type: \"remove\", key });\n };\n\n return {\n async getItem<T>(key: string): Promise<T | null> {\n return map.has(key) ? clone(map.get(key) as T) : null;\n },\n async setItem<T>(key: string, value: T): Promise<void> {\n set(key, value);\n },\n async removeItem(key: string): Promise<void> {\n remove(key);\n },\n async getKeys(prefix: string): Promise<string[]> {\n const out: string[] = [];\n for (const k of map.keys()) if (k.startsWith(prefix)) out.push(k);\n return out;\n },\n async watch(prefix, cb): Promise<() => void> {\n const entry = { prefix, cb };\n watchers.add(entry);\n return () => {\n watchers.delete(entry);\n };\n },\n async compareAndSwap<T>(input: { key: string; expected: T | null; next: T }): Promise<boolean> {\n const current = map.has(input.key) ? map.get(input.key) : null;\n if (!deepEqual(current, input.expected)) return false;\n set(input.key, input.next);\n return true;\n },\n async claimDue(input): Promise<Delivery[]> {\n const nowMillis = Date.parse(input.now);\n const due: string[] = [];\n for (const k of map.keys()) if (DUE_KEY_RE.test(k)) due.push(k);\n due.sort(); // lexicographic = chronological (13-digit zero-padded millis)\n\n const claimed: Delivery[] = [];\n for (const key of due) {\n if (claimed.length >= input.limit) break;\n const parsed = parseDueKey(key);\n if (!parsed || parsed.dueAtMillis > nowMillis) continue;\n const entry = map.get(key) as DueEntry | undefined;\n if (!entry) continue;\n const dKey = deliveryKey(entry.app, entry.deliveryId);\n const delivery = map.get(dKey) as Delivery | undefined;\n // Orphaned or already-terminal entries are garbage — sweep them.\n if (!delivery || delivery.status === \"succeeded\" || delivery.status === \"failed\") {\n remove(key);\n continue;\n }\n // Claimable = pending, or delivering with an expired lease.\n const leaseExpired =\n delivery.status === \"delivering\" &&\n (!delivery.leaseUntil || Date.parse(delivery.leaseUntil) <= nowMillis);\n if (delivery.status !== \"pending\" && !leaseExpired) continue;\n\n const leaseUntil = new Date(nowMillis + input.leaseMs).toISOString();\n const next: Delivery = {\n ...delivery,\n status: \"delivering\",\n leaseUntil,\n updatedAt: input.now,\n };\n set(dKey, next);\n // Reposition the due entry at the lease expiry so a crashed claimer's\n // work re-surfaces automatically.\n remove(key);\n set(dueKey(entry.app, nowMillis + input.leaseMs, entry.deliveryId), entry);\n claimed.push(clone(next));\n }\n return claimed;\n },\n } satisfies WebhooksStorage & MemoryWebhooksStorage;\n}\n"],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
+ import { T as parseDueKey, m as dueKey, p as deliveryKey } from "./keys-Byyj4quQ.mjs";
3
+ //#region src/storage/memory.ts
4
+ /**
5
+ * In-memory storage adapter. Zero deps. Useful for tests, dev, and the demo.
6
+ * Values are deep-cloned on write and read so callers cannot mutate stored
7
+ * state by reference. Implements every optional capability: `watch`,
8
+ * `compareAndSwap`, and native `claimDue` (the delivery queue).
9
+ *
10
+ * @module
11
+ */
12
+ var memory_exports = /* @__PURE__ */ __exportAll({ createMemoryStorage: () => createMemoryStorage });
13
+ const clone = (value) => value === void 0 ? value : structuredClone(value);
14
+ function deepEqual(a, b) {
15
+ if (a === b) return true;
16
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
17
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
18
+ const aKeys = Object.keys(a);
19
+ const bKeys = Object.keys(b);
20
+ if (aKeys.length !== bKeys.length) return false;
21
+ return aKeys.every((k) => deepEqual(a[k], b[k]));
22
+ }
23
+ const DUE_KEY_RE = new RegExp(`^whk/[^/]+/due/`);
24
+ /**
25
+ * Create an in-memory {@link WebhooksStorage} with every optional capability.
26
+ * `watch` fires callbacks on the next microtask after a write/remove;
27
+ * `compareAndSwap` compares deep equality; `claimDue` scans the due index
28
+ * across all applications and claims atomically (single-threaded JS makes the
29
+ * scan-and-write race-free in-process).
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { createMemoryStorage } from "@xtandard/webhooks/storage/memory";
34
+ *
35
+ * const storage = createMemoryStorage();
36
+ * ```
37
+ */
38
+ function createMemoryStorage(options = {}) {
39
+ const map = /* @__PURE__ */ new Map();
40
+ if (options.initial) for (const [k, val] of Object.entries(options.initial)) map.set(k, clone(val));
41
+ const watchers = /* @__PURE__ */ new Set();
42
+ const notify = (event) => {
43
+ for (const w of watchers) if (event.key.startsWith(w.prefix)) queueMicrotask(() => w.cb(event));
44
+ };
45
+ const set = (key, value) => {
46
+ map.set(key, clone(value));
47
+ notify({
48
+ type: "update",
49
+ key
50
+ });
51
+ };
52
+ const remove = (key) => {
53
+ if (map.delete(key)) notify({
54
+ type: "remove",
55
+ key
56
+ });
57
+ };
58
+ return {
59
+ async getItem(key) {
60
+ return map.has(key) ? clone(map.get(key)) : null;
61
+ },
62
+ async setItem(key, value) {
63
+ set(key, value);
64
+ },
65
+ async removeItem(key) {
66
+ remove(key);
67
+ },
68
+ async getKeys(prefix) {
69
+ const out = [];
70
+ for (const k of map.keys()) if (k.startsWith(prefix)) out.push(k);
71
+ return out;
72
+ },
73
+ async watch(prefix, cb) {
74
+ const entry = {
75
+ prefix,
76
+ cb
77
+ };
78
+ watchers.add(entry);
79
+ return () => {
80
+ watchers.delete(entry);
81
+ };
82
+ },
83
+ async compareAndSwap(input) {
84
+ if (!deepEqual(map.has(input.key) ? map.get(input.key) : null, input.expected)) return false;
85
+ set(input.key, input.next);
86
+ return true;
87
+ },
88
+ async claimDue(input) {
89
+ const nowMillis = Date.parse(input.now);
90
+ const due = [];
91
+ for (const k of map.keys()) if (DUE_KEY_RE.test(k)) due.push(k);
92
+ due.sort();
93
+ const claimed = [];
94
+ for (const key of due) {
95
+ if (claimed.length >= input.limit) break;
96
+ const parsed = parseDueKey(key);
97
+ if (!parsed || parsed.dueAtMillis > nowMillis) continue;
98
+ const entry = map.get(key);
99
+ if (!entry) continue;
100
+ const dKey = deliveryKey(entry.app, entry.deliveryId);
101
+ const delivery = map.get(dKey);
102
+ if (!delivery || delivery.status === "succeeded" || delivery.status === "failed") {
103
+ remove(key);
104
+ continue;
105
+ }
106
+ const leaseExpired = delivery.status === "delivering" && (!delivery.leaseUntil || Date.parse(delivery.leaseUntil) <= nowMillis);
107
+ if (delivery.status !== "pending" && !leaseExpired) continue;
108
+ const leaseUntil = new Date(nowMillis + input.leaseMs).toISOString();
109
+ const next = {
110
+ ...delivery,
111
+ status: "delivering",
112
+ leaseUntil,
113
+ updatedAt: input.now
114
+ };
115
+ set(dKey, next);
116
+ remove(key);
117
+ set(dueKey(entry.app, nowMillis + input.leaseMs, entry.deliveryId), entry);
118
+ claimed.push(clone(next));
119
+ }
120
+ return claimed;
121
+ }
122
+ };
123
+ }
124
+ //#endregion
125
+ export { memory_exports as n, createMemoryStorage as t };
126
+
127
+ //# sourceMappingURL=memory-BMsSSwqn.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-BMsSSwqn.mjs","names":[],"sources":["../src/storage/memory.ts"],"sourcesContent":["/**\n * In-memory storage adapter. Zero deps. Useful for tests, dev, and the demo.\n * Values are deep-cloned on write and read so callers cannot mutate stored\n * state by reference. Implements every optional capability: `watch`,\n * `compareAndSwap`, and native `claimDue` (the delivery queue).\n *\n * @module\n */\n\nimport { dueKey, deliveryKey, parseDueKey, ROOT, type DueEntry } from \"../keys.ts\";\nimport type { Delivery } from \"../schema.ts\";\nimport type {\n CompareAndSwapWebhooksStorage,\n DeliveryQueueStorage,\n StorageChangeEvent,\n WatchableWebhooksStorage,\n WebhooksStorage,\n} from \"./contract.ts\";\n\n/** Options for {@link createMemoryStorage}. */\nexport interface MemoryStorageOptions {\n /** Optional seed data (key → value). */\n initial?: Record<string, unknown>;\n}\n\n/** The full capability set the memory adapter implements. */\nexport type MemoryWebhooksStorage = WatchableWebhooksStorage &\n CompareAndSwapWebhooksStorage &\n DeliveryQueueStorage;\n\nconst clone = <T>(value: T): T => (value === undefined ? value : (structuredClone(value) as T));\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((k) =>\n deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]),\n );\n}\n\nconst DUE_KEY_RE = new RegExp(`^${ROOT}/[^/]+/due/`);\n\n/**\n * Create an in-memory {@link WebhooksStorage} with every optional capability.\n * `watch` fires callbacks on the next microtask after a write/remove;\n * `compareAndSwap` compares deep equality; `claimDue` scans the due index\n * across all applications and claims atomically (single-threaded JS makes the\n * scan-and-write race-free in-process).\n *\n * @example\n * ```ts\n * import { createMemoryStorage } from \"@xtandard/webhooks/storage/memory\";\n *\n * const storage = createMemoryStorage();\n * ```\n */\nexport function createMemoryStorage(options: MemoryStorageOptions = {}): MemoryWebhooksStorage {\n const map = new Map<string, unknown>();\n if (options.initial) {\n for (const [k, val] of Object.entries(options.initial)) map.set(k, clone(val));\n }\n\n const watchers = new Set<{ prefix: string; cb: (event: StorageChangeEvent) => void }>();\n const notify = (event: StorageChangeEvent) => {\n for (const w of watchers) {\n if (event.key.startsWith(w.prefix)) queueMicrotask(() => w.cb(event));\n }\n };\n\n const set = (key: string, value: unknown) => {\n map.set(key, clone(value));\n notify({ type: \"update\", key });\n };\n const remove = (key: string) => {\n if (map.delete(key)) notify({ type: \"remove\", key });\n };\n\n return {\n async getItem<T>(key: string): Promise<T | null> {\n return map.has(key) ? clone(map.get(key) as T) : null;\n },\n async setItem<T>(key: string, value: T): Promise<void> {\n set(key, value);\n },\n async removeItem(key: string): Promise<void> {\n remove(key);\n },\n async getKeys(prefix: string): Promise<string[]> {\n const out: string[] = [];\n for (const k of map.keys()) if (k.startsWith(prefix)) out.push(k);\n return out;\n },\n async watch(prefix, cb): Promise<() => void> {\n const entry = { prefix, cb };\n watchers.add(entry);\n return () => {\n watchers.delete(entry);\n };\n },\n async compareAndSwap<T>(input: { key: string; expected: T | null; next: T }): Promise<boolean> {\n const current = map.has(input.key) ? map.get(input.key) : null;\n if (!deepEqual(current, input.expected)) return false;\n set(input.key, input.next);\n return true;\n },\n async claimDue(input): Promise<Delivery[]> {\n const nowMillis = Date.parse(input.now);\n const due: string[] = [];\n for (const k of map.keys()) if (DUE_KEY_RE.test(k)) due.push(k);\n due.sort(); // lexicographic = chronological (13-digit zero-padded millis)\n\n const claimed: Delivery[] = [];\n for (const key of due) {\n if (claimed.length >= input.limit) break;\n const parsed = parseDueKey(key);\n if (!parsed || parsed.dueAtMillis > nowMillis) continue;\n const entry = map.get(key) as DueEntry | undefined;\n if (!entry) continue;\n const dKey = deliveryKey(entry.app, entry.deliveryId);\n const delivery = map.get(dKey) as Delivery | undefined;\n // Orphaned or already-terminal entries are garbage — sweep them.\n if (!delivery || delivery.status === \"succeeded\" || delivery.status === \"failed\") {\n remove(key);\n continue;\n }\n // Claimable = pending, or delivering with an expired lease.\n const leaseExpired =\n delivery.status === \"delivering\" &&\n (!delivery.leaseUntil || Date.parse(delivery.leaseUntil) <= nowMillis);\n if (delivery.status !== \"pending\" && !leaseExpired) continue;\n\n const leaseUntil = new Date(nowMillis + input.leaseMs).toISOString();\n const next: Delivery = {\n ...delivery,\n status: \"delivering\",\n leaseUntil,\n updatedAt: input.now,\n };\n set(dKey, next);\n // Reposition the due entry at the lease expiry so a crashed claimer's\n // work re-surfaces automatically.\n remove(key);\n set(dueKey(entry.app, nowMillis + input.leaseMs, entry.deliveryId), entry);\n claimed.push(clone(next));\n }\n return claimed;\n },\n } satisfies WebhooksStorage & MemoryWebhooksStorage;\n}\n"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { a as WatchableWebhooksStorage, n as DeliveryQueueStorage, t as CompareAndSwapWebhooksStorage } from "./contract-8h-Azxa5.cjs";
2
+
3
+ //#region src/storage/memory.d.ts
4
+ /** Options for {@link createMemoryStorage}. */
5
+ interface MemoryStorageOptions {
6
+ /** Optional seed data (key → value). */
7
+ initial?: Record<string, unknown>;
8
+ }
9
+ /** The full capability set the memory adapter implements. */
10
+ type MemoryWebhooksStorage = WatchableWebhooksStorage & CompareAndSwapWebhooksStorage & DeliveryQueueStorage;
11
+ /**
12
+ * Create an in-memory {@link WebhooksStorage} with every optional capability.
13
+ * `watch` fires callbacks on the next microtask after a write/remove;
14
+ * `compareAndSwap` compares deep equality; `claimDue` scans the due index
15
+ * across all applications and claims atomically (single-threaded JS makes the
16
+ * scan-and-write race-free in-process).
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { createMemoryStorage } from "@xtandard/webhooks/storage/memory";
21
+ *
22
+ * const storage = createMemoryStorage();
23
+ * ```
24
+ */
25
+ declare function createMemoryStorage(options?: MemoryStorageOptions): MemoryWebhooksStorage;
26
+ //#endregion
27
+ export { MemoryWebhooksStorage as n, createMemoryStorage as r, MemoryStorageOptions as t };
28
+ //# sourceMappingURL=memory-FnMJWCmB.d.cts.map
@@ -0,0 +1,28 @@
1
+ import { a as WatchableWebhooksStorage, n as DeliveryQueueStorage, t as CompareAndSwapWebhooksStorage } from "./contract-DhQ4JjGG.mjs";
2
+
3
+ //#region src/storage/memory.d.ts
4
+ /** Options for {@link createMemoryStorage}. */
5
+ interface MemoryStorageOptions {
6
+ /** Optional seed data (key → value). */
7
+ initial?: Record<string, unknown>;
8
+ }
9
+ /** The full capability set the memory adapter implements. */
10
+ type MemoryWebhooksStorage = WatchableWebhooksStorage & CompareAndSwapWebhooksStorage & DeliveryQueueStorage;
11
+ /**
12
+ * Create an in-memory {@link WebhooksStorage} with every optional capability.
13
+ * `watch` fires callbacks on the next microtask after a write/remove;
14
+ * `compareAndSwap` compares deep equality; `claimDue` scans the due index
15
+ * across all applications and claims atomically (single-threaded JS makes the
16
+ * scan-and-write race-free in-process).
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { createMemoryStorage } from "@xtandard/webhooks/storage/memory";
21
+ *
22
+ * const storage = createMemoryStorage();
23
+ * ```
24
+ */
25
+ declare function createMemoryStorage(options?: MemoryStorageOptions): MemoryWebhooksStorage;
26
+ //#endregion
27
+ export { MemoryWebhooksStorage as n, createMemoryStorage as r, MemoryStorageOptions as t };
28
+ //# sourceMappingURL=memory-qIvANEs_.d.mts.map
@@ -0,0 +1,108 @@
1
+ const require_keys = require("./keys-FiKpaVHX.cjs");
2
+ const require_contract = require("./contract-Bf1qguwt.cjs");
3
+ //#region src/storage/mongodb.ts
4
+ var mongodb_exports = /* @__PURE__ */ require_keys.__exportAll({ createMongoStorage: () => createMongoStorage });
5
+ /**
6
+ * Escape characters that are special in a regular expression so a literal
7
+ * prefix string can be embedded into an anchored `^...` match.
8
+ */
9
+ function escapeRegex(input) {
10
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+ /**
13
+ * Create a MongoDB-backed {@link MongoWebhooksStorage}. Connection is lazy: the
14
+ * client is created/connected on the first storage operation and reused
15
+ * thereafter, guarded by a single connection promise so concurrent calls connect
16
+ * once.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { createMongoStorage } from "@xtandard/webhooks/storage/mongodb";
21
+ *
22
+ * const storage = createMongoStorage({
23
+ * url: process.env.MONGODB_URL ?? "mongodb://localhost:27017",
24
+ * dbName: "myapp",
25
+ * collectionName: "webhooks_kv",
26
+ * });
27
+ *
28
+ * // Disconnect when the process exits:
29
+ * // process.on("SIGTERM", () => storage.close());
30
+ * ```
31
+ */
32
+ function createMongoStorage(options) {
33
+ const { url, dbName = "xtandard_webhooks", collectionName = "webhooks_kv" } = options;
34
+ const ownsClient = !options.client;
35
+ let client = options.client;
36
+ let connecting;
37
+ /** Resolve a connected collection, creating/connecting the client on first use. */
38
+ async function getCollection() {
39
+ connecting ??= (async () => {
40
+ if (!client) {
41
+ let MongoClientCtor;
42
+ try {
43
+ ({MongoClient: MongoClientCtor} = await import("mongodb"));
44
+ } catch {
45
+ require_contract.requirePeer("mongodb", "storage/mongodb");
46
+ }
47
+ if (url === void 0) throw new Error("@xtandard/webhooks/storage/mongodb requires either a \"client\" or a \"url\" option.");
48
+ client = new MongoClientCtor(url);
49
+ }
50
+ try {
51
+ await client.connect();
52
+ } catch (error) {
53
+ if (!isAlreadyConnected(error)) throw error;
54
+ }
55
+ return client.db(dbName).collection(collectionName);
56
+ })();
57
+ try {
58
+ return await connecting;
59
+ } catch (error) {
60
+ connecting = void 0;
61
+ throw error;
62
+ }
63
+ }
64
+ return {
65
+ async getItem(key) {
66
+ const doc = await (await getCollection()).findOne({ _id: key });
67
+ return doc ? doc.value : null;
68
+ },
69
+ async setItem(key, value) {
70
+ await (await getCollection()).updateOne({ _id: key }, { $set: { value } }, { upsert: true });
71
+ },
72
+ async removeItem(key) {
73
+ await (await getCollection()).deleteOne({ _id: key });
74
+ },
75
+ async getKeys(prefix) {
76
+ const cursor = (await getCollection()).find({ _id: { $regex: `^${escapeRegex(prefix)}` } }).project({ _id: 1 });
77
+ const out = [];
78
+ for await (const doc of cursor) out.push(doc._id);
79
+ return out;
80
+ },
81
+ async close() {
82
+ if (ownsClient && client) await client.close();
83
+ }
84
+ };
85
+ }
86
+ /**
87
+ * Heuristic: does this error indicate the client was already connected? The
88
+ * driver's exact message has varied across versions, so match loosely.
89
+ */
90
+ function isAlreadyConnected(error) {
91
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
92
+ return message.includes("already") && message.includes("connect");
93
+ }
94
+ //#endregion
95
+ Object.defineProperty(exports, "createMongoStorage", {
96
+ enumerable: true,
97
+ get: function() {
98
+ return createMongoStorage;
99
+ }
100
+ });
101
+ Object.defineProperty(exports, "mongodb_exports", {
102
+ enumerable: true,
103
+ get: function() {
104
+ return mongodb_exports;
105
+ }
106
+ });
107
+
108
+ //# sourceMappingURL=mongodb-Cy8yo0uk.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb-Cy8yo0uk.cjs","names":[],"sources":["../src/storage/mongodb.ts"],"sourcesContent":["/**\n * MongoDB storage adapter built on the [`mongodb`](https://github.com/mongodb/node-mongodb-native)\n * driver, an optional peer dependency. You can either pass a pre-constructed\n * (optionally pre-connected) `MongoClient`, or a connection `url` to create and\n * connect a client lazily on first use.\n *\n * The backend is a single collection of `{ _id: <key>, value: <any> }` documents.\n * The storage key is stored verbatim as the document `_id`, and the value is\n * stored directly as a BSON field — MongoDB persists arbitrary objects, arrays,\n * and primitives, so no JSON (de)serialisation round-trip is needed.\n *\n * `getKeys(prefix)` matches `_id` against an anchored regular expression\n * (`^<escaped prefix>`), projecting only `_id` so the server never ships values\n * back for a key listing.\n *\n * ## Why no `watch`\n *\n * MongoDB change streams (the natural way to back {@link WatchableWebhooksStorage})\n * require a replica set or sharded cluster — they are unavailable on a standalone\n * `mongod`. To keep this adapter usable against any deployment, `watch` is\n * intentionally **not** implemented; the core feature-detects its absence via\n * {@link import(\"./contract.ts\").isWatchable} and falls back to polling.\n *\n * @module\n */\n\nimport type { MongoClient, Collection, Document } from \"mongodb\";\nimport { requirePeer } from \"./contract.ts\";\nimport type { WebhooksStorage } from \"./contract.ts\";\n\n/** Options for {@link createMongoStorage}. */\nexport interface MongoStorageOptions {\n /** A pre-constructed (optionally pre-connected) mongodb `MongoClient`. */\n client?: MongoClient;\n /**\n * Connection string (e.g. `mongodb://localhost:27017`). When no `client` is\n * given, a `MongoClient` is created lazily on first use via a dynamic\n * `import(\"mongodb\")`.\n */\n url?: string;\n /** Database name. Defaults to `\"xtandard_webhooks\"`. */\n dbName?: string;\n /** Collection name. Defaults to `\"webhooks_kv\"`. */\n collectionName?: string;\n}\n\n/**\n * A {@link WebhooksStorage} backed by MongoDB, plus a `close()` method that\n * disconnects the client — but only the one this adapter created. A client you\n * passed in is left for you to manage.\n *\n * Note: `watch` is deliberately absent (see the module docs); this is a plain\n * {@link WebhooksStorage}, not a {@link import(\"./contract.ts\").WatchableWebhooksStorage}.\n */\nexport interface MongoWebhooksStorage extends WebhooksStorage {\n /** Disconnect the underlying client if this adapter created it. No-op otherwise. */\n close(): Promise<void>;\n}\n\n/** Shape of a single key/value document stored in the backing collection. */\ninterface KvDoc extends Document {\n /** The storage key, stored verbatim as the Mongo document id. */\n _id: string;\n /** The stored value — arbitrary BSON (object, array, or primitive). */\n value: unknown;\n}\n\n/**\n * Escape characters that are special in a regular expression so a literal\n * prefix string can be embedded into an anchored `^...` match.\n */\nfunction escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Create a MongoDB-backed {@link MongoWebhooksStorage}. Connection is lazy: the\n * client is created/connected on the first storage operation and reused\n * thereafter, guarded by a single connection promise so concurrent calls connect\n * once.\n *\n * @example\n * ```ts\n * import { createMongoStorage } from \"@xtandard/webhooks/storage/mongodb\";\n *\n * const storage = createMongoStorage({\n * url: process.env.MONGODB_URL ?? \"mongodb://localhost:27017\",\n * dbName: \"myapp\",\n * collectionName: \"webhooks_kv\",\n * });\n *\n * // Disconnect when the process exits:\n * // process.on(\"SIGTERM\", () => storage.close());\n * ```\n */\nexport function createMongoStorage(options: MongoStorageOptions): MongoWebhooksStorage {\n const { url, dbName = \"xtandard_webhooks\", collectionName = \"webhooks_kv\" } = options;\n const ownsClient = !options.client;\n\n let client: MongoClient | undefined = options.client;\n let connecting: Promise<Collection<KvDoc>> | undefined;\n\n /** Resolve a connected collection, creating/connecting the client on first use. */\n async function getCollection(): Promise<Collection<KvDoc>> {\n connecting ??= (async () => {\n if (!client) {\n let MongoClientCtor: new (url: string) => MongoClient;\n try {\n ({ MongoClient: MongoClientCtor } = (await import(\"mongodb\")) as unknown as {\n MongoClient: new (url: string) => MongoClient;\n });\n } catch {\n requirePeer(\"mongodb\", \"storage/mongodb\");\n }\n if (url === undefined) {\n throw new Error(\n '@xtandard/webhooks/storage/mongodb requires either a \"client\" or a \"url\" option.',\n );\n }\n client = new MongoClientCtor(url);\n }\n // node-mongodb's connect() is idempotent — calling it on an already\n // connected client is a no-op — but guard regardless so a passed-in,\n // already-connected client never surfaces a spurious error.\n try {\n await client.connect();\n } catch (error) {\n if (!isAlreadyConnected(error)) throw error;\n }\n return client.db(dbName).collection<KvDoc>(collectionName);\n })();\n try {\n return await connecting;\n } catch (error) {\n // Let a later operation retry the connection rather than caching a failure.\n connecting = undefined;\n throw error;\n }\n }\n\n return {\n async getItem<T>(key: string): Promise<T | null> {\n const collection = await getCollection();\n const doc = await collection.findOne({ _id: key });\n return doc ? (doc.value as T) : null;\n },\n\n async setItem<T>(key: string, value: T): Promise<void> {\n const collection = await getCollection();\n await collection.updateOne({ _id: key }, { $set: { value } }, { upsert: true });\n },\n\n async removeItem(key: string): Promise<void> {\n const collection = await getCollection();\n await collection.deleteOne({ _id: key });\n },\n\n async getKeys(prefix: string): Promise<string[]> {\n const collection = await getCollection();\n const cursor = collection\n .find({ _id: { $regex: `^${escapeRegex(prefix)}` } })\n .project<{ _id: string }>({ _id: 1 });\n const out: string[] = [];\n for await (const doc of cursor) out.push(doc._id);\n return out;\n },\n\n async close(): Promise<void> {\n if (ownsClient && client) await client.close();\n },\n } satisfies MongoWebhooksStorage;\n}\n\n/**\n * Heuristic: does this error indicate the client was already connected? The\n * driver's exact message has varied across versions, so match loosely.\n */\nfunction isAlreadyConnected(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : \"\";\n return message.includes(\"already\") && message.includes(\"connect\");\n}\n"],"mappings":";;;;;;;;AAuEA,SAAS,YAAY,OAAuB;CAC1C,OAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,mBAAmB,SAAoD;CACrF,MAAM,EAAE,KAAK,SAAS,qBAAqB,iBAAiB,kBAAkB;CAC9E,MAAM,aAAa,CAAC,QAAQ;CAE5B,IAAI,SAAkC,QAAQ;CAC9C,IAAI;;CAGJ,eAAe,gBAA4C;EACzD,gBAAgB,YAAY;GAC1B,IAAI,CAAC,QAAQ;IACX,IAAI;IACJ,IAAI;KACF,CAAC,CAAE,aAAa,mBAAqB,MAAM,OAAO;IAGpD,QAAQ;KACN,iBAAA,YAAY,WAAW,iBAAiB;IAC1C;IACA,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MACR,sFACF;IAEF,SAAS,IAAI,gBAAgB,GAAG;GAClC;GAIA,IAAI;IACF,MAAM,OAAO,QAAQ;GACvB,SAAS,OAAO;IACd,IAAI,CAAC,mBAAmB,KAAK,GAAG,MAAM;GACxC;GACA,OAAO,OAAO,GAAG,MAAM,EAAE,WAAkB,cAAc;EAC3D,GAAG;EACH,IAAI;GACF,OAAO,MAAM;EACf,SAAS,OAAO;GAEd,aAAa,KAAA;GACb,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAW,KAAgC;GAE/C,MAAM,MAAM,OAAM,MADO,cAAc,GACV,QAAQ,EAAE,KAAK,IAAI,CAAC;GACjD,OAAO,MAAO,IAAI,QAAc;EAClC;EAEA,MAAM,QAAW,KAAa,OAAyB;GAErD,OAAM,MADmB,cAAc,GACtB,UAAU,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,KAAK,CAAC;EAChF;EAEA,MAAM,WAAW,KAA4B;GAE3C,OAAM,MADmB,cAAc,GACtB,UAAU,EAAE,KAAK,IAAI,CAAC;EACzC;EAEA,MAAM,QAAQ,QAAmC;GAE/C,MAAM,UAAS,MADU,cAAc,GAEpC,KAAK,EAAE,KAAK,EAAE,QAAQ,IAAI,YAAY,MAAM,IAAI,EAAE,CAAC,EACnD,QAAyB,EAAE,KAAK,EAAE,CAAC;GACtC,MAAM,MAAgB,CAAC;GACvB,WAAW,MAAM,OAAO,QAAQ,IAAI,KAAK,IAAI,GAAG;GAChD,OAAO;EACT;EAEA,MAAM,QAAuB;GAC3B,IAAI,cAAc,QAAQ,MAAM,OAAO,MAAM;EAC/C;CACF;AACF;;;;;AAMA,SAAS,mBAAmB,OAAyB;CACnD,MAAM,UAAU,iBAAiB,QAAQ,MAAM,QAAQ,YAAY,IAAI;CACvE,OAAO,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,SAAS;AAClE"}
@@ -0,0 +1,97 @@
1
+ import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
+ import { a as requirePeer } from "./contract-BEhDcd_5.mjs";
3
+ //#region src/storage/mongodb.ts
4
+ var mongodb_exports = /* @__PURE__ */ __exportAll({ createMongoStorage: () => createMongoStorage });
5
+ /**
6
+ * Escape characters that are special in a regular expression so a literal
7
+ * prefix string can be embedded into an anchored `^...` match.
8
+ */
9
+ function escapeRegex(input) {
10
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+ /**
13
+ * Create a MongoDB-backed {@link MongoWebhooksStorage}. Connection is lazy: the
14
+ * client is created/connected on the first storage operation and reused
15
+ * thereafter, guarded by a single connection promise so concurrent calls connect
16
+ * once.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { createMongoStorage } from "@xtandard/webhooks/storage/mongodb";
21
+ *
22
+ * const storage = createMongoStorage({
23
+ * url: process.env.MONGODB_URL ?? "mongodb://localhost:27017",
24
+ * dbName: "myapp",
25
+ * collectionName: "webhooks_kv",
26
+ * });
27
+ *
28
+ * // Disconnect when the process exits:
29
+ * // process.on("SIGTERM", () => storage.close());
30
+ * ```
31
+ */
32
+ function createMongoStorage(options) {
33
+ const { url, dbName = "xtandard_webhooks", collectionName = "webhooks_kv" } = options;
34
+ const ownsClient = !options.client;
35
+ let client = options.client;
36
+ let connecting;
37
+ /** Resolve a connected collection, creating/connecting the client on first use. */
38
+ async function getCollection() {
39
+ connecting ??= (async () => {
40
+ if (!client) {
41
+ let MongoClientCtor;
42
+ try {
43
+ ({MongoClient: MongoClientCtor} = await import("mongodb"));
44
+ } catch {
45
+ requirePeer("mongodb", "storage/mongodb");
46
+ }
47
+ if (url === void 0) throw new Error("@xtandard/webhooks/storage/mongodb requires either a \"client\" or a \"url\" option.");
48
+ client = new MongoClientCtor(url);
49
+ }
50
+ try {
51
+ await client.connect();
52
+ } catch (error) {
53
+ if (!isAlreadyConnected(error)) throw error;
54
+ }
55
+ return client.db(dbName).collection(collectionName);
56
+ })();
57
+ try {
58
+ return await connecting;
59
+ } catch (error) {
60
+ connecting = void 0;
61
+ throw error;
62
+ }
63
+ }
64
+ return {
65
+ async getItem(key) {
66
+ const doc = await (await getCollection()).findOne({ _id: key });
67
+ return doc ? doc.value : null;
68
+ },
69
+ async setItem(key, value) {
70
+ await (await getCollection()).updateOne({ _id: key }, { $set: { value } }, { upsert: true });
71
+ },
72
+ async removeItem(key) {
73
+ await (await getCollection()).deleteOne({ _id: key });
74
+ },
75
+ async getKeys(prefix) {
76
+ const cursor = (await getCollection()).find({ _id: { $regex: `^${escapeRegex(prefix)}` } }).project({ _id: 1 });
77
+ const out = [];
78
+ for await (const doc of cursor) out.push(doc._id);
79
+ return out;
80
+ },
81
+ async close() {
82
+ if (ownsClient && client) await client.close();
83
+ }
84
+ };
85
+ }
86
+ /**
87
+ * Heuristic: does this error indicate the client was already connected? The
88
+ * driver's exact message has varied across versions, so match loosely.
89
+ */
90
+ function isAlreadyConnected(error) {
91
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
92
+ return message.includes("already") && message.includes("connect");
93
+ }
94
+ //#endregion
95
+ export { mongodb_exports as n, createMongoStorage as t };
96
+
97
+ //# sourceMappingURL=mongodb-Ddaq9mml.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb-Ddaq9mml.mjs","names":[],"sources":["../src/storage/mongodb.ts"],"sourcesContent":["/**\n * MongoDB storage adapter built on the [`mongodb`](https://github.com/mongodb/node-mongodb-native)\n * driver, an optional peer dependency. You can either pass a pre-constructed\n * (optionally pre-connected) `MongoClient`, or a connection `url` to create and\n * connect a client lazily on first use.\n *\n * The backend is a single collection of `{ _id: <key>, value: <any> }` documents.\n * The storage key is stored verbatim as the document `_id`, and the value is\n * stored directly as a BSON field — MongoDB persists arbitrary objects, arrays,\n * and primitives, so no JSON (de)serialisation round-trip is needed.\n *\n * `getKeys(prefix)` matches `_id` against an anchored regular expression\n * (`^<escaped prefix>`), projecting only `_id` so the server never ships values\n * back for a key listing.\n *\n * ## Why no `watch`\n *\n * MongoDB change streams (the natural way to back {@link WatchableWebhooksStorage})\n * require a replica set or sharded cluster — they are unavailable on a standalone\n * `mongod`. To keep this adapter usable against any deployment, `watch` is\n * intentionally **not** implemented; the core feature-detects its absence via\n * {@link import(\"./contract.ts\").isWatchable} and falls back to polling.\n *\n * @module\n */\n\nimport type { MongoClient, Collection, Document } from \"mongodb\";\nimport { requirePeer } from \"./contract.ts\";\nimport type { WebhooksStorage } from \"./contract.ts\";\n\n/** Options for {@link createMongoStorage}. */\nexport interface MongoStorageOptions {\n /** A pre-constructed (optionally pre-connected) mongodb `MongoClient`. */\n client?: MongoClient;\n /**\n * Connection string (e.g. `mongodb://localhost:27017`). When no `client` is\n * given, a `MongoClient` is created lazily on first use via a dynamic\n * `import(\"mongodb\")`.\n */\n url?: string;\n /** Database name. Defaults to `\"xtandard_webhooks\"`. */\n dbName?: string;\n /** Collection name. Defaults to `\"webhooks_kv\"`. */\n collectionName?: string;\n}\n\n/**\n * A {@link WebhooksStorage} backed by MongoDB, plus a `close()` method that\n * disconnects the client — but only the one this adapter created. A client you\n * passed in is left for you to manage.\n *\n * Note: `watch` is deliberately absent (see the module docs); this is a plain\n * {@link WebhooksStorage}, not a {@link import(\"./contract.ts\").WatchableWebhooksStorage}.\n */\nexport interface MongoWebhooksStorage extends WebhooksStorage {\n /** Disconnect the underlying client if this adapter created it. No-op otherwise. */\n close(): Promise<void>;\n}\n\n/** Shape of a single key/value document stored in the backing collection. */\ninterface KvDoc extends Document {\n /** The storage key, stored verbatim as the Mongo document id. */\n _id: string;\n /** The stored value — arbitrary BSON (object, array, or primitive). */\n value: unknown;\n}\n\n/**\n * Escape characters that are special in a regular expression so a literal\n * prefix string can be embedded into an anchored `^...` match.\n */\nfunction escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Create a MongoDB-backed {@link MongoWebhooksStorage}. Connection is lazy: the\n * client is created/connected on the first storage operation and reused\n * thereafter, guarded by a single connection promise so concurrent calls connect\n * once.\n *\n * @example\n * ```ts\n * import { createMongoStorage } from \"@xtandard/webhooks/storage/mongodb\";\n *\n * const storage = createMongoStorage({\n * url: process.env.MONGODB_URL ?? \"mongodb://localhost:27017\",\n * dbName: \"myapp\",\n * collectionName: \"webhooks_kv\",\n * });\n *\n * // Disconnect when the process exits:\n * // process.on(\"SIGTERM\", () => storage.close());\n * ```\n */\nexport function createMongoStorage(options: MongoStorageOptions): MongoWebhooksStorage {\n const { url, dbName = \"xtandard_webhooks\", collectionName = \"webhooks_kv\" } = options;\n const ownsClient = !options.client;\n\n let client: MongoClient | undefined = options.client;\n let connecting: Promise<Collection<KvDoc>> | undefined;\n\n /** Resolve a connected collection, creating/connecting the client on first use. */\n async function getCollection(): Promise<Collection<KvDoc>> {\n connecting ??= (async () => {\n if (!client) {\n let MongoClientCtor: new (url: string) => MongoClient;\n try {\n ({ MongoClient: MongoClientCtor } = (await import(\"mongodb\")) as unknown as {\n MongoClient: new (url: string) => MongoClient;\n });\n } catch {\n requirePeer(\"mongodb\", \"storage/mongodb\");\n }\n if (url === undefined) {\n throw new Error(\n '@xtandard/webhooks/storage/mongodb requires either a \"client\" or a \"url\" option.',\n );\n }\n client = new MongoClientCtor(url);\n }\n // node-mongodb's connect() is idempotent — calling it on an already\n // connected client is a no-op — but guard regardless so a passed-in,\n // already-connected client never surfaces a spurious error.\n try {\n await client.connect();\n } catch (error) {\n if (!isAlreadyConnected(error)) throw error;\n }\n return client.db(dbName).collection<KvDoc>(collectionName);\n })();\n try {\n return await connecting;\n } catch (error) {\n // Let a later operation retry the connection rather than caching a failure.\n connecting = undefined;\n throw error;\n }\n }\n\n return {\n async getItem<T>(key: string): Promise<T | null> {\n const collection = await getCollection();\n const doc = await collection.findOne({ _id: key });\n return doc ? (doc.value as T) : null;\n },\n\n async setItem<T>(key: string, value: T): Promise<void> {\n const collection = await getCollection();\n await collection.updateOne({ _id: key }, { $set: { value } }, { upsert: true });\n },\n\n async removeItem(key: string): Promise<void> {\n const collection = await getCollection();\n await collection.deleteOne({ _id: key });\n },\n\n async getKeys(prefix: string): Promise<string[]> {\n const collection = await getCollection();\n const cursor = collection\n .find({ _id: { $regex: `^${escapeRegex(prefix)}` } })\n .project<{ _id: string }>({ _id: 1 });\n const out: string[] = [];\n for await (const doc of cursor) out.push(doc._id);\n return out;\n },\n\n async close(): Promise<void> {\n if (ownsClient && client) await client.close();\n },\n } satisfies MongoWebhooksStorage;\n}\n\n/**\n * Heuristic: does this error indicate the client was already connected? The\n * driver's exact message has varied across versions, so match loosely.\n */\nfunction isAlreadyConnected(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : \"\";\n return message.includes(\"already\") && message.includes(\"connect\");\n}\n"],"mappings":";;;;;;;;AAuEA,SAAS,YAAY,OAAuB;CAC1C,OAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,mBAAmB,SAAoD;CACrF,MAAM,EAAE,KAAK,SAAS,qBAAqB,iBAAiB,kBAAkB;CAC9E,MAAM,aAAa,CAAC,QAAQ;CAE5B,IAAI,SAAkC,QAAQ;CAC9C,IAAI;;CAGJ,eAAe,gBAA4C;EACzD,gBAAgB,YAAY;GAC1B,IAAI,CAAC,QAAQ;IACX,IAAI;IACJ,IAAI;KACF,CAAC,CAAE,aAAa,mBAAqB,MAAM,OAAO;IAGpD,QAAQ;KACN,YAAY,WAAW,iBAAiB;IAC1C;IACA,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MACR,sFACF;IAEF,SAAS,IAAI,gBAAgB,GAAG;GAClC;GAIA,IAAI;IACF,MAAM,OAAO,QAAQ;GACvB,SAAS,OAAO;IACd,IAAI,CAAC,mBAAmB,KAAK,GAAG,MAAM;GACxC;GACA,OAAO,OAAO,GAAG,MAAM,EAAE,WAAkB,cAAc;EAC3D,GAAG;EACH,IAAI;GACF,OAAO,MAAM;EACf,SAAS,OAAO;GAEd,aAAa,KAAA;GACb,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAW,KAAgC;GAE/C,MAAM,MAAM,OAAM,MADO,cAAc,GACV,QAAQ,EAAE,KAAK,IAAI,CAAC;GACjD,OAAO,MAAO,IAAI,QAAc;EAClC;EAEA,MAAM,QAAW,KAAa,OAAyB;GAErD,OAAM,MADmB,cAAc,GACtB,UAAU,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,KAAK,CAAC;EAChF;EAEA,MAAM,WAAW,KAA4B;GAE3C,OAAM,MADmB,cAAc,GACtB,UAAU,EAAE,KAAK,IAAI,CAAC;EACzC;EAEA,MAAM,QAAQ,QAAmC;GAE/C,MAAM,UAAS,MADU,cAAc,GAEpC,KAAK,EAAE,KAAK,EAAE,QAAQ,IAAI,YAAY,MAAM,IAAI,EAAE,CAAC,EACnD,QAAyB,EAAE,KAAK,EAAE,CAAC;GACtC,MAAM,MAAgB,CAAC;GACvB,WAAW,MAAM,OAAO,QAAQ,IAAI,KAAK,IAAI,GAAG;GAChD,OAAO;EACT;EAEA,MAAM,QAAuB;GAC3B,IAAI,cAAc,QAAQ,MAAM,OAAO,MAAM;EAC/C;CACF;AACF;;;;;AAMA,SAAS,mBAAmB,OAAyB;CACnD,MAAM,UAAU,iBAAiB,QAAQ,MAAM,QAAQ,YAAY,IAAI;CACvE,OAAO,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,SAAS;AAClE"}
@@ -0,0 +1,23 @@
1
+ import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
+ //#region src/authorization/none.ts
3
+ var none_exports = /* @__PURE__ */ __exportAll({ noAuthorization: () => noAuthorization });
4
+ /**
5
+ * Create an {@link AuthorizationProvider} that authorizes everything.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { noAuthorization } from "@xtandard/webhooks/authorization/none";
10
+ *
11
+ * const authz = noAuthorization();
12
+ * await authz.authorize(input); // → true, always
13
+ * ```
14
+ */
15
+ function noAuthorization() {
16
+ return { async authorize(_input) {
17
+ return true;
18
+ } };
19
+ }
20
+ //#endregion
21
+ export { none_exports as n, noAuthorization as t };
22
+
23
+ //# sourceMappingURL=none-BnZtaGNJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"none-BnZtaGNJ.mjs","names":[],"sources":["../src/authorization/none.ts"],"sourcesContent":["/**\n * The \"no authorization\" {@link AuthorizationProvider}. Every action is allowed.\n *\n * This grants unconditional access regardless of the principal (even `null`) or\n * the action/resource. It is the right choice for embedded usage and local\n * development where the admin API is not exposed to untrusted callers — commonly\n * paired with `noAuth()`.\n *\n * Do **not** use it for a network-exposed admin surface; reach for\n * `rolesAuthorization()` (or a delegated provider) instead.\n *\n * @module\n */\n\nimport type { AuthorizationProvider, AuthorizeInput } from \"./contract.ts\";\n\n/**\n * Create an {@link AuthorizationProvider} that authorizes everything.\n *\n * @example\n * ```ts\n * import { noAuthorization } from \"@xtandard/webhooks/authorization/none\";\n *\n * const authz = noAuthorization();\n * await authz.authorize(input); // → true, always\n * ```\n */\nexport function noAuthorization(): AuthorizationProvider {\n return {\n async authorize(_input: AuthorizeInput): Promise<boolean> {\n return true;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AA2BA,SAAgB,kBAAyC;CACvD,OAAO,EACL,MAAM,UAAU,QAA0C;EACxD,OAAO;CACT,EACF;AACF"}