easy-ping 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -4
- package/dist/{adapter-D3lTy-4E.d.cts → adapter-CV5Yt2c6.d.cts} +1 -1
- package/dist/{adapter-D3lTy-4E.d.ts → adapter-CV5Yt2c6.d.ts} +1 -1
- package/dist/adapters/drizzle.d.cts +1 -1
- package/dist/adapters/drizzle.d.ts +1 -1
- package/dist/adapters/mongodb.d.cts +2 -2
- package/dist/adapters/mongodb.d.ts +2 -2
- package/dist/adapters/mysql.d.cts +2 -2
- package/dist/adapters/mysql.d.ts +2 -2
- package/dist/adapters/postgres.d.cts +2 -2
- package/dist/adapters/postgres.d.ts +2 -2
- package/dist/adapters/sqlite.d.cts +2 -2
- package/dist/adapters/sqlite.d.ts +2 -2
- package/dist/chunk-R3BE67L7.js +13 -0
- package/dist/chunk-R3BE67L7.js.map +1 -0
- package/dist/chunk-SSHVZHSJ.js +100 -0
- package/dist/chunk-SSHVZHSJ.js.map +1 -0
- package/dist/client.cjs +7 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +7 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -11
- package/dist/index.js.map +1 -1
- package/dist/{plugin-CHd2ZiGC.d.cts → plugin-6S8tpH0n.d.cts} +1 -1
- package/dist/{plugin-Cg9usuUi.d.ts → plugin-C4fikLl7.d.ts} +1 -1
- package/dist/plugins/digests.d.cts +2 -2
- package/dist/plugins/digests.d.ts +2 -2
- package/dist/plugins/mobile-push.cjs +307 -0
- package/dist/plugins/mobile-push.cjs.map +1 -0
- package/dist/plugins/mobile-push.d.cts +117 -0
- package/dist/plugins/mobile-push.d.ts +117 -0
- package/dist/plugins/mobile-push.js +261 -0
- package/dist/plugins/mobile-push.js.map +1 -0
- package/dist/plugins/preferences-client.d.cts +2 -2
- package/dist/plugins/preferences-client.d.ts +2 -2
- package/dist/plugins/preferences-react.d.cts +1 -1
- package/dist/plugins/preferences-react.d.ts +1 -1
- package/dist/plugins/preferences.cjs +9 -1
- package/dist/plugins/preferences.cjs.map +1 -1
- package/dist/plugins/preferences.d.cts +2 -2
- package/dist/plugins/preferences.d.ts +2 -2
- package/dist/plugins/preferences.js +9 -1
- package/dist/plugins/preferences.js.map +1 -1
- package/dist/plugins/push.d.cts +2 -2
- package/dist/plugins/push.d.ts +2 -2
- package/dist/plugins/telegram.cjs +379 -0
- package/dist/plugins/telegram.cjs.map +1 -0
- package/dist/plugins/telegram.d.cts +153 -0
- package/dist/plugins/telegram.d.ts +153 -0
- package/dist/plugins/telegram.js +313 -0
- package/dist/plugins/telegram.js.map +1 -0
- package/dist/providers/expo-push.cjs +114 -0
- package/dist/providers/expo-push.cjs.map +1 -0
- package/dist/providers/expo-push.d.cts +61 -0
- package/dist/providers/expo-push.d.ts +61 -0
- package/dist/providers/expo-push.js +109 -0
- package/dist/providers/expo-push.js.map +1 -0
- package/dist/providers/telegram.cjs +103 -0
- package/dist/providers/telegram.cjs.map +1 -0
- package/dist/providers/telegram.d.cts +83 -0
- package/dist/providers/telegram.d.ts +83 -0
- package/dist/providers/telegram.js +3 -0
- package/dist/providers/telegram.js.map +1 -0
- package/dist/providers/web-push.d.cts +2 -2
- package/dist/providers/web-push.d.ts +2 -2
- package/dist/react-native.cjs +637 -0
- package/dist/react-native.cjs.map +1 -0
- package/dist/react-native.d.cts +145 -0
- package/dist/react-native.d.ts +145 -0
- package/dist/react-native.js +609 -0
- package/dist/react-native.js.map +1 -0
- package/dist/react.cjs +7 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +7 -2
- package/dist/react.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +38 -4
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/index.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["export type NotificationView = {\n id: string;\n userId: string;\n type: string;\n payload: unknown;\n actorId: string | null;\n groupKey: string | null;\n seenAt: string | null;\n readAt: string | null;\n createdAt: string;\n};\n\nexport type NotifyState = {\n notifications: readonly NotificationView[];\n /** Badge count — cleared when the dropdown opens. Server-authoritative. */\n unseenCount: number;\n /** Items still bold. Derived from what is loaded, not a server total. */\n unreadCount: number;\n nextCursor: string | null;\n isLoading: boolean;\n error: Error | null;\n};\n\nexport type Transport = \"auto\" | \"sse\" | \"poll\";\n\nexport type TransportStatus = {\n /** \"leader\" holds this browser's single connection; \"follower\" mirrors it; \"solo\" has no Web Locks. */\n role: \"leader\" | \"follower\" | \"solo\";\n /** What the leader is using right now. */\n transport: \"sse\" | \"poll\";\n /** True while the event stream has said `ready` and not since closed. */\n connected: boolean;\n};\n\ntype LockManagerLike = {\n request(\n name: string,\n options: { mode: \"exclusive\"; signal?: AbortSignal },\n callback: () => Promise<void>,\n ): Promise<void>;\n};\n\ntype ChannelLike = {\n postMessage(message: unknown): void;\n addEventListener(type: \"message\", handler: (event: { data: unknown }) => void): void;\n removeEventListener(type: \"message\", handler: (event: { data: unknown }) => void): void;\n close(): void;\n};\n\ntype MessageTargetLike = {\n addEventListener(type: string, handler: (event: { data?: unknown }) => void): void;\n removeEventListener(type: string, handler: (event: { data?: unknown }) => void): void;\n};\n\nexport type NotifyClientOptions = {\n baseUrl?: string | undefined;\n fetch?: typeof globalThis.fetch | undefined;\n limit?: number | undefined;\n /** Poll interval while the user is active and no stream is connected. Default 15 s. */\n pollIntervalMs?: number | undefined;\n /** Ceiling the idle backoff grows to. Default 10 min. */\n maxPollIntervalMs?: number | undefined;\n /** Poll interval while the event stream is connected: a safety net. Default 5 min. */\n safetyNetMs?: number | undefined;\n /** How long after user input polling stays at the base interval. Default 60 s. */\n activeWindowMs?: number | undefined;\n /** \"auto\" streams when a DOM is present, \"poll\" never streams, \"sse\" always tries. */\n transport?: Transport | undefined;\n /** Injectable so the poller can be tested without a DOM. */\n isDocumentHidden?: (() => boolean) | undefined;\n /** Web Locks, for one connection per browser. Defaults to navigator.locks; null disables. */\n locks?: LockManagerLike | null | undefined;\n /** Cross-tab channel factory. Defaults to BroadcastChannel; null disables. */\n channel?: (() => ChannelLike) | null | undefined;\n /** Where push relays arrive. Defaults to navigator.serviceWorker; null disables. */\n serviceWorker?: MessageTargetLike | null | undefined;\n /** Where user activity is observed. Defaults to window; null disables. */\n activityTarget?: MessageTargetLike | null | undefined;\n now?: (() => number) | undefined;\n /**\n * Separates browsers' tab groups when identity is not a cookie: pass the\n * current user's id (or session id) so tabs signed in as different users\n * never share a leader or mirror each other's inbox.\n */\n scope?: string | undefined;\n};\n\nconst INITIAL: NotifyState = {\n notifications: [],\n unseenCount: 0,\n unreadCount: 0,\n nextCursor: null,\n isLoading: true,\n error: null,\n};\n\nconst countUnread = (rows: readonly NotificationView[]) =>\n rows.reduce((total, row) => (row.readAt === null ? total + 1 : total), 0);\n\n/** A stream that dies this fast, repeatedly, is being cut by the platform: stop trying. */\nconst SHORT_STREAM_MS = 15_000;\nconst SHORT_STREAMS_BEFORE_GIVING_UP = 3;\n\ntype TabMessage =\n | { type: \"hello\" }\n | { type: \"state\"; state: NotifyState }\n | { type: \"mutated\" }\n | { type: \"leader\" };\n\nexport function createNotifyClient(options: NotifyClientOptions = {}) {\n const baseUrl = (options.baseUrl ?? \"/api/notifications\").replace(/\\/$/, \"\");\n const doFetch = options.fetch ?? ((...args) => globalThis.fetch(...args));\n const limit = options.limit ?? 20;\n const pollIntervalMs = options.pollIntervalMs ?? 15_000;\n const maxPollIntervalMs = options.maxPollIntervalMs ?? 10 * 60_000;\n const safetyNetMs = options.safetyNetMs ?? 5 * 60_000;\n const activeWindowMs = options.activeWindowMs ?? 60_000;\n const now = options.now ?? (() => Date.now());\n const groupKey = options.scope ? `${baseUrl}#${options.scope}` : baseUrl;\n const hasDom = typeof window !== \"undefined\" && typeof document !== \"undefined\";\n const transport: Transport = options.transport ?? \"auto\";\n const wantsStream = transport === \"sse\" || (transport === \"auto\" && hasDom);\n const isHidden =\n options.isDocumentHidden ??\n (() => typeof document !== \"undefined\" && document.visibilityState === \"hidden\");\n\n const locks =\n options.locks === undefined\n ? typeof navigator !== \"undefined\"\n ? ((navigator as { locks?: LockManagerLike }).locks ?? null)\n : null\n : options.locks;\n const makeChannel =\n options.channel === undefined\n ? typeof BroadcastChannel !== \"undefined\"\n ? () => new BroadcastChannel(`easy-ping:${groupKey}`) as unknown as ChannelLike\n : null\n : options.channel;\n const serviceWorker =\n options.serviceWorker === undefined\n ? typeof navigator !== \"undefined\"\n ? ((navigator as { serviceWorker?: MessageTargetLike }).serviceWorker ?? null)\n : null\n : options.serviceWorker;\n const activityTarget =\n options.activityTarget === undefined\n ? hasDom\n ? (window as unknown as MessageTargetLike)\n : null\n : options.activityTarget;\n\n let state: NotifyState = INITIAL;\n const listeners = new Set<(state: NotifyState) => void>();\n\n let role: TransportStatus[\"role\"] = \"solo\";\n let running = false;\n let channel: ChannelLike | undefined;\n let releaseLock: (() => void) | undefined;\n let lockAbort: AbortController | undefined;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let consecutiveFailures = 0;\n let idleTicks = 0;\n let activeUntil = 0;\n let lastRefreshAt = 0;\n let refreshing: Promise<void> | undefined;\n let refreshAgain = false;\n\n let streamAbort: AbortController | undefined;\n let streamConnected = false;\n let streamDisabled = !wantsStream;\n let shortStreams = 0;\n let streamFailures = 0;\n\n // Bumped per mutation so an in-flight poll cannot revert an optimistic update.\n let generation = 0;\n\n // A follower's own optimistic changes, kept until the leader's state reflects them.\n const localReads = new Set<string>();\n let localSeen = false;\n\n const stamp = () => new Date().toISOString();\n\n function emit() {\n for (const listener of listeners) listener(state);\n }\n\n function set(patch: Partial<NotifyState>) {\n state = { ...state, ...patch };\n state = { ...state, unreadCount: countUnread(state.notifications) };\n emit();\n if (role === \"leader\") post({ type: \"state\", state });\n }\n\n function post(message: TabMessage) {\n try {\n channel?.postMessage(message);\n } catch {\n // channel closed\n }\n }\n\n async function request<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await doFetch(`${baseUrl}${path}`, {\n ...init,\n headers: { \"content-type\": \"application/json\", ...init?.headers },\n });\n if (!response.ok)\n throw new Error(`${init?.method ?? \"GET\"} ${path} failed: ${response.status}`);\n return (await response.json()) as T;\n }\n\n async function fetchPage(): Promise<void> {\n const startedAt = generation;\n\n const page = await request<{\n notifications: NotificationView[];\n nextCursor: string | null;\n unseenCount?: number;\n }>(`/?limit=${limit}`);\n\n const unseenCount = page.unseenCount ?? (await request<{ unseen: number }>(\"/count\")).unseen;\n\n // A mutation landed while this was in flight; the response is stale.\n if (generation !== startedAt) return;\n\n set({\n notifications: page.notifications,\n nextCursor: page.nextCursor,\n unseenCount,\n isLoading: false,\n error: null,\n });\n }\n\n /** Coalesces overlapping calls: a second request while one is in flight runs once after it. */\n function refresh(): Promise<void> {\n if (refreshing) {\n refreshAgain = true;\n return refreshing;\n }\n refreshing = (async () => {\n try {\n do {\n refreshAgain = false;\n await fetchPage();\n lastRefreshAt = now();\n } while (refreshAgain);\n } finally {\n refreshing = undefined;\n }\n })();\n return refreshing;\n }\n\n let loadingMore: Promise<void> | undefined;\n\n async function loadMore(): Promise<void> {\n if (!state.nextCursor) return;\n\n // Share the in-flight request so two clicks cannot append the same page twice.\n if (loadingMore) return loadingMore;\n\n const cursor = state.nextCursor;\n\n loadingMore = (async () => {\n try {\n const page = await request<{\n notifications: NotificationView[];\n nextCursor: string | null;\n }>(`/?limit=${limit}&cursor=${encodeURIComponent(cursor)}`);\n\n const known = new Set(state.notifications.map((row) => row.id));\n\n set({\n notifications: [\n ...state.notifications,\n ...page.notifications.filter((row) => !known.has(row.id)),\n ],\n nextCursor: page.nextCursor,\n });\n } finally {\n loadingMore = undefined;\n }\n })();\n\n return loadingMore;\n }\n\n /** Applies a patch immediately and reverts it if the request fails. */\n async function optimistic(\n apply: (rows: readonly NotificationView[]) => readonly NotificationView[],\n patch: Partial<NotifyState>,\n send: () => Promise<unknown>,\n ): Promise<void> {\n const previous = state;\n generation += 1;\n set({ notifications: apply(state.notifications), ...patch });\n\n try {\n await send();\n if (role === \"follower\") post({ type: \"mutated\" });\n } catch (error) {\n generation += 1;\n state = previous;\n emit();\n throw error;\n }\n }\n\n async function markAsRead(id: string): Promise<void> {\n if (role === \"follower\") localReads.add(id);\n await optimistic(\n (rows) =>\n rows.map((row) => (row.id === id ? { ...row, readAt: row.readAt ?? stamp() } : row)),\n {},\n () => request(\"/read\", { method: \"POST\", body: JSON.stringify({ ids: [id] }) }),\n ).catch((error) => {\n localReads.delete(id);\n throw error;\n });\n }\n\n async function markAllRead(): Promise<void> {\n if (role === \"follower\") for (const row of state.notifications) localReads.add(row.id);\n await optimistic(\n (rows) => rows.map((row) => ({ ...row, readAt: row.readAt ?? stamp() })),\n {},\n () => request(\"/read-all\", { method: \"POST\" }),\n ).catch((error) => {\n localReads.clear();\n throw error;\n });\n }\n\n async function markSeen(): Promise<void> {\n // Only what the user could have seen: no cutoff would mark newer arrivals seen too.\n const newest = state.notifications[0]?.createdAt;\n if (role === \"follower\") localSeen = true;\n\n await optimistic(\n (rows) => rows.map((row) => ({ ...row, seenAt: row.seenAt ?? stamp() })),\n { unseenCount: 0 },\n () =>\n request(\"/seen\", {\n method: \"POST\",\n body: JSON.stringify(newest ? { before: newest } : {}),\n }),\n ).catch((error) => {\n localSeen = false;\n throw error;\n });\n }\n\n // ---- follower: mirror the leader, keep own optimistic changes until confirmed ----\n\n function applyRemoteState(remote: NotifyState) {\n const notifications = remote.notifications.map((row) => {\n if (!localReads.has(row.id)) return row;\n if (row.readAt) {\n localReads.delete(row.id);\n return row;\n }\n return { ...row, readAt: stamp() };\n });\n if (localSeen && remote.unseenCount === 0) localSeen = false;\n state = {\n ...remote,\n notifications,\n unseenCount: localSeen ? 0 : remote.unseenCount,\n unreadCount: countUnread(notifications),\n };\n emit();\n }\n\n function onTabMessage(event: { data: unknown }) {\n const message = event.data as TabMessage | null;\n if (!message || typeof message !== \"object\") return;\n if (role === \"leader\") {\n if (message.type === \"hello\") post({ type: \"state\", state });\n else if (message.type === \"mutated\") void refresh().catch(() => {});\n return;\n }\n if (message.type === \"state\") applyRemoteState(message.state);\n }\n\n // ---- leader / solo: polling with activity-shaped backoff ----\n\n function schedule(delay: number) {\n clearTimeout(timer);\n timer = setTimeout(tick, delay);\n }\n\n function nextDelay(): number {\n if (consecutiveFailures > 0) {\n return Math.min(pollIntervalMs * 2 ** consecutiveFailures, maxPollIntervalMs);\n }\n if (streamConnected) return safetyNetMs;\n if (now() < activeUntil) return pollIntervalMs;\n idleTicks += 1;\n return Math.min(pollIntervalMs * 2 ** idleTicks, maxPollIntervalMs);\n }\n\n async function tick() {\n if (!running) return;\n // A hidden tab is not looking at the bell.\n if (isHidden()) {\n schedule(pollIntervalMs);\n return;\n }\n\n try {\n await refresh();\n consecutiveFailures = 0;\n } catch (error) {\n consecutiveFailures += 1;\n set({ isLoading: false, error: error instanceof Error ? error : new Error(String(error)) });\n }\n\n if (running) schedule(nextDelay());\n }\n\n /** User input or a returning tab: poll fast again, and catch up now if the last refresh is stale. */\n function onActivity() {\n if (!running || role === \"follower\") return;\n activeUntil = now() + activeWindowMs;\n idleTicks = 0;\n if (!isHidden() && now() - lastRefreshAt > pollIntervalMs) {\n void refresh().catch(() => {});\n schedule(nextDelay());\n }\n }\n\n const onVisibility = () => {\n if (!isHidden()) onActivity();\n };\n\n const ACTIVITY_EVENTS = [\"focus\", \"pointerdown\", \"keydown\", \"online\"];\n\n // ---- leader / solo: the event stream ----\n\n function changed() {\n if (!running) return;\n void refresh().catch(() => {});\n }\n\n async function readStream(signal: AbortSignal): Promise<void> {\n const openedAt = now();\n const response = await doFetch(`${baseUrl}/events`, {\n headers: { accept: \"text/event-stream\" },\n signal,\n });\n if (!response.ok || !response.body) throw new Error(`GET /events failed: ${response.status}`);\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let eventName = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let newline = buffer.indexOf(\"\\n\");\n while (newline >= 0) {\n const line = buffer.slice(0, newline).replace(/\\r$/, \"\");\n buffer = buffer.slice(newline + 1);\n if (line === \"\") {\n if (eventName === \"ready\") {\n streamConnected = true;\n streamFailures = 0;\n schedule(nextDelay());\n } else if (eventName === \"changed\") changed();\n eventName = \"\";\n } else if (line.startsWith(\"event:\")) eventName = line.slice(6).trim();\n newline = buffer.indexOf(\"\\n\");\n }\n }\n } finally {\n streamConnected = false;\n if (now() - openedAt < SHORT_STREAM_MS) shortStreams += 1;\n else shortStreams = 0;\n }\n }\n\n const isFollower = () => role === \"follower\";\n\n async function streamLoop() {\n while (running && !streamDisabled && !isFollower()) {\n streamAbort = new AbortController();\n try {\n await readStream(streamAbort.signal);\n } catch {\n if (!running) return;\n streamFailures += 1;\n }\n if (!running || isFollower()) return;\n if (shortStreams >= SHORT_STREAMS_BEFORE_GIVING_UP) {\n // The platform cuts streams too fast for them to be worth it here.\n streamDisabled = true;\n schedule(nextDelay());\n return;\n }\n await new Promise((resolve) =>\n setTimeout(resolve, Math.min(3_000 * 2 ** streamFailures, 60_000)),\n );\n }\n }\n\n // ---- roles ----\n\n function becomeLeader() {\n role = \"leader\";\n post({ type: \"leader\" });\n post({ type: \"state\", state });\n void tick();\n if (!streamDisabled) void streamLoop();\n }\n\n function becomeFollower() {\n role = \"follower\";\n post({ type: \"hello\" });\n }\n\n function acquireLeadership() {\n if (!locks || !channel) {\n role = \"solo\";\n void tick();\n if (!streamDisabled) void streamLoop();\n return;\n }\n becomeFollower();\n lockAbort = new AbortController();\n locks\n .request(\n `easy-ping:leader:${groupKey}`,\n { mode: \"exclusive\", signal: lockAbort.signal },\n () =>\n new Promise<void>((resolve) => {\n releaseLock = resolve;\n if (running) becomeLeader();\n else resolve();\n }),\n )\n .catch(() => {\n // aborted on stop, or Web Locks refused; either way no leadership here\n });\n }\n\n function start() {\n running = true;\n channel = makeChannel ? makeChannel() : undefined;\n channel?.addEventListener(\"message\", onTabMessage);\n serviceWorker?.addEventListener(\"message\", onRelay);\n if (activityTarget) {\n for (const name of ACTIVITY_EVENTS) activityTarget.addEventListener(name, onActivity);\n }\n if (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", onVisibility);\n }\n activeUntil = now() + activeWindowMs;\n acquireLeadership();\n }\n\n function stop() {\n running = false;\n clearTimeout(timer);\n timer = undefined;\n streamAbort?.abort();\n streamAbort = undefined;\n lockAbort?.abort();\n lockAbort = undefined;\n releaseLock?.();\n releaseLock = undefined;\n channel?.removeEventListener(\"message\", onTabMessage);\n channel?.close();\n channel = undefined;\n serviceWorker?.removeEventListener(\"message\", onRelay);\n if (activityTarget) {\n for (const name of ACTIVITY_EVENTS) activityTarget.removeEventListener(name, onActivity);\n }\n if (typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", onVisibility);\n }\n role = \"solo\";\n }\n\n // ---- signals from outside: the service worker relay and the piggyback header ----\n\n function onRelay(event: { data?: unknown }) {\n const data = event.data as { source?: unknown; type?: unknown } | null;\n if (data?.source === \"easy-ping\" && data.type === \"changed\") changed();\n }\n\n let lastInboxVersion: string | null = null;\n\n // Only the app's own origin may steer the bell; a third-party response cannot.\n const sameOrigin = (response: Response) => {\n if (!response.url || typeof location === \"undefined\") return true;\n try {\n return new URL(response.url).origin === location.origin;\n } catch {\n return false;\n }\n };\n\n /**\n * Wraps your app's fetch so responses carrying the inbox-version header\n * refresh the bell when the version moves. Active users then never need a\n * dedicated poll. RFC 0006 idea 1.\n */\n function instrument(inner: typeof globalThis.fetch): typeof globalThis.fetch {\n return (async (...args: Parameters<typeof globalThis.fetch>) => {\n const response = await inner(...args);\n const version = sameOrigin(response) ? response.headers.get(\"x-easy-ping-inbox\") : null;\n if (version !== null && version !== lastInboxVersion) {\n const first = lastInboxVersion === null;\n lastInboxVersion = version;\n if (!first && role !== \"follower\") changed();\n }\n return response;\n }) as typeof globalThis.fetch;\n }\n\n function subscribe(listener: (state: NotifyState) => void): () => void {\n listeners.add(listener);\n listener(state);\n\n // One transport regardless of how many components subscribe.\n if (listeners.size === 1) start();\n\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) stop();\n };\n }\n\n return {\n subscribe,\n refresh,\n loadMore,\n markAsRead,\n markAllRead,\n markSeen,\n instrument,\n getState: () => state,\n getTransport: (): TransportStatus => ({\n role,\n transport: streamDisabled ? \"poll\" : \"sse\",\n connected: streamConnected,\n }),\n };\n}\n\nexport type NotifyClient = ReturnType<typeof createNotifyClient>;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/index.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["export type NotificationView = {\n id: string;\n userId: string;\n type: string;\n payload: unknown;\n actorId: string | null;\n groupKey: string | null;\n seenAt: string | null;\n readAt: string | null;\n createdAt: string;\n};\n\nexport type NotifyState = {\n notifications: readonly NotificationView[];\n /** Badge count — cleared when the dropdown opens. Server-authoritative. */\n unseenCount: number;\n /** Items still bold. Derived from what is loaded, not a server total. */\n unreadCount: number;\n nextCursor: string | null;\n isLoading: boolean;\n error: Error | null;\n};\n\nexport type Transport = \"auto\" | \"sse\" | \"poll\";\n\nexport type TransportStatus = {\n /** \"leader\" holds this browser's single connection; \"follower\" mirrors it; \"solo\" has no Web Locks. */\n role: \"leader\" | \"follower\" | \"solo\";\n /** What the leader is using right now. */\n transport: \"sse\" | \"poll\";\n /** True while the event stream has said `ready` and not since closed. */\n connected: boolean;\n};\n\ntype LockManagerLike = {\n request(\n name: string,\n options: { mode: \"exclusive\"; signal?: AbortSignal },\n callback: () => Promise<void>,\n ): Promise<void>;\n};\n\ntype ChannelLike = {\n postMessage(message: unknown): void;\n addEventListener(type: \"message\", handler: (event: { data: unknown }) => void): void;\n removeEventListener(type: \"message\", handler: (event: { data: unknown }) => void): void;\n close(): void;\n};\n\ntype MessageTargetLike = {\n addEventListener(type: string, handler: (event: { data?: unknown }) => void): void;\n removeEventListener(type: string, handler: (event: { data?: unknown }) => void): void;\n};\n\nexport type NotifyClientOptions = {\n baseUrl?: string | undefined;\n fetch?: typeof globalThis.fetch | undefined;\n limit?: number | undefined;\n /** Poll interval while the user is active and no stream is connected. Default 15 s. */\n pollIntervalMs?: number | undefined;\n /** Ceiling the idle backoff grows to. Default 10 min. */\n maxPollIntervalMs?: number | undefined;\n /** Poll interval while the event stream is connected: a safety net. Default 5 min. */\n safetyNetMs?: number | undefined;\n /** How long after user input polling stays at the base interval. Default 60 s. */\n activeWindowMs?: number | undefined;\n /** \"auto\" streams when a DOM is present, \"poll\" never streams, \"sse\" always tries. */\n transport?: Transport | undefined;\n /** Injectable so the poller can be tested without a DOM. */\n isDocumentHidden?: (() => boolean) | undefined;\n /** Web Locks, for one connection per browser. Defaults to navigator.locks; null disables. */\n locks?: LockManagerLike | null | undefined;\n /** Cross-tab channel factory. Defaults to BroadcastChannel; null disables. */\n channel?: (() => ChannelLike) | null | undefined;\n /** Where push relays arrive. Defaults to navigator.serviceWorker; null disables. */\n serviceWorker?: MessageTargetLike | null | undefined;\n /** Where user activity is observed. Defaults to window; null disables. */\n activityTarget?: MessageTargetLike | null | undefined;\n now?: (() => number) | undefined;\n /**\n * Separates browsers' tab groups when identity is not a cookie: pass the\n * current user's id (or session id) so tabs signed in as different users\n * never share a leader or mirror each other's inbox.\n */\n scope?: string | undefined;\n};\n\nconst INITIAL: NotifyState = {\n notifications: [],\n unseenCount: 0,\n unreadCount: 0,\n nextCursor: null,\n isLoading: true,\n error: null,\n};\n\nconst countUnread = (rows: readonly NotificationView[]) =>\n rows.reduce((total, row) => (row.readAt === null ? total + 1 : total), 0);\n\n/** A stream that dies this fast, repeatedly, is being cut by the platform: stop trying. */\nconst SHORT_STREAM_MS = 15_000;\nconst SHORT_STREAMS_BEFORE_GIVING_UP = 3;\n\ntype TabMessage =\n | { type: \"hello\" }\n | { type: \"state\"; state: NotifyState }\n | { type: \"mutated\" }\n | { type: \"leader\" };\n\nexport function createNotifyClient(options: NotifyClientOptions = {}) {\n const baseUrl = (options.baseUrl ?? \"/api/notifications\").replace(/\\/$/, \"\");\n const doFetch = options.fetch ?? ((...args) => globalThis.fetch(...args));\n const limit = options.limit ?? 20;\n const pollIntervalMs = options.pollIntervalMs ?? 15_000;\n const maxPollIntervalMs = options.maxPollIntervalMs ?? 10 * 60_000;\n const safetyNetMs = options.safetyNetMs ?? 5 * 60_000;\n const activeWindowMs = options.activeWindowMs ?? 60_000;\n const now = options.now ?? (() => Date.now());\n const groupKey = options.scope ? `${baseUrl}#${options.scope}` : baseUrl;\n const hasDom = typeof window !== \"undefined\" && typeof document !== \"undefined\";\n const transport: Transport = options.transport ?? \"auto\";\n const wantsStream = transport === \"sse\" || (transport === \"auto\" && hasDom);\n const isHidden =\n options.isDocumentHidden ??\n (() => typeof document !== \"undefined\" && document.visibilityState === \"hidden\");\n\n const locks =\n options.locks === undefined\n ? typeof navigator !== \"undefined\"\n ? ((navigator as { locks?: LockManagerLike }).locks ?? null)\n : null\n : options.locks;\n const makeChannel =\n options.channel === undefined\n ? typeof BroadcastChannel !== \"undefined\"\n ? () => new BroadcastChannel(`easy-ping:${groupKey}`) as unknown as ChannelLike\n : null\n : options.channel;\n const serviceWorker =\n options.serviceWorker === undefined\n ? typeof navigator !== \"undefined\"\n ? ((navigator as { serviceWorker?: MessageTargetLike }).serviceWorker ?? null)\n : null\n : options.serviceWorker;\n const activityTarget =\n options.activityTarget === undefined\n ? hasDom\n ? (window as unknown as MessageTargetLike)\n : null\n : options.activityTarget;\n\n let state: NotifyState = INITIAL;\n const listeners = new Set<(state: NotifyState) => void>();\n\n let role: TransportStatus[\"role\"] = \"solo\";\n let running = false;\n let channel: ChannelLike | undefined;\n let releaseLock: (() => void) | undefined;\n let lockAbort: AbortController | undefined;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let consecutiveFailures = 0;\n let idleTicks = 0;\n let activeUntil = 0;\n let lastRefreshAt = 0;\n let refreshing: Promise<void> | undefined;\n let refreshAgain = false;\n\n let streamAbort: AbortController | undefined;\n let streamConnected = false;\n let streamDisabled = !wantsStream;\n let shortStreams = 0;\n let streamFailures = 0;\n\n // Bumped per mutation so an in-flight poll cannot revert an optimistic update.\n let generation = 0;\n\n // A follower's own optimistic changes, kept until the leader's state reflects them.\n const localReads = new Set<string>();\n let localSeen = false;\n\n const stamp = () => new Date().toISOString();\n\n function emit() {\n for (const listener of listeners) listener(state);\n }\n\n function set(patch: Partial<NotifyState>) {\n state = { ...state, ...patch };\n state = { ...state, unreadCount: countUnread(state.notifications) };\n emit();\n if (role === \"leader\") post({ type: \"state\", state });\n }\n\n function post(message: TabMessage) {\n try {\n channel?.postMessage(message);\n } catch {\n // channel closed\n }\n }\n\n async function request<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await doFetch(`${baseUrl}${path}`, {\n ...init,\n headers: { \"content-type\": \"application/json\", ...init?.headers },\n });\n if (!response.ok)\n throw new Error(`${init?.method ?? \"GET\"} ${path} failed: ${response.status}`);\n return (await response.json()) as T;\n }\n\n async function fetchPage(): Promise<void> {\n const startedAt = generation;\n\n const page = await request<{\n notifications: NotificationView[];\n nextCursor: string | null;\n unseenCount?: number;\n }>(`/?limit=${limit}`);\n\n const unseenCount = page.unseenCount ?? (await request<{ unseen: number }>(\"/count\")).unseen;\n\n // A mutation landed while this was in flight; the response is stale.\n if (generation !== startedAt) return;\n\n set({\n notifications: page.notifications,\n nextCursor: page.nextCursor,\n unseenCount,\n isLoading: false,\n error: null,\n });\n }\n\n /** Coalesces overlapping calls: a second request while one is in flight runs once after it. */\n function refresh(): Promise<void> {\n if (refreshing) {\n refreshAgain = true;\n return refreshing;\n }\n refreshing = (async () => {\n try {\n do {\n refreshAgain = false;\n await fetchPage();\n lastRefreshAt = now();\n } while (refreshAgain);\n } finally {\n refreshing = undefined;\n }\n })();\n return refreshing;\n }\n\n let loadingMore: Promise<void> | undefined;\n\n async function loadMore(): Promise<void> {\n if (!state.nextCursor) return;\n\n // Share the in-flight request so two clicks cannot append the same page twice.\n if (loadingMore) return loadingMore;\n\n const cursor = state.nextCursor;\n\n loadingMore = (async () => {\n try {\n const page = await request<{\n notifications: NotificationView[];\n nextCursor: string | null;\n }>(`/?limit=${limit}&cursor=${encodeURIComponent(cursor)}`);\n\n const known = new Set(state.notifications.map((row) => row.id));\n\n set({\n notifications: [\n ...state.notifications,\n ...page.notifications.filter((row) => !known.has(row.id)),\n ],\n nextCursor: page.nextCursor,\n });\n } finally {\n loadingMore = undefined;\n }\n })();\n\n return loadingMore;\n }\n\n /** Applies a patch immediately and reverts it if the request fails. */\n async function optimistic(\n apply: (rows: readonly NotificationView[]) => readonly NotificationView[],\n patch: Partial<NotifyState>,\n send: () => Promise<unknown>,\n ): Promise<void> {\n const previous = state;\n generation += 1;\n set({ notifications: apply(state.notifications), ...patch });\n\n try {\n await send();\n if (role === \"follower\") post({ type: \"mutated\" });\n } catch (error) {\n generation += 1;\n state = previous;\n emit();\n throw error;\n }\n }\n\n async function markAsRead(id: string): Promise<void> {\n if (role === \"follower\") localReads.add(id);\n await optimistic(\n (rows) =>\n rows.map((row) => (row.id === id ? { ...row, readAt: row.readAt ?? stamp() } : row)),\n {},\n () => request(\"/read\", { method: \"POST\", body: JSON.stringify({ ids: [id] }) }),\n ).catch((error) => {\n localReads.delete(id);\n throw error;\n });\n }\n\n async function markAllRead(): Promise<void> {\n if (role === \"follower\") for (const row of state.notifications) localReads.add(row.id);\n await optimistic(\n (rows) => rows.map((row) => ({ ...row, readAt: row.readAt ?? stamp() })),\n {},\n () => request(\"/read-all\", { method: \"POST\" }),\n ).catch((error) => {\n localReads.clear();\n throw error;\n });\n }\n\n async function markSeen(): Promise<void> {\n // Only what the user could have seen: no cutoff would mark newer arrivals seen too.\n const newest = state.notifications[0]?.createdAt;\n if (role === \"follower\") localSeen = true;\n\n await optimistic(\n (rows) => rows.map((row) => ({ ...row, seenAt: row.seenAt ?? stamp() })),\n { unseenCount: 0 },\n () =>\n request(\"/seen\", {\n method: \"POST\",\n body: JSON.stringify(newest ? { before: newest } : {}),\n }),\n ).catch((error) => {\n localSeen = false;\n throw error;\n });\n }\n\n // ---- follower: mirror the leader, keep own optimistic changes until confirmed ----\n\n function applyRemoteState(remote: NotifyState) {\n const notifications = remote.notifications.map((row) => {\n if (!localReads.has(row.id)) return row;\n if (row.readAt) {\n localReads.delete(row.id);\n return row;\n }\n return { ...row, readAt: stamp() };\n });\n if (localSeen && remote.unseenCount === 0) localSeen = false;\n state = {\n ...remote,\n notifications,\n unseenCount: localSeen ? 0 : remote.unseenCount,\n unreadCount: countUnread(notifications),\n };\n emit();\n }\n\n function onTabMessage(event: { data: unknown }) {\n const message = event.data as TabMessage | null;\n if (!message || typeof message !== \"object\") return;\n if (role === \"leader\") {\n if (message.type === \"hello\") post({ type: \"state\", state });\n else if (message.type === \"mutated\") void refresh().catch(() => {});\n return;\n }\n if (message.type === \"state\") applyRemoteState(message.state);\n }\n\n // ---- leader / solo: polling with activity-shaped backoff ----\n\n function schedule(delay: number) {\n clearTimeout(timer);\n timer = setTimeout(tick, delay);\n }\n\n function nextDelay(): number {\n if (consecutiveFailures > 0) {\n return Math.min(pollIntervalMs * 2 ** consecutiveFailures, maxPollIntervalMs);\n }\n if (streamConnected) return safetyNetMs;\n if (now() < activeUntil) return pollIntervalMs;\n idleTicks += 1;\n return Math.min(pollIntervalMs * 2 ** idleTicks, maxPollIntervalMs);\n }\n\n async function tick() {\n if (!running) return;\n // A hidden tab is not looking at the bell.\n if (isHidden()) {\n schedule(pollIntervalMs);\n return;\n }\n\n try {\n await refresh();\n consecutiveFailures = 0;\n } catch (error) {\n consecutiveFailures += 1;\n set({ isLoading: false, error: error instanceof Error ? error : new Error(String(error)) });\n }\n\n if (running) schedule(nextDelay());\n }\n\n /** User input or a returning tab: poll fast again, and catch up now if the last refresh is stale. */\n function onActivity() {\n if (!running || role === \"follower\") return;\n activeUntil = now() + activeWindowMs;\n idleTicks = 0;\n if (!isHidden() && now() - lastRefreshAt > pollIntervalMs) {\n void refresh().catch(() => {});\n schedule(nextDelay());\n }\n }\n\n const onVisibility = () => {\n if (!isHidden()) onActivity();\n };\n\n const ACTIVITY_EVENTS = [\"focus\", \"pointerdown\", \"keydown\", \"online\"];\n\n // ---- leader / solo: the event stream ----\n\n function changed() {\n if (!running) return;\n void refresh().catch(() => {});\n }\n\n async function readStream(signal: AbortSignal): Promise<void> {\n const openedAt = now();\n const response = await doFetch(`${baseUrl}/events`, {\n headers: { accept: \"text/event-stream\" },\n signal,\n });\n if (!response.ok) throw new Error(`GET /events failed: ${response.status}`);\n if (!response.body) {\n // This fetch cannot stream a body (React Native's built-in one, some polyfills): poll instead.\n streamDisabled = true;\n schedule(nextDelay());\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let eventName = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let newline = buffer.indexOf(\"\\n\");\n while (newline >= 0) {\n const line = buffer.slice(0, newline).replace(/\\r$/, \"\");\n buffer = buffer.slice(newline + 1);\n if (line === \"\") {\n if (eventName === \"ready\") {\n streamConnected = true;\n streamFailures = 0;\n schedule(nextDelay());\n } else if (eventName === \"changed\") changed();\n eventName = \"\";\n } else if (line.startsWith(\"event:\")) eventName = line.slice(6).trim();\n newline = buffer.indexOf(\"\\n\");\n }\n }\n } finally {\n streamConnected = false;\n if (now() - openedAt < SHORT_STREAM_MS) shortStreams += 1;\n else shortStreams = 0;\n }\n }\n\n const isFollower = () => role === \"follower\";\n\n async function streamLoop() {\n while (running && !streamDisabled && !isFollower()) {\n streamAbort = new AbortController();\n try {\n await readStream(streamAbort.signal);\n } catch {\n if (!running) return;\n streamFailures += 1;\n }\n if (!running || isFollower() || streamDisabled) return;\n if (shortStreams >= SHORT_STREAMS_BEFORE_GIVING_UP) {\n // The platform cuts streams too fast for them to be worth it here.\n streamDisabled = true;\n schedule(nextDelay());\n return;\n }\n await new Promise((resolve) =>\n setTimeout(resolve, Math.min(3_000 * 2 ** streamFailures, 60_000)),\n );\n }\n }\n\n // ---- roles ----\n\n function becomeLeader() {\n role = \"leader\";\n post({ type: \"leader\" });\n post({ type: \"state\", state });\n void tick();\n if (!streamDisabled) void streamLoop();\n }\n\n function becomeFollower() {\n role = \"follower\";\n post({ type: \"hello\" });\n }\n\n function acquireLeadership() {\n if (!locks || !channel) {\n role = \"solo\";\n void tick();\n if (!streamDisabled) void streamLoop();\n return;\n }\n becomeFollower();\n lockAbort = new AbortController();\n locks\n .request(\n `easy-ping:leader:${groupKey}`,\n { mode: \"exclusive\", signal: lockAbort.signal },\n () =>\n new Promise<void>((resolve) => {\n releaseLock = resolve;\n if (running) becomeLeader();\n else resolve();\n }),\n )\n .catch(() => {\n // aborted on stop, or Web Locks refused; either way no leadership here\n });\n }\n\n function start() {\n running = true;\n channel = makeChannel ? makeChannel() : undefined;\n channel?.addEventListener(\"message\", onTabMessage);\n serviceWorker?.addEventListener(\"message\", onRelay);\n if (activityTarget) {\n for (const name of ACTIVITY_EVENTS) activityTarget.addEventListener(name, onActivity);\n }\n if (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", onVisibility);\n }\n activeUntil = now() + activeWindowMs;\n acquireLeadership();\n }\n\n function stop() {\n running = false;\n clearTimeout(timer);\n timer = undefined;\n streamAbort?.abort();\n streamAbort = undefined;\n lockAbort?.abort();\n lockAbort = undefined;\n releaseLock?.();\n releaseLock = undefined;\n channel?.removeEventListener(\"message\", onTabMessage);\n channel?.close();\n channel = undefined;\n serviceWorker?.removeEventListener(\"message\", onRelay);\n if (activityTarget) {\n for (const name of ACTIVITY_EVENTS) activityTarget.removeEventListener(name, onActivity);\n }\n if (typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", onVisibility);\n }\n role = \"solo\";\n }\n\n // ---- signals from outside: the service worker relay and the piggyback header ----\n\n function onRelay(event: { data?: unknown }) {\n const data = event.data as { source?: unknown; type?: unknown } | null;\n if (data?.source === \"easy-ping\" && data.type === \"changed\") changed();\n }\n\n let lastInboxVersion: string | null = null;\n\n // Only the app's own origin may steer the bell; a third-party response cannot.\n const sameOrigin = (response: Response) => {\n if (!response.url || typeof location === \"undefined\") return true;\n try {\n return new URL(response.url).origin === location.origin;\n } catch {\n return false;\n }\n };\n\n /**\n * Wraps your app's fetch so responses carrying the inbox-version header\n * refresh the bell when the version moves. Active users then never need a\n * dedicated poll. RFC 0006 idea 1.\n */\n function instrument(inner: typeof globalThis.fetch): typeof globalThis.fetch {\n return (async (...args: Parameters<typeof globalThis.fetch>) => {\n const response = await inner(...args);\n const version = sameOrigin(response) ? response.headers.get(\"x-easy-ping-inbox\") : null;\n if (version !== null && version !== lastInboxVersion) {\n const first = lastInboxVersion === null;\n lastInboxVersion = version;\n if (!first && role !== \"follower\") changed();\n }\n return response;\n }) as typeof globalThis.fetch;\n }\n\n function subscribe(listener: (state: NotifyState) => void): () => void {\n listeners.add(listener);\n listener(state);\n\n // One transport regardless of how many components subscribe.\n if (listeners.size === 1) start();\n\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) stop();\n };\n }\n\n return {\n subscribe,\n refresh,\n loadMore,\n markAsRead,\n markAllRead,\n markSeen,\n instrument,\n getState: () => state,\n getTransport: (): TransportStatus => ({\n role,\n transport: streamDisabled ? \"poll\" : \"sse\",\n connected: streamConnected,\n }),\n };\n}\n\nexport type NotifyClient = ReturnType<typeof createNotifyClient>;\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-
|
|
2
|
-
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-
|
|
1
|
+
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-CV5Yt2c6.cjs';
|
|
2
|
+
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-CV5Yt2c6.cjs';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
-
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-
|
|
5
|
-
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-
|
|
4
|
+
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-6S8tpH0n.cjs';
|
|
5
|
+
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-6S8tpH0n.cjs';
|
|
6
6
|
import { E as EmailProvider } from './provider-CA45WVqt.cjs';
|
|
7
7
|
export { a as EmailMessage, P as ProviderSendResult } from './provider-CA45WVqt.cjs';
|
|
8
8
|
import { S as Signals } from './signals-kPmOlKDX.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-
|
|
2
|
-
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-
|
|
1
|
+
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-CV5Yt2c6.js';
|
|
2
|
+
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-CV5Yt2c6.js';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
-
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-
|
|
5
|
-
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-
|
|
4
|
+
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-C4fikLl7.js';
|
|
5
|
+
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-C4fikLl7.js';
|
|
6
6
|
import { E as EmailProvider } from './provider-CA45WVqt.js';
|
|
7
7
|
export { a as EmailMessage, P as ProviderSendResult } from './provider-CA45WVqt.js';
|
|
8
8
|
import { S as Signals } from './signals-kPmOlKDX.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRunner } from './chunk-JKTMUMQK.js';
|
|
2
2
|
export { createRunner, redactErrorMessage } from './chunk-JKTMUMQK.js';
|
|
3
|
+
export { escapeHtml } from './chunk-R3BE67L7.js';
|
|
3
4
|
export { defineClientPlugin, definePlugin } from './chunk-E4IZ2CQC.js';
|
|
4
5
|
import { createHandler, createScopedSigner } from './chunk-JOVIZXO6.js';
|
|
5
6
|
export { DEFAULT_TOKEN_TTL_SECONDS, MAX_BODY_BYTES, MAX_TOKEN_TTL_SECONDS, expiresIn, readJsonBody, signToken, verifyToken } from './chunk-JOVIZXO6.js';
|
|
@@ -18,16 +19,6 @@ function defineNotification(definition) {
|
|
|
18
19
|
return definition;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
// src/core/html.ts
|
|
22
|
-
var ESCAPES = {
|
|
23
|
-
"&": "&",
|
|
24
|
-
"<": "<",
|
|
25
|
-
">": ">",
|
|
26
|
-
'"': """,
|
|
27
|
-
"'": "'"
|
|
28
|
-
};
|
|
29
|
-
var escapeHtml = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ESCAPES[char] ?? char);
|
|
30
|
-
|
|
31
22
|
// src/core/rate-limit.ts
|
|
32
23
|
var clientAddress = (request) => {
|
|
33
24
|
const headers = request.headers;
|
|
@@ -535,6 +526,6 @@ function easyPing(config) {
|
|
|
535
526
|
};
|
|
536
527
|
}
|
|
537
528
|
|
|
538
|
-
export { INBOX_VERSION_HEADER, MIN_SECRET_LENGTH, createRateLimiter, defineNotification, easyPing
|
|
529
|
+
export { INBOX_VERSION_HEADER, MIN_SECRET_LENGTH, createRateLimiter, defineNotification, easyPing };
|
|
539
530
|
//# sourceMappingURL=index.js.map
|
|
540
531
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/core/definition.ts","../src/core/html.ts","../src/core/rate-limit.ts","../src/core/send.ts","../src/core/instance.ts"],"names":["bucket","handler"],"mappings":"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","file":"index.js","sourcesContent":["import type { DatabaseAdapter } from \"./adapter\";\nimport type { NotificationDefinitions, PayloadOf } from \"./definition\";\nimport type { Logger } from \"./errors\";\nimport type { AnyPlugin, Promisable, RouteScope } from \"./plugin\";\nimport type { EmailProvider } from \"./provider\";\nimport type { RateLimitConfig } from \"./rate-limit\";\nimport type { Signals } from \"./signals\";\nimport type { Channel, DeliveryMode, DeliveryRecord, Recipient, SkipReason } from \"./types\";\n\nexport type SessionConfig = {\n /** null yields 401; throwing yields 500. Deliberately distinct. */\n getUserId: (request: Request) => string | null | Promise<string | null>;\n};\n\nexport type DeliveryConfig = {\n mode?: DeliveryMode;\n maxAttempts?: number;\n /** Must exceed the slowest provider timeout, or duplicates are systematic. */\n leaseMs?: number;\n batchSize?: number;\n /** inline mode only. */\n throwOnError?: boolean;\n backoff?: \"exponential\" | ((attempt: number) => number);\n /** Next.js `after` or Cloudflare `ctx.waitUntil`. Injected, not detected. */\n waitUntil?: (promise: Promise<unknown>) => void;\n /**\n * Run one small claim-and-deliver pass after any request the handler serves,\n * at most once per `everyMs` per process. Delivery then rides on your own\n * traffic and the cron becomes a backstop. Ignored in `inline` mode. RFC 0006 idea 5.\n */\n sweepOnRequest?: true | { everyMs?: number; limit?: number };\n};\n\nexport type EventsConfig = {\n /** Comment line keeping proxies from closing an idle stream. Default 25 s. */\n heartbeatMs?: number;\n /**\n * Without a cross-process signal source, each stream checks the database\n * for changes this often on its subscriber's behalf. Default 30 s; 0 disables.\n */\n probeIntervalMs?: number;\n /** Concurrent streams one user may hold in this process before a 429. Default 10. */\n maxStreamsPerUser?: number;\n /** Concurrent streams this process holds in total before a 503. Default 5000. */\n maxStreams?: number;\n /** Close the stream after this long; the client reconnects. 0 (default) leaves it open. */\n maxDurationMs?: number;\n};\n\nexport type ChannelsConfig = {\n inApp?: { enabled: boolean };\n email?: { provider: EmailProvider };\n};\n\nexport type EasyPingConfig<TDefs extends NotificationDefinitions> = {\n database: DatabaseAdapter;\n session: SessionConfig;\n /** Signs unsubscribe and other tokenised links. See RFC 0002 §5. */\n secret: string;\n getRecipients: (userIds: readonly string[]) => Promise<readonly Recipient[]>;\n notifications: TDefs;\n channels: ChannelsConfig;\n delivery?: DeliveryConfig;\n /** Required by `cron` and `deferred` modes. See RFC 0002 §4. */\n cron?: {\n secret: string;\n /** Sweeps one POST /cron may run before returning. Defaults to 50. */\n maxSweeps?: number;\n };\n /** Bearer secret for plugin machine routes. Falls back to `cron.secret`. */\n machineSecret?: string;\n /** In-process fixed-window limiter, applied before routing. */\n rateLimit?: RateLimitConfig;\n /**\n * How \"something changed\" travels between the sender, the worker and open\n * event streams. Defaults to in-process. Supply a LISTEN/NOTIFY or change\n * stream implementation to cross replicas. RFC 0006 §4C.\n */\n signals?: Signals;\n /** `GET /events`, a Server-Sent Events stream per user. `false` unmounts it. */\n events?: EventsConfig | false;\n plugins?: readonly AnyPlugin[];\n /** Where the handler is mounted, used to strip the prefix off incoming URLs. */\n basePath?: string;\n /** Origins other than the request host that may POST. `\"*.example.com\"` matches subdomains. */\n trustedOrigins?: readonly string[];\n /** Runs before routing. Return a Response to short-circuit — a rate limiter's 429, say. */\n // biome-ignore lint/suspicious/noConfusingVoidType: a hook that returns nothing is the common case\n onRequest?: (request: Request) => Promisable<Response | undefined | void>;\n /** Defaults to 64 KiB. */\n maxBodyBytes?: number;\n /** Table-name prefix; must match the one given to the adapter. */\n tablePrefix?: string;\n logger?: Logger;\n};\n\nexport type SendArgs<TDefs extends NotificationDefinitions, TKey extends keyof TDefs> = {\n to: string | readonly string[];\n payload: PayloadOf<TDefs[TKey]>;\n actorId?: string;\n dedupeKey?: string;\n overrides?: { channels?: readonly Channel[] };\n};\n\nexport type SendResult = {\n notifications: readonly {\n id: string;\n userId: string;\n deliveries: readonly { id: string; channel: Channel }[];\n }[];\n skipped: readonly { userId: string; reason: SkipReason }[];\n};\n\nexport type Worker = {\n stop: () => Promise<void>;\n};\n\nexport type MountedRoute = {\n method: \"GET\" | \"POST\";\n path: string;\n scope: RouteScope;\n /** \"core\" for the built-in routes, otherwise the plugin id. */\n owner: string;\n};\n\nexport type HealthReport = {\n mode: DeliveryMode;\n cronMounted: boolean;\n /** True when the chosen mode relies on a cron sweep that is not wired up. */\n cronRequiredButMissing: boolean;\n warnings: readonly string[];\n};\n\n/** Response header carrying a per-user inbox version, so app traffic can replace polling. */\nexport const INBOX_VERSION_HEADER = \"x-easy-ping-inbox\";\n\nexport type EasyPing<TDefs extends NotificationDefinitions> = {\n send<TKey extends keyof TDefs & string>(\n type: TKey,\n args: SendArgs<TDefs, TKey>,\n ): Promise<SendResult>;\n\n handler: {\n /** Method-agnostic entry, for toNodeHandler and single-dispatch frameworks. */\n handle: (request: Request) => Promise<Response>;\n GET: (request: Request) => Promise<Response>;\n POST: (request: Request) => Promise<Response>;\n };\n\n /** Idle interval defaults to 10 s; a send in this process wakes the loop at once. */\n startWorker(options?: { intervalMs?: number; batchSize?: number }): Worker;\n healthCheck(): Promise<HealthReport>;\n\n /** Every mounted route with its auth scope, so `custom`-scoped ones stay visible. RFC 0002 §3. */\n listRoutes(): readonly MountedRoute[];\n\n /**\n * Headers to attach to any response your app sends this user. The client's\n * `instrument(fetch)` reads them and refreshes only when the version moved,\n * so an active user never needs a dedicated poll. RFC 0006 idea 1.\n */\n inboxHeaders(userId: string): Record<string, string>;\n\n /** Deliveries that exhausted their attempts, newest first. Wire it to an admin page or an alert. */\n getFailedDeliveries(options?: {\n /** Defaults to the last 24 hours. */\n since?: Date;\n /** Defaults to 100. */\n limit?: number;\n }): Promise<readonly DeliveryRecord[]>;\n};\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { Channel } from \"./types\";\n\nexport type PayloadFromSchema<TSchema> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : Record<string, unknown>;\n\nexport type EmailTemplate<TPayload> = {\n subject: (payload: TPayload) => string;\n /** Rendered HTML. react-email users wrap: `(p) => render(<Email {...p} />)`. */\n template: (payload: TPayload) => string | Promise<string>;\n};\n\nexport type NotificationDefinition<\n TSchema extends StandardSchemaV1 | undefined = StandardSchemaV1 | undefined,\n> = {\n schema?: TSchema;\n channels: readonly Channel[];\n maxAttempts?: number;\n email?: EmailTemplate<PayloadFromSchema<TSchema>>;\n};\n\n/** Binds TSchema so email callbacks infer the payload; TS cannot infer from a sibling property. */\nexport function defineNotification<TSchema extends StandardSchemaV1 | undefined = undefined>(\n definition: NotificationDefinition<TSchema>,\n): NotificationDefinition<TSchema> {\n return definition;\n}\n\n/** Erased form. `EmailTemplate<never>` because callbacks are contravariant. */\nexport type AnyNotificationDefinition = {\n schema?: StandardSchemaV1 | undefined;\n channels: readonly Channel[];\n maxAttempts?: number | undefined;\n email?: EmailTemplate<never> | undefined;\n};\n\nexport type NotificationDefinitions = Record<string, AnyNotificationDefinition>;\n\nexport type PayloadOf<TDef> = TDef extends { schema: infer TSchema extends StandardSchemaV1 }\n ? StandardSchemaV1.InferOutput<TSchema>\n : Record<string, unknown>;\n","const ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/** For template literals: anything a user typed goes through this, or their HTML ships from your domain. */\nexport const escapeHtml = (value: unknown): string =>\n String(value ?? \"\").replace(/[&<>\"']/g, (char) => ESCAPES[char] ?? char);\n","export type RateLimitConfig = {\n /** Requests allowed per key per window. */\n max: number;\n windowMs: number;\n /** Bucket key. Defaults to the client address from proxy headers; null exempts the request. */\n key?: (request: Request) => string | null;\n};\n\nconst clientAddress = (request: Request): string | null => {\n const headers = request.headers;\n return (\n headers.get(\"cf-connecting-ip\") ??\n headers.get(\"x-real-ip\") ??\n headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ??\n null\n );\n};\n\n/** Fixed window in process memory: per isolate or replica, so a bound rather than a quota. */\nexport function createRateLimiter(config: RateLimitConfig): (request: Request) => Response | null {\n const keyOf = config.key ?? clientAddress;\n const buckets = new Map<string, { count: number; resetAt: number }>();\n let lastSweep = 0;\n\n return (request) => {\n const key = keyOf(request);\n if (key === null) return null;\n\n const now = Date.now();\n\n // Sweep expired buckets at most once per window.\n if (now - lastSweep > config.windowMs) {\n for (const [id, bucket] of buckets) if (bucket.resetAt <= now) buckets.delete(id);\n lastSweep = now;\n }\n\n let bucket = buckets.get(key);\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + config.windowMs };\n buckets.set(key, bucket);\n }\n\n bucket.count += 1;\n if (bucket.count <= config.max) return null;\n\n return new Response(null, {\n status: 429,\n headers: {\n \"retry-after\": String(Math.max(1, Math.ceil((bucket.resetAt - now) / 1000))),\n \"cache-control\": \"private, no-store\",\n },\n });\n };\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { DatabaseAdapter, InsertDelivery, InsertNotification } from \"./adapter\";\nimport type { ChannelsConfig, SendResult } from \"./config\";\nimport type { NotificationDefinitions } from \"./definition\";\nimport { type Logger, ValidationError } from \"./errors\";\nimport type { AnyPlugin, BeforeSendResult, ChannelDecision } from \"./plugin\";\nimport type { Channel, Recipient, SkipReason } from \"./types\";\n\nexport type SendPipelineDeps = {\n adapter: DatabaseAdapter;\n definitions: NotificationDefinitions;\n channels: ChannelsConfig;\n plugins: readonly AnyPlugin[];\n getRecipients: (userIds: readonly string[]) => Promise<readonly Recipient[]>;\n maxAttempts: number;\n logger: Logger;\n generateId?: () => string;\n};\n\nexport type SendInput = {\n to: string | readonly string[];\n payload: unknown;\n actorId?: string | undefined;\n dedupeKey?: string | undefined;\n overrides?: { channels?: readonly Channel[] } | undefined;\n};\n\nasync function validatePayload(type: string, schema: StandardSchemaV1, payload: unknown) {\n const result = await schema[\"~standard\"].validate(payload);\n if (result.issues) {\n throw new ValidationError(\n type,\n result.issues.map((issue) => ({\n message: issue.message,\n ...(issue.path ? { path: issue.path.map(String).join(\".\") } : {}),\n })),\n );\n }\n return result.value;\n}\n\n/** inApp is on unless disabled; every other channel needs a provider or a plugin that delivers it. */\nexport function isChannelUsable(\n channel: Channel,\n channels: ChannelsConfig,\n plugins: readonly AnyPlugin[] = [],\n): boolean {\n if (channel === \"inApp\") return channels.inApp?.enabled !== false;\n if (channel === \"email\") return Boolean(channels.email?.provider);\n // Anything else is usable only if a plugin claims it and can deliver it.\n return plugins.some((plugin) => plugin.channels?.includes(channel) && plugin.hooks?.deliver);\n}\n\n/** One prepare() per plugin per send, keyed by plugin id. */\nasync function runPrepare(\n plugins: readonly AnyPlugin[],\n ctx: { type: string; recipients: readonly Recipient[] },\n logger: Logger,\n): Promise<Map<string, unknown>> {\n const prepared = new Map<string, unknown>();\n\n for (const plugin of plugins) {\n const hook = plugin.hooks?.prepare;\n if (!hook) continue;\n try {\n prepared.set(plugin.id, await hook(ctx));\n } catch (error) {\n // Fail closed: a failed bulk load must not decide with stale assumptions.\n logger.error(`plugin \"${plugin.id}\" prepare threw; its hooks will see undefined`, {\n type: ctx.type,\n error,\n });\n prepared.set(plugin.id, undefined);\n }\n }\n\n return prepared;\n}\n\nasync function runBeforeSend(\n plugins: readonly AnyPlugin[],\n base: { type: string; recipient: Recipient; actorId: string | null },\n payload: unknown,\n dedupeKey: string | null,\n prepared: Map<string, unknown>,\n logger: Logger,\n): Promise<BeforeSendResult> {\n let currentPayload = payload;\n let currentDedupeKey = dedupeKey;\n\n for (const plugin of plugins) {\n const hook = plugin.hooks?.beforeSend;\n if (!hook) continue;\n\n let result: BeforeSendResult;\n try {\n result = await hook({\n ...base,\n payload: currentPayload,\n dedupeKey: currentDedupeKey,\n prepared: prepared.get(plugin.id),\n });\n } catch (error) {\n // Fail closed: failing open would email people who opted out. RFC 0004 §5.\n logger.error(`plugin \"${plugin.id}\" beforeSend threw; cancelling send`, {\n type: base.type,\n userId: base.recipient.userId,\n error,\n });\n return { action: \"cancel\", reason: `plugin \"${plugin.id}\" beforeSend threw` };\n }\n\n if (result.action === \"cancel\") return result;\n if (result.payload !== undefined) currentPayload = result.payload;\n if (result.dedupeKey !== undefined) currentDedupeKey = result.dedupeKey;\n }\n\n return {\n action: \"continue\",\n payload: currentPayload,\n ...(currentDedupeKey === null ? {} : { dedupeKey: currentDedupeKey }),\n };\n}\n\nasync function runResolveChannels(\n plugins: readonly AnyPlugin[],\n base: { type: string; recipient: Recipient; payload: unknown },\n initial: readonly ChannelDecision[],\n prepared: Map<string, unknown>,\n logger: Logger,\n): Promise<readonly ChannelDecision[]> {\n let decisions = initial;\n\n for (const plugin of plugins) {\n const hook = plugin.hooks?.resolveChannels;\n if (!hook) continue;\n\n try {\n decisions = [...(await hook({ ...base, decisions, prepared: prepared.get(plugin.id) }))];\n } catch (error) {\n // Fail closed: keep the decisions already made. RFC 0004 §5.\n logger.error(`plugin \"${plugin.id}\" resolveChannels threw; keeping prior decisions`, {\n type: base.type,\n userId: base.recipient.userId,\n error,\n });\n return decisions;\n }\n }\n\n return decisions;\n}\n\nexport async function runSend(\n type: string,\n input: SendInput,\n deps: SendPipelineDeps,\n): Promise<SendResult> {\n const definition = deps.definitions[type];\n if (!definition) {\n throw new ValidationError(type, [{ message: `unknown notification type \"${type}\"` }]);\n }\n\n const payload = definition.schema\n ? await validatePayload(type, definition.schema, input.payload)\n : input.payload;\n\n const userIds = [...new Set(Array.isArray(input.to) ? input.to : [input.to as string])];\n const recipients = await deps.getRecipients(userIds);\n const byUserId = new Map(recipients.map((recipient) => [recipient.userId, recipient]));\n\n const newId = deps.generateId ?? (() => crypto.randomUUID());\n const skipped: { userId: string; reason: SkipReason }[] = [];\n const rows: InsertNotification[] = [];\n const actorId = input.actorId ?? null;\n\n const declared = input.overrides?.channels ?? definition.channels;\n const requested = declared.filter((channel) =>\n isChannelUsable(channel, deps.channels, deps.plugins),\n );\n\n // Distinct from \"no-channels\": a missing provider is not an opt-out.\n if (declared.length > 0 && requested.length === 0) {\n return {\n notifications: [],\n skipped: userIds.map((userId) => ({ userId, reason: \"channel-unavailable\" as const })),\n };\n }\n\n const prepared = await runPrepare(deps.plugins, { type, recipients }, deps.logger);\n\n for (const userId of userIds) {\n const recipient = byUserId.get(userId);\n if (!recipient) {\n skipped.push({ userId, reason: \"no-recipient\" });\n continue;\n }\n\n const before = await runBeforeSend(\n deps.plugins,\n { type, recipient, actorId },\n payload,\n input.dedupeKey ?? null,\n prepared,\n deps.logger,\n );\n\n if (before.action === \"cancel\") {\n skipped.push({ userId, reason: \"no-channels\" });\n continue;\n }\n\n const resolved = await runResolveChannels(\n deps.plugins,\n { type, recipient, payload: before.payload ?? payload },\n requested.map((channel) => ({ channel, action: \"send\" as const })),\n prepared,\n deps.logger,\n );\n\n const now = new Date();\n const deliveries: InsertDelivery[] = resolved\n .filter((decision) => decision.action !== \"skip\")\n .filter((decision) => isChannelUsable(decision.channel, deps.channels, deps.plugins))\n .map((decision) => ({\n id: newId(),\n channel: decision.channel,\n maxAttempts: definition.maxAttempts ?? deps.maxAttempts,\n // defer is just not_before; the claim query refuses rows whose time has not come. RFC 0004 §4.2.\n notBefore: decision.action === \"defer\" && decision.notBefore ? decision.notBefore : now,\n }));\n\n if (deliveries.length === 0) {\n skipped.push({ userId, reason: \"no-channels\" });\n continue;\n }\n\n const dedupeKey = before.action === \"continue\" ? before.dedupeKey : undefined;\n\n rows.push({\n id: newId(),\n userId,\n type,\n payload: before.payload ?? payload,\n ...(actorId ? { actorId } : {}),\n ...(dedupeKey ? { dedupeKey } : {}),\n deliveries,\n });\n }\n\n if (rows.length === 0) return { notifications: [], skipped };\n\n const { created, deduped } = await deps.adapter.createNotifications(rows);\n const createdSet = new Set(created);\n\n for (const row of rows) {\n if (deduped.includes(row.id)) skipped.push({ userId: row.userId, reason: \"deduped\" });\n }\n\n const notifications = rows\n .filter((row) => createdSet.has(row.id))\n .map((row) => ({\n id: row.id,\n userId: row.userId,\n deliveries: row.deliveries.map((delivery) => ({\n id: delivery.id,\n channel: delivery.channel,\n })),\n }));\n\n for (const plugin of deps.plugins) {\n const hook = plugin.hooks?.afterSend;\n if (!hook) continue;\n try {\n await hook({\n type,\n payload,\n notifications: notifications.map(({ id, userId }) => ({ id, userId })),\n prepared: prepared.get(plugin.id),\n });\n } catch (error) {\n // Fails open: the rows are committed.\n deps.logger.error(`plugin \"${plugin.id}\" afterSend threw; ignoring`, { type, error });\n }\n }\n\n return { notifications, skipped };\n}\n","import type { Backoff } from \"./backoff\";\nimport {\n type EasyPing,\n type EasyPingConfig,\n type HealthReport,\n INBOX_VERSION_HEADER,\n type MountedRoute,\n type SendArgs,\n type SendResult,\n} from \"./config\";\nimport type { NotificationDefinitions } from \"./definition\";\nimport { ConfigError, consoleLogger } from \"./errors\";\nimport { createHandler } from \"./handler\";\nimport type { AnyPlugin } from \"./plugin\";\nimport { createRateLimiter } from \"./rate-limit\";\nimport { createRunner } from \"./runner\";\nimport { isChannelUsable, runSend } from \"./send\";\nimport { createMemorySignals, DELIVERIES_CHANNEL, inboxChannel, type Signals } from \"./signals\";\nimport { createPluginStore } from \"./store\";\nimport { createScopedSigner } from \"./tokens\";\nimport type { DeliveryMode } from \"./types\";\n\nconst CORE_ROUTES: readonly MountedRoute[] = [\n { method: \"GET\", path: \"/\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"GET\", path: \"/count\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/seen\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/read\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/read-all\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"GET\", path: \"/events\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/cron\", scope: { type: \"machine\" }, owner: \"core\" },\n];\n\nconst DEFAULTS = {\n maxAttempts: 5,\n leaseMs: 60_000,\n batchSize: 20,\n basePath: \"/api/notifications\",\n mode: \"cron\" as DeliveryMode,\n};\n\nexport const MIN_SECRET_LENGTH = 16;\n\nconst PLACEHOLDER_SECRETS = new Set([\n \"secret\",\n \"changeme\",\n \"change-me\",\n \"replace-me\",\n \"replace-me-too\",\n \"password\",\n \"demo-signing-secret\",\n \"demo-cron-secret\",\n]);\n\nfunction checkSecretStrength(name: string, value: string) {\n if (value.length < MIN_SECRET_LENGTH || PLACEHOLDER_SECRETS.has(value.toLowerCase())) {\n throw new ConfigError(\n `\\`${name}\\` must be at least ${MIN_SECRET_LENGTH} characters and not a placeholder. ` +\n \"Generate one with `openssl rand -base64 32`.\",\n );\n }\n}\n\nfunction validate(config: EasyPingConfig<NotificationDefinitions>, plugins: readonly AnyPlugin[]) {\n if (!config.database) throw new ConfigError(\"`database` is required.\");\n if (!config.notifications) throw new ConfigError(\"`notifications` is required.\");\n\n // No default resolver and no dev bypass: an insecure default ships, a crash does not.\n if (typeof config.session?.getUserId !== \"function\") {\n throw new ConfigError(\n \"`session.getUserId` is required — the mounted endpoints serve a user's private inbox \" +\n \"and must resolve the caller. See RFC 0002.\",\n );\n }\n\n if (!config.secret) {\n throw new ConfigError(\n \"`secret` is required — it signs unsubscribe and other session-less links. \" +\n \"Adding it later is a breaking change, so it is mandatory from the start.\",\n );\n }\n checkSecretStrength(\"secret\", config.secret);\n if (config.cron?.secret) checkSecretStrength(\"cron.secret\", config.cron.secret);\n if (config.machineSecret) checkSecretStrength(\"machineSecret\", config.machineSecret);\n\n if (config.rateLimit && (config.rateLimit.max < 1 || config.rateLimit.windowMs < 1)) {\n throw new ConfigError(\"`rateLimit.max` and `rateLimit.windowMs` must both be positive.\");\n }\n\n if (typeof config.getRecipients !== \"function\") {\n throw new ConfigError(\"`getRecipients` is required to resolve emails and timezones.\");\n }\n\n const seen = new Set<string>();\n for (const plugin of plugins) {\n if (seen.has(plugin.id)) {\n throw new ConfigError(`duplicate plugin id \"${plugin.id}\" — ids must be unique.`);\n }\n seen.add(plugin.id);\n }\n\n for (const plugin of plugins) {\n for (const dependency of plugin.dependsOn ?? []) {\n if (!seen.has(dependency)) {\n throw new ConfigError(\n `plugin \"${plugin.id}\" requires \"${dependency}\", which is not registered.`,\n );\n }\n }\n }\n\n const routes = new Set<string>();\n for (const plugin of plugins) {\n for (const route of plugin.routes ?? []) {\n const signature = `${route.method} ${route.path}`;\n if (routes.has(signature)) {\n throw new ConfigError(`route collision: two plugins both registered \"${signature}\".`);\n }\n routes.add(signature);\n }\n }\n\n const mode = config.delivery?.mode ?? DEFAULTS.mode;\n if ((mode === \"cron\" || mode === \"deferred\") && !config.cron?.secret) {\n throw new ConfigError(\n `delivery.mode \"${mode}\" relies on the cron sweep, so \\`cron.secret\\` is required. ` +\n \"Left unset the endpoint would be unauthenticated — a free flush-everything trigger \" +\n \"against your email provider. See RFC 0002 §4.\",\n );\n }\n}\n\nexport function easyPing<TDefs extends NotificationDefinitions>(\n config: EasyPingConfig<TDefs>,\n): EasyPing<TDefs> {\n const plugins = config.plugins ?? [];\n validate(config as EasyPingConfig<NotificationDefinitions>, plugins);\n\n const logger = config.logger ?? consoleLogger;\n const tablePrefix = config.tablePrefix ?? \"\";\n const mode = config.delivery?.mode ?? DEFAULTS.mode;\n const leaseMs = config.delivery?.leaseMs ?? DEFAULTS.leaseMs;\n const warnings: string[] = [];\n\n // Below this ratio a slow send is re-claimed mid-flight. RFC 0003 §7.\n const providerTimeout = config.channels.email?.provider?.timeoutMs;\n if (providerTimeout !== undefined && providerTimeout >= leaseMs / 2) {\n warnings.push(\n `email provider timeout (${providerTimeout}ms) is at least half of delivery.leaseMs ` +\n `(${leaseMs}ms); raise leaseMs or duplicate sends become systematic.`,\n );\n }\n\n const unusable = new Map<string, string[]>();\n for (const [type, definition] of Object.entries(config.notifications)) {\n const missing = definition.channels.filter(\n (channel) => !isChannelUsable(channel, config.channels, plugins),\n );\n if (missing.length > 0) unusable.set(type, [...missing]);\n }\n\n for (const [type, channels] of unusable) {\n const dead = channels.length === config.notifications[type]?.channels.length;\n warnings.push(\n `notification \"${type}\" declares ${channels.map((c) => `\"${c}\"`).join(\", \")} ` +\n `but no provider is configured for ${channels.length > 1 ? \"them\" : \"it\"}` +\n (dead ? \" — this type can never be delivered.\" : \"; those channels will be skipped.\"),\n );\n }\n\n if (mode === \"deferred\" && !config.delivery?.waitUntil) {\n warnings.push(\n \"delivery.mode is 'deferred' but no delivery.waitUntil was supplied; \" +\n \"delivery will wait for the cron sweep.\",\n );\n }\n\n // Custom-scoped routes are named at startup with their justification. RFC 0002 §3.\n for (const plugin of plugins) {\n for (const route of plugin.routes ?? []) {\n if (route.scope.type === \"custom\") {\n warnings.push(\n `plugin \"${plugin.id}\" mounts ${route.method} ${route.path} with custom auth: ` +\n route.scope.justification,\n );\n }\n }\n }\n\n for (const warning of warnings) logger.warn(warning);\n\n const baseSignals = config.signals ?? createMemorySignals();\n\n // Per-user inbox versions for the piggyback header. In-process counters, so\n // another replica's number differs; the client treats any change as \"look\",\n // which costs a spurious refresh, never a missed one.\n const inboxVersions = new Map<string, number>();\n const signals: Signals = {\n crossProcess: baseSignals.crossProcess,\n publish(channel) {\n if (channel.startsWith(\"inbox:\")) {\n if (inboxVersions.size > 50_000) inboxVersions.clear();\n const userId = channel.slice(\"inbox:\".length);\n inboxVersions.set(userId, (inboxVersions.get(userId) ?? 0) + 1);\n }\n baseSignals.publish(channel);\n },\n subscribe: (channel, handler) => baseSignals.subscribe(channel, handler),\n };\n\n const runner = createRunner({\n adapter: config.database,\n definitions: config.notifications,\n channels: config.channels,\n plugins,\n getRecipients: config.getRecipients,\n logger,\n leaseMs,\n batchSize: config.delivery?.batchSize ?? DEFAULTS.batchSize,\n backoff: (config.delivery?.backoff ?? \"exponential\") as Backoff,\n signals,\n });\n\n const pluginRoutes = plugins.flatMap((plugin) => plugin.routes ?? []);\n\n const events =\n config.events === false\n ? false\n : {\n heartbeatMs: config.events?.heartbeatMs ?? 25_000,\n probeIntervalMs: config.events?.probeIntervalMs ?? 30_000,\n maxDurationMs: config.events?.maxDurationMs ?? 0,\n maxStreamsPerUser: config.events?.maxStreamsPerUser ?? 10,\n maxStreams: config.events?.maxStreams ?? 5_000,\n };\n\n const sweepConfig = config.delivery?.sweepOnRequest;\n let lastRequestSweep = 0;\n const sweepOnRequest =\n sweepConfig && mode !== \"inline\"\n ? () => {\n const settings = sweepConfig === true ? {} : sweepConfig;\n const now = Date.now();\n if (now - lastRequestSweep < (settings.everyMs ?? 5_000)) return;\n lastRequestSweep = now;\n const pass = runner\n .runOnce({ limit: settings.limit ?? 5 })\n .catch((error) => logger.error(\"request-driven sweep failed\", { error }));\n config.delivery?.waitUntil?.(pass);\n }\n : undefined;\n\n const handler = createHandler({\n adapter: config.database,\n session: config.session,\n runner,\n logger,\n secret: config.secret,\n signals,\n events,\n sweepOnRequest,\n basePath: config.basePath ?? DEFAULTS.basePath,\n pluginRoutes,\n trustedOrigins: config.trustedOrigins,\n onRequest: config.onRequest,\n maxBodyBytes: config.maxBodyBytes,\n rateLimit: config.rateLimit ? createRateLimiter(config.rateLimit) : undefined,\n cronMaxSweeps: config.cron?.maxSweeps,\n ...(config.cron?.secret ? { cronSecret: config.cron.secret } : {}),\n ...((config.machineSecret ?? config.cron?.secret)\n ? { machineSecret: config.machineSecret ?? config.cron?.secret }\n : {}),\n });\n\n async function send<TKey extends keyof TDefs & string>(\n type: TKey,\n args: SendArgs<TDefs, TKey>,\n ): Promise<SendResult> {\n const result = await runSend(\n type,\n {\n to: args.to,\n payload: args.payload,\n actorId: args.actorId,\n dedupeKey: args.dedupeKey,\n overrides: args.overrides,\n },\n {\n adapter: config.database,\n definitions: config.notifications,\n channels: config.channels,\n plugins,\n getRecipients: config.getRecipients,\n maxAttempts: config.delivery?.maxAttempts ?? DEFAULTS.maxAttempts,\n logger,\n },\n );\n\n // Rows are committed; everything below only affects latency. RFC 0001 §8.1.\n if (result.notifications.length > 0) {\n for (const notification of result.notifications) {\n signals.publish(inboxChannel(notification.userId));\n }\n signals.publish(DELIVERIES_CHANNEL);\n\n // Scoped to this send, or an inline send flushes the whole backlog.\n const ids = result.notifications.flatMap((notification) =>\n notification.deliveries.map((delivery) => delivery.id),\n );\n\n if (mode === \"inline\") await runner.drain({ ids });\n else if (mode === \"deferred\") runner.scheduleDeferred(config.delivery?.waitUntil, ids);\n }\n\n return result;\n }\n\n // After send() exists, before any hook or route can run.\n for (const plugin of plugins) {\n const purposes = new Set(\n (plugin.routes ?? []).flatMap((route) =>\n route.scope.type === \"signed\" ? [route.scope.purpose] : [],\n ),\n );\n plugin.init?.({\n store: createPluginStore(plugin.id, plugin.schema, config.database, tablePrefix),\n sign: createScopedSigner(\n config.secret,\n plugin.id,\n purposes,\n (message) => new ConfigError(message),\n ),\n logger,\n notificationTypes: Object.keys(config.notifications),\n getRecipients: config.getRecipients,\n send: (type, args) => send(type as keyof TDefs & string, args as never) as Promise<unknown>,\n });\n }\n\n return {\n send,\n handler,\n\n startWorker: (options) => runner.startWorker(options ?? {}),\n\n inboxHeaders: (userId) => ({\n [INBOX_VERSION_HEADER]: String(inboxVersions.get(userId) ?? 0),\n }),\n\n listRoutes: () => [\n ...CORE_ROUTES.filter(\n (route) =>\n (route.path !== \"/cron\" || Boolean(config.cron?.secret)) &&\n (route.path !== \"/events\" || events !== false),\n ),\n ...plugins.flatMap((plugin) =>\n (plugin.routes ?? []).map((route) => ({\n method: route.method,\n path: route.path,\n scope: route.scope,\n owner: plugin.id,\n })),\n ),\n ],\n\n getFailedDeliveries: (options = {}) =>\n config.database.getFailedDeliveries({\n since: options.since ?? new Date(Date.now() - 24 * 3600_000),\n limit: Math.min(options.limit ?? 100, 1000),\n }),\n\n healthCheck: async (): Promise<HealthReport> => {\n const cronMounted = Boolean(config.cron?.secret);\n return {\n mode,\n cronMounted,\n cronRequiredButMissing:\n !cronMounted && (mode === \"cron\" || mode === \"deferred\" || mode === \"inline\"),\n warnings,\n };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/definition.ts","../src/core/rate-limit.ts","../src/core/send.ts","../src/core/instance.ts"],"names":["bucket","handler"],"mappings":";;;;;;;;;;;;;;AAsIO,IAAM,oBAAA,GAAuB;;;AC/G7B,SAAS,mBACd,UAAA,EACiC;AACjC,EAAA,OAAO,UAAA;AACT;;;ACnBA,IAAM,aAAA,GAAgB,CAAC,OAAA,KAAoC;AACzD,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,OACE,QAAQ,GAAA,CAAI,kBAAkB,KAC9B,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,IACvB,OAAA,CAAQ,GAAA,CAAI,iBAAiB,GAAG,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA,EAAG,MAAK,IACpD,IAAA;AAEJ,CAAA;AAGO,SAAS,kBAAkB,MAAA,EAAgE;AAChG,EAAA,MAAM,KAAA,GAAQ,OAAO,GAAA,IAAO,aAAA;AAC5B,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAgD;AACpE,EAAA,IAAI,SAAA,GAAY,CAAA;AAEhB,EAAA,OAAO,CAAC,OAAA,KAAY;AAClB,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,CAAA;AACzB,IAAA,IAAI,GAAA,KAAQ,MAAM,OAAO,IAAA;AAEzB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAGrB,IAAA,IAAI,GAAA,GAAM,SAAA,GAAY,MAAA,CAAO,QAAA,EAAU;AACrC,MAAA,KAAA,MAAW,CAAC,EAAA,EAAIA,OAAM,CAAA,IAAK,OAAA,EAAS,IAAIA,OAAAA,CAAO,OAAA,IAAW,GAAA,EAAK,OAAA,CAAQ,MAAA,CAAO,EAAE,CAAA;AAChF,MAAA,SAAA,GAAY,GAAA;AAAA,IACd;AAEA,IAAA,IAAI,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAC5B,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,OAAA,IAAW,GAAA,EAAK;AACpC,MAAA,MAAA,GAAS,EAAE,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,GAAA,GAAM,OAAO,QAAA,EAAS;AACpD,MAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,IACzB;AAEA,IAAA,MAAA,CAAO,KAAA,IAAS,CAAA;AAChB,IAAA,IAAI,MAAA,CAAO,KAAA,IAAS,MAAA,CAAO,GAAA,EAAK,OAAO,IAAA;AAEvC,IAAA,OAAO,IAAI,SAAS,IAAA,EAAM;AAAA,MACxB,MAAA,EAAQ,GAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,aAAA,EAAe,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAA,CAAM,MAAA,CAAO,OAAA,GAAU,GAAA,IAAO,GAAI,CAAC,CAAC,CAAA;AAAA,QAC3E,eAAA,EAAiB;AAAA;AACnB,KACD,CAAA;AAAA,EACH,CAAA;AACF;;;AC1BA,eAAe,eAAA,CAAgB,IAAA,EAAc,MAAA,EAA0B,OAAA,EAAkB;AACvF,EAAA,MAAM,SAAS,MAAM,MAAA,CAAO,WAAW,CAAA,CAAE,SAAS,OAAO,CAAA;AACzD,EAAA,IAAI,OAAO,MAAA,EAAQ;AACjB,IAAA,MAAM,IAAI,eAAA;AAAA,MACR,IAAA;AAAA,MACA,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,QAC5B,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,GAAI,KAAA,CAAM,IAAA,GAAO,EAAE,MAAM,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,KAAM;AAAC,OACjE,CAAE;AAAA,KACJ;AAAA,EACF;AACA,EAAA,OAAO,MAAA,CAAO,KAAA;AAChB;AAGO,SAAS,eAAA,CACd,OAAA,EACA,QAAA,EACA,OAAA,GAAgC,EAAC,EACxB;AACT,EAAA,IAAI,OAAA,KAAY,OAAA,EAAS,OAAO,QAAA,CAAS,OAAO,OAAA,KAAY,KAAA;AAC5D,EAAA,IAAI,YAAY,OAAA,EAAS,OAAO,OAAA,CAAQ,QAAA,CAAS,OAAO,QAAQ,CAAA;AAEhE,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,CAAC,MAAA,KAAW,MAAA,CAAO,QAAA,EAAU,QAAA,CAAS,OAAO,CAAA,IAAK,MAAA,CAAO,KAAA,EAAO,OAAO,CAAA;AAC7F;AAGA,eAAe,UAAA,CACb,OAAA,EACA,GAAA,EACA,MAAA,EAC+B;AAC/B,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAqB;AAE1C,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,IAAA,GAAO,OAAO,KAAA,EAAO,OAAA;AAC3B,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,IAAI;AACF,MAAA,QAAA,CAAS,IAAI,MAAA,CAAO,EAAA,EAAI,MAAM,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,IACzC,SAAS,KAAA,EAAO;AAEd,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,6CAAA,CAAA,EAAiD;AAAA,QAChF,MAAM,GAAA,CAAI,IAAA;AAAA,QACV;AAAA,OACD,CAAA;AACD,MAAA,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,EAAA,EAAI,MAAS,CAAA;AAAA,IACnC;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;AAEA,eAAe,cACb,OAAA,EACA,IAAA,EACA,OAAA,EACA,SAAA,EACA,UACA,MAAA,EAC2B;AAC3B,EAAA,IAAI,cAAA,GAAiB,OAAA;AACrB,EAAA,IAAI,gBAAA,GAAmB,SAAA;AAEvB,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,IAAA,GAAO,OAAO,KAAA,EAAO,UAAA;AAC3B,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAA,GAAS,MAAM,IAAA,CAAK;AAAA,QAClB,GAAG,IAAA;AAAA,QACH,OAAA,EAAS,cAAA;AAAA,QACT,SAAA,EAAW,gBAAA;AAAA,QACX,QAAA,EAAU,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,EAAE;AAAA,OACjC,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AAEd,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,mCAAA,CAAA,EAAuC;AAAA,QACtE,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,MAAA,EAAQ,KAAK,SAAA,CAAU,MAAA;AAAA,QACvB;AAAA,OACD,CAAA;AACD,MAAA,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAQ,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,kBAAA,CAAA,EAAqB;AAAA,IAC9E;AAEA,IAAA,IAAI,MAAA,CAAO,MAAA,KAAW,QAAA,EAAU,OAAO,MAAA;AACvC,IAAA,IAAI,MAAA,CAAO,OAAA,KAAY,MAAA,EAAW,cAAA,GAAiB,MAAA,CAAO,OAAA;AAC1D,IAAA,IAAI,MAAA,CAAO,SAAA,KAAc,MAAA,EAAW,gBAAA,GAAmB,MAAA,CAAO,SAAA;AAAA,EAChE;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,UAAA;AAAA,IACR,OAAA,EAAS,cAAA;AAAA,IACT,GAAI,gBAAA,KAAqB,IAAA,GAAO,EAAC,GAAI,EAAE,WAAW,gBAAA;AAAiB,GACrE;AACF;AAEA,eAAe,kBAAA,CACb,OAAA,EACA,IAAA,EACA,OAAA,EACA,UACA,MAAA,EACqC;AACrC,EAAA,IAAI,SAAA,GAAY,OAAA;AAEhB,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,IAAA,GAAO,OAAO,KAAA,EAAO,eAAA;AAC3B,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,IAAI;AACF,MAAA,SAAA,GAAY,CAAC,GAAI,MAAM,IAAA,CAAK,EAAE,GAAG,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,SAAS,GAAA,CAAI,MAAA,CAAO,EAAE,CAAA,EAAG,CAAE,CAAA;AAAA,IACzF,SAAS,KAAA,EAAO;AAEd,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,gDAAA,CAAA,EAAoD;AAAA,QACnF,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,MAAA,EAAQ,KAAK,SAAA,CAAU,MAAA;AAAA,QACvB;AAAA,OACD,CAAA;AACD,MAAA,OAAO,SAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,SAAA;AACT;AAEA,eAAsB,OAAA,CACpB,IAAA,EACA,KAAA,EACA,IAAA,EACqB;AACrB,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,WAAA,CAAY,IAAI,CAAA;AACxC,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,MAAM,IAAI,eAAA,CAAgB,IAAA,EAAM,CAAC,EAAE,SAAS,CAAA,2BAAA,EAA8B,IAAI,CAAA,CAAA,CAAA,EAAK,CAAC,CAAA;AAAA,EACtF;AAEA,EAAA,MAAM,OAAA,GAAU,UAAA,CAAW,MAAA,GACvB,MAAM,eAAA,CAAgB,IAAA,EAAM,UAAA,CAAW,MAAA,EAAQ,KAAA,CAAM,OAAO,CAAA,GAC5D,KAAA,CAAM,OAAA;AAEV,EAAA,MAAM,UAAU,CAAC,GAAG,IAAI,GAAA,CAAI,MAAM,OAAA,CAAQ,KAAA,CAAM,EAAE,CAAA,GAAI,MAAM,EAAA,GAAK,CAAC,KAAA,CAAM,EAAY,CAAC,CAAC,CAAA;AACtF,EAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,aAAA,CAAc,OAAO,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,SAAA,KAAc,CAAC,SAAA,CAAU,MAAA,EAAQ,SAAS,CAAC,CAAC,CAAA;AAErF,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,KAAe,MAAM,OAAO,UAAA,EAAW,CAAA;AAC1D,EAAA,MAAM,UAAoD,EAAC;AAC3D,EAAA,MAAM,OAA6B,EAAC;AACpC,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,IAAA;AAEjC,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,SAAA,EAAW,QAAA,IAAY,UAAA,CAAW,QAAA;AACzD,EAAA,MAAM,YAAY,QAAA,CAAS,MAAA;AAAA,IAAO,CAAC,OAAA,KACjC,eAAA,CAAgB,SAAS,IAAA,CAAK,QAAA,EAAU,KAAK,OAAO;AAAA,GACtD;AAGA,EAAA,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,IAAK,SAAA,CAAU,WAAW,CAAA,EAAG;AACjD,IAAA,OAAO;AAAA,MACL,eAAe,EAAC;AAAA,MAChB,OAAA,EAAS,QAAQ,GAAA,CAAI,CAAC,YAAY,EAAE,MAAA,EAAQ,MAAA,EAAQ,qBAAA,EAA+B,CAAE;AAAA,KACvF;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,IAAA,CAAK,OAAA,EAAS,EAAE,IAAA,EAAM,UAAA,EAAW,EAAG,IAAA,CAAK,MAAM,CAAA;AAEjF,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,SAAA,GAAY,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA;AACrC,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,gBAAgB,CAAA;AAC/C,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAS,MAAM,aAAA;AAAA,MACnB,IAAA,CAAK,OAAA;AAAA,MACL,EAAE,IAAA,EAAM,SAAA,EAAW,OAAA,EAAQ;AAAA,MAC3B,OAAA;AAAA,MACA,MAAM,SAAA,IAAa,IAAA;AAAA,MACnB,QAAA;AAAA,MACA,IAAA,CAAK;AAAA,KACP;AAEA,IAAA,IAAI,MAAA,CAAO,WAAW,QAAA,EAAU;AAC9B,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,eAAe,CAAA;AAC9C,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,WAAW,MAAM,kBAAA;AAAA,MACrB,IAAA,CAAK,OAAA;AAAA,MACL,EAAE,IAAA,EAAM,SAAA,EAAW,OAAA,EAAS,MAAA,CAAO,WAAW,OAAA,EAAQ;AAAA,MACtD,SAAA,CAAU,IAAI,CAAC,OAAA,MAAa,EAAE,OAAA,EAAS,MAAA,EAAQ,QAAgB,CAAE,CAAA;AAAA,MACjE,QAAA;AAAA,MACA,IAAA,CAAK;AAAA,KACP;AAEA,IAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AACrB,IAAA,MAAM,UAAA,GAA+B,SAClC,MAAA,CAAO,CAAC,aAAa,QAAA,CAAS,MAAA,KAAW,MAAM,CAAA,CAC/C,MAAA,CAAO,CAAC,aAAa,eAAA,CAAgB,QAAA,CAAS,OAAA,EAAS,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,OAAO,CAAC,CAAA,CACnF,GAAA,CAAI,CAAC,QAAA,MAAc;AAAA,MAClB,IAAI,KAAA,EAAM;AAAA,MACV,SAAS,QAAA,CAAS,OAAA;AAAA,MAClB,WAAA,EAAa,UAAA,CAAW,WAAA,IAAe,IAAA,CAAK,WAAA;AAAA;AAAA,MAE5C,WAAW,QAAA,CAAS,MAAA,KAAW,WAAW,QAAA,CAAS,SAAA,GAAY,SAAS,SAAA,GAAY;AAAA,KACtF,CAAE,CAAA;AAEJ,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,eAAe,CAAA;AAC9C,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,MAAA,KAAW,UAAA,GAAa,OAAO,SAAA,GAAY,MAAA;AAEpE,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,IAAI,KAAA,EAAM;AAAA,MACV,MAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAA,EAAS,OAAO,OAAA,IAAW,OAAA;AAAA,MAC3B,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY,EAAC;AAAA,MAC7B,GAAI,SAAA,GAAY,EAAE,SAAA,KAAc,EAAC;AAAA,MACjC;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG,OAAO,EAAE,aAAA,EAAe,IAAI,OAAA,EAAQ;AAE3D,EAAA,MAAM,EAAE,SAAS,OAAA,EAAQ,GAAI,MAAM,IAAA,CAAK,OAAA,CAAQ,oBAAoB,IAAI,CAAA;AACxE,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,OAAO,CAAA;AAElC,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA,EAAG,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ,MAAA,EAAQ,WAAW,CAAA;AAAA,EACtF;AAEA,EAAA,MAAM,aAAA,GAAgB,IAAA,CACnB,MAAA,CAAO,CAAC,GAAA,KAAQ,UAAA,CAAW,GAAA,CAAI,GAAA,CAAI,EAAE,CAAC,CAAA,CACtC,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,IACb,IAAI,GAAA,CAAI,EAAA;AAAA,IACR,QAAQ,GAAA,CAAI,MAAA;AAAA,IACZ,UAAA,EAAY,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,QAAA,MAAc;AAAA,MAC5C,IAAI,QAAA,CAAS,EAAA;AAAA,MACb,SAAS,QAAA,CAAS;AAAA,KACpB,CAAE;AAAA,GACJ,CAAE,CAAA;AAEJ,EAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,IAAA,MAAM,IAAA,GAAO,OAAO,KAAA,EAAO,SAAA;AAC3B,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK;AAAA,QACT,IAAA;AAAA,QACA,OAAA;AAAA,QACA,aAAA,EAAe,aAAA,CAAc,GAAA,CAAI,CAAC,EAAE,EAAA,EAAI,MAAA,EAAO,MAAO,EAAE,EAAA,EAAI,MAAA,EAAO,CAAE,CAAA;AAAA,QACrE,QAAA,EAAU,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,EAAE;AAAA,OACjC,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AAEd,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,2BAAA,CAAA,EAA+B,EAAE,IAAA,EAAM,KAAA,EAAO,CAAA;AAAA,IACtF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,eAAe,OAAA,EAAQ;AAClC;;;ACzQA,IAAM,WAAA,GAAuC;AAAA,EAC3C,EAAE,MAAA,EAAQ,KAAA,EAAO,IAAA,EAAM,GAAA,EAAK,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,KAAA,EAAO,MAAA,EAAO;AAAA,EACnE,EAAE,MAAA,EAAQ,KAAA,EAAO,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,KAAA,EAAO,MAAA,EAAO;AAAA,EACxE,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,KAAA,EAAO,MAAA,EAAO;AAAA,EACxE,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,KAAA,EAAO,MAAA,EAAO;AAAA,EACxE,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,EAAM,WAAA,EAAa,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,KAAA,EAAO,MAAA,EAAO;AAAA,EAC5E,EAAE,MAAA,EAAQ,KAAA,EAAO,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,KAAA,EAAO,MAAA,EAAO;AAAA,EACzE,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,EAAE,IAAA,EAAM,SAAA,EAAU,EAAG,KAAA,EAAO,MAAA;AACtE,CAAA;AAEA,IAAM,QAAA,GAAW;AAAA,EACf,WAAA,EAAa,CAAA;AAAA,EACb,OAAA,EAAS,GAAA;AAAA,EACT,SAAA,EAAW,EAAA;AAAA,EACX,QAAA,EAAU,oBAAA;AAAA,EACV,IAAA,EAAM;AACR,CAAA;AAEO,IAAM,iBAAA,GAAoB;AAEjC,IAAM,mBAAA,uBAA0B,GAAA,CAAI;AAAA,EAClC,QAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAED,SAAS,mBAAA,CAAoB,MAAc,KAAA,EAAe;AACxD,EAAA,IAAI,KAAA,CAAM,SAAS,iBAAA,IAAqB,mBAAA,CAAoB,IAAI,KAAA,CAAM,WAAA,EAAa,CAAA,EAAG;AACpF,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,CAAA,EAAA,EAAK,IAAI,CAAA,oBAAA,EAAuB,iBAAiB,CAAA,iFAAA;AAAA,KAEnD;AAAA,EACF;AACF;AAEA,SAAS,QAAA,CAAS,QAAiD,OAAA,EAA+B;AAChG,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,EAAU,MAAM,IAAI,YAAY,yBAAyB,CAAA;AACrE,EAAA,IAAI,CAAC,MAAA,CAAO,aAAA,EAAe,MAAM,IAAI,YAAY,8BAA8B,CAAA;AAG/E,EAAA,IAAI,OAAO,MAAA,CAAO,OAAA,EAAS,SAAA,KAAc,UAAA,EAAY;AACnD,IAAA,MAAM,IAAI,WAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,IAAA,MAAM,IAAI,WAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,mBAAA,CAAoB,QAAA,EAAU,OAAO,MAAM,CAAA;AAC3C,EAAA,IAAI,OAAO,IAAA,EAAM,MAAA,sBAA4B,aAAA,EAAe,MAAA,CAAO,KAAK,MAAM,CAAA;AAC9E,EAAA,IAAI,MAAA,CAAO,aAAA,EAAe,mBAAA,CAAoB,eAAA,EAAiB,OAAO,aAAa,CAAA;AAEnF,EAAA,IAAI,MAAA,CAAO,cAAc,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA,IAAK,MAAA,CAAO,SAAA,CAAU,QAAA,GAAW,CAAA,CAAA,EAAI;AACnF,IAAA,MAAM,IAAI,YAAY,iEAAiE,CAAA;AAAA,EACzF;AAEA,EAAA,IAAI,OAAO,MAAA,CAAO,aAAA,KAAkB,UAAA,EAAY;AAC9C,IAAA,MAAM,IAAI,YAAY,8DAA8D,CAAA;AAAA,EACtF;AAEA,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,EAAE,CAAA,EAAG;AACvB,MAAA,MAAM,IAAI,WAAA,CAAY,CAAA,qBAAA,EAAwB,MAAA,CAAO,EAAE,CAAA,4BAAA,CAAyB,CAAA;AAAA,IAClF;AACA,IAAA,IAAA,CAAK,GAAA,CAAI,OAAO,EAAE,CAAA;AAAA,EACpB;AAEA,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,KAAA,MAAW,UAAA,IAAc,MAAA,CAAO,SAAA,IAAa,EAAC,EAAG;AAC/C,MAAA,IAAI,CAAC,IAAA,CAAK,GAAA,CAAI,UAAU,CAAA,EAAG;AACzB,QAAA,MAAM,IAAI,WAAA;AAAA,UACR,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,YAAA,EAAe,UAAU,CAAA,2BAAA;AAAA,SAC/C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAY;AAC/B,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,IAAU,EAAC,EAAG;AACvC,MAAA,MAAM,YAAY,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,MAAM,IAAI,CAAA,CAAA;AAC/C,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,SAAS,CAAA,EAAG;AACzB,QAAA,MAAM,IAAI,WAAA,CAAY,CAAA,8CAAA,EAAiD,SAAS,CAAA,EAAA,CAAI,CAAA;AAAA,MACtF;AACA,MAAA,MAAA,CAAO,IAAI,SAAS,CAAA;AAAA,IACtB;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,QAAA,EAAU,IAAA,IAAQ,QAAA,CAAS,IAAA;AAC/C,EAAA,IAAA,CAAK,SAAS,MAAA,IAAU,IAAA,KAAS,eAAe,CAAC,MAAA,CAAO,MAAM,MAAA,EAAQ;AACpE,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,kBAAkB,IAAI,CAAA,oMAAA;AAAA,KAGxB;AAAA,EACF;AACF;AAEO,SAAS,SACd,MAAA,EACiB;AACjB,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,IAAW,EAAC;AACnC,EAAA,QAAA,CAAS,QAAmD,OAAO,CAAA;AAEnE,EAAA,MAAM,MAAA,GAAS,OAAO,MAAA,IAAU,aAAA;AAChC,EAAA,MAAM,WAAA,GAAc,OAAO,WAAA,IAAe,EAAA;AAC1C,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,QAAA,EAAU,IAAA,IAAQ,QAAA,CAAS,IAAA;AAC/C,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,QAAA,EAAU,OAAA,IAAW,QAAA,CAAS,OAAA;AACrD,EAAA,MAAM,WAAqB,EAAC;AAG5B,EAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,QAAA,CAAS,KAAA,EAAO,QAAA,EAAU,SAAA;AACzD,EAAA,IAAI,eAAA,KAAoB,MAAA,IAAa,eAAA,IAAmB,OAAA,GAAU,CAAA,EAAG;AACnE,IAAA,QAAA,CAAS,IAAA;AAAA,MACP,CAAA,wBAAA,EAA2B,eAAe,CAAA,0CAAA,EACpC,OAAO,CAAA,wDAAA;AAAA,KACf;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAsB;AAC3C,EAAA,KAAA,MAAW,CAAC,MAAM,UAAU,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,aAAa,CAAA,EAAG;AACrE,IAAA,MAAM,OAAA,GAAU,WAAW,QAAA,CAAS,MAAA;AAAA,MAClC,CAAC,OAAA,KAAY,CAAC,gBAAgB,OAAA,EAAS,MAAA,CAAO,UAAU,OAAO;AAAA,KACjE;AACA,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG,QAAA,CAAS,IAAI,IAAA,EAAM,CAAC,GAAG,OAAO,CAAC,CAAA;AAAA,EACzD;AAEA,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,QAAQ,CAAA,IAAK,QAAA,EAAU;AACvC,IAAA,MAAM,OAAO,QAAA,CAAS,MAAA,KAAW,OAAO,aAAA,CAAc,IAAI,GAAG,QAAA,CAAS,MAAA;AACtE,IAAA,QAAA,CAAS,IAAA;AAAA,MACP,CAAA,cAAA,EAAiB,IAAI,CAAA,WAAA,EAAc,QAAA,CAAS,IAAI,CAAC,CAAA,KAAM,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,KAAK,IAAI,CAAC,sCACpC,QAAA,CAAS,MAAA,GAAS,IAAI,MAAA,GAAS,IAAI,CAAA,CAAA,IACvE,IAAA,GAAO,2CAAA,GAAyC,mCAAA;AAAA,KACrD;AAAA,EACF;AAEA,EAAA,IAAI,IAAA,KAAS,UAAA,IAAc,CAAC,MAAA,CAAO,UAAU,SAAA,EAAW;AACtD,IAAA,QAAA,CAAS,IAAA;AAAA,MACP;AAAA,KAEF;AAAA,EACF;AAGA,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,IAAU,EAAC,EAAG;AACvC,MAAA,IAAI,KAAA,CAAM,KAAA,CAAM,IAAA,KAAS,QAAA,EAAU;AACjC,QAAA,QAAA,CAAS,IAAA;AAAA,UACP,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,SAAA,EAAY,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,KAAA,CAAM,IAAI,CAAA,mBAAA,CAAA,GACxD,KAAA,CAAM,KAAA,CAAM;AAAA,SAChB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,OAAA,IAAW,QAAA,EAAU,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAEnD,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,OAAA,IAAW,mBAAA,EAAoB;AAK1D,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAoB;AAC9C,EAAA,MAAM,OAAA,GAAmB;AAAA,IACvB,cAAc,WAAA,CAAY,YAAA;AAAA,IAC1B,QAAQ,OAAA,EAAS;AACf,MAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,QAAQ,CAAA,EAAG;AAChC,QAAA,IAAI,aAAA,CAAc,IAAA,GAAO,GAAA,EAAQ,aAAA,CAAc,KAAA,EAAM;AACrD,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAC5C,QAAA,aAAA,CAAc,IAAI,MAAA,EAAA,CAAS,aAAA,CAAc,IAAI,MAAM,CAAA,IAAK,KAAK,CAAC,CAAA;AAAA,MAChE;AACA,MAAA,WAAA,CAAY,QAAQ,OAAO,CAAA;AAAA,IAC7B,CAAA;AAAA,IACA,WAAW,CAAC,OAAA,EAASC,aAAY,WAAA,CAAY,SAAA,CAAU,SAASA,QAAO;AAAA,GACzE;AAEA,EAAA,MAAM,SAAS,YAAA,CAAa;AAAA,IAC1B,SAAS,MAAA,CAAO,QAAA;AAAA,IAChB,aAAa,MAAA,CAAO,aAAA;AAAA,IACpB,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,OAAA;AAAA,IACA,eAAe,MAAA,CAAO,aAAA;AAAA,IACtB,MAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA,EAAW,MAAA,CAAO,QAAA,EAAU,SAAA,IAAa,QAAA,CAAS,SAAA;AAAA,IAClD,OAAA,EAAU,MAAA,CAAO,QAAA,EAAU,OAAA,IAAW,aAAA;AAAA,IACtC;AAAA,GACD,CAAA;AAED,EAAA,MAAM,YAAA,GAAe,QAAQ,OAAA,CAAQ,CAAC,WAAW,MAAA,CAAO,MAAA,IAAU,EAAE,CAAA;AAEpE,EAAA,MAAM,MAAA,GACJ,MAAA,CAAO,MAAA,KAAW,KAAA,GACd,KAAA,GACA;AAAA,IACE,WAAA,EAAa,MAAA,CAAO,MAAA,EAAQ,WAAA,IAAe,IAAA;AAAA,IAC3C,eAAA,EAAiB,MAAA,CAAO,MAAA,EAAQ,eAAA,IAAmB,GAAA;AAAA,IACnD,aAAA,EAAe,MAAA,CAAO,MAAA,EAAQ,aAAA,IAAiB,CAAA;AAAA,IAC/C,iBAAA,EAAmB,MAAA,CAAO,MAAA,EAAQ,iBAAA,IAAqB,EAAA;AAAA,IACvD,UAAA,EAAY,MAAA,CAAO,MAAA,EAAQ,UAAA,IAAc;AAAA,GAC3C;AAEN,EAAA,MAAM,WAAA,GAAc,OAAO,QAAA,EAAU,cAAA;AACrC,EAAA,IAAI,gBAAA,GAAmB,CAAA;AACvB,EAAA,MAAM,cAAA,GACJ,WAAA,IAAe,IAAA,KAAS,QAAA,GACpB,MAAM;AACJ,IAAA,MAAM,QAAA,GAAW,WAAA,KAAgB,IAAA,GAAO,EAAC,GAAI,WAAA;AAC7C,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,GAAA,GAAM,gBAAA,IAAoB,QAAA,CAAS,OAAA,IAAW,GAAA,CAAA,EAAQ;AAC1D,IAAA,gBAAA,GAAmB,GAAA;AACnB,IAAA,MAAM,OAAO,MAAA,CACV,OAAA,CAAQ,EAAE,KAAA,EAAO,QAAA,CAAS,SAAS,CAAA,EAAG,EACtC,KAAA,CAAM,CAAC,UAAU,MAAA,CAAO,KAAA,CAAM,+BAA+B,EAAE,KAAA,EAAO,CAAC,CAAA;AAC1E,IAAA,MAAA,CAAO,QAAA,EAAU,YAAY,IAAI,CAAA;AAAA,EACnC,CAAA,GACA,MAAA;AAEN,EAAA,MAAM,UAAU,aAAA,CAAc;AAAA,IAC5B,SAAS,MAAA,CAAO,QAAA;AAAA,IAChB,SAAS,MAAA,CAAO,OAAA;AAAA,IAChB,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAQ,MAAA,CAAO,MAAA;AAAA,IACf,OAAA;AAAA,IACA,MAAA;AAAA,IACA,cAAA;AAAA,IACA,QAAA,EAAU,MAAA,CAAO,QAAA,IAAY,QAAA,CAAS,QAAA;AAAA,IACtC,YAAA;AAAA,IACA,gBAAgB,MAAA,CAAO,cAAA;AAAA,IACvB,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,WAAW,MAAA,CAAO,SAAA,GAAY,iBAAA,CAAkB,MAAA,CAAO,SAAS,CAAA,GAAI,MAAA;AAAA,IACpE,aAAA,EAAe,OAAO,IAAA,EAAM,SAAA;AAAA,IAC5B,GAAI,MAAA,CAAO,IAAA,EAAM,MAAA,GAAS,EAAE,YAAY,MAAA,CAAO,IAAA,CAAK,MAAA,EAAO,GAAI,EAAC;AAAA,IAChE,GAAK,MAAA,CAAO,aAAA,IAAiB,MAAA,CAAO,MAAM,MAAA,GACtC,EAAE,aAAA,EAAe,MAAA,CAAO,aAAA,IAAiB,MAAA,CAAO,IAAA,EAAM,MAAA,KACtD;AAAC,GACN,CAAA;AAED,EAAA,eAAe,IAAA,CACb,MACA,IAAA,EACqB;AACrB,IAAA,MAAM,SAAS,MAAM,OAAA;AAAA,MACnB,IAAA;AAAA,MACA;AAAA,QACE,IAAI,IAAA,CAAK,EAAA;AAAA,QACT,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,WAAW,IAAA,CAAK;AAAA,OAClB;AAAA,MACA;AAAA,QACE,SAAS,MAAA,CAAO,QAAA;AAAA,QAChB,aAAa,MAAA,CAAO,aAAA;AAAA,QACpB,UAAU,MAAA,CAAO,QAAA;AAAA,QACjB,OAAA;AAAA,QACA,eAAe,MAAA,CAAO,aAAA;AAAA,QACtB,WAAA,EAAa,MAAA,CAAO,QAAA,EAAU,WAAA,IAAe,QAAA,CAAS,WAAA;AAAA,QACtD;AAAA;AACF,KACF;AAGA,IAAA,IAAI,MAAA,CAAO,aAAA,CAAc,MAAA,GAAS,CAAA,EAAG;AACnC,MAAA,KAAA,MAAW,YAAA,IAAgB,OAAO,aAAA,EAAe;AAC/C,QAAA,OAAA,CAAQ,OAAA,CAAQ,YAAA,CAAa,YAAA,CAAa,MAAM,CAAC,CAAA;AAAA,MACnD;AACA,MAAA,OAAA,CAAQ,QAAQ,kBAAkB,CAAA;AAGlC,MAAA,MAAM,GAAA,GAAM,OAAO,aAAA,CAAc,OAAA;AAAA,QAAQ,CAAC,iBACxC,YAAA,CAAa,UAAA,CAAW,IAAI,CAAC,QAAA,KAAa,SAAS,EAAE;AAAA,OACvD;AAEA,MAAA,IAAI,SAAS,QAAA,EAAU,MAAM,OAAO,KAAA,CAAM,EAAE,KAAK,CAAA;AAAA,WAAA,IACxC,SAAS,UAAA,EAAY,MAAA,CAAO,iBAAiB,MAAA,CAAO,QAAA,EAAU,WAAW,GAAG,CAAA;AAAA,IACvF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAGA,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,WAAW,IAAI,GAAA;AAAA,MAAA,CAClB,MAAA,CAAO,MAAA,IAAU,EAAC,EAAG,OAAA;AAAA,QAAQ,CAAC,KAAA,KAC7B,KAAA,CAAM,KAAA,CAAM,IAAA,KAAS,QAAA,GAAW,CAAC,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,GAAI;AAAC;AAC3D,KACF;AACA,IAAA,MAAA,CAAO,IAAA,GAAO;AAAA,MACZ,KAAA,EAAO,kBAAkB,MAAA,CAAO,EAAA,EAAI,OAAO,MAAA,EAAQ,MAAA,CAAO,UAAU,WAAW,CAAA;AAAA,MAC/E,IAAA,EAAM,kBAAA;AAAA,QACJ,MAAA,CAAO,MAAA;AAAA,QACP,MAAA,CAAO,EAAA;AAAA,QACP,QAAA;AAAA,QACA,CAAC,OAAA,KAAY,IAAI,WAAA,CAAY,OAAO;AAAA,OACtC;AAAA,MACA,MAAA;AAAA,MACA,iBAAA,EAAmB,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,aAAa,CAAA;AAAA,MACnD,eAAe,MAAA,CAAO,aAAA;AAAA,MACtB,MAAM,CAAC,IAAA,EAAM,IAAA,KAAS,IAAA,CAAK,MAA8B,IAAa;AAAA,KACvE,CAAA;AAAA,EACH;AAEA,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,OAAA;AAAA,IAEA,aAAa,CAAC,OAAA,KAAY,OAAO,WAAA,CAAY,OAAA,IAAW,EAAE,CAAA;AAAA,IAE1D,YAAA,EAAc,CAAC,MAAA,MAAY;AAAA,MACzB,CAAC,oBAAoB,GAAG,MAAA,CAAO,cAAc,GAAA,CAAI,MAAM,KAAK,CAAC;AAAA,KAC/D,CAAA;AAAA,IAEA,YAAY,MAAM;AAAA,MAChB,GAAG,WAAA,CAAY,MAAA;AAAA,QACb,CAAC,KAAA,KAAA,CACE,KAAA,CAAM,IAAA,KAAS,OAAA,IAAW,OAAA,CAAQ,MAAA,CAAO,IAAA,EAAM,MAAM,CAAA,MACrD,KAAA,CAAM,IAAA,KAAS,aAAa,MAAA,KAAW,KAAA;AAAA,OAC5C;AAAA,MACA,GAAG,OAAA,CAAQ,OAAA;AAAA,QAAQ,CAAC,YACjB,MAAA,CAAO,MAAA,IAAU,EAAC,EAAG,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,UACpC,QAAQ,KAAA,CAAM,MAAA;AAAA,UACd,MAAM,KAAA,CAAM,IAAA;AAAA,UACZ,OAAO,KAAA,CAAM,KAAA;AAAA,UACb,OAAO,MAAA,CAAO;AAAA,SAChB,CAAE;AAAA;AACJ,KACF;AAAA,IAEA,qBAAqB,CAAC,OAAA,GAAU,EAAC,KAC/B,MAAA,CAAO,SAAS,mBAAA,CAAoB;AAAA,MAClC,KAAA,EAAO,QAAQ,KAAA,IAAS,IAAI,KAAK,IAAA,CAAK,GAAA,EAAI,GAAI,EAAA,GAAK,IAAQ,CAAA;AAAA,MAC3D,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,KAAA,IAAS,KAAK,GAAI;AAAA,KAC3C,CAAA;AAAA,IAEH,aAAa,YAAmC;AAC9C,MAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,MAAA,CAAO,IAAA,EAAM,MAAM,CAAA;AAC/C,MAAA,OAAO;AAAA,QACL,IAAA;AAAA,QACA,WAAA;AAAA,QACA,wBACE,CAAC,WAAA,KAAgB,SAAS,MAAA,IAAU,IAAA,KAAS,cAAc,IAAA,KAAS,QAAA,CAAA;AAAA,QACtE;AAAA,OACF;AAAA,IACF;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import type { DatabaseAdapter } from \"./adapter\";\nimport type { NotificationDefinitions, PayloadOf } from \"./definition\";\nimport type { Logger } from \"./errors\";\nimport type { AnyPlugin, Promisable, RouteScope } from \"./plugin\";\nimport type { EmailProvider } from \"./provider\";\nimport type { RateLimitConfig } from \"./rate-limit\";\nimport type { Signals } from \"./signals\";\nimport type { Channel, DeliveryMode, DeliveryRecord, Recipient, SkipReason } from \"./types\";\n\nexport type SessionConfig = {\n /** null yields 401; throwing yields 500. Deliberately distinct. */\n getUserId: (request: Request) => string | null | Promise<string | null>;\n};\n\nexport type DeliveryConfig = {\n mode?: DeliveryMode;\n maxAttempts?: number;\n /** Must exceed the slowest provider timeout, or duplicates are systematic. */\n leaseMs?: number;\n batchSize?: number;\n /** inline mode only. */\n throwOnError?: boolean;\n backoff?: \"exponential\" | ((attempt: number) => number);\n /** Next.js `after` or Cloudflare `ctx.waitUntil`. Injected, not detected. */\n waitUntil?: (promise: Promise<unknown>) => void;\n /**\n * Run one small claim-and-deliver pass after any request the handler serves,\n * at most once per `everyMs` per process. Delivery then rides on your own\n * traffic and the cron becomes a backstop. Ignored in `inline` mode. RFC 0006 idea 5.\n */\n sweepOnRequest?: true | { everyMs?: number; limit?: number };\n};\n\nexport type EventsConfig = {\n /** Comment line keeping proxies from closing an idle stream. Default 25 s. */\n heartbeatMs?: number;\n /**\n * Without a cross-process signal source, each stream checks the database\n * for changes this often on its subscriber's behalf. Default 30 s; 0 disables.\n */\n probeIntervalMs?: number;\n /** Concurrent streams one user may hold in this process before a 429. Default 10. */\n maxStreamsPerUser?: number;\n /** Concurrent streams this process holds in total before a 503. Default 5000. */\n maxStreams?: number;\n /** Close the stream after this long; the client reconnects. 0 (default) leaves it open. */\n maxDurationMs?: number;\n};\n\nexport type ChannelsConfig = {\n inApp?: { enabled: boolean };\n email?: { provider: EmailProvider };\n};\n\nexport type EasyPingConfig<TDefs extends NotificationDefinitions> = {\n database: DatabaseAdapter;\n session: SessionConfig;\n /** Signs unsubscribe and other tokenised links. See RFC 0002 §5. */\n secret: string;\n getRecipients: (userIds: readonly string[]) => Promise<readonly Recipient[]>;\n notifications: TDefs;\n channels: ChannelsConfig;\n delivery?: DeliveryConfig;\n /** Required by `cron` and `deferred` modes. See RFC 0002 §4. */\n cron?: {\n secret: string;\n /** Sweeps one POST /cron may run before returning. Defaults to 50. */\n maxSweeps?: number;\n };\n /** Bearer secret for plugin machine routes. Falls back to `cron.secret`. */\n machineSecret?: string;\n /** In-process fixed-window limiter, applied before routing. */\n rateLimit?: RateLimitConfig;\n /**\n * How \"something changed\" travels between the sender, the worker and open\n * event streams. Defaults to in-process. Supply a LISTEN/NOTIFY or change\n * stream implementation to cross replicas. RFC 0006 §4C.\n */\n signals?: Signals;\n /** `GET /events`, a Server-Sent Events stream per user. `false` unmounts it. */\n events?: EventsConfig | false;\n plugins?: readonly AnyPlugin[];\n /** Where the handler is mounted, used to strip the prefix off incoming URLs. */\n basePath?: string;\n /** Origins other than the request host that may POST. `\"*.example.com\"` matches subdomains. */\n trustedOrigins?: readonly string[];\n /** Runs before routing. Return a Response to short-circuit — a rate limiter's 429, say. */\n // biome-ignore lint/suspicious/noConfusingVoidType: a hook that returns nothing is the common case\n onRequest?: (request: Request) => Promisable<Response | undefined | void>;\n /** Defaults to 64 KiB. */\n maxBodyBytes?: number;\n /** Table-name prefix; must match the one given to the adapter. */\n tablePrefix?: string;\n logger?: Logger;\n};\n\nexport type SendArgs<TDefs extends NotificationDefinitions, TKey extends keyof TDefs> = {\n to: string | readonly string[];\n payload: PayloadOf<TDefs[TKey]>;\n actorId?: string;\n dedupeKey?: string;\n overrides?: { channels?: readonly Channel[] };\n};\n\nexport type SendResult = {\n notifications: readonly {\n id: string;\n userId: string;\n deliveries: readonly { id: string; channel: Channel }[];\n }[];\n skipped: readonly { userId: string; reason: SkipReason }[];\n};\n\nexport type Worker = {\n stop: () => Promise<void>;\n};\n\nexport type MountedRoute = {\n method: \"GET\" | \"POST\";\n path: string;\n scope: RouteScope;\n /** \"core\" for the built-in routes, otherwise the plugin id. */\n owner: string;\n};\n\nexport type HealthReport = {\n mode: DeliveryMode;\n cronMounted: boolean;\n /** True when the chosen mode relies on a cron sweep that is not wired up. */\n cronRequiredButMissing: boolean;\n warnings: readonly string[];\n};\n\n/** Response header carrying a per-user inbox version, so app traffic can replace polling. */\nexport const INBOX_VERSION_HEADER = \"x-easy-ping-inbox\";\n\nexport type EasyPing<TDefs extends NotificationDefinitions> = {\n send<TKey extends keyof TDefs & string>(\n type: TKey,\n args: SendArgs<TDefs, TKey>,\n ): Promise<SendResult>;\n\n handler: {\n /** Method-agnostic entry, for toNodeHandler and single-dispatch frameworks. */\n handle: (request: Request) => Promise<Response>;\n GET: (request: Request) => Promise<Response>;\n POST: (request: Request) => Promise<Response>;\n };\n\n /** Idle interval defaults to 10 s; a send in this process wakes the loop at once. */\n startWorker(options?: { intervalMs?: number; batchSize?: number }): Worker;\n healthCheck(): Promise<HealthReport>;\n\n /** Every mounted route with its auth scope, so `custom`-scoped ones stay visible. RFC 0002 §3. */\n listRoutes(): readonly MountedRoute[];\n\n /**\n * Headers to attach to any response your app sends this user. The client's\n * `instrument(fetch)` reads them and refreshes only when the version moved,\n * so an active user never needs a dedicated poll. RFC 0006 idea 1.\n */\n inboxHeaders(userId: string): Record<string, string>;\n\n /** Deliveries that exhausted their attempts, newest first. Wire it to an admin page or an alert. */\n getFailedDeliveries(options?: {\n /** Defaults to the last 24 hours. */\n since?: Date;\n /** Defaults to 100. */\n limit?: number;\n }): Promise<readonly DeliveryRecord[]>;\n};\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { Channel } from \"./types\";\n\nexport type PayloadFromSchema<TSchema> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : Record<string, unknown>;\n\nexport type EmailTemplate<TPayload> = {\n subject: (payload: TPayload) => string;\n /** Rendered HTML. react-email users wrap: `(p) => render(<Email {...p} />)`. */\n template: (payload: TPayload) => string | Promise<string>;\n};\n\nexport type NotificationDefinition<\n TSchema extends StandardSchemaV1 | undefined = StandardSchemaV1 | undefined,\n> = {\n schema?: TSchema;\n channels: readonly Channel[];\n maxAttempts?: number;\n email?: EmailTemplate<PayloadFromSchema<TSchema>>;\n};\n\n/** Binds TSchema so email callbacks infer the payload; TS cannot infer from a sibling property. */\nexport function defineNotification<TSchema extends StandardSchemaV1 | undefined = undefined>(\n definition: NotificationDefinition<TSchema>,\n): NotificationDefinition<TSchema> {\n return definition;\n}\n\n/** Erased form. `EmailTemplate<never>` because callbacks are contravariant. */\nexport type AnyNotificationDefinition = {\n schema?: StandardSchemaV1 | undefined;\n channels: readonly Channel[];\n maxAttempts?: number | undefined;\n email?: EmailTemplate<never> | undefined;\n};\n\nexport type NotificationDefinitions = Record<string, AnyNotificationDefinition>;\n\nexport type PayloadOf<TDef> = TDef extends { schema: infer TSchema extends StandardSchemaV1 }\n ? StandardSchemaV1.InferOutput<TSchema>\n : Record<string, unknown>;\n","export type RateLimitConfig = {\n /** Requests allowed per key per window. */\n max: number;\n windowMs: number;\n /** Bucket key. Defaults to the client address from proxy headers; null exempts the request. */\n key?: (request: Request) => string | null;\n};\n\nconst clientAddress = (request: Request): string | null => {\n const headers = request.headers;\n return (\n headers.get(\"cf-connecting-ip\") ??\n headers.get(\"x-real-ip\") ??\n headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ??\n null\n );\n};\n\n/** Fixed window in process memory: per isolate or replica, so a bound rather than a quota. */\nexport function createRateLimiter(config: RateLimitConfig): (request: Request) => Response | null {\n const keyOf = config.key ?? clientAddress;\n const buckets = new Map<string, { count: number; resetAt: number }>();\n let lastSweep = 0;\n\n return (request) => {\n const key = keyOf(request);\n if (key === null) return null;\n\n const now = Date.now();\n\n // Sweep expired buckets at most once per window.\n if (now - lastSweep > config.windowMs) {\n for (const [id, bucket] of buckets) if (bucket.resetAt <= now) buckets.delete(id);\n lastSweep = now;\n }\n\n let bucket = buckets.get(key);\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + config.windowMs };\n buckets.set(key, bucket);\n }\n\n bucket.count += 1;\n if (bucket.count <= config.max) return null;\n\n return new Response(null, {\n status: 429,\n headers: {\n \"retry-after\": String(Math.max(1, Math.ceil((bucket.resetAt - now) / 1000))),\n \"cache-control\": \"private, no-store\",\n },\n });\n };\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { DatabaseAdapter, InsertDelivery, InsertNotification } from \"./adapter\";\nimport type { ChannelsConfig, SendResult } from \"./config\";\nimport type { NotificationDefinitions } from \"./definition\";\nimport { type Logger, ValidationError } from \"./errors\";\nimport type { AnyPlugin, BeforeSendResult, ChannelDecision } from \"./plugin\";\nimport type { Channel, Recipient, SkipReason } from \"./types\";\n\nexport type SendPipelineDeps = {\n adapter: DatabaseAdapter;\n definitions: NotificationDefinitions;\n channels: ChannelsConfig;\n plugins: readonly AnyPlugin[];\n getRecipients: (userIds: readonly string[]) => Promise<readonly Recipient[]>;\n maxAttempts: number;\n logger: Logger;\n generateId?: () => string;\n};\n\nexport type SendInput = {\n to: string | readonly string[];\n payload: unknown;\n actorId?: string | undefined;\n dedupeKey?: string | undefined;\n overrides?: { channels?: readonly Channel[] } | undefined;\n};\n\nasync function validatePayload(type: string, schema: StandardSchemaV1, payload: unknown) {\n const result = await schema[\"~standard\"].validate(payload);\n if (result.issues) {\n throw new ValidationError(\n type,\n result.issues.map((issue) => ({\n message: issue.message,\n ...(issue.path ? { path: issue.path.map(String).join(\".\") } : {}),\n })),\n );\n }\n return result.value;\n}\n\n/** inApp is on unless disabled; every other channel needs a provider or a plugin that delivers it. */\nexport function isChannelUsable(\n channel: Channel,\n channels: ChannelsConfig,\n plugins: readonly AnyPlugin[] = [],\n): boolean {\n if (channel === \"inApp\") return channels.inApp?.enabled !== false;\n if (channel === \"email\") return Boolean(channels.email?.provider);\n // Anything else is usable only if a plugin claims it and can deliver it.\n return plugins.some((plugin) => plugin.channels?.includes(channel) && plugin.hooks?.deliver);\n}\n\n/** One prepare() per plugin per send, keyed by plugin id. */\nasync function runPrepare(\n plugins: readonly AnyPlugin[],\n ctx: { type: string; recipients: readonly Recipient[] },\n logger: Logger,\n): Promise<Map<string, unknown>> {\n const prepared = new Map<string, unknown>();\n\n for (const plugin of plugins) {\n const hook = plugin.hooks?.prepare;\n if (!hook) continue;\n try {\n prepared.set(plugin.id, await hook(ctx));\n } catch (error) {\n // Fail closed: a failed bulk load must not decide with stale assumptions.\n logger.error(`plugin \"${plugin.id}\" prepare threw; its hooks will see undefined`, {\n type: ctx.type,\n error,\n });\n prepared.set(plugin.id, undefined);\n }\n }\n\n return prepared;\n}\n\nasync function runBeforeSend(\n plugins: readonly AnyPlugin[],\n base: { type: string; recipient: Recipient; actorId: string | null },\n payload: unknown,\n dedupeKey: string | null,\n prepared: Map<string, unknown>,\n logger: Logger,\n): Promise<BeforeSendResult> {\n let currentPayload = payload;\n let currentDedupeKey = dedupeKey;\n\n for (const plugin of plugins) {\n const hook = plugin.hooks?.beforeSend;\n if (!hook) continue;\n\n let result: BeforeSendResult;\n try {\n result = await hook({\n ...base,\n payload: currentPayload,\n dedupeKey: currentDedupeKey,\n prepared: prepared.get(plugin.id),\n });\n } catch (error) {\n // Fail closed: failing open would email people who opted out. RFC 0004 §5.\n logger.error(`plugin \"${plugin.id}\" beforeSend threw; cancelling send`, {\n type: base.type,\n userId: base.recipient.userId,\n error,\n });\n return { action: \"cancel\", reason: `plugin \"${plugin.id}\" beforeSend threw` };\n }\n\n if (result.action === \"cancel\") return result;\n if (result.payload !== undefined) currentPayload = result.payload;\n if (result.dedupeKey !== undefined) currentDedupeKey = result.dedupeKey;\n }\n\n return {\n action: \"continue\",\n payload: currentPayload,\n ...(currentDedupeKey === null ? {} : { dedupeKey: currentDedupeKey }),\n };\n}\n\nasync function runResolveChannels(\n plugins: readonly AnyPlugin[],\n base: { type: string; recipient: Recipient; payload: unknown },\n initial: readonly ChannelDecision[],\n prepared: Map<string, unknown>,\n logger: Logger,\n): Promise<readonly ChannelDecision[]> {\n let decisions = initial;\n\n for (const plugin of plugins) {\n const hook = plugin.hooks?.resolveChannels;\n if (!hook) continue;\n\n try {\n decisions = [...(await hook({ ...base, decisions, prepared: prepared.get(plugin.id) }))];\n } catch (error) {\n // Fail closed: keep the decisions already made. RFC 0004 §5.\n logger.error(`plugin \"${plugin.id}\" resolveChannels threw; keeping prior decisions`, {\n type: base.type,\n userId: base.recipient.userId,\n error,\n });\n return decisions;\n }\n }\n\n return decisions;\n}\n\nexport async function runSend(\n type: string,\n input: SendInput,\n deps: SendPipelineDeps,\n): Promise<SendResult> {\n const definition = deps.definitions[type];\n if (!definition) {\n throw new ValidationError(type, [{ message: `unknown notification type \"${type}\"` }]);\n }\n\n const payload = definition.schema\n ? await validatePayload(type, definition.schema, input.payload)\n : input.payload;\n\n const userIds = [...new Set(Array.isArray(input.to) ? input.to : [input.to as string])];\n const recipients = await deps.getRecipients(userIds);\n const byUserId = new Map(recipients.map((recipient) => [recipient.userId, recipient]));\n\n const newId = deps.generateId ?? (() => crypto.randomUUID());\n const skipped: { userId: string; reason: SkipReason }[] = [];\n const rows: InsertNotification[] = [];\n const actorId = input.actorId ?? null;\n\n const declared = input.overrides?.channels ?? definition.channels;\n const requested = declared.filter((channel) =>\n isChannelUsable(channel, deps.channels, deps.plugins),\n );\n\n // Distinct from \"no-channels\": a missing provider is not an opt-out.\n if (declared.length > 0 && requested.length === 0) {\n return {\n notifications: [],\n skipped: userIds.map((userId) => ({ userId, reason: \"channel-unavailable\" as const })),\n };\n }\n\n const prepared = await runPrepare(deps.plugins, { type, recipients }, deps.logger);\n\n for (const userId of userIds) {\n const recipient = byUserId.get(userId);\n if (!recipient) {\n skipped.push({ userId, reason: \"no-recipient\" });\n continue;\n }\n\n const before = await runBeforeSend(\n deps.plugins,\n { type, recipient, actorId },\n payload,\n input.dedupeKey ?? null,\n prepared,\n deps.logger,\n );\n\n if (before.action === \"cancel\") {\n skipped.push({ userId, reason: \"no-channels\" });\n continue;\n }\n\n const resolved = await runResolveChannels(\n deps.plugins,\n { type, recipient, payload: before.payload ?? payload },\n requested.map((channel) => ({ channel, action: \"send\" as const })),\n prepared,\n deps.logger,\n );\n\n const now = new Date();\n const deliveries: InsertDelivery[] = resolved\n .filter((decision) => decision.action !== \"skip\")\n .filter((decision) => isChannelUsable(decision.channel, deps.channels, deps.plugins))\n .map((decision) => ({\n id: newId(),\n channel: decision.channel,\n maxAttempts: definition.maxAttempts ?? deps.maxAttempts,\n // defer is just not_before; the claim query refuses rows whose time has not come. RFC 0004 §4.2.\n notBefore: decision.action === \"defer\" && decision.notBefore ? decision.notBefore : now,\n }));\n\n if (deliveries.length === 0) {\n skipped.push({ userId, reason: \"no-channels\" });\n continue;\n }\n\n const dedupeKey = before.action === \"continue\" ? before.dedupeKey : undefined;\n\n rows.push({\n id: newId(),\n userId,\n type,\n payload: before.payload ?? payload,\n ...(actorId ? { actorId } : {}),\n ...(dedupeKey ? { dedupeKey } : {}),\n deliveries,\n });\n }\n\n if (rows.length === 0) return { notifications: [], skipped };\n\n const { created, deduped } = await deps.adapter.createNotifications(rows);\n const createdSet = new Set(created);\n\n for (const row of rows) {\n if (deduped.includes(row.id)) skipped.push({ userId: row.userId, reason: \"deduped\" });\n }\n\n const notifications = rows\n .filter((row) => createdSet.has(row.id))\n .map((row) => ({\n id: row.id,\n userId: row.userId,\n deliveries: row.deliveries.map((delivery) => ({\n id: delivery.id,\n channel: delivery.channel,\n })),\n }));\n\n for (const plugin of deps.plugins) {\n const hook = plugin.hooks?.afterSend;\n if (!hook) continue;\n try {\n await hook({\n type,\n payload,\n notifications: notifications.map(({ id, userId }) => ({ id, userId })),\n prepared: prepared.get(plugin.id),\n });\n } catch (error) {\n // Fails open: the rows are committed.\n deps.logger.error(`plugin \"${plugin.id}\" afterSend threw; ignoring`, { type, error });\n }\n }\n\n return { notifications, skipped };\n}\n","import type { Backoff } from \"./backoff\";\nimport {\n type EasyPing,\n type EasyPingConfig,\n type HealthReport,\n INBOX_VERSION_HEADER,\n type MountedRoute,\n type SendArgs,\n type SendResult,\n} from \"./config\";\nimport type { NotificationDefinitions } from \"./definition\";\nimport { ConfigError, consoleLogger } from \"./errors\";\nimport { createHandler } from \"./handler\";\nimport type { AnyPlugin } from \"./plugin\";\nimport { createRateLimiter } from \"./rate-limit\";\nimport { createRunner } from \"./runner\";\nimport { isChannelUsable, runSend } from \"./send\";\nimport { createMemorySignals, DELIVERIES_CHANNEL, inboxChannel, type Signals } from \"./signals\";\nimport { createPluginStore } from \"./store\";\nimport { createScopedSigner } from \"./tokens\";\nimport type { DeliveryMode } from \"./types\";\n\nconst CORE_ROUTES: readonly MountedRoute[] = [\n { method: \"GET\", path: \"/\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"GET\", path: \"/count\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/seen\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/read\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/read-all\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"GET\", path: \"/events\", scope: { type: \"user\" }, owner: \"core\" },\n { method: \"POST\", path: \"/cron\", scope: { type: \"machine\" }, owner: \"core\" },\n];\n\nconst DEFAULTS = {\n maxAttempts: 5,\n leaseMs: 60_000,\n batchSize: 20,\n basePath: \"/api/notifications\",\n mode: \"cron\" as DeliveryMode,\n};\n\nexport const MIN_SECRET_LENGTH = 16;\n\nconst PLACEHOLDER_SECRETS = new Set([\n \"secret\",\n \"changeme\",\n \"change-me\",\n \"replace-me\",\n \"replace-me-too\",\n \"password\",\n \"demo-signing-secret\",\n \"demo-cron-secret\",\n]);\n\nfunction checkSecretStrength(name: string, value: string) {\n if (value.length < MIN_SECRET_LENGTH || PLACEHOLDER_SECRETS.has(value.toLowerCase())) {\n throw new ConfigError(\n `\\`${name}\\` must be at least ${MIN_SECRET_LENGTH} characters and not a placeholder. ` +\n \"Generate one with `openssl rand -base64 32`.\",\n );\n }\n}\n\nfunction validate(config: EasyPingConfig<NotificationDefinitions>, plugins: readonly AnyPlugin[]) {\n if (!config.database) throw new ConfigError(\"`database` is required.\");\n if (!config.notifications) throw new ConfigError(\"`notifications` is required.\");\n\n // No default resolver and no dev bypass: an insecure default ships, a crash does not.\n if (typeof config.session?.getUserId !== \"function\") {\n throw new ConfigError(\n \"`session.getUserId` is required — the mounted endpoints serve a user's private inbox \" +\n \"and must resolve the caller. See RFC 0002.\",\n );\n }\n\n if (!config.secret) {\n throw new ConfigError(\n \"`secret` is required — it signs unsubscribe and other session-less links. \" +\n \"Adding it later is a breaking change, so it is mandatory from the start.\",\n );\n }\n checkSecretStrength(\"secret\", config.secret);\n if (config.cron?.secret) checkSecretStrength(\"cron.secret\", config.cron.secret);\n if (config.machineSecret) checkSecretStrength(\"machineSecret\", config.machineSecret);\n\n if (config.rateLimit && (config.rateLimit.max < 1 || config.rateLimit.windowMs < 1)) {\n throw new ConfigError(\"`rateLimit.max` and `rateLimit.windowMs` must both be positive.\");\n }\n\n if (typeof config.getRecipients !== \"function\") {\n throw new ConfigError(\"`getRecipients` is required to resolve emails and timezones.\");\n }\n\n const seen = new Set<string>();\n for (const plugin of plugins) {\n if (seen.has(plugin.id)) {\n throw new ConfigError(`duplicate plugin id \"${plugin.id}\" — ids must be unique.`);\n }\n seen.add(plugin.id);\n }\n\n for (const plugin of plugins) {\n for (const dependency of plugin.dependsOn ?? []) {\n if (!seen.has(dependency)) {\n throw new ConfigError(\n `plugin \"${plugin.id}\" requires \"${dependency}\", which is not registered.`,\n );\n }\n }\n }\n\n const routes = new Set<string>();\n for (const plugin of plugins) {\n for (const route of plugin.routes ?? []) {\n const signature = `${route.method} ${route.path}`;\n if (routes.has(signature)) {\n throw new ConfigError(`route collision: two plugins both registered \"${signature}\".`);\n }\n routes.add(signature);\n }\n }\n\n const mode = config.delivery?.mode ?? DEFAULTS.mode;\n if ((mode === \"cron\" || mode === \"deferred\") && !config.cron?.secret) {\n throw new ConfigError(\n `delivery.mode \"${mode}\" relies on the cron sweep, so \\`cron.secret\\` is required. ` +\n \"Left unset the endpoint would be unauthenticated — a free flush-everything trigger \" +\n \"against your email provider. See RFC 0002 §4.\",\n );\n }\n}\n\nexport function easyPing<TDefs extends NotificationDefinitions>(\n config: EasyPingConfig<TDefs>,\n): EasyPing<TDefs> {\n const plugins = config.plugins ?? [];\n validate(config as EasyPingConfig<NotificationDefinitions>, plugins);\n\n const logger = config.logger ?? consoleLogger;\n const tablePrefix = config.tablePrefix ?? \"\";\n const mode = config.delivery?.mode ?? DEFAULTS.mode;\n const leaseMs = config.delivery?.leaseMs ?? DEFAULTS.leaseMs;\n const warnings: string[] = [];\n\n // Below this ratio a slow send is re-claimed mid-flight. RFC 0003 §7.\n const providerTimeout = config.channels.email?.provider?.timeoutMs;\n if (providerTimeout !== undefined && providerTimeout >= leaseMs / 2) {\n warnings.push(\n `email provider timeout (${providerTimeout}ms) is at least half of delivery.leaseMs ` +\n `(${leaseMs}ms); raise leaseMs or duplicate sends become systematic.`,\n );\n }\n\n const unusable = new Map<string, string[]>();\n for (const [type, definition] of Object.entries(config.notifications)) {\n const missing = definition.channels.filter(\n (channel) => !isChannelUsable(channel, config.channels, plugins),\n );\n if (missing.length > 0) unusable.set(type, [...missing]);\n }\n\n for (const [type, channels] of unusable) {\n const dead = channels.length === config.notifications[type]?.channels.length;\n warnings.push(\n `notification \"${type}\" declares ${channels.map((c) => `\"${c}\"`).join(\", \")} ` +\n `but no provider is configured for ${channels.length > 1 ? \"them\" : \"it\"}` +\n (dead ? \" — this type can never be delivered.\" : \"; those channels will be skipped.\"),\n );\n }\n\n if (mode === \"deferred\" && !config.delivery?.waitUntil) {\n warnings.push(\n \"delivery.mode is 'deferred' but no delivery.waitUntil was supplied; \" +\n \"delivery will wait for the cron sweep.\",\n );\n }\n\n // Custom-scoped routes are named at startup with their justification. RFC 0002 §3.\n for (const plugin of plugins) {\n for (const route of plugin.routes ?? []) {\n if (route.scope.type === \"custom\") {\n warnings.push(\n `plugin \"${plugin.id}\" mounts ${route.method} ${route.path} with custom auth: ` +\n route.scope.justification,\n );\n }\n }\n }\n\n for (const warning of warnings) logger.warn(warning);\n\n const baseSignals = config.signals ?? createMemorySignals();\n\n // Per-user inbox versions for the piggyback header. In-process counters, so\n // another replica's number differs; the client treats any change as \"look\",\n // which costs a spurious refresh, never a missed one.\n const inboxVersions = new Map<string, number>();\n const signals: Signals = {\n crossProcess: baseSignals.crossProcess,\n publish(channel) {\n if (channel.startsWith(\"inbox:\")) {\n if (inboxVersions.size > 50_000) inboxVersions.clear();\n const userId = channel.slice(\"inbox:\".length);\n inboxVersions.set(userId, (inboxVersions.get(userId) ?? 0) + 1);\n }\n baseSignals.publish(channel);\n },\n subscribe: (channel, handler) => baseSignals.subscribe(channel, handler),\n };\n\n const runner = createRunner({\n adapter: config.database,\n definitions: config.notifications,\n channels: config.channels,\n plugins,\n getRecipients: config.getRecipients,\n logger,\n leaseMs,\n batchSize: config.delivery?.batchSize ?? DEFAULTS.batchSize,\n backoff: (config.delivery?.backoff ?? \"exponential\") as Backoff,\n signals,\n });\n\n const pluginRoutes = plugins.flatMap((plugin) => plugin.routes ?? []);\n\n const events =\n config.events === false\n ? false\n : {\n heartbeatMs: config.events?.heartbeatMs ?? 25_000,\n probeIntervalMs: config.events?.probeIntervalMs ?? 30_000,\n maxDurationMs: config.events?.maxDurationMs ?? 0,\n maxStreamsPerUser: config.events?.maxStreamsPerUser ?? 10,\n maxStreams: config.events?.maxStreams ?? 5_000,\n };\n\n const sweepConfig = config.delivery?.sweepOnRequest;\n let lastRequestSweep = 0;\n const sweepOnRequest =\n sweepConfig && mode !== \"inline\"\n ? () => {\n const settings = sweepConfig === true ? {} : sweepConfig;\n const now = Date.now();\n if (now - lastRequestSweep < (settings.everyMs ?? 5_000)) return;\n lastRequestSweep = now;\n const pass = runner\n .runOnce({ limit: settings.limit ?? 5 })\n .catch((error) => logger.error(\"request-driven sweep failed\", { error }));\n config.delivery?.waitUntil?.(pass);\n }\n : undefined;\n\n const handler = createHandler({\n adapter: config.database,\n session: config.session,\n runner,\n logger,\n secret: config.secret,\n signals,\n events,\n sweepOnRequest,\n basePath: config.basePath ?? DEFAULTS.basePath,\n pluginRoutes,\n trustedOrigins: config.trustedOrigins,\n onRequest: config.onRequest,\n maxBodyBytes: config.maxBodyBytes,\n rateLimit: config.rateLimit ? createRateLimiter(config.rateLimit) : undefined,\n cronMaxSweeps: config.cron?.maxSweeps,\n ...(config.cron?.secret ? { cronSecret: config.cron.secret } : {}),\n ...((config.machineSecret ?? config.cron?.secret)\n ? { machineSecret: config.machineSecret ?? config.cron?.secret }\n : {}),\n });\n\n async function send<TKey extends keyof TDefs & string>(\n type: TKey,\n args: SendArgs<TDefs, TKey>,\n ): Promise<SendResult> {\n const result = await runSend(\n type,\n {\n to: args.to,\n payload: args.payload,\n actorId: args.actorId,\n dedupeKey: args.dedupeKey,\n overrides: args.overrides,\n },\n {\n adapter: config.database,\n definitions: config.notifications,\n channels: config.channels,\n plugins,\n getRecipients: config.getRecipients,\n maxAttempts: config.delivery?.maxAttempts ?? DEFAULTS.maxAttempts,\n logger,\n },\n );\n\n // Rows are committed; everything below only affects latency. RFC 0001 §8.1.\n if (result.notifications.length > 0) {\n for (const notification of result.notifications) {\n signals.publish(inboxChannel(notification.userId));\n }\n signals.publish(DELIVERIES_CHANNEL);\n\n // Scoped to this send, or an inline send flushes the whole backlog.\n const ids = result.notifications.flatMap((notification) =>\n notification.deliveries.map((delivery) => delivery.id),\n );\n\n if (mode === \"inline\") await runner.drain({ ids });\n else if (mode === \"deferred\") runner.scheduleDeferred(config.delivery?.waitUntil, ids);\n }\n\n return result;\n }\n\n // After send() exists, before any hook or route can run.\n for (const plugin of plugins) {\n const purposes = new Set(\n (plugin.routes ?? []).flatMap((route) =>\n route.scope.type === \"signed\" ? [route.scope.purpose] : [],\n ),\n );\n plugin.init?.({\n store: createPluginStore(plugin.id, plugin.schema, config.database, tablePrefix),\n sign: createScopedSigner(\n config.secret,\n plugin.id,\n purposes,\n (message) => new ConfigError(message),\n ),\n logger,\n notificationTypes: Object.keys(config.notifications),\n getRecipients: config.getRecipients,\n send: (type, args) => send(type as keyof TDefs & string, args as never) as Promise<unknown>,\n });\n }\n\n return {\n send,\n handler,\n\n startWorker: (options) => runner.startWorker(options ?? {}),\n\n inboxHeaders: (userId) => ({\n [INBOX_VERSION_HEADER]: String(inboxVersions.get(userId) ?? 0),\n }),\n\n listRoutes: () => [\n ...CORE_ROUTES.filter(\n (route) =>\n (route.path !== \"/cron\" || Boolean(config.cron?.secret)) &&\n (route.path !== \"/events\" || events !== false),\n ),\n ...plugins.flatMap((plugin) =>\n (plugin.routes ?? []).map((route) => ({\n method: route.method,\n path: route.path,\n scope: route.scope,\n owner: plugin.id,\n })),\n ),\n ],\n\n getFailedDeliveries: (options = {}) =>\n config.database.getFailedDeliveries({\n since: options.since ?? new Date(Date.now() - 24 * 3600_000),\n limit: Math.min(options.limit ?? 100, 1000),\n }),\n\n healthCheck: async (): Promise<HealthReport> => {\n const cronMounted = Boolean(config.cron?.secret);\n return {\n mode,\n cronMounted,\n cronRequiredButMissing:\n !cronMounted && (mode === \"cron\" || mode === \"deferred\" || mode === \"inline\"),\n warnings,\n };\n },\n };\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as Recipient, C as Channel, f as DeliveryOutcome, k as PluginStore } from './adapter-
|
|
1
|
+
import { R as Recipient, C as Channel, f as DeliveryOutcome, k as PluginStore } from './adapter-CV5Yt2c6.cjs';
|
|
2
2
|
|
|
3
3
|
declare class EasyPingError extends Error {
|
|
4
4
|
constructor(message: string);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as Recipient, C as Channel, f as DeliveryOutcome, k as PluginStore } from './adapter-
|
|
1
|
+
import { R as Recipient, C as Channel, f as DeliveryOutcome, k as PluginStore } from './adapter-CV5Yt2c6.js';
|
|
2
2
|
|
|
3
3
|
declare class EasyPingError extends Error {
|
|
4
4
|
constructor(message: string);
|