@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.
- package/LICENSE +21 -0
- package/README.md +315 -0
- package/bin/xtandard-webhooks.mjs +3 -0
- package/dist/basic-BIW3Rvuz.cjs +199 -0
- package/dist/basic-BIW3Rvuz.cjs.map +1 -0
- package/dist/basic-DKk0Xfuu.mjs +176 -0
- package/dist/basic-DKk0Xfuu.mjs.map +1 -0
- package/dist/chunk-D7D4PA-g.mjs +13 -0
- package/dist/cli.cjs +655 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +42 -0
- package/dist/cli.d.mts +42 -0
- package/dist/cli.mjs +653 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/contract-8h-Azxa5.d.cts +71 -0
- package/dist/contract-9XpcwcCn.mjs +22 -0
- package/dist/contract-9XpcwcCn.mjs.map +1 -0
- package/dist/contract-B2d5dNU3.cjs +33 -0
- package/dist/contract-B2d5dNU3.cjs.map +1 -0
- package/dist/contract-BEhDcd_5.mjs +28 -0
- package/dist/contract-BEhDcd_5.mjs.map +1 -0
- package/dist/contract-Bf1qguwt.cjs +57 -0
- package/dist/contract-Bf1qguwt.cjs.map +1 -0
- package/dist/contract-Bnb3fgRJ.d.cts +177 -0
- package/dist/contract-C2r2Xzwp.d.mts +46 -0
- package/dist/contract-CiPskNvS.d.cts +46 -0
- package/dist/contract-DhQ4JjGG.d.mts +71 -0
- package/dist/contract-T1kcZNdG.d.mts +177 -0
- package/dist/contract-lETlIuXo.d.cts +30 -0
- package/dist/contract-lETlIuXo.d.mts +30 -0
- package/dist/core-CMpnmI5Q.mjs +1605 -0
- package/dist/core-CMpnmI5Q.mjs.map +1 -0
- package/dist/core-DT4ppWh8.d.mts +502 -0
- package/dist/core-KJawHjFF.d.cts +502 -0
- package/dist/core-ZGhH6Vs2.cjs +1790 -0
- package/dist/core-ZGhH6Vs2.cjs.map +1 -0
- package/dist/core.cjs +8 -0
- package/dist/core.d.cts +2 -0
- package/dist/core.d.mts +2 -0
- package/dist/core.mjs +2 -0
- package/dist/create-fetch-handler-BIdk9P30.mjs +1724 -0
- package/dist/create-fetch-handler-BIdk9P30.mjs.map +1 -0
- package/dist/create-fetch-handler-CmooujQo.cjs +1771 -0
- package/dist/create-fetch-handler-CmooujQo.cjs.map +1 -0
- package/dist/create-fetch-handler-Dlkhustu.d.cts +162 -0
- package/dist/create-fetch-handler-jy3hy5nZ.d.mts +162 -0
- package/dist/dispatcher-B0xTEHt1.cjs +212 -0
- package/dist/dispatcher-B0xTEHt1.cjs.map +1 -0
- package/dist/dispatcher-Coubwrka.mjs +196 -0
- package/dist/dispatcher-Coubwrka.mjs.map +1 -0
- package/dist/entry-auth-basic.cjs +5 -0
- package/dist/entry-auth-basic.d.cts +83 -0
- package/dist/entry-auth-basic.d.mts +83 -0
- package/dist/entry-auth-basic.mjs +2 -0
- package/dist/entry-auth-delegated.cjs +28 -0
- package/dist/entry-auth-delegated.cjs.map +1 -0
- package/dist/entry-auth-delegated.d.cts +36 -0
- package/dist/entry-auth-delegated.d.mts +36 -0
- package/dist/entry-auth-delegated.mjs +27 -0
- package/dist/entry-auth-delegated.mjs.map +1 -0
- package/dist/entry-auth-none.cjs +4 -0
- package/dist/entry-auth-none.d.cts +25 -0
- package/dist/entry-auth-none.d.mts +25 -0
- package/dist/entry-auth-none.mjs +2 -0
- package/dist/entry-authorization-delegated.cjs +27 -0
- package/dist/entry-authorization-delegated.cjs.map +1 -0
- package/dist/entry-authorization-delegated.d.cts +31 -0
- package/dist/entry-authorization-delegated.d.mts +31 -0
- package/dist/entry-authorization-delegated.mjs +26 -0
- package/dist/entry-authorization-delegated.mjs.map +1 -0
- package/dist/entry-authorization-none.cjs +3 -0
- package/dist/entry-authorization-none.d.cts +18 -0
- package/dist/entry-authorization-none.d.mts +18 -0
- package/dist/entry-authorization-none.mjs +2 -0
- package/dist/entry-authorization-roles.cjs +6 -0
- package/dist/entry-authorization-roles.d.cts +65 -0
- package/dist/entry-authorization-roles.d.mts +65 -0
- package/dist/entry-authorization-roles.mjs +2 -0
- package/dist/entry-bun.cjs +24 -0
- package/dist/entry-bun.cjs.map +1 -0
- package/dist/entry-bun.d.cts +8 -0
- package/dist/entry-bun.d.mts +8 -0
- package/dist/entry-bun.mjs +23 -0
- package/dist/entry-bun.mjs.map +1 -0
- package/dist/entry-drizzle-mysql.cjs +20 -0
- package/dist/entry-drizzle-mysql.cjs.map +1 -0
- package/dist/entry-drizzle-mysql.d.cts +27 -0
- package/dist/entry-drizzle-mysql.d.mts +27 -0
- package/dist/entry-drizzle-mysql.mjs +19 -0
- package/dist/entry-drizzle-mysql.mjs.map +1 -0
- package/dist/entry-drizzle-pg.cjs +21 -0
- package/dist/entry-drizzle-pg.cjs.map +1 -0
- package/dist/entry-drizzle-pg.d.cts +26 -0
- package/dist/entry-drizzle-pg.d.mts +26 -0
- package/dist/entry-drizzle-pg.mjs +20 -0
- package/dist/entry-drizzle-pg.mjs.map +1 -0
- package/dist/entry-drizzle-sqlite.cjs +21 -0
- package/dist/entry-drizzle-sqlite.cjs.map +1 -0
- package/dist/entry-drizzle-sqlite.d.cts +23 -0
- package/dist/entry-drizzle-sqlite.d.mts +23 -0
- package/dist/entry-drizzle-sqlite.mjs +20 -0
- package/dist/entry-drizzle-sqlite.mjs.map +1 -0
- package/dist/entry-elysia.cjs +125 -0
- package/dist/entry-elysia.cjs.map +1 -0
- package/dist/entry-elysia.d.cts +1017 -0
- package/dist/entry-elysia.d.mts +1017 -0
- package/dist/entry-elysia.mjs +123 -0
- package/dist/entry-elysia.mjs.map +1 -0
- package/dist/entry-express.cjs +57 -0
- package/dist/entry-express.cjs.map +1 -0
- package/dist/entry-express.d.cts +15 -0
- package/dist/entry-express.d.mts +15 -0
- package/dist/entry-express.mjs +56 -0
- package/dist/entry-express.mjs.map +1 -0
- package/dist/entry-hono.cjs +35 -0
- package/dist/entry-hono.cjs.map +1 -0
- package/dist/entry-hono.d.cts +16 -0
- package/dist/entry-hono.d.mts +16 -0
- package/dist/entry-hono.mjs +34 -0
- package/dist/entry-hono.mjs.map +1 -0
- package/dist/entry-hooks-log.cjs +22 -0
- package/dist/entry-hooks-log.cjs.map +1 -0
- package/dist/entry-hooks-log.d.cts +23 -0
- package/dist/entry-hooks-log.d.mts +23 -0
- package/dist/entry-hooks-log.mjs +21 -0
- package/dist/entry-hooks-log.mjs.map +1 -0
- package/dist/entry-storage-cloudflare-kv.cjs +47 -0
- package/dist/entry-storage-cloudflare-kv.cjs.map +1 -0
- package/dist/entry-storage-cloudflare-kv.d.cts +42 -0
- package/dist/entry-storage-cloudflare-kv.d.mts +42 -0
- package/dist/entry-storage-cloudflare-kv.mjs +46 -0
- package/dist/entry-storage-cloudflare-kv.mjs.map +1 -0
- package/dist/entry-storage-drizzle.cjs +78 -0
- package/dist/entry-storage-drizzle.cjs.map +1 -0
- package/dist/entry-storage-drizzle.d.cts +30 -0
- package/dist/entry-storage-drizzle.d.mts +30 -0
- package/dist/entry-storage-drizzle.mjs +77 -0
- package/dist/entry-storage-drizzle.mjs.map +1 -0
- package/dist/entry-storage-file.cjs +4 -0
- package/dist/entry-storage-file.d.cts +30 -0
- package/dist/entry-storage-file.d.mts +30 -0
- package/dist/entry-storage-file.mjs +2 -0
- package/dist/entry-storage-libsql.cjs +3 -0
- package/dist/entry-storage-libsql.d.cts +48 -0
- package/dist/entry-storage-libsql.d.mts +48 -0
- package/dist/entry-storage-libsql.mjs +2 -0
- package/dist/entry-storage-memory.cjs +3 -0
- package/dist/entry-storage-memory.d.cts +2 -0
- package/dist/entry-storage-memory.d.mts +2 -0
- package/dist/entry-storage-memory.mjs +2 -0
- package/dist/entry-storage-mongodb.cjs +3 -0
- package/dist/entry-storage-mongodb.d.cts +55 -0
- package/dist/entry-storage-mongodb.d.mts +55 -0
- package/dist/entry-storage-mongodb.mjs +2 -0
- package/dist/entry-storage-postgres.cjs +3 -0
- package/dist/entry-storage-postgres.d.cts +62 -0
- package/dist/entry-storage-postgres.d.mts +62 -0
- package/dist/entry-storage-postgres.mjs +2 -0
- package/dist/entry-storage-redis.cjs +4 -0
- package/dist/entry-storage-redis.d.cts +77 -0
- package/dist/entry-storage-redis.d.mts +77 -0
- package/dist/entry-storage-redis.mjs +2 -0
- package/dist/entry-storage-sqlite.cjs +3 -0
- package/dist/entry-storage-sqlite.d.cts +36 -0
- package/dist/entry-storage-sqlite.d.mts +36 -0
- package/dist/entry-storage-sqlite.mjs +2 -0
- package/dist/entry-storage-unstorage.cjs +42 -0
- package/dist/entry-storage-unstorage.cjs.map +1 -0
- package/dist/entry-storage-unstorage.d.cts +29 -0
- package/dist/entry-storage-unstorage.d.mts +29 -0
- package/dist/entry-storage-unstorage.mjs +41 -0
- package/dist/entry-storage-unstorage.mjs.map +1 -0
- package/dist/file-COBYZA4Q.cjs +148 -0
- package/dist/file-COBYZA4Q.cjs.map +1 -0
- package/dist/file-fi02eFHk.mjs +131 -0
- package/dist/file-fi02eFHk.mjs.map +1 -0
- package/dist/index.cjs +123 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +368 -0
- package/dist/index.d.mts +366 -0
- package/dist/index.mjs +61 -0
- package/dist/index.mjs.map +1 -0
- package/dist/keys-Byyj4quQ.mjs +111 -0
- package/dist/keys-Byyj4quQ.mjs.map +1 -0
- package/dist/keys-FiKpaVHX.cjs +302 -0
- package/dist/keys-FiKpaVHX.cjs.map +1 -0
- package/dist/libsql-bpVi0bXN.mjs +113 -0
- package/dist/libsql-bpVi0bXN.mjs.map +1 -0
- package/dist/libsql-pPJEo1e4.cjs +124 -0
- package/dist/libsql-pPJEo1e4.cjs.map +1 -0
- package/dist/memory-8Ef-PL5a.cjs +137 -0
- package/dist/memory-8Ef-PL5a.cjs.map +1 -0
- package/dist/memory-BMsSSwqn.mjs +127 -0
- package/dist/memory-BMsSSwqn.mjs.map +1 -0
- package/dist/memory-FnMJWCmB.d.cts +28 -0
- package/dist/memory-qIvANEs_.d.mts +28 -0
- package/dist/mongodb-Cy8yo0uk.cjs +108 -0
- package/dist/mongodb-Cy8yo0uk.cjs.map +1 -0
- package/dist/mongodb-Ddaq9mml.mjs +97 -0
- package/dist/mongodb-Ddaq9mml.mjs.map +1 -0
- package/dist/none-BnZtaGNJ.mjs +23 -0
- package/dist/none-BnZtaGNJ.mjs.map +1 -0
- package/dist/none-CAsxCOWN.cjs +49 -0
- package/dist/none-CAsxCOWN.cjs.map +1 -0
- package/dist/none-CZVrfnmF.cjs +33 -0
- package/dist/none-CZVrfnmF.cjs.map +1 -0
- package/dist/none-GhVIoh_s.mjs +33 -0
- package/dist/none-GhVIoh_s.mjs.map +1 -0
- package/dist/postgres-C8WbchFa.cjs +134 -0
- package/dist/postgres-C8WbchFa.cjs.map +1 -0
- package/dist/postgres-c3pAhmhr.mjs +123 -0
- package/dist/postgres-c3pAhmhr.mjs.map +1 -0
- package/dist/react.css +1 -0
- package/dist/react.js +31465 -0
- package/dist/receiver.cjs +43 -0
- package/dist/receiver.cjs.map +1 -0
- package/dist/receiver.d.cts +36 -0
- package/dist/receiver.d.mts +36 -0
- package/dist/receiver.mjs +40 -0
- package/dist/receiver.mjs.map +1 -0
- package/dist/redis-CFJkuSgB.cjs +270 -0
- package/dist/redis-CFJkuSgB.cjs.map +1 -0
- package/dist/redis-CvLi0KF7.mjs +254 -0
- package/dist/redis-CvLi0KF7.mjs.map +1 -0
- package/dist/roles-D0G9XqBq.cjs +128 -0
- package/dist/roles-D0G9XqBq.cjs.map +1 -0
- package/dist/roles-vp361lTk.mjs +99 -0
- package/dist/roles-vp361lTk.mjs.map +1 -0
- package/dist/schema-mo__wv4P.d.cts +233 -0
- package/dist/schema-mo__wv4P.d.mts +233 -0
- package/dist/schema.cjs +13 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +2 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +11 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/signing.cjs +162 -0
- package/dist/signing.cjs.map +1 -0
- package/dist/signing.d.cts +73 -0
- package/dist/signing.d.mts +73 -0
- package/dist/signing.mjs +156 -0
- package/dist/signing.mjs.map +1 -0
- package/dist/sqlite-Cmqnrjes.mjs +67 -0
- package/dist/sqlite-Cmqnrjes.mjs.map +1 -0
- package/dist/sqlite-Dcufk0x3.cjs +78 -0
- package/dist/sqlite-Dcufk0x3.cjs.map +1 -0
- package/dist/table-Ce3Tzwqs.d.cts +11 -0
- package/dist/table-Ce3Tzwqs.d.mts +11 -0
- package/dist/testing.cjs +134 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +80 -0
- package/dist/testing.d.mts +80 -0
- package/dist/testing.mjs +131 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-react/react.d.ts +98 -0
- package/dist/types-react/schema.d.ts +229 -0
- package/dist/types-react/ui/App.d.ts +22 -0
- package/dist/types-react/ui/api.d.ts +97 -0
- package/dist/types-react/ui/components/JsonCodeEditor.d.ts +12 -0
- package/dist/types-react/ui/components/ThemeToggle.d.ts +2 -0
- package/dist/types-react/ui/components/Toast.d.ts +16 -0
- package/dist/types-react/ui/components/primitives.d.ts +50 -0
- package/dist/types-react/ui/components/ui-bits.d.ts +22 -0
- package/dist/types-react/ui/components/webhook-bits.d.ts +51 -0
- package/dist/types-react/ui/lib/format.d.ts +39 -0
- package/dist/types-react/ui/lib/nav-guard.d.ts +20 -0
- package/dist/types-react/ui/lib/utils.d.ts +3 -0
- package/dist/types-react/ui/theme.d.ts +12 -0
- package/dist/types-react/ui/types.d.ts +80 -0
- package/dist/types-react/ui/views/AuditView.d.ts +6 -0
- package/dist/types-react/ui/views/DeliveriesView.d.ts +12 -0
- package/dist/types-react/ui/views/EndpointsView.d.ts +11 -0
- package/dist/types-react/ui/views/EventTypesView.d.ts +11 -0
- package/dist/types-react/ui/views/MessagesView.d.ts +10 -0
- package/dist/types-react/ui/views/OverviewView.d.ts +12 -0
- package/dist/ui/assets/index-B0eoQX2U.css +1 -0
- package/dist/ui/assets/index-S5t_CLOe.js +209 -0
- package/dist/ui/index.html +14 -0
- 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"}
|