@seatlayer/js 0.47.2 → 0.48.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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/manageApi.ts"],"sourcesContent":["/**\n * Organizer manage-surface client for workers/api (the `/v1/events/:key/*`\n * inventory routes + the public realtime channel). Companion to api.ts (the\n * buyer `/pub/*` client) — kept separate because the manage surface is\n * token-authed (Bearer) and cross-origin from the CMS:\n *\n * - Writes + reports send `Authorization: Bearer <token>` where the token is\n * a short-lived, event-scoped organizer manage token (`mse_…`, minted by\n * NestJS) OR a tenant secret key (`sk_…`). Both are accepted by the worker's\n * `eitherAuth` on block / unblock / unblock-all / unbook / hold-ttl / report\n * / log. The Authorization header also exempts the call from the worker's\n * cookie-CSRF gate, so no extra client header is needed.\n * - `credentials: 'omit'` — there is no session cookie; the CMS runs\n * cross-origin. The worker's credentialed CORS still echoes the CMS origin.\n * - `/pub/events/:key/chart` stays public: geometry is the same map buyers\n * see. The seat STATE reads are not. `/pub/.../objects` and an unticketed\n * `/pub/.../subscribe` both answer with the BUYER projection, which shows\n * inventory the caller may not buy as a neutral `blocked` — so an organizer\n * reading them sees its own channel allocations as blocked seats. Both now\n * go through the token: `/v1/events/:key/objects` for the snapshot, and a\n * `/v1/events/:key/subscribe-tickets` mint for the socket's scope.\n *\n * `box-book` is intentionally omitted for M1 (box office ships in M2, and the\n * route is still session-only server-side).\n */\nimport type { AvailabilityRule, ChartDoc } from '@seatlayer/core';\nimport type {\n AccessLinkRecord,\n AccessLinkReveal,\n AccessLinkStatusRecord,\n AssignmentResult,\n ChannelAccessIntent,\n ChannelListResult,\n ChannelRecord,\n} from './channelPlan';\n\nexport type { AvailabilityRule } from '@seatlayer/core';\n\n/** One page of the organizer-only label → channel projection. */\nexport interface ChannelAllocationPage {\n assignmentVersion: number;\n allocations: Array<{ label: string; channelId: string }>;\n nextAfterLabel: string | null;\n}\n\nexport interface ChannelAuditEntry {\n id: number;\n at: number;\n actor: string | null;\n action: string;\n channelId: string | null;\n assignmentVersion: number;\n before: unknown;\n after: unknown;\n reason: string | null;\n}\n\nexport interface ChannelAuditPage {\n entries: ChannelAuditEntry[];\n nextBefore: number | null;\n}\n\n/**\n * Buyer projection for a preview audience — the same scoped server view the\n * buyer SDK receives. When an audience cannot be previewed (a paused or\n * archived channel), the server answers `{available:false, unavailable:[…]}`\n * and the UI shows the real paused/unavailable landing state instead of\n * rendering those seats as eligible.\n *\n * Fields stay optional: a worker that predates the hardening merge 404s here,\n * and Channels mode says the preview needs a newer server rather than faking a\n * projection client-side.\n */\nexport interface ChannelPreviewProjection {\n available?: boolean;\n unavailable?: Array<{ channelId: string; state: 'paused' | 'archived' | string }>;\n channelIds?: string[];\n includePublic?: boolean;\n /** Labels this audience may buy. Everything else renders as ONE neutral\n * unavailable state so preview never leaks which channel holds a seat. */\n eligible?: string[];\n counts?: { eligible?: number; free?: number; held?: number; booked?: number };\n}\n\nexport class ManageApiError extends Error {\n status: number;\n code?: string;\n /** Present when a block/unbook 409s because seats were just taken. */\n conflicts?: { label: string; reason?: string }[];\n /**\n * Structured refusal detail. The channel routes use it for the two 409s a UI\n * must render rather than merely report: `channel_archive_blocked_by_holds`\n * carries {activeHolds, heldUnits, latestHoldExpiresAt, retryAfterMs}, and\n * `channel_assignment_conflict` carries the current assignmentVersion.\n */\n details?: Record<string, unknown>;\n /**\n * The server's own human sentence, when it sent one. `message` is the machine\n * code (that is what `error` carries), so a UI that wants to state a PLATFORM\n * RULE — \"redemptions must be between 1 and 10 000\" — reads this instead of\n * re-encoding the bound locally and risking disagreement with the server.\n */\n serverMessage?: string;\n\n constructor(\n status: number,\n message: string,\n code?: string,\n conflicts?: { label: string; reason?: string }[],\n details?: Record<string, unknown>,\n serverMessage?: string,\n ) {\n super(message);\n this.name = 'ManageApiError';\n this.status = status;\n this.code = code;\n this.conflicts = conflicts;\n this.details = details;\n this.serverMessage = serverMessage;\n }\n}\n\nexport interface ReportByStatus {\n free: number;\n held: number;\n booked: number;\n not_for_sale: number;\n}\n\nexport interface ReportCategoryRow {\n category: string;\n total: number;\n free: number;\n held: number;\n booked: number;\n not_for_sale: number;\n /** Exact sum of booked unit_price snapshots, in major currency units. */\n bookedRevenue: number;\n}\n\nexport interface ReportCategoryMeta {\n key: string;\n label: string;\n color: string;\n price: number;\n}\n\nexport interface ReportResult {\n report: { byStatus: ReportByStatus; byCategory: ReportCategoryRow[]; bySection?: ControlRoomSectionMetric[] };\n event: { key: string; name: string; seatTotal: number; currency?: string };\n categories: ReportCategoryMeta[];\n}\n\nexport interface ControlRoomSectionMetric {\n sectionId: string;\n sectionLabel: string;\n zoneId: string | null;\n total: number;\n free: number;\n held: number;\n booked: number;\n not_for_sale: number;\n bookedRevenue: number;\n}\n\n/** Recent seat-state change safe for an event:view control-room grant. Full\n * audit references remain available only through the event:reports log API. */\nexport interface ControlRoomActivityEntry {\n id: number;\n at: number;\n action: string;\n labels: string[];\n}\n\nexport interface ControlRoomSnapshot {\n version: number;\n currency: string;\n totals: { free: number; held: number; booked: number; blocked: number };\n revenue: { gross: number; bySection: ControlRoomSectionMetric[] };\n velocity: {\n windowMinutes: number;\n bySection: Array<{\n sectionId: string;\n netBooked: number;\n grossRevenue: number;\n previousNetBooked: number;\n trend: 'rising' | 'steady' | 'cooling';\n }>;\n };\n presence: { shoppingSessions: number; activeHolds: number };\n /** Present on workers that support reload-safe activity hydration. */\n activity?: ControlRoomActivityEntry[];\n event: { key: string; name: string; seatTotal: number; currency?: string };\n}\n\nexport interface LogEntry {\n id: number;\n at: number;\n action: string;\n labels: string[];\n ref: string | null;\n}\n\nexport interface LogPage {\n entries: LogEntry[];\n nextBefore: number | null;\n}\n\n/**\n * A one-use WebSocket subscribe ticket. `protocols` is exactly what to hand\n * `new WebSocket(url, protocols)` — the ticket rides in `Sec-WebSocket-Protocol`\n * because a browser socket cannot carry an Authorization header and a bearer\n * must never travel in a URL.\n */\nexport interface SubscribeTicket {\n ticket: string;\n expiresAt: number;\n protocol: string;\n protocols: string[];\n}\n\nexport interface PubObjectsResult {\n /** Every non-free seat's status keyed by label (free seats omitted). */\n seats: Record<string, string>;\n hidden?: string[];\n closed?: string[];\n updatedAt: number;\n}\n\nexport interface PubChartResult {\n event: {\n key: string;\n name: string;\n status?: string;\n venue?: string | null;\n startsAt?: number | null;\n currency?: string;\n mode?: string;\n };\n doc: ChartDoc;\n}\n\nasync function parse<T>(res: Response): Promise<T> {\n const isJson = (res.headers.get('content-type') ?? '').includes('application/json');\n const data = isJson ? await res.json().catch(() => null) : null;\n if (!res.ok) {\n const err = data as {\n error?: string;\n code?: string;\n conflicts?: { label: string; reason?: string }[];\n details?: Record<string, unknown>;\n message?: string;\n } | null;\n throw new ManageApiError(\n res.status,\n err?.error ?? `request_failed_${res.status}`,\n err?.code,\n err?.conflicts,\n err?.details,\n typeof err?.message === 'string' ? err.message : undefined,\n );\n }\n return data as T;\n}\n\n/**\n * Bound to one apiBase + one event-scoped token. Rebuild (or `setToken`) when a\n * token is re-minted on 401.\n */\nexport class ManageApi {\n private base: string;\n private token: string;\n\n constructor(apiBase: string, token: string) {\n this.base = apiBase.replace(/\\/+$/, '');\n this.token = token;\n }\n\n /** Swap the Bearer token in place (SeatManager re-mints on 401). */\n setToken(token: string): void {\n this.token = token;\n }\n\n private auth<T>(\n path: string,\n init: { method?: 'GET' | 'POST' | 'PATCH' | 'DELETE'; body?: unknown } = {},\n ): Promise<T> {\n const method = init.method ?? 'GET';\n const headers: Record<string, string> = { Authorization: `Bearer ${this.token}` };\n let body: string | undefined;\n if (init.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.body);\n }\n return fetch(`${this.base}${path}`, { method, headers, body, credentials: 'omit' }).then((r) => parse<T>(r));\n }\n\n private pub<T>(path: string): Promise<T> {\n return fetch(`${this.base}${path}`, { credentials: 'omit' }).then((r) => parse<T>(r));\n }\n\n // ---- realtime read ----\n\n /** The chart geometry. Genuinely public — it is the same map buyers see. */\n chart(key: string): Promise<PubChartResult> {\n return this.pub(`/pub/events/${encodeURIComponent(key)}/chart`);\n }\n\n /**\n * The ORGANIZER's seat map: physical state, token-authed.\n *\n * This used to read `/pub/events/:key/objects` with no credential, which\n * answers with the BUYER projection — every unit the caller may not buy\n * collapses to a neutral `blocked`. An anonymous caller may buy only Public\n * sale inventory, so the cockpit rendered every channel-allocated seat as\n * blocked and then computed its KPIs, sell-through and (worse) its\n * block/unblock target sets from that. `/v1/events/:key/objects` returns the\n * unprojected snapshot the control-room read model already trusts.\n */\n objects(key: string): Promise<PubObjectsResult> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/objects`);\n }\n\n /**\n * Exchange the manage token for a one-use organizer socket ticket.\n *\n * A browser `WebSocket` cannot send an Authorization header, so the socket's\n * scope is established here, over ordinary HTTPS. Without it the DO treats a\n * manager socket as an anonymous public buyer and projects its deltas — so a\n * hold inside a private allocation is structurally suppressed and the map\n * drifts away from the truth `objects()` just established.\n *\n * Tickets are single-redemption and expire in ~30s: mint one per connect.\n */\n subscribeTicket(key: string): Promise<SubscribeTicket> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/subscribe-tickets`, { method: 'POST' });\n }\n\n socketUrl(key: string): string {\n return `${this.base.replace(/^http/, 'ws')}/pub/events/${encodeURIComponent(key)}/subscribe?surface=manager`;\n }\n\n // ---- inventory writes (token) ----\n\n /** Take FREE seats off sale in one batched call. Optional `releaseAt` (epoch\n * ms, future) auto-returns them to sale; `reason` tags the block (M3 uses it).\n * Throws ManageApiError 409 (conflicts) if any seat was just taken. */\n block(\n key: string,\n labels: string[],\n opts: { releaseAt?: number; reason?: string } = {},\n ): Promise<{ ok: true; blocked: string[] }> {\n const body: Record<string, unknown> = { labels };\n if (typeof opts.releaseAt === 'number') body.releaseAt = opts.releaseAt;\n if (opts.reason) body.reason = opts.reason;\n return this.auth(`/v1/events/${encodeURIComponent(key)}/block`, { method: 'POST', body });\n }\n\n /** Return specific blocked seats to sale (one batched call). */\n unblock(key: string, labels: string[]): Promise<{ ok: true; unblocked: string[] }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/unblock`, { method: 'POST', body: { labels } });\n }\n\n /** Return every blocked seat to sale; resolves with the freed count. */\n unblockAll(key: string): Promise<{ ok: true; freed: number }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/unblock-all`, { method: 'POST' });\n }\n\n /** Cancel bookings — return BOOKED seats to free (credit not refunded).\n * Guarded by the original booking reference. */\n unbook(key: string, labels: string[], bookingRef: string): Promise<{ ok: true; unbooked: string[] }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/unbook`, { method: 'POST', body: { labels, bookingRef } });\n }\n\n /** Set (ms, clamped 1–60 min server-side) or clear (null) the hold TTL. */\n setHoldTtl(key: string, holdTtlMs: number | null): Promise<{ ok: true; holdTtlMs: number | null }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/hold-ttl`, { method: 'POST', body: { holdTtlMs } });\n }\n\n // ---- availability windows (token) ----\n\n /** The organizer's current per section/zone availability windows (needs\n * `event:view`). Ids absent from `rules` are open / on sale. */\n availability(key: string): Promise<{ rules: Record<string, AvailabilityRule> }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/availability`);\n }\n\n /** Replace the availability windows for a set of section/zone ids (needs\n * `event:block`). Ids absent from `rules` become open / on sale; a zone rule\n * cascades to its sections. The worker derives each id's seat labels, so\n * `labels` on the sent rules is best-effort. Resolves with the authoritative\n * effective `hidden` set (a due rule may fire at once) and the server-cleaned\n * `rules` map (fired timed/threshold windows dropped). */\n setAvailability(\n key: string,\n rules: Record<string, AvailabilityRule>,\n ): Promise<{ ok: true; hidden: string[]; rules: Record<string, AvailabilityRule> }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/availability`, { method: 'POST', body: { rules } });\n }\n\n // ---- sales channels (token, capability-gated) ----\n // Reads need `event:channels:view`, mutations `event:channels:manage`.\n // `event:block` grants NEITHER (spec §10), so a Block-only cockpit token gets\n // a 403 here and Channels mode never renders.\n\n /** Allocation list with exact per-channel counts. `includeArchived` adds the\n * read-only archived rows behind the rail's \"Show archived\" control. */\n channels(key: string, opts: { includeArchived?: boolean } = {}): Promise<ChannelListResult> {\n const qs = opts.includeArchived ? '?includeArchived=1' : '';\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels${qs}`);\n }\n\n /** One page of the label → channel map that paints the allocation overlay.\n * Paged by label; follow `nextAfterLabel` until it is null. */\n channelAllocation(\n key: string,\n opts: { afterLabel?: string; limit?: number } = {},\n ): Promise<ChannelAllocationPage> {\n const params = new URLSearchParams();\n if (opts.afterLabel) params.set('afterLabel', opts.afterLabel);\n if (opts.limit != null) params.set('limit', String(opts.limit));\n const qs = params.toString();\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels/allocation${qs ? `?${qs}` : ''}`);\n }\n\n channelAudit(key: string, opts: { limit?: number; before?: number } = {}): Promise<ChannelAuditPage> {\n const params = new URLSearchParams();\n if (opts.limit != null) params.set('limit', String(opts.limit));\n if (opts.before != null) params.set('before', String(opts.before));\n const qs = params.toString();\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels/audit${qs ? `?${qs}` : ''}`);\n }\n\n createChannel(\n key: string,\n input: { name: string; color?: string | null; marker?: string | null; externalRef?: string | null },\n ): Promise<{ ok: true; channel: ChannelRecord }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels`, { method: 'POST', body: input });\n }\n\n renameChannel(key: string, channelId: string, name: string): Promise<{ ok: true; channel: ChannelRecord }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}`, {\n method: 'PATCH', body: { name },\n });\n }\n\n setChannelPaused(key: string, channelId: string, paused: boolean): Promise<{ ok: true; channel: ChannelRecord }> {\n const path = paused ? 'pause' : 'unpause';\n return this.auth(\n `/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/${path}`,\n { method: 'POST', body: {} },\n );\n }\n\n /** Archive with a mandatory destination for the remaining allocation.\n * Throws ManageApiError 409 `channel_archive_blocked_by_holds` while any hold\n * is live; `err.details` carries the exact counts + retry window. */\n archiveChannel(\n key: string,\n channelId: string,\n destination: string | null,\n ): Promise<{ ok: true; channel: ChannelRecord; assignmentVersion: number; moved: number }> {\n return this.auth(\n `/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/archive`,\n { method: 'POST', body: { destination } },\n );\n }\n\n /**\n * Versioned Apply. A stale `assignmentVersion` mutates NOTHING and throws\n * ManageApiError 409 `channel_assignment_conflict` — the caller keeps its\n * selection and offers \"Refresh and review\". There is no dry-run: the review\n * sheet previews locally, this call returns the authoritative buckets.\n */\n applyChannelAssignment(\n key: string,\n input: { targetChannelId: string | null; labels: string[]; assignmentVersion: number },\n ): Promise<AssignmentResult> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels/assignments`, {\n method: 'POST',\n body: {\n targetChannelId: input.targetChannelId || null,\n labels: input.labels,\n assignmentVersion: input.assignmentVersion,\n },\n });\n }\n\n /**\n * Read-only buyer projection for an audience (§8.6) — the SAME scoped server\n * view the buyer SDK receives, never a local approximation.\n *\n * Ships on the access-hardening branch. Older workers 404/405 here; callers\n * MUST feature-detect and quietly say the preview needs a newer server rather\n * than faking a projection client-side.\n */\n channelPreview(\n key: string,\n channelIds: string[],\n opts: { includePublic?: boolean } = {},\n ): Promise<ChannelPreviewProjection> {\n const params = new URLSearchParams();\n if (channelIds.length) params.set('channelIds', channelIds.join(','));\n if (opts.includePublic != null) params.set('includePublic', opts.includePublic ? '1' : '0');\n const qs = params.toString();\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels/preview${qs ? `?${qs}` : ''}`);\n }\n\n /**\n * Choose which sale route this channel opens.\n *\n * Since the server's 2026-08-06 change this is AUTHORIZATION, not a label:\n * exactly one of the four routes may mint buyer access for the channel and the\n * other three refuse with 409 `channel_access_intent_forbids`. The default is\n * `none`, which refuses all four — so a route has to be declared before any\n * buyer-facing action on the channel can succeed.\n *\n * Switching the route while buyers are already inside the current one is\n * refused with 409 `channel_intent_switch_blocked`, whose `details` name what\n * is live (`liveAccessLinks`, `activeSessions`). Retry with\n * `acknowledgeLiveAccess: true`: hosted links on the channel are revoked,\n * while sessions already minted keep their holds and drain on their own.\n * `intentSwitch` is present on the response ONLY when the switch disturbed\n * something, so the ordinary case stays the two-key body it has always been.\n */\n setChannelAccessIntent(\n key: string,\n channelId: string,\n accessIntent: ChannelAccessIntent,\n opts: { acknowledgeLiveAccess?: boolean; reason?: string } = {},\n ): Promise<{\n ok: true;\n channel: ChannelRecord;\n intentSwitch?: { closedLinks: number; keptSessions: number };\n }> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}`, {\n method: 'PATCH',\n body: {\n accessIntent,\n ...(opts.acknowledgeLiveAccess ? { acknowledgeLiveAccess: true } : {}),\n ...(opts.reason ? { reason: opts.reason } : {}),\n },\n });\n }\n\n // ---- hosted access links (M8) ----\n\n /**\n * Mint a hosted access link. The 201 is the ONE and ONLY time `url` and\n * `capability` exist outside the buyer's browser — SeatLayer keeps a hash, so\n * there is no route, cache, or support escalation that can produce this string\n * again. Callers must reveal it immediately and then let it go.\n *\n * Every omitted field takes the server's default: expiry = when the event\n * starts, 100 redemptions, 4 seats per buyer, this channel's allocation only.\n * Platform bounds are enforced server-side and reported as 422 with the rule\n * spelled out in `ManageApiError.serverMessage`.\n *\n * NOT a side effect any more. This used to SET the channel's access intent to\n * `hosted_link`; since 2026-08-06 it REQUIRES it, and a channel declaring any\n * other route refuses with 409 `channel_access_intent_forbids`. Callers must\n * declare the route first — `ChannelsMode` does exactly that before it\n * creates, so a first buyer link on a fresh channel is still one gesture.\n */\n createAccessLink(\n key: string,\n channelId: string,\n input: {\n label?: string | null;\n /** Absolute epoch ms. Omit for \"when the event starts\". */\n expiresAt?: number;\n maxRedemptions?: number;\n maxQuantity?: number;\n includePublic?: boolean;\n } = {},\n ): Promise<AccessLinkReveal> {\n return this.auth(\n `/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/access-links`,\n { method: 'POST', body: input },\n );\n }\n\n /** Status only — label, expiry, redemptions, per-buyer cap, lineage, and the\n * live session count. Never the url, never the capability. Needs `:view`. */\n accessLinks(key: string, channelId: string): Promise<{ links: AccessLinkStatusRecord[] }> {\n return this.auth(\n `/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/access-links`,\n );\n }\n\n /**\n * Rotate — the ONLY recovery for a link nobody kept. The old URL stops opening\n * immediately and the response is a fresh one-time reveal.\n *\n * `endActiveSessions` is REQUIRED, not defaulted: the organizer must say\n * whether buyers already inside finish their checkout or lose access now. The\n * server answers 422 `end_active_sessions_required` if it is omitted, and that\n * refusal is correct — a UI must not pick either branch on their behalf.\n */\n rotateAccessLink(\n key: string,\n channelId: string,\n linkId: string,\n endActiveSessions: boolean,\n ): Promise<AccessLinkReveal & { previous: AccessLinkRecord; endedSessions: number }> {\n return this.auth(\n `/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}`\n + `/access-links/${encodeURIComponent(linkId)}/rotate`,\n { method: 'POST', body: { endActiveSessions } },\n );\n }\n\n /** Revoke. The link stops opening immediately; `endActiveSessions` decides\n * whether the buyers already inside keep their sessions. */\n revokeAccessLink(\n key: string,\n channelId: string,\n linkId: string,\n endActiveSessions = false,\n ): Promise<{ ok: true; link: AccessLinkRecord; endedSessions: number }> {\n const qs = endActiveSessions ? '?endActiveSessions=1' : '';\n return this.auth(\n `/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}`\n + `/access-links/${encodeURIComponent(linkId)}${qs}`,\n { method: 'DELETE' },\n );\n }\n\n // ---- reports (token) ----\n\n report(key: string): Promise<ReportResult> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/report`);\n }\n\n controlRoom(key: string, windowMinutes = 15): Promise<ControlRoomSnapshot> {\n return this.auth(`/v1/events/${encodeURIComponent(key)}/control-room?window=${windowMinutes}`);\n }\n\n log(key: string, opts: { limit?: number; before?: number } = {}): Promise<LogPage> {\n const params = new URLSearchParams();\n if (opts.limit != null) params.set('limit', String(opts.limit));\n if (opts.before != null) params.set('before', String(opts.before));\n const qs = params.toString();\n return this.auth(`/v1/events/${encodeURIComponent(key)}/log${qs ? `?${qs}` : ''}`);\n }\n\n /** CSV report as a Blob (Bearer auth can't ride a plain <a href>). Host builds\n * an object URL for download. */\n async reportCsv(key: string): Promise<Blob> {\n const res = await fetch(`${this.base}/v1/events/${encodeURIComponent(key)}/report.csv`, {\n headers: { Authorization: `Bearer ${this.token}` },\n credentials: 'omit',\n });\n if (!res.ok) throw new ManageApiError(res.status, `request_failed_${res.status}`);\n return res.blob();\n }\n}\n"],"mappings":"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","names":[]}