@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
package/dist/cli.mjs ADDED
@@ -0,0 +1,653 @@
1
+ import { T as parseDurationList, o as createWebhooksCore, w as durationToMs } from "./core-CMpnmI5Q.mjs";
2
+ //#region src/cli.ts
3
+ const env = (key, fallback = "") => process.env[key] ?? fallback;
4
+ const isTruthy = (value) => value === "1" || value.toLowerCase() === "true";
5
+ /**
6
+ * The driver env var for a role: `STORAGE_DRIVER` for the control plane,
7
+ * `QUEUE_STORAGE_DRIVER` for the delivery queue.
8
+ */
9
+ const driverEnv = (role) => role === "STORAGE" ? "STORAGE_DRIVER" : "QUEUE_STORAGE_DRIVER";
10
+ /**
11
+ * Parse a duration env like `"30d"`, `"34h"`, `"90m"`, `"10s"` into a
12
+ * {@link WebhookDuration}, warning and ignoring malformed values.
13
+ */
14
+ function parseDurationEnv(name) {
15
+ const raw = env(name);
16
+ if (!raw) return void 0;
17
+ try {
18
+ durationToMs(raw);
19
+ return raw;
20
+ } catch {
21
+ process.stderr.write(`[xtandard/webhooks] Ignoring ${name}="${raw}" — expected <number><unit> with unit ms|s|m|h|d (e.g. 34h, 30d).\n`);
22
+ return;
23
+ }
24
+ }
25
+ /**
26
+ * Retention from env: `MESSAGE_KEEP_LAST` / `MESSAGE_MAX_AGE` and
27
+ * `AUDIT_KEEP_LAST` / `AUDIT_MAX_AGE`. Returns undefined when none are set.
28
+ */
29
+ function retentionFromEnv() {
30
+ const rule = (keepVar, ageVar) => {
31
+ const keepRaw = env(keepVar);
32
+ const keepLast = keepRaw && /^\d+$/.test(keepRaw) ? Number(keepRaw) : void 0;
33
+ if (keepRaw && keepLast === void 0) process.stderr.write(`[xtandard/webhooks] Ignoring ${keepVar}="${keepRaw}" — expected a number.\n`);
34
+ const maxAge = parseDurationEnv(ageVar);
35
+ if (keepLast === void 0 && !maxAge) return void 0;
36
+ return {
37
+ ...keepLast !== void 0 ? { keepLast } : {},
38
+ ...maxAge !== void 0 ? { maxAge } : {}
39
+ };
40
+ };
41
+ const messages = rule("MESSAGE_KEEP_LAST", "MESSAGE_MAX_AGE");
42
+ const audit = rule("AUDIT_KEEP_LAST", "AUDIT_MAX_AGE");
43
+ if (!messages && !audit) return void 0;
44
+ return {
45
+ ...messages ? { messages } : {},
46
+ ...audit ? { audit } : {}
47
+ };
48
+ }
49
+ /** Dispatcher config from env: `RETRY_SCHEDULE` (comma list, e.g. `0s,5s,5m`). */
50
+ function dispatcherOptionsFromEnv() {
51
+ const raw = env("RETRY_SCHEDULE");
52
+ if (!raw) return {};
53
+ try {
54
+ return { retrySchedule: parseDurationList(raw) };
55
+ } catch {
56
+ process.stderr.write(`[xtandard/webhooks] Ignoring RETRY_SCHEDULE="${raw}" — expected a comma list of durations (e.g. "0s,5s,5m,30m,2h,5h,10h").\n`);
57
+ return {};
58
+ }
59
+ }
60
+ /** Whether the in-process dispatcher is enabled (`DISPATCHER=0|false` disables). */
61
+ function dispatcherEnabled() {
62
+ const raw = env("DISPATCHER");
63
+ if (!raw) return true;
64
+ return !(raw === "0" || raw.toLowerCase() === "false");
65
+ }
66
+ /**
67
+ * Build one storage role from env. `STORAGE` holds the control plane
68
+ * (applications, event types, endpoints, messages, audit); `QUEUE` holds
69
+ * deliveries + the due index and defaults to the same store (see
70
+ * {@link buildQueueStorage}).
71
+ */
72
+ async function buildStorage(role) {
73
+ const driver = env(driverEnv(role), "file") || "file";
74
+ const r = role.toLowerCase();
75
+ switch (driver) {
76
+ case "redis": {
77
+ const { createRedisStorage } = await import("./redis-CvLi0KF7.mjs").then((n) => n.r);
78
+ return createRedisStorage({
79
+ url: env("REDIS_URL", "redis://localhost:6379"),
80
+ prefix: env(`${role}_PREFIX`, `xtandard:webhooks:${r}`)
81
+ });
82
+ }
83
+ case "postgres": {
84
+ const { createPostgresStorage } = await import("./postgres-c3pAhmhr.mjs").then((n) => n.n);
85
+ return createPostgresStorage({
86
+ connectionString: env("DATABASE_URL") || env("POSTGRES_URL", "postgres://localhost:5432/postgres"),
87
+ table: env(`${role}_PG_TABLE`, `xtandard_webhooks_${r}`)
88
+ });
89
+ }
90
+ case "mongodb": {
91
+ const { createMongoStorage } = await import("./mongodb-Ddaq9mml.mjs").then((n) => n.n);
92
+ return createMongoStorage({
93
+ url: env("MONGO_URL", "mongodb://localhost:27017"),
94
+ dbName: env("MONGO_DB", "xtandard_webhooks"),
95
+ collectionName: env(`${role}_MONGO_COLLECTION`, `webhooks_${r}`)
96
+ });
97
+ }
98
+ case "sqlite": {
99
+ const { createSqliteStorage } = await import("./sqlite-Cmqnrjes.mjs").then((n) => n.n);
100
+ return createSqliteStorage({ path: env(`${role}_SQLITE_PATH`, `./.webhooks/${r}.sqlite`) });
101
+ }
102
+ case "libsql": {
103
+ const { createLibsqlStorage } = await import("./libsql-bpVi0bXN.mjs").then((n) => n.n);
104
+ const authToken = env("LIBSQL_AUTH_TOKEN");
105
+ return createLibsqlStorage({
106
+ url: env(`${role}_LIBSQL_URL`) || env("LIBSQL_URL", `file:./.webhooks/${r}.db`),
107
+ ...authToken ? { authToken } : {}
108
+ });
109
+ }
110
+ case "memory": {
111
+ const { createMemoryStorage } = await import("./memory-BMsSSwqn.mjs").then((n) => n.n);
112
+ return createMemoryStorage();
113
+ }
114
+ default: {
115
+ const { createFileStorage } = await import("./file-fi02eFHk.mjs").then((n) => n.r);
116
+ return createFileStorage({ dir: env(`${role}_FILE_DIR`, `./.webhooks/${r}`) });
117
+ }
118
+ }
119
+ }
120
+ /**
121
+ * The queue store, or `undefined` to share the control-plane store. Only built
122
+ * when `QUEUE_STORAGE_DRIVER` is explicitly set (split-plane deployments:
123
+ * control data in Postgres, queue in Redis).
124
+ */
125
+ async function buildQueueStorage() {
126
+ if (!env("QUEUE_STORAGE_DRIVER")) return void 0;
127
+ return buildStorage("QUEUE");
128
+ }
129
+ /**
130
+ * A human-readable, log-safe description of where a storage role persists data
131
+ * — for the `serve`/`dispatch` startup banner. File/SQLite paths are resolved
132
+ * to absolute so "where did my webhooks go?" is obvious; connection-string
133
+ * drivers print only the driver name (never the URL, which may carry
134
+ * credentials).
135
+ */
136
+ async function describeStorage(role) {
137
+ const { resolve } = await import("node:path");
138
+ const driver = env(driverEnv(role), "file") || "file";
139
+ const r = role.toLowerCase();
140
+ switch (driver) {
141
+ case "file": return `file → ${resolve(env(`${role}_FILE_DIR`, `./.webhooks/${r}`))}`;
142
+ case "sqlite": return `sqlite → ${resolve(env(`${role}_SQLITE_PATH`, `./.webhooks/${r}.sqlite`))}`;
143
+ case "memory": return "memory (ephemeral — not persisted)";
144
+ default: return driver;
145
+ }
146
+ }
147
+ /** Build the auth + authorization providers from env (mirrors the standalone app). */
148
+ async function buildAuth() {
149
+ if (env("AUTH_MODE", "none") === "basic") {
150
+ const [{ basicAuth }, { rolesAuthorization }] = await Promise.all([import("./basic-DKk0Xfuu.mjs").then((n) => n.n), import("./roles-vp361lTk.mjs").then((n) => n.a)]);
151
+ const passwordHash = env("AUTH_PASSWORD_HASH");
152
+ const password = env("AUTH_PASSWORD");
153
+ if (!passwordHash && !password) process.stderr.write("[xtandard/webhooks] AUTH_MODE=basic but neither AUTH_PASSWORD_HASH nor AUTH_PASSWORD is set.\n");
154
+ return {
155
+ auth: basicAuth({ users: [{
156
+ username: env("AUTH_USERNAME", "admin"),
157
+ ...passwordHash ? { passwordHash } : {},
158
+ ...password ? { password } : {},
159
+ roles: ["admin"]
160
+ }] }),
161
+ authorization: rolesAuthorization({})
162
+ };
163
+ }
164
+ const [{ noAuth }, { noAuthorization }] = await Promise.all([import("./none-GhVIoh_s.mjs").then((n) => n.r), import("./none-BnZtaGNJ.mjs").then((n) => n.n)]);
165
+ return {
166
+ auth: noAuth(),
167
+ authorization: noAuthorization()
168
+ };
169
+ }
170
+ /**
171
+ * Serve a web-standard fetch handler under whatever runtime the CLI runs on:
172
+ * `Bun.serve` under `bunx`, a `node:http` bridge under `npx`/Node. Resolves only
173
+ * once the server is listening; the process then stays alive on the open socket.
174
+ */
175
+ async function startServer(port, fetch) {
176
+ const bun = globalThis.Bun;
177
+ if (bun) {
178
+ bun.serve({
179
+ port,
180
+ fetch
181
+ });
182
+ return;
183
+ }
184
+ const { createServer } = await import("node:http");
185
+ const server = createServer((req, res) => {
186
+ (async () => {
187
+ try {
188
+ const chunks = [];
189
+ for await (const c of req) chunks.push(c);
190
+ const method = req.method ?? "GET";
191
+ const headers = new Headers();
192
+ for (const [k, v] of Object.entries(req.headers)) {
193
+ if (v === void 0) continue;
194
+ headers.set(k, Array.isArray(v) ? v.join(", ") : v);
195
+ }
196
+ const url = `http://${req.headers.host ?? `localhost:${port}`}${req.url ?? "/"}`;
197
+ const hasBody = method !== "GET" && method !== "HEAD" && chunks.length > 0;
198
+ const response = await fetch(new Request(url, {
199
+ method,
200
+ headers,
201
+ body: hasBody ? Buffer.concat(chunks) : void 0
202
+ }));
203
+ res.statusCode = response.status;
204
+ response.headers.forEach((value, key) => res.setHeader(key, value));
205
+ res.end(Buffer.from(await response.arrayBuffer()));
206
+ } catch (err) {
207
+ res.statusCode = 500;
208
+ res.end(`Internal error: ${err instanceof Error ? err.message : String(err)}`);
209
+ }
210
+ })();
211
+ });
212
+ await new Promise((resolve) => server.listen(port, resolve));
213
+ }
214
+ /**
215
+ * Format one captured inbound webhook for the terminal — the `listen` command's
216
+ * pretty-printer. Pure and side-effect-free so it can be unit-tested.
217
+ */
218
+ function formatInboundWebhook(input) {
219
+ const wh = (name) => input.headers[name] ?? input.headers[name.toLowerCase()] ?? "—";
220
+ const badge = input.verification.state === "ok" ? "signature: VERIFIED" : input.verification.state === "failed" ? `signature: FAILED (${input.verification.reason})` : "signature: not checked (pass --secret to verify)";
221
+ let prettyBody = input.body;
222
+ try {
223
+ prettyBody = JSON.stringify(JSON.parse(input.body), null, 2);
224
+ } catch {}
225
+ return [
226
+ `\n── #${input.index} ${input.method} ${input.path} ${input.at} ──`,
227
+ `webhook-id: ${wh("webhook-id")}`,
228
+ `webhook-timestamp: ${wh("webhook-timestamp")}`,
229
+ `webhook-signature: ${wh("webhook-signature")}`,
230
+ badge,
231
+ "body:",
232
+ prettyBody,
233
+ ""
234
+ ].join("\n");
235
+ }
236
+ /** Minimal flag/value argv parser: `--key value` and `--flag`. */
237
+ function parseArgs(argv) {
238
+ const _ = [];
239
+ const flags = {};
240
+ for (let i = 0; i < argv.length; i++) {
241
+ const a = argv[i];
242
+ if (a.startsWith("--")) {
243
+ const key = a.slice(2);
244
+ const next = argv[i + 1];
245
+ if (next === void 0 || next.startsWith("--")) flags[key] = true;
246
+ else {
247
+ flags[key] = next;
248
+ i++;
249
+ }
250
+ } else _.push(a);
251
+ }
252
+ return {
253
+ _,
254
+ flags
255
+ };
256
+ }
257
+ /** A core over the env-configured storage (dispatcher NOT started). */
258
+ async function makeCore() {
259
+ const [storage, queueStorage] = await Promise.all([buildStorage("STORAGE"), buildQueueStorage()]);
260
+ const retention = retentionFromEnv();
261
+ return createWebhooksCore({
262
+ storage,
263
+ ...queueStorage ? { queueStorage } : {},
264
+ ...retention ? { retention } : {},
265
+ dispatcher: dispatcherOptionsFromEnv()
266
+ });
267
+ }
268
+ /** Read this package's version from the nearest package.json (dist or src). */
269
+ async function pkgVersion() {
270
+ try {
271
+ const { readFile } = await import("node:fs/promises");
272
+ const { fileURLToPath } = await import("node:url");
273
+ return JSON.parse(await readFile(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8")).version ?? "unknown";
274
+ } catch {
275
+ return "unknown";
276
+ }
277
+ }
278
+ function helpText(version) {
279
+ return `xtandard-webhooks v${version} — self-hosted, Standard Webhooks-compliant outbound webhook control plane
280
+
281
+ Usage:
282
+ xtandard-webhooks <command> [options] # the binary, after: npm i -g @xtandard/webhooks
283
+ npx @xtandard/webhooks <command> # without installing (or: bunx @xtandard/webhooks …)
284
+
285
+ Commands:
286
+ serve [--port <n>] Run the admin panel + API + delivery dispatcher (no Docker).
287
+ dispatch [--port <n>] Run the delivery dispatcher ONLY (split-worker mode).
288
+ With --port/$PORT: also serves GET /healthcheck.
289
+ init [--app <key>] Create an application + an example event type.
290
+ list-apps List applications.
291
+ list-endpoints --app <key> List an application's endpoints.
292
+ publish --app <key> --type <event> --data '<json>' [--idempotency-key <k>]
293
+ Publish a message (ingest from shell/CI).
294
+ retry --app <key> --delivery <id>
295
+ Re-queue a dead-lettered delivery.
296
+ verify --secret <whsec_…> --payload-file <path> --headers-file <path>
297
+ Verify a captured webhook (receiver-side debugging):
298
+ reads the raw payload + a JSON headers object,
299
+ prints the envelope, exits 1 on failure.
300
+ sign --secret <whsec_…> --data '<json>' [--id <msg_…>] [--timestamp <unix>] [--url <target>]
301
+ Build a signed Standard Webhooks request (the
302
+ signature playground): prints the headers + body,
303
+ and a ready-to-run curl when --url is given.
304
+ listen [--port <n>] [--secret <whsec_…>] [--status <code>]
305
+ Run a local inspecting receiver (the webhook.site
306
+ you host yourself): pretty-prints every incoming
307
+ webhook; with --secret, verifies the signature.
308
+
309
+ Global options:
310
+ -h, --help Show this help.
311
+ -v, --version Print the version.
312
+
313
+ \`serve\` / \`dispatch\` options:
314
+ --port <n> Port to listen on (default: $PORT or 3000).
315
+
316
+ Environment variables
317
+ Storage (control plane STORAGE_; delivery queue QUEUE_, defaults to the same store):
318
+ STORAGE_DRIVER, QUEUE_STORAGE_DRIVER
319
+ memory | file | redis | postgres | mongodb | sqlite | libsql
320
+ (CLI default: file · Docker default: memory)
321
+ REDIS_URL redis://localhost:6379 (driver: redis)
322
+ STORAGE_PREFIX, QUEUE_PREFIX key namespace (driver: redis)
323
+ DATABASE_URL | POSTGRES_URL postgres://… (driver: postgres)
324
+ STORAGE_PG_TABLE, QUEUE_PG_TABLE (driver: postgres)
325
+ MONGO_URL, MONGO_DB, STORAGE_MONGO_COLLECTION, QUEUE_MONGO_COLLECTION (driver: mongodb)
326
+ STORAGE_FILE_DIR, QUEUE_FILE_DIR default ./.webhooks/{storage,queue} (driver: file)
327
+ STORAGE_SQLITE_PATH, QUEUE_SQLITE_PATH (driver: sqlite, Bun only)
328
+ LIBSQL_URL | STORAGE_LIBSQL_URL, QUEUE_LIBSQL_URL, LIBSQL_AUTH_TOKEN (driver: libsql)
329
+
330
+ Server (\`serve\` / standalone):
331
+ PORT 3000 Port to listen on (or --port).
332
+ BASE_PATH "" URL prefix, e.g. "/webhooks".
333
+ TITLE Navbar wordmark.
334
+ LOGO_URL Logo image URL.
335
+ READONLY 1|true Block all mutating operations.
336
+ AUTH_MODE none|basic Authentication mode (default none).
337
+ AUTH_USERNAME admin Username for basic auth.
338
+ AUTH_PASSWORD_HASH scrypt hash (preferred; see docs/AUTH.md).
339
+ AUTH_PASSWORD Plaintext password (dev only).
340
+ PORTAL_SECRET Enables portal-token access (whpt_… bearer tokens).
341
+
342
+ Delivery (\`serve\` / \`dispatch\` / standalone):
343
+ DISPATCHER 1 0|false disables the in-process dispatcher
344
+ (host publishes only; a split worker delivers).
345
+ RETRY_SCHEDULE Comma list of retry delays, e.g. "0s,5s,5m,30m,2h,5h,10h".
346
+
347
+ Retention (pruned opportunistically after publishes; kept if EITHER rule keeps it;
348
+ messages with a non-terminal delivery are always kept):
349
+ MESSAGE_KEEP_LAST Keep at most the N most recent messages per app.
350
+ MESSAGE_MAX_AGE 30d Keep messages newer than this (ms|s|m|h|d, e.g. 34h).
351
+ AUDIT_KEEP_LAST Keep at most the N most recent audit entries.
352
+ AUDIT_MAX_AGE 90d Keep audit entries newer than this.
353
+
354
+ Examples:
355
+ # Quick local panel (file storage, no auth):
356
+ npx @xtandard/webhooks serve --port 3004
357
+
358
+ # Production-ish: Redis storage + basic auth:
359
+ PORT=4000 AUTH_MODE=basic AUTH_USERNAME=admin AUTH_PASSWORD=secret \\
360
+ STORAGE_DRIVER=redis REDIS_URL=redis://localhost:6379 \\
361
+ npx @xtandard/webhooks serve
362
+
363
+ # Split planes: Postgres control plane + Redis queue:
364
+ STORAGE_DRIVER=postgres DATABASE_URL=postgres://localhost:5432/webhooks \\
365
+ QUEUE_STORAGE_DRIVER=redis REDIS_URL=redis://localhost:6379 \\
366
+ npx @xtandard/webhooks serve
367
+
368
+ # Split worker: the web process publishes only (DISPATCHER=0); this delivers:
369
+ STORAGE_DRIVER=redis REDIS_URL=redis://localhost:6379 \\
370
+ npx @xtandard/webhooks dispatch
371
+
372
+ # Publish from CI:
373
+ xtandard-webhooks publish --app acme --type invoice.paid --data '{"invoiceId":"inv_1"}'
374
+
375
+ # Debug a captured webhook on the receiving side:
376
+ xtandard-webhooks verify --secret whsec_… --payload-file body.json --headers-file headers.json
377
+
378
+ # Inspect webhooks locally (point an endpoint at http://localhost:4000):
379
+ xtandard-webhooks listen --port 4000 --secret whsec_…
380
+
381
+ # Build + fire a signed request by hand (the signature playground):
382
+ xtandard-webhooks sign --secret whsec_… --data '{"hi":1}' --url http://localhost:4000
383
+
384
+ Receivers verify with @xtandard/webhooks/receiver — or any Standard Webhooks
385
+ library. Docs: https://github.com/xantiagoma/xtandard-webhooks
386
+ `;
387
+ }
388
+ /** Entry point. Returns the process exit code. */
389
+ async function run(argv) {
390
+ const { _, flags } = parseArgs(argv);
391
+ const command = _[0];
392
+ if (flags.version === true || argv.includes("-v") || command === "version") {
393
+ process.stdout.write(`${await pkgVersion()}\n`);
394
+ return 0;
395
+ }
396
+ const wantsHelp = Boolean(flags.help) || argv.includes("-h") || command === "help";
397
+ if (!command || wantsHelp) {
398
+ process.stdout.write(helpText(await pkgVersion()));
399
+ return wantsHelp ? 0 : 1;
400
+ }
401
+ try {
402
+ switch (command) {
403
+ case "init": {
404
+ const core = await makeCore();
405
+ const app = typeof flags.app === "string" ? flags.app : env("APP", "default");
406
+ if (!await core.getApplication(app)) await core.createApplication({ key: app });
407
+ await core.upsertEventType({
408
+ name: "example.ping",
409
+ description: "Example event type created by `xtandard-webhooks init`."
410
+ });
411
+ process.stdout.write(`Initialized application "${app}" with event type "example.ping".\nAdd endpoints in the panel (xtandard-webhooks serve), then:\n xtandard-webhooks publish --app ${app} --type example.ping --data '{"hello":"world"}'\n`);
412
+ return 0;
413
+ }
414
+ case "list-apps": {
415
+ const apps = await (await makeCore()).listApplications();
416
+ if (apps.length === 0) process.stdout.write("No applications.\n");
417
+ for (const app of apps) process.stdout.write(`${app.key}${app.name ? ` ${app.name}` : ""}\n`);
418
+ return 0;
419
+ }
420
+ case "list-endpoints": {
421
+ if (typeof flags.app !== "string") {
422
+ process.stderr.write("Usage: xtandard-webhooks list-endpoints --app <key>\n");
423
+ return 1;
424
+ }
425
+ const endpoints = await (await makeCore()).listEndpoints(flags.app);
426
+ if (endpoints.length === 0) process.stdout.write("No endpoints.\n");
427
+ for (const endpoint of endpoints) {
428
+ const subscriptions = endpoint.eventTypes?.length ? endpoint.eventTypes.join(",") : "all events";
429
+ process.stdout.write(`${endpoint.disabled ? "○" : "●"} ${endpoint.id} ${endpoint.url} [${subscriptions}]\n`);
430
+ }
431
+ return 0;
432
+ }
433
+ case "publish": {
434
+ if (typeof flags.app !== "string" || typeof flags.type !== "string") {
435
+ process.stderr.write("Usage: xtandard-webhooks publish --app <key> --type <event> --data '<json>' [--idempotency-key <k>]\n");
436
+ return 1;
437
+ }
438
+ let payload = {};
439
+ if (typeof flags.data === "string") try {
440
+ payload = JSON.parse(flags.data);
441
+ } catch {
442
+ process.stderr.write("Invalid --data JSON.\n");
443
+ return 1;
444
+ }
445
+ const core = await makeCore();
446
+ const idempotencyKey = typeof flags["idempotency-key"] === "string" ? flags["idempotency-key"] : void 0;
447
+ const result = await core.publish(flags.app, {
448
+ eventType: flags.type,
449
+ payload,
450
+ ...idempotencyKey !== void 0 ? { idempotencyKey } : {}
451
+ });
452
+ process.stdout.write(`${result.deduplicated ? "Deduplicated" : "Published"} ${result.message.id} → ${result.deliveries.length} deliver${result.deliveries.length === 1 ? "y" : "ies"} queued.\n`);
453
+ return 0;
454
+ }
455
+ case "retry": {
456
+ if (typeof flags.app !== "string" || typeof flags.delivery !== "string") {
457
+ process.stderr.write("Usage: xtandard-webhooks retry --app <key> --delivery <id>\n");
458
+ return 1;
459
+ }
460
+ const delivery = await (await makeCore()).retryDelivery(flags.app, flags.delivery);
461
+ process.stdout.write(`Re-queued delivery ${delivery.id} (status: ${delivery.status}).\n`);
462
+ return 0;
463
+ }
464
+ case "verify": {
465
+ if (typeof flags.secret !== "string" || typeof flags["payload-file"] !== "string" || typeof flags["headers-file"] !== "string") {
466
+ process.stderr.write("Usage: xtandard-webhooks verify --secret <whsec_…> --payload-file <path> --headers-file <path>\n");
467
+ return 1;
468
+ }
469
+ const { readFile } = await import("node:fs/promises");
470
+ const payload = await readFile(flags["payload-file"], "utf8");
471
+ let headers;
472
+ try {
473
+ headers = JSON.parse(await readFile(flags["headers-file"], "utf8"));
474
+ } catch {
475
+ process.stderr.write("Invalid --headers-file: expected a JSON object of headers.\n");
476
+ return 1;
477
+ }
478
+ const { verify, WebhookVerificationError } = await import("./signing.mjs");
479
+ try {
480
+ const envelope = await verify({
481
+ payload,
482
+ headers,
483
+ secret: flags.secret
484
+ });
485
+ process.stdout.write(`Signature OK.\n${JSON.stringify(envelope, null, 2)}\n`);
486
+ return 0;
487
+ } catch (err) {
488
+ if (err instanceof WebhookVerificationError) {
489
+ process.stderr.write(`Verification FAILED: ${err.message}\n`);
490
+ return 1;
491
+ }
492
+ throw err;
493
+ }
494
+ }
495
+ case "sign": {
496
+ if (typeof flags.secret !== "string" || typeof flags.data !== "string") {
497
+ process.stderr.write("Usage: xtandard-webhooks sign --secret <whsec_…> --data '<json>' [--id <msg_…>] [--timestamp <unix-seconds>] [--url <post-target>]\n");
498
+ return 1;
499
+ }
500
+ try {
501
+ JSON.parse(flags.data);
502
+ } catch {
503
+ process.stderr.write("Invalid --data: expected a JSON string.\n");
504
+ return 1;
505
+ }
506
+ const { newId } = await import("./core-CMpnmI5Q.mjs").then((n) => n.S);
507
+ const { sign } = await import("./signing.mjs");
508
+ const id = typeof flags.id === "string" ? flags.id : newId("msg");
509
+ const timestamp = typeof flags.timestamp === "string" ? Number(flags.timestamp) : Math.floor(Date.now() / 1e3);
510
+ const signature = await sign(flags.secret, id, timestamp, flags.data);
511
+ const headerLines = [
512
+ `webhook-id: ${id}`,
513
+ `webhook-timestamp: ${timestamp}`,
514
+ `webhook-signature: ${signature}`
515
+ ];
516
+ process.stdout.write(`${headerLines.join("\n")}\n\n${flags.data}\n`);
517
+ if (typeof flags.url === "string") {
518
+ const curl = [
519
+ `curl -X POST ${flags.url} \\`,
520
+ ` -H 'content-type: application/json' \\`,
521
+ ` -H 'webhook-id: ${id}' \\`,
522
+ ` -H 'webhook-timestamp: ${timestamp}' \\`,
523
+ ` -H 'webhook-signature: ${signature}' \\`,
524
+ ` -d '${flags.data.replace(/'/g, "'\\''")}'`
525
+ ].join("\n");
526
+ process.stdout.write(`\n${curl}\n`);
527
+ }
528
+ return 0;
529
+ }
530
+ case "listen": {
531
+ const port = Number(flags.port || env("PORT", "4000"));
532
+ const secret = typeof flags.secret === "string" ? flags.secret : void 0;
533
+ const status = typeof flags.status === "string" ? Number(flags.status) : 200;
534
+ const { verify, WebhookVerificationError } = await import("./signing.mjs");
535
+ let count = 0;
536
+ await startServer(port, async (request) => {
537
+ const url = new URL(request.url);
538
+ if (url.pathname === "/healthcheck") return new Response(JSON.stringify({ status: "ok" }), { headers: { "content-type": "application/json" } });
539
+ const body = await request.text();
540
+ const headers = {};
541
+ request.headers.forEach((value, key) => {
542
+ headers[key] = value;
543
+ });
544
+ let verification = { state: "unchecked" };
545
+ if (secret) try {
546
+ await verify({
547
+ payload: body,
548
+ headers,
549
+ secret
550
+ });
551
+ verification = { state: "ok" };
552
+ } catch (err) {
553
+ verification = {
554
+ state: "failed",
555
+ reason: err instanceof WebhookVerificationError ? err.message : String(err)
556
+ };
557
+ }
558
+ count += 1;
559
+ process.stdout.write(formatInboundWebhook({
560
+ index: count,
561
+ method: request.method,
562
+ path: url.pathname,
563
+ headers,
564
+ body,
565
+ verification,
566
+ at: (/* @__PURE__ */ new Date()).toISOString()
567
+ }));
568
+ if (verification.state === "failed") return new Response("invalid signature", { status: 401 });
569
+ return new Response("ok", { status });
570
+ });
571
+ process.stdout.write(`[xtandard/webhooks] listening for webhooks on http://localhost:${port}${secret ? " (verifying signatures)" : " (not verifying — pass --secret)"}\n`);
572
+ return await new Promise(() => {});
573
+ }
574
+ case "dispatch": {
575
+ const core = await makeCore();
576
+ const { createDispatcher } = await import("./dispatcher-Coubwrka.mjs").then((n) => n.r);
577
+ createDispatcher(core).start();
578
+ const [storageDesc, queueDesc] = await Promise.all([describeStorage("STORAGE"), env("QUEUE_STORAGE_DRIVER") ? describeStorage("QUEUE") : Promise.resolve("(same store)")]);
579
+ process.stdout.write(`[xtandard/webhooks] storage: ${storageDesc}\n`);
580
+ process.stdout.write(`[xtandard/webhooks] queue: ${queueDesc}\n`);
581
+ const portRaw = typeof flags.port === "string" ? flags.port : env("PORT");
582
+ if (portRaw) {
583
+ const port = Number(portRaw);
584
+ await startServer(port, (request) => {
585
+ if (new URL(request.url).pathname === "/healthcheck") return new Response(JSON.stringify({
586
+ status: "ok",
587
+ dispatcher: "running"
588
+ }), { headers: { "content-type": "application/json" } });
589
+ return new Response("Not Found", { status: 404 });
590
+ });
591
+ process.stdout.write(`[xtandard/webhooks] dispatcher running; healthcheck on http://localhost:${port}/healthcheck\n`);
592
+ } else {
593
+ setInterval(() => {}, 2 ** 30);
594
+ process.stdout.write("[xtandard/webhooks] dispatcher running.\n");
595
+ }
596
+ return await new Promise(() => {});
597
+ }
598
+ case "serve": {
599
+ const port = Number(flags.port || env("PORT", "3000"));
600
+ const basePath = env("BASE_PATH", "");
601
+ const title = env("TITLE", "@xtandard/webhooks");
602
+ const logoUrl = env("LOGO_URL");
603
+ const readonly = isTruthy(env("READONLY"));
604
+ const authMode = env("AUTH_MODE", "none");
605
+ const portalSecret = env("PORTAL_SECRET");
606
+ const { createFetchHandler } = await import("./create-fetch-handler-BIdk9P30.mjs").then((n) => n.n);
607
+ const [storage, queueStorage] = await Promise.all([buildStorage("STORAGE"), buildQueueStorage()]);
608
+ const { auth, authorization } = await buildAuth();
609
+ if (authMode === "none") process.stderr.write("[xtandard/webhooks] AUTH_MODE=none — do NOT expose this publicly without authentication.\n");
610
+ const retention = retentionFromEnv();
611
+ const panel = createFetchHandler({
612
+ storage,
613
+ ...queueStorage ? { queueStorage } : {},
614
+ basePath,
615
+ title,
616
+ ...logoUrl ? { logoUrl } : {},
617
+ readonly,
618
+ auth,
619
+ authorization,
620
+ ...portalSecret ? { portal: { secret: portalSecret } } : {},
621
+ ...retention ? { retention } : {},
622
+ dispatcher: dispatcherEnabled() ? dispatcherOptionsFromEnv() : false
623
+ });
624
+ const normalizedBase = basePath && basePath !== "/" ? basePath.startsWith("/") ? basePath : `/${basePath}` : "";
625
+ const handler = (request) => {
626
+ const url = new URL(request.url);
627
+ if (url.pathname === "/healthcheck" || url.pathname === `${normalizedBase}/healthcheck`) return new Response(JSON.stringify({
628
+ status: "ok",
629
+ title
630
+ }), { headers: { "content-type": "application/json" } });
631
+ return panel.fetch(request);
632
+ };
633
+ const [storageDesc, queueDesc] = await Promise.all([describeStorage("STORAGE"), env("QUEUE_STORAGE_DRIVER") ? describeStorage("QUEUE") : Promise.resolve("(same store)")]);
634
+ process.stdout.write(`[xtandard/webhooks] storage: ${storageDesc}\n`);
635
+ process.stdout.write(`[xtandard/webhooks] queue: ${queueDesc}\n`);
636
+ process.stdout.write(`[xtandard/webhooks] dispatcher: ${panel.dispatcher ? "running" : "disabled (DISPATCHER=0)"}\n`);
637
+ await startServer(port, handler);
638
+ process.stdout.write(`[xtandard/webhooks] listening on http://localhost:${port}${normalizedBase || "/"}\n`);
639
+ return await new Promise(() => {});
640
+ }
641
+ default:
642
+ process.stderr.write(`Unknown command: ${command}\n\n${helpText(await pkgVersion())}`);
643
+ return 1;
644
+ }
645
+ } catch (err) {
646
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
647
+ return 1;
648
+ }
649
+ }
650
+ //#endregion
651
+ export { formatInboundWebhook, run };
652
+
653
+ //# sourceMappingURL=cli.mjs.map