@xtandard/webhooks 0.1.1 → 0.1.3
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/README.md +1 -0
- package/dist/cli.cjs +4 -4
- package/dist/cli.mjs +4 -4
- package/dist/{core-CF8U0hgi.cjs → core-DkIXozBY.cjs} +2 -2
- package/dist/{core-CF8U0hgi.cjs.map → core-DkIXozBY.cjs.map} +1 -1
- package/dist/{core-C9mPi4iw.mjs → core-tmmtadF1.mjs} +2 -2
- package/dist/{core-C9mPi4iw.mjs.map → core-tmmtadF1.mjs.map} +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.mjs +1 -1
- package/dist/{create-fetch-handler-BILaZ2Z7.cjs → create-fetch-handler-B5WBbf8x.cjs} +3 -3
- package/dist/{create-fetch-handler-BILaZ2Z7.cjs.map → create-fetch-handler-B5WBbf8x.cjs.map} +1 -1
- package/dist/{create-fetch-handler-D0F3cjoq.mjs → create-fetch-handler-DZohI08M.mjs} +3 -3
- package/dist/{create-fetch-handler-D0F3cjoq.mjs.map → create-fetch-handler-DZohI08M.mjs.map} +1 -1
- package/dist/{dispatcher-DOOCJJxM.cjs → dispatcher-Bn4O874S.cjs} +3 -3
- package/dist/{dispatcher-DOOCJJxM.cjs.map → dispatcher-Bn4O874S.cjs.map} +1 -1
- package/dist/{dispatcher-DuBrQL46.mjs → dispatcher-CQcoIsS9.mjs} +3 -3
- package/dist/{dispatcher-DuBrQL46.mjs.map → dispatcher-CQcoIsS9.mjs.map} +1 -1
- package/dist/elysia-shared-CgTU3yPa.mjs +47 -0
- package/dist/elysia-shared-CgTU3yPa.mjs.map +1 -0
- package/dist/elysia-shared-Cop1-ie0.cjs +58 -0
- package/dist/elysia-shared-Cop1-ie0.cjs.map +1 -0
- package/dist/elysia-shared-CvSsqMbR.d.mts +19 -0
- package/dist/elysia-shared-DSSrfuoF.d.cts +19 -0
- package/dist/entry-bun.cjs +1 -1
- package/dist/entry-bun.mjs +1 -1
- package/dist/entry-elysia.cjs +5 -25
- package/dist/entry-elysia.cjs.map +1 -1
- package/dist/entry-elysia.d.cts +5 -15
- package/dist/entry-elysia.d.mts +5 -15
- package/dist/entry-elysia.mjs +4 -24
- package/dist/entry-elysia.mjs.map +1 -1
- package/dist/entry-elysia2.cjs +95 -0
- package/dist/entry-elysia2.cjs.map +1 -0
- package/dist/entry-elysia2.d.cts +1273 -0
- package/dist/entry-elysia2.d.mts +1273 -0
- package/dist/entry-elysia2.mjs +93 -0
- package/dist/entry-elysia2.mjs.map +1 -0
- package/dist/entry-express.cjs +1 -1
- package/dist/entry-express.mjs +1 -1
- package/dist/entry-hono.cjs +1 -1
- package/dist/entry-hono.mjs +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +3 -3
- package/dist/react.css +1 -1
- package/dist/react.js +2522 -2506
- package/dist/testing.cjs +2 -2
- package/dist/testing.mjs +2 -2
- package/dist/types-elysia2/adapters/elysia-shared.d.ts +34 -0
- package/dist/types-elysia2/adapters/elysia2.d.ts +820 -0
- package/dist/types-elysia2/auth/contract.d.ts +26 -0
- package/dist/types-elysia2/authorization/contract.d.ts +50 -0
- package/dist/types-elysia2/canonical.d.ts +27 -0
- package/dist/types-elysia2/core.d.ts +344 -0
- package/dist/types-elysia2/deliver.d.ts +79 -0
- package/dist/types-elysia2/delivery-sink.d.ts +57 -0
- package/dist/types-elysia2/dispatcher.d.ts +80 -0
- package/dist/types-elysia2/duration.d.ts +26 -0
- package/dist/types-elysia2/entry-elysia2.d.ts +8 -0
- package/dist/types-elysia2/hooks/contract.d.ts +203 -0
- package/dist/types-elysia2/id.d.ts +24 -0
- package/dist/types-elysia2/keys.d.ts +98 -0
- package/dist/types-elysia2/portal.d.ts +69 -0
- package/dist/types-elysia2/schema.d.ts +229 -0
- package/dist/types-elysia2/server/base-path.d.ts +15 -0
- package/dist/types-elysia2/server/cors.d.ts +36 -0
- package/dist/types-elysia2/server/create-fetch-handler.d.ts +121 -0
- package/dist/types-elysia2/server/openapi.d.ts +25 -0
- package/dist/types-elysia2/server/render-index-html.d.ts +18 -0
- package/dist/types-elysia2/server/routes.d.ts +52 -0
- package/dist/types-elysia2/server/static-assets.d.ts +13 -0
- package/dist/types-elysia2/signing.d.ts +88 -0
- package/dist/types-elysia2/storage/contract.d.ts +75 -0
- package/dist/types-elysia2/validation.d.ts +82 -0
- package/dist/types-elysia2/version.d.ts +8 -0
- package/dist/types-react/ui/lib/portal-container.d.ts +24 -0
- package/dist/ui/assets/{index-S5t_CLOe.js → index-BgyX_njf.js} +44 -44
- package/dist/ui/assets/index-mJCJq_EB.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +23 -6
- package/dist/ui/assets/index-B0eoQX2U.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher-DOOCJJxM.cjs","names":["durationToMs","dueKey","deliveryKey","attemptDelivery"],"sources":["../src/dispatcher.ts"],"sourcesContent":["/**\n * The delivery engine. Polls the due index, claims deliveries (lease-based,\n * multi-instance safe when storage supports CAS or the `deliveryQueue`\n * capability), performs the signed HTTP attempts, and drives each delivery's\n * state machine through the retry schedule into success or dead-letter.\n *\n * Runs in-process: the panel starts one by default, a split worker runs one\n * via the CLI (`xtandard-webhooks dispatch`), and tests drive {@link Dispatcher.tick}\n * manually. All timers are `unref()`ed so a dispatcher never keeps a process\n * alive on its own. Semantics are **at-least-once**: a crashed process loses\n * nothing (leases expire, the next tick reclaims); receivers dedupe on\n * `webhook-id`.\n *\n * @module\n */\n\nimport { attemptDelivery, type AttemptOutcome } from \"./deliver.ts\";\nimport type { WebhooksCore } from \"./core.ts\";\nimport { durationToMs } from \"./duration.ts\";\nimport { deliveryKey, dueKey, type DueEntry } from \"./keys.ts\";\nimport type { Delivery, WebhookDuration } from \"./schema.ts\";\nimport { VERSION } from \"./version.ts\";\n\n/** Options for {@link createDispatcher} (also accepted on panels and the core). */\nexport interface DispatcherOptions {\n /** How often to poll for due deliveries. Default `1000`. */\n pollIntervalMs?: number;\n /** Max deliveries claimed per tick. Default `20`. */\n batchSize?: number;\n /** Max in-flight HTTP attempts. Default `8`. */\n concurrency?: number;\n /** Per-attempt timeout (AbortController). Default `20_000`. */\n timeoutMs?: number;\n /** Claim lease duration; an expired lease makes a claim reclaimable. Default `60_000`. */\n leaseMs?: number;\n /**\n * Delay before attempt N+1 after attempt N fails (index 0 = the initial\n * attempt's delay). Exhausting the schedule dead-letters the delivery.\n * Default `[\"0s\", \"5s\", \"5m\", \"30m\", \"2h\", \"5h\", \"10h\"]` (Svix-compatible).\n */\n retrySchedule?: WebhookDuration[];\n /**\n * Auto-disable endpoints whose every attempt has failed for this many\n * consecutive days. `false` disables the policy. Default `{ failingForDays: 5 }`.\n */\n autoDisable?: { failingForDays?: number } | false;\n /** Cap on stored response-body characters per attempt. Default `4096`. */\n responseBodyLimit?: number;\n /** Injectable fetch (tests, instrumentation). Default: global fetch. */\n fetch?: typeof fetch;\n /** `user-agent` header. Default `\"xtandard-webhooks/<version>\"`. */\n userAgent?: string;\n}\n\n/** The delivery engine handle. */\nexport interface Dispatcher {\n /** Begin polling. Idempotent. */\n start(): void;\n /** Stop polling and wait for in-flight attempts to finish. */\n stop(): Promise<void>;\n /**\n * Run one manual pass: claim due deliveries, attempt them, record outcomes.\n * Returns the number of attempts made — the unit-test surface (tests never\n * assert on timers).\n */\n tick(): Promise<number>;\n readonly running: boolean;\n}\n\n/** The default retry schedule (Svix-compatible). */\nexport const DEFAULT_RETRY_SCHEDULE: WebhookDuration[] = [\n \"0s\",\n \"5s\",\n \"5m\",\n \"30m\",\n \"2h\",\n \"5h\",\n \"10h\",\n];\n\n/** Fractional jitter applied to every retry delay (±10%). */\nconst JITTER = 0.1;\n\n/**\n * Create a dispatcher over a core. Not started — call\n * {@link Dispatcher.start}, or drive {@link Dispatcher.tick} manually.\n *\n * @example\n * ```ts\n * import { createWebhooksCore, createDispatcher } from \"@xtandard/webhooks\";\n * import { createMemoryStorage } from \"@xtandard/webhooks/storage/memory\";\n *\n * const core = createWebhooksCore({ storage: createMemoryStorage() });\n * const dispatcher = createDispatcher(core);\n * dispatcher.start();\n * ```\n */\nexport function createDispatcher(core: WebhooksCore, options: DispatcherOptions = {}): Dispatcher {\n const merged = { ...core.options.dispatcher, ...options };\n const pollIntervalMs = merged.pollIntervalMs ?? 1000;\n const batchSize = merged.batchSize ?? 20;\n const concurrency = merged.concurrency ?? 8;\n const timeoutMs = merged.timeoutMs ?? 20_000;\n // The lease must outlast a single attempt, or a slow-but-alive attempt runs\n // past its lease and a second dispatcher reclaims and re-sends it. Enforce\n // lease > timeout with headroom for recording the outcome.\n const configuredLeaseMs = merged.leaseMs ?? 60_000;\n const leaseMs = Math.max(configuredLeaseMs, timeoutMs + 10_000);\n const schedule = merged.retrySchedule ?? DEFAULT_RETRY_SCHEDULE;\n const autoDisable = merged.autoDisable ?? { failingForDays: 5 };\n const responseBodyLimit = merged.responseBodyLimit ?? 4096;\n const userAgent = merged.userAgent ?? `xtandard-webhooks/${VERSION}`;\n const doFetch = merged.fetch;\n const now = core.options.now;\n\n let timer: ReturnType<typeof setInterval> | null = null;\n let inFlight: Promise<number> | null = null;\n\n /** Delay before the next attempt, per schedule position, with ±10% jitter. */\n function nextDelayMs(attemptsMade: number): number | null {\n if (attemptsMade >= schedule.length) return null; // exhausted\n const nominal = durationToMs(schedule[attemptsMade] as WebhookDuration);\n const jitter = 1 + (Math.random() * 2 - 1) * JITTER;\n return Math.round(nominal * jitter);\n }\n\n async function processClaim(delivery: Delivery): Promise<boolean> {\n const app = delivery.applicationKey;\n const trigger = delivery.pendingTrigger ?? \"schedule\";\n\n const failTerminal = async (error: string, eventType: string): Promise<boolean> => {\n const outcome: AttemptOutcome = {\n ok: false,\n error,\n durationMs: 0,\n at: new Date(now()).toISOString(),\n };\n await core.recordAttempt({ delivery, outcome, trigger, nextAttemptAt: null, eventType });\n return true;\n };\n\n const message = await core.getMessage(app, delivery.messageId);\n if (!message) return failTerminal(\"Message no longer exists\", \"unknown\");\n\n const endpoint = await core.getEndpoint(app, delivery.endpointId);\n if (!endpoint) return failTerminal(\"Endpoint no longer exists\", message.eventType);\n\n if (endpoint.disabled) {\n // Held, not failed: release the claim and re-check after the lease\n // window. Re-enabling the endpoint resumes delivery automatically.\n const queue = core.options.queueStorage;\n const recheckAt = now() + leaseMs;\n const released: Delivery = {\n ...delivery,\n status: \"pending\",\n nextAttemptAt: new Date(recheckAt).toISOString(),\n leaseUntil: null,\n updatedAt: new Date(now()).toISOString(),\n };\n // Remove the lease-position due entry the claim created, then park the\n // delivery at the recheck time.\n if (delivery.leaseUntil) {\n await queue.removeItem(dueKey(app, Date.parse(delivery.leaseUntil), delivery.id));\n }\n await queue.setItem(deliveryKey(app, delivery.id), released);\n await queue.setItem<DueEntry>(dueKey(app, recheckAt, delivery.id), {\n app,\n deliveryId: delivery.id,\n });\n return false; // no attempt made\n }\n\n const outcome = await attemptDelivery({\n endpoint,\n messageId: delivery.messageId,\n body: message.envelope,\n timeoutMs,\n responseBodyLimit,\n userAgent,\n ...(doFetch ? { fetch: doFetch } : {}),\n nowMs: now(),\n });\n\n const attemptsMade = delivery.attemptCount + 1;\n const delay = outcome.ok ? null : nextDelayMs(attemptsMade);\n await core.recordAttempt({\n delivery,\n outcome,\n trigger,\n nextAttemptAt: delay === null ? null : new Date(now() + delay).toISOString(),\n eventType: message.eventType,\n });\n await core.noteEndpointOutcome(app, endpoint.id, outcome.ok, autoDisable);\n return true;\n }\n\n async function runTick(): Promise<number> {\n const claimed = await core.claimDueDeliveries({ limit: batchSize, leaseMs });\n if (claimed.length === 0) return 0;\n\n let attempts = 0;\n let cursor = 0;\n const workers = Array.from({ length: Math.min(concurrency, claimed.length) }, async () => {\n while (cursor < claimed.length) {\n const delivery = claimed[cursor++] as Delivery;\n try {\n if (await processClaim(delivery)) attempts++;\n } catch (error) {\n // A storage failure mid-claim: leave the delivery leased; the lease\n // expiry re-exposes it. Never let one claim kill the tick.\n // eslint-disable-next-line no-console\n console.warn(`[@xtandard/webhooks] delivery ${delivery.id} processing failed:`, error);\n }\n }\n });\n await Promise.all(workers);\n return attempts;\n }\n\n const dispatcher: Dispatcher = {\n start() {\n if (timer) return;\n timer = setInterval(() => {\n if (inFlight) return; // never overlap ticks\n inFlight = runTick()\n .catch((error) => {\n // eslint-disable-next-line no-console\n console.warn(\"[@xtandard/webhooks] dispatcher tick failed:\", error);\n return 0;\n })\n .finally(() => {\n inFlight = null;\n });\n }, pollIntervalMs);\n // Never keep the host process alive just to poll.\n (timer as unknown as { unref?: () => void }).unref?.();\n },\n\n async stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n if (inFlight) await inFlight;\n },\n\n async tick() {\n // Manual ticks also serialize against the poller.\n while (inFlight) await inFlight;\n inFlight = runTick().finally(() => {\n inFlight = null;\n });\n return inFlight;\n },\n\n get running() {\n return timer !== null;\n },\n };\n\n return dispatcher;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dispatcher-Bn4O874S.cjs","names":["durationToMs","dueKey","deliveryKey","attemptDelivery"],"sources":["../src/dispatcher.ts"],"sourcesContent":["/**\n * The delivery engine. Polls the due index, claims deliveries (lease-based,\n * multi-instance safe when storage supports CAS or the `deliveryQueue`\n * capability), performs the signed HTTP attempts, and drives each delivery's\n * state machine through the retry schedule into success or dead-letter.\n *\n * Runs in-process: the panel starts one by default, a split worker runs one\n * via the CLI (`xtandard-webhooks dispatch`), and tests drive {@link Dispatcher.tick}\n * manually. All timers are `unref()`ed so a dispatcher never keeps a process\n * alive on its own. Semantics are **at-least-once**: a crashed process loses\n * nothing (leases expire, the next tick reclaims); receivers dedupe on\n * `webhook-id`.\n *\n * @module\n */\n\nimport { attemptDelivery, type AttemptOutcome } from \"./deliver.ts\";\nimport type { WebhooksCore } from \"./core.ts\";\nimport { durationToMs } from \"./duration.ts\";\nimport { deliveryKey, dueKey, type DueEntry } from \"./keys.ts\";\nimport type { Delivery, WebhookDuration } from \"./schema.ts\";\nimport { VERSION } from \"./version.ts\";\n\n/** Options for {@link createDispatcher} (also accepted on panels and the core). */\nexport interface DispatcherOptions {\n /** How often to poll for due deliveries. Default `1000`. */\n pollIntervalMs?: number;\n /** Max deliveries claimed per tick. Default `20`. */\n batchSize?: number;\n /** Max in-flight HTTP attempts. Default `8`. */\n concurrency?: number;\n /** Per-attempt timeout (AbortController). Default `20_000`. */\n timeoutMs?: number;\n /** Claim lease duration; an expired lease makes a claim reclaimable. Default `60_000`. */\n leaseMs?: number;\n /**\n * Delay before attempt N+1 after attempt N fails (index 0 = the initial\n * attempt's delay). Exhausting the schedule dead-letters the delivery.\n * Default `[\"0s\", \"5s\", \"5m\", \"30m\", \"2h\", \"5h\", \"10h\"]` (Svix-compatible).\n */\n retrySchedule?: WebhookDuration[];\n /**\n * Auto-disable endpoints whose every attempt has failed for this many\n * consecutive days. `false` disables the policy. Default `{ failingForDays: 5 }`.\n */\n autoDisable?: { failingForDays?: number } | false;\n /** Cap on stored response-body characters per attempt. Default `4096`. */\n responseBodyLimit?: number;\n /** Injectable fetch (tests, instrumentation). Default: global fetch. */\n fetch?: typeof fetch;\n /** `user-agent` header. Default `\"xtandard-webhooks/<version>\"`. */\n userAgent?: string;\n}\n\n/** The delivery engine handle. */\nexport interface Dispatcher {\n /** Begin polling. Idempotent. */\n start(): void;\n /** Stop polling and wait for in-flight attempts to finish. */\n stop(): Promise<void>;\n /**\n * Run one manual pass: claim due deliveries, attempt them, record outcomes.\n * Returns the number of attempts made — the unit-test surface (tests never\n * assert on timers).\n */\n tick(): Promise<number>;\n readonly running: boolean;\n}\n\n/** The default retry schedule (Svix-compatible). */\nexport const DEFAULT_RETRY_SCHEDULE: WebhookDuration[] = [\n \"0s\",\n \"5s\",\n \"5m\",\n \"30m\",\n \"2h\",\n \"5h\",\n \"10h\",\n];\n\n/** Fractional jitter applied to every retry delay (±10%). */\nconst JITTER = 0.1;\n\n/**\n * Create a dispatcher over a core. Not started — call\n * {@link Dispatcher.start}, or drive {@link Dispatcher.tick} manually.\n *\n * @example\n * ```ts\n * import { createWebhooksCore, createDispatcher } from \"@xtandard/webhooks\";\n * import { createMemoryStorage } from \"@xtandard/webhooks/storage/memory\";\n *\n * const core = createWebhooksCore({ storage: createMemoryStorage() });\n * const dispatcher = createDispatcher(core);\n * dispatcher.start();\n * ```\n */\nexport function createDispatcher(core: WebhooksCore, options: DispatcherOptions = {}): Dispatcher {\n const merged = { ...core.options.dispatcher, ...options };\n const pollIntervalMs = merged.pollIntervalMs ?? 1000;\n const batchSize = merged.batchSize ?? 20;\n const concurrency = merged.concurrency ?? 8;\n const timeoutMs = merged.timeoutMs ?? 20_000;\n // The lease must outlast a single attempt, or a slow-but-alive attempt runs\n // past its lease and a second dispatcher reclaims and re-sends it. Enforce\n // lease > timeout with headroom for recording the outcome.\n const configuredLeaseMs = merged.leaseMs ?? 60_000;\n const leaseMs = Math.max(configuredLeaseMs, timeoutMs + 10_000);\n const schedule = merged.retrySchedule ?? DEFAULT_RETRY_SCHEDULE;\n const autoDisable = merged.autoDisable ?? { failingForDays: 5 };\n const responseBodyLimit = merged.responseBodyLimit ?? 4096;\n const userAgent = merged.userAgent ?? `xtandard-webhooks/${VERSION}`;\n const doFetch = merged.fetch;\n const now = core.options.now;\n\n let timer: ReturnType<typeof setInterval> | null = null;\n let inFlight: Promise<number> | null = null;\n\n /** Delay before the next attempt, per schedule position, with ±10% jitter. */\n function nextDelayMs(attemptsMade: number): number | null {\n if (attemptsMade >= schedule.length) return null; // exhausted\n const nominal = durationToMs(schedule[attemptsMade] as WebhookDuration);\n const jitter = 1 + (Math.random() * 2 - 1) * JITTER;\n return Math.round(nominal * jitter);\n }\n\n async function processClaim(delivery: Delivery): Promise<boolean> {\n const app = delivery.applicationKey;\n const trigger = delivery.pendingTrigger ?? \"schedule\";\n\n const failTerminal = async (error: string, eventType: string): Promise<boolean> => {\n const outcome: AttemptOutcome = {\n ok: false,\n error,\n durationMs: 0,\n at: new Date(now()).toISOString(),\n };\n await core.recordAttempt({ delivery, outcome, trigger, nextAttemptAt: null, eventType });\n return true;\n };\n\n const message = await core.getMessage(app, delivery.messageId);\n if (!message) return failTerminal(\"Message no longer exists\", \"unknown\");\n\n const endpoint = await core.getEndpoint(app, delivery.endpointId);\n if (!endpoint) return failTerminal(\"Endpoint no longer exists\", message.eventType);\n\n if (endpoint.disabled) {\n // Held, not failed: release the claim and re-check after the lease\n // window. Re-enabling the endpoint resumes delivery automatically.\n const queue = core.options.queueStorage;\n const recheckAt = now() + leaseMs;\n const released: Delivery = {\n ...delivery,\n status: \"pending\",\n nextAttemptAt: new Date(recheckAt).toISOString(),\n leaseUntil: null,\n updatedAt: new Date(now()).toISOString(),\n };\n // Remove the lease-position due entry the claim created, then park the\n // delivery at the recheck time.\n if (delivery.leaseUntil) {\n await queue.removeItem(dueKey(app, Date.parse(delivery.leaseUntil), delivery.id));\n }\n await queue.setItem(deliveryKey(app, delivery.id), released);\n await queue.setItem<DueEntry>(dueKey(app, recheckAt, delivery.id), {\n app,\n deliveryId: delivery.id,\n });\n return false; // no attempt made\n }\n\n const outcome = await attemptDelivery({\n endpoint,\n messageId: delivery.messageId,\n body: message.envelope,\n timeoutMs,\n responseBodyLimit,\n userAgent,\n ...(doFetch ? { fetch: doFetch } : {}),\n nowMs: now(),\n });\n\n const attemptsMade = delivery.attemptCount + 1;\n const delay = outcome.ok ? null : nextDelayMs(attemptsMade);\n await core.recordAttempt({\n delivery,\n outcome,\n trigger,\n nextAttemptAt: delay === null ? null : new Date(now() + delay).toISOString(),\n eventType: message.eventType,\n });\n await core.noteEndpointOutcome(app, endpoint.id, outcome.ok, autoDisable);\n return true;\n }\n\n async function runTick(): Promise<number> {\n const claimed = await core.claimDueDeliveries({ limit: batchSize, leaseMs });\n if (claimed.length === 0) return 0;\n\n let attempts = 0;\n let cursor = 0;\n const workers = Array.from({ length: Math.min(concurrency, claimed.length) }, async () => {\n while (cursor < claimed.length) {\n const delivery = claimed[cursor++] as Delivery;\n try {\n if (await processClaim(delivery)) attempts++;\n } catch (error) {\n // A storage failure mid-claim: leave the delivery leased; the lease\n // expiry re-exposes it. Never let one claim kill the tick.\n // eslint-disable-next-line no-console\n console.warn(`[@xtandard/webhooks] delivery ${delivery.id} processing failed:`, error);\n }\n }\n });\n await Promise.all(workers);\n return attempts;\n }\n\n const dispatcher: Dispatcher = {\n start() {\n if (timer) return;\n timer = setInterval(() => {\n if (inFlight) return; // never overlap ticks\n inFlight = runTick()\n .catch((error) => {\n // eslint-disable-next-line no-console\n console.warn(\"[@xtandard/webhooks] dispatcher tick failed:\", error);\n return 0;\n })\n .finally(() => {\n inFlight = null;\n });\n }, pollIntervalMs);\n // Never keep the host process alive just to poll.\n (timer as unknown as { unref?: () => void }).unref?.();\n },\n\n async stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n if (inFlight) await inFlight;\n },\n\n async tick() {\n // Manual ticks also serialize against the poller.\n while (inFlight) await inFlight;\n inFlight = runTick().finally(() => {\n inFlight = null;\n });\n return inFlight;\n },\n\n get running() {\n return timer !== null;\n },\n };\n\n return dispatcher;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
|
|
2
|
-
import { A as attemptDelivery, w as durationToMs } from "./core-
|
|
2
|
+
import { A as attemptDelivery, w as durationToMs } from "./core-tmmtadF1.mjs";
|
|
3
3
|
import { m as dueKey, p as deliveryKey } from "./keys-Byyj4quQ.mjs";
|
|
4
4
|
//#region src/dispatcher.ts
|
|
5
5
|
/**
|
|
@@ -61,7 +61,7 @@ function createDispatcher(core, options = {}) {
|
|
|
61
61
|
const schedule = merged.retrySchedule ?? DEFAULT_RETRY_SCHEDULE;
|
|
62
62
|
const autoDisable = merged.autoDisable ?? { failingForDays: 5 };
|
|
63
63
|
const responseBodyLimit = merged.responseBodyLimit ?? 4096;
|
|
64
|
-
const userAgent = merged.userAgent ?? `xtandard-webhooks/0.1.
|
|
64
|
+
const userAgent = merged.userAgent ?? `xtandard-webhooks/0.1.3`;
|
|
65
65
|
const doFetch = merged.fetch;
|
|
66
66
|
const now = core.options.now;
|
|
67
67
|
let timer = null;
|
|
@@ -193,4 +193,4 @@ function createDispatcher(core, options = {}) {
|
|
|
193
193
|
//#endregion
|
|
194
194
|
export { createDispatcher as n, dispatcher_exports as r, DEFAULT_RETRY_SCHEDULE as t };
|
|
195
195
|
|
|
196
|
-
//# sourceMappingURL=dispatcher-
|
|
196
|
+
//# sourceMappingURL=dispatcher-CQcoIsS9.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher-DuBrQL46.mjs","names":[],"sources":["../src/dispatcher.ts"],"sourcesContent":["/**\n * The delivery engine. Polls the due index, claims deliveries (lease-based,\n * multi-instance safe when storage supports CAS or the `deliveryQueue`\n * capability), performs the signed HTTP attempts, and drives each delivery's\n * state machine through the retry schedule into success or dead-letter.\n *\n * Runs in-process: the panel starts one by default, a split worker runs one\n * via the CLI (`xtandard-webhooks dispatch`), and tests drive {@link Dispatcher.tick}\n * manually. All timers are `unref()`ed so a dispatcher never keeps a process\n * alive on its own. Semantics are **at-least-once**: a crashed process loses\n * nothing (leases expire, the next tick reclaims); receivers dedupe on\n * `webhook-id`.\n *\n * @module\n */\n\nimport { attemptDelivery, type AttemptOutcome } from \"./deliver.ts\";\nimport type { WebhooksCore } from \"./core.ts\";\nimport { durationToMs } from \"./duration.ts\";\nimport { deliveryKey, dueKey, type DueEntry } from \"./keys.ts\";\nimport type { Delivery, WebhookDuration } from \"./schema.ts\";\nimport { VERSION } from \"./version.ts\";\n\n/** Options for {@link createDispatcher} (also accepted on panels and the core). */\nexport interface DispatcherOptions {\n /** How often to poll for due deliveries. Default `1000`. */\n pollIntervalMs?: number;\n /** Max deliveries claimed per tick. Default `20`. */\n batchSize?: number;\n /** Max in-flight HTTP attempts. Default `8`. */\n concurrency?: number;\n /** Per-attempt timeout (AbortController). Default `20_000`. */\n timeoutMs?: number;\n /** Claim lease duration; an expired lease makes a claim reclaimable. Default `60_000`. */\n leaseMs?: number;\n /**\n * Delay before attempt N+1 after attempt N fails (index 0 = the initial\n * attempt's delay). Exhausting the schedule dead-letters the delivery.\n * Default `[\"0s\", \"5s\", \"5m\", \"30m\", \"2h\", \"5h\", \"10h\"]` (Svix-compatible).\n */\n retrySchedule?: WebhookDuration[];\n /**\n * Auto-disable endpoints whose every attempt has failed for this many\n * consecutive days. `false` disables the policy. Default `{ failingForDays: 5 }`.\n */\n autoDisable?: { failingForDays?: number } | false;\n /** Cap on stored response-body characters per attempt. Default `4096`. */\n responseBodyLimit?: number;\n /** Injectable fetch (tests, instrumentation). Default: global fetch. */\n fetch?: typeof fetch;\n /** `user-agent` header. Default `\"xtandard-webhooks/<version>\"`. */\n userAgent?: string;\n}\n\n/** The delivery engine handle. */\nexport interface Dispatcher {\n /** Begin polling. Idempotent. */\n start(): void;\n /** Stop polling and wait for in-flight attempts to finish. */\n stop(): Promise<void>;\n /**\n * Run one manual pass: claim due deliveries, attempt them, record outcomes.\n * Returns the number of attempts made — the unit-test surface (tests never\n * assert on timers).\n */\n tick(): Promise<number>;\n readonly running: boolean;\n}\n\n/** The default retry schedule (Svix-compatible). */\nexport const DEFAULT_RETRY_SCHEDULE: WebhookDuration[] = [\n \"0s\",\n \"5s\",\n \"5m\",\n \"30m\",\n \"2h\",\n \"5h\",\n \"10h\",\n];\n\n/** Fractional jitter applied to every retry delay (±10%). */\nconst JITTER = 0.1;\n\n/**\n * Create a dispatcher over a core. Not started — call\n * {@link Dispatcher.start}, or drive {@link Dispatcher.tick} manually.\n *\n * @example\n * ```ts\n * import { createWebhooksCore, createDispatcher } from \"@xtandard/webhooks\";\n * import { createMemoryStorage } from \"@xtandard/webhooks/storage/memory\";\n *\n * const core = createWebhooksCore({ storage: createMemoryStorage() });\n * const dispatcher = createDispatcher(core);\n * dispatcher.start();\n * ```\n */\nexport function createDispatcher(core: WebhooksCore, options: DispatcherOptions = {}): Dispatcher {\n const merged = { ...core.options.dispatcher, ...options };\n const pollIntervalMs = merged.pollIntervalMs ?? 1000;\n const batchSize = merged.batchSize ?? 20;\n const concurrency = merged.concurrency ?? 8;\n const timeoutMs = merged.timeoutMs ?? 20_000;\n // The lease must outlast a single attempt, or a slow-but-alive attempt runs\n // past its lease and a second dispatcher reclaims and re-sends it. Enforce\n // lease > timeout with headroom for recording the outcome.\n const configuredLeaseMs = merged.leaseMs ?? 60_000;\n const leaseMs = Math.max(configuredLeaseMs, timeoutMs + 10_000);\n const schedule = merged.retrySchedule ?? DEFAULT_RETRY_SCHEDULE;\n const autoDisable = merged.autoDisable ?? { failingForDays: 5 };\n const responseBodyLimit = merged.responseBodyLimit ?? 4096;\n const userAgent = merged.userAgent ?? `xtandard-webhooks/${VERSION}`;\n const doFetch = merged.fetch;\n const now = core.options.now;\n\n let timer: ReturnType<typeof setInterval> | null = null;\n let inFlight: Promise<number> | null = null;\n\n /** Delay before the next attempt, per schedule position, with ±10% jitter. */\n function nextDelayMs(attemptsMade: number): number | null {\n if (attemptsMade >= schedule.length) return null; // exhausted\n const nominal = durationToMs(schedule[attemptsMade] as WebhookDuration);\n const jitter = 1 + (Math.random() * 2 - 1) * JITTER;\n return Math.round(nominal * jitter);\n }\n\n async function processClaim(delivery: Delivery): Promise<boolean> {\n const app = delivery.applicationKey;\n const trigger = delivery.pendingTrigger ?? \"schedule\";\n\n const failTerminal = async (error: string, eventType: string): Promise<boolean> => {\n const outcome: AttemptOutcome = {\n ok: false,\n error,\n durationMs: 0,\n at: new Date(now()).toISOString(),\n };\n await core.recordAttempt({ delivery, outcome, trigger, nextAttemptAt: null, eventType });\n return true;\n };\n\n const message = await core.getMessage(app, delivery.messageId);\n if (!message) return failTerminal(\"Message no longer exists\", \"unknown\");\n\n const endpoint = await core.getEndpoint(app, delivery.endpointId);\n if (!endpoint) return failTerminal(\"Endpoint no longer exists\", message.eventType);\n\n if (endpoint.disabled) {\n // Held, not failed: release the claim and re-check after the lease\n // window. Re-enabling the endpoint resumes delivery automatically.\n const queue = core.options.queueStorage;\n const recheckAt = now() + leaseMs;\n const released: Delivery = {\n ...delivery,\n status: \"pending\",\n nextAttemptAt: new Date(recheckAt).toISOString(),\n leaseUntil: null,\n updatedAt: new Date(now()).toISOString(),\n };\n // Remove the lease-position due entry the claim created, then park the\n // delivery at the recheck time.\n if (delivery.leaseUntil) {\n await queue.removeItem(dueKey(app, Date.parse(delivery.leaseUntil), delivery.id));\n }\n await queue.setItem(deliveryKey(app, delivery.id), released);\n await queue.setItem<DueEntry>(dueKey(app, recheckAt, delivery.id), {\n app,\n deliveryId: delivery.id,\n });\n return false; // no attempt made\n }\n\n const outcome = await attemptDelivery({\n endpoint,\n messageId: delivery.messageId,\n body: message.envelope,\n timeoutMs,\n responseBodyLimit,\n userAgent,\n ...(doFetch ? { fetch: doFetch } : {}),\n nowMs: now(),\n });\n\n const attemptsMade = delivery.attemptCount + 1;\n const delay = outcome.ok ? null : nextDelayMs(attemptsMade);\n await core.recordAttempt({\n delivery,\n outcome,\n trigger,\n nextAttemptAt: delay === null ? null : new Date(now() + delay).toISOString(),\n eventType: message.eventType,\n });\n await core.noteEndpointOutcome(app, endpoint.id, outcome.ok, autoDisable);\n return true;\n }\n\n async function runTick(): Promise<number> {\n const claimed = await core.claimDueDeliveries({ limit: batchSize, leaseMs });\n if (claimed.length === 0) return 0;\n\n let attempts = 0;\n let cursor = 0;\n const workers = Array.from({ length: Math.min(concurrency, claimed.length) }, async () => {\n while (cursor < claimed.length) {\n const delivery = claimed[cursor++] as Delivery;\n try {\n if (await processClaim(delivery)) attempts++;\n } catch (error) {\n // A storage failure mid-claim: leave the delivery leased; the lease\n // expiry re-exposes it. Never let one claim kill the tick.\n // eslint-disable-next-line no-console\n console.warn(`[@xtandard/webhooks] delivery ${delivery.id} processing failed:`, error);\n }\n }\n });\n await Promise.all(workers);\n return attempts;\n }\n\n const dispatcher: Dispatcher = {\n start() {\n if (timer) return;\n timer = setInterval(() => {\n if (inFlight) return; // never overlap ticks\n inFlight = runTick()\n .catch((error) => {\n // eslint-disable-next-line no-console\n console.warn(\"[@xtandard/webhooks] dispatcher tick failed:\", error);\n return 0;\n })\n .finally(() => {\n inFlight = null;\n });\n }, pollIntervalMs);\n // Never keep the host process alive just to poll.\n (timer as unknown as { unref?: () => void }).unref?.();\n },\n\n async stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n if (inFlight) await inFlight;\n },\n\n async tick() {\n // Manual ticks also serialize against the poller.\n while (inFlight) await inFlight;\n inFlight = runTick().finally(() => {\n inFlight = null;\n });\n return inFlight;\n },\n\n get running() {\n return timer !== null;\n },\n };\n\n return dispatcher;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dispatcher-CQcoIsS9.mjs","names":[],"sources":["../src/dispatcher.ts"],"sourcesContent":["/**\n * The delivery engine. Polls the due index, claims deliveries (lease-based,\n * multi-instance safe when storage supports CAS or the `deliveryQueue`\n * capability), performs the signed HTTP attempts, and drives each delivery's\n * state machine through the retry schedule into success or dead-letter.\n *\n * Runs in-process: the panel starts one by default, a split worker runs one\n * via the CLI (`xtandard-webhooks dispatch`), and tests drive {@link Dispatcher.tick}\n * manually. All timers are `unref()`ed so a dispatcher never keeps a process\n * alive on its own. Semantics are **at-least-once**: a crashed process loses\n * nothing (leases expire, the next tick reclaims); receivers dedupe on\n * `webhook-id`.\n *\n * @module\n */\n\nimport { attemptDelivery, type AttemptOutcome } from \"./deliver.ts\";\nimport type { WebhooksCore } from \"./core.ts\";\nimport { durationToMs } from \"./duration.ts\";\nimport { deliveryKey, dueKey, type DueEntry } from \"./keys.ts\";\nimport type { Delivery, WebhookDuration } from \"./schema.ts\";\nimport { VERSION } from \"./version.ts\";\n\n/** Options for {@link createDispatcher} (also accepted on panels and the core). */\nexport interface DispatcherOptions {\n /** How often to poll for due deliveries. Default `1000`. */\n pollIntervalMs?: number;\n /** Max deliveries claimed per tick. Default `20`. */\n batchSize?: number;\n /** Max in-flight HTTP attempts. Default `8`. */\n concurrency?: number;\n /** Per-attempt timeout (AbortController). Default `20_000`. */\n timeoutMs?: number;\n /** Claim lease duration; an expired lease makes a claim reclaimable. Default `60_000`. */\n leaseMs?: number;\n /**\n * Delay before attempt N+1 after attempt N fails (index 0 = the initial\n * attempt's delay). Exhausting the schedule dead-letters the delivery.\n * Default `[\"0s\", \"5s\", \"5m\", \"30m\", \"2h\", \"5h\", \"10h\"]` (Svix-compatible).\n */\n retrySchedule?: WebhookDuration[];\n /**\n * Auto-disable endpoints whose every attempt has failed for this many\n * consecutive days. `false` disables the policy. Default `{ failingForDays: 5 }`.\n */\n autoDisable?: { failingForDays?: number } | false;\n /** Cap on stored response-body characters per attempt. Default `4096`. */\n responseBodyLimit?: number;\n /** Injectable fetch (tests, instrumentation). Default: global fetch. */\n fetch?: typeof fetch;\n /** `user-agent` header. Default `\"xtandard-webhooks/<version>\"`. */\n userAgent?: string;\n}\n\n/** The delivery engine handle. */\nexport interface Dispatcher {\n /** Begin polling. Idempotent. */\n start(): void;\n /** Stop polling and wait for in-flight attempts to finish. */\n stop(): Promise<void>;\n /**\n * Run one manual pass: claim due deliveries, attempt them, record outcomes.\n * Returns the number of attempts made — the unit-test surface (tests never\n * assert on timers).\n */\n tick(): Promise<number>;\n readonly running: boolean;\n}\n\n/** The default retry schedule (Svix-compatible). */\nexport const DEFAULT_RETRY_SCHEDULE: WebhookDuration[] = [\n \"0s\",\n \"5s\",\n \"5m\",\n \"30m\",\n \"2h\",\n \"5h\",\n \"10h\",\n];\n\n/** Fractional jitter applied to every retry delay (±10%). */\nconst JITTER = 0.1;\n\n/**\n * Create a dispatcher over a core. Not started — call\n * {@link Dispatcher.start}, or drive {@link Dispatcher.tick} manually.\n *\n * @example\n * ```ts\n * import { createWebhooksCore, createDispatcher } from \"@xtandard/webhooks\";\n * import { createMemoryStorage } from \"@xtandard/webhooks/storage/memory\";\n *\n * const core = createWebhooksCore({ storage: createMemoryStorage() });\n * const dispatcher = createDispatcher(core);\n * dispatcher.start();\n * ```\n */\nexport function createDispatcher(core: WebhooksCore, options: DispatcherOptions = {}): Dispatcher {\n const merged = { ...core.options.dispatcher, ...options };\n const pollIntervalMs = merged.pollIntervalMs ?? 1000;\n const batchSize = merged.batchSize ?? 20;\n const concurrency = merged.concurrency ?? 8;\n const timeoutMs = merged.timeoutMs ?? 20_000;\n // The lease must outlast a single attempt, or a slow-but-alive attempt runs\n // past its lease and a second dispatcher reclaims and re-sends it. Enforce\n // lease > timeout with headroom for recording the outcome.\n const configuredLeaseMs = merged.leaseMs ?? 60_000;\n const leaseMs = Math.max(configuredLeaseMs, timeoutMs + 10_000);\n const schedule = merged.retrySchedule ?? DEFAULT_RETRY_SCHEDULE;\n const autoDisable = merged.autoDisable ?? { failingForDays: 5 };\n const responseBodyLimit = merged.responseBodyLimit ?? 4096;\n const userAgent = merged.userAgent ?? `xtandard-webhooks/${VERSION}`;\n const doFetch = merged.fetch;\n const now = core.options.now;\n\n let timer: ReturnType<typeof setInterval> | null = null;\n let inFlight: Promise<number> | null = null;\n\n /** Delay before the next attempt, per schedule position, with ±10% jitter. */\n function nextDelayMs(attemptsMade: number): number | null {\n if (attemptsMade >= schedule.length) return null; // exhausted\n const nominal = durationToMs(schedule[attemptsMade] as WebhookDuration);\n const jitter = 1 + (Math.random() * 2 - 1) * JITTER;\n return Math.round(nominal * jitter);\n }\n\n async function processClaim(delivery: Delivery): Promise<boolean> {\n const app = delivery.applicationKey;\n const trigger = delivery.pendingTrigger ?? \"schedule\";\n\n const failTerminal = async (error: string, eventType: string): Promise<boolean> => {\n const outcome: AttemptOutcome = {\n ok: false,\n error,\n durationMs: 0,\n at: new Date(now()).toISOString(),\n };\n await core.recordAttempt({ delivery, outcome, trigger, nextAttemptAt: null, eventType });\n return true;\n };\n\n const message = await core.getMessage(app, delivery.messageId);\n if (!message) return failTerminal(\"Message no longer exists\", \"unknown\");\n\n const endpoint = await core.getEndpoint(app, delivery.endpointId);\n if (!endpoint) return failTerminal(\"Endpoint no longer exists\", message.eventType);\n\n if (endpoint.disabled) {\n // Held, not failed: release the claim and re-check after the lease\n // window. Re-enabling the endpoint resumes delivery automatically.\n const queue = core.options.queueStorage;\n const recheckAt = now() + leaseMs;\n const released: Delivery = {\n ...delivery,\n status: \"pending\",\n nextAttemptAt: new Date(recheckAt).toISOString(),\n leaseUntil: null,\n updatedAt: new Date(now()).toISOString(),\n };\n // Remove the lease-position due entry the claim created, then park the\n // delivery at the recheck time.\n if (delivery.leaseUntil) {\n await queue.removeItem(dueKey(app, Date.parse(delivery.leaseUntil), delivery.id));\n }\n await queue.setItem(deliveryKey(app, delivery.id), released);\n await queue.setItem<DueEntry>(dueKey(app, recheckAt, delivery.id), {\n app,\n deliveryId: delivery.id,\n });\n return false; // no attempt made\n }\n\n const outcome = await attemptDelivery({\n endpoint,\n messageId: delivery.messageId,\n body: message.envelope,\n timeoutMs,\n responseBodyLimit,\n userAgent,\n ...(doFetch ? { fetch: doFetch } : {}),\n nowMs: now(),\n });\n\n const attemptsMade = delivery.attemptCount + 1;\n const delay = outcome.ok ? null : nextDelayMs(attemptsMade);\n await core.recordAttempt({\n delivery,\n outcome,\n trigger,\n nextAttemptAt: delay === null ? null : new Date(now() + delay).toISOString(),\n eventType: message.eventType,\n });\n await core.noteEndpointOutcome(app, endpoint.id, outcome.ok, autoDisable);\n return true;\n }\n\n async function runTick(): Promise<number> {\n const claimed = await core.claimDueDeliveries({ limit: batchSize, leaseMs });\n if (claimed.length === 0) return 0;\n\n let attempts = 0;\n let cursor = 0;\n const workers = Array.from({ length: Math.min(concurrency, claimed.length) }, async () => {\n while (cursor < claimed.length) {\n const delivery = claimed[cursor++] as Delivery;\n try {\n if (await processClaim(delivery)) attempts++;\n } catch (error) {\n // A storage failure mid-claim: leave the delivery leased; the lease\n // expiry re-exposes it. Never let one claim kill the tick.\n // eslint-disable-next-line no-console\n console.warn(`[@xtandard/webhooks] delivery ${delivery.id} processing failed:`, error);\n }\n }\n });\n await Promise.all(workers);\n return attempts;\n }\n\n const dispatcher: Dispatcher = {\n start() {\n if (timer) return;\n timer = setInterval(() => {\n if (inFlight) return; // never overlap ticks\n inFlight = runTick()\n .catch((error) => {\n // eslint-disable-next-line no-console\n console.warn(\"[@xtandard/webhooks] dispatcher tick failed:\", error);\n return 0;\n })\n .finally(() => {\n inFlight = null;\n });\n }, pollIntervalMs);\n // Never keep the host process alive just to poll.\n (timer as unknown as { unref?: () => void }).unref?.();\n },\n\n async stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n if (inFlight) await inFlight;\n },\n\n async tick() {\n // Manual ticks also serialize against the poller.\n while (inFlight) await inFlight;\n inFlight = runTick().finally(() => {\n inFlight = null;\n });\n return inFlight;\n },\n\n get running() {\n return timer !== null;\n },\n };\n\n return dispatcher;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { t as createFetchHandler } from "./create-fetch-handler-DZohI08M.mjs";
|
|
2
|
+
//#region src/adapters/elysia-shared.ts
|
|
3
|
+
/**
|
|
4
|
+
* Elysia-version-agnostic pieces shared by the 1.x (`./elysia.ts`) and 2.x
|
|
5
|
+
* (`./elysia2.ts`) adapters. Must not import `elysia`: each adapter's types are
|
|
6
|
+
* built against a different major.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
11
|
+
function webhooksPanel(options) {
|
|
12
|
+
const handler = createFetchHandler(options);
|
|
13
|
+
const fn = ((request) => handler.fetch(request));
|
|
14
|
+
fn.core = handler.core;
|
|
15
|
+
fn.dispatcher = handler.dispatcher;
|
|
16
|
+
fn.openapi = handler.openapi;
|
|
17
|
+
return fn;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build the handler every declared route delegates to. Elysia parses the body
|
|
21
|
+
* for declared routes, draining the stream; rebuild the Request from the parsed
|
|
22
|
+
* body before delegating (mirrors the Express adapter).
|
|
23
|
+
*/
|
|
24
|
+
function createPass(options) {
|
|
25
|
+
const prefix = options.prefix ?? "/webhooks";
|
|
26
|
+
const handler = createFetchHandler({
|
|
27
|
+
...options,
|
|
28
|
+
basePath: options.basePath ?? prefix
|
|
29
|
+
});
|
|
30
|
+
const pass = (ctx) => {
|
|
31
|
+
const r = ctx.request;
|
|
32
|
+
if (ctx.body != null && (r.method === "POST" || r.method === "PUT")) return handler.fetch(new Request(r.url, {
|
|
33
|
+
method: r.method,
|
|
34
|
+
headers: r.headers,
|
|
35
|
+
body: JSON.stringify(ctx.body)
|
|
36
|
+
}));
|
|
37
|
+
return handler.fetch(r);
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
prefix,
|
|
41
|
+
pass
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { webhooksPanel as n, createPass as t };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=elysia-shared-CgTU3yPa.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elysia-shared-CgTU3yPa.mjs","names":[],"sources":["../src/adapters/elysia-shared.ts"],"sourcesContent":["/**\n * Elysia-version-agnostic pieces shared by the 1.x (`./elysia.ts`) and 2.x\n * (`./elysia2.ts`) adapters. Must not import `elysia`: each adapter's types are\n * built against a different major.\n *\n * @module\n */\n\nimport { createFetchHandler, type WebhooksPanelOptions } from \"../server/create-fetch-handler.ts\";\n\n/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */\nexport type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {\n core: ReturnType<typeof createFetchHandler>[\"core\"];\n dispatcher: ReturnType<typeof createFetchHandler>[\"dispatcher\"];\n openapi: ReturnType<typeof createFetchHandler>[\"openapi\"];\n};\n\n/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */\nexport function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler {\n const handler = createFetchHandler(options);\n const fn = ((request: Request) => handler.fetch(request)) as ElysiaWebhooksHandler;\n fn.core = handler.core;\n fn.dispatcher = handler.dispatcher;\n fn.openapi = handler.openapi;\n return fn;\n}\n\n/** Options for `webhooksElysia`. */\nexport interface WebhooksElysiaOptions extends WebhooksPanelOptions {\n /** Mount prefix; also used as the panel basePath. Default `\"/webhooks\"`. */\n prefix?: string;\n}\n\n/**\n * Build the handler every declared route delegates to. Elysia parses the body\n * for declared routes, draining the stream; rebuild the Request from the parsed\n * body before delegating (mirrors the Express adapter).\n */\nexport function createPass(options: WebhooksElysiaOptions): {\n prefix: string;\n pass: (ctx: { request: Request; body?: unknown }) => Promise<Response>;\n} {\n const prefix = options.prefix ?? \"/webhooks\";\n const handler = createFetchHandler({ ...options, basePath: options.basePath ?? prefix });\n\n const pass = (ctx: { request: Request; body?: unknown }): Promise<Response> => {\n const r = ctx.request;\n if (ctx.body != null && (r.method === \"POST\" || r.method === \"PUT\")) {\n return handler.fetch(\n new Request(r.url, {\n method: r.method,\n headers: r.headers,\n body: JSON.stringify(ctx.body),\n }),\n );\n }\n return handler.fetch(r);\n };\n\n return { prefix, pass };\n}\n\nexport type { WebhooksPanelOptions };\n"],"mappings":";;;;;;;;;;AAkBA,SAAgB,cAAc,SAAsD;CAClF,MAAM,UAAU,mBAAmB,OAAO;CAC1C,MAAM,OAAO,YAAqB,QAAQ,MAAM,OAAO;CACvD,GAAG,OAAO,QAAQ;CAClB,GAAG,aAAa,QAAQ;CACxB,GAAG,UAAU,QAAQ;CACrB,OAAO;AACT;;;;;;AAaA,SAAgB,WAAW,SAGzB;CACA,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,UAAU,mBAAmB;EAAE,GAAG;EAAS,UAAU,QAAQ,YAAY;CAAO,CAAC;CAEvF,MAAM,QAAQ,QAAiE;EAC7E,MAAM,IAAI,IAAI;EACd,IAAI,IAAI,QAAQ,SAAS,EAAE,WAAW,UAAU,EAAE,WAAW,QAC3D,OAAO,QAAQ,MACb,IAAI,QAAQ,EAAE,KAAK;GACjB,QAAQ,EAAE;GACV,SAAS,EAAE;GACX,MAAM,KAAK,UAAU,IAAI,IAAI;EAC/B,CAAC,CACH;EAEF,OAAO,QAAQ,MAAM,CAAC;CACxB;CAEA,OAAO;EAAE;EAAQ;CAAK;AACxB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const require_create_fetch_handler = require("./create-fetch-handler-B5WBbf8x.cjs");
|
|
2
|
+
//#region src/adapters/elysia-shared.ts
|
|
3
|
+
/**
|
|
4
|
+
* Elysia-version-agnostic pieces shared by the 1.x (`./elysia.ts`) and 2.x
|
|
5
|
+
* (`./elysia2.ts`) adapters. Must not import `elysia`: each adapter's types are
|
|
6
|
+
* built against a different major.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
11
|
+
function webhooksPanel(options) {
|
|
12
|
+
const handler = require_create_fetch_handler.createFetchHandler(options);
|
|
13
|
+
const fn = ((request) => handler.fetch(request));
|
|
14
|
+
fn.core = handler.core;
|
|
15
|
+
fn.dispatcher = handler.dispatcher;
|
|
16
|
+
fn.openapi = handler.openapi;
|
|
17
|
+
return fn;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build the handler every declared route delegates to. Elysia parses the body
|
|
21
|
+
* for declared routes, draining the stream; rebuild the Request from the parsed
|
|
22
|
+
* body before delegating (mirrors the Express adapter).
|
|
23
|
+
*/
|
|
24
|
+
function createPass(options) {
|
|
25
|
+
const prefix = options.prefix ?? "/webhooks";
|
|
26
|
+
const handler = require_create_fetch_handler.createFetchHandler({
|
|
27
|
+
...options,
|
|
28
|
+
basePath: options.basePath ?? prefix
|
|
29
|
+
});
|
|
30
|
+
const pass = (ctx) => {
|
|
31
|
+
const r = ctx.request;
|
|
32
|
+
if (ctx.body != null && (r.method === "POST" || r.method === "PUT")) return handler.fetch(new Request(r.url, {
|
|
33
|
+
method: r.method,
|
|
34
|
+
headers: r.headers,
|
|
35
|
+
body: JSON.stringify(ctx.body)
|
|
36
|
+
}));
|
|
37
|
+
return handler.fetch(r);
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
prefix,
|
|
41
|
+
pass
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
Object.defineProperty(exports, "createPass", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
get: function() {
|
|
48
|
+
return createPass;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(exports, "webhooksPanel", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function() {
|
|
54
|
+
return webhooksPanel;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=elysia-shared-Cop1-ie0.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elysia-shared-Cop1-ie0.cjs","names":["createFetchHandler"],"sources":["../src/adapters/elysia-shared.ts"],"sourcesContent":["/**\n * Elysia-version-agnostic pieces shared by the 1.x (`./elysia.ts`) and 2.x\n * (`./elysia2.ts`) adapters. Must not import `elysia`: each adapter's types are\n * built against a different major.\n *\n * @module\n */\n\nimport { createFetchHandler, type WebhooksPanelOptions } from \"../server/create-fetch-handler.ts\";\n\n/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */\nexport type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {\n core: ReturnType<typeof createFetchHandler>[\"core\"];\n dispatcher: ReturnType<typeof createFetchHandler>[\"dispatcher\"];\n openapi: ReturnType<typeof createFetchHandler>[\"openapi\"];\n};\n\n/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */\nexport function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler {\n const handler = createFetchHandler(options);\n const fn = ((request: Request) => handler.fetch(request)) as ElysiaWebhooksHandler;\n fn.core = handler.core;\n fn.dispatcher = handler.dispatcher;\n fn.openapi = handler.openapi;\n return fn;\n}\n\n/** Options for `webhooksElysia`. */\nexport interface WebhooksElysiaOptions extends WebhooksPanelOptions {\n /** Mount prefix; also used as the panel basePath. Default `\"/webhooks\"`. */\n prefix?: string;\n}\n\n/**\n * Build the handler every declared route delegates to. Elysia parses the body\n * for declared routes, draining the stream; rebuild the Request from the parsed\n * body before delegating (mirrors the Express adapter).\n */\nexport function createPass(options: WebhooksElysiaOptions): {\n prefix: string;\n pass: (ctx: { request: Request; body?: unknown }) => Promise<Response>;\n} {\n const prefix = options.prefix ?? \"/webhooks\";\n const handler = createFetchHandler({ ...options, basePath: options.basePath ?? prefix });\n\n const pass = (ctx: { request: Request; body?: unknown }): Promise<Response> => {\n const r = ctx.request;\n if (ctx.body != null && (r.method === \"POST\" || r.method === \"PUT\")) {\n return handler.fetch(\n new Request(r.url, {\n method: r.method,\n headers: r.headers,\n body: JSON.stringify(ctx.body),\n }),\n );\n }\n return handler.fetch(r);\n };\n\n return { prefix, pass };\n}\n\nexport type { WebhooksPanelOptions };\n"],"mappings":";;;;;;;;;;AAkBA,SAAgB,cAAc,SAAsD;CAClF,MAAM,UAAUA,6BAAAA,mBAAmB,OAAO;CAC1C,MAAM,OAAO,YAAqB,QAAQ,MAAM,OAAO;CACvD,GAAG,OAAO,QAAQ;CAClB,GAAG,aAAa,QAAQ;CACxB,GAAG,UAAU,QAAQ;CACrB,OAAO;AACT;;;;;;AAaA,SAAgB,WAAW,SAGzB;CACA,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,UAAUA,6BAAAA,mBAAmB;EAAE,GAAG;EAAS,UAAU,QAAQ,YAAY;CAAO,CAAC;CAEvF,MAAM,QAAQ,QAAiE;EAC7E,MAAM,IAAI,IAAI;EACd,IAAI,IAAI,QAAQ,SAAS,EAAE,WAAW,UAAU,EAAE,WAAW,QAC3D,OAAO,QAAQ,MACb,IAAI,QAAQ,EAAE,KAAK;GACjB,QAAQ,EAAE;GACV,SAAS,EAAE;GACX,MAAM,KAAK,UAAU,IAAI,IAAI;EAC/B,CAAC,CACH;EAEF,OAAO,QAAQ,MAAM,CAAC;CACxB;CAEA,OAAO;EAAE;EAAQ;CAAK;AACxB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { n as WebhooksPanelOptions, r as createFetchHandler } from "./create-fetch-handler-BN9vXbgW.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/elysia-shared.d.ts
|
|
4
|
+
/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */
|
|
5
|
+
type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {
|
|
6
|
+
core: ReturnType<typeof createFetchHandler>["core"];
|
|
7
|
+
dispatcher: ReturnType<typeof createFetchHandler>["dispatcher"];
|
|
8
|
+
openapi: ReturnType<typeof createFetchHandler>["openapi"];
|
|
9
|
+
};
|
|
10
|
+
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
11
|
+
declare function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler;
|
|
12
|
+
/** Options for `webhooksElysia`. */
|
|
13
|
+
interface WebhooksElysiaOptions extends WebhooksPanelOptions {
|
|
14
|
+
/** Mount prefix; also used as the panel basePath. Default `"/webhooks"`. */
|
|
15
|
+
prefix?: string;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { WebhooksElysiaOptions as n, webhooksPanel as r, ElysiaWebhooksHandler as t };
|
|
19
|
+
//# sourceMappingURL=elysia-shared-CvSsqMbR.d.mts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { n as WebhooksPanelOptions, r as createFetchHandler } from "./create-fetch-handler-D9ZRfrY6.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/elysia-shared.d.ts
|
|
4
|
+
/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */
|
|
5
|
+
type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {
|
|
6
|
+
core: ReturnType<typeof createFetchHandler>["core"];
|
|
7
|
+
dispatcher: ReturnType<typeof createFetchHandler>["dispatcher"];
|
|
8
|
+
openapi: ReturnType<typeof createFetchHandler>["openapi"];
|
|
9
|
+
};
|
|
10
|
+
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
11
|
+
declare function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler;
|
|
12
|
+
/** Options for `webhooksElysia`. */
|
|
13
|
+
interface WebhooksElysiaOptions extends WebhooksPanelOptions {
|
|
14
|
+
/** Mount prefix; also used as the panel basePath. Default `"/webhooks"`. */
|
|
15
|
+
prefix?: string;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { WebhooksElysiaOptions as n, webhooksPanel as r, ElysiaWebhooksHandler as t };
|
|
19
|
+
//# sourceMappingURL=elysia-shared-DSSrfuoF.d.cts.map
|
package/dist/entry-bun.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_create_fetch_handler = require("./create-fetch-handler-
|
|
2
|
+
const require_create_fetch_handler = require("./create-fetch-handler-B5WBbf8x.cjs");
|
|
3
3
|
//#region src/adapters/bun.ts
|
|
4
4
|
/**
|
|
5
5
|
* Bun adapter. The handler is already web-standard, so this is a passthrough you
|
package/dist/entry-bun.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createFetchHandler } from "./create-fetch-handler-
|
|
1
|
+
import { t as createFetchHandler } from "./create-fetch-handler-DZohI08M.mjs";
|
|
2
2
|
//#region src/adapters/bun.ts
|
|
3
3
|
/**
|
|
4
4
|
* Bun adapter. The handler is already web-standard, so this is a passthrough you
|
package/dist/entry-elysia.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_elysia_shared = require("./elysia-shared-Cop1-ie0.cjs");
|
|
3
3
|
let elysia = require("elysia");
|
|
4
4
|
//#region src/adapters/elysia.ts
|
|
5
5
|
/**
|
|
@@ -28,15 +28,6 @@ let elysia = require("elysia");
|
|
|
28
28
|
*
|
|
29
29
|
* @module
|
|
30
30
|
*/
|
|
31
|
-
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
32
|
-
function webhooksPanel(options) {
|
|
33
|
-
const handler = require_create_fetch_handler.createFetchHandler(options);
|
|
34
|
-
const fn = ((request) => handler.fetch(request));
|
|
35
|
-
fn.core = handler.core;
|
|
36
|
-
fn.dispatcher = handler.dispatcher;
|
|
37
|
-
fn.openapi = handler.openapi;
|
|
38
|
-
return fn;
|
|
39
|
-
}
|
|
40
31
|
const appParams = elysia.t.Object({ app: elysia.t.String() });
|
|
41
32
|
const endpointParams = elysia.t.Object({
|
|
42
33
|
app: elysia.t.String(),
|
|
@@ -49,22 +40,11 @@ const nameParams = elysia.t.Object({ name: elysia.t.String() });
|
|
|
49
40
|
* Routes are declared for the typed surface; every handler delegates to the
|
|
50
41
|
* shared fetch pipeline (auth, authorization, portal scoping, error mapping
|
|
51
42
|
* reused). A catch-all also serves the bundled UI.
|
|
43
|
+
*
|
|
44
|
+
* Elysia 1.x only — for Elysia 2 import from `@xtandard/webhooks/elysia2`.
|
|
52
45
|
*/
|
|
53
46
|
function webhooksElysia(options) {
|
|
54
|
-
const prefix = options
|
|
55
|
-
const handler = require_create_fetch_handler.createFetchHandler({
|
|
56
|
-
...options,
|
|
57
|
-
basePath: options.basePath ?? prefix
|
|
58
|
-
});
|
|
59
|
-
const pass = (ctx) => {
|
|
60
|
-
const r = ctx.request;
|
|
61
|
-
if (ctx.body != null && (r.method === "POST" || r.method === "PUT")) return handler.fetch(new Request(r.url, {
|
|
62
|
-
method: r.method,
|
|
63
|
-
headers: r.headers,
|
|
64
|
-
body: JSON.stringify(ctx.body)
|
|
65
|
-
}));
|
|
66
|
-
return handler.fetch(r);
|
|
67
|
-
};
|
|
47
|
+
const { prefix, pass } = require_elysia_shared.createPass(options);
|
|
68
48
|
const app = "/api/applications/:app";
|
|
69
49
|
const endpoint = `${app}/endpoints/:id`;
|
|
70
50
|
return new elysia.Elysia({
|
|
@@ -120,6 +100,6 @@ function webhooksElysia(options) {
|
|
|
120
100
|
}
|
|
121
101
|
//#endregion
|
|
122
102
|
exports.webhooksElysia = webhooksElysia;
|
|
123
|
-
exports.webhooksPanel = webhooksPanel;
|
|
103
|
+
exports.webhooksPanel = require_elysia_shared.webhooksPanel;
|
|
124
104
|
|
|
125
105
|
//# sourceMappingURL=entry-elysia.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-elysia.cjs","names":["createFetchHandler","t","Elysia"],"sources":["../src/adapters/elysia.ts"],"sourcesContent":["/**\n * Elysia adapter. Two ways to mount:\n *\n * 1. {@link webhooksPanel} — a web-standard handler for `Elysia.mount(path, handler)`.\n * Simplest, but opaque to Eden (the typed client can't see the routes).\n *\n * ```ts\n * new Elysia().mount(\"/webhooks\", webhooksPanel({ basePath: \"/webhooks\", storage }));\n * ```\n *\n * 2. {@link webhooksElysia} — a typed Elysia plugin that *declares* the admin routes,\n * so **Eden treaty** infers them: `edenClient.webhooks.api.applications.get()`, etc.\n * Each handler delegates to the same pipeline (auth/validation/logic reused).\n *\n * ```ts\n * import { treaty } from \"@elysiajs/eden\";\n * const app = new Elysia().use(webhooksElysia({ prefix: \"/webhooks\", storage }));\n * const client = treaty<typeof app>(\"localhost:3000\");\n * await client.webhooks.api.applications.get();\n * ```\n *\n * The panel starts the delivery dispatcher by default; pass `dispatcher: false`\n * for split-worker deployments.\n *\n * @module\n */\n\nimport { Elysia, t } from \"elysia\";\nimport { createFetchHandler, type WebhooksPanelOptions } from \"../server/create-fetch-handler.ts\";\n\n/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */\nexport type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {\n core: ReturnType<typeof createFetchHandler>[\"core\"];\n dispatcher: ReturnType<typeof createFetchHandler>[\"dispatcher\"];\n openapi: ReturnType<typeof createFetchHandler>[\"openapi\"];\n};\n\n/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */\nexport function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler {\n const handler = createFetchHandler(options);\n const fn = ((request: Request) => handler.fetch(request)) as ElysiaWebhooksHandler;\n fn.core = handler.core;\n fn.dispatcher = handler.dispatcher;\n fn.openapi = handler.openapi;\n return fn;\n}\n\n/** Options for {@link webhooksElysia}. */\nexport interface WebhooksElysiaOptions extends WebhooksPanelOptions {\n /** Mount prefix; also used as the panel basePath. Default `\"/webhooks\"`. */\n prefix?: string;\n}\n\nconst appParams = t.Object({ app: t.String() });\nconst endpointParams = t.Object({ app: t.String(), id: t.String() });\nconst nameParams = t.Object({ name: t.String() });\n\n/**\n * Typed Elysia plugin exposing the admin API so the **Eden** client can call it\n * with full path/method/param typing (`edenClient.webhooks.api.applications.get()`).\n * Routes are declared for the typed surface; every handler delegates to the\n * shared fetch pipeline (auth, authorization, portal scoping, error mapping\n * reused). A catch-all also serves the bundled UI.\n */\nexport function webhooksElysia(options: WebhooksElysiaOptions) {\n const prefix = options.prefix ?? \"/webhooks\";\n const handler = createFetchHandler({ ...options, basePath: options.basePath ?? prefix });\n\n // Elysia parses the body for declared routes, draining the stream; rebuild the\n // Request from the parsed body before delegating (mirrors the Express adapter).\n const pass = (ctx: { request: Request; body?: unknown }): Promise<Response> => {\n const r = ctx.request;\n if (ctx.body != null && (r.method === \"POST\" || r.method === \"PUT\")) {\n return handler.fetch(\n new Request(r.url, {\n method: r.method,\n headers: r.headers,\n body: JSON.stringify(ctx.body),\n }),\n );\n }\n return handler.fetch(r);\n };\n\n const app = \"/api/applications/:app\";\n const endpoint = `${app}/endpoints/:id`;\n\n return new Elysia({ prefix, name: \"xtandard-webhooks\" })\n .get(\"/config\", pass)\n .get(\"/api/openapi.json\", pass)\n .get(\"/api/event-types.json\", pass)\n .get(\"/api/applications\", pass)\n .post(\"/api/applications\", pass, {\n body: t.Object({\n key: t.String(),\n name: t.Optional(t.String()),\n metadata: t.Optional(t.Any()),\n }),\n })\n .get(app, pass, { params: appParams })\n .put(app, pass, { params: appParams, body: t.Any() })\n .delete(app, pass, { params: appParams })\n .get(\"/api/event-types\", pass)\n .post(\"/api/event-types\", pass, {\n body: t.Object({\n name: t.String(),\n description: t.Optional(t.String()),\n groupName: t.Optional(t.String()),\n schema: t.Optional(t.Any()),\n deprecated: t.Optional(t.Boolean()),\n }),\n })\n .get(\"/api/event-types/:name\", pass, { params: nameParams })\n .put(\"/api/event-types/:name\", pass, { params: nameParams, body: t.Any() })\n .delete(\"/api/event-types/:name\", pass, { params: nameParams })\n .get(`${app}/endpoints`, pass, { params: appParams })\n .post(`${app}/endpoints`, pass, {\n params: appParams,\n body: t.Object({\n url: t.String(),\n description: t.Optional(t.String()),\n eventTypes: t.Optional(t.Array(t.String())),\n headers: t.Optional(t.Record(t.String(), t.String())),\n metadata: t.Optional(t.Any()),\n disabled: t.Optional(t.Boolean()),\n }),\n })\n .get(endpoint, pass, { params: endpointParams })\n .put(endpoint, pass, { params: endpointParams, body: t.Any() })\n .delete(endpoint, pass, { params: endpointParams })\n .get(`${endpoint}/secret`, pass, { params: endpointParams })\n .post(`${endpoint}/rotate-secret`, pass, { params: endpointParams })\n .post(`${endpoint}/enable`, pass, { params: endpointParams })\n .post(`${endpoint}/disable`, pass, { params: endpointParams })\n .post(`${endpoint}/test`, pass, {\n params: endpointParams,\n body: t.Object({ eventType: t.String(), payload: t.Optional(t.Any()) }),\n })\n .post(`${endpoint}/recover`, pass, {\n params: endpointParams,\n body: t.Object({ since: t.String() }),\n })\n .get(`${app}/messages`, pass, { params: appParams })\n .post(`${app}/messages`, pass, {\n params: appParams,\n body: t.Object({\n eventType: t.String(),\n payload: t.Any(),\n timestamp: t.Optional(t.String()),\n idempotencyKey: t.Optional(t.String()),\n }),\n })\n .get(`${app}/messages/:id`, pass, { params: endpointParams })\n .get(`${app}/deliveries`, pass, { params: appParams })\n .get(`${app}/deliveries/:id`, pass, { params: endpointParams })\n .post(`${app}/deliveries/:id/retry`, pass, { params: endpointParams })\n .get(`${app}/audit`, pass, { params: appParams })\n .all(\"/*\", pass); // bundled UI assets + SPA fallback\n}\n\nexport type { WebhooksPanelOptions };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entry-elysia.cjs","names":["t","createPass","Elysia"],"sources":["../src/adapters/elysia.ts"],"sourcesContent":["/**\n * Elysia adapter. Two ways to mount:\n *\n * 1. {@link webhooksPanel} — a web-standard handler for `Elysia.mount(path, handler)`.\n * Simplest, but opaque to Eden (the typed client can't see the routes).\n *\n * ```ts\n * new Elysia().mount(\"/webhooks\", webhooksPanel({ basePath: \"/webhooks\", storage }));\n * ```\n *\n * 2. {@link webhooksElysia} — a typed Elysia plugin that *declares* the admin routes,\n * so **Eden treaty** infers them: `edenClient.webhooks.api.applications.get()`, etc.\n * Each handler delegates to the same pipeline (auth/validation/logic reused).\n *\n * ```ts\n * import { treaty } from \"@elysiajs/eden\";\n * const app = new Elysia().use(webhooksElysia({ prefix: \"/webhooks\", storage }));\n * const client = treaty<typeof app>(\"localhost:3000\");\n * await client.webhooks.api.applications.get();\n * ```\n *\n * The panel starts the delivery dispatcher by default; pass `dispatcher: false`\n * for split-worker deployments.\n *\n * @module\n */\n\nimport { Elysia, t } from \"elysia\";\nimport {\n createPass,\n webhooksPanel,\n type ElysiaWebhooksHandler,\n type WebhooksElysiaOptions,\n type WebhooksPanelOptions,\n} from \"./elysia-shared.ts\";\n\nexport { webhooksPanel };\nexport type { ElysiaWebhooksHandler, WebhooksElysiaOptions, WebhooksPanelOptions };\n\nconst appParams = t.Object({ app: t.String() });\nconst endpointParams = t.Object({ app: t.String(), id: t.String() });\nconst nameParams = t.Object({ name: t.String() });\n\n/**\n * Typed Elysia plugin exposing the admin API so the **Eden** client can call it\n * with full path/method/param typing (`edenClient.webhooks.api.applications.get()`).\n * Routes are declared for the typed surface; every handler delegates to the\n * shared fetch pipeline (auth, authorization, portal scoping, error mapping\n * reused). A catch-all also serves the bundled UI.\n *\n * Elysia 1.x only — for Elysia 2 import from `@xtandard/webhooks/elysia2`.\n */\nexport function webhooksElysia(options: WebhooksElysiaOptions) {\n const { prefix, pass } = createPass(options);\n\n const app = \"/api/applications/:app\";\n const endpoint = `${app}/endpoints/:id`;\n\n return new Elysia({ prefix, name: \"xtandard-webhooks\" })\n .get(\"/config\", pass)\n .get(\"/api/openapi.json\", pass)\n .get(\"/api/event-types.json\", pass)\n .get(\"/api/applications\", pass)\n .post(\"/api/applications\", pass, {\n body: t.Object({\n key: t.String(),\n name: t.Optional(t.String()),\n metadata: t.Optional(t.Any()),\n }),\n })\n .get(app, pass, { params: appParams })\n .put(app, pass, { params: appParams, body: t.Any() })\n .delete(app, pass, { params: appParams })\n .get(\"/api/event-types\", pass)\n .post(\"/api/event-types\", pass, {\n body: t.Object({\n name: t.String(),\n description: t.Optional(t.String()),\n groupName: t.Optional(t.String()),\n schema: t.Optional(t.Any()),\n deprecated: t.Optional(t.Boolean()),\n }),\n })\n .get(\"/api/event-types/:name\", pass, { params: nameParams })\n .put(\"/api/event-types/:name\", pass, { params: nameParams, body: t.Any() })\n .delete(\"/api/event-types/:name\", pass, { params: nameParams })\n .get(`${app}/endpoints`, pass, { params: appParams })\n .post(`${app}/endpoints`, pass, {\n params: appParams,\n body: t.Object({\n url: t.String(),\n description: t.Optional(t.String()),\n eventTypes: t.Optional(t.Array(t.String())),\n headers: t.Optional(t.Record(t.String(), t.String())),\n metadata: t.Optional(t.Any()),\n disabled: t.Optional(t.Boolean()),\n }),\n })\n .get(endpoint, pass, { params: endpointParams })\n .put(endpoint, pass, { params: endpointParams, body: t.Any() })\n .delete(endpoint, pass, { params: endpointParams })\n .get(`${endpoint}/secret`, pass, { params: endpointParams })\n .post(`${endpoint}/rotate-secret`, pass, { params: endpointParams })\n .post(`${endpoint}/enable`, pass, { params: endpointParams })\n .post(`${endpoint}/disable`, pass, { params: endpointParams })\n .post(`${endpoint}/test`, pass, {\n params: endpointParams,\n body: t.Object({ eventType: t.String(), payload: t.Optional(t.Any()) }),\n })\n .post(`${endpoint}/recover`, pass, {\n params: endpointParams,\n body: t.Object({ since: t.String() }),\n })\n .get(`${app}/messages`, pass, { params: appParams })\n .post(`${app}/messages`, pass, {\n params: appParams,\n body: t.Object({\n eventType: t.String(),\n payload: t.Any(),\n timestamp: t.Optional(t.String()),\n idempotencyKey: t.Optional(t.String()),\n }),\n })\n .get(`${app}/messages/:id`, pass, { params: endpointParams })\n .get(`${app}/deliveries`, pass, { params: appParams })\n .get(`${app}/deliveries/:id`, pass, { params: endpointParams })\n .post(`${app}/deliveries/:id/retry`, pass, { params: endpointParams })\n .get(`${app}/audit`, pass, { params: appParams })\n .all(\"/*\", pass); // bundled UI assets + SPA fallback\n}\n"],"mappings":"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"}
|
package/dist/entry-elysia.d.cts
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
|
-
import { n as WebhooksPanelOptions
|
|
1
|
+
import { n as WebhooksPanelOptions } from "./create-fetch-handler-D9ZRfrY6.cjs";
|
|
2
|
+
import { n as WebhooksElysiaOptions, r as webhooksPanel, t as ElysiaWebhooksHandler } from "./elysia-shared-DSSrfuoF.cjs";
|
|
2
3
|
import { Elysia } from "elysia";
|
|
3
4
|
|
|
4
5
|
//#region src/adapters/elysia.d.ts
|
|
5
|
-
/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */
|
|
6
|
-
type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {
|
|
7
|
-
core: ReturnType<typeof createFetchHandler>["core"];
|
|
8
|
-
dispatcher: ReturnType<typeof createFetchHandler>["dispatcher"];
|
|
9
|
-
openapi: ReturnType<typeof createFetchHandler>["openapi"];
|
|
10
|
-
};
|
|
11
|
-
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
12
|
-
declare function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler;
|
|
13
|
-
/** Options for {@link webhooksElysia}. */
|
|
14
|
-
interface WebhooksElysiaOptions extends WebhooksPanelOptions {
|
|
15
|
-
/** Mount prefix; also used as the panel basePath. Default `"/webhooks"`. */
|
|
16
|
-
prefix?: string;
|
|
17
|
-
}
|
|
18
6
|
/**
|
|
19
7
|
* Typed Elysia plugin exposing the admin API so the **Eden** client can call it
|
|
20
8
|
* with full path/method/param typing (`edenClient.webhooks.api.applications.get()`).
|
|
21
9
|
* Routes are declared for the typed surface; every handler delegates to the
|
|
22
10
|
* shared fetch pipeline (auth, authorization, portal scoping, error mapping
|
|
23
11
|
* reused). A catch-all also serves the bundled UI.
|
|
12
|
+
*
|
|
13
|
+
* Elysia 1.x only — for Elysia 2 import from `@xtandard/webhooks/elysia2`.
|
|
24
14
|
*/
|
|
25
15
|
declare function webhooksElysia(options: WebhooksElysiaOptions): Elysia<string, {
|
|
26
16
|
decorator: {};
|
|
@@ -1013,5 +1003,5 @@ declare function webhooksElysia(options: WebhooksElysiaOptions): Elysia<string,
|
|
|
1013
1003
|
response: {};
|
|
1014
1004
|
}>;
|
|
1015
1005
|
//#endregion
|
|
1016
|
-
export { ElysiaWebhooksHandler, WebhooksElysiaOptions, type WebhooksPanelOptions, webhooksElysia, webhooksPanel };
|
|
1006
|
+
export { type ElysiaWebhooksHandler, type WebhooksElysiaOptions, type WebhooksPanelOptions, webhooksElysia, webhooksPanel };
|
|
1017
1007
|
//# sourceMappingURL=entry-elysia.d.cts.map
|
package/dist/entry-elysia.d.mts
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
|
-
import { n as WebhooksPanelOptions
|
|
1
|
+
import { n as WebhooksPanelOptions } from "./create-fetch-handler-BN9vXbgW.mjs";
|
|
2
|
+
import { n as WebhooksElysiaOptions, r as webhooksPanel, t as ElysiaWebhooksHandler } from "./elysia-shared-CvSsqMbR.mjs";
|
|
2
3
|
import { Elysia } from "elysia";
|
|
3
4
|
|
|
4
5
|
//#region src/adapters/elysia.d.ts
|
|
5
|
-
/** A web-standard fetch handler with `core` + `dispatcher` + `openapi()` attached. */
|
|
6
|
-
type ElysiaWebhooksHandler = ((request: Request) => Promise<Response>) & {
|
|
7
|
-
core: ReturnType<typeof createFetchHandler>["core"];
|
|
8
|
-
dispatcher: ReturnType<typeof createFetchHandler>["dispatcher"];
|
|
9
|
-
openapi: ReturnType<typeof createFetchHandler>["openapi"];
|
|
10
|
-
};
|
|
11
|
-
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
12
|
-
declare function webhooksPanel(options: WebhooksPanelOptions): ElysiaWebhooksHandler;
|
|
13
|
-
/** Options for {@link webhooksElysia}. */
|
|
14
|
-
interface WebhooksElysiaOptions extends WebhooksPanelOptions {
|
|
15
|
-
/** Mount prefix; also used as the panel basePath. Default `"/webhooks"`. */
|
|
16
|
-
prefix?: string;
|
|
17
|
-
}
|
|
18
6
|
/**
|
|
19
7
|
* Typed Elysia plugin exposing the admin API so the **Eden** client can call it
|
|
20
8
|
* with full path/method/param typing (`edenClient.webhooks.api.applications.get()`).
|
|
21
9
|
* Routes are declared for the typed surface; every handler delegates to the
|
|
22
10
|
* shared fetch pipeline (auth, authorization, portal scoping, error mapping
|
|
23
11
|
* reused). A catch-all also serves the bundled UI.
|
|
12
|
+
*
|
|
13
|
+
* Elysia 1.x only — for Elysia 2 import from `@xtandard/webhooks/elysia2`.
|
|
24
14
|
*/
|
|
25
15
|
declare function webhooksElysia(options: WebhooksElysiaOptions): Elysia<string, {
|
|
26
16
|
decorator: {};
|
|
@@ -1013,5 +1003,5 @@ declare function webhooksElysia(options: WebhooksElysiaOptions): Elysia<string,
|
|
|
1013
1003
|
response: {};
|
|
1014
1004
|
}>;
|
|
1015
1005
|
//#endregion
|
|
1016
|
-
export { ElysiaWebhooksHandler, WebhooksElysiaOptions, type WebhooksPanelOptions, webhooksElysia, webhooksPanel };
|
|
1006
|
+
export { type ElysiaWebhooksHandler, type WebhooksElysiaOptions, type WebhooksPanelOptions, webhooksElysia, webhooksPanel };
|
|
1017
1007
|
//# sourceMappingURL=entry-elysia.d.mts.map
|
package/dist/entry-elysia.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { n as webhooksPanel, t as createPass } from "./elysia-shared-CgTU3yPa.mjs";
|
|
2
2
|
import { Elysia, t } from "elysia";
|
|
3
3
|
//#region src/adapters/elysia.ts
|
|
4
4
|
/**
|
|
@@ -27,15 +27,6 @@ import { Elysia, t } from "elysia";
|
|
|
27
27
|
*
|
|
28
28
|
* @module
|
|
29
29
|
*/
|
|
30
|
-
/** Create a panel handler suitable for `Elysia.mount(path, handler)`. */
|
|
31
|
-
function webhooksPanel(options) {
|
|
32
|
-
const handler = createFetchHandler(options);
|
|
33
|
-
const fn = ((request) => handler.fetch(request));
|
|
34
|
-
fn.core = handler.core;
|
|
35
|
-
fn.dispatcher = handler.dispatcher;
|
|
36
|
-
fn.openapi = handler.openapi;
|
|
37
|
-
return fn;
|
|
38
|
-
}
|
|
39
30
|
const appParams = t.Object({ app: t.String() });
|
|
40
31
|
const endpointParams = t.Object({
|
|
41
32
|
app: t.String(),
|
|
@@ -48,22 +39,11 @@ const nameParams = t.Object({ name: t.String() });
|
|
|
48
39
|
* Routes are declared for the typed surface; every handler delegates to the
|
|
49
40
|
* shared fetch pipeline (auth, authorization, portal scoping, error mapping
|
|
50
41
|
* reused). A catch-all also serves the bundled UI.
|
|
42
|
+
*
|
|
43
|
+
* Elysia 1.x only — for Elysia 2 import from `@xtandard/webhooks/elysia2`.
|
|
51
44
|
*/
|
|
52
45
|
function webhooksElysia(options) {
|
|
53
|
-
const prefix = options
|
|
54
|
-
const handler = createFetchHandler({
|
|
55
|
-
...options,
|
|
56
|
-
basePath: options.basePath ?? prefix
|
|
57
|
-
});
|
|
58
|
-
const pass = (ctx) => {
|
|
59
|
-
const r = ctx.request;
|
|
60
|
-
if (ctx.body != null && (r.method === "POST" || r.method === "PUT")) return handler.fetch(new Request(r.url, {
|
|
61
|
-
method: r.method,
|
|
62
|
-
headers: r.headers,
|
|
63
|
-
body: JSON.stringify(ctx.body)
|
|
64
|
-
}));
|
|
65
|
-
return handler.fetch(r);
|
|
66
|
-
};
|
|
46
|
+
const { prefix, pass } = createPass(options);
|
|
67
47
|
const app = "/api/applications/:app";
|
|
68
48
|
const endpoint = `${app}/endpoints/:id`;
|
|
69
49
|
return new Elysia({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-elysia.mjs","names":[],"sources":["../src/adapters/elysia.ts"],"sourcesContent":["/**\n * Elysia adapter. Two ways to mount:\n *\n * 1. {@link webhooksPanel} — a web-standard handler for `Elysia.mount(path, handler)`.\n * Simplest, but opaque to Eden (the typed client can't see the routes).\n *\n * ```ts\n * new Elysia().mount(\"/webhooks\", webhooksPanel({ basePath: \"/webhooks\", storage }));\n * ```\n *\n * 2. {@link webhooksElysia} — a typed Elysia plugin that *declares* the admin routes,\n * so **Eden treaty** infers them: `edenClient.webhooks.api.applications.get()`, etc.\n * Each handler delegates to the same pipeline (auth/validation/logic reused).\n *\n * ```ts\n * import { treaty } from \"@elysiajs/eden\";\n * const app = new Elysia().use(webhooksElysia({ prefix: \"/webhooks\", storage }));\n * const client = treaty<typeof app>(\"localhost:3000\");\n * await client.webhooks.api.applications.get();\n * ```\n *\n * The panel starts the delivery dispatcher by default; pass `dispatcher: false`\n * for split-worker deployments.\n *\n * @module\n */\n\nimport { Elysia, t } from \"elysia\";\nimport {
|
|
1
|
+
{"version":3,"file":"entry-elysia.mjs","names":[],"sources":["../src/adapters/elysia.ts"],"sourcesContent":["/**\n * Elysia adapter. Two ways to mount:\n *\n * 1. {@link webhooksPanel} — a web-standard handler for `Elysia.mount(path, handler)`.\n * Simplest, but opaque to Eden (the typed client can't see the routes).\n *\n * ```ts\n * new Elysia().mount(\"/webhooks\", webhooksPanel({ basePath: \"/webhooks\", storage }));\n * ```\n *\n * 2. {@link webhooksElysia} — a typed Elysia plugin that *declares* the admin routes,\n * so **Eden treaty** infers them: `edenClient.webhooks.api.applications.get()`, etc.\n * Each handler delegates to the same pipeline (auth/validation/logic reused).\n *\n * ```ts\n * import { treaty } from \"@elysiajs/eden\";\n * const app = new Elysia().use(webhooksElysia({ prefix: \"/webhooks\", storage }));\n * const client = treaty<typeof app>(\"localhost:3000\");\n * await client.webhooks.api.applications.get();\n * ```\n *\n * The panel starts the delivery dispatcher by default; pass `dispatcher: false`\n * for split-worker deployments.\n *\n * @module\n */\n\nimport { Elysia, t } from \"elysia\";\nimport {\n createPass,\n webhooksPanel,\n type ElysiaWebhooksHandler,\n type WebhooksElysiaOptions,\n type WebhooksPanelOptions,\n} from \"./elysia-shared.ts\";\n\nexport { webhooksPanel };\nexport type { ElysiaWebhooksHandler, WebhooksElysiaOptions, WebhooksPanelOptions };\n\nconst appParams = t.Object({ app: t.String() });\nconst endpointParams = t.Object({ app: t.String(), id: t.String() });\nconst nameParams = t.Object({ name: t.String() });\n\n/**\n * Typed Elysia plugin exposing the admin API so the **Eden** client can call it\n * with full path/method/param typing (`edenClient.webhooks.api.applications.get()`).\n * Routes are declared for the typed surface; every handler delegates to the\n * shared fetch pipeline (auth, authorization, portal scoping, error mapping\n * reused). A catch-all also serves the bundled UI.\n *\n * Elysia 1.x only — for Elysia 2 import from `@xtandard/webhooks/elysia2`.\n */\nexport function webhooksElysia(options: WebhooksElysiaOptions) {\n const { prefix, pass } = createPass(options);\n\n const app = \"/api/applications/:app\";\n const endpoint = `${app}/endpoints/:id`;\n\n return new Elysia({ prefix, name: \"xtandard-webhooks\" })\n .get(\"/config\", pass)\n .get(\"/api/openapi.json\", pass)\n .get(\"/api/event-types.json\", pass)\n .get(\"/api/applications\", pass)\n .post(\"/api/applications\", pass, {\n body: t.Object({\n key: t.String(),\n name: t.Optional(t.String()),\n metadata: t.Optional(t.Any()),\n }),\n })\n .get(app, pass, { params: appParams })\n .put(app, pass, { params: appParams, body: t.Any() })\n .delete(app, pass, { params: appParams })\n .get(\"/api/event-types\", pass)\n .post(\"/api/event-types\", pass, {\n body: t.Object({\n name: t.String(),\n description: t.Optional(t.String()),\n groupName: t.Optional(t.String()),\n schema: t.Optional(t.Any()),\n deprecated: t.Optional(t.Boolean()),\n }),\n })\n .get(\"/api/event-types/:name\", pass, { params: nameParams })\n .put(\"/api/event-types/:name\", pass, { params: nameParams, body: t.Any() })\n .delete(\"/api/event-types/:name\", pass, { params: nameParams })\n .get(`${app}/endpoints`, pass, { params: appParams })\n .post(`${app}/endpoints`, pass, {\n params: appParams,\n body: t.Object({\n url: t.String(),\n description: t.Optional(t.String()),\n eventTypes: t.Optional(t.Array(t.String())),\n headers: t.Optional(t.Record(t.String(), t.String())),\n metadata: t.Optional(t.Any()),\n disabled: t.Optional(t.Boolean()),\n }),\n })\n .get(endpoint, pass, { params: endpointParams })\n .put(endpoint, pass, { params: endpointParams, body: t.Any() })\n .delete(endpoint, pass, { params: endpointParams })\n .get(`${endpoint}/secret`, pass, { params: endpointParams })\n .post(`${endpoint}/rotate-secret`, pass, { params: endpointParams })\n .post(`${endpoint}/enable`, pass, { params: endpointParams })\n .post(`${endpoint}/disable`, pass, { params: endpointParams })\n .post(`${endpoint}/test`, pass, {\n params: endpointParams,\n body: t.Object({ eventType: t.String(), payload: t.Optional(t.Any()) }),\n })\n .post(`${endpoint}/recover`, pass, {\n params: endpointParams,\n body: t.Object({ since: t.String() }),\n })\n .get(`${app}/messages`, pass, { params: appParams })\n .post(`${app}/messages`, pass, {\n params: appParams,\n body: t.Object({\n eventType: t.String(),\n payload: t.Any(),\n timestamp: t.Optional(t.String()),\n idempotencyKey: t.Optional(t.String()),\n }),\n })\n .get(`${app}/messages/:id`, pass, { params: endpointParams })\n .get(`${app}/deliveries`, pass, { params: appParams })\n .get(`${app}/deliveries/:id`, pass, { params: endpointParams })\n .post(`${app}/deliveries/:id/retry`, pass, { params: endpointParams })\n .get(`${app}/audit`, pass, { params: appParams })\n .all(\"/*\", pass); // bundled UI assets + SPA fallback\n}\n"],"mappings":"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"}
|