@layers/amba-web 4.0.10 → 4.0.11
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 +11 -10
- package/dist/index.browser.d.ts +47 -4
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +240 -69
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +47 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +240 -69
- package/dist/index.js.map +1 -1
- package/dist-rn/index.d.ts +47 -4
- package/dist-rn/index.js +240 -69
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","names":["coreInit: CoreInit | undefined","coreWasmClass: CoreWasmClass | undefined","init","prefix: string","block: ContentBlock","messages: AiMessage[]","variableBlocks: ContentBlock[]","leadingBlocks: unknown[]","DEFAULT_API_URL","ctx: RealtimeContext","name: string","controller: AbortController | null","idx: number","dataLines: string[]","conversationId: string","dataLines: string[]","payload: Record<string, unknown>","resp: Response","parsed: unknown","split: { frame: string; rest: string } | null","full: OutboxOp","replayError: unknown","existing: ServiceWorkerRegistration | undefined","priorTimer","entry: PendingTimer","out: ScheduledLocalNotification[]","coreInstance: AmbaCoreWasm | null","_configuredProjectId: string | null","_configuredApiUrl: string","_configuredClientKey: string | null","_currentSessionToken: string | null","_currentRefreshToken: string | null","_currentSessionExpiresAt: string | null","_currentUser: User | null","snapshot: PersistedBrowserIdentity","_configuredFunctionsBaseUrl: string | null","_resolvedFunctionsProjectId: string | null","_functionsProjectIdInflight: Promise<string> | null","_persistence: LocalStoragePersistence | null","_offlineQueue: OfflineQueue | null","_offlineDetach: (() => void) | null","headers: Record<string, string>","body: Record<string, unknown>","resp: Response","wire: Record<string, unknown>","nextCore: AmbaCoreWasm","candidate: Record<string, unknown>","parsed: unknown","uploadHeaders: Record<string, string>","requestBody: string | undefined"],"sources":["../src/wasm-loader.ts","../src/storage-adapter.ts","../src/json.ts","../src/ai-images.ts","../src/error.ts","../src/realtime.ts","../src/ai-stream.ts","../src/resilience.ts","../src/push-rotation.ts","../src/local-notifications.ts","../src/index.ts","../src/browser.ts"],"sourcesContent":["import type { AmbaCoreWasm, InitInput, InitOutput } from \"@layers/amba-core\";\n\ntype CoreInit = (\n moduleOrPath?:\n | { module_or_path: InitInput | Promise<InitInput> }\n | InitInput\n | Promise<InitInput>,\n) => Promise<InitOutput>;\ntype CoreWasmClass = {\n init(configJson: string, fetchFn: typeof fetch): AmbaCoreWasm;\n};\n\nlet coreInit: CoreInit | undefined;\nlet coreWasmClass: CoreWasmClass | undefined;\n\nexport function setCoreRuntime(init: CoreInit, wasmClass: CoreWasmClass): void {\n coreInit = init;\n coreWasmClass = wasmClass;\n}\n\nexport async function getCoreRuntime(): Promise<{\n initCore: CoreInit;\n wasmClass: CoreWasmClass;\n}> {\n if (coreInit === undefined || coreWasmClass === undefined) {\n const core = await import(\"@layers/amba-core\");\n setCoreRuntime(core.default, core.AmbaCoreWasm);\n }\n if (coreInit === undefined || coreWasmClass === undefined) {\n throw new Error(\"Amba.configure: core WASM runtime failed to load\");\n }\n return {\n initCore: coreInit,\n wasmClass: coreWasmClass,\n };\n}\n","/**\n * Browser localStorage adapter for SDK identity persistence.\n *\n * Browsers can deny or throw on localStorage access (privacy mode, sandboxed\n * iframes, quota exhaustion). Persistence is best-effort: storage failures\n * must never turn a successful auth/network operation into an SDK failure.\n */\nexport class LocalStoragePersistence {\n constructor(private readonly prefix: string) {}\n\n private get storage(): Storage | null {\n try {\n return globalThis.localStorage ?? null;\n } catch {\n // Accessing window.localStorage itself can throw in sandboxed iframes.\n return null;\n }\n }\n\n get available(): boolean {\n return this.storage !== null;\n }\n\n read(key: string): string | null {\n const storage = this.storage;\n if (!storage) return null;\n try {\n return storage.getItem(`${this.prefix}:${key}`);\n } catch {\n return null;\n }\n }\n\n write(key: string, value: string): void {\n const storage = this.storage;\n if (!storage) return;\n try {\n storage.setItem(`${this.prefix}:${key}`, value);\n } catch {\n // Best effort — the live core remains authoritative for this page.\n }\n }\n\n delete(key: string): void {\n const storage = this.storage;\n if (!storage) return;\n try {\n storage.removeItem(`${this.prefix}:${key}`);\n } catch {\n // Best effort — see write().\n }\n }\n}\n","/**\n * JSON encode helper — guarantees stable serialization across browser\n * runtimes and provides a single hook for future schema validation.\n */\nexport function encodeJson(value: unknown): string {\n return JSON.stringify(value);\n}\n","/**\n * Vision (multimodal) image-input expansion.\n *\n * `Amba.ai.*.create({ prompt_slug, images: [...] })` is the ergonomic way to\n * send images to a vision-capable prompt: you pass vendor-neutral\n * `{ url }` / `{ data, mime }` and Amba maps them to the model's native image\n * format server-side. This helper expands the convenience `images` array into\n * vendor-neutral content blocks on the request's `messages` before it goes over\n * the wire — so the request the backend receives carries\n * `messages[].content = [{type:'text',...}, {type:'image',...}]`, which the AI\n * gateway then maps per provider.\n *\n * Behavior:\n * - No `images` → the request is returned UNCHANGED (text-only stays\n * byte-identical; not even a clone is made).\n * - `images` present → they attach to the LAST user message. If that\n * message's content is a string, it's lifted into a `{type:'text'}` block\n * first so text + images sit side by side. If there's no user message, a\n * fresh one carrying just the images is appended.\n * - The `images` field itself is stripped from the outgoing request (it's an\n * SDK convenience, not part of the backend wire contract).\n */\nimport type { AiImageInput, AiMessage, AiMessageRequest } from \"./types\";\n\ntype ContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"image\"; url?: string; data?: string; mime?: string };\n\n/**\n * Normalize an image input into a content block, applying the SAME validity\n * rule the backend's gateway + the MCP tool use: trim `url`/`data`, treat a\n * blank/whitespace-only value as absent, and return `null` when NEITHER a real\n * url nor real data survives. A `null` block is dropped by the caller so we\n * never emit an empty image — keeping the SDK, MCP, and gateway in agreement.\n */\nfunction normalizeImageBlock(img: AiImageInput): ContentBlock | null {\n const url =\n typeof img.url === \"string\" && img.url.trim().length > 0 ? img.url.trim() : undefined;\n const data =\n typeof img.data === \"string\" && img.data.trim().length > 0 ? img.data.trim() : undefined;\n if (!url && !data) return null;\n const block: ContentBlock = { type: \"image\" };\n if (url !== undefined) block.url = url;\n if (data !== undefined) block.data = data;\n if (img.mime !== undefined) block.mime = img.mime;\n return block;\n}\n\n/**\n * Expand a request's convenience `images` into content blocks on `messages`.\n * Returns the SAME object reference when there's nothing to expand, or a new\n * request (with `images` removed) when images were attached.\n */\nexport function expandAiImages(request: AiMessageRequest): AiMessageRequest {\n const images = request.images;\n if (!images || images.length === 0) return request;\n\n const imageBlocks = images\n .map(normalizeImageBlock)\n .filter((b): b is ContentBlock => b !== null);\n\n // Strip `images` from the outgoing request regardless — it's SDK-only.\n const { images: _images, ...rest } = request;\n void _images;\n if (imageBlocks.length === 0) return rest;\n\n const messages: AiMessage[] = (rest.messages ?? []).map((m) => ({ ...m }));\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]!.role === \"user\") {\n lastUserIdx = i;\n break;\n }\n }\n if (lastUserIdx === -1) {\n // No user message to attach to. Synthesize one — but DON'T drop the\n // caller's template `variables`: the backend prefers `messages` over\n // `variables` (mapped to `context`), so an images-only message would\n // silently stop the prompt's variable substitution from applying. Carry\n // the variables as a leading text block (mirroring how the backend folds\n // `context` into a user message) so images AND variables both take effect.\n const variableBlocks: ContentBlock[] =\n rest.variables && Object.keys(rest.variables).length > 0\n ? [{ type: \"text\", text: JSON.stringify(rest.variables) }]\n : [];\n messages.push({ role: \"user\", content: [...variableBlocks, ...imageBlocks] });\n } else {\n const existing = messages[lastUserIdx]!.content;\n // Preserve the existing content WITHOUT data loss when attaching images:\n // - string → kept as a text block.\n // - array → its blocks are kept as-is.\n // - any other JSON value (object/number/etc.) → coerced to a text block\n // via JSON.stringify so it survives alongside the images instead of\n // being silently dropped. `null`/`undefined` contribute nothing.\n let leadingBlocks: unknown[];\n if (typeof existing === \"string\") {\n leadingBlocks = [{ type: \"text\", text: existing }];\n } else if (Array.isArray(existing)) {\n leadingBlocks = existing;\n } else if (existing === null || existing === undefined) {\n leadingBlocks = [];\n } else {\n leadingBlocks = [{ type: \"text\", text: JSON.stringify(existing) }];\n }\n messages[lastUserIdx] = {\n ...messages[lastUserIdx]!,\n content: [...leadingBlocks, ...imageBlocks],\n };\n }\n return { ...rest, messages };\n}\n","/**\n * Typed error class for the amba SDK + helper that converts arbitrary\n * thrown values (native Error, plain objects, runtime-emitted errors)\n * into a stable `AmbaApiError` instance.\n *\n * Callers can `instanceof`-narrow safely, branch on `.code`, and pull\n * extra payload from `.details` (raw HTTP body, validation field paths,\n * etc.). The wrapper methods on `Amba` wrap every async call in\n * `toAmbaApiError`, so consumers see one error shape regardless of\n * which layer (network, runtime, validation) failed.\n */\n\n/**\n * Stable error codes surfaced by the SDK. Strings rather than an enum\n * so customers can extend with custom codes by simply throwing\n * `new AmbaApiError(\"MY_CODE\", \"...\")` without coordinating with this\n * package's TypeScript types.\n */\nexport type AmbaApiErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"VALIDATION_ERROR\"\n | \"NETWORK_ERROR\"\n | \"HTTP_ERROR\"\n | \"CIRCUIT_OPEN\"\n | \"CONSENT_NOT_GRANTED\"\n | \"NOT_INITIALIZED\"\n | \"INVALID_CONFIG\"\n | \"INVALID_ARGUMENT\"\n | \"PUSH_PERMISSION_DENIED\"\n | \"PUSH_REGISTRATION_FAILED\"\n | \"LOCAL_NOTIFICATIONS_UNAVAILABLE\"\n | \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\"\n | \"NOT_SANDBOX_TIER\"\n | \"UNKNOWN_ERROR\"\n | (string & {});\n\nexport class AmbaApiError extends Error {\n readonly code: AmbaApiErrorCode;\n readonly details?: unknown;\n /**\n * Server-side request / trace id (the `X-Amba-Request-Id` response\n * header, falling back to the `request_id` field in the standard error\n * envelope). Present on errors thrown by `Amba.functions.invoke()` so a\n * failed run can be correlated with server logs\n * (`functions logs --request-id <traceId>`). `undefined` when the failure\n * happened before a response was received (e.g. a transport error).\n */\n traceId?: string;\n constructor(\n code: AmbaApiErrorCode,\n message: string,\n details?: unknown,\n ) {\n super(message);\n this.name = \"AmbaApiError\";\n this.code = code;\n this.details = details;\n // Restore prototype chain when transpiled to ES5 targets — Error\n // subclasses lose their prototype otherwise and `instanceof` breaks.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Best-effort: pull a stable error code out of an `AmbaError` display\n * string. Used by `toAmbaApiError` when the underlying thrown value is a\n * generic `Error` produced by the SDK core.\n *\n * Keep in sync with the SDK core's `AmbaError` format strings — but the\n * parser falls back to UNKNOWN_ERROR for any pattern it doesn't recognize,\n * so a stale match list isn't a footgun, just a quality-of-life regression.\n */\nfunction codeFromMessage(message: string): AmbaApiErrorCode {\n // Cheapest checks first — most production errors are auth or HTTP.\n if (message.startsWith(\"Unauthorized\")) return \"UNAUTHORIZED\";\n if (message.startsWith(\"Forbidden\")) return \"FORBIDDEN\";\n if (message.startsWith(\"Not found\")) return \"NOT_FOUND\";\n if (message.startsWith(\"Conflict\")) return \"CONFLICT\";\n if (message.startsWith(\"Rate limited\")) return \"RATE_LIMITED\";\n if (message.startsWith(\"Validation error\")) return \"VALIDATION_ERROR\";\n if (message.startsWith(\"Network error\")) return \"NETWORK_ERROR\";\n if (message.startsWith(\"HTTP error\")) return \"HTTP_ERROR\";\n if (message.startsWith(\"Circuit breaker\")) return \"CIRCUIT_OPEN\";\n if (message.startsWith(\"Consent not granted\")) return \"CONSENT_NOT_GRANTED\";\n if (message.includes(\"not initialized\")) return \"NOT_INITIALIZED\";\n if (message.startsWith(\"Invalid configuration\")) return \"INVALID_CONFIG\";\n if (message.startsWith(\"Invalid argument\")) return \"INVALID_ARGUMENT\";\n return \"UNKNOWN_ERROR\";\n}\n\n/**\n * Coerce any thrown value into an `AmbaApiError`. Idempotent — passing\n * an existing `AmbaApiError` returns it unchanged so wrappers can stack\n * without re-wrapping.\n *\n * Heuristics:\n * 1. Existing `AmbaApiError` → returned as-is.\n * 2. Object with `.code` → use that code + `.message` + `.details`.\n * 3. `Error` (e.g. runtime-emitted error) → infer code from message prefix.\n * 4. Anything else → stringify and tag UNKNOWN_ERROR.\n */\nexport function toAmbaApiError(err: unknown): AmbaApiError {\n if (err instanceof AmbaApiError) return err;\n if (err && typeof err === \"object\") {\n const obj = err as { code?: unknown; message?: unknown; details?: unknown };\n if (typeof obj.code === \"string\") {\n return new AmbaApiError(\n obj.code,\n typeof obj.message === \"string\" ? obj.message : \"unknown\",\n obj.details,\n );\n }\n }\n if (err instanceof Error) {\n return new AmbaApiError(codeFromMessage(err.message), err.message);\n }\n return new AmbaApiError(\"UNKNOWN_ERROR\", String(err));\n}\n\n/**\n * Wrap a thunk so any thrown value becomes an `AmbaApiError`. The\n * wrapper methods on `Amba` use this to convert runtime-emitted errors\n * uniformly at the SDK boundary.\n */\nexport async function withAmbaError<T>(thunk: () => Promise<T>): Promise<T> {\n try {\n return await thunk();\n } catch (err) {\n throw toAmbaApiError(err);\n }\n}\n","/**\n * Live collection subscriptions for `@layers/amba-web` (PROTOTYPE — T13).\n *\n * Public surface is intentionally tiny and provider-agnostic:\n *\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op is \"INSERT\" | \"UPDATE\" | \"DELETE\"\n * // change.row is the affected row\n * });\n * unsub(); // stop listening\n *\n * The transport (a streamed HTTP response) and the server-side fan-out\n * mechanism are deliberately NOT named here — customers only ever see\n * `.subscribe()`. Internally this opens a long-lived streamed GET to the\n * realtime endpoint and reconnects with backoff if the connection drops.\n *\n * Delivery mirrors `Amba.collection.find()` exactly: you receive only rows\n * your session owns, optionally narrowed by `.where(...)` equality filters.\n */\n\nexport type RowChangeOp = \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\nexport interface RealtimeChange<T = unknown> {\n /** Which kind of write happened. */\n op: RowChangeOp;\n /**\n * The affected row. For deletes this is a partial row (at minimum `id`).\n * If the row was too large to deliver inline, `truncated` is `true` and\n * `row` carries just `id` — re-fetch with `Amba.collection.find()` /\n * `findOne()` when you need the full body.\n */\n row: T;\n truncated?: boolean;\n}\n\nexport interface SubscribeOptions {\n /** Called once when the live connection is (re)established. */\n onOpen?: () => void;\n /** Called on a connection error before the SDK retries. */\n onError?: (err: unknown) => void;\n}\n\n/** Unsubscribe handle returned by `.subscribe(...)`. Call it to stop. */\nexport type Unsubscribe = () => void;\n\n/**\n * Resolved at call time so the latest session token is used. Returns the\n * auth headers, or `null` when there's no live session (subscribe throws).\n */\nexport interface RealtimeContext {\n baseUrl: string;\n authHeaders: () => Record<string, string> | null;\n}\n\nconst INITIAL_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 15_000;\n\nconst DEFAULT_API_URL = \"https://api.amba.dev\";\nconst DEFAULT_REALTIME_URL = \"https://realtime.amba.host\";\n\n/**\n * Resolve the base URL for live-update connections. Long-lived streams are\n * served from a dedicated realtime host (no request-duration ceiling), so\n * when the SDK points at the default production API the stream connects\n * there instead. Any custom `apiUrl` (dev, staging, self-hosted, proxy)\n * keeps live updates on that same origin — override semantics unchanged.\n */\nexport function resolveRealtimeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\") === DEFAULT_API_URL ? DEFAULT_REALTIME_URL : baseUrl;\n}\n\n/**\n * Builder returned by `Amba.collection(name)`. Chain `.where(...)` to\n * narrow, then `.subscribe(cb)` to start streaming.\n */\nexport class CollectionSubscription<T = unknown> {\n private readonly filters: Record<string, unknown> = {};\n\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly name: string,\n ) {}\n\n /** Add an equality filter. Chainable. All filters must match (AND). */\n where(column: string, value: unknown): this {\n this.filters[column] = value;\n return this;\n }\n\n /**\n * Start listening. `cb` fires once per matching row change. Returns an\n * unsubscribe handle — call it to close the connection.\n */\n subscribe(cb: (change: RealtimeChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.\",\n );\n }\n const qs =\n Object.keys(this.filters).length > 0\n ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}`\n : \"\";\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${encodeURIComponent(\n this.name,\n )}${qs}`;\n return openStream<T>({\n url,\n authHeaders: this.ctx.authHeaders,\n onChange: cb,\n options,\n });\n }\n}\n\n/**\n * Inputs to a single realtime stream. The collection and conversation paths\n * differ only in how `url` is built; everything downstream (transport, frame\n * parsing, backoff/reconnect) is identical, so they share one implementation.\n */\ninterface StreamArgs<T> {\n url: string;\n authHeaders: () => Record<string, string> | null;\n onChange: (change: RealtimeChange<T>) => void;\n options: SubscribeOptions;\n}\n\n/**\n * Open a streamed GET to a realtime endpoint and dispatch row-change events\n * to `onChange`. Reconnects with exponential backoff until the returned\n * unsubscribe handle is invoked. Shared by both collection and conversation\n * subscriptions — the only thing that differs between them is the URL.\n *\n * Uses `fetch` + a streamed response body (rather than the browser's\n * native `EventSource`) because `EventSource` cannot set request headers —\n * which would force the session token into the URL query string, where it\n * would leak into access logs. Streaming `fetch` keeps auth in headers.\n */\nfunction openStream<T>(args: StreamArgs<T>): Unsubscribe {\n let stopped = false;\n let controller: AbortController | null = null;\n let backoff = INITIAL_BACKOFF_MS;\n\n const run = async (): Promise<void> => {\n while (!stopped) {\n controller = new AbortController();\n try {\n const headers = args.authHeaders();\n if (!headers) throw new Error(\"no active session\");\n const resp = await fetch(args.url, {\n method: \"GET\",\n headers: { ...headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\n });\n if (!resp.ok || !resp.body) {\n throw new Error(`realtime connect failed: HTTP ${resp.status}`);\n }\n // Reset backoff only once the connection proves HEALTHY (first\n // frame received), not merely on a successful connect. A server\n // that accepts the connection then drops it immediately would\n // otherwise reset backoff every loop → a tight reconnect storm\n // with no exponential growth.\n await consume(resp.body, args, () => {\n backoff = INITIAL_BACKOFF_MS;\n args.options.onOpen?.();\n });\n } catch (err) {\n if (stopped) break;\n args.options.onError?.(err);\n }\n if (stopped) break;\n await delay(backoff);\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);\n }\n };\n\n void run();\n\n return () => {\n stopped = true;\n controller?.abort();\n };\n}\n\nasync function consume<T>(\n body: ReadableStream<Uint8Array>,\n args: StreamArgs<T>,\n onFirstFrame: () => void,\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let sawFrame = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n onFirstFrame();\n }\n dispatchFrame<T>(frame, args.onChange);\n }\n }\n}\n\nfunction dispatchFrame<T>(frame: string, onChange: (change: RealtimeChange<T>) => void): void {\n let event = \"\";\n const dataLines: string[] = [];\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n if (line.startsWith(\"event:\")) event = line.slice(\"event:\".length).trim();\n else if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trim());\n }\n if (event !== \"change\") return; // ignore ready / error / heartbeat\n try {\n const payload = JSON.parse(dataLines.join(\"\\n\")) as {\n op: RowChangeOp;\n row: T;\n truncated?: boolean;\n };\n onChange({ op: payload.op, row: payload.row, truncated: payload.truncated });\n } catch {\n // malformed frame — skip rather than tear down the stream\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ── Messaging: live conversation subscriptions ─────────────────────────────\n\n/**\n * A live message change in a conversation. `op` is \"INSERT\" for a new\n * message; moderation removals arrive as \"DELETE\". For a delete (or when a\n * message was too large to deliver inline) `row` may be partial — re-fetch\n * with `Amba.messaging.listMessages(conversationId)` when you need the full\n * body.\n */\nexport type MessageChange<T = unknown> = RealtimeChange<T>;\n\n/**\n * Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`\n * to receive new messages live instead of polling.\n *\n * const unsub = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => {\n * // change.op === \"INSERT\"; change.row is the new message\n * });\n * unsub(); // stop listening\n *\n * Delivery is scoped to conversation participants — you receive messages for\n * a conversation only if your session is a member of it. Reconnects with the\n * same exponential backoff as collection subscriptions.\n */\nexport class ConversationSubscription<T = unknown> {\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly conversationId: string,\n ) {}\n\n subscribe(cb: (change: MessageChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${encodeURIComponent(\n this.conversationId,\n )}`;\n return openStream<T>({\n url,\n authHeaders: this.ctx.authHeaders,\n onChange: cb,\n options,\n });\n }\n}\n\n// ── Gamification: live xp / level / achievement / streak updates ────────────\n\n/** Which slice of the signed-in user's gamification state changed. */\nexport type GamificationKind = \"xp\" | \"level\" | \"achievement\" | \"streak\";\n\n/**\n * A live change to the signed-in user's gamification state. `kind` says which\n * slice moved; `data` carries the new state inline so you can update without a\n * re-fetch:\n * - `xp` — `{ total_xp, level, delta }`\n * - `level` — `{ level, total_xp }`\n * - `achievement` — `{ achievement_id, unlocked }`\n * - `streak` — `{ streak_id, count, status, ... }`\n *\n * If a payload was too large to deliver inline, `truncated` is `true` and `data`\n * is empty — re-fetch with `Amba.xp.getBalance()` / `Amba.streaks.getAll()` etc.\n */\nexport interface GamificationChange<T = Record<string, unknown>> {\n kind: GamificationKind;\n data: T;\n truncated?: boolean;\n}\n\n/**\n * Builder returned by `Amba.gamification`. Call `.subscribe(cb)` to receive the\n * signed-in user's xp / level / achievement / streak changes live instead of\n * polling.\n *\n * const unsub = Amba.gamification.subscribe((change) => {\n * // change.kind: \"xp\" | \"level\" | \"achievement\" | \"streak\"\n * // change.data: the new state\n * });\n * unsub(); // stop listening\n *\n * Delivery is scoped to the signed-in user — you only ever receive your own\n * gamification changes, never another user's. Reconnects with the same\n * exponential backoff as collection subscriptions.\n */\nexport class GamificationSubscription {\n constructor(private readonly ctx: RealtimeContext) {}\n\n subscribe(\n cb: (change: GamificationChange) => void,\n options: SubscribeOptions = {},\n ): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.gamification.subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/gamification`;\n return openGamificationStream({\n url,\n authHeaders: this.ctx.authHeaders,\n onChange: cb,\n options,\n });\n }\n}\n\ninterface GamificationStreamArgs {\n url: string;\n authHeaders: () => Record<string, string> | null;\n onChange: (change: GamificationChange) => void;\n options: SubscribeOptions;\n}\n\n/**\n * Open a streamed GET to the gamification realtime endpoint. Identical\n * transport + backoff/reconnect to {@link openStream}; only the frame payload\n * shape differs (gamification carries `kind`, not `op`/`row`), so it gets its\n * own thin consumer rather than overloading the row-change parser.\n */\nfunction openGamificationStream(args: GamificationStreamArgs): Unsubscribe {\n let stopped = false;\n let controller: AbortController | null = null;\n let backoff = INITIAL_BACKOFF_MS;\n\n const run = async (): Promise<void> => {\n while (!stopped) {\n controller = new AbortController();\n try {\n const headers = args.authHeaders();\n if (!headers) throw new Error(\"no active session\");\n const resp = await fetch(args.url, {\n method: \"GET\",\n headers: { ...headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\n });\n if (!resp.ok || !resp.body) {\n throw new Error(`realtime connect failed: HTTP ${resp.status}`);\n }\n await consumeGamification(resp.body, args, () => {\n backoff = INITIAL_BACKOFF_MS;\n args.options.onOpen?.();\n });\n } catch (err) {\n if (stopped) break;\n args.options.onError?.(err);\n }\n if (stopped) break;\n await delay(backoff);\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);\n }\n };\n\n void run();\n\n return () => {\n stopped = true;\n controller?.abort();\n };\n}\n\nasync function consumeGamification(\n body: ReadableStream<Uint8Array>,\n args: GamificationStreamArgs,\n onFirstFrame: () => void,\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let sawFrame = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n onFirstFrame();\n }\n dispatchGamificationFrame(frame, args.onChange);\n }\n }\n}\n\nfunction dispatchGamificationFrame(\n frame: string,\n onChange: (change: GamificationChange) => void,\n): void {\n let event = \"\";\n const dataLines: string[] = [];\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n if (line.startsWith(\"event:\")) event = line.slice(\"event:\".length).trim();\n else if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trim());\n }\n if (event !== \"change\") return; // ignore ready / error / heartbeat\n try {\n const payload = JSON.parse(dataLines.join(\"\\n\")) as {\n kind: GamificationKind;\n data: Record<string, unknown>;\n truncated?: boolean;\n };\n onChange({ kind: payload.kind, data: payload.data, truncated: payload.truncated });\n } catch {\n // malformed frame — skip rather than tear down the stream\n }\n}\n","/**\n * Client-side consumption of a streamed (SSE) AI response for\n * `@layers/amba-web`.\n *\n * The buffered `Amba.ai.anthropic.messages.create()` decodes a single JSON\n * envelope and therefore can't surface tokens as they arrive. The streaming\n * methods (`Amba.ai.anthropic.messages.stream()` /\n * `Amba.ai.openai.chat.completions.stream()`) open the same backend route with\n * `stream: true` and read the response body incrementally, yielding text\n * deltas the moment the model emits them plus a final `done` event carrying\n * token usage.\n *\n * The backend pipes the *vendor* SSE through verbatim (its server-side tee\n * still records usage/cost), so this parser understands both wire shapes:\n *\n * - Anthropic Messages SSE: `event: content_block_delta` frames with a\n * `{delta:{type:'text_delta', text}}` payload; usage arrives split across\n * the initial `message_start` (input tokens) and the final `message_delta`\n * (output tokens); the stream ends with `message_stop`.\n * - OpenAI Chat Completions SSE: `data:` frames with\n * `{choices:[{delta:{content}}]}`; usage (when the caller opts in via\n * `stream_options.include_usage`) arrives on the last data frame; the\n * stream ends with a literal `data: [DONE]`.\n *\n * Both are line-delimited SSE framed on a blank line (`\\n\\n`), matching the\n * realtime transport's reader. We surface one vendor-neutral event union so\n * callers don't branch on provider.\n */\n\nimport { AmbaApiError } from \"./error\";\nimport type { AiUsage } from \"./types\";\n\n/** A streamed token delta — append `text` to your running output buffer. */\nexport interface AiStreamDelta {\n type: \"delta\";\n /** The incremental text the model just produced. */\n text: string;\n}\n\n/**\n * Terminal event. Emitted once when the stream completes cleanly. `usage`\n * carries the token accounting accumulated across the stream (zeroed fields\n * when the vendor didn't report a given counter); `model` / `stopReason` are\n * populated when the vendor surfaced them.\n */\nexport interface AiStreamDone {\n type: \"done\";\n usage: AiUsage;\n model?: string;\n stopReason?: string | null;\n}\n\n/** Vendor-neutral event yielded by the streaming AI methods. */\nexport type AiStreamEvent = AiStreamDelta | AiStreamDone;\n\n/** Options accepted by the streaming AI methods. */\nexport interface AiStreamOptions {\n /**\n * Optional callback invoked for every text delta as it arrives — a\n * convenience over iterating, useful when you only care about appending\n * tokens to the UI. Fires in addition to the yielded `delta` events.\n */\n onChunk?: (text: string) => void;\n}\n\nexport interface InvokeAiStreamArgs {\n /** Full URL of the backend streaming route. */\n url: string;\n /** Auth + content headers (X-Api-Key, optional Bearer, content-type). */\n headers: Record<string, string>;\n /** JSON request body (already includes `stream: true`). */\n body: string;\n /** Forwarded `onChunk` callback. */\n onChunk?: (text: string) => void;\n}\n\n/**\n * Mutable accumulator threaded through frame parsing so usage reported across\n * multiple SSE frames (Anthropic splits input/output across `message_start`\n * and `message_delta`) is merged into one final `AiUsage`.\n */\ninterface UsageAccumulator {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n model?: string;\n stopReason?: string | null;\n}\n\nfunction newUsageAccumulator(): UsageAccumulator {\n return {\n input_tokens: 0,\n output_tokens: 0,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n };\n}\n\nfunction toAiUsage(acc: UsageAccumulator): AiUsage {\n return {\n input_tokens: acc.input_tokens,\n output_tokens: acc.output_tokens,\n cache_creation_input_tokens: acc.cache_creation_input_tokens,\n cache_read_input_tokens: acc.cache_read_input_tokens,\n };\n}\n\n/** Read a numeric field defensively (vendors occasionally send null). */\nfunction num(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) ? v : 0;\n}\n\n/**\n * Merge a vendor `usage`-shaped object into the accumulator. Handles both\n * Anthropic (`input_tokens` / `output_tokens` / `cache_*`) and OpenAI\n * (`prompt_tokens` / `completion_tokens`) field names, taking the max so a\n * later frame that re-reports a counter doesn't shrink it.\n */\nfunction mergeUsage(acc: UsageAccumulator, usage: Record<string, unknown>): void {\n const input = usage[\"input_tokens\"] ?? usage[\"prompt_tokens\"];\n const output = usage[\"output_tokens\"] ?? usage[\"completion_tokens\"];\n if (input !== undefined) acc.input_tokens = Math.max(acc.input_tokens, num(input));\n if (output !== undefined) acc.output_tokens = Math.max(acc.output_tokens, num(output));\n if (usage[\"cache_creation_input_tokens\"] !== undefined) {\n acc.cache_creation_input_tokens = Math.max(\n acc.cache_creation_input_tokens,\n num(usage[\"cache_creation_input_tokens\"]),\n );\n }\n if (usage[\"cache_read_input_tokens\"] !== undefined) {\n acc.cache_read_input_tokens = Math.max(\n acc.cache_read_input_tokens,\n num(usage[\"cache_read_input_tokens\"]),\n );\n }\n}\n\n/**\n * Parse one SSE frame (everything between blank lines) and return the text\n * delta it carries, if any. Side-effect: folds any usage / model / stop\n * metadata into `acc`. Returns `null` for keepalive comments, the `[DONE]`\n * sentinel, and frames with no text (e.g. Anthropic's `message_start`).\n *\n * Tolerant by design — a malformed `data:` payload is skipped rather than\n * tearing down the stream.\n */\nexport function parseAiSseFrame(frame: string, acc: UsageAccumulator): string | null {\n const dataLines: string[] = [];\n // Split on any line ending (\\r\\n, \\n, or a bare \\r) so a CRLF wire shape\n // (some servers/proxies emit it) doesn't leave a trailing \\r on each field.\n for (const line of frame.split(/\\r\\n|\\r|\\n/)) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n // `event:` lines are advisory — the JSON payload's own `type` is what we\n // branch on, so we only need the `data:` lines.\n if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trimStart());\n }\n if (dataLines.length === 0) return null;\n const data = dataLines.join(\"\\n\").trim();\n if (data === \"\" || data === \"[DONE]\") return null; // OpenAI terminal sentinel\n\n let payload: Record<string, unknown>;\n try {\n payload = JSON.parse(data) as Record<string, unknown>;\n } catch {\n return null; // malformed frame — skip\n }\n\n const type = typeof payload[\"type\"] === \"string\" ? (payload[\"type\"] as string) : \"\";\n\n // ── Anthropic shapes ───────────────────────────────────────────────\n if (type === \"content_block_delta\") {\n const delta = payload[\"delta\"] as Record<string, unknown> | undefined;\n const text = delta?.[\"text\"];\n return typeof text === \"string\" ? text : null;\n }\n if (type === \"message_start\") {\n const message = payload[\"message\"] as Record<string, unknown> | undefined;\n const usage = message?.[\"usage\"];\n if (usage && typeof usage === \"object\") mergeUsage(acc, usage as Record<string, unknown>);\n if (typeof message?.[\"model\"] === \"string\") acc.model = message[\"model\"] as string;\n return null;\n }\n if (type === \"message_delta\") {\n const usage = payload[\"usage\"];\n if (usage && typeof usage === \"object\") mergeUsage(acc, usage as Record<string, unknown>);\n const delta = payload[\"delta\"] as Record<string, unknown> | undefined;\n if (typeof delta?.[\"stop_reason\"] === \"string\") acc.stopReason = delta[\"stop_reason\"] as string;\n return null;\n }\n if (type === \"message_stop\") return null;\n\n // ── OpenAI Chat Completions shape ──────────────────────────────────\n // Frames carry `choices[0].delta.content`; the final frame (when the caller\n // opted into `stream_options.include_usage`) carries top-level `usage`.\n if (Array.isArray(payload[\"choices\"])) {\n const choices = payload[\"choices\"] as Array<Record<string, unknown>>;\n if (typeof payload[\"model\"] === \"string\") acc.model = payload[\"model\"] as string;\n const usage = payload[\"usage\"];\n if (usage && typeof usage === \"object\") mergeUsage(acc, usage as Record<string, unknown>);\n const first = choices[0];\n if (first) {\n const finish = first[\"finish_reason\"];\n if (typeof finish === \"string\") acc.stopReason = finish;\n const delta = first[\"delta\"] as Record<string, unknown> | undefined;\n const content = delta?.[\"content\"];\n if (typeof content === \"string\" && content.length > 0) return content;\n }\n return null;\n }\n\n return null;\n}\n\n/**\n * SSE event-frame delimiter: a blank line. The spec permits any line ending\n * (`\\r\\n`, `\\n`, `\\r`), so a blank line is two consecutive line breaks in any\n * combination — `\\r\\n\\r\\n`, `\\n\\n`, or `\\r\\r`. Some servers / proxies emit\n * CRLF, so splitting on `\\n\\n` alone would never frame those streams.\n */\nconst SSE_FRAME_DELIMITER = /\\r\\n\\r\\n|\\n\\n|\\r\\r/;\n\n/**\n * Pull the next complete SSE frame off the front of `buffer`, returning the\n * frame text (without the trailing blank-line delimiter) and the remaining\n * buffer, or `null` when no complete frame is present yet.\n *\n * Only *complete* delimiters match: a buffer ending mid-delimiter (e.g.\n * `…\\r\\n\\r`, awaiting the final `\\n`) leaves the partial bytes buffered for the\n * next read rather than framing prematurely.\n */\nexport function splitNextSseFrame(buffer: string): { frame: string; rest: string } | null {\n const m = SSE_FRAME_DELIMITER.exec(buffer);\n if (!m) return null;\n return {\n frame: buffer.slice(0, m.index),\n rest: buffer.slice(m.index + m[0].length),\n };\n}\n\n/**\n * Open the streaming route and yield vendor-neutral events. Throws an\n * `AmbaApiError` on a transport failure or a non-2xx response (the error body\n * is read and surfaced just like the buffered path).\n *\n * The generator yields zero or more `{type:'delta'}` events followed by\n * exactly one terminal `{type:'done'}` when the stream completes cleanly.\n */\nexport async function* invokeAiStream(\n args: InvokeAiStreamArgs,\n): AsyncGenerator<AiStreamEvent, void, unknown> {\n let resp: Response;\n try {\n resp = await fetch(args.url, {\n method: \"POST\",\n headers: args.headers,\n body: args.body,\n });\n } catch (err) {\n throw new AmbaApiError(\"NETWORK_ERROR\", `AI stream network error: ${String(err)}`);\n }\n\n if (!resp.ok) {\n // Mirror the buffered path's error mapping: read the body, lift the\n // `{error:{code,message}}` envelope when present.\n const text = await resp.text().catch(() => \"\");\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\" ? envelope.error.code : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : typeof parsed === \"string\" && parsed.length > 0\n ? parsed\n : `AI stream failed with status ${resp.status}`;\n throw new AmbaApiError(code, message, parsed);\n }\n\n if (!resp.body) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n \"AI stream response had no readable body (the environment does not expose a streaming fetch body).\",\n );\n }\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n const acc = newUsageAccumulator();\n let buffer = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (value) buffer += decoder.decode(value, { stream: true });\n if (done) {\n // Final flush: drain any bytes the streaming decoder is still holding\n // (a multi-byte UTF-8 char split across the last two reads, or a\n // trailing partial sequence). Without it those bytes are silently\n // dropped at EOF. `decode()` with no args ends the stream.\n buffer += decoder.decode();\n }\n let split: { frame: string; rest: string } | null;\n while ((split = splitNextSseFrame(buffer)) !== null) {\n buffer = split.rest;\n const text = parseAiSseFrame(split.frame, acc);\n if (text !== null && text.length > 0) {\n args.onChunk?.(text);\n yield { type: \"delta\", text };\n }\n }\n if (done) {\n // Flush a trailing frame that wasn't terminated by a blank line.\n const tail = buffer.trim();\n if (tail.length > 0) {\n const text = parseAiSseFrame(tail, acc);\n if (text !== null && text.length > 0) {\n args.onChunk?.(text);\n yield { type: \"delta\", text };\n }\n }\n break;\n }\n }\n } finally {\n // Best-effort release; a thrown consumer (e.g. `break` in a `for await`)\n // shouldn't leak the lock.\n try {\n reader.releaseLock();\n } catch {\n // already released\n }\n }\n\n yield {\n type: \"done\",\n usage: toAiUsage(acc),\n model: acc.model,\n stopReason: acc.stopReason ?? null,\n };\n}\n","/**\n * Offline mutation queue + optimistic-update helpers — framework-agnostic\n * orchestration that sits ABOVE the collection / event write methods.\n *\n * Nothing here touches the SDK runtime directly: the queue is handed a\n * small `KvStore` (durable key/value persistence) and a `WriteExecutor`\n * (the function that actually performs a queued mutation against the API).\n * That keeps the same code identical across every JS SDK target — only the\n * adapters that supply storage + the executor differ per platform.\n *\n * Design:\n * - A write that fails because the device is offline (a transport-level\n * network error, or `navigator.onLine === false`) is appended to a\n * durable outbox instead of throwing all the way up.\n * - Each queued op carries a stable idempotency key. On replay the key is\n * forwarded to the API's collection-write idempotency support so a\n * mutation that actually reached the server before the connection\n * dropped is never applied twice.\n * - On reconnect (`online` event) the outbox is flushed in FIFO order;\n * each op is removed only after it succeeds, so a mid-flush failure\n * leaves the remainder intact for the next attempt.\n */\n\n/**\n * Minimal durable key/value contract the outbox persists through. Both the\n * browser `localStorage` adapter and the React Native AsyncStorage adapter\n * satisfy this — the queue never assumes anything else.\n */\nexport interface KvStore {\n get(key: string): Promise<string | null> | string | null;\n set(key: string, value: string): Promise<void> | void;\n delete(key: string): Promise<void> | void;\n}\n\n/** The kinds of mutation the outbox can durably replay. */\nexport type OutboxOpKind =\n | \"collection.insert\"\n | \"collection.update\"\n | \"collection.delete\"\n | \"track\";\n\n/**\n * A single durably-queued mutation. `idempotencyKey` is reused verbatim on\n * every replay attempt so the server dedupes a COLLECTION write that landed\n * before the connection dropped (the events `track` API has no idempotency\n * channel, so `track` replays are at-least-once — a rare reconnect duplicate\n * is acceptable for fire-and-forget analytics). `enqueuedAt` is an ISO\n * timestamp for inspection.\n */\nexport interface OutboxOp {\n /** Stable per-op id (the idempotency key for collection writes). */\n idempotencyKey: string;\n kind: OutboxOpKind;\n /** Target collection name (absent for `track`). */\n collection?: string;\n /** Row id for update / delete. */\n id?: string;\n /** Insert row / update set payload. */\n payload?: Record<string, unknown>;\n /** Event name for `track`. */\n event?: string;\n /** Event properties for `track`. */\n properties?: Record<string, unknown>;\n /** Whether the tracked event was telemetry-tier. */\n telemetry?: boolean;\n /**\n * ISO timestamp the `track` originally OCCURRED, captured at enqueue. Replayed\n * as the events body `timestamp` (→ `occurred_at`) so a buffered event keeps\n * its real time instead of being re-stamped at flush. (Attribution is by the\n * session user at replay time — the events route has no body-id channel.)\n */\n occurredAt?: string;\n enqueuedAt: string;\n}\n\n/**\n * Performs one queued mutation against the live API. Implemented per-SDK so\n * the queue stays platform-agnostic. MUST forward `op.idempotencyKey` on\n * collection INSERTS (the API dedupes inserts on `(collection,\n * idempotency_key)`); `track` replays carry no idempotency channel and are\n * at-least-once. Throws on failure — the queue decides whether to retain or\n * drop the op.\n */\nexport type WriteExecutor = (op: OutboxOp) => Promise<void>;\n\n/** Result of a {@link OfflineQueue.flush}. */\nexport interface FlushResult {\n /** Ops successfully replayed and removed from the outbox. */\n flushed: number;\n /**\n * Ops dropped because their replay failed deterministically (a coded server\n * rejection like `VALIDATION_ERROR` / `NOT_FOUND` that would fail identically\n * forever). Dropping them prevents one poisoned op from wedging the queue;\n * the drain continues past it.\n */\n dropped: number;\n /** Ops still pending after the flush (a replay failed and stopped it). */\n remaining: number;\n}\n\n/**\n * Best-effort online probe. In a browser/RN runtime `navigator.onLine`\n * exists; under Node / SSR it doesn't, so we treat \"unknown\" as online\n * (the executor's own error handling then decides).\n */\nexport function isOnline(): boolean {\n const nav =\n typeof navigator !== \"undefined\"\n ? (navigator as { onLine?: boolean })\n : undefined;\n if (nav && typeof nav.onLine === \"boolean\") return nav.onLine;\n return true;\n}\n\n/**\n * Error codes the server raised deterministically — replaying them would fail\n * identically, so they're NEVER queued even when the device reports offline.\n * (A request can fail validation/auth/etc. while `navigator.onLine` is briefly\n * false; we must not buffer-and-retry those forever.)\n */\nconst DETERMINISTIC_ERROR_CODES = new Set([\n \"VALIDATION_ERROR\",\n \"INVALID_ARGUMENT\",\n \"INVALID_INPUT\",\n \"UNAUTHORIZED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"CONSENT_NOT_GRANTED\",\n \"NOT_INITIALIZED\",\n]);\n\n/**\n * Is this a deterministic server rejection — a coded error the server would\n * raise identically on every replay (bad input, auth, not-found, conflict)? A\n * queued op that fails this way is poison: retrying it forever would wedge the\n * queue, so the flush DROPS it. Everything else (transport failures, 5xx\n * `HTTP_ERROR`, `RATE_LIMITED`, `CIRCUIT_OPEN`, anything uncoded) is treated as\n * transient and RETAINED for a later attempt — favoring retain over data loss.\n */\nexport function isDeterministicError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n return typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code);\n}\n\n/**\n * Heuristic: does a thrown value look like a transient offline / transport\n * failure (vs a deterministic 4xx the server would reject again)? Used at\n * ENQUEUE time to decide whether a failed write is worth buffering — a\n * `VALIDATION_ERROR` will fail identically on replay, so it throws instead.\n *\n * A deterministic server error is excluded even when the device reports\n * offline: the request clearly reached the server (it got a coded response),\n * so retrying it on reconnect is pointless and would wedge the queue.\n *\n * NOTE: this is intentionally stricter than {@link isDeterministicError}'s\n * inverse — at enqueue time we only buffer GENUINE connectivity failures (a\n * non-offline 5xx/429 surfaces to the caller immediately rather than silently\n * queueing). The flush drop-decision uses {@link isDeterministicError} so a\n * transient 5xx/429 encountered DURING replay is retained, not dropped.\n */\nexport function isOfflineError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n if (typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code)) {\n return false;\n }\n if (code === \"NETWORK_ERROR\") return true;\n // `fetch` signals a transport failure (DNS / refused / offline) by throwing a\n // `TypeError` with a runtime-specific message — \"Failed to fetch\" (Chrome),\n // \"Network request failed\" (RN/Hermes), \"Load failed\" (Safari),\n // \"fetch failed\" (undici/Node). Match those messages rather than EVERY\n // TypeError, so a genuine programming `TypeError` (e.g. reading a property of\n // undefined) surfaces to the caller instead of being silently buffered —\n // especially on Node where `isOnline()` is always true.\n const msg = (err as { message?: unknown } | null | undefined)?.message;\n const looksLikeTransport =\n typeof msg === \"string\" &&\n /network (error|request failed)|failed to fetch|fetch failed|load failed/i.test(\n msg,\n );\n if (looksLikeTransport) return true;\n // No transport signal on the error itself — fall back to the runtime's\n // online state. Treat \"offline\" as queueable only for errors that DIDN'T\n // carry a deterministic server code (handled above).\n return !isOnline();\n}\n\n/** Generate a stable idempotency key for a queued op. */\nexport function newIdempotencyKey(): string {\n const c =\n typeof globalThis !== \"undefined\"\n ? (globalThis.crypto as { randomUUID?: () => string } | undefined)\n : undefined;\n if (c && typeof c.randomUUID === \"function\") return `amba_obx_${c.randomUUID()}`;\n // Fallback for runtimes without crypto.randomUUID — collision-resistant\n // enough for a per-device outbox (the server still dedupes on the key).\n return `amba_obx_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 12)}`;\n}\n\n/**\n * Durable, ordered, idempotent offline outbox. Opt-in via `Amba.offline`.\n * Single in-flight flush is enforced so concurrent reconnect signals don't\n * double-replay.\n */\nexport class OfflineQueue {\n private readonly store: KvStore;\n private readonly executor: WriteExecutor;\n private readonly storageKey: string;\n /**\n * Notified when a queued op is dropped because its replay failed\n * deterministically (a poisoned op the server will always reject). Lets the\n * SDK surface the loss (debug log) instead of silently discarding.\n */\n private readonly onDropped?: (op: OutboxOp, err: unknown) => void;\n private flushing: Promise<FlushResult> | null = null;\n /**\n * Serializes every read-modify-write of the persisted outbox. Each mutation\n * chains onto the previous one's promise, so concurrent `enqueue`s — and an\n * `enqueue` racing the per-op removal inside `flush` — can never read a stale\n * snapshot and clobber each other's writes (the lost-update bug). All\n * storage mutations funnel through `mutate()`; only `pendingCount`/`pending`\n * read without taking the lock (a stale-by-one read is harmless).\n */\n private tail: Promise<unknown> = Promise.resolve();\n /** Detach function for the auto-flush `online` listener, if installed. */\n private detachOnline: (() => void) | null = null;\n /**\n * Set once `dispose()` is called (on teardown / reconfigure). A disposed\n * queue refuses to mutate the persisted outbox, so an orphaned in-flight\n * flush — or any late call against the old instance — can never race the new\n * queue by reading/writing the same storage after it's been replaced.\n */\n private disposed = false;\n\n constructor(opts: {\n store: KvStore;\n executor: WriteExecutor;\n /** Persistence key for the serialized queue. */\n storageKey?: string;\n /** Called when a deterministically-failing op is dropped from the queue. */\n onDropped?: (op: OutboxOp, err: unknown) => void;\n }) {\n this.store = opts.store;\n this.executor = opts.executor;\n this.storageKey = opts.storageKey ?? \"offline_outbox\";\n this.onDropped = opts.onDropped;\n }\n\n /**\n * Run `fn` exclusively against the persisted outbox: it chains after every\n * prior mutation and the next one waits for it. `fn` receives the current\n * ops and returns the next ops to persist plus its own result.\n */\n private mutate<R>(\n fn: (ops: OutboxOp[]) => { next: OutboxOp[]; result: R } | Promise<{ next: OutboxOp[]; result: R }>,\n opts?: { rejectIfDisposed?: boolean },\n ): Promise<R> {\n const run = this.tail.then(async () => {\n // Re-check disposal AFTER taking the lock. `save()` is already a no-op\n // when disposed, but a caller that needs to KNOW its write didn't persist\n // (enqueue) must REJECT rather than silently resolve — otherwise a\n // dispose racing between `getOfflineQueue()` and the awaited `enqueue()`\n // would hand the caller a pending-success for a write that's now lost.\n if (this.disposed && opts?.rejectIfDisposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const ops = await this.load();\n const { next, result } = await fn(ops);\n await this.save(next);\n return result;\n });\n // Keep the chain alive even if this mutation rejects, so a single failure\n // doesn't permanently wedge every future mutation.\n this.tail = run.catch(() => undefined);\n return run;\n }\n\n private async load(): Promise<OutboxOp[]> {\n const raw = await this.store.get(this.storageKey);\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as unknown;\n return Array.isArray(parsed) ? (parsed as OutboxOp[]) : [];\n } catch {\n // Corrupt payload — start clean rather than wedge the queue forever.\n return [];\n }\n }\n\n private async save(ops: OutboxOp[]): Promise<void> {\n // A disposed queue must never write the shared storage — a new queue may\n // already own it. Drop the write silently so an orphaned in-flight flush\n // (or any late mutation) can't clobber the replacement queue's outbox.\n if (this.disposed) return;\n if (ops.length === 0) {\n await this.store.delete(this.storageKey);\n return;\n }\n await this.store.set(this.storageKey, JSON.stringify(ops));\n }\n\n /**\n * Permanently retire this queue. Detaches the auto-flush listener and marks\n * it disposed so it can no longer mutate the persisted outbox. Returns once\n * any in-flight flush + pending mutation chain has settled, so a caller (e.g.\n * `configure()` teardown) can safely create a replacement queue knowing the\n * old one will never touch storage again. Idempotent.\n */\n async dispose(): Promise<void> {\n this.disposed = true;\n if (this.detachOnline) {\n this.detachOnline();\n this.detachOnline = null;\n }\n // Await the in-flight flush + the mutation chain so all already-scheduled\n // storage work has run (its writes are now suppressed by the disposed\n // guard) before the caller installs a new queue.\n try {\n await this.flushing;\n } catch {\n // a failing flush still settles the lifecycle\n }\n await this.tail.catch(() => undefined);\n }\n\n /** Whether this queue has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /** Append an op to the durable outbox. Returns the persisted op. */\n async enqueue(\n op: Omit<OutboxOp, \"idempotencyKey\" | \"enqueuedAt\"> & {\n idempotencyKey?: string;\n },\n ): Promise<OutboxOp> {\n // Fast-fail if already disposed; otherwise the re-check inside `mutate`\n // (after the lock) catches a dispose that races this call. Either way a\n // disposed queue REJECTS the enqueue rather than silently swallowing the\n // write — so the SDK's diverting write surfaces a failure instead of\n // handing the caller a pending-success for a write that never persisted.\n if (this.disposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const full: OutboxOp = {\n ...op,\n idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),\n enqueuedAt: new Date().toISOString(),\n };\n return this.mutate((ops) => ({ next: [...ops, full], result: full }), {\n rejectIfDisposed: true,\n });\n }\n\n /** Number of ops awaiting replay. */\n async pendingCount(): Promise<number> {\n return (await this.load()).length;\n }\n\n /** Snapshot the pending ops (a copy — mutating it does not affect the queue). */\n async pending(): Promise<OutboxOp[]> {\n return [...(await this.load())];\n }\n\n /** Drop every queued op without replaying. */\n async clear(): Promise<void> {\n await this.mutate(() => ({ next: [], result: undefined }));\n }\n\n /**\n * Replay the outbox in FIFO order. Each op is removed only after it\n * succeeds; the first failure stops the flush and retains the remainder\n * (so order is preserved and nothing is dropped mid-batch). Concurrent\n * calls share one in-flight flush.\n */\n async flush(): Promise<FlushResult> {\n if (this.disposed) return { flushed: 0, dropped: 0, remaining: 0 };\n if (this.flushing) return this.flushing;\n this.flushing = this.runFlush().finally(() => {\n this.flushing = null;\n });\n return this.flushing;\n }\n\n private async runFlush(): Promise<FlushResult> {\n let flushed = 0;\n let dropped = 0;\n // Drive the flush one op at a time through `mutate()`, so a concurrent\n // `enqueue` (e.g. a new offline write mid-flush) interleaves safely\n // instead of being lost to a stale read-modify-write. Each step replays\n // the head op (outside the lock — it can be slow) then atomically removes\n // exactly that op if it's still the head.\n //\n // The drain is NOT gated on `navigator.onLine`: that's meaningless on\n // React Native (no real `navigator.onLine`), where the documented pattern\n // is to call `flush()` from a NetInfo connectivity listener — so an\n // explicit flush must always ATTEMPT the drain. If the device is in fact\n // still offline, the executor throws a transport error on the first op and\n // the loop stops below, retaining everything for the next attempt. (The\n // web auto-flush path only fires on the `online` event anyway, so it's\n // already online when it runs.)\n for (;;) {\n // Stop immediately if disposed mid-drain (a reconfigure replaced us): the\n // save guard already suppresses writes, this just avoids executing more\n // ops against a retired queue.\n if (this.disposed) break;\n const head = (await this.load())[0];\n if (!head) break;\n let replayError: unknown = undefined;\n let ok = false;\n try {\n await this.executor(head);\n ok = true;\n } catch (err) {\n replayError = err;\n }\n\n if (!ok && !isDeterministicError(replayError)) {\n // A NON-deterministic replay failure — a transport blip, a 5xx\n // `HTTP_ERROR`, `RATE_LIMITED` (429), `CIRCUIT_OPEN`, or any uncoded\n // error. These may well succeed later, so STOP here and keep this op\n // plus everything after it for the next attempt (favor retain over data\n // loss). Only a deterministic server rejection (handled below) is\n // dropped.\n //\n // The replay ran OUTSIDE the lock, so a concurrent `clear()` (or a\n // drain that already removed this op) could have emptied the store\n // while it was in flight. Re-assert the un-applied op's presence under\n // the lock — re-prepend it if it's gone — so an unapplied mutation is\n // never silently lost to a clear/flush interleaving. at-least-once wins\n // over honoring a clear that raced an in-flight, un-applied write; the\n // caller can `clear()` again once the flush settles.\n await this.mutate((ops) => {\n const present = ops.some(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n return {\n next: present ? ops : [head, ...ops],\n result: undefined,\n };\n });\n break;\n }\n\n // Either the replay succeeded, OR it failed deterministically (a coded\n // server rejection that will fail identically forever). In both cases the\n // head op leaves the queue: a success drains it, a poisoned op is DROPPED\n // so it can't wedge everything behind it. Removal is by identity under\n // the lock — robust against a concurrent enqueue having shifted the\n // array.\n const removed = await this.mutate((ops) => {\n const idx = ops.findIndex(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n if (idx === -1) return { next: ops, result: false };\n const next = ops.slice(0, idx).concat(ops.slice(idx + 1));\n return { next, result: true };\n });\n if (!removed) break; // shouldn't happen, but never spin\n if (ok) {\n flushed += 1;\n } else {\n dropped += 1;\n try {\n this.onDropped?.(head, replayError);\n } catch {\n // A throwing drop-observer must not break the flush.\n }\n }\n }\n const remaining = (await this.load()).length;\n return { flushed, dropped, remaining };\n }\n\n /**\n * Install an auto-flush listener on the runtime's `online` event so the\n * queue drains the moment connectivity returns. Idempotent — a second\n * call is a no-op. Returns a detach function.\n */\n startAutoFlush(): () => void {\n if (this.detachOnline) return this.detachOnline;\n const target =\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { addEventListener?: unknown }).addEventListener ===\n \"function\"\n ? (globalThis as unknown as {\n addEventListener(t: string, cb: () => void): void;\n removeEventListener(t: string, cb: () => void): void;\n })\n : null;\n if (!target) {\n // No event target (Node / SSR) — nothing to listen on. Still return a\n // detach so callers can treat the API uniformly.\n this.detachOnline = () => {};\n return this.detachOnline;\n }\n const handler = () => {\n void this.flush();\n };\n target.addEventListener(\"online\", handler);\n this.detachOnline = () => {\n target.removeEventListener(\"online\", handler);\n this.detachOnline = null;\n };\n return this.detachOnline;\n }\n}\n\n// ── Optimistic update helper ─────────────────────────────────────────────\n\n/**\n * Apply a local change immediately, fire the real mutation, and roll the\n * local change back if the mutation fails. Framework-agnostic: `apply` and\n * `rollback` operate on whatever local view you hand them (a React state\n * setter, a store, an in-memory cache).\n *\n * Resolves with the mutation's result on success. On failure it invokes\n * `rollback` (with the value `apply` returned, so you can restore a captured\n * snapshot) and rethrows the original error.\n *\n * @example\n * await withOptimistic({\n * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },\n * mutate: () => Amba.collections.insert(\"todos\", draft),\n * rollback: (prev) => setTodos(prev),\n * });\n */\nexport async function withOptimistic<TApply, TResult>(spec: {\n /** Mutate the local view now. Return a snapshot for `rollback` to restore. */\n apply: () => TApply;\n /** The real async mutation against the API. */\n mutate: () => Promise<TResult>;\n /** Undo the local change. Receives whatever `apply` returned. */\n rollback: (applied: TApply) => void;\n}): Promise<TResult> {\n const applied = spec.apply();\n try {\n return await spec.mutate();\n } catch (err) {\n try {\n spec.rollback(applied);\n } catch {\n // A throwing rollback must not mask the original mutation error.\n }\n throw err;\n }\n}\n","/**\n * Browser push-token rotation.\n *\n * The browser can silently rotate a Web Push subscription (key rollover,\n * permission re-grant). When it does, the service worker receives a\n * `pushsubscriptionchange` event and the OLD subscription stops delivering.\n * Auto-rotation listens for that event from the page and re-registers the\n * fresh subscription's endpoint with the backend so notifications keep\n * flowing without the app having to notice.\n *\n * The actual re-register is delegated to a caller-supplied callback (the SDK\n * wires it to its idempotent `push.register` path), so this module stays free\n * of any SDK-runtime coupling and is unit-testable with a fake event target.\n */\n\n/** Re-registers a rotated push token with the backend. */\nexport type ReRegister = (token: string, platform: \"web\") => Promise<unknown>;\n\n/**\n * Subscribe to browser push-subscription rotation. On a\n * `pushsubscriptionchange`, the new subscription endpoint is forwarded to\n * `reRegister` (which the SDK points at its idempotent token-upsert).\n *\n * Returns an unsubscribe function. No-ops (returning a no-op detacher) when\n * the runtime has no service-worker / push support — e.g. SSR or a browser\n * without Web Push — so callers can invoke it unconditionally.\n */\n/**\n * Are we executing inside a ServiceWorkerGlobalScope? `pushsubscriptionchange`\n * ONLY fires there — on a normal page `self === window`, where the event never\n * fires, so attaching a listener silently does nothing. We detect the scope by\n * the `ServiceWorkerGlobalScope` constructor when present, falling back to the\n * SW-only `self.registration.pushManager` shape (a window has no `registration`\n * on its global).\n */\nfunction inServiceWorkerScope(g: unknown): boolean {\n if (typeof g !== \"object\" || g === null) return false;\n const swCtor = (globalThis as { ServiceWorkerGlobalScope?: unknown })\n .ServiceWorkerGlobalScope;\n if (typeof swCtor === \"function\" && g instanceof (swCtor as new () => object)) {\n return true;\n }\n // Fallback: the SW global exposes `registration.pushManager`; a window does\n // not expose `registration` on its global object.\n const reg = (g as { registration?: { pushManager?: unknown } }).registration;\n return !!reg && typeof reg.pushManager === \"object\" && reg.pushManager !== null;\n}\n\nlet pageScopeWarned = false;\n\nexport function enableWebPushAutoRotation(reRegister: ReRegister): () => void {\n // `pushsubscriptionchange` fires inside the SERVICE WORKER, not the page.\n // Only attach when we're actually in a ServiceWorkerGlobalScope — on a normal\n // page `self` IS `window`, where the event never fires, so a listener there\n // would silently never observe rotations. From a page, the documented pattern\n // is to call this inside your service worker (or forward the event from the\n // worker via `postMessage`).\n const g = typeof self !== \"undefined\" ? (self as unknown) : undefined;\n if (!g || !inServiceWorkerScope(g)) {\n if (!pageScopeWarned && typeof console !== \"undefined\") {\n pageScopeWarned = true;\n console.warn(\n \"[amba] push.enableAutoRotation() must run inside your service worker — `pushsubscriptionchange` doesn't fire on a page/window. Call it from your amba service worker. No-op here.\",\n );\n }\n return () => {};\n }\n const sw = g as {\n addEventListener?: (t: string, cb: (e: unknown) => void) => void;\n removeEventListener?: (t: string, cb: (e: unknown) => void) => void;\n registration?: {\n pushManager?: {\n getSubscription?: () => Promise<{ endpoint?: string } | null>;\n };\n };\n };\n\n if (typeof sw.addEventListener !== \"function\") {\n return () => {};\n }\n\n const onChange = (event: unknown): void => {\n void (async () => {\n try {\n const ev = event as {\n newSubscription?: { endpoint?: string } | null;\n waitUntil?: (p: Promise<unknown>) => void;\n data?: unknown;\n };\n // Prefer the event's `newSubscription`; fall back to re-reading the\n // current subscription off the registration (some browsers leave\n // `newSubscription` null and expect a re-subscribe).\n let endpoint = ev?.newSubscription?.endpoint;\n if (!endpoint && sw.registration?.pushManager?.getSubscription) {\n const current = await sw.registration.pushManager.getSubscription();\n endpoint = current?.endpoint ?? undefined;\n }\n if (!endpoint) return;\n const work = reRegister(endpoint, \"web\");\n // Keep the worker alive until the re-register settles, when the\n // event supports it.\n if (typeof ev?.waitUntil === \"function\") ev.waitUntil(work);\n await work;\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] push auto-rotation re-register failed\", err);\n }\n }\n })();\n };\n\n sw.addEventListener(\"pushsubscriptionchange\", onChange);\n return () => {\n sw.removeEventListener?.(\"pushsubscriptionchange\", onChange);\n };\n}\n","/**\n * On-device local notifications for the browser.\n *\n * A *local* notification fires from the device without a server\n * round-trip — distinct from remote push (`Amba.push.*`). On the web the\n * scheduling story is uneven, and this module is honest about it rather\n * than silently pretending to schedule:\n *\n * - **Scheduled delivery** (fire at a future `at` / after `inSeconds`)\n * requires the experimental **Notification Triggers API**\n * (`TimestampTrigger` + a Service Worker registration's\n * `showNotification({ showTrigger })`). It currently ships only in\n * Chromium-family browsers and only over HTTPS with an active Service\n * Worker. When it's present we register the trigger with the OS, so\n * the notification fires even if the tab/browser is closed.\n *\n * - **No Triggers API** → we cannot hand a future-dated notification to\n * the OS. Rather than no-op, we fall back to an **in-page timer**\n * (`setTimeout`) that fires the notification while the page stays\n * open, and the returned handle's `scheduledVia` is `\"timer\"` so\n * callers can detect the degraded mode. If the page is closed before\n * the timer elapses, the notification does not fire — this is a\n * hard browser limitation, surfaced honestly, not hidden.\n *\n * - **Notification API absent** (e.g. SSR, or a browser with\n * notifications disabled) → every method throws a typed\n * `AmbaApiError` (`LOCAL_NOTIFICATIONS_UNAVAILABLE`), never a silent\n * success.\n *\n * Permission is requested on first `scheduleLocal`; a denied prompt\n * throws `LOCAL_NOTIFICATIONS_PERMISSION_DENIED`.\n */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type {\n LocalNotificationHandle,\n ScheduleLocalNotificationInput,\n ScheduledLocalNotification,\n} from \"./types.js\";\n\ninterface TimestampTriggerCtor {\n new (timestamp: number): unknown;\n}\n\n/**\n * Browsers store a `setTimeout` delay in a signed 32-bit int. A delay\n * larger than `2^31 - 1` ms (~24.8 days) overflows and is clamped — most\n * browsers fire (often immediately) rather than wait. So a far-future\n * local notification scheduled via the in-page timer fallback would fire\n * at the wrong time. We chunk long delays: arm a timer for at most\n * `MAX_TIMEOUT_MS`, and on wake re-arm for the remainder until due.\n */\nconst MAX_TIMEOUT_MS = 2_147_483_647; // 2^31 - 1\n\n/**\n * In-page timer registry for the degraded (no-Triggers-API) path.\n *\n * `handle` always points at the CURRENTLY-armed chunk's timeout id (it is\n * reassigned on each re-arm), so `clearTimeout(handle)` cancels the timer\n * even mid-chunk — `cancelLocal` and reschedule stay correct for\n * arbitrarily long delays. `fireAt` is the absolute target time used to\n * compute each chunk's remaining delay.\n */\ninterface PendingTimer {\n handle: ReturnType<typeof setTimeout>;\n fireAt: number;\n record: ScheduledLocalNotification;\n}\nconst pendingTimers = new Map<string, PendingTimer>();\n\nfunction hasNotificationApi(): boolean {\n return (\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { Notification?: unknown }).Notification ===\n \"function\"\n );\n}\n\nfunction timestampTriggerCtor(): TimestampTriggerCtor | undefined {\n const ctor = (globalThis as { TimestampTrigger?: unknown }).TimestampTrigger;\n return typeof ctor === \"function\"\n ? (ctor as TimestampTriggerCtor)\n : undefined;\n}\n\nfunction genId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `amba_local_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Per-INSTANCE token. The stable `__ambaLocalId` survives a reschedule of the\n * same id; this token does not — every individual `scheduleLocal` call mints a\n * fresh one. It lets `listScheduledLocal` distinguish a NEW pending schedule\n * for an id from an OLD already-fired-but-still-visible notification of the\n * SAME id (which the Service Worker reports under `getNotifications()`),\n * instead of conflating the two by id and hiding the pending one. (Round-5.)\n */\nfunction genInstanceToken(): string {\n return `${Date.now()}_${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Resolve the id a schedule will be filed under. A caller may omit `id`\n * (we mint one) but must not pass a BLANK/whitespace id: `cancelLocal`\n * rejects blank ids, so returning one in the handle would make the\n * notification uncancellable. We reject blank at schedule time so every\n * returned handle id round-trips through `cancelLocal`. Shared by both\n * SDK platforms (web + react-native) for identical validation.\n */\nfunction normalizeScheduleId(rawId: string | undefined): string {\n if (rawId === undefined) return genId();\n if (typeof rawId !== \"string\" || rawId.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: id, when provided, must be a non-empty string\",\n );\n }\n return rawId;\n}\n\n/**\n * A validated trigger, ready to be turned into an absolute fire time.\n *\n * Split from the fire-time computation on purpose: validation runs BEFORE we\n * request permission (so bad input fails fast), but the absolute fire time\n * for a RELATIVE (`inSeconds`) trigger must be computed AFTER permission\n * resolves — otherwise the (potentially long) time the user spends on the\n * permission prompt is silently subtracted from the delay and the\n * notification fires sooner than `inSeconds` after scheduling completes.\n * Absolute (`at`) triggers are a fixed wall-clock time and are unaffected.\n */\ntype ParsedTrigger =\n | { kind: \"absolute\"; at: number }\n | { kind: \"relative\"; seconds: number };\n\nfunction parseTrigger(input: ScheduleLocalNotificationInput): ParsedTrigger {\n const trigger = input.trigger;\n // Guard that `trigger` is a non-null object before `in`-probing it — a\n // missing or non-object trigger must surface the documented\n // INVALID_ARGUMENT AmbaApiError, not a raw `TypeError` from `\"at\" in null`.\n if (!trigger || typeof trigger !== \"object\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number }\",\n );\n }\n if (\"at\" in trigger) {\n const at = trigger.at instanceof Date ? trigger.at : new Date(trigger.at);\n const ms = at.getTime();\n if (Number.isNaN(ms)) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.at must be a valid Date\",\n );\n }\n return { kind: \"absolute\", at: ms };\n }\n if (\"inSeconds\" in trigger) {\n if (!Number.isFinite(trigger.inSeconds) || trigger.inSeconds < 0) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.inSeconds must be a non-negative number\",\n );\n }\n return { kind: \"relative\", seconds: trigger.inSeconds };\n }\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number }\",\n );\n}\n\n/**\n * Resolve a parsed trigger to an absolute fire time (ms epoch). For a\n * relative trigger this is `now + seconds` — call it ONLY after permission\n * has resolved so `inSeconds` counts from when scheduling actually completes.\n */\nfunction computeFireAt(parsed: ParsedTrigger): number {\n return parsed.kind === \"absolute\"\n ? parsed.at\n : Date.now() + parsed.seconds * 1000;\n}\n\nfunction validateContent(input: ScheduleLocalNotificationInput): void {\n if (!input || typeof input.title !== \"string\" || input.title.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: title is required\",\n );\n }\n if (typeof input.body !== \"string\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: body must be a string\",\n );\n }\n}\n\nasync function ensurePermission(): Promise<void> {\n const N = (globalThis as { Notification: typeof Notification }).Notification;\n let permission = N.permission;\n if (permission === \"default\") {\n permission = await N.requestPermission();\n }\n if (permission !== \"granted\") {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\",\n \"user denied notification permission — local notifications cannot be shown\",\n );\n }\n}\n\n/**\n * How long we'll wait for an existing-but-not-yet-active registration to\n * become `ready` before falling through to the in-page timer path. A real\n * activation is near-instant; this cap only guards against a wedged\n * activation hanging the call.\n */\nconst SW_READY_TIMEOUT_MS = 1_000;\n\n/**\n * Resolve the Service Worker registration to use for OS-trigger\n * notifications — WITHOUT ever hanging.\n *\n * The footgun this avoids: `navigator.serviceWorker.ready` NEVER resolves\n * when no service worker is registered (the common case for a page that has\n * the Notifications API but no SW). Awaiting it unconditionally would hang\n * `scheduleLocal` / `cancelLocal` / `listScheduledLocal` forever instead of\n * falling back to the documented in-page timer path. (Round-6 HIGH.)\n *\n * Instead we first call `getRegistration()`, which resolves to `undefined`\n * when nothing is registered (no hang). Only if a registration exists do we\n * wait — briefly, raced against a timeout — for `.ready`, so an active SW is\n * used while a wedged activation can't stall the caller.\n */\nasync function getServiceWorkerRegistration(): Promise<\n ServiceWorkerRegistration | undefined\n> {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n if (!nav || !(\"serviceWorker\" in nav)) return undefined;\n const swc = nav.serviceWorker;\n\n let existing: ServiceWorkerRegistration | undefined;\n try {\n // Resolves to `undefined` when no SW is registered — does NOT hang.\n existing = await swc.getRegistration();\n } catch {\n return undefined;\n }\n if (!existing) return undefined;\n\n // A registration exists. Prefer the `ready` (active) registration, but\n // never block on it indefinitely: race `.ready` against a short timeout\n // and fall back to the registration we already have.\n try {\n const ready = await Promise.race([\n swc.ready,\n new Promise<undefined>((resolve) =>\n setTimeout(() => resolve(undefined), SW_READY_TIMEOUT_MS),\n ),\n ]);\n return ready ?? existing;\n } catch {\n return existing;\n }\n}\n\n/**\n * Cancel (close) any Service Worker OS-trigger notification scheduled by us\n * under `tag === id`. Passes `includeTriggered: true` so that\n * scheduled-but-not-yet-fired trigger notifications are returned by\n * `getNotifications` — without it, pending OS-trigger notifications are\n * invisible and therefore impossible to cancel. No-op when there's no\n * registration (degraded / timer-only environments).\n */\nasync function cancelOsTriggerNotification(\n registration: ServiceWorkerRegistration | undefined,\n id: string,\n): Promise<void> {\n if (!registration) return;\n try {\n const matches = await registration.getNotifications({\n tag: id,\n // `includeTriggered` is part of the experimental Notification Triggers\n // API; not declared in lib.dom.d.ts, hence the cast.\n includeTriggered: true,\n } as GetNotificationOptions & { includeTriggered?: boolean });\n for (const n of matches) {\n // Only close notifications WE scheduled (carry the Amba marker for this\n // id). A same-`tag` notification created by other code or remote push\n // must never be closed by us. (Marker-filtering class, round-4.)\n const data = (n.data as Record<string, unknown> | undefined) ?? {};\n if (data.__ambaLocalId !== id) continue;\n n.close();\n }\n } catch {\n // getNotifications can reject in some browsers; the caller's timer-path\n // cleanup already covers the degraded case.\n }\n}\n\n/**\n * Show the in-page `Notification` for a fired timer. Tolerant of permission\n * being revoked between scheduling and firing (construction can throw).\n */\nfunction fireInPageNotification(\n record: ScheduledLocalNotification,\n id: string,\n): void {\n try {\n const N = (globalThis as { Notification: typeof Notification }).Notification;\n // eslint-disable-next-line no-new\n new N(record.title, {\n body: record.body,\n data: { ...record.data, __ambaLocalId: id },\n tag: id,\n });\n } catch {\n // Notification construction can throw if permission was revoked between\n // scheduling and firing; nothing actionable here.\n }\n}\n\n/**\n * Arm the next chunk of an in-page timer for the registry entry under `id`.\n * If the remaining delay until `entry.fireAt` exceeds `MAX_TIMEOUT_MS`, arm\n * for `MAX_TIMEOUT_MS` and re-arm on wake; otherwise arm for the exact\n * remainder, then fire and remove the entry.\n *\n * `entry.handle` is reassigned to each chunk's timeout id, so a concurrent\n * `clearTimeout(entry.handle)` (cancel / reschedule) always targets the live\n * chunk and stops the chain.\n */\nfunction armTimerChunk(id: string, entry: PendingTimer): void {\n const remaining = entry.fireAt - Date.now();\n if (remaining > MAX_TIMEOUT_MS) {\n entry.handle = setTimeout(() => {\n // Re-arm only if this entry is still the live one for `id` (it may have\n // been cancelled or replaced while the chunk was pending).\n const current = pendingTimers.get(id);\n if (current === entry) armTimerChunk(id, entry);\n }, MAX_TIMEOUT_MS);\n return;\n }\n entry.handle = setTimeout(\n () => {\n // Only fire if we're still the live entry for this id.\n if (pendingTimers.get(id) !== entry) return;\n pendingTimers.delete(id);\n fireInPageNotification(entry.record, id);\n },\n Math.max(0, remaining),\n );\n}\n\n/**\n * The web `notifications` namespace. Web capability is partial — see the\n * module-level doc. The returned handle includes `scheduledVia` so apps\n * can detect whether the notification was handed to the OS\n * (`\"os-trigger\"`) or relies on an in-page timer (`\"timer\"`).\n */\nexport const notifications = {\n async scheduleLocal(\n input: ScheduleLocalNotificationInput,\n ): Promise<LocalNotificationHandle & { scheduledVia: \"os-trigger\" | \"timer\" }> {\n validateContent(input);\n if (!hasNotificationApi()) {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_UNAVAILABLE\",\n \"the Notifications API is not available in this environment (server-side rendering, or a browser with notifications disabled). Local notifications require a browser context.\",\n );\n }\n // Validate the trigger + id BEFORE requesting permission / committing\n // anything (fail fast on bad input; a blank caller id never yields an\n // uncancellable handle). But DEFER the absolute fire-time computation\n // until after permission resolves, so a relative `inSeconds` delay counts\n // from when scheduling completes — not from before the (possibly slow)\n // permission prompt. (Round-5.)\n const parsedTrigger = parseTrigger(input);\n const id = normalizeScheduleId(input.id);\n await ensurePermission();\n const fireAt = computeFireAt(parsedTrigger);\n // Fresh per-instance token for THIS schedule (round-5: lets the list\n // distinguish a new pending schedule from a stale visible one of same id).\n const instanceToken = genInstanceToken();\n\n const TriggerCtor = timestampTriggerCtor();\n const registration = await getServiceWorkerRegistration();\n\n // Re-scheduling with a caller-supplied id must REPLACE, not duplicate —\n // and the prior schedule and the new one may live on DIFFERENT paths.\n // But replacement must be ATOMIC: if installing the new schedule fails,\n // the PRIOR schedule must remain intact (don't leave the user with\n // neither the old nor the new). So we tear the prior schedule down only\n // AFTER the replacement is committed.\n //\n // Path matrix (each tears down BOTH prior mechanisms after commit):\n // - timer → timer: clear the old `setTimeout` (else double-fire).\n // - os-trigger → os-trigger: `showNotification` replaces by `tag`, but we\n // also explicitly cancel for uniformity.\n // - os-trigger → timer: the prior OS notification is NOT replaced by a\n // `setTimeout`, so we cancel it explicitly (round-2 gap).\n // - timer → os-trigger: symmetric; clear the stale timer.\n\n // Best path: hand a future-dated trigger to the OS via the Service\n // Worker. Survives the page/browser closing.\n if (TriggerCtor && registration) {\n // Commit the new OS-trigger notification FIRST. If this rejects, we\n // return the error WITHOUT having touched the prior schedule, so the\n // previous reminder still fires (round-3: atomic reschedule). The new\n // notification uses the same `tag`, so it replaces any prior OS-trigger\n // notification for this id automatically.\n await registration.showNotification(input.title, {\n body: input.body,\n data: { ...input.data, __ambaLocalId: id, __ambaInstance: instanceToken },\n tag: id,\n // `showTrigger` is part of the experimental Notification Triggers\n // API; typed as unknown since lib.dom.d.ts doesn't declare it.\n showTrigger: new TriggerCtor(fireAt),\n } as NotificationOptions & { showTrigger: unknown });\n\n // Replacement committed — now tear down a prior in-page timer for this\n // id (the same-tag OS notification was already replaced above).\n const priorTimer = pendingTimers.get(id);\n if (priorTimer) {\n clearTimeout(priorTimer.handle);\n pendingTimers.delete(id);\n }\n return { id, scheduledVia: \"os-trigger\" };\n }\n\n // Degraded path: in-page timer. Only fires while the page stays open.\n // We surface this via `scheduledVia: \"timer\"` rather than no-op'ing.\n //\n // Arming a `setTimeout` is synchronous and can't reject, so we can tear\n // down the prior schedule first here without an atomicity risk. We must\n // cancel BOTH a prior in-page timer AND any prior OS-trigger notification\n // (the latter is async; `cancelOsTriggerNotification` swallows failures).\n const priorTimer = pendingTimers.get(id);\n if (priorTimer) {\n clearTimeout(priorTimer.handle);\n pendingTimers.delete(id);\n }\n await cancelOsTriggerNotification(registration, id);\n\n const entry: PendingTimer = {\n // Placeholder; `armTimerChunk` assigns the real handle synchronously.\n handle: undefined as unknown as ReturnType<typeof setTimeout>,\n fireAt,\n record: {\n id,\n title: input.title,\n body: input.body,\n data: input.data,\n },\n };\n // Register the entry BEFORE arming so the chunk callback's\n // `pendingTimers.get(id) === entry` liveness check passes.\n pendingTimers.set(id, entry);\n // Chunk long delays so a far-future schedule (> ~24.8 days) fires at the\n // right time instead of overflowing the 32-bit `setTimeout` delay.\n armTimerChunk(id, entry);\n return { id, scheduledVia: \"timer\" };\n },\n\n async cancelLocal(id: string): Promise<void> {\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.cancelLocal: id is required\",\n );\n }\n // Cancel an in-page timer if we own one.\n const timer = pendingTimers.get(id);\n if (timer) {\n clearTimeout(timer.handle);\n pendingTimers.delete(id);\n }\n // Cancel any OS-trigger notification registered under this tag —\n // including scheduled-but-unfired ones (`includeTriggered: true`).\n const registration = await getServiceWorkerRegistration();\n await cancelOsTriggerNotification(registration, id);\n },\n\n async listScheduledLocal(): Promise<ScheduledLocalNotification[]> {\n const out: ScheduledLocalNotification[] = [];\n // In-page timers we're tracking this session — these are by definition\n // pending (a fired timer deletes its own entry).\n for (const { record } of pendingTimers.values()) out.push({ ...record });\n\n const registration = await getServiceWorkerRegistration();\n if (registration) {\n try {\n // `getNotifications()` (no flag) returns only ALREADY-SHOWN (fired or\n // active) notifications. With `includeTriggered: true` it ALSO returns\n // scheduled-but-unfired trigger notifications. So the set we want —\n // PENDING (unfired) — is the includeTriggered set MINUS the shown set.\n // Listing the includeTriggered set verbatim would wrongly report\n // already-fired reminders as still scheduled. (Round-4 gap.)\n const [withTriggered, shownOnly] = await Promise.all([\n registration.getNotifications({\n includeTriggered: true,\n } as GetNotificationOptions & { includeTriggered?: boolean }),\n registration.getNotifications(),\n ]);\n // Build the \"already shown/fired\" set keyed by per-INSTANCE token, not\n // by stable id. Keying by id would wrongly hide a NEW pending schedule\n // of an id whenever an OLD fired notification with the same id is still\n // visible (not yet dismissed). The instance token is unique per\n // `scheduleLocal` call, so the old visible one and the new pending one\n // never collide. (Round-5.)\n const shownInstances = new Set<string>();\n for (const n of shownOnly) {\n const data = (n.data as Record<string, unknown> | undefined) ?? {};\n const token = data.__ambaInstance as string | undefined;\n if (token) shownInstances.add(token);\n }\n for (const n of withTriggered) {\n const data = (n.data as Record<string, unknown> | undefined) ?? {};\n // Only surface notifications WE scheduled. A Service Worker may\n // hold notifications created by other code (or remote push); those\n // carry no `__ambaLocalId` marker and must not leak into this list.\n const id = data.__ambaLocalId as string | undefined;\n if (!id) continue;\n // Skip already-fired/shown instances — only PENDING ones belong in\n // \"scheduled\". Matched by instance token so a stale visible\n // notification of the same id doesn't hide a fresh pending one.\n const token = data.__ambaInstance as string | undefined;\n if (token && shownInstances.has(token)) continue;\n if (out.some((existing) => existing.id === id)) continue;\n out.push({\n id,\n title: n.title,\n body: (n as { body?: string }).body ?? \"\",\n data,\n });\n }\n } catch {\n // Non-fatal — return what we have from the timer registry.\n }\n }\n return out;\n },\n};\n\nexport type LocalNotificationsNamespace = typeof notifications;\n\n/** @internal test hook — clears the in-page timer registry. */\nexport function __resetLocalNotificationTimers(): void {\n for (const { handle } of pendingTimers.values()) clearTimeout(handle);\n pendingTimers.clear();\n}\n","/**\n * @layers/amba-web — browser SDK for the amba platform.\n *\n * Exposes the full SDK 4.0 amba surface (34 modules) as idiomatic\n * TypeScript. All async operations return native Promises.\n *\n * @example\n * ```ts\n * import { Amba } from '@layers/amba-web';\n *\n * await Amba.configure({ projectId: 'proj_...', clientKey: 'amba_ck_...' });\n *\n * const session = await Amba.auth.signInAnonymously();\n * await Amba.events.track('app_opened', { source: 'deep_link' });\n *\n * const todos = await Amba.collections.find('todos', { limit: 20 });\n * await Amba.collections.insert('todos', { title: 'Buy milk' });\n * ```\n */\n\nimport type { AmbaCoreWasm } from \"@layers/amba-core\";\n\nexport type * from \"./types\";\nimport type {\n AmbaConfig,\n AuthResult,\n User,\n FindOptions,\n FindResponse,\n Filter,\n FilterValue,\n UpsertOptions,\n DeleteManyResult,\n TransactionOp,\n TransactionOpResult,\n ConfigBundle,\n FlagAssignment,\n PingResult,\n UserEntitlement,\n RestoreResult,\n OfferingsResult,\n MediaAsset,\n MediaCatalog,\n MediaListResponse,\n PresignData,\n PushPlatform,\n PushToken,\n AiMessageRequest,\n AiMessageResponse,\n Achievement,\n AchievementProgress,\n Challenge,\n ChallengeProgress,\n CurrencyBalance,\n CurrencyTransaction,\n InventoryItem,\n PurchaseRequest,\n ConsumeRequest,\n Leaderboard,\n LeaderboardEntry,\n Store,\n PurchaseOption,\n PurchaseResult,\n XpBalance,\n XpTransaction,\n Streak,\n FeedResponse,\n Friendship,\n Group,\n GroupMember,\n GroupCreate,\n GroupUpdate,\n Conversation,\n Message,\n SendMessageRequest,\n CreateConversationRequest,\n ListMessagesOptions,\n MarkReadResult,\n ConversationParticipant,\n Report,\n ReportRequest,\n ReferralCode,\n ReferralClaim,\n Review,\n ReviewCreate,\n ReviewUpdate,\n Role,\n CatalogItem,\n ContentItem,\n DeepLink,\n DeepLinkCreate,\n OnboardingStatus,\n AppSession,\n SyncChange,\n PushChangesResult,\n PullChangesResult,\n PullChangesCursor,\n LeagueMembership,\n LeagueCohortView,\n NearestOptions,\n NearestResponse,\n LinkedIdentifier,\n EventExplainResult,\n} from \"./types\";\n\nimport { LocalStoragePersistence } from \"./storage-adapter\";\nimport { encodeJson } from \"./json\";\nimport { expandAiImages } from \"./ai-images\";\nimport { AmbaApiError, toAmbaApiError } from \"./error\";\nimport type { Session } from \"./types\";\nimport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime\";\nimport { invokeAiStream } from \"./ai-stream\";\nimport type { AiStreamEvent, AiStreamOptions } from \"./ai-stream\";\nimport {\n OfflineQueue,\n isOfflineError,\n newIdempotencyKey,\n withOptimistic,\n} from \"./resilience\";\nimport type { KvStore, OutboxOp, FlushResult } from \"./resilience\";\nimport { enableWebPushAutoRotation } from \"./push-rotation\";\nimport { notifications } from \"./local-notifications\";\nimport { getCoreRuntime } from \"./wasm-loader\";\n\n// Replaced at build time for the two public ESM entry points. Keeping this as\n// a compile-time constant lets the dedicated browser bundle discard the\n// entire Node-only WASM loader (including its `node:` imports), while the\n// default/Node bundle retains it. The `typeof` fallback keeps direct source\n// execution under Vitest safe.\ndeclare const __AMBA_BROWSER_BUILD__: boolean;\nconst IS_BROWSER_BUILD =\n typeof __AMBA_BROWSER_BUILD__ !== \"undefined\" && __AMBA_BROWSER_BUILD__;\n\nexport { withOptimistic } from \"./resilience\";\nexport type {\n OutboxOp,\n OutboxOpKind,\n FlushResult,\n KvStore,\n} from \"./resilience\";\n\nexport { AmbaApiError } from \"./error\";\nexport type { AmbaApiErrorCode } from \"./error\";\nexport type { LocalNotificationsNamespace } from \"./local-notifications\";\nexport type {\n AiStreamEvent,\n AiStreamDelta,\n AiStreamDone,\n AiStreamOptions,\n} from \"./ai-stream\";\nexport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime\";\nexport type {\n RealtimeChange,\n MessageChange,\n RowChangeOp,\n SubscribeOptions,\n Unsubscribe,\n GamificationChange,\n GamificationKind,\n} from \"./realtime\";\n\nconst DEFAULT_API_URL = \"https://api.amba.dev\";\n\n/**\n * Default host that serves deployed functions. `Amba.functions.invoke(name)`\n * targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a\n * `functionsBaseUrl` override is configured.\n */\nconst DEFAULT_FUNCTIONS_HOST = \"fn.amba.host\";\nconst PROJECT_ID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Internal state — one instance per page. Created lazily by `Amba.configure()`.\n */\nlet coreInstance: AmbaCoreWasm | null = null;\n\n/**\n * Stored at configure-time so `Amba.projectId` returns the configured\n * value without going through the engine. Cached only after the\n * core finishes initializing.\n */\nlet _configuredProjectId: string | null = null;\n\n/**\n * Realtime (`Amba.collection(name).subscribe`) needs the API base URL,\n * client key, and current session token in the TS layer to open the\n * streamed connection — the engine doesn't expose them back. We capture\n * the base URL + client key at configure-time and the session token from\n * every auth result (the engine rotates it internally on refresh; the\n * subscribe connection picks up the latest at connect time).\n */\nlet _configuredApiUrl: string = DEFAULT_API_URL;\nlet _configuredClientKey: string | null = null;\nlet _currentSessionToken: string | null = null;\nlet _currentRefreshToken: string | null = null;\nlet _currentSessionExpiresAt: string | null = null;\nlet _currentUser: User | null = null;\n\nconst BROWSER_IDENTITY_VERSION = 1;\nconst DEFAULT_SESSION_TTL_MS = 60 * 60 * 1000;\n\ninterface PersistedBrowserIdentity {\n version: typeof BROWSER_IDENTITY_VERSION;\n project_id: string;\n anonymous_id: string;\n app_user_id: string | null;\n session_token: string | null;\n refresh_token: string | null;\n expires_at: string | null;\n user: User | null;\n}\n\nfunction identityStorageKey(projectId: string): string {\n return `identity:v${BROWSER_IDENTITY_VERSION}:project:${encodeURIComponent(projectId)}`;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction containsControlCharacter(value: string): boolean {\n return Array.from(value).some((char) => {\n const code = char.charCodeAt(0);\n return code <= 0x1f || code === 0x7f;\n });\n}\n\n/** Read + validate a project-bound identity snapshot. Corrupt or cross-project\n * data is removed instead of being handed to the core. */\nfunction readPersistedIdentity(\n persistence: LocalStoragePersistence,\n projectId: string,\n): PersistedBrowserIdentity | null {\n const key = identityStorageKey(projectId);\n const raw = persistence.read(key);\n if (!raw) return null;\n try {\n const value = JSON.parse(raw) as Partial<PersistedBrowserIdentity>;\n const hasTokens = value.session_token != null || value.refresh_token != null;\n if (\n value.version !== BROWSER_IDENTITY_VERSION ||\n value.project_id !== projectId ||\n !isNonEmptyString(value.anonymous_id) ||\n (hasTokens && !isNonEmptyString(value.app_user_id)) ||\n (value.session_token != null && !isNonEmptyString(value.refresh_token)) ||\n (value.session_token != null && !isNonEmptyString(value.session_token)) ||\n (value.refresh_token != null && !isNonEmptyString(value.refresh_token)) ||\n (value.expires_at != null &&\n (!isNonEmptyString(value.expires_at) ||\n !Number.isFinite(Date.parse(value.expires_at))))\n ) {\n throw new Error(\"invalid identity snapshot\");\n }\n\n const expiresAt = isNonEmptyString(value.expires_at)\n ? Date.parse(value.expires_at)\n : Number.NaN;\n const sessionExpired =\n value.session_token != null &&\n Number.isFinite(expiresAt) &&\n expiresAt <= Date.now();\n return {\n version: BROWSER_IDENTITY_VERSION,\n project_id: projectId,\n anonymous_id: value.anonymous_id,\n app_user_id: isNonEmptyString(value.app_user_id)\n ? value.app_user_id\n : null,\n session_token: sessionExpired ? null : (value.session_token ?? null),\n refresh_token: value.refresh_token ?? null,\n expires_at: sessionExpired ? null : (value.expires_at ?? null),\n user:\n value.user && typeof value.user === \"object\" && isNonEmptyString(value.user.id)\n ? value.user\n : null,\n };\n } catch {\n persistence.delete(key);\n return null;\n }\n}\n\nfunction writeIdentitySnapshot(): void {\n if (\n !_persistence?.available ||\n !_configuredProjectId ||\n !coreInstance\n )\n return;\n const snapshot: PersistedBrowserIdentity = {\n version: BROWSER_IDENTITY_VERSION,\n project_id: _configuredProjectId,\n anonymous_id: coreInstance.anonymousId(),\n app_user_id: coreInstance.appUserId() ?? null,\n session_token: _currentSessionToken,\n refresh_token: _currentRefreshToken,\n expires_at: _currentSessionExpiresAt,\n user: _currentUser,\n };\n _persistence.write(identityStorageKey(_configuredProjectId), JSON.stringify(snapshot));\n}\n\n/**\n * Functions invocation state. `_configuredFunctionsBaseUrl` is the optional\n * override from `Amba.configure({ functionsBaseUrl })`. The default-host path\n * caches its resolved project id and shares an in-flight diagnostics fallback\n * across concurrent invokes. All three reset on every `configure()`.\n */\nlet _configuredFunctionsBaseUrl: string | null = null;\nlet _resolvedFunctionsProjectId: string | null = null;\nlet _functionsProjectIdInflight: Promise<string> | null = null;\n\n/**\n * Offline-resilience state. `_persistence` is the localStorage-backed store\n * captured at configure-time; `_offlineQueue` is lazily built the first time\n * `Amba.offline.enable()` runs. `_offlineEnabled` gates whether failed writes\n * are diverted to the durable outbox (opt-in — default behavior is unchanged,\n * a failed write throws). All three reset on every `configure()`.\n */\nlet _persistence: LocalStoragePersistence | null = null;\nlet _offlineQueue: OfflineQueue | null = null;\nlet _offlineEnabled = false;\n/**\n * Detacher for the active queue's `online` auto-flush listener, if installed.\n * Held so a `configure()` (or a fresh `enable()`) can tear it down — otherwise\n * the previous queue's listener leaks and fires against a dead queue.\n */\nlet _offlineDetach: (() => void) | null = null;\n/**\n * Monotonic token identifying the CURRENT offline activation. Bumped on every\n * `enable()` AND every teardown/`configure()`, and captured by each `enable()`\n * into its disable callback. A disabler only acts while the token still matches\n * — so a stale disabler (held across a re-`enable()` OR a reconfigure) becomes\n * a no-op instead of wrongly turning the now-active outbox off.\n */\nlet _offlineEnableToken = 0;\n\n/**\n * Tear down any active offline wiring: detach the `online` listener, DISPOSE\n * the active queue (awaiting any in-flight flush so it can never write the\n * shared outbox again), and drop the references. Bumps the generation so stale\n * `enable()` disable callbacks no-op. Called before re-initializing on\n * `configure()`. Async so the in-flight flush is awaited before a replacement\n * queue is installed — preventing an orphaned flush from racing the new queue.\n */\nasync function teardownOffline(): Promise<void> {\n if (_offlineDetach) {\n _offlineDetach();\n _offlineDetach = null;\n }\n const old = _offlineQueue;\n _offlineQueue = null;\n _offlineEnabled = false;\n _offlineEnableToken += 1;\n if (old) await old.dispose();\n}\n\n/**\n * Bridge the localStorage persistence adapter to the {@link KvStore} contract\n * the offline outbox persists through. Namespaced under the configured\n * storage prefix so multiple projects in one origin don't collide.\n */\nfunction offlineStore(): KvStore {\n const p = _persistence;\n if (!p) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before using Amba.offline.\",\n );\n }\n return {\n get: (key) => p.read(key),\n set: (key, value) => p.write(key, value),\n delete: (key) => p.delete(key),\n };\n}\n\n/**\n * Replay a single queued mutation against the live API, forwarding the op's\n * idempotency key on collection writes so a write that landed before the\n * connection dropped is never double-applied. Used as the {@link OfflineQueue}\n * executor.\n */\nasync function replayOutboxOp(op: OutboxOp): Promise<void> {\n switch (op.kind) {\n case \"collection.insert\":\n await coreCall((c) =>\n c.collectionsInsert(\n op.collection as string,\n encodeJson({ ...op.payload, idempotency_key: op.idempotencyKey }),\n ),\n );\n return;\n case \"collection.update\":\n await coreCall((c) =>\n c.collectionsUpdate(\n op.collection as string,\n op.id as string,\n encodeJson(op.payload ?? {}),\n ),\n );\n return;\n case \"collection.delete\":\n await coreCall((c) =>\n c.collectionsDelete(op.collection as string, op.id as string),\n );\n return;\n case \"track\":\n // Replay through a direct events POST when the original time was captured\n // at enqueue, so the event keeps its ORIGINAL `occurred_at` rather than\n // being re-stamped to flush-time (which `core.track` would do). The\n // server attributes engagement events to the CURRENT session user — the\n // events route has no anonymous/body-id attribution channel — so a\n // buffered event is recorded against whoever is signed in at replay; only\n // its timestamp is preserved. (Core fallback for ops queued before\n // capture existed.)\n if (op.occurredAt !== undefined) {\n await replayTrackDirect(op);\n } else {\n await coreCall((c) =>\n c.track(\n op.event as string,\n op.properties ? encodeJson(op.properties) : undefined,\n op.telemetry,\n ),\n );\n }\n return;\n }\n}\n\n/**\n * Replay a buffered `track` op via a direct POST to `/v1/client/events`,\n * preserving the original `timestamp` captured at enqueue (the events route\n * honors body `timestamp` → `occurred_at`, but attributes the event to the\n * current session user — it has no body-`anonymous_id` attribution). Throws on\n * a transport failure or non-2xx so the queue retains the op.\n *\n * A `401`/`403` (no / expired session at replay) is surfaced as a coded\n * `NETWORK_ERROR` so the outbox RETAINS the event for a later attempt — an\n * unauthenticated replay must NOT be dropped as a deterministic rejection\n * (the session may refresh and the event then lands).\n */\nasync function replayTrackDirect(op: OutboxOp): Promise<void> {\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (_configuredClientKey) headers[\"X-Api-Key\"] = _configuredClientKey;\n if (_currentSessionToken)\n headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n const body: Record<string, unknown> = {\n event: op.event,\n properties: op.properties ?? undefined,\n timestamp: op.occurredAt,\n ...(op.telemetry ? { telemetry: true } : {}),\n };\n // Unique var name (not the generic `url`) so the SDK-route-drift extractor\n // maps THIS fetch to /v1/client/events — it keys url-vars by identifier, and\n // a shared `url` name across call-sites collides (sdk-route-drift.test.ts).\n const eventsUrl = `${_configuredApiUrl.replace(/\\/$/, \"\")}/v1/client/events`;\n let resp: Response;\n try {\n resp = await fetch(eventsUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n } catch (err) {\n throw new AmbaApiError(\"NETWORK_ERROR\", `track replay network error: ${String(err)}`);\n }\n if (!resp.ok) {\n // Treat a missing/expired session at replay as transient — retain, don't\n // drop. The outbox's deterministic-drop set includes UNAUTHORIZED/FORBIDDEN\n // (right for an immediate write the caller can fix), but for an\n // already-buffered analytics event a re-auth on the next flush should let\n // it through rather than silently losing it.\n if (resp.status === 401 || resp.status === 403) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `track replay deferred: no valid session (status ${resp.status})`,\n );\n }\n const text = await resp.text().catch(() => \"\");\n let code = `HTTP_${resp.status}`;\n try {\n const parsed = JSON.parse(text) as { error?: { code?: unknown } };\n if (typeof parsed?.error?.code === \"string\") code = parsed.error.code;\n } catch {\n // non-JSON body — keep the synthesized HTTP_<status> code\n }\n throw new AmbaApiError(code, `track replay failed with status ${resp.status}`, text);\n }\n}\n\nfunction getOfflineQueue(): OfflineQueue {\n if (!_offlineQueue) {\n _offlineQueue = new OfflineQueue({\n store: offlineStore(),\n // Scope the outbox by project so switching projects (same storage prefix)\n // can't replay a prior project's queued writes through the new project's\n // credentials — they'd land in the wrong project. Each project's outbox\n // is independent and persists under its own key.\n storageKey: `offline_outbox:${_configuredProjectId ?? \"default\"}`,\n executor: replayOutboxOp,\n onDropped: (op, err) => {\n // A queued write the server now rejects deterministically (e.g. a\n // VALIDATION_ERROR on replay) is dropped so it can't wedge the queue.\n // Surface it rather than discarding silently.\n if (typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped un-replayable offline ${op.kind} (${op.collection ?? op.event ?? \"?\"}):`,\n err,\n );\n }\n },\n });\n }\n return _offlineQueue;\n}\n\n// Direct (never-queued) collection writes. The optimistic helpers use these so\n// a write either truly succeeds (resolve with the server row) or truly fails\n// (reject → rollback). Optimistic UI needs an unambiguous outcome, so it must\n// NOT silently divert to the offline outbox — a queued-pending stub is not a\n// confirmed write, and treating it as success would commit the optimistic value\n// as final even though the server never saw it.\nasync function insertDirect<T>(\n name: string,\n row: Record<string, unknown>,\n): Promise<T> {\n return (await coreCall((c) => c.collectionsInsert(name, encodeJson(row)))) as T;\n}\nasync function updateDirect<T>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n): Promise<T> {\n return (await coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set)),\n )) as T;\n}\nasync function deleteDirect(\n name: string,\n id: string,\n): Promise<{ data: { deleted: boolean } }> {\n return (await coreCall((c) => c.collectionsDelete(name, id))) as {\n data: { deleted: boolean };\n };\n}\n\nfunction requireCore(): AmbaCoreWasm {\n if (!coreInstance) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.\",\n );\n }\n return coreInstance;\n}\n\n/**\n * Run a single SDK-runtime call and rewrap any thrown value into a\n * stable `AmbaApiError`. Every method on `Amba` funnels through this\n * so callers see one error shape regardless of which layer failed\n * (network, validation, etc.). Sync `requireCore()` accessors\n * (e.g. the `anonymousId` getter) already throw the typed shape on\n * their own.\n */\nasync function coreCall<T>(fn: (c: AmbaCoreWasm) => Promise<T>): Promise<T> {\n try {\n return await fn(requireCore());\n } catch (err) {\n throw toAmbaApiError(err);\n }\n}\n\n/**\n * In-SDK auth-state pub/sub. `signIn*` / `signUp*` / `signOut` /\n * `refresh` call `notifyAuthSubscribers` after their underlying core\n * call resolves so subscribers always see a consistent `Session | null`\n * snapshot. Subscriber callbacks run synchronously inside the\n * notification path — keep them light; offload heavy work to your own\n * scheduler.\n */\ntype AuthSubscriber = (session: Session | null) => void;\nconst authSubscribers = new Set<AuthSubscriber>();\n\n/**\n * Translate a public `TransactionOp` (camelCase `onConflict` /\n * `conflictTarget`) into the server's wire op (snake_case `on_conflict` /\n * `conflict_target`). Unset optionals are dropped so the server applies its\n * defaults.\n */\nfunction toWireTransactionOp(op: TransactionOp): Record<string, unknown> {\n const wire: Record<string, unknown> = { collection: op.collection, op: op.op };\n if (op.id !== undefined) wire[\"id\"] = op.id;\n if (op.row !== undefined) wire[\"row\"] = op.row;\n if (op.set !== undefined) wire[\"set\"] = op.set;\n if (op.where !== undefined) wire[\"where\"] = op.where;\n if (op.expected !== undefined) wire[\"expected\"] = op.expected;\n if (op.onConflict !== undefined) wire[\"on_conflict\"] = op.onConflict;\n if (op.conflictTarget !== undefined) wire[\"conflict_target\"] = op.conflictTarget;\n return wire;\n}\n\nfunction snapshotSession(user: User | null): Session | null {\n if (!user || !_currentSessionToken) return null;\n return {\n sessionToken: _currentSessionToken,\n refreshToken: _currentRefreshToken ?? \"\",\n user,\n expiresAt: _currentSessionExpiresAt ?? \"\",\n };\n}\n\nfunction notifyAuthSubscribers(session: Session | null): void {\n for (const cb of authSubscribers) {\n try {\n cb(session);\n } catch (err) {\n // Subscriber errors are isolated — one bad callback doesn't\n // block the rest. Surface in debug logs but don't rethrow into\n // the auth call path, which would mask the success result.\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] onAuthStateChange callback threw\", err);\n }\n }\n }\n}\n\nasync function notifyFromAuthResult(result: AuthResult): Promise<void> {\n _currentSessionToken = result.session_token;\n _currentRefreshToken = result.refresh_token;\n _currentSessionExpiresAt =\n result.expires_at && Number.isFinite(Date.parse(result.expires_at))\n ? result.expires_at\n : new Date(Date.now() + DEFAULT_SESSION_TTL_MS).toISOString();\n _currentUser = result.user;\n writeIdentitySnapshot();\n notifyAuthSubscribers(snapshotSession(result.user));\n}\n\n/**\n * Build the auth headers for a realtime subscribe connection, or `null`\n * when there's no live session. Read lazily at connect-time so reconnects\n * pick up a rotated session token.\n */\nfunction realtimeAuthHeaders(): Record<string, string> | null {\n if (!_configuredClientKey || !_currentSessionToken) return null;\n return {\n \"X-Api-Key\": _configuredClientKey,\n Authorization: `Bearer ${_currentSessionToken}`,\n };\n}\n\n/** SDK-facing AI streaming routes (vendor split is a stable wire shape). */\nconst AI_ANTHROPIC_PATH = \"/v1/client/ai/anthropic/messages\";\nconst AI_OPENAI_PATH = \"/v1/client/ai/openai/chat/completions\";\n\n/**\n * Build the request (url + headers + body) for a streamed AI invocation.\n * Forces `stream: true` onto the request, injects the client key + live\n * session token, and targets the configured API base. Shared by the\n * Anthropic + OpenAI streaming methods so the auth/path wiring can't drift.\n */\nfunction buildAiStreamRequest(\n path: string,\n request: AiMessageRequest,\n): { url: string; headers: Record<string, string>; body: string } {\n requireCore();\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before streaming AI.\",\n );\n }\n if (!request.prompt_slug || request.prompt_slug.trim() === \"\") {\n throw new AmbaApiError(\"INVALID_ARGUMENT\", \"prompt_slug is required\");\n }\n const headers: Record<string, string> = {\n \"X-Api-Key\": _configuredClientKey,\n \"content-type\": \"application/json\",\n accept: \"text/event-stream\",\n };\n if (_currentSessionToken) headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n // Expand any convenience `images` into content blocks before serializing.\n const expanded = expandAiImages(request);\n return {\n url: `${_configuredApiUrl.replace(/\\/$/, \"\")}${path}`,\n headers,\n body: JSON.stringify({ ...expanded, stream: true }),\n };\n}\n\n/**\n * Resolve the full invocation URL for a deployed function.\n *\n * Resolution checks `functionsBaseUrl` first, then uses a UUID-shaped\n * configured project id directly, and finally falls back to a deduped\n * `diagnostics.ping` for the authoritative server-side project id. The\n * resolved default-host id is cached for subsequent invokes.\n */\nasync function resolveFunctionUrl(name: string): Promise<string> {\n if (_configuredFunctionsBaseUrl) {\n return `${_configuredFunctionsBaseUrl.replace(/\\/$/, \"\")}/${encodeURIComponent(name)}`;\n }\n const projectId = await resolveFunctionsProjectId();\n return `https://${projectId}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(name)}`;\n}\n\nasync function resolveFunctionsProjectId(): Promise<string> {\n if (_resolvedFunctionsProjectId) {\n return _resolvedFunctionsProjectId;\n }\n if (\n _configuredProjectId &&\n PROJECT_ID_PATTERN.test(_configuredProjectId)\n ) {\n _resolvedFunctionsProjectId = _configuredProjectId.toLowerCase();\n return _resolvedFunctionsProjectId;\n }\n if (!_functionsProjectIdInflight) {\n const configuredProjectId = _configuredProjectId;\n const resolution = (async () => {\n const ping = await Amba.diagnostics.ping();\n if (!ping.ok || !ping.server_project_id) {\n throw new AmbaApiError(\n \"FUNCTIONS_HOST_UNRESOLVED\",\n \"Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.\",\n ping,\n );\n }\n if (\n _configuredProjectId === configuredProjectId &&\n !_configuredFunctionsBaseUrl\n ) {\n _resolvedFunctionsProjectId = ping.server_project_id;\n }\n return ping.server_project_id;\n })();\n _functionsProjectIdInflight = resolution;\n void resolution.then(\n () => {\n if (_functionsProjectIdInflight === resolution) {\n _functionsProjectIdInflight = null;\n }\n },\n () => {\n if (_functionsProjectIdInflight === resolution) {\n _functionsProjectIdInflight = null;\n }\n },\n );\n }\n return _functionsProjectIdInflight;\n}\n\n/** Options for `Amba.functions.invoke`. */\nexport interface InvokeOptions {\n /** HTTP method. Defaults to `\"POST\"`. */\n method?: \"POST\" | \"GET\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Extra headers merged on top of the injected auth headers. */\n headers?: Record<string, string>;\n /**\n * Called with the server-side request / trace id (the\n * `X-Amba-Request-Id` response header) as soon as a response is\n * received — on both success and failure. Use it to correlate a run with\n * server logs (`functions logs --request-id <traceId>`). `null` when the\n * server returned no trace id (e.g. a pre-auth rejection that doesn't\n * stamp the header).\n *\n * The same id is also attached to any thrown `AmbaApiError` as\n * `error.traceId`, so error-path correlation works without a callback.\n */\n onTraceId?: (traceId: string | null) => void;\n}\n\n/**\n * Immutable browser collection scope for a server-verified group context.\n * Each method carries its own `groupId` into the Rust request path; no mutable\n * global header state is used, so parallel group A/group B calls cannot leak.\n *\n * Group-scoped writes deliberately bypass the global offline outbox. Replaying\n * a queued group mutation without its membership context would be unsafe;\n * callers receive the real network outcome and can retry in the same scope.\n */\nexport class AmbaGroupScopedClient {\n readonly groupId: string;\n\n constructor(groupId: string) {\n const normalized = groupId.trim();\n if (!normalized || containsControlCharacter(normalized)) {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"Amba.asGroup: groupId must be non-empty and contain no control characters\",\n );\n }\n this.groupId = normalized;\n }\n\n asGroup(groupId: string): AmbaGroupScopedClient {\n return new AmbaGroupScopedClient(groupId);\n }\n\n inGroup(groupId: string): AmbaGroupScopedClient {\n return this.asGroup(groupId);\n }\n\n collection<T = unknown>(name: string): CollectionSubscription<T> {\n const groupId = this.groupId;\n return new CollectionSubscription<T>(\n {\n baseUrl: _configuredApiUrl,\n authHeaders: () => {\n const headers = realtimeAuthHeaders();\n return headers ? { ...headers, \"X-Group-Context\": groupId } : null;\n },\n },\n name,\n );\n }\n\n collections = {\n find: async <T = unknown>(\n name: string,\n options: FindOptions = {},\n ): Promise<FindResponse<T>> =>\n (await coreCall((c) =>\n c.collectionsFind(name, encodeJson(options), this.groupId),\n )) as FindResponse<T>,\n\n findOne: async <T = unknown>(name: string, id: string): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsFindOne(name, id, this.groupId),\n )) as T,\n\n insert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n ): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsInsert(name, encodeJson(row), this.groupId),\n )) as T,\n\n update: async <T = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n ): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set), this.groupId),\n )) as T,\n\n delete: async (\n name: string,\n id: string,\n ): Promise<{ data: { deleted: boolean } }> =>\n (await coreCall((c) =>\n c.collectionsDelete(name, id, this.groupId),\n )) as { data: { deleted: boolean } },\n\n upsert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n options: UpsertOptions,\n ): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsUpsert(\n name,\n encodeJson(row),\n encodeJson({\n on_conflict: options.onConflict,\n conflict_target: options.conflictTarget,\n }),\n this.groupId,\n ),\n )) as T,\n\n deleteWhere: async (\n name: string,\n filter: Filter,\n ): Promise<DeleteManyResult> =>\n (await coreCall((c) =>\n c.collectionsDeleteWhere(name, encodeJson(filter), this.groupId),\n )) as DeleteManyResult,\n\n restore: async <T = unknown>(name: string, id: string): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsRestore(name, id, this.groupId),\n )) as T,\n\n transaction: async (\n operations: TransactionOp[],\n ): Promise<TransactionOpResult[]> => {\n const env = (await coreCall((c) =>\n c.collectionsTransaction(\n encodeJson(operations.map(toWireTransactionOp)),\n this.groupId,\n ),\n )) as { data: TransactionOpResult[] };\n return env.data;\n },\n\n findNearest: async <T = unknown>(\n name: string,\n options: NearestOptions,\n ): Promise<NearestResponse<T>> =>\n (await coreCall((c) =>\n c.collectionsFindNearest(name, encodeJson(options), this.groupId),\n )) as NearestResponse<T>,\n\n count: async (name: string, filter?: Filter): Promise<number> =>\n (await coreCall((c) =>\n c.collectionsCount(\n name,\n filter ? encodeJson(filter) : undefined,\n this.groupId,\n ),\n )) as number,\n\n optimisticInsert: async <T = unknown, TApply = unknown>(\n name: string,\n row: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () =>\n coreCall((c) =>\n c.collectionsInsert(name, encodeJson(row), this.groupId),\n ) as Promise<T>,\n rollback: view.rollback,\n }),\n\n optimisticUpdate: async <T = unknown, TApply = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () =>\n coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set), this.groupId),\n ) as Promise<T>,\n rollback: view.rollback,\n }),\n\n optimisticDelete: async <TApply = unknown>(\n name: string,\n id: string,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<{ data: { deleted: boolean } }> =>\n withOptimistic({\n apply: view.apply,\n mutate: () =>\n coreCall((c) =>\n c.collectionsDelete(name, id, this.groupId),\n ) as Promise<{ data: { deleted: boolean } }>,\n rollback: view.rollback,\n }),\n\n where: {\n eq: (column: string, value: FilterValue): Filter => ({ column, op: \"eq\", value }),\n ne: (column: string, value: FilterValue): Filter => ({ column, op: \"ne\", value }),\n gt: (column: string, value: FilterValue): Filter => ({ column, op: \"gt\", value }),\n gte: (column: string, value: FilterValue): Filter => ({ column, op: \"gte\", value }),\n lt: (column: string, value: FilterValue): Filter => ({ column, op: \"lt\", value }),\n lte: (column: string, value: FilterValue): Filter => ({ column, op: \"lte\", value }),\n in: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"in\",\n value: values,\n }),\n isNull: (column: string): Filter => ({ column, op: \"is_null\" }),\n isNotNull: (column: string): Filter => ({ column, op: \"is_not_null\" }),\n and: (...filters: Filter[]): Filter => ({ and: filters }),\n or: (...filters: Filter[]): Filter => ({ or: filters }),\n not: (filter: Filter): Filter => ({ not: filter }),\n },\n };\n}\n\n/**\n * Top-level SDK namespace.\n *\n * Stateless from the JS side — all state lives in the SDK runtime. Calling\n * `Amba.configure()` multiple times replaces the previous instance.\n */\nexport const Amba = {\n /**\n * Initialize the SDK. Must be called once at app start.\n *\n * Seeds an `anonymous_id` from localStorage (if present) or generates a\n * fresh one.\n */\n configure: async (config: AmbaConfig): Promise<void> => {\n if (!config.projectId || config.projectId.trim() === \"\") {\n throw new Error(\"Amba.configure: projectId is required\");\n }\n if (!config.clientKey || config.clientKey.trim() === \"\") {\n throw new Error(\"Amba.configure: clientKey is required\");\n }\n if (config.wasmUrl !== undefined && config.wasmModule !== undefined) {\n throw new Error(\"Amba.configure: pass only one of wasmUrl or wasmModule\");\n } else {\n const { initCore } = await getCoreRuntime();\n const explicitWasmInput = config.wasmModule ?? config.wasmUrl;\n if (explicitWasmInput !== undefined) {\n await initCore({ module_or_path: explicitWasmInput });\n } else {\n // wasm-pack --target web's auto-loader does `fetch(new URL(\"./amba_core_bg.wasm\", import.meta.url))`.\n // That resolves to a `file://` URL in Node, and Node's undici doesn't\n // support the `file:` scheme — throws `fetch failed: not implemented`.\n // In Node (server-rendering, Jest/Vitest under jsdom, customer-shoes\n // smoke), read the wasm via fs and pass the bytes to init() directly.\n // In browsers, the browser export condition swaps in @layers/amba-core/inline\n // before configure() runs, so initCore() decodes embedded wasm bytes instead\n // of asking wasm-pack to infer a sibling .wasm URL under Vite's dep cache.\n if (\n !IS_BROWSER_BUILD &&\n typeof process !== \"undefined\" &&\n !!(process.versions && process.versions.node)\n ) {\n const [\n { readFile },\n { fileURLToPath, pathToFileURL },\n { createRequire },\n ] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:url\"),\n import(\"node:module\"),\n ]);\n // Resolve the wasm path through @layers/amba-core's package\n // root so we follow the same module resolution as the .js glue\n // — works whether the consumer used a bundler or a raw `node`.\n //\n // Resolution strategy, in order:\n // 1. `import.meta.resolve` (Node ≥20.6, stable in 22). Async or sync\n // depending on Node version — both return a string when awaited.\n // 2. `require.resolve` via `createRequire(import.meta.url)`. Uses\n // Node's standard module resolution which honors pnpm's\n // `.pnpm/<pkg>@<ver>/node_modules/<pkg>` layout. The previous\n // `../node_modules/...` hard-coded fallback failed on pnpm.\n // This mirrors the Node SDK fix at `packages/node/src/index.ts:124`.\n const resolveFn = (\n import.meta as unknown as {\n resolve?: (s: string) => string | Promise<string>;\n }\n ).resolve;\n let wasmJsUrl: string;\n if (resolveFn) {\n wasmJsUrl = await resolveFn(\"@layers/amba-core/wasm\");\n } else {\n const req = createRequire(import.meta.url);\n // `require.resolve` returns a filesystem path; convert to a\n // file:// URL so `fileURLToPath` below works uniformly with the\n // `import.meta.resolve` branch.\n wasmJsUrl = pathToFileURL(\n req.resolve(\"@layers/amba-core/wasm\"),\n ).href;\n }\n const bytes = await readFile(fileURLToPath(wasmJsUrl));\n await initCore({ module_or_path: bytes });\n } else await initCore();\n }\n }\n const persistence = new LocalStoragePersistence(\n config.storagePrefix ?? \"amba\",\n );\n const restoredIdentity = readPersistedIdentity(persistence, config.projectId);\n\n // Wrap `AmbaCoreWasm.init`'s throw with `toAmbaApiError` so\n // configure() rejects with an `AmbaApiError` instance — same\n // contract as every method call. Without this, the engine's\n // post-#63 `JsValue` throws would surface to customers as plain\n // objects, breaking `e instanceof Error` checks in\n // configure-failure handlers. (Reported by Cursor BugBot on\n // PR #156, high.)\n //\n // Build the new core BEFORE touching any module state (offline teardown,\n // persistence, the `_configured*` cache): if init throws, the previous\n // session — including its offline wiring + persistence — stays fully intact\n // rather than being left half-reconfigured for a failed attempt.\n let nextCore: AmbaCoreWasm;\n try {\n const { wasmClass } = await getCoreRuntime();\n nextCore = wasmClass.init(\n encodeJson({\n api_key: config.clientKey,\n base_url: config.apiUrl,\n project_id: config.projectId,\n sdk_platform: \"web\",\n sdk_wrapper_version: `amba-web/${SDK_VERSION}`,\n consent_required: config.consentRequired,\n debug: config.debug,\n }),\n globalThis.fetch.bind(globalThis),\n );\n if (restoredIdentity) {\n // Hydrate the Rust core itself — not merely TS getters — so every\n // core-backed request and refresh sees the restored credentials.\n nextCore.hydrateIdentity(\n encodeJson({\n anonymous_id: restoredIdentity.anonymous_id,\n app_user_id: restoredIdentity.app_user_id,\n session_token: restoredIdentity.session_token,\n refresh_token: restoredIdentity.refresh_token,\n expires_at: restoredIdentity.expires_at,\n }),\n );\n }\n } catch (err) {\n throw toAmbaApiError(err);\n }\n\n // Init succeeded — now it's safe to swap state. Tear down any prior offline\n // wiring (detach the previous queue's `online` listener, dispose it, and\n // AWAIT any in-flight flush so the orphaned queue can never write the\n // outbox again), rebind persistence, and commit the core + config cache.\n await teardownOffline();\n _persistence = persistence;\n coreInstance = nextCore;\n _configuredProjectId = config.projectId;\n _configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;\n _configuredClientKey = config.clientKey;\n _currentSessionToken = nextCore.isAuthenticated()\n ? (restoredIdentity?.session_token ?? null)\n : null;\n _currentRefreshToken = restoredIdentity?.refresh_token ?? null;\n _currentSessionExpiresAt = nextCore.isAuthenticated()\n ? (restoredIdentity?.expires_at ?? null)\n : null;\n _currentUser = nextCore.isAuthenticated()\n ? (restoredIdentity?.user ?? null)\n : null;\n _configuredFunctionsBaseUrl = config.functionsBaseUrl ?? null;\n // A fresh configure() may point at a different project — drop any cached\n // function-host resolution so the next invoke re-resolves.\n _resolvedFunctionsProjectId = null;\n _functionsProjectIdInflight = null;\n if (\n !_configuredFunctionsBaseUrl &&\n !PROJECT_ID_PATTERN.test(config.projectId)\n ) {\n void resolveFunctionsProjectId().catch(() => {});\n }\n // Persist the core-minted anonymous id even before sign-in so a plain\n // configure/reload cycle keeps the same browser identity.\n writeIdentitySnapshot();\n },\n\n /** Project id this SDK instance is scoped to. */\n get projectId(): string {\n if (!_configuredProjectId) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.\",\n );\n }\n return _configuredProjectId;\n },\n\n /** Anonymous id — stable per browser. */\n get anonymousId(): string {\n return requireCore().anonymousId();\n },\n\n /** Authenticated user id, if a session is live. */\n get appUserId(): string | undefined {\n return requireCore().appUserId() ?? undefined;\n },\n\n /** Whether a session token is currently held. */\n get isAuthenticated(): boolean {\n return requireCore().isAuthenticated();\n },\n\n /** Toggle verbose logging at runtime. */\n setDebug: (enabled: boolean): void => {\n requireCore().setDebug(enabled);\n },\n\n // ── Core 9 namespaces ─────────────────────────────────────────────\n\n /** Track engagement events. */\n events: {\n /**\n * Track an event.\n *\n * The default route writes to the engagement meter — events that\n * fan out to segments, workflows, and push. Pass\n * `{ telemetry: true }` for high-volume, fire-and-forget events\n * that DON'T need fan-out: UI button clicks, scroll-depth markers,\n * game-loop telemetry. Telemetry events are billed at $0.10 per 1M\n * overage vs $0.50 per 10K for engagement events — a ~500×\n * difference that mirrors the underlying compute cost.\n *\n * Telemetry events are persisted and queryable via analytics, but\n * skip segment recompute, streak qualification, currency grant\n * rules, and `last_seen_at` updates.\n *\n * @example\n * await Amba.events.track('purchase_completed', { amount: 9.99 });\n * await Amba.events.track('scroll_depth', { px: 1024 }, { telemetry: true });\n */\n track: async (\n event: string,\n properties?: Record<string, unknown>,\n options?: { telemetry?: boolean },\n ): Promise<void> => {\n try {\n await coreCall((c) => c.track(\n event,\n properties ? encodeJson(properties) : undefined,\n options?.telemetry,\n ));\n } catch (err) {\n // When the offline outbox is enabled, a transport failure buffers the\n // event for replay-on-reconnect instead of being lost. Deterministic\n // failures (validation, auth) still throw.\n if (_offlineEnabled && isOfflineError(err)) {\n // Capture the original event TIME so the buffered event keeps its\n // `occurred_at` on replay (the events route honors a body timestamp).\n // We do NOT capture an identity: that route attributes engagement\n // events to the session user at replay time, with no body-id\n // attribution channel — so a buffered event lands against whoever is\n // signed in when it flushes.\n await getOfflineQueue().enqueue({\n kind: \"track\",\n event,\n properties,\n telemetry: options?.telemetry,\n occurredAt: new Date().toISOString(),\n });\n return;\n }\n throw err;\n }\n },\n\n /**\n * Dry-run an event against the rule engine WITHOUT firing it. Returns\n * which rules would match and what each WOULD produce — XP awards,\n * currency grants, feed items, streak qualification, and webhook\n * deliveries — so you can predict the consequences of a `track` call\n * before committing any side effect. Nothing is written.\n *\n * Pass `{ userId }` to also preview per-user gating (daily limits,\n * cooldowns, balance caps); omit it for the user-independent base match.\n *\n * @example\n * const plan = await Amba.events.explain('lesson_completed', { score: 90 });\n * // plan.matched → [{ rule_type: 'xp', would: { xp_delta: 25 }, ... }, ...]\n */\n explain: async (\n event: string,\n properties?: Record<string, unknown>,\n options?: { userId?: string | null },\n ): Promise<EventExplainResult> => {\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before Amba.events.explain().\",\n );\n }\n if (!event || event.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.events.explain: an event name is required.\",\n );\n }\n\n const candidate: Record<string, unknown> = { name: event };\n if (properties !== undefined) candidate[\"properties\"] = properties;\n if (options && \"userId\" in options) candidate[\"user_id\"] = options.userId ?? null;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-Api-Key\": _configuredClientKey,\n };\n if (_currentSessionToken) headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n\n let resp: Response;\n try {\n resp = await fetch(\n `${_configuredApiUrl.replace(/\\/$/, \"\")}/v1/client/events/explain`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({ event: candidate }),\n },\n );\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.events.explain(\"${event}\") network error: ${String(err)}`,\n );\n }\n\n const text = await resp.text();\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = undefined;\n }\n }\n if (!resp.ok) {\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\"\n ? envelope.error.code\n : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : `Amba.events.explain(\"${event}\") failed with status ${resp.status}`;\n throw new AmbaApiError(code, message, parsed);\n }\n return (parsed as { data: EventExplainResult }).data;\n },\n },\n\n /** Authentication flows. */\n auth: {\n signInAnonymously: async (): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInAnonymously())) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n signInWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInWithEmail(\n email,\n password,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n signUpWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signUpWithEmail(\n email,\n password,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Sign in with an Apple identity token (the JWT returned by Sign\n * in with Apple). Replaces the SDK 3.x polymorphic\n * `signInWithSocial('apple', token)` form — that surface is\n * removed in 4.0.\n */\n signInWithApple: async (identityToken: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInWithSocial(\n \"apple\",\n identityToken,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Sign in with a Google ID token (the JWT returned by Google\n * Sign-In). Replaces the SDK 3.x polymorphic\n * `signInWithSocial('google', token)` form — that surface is\n * removed in 4.0.\n */\n signInWithGoogle: async (idToken: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInWithSocial(\n \"google\",\n idToken,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Request a one-time passcode for `email`. The server emails a\n * 6-digit code; the user types it into the app, then call\n * `verifyEmailOtp(email, code)` to exchange for a session. Always\n * resolves successfully regardless of email registration — the\n * server uses uniform responses to prevent account enumeration.\n */\n requestEmailOtp: async (email: string): Promise<void> => {\n await coreCall((c) => c.requestEmailOtp(email));\n },\n /**\n * Exchange `(email, code)` for a session. Returns the same\n * `AuthResult` shape as `signInWithEmail` and persists tokens for\n * subsequent calls. Rejects with a single error code for every\n * failure mode (wrong/expired/exhausted) — the server intentionally\n * does not distinguish them to prevent enumeration.\n */\n verifyEmailOtp: async (email: string, code: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.verifyEmailOtp(email, code))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Request a one-time passcode by SMS. `phone` MUST be E.164\n * (starts with `+`, 8–15 total digits). The SDK rejects\n * anything else with a `VALIDATION_ERROR` before the network\n * call. Server sends a 6-digit code; user types it into the\n * app, then call `verifySmsOtp(phone, code)` to exchange for\n * a session. Always resolves successfully regardless of phone\n * registration — uniform responses prevent enumeration.\n */\n requestSmsOtp: async (phone: string): Promise<void> => {\n await coreCall((c) => c.requestSmsOtp(phone));\n },\n /**\n * Exchange `(phone, code)` for a session. Same shape +\n * failure semantics as `verifyEmailOtp`. Persists tokens for\n * subsequent calls.\n */\n verifySmsOtp: async (phone: string, code: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.verifySmsOtp(phone, code))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Request a magic-link email for `email`. The server emails the\n * link; the user clicks it, which delivers a one-time token back\n * to the app. Exchange that token for a session with\n * `verifyMagicLink(token)`. Always resolves successfully\n * regardless of email registration (uniform responses prevent\n * account enumeration).\n */\n requestMagicLink: async (email: string): Promise<void> => {\n await coreCall((c) => c.requestMagicLink(email));\n },\n /**\n * Exchange a magic-link `token` for a session. Returns the same\n * `AuthResult` shape as `signInWithEmail` and persists tokens for\n * subsequent calls.\n */\n verifyMagicLink: async (token: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.verifyMagicLink(token))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Link an additional auth provider to the current session — used to\n * upgrade an anonymous session to an identified one without losing\n * the user's data. `provider` is one of the supported identity\n * strings (e.g. `\"apple\"`, `\"google\"`); `credential` is the\n * provider-issued token (identity-token / id-token / OTP code).\n */\n linkAccount: async (\n provider: string,\n credential: string,\n ): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.linkAccount(\n provider,\n credential,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n\n /**\n * Attach a verified phone number to the **current session's user** —\n * without minting new tokens and without changing `user_id`.\n *\n * Flow: call `requestSmsOtp(phone)` first to mail the 6-digit code,\n * then call THIS instead of `verifySmsOtp` when the desired outcome\n * is \"decorate the current (often anonymous) user with this phone\"\n * rather than \"switch to the user who owns this phone\".\n *\n * Guarantees (symmetric to `linkEmailOtp`):\n *\n * - **Same `user_id`** before and after — collections, holds, XP,\n * `anonymous_id` all carry over.\n * - **Session token unchanged.** The existing token stays valid;\n * `onAuthStateChange` does NOT fire (no rotation).\n * - **Idempotent.** Re-linking the same phone to the same user\n * succeeds — no-op apart from `last_seen_at`.\n * - **Collision.** If the phone is already verified to a different\n * user, throws an error with `code = \"PHONE_ALREADY_LINKED\"` and\n * `details.conflicting_user_id` populated. The caller decides\n * whether to abort or switch identities via `verifySmsOtp`.\n *\n * Requires an active session — throws `UNAUTHORIZED` otherwise.\n *\n * @returns the updated user record (with `phone` set and\n * `phone_verified = true`).\n */\n linkSmsOtp: async (phone: string, code: string): Promise<User> => {\n return (await coreCall((c) => c.linkSmsOtp(phone, code))) as User;\n },\n\n /**\n * Attach a verified email to the current session's user. Symmetric\n * to `linkSmsOtp` — see that for the full guarantee list. Collision\n * code is `EMAIL_ALREADY_LINKED`.\n */\n linkEmailOtp: async (email: string, code: string): Promise<User> => {\n return (await coreCall((c) => c.linkEmailOtp(email, code))) as User;\n },\n\n /**\n * Upgrade an anonymous (or any current) session by attaching an\n * email + password credential — converting the current user in place\n * to an email-identified account so its data (streak, library,\n * progress, `anonymous_id`) is preserved rather than minting a new\n * user.\n *\n * Guarantees (symmetric to `linkEmailOtp`):\n *\n * - **Same `user_id`** before and after.\n * - **Session token unchanged.** No rotation; `onAuthStateChange`\n * does NOT fire.\n * - **Idempotent.** Re-linking the same email to the same user\n * succeeds.\n * - **Collision.** If the email is already owned by a different user,\n * throws an error with `code = \"EMAIL_ALREADY_LINKED\"` and\n * `details.conflicting_user_id` populated.\n *\n * After linking, the user can sign in with `signInWithEmail(email,\n * password)`. Requires an active session — throws `UNAUTHORIZED`\n * otherwise.\n *\n * @returns the updated user record (with `email` set and\n * `email_verified = true`).\n */\n linkEmailPassword: async (email: string, password: string): Promise<User> => {\n return (await coreCall((c) => c.linkEmailPassword(email, password))) as User;\n },\n\n /**\n * Detach the SMS-OTP identifier from the current session's user.\n * Refused with `LAST_IDENTIFIER` if this would leave the account\n * with no recoverable identifier (link another first, then unlink).\n * No token rotation.\n */\n unlinkSmsOtp: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkSmsOtp())) as User;\n },\n\n /** Detach the email-OTP identifier. Same guarantees as `unlinkSmsOtp`. */\n unlinkEmailOtp: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkEmailOtp())) as User;\n },\n\n /** Detach the Apple social identity from the current session's user. */\n unlinkApple: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkApple())) as User;\n },\n\n /** Detach the Google social identity from the current session's user. */\n unlinkGoogle: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkGoogle())) as User;\n },\n\n /**\n * Snapshot the identifier providers attached to the current\n * session's user. Lets a host app gate \"should I show the verify-\n * email prompt\" without a profile round-trip.\n *\n * Identifier values come back **masked** — raw values stay\n * server-side.\n */\n getCurrentIdentifiers: async (): Promise<LinkedIdentifier[]> => {\n return (await coreCall((c) => c.currentIdentifiers())) as LinkedIdentifier[];\n },\n signOut: async (rotateAnonymousId = false): Promise<void> => {\n await coreCall((c) => c.signOut(rotateAnonymousId));\n _currentSessionToken = null;\n _currentRefreshToken = null;\n _currentSessionExpiresAt = null;\n _currentUser = null;\n writeIdentitySnapshot();\n notifyAuthSubscribers(null);\n },\n refresh: async (): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.refreshSession())) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n me: async (): Promise<User> => {\n const user = (await coreCall((c) => c.me())) as User;\n _currentUser = user;\n writeIdentitySnapshot();\n return user;\n },\n\n /**\n * Snapshot the current session, or `null` if no session is live.\n * Tokens are returned from the browser's durable session snapshot. Treat\n * them as credentials: do not log them or copy them into app-managed\n * storage.\n */\n getSession: async (): Promise<Session | null> => {\n const core = requireCore();\n if (!core.isAuthenticated()) return null;\n const user = (await coreCall((c) => c.me())) as User;\n _currentUser = user;\n writeIdentitySnapshot();\n return snapshotSession(user);\n },\n\n /**\n * Stable anonymous identifier (kept in memory by the SDK runtime).\n * Sync today, async for parity with the RN/native shape where the\n * platform may need to await a persistence read.\n */\n getAnonymousId: async (): Promise<string> => {\n return requireCore().anonymousId();\n },\n\n /**\n * Subscribe to session changes. Fires after every `signIn*`,\n * `signUp*`, `refresh`, and `signOut` call. Returns an\n * unsubscribe function — call it to remove the callback.\n *\n * Does NOT fire an initial snapshot when subscribed; call\n * `getSession()` once after subscribing if you need the\n * current state up front.\n */\n onAuthStateChange: (\n cb: (session: Session | null) => void,\n ): (() => void) => {\n authSubscribers.add(cb);\n return () => {\n authSubscribers.delete(cb);\n };\n },\n },\n\n /** Profile read/update (SDK 4.0). */\n users: {\n // <generated:users>\n /**\n * Get a user profile. `userId` omitted → current user\n * (`GET /v1/client/users/me`); `userId` set → public-safe profile\n * of the other user (`GET /v1/client/users/{id}`).\n */\n get: async (userId?: string): Promise<User> => {\n return (await coreCall((c) => c.usersGet(\n userId ?? undefined,\n ))) as User;\n },\n /**\n * Update a profile. Two shapes:\n * - `update(patch)` — current user (`PATCH /users/me`).\n * - `update(userId, patch)` — admin-style cross-user update\n * (`PATCH /users/{id}`); the server requires the caller to\n * hold `users:write` and 403s otherwise.\n *\n * Allowed keys (both shapes): `display_name`, `avatar_url`,\n * `properties`, `timezone`.\n */\n update: (((\n arg1: string | Partial<User>,\n arg2?: Partial<User>,\n ): Promise<User> => {\n const isById = typeof arg1 === \"string\";\n const userId = isById ? (arg1 as string) : undefined;\n const patch = isById ? (arg2 ?? {}) : (arg1 as Partial<User>);\n return coreCall((c) => c.usersUpdate(\n userId ?? undefined,\n encodeJson(patch),\n )) as Promise<User>;\n }) as {\n (patch: Partial<User>): Promise<User>;\n (userId: string, patch: Partial<User>): Promise<User>;\n }),\n // </generated:users>\n },\n\n /** List + revoke customer-recorded app sessions (SDK 4.0). */\n sessions: {\n // <generated:sessions>\n /** `GET /v1/client/sessions` — list the caller's app sessions. */\n list: async (): Promise<AppSession[]> => {\n return (await coreCall((c) => c.sessionsList())) as AppSession[];\n },\n /**\n * `POST /v1/client/sessions/:id/end` — end the session with the\n * given id. The 4.0 `revoke` contract returns unit; customers who\n * need the ended row should use the lower-level `sessions.end`\n * helper on the core (not exposed on the public SDK).\n */\n revoke: async (sessionId: string): Promise<void> => {\n await coreCall((c) => c.sessionsRevoke(sessionId));\n },\n // </generated:sessions>\n },\n\n /**\n * Scope collection calls to a group the current session belongs to.\n * Membership and role are verified by the API; an unknown/unauthorized\n * group is rejected server-side. The returned caller is immutable and safe\n * to use concurrently with callers scoped to other groups.\n */\n asGroup: (groupId: string): AmbaGroupScopedClient => {\n requireCore();\n return new AmbaGroupScopedClient(groupId);\n },\n\n /** Alias of {@link Amba.asGroup}. */\n inGroup: (groupId: string): AmbaGroupScopedClient => {\n requireCore();\n return new AmbaGroupScopedClient(groupId);\n },\n\n /**\n * Live collection subscriptions (SDK 4.0). Returns a builder — chain\n * `.where(column, value)` to narrow, then `.subscribe(cb)` to start\n * receiving row changes. `subscribe` returns an unsubscribe handle.\n *\n * @example\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op: \"INSERT\" | \"UPDATE\" | \"DELETE\"; change.row: the row\n * });\n * // later: unsub();\n *\n * Delivery mirrors `Amba.collections.find()` — you only receive rows your\n * session owns. Requires an authenticated session.\n */\n collection: <T = unknown>(name: string): CollectionSubscription<T> =>\n new CollectionSubscription<T>(\n { baseUrl: _configuredApiUrl, authHeaders: realtimeAuthHeaders },\n name,\n ),\n\n /** Typed collection CRUD with auto-RLS. */\n collections: {\n // <generated:collections.find>\n find: async <T = unknown>(\n name: string,\n options: FindOptions = {},\n ): Promise<FindResponse<T>> => {\n return (await coreCall((c) => c.collectionsFind(\n name,\n encodeJson(options),\n ))) as FindResponse<T>;\n },\n // </generated:collections.find>\n findOne: async <T = unknown>(name: string, id: string): Promise<T> => {\n return (await coreCall((c) => c.collectionsFindOne(name, id))) as T;\n },\n insert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n ): Promise<T> => {\n // When the outbox is enabled, mint the idempotency key up front and send\n // it on the live attempt too. If that attempt actually reached the server\n // before the connection dropped, the queued replay carries the SAME key\n // and the API dedupes it (migration 056) — no double-insert.\n if (_offlineEnabled) {\n const idempotencyKey =\n (typeof row[\"idempotency_key\"] === \"string\"\n ? (row[\"idempotency_key\"] as string)\n : undefined) ?? newIdempotencyKey();\n const wireRow = { ...row, idempotency_key: idempotencyKey };\n try {\n return (await coreCall((c) =>\n c.collectionsInsert(name, encodeJson(wireRow)),\n )) as T;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue().enqueue({\n kind: \"collection.insert\",\n collection: name,\n payload: row,\n idempotencyKey,\n });\n return { ...row, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n return (await coreCall((c) => c.collectionsInsert(\n name,\n encodeJson(row),\n ))) as T;\n },\n update: async <T = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n ): Promise<T> => {\n if (_offlineEnabled) {\n try {\n return (await coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set)),\n )) as T;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue().enqueue({\n kind: \"collection.update\",\n collection: name,\n id,\n payload: set,\n });\n return { id, ...set, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n return (await coreCall((c) => c.collectionsUpdate(\n name,\n id,\n encodeJson(set),\n ))) as T;\n },\n delete: async (\n name: string,\n id: string,\n ): Promise<{ data: { deleted: boolean } }> => {\n if (_offlineEnabled) {\n try {\n return (await coreCall((c) => c.collectionsDelete(name, id))) as {\n data: { deleted: boolean };\n };\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue().enqueue({\n kind: \"collection.delete\",\n collection: name,\n id,\n });\n return { data: { deleted: true } };\n }\n throw err;\n }\n }\n return (await coreCall((c) => c.collectionsDelete(name, id))) as {\n data: { deleted: boolean };\n };\n },\n /**\n * Insert a row, resolving a unique-index conflict instead of failing.\n *\n * With `options.onConflict: \"ignore\"` a conflicting row is left\n * untouched and returned; with `\"update\"` the supplied columns are\n * merged into the existing row. `options.conflictTarget` names the\n * unique-index columns to arbitrate on (e.g. `[\"user_id\", \"kind\"]`) and\n * every one must be present in `row`. Auto-RLS still applies — the\n * ignore-fallback / merge is scoped to the caller's own rows.\n *\n * @example\n * await Amba.collections.upsert(\"prefs\",\n * { kind: \"theme\", value: \"dark\" },\n * { onConflict: \"update\", conflictTarget: [\"kind\"] });\n */\n upsert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n options: UpsertOptions,\n ): Promise<T> => {\n // The core expects snake_case wire keys (`on_conflict` /\n // `conflict_target`); the public option object is camelCase.\n const wire = {\n on_conflict: options.onConflict,\n conflict_target: options.conflictTarget,\n };\n return (await coreCall((c) => c.collectionsUpsert(\n name,\n encodeJson(row),\n encodeJson(wire),\n ))) as T;\n },\n /**\n * Bulk soft-delete every row matching `filter`. Returns the count of\n * rows deleted. Auto-RLS scopes the delete to the caller's own rows;\n * soft-deleted rows can be brought back with {@link restore}.\n *\n * @example\n * const { data } = await Amba.collections.deleteWhere(\n * \"todos\", Amba.collections.where.eq(\"status\", \"done\"));\n * console.log(`${data.deleted} removed`);\n */\n deleteWhere: async (\n name: string,\n filter: Filter,\n ): Promise<DeleteManyResult> => {\n return (await coreCall((c) => c.collectionsDeleteWhere(\n name,\n encodeJson(filter),\n ))) as DeleteManyResult;\n },\n /**\n * Restore a previously soft-deleted row by id (clears `deleted_at`).\n * Returns the restored row. Only rows currently soft-deleted and owned\n * by the caller can be restored; anything else resolves as not-found.\n */\n restore: async <T = unknown>(name: string, id: string): Promise<T> => {\n return (await coreCall((c) => c.collectionsRestore(name, id))) as T;\n },\n /**\n * Run an ordered list of writes (insert / update / delete, across any\n * collections) as ONE atomic transaction. The whole batch commits, or — if\n * any op fails — rolls back entirely; a partial multi-write can never\n * persist. Resolves to the per-op results; throws naming the failing op\n * index (`details.opIndex`) on rollback. Each op honors the same auto-RLS /\n * owner-scoping / compare-and-set rules as the single-row methods.\n *\n * @example\n * await Amba.collections.transaction([\n * { collection: \"carts\", op: \"insert\", row: { sku: \"widget\", qty: 2 } },\n * { collection: \"inventory\", op: \"update\", id: invId, set: { reserved: 2 } },\n * ]);\n */\n transaction: async (\n operations: TransactionOp[],\n ): Promise<TransactionOpResult[]> => {\n const env = (await coreCall((c) =>\n c.collectionsTransaction(encodeJson(operations.map(toWireTransactionOp))),\n )) as { data: TransactionOpResult[] };\n return env.data;\n },\n /**\n * Vector-search the collection (SDK 4.0). Returns the `k` nearest\n * neighbors of `options.query_vector` against the column named\n * `options.vector_field`. Distance metric and index strategy are\n * server-side; pass `options.filter` to narrow the candidate set\n * before ranking.\n *\n * Spec note: the canonical `findNearest(name, vector, k, filter?)`\n * shape omits the column name; the SDK widens the contract to a\n * single typed options object so `vector_field` is required and\n * unambiguous in TS.\n */\n findNearest: async <T = unknown>(\n name: string,\n options: NearestOptions,\n ): Promise<NearestResponse<T>> => {\n return (await coreCall((c) => c.collectionsFindNearest(\n name,\n encodeJson(options),\n ))) as NearestResponse<T>;\n },\n /**\n * Count rows in the collection (SDK 4.0). Pass an optional filter\n * to scope; omit it for the total. The server's response unwraps to\n * a bare number on the JS side.\n */\n count: async (name: string, filter?: Filter): Promise<number> => {\n return (await coreCall((c) => c.collectionsCount(\n name,\n filter ? encodeJson(filter) : undefined,\n ))) as number;\n },\n /**\n * Optimistic insert — update a local view immediately, fire the real\n * insert, and roll the local view back if it fails. The reconcile is your\n * own (replace the temporary local row with the server's authoritative\n * row in your `apply`/`rollback` handlers). Thin convenience over the\n * exported {@link withOptimistic} so the common single-write case reads\n * clean; for multi-step flows use `withOptimistic` directly.\n *\n * Optimistic writes always hit the network directly — they are NOT\n * diverted to the offline outbox even when `Amba.offline` is enabled.\n * Optimistic UI needs an unambiguous outcome: this resolves only on a\n * confirmed server write, and rejects (running your `rollback`) on any\n * failure including being offline. To buffer-and-replay instead, use the\n * plain `insert`/`update`/`delete` with the outbox on.\n *\n * @example\n * await Amba.collections.optimisticInsert(\"todos\", draft, {\n * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },\n * rollback: (prev) => setTodos(prev),\n * });\n */\n optimisticInsert: async <T = unknown, TApply = unknown>(\n name: string,\n row: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => insertDirect<T>(name, row),\n rollback: view.rollback,\n }),\n /** Optimistic update — see {@link optimisticInsert}. */\n optimisticUpdate: async <T = unknown, TApply = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => updateDirect<T>(name, id, set),\n rollback: view.rollback,\n }),\n /** Optimistic delete — see {@link optimisticInsert}. */\n optimisticDelete: async <TApply = unknown>(\n name: string,\n id: string,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<{ data: { deleted: boolean } }> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => deleteDirect(name, id),\n rollback: view.rollback,\n }),\n /** Filter DSL helpers — pass into `find({ filter: f })`. */\n where: {\n eq: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"eq\",\n value,\n }),\n ne: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"ne\",\n value,\n }),\n gt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gt\",\n value,\n }),\n gte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gte\",\n value,\n }),\n lt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lt\",\n value,\n }),\n lte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lte\",\n value,\n }),\n in: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"in\",\n value: values,\n }),\n notIn: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"not_in\",\n value: values,\n }),\n like: (column: string, pattern: string): Filter => ({\n column,\n op: \"like\",\n value: pattern,\n }),\n ilike: (column: string, pattern: string): Filter => ({\n column,\n op: \"ilike\",\n value: pattern,\n }),\n isNull: (column: string): Filter => ({ column, op: \"is_null\" }),\n isNotNull: (column: string): Filter => ({ column, op: \"is_not_null\" }),\n and: (...filters: Filter[]): Filter => ({ and: filters }),\n or: (...filters: Filter[]): Filter => ({ or: filters }),\n not: (filter: Filter): Filter => ({ not: filter }),\n },\n },\n\n /** Media uploads + CDN URL helpers. */\n storage: {\n presign: async (params: {\n bucket: string;\n filename: string;\n mimeType: string;\n sizeBytes: number;\n retentionDays?: number;\n }): Promise<PresignData> => {\n return (await coreCall((c) => c.storagePresign(\n params.bucket,\n params.filename,\n params.mimeType,\n params.sizeBytes,\n params.retentionDays ?? undefined,\n ))) as PresignData;\n },\n /**\n * Complete-flow upload: presign → PUT to object storage → commit.\n * Returns the finalized MediaAsset.\n */\n upload: async (params: {\n bucket: string;\n file: File | Blob;\n filename?: string;\n retentionDays?: number;\n }): Promise<MediaAsset> => {\n const filename =\n params.filename ??\n (\"name\" in params.file ? params.file.name : \"upload\");\n const mimeType = params.file.type || \"application/octet-stream\";\n const presign = await Amba.storage.presign({\n bucket: params.bucket,\n filename,\n mimeType,\n sizeBytes: params.file.size,\n retentionDays: params.retentionDays,\n });\n\n const uploadHeaders: Record<string, string> = {};\n for (const [k, v] of presign.upload_headers) {\n uploadHeaders[k] = v;\n }\n const putResp = await fetch(presign.upload_url, {\n method: \"PUT\",\n headers: uploadHeaders,\n body: params.file,\n });\n if (!putResp.ok) {\n throw new Error(`upload to storage failed: status ${putResp.status}`);\n }\n\n return (await coreCall((c) => c.storageCommit(\n presign.upload_id,\n presign.asset_id,\n ))) as MediaAsset;\n },\n // <generated:storage>\n /**\n * List media assets (SDK 4.0). Pass `prefix` to scope to a key\n * prefix (filename / path-like). Without it, returns the caller's\n * full list. Server enforces RLS — only the calling user's assets\n * are returned.\n */\n list: async (prefix?: string): Promise<MediaListResponse> => {\n return (await coreCall((c) => c.storageList(\n prefix ?? undefined,\n ))) as MediaListResponse;\n },\n /**\n * Soft-delete the asset (SDK 4.0). Accepts the asset id returned\n * by `presign` / `commit` / `list`. The corresponding storage object\n * is removed asynchronously on the server.\n */\n delete: async (assetId: string): Promise<void> => {\n await coreCall((c) => c.storageDelete(assetId));\n },\n // </generated:storage>\n /**\n * Download an asset's bytes (SDK 4.0). Returns a `Blob` wrapping\n * the Uint8Array returned by the engine, so consumers can\n * call `.text()` / `.arrayBuffer()` / `.stream()` directly or\n * pipe to a `URL.createObjectURL()` for preview. The two-step\n * fetch (asset metadata → CDN GET) is handled inside the SDK\n * core; this method is single-call from the consumer.\n */\n download: async (assetId: string): Promise<Blob> => {\n const bytes = (await coreCall((c) => c.storageDownload(\n assetId,\n ))) as Uint8Array;\n // `Blob` is universally available in browsers and Node 18+,\n // so no platform-shim needed. Type defaults to\n // `application/octet-stream` — the caller knows the real type\n // from their original upload metadata if they need it.\n // Cast through `BlobPart` — under newer @types/node typings,\n // `Uint8Array<ArrayBufferLike>` widens to `ArrayBufferLike`\n // which TS refuses to narrow back to `BlobPart`'s\n // `ArrayBufferView<ArrayBuffer>` constituent. The runtime\n // is fine — `Blob` accepts any TypedArray.\n return new Blob([bytes as unknown as BlobPart]);\n },\n },\n\n /** Curated public-asset catalogs. */\n media: {\n /**\n * Fetch a public media catalog by slug\n * (`GET /v1/client/media/catalogs/:slug`). A catalog is a named,\n * curated bundle of assets your app loads in one call — each item\n * resolves to a stable, long-cacheable public URL, returned in the\n * order configured for the catalog. Only catalogs published as public\n * are readable; the call needs the client key but no signed-in user.\n */\n catalog: async (slug: string): Promise<MediaCatalog> => {\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before reading a media catalog.\",\n );\n }\n if (!slug || slug.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.media.catalog: a catalog slug is required.\",\n );\n }\n\n const url = `${_configuredApiUrl.replace(/\\/$/, \"\")}/v1/client/media/catalogs/${encodeURIComponent(\n slug,\n )}`;\n let resp: Response;\n try {\n resp = await fetch(url, {\n method: \"GET\",\n headers: { \"X-Api-Key\": _configuredClientKey },\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.media.catalog(\"${slug}\") network error: ${String(err)}`,\n );\n }\n\n const text = await resp.text();\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = undefined;\n }\n }\n if (!resp.ok) {\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\"\n ? envelope.error.code\n : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : `Amba.media.catalog(\"${slug}\") failed with status ${resp.status}`;\n throw new AmbaApiError(code, message, parsed);\n }\n return (parsed as { data: MediaCatalog }).data;\n },\n },\n\n /** Offline change replay (SDK 4.0). */\n sync: {\n /**\n * Replay a batch of buffered mutations\n * (`POST /v1/client/sync`). The server applies them in order and\n * returns the count of successfully-applied changes plus any\n * per-row conflicts. Conflict resolution in 4.0 is always\n * server-wins; the `SyncConflict.resolution` field is reserved\n * for future strategies.\n */\n pushChanges: async (\n changes: SyncChange[],\n ): Promise<PushChangesResult> => {\n return (await coreCall((c) => c.syncPushChanges(\n encodeJson(changes),\n ))) as PushChangesResult;\n },\n /**\n * Pull remote-origin changes since the supplied cursor\n * (`GET /v1/client/sync`). Pass the `checkpoint_token` from the\n * previous `pushChanges` / `pullChanges` reply, and the\n * `entity_type` to scope to a single collection. On the first\n * call, omit `checkpoint_token` to receive the full backfill.\n */\n pullChanges: async (\n since: PullChangesCursor,\n ): Promise<PullChangesResult> => {\n return (await coreCall((c) => c.syncPullChanges(\n encodeJson(since),\n ))) as PullChangesResult;\n },\n },\n\n /**\n * Automatic offline mutation queue (opt-in).\n *\n * Once enabled, a collection write (`insert` / `update` / `delete`) or a\n * `track` that fails because the device is offline is buffered to a durable\n * local outbox instead of throwing — then replayed in order the moment\n * connectivity returns. Replays reuse a per-op idempotency key so a write\n * that actually reached the server before the connection dropped is never\n * applied twice.\n *\n * This is distinct from `Amba.sync` (manual change replay): `offline` is\n * the automatic, fire-and-forget safety net for ordinary writes.\n *\n * @example\n * Amba.offline.enable(); // call once after configure()\n * await Amba.collections.insert(\"todos\", { title: \"Buy milk\" }); // queues if offline\n * // ...reconnect...\n * console.log(await Amba.offline.pendingCount()); // drains automatically\n */\n offline: {\n /**\n * Turn the outbox on and start auto-flushing on reconnect. Idempotent —\n * a repeat call detaches the prior `online` listener before re-attaching,\n * so it never stacks listeners. Immediately attempts a flush in case ops\n * were persisted in a prior session. Returns a disable function that\n * detaches the reconnect listener (queued ops are NOT dropped — call\n * `clear()` for that).\n *\n * The disabler is tied to THIS activation: once a later `enable()` (or a\n * `configure()`) supersedes it, calling it is a no-op — so a stale disabler\n * can never turn a freshly re-enabled outbox off.\n */\n enable: (): (() => void) => {\n // Detach any prior listener first so a re-`enable()` (or one after a\n // reconfigure that left wiring) can't leak / stack listeners.\n if (_offlineDetach) {\n _offlineDetach();\n _offlineDetach = null;\n }\n _offlineEnabled = true;\n // Bump + capture the activation token so this call's disabler only acts\n // while it's still the current activation.\n _offlineEnableToken += 1;\n const token = _offlineEnableToken;\n const q = getOfflineQueue();\n _offlineDetach = q.startAutoFlush();\n // Best-effort drain of anything persisted from a previous run.\n void q.flush();\n return () => {\n // No-op if a later enable() or a configure() superseded this activation\n // — the token moved on, so this disabler is stale and must not flip the\n // active outbox off or detach the current listener.\n if (_offlineEnableToken !== token) return;\n _offlineEnabled = false;\n if (_offlineDetach) {\n _offlineDetach();\n _offlineDetach = null;\n }\n };\n },\n /** Whether the outbox is currently enabled. */\n isEnabled: (): boolean => _offlineEnabled,\n /** Count of writes awaiting replay. */\n pendingCount: async (): Promise<number> => getOfflineQueue().pendingCount(),\n /** Snapshot the pending ops (a copy — safe to inspect). */\n pending: async (): Promise<OutboxOp[]> => getOfflineQueue().pending(),\n /**\n * Force a replay attempt now (also runs automatically on reconnect).\n * Resolves with `{ flushed, dropped, remaining }` (dropped = ops the server rejected deterministically on replay, removed so they cannot wedge the queue).\n */\n flush: async (): Promise<FlushResult> => getOfflineQueue().flush(),\n /** Drop every queued write without replaying. */\n clear: async (): Promise<void> => getOfflineQueue().clear(),\n },\n\n /** Push notifications. */\n push: {\n register: async (\n token: string,\n platform: PushPlatform,\n bundleId?: string,\n ): Promise<PushToken> => {\n return (await coreCall((c) => c.pushRegister(\n token,\n platform,\n bundleId ?? undefined,\n ))) as PushToken;\n },\n unregister: async (token: string): Promise<void> => {\n await coreCall((c) => c.pushUnregister(token));\n },\n getTokens: async (): Promise<PushToken[]> => {\n return (await coreCall((c) => c.pushGetTokens())) as PushToken[];\n },\n subscribe: async (topic: string): Promise<void> => {\n await coreCall((c) => c.pushSubscribe(topic));\n },\n unsubscribe: async (topic: string): Promise<void> => {\n await coreCall((c) => c.pushUnsubscribe(topic));\n },\n /**\n * Auto re-register the device's Web Push subscription when the browser\n * rotates it.\n *\n * The browser can silently roll a push subscription (key rollover,\n * permission re-grant); when it does, the service worker fires\n * `pushsubscriptionchange` and the old endpoint stops delivering. Call\n * this from your service worker (where that event fires) and the SDK\n * re-registers the fresh endpoint through the idempotent\n * {@link register} path automatically.\n *\n * No-op (returns a no-op detacher) in runtimes without service-worker /\n * push support, so it's safe to call unconditionally. Returns an\n * unsubscribe function.\n *\n * @example\n * // in your service worker\n * Amba.push.enableAutoRotation();\n */\n enableAutoRotation: (): (() => void) =>\n enableWebPushAutoRotation((token) => Amba.push.register(token, \"web\")),\n },\n\n /**\n * On-device local notifications — `scheduleLocal` / `cancelLocal` /\n * `listScheduledLocal`. Fire from the device without a server\n * round-trip (reminders, streak nudges), distinct from remote push\n * (`Amba.push.*`). Browser scheduling is partial: the OS-backed path\n * needs the experimental Notification Triggers API + an active Service\n * Worker (Chromium today); without it the SDK falls back to an in-page\n * timer and reports `scheduledVia: \"timer\"` so the degradation is\n * visible — never a silent no-op. See `./local-notifications.ts`.\n */\n notifications,\n\n /** Subscription entitlements — feature gates resolved server-side. */\n entitlements: {\n // <generated:entitlements.list>\n list: async (): Promise<UserEntitlement[]> => {\n return (await coreCall((c) => c.entitlementsList())) as UserEntitlement[];\n },\n // </generated:entitlements.list>\n // <generated:entitlements.has>\n has: async (name: string): Promise<boolean> => {\n return (await coreCall((c) => c.entitlementsHas(name))) as boolean;\n },\n // </generated:entitlements.has>\n // <generated:entitlements.restore>\n /**\n * Restore purchases — re-sync the user's owned entitlements from the\n * server and return the active set. Required for App Store review (3.1.1);\n * wire it to a \"Restore Purchases\" button. Rejects (access unchanged) if\n * the server can't reach the validation engine — show a retry message.\n */\n restore: async (): Promise<RestoreResult> => {\n return (await coreCall((c) => c.entitlementsRestore())) as RestoreResult;\n },\n // </generated:entitlements.restore>\n },\n\n // <generated:offerings>\n /**\n * Subscription offerings — the packages your paywall renders, provider-\n * neutral. Pass an `offeringId` to fetch one offering; omit it for all.\n */\n offerings: async (offeringId?: string): Promise<OfferingsResult> => {\n return (await coreCall((c) =>\n offeringId ? c.offeringsGet(offeringId) : c.offeringsList(),\n )) as OfferingsResult;\n },\n // </generated:offerings>\n\n /** LLM proxy. */\n ai: {\n anthropic: {\n messages: {\n create: async (\n request: AiMessageRequest,\n ): Promise<AiMessageResponse> => {\n // Expand convenience `images` into content blocks; text-only\n // requests pass through unchanged (same object reference).\n return (await coreCall((c) => c.aiAnthropicMessages(\n encodeJson(expandAiImages(request)),\n ))) as AiMessageResponse;\n },\n /**\n * Stream the response token-by-token instead of waiting for the full\n * buffered reply. Returns an async iterable of vendor-neutral events:\n * zero or more `{type:'delta', text}` as tokens arrive, then exactly\n * one `{type:'done', usage, model, stopReason}` when the stream ends.\n *\n * ```ts\n * let answer = \"\";\n * for await (const ev of Amba.ai.anthropic.messages.stream({ prompt_slug: \"chat\" })) {\n * if (ev.type === \"delta\") answer += ev.text;\n * else console.log(\"tokens:\", ev.usage.output_tokens);\n * }\n * ```\n *\n * Pass `{ onChunk }` to also receive each delta via callback. Throws\n * an `AmbaApiError` on a transport failure or a non-2xx response.\n */\n stream: (\n request: AiMessageRequest,\n options?: AiStreamOptions,\n ): AsyncGenerator<AiStreamEvent, void, unknown> => {\n const { url, headers, body } = buildAiStreamRequest(\n AI_ANTHROPIC_PATH,\n request,\n );\n return invokeAiStream({ url, headers, body, onChunk: options?.onChunk });\n },\n },\n },\n openai: {\n chat: {\n completions: {\n /**\n * Stream an OpenAI-routed prompt token-by-token. Same event shape\n * as `Amba.ai.anthropic.messages.stream` — the vendor split is just\n * a stable wire path; the server resolves the real provider from the\n * prompt's registered configuration.\n */\n stream: (\n request: AiMessageRequest,\n options?: AiStreamOptions,\n ): AsyncGenerator<AiStreamEvent, void, unknown> => {\n const { url, headers, body } = buildAiStreamRequest(\n AI_OPENAI_PATH,\n request,\n );\n return invokeAiStream({ url, headers, body, onChunk: options?.onChunk });\n },\n },\n },\n },\n },\n\n /**\n * Invoke a deployed function by name, with the project's client key (and\n * the live session token, if any) injected automatically.\n *\n * This is the authenticated way to call your functions — prefer it over\n * hand-rolling `fetch` against a function URL, which leaves the call\n * unauthenticated (a footgun when the function holds secrets).\n *\n * The function host is selected in order from `functionsBaseUrl`, a\n * UUID-shaped configured project id, or a deduped diagnostics ping that\n * supplies the authoritative server-side project id.\n *\n * The return value is unchanged (the parsed body, typed `T`). To\n * correlate a run with server logs, read the trace id additively:\n * pass `options.onTraceId` to receive the `X-Amba-Request-Id` on every\n * response, and on failure read `error.traceId` off the thrown\n * `AmbaApiError`. Then `functions logs --request-id <traceId>` pulls the\n * matching server log.\n *\n * @example\n * const result = await Amba.functions.invoke<{ ok: boolean }>(\n * 'send_welcome_email',\n * { to: 'user@example.com' },\n * { onTraceId: (id) => console.log('trace', id) },\n * );\n *\n * @example\n * try {\n * await Amba.functions.invoke('charge', { amount: 100 });\n * } catch (err) {\n * if (err instanceof AmbaApiError) {\n * console.error(`failed (trace ${err.traceId}): ${err.message}`);\n * }\n * }\n *\n * @param name The deployed function name.\n * @param body JSON-serializable request body (omitted for GET).\n * @param options Method, extra headers, and an `onTraceId` callback.\n * @returns The parsed JSON response (or the raw text if not JSON).\n * @throws AmbaApiError on a network failure or a non-2xx response. When\n * the function / router returns the standard `{ error: { code, message } }`\n * envelope, those surface as the thrown error's `code` + `message`; the\n * server trace id (when present) is attached as `error.traceId`.\n */\n functions: {\n invoke: async <T = unknown>(\n name: string,\n body?: unknown,\n options?: InvokeOptions,\n ): Promise<T> => {\n // Enforce configured state (consistent with every other method) and\n // capture the client key the function router authenticates against.\n requireCore();\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before Amba.functions.invoke().\",\n );\n }\n if (!name || name.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.functions.invoke: a function name is required.\",\n );\n }\n\n const url = await resolveFunctionUrl(name);\n const method = options?.method ?? \"POST\";\n const headers: Record<string, string> = {\n \"X-Api-Key\": _configuredClientKey,\n ...options?.headers,\n };\n // Inject the live session token so the function runs in the end-user's\n // authenticated context. Anonymous (pre-sign-in) invokes still carry\n // the client key, which the router accepts for non-public functions.\n if (_currentSessionToken) {\n headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n }\n let requestBody: string | undefined;\n if (body !== undefined && method !== \"GET\") {\n headers[\"content-type\"] = \"application/json\";\n requestBody = JSON.stringify(body);\n }\n\n let resp: Response;\n try {\n resp = await fetch(url, { method, headers, body: requestBody });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.functions.invoke(\"${name}\") network error: ${String(err)}`,\n );\n }\n\n const text = await resp.text();\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n // Function returned non-JSON (e.g. plain text / HTML) — pass it\n // through verbatim rather than guessing a shape.\n parsed = text;\n }\n }\n\n // Surface the server-side trace id. The router stamps it on the\n // `X-Amba-Request-Id` response header (post-auth) and also embeds it\n // in the error envelope — so we prefer the header and fall back to\n // the body when a pre-auth rejection stripped it from the header.\n // `functions logs --request-id <traceId>` lets a caller pull the\n // matching server log for a failed run.\n //\n // The router envelope nests it as `error.request_id`\n // (apps/edge-router/src/errors.ts) — that's the field pre-auth\n // rejections (which omit the header) actually carry. We also accept a\n // top-level `request_id` as a forward-compatible fallback.\n const envelopeRequestId =\n parsed && typeof parsed === \"object\"\n ? ((parsed as { error?: { request_id?: unknown } }).error\n ?.request_id ?? (parsed as { request_id?: unknown }).request_id)\n : undefined;\n const traceId =\n resp.headers.get(\"X-Amba-Request-Id\") ??\n (typeof envelopeRequestId === \"string\" ? envelopeRequestId : null);\n options?.onTraceId?.(traceId);\n\n if (!resp.ok) {\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\"\n ? envelope.error.code\n : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : typeof parsed === \"string\" && parsed.length > 0\n ? parsed\n : `Amba.functions.invoke(\"${name}\") failed with status ${resp.status}`;\n const error = new AmbaApiError(code, message, parsed);\n if (traceId !== null) error.traceId = traceId;\n throw error;\n }\n\n return parsed as T;\n },\n },\n\n /** Remote config bundle. */\n config: {\n // <generated:config>\n fetch: async (): Promise<ConfigBundle> => {\n return (await coreCall((c) => c.configFetch())) as ConfigBundle;\n },\n // </generated:config>\n },\n\n /** Server-evaluated feature flags. */\n flags: {\n // <generated:flags>\n fetch: async (): Promise<FlagAssignment[]> => {\n return (await coreCall((c) => c.flagsFetch())) as FlagAssignment[];\n },\n /**\n * Single-flag lookup (SDK 4.0). Wraps `GET /v1/client/flags/{key}`.\n * Returns `null` for unknown / disabled keys; other errors\n * (auth, rate-limit, network) propagate.\n */\n get: async (key: string): Promise<FlagAssignment | null> => {\n return (await coreCall((c) => c.flagsGet(key))) as FlagAssignment | null;\n },\n // </generated:flags>\n },\n\n /**\n * Wire-verify primitive. Call once after `configure()` to confirm\n * the SDK is talking to the right project with the right key.\n *\n * The response is entirely server-decided — none of the fields are\n * derived from request parameters — so it's trustworthy as a setup\n * check. Common usage:\n *\n * ```ts\n * const ping = await Amba.diagnostics.ping();\n * if (!ping.ok || ping.server_project_id !== expectedProjectId) {\n * throw new Error(`amba misconfigured: ${JSON.stringify(ping)}`);\n * }\n * ```\n */\n diagnostics: {\n ping: async (): Promise<PingResult> => {\n return (await coreCall((c) => c.diagnosticsPing())) as PingResult;\n },\n },\n\n // ── Gamification ──────────────────────────────────────────────────\n\n /** Achievement definitions + per-user progress. */\n achievements: {\n // <generated:achievements>\n getAll: async (): Promise<Achievement[]> =>\n (await coreCall((c) => c.achievementsGetAll())) as Achievement[],\n getProgress: async (): Promise<AchievementProgress[]> =>\n (await coreCall((c) => c.achievementsGetProgress())) as AchievementProgress[],\n // </generated:achievements>\n },\n\n /** Time-bounded challenges with rewards. */\n challenges: {\n // <generated:challenges>\n getActive: async (): Promise<Challenge[]> =>\n (await coreCall((c) => c.challengesGetActive())) as Challenge[],\n get: async (id: string): Promise<Challenge> =>\n (await coreCall((c) => c.challengesGet(id))) as Challenge,\n getProgress: async (id: string): Promise<ChallengeProgress> =>\n (await coreCall((c) => c.challengesGetProgress(id))) as ChallengeProgress,\n claim: async (id: string): Promise<ChallengeProgress> =>\n (await coreCall((c) => c.challengesClaim(id))) as ChallengeProgress,\n // </generated:challenges>\n },\n\n /** Virtual currency balances + ledger. */\n currencies: {\n // <generated:currencies>\n getBalance: async (): Promise<CurrencyBalance[]> =>\n (await coreCall((c) => c.currenciesGetBalance())) as CurrencyBalance[],\n getTransactions: async (\n currencyKey: string,\n ): Promise<CurrencyTransaction[]> =>\n (await coreCall((c) => c.currenciesGetTransactions(\n currencyKey,\n ))) as CurrencyTransaction[],\n // </generated:currencies>\n },\n\n /** User inventory items + purchase/consume. */\n inventory: {\n // <generated:inventory>\n getItems: async (): Promise<InventoryItem[]> =>\n (await coreCall((c) => c.inventoryGetItems())) as InventoryItem[],\n getItem: async (id: string): Promise<InventoryItem> =>\n (await coreCall((c) => c.inventoryGetItem(id))) as InventoryItem,\n purchase: async (request: PurchaseRequest): Promise<InventoryItem> =>\n (await coreCall((c) => c.inventoryPurchase(\n encodeJson(request),\n ))) as InventoryItem,\n consume: async (request: ConsumeRequest): Promise<InventoryItem> =>\n (await coreCall((c) => c.inventoryConsume(\n encodeJson(request),\n ))) as InventoryItem,\n // </generated:inventory>\n },\n\n /** Score rankings. */\n leaderboards: {\n // <generated:leaderboards>\n get: async (key: string): Promise<Leaderboard> =>\n (await coreCall((c) => c.leaderboardsGet(key))) as Leaderboard,\n getEntries: async (\n key: string,\n limit?: number,\n ): Promise<LeaderboardEntry[]> =>\n (await coreCall((c) => c.leaderboardsGetEntries(\n key,\n limit ?? undefined,\n ))) as LeaderboardEntry[],\n getMyRank: async (key: string): Promise<LeaderboardEntry> =>\n (await coreCall((c) => c.leaderboardsGetMyRank(key))) as LeaderboardEntry,\n // </generated:leaderboards>\n },\n\n /**\n * Tiered weekly leaderboard cohorts (SDK 4.0). Each user is bucketed\n * into a small cohort (~25 members) within their league tier; the\n * top scorers each week promote and the bottom scorers demote.\n */\n leagues: {\n // <generated:leagues>\n /**\n * `GET /v1/client/leagues/me` — the caller's active-cohort\n * standing plus live rank/score and member count.\n */\n me: async (): Promise<LeagueMembership> => {\n return (await coreCall((c) => c.leaguesMe())) as LeagueMembership;\n },\n /**\n * `GET /v1/client/leagues/me/cohort` — the caller's cohort with\n * every member's anonymised standing (display name, score, rank).\n * Notably no user ids — the server strips them so a leaderboard\n * UI can render the cohort without exposing identities.\n */\n cohort: async (): Promise<LeagueCohortView> => {\n return (await coreCall((c) => c.leaguesCohort())) as LeagueCohortView;\n },\n // </generated:leagues>\n },\n\n /** Purchase storefronts. */\n stores: {\n // <generated:stores>\n list: async (): Promise<Store[]> =>\n (await coreCall((c) => c.storesList())) as Store[],\n getPurchaseOptions: async (storeKey: string): Promise<PurchaseOption[]> =>\n (await coreCall((c) => c.storesGetPurchaseOptions(\n storeKey,\n ))) as PurchaseOption[],\n purchase: async (\n storeKey: string,\n purchaseOptionId: string,\n receipt: unknown,\n ): Promise<PurchaseResult> =>\n // `JSON.stringify(undefined)` returns the JS value `undefined`,\n // not a string — passing that to the SDK core's `storesPurchase` binding\n // crashes. Default the missing-receipt case to an empty object so\n // the server still receives valid JSON (BugBot Low 2026-05-19).\n (await coreCall((c) => c.storesPurchase(\n storeKey,\n purchaseOptionId,\n encodeJson(receipt ?? {}),\n ))) as PurchaseResult,\n // </generated:stores>\n },\n\n /** Experience points + level system. */\n xp: {\n // <generated:xp>\n getBalance: async (): Promise<XpBalance> =>\n (await coreCall((c) => c.xpGetBalance())) as XpBalance,\n getHistory: async (limit?: number): Promise<XpTransaction[]> =>\n (await coreCall((c) => c.xpGetHistory(\n limit ?? undefined,\n ))) as XpTransaction[],\n claim: async (grantKey: string): Promise<XpTransaction> =>\n (await coreCall((c) => c.xpClaim(grantKey))) as XpTransaction,\n // </generated:xp>\n },\n\n /** Daily-engagement streaks. */\n streaks: {\n // <generated:streaks>\n getAll: async (): Promise<Streak[]> =>\n (await coreCall((c) => c.streaksGetAll())) as Streak[],\n qualify: async (streakKey: string): Promise<Streak> =>\n (await coreCall((c) => c.streaksQualify(streakKey))) as Streak,\n // </generated:streaks>\n },\n\n /**\n * Live gamification updates for the signed-in user. Call `.subscribe(cb)` to\n * receive xp / level / achievement / streak changes the instant they happen,\n * instead of polling `xp.getBalance()` / `streaks.getAll()` on a timer.\n *\n * @example\n * const unsub = Amba.gamification.subscribe((change) => {\n * // change.kind: \"xp\" | \"level\" | \"achievement\" | \"streak\"\n * if (change.kind === \"level\") showLevelUp(change.data.level);\n * });\n * // later: unsub();\n *\n * You only ever receive your own gamification changes — never another user's.\n * Requires an authenticated session.\n */\n get gamification(): GamificationSubscription {\n // Getter (not an eager value) so `baseUrl` is read at access time — the\n // `Amba` object literal evaluates at import, BEFORE `init()` sets\n // `_configuredApiUrl`, so capturing it eagerly would pin the default URL.\n return new GamificationSubscription({\n baseUrl: _configuredApiUrl,\n authHeaders: realtimeAuthHeaders,\n });\n },\n\n // ── Social ─────────────────────────────────────────────────────────\n\n /** Activity feeds. */\n feeds: {\n // <generated:feeds>\n getActivity: async (\n feed?: string,\n cursor?: string,\n ): Promise<FeedResponse> =>\n (await coreCall((c) => c.feedsGetActivity(\n feed ?? undefined,\n cursor ?? undefined,\n ))) as FeedResponse,\n // </generated:feeds>\n },\n\n /** Friend graph + block list. */\n friends: {\n // <generated:friends>\n getList: async (): Promise<Friendship[]> =>\n (await coreCall((c) => c.friendsGetList())) as Friendship[],\n getFriends: async (): Promise<Friendship[]> =>\n (await coreCall((c) => c.friendsGetFriends())) as Friendship[],\n /**\n * Send a friend request to `userId` (SDK 4.0). Returns the\n * resulting `Friendship` row in `pending` state.\n */\n sendRequest: async (userId: string): Promise<Friendship> =>\n (await coreCall((c) => c.friendsSendRequest(userId))) as Friendship,\n /**\n * Accept a pending incoming friend request (SDK 4.0). Pass the\n * friendship id from `getList()`.\n */\n acceptRequest: async (friendshipId: string): Promise<Friendship> =>\n (await coreCall((c) => c.friendsAcceptRequest(\n friendshipId,\n ))) as Friendship,\n /**\n * Decline a pending incoming friend request (SDK 4.0). Removes\n * the pending row; idempotent.\n */\n declineRequest: async (friendshipId: string): Promise<void> => {\n await coreCall((c) => c.friendsDeclineRequest(friendshipId));\n },\n blockUser: async (userId: string): Promise<Friendship> =>\n (await coreCall((c) => c.friendsBlockUser(userId))) as Friendship,\n unblockUser: async (userId: string): Promise<void> => {\n await coreCall((c) => c.friendsUnblockUser(userId));\n },\n removeBlock: async (friendshipId: string): Promise<void> => {\n await coreCall((c) => c.friendsRemoveBlock(friendshipId));\n },\n /**\n * Unfriend by the other user's id (SDK 4.0). Wraps\n * `DELETE /v1/client/friends/by-user/{userId}`. Server preserves\n * blocked rows; use `unblockUser` to clear those. 404 propagates\n * as `AmbaApiError`.\n */\n removeFriend: async (userId: string): Promise<void> => {\n await coreCall((c) => c.friendsRemoveFriend(userId));\n },\n // </generated:friends>\n },\n\n /** Multi-user groups. */\n groups: {\n // <generated:groups>\n create: async (params: GroupCreate): Promise<Group> =>\n (await coreCall((c) => c.groupsCreate(encodeJson(params)))) as Group,\n get: async (id: string): Promise<Group> =>\n (await coreCall((c) => c.groupsGet(id))) as Group,\n update: async (id: string, patch: GroupUpdate): Promise<Group> =>\n (await coreCall((c) => c.groupsUpdate(id, encodeJson(patch)))) as Group,\n delete: async (id: string): Promise<void> => {\n await coreCall((c) => c.groupsDelete(id));\n },\n getMembers: async (id: string): Promise<GroupMember[]> =>\n (await coreCall((c) => c.groupsGetMembers(id))) as GroupMember[],\n join: async (id: string): Promise<GroupMember> =>\n (await coreCall((c) => c.groupsJoin(id))) as GroupMember,\n leave: async (id: string): Promise<void> => {\n await coreCall((c) => c.groupsLeave(id));\n },\n invite: async (id: string, userId: string): Promise<GroupMember> =>\n (await coreCall((c) => c.groupsInvite(id, userId))) as GroupMember,\n // </generated:groups>\n },\n\n /**\n * User-to-user direct messaging.\n *\n * Paths mirror the API exactly — see `apps/api/src/routes/client/messaging.ts`:\n *\n * | SDK method | API route |\n * | --------------------- | -------------------------------------------------------- |\n * | `createConversation` | POST `/v1/client/messaging/conversations` |\n * | `getConversations` | GET `/v1/client/messaging/conversations` |\n * | `listConversations` | (alias for `getConversations`) |\n * | `sendMessage` | POST `/v1/client/messaging/conversations/:id/messages` |\n * | `listMessages` | GET `/v1/client/messaging/conversations/:id/messages` |\n * | `markRead` | POST `/v1/client/messaging/conversations/:id/read` |\n *\n * Task #62 corrected `sendMessage`'s path (it previously POSTed to\n * `/messaging/messages`, a route that doesn't exist on the API), and\n * added the four missing helpers. The previous `getMessage(id)` helper\n * has been removed — there is no GET-by-id route on the server; the\n * shape consumers actually want is `listMessages(conversationId, opts)`.\n */\n messaging: {\n // <generated:messaging>\n /**\n * `POST /v1/client/messaging/conversations` — create (or fetch the\n * existing direct) conversation. For `type: \"direct\"` with exactly\n * one other participant, the server returns the existing row if\n * one already exists between these two users — calls are\n * idempotent.\n */\n createConversation: async (\n request: CreateConversationRequest,\n ): Promise<Conversation> =>\n (await coreCall((c) => c.messagingCreateConversation(\n encodeJson(request),\n ))) as Conversation,\n\n /**\n * `GET /v1/client/messaging/conversations` — list the caller's\n * conversations. Canonical 4.0 name; the legacy `getConversations`\n * / `listConversations` are removed in 4.0 per the spec.\n */\n conversations: async (): Promise<Conversation[]> =>\n (await coreCall((c) => c.messagingGetConversations())) as Conversation[],\n\n /**\n * `POST /v1/client/messaging/conversations/:conversationId/messages`\n * — send a message into an existing conversation. The\n * `conversation_id` argument is a path parameter (not a body\n * field) — the previous SDK shape mistakenly placed it in the\n * body, which the API ignored because the route was wrong anyway.\n */\n sendMessage: async (\n conversationId: string,\n request: SendMessageRequest,\n ): Promise<Message> =>\n // The engine ABI takes a single JSON arg with\n // `conversation_id` inline — we provide a cleaner two-positional\n // shape to TS callers and re-fold conversation_id into the\n // payload here. The engine extracts it before posting.\n (await coreCall((c) => c.messagingSendMessage(\n encodeJson({ ...request, conversation_id: conversationId }),\n ))) as Message,\n\n /**\n * `GET /v1/client/messaging/conversations/:conversationId/messages`\n * — list messages in a conversation. Pagination uses `?limit=&offset=`\n * (the server's `parsePagination` clamps both; defaults to 50 / 0).\n */\n listMessages: async (\n conversationId: string,\n opts?: ListMessagesOptions,\n ): Promise<Message[]> =>\n (await coreCall((c) => c.messagingListMessages(\n conversationId,\n opts?.limit ?? undefined,\n opts?.offset ?? undefined,\n ))) as Message[],\n\n /**\n * `GET /v1/client/messaging/conversations/:conversationId/messages/:messageId`\n * — fetch a single message by id. Returns `null` when the message\n * doesn't exist or the caller can't see it. (SDK 4.0: Phase A\n * shipped the missing `amba_messaging_get_message` core symbol the\n * native SDKs were already trying to call.)\n */\n getMessage: async (\n conversationId: string,\n messageId: string,\n ): Promise<Message | null> => {\n return (await coreCall((c) => c.messagingGetMessage(\n conversationId,\n messageId,\n ))) as Message | null;\n },\n\n /**\n * `POST /v1/client/messaging/conversations/:conversationId/participants`\n * — add a participant to an existing conversation. The caller must\n * already be a participant (the server returns `403\n * NOT_A_PARTICIPANT` otherwise) — this is the peer-invite path.\n * Idempotent: re-adding an existing member returns the existing\n * participant row, not an error. Enables dynamic membership growth\n * (e.g. a circle whose members qualify over time). Server-side\n * membership management (admin/matchmaker) uses the admin\n * add-participant endpoint instead.\n */\n addParticipant: async (\n conversationId: string,\n userId: string,\n ): Promise<ConversationParticipant> =>\n (await coreCall((c) => c.messagingAddParticipant(\n conversationId,\n userId,\n ))) as ConversationParticipant,\n\n /**\n * `POST /v1/client/messaging/conversations/:conversationId/read`\n * — bump the caller's `last_read_at` for the conversation. Returns\n * the updated participant row.\n */\n markRead: async (conversationId: string): Promise<MarkReadResult> =>\n (await coreCall((c) => c.messagingMarkRead(\n conversationId,\n ))) as MarkReadResult,\n // </generated:messaging>\n\n /**\n * Live message subscriptions (SDK 4.0). Returns a builder — call\n * `.subscribe(cb)` to receive new messages in a conversation live\n * instead of polling `listMessages`. `subscribe` returns an\n * unsubscribe handle.\n *\n * @example\n * const unsub = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => {\n * // change.op === \"INSERT\"; change.row is the new message\n * appendMessageToThread(change.row);\n * });\n * // later: unsub();\n *\n * Delivery is scoped to conversation participants — you receive a\n * conversation's messages only if your session is a member of it.\n * Requires an authenticated session.\n */\n conversation: <T = Message>(conversationId: string): ConversationSubscription<T> =>\n new ConversationSubscription<T>(\n { baseUrl: _configuredApiUrl, authHeaders: realtimeAuthHeaders },\n conversationId,\n ),\n },\n\n /** Report users / content for moderation. */\n moderation: {\n reportUser: async (request: ReportRequest): Promise<Report> =>\n (await coreCall((c) => c.moderationReportUser(\n encodeJson(request),\n ))) as Report,\n reportContent: async (request: ReportRequest): Promise<Report> =>\n (await coreCall((c) => c.moderationReportContent(\n encodeJson(request),\n ))) as Report,\n getReportStatus: async (id: string): Promise<Report> =>\n (await coreCall((c) => c.moderationGetReportStatus(id))) as Report,\n },\n\n /** User reviews of catalog items or other users. */\n reviews: {\n // <generated:reviews>\n list: async (targetType: string, targetId: string): Promise<Review[]> =>\n (await coreCall((c) => c.reviewsList(targetType, targetId))) as Review[],\n create: async (params: ReviewCreate): Promise<Review> =>\n (await coreCall((c) => c.reviewsCreate(encodeJson(params)))) as Review,\n update: async (id: string, patch: ReviewUpdate): Promise<Review> =>\n (await coreCall((c) => c.reviewsUpdate(id, encodeJson(patch)))) as Review,\n delete: async (id: string): Promise<void> => {\n await coreCall((c) => c.reviewsDelete(id));\n },\n // </generated:reviews>\n },\n\n /** RBAC roles + permission checks. */\n roles: {\n // <generated:roles>\n getMyRoles: async (): Promise<Role[]> =>\n (await coreCall((c) => c.rolesGetMyRoles())) as Role[],\n hasPermission: async (permission: string): Promise<boolean> =>\n (await coreCall((c) => c.rolesHasPermission(permission))) as boolean,\n // </generated:roles>\n },\n\n /** Referral codes + claim flow. */\n referrals: {\n // <generated:referrals>\n getReferralCode: async (): Promise<ReferralCode> =>\n (await coreCall((c) => c.referralsGetReferralCode())) as ReferralCode,\n claimReferral: async (code: string): Promise<ReferralClaim> =>\n (await coreCall((c) => c.referralsClaimReferral(code))) as ReferralClaim,\n create: async (\n code?: string,\n maxUses?: number,\n ): Promise<ReferralCode> =>\n (await coreCall((c) => c.referralsCreate(\n code ?? undefined,\n maxUses ?? undefined,\n ))) as ReferralCode,\n // </generated:referrals>\n },\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n /** Read-only product catalog. */\n catalog: {\n // <generated:catalog>\n list: async (): Promise<CatalogItem[]> =>\n (await coreCall((c) => c.catalogList())) as CatalogItem[],\n /**\n * Fetch a single catalog item by id (SDK 4.0).\n * `GET /v1/client/catalog/:id`.\n */\n get: async (id: string): Promise<CatalogItem> =>\n (await coreCall((c) => c.catalogGet(id))) as CatalogItem,\n // </generated:catalog>\n },\n\n /** Developer-defined content items (daily quotes, lessons, etc.). */\n content: {\n // <generated:content>\n /**\n * Get today's published item for `channel`. Defaults to `\"default\"`\n * so single-channel apps can call `Amba.content.today()` bare.\n * (Canonical 4.0 name; legacy `getToday` is removed.)\n */\n today: async (channel: string = \"default\"): Promise<ContentItem | null> =>\n (await coreCall((c) => c.contentGetToday(channel))) as ContentItem | null,\n /**\n * Paginated library list for `channel`. `options.limit` caps page size\n * (server default applies when omitted); `options.cursor` is the\n * opaque token returned by a prior response's `next_cursor`.\n * (Canonical 4.0 name; legacy `getLibrary` is removed.)\n */\n library: async (\n channel: string = \"default\",\n options?: { limit?: number; cursor?: string },\n ): Promise<ContentItem[]> =>\n (await coreCall((c) => c.contentGetLibrary(\n channel,\n options?.limit ?? undefined,\n options?.cursor ?? undefined,\n ))) as ContentItem[],\n item: async (id: string): Promise<ContentItem> =>\n (await coreCall((c) => c.contentGetItem(id))) as ContentItem,\n updateItem: async (id: string, state: unknown): Promise<ContentItem> =>\n (await coreCall((c) => c.contentUpdateItem(\n id,\n encodeJson(state),\n ))) as ContentItem,\n createItem: async (\n channel: string,\n item: unknown,\n ): Promise<ContentItem> =>\n (await coreCall((c) => c.contentCreateItem(\n channel,\n encodeJson(item),\n ))) as ContentItem,\n // </generated:content>\n },\n\n /** Server-managed deep link records. */\n deepLinks: {\n // <generated:deepLinks>\n get: async (shortCode: string): Promise<DeepLink> =>\n (await coreCall((c) => c.deepLinksGet(shortCode))) as DeepLink,\n // </generated:deepLinks>\n create: async (params: DeepLinkCreate): Promise<DeepLink> =>\n (await coreCall((c) => c.deepLinksCreate(encodeJson(params)))) as DeepLink,\n },\n\n /** Step-by-step user onboarding flow. */\n onboarding: {\n // <generated:onboarding>\n getStatus: async (): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingGetStatus())) as OnboardingStatus,\n nextStep: async (payload: unknown): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingNextStep(\n encodeJson(payload),\n ))) as OnboardingStatus,\n skipStep: async (): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingSkipStep())) as OnboardingStatus,\n complete: async (): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingComplete())) as OnboardingStatus,\n // </generated:onboarding>\n },\n\n};\n\n/** Escape hatch — the raw SDK runtime handle, for namespaces not yet wrapped. */\nexport function getCoreWasm(): AmbaCoreWasm {\n return requireCore();\n}\n\n/**\n * Package version stamped at build time. Sent in the\n * `sdk_wrapper_version` header on every customer request.\n *\n * **Must match `package.json#version` exactly** — the\n * `verify-sdk-version-coherence` GH action gates every publish on the\n * pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).\n */\nexport const SDK_VERSION = \"4.0.10\";\n","import init, { AmbaCoreWasm } from \"@layers/amba-core/inline\";\n\nimport { setCoreRuntime } from \"./wasm-loader\";\n\nsetCoreRuntime(init, AmbaCoreWasm);\n\nexport * from \"./index\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","names":["coreInit: CoreInit | undefined","coreWasmClass: CoreWasmClass | undefined","init","prefix: string","block: ContentBlock","messages: AiMessage[]","variableBlocks: ContentBlock[]","leadingBlocks: unknown[]","DEFAULT_API_URL","ctx: RealtimeContext","name: string","controller: AbortController | null","idx: number","dataLines: string[]","conversationId: string","dataLines: string[]","payload: Record<string, unknown>","resp: Response","parsed: unknown","split: { frame: string; rest: string } | null","full: OutboxOp","replayError: unknown","existing: ServiceWorkerRegistration | undefined","priorTimer","entry: PendingTimer","out: ScheduledLocalNotification[]","coreInstance: AmbaCoreWasm | null","_configuredProjectId: string | null","_configuredApiUrl: string","_configuredClientKey: string | null","_currentSessionToken: string | null","_currentRefreshToken: string | null","_currentSessionExpiresAt: string | null","_currentUser: User | null","_sessionPersistence: SessionPersistence","SESSION_PERSISTENCE_MODES: readonly SessionPersistence[]","snapshot: PersistedBrowserIdentity","_configuredFunctionsBaseUrl: string | null","_resolvedFunctionsProjectId: string | null","_functionsProjectIdInflight: Promise<string> | null","_persistence: LocalStoragePersistence | null","_offlineQueue: OfflineQueue | null","_offlineDetach: (() => void) | null","headers: Record<string, string>","body: Record<string, unknown>","resp: Response","wire: Record<string, unknown>","nextCore: AmbaCoreWasm","candidate: Record<string, unknown>","parsed: unknown","uploadHeaders: Record<string, string>","requestBody: string | undefined"],"sources":["../src/wasm-loader.ts","../src/storage-adapter.ts","../src/json.ts","../src/ai-images.ts","../src/error.ts","../src/path-segment.ts","../src/realtime.ts","../src/ai-stream.ts","../src/resilience.ts","../src/push-rotation.ts","../src/local-notifications.ts","../src/index.ts","../src/browser.ts"],"sourcesContent":["import type { AmbaCoreWasm, InitInput, InitOutput } from \"@layers/amba-core\";\n\ntype CoreInit = (\n moduleOrPath?:\n | { module_or_path: InitInput | Promise<InitInput> }\n | InitInput\n | Promise<InitInput>,\n) => Promise<InitOutput>;\ntype CoreWasmClass = {\n init(configJson: string, fetchFn: typeof fetch): AmbaCoreWasm;\n};\n\nlet coreInit: CoreInit | undefined;\nlet coreWasmClass: CoreWasmClass | undefined;\n\nexport function setCoreRuntime(init: CoreInit, wasmClass: CoreWasmClass): void {\n coreInit = init;\n coreWasmClass = wasmClass;\n}\n\nexport async function getCoreRuntime(): Promise<{\n initCore: CoreInit;\n wasmClass: CoreWasmClass;\n}> {\n if (coreInit === undefined || coreWasmClass === undefined) {\n const core = await import(\"@layers/amba-core\");\n setCoreRuntime(core.default, core.AmbaCoreWasm);\n }\n if (coreInit === undefined || coreWasmClass === undefined) {\n throw new Error(\"Amba.configure: core WASM runtime failed to load\");\n }\n return {\n initCore: coreInit,\n wasmClass: coreWasmClass,\n };\n}\n","/**\n * Browser localStorage adapter for SDK identity persistence.\n *\n * Browsers can deny or throw on localStorage access (privacy mode, sandboxed\n * iframes, quota exhaustion). Persistence is best-effort: storage failures\n * must never turn a successful auth/network operation into an SDK failure.\n */\nexport class LocalStoragePersistence {\n constructor(private readonly prefix: string) {}\n\n private get storage(): Storage | null {\n try {\n return globalThis.localStorage ?? null;\n } catch {\n // Accessing window.localStorage itself can throw in sandboxed iframes.\n return null;\n }\n }\n\n get available(): boolean {\n return this.storage !== null;\n }\n\n read(key: string): string | null {\n const storage = this.storage;\n if (!storage) return null;\n try {\n return storage.getItem(`${this.prefix}:${key}`);\n } catch {\n return null;\n }\n }\n\n write(key: string, value: string): void {\n const storage = this.storage;\n if (!storage) return;\n try {\n storage.setItem(`${this.prefix}:${key}`, value);\n } catch {\n // Best effort — the live core remains authoritative for this page.\n }\n }\n\n delete(key: string): void {\n const storage = this.storage;\n if (!storage) return;\n try {\n storage.removeItem(`${this.prefix}:${key}`);\n } catch {\n // Best effort — see write().\n }\n }\n}\n","/**\n * JSON encode helper — guarantees stable serialization across browser\n * runtimes and provides a single hook for future schema validation.\n */\nexport function encodeJson(value: unknown): string {\n return JSON.stringify(value);\n}\n","/**\n * Vision (multimodal) image-input expansion.\n *\n * `Amba.ai.*.create({ prompt_slug, images: [...] })` is the ergonomic way to\n * send images to a vision-capable prompt: you pass vendor-neutral\n * `{ url }` / `{ data, mime }` and Amba maps them to the model's native image\n * format server-side. This helper expands the convenience `images` array into\n * vendor-neutral content blocks on the request's `messages` before it goes over\n * the wire — so the request the backend receives carries\n * `messages[].content = [{type:'text',...}, {type:'image',...}]`, which the AI\n * gateway then maps per provider.\n *\n * Behavior:\n * - No `images` → the request is returned UNCHANGED (text-only stays\n * byte-identical; not even a clone is made).\n * - `images` present → they attach to the LAST user message. If that\n * message's content is a string, it's lifted into a `{type:'text'}` block\n * first so text + images sit side by side. If there's no user message, a\n * fresh one carrying just the images is appended.\n * - The `images` field itself is stripped from the outgoing request (it's an\n * SDK convenience, not part of the backend wire contract).\n */\nimport type { AiImageInput, AiMessage, AiMessageRequest } from \"./types\";\n\ntype ContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"image\"; url?: string; data?: string; mime?: string };\n\n/**\n * Normalize an image input into a content block, applying the SAME validity\n * rule the backend's gateway + the MCP tool use: trim `url`/`data`, treat a\n * blank/whitespace-only value as absent, and return `null` when NEITHER a real\n * url nor real data survives. A `null` block is dropped by the caller so we\n * never emit an empty image — keeping the SDK, MCP, and gateway in agreement.\n */\nfunction normalizeImageBlock(img: AiImageInput): ContentBlock | null {\n const url =\n typeof img.url === \"string\" && img.url.trim().length > 0 ? img.url.trim() : undefined;\n const data =\n typeof img.data === \"string\" && img.data.trim().length > 0 ? img.data.trim() : undefined;\n if (!url && !data) return null;\n const block: ContentBlock = { type: \"image\" };\n if (url !== undefined) block.url = url;\n if (data !== undefined) block.data = data;\n if (img.mime !== undefined) block.mime = img.mime;\n return block;\n}\n\n/**\n * Expand a request's convenience `images` into content blocks on `messages`.\n * Returns the SAME object reference when there's nothing to expand, or a new\n * request (with `images` removed) when images were attached.\n */\nexport function expandAiImages(request: AiMessageRequest): AiMessageRequest {\n const images = request.images;\n if (!images || images.length === 0) return request;\n\n const imageBlocks = images\n .map(normalizeImageBlock)\n .filter((b): b is ContentBlock => b !== null);\n\n // Strip `images` from the outgoing request regardless — it's SDK-only.\n const { images: _images, ...rest } = request;\n void _images;\n if (imageBlocks.length === 0) return rest;\n\n const messages: AiMessage[] = (rest.messages ?? []).map((m) => ({ ...m }));\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]!.role === \"user\") {\n lastUserIdx = i;\n break;\n }\n }\n if (lastUserIdx === -1) {\n // No user message to attach to. Synthesize one — but DON'T drop the\n // caller's template `variables`: the backend prefers `messages` over\n // `variables` (mapped to `context`), so an images-only message would\n // silently stop the prompt's variable substitution from applying. Carry\n // the variables as a leading text block (mirroring how the backend folds\n // `context` into a user message) so images AND variables both take effect.\n const variableBlocks: ContentBlock[] =\n rest.variables && Object.keys(rest.variables).length > 0\n ? [{ type: \"text\", text: JSON.stringify(rest.variables) }]\n : [];\n messages.push({ role: \"user\", content: [...variableBlocks, ...imageBlocks] });\n } else {\n const existing = messages[lastUserIdx]!.content;\n // Preserve the existing content WITHOUT data loss when attaching images:\n // - string → kept as a text block.\n // - array → its blocks are kept as-is.\n // - any other JSON value (object/number/etc.) → coerced to a text block\n // via JSON.stringify so it survives alongside the images instead of\n // being silently dropped. `null`/`undefined` contribute nothing.\n let leadingBlocks: unknown[];\n if (typeof existing === \"string\") {\n leadingBlocks = [{ type: \"text\", text: existing }];\n } else if (Array.isArray(existing)) {\n leadingBlocks = existing;\n } else if (existing === null || existing === undefined) {\n leadingBlocks = [];\n } else {\n leadingBlocks = [{ type: \"text\", text: JSON.stringify(existing) }];\n }\n messages[lastUserIdx] = {\n ...messages[lastUserIdx]!,\n content: [...leadingBlocks, ...imageBlocks],\n };\n }\n return { ...rest, messages };\n}\n","/**\n * Typed error class for the amba SDK + helper that converts arbitrary\n * thrown values (native Error, plain objects, runtime-emitted errors)\n * into a stable `AmbaApiError` instance.\n *\n * Callers can `instanceof`-narrow safely, branch on `.code`, and pull\n * extra payload from `.details` (raw HTTP body, validation field paths,\n * etc.). The wrapper methods on `Amba` wrap every async call in\n * `toAmbaApiError`, so consumers see one error shape regardless of\n * which layer (network, runtime, validation) failed.\n */\n\n/**\n * Stable error codes surfaced by the SDK. Strings rather than an enum\n * so customers can extend with custom codes by simply throwing\n * `new AmbaApiError(\"MY_CODE\", \"...\")` without coordinating with this\n * package's TypeScript types.\n */\nexport type AmbaApiErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"VALIDATION_ERROR\"\n | \"NETWORK_ERROR\"\n | \"HTTP_ERROR\"\n | \"CIRCUIT_OPEN\"\n | \"CONSENT_NOT_GRANTED\"\n | \"NOT_INITIALIZED\"\n | \"INVALID_CONFIG\"\n | \"INVALID_ARGUMENT\"\n | \"PUSH_PERMISSION_DENIED\"\n | \"PUSH_REGISTRATION_FAILED\"\n | \"LOCAL_NOTIFICATIONS_UNAVAILABLE\"\n | \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\"\n | \"NOT_SANDBOX_TIER\"\n | \"UNKNOWN_ERROR\"\n | (string & {});\n\nexport class AmbaApiError extends Error {\n readonly code: AmbaApiErrorCode;\n readonly details?: unknown;\n /**\n * Server-side request / trace id (the `X-Amba-Request-Id` response\n * header, falling back to the `request_id` field in the standard error\n * envelope). Present on errors thrown by `Amba.functions.invoke()` so a\n * failed run can be correlated with server logs\n * (`functions logs --request-id <traceId>`). `undefined` when the failure\n * happened before a response was received (e.g. a transport error).\n */\n traceId?: string;\n constructor(\n code: AmbaApiErrorCode,\n message: string,\n details?: unknown,\n ) {\n super(message);\n this.name = \"AmbaApiError\";\n this.code = code;\n this.details = details;\n // Restore prototype chain when transpiled to ES5 targets — Error\n // subclasses lose their prototype otherwise and `instanceof` breaks.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Best-effort: pull a stable error code out of an `AmbaError` display\n * string. Used by `toAmbaApiError` when the underlying thrown value is a\n * generic `Error` produced by the SDK core.\n *\n * Keep in sync with the SDK core's `AmbaError` format strings — but the\n * parser falls back to UNKNOWN_ERROR for any pattern it doesn't recognize,\n * so a stale match list isn't a footgun, just a quality-of-life regression.\n */\nfunction codeFromMessage(message: string): AmbaApiErrorCode {\n // Cheapest checks first — most production errors are auth or HTTP.\n if (message.startsWith(\"Unauthorized\")) return \"UNAUTHORIZED\";\n if (message.startsWith(\"Forbidden\")) return \"FORBIDDEN\";\n if (message.startsWith(\"Not found\")) return \"NOT_FOUND\";\n if (message.startsWith(\"Conflict\")) return \"CONFLICT\";\n if (message.startsWith(\"Rate limited\")) return \"RATE_LIMITED\";\n if (message.startsWith(\"Validation error\")) return \"VALIDATION_ERROR\";\n if (message.startsWith(\"Network error\")) return \"NETWORK_ERROR\";\n if (message.startsWith(\"HTTP error\")) return \"HTTP_ERROR\";\n if (message.startsWith(\"Circuit breaker\")) return \"CIRCUIT_OPEN\";\n if (message.startsWith(\"Consent not granted\")) return \"CONSENT_NOT_GRANTED\";\n if (message.includes(\"not initialized\")) return \"NOT_INITIALIZED\";\n if (message.startsWith(\"Invalid configuration\")) return \"INVALID_CONFIG\";\n if (message.startsWith(\"Invalid argument\")) return \"INVALID_ARGUMENT\";\n return \"UNKNOWN_ERROR\";\n}\n\n/**\n * Coerce any thrown value into an `AmbaApiError`. Idempotent — passing\n * an existing `AmbaApiError` returns it unchanged so wrappers can stack\n * without re-wrapping.\n *\n * Heuristics:\n * 1. Existing `AmbaApiError` → returned as-is.\n * 2. Object with `.code` → use that code + `.message` + `.details`.\n * 3. `Error` (e.g. runtime-emitted error) → infer code from message prefix.\n * 4. Anything else → stringify and tag UNKNOWN_ERROR.\n */\nexport function toAmbaApiError(err: unknown): AmbaApiError {\n if (err instanceof AmbaApiError) return err;\n if (err && typeof err === \"object\") {\n const obj = err as { code?: unknown; message?: unknown; details?: unknown };\n if (typeof obj.code === \"string\") {\n return new AmbaApiError(\n obj.code,\n typeof obj.message === \"string\" ? obj.message : \"unknown\",\n obj.details,\n );\n }\n }\n if (err instanceof Error) {\n return new AmbaApiError(codeFromMessage(err.message), err.message);\n }\n return new AmbaApiError(\"UNKNOWN_ERROR\", String(err));\n}\n\n/**\n * Wrap a thunk so any thrown value becomes an `AmbaApiError`. The\n * wrapper methods on `Amba` use this to convert runtime-emitted errors\n * uniformly at the SDK boundary.\n */\nexport async function withAmbaError<T>(thunk: () => Promise<T>): Promise<T> {\n try {\n return await thunk();\n } catch (err) {\n throw toAmbaApiError(err);\n }\n}\n","import { AmbaApiError } from \"./error\";\n\n/**\n * Encode one caller-supplied id, key, slug or name as a single URL path\n * segment. `encodeURIComponent` escapes `/`, `?` and `#`, so the value stays\n * inside its segment. `.` and `..` are refused because URL resolution treats\n * them as steps to another route, and the empty string is refused because it\n * collapses onto the parent route.\n */\nexport function pathSegment(value: string): string {\n if (value === \"\" || value === \".\" || value === \"..\") {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n `path segment must not be ${value === \"\" ? \"empty\" : `'${value}'`}`,\n );\n }\n return encodeURIComponent(value);\n}\n","/**\n * Live collection subscriptions for `@layers/amba-web` (PROTOTYPE — T13).\n *\n * Public surface is intentionally tiny and provider-agnostic:\n *\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op is \"INSERT\" | \"UPDATE\" | \"DELETE\"\n * // change.row is the affected row\n * });\n * unsub(); // stop listening\n *\n * The transport (a streamed HTTP response) and the server-side fan-out\n * mechanism are deliberately NOT named here — customers only ever see\n * `.subscribe()`. Internally this opens a long-lived streamed GET to the\n * realtime endpoint and reconnects with backoff if the connection drops.\n *\n * Delivery mirrors `Amba.collection.find()` exactly: you receive only rows\n * your session owns, optionally narrowed by `.where(...)` equality filters.\n */\n\nimport { pathSegment } from \"./path-segment\";\n\nexport type RowChangeOp = \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\nexport interface RealtimeChange<T = unknown> {\n /** Which kind of write happened. */\n op: RowChangeOp;\n /**\n * The affected row. For deletes this is a partial row (at minimum `id`).\n * If the row was too large to deliver inline, `truncated` is `true` and\n * `row` carries just `id` — re-fetch with `Amba.collection.find()` /\n * `findOne()` when you need the full body.\n */\n row: T;\n truncated?: boolean;\n}\n\nexport interface SubscribeOptions {\n /** Called once when the live connection is (re)established. */\n onOpen?: () => void;\n /** Called on a connection error before the SDK retries. */\n onError?: (err: unknown) => void;\n}\n\n/** Unsubscribe handle returned by `.subscribe(...)`. Call it to stop. */\nexport type Unsubscribe = () => void;\n\n/**\n * Resolved at call time so the latest session token is used. Returns the\n * auth headers, or `null` when there's no live session (subscribe throws).\n */\nexport interface RealtimeContext {\n baseUrl: string;\n authHeaders: () => Record<string, string> | null;\n}\n\nconst INITIAL_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 15_000;\n\nconst DEFAULT_API_URL = \"https://api.amba.dev\";\nconst DEFAULT_REALTIME_URL = \"https://realtime.amba.host\";\n\n/**\n * Resolve the base URL for live-update connections. Long-lived streams are\n * served from a dedicated realtime host (no request-duration ceiling), so\n * when the SDK points at the default production API the stream connects\n * there instead. Any custom `apiUrl` (dev, staging, self-hosted, proxy)\n * keeps live updates on that same origin — override semantics unchanged.\n */\nexport function resolveRealtimeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\") === DEFAULT_API_URL ? DEFAULT_REALTIME_URL : baseUrl;\n}\n\n/**\n * Builder returned by `Amba.collection(name)`. Chain `.where(...)` to\n * narrow, then `.subscribe(cb)` to start streaming.\n */\nexport class CollectionSubscription<T = unknown> {\n private readonly filters: Record<string, unknown> = {};\n\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly name: string,\n ) {}\n\n /** Add an equality filter. Chainable. All filters must match (AND). */\n where(column: string, value: unknown): this {\n this.filters[column] = value;\n return this;\n }\n\n /**\n * Start listening. `cb` fires once per matching row change. Returns an\n * unsubscribe handle — call it to close the connection.\n */\n subscribe(cb: (change: RealtimeChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.\",\n );\n }\n const qs =\n Object.keys(this.filters).length > 0\n ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}`\n : \"\";\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${pathSegment(\n this.name,\n )}${qs}`;\n return openStream<T>({\n url,\n authHeaders: this.ctx.authHeaders,\n onChange: cb,\n options,\n });\n }\n}\n\n/**\n * Inputs to a single realtime stream. The collection and conversation paths\n * differ only in how `url` is built; everything downstream (transport, frame\n * parsing, backoff/reconnect) is identical, so they share one implementation.\n */\ninterface StreamArgs<T> {\n url: string;\n authHeaders: () => Record<string, string> | null;\n onChange: (change: RealtimeChange<T>) => void;\n options: SubscribeOptions;\n}\n\n/**\n * Open a streamed GET to a realtime endpoint and dispatch row-change events\n * to `onChange`. Reconnects with exponential backoff until the returned\n * unsubscribe handle is invoked. Shared by both collection and conversation\n * subscriptions — the only thing that differs between them is the URL.\n *\n * Uses `fetch` + a streamed response body (rather than the browser's\n * native `EventSource`) because `EventSource` cannot set request headers —\n * which would force the session token into the URL query string, where it\n * would leak into access logs. Streaming `fetch` keeps auth in headers.\n */\nfunction openStream<T>(args: StreamArgs<T>): Unsubscribe {\n let stopped = false;\n let controller: AbortController | null = null;\n let backoff = INITIAL_BACKOFF_MS;\n\n const run = async (): Promise<void> => {\n while (!stopped) {\n controller = new AbortController();\n try {\n const headers = args.authHeaders();\n if (!headers) throw new Error(\"no active session\");\n const resp = await fetch(args.url, {\n method: \"GET\",\n headers: { ...headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\n });\n if (!resp.ok || !resp.body) {\n throw new Error(`realtime connect failed: HTTP ${resp.status}`);\n }\n // Reset backoff only once the connection proves HEALTHY (first\n // frame received), not merely on a successful connect. A server\n // that accepts the connection then drops it immediately would\n // otherwise reset backoff every loop → a tight reconnect storm\n // with no exponential growth.\n await consume(resp.body, args, () => {\n backoff = INITIAL_BACKOFF_MS;\n args.options.onOpen?.();\n });\n } catch (err) {\n if (stopped) break;\n args.options.onError?.(err);\n }\n if (stopped) break;\n await delay(backoff);\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);\n }\n };\n\n void run();\n\n return () => {\n stopped = true;\n controller?.abort();\n };\n}\n\nasync function consume<T>(\n body: ReadableStream<Uint8Array>,\n args: StreamArgs<T>,\n onFirstFrame: () => void,\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let sawFrame = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n onFirstFrame();\n }\n dispatchFrame<T>(frame, args.onChange);\n }\n }\n}\n\nfunction dispatchFrame<T>(frame: string, onChange: (change: RealtimeChange<T>) => void): void {\n let event = \"\";\n const dataLines: string[] = [];\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n if (line.startsWith(\"event:\")) event = line.slice(\"event:\".length).trim();\n else if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trim());\n }\n if (event !== \"change\") return; // ignore ready / error / heartbeat\n try {\n const payload = JSON.parse(dataLines.join(\"\\n\")) as {\n op: RowChangeOp;\n row: T;\n truncated?: boolean;\n };\n onChange({ op: payload.op, row: payload.row, truncated: payload.truncated });\n } catch {\n // malformed frame — skip rather than tear down the stream\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ── Messaging: live conversation subscriptions ─────────────────────────────\n\n/**\n * A live message change in a conversation. `op` is \"INSERT\" for a new\n * message; moderation removals arrive as \"DELETE\". For a delete (or when a\n * message was too large to deliver inline) `row` may be partial — re-fetch\n * with `Amba.messaging.listMessages(conversationId)` when you need the full\n * body.\n */\nexport type MessageChange<T = unknown> = RealtimeChange<T>;\n\n/**\n * Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`\n * to receive new messages live instead of polling.\n *\n * const unsub = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => {\n * // change.op === \"INSERT\"; change.row is the new message\n * });\n * unsub(); // stop listening\n *\n * Delivery is scoped to conversation participants — you receive messages for\n * a conversation only if your session is a member of it. Reconnects with the\n * same exponential backoff as collection subscriptions.\n */\nexport class ConversationSubscription<T = unknown> {\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly conversationId: string,\n ) {}\n\n subscribe(cb: (change: MessageChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${pathSegment(\n this.conversationId,\n )}`;\n return openStream<T>({\n url,\n authHeaders: this.ctx.authHeaders,\n onChange: cb,\n options,\n });\n }\n}\n\n// ── Gamification: live xp / level / achievement / streak updates ────────────\n\n/** Which slice of the signed-in user's gamification state changed. */\nexport type GamificationKind = \"xp\" | \"level\" | \"achievement\" | \"streak\";\n\n/**\n * A live change to the signed-in user's gamification state. `kind` says which\n * slice moved; `data` carries the new state inline so you can update without a\n * re-fetch:\n * - `xp` — `{ total_xp, level, delta }`\n * - `level` — `{ level, total_xp }`\n * - `achievement` — `{ achievement_id, unlocked }`\n * - `streak` — `{ streak_id, count, status, ... }`\n *\n * If a payload was too large to deliver inline, `truncated` is `true` and `data`\n * is empty — re-fetch with `Amba.xp.getBalance()` / `Amba.streaks.getAll()` etc.\n */\nexport interface GamificationChange<T = Record<string, unknown>> {\n kind: GamificationKind;\n data: T;\n truncated?: boolean;\n}\n\n/**\n * Builder returned by `Amba.gamification`. Call `.subscribe(cb)` to receive the\n * signed-in user's xp / level / achievement / streak changes live instead of\n * polling.\n *\n * const unsub = Amba.gamification.subscribe((change) => {\n * // change.kind: \"xp\" | \"level\" | \"achievement\" | \"streak\"\n * // change.data: the new state\n * });\n * unsub(); // stop listening\n *\n * Delivery is scoped to the signed-in user — you only ever receive your own\n * gamification changes, never another user's. Reconnects with the same\n * exponential backoff as collection subscriptions.\n */\nexport class GamificationSubscription {\n constructor(private readonly ctx: RealtimeContext) {}\n\n subscribe(\n cb: (change: GamificationChange) => void,\n options: SubscribeOptions = {},\n ): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.gamification.subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/gamification`;\n return openGamificationStream({\n url,\n authHeaders: this.ctx.authHeaders,\n onChange: cb,\n options,\n });\n }\n}\n\ninterface GamificationStreamArgs {\n url: string;\n authHeaders: () => Record<string, string> | null;\n onChange: (change: GamificationChange) => void;\n options: SubscribeOptions;\n}\n\n/**\n * Open a streamed GET to the gamification realtime endpoint. Identical\n * transport + backoff/reconnect to {@link openStream}; only the frame payload\n * shape differs (gamification carries `kind`, not `op`/`row`), so it gets its\n * own thin consumer rather than overloading the row-change parser.\n */\nfunction openGamificationStream(args: GamificationStreamArgs): Unsubscribe {\n let stopped = false;\n let controller: AbortController | null = null;\n let backoff = INITIAL_BACKOFF_MS;\n\n const run = async (): Promise<void> => {\n while (!stopped) {\n controller = new AbortController();\n try {\n const headers = args.authHeaders();\n if (!headers) throw new Error(\"no active session\");\n const resp = await fetch(args.url, {\n method: \"GET\",\n headers: { ...headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\n });\n if (!resp.ok || !resp.body) {\n throw new Error(`realtime connect failed: HTTP ${resp.status}`);\n }\n await consumeGamification(resp.body, args, () => {\n backoff = INITIAL_BACKOFF_MS;\n args.options.onOpen?.();\n });\n } catch (err) {\n if (stopped) break;\n args.options.onError?.(err);\n }\n if (stopped) break;\n await delay(backoff);\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);\n }\n };\n\n void run();\n\n return () => {\n stopped = true;\n controller?.abort();\n };\n}\n\nasync function consumeGamification(\n body: ReadableStream<Uint8Array>,\n args: GamificationStreamArgs,\n onFirstFrame: () => void,\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let sawFrame = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n onFirstFrame();\n }\n dispatchGamificationFrame(frame, args.onChange);\n }\n }\n}\n\nfunction dispatchGamificationFrame(\n frame: string,\n onChange: (change: GamificationChange) => void,\n): void {\n let event = \"\";\n const dataLines: string[] = [];\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n if (line.startsWith(\"event:\")) event = line.slice(\"event:\".length).trim();\n else if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trim());\n }\n if (event !== \"change\") return; // ignore ready / error / heartbeat\n try {\n const payload = JSON.parse(dataLines.join(\"\\n\")) as {\n kind: GamificationKind;\n data: Record<string, unknown>;\n truncated?: boolean;\n };\n onChange({ kind: payload.kind, data: payload.data, truncated: payload.truncated });\n } catch {\n // malformed frame — skip rather than tear down the stream\n }\n}\n","/**\n * Client-side consumption of a streamed (SSE) AI response for\n * `@layers/amba-web`.\n *\n * The buffered `Amba.ai.anthropic.messages.create()` decodes a single JSON\n * envelope and therefore can't surface tokens as they arrive. The streaming\n * methods (`Amba.ai.anthropic.messages.stream()` /\n * `Amba.ai.openai.chat.completions.stream()`) open the same backend route with\n * `stream: true` and read the response body incrementally, yielding text\n * deltas the moment the model emits them plus a final `done` event carrying\n * token usage.\n *\n * The backend pipes the *vendor* SSE through verbatim (its server-side tee\n * still records usage/cost), so this parser understands both wire shapes:\n *\n * - Anthropic Messages SSE: `event: content_block_delta` frames with a\n * `{delta:{type:'text_delta', text}}` payload; usage arrives split across\n * the initial `message_start` (input tokens) and the final `message_delta`\n * (output tokens); the stream ends with `message_stop`.\n * - OpenAI Chat Completions SSE: `data:` frames with\n * `{choices:[{delta:{content}}]}`; usage (when the caller opts in via\n * `stream_options.include_usage`) arrives on the last data frame; the\n * stream ends with a literal `data: [DONE]`.\n *\n * Both are line-delimited SSE framed on a blank line (`\\n\\n`), matching the\n * realtime transport's reader. We surface one vendor-neutral event union so\n * callers don't branch on provider.\n */\n\nimport { AmbaApiError } from \"./error\";\nimport type { AiUsage } from \"./types\";\n\n/** A streamed token delta — append `text` to your running output buffer. */\nexport interface AiStreamDelta {\n type: \"delta\";\n /** The incremental text the model just produced. */\n text: string;\n}\n\n/**\n * Terminal event. Emitted once when the stream completes cleanly. `usage`\n * carries the token accounting accumulated across the stream (zeroed fields\n * when the vendor didn't report a given counter); `model` / `stopReason` are\n * populated when the vendor surfaced them.\n */\nexport interface AiStreamDone {\n type: \"done\";\n usage: AiUsage;\n model?: string;\n stopReason?: string | null;\n}\n\n/** Vendor-neutral event yielded by the streaming AI methods. */\nexport type AiStreamEvent = AiStreamDelta | AiStreamDone;\n\n/** Options accepted by the streaming AI methods. */\nexport interface AiStreamOptions {\n /**\n * Optional callback invoked for every text delta as it arrives — a\n * convenience over iterating, useful when you only care about appending\n * tokens to the UI. Fires in addition to the yielded `delta` events.\n */\n onChunk?: (text: string) => void;\n}\n\nexport interface InvokeAiStreamArgs {\n /** Full URL of the backend streaming route. */\n url: string;\n /** Auth + content headers (X-Api-Key, optional Bearer, content-type). */\n headers: Record<string, string>;\n /** JSON request body (already includes `stream: true`). */\n body: string;\n /** Forwarded `onChunk` callback. */\n onChunk?: (text: string) => void;\n}\n\n/**\n * Mutable accumulator threaded through frame parsing so usage reported across\n * multiple SSE frames (Anthropic splits input/output across `message_start`\n * and `message_delta`) is merged into one final `AiUsage`.\n */\ninterface UsageAccumulator {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n model?: string;\n stopReason?: string | null;\n}\n\nfunction newUsageAccumulator(): UsageAccumulator {\n return {\n input_tokens: 0,\n output_tokens: 0,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n };\n}\n\nfunction toAiUsage(acc: UsageAccumulator): AiUsage {\n return {\n input_tokens: acc.input_tokens,\n output_tokens: acc.output_tokens,\n cache_creation_input_tokens: acc.cache_creation_input_tokens,\n cache_read_input_tokens: acc.cache_read_input_tokens,\n };\n}\n\n/** Read a numeric field defensively (vendors occasionally send null). */\nfunction num(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) ? v : 0;\n}\n\n/**\n * Merge a vendor `usage`-shaped object into the accumulator. Handles both\n * Anthropic (`input_tokens` / `output_tokens` / `cache_*`) and OpenAI\n * (`prompt_tokens` / `completion_tokens`) field names, taking the max so a\n * later frame that re-reports a counter doesn't shrink it.\n */\nfunction mergeUsage(acc: UsageAccumulator, usage: Record<string, unknown>): void {\n const input = usage[\"input_tokens\"] ?? usage[\"prompt_tokens\"];\n const output = usage[\"output_tokens\"] ?? usage[\"completion_tokens\"];\n if (input !== undefined) acc.input_tokens = Math.max(acc.input_tokens, num(input));\n if (output !== undefined) acc.output_tokens = Math.max(acc.output_tokens, num(output));\n if (usage[\"cache_creation_input_tokens\"] !== undefined) {\n acc.cache_creation_input_tokens = Math.max(\n acc.cache_creation_input_tokens,\n num(usage[\"cache_creation_input_tokens\"]),\n );\n }\n if (usage[\"cache_read_input_tokens\"] !== undefined) {\n acc.cache_read_input_tokens = Math.max(\n acc.cache_read_input_tokens,\n num(usage[\"cache_read_input_tokens\"]),\n );\n }\n}\n\n/**\n * Parse one SSE frame (everything between blank lines) and return the text\n * delta it carries, if any. Side-effect: folds any usage / model / stop\n * metadata into `acc`. Returns `null` for keepalive comments, the `[DONE]`\n * sentinel, and frames with no text (e.g. Anthropic's `message_start`).\n *\n * Tolerant by design — a malformed `data:` payload is skipped rather than\n * tearing down the stream.\n */\nexport function parseAiSseFrame(frame: string, acc: UsageAccumulator): string | null {\n const dataLines: string[] = [];\n // Split on any line ending (\\r\\n, \\n, or a bare \\r) so a CRLF wire shape\n // (some servers/proxies emit it) doesn't leave a trailing \\r on each field.\n for (const line of frame.split(/\\r\\n|\\r|\\n/)) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n // `event:` lines are advisory — the JSON payload's own `type` is what we\n // branch on, so we only need the `data:` lines.\n if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trimStart());\n }\n if (dataLines.length === 0) return null;\n const data = dataLines.join(\"\\n\").trim();\n if (data === \"\" || data === \"[DONE]\") return null; // OpenAI terminal sentinel\n\n let payload: Record<string, unknown>;\n try {\n payload = JSON.parse(data) as Record<string, unknown>;\n } catch {\n return null; // malformed frame — skip\n }\n\n const type = typeof payload[\"type\"] === \"string\" ? (payload[\"type\"] as string) : \"\";\n\n // ── Anthropic shapes ───────────────────────────────────────────────\n if (type === \"content_block_delta\") {\n const delta = payload[\"delta\"] as Record<string, unknown> | undefined;\n const text = delta?.[\"text\"];\n return typeof text === \"string\" ? text : null;\n }\n if (type === \"message_start\") {\n const message = payload[\"message\"] as Record<string, unknown> | undefined;\n const usage = message?.[\"usage\"];\n if (usage && typeof usage === \"object\") mergeUsage(acc, usage as Record<string, unknown>);\n if (typeof message?.[\"model\"] === \"string\") acc.model = message[\"model\"] as string;\n return null;\n }\n if (type === \"message_delta\") {\n const usage = payload[\"usage\"];\n if (usage && typeof usage === \"object\") mergeUsage(acc, usage as Record<string, unknown>);\n const delta = payload[\"delta\"] as Record<string, unknown> | undefined;\n if (typeof delta?.[\"stop_reason\"] === \"string\") acc.stopReason = delta[\"stop_reason\"] as string;\n return null;\n }\n if (type === \"message_stop\") return null;\n\n // ── OpenAI Chat Completions shape ──────────────────────────────────\n // Frames carry `choices[0].delta.content`; the final frame (when the caller\n // opted into `stream_options.include_usage`) carries top-level `usage`.\n if (Array.isArray(payload[\"choices\"])) {\n const choices = payload[\"choices\"] as Array<Record<string, unknown>>;\n if (typeof payload[\"model\"] === \"string\") acc.model = payload[\"model\"] as string;\n const usage = payload[\"usage\"];\n if (usage && typeof usage === \"object\") mergeUsage(acc, usage as Record<string, unknown>);\n const first = choices[0];\n if (first) {\n const finish = first[\"finish_reason\"];\n if (typeof finish === \"string\") acc.stopReason = finish;\n const delta = first[\"delta\"] as Record<string, unknown> | undefined;\n const content = delta?.[\"content\"];\n if (typeof content === \"string\" && content.length > 0) return content;\n }\n return null;\n }\n\n return null;\n}\n\n/**\n * SSE event-frame delimiter: a blank line. The spec permits any line ending\n * (`\\r\\n`, `\\n`, `\\r`), so a blank line is two consecutive line breaks in any\n * combination — `\\r\\n\\r\\n`, `\\n\\n`, or `\\r\\r`. Some servers / proxies emit\n * CRLF, so splitting on `\\n\\n` alone would never frame those streams.\n */\nconst SSE_FRAME_DELIMITER = /\\r\\n\\r\\n|\\n\\n|\\r\\r/;\n\n/**\n * Pull the next complete SSE frame off the front of `buffer`, returning the\n * frame text (without the trailing blank-line delimiter) and the remaining\n * buffer, or `null` when no complete frame is present yet.\n *\n * Only *complete* delimiters match: a buffer ending mid-delimiter (e.g.\n * `…\\r\\n\\r`, awaiting the final `\\n`) leaves the partial bytes buffered for the\n * next read rather than framing prematurely.\n */\nexport function splitNextSseFrame(buffer: string): { frame: string; rest: string } | null {\n const m = SSE_FRAME_DELIMITER.exec(buffer);\n if (!m) return null;\n return {\n frame: buffer.slice(0, m.index),\n rest: buffer.slice(m.index + m[0].length),\n };\n}\n\n/**\n * Open the streaming route and yield vendor-neutral events. Throws an\n * `AmbaApiError` on a transport failure or a non-2xx response (the error body\n * is read and surfaced just like the buffered path).\n *\n * The generator yields zero or more `{type:'delta'}` events followed by\n * exactly one terminal `{type:'done'}` when the stream completes cleanly.\n */\nexport async function* invokeAiStream(\n args: InvokeAiStreamArgs,\n): AsyncGenerator<AiStreamEvent, void, unknown> {\n let resp: Response;\n try {\n resp = await fetch(args.url, {\n method: \"POST\",\n headers: args.headers,\n body: args.body,\n });\n } catch (err) {\n throw new AmbaApiError(\"NETWORK_ERROR\", `AI stream network error: ${String(err)}`);\n }\n\n if (!resp.ok) {\n // Mirror the buffered path's error mapping: read the body, lift the\n // `{error:{code,message}}` envelope when present.\n const text = await resp.text().catch(() => \"\");\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\" ? envelope.error.code : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : typeof parsed === \"string\" && parsed.length > 0\n ? parsed\n : `AI stream failed with status ${resp.status}`;\n throw new AmbaApiError(code, message, parsed);\n }\n\n if (!resp.body) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n \"AI stream response had no readable body (the environment does not expose a streaming fetch body).\",\n );\n }\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n const acc = newUsageAccumulator();\n let buffer = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (value) buffer += decoder.decode(value, { stream: true });\n if (done) {\n // Final flush: drain any bytes the streaming decoder is still holding\n // (a multi-byte UTF-8 char split across the last two reads, or a\n // trailing partial sequence). Without it those bytes are silently\n // dropped at EOF. `decode()` with no args ends the stream.\n buffer += decoder.decode();\n }\n let split: { frame: string; rest: string } | null;\n while ((split = splitNextSseFrame(buffer)) !== null) {\n buffer = split.rest;\n const text = parseAiSseFrame(split.frame, acc);\n if (text !== null && text.length > 0) {\n args.onChunk?.(text);\n yield { type: \"delta\", text };\n }\n }\n if (done) {\n // Flush a trailing frame that wasn't terminated by a blank line.\n const tail = buffer.trim();\n if (tail.length > 0) {\n const text = parseAiSseFrame(tail, acc);\n if (text !== null && text.length > 0) {\n args.onChunk?.(text);\n yield { type: \"delta\", text };\n }\n }\n break;\n }\n }\n } finally {\n // Best-effort release; a thrown consumer (e.g. `break` in a `for await`)\n // shouldn't leak the lock.\n try {\n reader.releaseLock();\n } catch {\n // already released\n }\n }\n\n yield {\n type: \"done\",\n usage: toAiUsage(acc),\n model: acc.model,\n stopReason: acc.stopReason ?? null,\n };\n}\n","/**\n * Offline mutation queue + optimistic-update helpers — framework-agnostic\n * orchestration that sits ABOVE the collection / event write methods.\n *\n * Nothing here touches the SDK runtime directly: the queue is handed a\n * small `KvStore` (durable key/value persistence) and a `WriteExecutor`\n * (the function that actually performs a queued mutation against the API).\n * That keeps the same code identical across every JS SDK target — only the\n * adapters that supply storage + the executor differ per platform.\n *\n * Design:\n * - A write that fails because the device is offline (a transport-level\n * network error, or `navigator.onLine === false`) is appended to a\n * durable outbox instead of throwing all the way up.\n * - Each queued op carries a stable idempotency key. On replay the key is\n * forwarded to the API's collection-write idempotency support so a\n * mutation that actually reached the server before the connection\n * dropped is never applied twice.\n * - On reconnect (`online` event) the outbox is flushed in FIFO order;\n * each op is removed only after it succeeds, so a mid-flush failure\n * leaves the remainder intact for the next attempt.\n * - Each op records its owner (the user id and anonymous id live when it\n * was queued). A flush replays only the current owner's ops and holds\n * the rest until that owner signs back in; `pending()` lists only the\n * current owner's ops. The SDKs clear the whole outbox on sign-out.\n */\n\n/**\n * Minimal durable key/value contract the outbox persists through. Both the\n * browser `localStorage` adapter and the React Native AsyncStorage adapter\n * satisfy this — the queue never assumes anything else.\n */\nexport interface KvStore {\n get(key: string): Promise<string | null> | string | null;\n set(key: string, value: string): Promise<void> | void;\n delete(key: string): Promise<void> | void;\n}\n\n/**\n * The identity a queued op belongs to: the signed-in user id (null before any\n * session) and the install's anonymous id, both captured at enqueue.\n */\nexport interface OutboxOwner {\n appUserId: string | null;\n anonymousId: string | null;\n}\n\n/**\n * Whether `op` belongs to `current`. An op queued under a user id matches only\n * that user id. An op queued before any session matches the same anonymous id,\n * so a guest's offline write lands once that guest's session starts. An op\n * with no recorded owner (queued by an SDK version before owners existed)\n * matches nobody.\n */\nexport function outboxOwnerMatches(op: OutboxOp, current: OutboxOwner): boolean {\n const owner = op.owner;\n if (!owner) return false;\n if (owner.appUserId) return owner.appUserId === current.appUserId;\n return owner.anonymousId !== null && owner.anonymousId === current.anonymousId;\n}\n\n/**\n * Who started a write and the queue's purge generation at that moment,\n * captured before the live attempt (`OfflineQueue.ticket()`). A request can\n * outlive a sign-out; the ticket keeps its queued copy attributed to the user\n * who made it, and refuses it when the queue was purged meanwhile.\n */\nexport interface OutboxTicket {\n owner: OutboxOwner | undefined;\n generation: number;\n}\n\n/** The kinds of mutation the outbox can durably replay. */\nexport type OutboxOpKind =\n | \"collection.insert\"\n | \"collection.update\"\n | \"collection.delete\"\n | \"track\";\n\n/**\n * A single durably-queued mutation. `idempotencyKey` is reused verbatim on\n * every replay attempt so the server dedupes a COLLECTION write that landed\n * before the connection dropped (the events `track` API has no idempotency\n * channel, so `track` replays are at-least-once — a rare reconnect duplicate\n * is acceptable for fire-and-forget analytics). `enqueuedAt` is an ISO\n * timestamp for inspection.\n */\nexport interface OutboxOp {\n /** Stable per-op id (the idempotency key for collection writes). */\n idempotencyKey: string;\n kind: OutboxOpKind;\n /** Target collection name (absent for `track`). */\n collection?: string;\n /** Row id for update / delete. */\n id?: string;\n /** Insert row / update set payload. */\n payload?: Record<string, unknown>;\n /** Event name for `track`. */\n event?: string;\n /** Event properties for `track`. */\n properties?: Record<string, unknown>;\n /** Whether the tracked event was telemetry-tier. */\n telemetry?: boolean;\n /**\n * ISO timestamp the `track` originally OCCURRED, captured at enqueue. Replayed\n * as the events body `timestamp` (→ `occurred_at`) so a buffered event keeps\n * its real time instead of being re-stamped at flush. (Attribution is by the\n * session user at replay time — the events route has no body-id channel.)\n */\n occurredAt?: string;\n /**\n * Who queued the op. Stamped at enqueue when the queue has a `currentOwner`\n * source; a flush replays the op only while the same owner is signed in.\n */\n owner?: OutboxOwner;\n enqueuedAt: string;\n}\n\n/**\n * Performs one queued mutation against the live API. Implemented per-SDK so\n * the queue stays platform-agnostic. MUST forward `op.idempotencyKey` on\n * collection INSERTS (the API dedupes inserts on `(collection,\n * idempotency_key)`); `track` replays carry no idempotency channel and are\n * at-least-once. Throws on failure — the queue decides whether to retain or\n * drop the op.\n */\nexport type WriteExecutor = (op: OutboxOp) => Promise<void>;\n\n/** Result of a {@link OfflineQueue.flush}. */\nexport interface FlushResult {\n /** Ops successfully replayed and removed from the outbox. */\n flushed: number;\n /**\n * Ops dropped because their replay failed deterministically (a coded server\n * rejection like `VALIDATION_ERROR` / `NOT_FOUND` that would fail identically\n * forever). Dropping them prevents one poisoned op from wedging the queue;\n * the drain continues past it.\n */\n dropped: number;\n /** Ops still pending after the flush (a replay failed and stopped it). */\n remaining: number;\n}\n\n/**\n * Best-effort online probe. In a browser/RN runtime `navigator.onLine`\n * exists; under Node / SSR it doesn't, so we treat \"unknown\" as online\n * (the executor's own error handling then decides).\n */\nexport function isOnline(): boolean {\n const nav =\n typeof navigator !== \"undefined\"\n ? (navigator as { onLine?: boolean })\n : undefined;\n if (nav && typeof nav.onLine === \"boolean\") return nav.onLine;\n return true;\n}\n\n/**\n * Error codes the server raised deterministically — replaying them would fail\n * identically, so they're NEVER queued even when the device reports offline.\n * (A request can fail validation/auth/etc. while `navigator.onLine` is briefly\n * false; we must not buffer-and-retry those forever.)\n */\nconst DETERMINISTIC_ERROR_CODES = new Set([\n \"VALIDATION_ERROR\",\n \"INVALID_ARGUMENT\",\n \"INVALID_INPUT\",\n \"UNAUTHORIZED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"CONSENT_NOT_GRANTED\",\n \"NOT_INITIALIZED\",\n]);\n\n/**\n * Is this a deterministic server rejection — a coded error the server would\n * raise identically on every replay (bad input, auth, not-found, conflict)? A\n * queued op that fails this way is poison: retrying it forever would wedge the\n * queue, so the flush DROPS it. Everything else (transport failures, 5xx\n * `HTTP_ERROR`, `RATE_LIMITED`, `CIRCUIT_OPEN`, anything uncoded) is treated as\n * transient and RETAINED for a later attempt — favoring retain over data loss.\n */\nexport function isDeterministicError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n return typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code);\n}\n\n/**\n * Heuristic: does a thrown value look like a transient offline / transport\n * failure (vs a deterministic 4xx the server would reject again)? Used at\n * ENQUEUE time to decide whether a failed write is worth buffering — a\n * `VALIDATION_ERROR` will fail identically on replay, so it throws instead.\n *\n * A deterministic server error is excluded even when the device reports\n * offline: the request clearly reached the server (it got a coded response),\n * so retrying it on reconnect is pointless and would wedge the queue.\n *\n * NOTE: this is intentionally stricter than {@link isDeterministicError}'s\n * inverse — at enqueue time we only buffer GENUINE connectivity failures (a\n * non-offline 5xx/429 surfaces to the caller immediately rather than silently\n * queueing). The flush drop-decision uses {@link isDeterministicError} so a\n * transient 5xx/429 encountered DURING replay is retained, not dropped.\n */\nexport function isOfflineError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n if (typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code)) {\n return false;\n }\n if (code === \"NETWORK_ERROR\") return true;\n // `fetch` signals a transport failure (DNS / refused / offline) by throwing a\n // `TypeError` with a runtime-specific message — \"Failed to fetch\" (Chrome),\n // \"Network request failed\" (RN/Hermes), \"Load failed\" (Safari),\n // \"fetch failed\" (undici/Node). Match those messages rather than EVERY\n // TypeError, so a genuine programming `TypeError` (e.g. reading a property of\n // undefined) surfaces to the caller instead of being silently buffered —\n // especially on Node where `isOnline()` is always true.\n const msg = (err as { message?: unknown } | null | undefined)?.message;\n const looksLikeTransport =\n typeof msg === \"string\" &&\n /network (error|request failed)|failed to fetch|fetch failed|load failed/i.test(\n msg,\n );\n if (looksLikeTransport) return true;\n // No transport signal on the error itself — fall back to the runtime's\n // online state. Treat \"offline\" as queueable only for errors that DIDN'T\n // carry a deterministic server code (handled above).\n return !isOnline();\n}\n\n/** Generate a stable idempotency key for a queued op. */\nexport function newIdempotencyKey(): string {\n const c =\n typeof globalThis !== \"undefined\"\n ? (globalThis.crypto as { randomUUID?: () => string } | undefined)\n : undefined;\n if (c && typeof c.randomUUID === \"function\") return `amba_obx_${c.randomUUID()}`;\n // Fallback for runtimes without crypto.randomUUID — collision-resistant\n // enough for a per-device outbox (the server still dedupes on the key).\n return `amba_obx_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 12)}`;\n}\n\n/**\n * Durable, ordered, idempotent offline outbox. Opt-in via `Amba.offline`.\n * Single in-flight flush is enforced so concurrent reconnect signals don't\n * double-replay.\n */\nexport class OfflineQueue {\n private readonly store: KvStore;\n private readonly executor: WriteExecutor;\n private readonly storageKey: string;\n /**\n * Notified when a queued op is dropped because its replay failed\n * deterministically (a poisoned op the server will always reject). Lets the\n * SDK surface the loss (debug log) instead of silently discarding.\n */\n private readonly onDropped?: (op: OutboxOp, err: unknown) => void;\n /**\n * The identity that is signed in right now. When set, enqueue stamps it on\n * each op and flush/pending only touch ops it owns.\n */\n private readonly currentOwner?: () => OutboxOwner;\n private flushing: Promise<FlushResult> | null = null;\n /**\n * Bumped by `purge()`. A replay that started before a purge sees the change\n * and does not re-queue its op.\n */\n private purgeGeneration = 0;\n /**\n * Serializes every read-modify-write of the persisted outbox. Each mutation\n * chains onto the previous one's promise, so concurrent `enqueue`s — and an\n * `enqueue` racing the per-op removal inside `flush` — can never read a stale\n * snapshot and clobber each other's writes (the lost-update bug). All\n * storage mutations funnel through `mutate()`; only `pendingCount`/`pending`\n * read without taking the lock (a stale-by-one read is harmless).\n */\n private tail: Promise<unknown> = Promise.resolve();\n /** Detach function for the auto-flush `online` listener, if installed. */\n private detachOnline: (() => void) | null = null;\n /**\n * Set once `dispose()` is called (on teardown / reconfigure). A disposed\n * queue refuses to mutate the persisted outbox, so an orphaned in-flight\n * flush — or any late call against the old instance — can never race the new\n * queue by reading/writing the same storage after it's been replaced.\n */\n private disposed = false;\n\n constructor(opts: {\n store: KvStore;\n executor: WriteExecutor;\n /** Persistence key for the serialized queue. */\n storageKey?: string;\n /** Called when a deterministically-failing op is dropped from the queue. */\n onDropped?: (op: OutboxOp, err: unknown) => void;\n /**\n * Returns the identity signed in right now. Binds each queued op to the\n * user who queued it: flush replays an op only for that user.\n */\n currentOwner?: () => OutboxOwner;\n }) {\n this.store = opts.store;\n this.executor = opts.executor;\n this.storageKey = opts.storageKey ?? \"offline_outbox\";\n this.onDropped = opts.onDropped;\n this.currentOwner = opts.currentOwner;\n }\n\n /**\n * The ops the current owner may see and replay. Without an owner source\n * every op qualifies.\n */\n private visible(ops: OutboxOp[]): OutboxOp[] {\n if (!this.currentOwner) return ops;\n const current = this.currentOwner();\n return ops.filter((op) => outboxOwnerMatches(op, current));\n }\n\n /**\n * Run `fn` exclusively against the persisted outbox: it chains after every\n * prior mutation and the next one waits for it. `fn` receives the current\n * ops and returns the next ops to persist plus its own result.\n */\n private mutate<R>(\n fn: (ops: OutboxOp[]) => { next: OutboxOp[]; result: R } | Promise<{ next: OutboxOp[]; result: R }>,\n opts?: { rejectIfDisposed?: boolean },\n ): Promise<R> {\n const run = this.tail.then(async () => {\n // Re-check disposal AFTER taking the lock. `save()` is already a no-op\n // when disposed, but a caller that needs to KNOW its write didn't persist\n // (enqueue) must REJECT rather than silently resolve — otherwise a\n // dispose racing between `getOfflineQueue()` and the awaited `enqueue()`\n // would hand the caller a pending-success for a write that's now lost.\n if (this.disposed && opts?.rejectIfDisposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const ops = await this.load();\n const { next, result } = await fn(ops);\n await this.save(next);\n return result;\n });\n // Keep the chain alive even if this mutation rejects, so a single failure\n // doesn't permanently wedge every future mutation.\n this.tail = run.catch(() => undefined);\n return run;\n }\n\n private async load(): Promise<OutboxOp[]> {\n const raw = await this.store.get(this.storageKey);\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as unknown;\n return Array.isArray(parsed) ? (parsed as OutboxOp[]) : [];\n } catch {\n // Corrupt payload — start clean rather than wedge the queue forever.\n return [];\n }\n }\n\n private async save(ops: OutboxOp[]): Promise<void> {\n // A disposed queue must never write the shared storage — a new queue may\n // already own it. Drop the write silently so an orphaned in-flight flush\n // (or any late mutation) can't clobber the replacement queue's outbox.\n if (this.disposed) return;\n if (ops.length === 0) {\n await this.store.delete(this.storageKey);\n return;\n }\n await this.store.set(this.storageKey, JSON.stringify(ops));\n }\n\n /**\n * Permanently retire this queue. Detaches the auto-flush listener and marks\n * it disposed so it can no longer mutate the persisted outbox. Returns once\n * any in-flight flush + pending mutation chain has settled, so a caller (e.g.\n * `configure()` teardown) can safely create a replacement queue knowing the\n * old one will never touch storage again. Idempotent.\n */\n async dispose(): Promise<void> {\n this.disposed = true;\n if (this.detachOnline) {\n this.detachOnline();\n this.detachOnline = null;\n }\n // Await the in-flight flush + the mutation chain so all already-scheduled\n // storage work has run (its writes are now suppressed by the disposed\n // guard) before the caller installs a new queue.\n try {\n await this.flushing;\n } catch {\n // a failing flush still settles the lifecycle\n }\n await this.tail.catch(() => undefined);\n }\n\n /** Whether this queue has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /**\n * Capture the current owner and purge generation. Take it before a write's\n * live attempt and pass it to {@link enqueue}.\n */\n ticket(): OutboxTicket {\n return { owner: this.currentOwner?.(), generation: this.purgeGeneration };\n }\n\n /**\n * Append an op to the durable outbox. Returns the persisted op. With a\n * `ticket`, the op belongs to the ticket's owner, and the enqueue rejects\n * (code `OFFLINE_QUEUE_PURGED`) when the queue was purged after the ticket\n * was taken, which is what a sign-out during the live attempt does.\n */\n async enqueue(\n op: Omit<OutboxOp, \"idempotencyKey\" | \"enqueuedAt\"> & {\n idempotencyKey?: string;\n },\n ticket?: OutboxTicket,\n ): Promise<OutboxOp> {\n // Fast-fail if already disposed; otherwise the re-check inside `mutate`\n // (after the lock) catches a dispose that races this call. Either way a\n // disposed queue REJECTS the enqueue rather than silently swallowing the\n // write — so the SDK's diverting write surfaces a failure instead of\n // handing the caller a pending-success for a write that never persisted.\n if (this.disposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const owner = op.owner ?? (ticket ? ticket.owner : this.currentOwner?.());\n const full: OutboxOp = {\n ...op,\n ...(owner ? { owner: { ...owner } } : {}),\n idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),\n enqueuedAt: new Date().toISOString(),\n };\n return this.mutate(\n (ops) => {\n // Checked under the lock: a purge that ran while the live attempt was\n // in flight wins, and the signed-out user's write is not queued.\n if (ticket && ticket.generation !== this.purgeGeneration) {\n const err = new Error(\n \"amba offline queue was cleared by a sign-out while this write was in flight\",\n ) as Error & { code: string };\n err.code = \"OFFLINE_QUEUE_PURGED\";\n throw err;\n }\n return { next: [...ops, full], result: full };\n },\n { rejectIfDisposed: true },\n );\n }\n\n /** Number of the current owner's ops awaiting replay. */\n async pendingCount(): Promise<number> {\n return this.visible(await this.load()).length;\n }\n\n /**\n * Snapshot the current owner's pending ops (a copy — mutating it does not\n * affect the queue). Ops queued by another user stay out of the list.\n */\n async pending(): Promise<OutboxOp[]> {\n return [...this.visible(await this.load())];\n }\n\n /** Drop every queued op, whoever queued it, without replaying. */\n async clear(): Promise<void> {\n await this.mutate(() => ({ next: [], result: undefined }));\n }\n\n /**\n * Drop every queued op, including one whose replay is in flight right now:\n * that op is not re-queued when its replay fails. The SDKs call this on\n * sign-out so nothing the signed-out user queued survives on the device.\n */\n async purge(): Promise<void> {\n this.purgeGeneration += 1;\n await this.clear();\n }\n\n /**\n * Replay the current owner's ops in FIFO order. Each op is removed only\n * after it succeeds; the first failure stops the flush and retains the\n * remainder (so order is preserved and nothing is dropped mid-batch). Ops\n * another user queued stay held for that user. Ops that can never replay\n * are dropped through `onDropped`: one with no recorded owner (code\n * `OFFLINE_OWNER_UNKNOWN`), and one queued before any session under an\n * anonymous id the install no longer has (code `OFFLINE_OWNER_GONE`).\n * Concurrent calls share one in-flight flush.\n */\n async flush(): Promise<FlushResult> {\n if (this.disposed) return { flushed: 0, dropped: 0, remaining: 0 };\n if (this.flushing) return this.flushing;\n this.flushing = this.runFlush().finally(() => {\n this.flushing = null;\n });\n return this.flushing;\n }\n\n private async runFlush(): Promise<FlushResult> {\n let flushed = 0;\n let dropped = 0;\n // Drive the flush one op at a time through `mutate()`, so a concurrent\n // `enqueue` (e.g. a new offline write mid-flush) interleaves safely\n // instead of being lost to a stale read-modify-write. Each step replays\n // the head op (outside the lock — it can be slow) then atomically removes\n // exactly that op if it's still the head.\n //\n // The drain is NOT gated on `navigator.onLine`: that's meaningless on\n // React Native (no real `navigator.onLine`), where the documented pattern\n // is to call `flush()` from a NetInfo connectivity listener — so an\n // explicit flush must always ATTEMPT the drain. If the device is in fact\n // still offline, the executor throws a transport error on the first op and\n // the loop stops below, retaining everything for the next attempt. (The\n // web auto-flush path only fires on the `online` event anyway, so it's\n // already online when it runs.)\n for (;;) {\n // Stop immediately if disposed mid-drain (a reconfigure replaced us): the\n // save guard already suppresses writes, this just avoids executing more\n // ops against a retired queue.\n if (this.disposed) break;\n const all = await this.load();\n if (this.currentOwner) {\n // Ops that can never match an owner again are dropped (reported)\n // instead of held forever: one from before owners existed, and a\n // pre-session op whose anonymous id the install no longer has\n // (rotated on sign-out, or a fresh one per page in memory mode).\n const current = this.currentOwner();\n const orphan = all.find(\n (o) =>\n !o.owner ||\n (o.owner.appUserId === null &&\n current.anonymousId !== null &&\n o.owner.anonymousId !== current.anonymousId),\n );\n if (orphan) {\n const removedOrphan = await this.removeOp(orphan);\n if (!removedOrphan) break;\n dropped += 1;\n this.reportDropped(\n orphan,\n orphan.owner\n ? {\n code: \"OFFLINE_OWNER_GONE\",\n message: \"queued before sign-in under an anonymous id this install no longer has; not replayed\",\n }\n : {\n code: \"OFFLINE_OWNER_UNKNOWN\",\n message: \"queued by an earlier SDK version without an owner; not replayed\",\n },\n );\n continue;\n }\n }\n const head = this.visible(all)[0];\n if (!head) break;\n const generation = this.purgeGeneration;\n let replayError: unknown = undefined;\n let ok = false;\n try {\n await this.executor(head);\n ok = true;\n } catch (err) {\n replayError = err;\n }\n\n if (!ok && !isDeterministicError(replayError)) {\n // A NON-deterministic replay failure — a transport blip, a 5xx\n // `HTTP_ERROR`, `RATE_LIMITED` (429), `CIRCUIT_OPEN`, or any uncoded\n // error. These may well succeed later, so STOP here and keep this op\n // plus everything after it for the next attempt (favor retain over data\n // loss). Only a deterministic server rejection (handled below) is\n // dropped.\n //\n // The replay ran OUTSIDE the lock, so a concurrent `clear()` (or a\n // drain that already removed this op) could have emptied the store\n // while it was in flight. Re-assert the un-applied op's presence under\n // the lock — re-prepend it if it's gone — so an unapplied mutation is\n // never silently lost to a clear/flush interleaving. at-least-once wins\n // over honoring a clear that raced an in-flight, un-applied write; the\n // caller can `clear()` again once the flush settles. A `purge()` (sign-\n // out) is final: the op stays gone.\n await this.mutate((ops) => {\n const present = ops.some(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n const requeue = !present && generation === this.purgeGeneration;\n return {\n next: requeue ? [head, ...ops] : ops,\n result: undefined,\n };\n });\n break;\n }\n\n // Either the replay succeeded, OR it failed deterministically (a coded\n // server rejection that will fail identically forever). In both cases the\n // head op leaves the queue: a success drains it, a poisoned op is DROPPED\n // so it can't wedge everything behind it. Removal is by identity under\n // the lock — robust against a concurrent enqueue having shifted the\n // array.\n const removed = await this.removeOp(head);\n if (!removed) break; // shouldn't happen, but never spin\n if (ok) {\n flushed += 1;\n } else {\n dropped += 1;\n this.reportDropped(head, replayError);\n }\n }\n const remaining = this.visible(await this.load()).length;\n return { flushed, dropped, remaining };\n }\n\n /**\n * Remove exactly `op` (by idempotency key) under the lock. Robust against a\n * concurrent enqueue having shifted the array. Resolves false when the op\n * is already gone.\n */\n private removeOp(op: OutboxOp): Promise<boolean> {\n return this.mutate((ops) => {\n const idx = ops.findIndex((o) => o.idempotencyKey === op.idempotencyKey);\n if (idx === -1) return { next: ops, result: false };\n const next = ops.slice(0, idx).concat(ops.slice(idx + 1));\n return { next, result: true };\n });\n }\n\n private reportDropped(op: OutboxOp, err: unknown): void {\n try {\n this.onDropped?.(op, err);\n } catch {\n // A throwing drop-observer must not break the flush.\n }\n }\n\n /**\n * Install an auto-flush listener on the runtime's `online` event so the\n * queue drains the moment connectivity returns. Idempotent — a second\n * call is a no-op. Returns a detach function.\n */\n startAutoFlush(): () => void {\n if (this.detachOnline) return this.detachOnline;\n const target =\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { addEventListener?: unknown }).addEventListener ===\n \"function\"\n ? (globalThis as unknown as {\n addEventListener(t: string, cb: () => void): void;\n removeEventListener(t: string, cb: () => void): void;\n })\n : null;\n if (!target) {\n // No event target (Node / SSR) — nothing to listen on. Still return a\n // detach so callers can treat the API uniformly.\n this.detachOnline = () => {};\n return this.detachOnline;\n }\n const handler = () => {\n void this.flush();\n };\n target.addEventListener(\"online\", handler);\n this.detachOnline = () => {\n target.removeEventListener(\"online\", handler);\n this.detachOnline = null;\n };\n return this.detachOnline;\n }\n}\n\n// ── Optimistic update helper ─────────────────────────────────────────────\n\n/**\n * Apply a local change immediately, fire the real mutation, and roll the\n * local change back if the mutation fails. Framework-agnostic: `apply` and\n * `rollback` operate on whatever local view you hand them (a React state\n * setter, a store, an in-memory cache).\n *\n * Resolves with the mutation's result on success. On failure it invokes\n * `rollback` (with the value `apply` returned, so you can restore a captured\n * snapshot) and rethrows the original error.\n *\n * @example\n * await withOptimistic({\n * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },\n * mutate: () => Amba.collections.insert(\"todos\", draft),\n * rollback: (prev) => setTodos(prev),\n * });\n */\nexport async function withOptimistic<TApply, TResult>(spec: {\n /** Mutate the local view now. Return a snapshot for `rollback` to restore. */\n apply: () => TApply;\n /** The real async mutation against the API. */\n mutate: () => Promise<TResult>;\n /** Undo the local change. Receives whatever `apply` returned. */\n rollback: (applied: TApply) => void;\n}): Promise<TResult> {\n const applied = spec.apply();\n try {\n return await spec.mutate();\n } catch (err) {\n try {\n spec.rollback(applied);\n } catch {\n // A throwing rollback must not mask the original mutation error.\n }\n throw err;\n }\n}\n","/**\n * Browser push-token rotation.\n *\n * The browser can silently rotate a Web Push subscription (key rollover,\n * permission re-grant). When it does, the service worker receives a\n * `pushsubscriptionchange` event and the OLD subscription stops delivering.\n * Auto-rotation listens for that event from the page and re-registers the\n * fresh subscription's endpoint with the backend so notifications keep\n * flowing without the app having to notice.\n *\n * The actual re-register is delegated to a caller-supplied callback (the SDK\n * wires it to its idempotent `push.register` path), so this module stays free\n * of any SDK-runtime coupling and is unit-testable with a fake event target.\n */\n\n/** Re-registers a rotated push token with the backend. */\nexport type ReRegister = (token: string, platform: \"web\") => Promise<unknown>;\n\n/**\n * Subscribe to browser push-subscription rotation. On a\n * `pushsubscriptionchange`, the new subscription endpoint is forwarded to\n * `reRegister` (which the SDK points at its idempotent token-upsert).\n *\n * Returns an unsubscribe function. No-ops (returning a no-op detacher) when\n * the runtime has no service-worker / push support — e.g. SSR or a browser\n * without Web Push — so callers can invoke it unconditionally.\n */\n/**\n * Are we executing inside a ServiceWorkerGlobalScope? `pushsubscriptionchange`\n * ONLY fires there — on a normal page `self === window`, where the event never\n * fires, so attaching a listener silently does nothing. We detect the scope by\n * the `ServiceWorkerGlobalScope` constructor when present, falling back to the\n * SW-only `self.registration.pushManager` shape (a window has no `registration`\n * on its global).\n */\nfunction inServiceWorkerScope(g: unknown): boolean {\n if (typeof g !== \"object\" || g === null) return false;\n const swCtor = (globalThis as { ServiceWorkerGlobalScope?: unknown })\n .ServiceWorkerGlobalScope;\n if (typeof swCtor === \"function\" && g instanceof (swCtor as new () => object)) {\n return true;\n }\n // Fallback: the SW global exposes `registration.pushManager`; a window does\n // not expose `registration` on its global object.\n const reg = (g as { registration?: { pushManager?: unknown } }).registration;\n return !!reg && typeof reg.pushManager === \"object\" && reg.pushManager !== null;\n}\n\nlet pageScopeWarned = false;\n\nexport function enableWebPushAutoRotation(reRegister: ReRegister): () => void {\n // `pushsubscriptionchange` fires inside the SERVICE WORKER, not the page.\n // Only attach when we're actually in a ServiceWorkerGlobalScope — on a normal\n // page `self` IS `window`, where the event never fires, so a listener there\n // would silently never observe rotations. From a page, the documented pattern\n // is to call this inside your service worker (or forward the event from the\n // worker via `postMessage`).\n const g = typeof self !== \"undefined\" ? (self as unknown) : undefined;\n if (!g || !inServiceWorkerScope(g)) {\n if (!pageScopeWarned && typeof console !== \"undefined\") {\n pageScopeWarned = true;\n console.warn(\n \"[amba] push.enableAutoRotation() must run inside your service worker — `pushsubscriptionchange` doesn't fire on a page/window. Call it from your amba service worker. No-op here.\",\n );\n }\n return () => {};\n }\n const sw = g as {\n addEventListener?: (t: string, cb: (e: unknown) => void) => void;\n removeEventListener?: (t: string, cb: (e: unknown) => void) => void;\n registration?: {\n pushManager?: {\n getSubscription?: () => Promise<{ endpoint?: string } | null>;\n };\n };\n };\n\n if (typeof sw.addEventListener !== \"function\") {\n return () => {};\n }\n\n const onChange = (event: unknown): void => {\n void (async () => {\n try {\n const ev = event as {\n newSubscription?: { endpoint?: string } | null;\n waitUntil?: (p: Promise<unknown>) => void;\n data?: unknown;\n };\n // Prefer the event's `newSubscription`; fall back to re-reading the\n // current subscription off the registration (some browsers leave\n // `newSubscription` null and expect a re-subscribe).\n let endpoint = ev?.newSubscription?.endpoint;\n if (!endpoint && sw.registration?.pushManager?.getSubscription) {\n const current = await sw.registration.pushManager.getSubscription();\n endpoint = current?.endpoint ?? undefined;\n }\n if (!endpoint) return;\n const work = reRegister(endpoint, \"web\");\n // Keep the worker alive until the re-register settles, when the\n // event supports it.\n if (typeof ev?.waitUntil === \"function\") ev.waitUntil(work);\n await work;\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] push auto-rotation re-register failed\", err);\n }\n }\n })();\n };\n\n sw.addEventListener(\"pushsubscriptionchange\", onChange);\n return () => {\n sw.removeEventListener?.(\"pushsubscriptionchange\", onChange);\n };\n}\n","/**\n * On-device local notifications for the browser.\n *\n * A *local* notification fires from the device without a server\n * round-trip — distinct from remote push (`Amba.push.*`). On the web the\n * scheduling story is uneven, and this module is honest about it rather\n * than silently pretending to schedule:\n *\n * - **Scheduled delivery** (fire at a future `at` / after `inSeconds`)\n * requires the experimental **Notification Triggers API**\n * (`TimestampTrigger` + a Service Worker registration's\n * `showNotification({ showTrigger })`). It currently ships only in\n * Chromium-family browsers and only over HTTPS with an active Service\n * Worker. When it's present we register the trigger with the OS, so\n * the notification fires even if the tab/browser is closed.\n *\n * - **No Triggers API** → we cannot hand a future-dated notification to\n * the OS. Rather than no-op, we fall back to an **in-page timer**\n * (`setTimeout`) that fires the notification while the page stays\n * open, and the returned handle's `scheduledVia` is `\"timer\"` so\n * callers can detect the degraded mode. If the page is closed before\n * the timer elapses, the notification does not fire — this is a\n * hard browser limitation, surfaced honestly, not hidden.\n *\n * - **Notification API absent** (e.g. SSR, or a browser with\n * notifications disabled) → every method throws a typed\n * `AmbaApiError` (`LOCAL_NOTIFICATIONS_UNAVAILABLE`), never a silent\n * success.\n *\n * Permission is requested on first `scheduleLocal`; a denied prompt\n * throws `LOCAL_NOTIFICATIONS_PERMISSION_DENIED`.\n */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type {\n LocalNotificationHandle,\n ScheduleLocalNotificationInput,\n ScheduledLocalNotification,\n} from \"./types.js\";\n\ninterface TimestampTriggerCtor {\n new (timestamp: number): unknown;\n}\n\n/**\n * Browsers store a `setTimeout` delay in a signed 32-bit int. A delay\n * larger than `2^31 - 1` ms (~24.8 days) overflows and is clamped — most\n * browsers fire (often immediately) rather than wait. So a far-future\n * local notification scheduled via the in-page timer fallback would fire\n * at the wrong time. We chunk long delays: arm a timer for at most\n * `MAX_TIMEOUT_MS`, and on wake re-arm for the remainder until due.\n */\nconst MAX_TIMEOUT_MS = 2_147_483_647; // 2^31 - 1\n\n/**\n * In-page timer registry for the degraded (no-Triggers-API) path.\n *\n * `handle` always points at the CURRENTLY-armed chunk's timeout id (it is\n * reassigned on each re-arm), so `clearTimeout(handle)` cancels the timer\n * even mid-chunk — `cancelLocal` and reschedule stay correct for\n * arbitrarily long delays. `fireAt` is the absolute target time used to\n * compute each chunk's remaining delay.\n */\ninterface PendingTimer {\n handle: ReturnType<typeof setTimeout>;\n fireAt: number;\n record: ScheduledLocalNotification;\n}\nconst pendingTimers = new Map<string, PendingTimer>();\n\nfunction hasNotificationApi(): boolean {\n return (\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { Notification?: unknown }).Notification ===\n \"function\"\n );\n}\n\nfunction timestampTriggerCtor(): TimestampTriggerCtor | undefined {\n const ctor = (globalThis as { TimestampTrigger?: unknown }).TimestampTrigger;\n return typeof ctor === \"function\"\n ? (ctor as TimestampTriggerCtor)\n : undefined;\n}\n\nfunction genId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `amba_local_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Per-INSTANCE token. The stable `__ambaLocalId` survives a reschedule of the\n * same id; this token does not — every individual `scheduleLocal` call mints a\n * fresh one. It lets `listScheduledLocal` distinguish a NEW pending schedule\n * for an id from an OLD already-fired-but-still-visible notification of the\n * SAME id (which the Service Worker reports under `getNotifications()`),\n * instead of conflating the two by id and hiding the pending one. (Round-5.)\n */\nfunction genInstanceToken(): string {\n return `${Date.now()}_${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Resolve the id a schedule will be filed under. A caller may omit `id`\n * (we mint one) but must not pass a BLANK/whitespace id: `cancelLocal`\n * rejects blank ids, so returning one in the handle would make the\n * notification uncancellable. We reject blank at schedule time so every\n * returned handle id round-trips through `cancelLocal`. Shared by both\n * SDK platforms (web + react-native) for identical validation.\n */\nfunction normalizeScheduleId(rawId: string | undefined): string {\n if (rawId === undefined) return genId();\n if (typeof rawId !== \"string\" || rawId.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: id, when provided, must be a non-empty string\",\n );\n }\n return rawId;\n}\n\n/**\n * A validated trigger, ready to be turned into an absolute fire time.\n *\n * Split from the fire-time computation on purpose: validation runs BEFORE we\n * request permission (so bad input fails fast), but the absolute fire time\n * for a RELATIVE (`inSeconds`) trigger must be computed AFTER permission\n * resolves — otherwise the (potentially long) time the user spends on the\n * permission prompt is silently subtracted from the delay and the\n * notification fires sooner than `inSeconds` after scheduling completes.\n * Absolute (`at`) triggers are a fixed wall-clock time and are unaffected.\n */\ntype ParsedTrigger =\n | { kind: \"absolute\"; at: number }\n | { kind: \"relative\"; seconds: number };\n\nfunction parseTrigger(input: ScheduleLocalNotificationInput): ParsedTrigger {\n const trigger = input.trigger;\n // Guard that `trigger` is a non-null object before `in`-probing it — a\n // missing or non-object trigger must surface the documented\n // INVALID_ARGUMENT AmbaApiError, not a raw `TypeError` from `\"at\" in null`.\n if (!trigger || typeof trigger !== \"object\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number }\",\n );\n }\n if (\"at\" in trigger) {\n const at = trigger.at instanceof Date ? trigger.at : new Date(trigger.at);\n const ms = at.getTime();\n if (Number.isNaN(ms)) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.at must be a valid Date\",\n );\n }\n return { kind: \"absolute\", at: ms };\n }\n if (\"inSeconds\" in trigger) {\n if (!Number.isFinite(trigger.inSeconds) || trigger.inSeconds < 0) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.inSeconds must be a non-negative number\",\n );\n }\n return { kind: \"relative\", seconds: trigger.inSeconds };\n }\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number }\",\n );\n}\n\n/**\n * Resolve a parsed trigger to an absolute fire time (ms epoch). For a\n * relative trigger this is `now + seconds` — call it ONLY after permission\n * has resolved so `inSeconds` counts from when scheduling actually completes.\n */\nfunction computeFireAt(parsed: ParsedTrigger): number {\n return parsed.kind === \"absolute\"\n ? parsed.at\n : Date.now() + parsed.seconds * 1000;\n}\n\nfunction validateContent(input: ScheduleLocalNotificationInput): void {\n if (!input || typeof input.title !== \"string\" || input.title.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: title is required\",\n );\n }\n if (typeof input.body !== \"string\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: body must be a string\",\n );\n }\n}\n\nasync function ensurePermission(): Promise<void> {\n const N = (globalThis as { Notification: typeof Notification }).Notification;\n let permission = N.permission;\n if (permission === \"default\") {\n permission = await N.requestPermission();\n }\n if (permission !== \"granted\") {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\",\n \"user denied notification permission — local notifications cannot be shown\",\n );\n }\n}\n\n/**\n * How long we'll wait for an existing-but-not-yet-active registration to\n * become `ready` before falling through to the in-page timer path. A real\n * activation is near-instant; this cap only guards against a wedged\n * activation hanging the call.\n */\nconst SW_READY_TIMEOUT_MS = 1_000;\n\n/**\n * Resolve the Service Worker registration to use for OS-trigger\n * notifications — WITHOUT ever hanging.\n *\n * The footgun this avoids: `navigator.serviceWorker.ready` NEVER resolves\n * when no service worker is registered (the common case for a page that has\n * the Notifications API but no SW). Awaiting it unconditionally would hang\n * `scheduleLocal` / `cancelLocal` / `listScheduledLocal` forever instead of\n * falling back to the documented in-page timer path. (Round-6 HIGH.)\n *\n * Instead we first call `getRegistration()`, which resolves to `undefined`\n * when nothing is registered (no hang). Only if a registration exists do we\n * wait — briefly, raced against a timeout — for `.ready`, so an active SW is\n * used while a wedged activation can't stall the caller.\n */\nasync function getServiceWorkerRegistration(): Promise<\n ServiceWorkerRegistration | undefined\n> {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n if (!nav || !(\"serviceWorker\" in nav)) return undefined;\n const swc = nav.serviceWorker;\n\n let existing: ServiceWorkerRegistration | undefined;\n try {\n // Resolves to `undefined` when no SW is registered — does NOT hang.\n existing = await swc.getRegistration();\n } catch {\n return undefined;\n }\n if (!existing) return undefined;\n\n // A registration exists. Prefer the `ready` (active) registration, but\n // never block on it indefinitely: race `.ready` against a short timeout\n // and fall back to the registration we already have.\n try {\n const ready = await Promise.race([\n swc.ready,\n new Promise<undefined>((resolve) =>\n setTimeout(() => resolve(undefined), SW_READY_TIMEOUT_MS),\n ),\n ]);\n return ready ?? existing;\n } catch {\n return existing;\n }\n}\n\n/**\n * Cancel (close) any Service Worker OS-trigger notification scheduled by us\n * under `tag === id`. Passes `includeTriggered: true` so that\n * scheduled-but-not-yet-fired trigger notifications are returned by\n * `getNotifications` — without it, pending OS-trigger notifications are\n * invisible and therefore impossible to cancel. No-op when there's no\n * registration (degraded / timer-only environments).\n */\nasync function cancelOsTriggerNotification(\n registration: ServiceWorkerRegistration | undefined,\n id: string,\n): Promise<void> {\n if (!registration) return;\n try {\n const matches = await registration.getNotifications({\n tag: id,\n // `includeTriggered` is part of the experimental Notification Triggers\n // API; not declared in lib.dom.d.ts, hence the cast.\n includeTriggered: true,\n } as GetNotificationOptions & { includeTriggered?: boolean });\n for (const n of matches) {\n // Only close notifications WE scheduled (carry the Amba marker for this\n // id). A same-`tag` notification created by other code or remote push\n // must never be closed by us. (Marker-filtering class, round-4.)\n const data = (n.data as Record<string, unknown> | undefined) ?? {};\n if (data.__ambaLocalId !== id) continue;\n n.close();\n }\n } catch {\n // getNotifications can reject in some browsers; the caller's timer-path\n // cleanup already covers the degraded case.\n }\n}\n\n/**\n * Show the in-page `Notification` for a fired timer. Tolerant of permission\n * being revoked between scheduling and firing (construction can throw).\n */\nfunction fireInPageNotification(\n record: ScheduledLocalNotification,\n id: string,\n): void {\n try {\n const N = (globalThis as { Notification: typeof Notification }).Notification;\n // eslint-disable-next-line no-new\n new N(record.title, {\n body: record.body,\n data: { ...record.data, __ambaLocalId: id },\n tag: id,\n });\n } catch {\n // Notification construction can throw if permission was revoked between\n // scheduling and firing; nothing actionable here.\n }\n}\n\n/**\n * Arm the next chunk of an in-page timer for the registry entry under `id`.\n * If the remaining delay until `entry.fireAt` exceeds `MAX_TIMEOUT_MS`, arm\n * for `MAX_TIMEOUT_MS` and re-arm on wake; otherwise arm for the exact\n * remainder, then fire and remove the entry.\n *\n * `entry.handle` is reassigned to each chunk's timeout id, so a concurrent\n * `clearTimeout(entry.handle)` (cancel / reschedule) always targets the live\n * chunk and stops the chain.\n */\nfunction armTimerChunk(id: string, entry: PendingTimer): void {\n const remaining = entry.fireAt - Date.now();\n if (remaining > MAX_TIMEOUT_MS) {\n entry.handle = setTimeout(() => {\n // Re-arm only if this entry is still the live one for `id` (it may have\n // been cancelled or replaced while the chunk was pending).\n const current = pendingTimers.get(id);\n if (current === entry) armTimerChunk(id, entry);\n }, MAX_TIMEOUT_MS);\n return;\n }\n entry.handle = setTimeout(\n () => {\n // Only fire if we're still the live entry for this id.\n if (pendingTimers.get(id) !== entry) return;\n pendingTimers.delete(id);\n fireInPageNotification(entry.record, id);\n },\n Math.max(0, remaining),\n );\n}\n\n/**\n * The web `notifications` namespace. Web capability is partial — see the\n * module-level doc. The returned handle includes `scheduledVia` so apps\n * can detect whether the notification was handed to the OS\n * (`\"os-trigger\"`) or relies on an in-page timer (`\"timer\"`).\n */\nexport const notifications = {\n async scheduleLocal(\n input: ScheduleLocalNotificationInput,\n ): Promise<LocalNotificationHandle & { scheduledVia: \"os-trigger\" | \"timer\" }> {\n validateContent(input);\n if (!hasNotificationApi()) {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_UNAVAILABLE\",\n \"the Notifications API is not available in this environment (server-side rendering, or a browser with notifications disabled). Local notifications require a browser context.\",\n );\n }\n // Validate the trigger + id BEFORE requesting permission / committing\n // anything (fail fast on bad input; a blank caller id never yields an\n // uncancellable handle). But DEFER the absolute fire-time computation\n // until after permission resolves, so a relative `inSeconds` delay counts\n // from when scheduling completes — not from before the (possibly slow)\n // permission prompt. (Round-5.)\n const parsedTrigger = parseTrigger(input);\n const id = normalizeScheduleId(input.id);\n await ensurePermission();\n const fireAt = computeFireAt(parsedTrigger);\n // Fresh per-instance token for THIS schedule (round-5: lets the list\n // distinguish a new pending schedule from a stale visible one of same id).\n const instanceToken = genInstanceToken();\n\n const TriggerCtor = timestampTriggerCtor();\n const registration = await getServiceWorkerRegistration();\n\n // Re-scheduling with a caller-supplied id must REPLACE, not duplicate —\n // and the prior schedule and the new one may live on DIFFERENT paths.\n // But replacement must be ATOMIC: if installing the new schedule fails,\n // the PRIOR schedule must remain intact (don't leave the user with\n // neither the old nor the new). So we tear the prior schedule down only\n // AFTER the replacement is committed.\n //\n // Path matrix (each tears down BOTH prior mechanisms after commit):\n // - timer → timer: clear the old `setTimeout` (else double-fire).\n // - os-trigger → os-trigger: `showNotification` replaces by `tag`, but we\n // also explicitly cancel for uniformity.\n // - os-trigger → timer: the prior OS notification is NOT replaced by a\n // `setTimeout`, so we cancel it explicitly (round-2 gap).\n // - timer → os-trigger: symmetric; clear the stale timer.\n\n // Best path: hand a future-dated trigger to the OS via the Service\n // Worker. Survives the page/browser closing.\n if (TriggerCtor && registration) {\n // Commit the new OS-trigger notification FIRST. If this rejects, we\n // return the error WITHOUT having touched the prior schedule, so the\n // previous reminder still fires (round-3: atomic reschedule). The new\n // notification uses the same `tag`, so it replaces any prior OS-trigger\n // notification for this id automatically.\n await registration.showNotification(input.title, {\n body: input.body,\n data: { ...input.data, __ambaLocalId: id, __ambaInstance: instanceToken },\n tag: id,\n // `showTrigger` is part of the experimental Notification Triggers\n // API; typed as unknown since lib.dom.d.ts doesn't declare it.\n showTrigger: new TriggerCtor(fireAt),\n } as NotificationOptions & { showTrigger: unknown });\n\n // Replacement committed — now tear down a prior in-page timer for this\n // id (the same-tag OS notification was already replaced above).\n const priorTimer = pendingTimers.get(id);\n if (priorTimer) {\n clearTimeout(priorTimer.handle);\n pendingTimers.delete(id);\n }\n return { id, scheduledVia: \"os-trigger\" };\n }\n\n // Degraded path: in-page timer. Only fires while the page stays open.\n // We surface this via `scheduledVia: \"timer\"` rather than no-op'ing.\n //\n // Arming a `setTimeout` is synchronous and can't reject, so we can tear\n // down the prior schedule first here without an atomicity risk. We must\n // cancel BOTH a prior in-page timer AND any prior OS-trigger notification\n // (the latter is async; `cancelOsTriggerNotification` swallows failures).\n const priorTimer = pendingTimers.get(id);\n if (priorTimer) {\n clearTimeout(priorTimer.handle);\n pendingTimers.delete(id);\n }\n await cancelOsTriggerNotification(registration, id);\n\n const entry: PendingTimer = {\n // Placeholder; `armTimerChunk` assigns the real handle synchronously.\n handle: undefined as unknown as ReturnType<typeof setTimeout>,\n fireAt,\n record: {\n id,\n title: input.title,\n body: input.body,\n data: input.data,\n },\n };\n // Register the entry BEFORE arming so the chunk callback's\n // `pendingTimers.get(id) === entry` liveness check passes.\n pendingTimers.set(id, entry);\n // Chunk long delays so a far-future schedule (> ~24.8 days) fires at the\n // right time instead of overflowing the 32-bit `setTimeout` delay.\n armTimerChunk(id, entry);\n return { id, scheduledVia: \"timer\" };\n },\n\n async cancelLocal(id: string): Promise<void> {\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.cancelLocal: id is required\",\n );\n }\n // Cancel an in-page timer if we own one.\n const timer = pendingTimers.get(id);\n if (timer) {\n clearTimeout(timer.handle);\n pendingTimers.delete(id);\n }\n // Cancel any OS-trigger notification registered under this tag —\n // including scheduled-but-unfired ones (`includeTriggered: true`).\n const registration = await getServiceWorkerRegistration();\n await cancelOsTriggerNotification(registration, id);\n },\n\n async listScheduledLocal(): Promise<ScheduledLocalNotification[]> {\n const out: ScheduledLocalNotification[] = [];\n // In-page timers we're tracking this session — these are by definition\n // pending (a fired timer deletes its own entry).\n for (const { record } of pendingTimers.values()) out.push({ ...record });\n\n const registration = await getServiceWorkerRegistration();\n if (registration) {\n try {\n // `getNotifications()` (no flag) returns only ALREADY-SHOWN (fired or\n // active) notifications. With `includeTriggered: true` it ALSO returns\n // scheduled-but-unfired trigger notifications. So the set we want —\n // PENDING (unfired) — is the includeTriggered set MINUS the shown set.\n // Listing the includeTriggered set verbatim would wrongly report\n // already-fired reminders as still scheduled. (Round-4 gap.)\n const [withTriggered, shownOnly] = await Promise.all([\n registration.getNotifications({\n includeTriggered: true,\n } as GetNotificationOptions & { includeTriggered?: boolean }),\n registration.getNotifications(),\n ]);\n // Build the \"already shown/fired\" set keyed by per-INSTANCE token, not\n // by stable id. Keying by id would wrongly hide a NEW pending schedule\n // of an id whenever an OLD fired notification with the same id is still\n // visible (not yet dismissed). The instance token is unique per\n // `scheduleLocal` call, so the old visible one and the new pending one\n // never collide. (Round-5.)\n const shownInstances = new Set<string>();\n for (const n of shownOnly) {\n const data = (n.data as Record<string, unknown> | undefined) ?? {};\n const token = data.__ambaInstance as string | undefined;\n if (token) shownInstances.add(token);\n }\n for (const n of withTriggered) {\n const data = (n.data as Record<string, unknown> | undefined) ?? {};\n // Only surface notifications WE scheduled. A Service Worker may\n // hold notifications created by other code (or remote push); those\n // carry no `__ambaLocalId` marker and must not leak into this list.\n const id = data.__ambaLocalId as string | undefined;\n if (!id) continue;\n // Skip already-fired/shown instances — only PENDING ones belong in\n // \"scheduled\". Matched by instance token so a stale visible\n // notification of the same id doesn't hide a fresh pending one.\n const token = data.__ambaInstance as string | undefined;\n if (token && shownInstances.has(token)) continue;\n if (out.some((existing) => existing.id === id)) continue;\n out.push({\n id,\n title: n.title,\n body: (n as { body?: string }).body ?? \"\",\n data,\n });\n }\n } catch {\n // Non-fatal — return what we have from the timer registry.\n }\n }\n return out;\n },\n};\n\nexport type LocalNotificationsNamespace = typeof notifications;\n\n/** @internal test hook — clears the in-page timer registry. */\nexport function __resetLocalNotificationTimers(): void {\n for (const { handle } of pendingTimers.values()) clearTimeout(handle);\n pendingTimers.clear();\n}\n","/**\n * @layers/amba-web — browser SDK for the amba platform.\n *\n * Exposes the full SDK 4.0 amba surface (34 modules) as idiomatic\n * TypeScript. All async operations return native Promises.\n *\n * @example\n * ```ts\n * import { Amba } from '@layers/amba-web';\n *\n * await Amba.configure({ projectId: 'proj_...', clientKey: 'amba_ck_...' });\n *\n * const session = await Amba.auth.signInAnonymously();\n * await Amba.events.track('app_opened', { source: 'deep_link' });\n *\n * const todos = await Amba.collections.find('todos', { limit: 20 });\n * await Amba.collections.insert('todos', { title: 'Buy milk' });\n * ```\n */\n\nimport type { AmbaCoreWasm } from \"@layers/amba-core\";\n\nexport type * from \"./types\";\nimport type {\n AmbaConfig,\n AuthResult,\n User,\n FindOptions,\n FindResponse,\n Filter,\n FilterValue,\n UpsertOptions,\n DeleteManyResult,\n TransactionOp,\n TransactionOpResult,\n ConfigBundle,\n FlagAssignment,\n PingResult,\n UserEntitlement,\n RestoreResult,\n OfferingsResult,\n MediaAsset,\n MediaCatalog,\n MediaListResponse,\n PresignData,\n PushPlatform,\n PushToken,\n AiMessageRequest,\n AiMessageResponse,\n Achievement,\n AchievementProgress,\n Challenge,\n ChallengeProgress,\n CurrencyBalance,\n CurrencyTransaction,\n InventoryItem,\n PurchaseRequest,\n ConsumeRequest,\n Leaderboard,\n LeaderboardEntry,\n Store,\n PurchaseOption,\n PurchaseResult,\n XpBalance,\n XpTransaction,\n Streak,\n FeedResponse,\n Friendship,\n Group,\n GroupMember,\n GroupCreate,\n GroupUpdate,\n Conversation,\n Message,\n SendMessageRequest,\n CreateConversationRequest,\n ListMessagesOptions,\n MarkReadResult,\n ConversationParticipant,\n Report,\n ReportRequest,\n ReferralCode,\n ReferralClaim,\n Review,\n ReviewCreate,\n ReviewUpdate,\n Role,\n CatalogItem,\n ContentItem,\n DeepLink,\n DeepLinkCreate,\n OnboardingStatus,\n AppSession,\n SyncChange,\n PushChangesResult,\n PullChangesResult,\n PullChangesCursor,\n LeagueMembership,\n LeagueCohortView,\n NearestOptions,\n NearestResponse,\n LinkedIdentifier,\n EventExplainResult,\n} from \"./types\";\n\nimport { LocalStoragePersistence } from \"./storage-adapter\";\nimport { encodeJson } from \"./json\";\nimport { expandAiImages } from \"./ai-images\";\nimport { AmbaApiError, toAmbaApiError } from \"./error\";\nimport type { Session, SessionPersistence } from \"./types\";\nimport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime\";\nimport { invokeAiStream } from \"./ai-stream\";\nimport { pathSegment } from \"./path-segment\";\nimport type { AiStreamEvent, AiStreamOptions } from \"./ai-stream\";\nimport {\n OfflineQueue,\n isOfflineError,\n newIdempotencyKey,\n withOptimistic,\n} from \"./resilience\";\nimport type { KvStore, OutboxOp, FlushResult } from \"./resilience\";\nimport { enableWebPushAutoRotation } from \"./push-rotation\";\nimport { notifications } from \"./local-notifications\";\nimport { getCoreRuntime } from \"./wasm-loader\";\n\n// Replaced at build time for the two public ESM entry points. Keeping this as\n// a compile-time constant lets the dedicated browser bundle discard the\n// entire Node-only WASM loader (including its `node:` imports), while the\n// default/Node bundle retains it. The `typeof` fallback keeps direct source\n// execution under Vitest safe.\ndeclare const __AMBA_BROWSER_BUILD__: boolean;\nconst IS_BROWSER_BUILD =\n typeof __AMBA_BROWSER_BUILD__ !== \"undefined\" && __AMBA_BROWSER_BUILD__;\n\nexport { withOptimistic } from \"./resilience\";\nexport type {\n OutboxOp,\n OutboxOpKind,\n FlushResult,\n KvStore,\n} from \"./resilience\";\n\nexport { AmbaApiError } from \"./error\";\nexport type { AmbaApiErrorCode } from \"./error\";\nexport type { LocalNotificationsNamespace } from \"./local-notifications\";\nexport type {\n AiStreamEvent,\n AiStreamDelta,\n AiStreamDone,\n AiStreamOptions,\n} from \"./ai-stream\";\nexport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime\";\nexport type {\n RealtimeChange,\n MessageChange,\n RowChangeOp,\n SubscribeOptions,\n Unsubscribe,\n GamificationChange,\n GamificationKind,\n} from \"./realtime\";\n\nconst DEFAULT_API_URL = \"https://api.amba.dev\";\n\n/**\n * Default host that serves deployed functions. `Amba.functions.invoke(name)`\n * targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a\n * `functionsBaseUrl` override is configured.\n */\nconst DEFAULT_FUNCTIONS_HOST = \"fn.amba.host\";\nconst PROJECT_ID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Internal state — one instance per page. Created lazily by `Amba.configure()`.\n */\nlet coreInstance: AmbaCoreWasm | null = null;\n\n/**\n * Stored at configure-time so `Amba.projectId` returns the configured\n * value without going through the engine. Cached only after the\n * core finishes initializing.\n */\nlet _configuredProjectId: string | null = null;\n\n/**\n * Realtime (`Amba.collection(name).subscribe`) needs the API base URL,\n * client key, and current session token in the TS layer to open the\n * streamed connection — the engine doesn't expose them back. We capture\n * the base URL + client key at configure-time and the session token from\n * every auth result (the engine rotates it internally on refresh; the\n * subscribe connection picks up the latest at connect time).\n */\nlet _configuredApiUrl: string = DEFAULT_API_URL;\nlet _configuredClientKey: string | null = null;\nlet _currentSessionToken: string | null = null;\nlet _currentRefreshToken: string | null = null;\nlet _currentSessionExpiresAt: string | null = null;\nlet _currentUser: User | null = null;\n\nconst BROWSER_IDENTITY_VERSION = 1;\nconst DEFAULT_SESSION_TTL_MS = 60 * 60 * 1000;\n\n/**\n * `AmbaConfig.sessionPersistence` for the current configuration. In\n * `\"memory\"` mode no identity snapshot (tokens, user id, anonymous id) is read\n * from or written to `localStorage`.\n */\nlet _sessionPersistence: SessionPersistence = \"local\";\nconst SESSION_PERSISTENCE_MODES: readonly SessionPersistence[] = [\"local\", \"memory\"];\n\ninterface PersistedBrowserIdentity {\n version: typeof BROWSER_IDENTITY_VERSION;\n project_id: string;\n anonymous_id: string;\n app_user_id: string | null;\n session_token: string | null;\n refresh_token: string | null;\n expires_at: string | null;\n user: User | null;\n}\n\nfunction identityStorageKey(projectId: string): string {\n return `identity:v${BROWSER_IDENTITY_VERSION}:project:${encodeURIComponent(projectId)}`;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction containsControlCharacter(value: string): boolean {\n return Array.from(value).some((char) => {\n const code = char.charCodeAt(0);\n return code <= 0x1f || code === 0x7f;\n });\n}\n\n/** Read + validate a project-bound identity snapshot. Corrupt or cross-project\n * data is removed instead of being handed to the core. */\nfunction readPersistedIdentity(\n persistence: LocalStoragePersistence,\n projectId: string,\n): PersistedBrowserIdentity | null {\n const key = identityStorageKey(projectId);\n const raw = persistence.read(key);\n if (!raw) return null;\n try {\n const value = JSON.parse(raw) as Partial<PersistedBrowserIdentity>;\n const hasTokens = value.session_token != null || value.refresh_token != null;\n if (\n value.version !== BROWSER_IDENTITY_VERSION ||\n value.project_id !== projectId ||\n !isNonEmptyString(value.anonymous_id) ||\n (hasTokens && !isNonEmptyString(value.app_user_id)) ||\n (value.session_token != null && !isNonEmptyString(value.refresh_token)) ||\n (value.session_token != null && !isNonEmptyString(value.session_token)) ||\n (value.refresh_token != null && !isNonEmptyString(value.refresh_token)) ||\n (value.expires_at != null &&\n (!isNonEmptyString(value.expires_at) ||\n !Number.isFinite(Date.parse(value.expires_at))))\n ) {\n throw new Error(\"invalid identity snapshot\");\n }\n\n const expiresAt = isNonEmptyString(value.expires_at)\n ? Date.parse(value.expires_at)\n : Number.NaN;\n const sessionExpired =\n value.session_token != null &&\n Number.isFinite(expiresAt) &&\n expiresAt <= Date.now();\n return {\n version: BROWSER_IDENTITY_VERSION,\n project_id: projectId,\n anonymous_id: value.anonymous_id,\n app_user_id: isNonEmptyString(value.app_user_id)\n ? value.app_user_id\n : null,\n session_token: sessionExpired ? null : (value.session_token ?? null),\n refresh_token: value.refresh_token ?? null,\n expires_at: sessionExpired ? null : (value.expires_at ?? null),\n user:\n value.user && typeof value.user === \"object\" && isNonEmptyString(value.user.id)\n ? value.user\n : null,\n };\n } catch {\n persistence.delete(key);\n return null;\n }\n}\n\n/**\n * The identity the current core holds, for a `\"memory\"` re-configure of the\n * same project: the new core starts from it, so a second `configure()` in the\n * same page keeps the session.\n */\nfunction liveIdentity(projectId: string): PersistedBrowserIdentity | null {\n if (!coreInstance || _configuredProjectId !== projectId) return null;\n return {\n version: BROWSER_IDENTITY_VERSION,\n project_id: projectId,\n anonymous_id: coreInstance.anonymousId(),\n app_user_id: coreInstance.appUserId() ?? null,\n session_token: _currentSessionToken,\n refresh_token: _currentRefreshToken,\n expires_at: _currentSessionExpiresAt,\n user: _currentUser,\n };\n}\n\nfunction writeIdentitySnapshot(): void {\n if (\n _sessionPersistence === \"memory\" ||\n !_persistence?.available ||\n !_configuredProjectId ||\n !coreInstance\n )\n return;\n const snapshot: PersistedBrowserIdentity = {\n version: BROWSER_IDENTITY_VERSION,\n project_id: _configuredProjectId,\n anonymous_id: coreInstance.anonymousId(),\n app_user_id: coreInstance.appUserId() ?? null,\n session_token: _currentSessionToken,\n refresh_token: _currentRefreshToken,\n expires_at: _currentSessionExpiresAt,\n user: _currentUser,\n };\n _persistence.write(identityStorageKey(_configuredProjectId), JSON.stringify(snapshot));\n}\n\n/**\n * Functions invocation state. `_configuredFunctionsBaseUrl` is the optional\n * override from `Amba.configure({ functionsBaseUrl })`. The default-host path\n * caches its resolved project id and shares an in-flight diagnostics fallback\n * across concurrent invokes. All three reset on every `configure()`.\n */\nlet _configuredFunctionsBaseUrl: string | null = null;\nlet _resolvedFunctionsProjectId: string | null = null;\nlet _functionsProjectIdInflight: Promise<string> | null = null;\n\n/**\n * Offline-resilience state. `_persistence` is the localStorage-backed store\n * captured at configure-time; `_offlineQueue` is lazily built the first time\n * `Amba.offline.enable()` runs. `_offlineEnabled` gates whether failed writes\n * are diverted to the durable outbox (opt-in — default behavior is unchanged,\n * a failed write throws). All three reset on every `configure()`.\n */\nlet _persistence: LocalStoragePersistence | null = null;\nlet _offlineQueue: OfflineQueue | null = null;\nlet _offlineEnabled = false;\n/**\n * Detacher for the active queue's `online` auto-flush listener, if installed.\n * Held so a `configure()` (or a fresh `enable()`) can tear it down — otherwise\n * the previous queue's listener leaks and fires against a dead queue.\n */\nlet _offlineDetach: (() => void) | null = null;\n/**\n * Monotonic token identifying the CURRENT offline activation. Bumped on every\n * `enable()` AND every teardown/`configure()`, and captured by each `enable()`\n * into its disable callback. A disabler only acts while the token still matches\n * — so a stale disabler (held across a re-`enable()` OR a reconfigure) becomes\n * a no-op instead of wrongly turning the now-active outbox off.\n */\nlet _offlineEnableToken = 0;\n\n/**\n * Tear down any active offline wiring: detach the `online` listener, DISPOSE\n * the active queue (awaiting any in-flight flush so it can never write the\n * shared outbox again), and drop the references. Bumps the generation so stale\n * `enable()` disable callbacks no-op. Called before re-initializing on\n * `configure()`. Async so the in-flight flush is awaited before a replacement\n * queue is installed — preventing an orphaned flush from racing the new queue.\n */\nasync function teardownOffline(): Promise<void> {\n if (_offlineDetach) {\n _offlineDetach();\n _offlineDetach = null;\n }\n const old = _offlineQueue;\n _offlineQueue = null;\n _offlineEnabled = false;\n _offlineEnableToken += 1;\n if (old) await old.dispose();\n}\n\n/**\n * Bridge the localStorage persistence adapter to the {@link KvStore} contract\n * the offline outbox persists through. Namespaced under the configured\n * storage prefix so multiple projects in one origin don't collide.\n */\nfunction offlineStore(): KvStore {\n const p = _persistence;\n if (!p) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before using Amba.offline.\",\n );\n }\n return {\n get: (key) => p.read(key),\n set: (key, value) => p.write(key, value),\n delete: (key) => p.delete(key),\n };\n}\n\n/**\n * Replay a single queued mutation against the live API, forwarding the op's\n * idempotency key on collection writes so a write that landed before the\n * connection dropped is never double-applied. Used as the {@link OfflineQueue}\n * executor.\n */\nasync function replayOutboxOp(op: OutboxOp): Promise<void> {\n switch (op.kind) {\n case \"collection.insert\":\n await coreCall((c) =>\n c.collectionsInsert(\n op.collection as string,\n encodeJson({ ...op.payload, idempotency_key: op.idempotencyKey }),\n ),\n );\n return;\n case \"collection.update\":\n await coreCall((c) =>\n c.collectionsUpdate(\n op.collection as string,\n op.id as string,\n encodeJson(op.payload ?? {}),\n ),\n );\n return;\n case \"collection.delete\":\n await coreCall((c) =>\n c.collectionsDelete(op.collection as string, op.id as string),\n );\n return;\n case \"track\":\n // Replay through a direct events POST when the original time was captured\n // at enqueue, so the event keeps its ORIGINAL `occurred_at` rather than\n // being re-stamped to flush-time (which `core.track` would do). The\n // server attributes engagement events to the session user at replay (the\n // events route has no body-id attribution channel); the outbox only\n // replays an op while its owner is signed in, so that is the user who\n // tracked it. (Core fallback for ops queued before capture existed.)\n if (op.occurredAt !== undefined) {\n await replayTrackDirect(op);\n } else {\n await coreCall((c) =>\n c.track(\n op.event as string,\n op.properties ? encodeJson(op.properties) : undefined,\n op.telemetry,\n ),\n );\n }\n return;\n }\n}\n\n/**\n * Replay a buffered `track` op via a direct POST to `/v1/client/events`,\n * preserving the original `timestamp` captured at enqueue (the events route\n * honors body `timestamp` → `occurred_at`, but attributes the event to the\n * current session user — it has no body-`anonymous_id` attribution). Throws on\n * a transport failure or non-2xx so the queue retains the op.\n *\n * A `401`/`403` (no / expired session at replay) is surfaced as a coded\n * `NETWORK_ERROR` so the outbox RETAINS the event for a later attempt — an\n * unauthenticated replay must NOT be dropped as a deterministic rejection\n * (the session may refresh and the event then lands).\n */\nasync function replayTrackDirect(op: OutboxOp): Promise<void> {\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (_configuredClientKey) headers[\"X-Api-Key\"] = _configuredClientKey;\n if (_currentSessionToken)\n headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n const body: Record<string, unknown> = {\n event: op.event,\n properties: op.properties ?? undefined,\n timestamp: op.occurredAt,\n ...(op.telemetry ? { telemetry: true } : {}),\n };\n // Unique var name (not the generic `url`) so the SDK-route-drift extractor\n // maps THIS fetch to /v1/client/events — it keys url-vars by identifier, and\n // a shared `url` name across call-sites collides (sdk-route-drift.test.ts).\n const eventsUrl = `${_configuredApiUrl.replace(/\\/$/, \"\")}/v1/client/events`;\n let resp: Response;\n try {\n resp = await fetch(eventsUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n } catch (err) {\n throw new AmbaApiError(\"NETWORK_ERROR\", `track replay network error: ${String(err)}`);\n }\n if (!resp.ok) {\n // Treat a missing/expired session at replay as transient — retain, don't\n // drop. The outbox's deterministic-drop set includes UNAUTHORIZED/FORBIDDEN\n // (right for an immediate write the caller can fix), but for an\n // already-buffered analytics event a re-auth on the next flush should let\n // it through rather than silently losing it.\n if (resp.status === 401 || resp.status === 403) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `track replay deferred: no valid session (status ${resp.status})`,\n );\n }\n const text = await resp.text().catch(() => \"\");\n let code = `HTTP_${resp.status}`;\n try {\n const parsed = JSON.parse(text) as { error?: { code?: unknown } };\n if (typeof parsed?.error?.code === \"string\") code = parsed.error.code;\n } catch {\n // non-JSON body — keep the synthesized HTTP_<status> code\n }\n throw new AmbaApiError(code, `track replay failed with status ${resp.status}`, text);\n }\n}\n\nfunction getOfflineQueue(): OfflineQueue {\n if (!_offlineQueue) {\n _offlineQueue = new OfflineQueue({\n store: offlineStore(),\n // Scope the outbox by project so switching projects (same storage prefix)\n // can't replay a prior project's queued writes through the new project's\n // credentials — they'd land in the wrong project. Each project's outbox\n // is independent and persists under its own key.\n storageKey: `offline_outbox:${_configuredProjectId ?? \"default\"}`,\n executor: replayOutboxOp,\n // Bind each queued write to the user who made it: a flush replays it\n // only while that user is signed in (signOut purges the outbox).\n // A user id counts only with a session (or refresh) token behind it.\n currentOwner: () => ({\n appUserId:\n _currentSessionToken || _currentRefreshToken\n ? (coreInstance?.appUserId() ?? null)\n : null,\n anonymousId: coreInstance?.anonymousId() ?? null,\n }),\n onDropped: (op, err) => {\n // A queued write the server now rejects deterministically (e.g. a\n // VALIDATION_ERROR on replay) is dropped so it can't wedge the queue.\n // Surface it rather than discarding silently.\n if (typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped un-replayable offline ${op.kind} (${op.collection ?? op.event ?? \"?\"}):`,\n err,\n );\n }\n },\n });\n }\n return _offlineQueue;\n}\n\n/**\n * Empty the offline outbox on sign-out, including writes queued on earlier\n * page loads, so nothing the signed-out user queued replays under the next\n * session.\n */\nasync function purgeOfflineOutbox(): Promise<void> {\n if (!_persistence) return;\n try {\n await getOfflineQueue().purge();\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] signOut could not clear the offline outbox\", err);\n }\n }\n}\n\n// Direct (never-queued) collection writes. The optimistic helpers use these so\n// a write either truly succeeds (resolve with the server row) or truly fails\n// (reject → rollback). Optimistic UI needs an unambiguous outcome, so it must\n// NOT silently divert to the offline outbox — a queued-pending stub is not a\n// confirmed write, and treating it as success would commit the optimistic value\n// as final even though the server never saw it.\nasync function insertDirect<T>(\n name: string,\n row: Record<string, unknown>,\n): Promise<T> {\n return (await coreCall((c) => c.collectionsInsert(name, encodeJson(row)))) as T;\n}\nasync function updateDirect<T>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n): Promise<T> {\n return (await coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set)),\n )) as T;\n}\nasync function deleteDirect(\n name: string,\n id: string,\n): Promise<{ data: { deleted: boolean } }> {\n return (await coreCall((c) => c.collectionsDelete(name, id))) as {\n data: { deleted: boolean };\n };\n}\n\nfunction requireCore(): AmbaCoreWasm {\n if (!coreInstance) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.\",\n );\n }\n return coreInstance;\n}\n\n/**\n * Run a single SDK-runtime call and rewrap any thrown value into a\n * stable `AmbaApiError`. Every method on `Amba` funnels through this\n * so callers see one error shape regardless of which layer failed\n * (network, validation, etc.). Sync `requireCore()` accessors\n * (e.g. the `anonymousId` getter) already throw the typed shape on\n * their own.\n */\nasync function coreCall<T>(fn: (c: AmbaCoreWasm) => Promise<T>): Promise<T> {\n try {\n return await fn(requireCore());\n } catch (err) {\n throw toAmbaApiError(err);\n }\n}\n\n/**\n * In-SDK auth-state pub/sub. `signIn*` / `signUp*` / `signOut` /\n * `refresh` call `notifyAuthSubscribers` after their underlying core\n * call resolves so subscribers always see a consistent `Session | null`\n * snapshot. Subscriber callbacks run synchronously inside the\n * notification path — keep them light; offload heavy work to your own\n * scheduler.\n */\ntype AuthSubscriber = (session: Session | null) => void;\nconst authSubscribers = new Set<AuthSubscriber>();\n\n/**\n * Translate a public `TransactionOp` (camelCase `onConflict` /\n * `conflictTarget`) into the server's wire op (snake_case `on_conflict` /\n * `conflict_target`). Unset optionals are dropped so the server applies its\n * defaults.\n */\nfunction toWireTransactionOp(op: TransactionOp): Record<string, unknown> {\n const wire: Record<string, unknown> = { collection: op.collection, op: op.op };\n if (op.id !== undefined) wire[\"id\"] = op.id;\n if (op.row !== undefined) wire[\"row\"] = op.row;\n if (op.set !== undefined) wire[\"set\"] = op.set;\n if (op.where !== undefined) wire[\"where\"] = op.where;\n if (op.expected !== undefined) wire[\"expected\"] = op.expected;\n if (op.onConflict !== undefined) wire[\"on_conflict\"] = op.onConflict;\n if (op.conflictTarget !== undefined) wire[\"conflict_target\"] = op.conflictTarget;\n return wire;\n}\n\nfunction snapshotSession(user: User | null): Session | null {\n if (!user || !_currentSessionToken) return null;\n return {\n sessionToken: _currentSessionToken,\n refreshToken: _currentRefreshToken ?? \"\",\n user,\n expiresAt: _currentSessionExpiresAt ?? \"\",\n };\n}\n\nfunction notifyAuthSubscribers(session: Session | null): void {\n for (const cb of authSubscribers) {\n try {\n cb(session);\n } catch (err) {\n // Subscriber errors are isolated — one bad callback doesn't\n // block the rest. Surface in debug logs but don't rethrow into\n // the auth call path, which would mask the success result.\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] onAuthStateChange callback threw\", err);\n }\n }\n }\n}\n\nasync function notifyFromAuthResult(result: AuthResult): Promise<void> {\n _currentSessionToken = result.session_token;\n _currentRefreshToken = result.refresh_token;\n _currentSessionExpiresAt =\n result.expires_at && Number.isFinite(Date.parse(result.expires_at))\n ? result.expires_at\n : new Date(Date.now() + DEFAULT_SESSION_TTL_MS).toISOString();\n _currentUser = result.user;\n writeIdentitySnapshot();\n notifyAuthSubscribers(snapshotSession(result.user));\n}\n\n/**\n * Build the auth headers for a realtime subscribe connection, or `null`\n * when there's no live session. Read lazily at connect-time so reconnects\n * pick up a rotated session token.\n */\nfunction realtimeAuthHeaders(): Record<string, string> | null {\n if (!_configuredClientKey || !_currentSessionToken) return null;\n return {\n \"X-Api-Key\": _configuredClientKey,\n Authorization: `Bearer ${_currentSessionToken}`,\n };\n}\n\n/** SDK-facing AI streaming routes (vendor split is a stable wire shape). */\nconst AI_ANTHROPIC_PATH = \"/v1/client/ai/anthropic/messages\";\nconst AI_OPENAI_PATH = \"/v1/client/ai/openai/chat/completions\";\n\n/**\n * Build the request (url + headers + body) for a streamed AI invocation.\n * Forces `stream: true` onto the request, injects the client key + live\n * session token, and targets the configured API base. Shared by the\n * Anthropic + OpenAI streaming methods so the auth/path wiring can't drift.\n */\nfunction buildAiStreamRequest(\n path: string,\n request: AiMessageRequest,\n): { url: string; headers: Record<string, string>; body: string } {\n requireCore();\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before streaming AI.\",\n );\n }\n if (!request.prompt_slug || request.prompt_slug.trim() === \"\") {\n throw new AmbaApiError(\"INVALID_ARGUMENT\", \"prompt_slug is required\");\n }\n const headers: Record<string, string> = {\n \"X-Api-Key\": _configuredClientKey,\n \"content-type\": \"application/json\",\n accept: \"text/event-stream\",\n };\n if (_currentSessionToken) headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n // Expand any convenience `images` into content blocks before serializing.\n const expanded = expandAiImages(request);\n return {\n url: `${_configuredApiUrl.replace(/\\/$/, \"\")}${path}`,\n headers,\n body: JSON.stringify({ ...expanded, stream: true }),\n };\n}\n\n/**\n * Resolve the full invocation URL for a deployed function.\n *\n * Resolution checks `functionsBaseUrl` first, then uses a UUID-shaped\n * configured project id directly, and finally falls back to a deduped\n * `diagnostics.ping` for the authoritative server-side project id. The\n * resolved default-host id is cached for subsequent invokes.\n */\nasync function resolveFunctionUrl(name: string): Promise<string> {\n if (_configuredFunctionsBaseUrl) {\n return `${_configuredFunctionsBaseUrl.replace(/\\/$/, \"\")}/${pathSegment(name)}`;\n }\n const projectId = await resolveFunctionsProjectId();\n return `https://${projectId}.${DEFAULT_FUNCTIONS_HOST}/${pathSegment(name)}`;\n}\n\nasync function resolveFunctionsProjectId(): Promise<string> {\n if (_resolvedFunctionsProjectId) {\n return _resolvedFunctionsProjectId;\n }\n if (\n _configuredProjectId &&\n PROJECT_ID_PATTERN.test(_configuredProjectId)\n ) {\n _resolvedFunctionsProjectId = _configuredProjectId.toLowerCase();\n return _resolvedFunctionsProjectId;\n }\n if (!_functionsProjectIdInflight) {\n const configuredProjectId = _configuredProjectId;\n const resolution = (async () => {\n const ping = await Amba.diagnostics.ping();\n if (!ping.ok || !ping.server_project_id) {\n throw new AmbaApiError(\n \"FUNCTIONS_HOST_UNRESOLVED\",\n \"Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.\",\n ping,\n );\n }\n if (\n _configuredProjectId === configuredProjectId &&\n !_configuredFunctionsBaseUrl\n ) {\n _resolvedFunctionsProjectId = ping.server_project_id;\n }\n return ping.server_project_id;\n })();\n _functionsProjectIdInflight = resolution;\n void resolution.then(\n () => {\n if (_functionsProjectIdInflight === resolution) {\n _functionsProjectIdInflight = null;\n }\n },\n () => {\n if (_functionsProjectIdInflight === resolution) {\n _functionsProjectIdInflight = null;\n }\n },\n );\n }\n return _functionsProjectIdInflight;\n}\n\n/** Options for `Amba.functions.invoke`. */\nexport interface InvokeOptions {\n /** HTTP method. Defaults to `\"POST\"`. */\n method?: \"POST\" | \"GET\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Extra headers merged on top of the injected auth headers. */\n headers?: Record<string, string>;\n /**\n * Called with the server-side request / trace id (the\n * `X-Amba-Request-Id` response header) as soon as a response is\n * received — on both success and failure. Use it to correlate a run with\n * server logs (`functions logs --request-id <traceId>`). `null` when the\n * server returned no trace id (e.g. a pre-auth rejection that doesn't\n * stamp the header).\n *\n * The same id is also attached to any thrown `AmbaApiError` as\n * `error.traceId`, so error-path correlation works without a callback.\n */\n onTraceId?: (traceId: string | null) => void;\n}\n\n/**\n * Immutable browser collection scope for a server-verified group context.\n * Each method carries its own `groupId` into the Rust request path; no mutable\n * global header state is used, so parallel group A/group B calls cannot leak.\n *\n * Group-scoped writes deliberately bypass the global offline outbox. Replaying\n * a queued group mutation without its membership context would be unsafe;\n * callers receive the real network outcome and can retry in the same scope.\n */\nexport class AmbaGroupScopedClient {\n readonly groupId: string;\n\n constructor(groupId: string) {\n const normalized = groupId.trim();\n if (!normalized || containsControlCharacter(normalized)) {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"Amba.asGroup: groupId must be non-empty and contain no control characters\",\n );\n }\n this.groupId = normalized;\n }\n\n asGroup(groupId: string): AmbaGroupScopedClient {\n return new AmbaGroupScopedClient(groupId);\n }\n\n inGroup(groupId: string): AmbaGroupScopedClient {\n return this.asGroup(groupId);\n }\n\n collection<T = unknown>(name: string): CollectionSubscription<T> {\n const groupId = this.groupId;\n return new CollectionSubscription<T>(\n {\n baseUrl: _configuredApiUrl,\n authHeaders: () => {\n const headers = realtimeAuthHeaders();\n return headers ? { ...headers, \"X-Group-Context\": groupId } : null;\n },\n },\n name,\n );\n }\n\n collections = {\n find: async <T = unknown>(\n name: string,\n options: FindOptions = {},\n ): Promise<FindResponse<T>> =>\n (await coreCall((c) =>\n c.collectionsFind(name, encodeJson(options), this.groupId),\n )) as FindResponse<T>,\n\n findOne: async <T = unknown>(name: string, id: string): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsFindOne(name, id, this.groupId),\n )) as T,\n\n insert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n ): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsInsert(name, encodeJson(row), this.groupId),\n )) as T,\n\n update: async <T = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n ): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set), this.groupId),\n )) as T,\n\n delete: async (\n name: string,\n id: string,\n ): Promise<{ data: { deleted: boolean } }> =>\n (await coreCall((c) =>\n c.collectionsDelete(name, id, this.groupId),\n )) as { data: { deleted: boolean } },\n\n upsert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n options: UpsertOptions,\n ): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsUpsert(\n name,\n encodeJson(row),\n encodeJson({\n on_conflict: options.onConflict,\n conflict_target: options.conflictTarget,\n }),\n this.groupId,\n ),\n )) as T,\n\n deleteWhere: async (\n name: string,\n filter: Filter,\n ): Promise<DeleteManyResult> =>\n (await coreCall((c) =>\n c.collectionsDeleteWhere(name, encodeJson(filter), this.groupId),\n )) as DeleteManyResult,\n\n restore: async <T = unknown>(name: string, id: string): Promise<T> =>\n (await coreCall((c) =>\n c.collectionsRestore(name, id, this.groupId),\n )) as T,\n\n transaction: async (\n operations: TransactionOp[],\n ): Promise<TransactionOpResult[]> => {\n const env = (await coreCall((c) =>\n c.collectionsTransaction(\n encodeJson(operations.map(toWireTransactionOp)),\n this.groupId,\n ),\n )) as { data: TransactionOpResult[] };\n return env.data;\n },\n\n findNearest: async <T = unknown>(\n name: string,\n options: NearestOptions,\n ): Promise<NearestResponse<T>> =>\n (await coreCall((c) =>\n c.collectionsFindNearest(name, encodeJson(options), this.groupId),\n )) as NearestResponse<T>,\n\n count: async (name: string, filter?: Filter): Promise<number> =>\n (await coreCall((c) =>\n c.collectionsCount(\n name,\n filter ? encodeJson(filter) : undefined,\n this.groupId,\n ),\n )) as number,\n\n optimisticInsert: async <T = unknown, TApply = unknown>(\n name: string,\n row: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () =>\n coreCall((c) =>\n c.collectionsInsert(name, encodeJson(row), this.groupId),\n ) as Promise<T>,\n rollback: view.rollback,\n }),\n\n optimisticUpdate: async <T = unknown, TApply = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () =>\n coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set), this.groupId),\n ) as Promise<T>,\n rollback: view.rollback,\n }),\n\n optimisticDelete: async <TApply = unknown>(\n name: string,\n id: string,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<{ data: { deleted: boolean } }> =>\n withOptimistic({\n apply: view.apply,\n mutate: () =>\n coreCall((c) =>\n c.collectionsDelete(name, id, this.groupId),\n ) as Promise<{ data: { deleted: boolean } }>,\n rollback: view.rollback,\n }),\n\n where: {\n eq: (column: string, value: FilterValue): Filter => ({ column, op: \"eq\", value }),\n ne: (column: string, value: FilterValue): Filter => ({ column, op: \"ne\", value }),\n gt: (column: string, value: FilterValue): Filter => ({ column, op: \"gt\", value }),\n gte: (column: string, value: FilterValue): Filter => ({ column, op: \"gte\", value }),\n lt: (column: string, value: FilterValue): Filter => ({ column, op: \"lt\", value }),\n lte: (column: string, value: FilterValue): Filter => ({ column, op: \"lte\", value }),\n in: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"in\",\n value: values,\n }),\n isNull: (column: string): Filter => ({ column, op: \"is_null\" }),\n isNotNull: (column: string): Filter => ({ column, op: \"is_not_null\" }),\n and: (...filters: Filter[]): Filter => ({ and: filters }),\n or: (...filters: Filter[]): Filter => ({ or: filters }),\n not: (filter: Filter): Filter => ({ not: filter }),\n },\n };\n}\n\n/**\n * Top-level SDK namespace.\n *\n * Stateless from the JS side — all state lives in the SDK runtime. Calling\n * `Amba.configure()` multiple times replaces the previous instance.\n */\nexport const Amba = {\n /**\n * Initialize the SDK. Must be called once at app start.\n *\n * Seeds an `anonymous_id` from localStorage (if present) or generates a\n * fresh one.\n */\n configure: async (config: AmbaConfig): Promise<void> => {\n if (!config.projectId || config.projectId.trim() === \"\") {\n throw new Error(\"Amba.configure: projectId is required\");\n }\n if (!config.clientKey || config.clientKey.trim() === \"\") {\n throw new Error(\"Amba.configure: clientKey is required\");\n }\n if (config.wasmUrl !== undefined && config.wasmModule !== undefined) {\n throw new Error(\"Amba.configure: pass only one of wasmUrl or wasmModule\");\n } else {\n const { initCore } = await getCoreRuntime();\n const explicitWasmInput = config.wasmModule ?? config.wasmUrl;\n if (explicitWasmInput !== undefined) {\n await initCore({ module_or_path: explicitWasmInput });\n } else {\n // wasm-pack --target web's auto-loader does `fetch(new URL(\"./amba_core_bg.wasm\", import.meta.url))`.\n // That resolves to a `file://` URL in Node, and Node's undici doesn't\n // support the `file:` scheme — throws `fetch failed: not implemented`.\n // In Node (server-rendering, Jest/Vitest under jsdom, customer-shoes\n // smoke), read the wasm via fs and pass the bytes to init() directly.\n // In browsers, the browser export condition swaps in @layers/amba-core/inline\n // before configure() runs, so initCore() decodes embedded wasm bytes instead\n // of asking wasm-pack to infer a sibling .wasm URL under Vite's dep cache.\n if (\n !IS_BROWSER_BUILD &&\n typeof process !== \"undefined\" &&\n !!(process.versions && process.versions.node)\n ) {\n const [\n { readFile },\n { fileURLToPath, pathToFileURL },\n { createRequire },\n ] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:url\"),\n import(\"node:module\"),\n ]);\n // Resolve the wasm path through @layers/amba-core's package\n // root so we follow the same module resolution as the .js glue\n // — works whether the consumer used a bundler or a raw `node`.\n //\n // Resolution strategy, in order:\n // 1. `import.meta.resolve` (Node ≥20.6, stable in 22). Async or sync\n // depending on Node version — both return a string when awaited.\n // 2. `require.resolve` via `createRequire(import.meta.url)`. Uses\n // Node's standard module resolution which honors pnpm's\n // `.pnpm/<pkg>@<ver>/node_modules/<pkg>` layout. The previous\n // `../node_modules/...` hard-coded fallback failed on pnpm.\n // This mirrors the Node SDK fix at `packages/node/src/index.ts:124`.\n const resolveFn = (\n import.meta as unknown as {\n resolve?: (s: string) => string | Promise<string>;\n }\n ).resolve;\n let wasmJsUrl: string;\n if (resolveFn) {\n wasmJsUrl = await resolveFn(\"@layers/amba-core/wasm\");\n } else {\n const req = createRequire(import.meta.url);\n // `require.resolve` returns a filesystem path; convert to a\n // file:// URL so `fileURLToPath` below works uniformly with the\n // `import.meta.resolve` branch.\n wasmJsUrl = pathToFileURL(\n req.resolve(\"@layers/amba-core/wasm\"),\n ).href;\n }\n const bytes = await readFile(fileURLToPath(wasmJsUrl));\n await initCore({ module_or_path: bytes });\n } else await initCore();\n }\n }\n const sessionPersistence = config.sessionPersistence ?? \"local\";\n if (!SESSION_PERSISTENCE_MODES.includes(sessionPersistence)) {\n throw new AmbaApiError(\n \"INVALID_CONFIG\",\n 'Amba.configure: sessionPersistence must be \"local\" or \"memory\"',\n );\n }\n const persistence = new LocalStoragePersistence(\n config.storagePrefix ?? \"amba\",\n );\n // Memory mode never restores a stored identity (and removes one an\n // earlier \"local\" configuration left behind); a re-configure of the same\n // project carries the page's live identity over.\n const restoredIdentity =\n sessionPersistence === \"memory\"\n ? liveIdentity(config.projectId)\n : readPersistedIdentity(persistence, config.projectId);\n\n // Wrap `AmbaCoreWasm.init`'s throw with `toAmbaApiError` so\n // configure() rejects with an `AmbaApiError` instance — same\n // contract as every method call. Without this, the engine's\n // post-#63 `JsValue` throws would surface to customers as plain\n // objects, breaking `e instanceof Error` checks in\n // configure-failure handlers. (Reported by Cursor BugBot on\n // PR #156, high.)\n //\n // Build the new core BEFORE touching any module state (offline teardown,\n // persistence, the `_configured*` cache): if init throws, the previous\n // session — including its offline wiring + persistence — stays fully intact\n // rather than being left half-reconfigured for a failed attempt.\n let nextCore: AmbaCoreWasm;\n try {\n const { wasmClass } = await getCoreRuntime();\n nextCore = wasmClass.init(\n encodeJson({\n api_key: config.clientKey,\n base_url: config.apiUrl,\n project_id: config.projectId,\n sdk_platform: \"web\",\n sdk_wrapper_version: `amba-web/${SDK_VERSION}`,\n consent_required: config.consentRequired,\n debug: config.debug,\n }),\n globalThis.fetch.bind(globalThis),\n );\n if (restoredIdentity) {\n // Hydrate the Rust core itself — not merely TS getters — so every\n // core-backed request and refresh sees the restored credentials.\n nextCore.hydrateIdentity(\n encodeJson({\n anonymous_id: restoredIdentity.anonymous_id,\n app_user_id: restoredIdentity.app_user_id,\n session_token: restoredIdentity.session_token,\n refresh_token: restoredIdentity.refresh_token,\n expires_at: restoredIdentity.expires_at,\n }),\n );\n }\n } catch (err) {\n throw toAmbaApiError(err);\n }\n\n // Init succeeded — now it's safe to swap state. Tear down any prior offline\n // wiring (detach the previous queue's `online` listener, dispose it, and\n // AWAIT any in-flight flush so the orphaned queue can never write the\n // outbox again), rebind persistence, and commit the core + config cache.\n await teardownOffline();\n _persistence = persistence;\n _sessionPersistence = sessionPersistence;\n if (sessionPersistence === \"memory\") {\n persistence.delete(identityStorageKey(config.projectId));\n }\n coreInstance = nextCore;\n _configuredProjectId = config.projectId;\n _configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;\n _configuredClientKey = config.clientKey;\n _currentSessionToken = nextCore.isAuthenticated()\n ? (restoredIdentity?.session_token ?? null)\n : null;\n _currentRefreshToken = restoredIdentity?.refresh_token ?? null;\n _currentSessionExpiresAt = nextCore.isAuthenticated()\n ? (restoredIdentity?.expires_at ?? null)\n : null;\n _currentUser = nextCore.isAuthenticated()\n ? (restoredIdentity?.user ?? null)\n : null;\n _configuredFunctionsBaseUrl = config.functionsBaseUrl ?? null;\n // A fresh configure() may point at a different project — drop any cached\n // function-host resolution so the next invoke re-resolves.\n _resolvedFunctionsProjectId = null;\n _functionsProjectIdInflight = null;\n if (\n !_configuredFunctionsBaseUrl &&\n !PROJECT_ID_PATTERN.test(config.projectId)\n ) {\n void resolveFunctionsProjectId().catch(() => {});\n }\n // Persist the core-minted anonymous id even before sign-in so a plain\n // configure/reload cycle keeps the same browser identity.\n writeIdentitySnapshot();\n },\n\n /** Project id this SDK instance is scoped to. */\n get projectId(): string {\n if (!_configuredProjectId) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.\",\n );\n }\n return _configuredProjectId;\n },\n\n /** Anonymous id — stable per browser. */\n get anonymousId(): string {\n return requireCore().anonymousId();\n },\n\n /** Authenticated user id, if a session is live. */\n get appUserId(): string | undefined {\n return requireCore().appUserId() ?? undefined;\n },\n\n /** Whether a session token is currently held. */\n get isAuthenticated(): boolean {\n return requireCore().isAuthenticated();\n },\n\n /** Toggle verbose logging at runtime. */\n setDebug: (enabled: boolean): void => {\n requireCore().setDebug(enabled);\n },\n\n // ── Core 9 namespaces ─────────────────────────────────────────────\n\n /** Track engagement events. */\n events: {\n /**\n * Track an event.\n *\n * The default route writes to the engagement meter — events that\n * fan out to segments, workflows, and push. Pass\n * `{ telemetry: true }` for high-volume, fire-and-forget events\n * that DON'T need fan-out: UI button clicks, scroll-depth markers,\n * game-loop telemetry. Telemetry events are billed at $0.10 per 1M\n * overage vs $0.50 per 10K for engagement events — a ~500×\n * difference that mirrors the underlying compute cost.\n *\n * Telemetry events are persisted and queryable via analytics, but\n * skip segment recompute, streak qualification, currency grant\n * rules, and `last_seen_at` updates.\n *\n * @example\n * await Amba.events.track('purchase_completed', { amount: 9.99 });\n * await Amba.events.track('scroll_depth', { px: 1024 }, { telemetry: true });\n */\n track: async (\n event: string,\n properties?: Record<string, unknown>,\n options?: { telemetry?: boolean },\n ): Promise<void> => {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : undefined;\n try {\n await coreCall((c) => c.track(\n event,\n properties ? encodeJson(properties) : undefined,\n options?.telemetry,\n ));\n } catch (err) {\n // When the offline outbox is enabled, a transport failure buffers the\n // event for replay-on-reconnect instead of being lost. Deterministic\n // failures (validation, auth) still throw.\n if (_offlineEnabled && isOfflineError(err)) {\n // Capture the original event TIME so the buffered event keeps its\n // `occurred_at` on replay (the events route honors a body timestamp).\n // The events route attributes to the session user at replay time, so\n // the outbox replays the event only while its owner (from the ticket)\n // is signed in.\n await getOfflineQueue().enqueue({\n kind: \"track\",\n event,\n properties,\n telemetry: options?.telemetry,\n occurredAt: new Date().toISOString(),\n }, outboxTicket);\n return;\n }\n throw err;\n }\n },\n\n /**\n * Dry-run an event against the rule engine WITHOUT firing it. Returns\n * which rules would match and what each WOULD produce — XP awards,\n * currency grants, feed items, streak qualification, and webhook\n * deliveries — so you can predict the consequences of a `track` call\n * before committing any side effect. Nothing is written.\n *\n * Pass `{ userId }` to also preview per-user gating (daily limits,\n * cooldowns, balance caps); omit it for the user-independent base match.\n *\n * @example\n * const plan = await Amba.events.explain('lesson_completed', { score: 90 });\n * // plan.matched → [{ rule_type: 'xp', would: { xp_delta: 25 }, ... }, ...]\n */\n explain: async (\n event: string,\n properties?: Record<string, unknown>,\n options?: { userId?: string | null },\n ): Promise<EventExplainResult> => {\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before Amba.events.explain().\",\n );\n }\n if (!event || event.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.events.explain: an event name is required.\",\n );\n }\n\n const candidate: Record<string, unknown> = { name: event };\n if (properties !== undefined) candidate[\"properties\"] = properties;\n if (options && \"userId\" in options) candidate[\"user_id\"] = options.userId ?? null;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-Api-Key\": _configuredClientKey,\n };\n if (_currentSessionToken) headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n\n let resp: Response;\n try {\n resp = await fetch(\n `${_configuredApiUrl.replace(/\\/$/, \"\")}/v1/client/events/explain`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({ event: candidate }),\n },\n );\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.events.explain(\"${event}\") network error: ${String(err)}`,\n );\n }\n\n const text = await resp.text();\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = undefined;\n }\n }\n if (!resp.ok) {\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\"\n ? envelope.error.code\n : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : `Amba.events.explain(\"${event}\") failed with status ${resp.status}`;\n throw new AmbaApiError(code, message, parsed);\n }\n return (parsed as { data: EventExplainResult }).data;\n },\n },\n\n /** Authentication flows. */\n auth: {\n signInAnonymously: async (): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInAnonymously())) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n signInWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInWithEmail(\n email,\n password,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n signUpWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signUpWithEmail(\n email,\n password,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Sign in with an Apple identity token (the JWT returned by Sign\n * in with Apple). Replaces the SDK 3.x polymorphic\n * `signInWithSocial('apple', token)` form — that surface is\n * removed in 4.0.\n */\n signInWithApple: async (identityToken: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInWithSocial(\n \"apple\",\n identityToken,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Sign in with a Google ID token (the JWT returned by Google\n * Sign-In). Replaces the SDK 3.x polymorphic\n * `signInWithSocial('google', token)` form — that surface is\n * removed in 4.0.\n */\n signInWithGoogle: async (idToken: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.signInWithSocial(\n \"google\",\n idToken,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Request a one-time passcode for `email`. The server emails a\n * 6-digit code; the user types it into the app, then call\n * `verifyEmailOtp(email, code)` to exchange for a session. Always\n * resolves successfully regardless of email registration — the\n * server uses uniform responses to prevent account enumeration.\n */\n requestEmailOtp: async (email: string): Promise<void> => {\n await coreCall((c) => c.requestEmailOtp(email));\n },\n /**\n * Exchange `(email, code)` for a session. Returns the same\n * `AuthResult` shape as `signInWithEmail` and persists tokens for\n * subsequent calls. Rejects with a single error code for every\n * failure mode (wrong/expired/exhausted) — the server intentionally\n * does not distinguish them to prevent enumeration.\n */\n verifyEmailOtp: async (email: string, code: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.verifyEmailOtp(email, code))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Request a one-time passcode by SMS. `phone` MUST be E.164\n * (starts with `+`, 8–15 total digits). The SDK rejects\n * anything else with a `VALIDATION_ERROR` before the network\n * call. Server sends a 6-digit code; user types it into the\n * app, then call `verifySmsOtp(phone, code)` to exchange for\n * a session. Always resolves successfully regardless of phone\n * registration — uniform responses prevent enumeration.\n */\n requestSmsOtp: async (phone: string): Promise<void> => {\n await coreCall((c) => c.requestSmsOtp(phone));\n },\n /**\n * Exchange `(phone, code)` for a session. Same shape +\n * failure semantics as `verifyEmailOtp`. Persists tokens for\n * subsequent calls.\n */\n verifySmsOtp: async (phone: string, code: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.verifySmsOtp(phone, code))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Request a magic-link email for `email`. The server emails the\n * link; the user clicks it, which delivers a one-time token back\n * to the app. Exchange that token for a session with\n * `verifyMagicLink(token)`. Always resolves successfully\n * regardless of email registration (uniform responses prevent\n * account enumeration).\n */\n requestMagicLink: async (email: string): Promise<void> => {\n await coreCall((c) => c.requestMagicLink(email));\n },\n /**\n * Exchange a magic-link `token` for a session. Returns the same\n * `AuthResult` shape as `signInWithEmail` and persists tokens for\n * subsequent calls.\n */\n verifyMagicLink: async (token: string): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.verifyMagicLink(token))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n /**\n * Link an additional auth provider to the current session — used to\n * upgrade an anonymous session to an identified one without losing\n * the user's data. `provider` is one of the supported identity\n * strings (e.g. `\"apple\"`, `\"google\"`); `credential` is the\n * provider-issued token (identity-token / id-token / OTP code).\n */\n linkAccount: async (\n provider: string,\n credential: string,\n ): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.linkAccount(\n provider,\n credential,\n ))) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n\n /**\n * Attach a verified phone number to the **current session's user** —\n * without minting new tokens and without changing `user_id`.\n *\n * Flow: call `requestSmsOtp(phone)` first to mail the 6-digit code,\n * then call THIS instead of `verifySmsOtp` when the desired outcome\n * is \"decorate the current (often anonymous) user with this phone\"\n * rather than \"switch to the user who owns this phone\".\n *\n * Guarantees (symmetric to `linkEmailOtp`):\n *\n * - **Same `user_id`** before and after — collections, holds, XP,\n * `anonymous_id` all carry over.\n * - **Session token unchanged.** The existing token stays valid;\n * `onAuthStateChange` does NOT fire (no rotation).\n * - **Idempotent.** Re-linking the same phone to the same user\n * succeeds — no-op apart from `last_seen_at`.\n * - **Collision.** If the phone is already verified to a different\n * user, throws an error with `code = \"PHONE_ALREADY_LINKED\"` and\n * `details.conflicting_user_id` populated. The caller decides\n * whether to abort or switch identities via `verifySmsOtp`.\n *\n * Requires an active session — throws `UNAUTHORIZED` otherwise.\n *\n * @returns the updated user record (with `phone` set and\n * `phone_verified = true`).\n */\n linkSmsOtp: async (phone: string, code: string): Promise<User> => {\n return (await coreCall((c) => c.linkSmsOtp(phone, code))) as User;\n },\n\n /**\n * Attach a verified email to the current session's user. Symmetric\n * to `linkSmsOtp` — see that for the full guarantee list. Collision\n * code is `EMAIL_ALREADY_LINKED`.\n */\n linkEmailOtp: async (email: string, code: string): Promise<User> => {\n return (await coreCall((c) => c.linkEmailOtp(email, code))) as User;\n },\n\n /**\n * Upgrade an anonymous (or any current) session by attaching an\n * email + password credential — converting the current user in place\n * to an email-identified account so its data (streak, library,\n * progress, `anonymous_id`) is preserved rather than minting a new\n * user.\n *\n * Guarantees (symmetric to `linkEmailOtp`):\n *\n * - **Same `user_id`** before and after.\n * - **Session token unchanged.** No rotation; `onAuthStateChange`\n * does NOT fire.\n * - **Idempotent.** Re-linking the same email to the same user\n * succeeds.\n * - **Collision.** If the email is already owned by a different user,\n * throws an error with `code = \"EMAIL_ALREADY_LINKED\"` and\n * `details.conflicting_user_id` populated.\n *\n * After linking, the user can sign in with `signInWithEmail(email,\n * password)`. Requires an active session — throws `UNAUTHORIZED`\n * otherwise.\n *\n * The address stays unverified (`email_verified = false`) until the\n * inbox is proven, unless this user had already verified the same\n * address. `linkEmailOtp` with the same address from this session\n * verifies it and keeps the password.\n *\n * @returns the updated user record (with `email` set).\n */\n linkEmailPassword: async (email: string, password: string): Promise<User> => {\n return (await coreCall((c) => c.linkEmailPassword(email, password))) as User;\n },\n\n /**\n * Detach the SMS-OTP identifier from the current session's user.\n * Refused with `LAST_IDENTIFIER` if this would leave the account\n * with no recoverable identifier (link another first, then unlink).\n * No token rotation.\n */\n unlinkSmsOtp: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkSmsOtp())) as User;\n },\n\n /** Detach the email-OTP identifier. Same guarantees as `unlinkSmsOtp`. */\n unlinkEmailOtp: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkEmailOtp())) as User;\n },\n\n /** Detach the Apple social identity from the current session's user. */\n unlinkApple: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkApple())) as User;\n },\n\n /** Detach the Google social identity from the current session's user. */\n unlinkGoogle: async (): Promise<User> => {\n return (await coreCall((c) => c.unlinkGoogle())) as User;\n },\n\n /**\n * Snapshot the identifier providers attached to the current\n * session's user. Lets a host app gate \"should I show the verify-\n * email prompt\" without a profile round-trip.\n *\n * Identifier values come back **masked** — raw values stay\n * server-side.\n */\n getCurrentIdentifiers: async (): Promise<LinkedIdentifier[]> => {\n return (await coreCall((c) => c.currentIdentifiers())) as LinkedIdentifier[];\n },\n signOut: async (rotateAnonymousId = false): Promise<void> => {\n await coreCall((c) => c.signOut(rotateAnonymousId));\n _currentSessionToken = null;\n _currentRefreshToken = null;\n _currentSessionExpiresAt = null;\n _currentUser = null;\n writeIdentitySnapshot();\n await purgeOfflineOutbox();\n notifyAuthSubscribers(null);\n },\n refresh: async (): Promise<AuthResult> => {\n const r = (await coreCall((c) => c.refreshSession())) as AuthResult;\n await notifyFromAuthResult(r);\n return r;\n },\n me: async (): Promise<User> => {\n const user = (await coreCall((c) => c.me())) as User;\n _currentUser = user;\n writeIdentitySnapshot();\n return user;\n },\n\n /**\n * Snapshot the current session, or `null` if no session is live.\n * Tokens are returned from the browser's durable session snapshot. Treat\n * them as credentials: do not log them or copy them into app-managed\n * storage.\n */\n getSession: async (): Promise<Session | null> => {\n const core = requireCore();\n if (!core.isAuthenticated()) return null;\n const user = (await coreCall((c) => c.me())) as User;\n _currentUser = user;\n writeIdentitySnapshot();\n return snapshotSession(user);\n },\n\n /**\n * Stable anonymous identifier (kept in memory by the SDK runtime).\n * Sync today, async for parity with the RN/native shape where the\n * platform may need to await a persistence read.\n */\n getAnonymousId: async (): Promise<string> => {\n return requireCore().anonymousId();\n },\n\n /**\n * Subscribe to session changes. Fires after every `signIn*`,\n * `signUp*`, `refresh`, and `signOut` call. Returns an\n * unsubscribe function — call it to remove the callback.\n *\n * Does NOT fire an initial snapshot when subscribed; call\n * `getSession()` once after subscribing if you need the\n * current state up front.\n */\n onAuthStateChange: (\n cb: (session: Session | null) => void,\n ): (() => void) => {\n authSubscribers.add(cb);\n return () => {\n authSubscribers.delete(cb);\n };\n },\n },\n\n /** Profile read/update (SDK 4.0). */\n users: {\n // <generated:users>\n /**\n * Get a user profile. `userId` omitted → current user\n * (`GET /v1/client/users/me`); `userId` set → public-safe profile\n * of the other user (`GET /v1/client/users/{id}`).\n */\n get: async (userId?: string): Promise<User> => {\n return (await coreCall((c) => c.usersGet(\n userId ?? undefined,\n ))) as User;\n },\n /**\n * Update a profile. Two shapes:\n * - `update(patch)` — current user (`PATCH /users/me`).\n * - `update(userId, patch)` — admin-style cross-user update\n * (`PATCH /users/{id}`); the server requires the caller to\n * hold `users:write` and 403s otherwise.\n *\n * Allowed keys (both shapes): `display_name`, `avatar_url`,\n * `properties`, `timezone`.\n */\n update: (((\n arg1: string | Partial<User>,\n arg2?: Partial<User>,\n ): Promise<User> => {\n const isById = typeof arg1 === \"string\";\n const userId = isById ? (arg1 as string) : undefined;\n const patch = isById ? (arg2 ?? {}) : (arg1 as Partial<User>);\n return coreCall((c) => c.usersUpdate(\n userId ?? undefined,\n encodeJson(patch),\n )) as Promise<User>;\n }) as {\n (patch: Partial<User>): Promise<User>;\n (userId: string, patch: Partial<User>): Promise<User>;\n }),\n // </generated:users>\n },\n\n /** List + revoke customer-recorded app sessions (SDK 4.0). */\n sessions: {\n // <generated:sessions>\n /** `GET /v1/client/sessions` — list the caller's app sessions. */\n list: async (): Promise<AppSession[]> => {\n return (await coreCall((c) => c.sessionsList())) as AppSession[];\n },\n /**\n * `POST /v1/client/sessions/:id/end` — end the session with the\n * given id. The 4.0 `revoke` contract returns unit; customers who\n * need the ended row should use the lower-level `sessions.end`\n * helper on the core (not exposed on the public SDK).\n */\n revoke: async (sessionId: string): Promise<void> => {\n await coreCall((c) => c.sessionsRevoke(sessionId));\n },\n // </generated:sessions>\n },\n\n /**\n * Scope collection calls to a group the current session belongs to.\n * Membership and role are verified by the API; an unknown/unauthorized\n * group is rejected server-side. The returned caller is immutable and safe\n * to use concurrently with callers scoped to other groups.\n */\n asGroup: (groupId: string): AmbaGroupScopedClient => {\n requireCore();\n return new AmbaGroupScopedClient(groupId);\n },\n\n /** Alias of {@link Amba.asGroup}. */\n inGroup: (groupId: string): AmbaGroupScopedClient => {\n requireCore();\n return new AmbaGroupScopedClient(groupId);\n },\n\n /**\n * Live collection subscriptions (SDK 4.0). Returns a builder — chain\n * `.where(column, value)` to narrow, then `.subscribe(cb)` to start\n * receiving row changes. `subscribe` returns an unsubscribe handle.\n *\n * @example\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op: \"INSERT\" | \"UPDATE\" | \"DELETE\"; change.row: the row\n * });\n * // later: unsub();\n *\n * Delivery mirrors `Amba.collections.find()` — you only receive rows your\n * session owns. Requires an authenticated session.\n */\n collection: <T = unknown>(name: string): CollectionSubscription<T> =>\n new CollectionSubscription<T>(\n { baseUrl: _configuredApiUrl, authHeaders: realtimeAuthHeaders },\n name,\n ),\n\n /** Typed collection CRUD with auto-RLS. */\n collections: {\n // <generated:collections.find>\n find: async <T = unknown>(\n name: string,\n options: FindOptions = {},\n ): Promise<FindResponse<T>> => {\n return (await coreCall((c) => c.collectionsFind(\n name,\n encodeJson(options),\n ))) as FindResponse<T>;\n },\n // </generated:collections.find>\n findOne: async <T = unknown>(name: string, id: string): Promise<T> => {\n return (await coreCall((c) => c.collectionsFindOne(name, id))) as T;\n },\n insert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n ): Promise<T> => {\n // When the outbox is enabled, mint the idempotency key up front and send\n // it on the live attempt too. If that attempt actually reached the server\n // before the connection dropped, the queued replay carries the SAME key\n // and the API dedupes it (migration 056) — no double-insert.\n if (_offlineEnabled) {\n const idempotencyKey =\n (typeof row[\"idempotency_key\"] === \"string\"\n ? (row[\"idempotency_key\"] as string)\n : undefined) ?? newIdempotencyKey();\n const wireRow = { ...row, idempotency_key: idempotencyKey };\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : undefined;\n try {\n return (await coreCall((c) =>\n c.collectionsInsert(name, encodeJson(wireRow)),\n )) as T;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue().enqueue({\n kind: \"collection.insert\",\n collection: name,\n payload: row,\n idempotencyKey,\n }, outboxTicket);\n return { ...row, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n return (await coreCall((c) => c.collectionsInsert(\n name,\n encodeJson(row),\n ))) as T;\n },\n update: async <T = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n ): Promise<T> => {\n if (_offlineEnabled) {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : undefined;\n try {\n return (await coreCall((c) =>\n c.collectionsUpdate(name, id, encodeJson(set)),\n )) as T;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue().enqueue({\n kind: \"collection.update\",\n collection: name,\n id,\n payload: set,\n }, outboxTicket);\n return { id, ...set, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n return (await coreCall((c) => c.collectionsUpdate(\n name,\n id,\n encodeJson(set),\n ))) as T;\n },\n delete: async (\n name: string,\n id: string,\n ): Promise<{ data: { deleted: boolean } }> => {\n if (_offlineEnabled) {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : undefined;\n try {\n return (await coreCall((c) => c.collectionsDelete(name, id))) as {\n data: { deleted: boolean };\n };\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue().enqueue({\n kind: \"collection.delete\",\n collection: name,\n id,\n }, outboxTicket);\n return { data: { deleted: true } };\n }\n throw err;\n }\n }\n return (await coreCall((c) => c.collectionsDelete(name, id))) as {\n data: { deleted: boolean };\n };\n },\n /**\n * Insert a row, resolving a unique-index conflict instead of failing.\n *\n * With `options.onConflict: \"ignore\"` a conflicting row is left\n * untouched and returned; with `\"update\"` the supplied columns are\n * merged into the existing row. `options.conflictTarget` names the\n * unique-index columns to arbitrate on (e.g. `[\"user_id\", \"kind\"]`) and\n * every one must be present in `row`. Auto-RLS still applies — the\n * ignore-fallback / merge is scoped to the caller's own rows.\n *\n * @example\n * await Amba.collections.upsert(\"prefs\",\n * { kind: \"theme\", value: \"dark\" },\n * { onConflict: \"update\", conflictTarget: [\"kind\"] });\n */\n upsert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n options: UpsertOptions,\n ): Promise<T> => {\n // The core expects snake_case wire keys (`on_conflict` /\n // `conflict_target`); the public option object is camelCase.\n const wire = {\n on_conflict: options.onConflict,\n conflict_target: options.conflictTarget,\n };\n return (await coreCall((c) => c.collectionsUpsert(\n name,\n encodeJson(row),\n encodeJson(wire),\n ))) as T;\n },\n /**\n * Bulk soft-delete every row matching `filter`. Returns the count of\n * rows deleted. Auto-RLS scopes the delete to the caller's own rows;\n * soft-deleted rows can be brought back with {@link restore}.\n *\n * @example\n * const { data } = await Amba.collections.deleteWhere(\n * \"todos\", Amba.collections.where.eq(\"status\", \"done\"));\n * console.log(`${data.deleted} removed`);\n */\n deleteWhere: async (\n name: string,\n filter: Filter,\n ): Promise<DeleteManyResult> => {\n return (await coreCall((c) => c.collectionsDeleteWhere(\n name,\n encodeJson(filter),\n ))) as DeleteManyResult;\n },\n /**\n * Restore a previously soft-deleted row by id (clears `deleted_at`).\n * Returns the restored row. Only rows currently soft-deleted and owned\n * by the caller can be restored; anything else resolves as not-found.\n */\n restore: async <T = unknown>(name: string, id: string): Promise<T> => {\n return (await coreCall((c) => c.collectionsRestore(name, id))) as T;\n },\n /**\n * Run an ordered list of writes (insert / update / delete, across any\n * collections) as ONE atomic transaction. The whole batch commits, or — if\n * any op fails — rolls back entirely; a partial multi-write can never\n * persist. Resolves to the per-op results; throws naming the failing op\n * index (`details.opIndex`) on rollback. Each op honors the same auto-RLS /\n * owner-scoping / compare-and-set rules as the single-row methods.\n *\n * @example\n * await Amba.collections.transaction([\n * { collection: \"carts\", op: \"insert\", row: { sku: \"widget\", qty: 2 } },\n * { collection: \"inventory\", op: \"update\", id: invId, set: { reserved: 2 } },\n * ]);\n */\n transaction: async (\n operations: TransactionOp[],\n ): Promise<TransactionOpResult[]> => {\n const env = (await coreCall((c) =>\n c.collectionsTransaction(encodeJson(operations.map(toWireTransactionOp))),\n )) as { data: TransactionOpResult[] };\n return env.data;\n },\n /**\n * Vector-search the collection (SDK 4.0). Returns the `k` nearest\n * neighbors of `options.query_vector` against the column named\n * `options.vector_field`. Distance metric and index strategy are\n * server-side; pass `options.filter` to narrow the candidate set\n * before ranking.\n *\n * Spec note: the canonical `findNearest(name, vector, k, filter?)`\n * shape omits the column name; the SDK widens the contract to a\n * single typed options object so `vector_field` is required and\n * unambiguous in TS.\n */\n findNearest: async <T = unknown>(\n name: string,\n options: NearestOptions,\n ): Promise<NearestResponse<T>> => {\n return (await coreCall((c) => c.collectionsFindNearest(\n name,\n encodeJson(options),\n ))) as NearestResponse<T>;\n },\n /**\n * Count rows in the collection (SDK 4.0). Pass an optional filter\n * to scope; omit it for the total. The server's response unwraps to\n * a bare number on the JS side.\n */\n count: async (name: string, filter?: Filter): Promise<number> => {\n return (await coreCall((c) => c.collectionsCount(\n name,\n filter ? encodeJson(filter) : undefined,\n ))) as number;\n },\n /**\n * Optimistic insert — update a local view immediately, fire the real\n * insert, and roll the local view back if it fails. The reconcile is your\n * own (replace the temporary local row with the server's authoritative\n * row in your `apply`/`rollback` handlers). Thin convenience over the\n * exported {@link withOptimistic} so the common single-write case reads\n * clean; for multi-step flows use `withOptimistic` directly.\n *\n * Optimistic writes always hit the network directly — they are NOT\n * diverted to the offline outbox even when `Amba.offline` is enabled.\n * Optimistic UI needs an unambiguous outcome: this resolves only on a\n * confirmed server write, and rejects (running your `rollback`) on any\n * failure including being offline. To buffer-and-replay instead, use the\n * plain `insert`/`update`/`delete` with the outbox on.\n *\n * @example\n * await Amba.collections.optimisticInsert(\"todos\", draft, {\n * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },\n * rollback: (prev) => setTodos(prev),\n * });\n */\n optimisticInsert: async <T = unknown, TApply = unknown>(\n name: string,\n row: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => insertDirect<T>(name, row),\n rollback: view.rollback,\n }),\n /** Optimistic update — see {@link optimisticInsert}. */\n optimisticUpdate: async <T = unknown, TApply = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => updateDirect<T>(name, id, set),\n rollback: view.rollback,\n }),\n /** Optimistic delete — see {@link optimisticInsert}. */\n optimisticDelete: async <TApply = unknown>(\n name: string,\n id: string,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<{ data: { deleted: boolean } }> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => deleteDirect(name, id),\n rollback: view.rollback,\n }),\n /** Filter DSL helpers — pass into `find({ filter: f })`. */\n where: {\n eq: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"eq\",\n value,\n }),\n ne: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"ne\",\n value,\n }),\n gt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gt\",\n value,\n }),\n gte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gte\",\n value,\n }),\n lt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lt\",\n value,\n }),\n lte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lte\",\n value,\n }),\n in: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"in\",\n value: values,\n }),\n notIn: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"not_in\",\n value: values,\n }),\n like: (column: string, pattern: string): Filter => ({\n column,\n op: \"like\",\n value: pattern,\n }),\n ilike: (column: string, pattern: string): Filter => ({\n column,\n op: \"ilike\",\n value: pattern,\n }),\n isNull: (column: string): Filter => ({ column, op: \"is_null\" }),\n isNotNull: (column: string): Filter => ({ column, op: \"is_not_null\" }),\n and: (...filters: Filter[]): Filter => ({ and: filters }),\n or: (...filters: Filter[]): Filter => ({ or: filters }),\n not: (filter: Filter): Filter => ({ not: filter }),\n },\n },\n\n /** Media uploads + CDN URL helpers. */\n storage: {\n presign: async (params: {\n bucket: string;\n filename: string;\n mimeType: string;\n sizeBytes: number;\n retentionDays?: number;\n }): Promise<PresignData> => {\n return (await coreCall((c) => c.storagePresign(\n params.bucket,\n params.filename,\n params.mimeType,\n params.sizeBytes,\n params.retentionDays ?? undefined,\n ))) as PresignData;\n },\n /**\n * Complete-flow upload: presign → PUT to object storage → commit.\n * Returns the finalized MediaAsset.\n */\n upload: async (params: {\n bucket: string;\n file: File | Blob;\n filename?: string;\n retentionDays?: number;\n }): Promise<MediaAsset> => {\n const filename =\n params.filename ??\n (\"name\" in params.file ? params.file.name : \"upload\");\n const mimeType = params.file.type || \"application/octet-stream\";\n const presign = await Amba.storage.presign({\n bucket: params.bucket,\n filename,\n mimeType,\n sizeBytes: params.file.size,\n retentionDays: params.retentionDays,\n });\n\n const uploadHeaders: Record<string, string> = {};\n for (const [k, v] of presign.upload_headers) {\n uploadHeaders[k] = v;\n }\n const putResp = await fetch(presign.upload_url, {\n method: \"PUT\",\n headers: uploadHeaders,\n body: params.file,\n });\n if (!putResp.ok) {\n throw new Error(`upload to storage failed: status ${putResp.status}`);\n }\n\n return (await coreCall((c) => c.storageCommit(\n presign.upload_id,\n presign.asset_id,\n ))) as MediaAsset;\n },\n // <generated:storage>\n /**\n * List media assets (SDK 4.0). Pass `prefix` to scope to a key\n * prefix (filename / path-like). Without it, returns the caller's\n * full list. Server enforces RLS — only the calling user's assets\n * are returned.\n */\n list: async (prefix?: string): Promise<MediaListResponse> => {\n return (await coreCall((c) => c.storageList(\n prefix ?? undefined,\n ))) as MediaListResponse;\n },\n /**\n * Soft-delete the asset (SDK 4.0). Accepts the asset id returned\n * by `presign` / `commit` / `list`. The corresponding storage object\n * is removed asynchronously on the server.\n */\n delete: async (assetId: string): Promise<void> => {\n await coreCall((c) => c.storageDelete(assetId));\n },\n // </generated:storage>\n /**\n * Download an asset's bytes (SDK 4.0). Returns a `Blob` wrapping\n * the Uint8Array returned by the engine, so consumers can\n * call `.text()` / `.arrayBuffer()` / `.stream()` directly or\n * pipe to a `URL.createObjectURL()` for preview. The two-step\n * fetch (asset metadata → CDN GET) is handled inside the SDK\n * core; this method is single-call from the consumer.\n */\n download: async (assetId: string): Promise<Blob> => {\n const bytes = (await coreCall((c) => c.storageDownload(\n assetId,\n ))) as Uint8Array;\n // `Blob` is universally available in browsers and Node 18+,\n // so no platform-shim needed. Type defaults to\n // `application/octet-stream` — the caller knows the real type\n // from their original upload metadata if they need it.\n // Cast through `BlobPart` — under newer @types/node typings,\n // `Uint8Array<ArrayBufferLike>` widens to `ArrayBufferLike`\n // which TS refuses to narrow back to `BlobPart`'s\n // `ArrayBufferView<ArrayBuffer>` constituent. The runtime\n // is fine — `Blob` accepts any TypedArray.\n return new Blob([bytes as unknown as BlobPart]);\n },\n },\n\n /** Curated public-asset catalogs. */\n media: {\n /**\n * Fetch a public media catalog by slug\n * (`GET /v1/client/media/catalogs/:slug`). A catalog is a named,\n * curated bundle of assets your app loads in one call — each item\n * resolves to a stable, long-cacheable public URL, returned in the\n * order configured for the catalog. Only catalogs published as public\n * are readable; the call needs the client key but no signed-in user.\n */\n catalog: async (slug: string): Promise<MediaCatalog> => {\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before reading a media catalog.\",\n );\n }\n if (!slug || slug.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.media.catalog: a catalog slug is required.\",\n );\n }\n\n const url = `${_configuredApiUrl.replace(/\\/$/, \"\")}/v1/client/media/catalogs/${pathSegment(\n slug,\n )}`;\n let resp: Response;\n try {\n resp = await fetch(url, {\n method: \"GET\",\n headers: { \"X-Api-Key\": _configuredClientKey },\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.media.catalog(\"${slug}\") network error: ${String(err)}`,\n );\n }\n\n const text = await resp.text();\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = undefined;\n }\n }\n if (!resp.ok) {\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\"\n ? envelope.error.code\n : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : `Amba.media.catalog(\"${slug}\") failed with status ${resp.status}`;\n throw new AmbaApiError(code, message, parsed);\n }\n return (parsed as { data: MediaCatalog }).data;\n },\n },\n\n /** Offline change replay (SDK 4.0). */\n sync: {\n /**\n * Replay a batch of buffered mutations\n * (`POST /v1/client/sync`). The server applies them in order and\n * returns the count of successfully-applied changes plus any\n * per-row conflicts. Conflict resolution in 4.0 is always\n * server-wins; the `SyncConflict.resolution` field is reserved\n * for future strategies.\n */\n pushChanges: async (\n changes: SyncChange[],\n ): Promise<PushChangesResult> => {\n return (await coreCall((c) => c.syncPushChanges(\n encodeJson(changes),\n ))) as PushChangesResult;\n },\n /**\n * Pull remote-origin changes since the supplied cursor\n * (`GET /v1/client/sync`). Pass the `checkpoint_token` from the\n * previous `pushChanges` / `pullChanges` reply, and the\n * `entity_type` to scope to a single collection. On the first\n * call, omit `checkpoint_token` to receive the full backfill.\n */\n pullChanges: async (\n since: PullChangesCursor,\n ): Promise<PullChangesResult> => {\n return (await coreCall((c) => c.syncPullChanges(\n encodeJson(since),\n ))) as PullChangesResult;\n },\n },\n\n /**\n * Automatic offline mutation queue (opt-in).\n *\n * Once enabled, a collection write (`insert` / `update` / `delete`) or a\n * `track` that fails because the device is offline is buffered to a durable\n * local outbox instead of throwing — then replayed in order the moment\n * connectivity returns. Replays reuse a per-op idempotency key so a write\n * that actually reached the server before the connection dropped is never\n * applied twice.\n *\n * This is distinct from `Amba.sync` (manual change replay): `offline` is\n * the automatic, fire-and-forget safety net for ordinary writes.\n *\n * @example\n * Amba.offline.enable(); // call once after configure()\n * await Amba.collections.insert(\"todos\", { title: \"Buy milk\" }); // queues if offline\n * // ...reconnect...\n * console.log(await Amba.offline.pendingCount()); // drains automatically\n */\n offline: {\n /**\n * Turn the outbox on and start auto-flushing on reconnect. Idempotent —\n * a repeat call detaches the prior `online` listener before re-attaching,\n * so it never stacks listeners. Immediately attempts a flush in case ops\n * were persisted in a prior session. Returns a disable function that\n * detaches the reconnect listener (queued ops are NOT dropped — call\n * `clear()` for that).\n *\n * The disabler is tied to THIS activation: once a later `enable()` (or a\n * `configure()`) supersedes it, calling it is a no-op — so a stale disabler\n * can never turn a freshly re-enabled outbox off.\n */\n enable: (): (() => void) => {\n // Detach any prior listener first so a re-`enable()` (or one after a\n // reconfigure that left wiring) can't leak / stack listeners.\n if (_offlineDetach) {\n _offlineDetach();\n _offlineDetach = null;\n }\n _offlineEnabled = true;\n // Bump + capture the activation token so this call's disabler only acts\n // while it's still the current activation.\n _offlineEnableToken += 1;\n const token = _offlineEnableToken;\n const q = getOfflineQueue();\n _offlineDetach = q.startAutoFlush();\n // Best-effort drain of anything persisted from a previous run.\n void q.flush();\n return () => {\n // No-op if a later enable() or a configure() superseded this activation\n // — the token moved on, so this disabler is stale and must not flip the\n // active outbox off or detach the current listener.\n if (_offlineEnableToken !== token) return;\n _offlineEnabled = false;\n if (_offlineDetach) {\n _offlineDetach();\n _offlineDetach = null;\n }\n };\n },\n /** Whether the outbox is currently enabled. */\n isEnabled: (): boolean => _offlineEnabled,\n /** Count of writes awaiting replay. */\n pendingCount: async (): Promise<number> => getOfflineQueue().pendingCount(),\n /** Snapshot the pending ops (a copy — safe to inspect). */\n pending: async (): Promise<OutboxOp[]> => getOfflineQueue().pending(),\n /**\n * Force a replay attempt now (also runs automatically on reconnect).\n * Resolves with `{ flushed, dropped, remaining }` (dropped = ops the server rejected deterministically on replay, removed so they cannot wedge the queue).\n */\n flush: async (): Promise<FlushResult> => getOfflineQueue().flush(),\n /** Drop every queued write without replaying. */\n clear: async (): Promise<void> => getOfflineQueue().clear(),\n },\n\n /** Push notifications. */\n push: {\n register: async (\n token: string,\n platform: PushPlatform,\n bundleId?: string,\n ): Promise<PushToken> => {\n return (await coreCall((c) => c.pushRegister(\n token,\n platform,\n bundleId ?? undefined,\n ))) as PushToken;\n },\n unregister: async (token: string): Promise<void> => {\n await coreCall((c) => c.pushUnregister(token));\n },\n getTokens: async (): Promise<PushToken[]> => {\n return (await coreCall((c) => c.pushGetTokens())) as PushToken[];\n },\n subscribe: async (topic: string): Promise<void> => {\n await coreCall((c) => c.pushSubscribe(topic));\n },\n unsubscribe: async (topic: string): Promise<void> => {\n await coreCall((c) => c.pushUnsubscribe(topic));\n },\n /**\n * Auto re-register the device's Web Push subscription when the browser\n * rotates it.\n *\n * The browser can silently roll a push subscription (key rollover,\n * permission re-grant); when it does, the service worker fires\n * `pushsubscriptionchange` and the old endpoint stops delivering. Call\n * this from your service worker (where that event fires) and the SDK\n * re-registers the fresh endpoint through the idempotent\n * {@link register} path automatically.\n *\n * No-op (returns a no-op detacher) in runtimes without service-worker /\n * push support, so it's safe to call unconditionally. Returns an\n * unsubscribe function.\n *\n * @example\n * // in your service worker\n * Amba.push.enableAutoRotation();\n */\n enableAutoRotation: (): (() => void) =>\n enableWebPushAutoRotation((token) => Amba.push.register(token, \"web\")),\n },\n\n /**\n * On-device local notifications — `scheduleLocal` / `cancelLocal` /\n * `listScheduledLocal`. Fire from the device without a server\n * round-trip (reminders, streak nudges), distinct from remote push\n * (`Amba.push.*`). Browser scheduling is partial: the OS-backed path\n * needs the experimental Notification Triggers API + an active Service\n * Worker (Chromium today); without it the SDK falls back to an in-page\n * timer and reports `scheduledVia: \"timer\"` so the degradation is\n * visible — never a silent no-op. See `./local-notifications.ts`.\n */\n notifications,\n\n /** Subscription entitlements — feature gates resolved server-side. */\n entitlements: {\n // <generated:entitlements.list>\n list: async (): Promise<UserEntitlement[]> => {\n return (await coreCall((c) => c.entitlementsList())) as UserEntitlement[];\n },\n // </generated:entitlements.list>\n // <generated:entitlements.has>\n has: async (name: string): Promise<boolean> => {\n return (await coreCall((c) => c.entitlementsHas(name))) as boolean;\n },\n // </generated:entitlements.has>\n // <generated:entitlements.restore>\n /**\n * Restore purchases — re-sync the user's owned entitlements from the\n * server and return the active set. Required for App Store review (3.1.1);\n * wire it to a \"Restore Purchases\" button. Rejects (access unchanged) if\n * the server can't reach the validation engine — show a retry message.\n */\n restore: async (): Promise<RestoreResult> => {\n return (await coreCall((c) => c.entitlementsRestore())) as RestoreResult;\n },\n // </generated:entitlements.restore>\n },\n\n // <generated:offerings>\n /**\n * Subscription offerings — the packages your paywall renders, provider-\n * neutral. Pass an `offeringId` to fetch one offering; omit it for all.\n */\n offerings: async (offeringId?: string): Promise<OfferingsResult> => {\n return (await coreCall((c) =>\n offeringId ? c.offeringsGet(offeringId) : c.offeringsList(),\n )) as OfferingsResult;\n },\n // </generated:offerings>\n\n /** LLM proxy. */\n ai: {\n anthropic: {\n messages: {\n create: async (\n request: AiMessageRequest,\n ): Promise<AiMessageResponse> => {\n // Expand convenience `images` into content blocks; text-only\n // requests pass through unchanged (same object reference).\n return (await coreCall((c) => c.aiAnthropicMessages(\n encodeJson(expandAiImages(request)),\n ))) as AiMessageResponse;\n },\n /**\n * Stream the response token-by-token instead of waiting for the full\n * buffered reply. Returns an async iterable of vendor-neutral events:\n * zero or more `{type:'delta', text}` as tokens arrive, then exactly\n * one `{type:'done', usage, model, stopReason}` when the stream ends.\n *\n * ```ts\n * let answer = \"\";\n * for await (const ev of Amba.ai.anthropic.messages.stream({ prompt_slug: \"chat\" })) {\n * if (ev.type === \"delta\") answer += ev.text;\n * else console.log(\"tokens:\", ev.usage.output_tokens);\n * }\n * ```\n *\n * Pass `{ onChunk }` to also receive each delta via callback. Throws\n * an `AmbaApiError` on a transport failure or a non-2xx response.\n */\n stream: (\n request: AiMessageRequest,\n options?: AiStreamOptions,\n ): AsyncGenerator<AiStreamEvent, void, unknown> => {\n const { url, headers, body } = buildAiStreamRequest(\n AI_ANTHROPIC_PATH,\n request,\n );\n return invokeAiStream({ url, headers, body, onChunk: options?.onChunk });\n },\n },\n },\n openai: {\n chat: {\n completions: {\n /**\n * Stream an OpenAI-routed prompt token-by-token. Same event shape\n * as `Amba.ai.anthropic.messages.stream` — the vendor split is just\n * a stable wire path; the server resolves the real provider from the\n * prompt's registered configuration.\n */\n stream: (\n request: AiMessageRequest,\n options?: AiStreamOptions,\n ): AsyncGenerator<AiStreamEvent, void, unknown> => {\n const { url, headers, body } = buildAiStreamRequest(\n AI_OPENAI_PATH,\n request,\n );\n return invokeAiStream({ url, headers, body, onChunk: options?.onChunk });\n },\n },\n },\n },\n },\n\n /**\n * Invoke a deployed function by name, with the project's client key (and\n * the live session token, if any) injected automatically.\n *\n * This is the authenticated way to call your functions — prefer it over\n * hand-rolling `fetch` against a function URL, which leaves the call\n * unauthenticated (a footgun when the function holds secrets).\n *\n * The function host is selected in order from `functionsBaseUrl`, a\n * UUID-shaped configured project id, or a deduped diagnostics ping that\n * supplies the authoritative server-side project id.\n *\n * The return value is unchanged (the parsed body, typed `T`). To\n * correlate a run with server logs, read the trace id additively:\n * pass `options.onTraceId` to receive the `X-Amba-Request-Id` on every\n * response, and on failure read `error.traceId` off the thrown\n * `AmbaApiError`. Then `functions logs --request-id <traceId>` pulls the\n * matching server log.\n *\n * @example\n * const result = await Amba.functions.invoke<{ ok: boolean }>(\n * 'send_welcome_email',\n * { to: 'user@example.com' },\n * { onTraceId: (id) => console.log('trace', id) },\n * );\n *\n * @example\n * try {\n * await Amba.functions.invoke('charge', { amount: 100 });\n * } catch (err) {\n * if (err instanceof AmbaApiError) {\n * console.error(`failed (trace ${err.traceId}): ${err.message}`);\n * }\n * }\n *\n * @param name The deployed function name.\n * @param body JSON-serializable request body (omitted for GET).\n * @param options Method, extra headers, and an `onTraceId` callback.\n * @returns The parsed JSON response (or the raw text if not JSON).\n * @throws AmbaApiError on a network failure or a non-2xx response. When\n * the function / router returns the standard `{ error: { code, message } }`\n * envelope, those surface as the thrown error's `code` + `message`; the\n * server trace id (when present) is attached as `error.traceId`.\n */\n functions: {\n invoke: async <T = unknown>(\n name: string,\n body?: unknown,\n options?: InvokeOptions,\n ): Promise<T> => {\n // Enforce configured state (consistent with every other method) and\n // capture the client key the function router authenticates against.\n requireCore();\n if (!_configuredClientKey) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before Amba.functions.invoke().\",\n );\n }\n if (!name || name.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.functions.invoke: a function name is required.\",\n );\n }\n\n const url = await resolveFunctionUrl(name);\n const method = options?.method ?? \"POST\";\n const headers: Record<string, string> = {\n \"X-Api-Key\": _configuredClientKey,\n ...options?.headers,\n };\n // Inject the live session token so the function runs in the end-user's\n // authenticated context. Anonymous (pre-sign-in) invokes still carry\n // the client key, which the router accepts for non-public functions.\n if (_currentSessionToken) {\n headers[\"Authorization\"] = `Bearer ${_currentSessionToken}`;\n }\n let requestBody: string | undefined;\n if (body !== undefined && method !== \"GET\") {\n headers[\"content-type\"] = \"application/json\";\n requestBody = JSON.stringify(body);\n }\n\n let resp: Response;\n try {\n resp = await fetch(url, { method, headers, body: requestBody });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.functions.invoke(\"${name}\") network error: ${String(err)}`,\n );\n }\n\n const text = await resp.text();\n let parsed: unknown = undefined;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n // Function returned non-JSON (e.g. plain text / HTML) — pass it\n // through verbatim rather than guessing a shape.\n parsed = text;\n }\n }\n\n // Surface the server-side trace id. The router stamps it on the\n // `X-Amba-Request-Id` response header (post-auth) and also embeds it\n // in the error envelope — so we prefer the header and fall back to\n // the body when a pre-auth rejection stripped it from the header.\n // `functions logs --request-id <traceId>` lets a caller pull the\n // matching server log for a failed run.\n //\n // The router envelope nests it as `error.request_id`\n // (apps/edge-router/src/errors.ts) — that's the field pre-auth\n // rejections (which omit the header) actually carry. We also accept a\n // top-level `request_id` as a forward-compatible fallback.\n const envelopeRequestId =\n parsed && typeof parsed === \"object\"\n ? ((parsed as { error?: { request_id?: unknown } }).error\n ?.request_id ?? (parsed as { request_id?: unknown }).request_id)\n : undefined;\n const traceId =\n resp.headers.get(\"X-Amba-Request-Id\") ??\n (typeof envelopeRequestId === \"string\" ? envelopeRequestId : null);\n options?.onTraceId?.(traceId);\n\n if (!resp.ok) {\n const envelope =\n parsed && typeof parsed === \"object\"\n ? (parsed as { error?: { code?: unknown; message?: unknown } })\n : undefined;\n const code =\n typeof envelope?.error?.code === \"string\"\n ? envelope.error.code\n : `HTTP_${resp.status}`;\n const message =\n typeof envelope?.error?.message === \"string\"\n ? envelope.error.message\n : typeof parsed === \"string\" && parsed.length > 0\n ? parsed\n : `Amba.functions.invoke(\"${name}\") failed with status ${resp.status}`;\n const error = new AmbaApiError(code, message, parsed);\n if (traceId !== null) error.traceId = traceId;\n throw error;\n }\n\n return parsed as T;\n },\n },\n\n /** Remote config bundle. */\n config: {\n // <generated:config>\n fetch: async (): Promise<ConfigBundle> => {\n return (await coreCall((c) => c.configFetch())) as ConfigBundle;\n },\n // </generated:config>\n },\n\n /** Server-evaluated feature flags. */\n flags: {\n // <generated:flags>\n fetch: async (): Promise<FlagAssignment[]> => {\n return (await coreCall((c) => c.flagsFetch())) as FlagAssignment[];\n },\n /**\n * Single-flag lookup (SDK 4.0). Wraps `GET /v1/client/flags/{key}`.\n * Returns `null` for unknown / disabled keys; other errors\n * (auth, rate-limit, network) propagate.\n */\n get: async (key: string): Promise<FlagAssignment | null> => {\n return (await coreCall((c) => c.flagsGet(key))) as FlagAssignment | null;\n },\n // </generated:flags>\n },\n\n /**\n * Wire-verify primitive. Call once after `configure()` to confirm\n * the SDK is talking to the right project with the right key.\n *\n * The response is entirely server-decided — none of the fields are\n * derived from request parameters — so it's trustworthy as a setup\n * check. Common usage:\n *\n * ```ts\n * const ping = await Amba.diagnostics.ping();\n * if (!ping.ok || ping.server_project_id !== expectedProjectId) {\n * throw new Error(`amba misconfigured: ${JSON.stringify(ping)}`);\n * }\n * ```\n */\n diagnostics: {\n ping: async (): Promise<PingResult> => {\n return (await coreCall((c) => c.diagnosticsPing())) as PingResult;\n },\n },\n\n // ── Gamification ──────────────────────────────────────────────────\n\n /** Achievement definitions + per-user progress. */\n achievements: {\n // <generated:achievements>\n getAll: async (): Promise<Achievement[]> =>\n (await coreCall((c) => c.achievementsGetAll())) as Achievement[],\n getProgress: async (): Promise<AchievementProgress[]> =>\n (await coreCall((c) => c.achievementsGetProgress())) as AchievementProgress[],\n // </generated:achievements>\n },\n\n /** Time-bounded challenges with rewards. */\n challenges: {\n // <generated:challenges>\n getActive: async (): Promise<Challenge[]> =>\n (await coreCall((c) => c.challengesGetActive())) as Challenge[],\n get: async (id: string): Promise<Challenge> =>\n (await coreCall((c) => c.challengesGet(id))) as Challenge,\n getProgress: async (id: string): Promise<ChallengeProgress> =>\n (await coreCall((c) => c.challengesGetProgress(id))) as ChallengeProgress,\n claim: async (id: string): Promise<ChallengeProgress> =>\n (await coreCall((c) => c.challengesClaim(id))) as ChallengeProgress,\n // </generated:challenges>\n },\n\n /** Virtual currency balances + ledger. */\n currencies: {\n // <generated:currencies>\n getBalance: async (): Promise<CurrencyBalance[]> =>\n (await coreCall((c) => c.currenciesGetBalance())) as CurrencyBalance[],\n getTransactions: async (\n currencyKey: string,\n ): Promise<CurrencyTransaction[]> =>\n (await coreCall((c) => c.currenciesGetTransactions(\n currencyKey,\n ))) as CurrencyTransaction[],\n // </generated:currencies>\n },\n\n /** User inventory items + purchase/consume. */\n inventory: {\n // <generated:inventory>\n getItems: async (): Promise<InventoryItem[]> =>\n (await coreCall((c) => c.inventoryGetItems())) as InventoryItem[],\n getItem: async (id: string): Promise<InventoryItem> =>\n (await coreCall((c) => c.inventoryGetItem(id))) as InventoryItem,\n purchase: async (request: PurchaseRequest): Promise<InventoryItem> =>\n (await coreCall((c) => c.inventoryPurchase(\n encodeJson(request),\n ))) as InventoryItem,\n consume: async (request: ConsumeRequest): Promise<InventoryItem> =>\n (await coreCall((c) => c.inventoryConsume(\n encodeJson(request),\n ))) as InventoryItem,\n // </generated:inventory>\n },\n\n /** Score rankings. */\n leaderboards: {\n // <generated:leaderboards>\n get: async (key: string): Promise<Leaderboard> =>\n (await coreCall((c) => c.leaderboardsGet(key))) as Leaderboard,\n getEntries: async (\n key: string,\n limit?: number,\n ): Promise<LeaderboardEntry[]> =>\n (await coreCall((c) => c.leaderboardsGetEntries(\n key,\n limit ?? undefined,\n ))) as LeaderboardEntry[],\n getMyRank: async (key: string): Promise<LeaderboardEntry> =>\n (await coreCall((c) => c.leaderboardsGetMyRank(key))) as LeaderboardEntry,\n // </generated:leaderboards>\n },\n\n /**\n * Tiered weekly leaderboard cohorts (SDK 4.0). Each user is bucketed\n * into a small cohort (~25 members) within their league tier; the\n * top scorers each week promote and the bottom scorers demote.\n */\n leagues: {\n // <generated:leagues>\n /**\n * `GET /v1/client/leagues/me` — the caller's active-cohort\n * standing plus live rank/score and member count.\n */\n me: async (): Promise<LeagueMembership> => {\n return (await coreCall((c) => c.leaguesMe())) as LeagueMembership;\n },\n /**\n * `GET /v1/client/leagues/me/cohort` — the caller's cohort with\n * every member's anonymised standing (display name, score, rank).\n * Notably no user ids — the server strips them so a leaderboard\n * UI can render the cohort without exposing identities.\n */\n cohort: async (): Promise<LeagueCohortView> => {\n return (await coreCall((c) => c.leaguesCohort())) as LeagueCohortView;\n },\n // </generated:leagues>\n },\n\n /** Purchase storefronts. */\n stores: {\n // <generated:stores>\n list: async (): Promise<Store[]> =>\n (await coreCall((c) => c.storesList())) as Store[],\n getPurchaseOptions: async (storeKey: string): Promise<PurchaseOption[]> =>\n (await coreCall((c) => c.storesGetPurchaseOptions(\n storeKey,\n ))) as PurchaseOption[],\n purchase: async (\n storeKey: string,\n purchaseOptionId: string,\n receipt: unknown,\n ): Promise<PurchaseResult> =>\n // `JSON.stringify(undefined)` returns the JS value `undefined`,\n // not a string — passing that to the SDK core's `storesPurchase` binding\n // crashes. Default the missing-receipt case to an empty object so\n // the server still receives valid JSON (BugBot Low 2026-05-19).\n (await coreCall((c) => c.storesPurchase(\n storeKey,\n purchaseOptionId,\n encodeJson(receipt ?? {}),\n ))) as PurchaseResult,\n // </generated:stores>\n },\n\n /** Experience points + level system. */\n xp: {\n // <generated:xp>\n getBalance: async (): Promise<XpBalance> =>\n (await coreCall((c) => c.xpGetBalance())) as XpBalance,\n getHistory: async (limit?: number): Promise<XpTransaction[]> =>\n (await coreCall((c) => c.xpGetHistory(\n limit ?? undefined,\n ))) as XpTransaction[],\n claim: async (grantKey: string): Promise<XpTransaction> =>\n (await coreCall((c) => c.xpClaim(grantKey))) as XpTransaction,\n // </generated:xp>\n },\n\n /** Daily-engagement streaks. */\n streaks: {\n // <generated:streaks>\n getAll: async (): Promise<Streak[]> =>\n (await coreCall((c) => c.streaksGetAll())) as Streak[],\n qualify: async (streakKey: string): Promise<Streak> =>\n (await coreCall((c) => c.streaksQualify(streakKey))) as Streak,\n // </generated:streaks>\n },\n\n /**\n * Live gamification updates for the signed-in user. Call `.subscribe(cb)` to\n * receive xp / level / achievement / streak changes the instant they happen,\n * instead of polling `xp.getBalance()` / `streaks.getAll()` on a timer.\n *\n * @example\n * const unsub = Amba.gamification.subscribe((change) => {\n * // change.kind: \"xp\" | \"level\" | \"achievement\" | \"streak\"\n * if (change.kind === \"level\") showLevelUp(change.data.level);\n * });\n * // later: unsub();\n *\n * You only ever receive your own gamification changes — never another user's.\n * Requires an authenticated session.\n */\n get gamification(): GamificationSubscription {\n // Getter (not an eager value) so `baseUrl` is read at access time — the\n // `Amba` object literal evaluates at import, BEFORE `init()` sets\n // `_configuredApiUrl`, so capturing it eagerly would pin the default URL.\n return new GamificationSubscription({\n baseUrl: _configuredApiUrl,\n authHeaders: realtimeAuthHeaders,\n });\n },\n\n // ── Social ─────────────────────────────────────────────────────────\n\n /** Activity feeds. */\n feeds: {\n // <generated:feeds>\n getActivity: async (\n feed?: string,\n cursor?: string,\n ): Promise<FeedResponse> =>\n (await coreCall((c) => c.feedsGetActivity(\n feed ?? undefined,\n cursor ?? undefined,\n ))) as FeedResponse,\n // </generated:feeds>\n },\n\n /** Friend graph + block list. */\n friends: {\n // <generated:friends>\n getList: async (): Promise<Friendship[]> =>\n (await coreCall((c) => c.friendsGetList())) as Friendship[],\n getFriends: async (): Promise<Friendship[]> =>\n (await coreCall((c) => c.friendsGetFriends())) as Friendship[],\n /**\n * Send a friend request to `userId` (SDK 4.0). Returns the\n * resulting `Friendship` row in `pending` state.\n */\n sendRequest: async (userId: string): Promise<Friendship> =>\n (await coreCall((c) => c.friendsSendRequest(userId))) as Friendship,\n /**\n * Accept a pending incoming friend request (SDK 4.0). Pass the\n * friendship id from `getList()`.\n */\n acceptRequest: async (friendshipId: string): Promise<Friendship> =>\n (await coreCall((c) => c.friendsAcceptRequest(\n friendshipId,\n ))) as Friendship,\n /**\n * Decline a pending incoming friend request (SDK 4.0). Removes\n * the pending row; idempotent.\n */\n declineRequest: async (friendshipId: string): Promise<void> => {\n await coreCall((c) => c.friendsDeclineRequest(friendshipId));\n },\n blockUser: async (userId: string): Promise<Friendship> =>\n (await coreCall((c) => c.friendsBlockUser(userId))) as Friendship,\n unblockUser: async (userId: string): Promise<void> => {\n await coreCall((c) => c.friendsUnblockUser(userId));\n },\n removeBlock: async (friendshipId: string): Promise<void> => {\n await coreCall((c) => c.friendsRemoveBlock(friendshipId));\n },\n /**\n * Unfriend by the other user's id (SDK 4.0). Wraps\n * `DELETE /v1/client/friends/by-user/{userId}`. Server preserves\n * blocked rows; use `unblockUser` to clear those. 404 propagates\n * as `AmbaApiError`.\n */\n removeFriend: async (userId: string): Promise<void> => {\n await coreCall((c) => c.friendsRemoveFriend(userId));\n },\n // </generated:friends>\n },\n\n /** Multi-user groups. */\n groups: {\n // <generated:groups>\n create: async (params: GroupCreate): Promise<Group> =>\n (await coreCall((c) => c.groupsCreate(encodeJson(params)))) as Group,\n get: async (id: string): Promise<Group> =>\n (await coreCall((c) => c.groupsGet(id))) as Group,\n update: async (id: string, patch: GroupUpdate): Promise<Group> =>\n (await coreCall((c) => c.groupsUpdate(id, encodeJson(patch)))) as Group,\n delete: async (id: string): Promise<void> => {\n await coreCall((c) => c.groupsDelete(id));\n },\n getMembers: async (id: string): Promise<GroupMember[]> =>\n (await coreCall((c) => c.groupsGetMembers(id))) as GroupMember[],\n join: async (id: string): Promise<GroupMember> =>\n (await coreCall((c) => c.groupsJoin(id))) as GroupMember,\n leave: async (id: string): Promise<void> => {\n await coreCall((c) => c.groupsLeave(id));\n },\n invite: async (id: string, userId: string): Promise<GroupMember> =>\n (await coreCall((c) => c.groupsInvite(id, userId))) as GroupMember,\n // </generated:groups>\n },\n\n /**\n * User-to-user direct messaging.\n *\n * Paths mirror the API exactly — see `apps/api/src/routes/client/messaging.ts`:\n *\n * | SDK method | API route |\n * | --------------------- | -------------------------------------------------------- |\n * | `createConversation` | POST `/v1/client/messaging/conversations` |\n * | `getConversations` | GET `/v1/client/messaging/conversations` |\n * | `listConversations` | (alias for `getConversations`) |\n * | `sendMessage` | POST `/v1/client/messaging/conversations/:id/messages` |\n * | `listMessages` | GET `/v1/client/messaging/conversations/:id/messages` |\n * | `markRead` | POST `/v1/client/messaging/conversations/:id/read` |\n *\n * Task #62 corrected `sendMessage`'s path (it previously POSTed to\n * `/messaging/messages`, a route that doesn't exist on the API), and\n * added the four missing helpers. The previous `getMessage(id)` helper\n * has been removed — there is no GET-by-id route on the server; the\n * shape consumers actually want is `listMessages(conversationId, opts)`.\n */\n messaging: {\n // <generated:messaging>\n /**\n * `POST /v1/client/messaging/conversations` — create (or fetch the\n * existing direct) conversation. For `type: \"direct\"` with exactly\n * one other participant, the server returns the existing row if\n * one already exists between these two users — calls are\n * idempotent.\n */\n createConversation: async (\n request: CreateConversationRequest,\n ): Promise<Conversation> =>\n (await coreCall((c) => c.messagingCreateConversation(\n encodeJson(request),\n ))) as Conversation,\n\n /**\n * `GET /v1/client/messaging/conversations` — list the caller's\n * conversations. Canonical 4.0 name; the legacy `getConversations`\n * / `listConversations` are removed in 4.0 per the spec.\n */\n conversations: async (): Promise<Conversation[]> =>\n (await coreCall((c) => c.messagingGetConversations())) as Conversation[],\n\n /**\n * `POST /v1/client/messaging/conversations/:conversationId/messages`\n * — send a message into an existing conversation. The\n * `conversation_id` argument is a path parameter (not a body\n * field) — the previous SDK shape mistakenly placed it in the\n * body, which the API ignored because the route was wrong anyway.\n */\n sendMessage: async (\n conversationId: string,\n request: SendMessageRequest,\n ): Promise<Message> =>\n // The engine ABI takes a single JSON arg with\n // `conversation_id` inline — we provide a cleaner two-positional\n // shape to TS callers and re-fold conversation_id into the\n // payload here. The engine extracts it before posting.\n (await coreCall((c) => c.messagingSendMessage(\n encodeJson({ ...request, conversation_id: conversationId }),\n ))) as Message,\n\n /**\n * `GET /v1/client/messaging/conversations/:conversationId/messages`\n * — list messages in a conversation. Pagination uses `?limit=&offset=`\n * (the server's `parsePagination` clamps both; defaults to 50 / 0).\n */\n listMessages: async (\n conversationId: string,\n opts?: ListMessagesOptions,\n ): Promise<Message[]> =>\n (await coreCall((c) => c.messagingListMessages(\n conversationId,\n opts?.limit ?? undefined,\n opts?.offset ?? undefined,\n ))) as Message[],\n\n /**\n * `GET /v1/client/messaging/conversations/:conversationId/messages/:messageId`\n * — fetch a single message by id. Returns `null` when the message\n * doesn't exist or the caller can't see it. (SDK 4.0: Phase A\n * shipped the missing `amba_messaging_get_message` core symbol the\n * native SDKs were already trying to call.)\n */\n getMessage: async (\n conversationId: string,\n messageId: string,\n ): Promise<Message | null> => {\n return (await coreCall((c) => c.messagingGetMessage(\n conversationId,\n messageId,\n ))) as Message | null;\n },\n\n /**\n * `POST /v1/client/messaging/conversations/:conversationId/participants`\n * — add a participant to an existing conversation. The caller must\n * already be a participant (the server returns `403\n * NOT_A_PARTICIPANT` otherwise) — this is the peer-invite path.\n * Idempotent: re-adding an existing member returns the existing\n * participant row, not an error. Enables dynamic membership growth\n * (e.g. a circle whose members qualify over time). Server-side\n * membership management (admin/matchmaker) uses the admin\n * add-participant endpoint instead.\n */\n addParticipant: async (\n conversationId: string,\n userId: string,\n ): Promise<ConversationParticipant> =>\n (await coreCall((c) => c.messagingAddParticipant(\n conversationId,\n userId,\n ))) as ConversationParticipant,\n\n /**\n * `POST /v1/client/messaging/conversations/:conversationId/read`\n * — bump the caller's `last_read_at` for the conversation. Returns\n * the updated participant row.\n */\n markRead: async (conversationId: string): Promise<MarkReadResult> =>\n (await coreCall((c) => c.messagingMarkRead(\n conversationId,\n ))) as MarkReadResult,\n // </generated:messaging>\n\n /**\n * Live message subscriptions (SDK 4.0). Returns a builder — call\n * `.subscribe(cb)` to receive new messages in a conversation live\n * instead of polling `listMessages`. `subscribe` returns an\n * unsubscribe handle.\n *\n * @example\n * const unsub = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => {\n * // change.op === \"INSERT\"; change.row is the new message\n * appendMessageToThread(change.row);\n * });\n * // later: unsub();\n *\n * Delivery is scoped to conversation participants — you receive a\n * conversation's messages only if your session is a member of it.\n * Requires an authenticated session.\n */\n conversation: <T = Message>(conversationId: string): ConversationSubscription<T> =>\n new ConversationSubscription<T>(\n { baseUrl: _configuredApiUrl, authHeaders: realtimeAuthHeaders },\n conversationId,\n ),\n },\n\n /** Report users / content for moderation. */\n moderation: {\n reportUser: async (request: ReportRequest): Promise<Report> =>\n (await coreCall((c) => c.moderationReportUser(\n encodeJson(request),\n ))) as Report,\n reportContent: async (request: ReportRequest): Promise<Report> =>\n (await coreCall((c) => c.moderationReportContent(\n encodeJson(request),\n ))) as Report,\n getReportStatus: async (id: string): Promise<Report> =>\n (await coreCall((c) => c.moderationGetReportStatus(id))) as Report,\n },\n\n /** User reviews of catalog items or other users. */\n reviews: {\n // <generated:reviews>\n list: async (targetType: string, targetId: string): Promise<Review[]> =>\n (await coreCall((c) => c.reviewsList(targetType, targetId))) as Review[],\n create: async (params: ReviewCreate): Promise<Review> =>\n (await coreCall((c) => c.reviewsCreate(encodeJson(params)))) as Review,\n update: async (id: string, patch: ReviewUpdate): Promise<Review> =>\n (await coreCall((c) => c.reviewsUpdate(id, encodeJson(patch)))) as Review,\n delete: async (id: string): Promise<void> => {\n await coreCall((c) => c.reviewsDelete(id));\n },\n // </generated:reviews>\n },\n\n /** RBAC roles + permission checks. */\n roles: {\n // <generated:roles>\n getMyRoles: async (): Promise<Role[]> =>\n (await coreCall((c) => c.rolesGetMyRoles())) as Role[],\n hasPermission: async (permission: string): Promise<boolean> =>\n (await coreCall((c) => c.rolesHasPermission(permission))) as boolean,\n // </generated:roles>\n },\n\n /** Referral codes + claim flow. */\n referrals: {\n // <generated:referrals>\n getReferralCode: async (): Promise<ReferralCode> =>\n (await coreCall((c) => c.referralsGetReferralCode())) as ReferralCode,\n claimReferral: async (code: string): Promise<ReferralClaim> =>\n (await coreCall((c) => c.referralsClaimReferral(code))) as ReferralClaim,\n create: async (\n code?: string,\n maxUses?: number,\n ): Promise<ReferralCode> =>\n (await coreCall((c) => c.referralsCreate(\n code ?? undefined,\n maxUses ?? undefined,\n ))) as ReferralCode,\n // </generated:referrals>\n },\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n /** Read-only product catalog. */\n catalog: {\n // <generated:catalog>\n list: async (): Promise<CatalogItem[]> =>\n (await coreCall((c) => c.catalogList())) as CatalogItem[],\n /**\n * Fetch a single catalog item by id (SDK 4.0).\n * `GET /v1/client/catalog/:id`.\n */\n get: async (id: string): Promise<CatalogItem> =>\n (await coreCall((c) => c.catalogGet(id))) as CatalogItem,\n // </generated:catalog>\n },\n\n /** Developer-defined content items (daily quotes, lessons, etc.). */\n content: {\n // <generated:content>\n /**\n * Get today's published item for `channel`. Defaults to `\"default\"`\n * so single-channel apps can call `Amba.content.today()` bare.\n * (Canonical 4.0 name; legacy `getToday` is removed.)\n */\n today: async (channel: string = \"default\"): Promise<ContentItem | null> =>\n (await coreCall((c) => c.contentGetToday(channel))) as ContentItem | null,\n /**\n * Paginated library list for `channel`. `options.limit` caps page size\n * (server default applies when omitted); `options.cursor` is the\n * opaque token returned by a prior response's `next_cursor`.\n * (Canonical 4.0 name; legacy `getLibrary` is removed.)\n */\n library: async (\n channel: string = \"default\",\n options?: { limit?: number; cursor?: string },\n ): Promise<ContentItem[]> =>\n (await coreCall((c) => c.contentGetLibrary(\n channel,\n options?.limit ?? undefined,\n options?.cursor ?? undefined,\n ))) as ContentItem[],\n item: async (id: string): Promise<ContentItem> =>\n (await coreCall((c) => c.contentGetItem(id))) as ContentItem,\n updateItem: async (id: string, state: unknown): Promise<ContentItem> =>\n (await coreCall((c) => c.contentUpdateItem(\n id,\n encodeJson(state),\n ))) as ContentItem,\n createItem: async (\n channel: string,\n item: unknown,\n ): Promise<ContentItem> =>\n (await coreCall((c) => c.contentCreateItem(\n channel,\n encodeJson(item),\n ))) as ContentItem,\n // </generated:content>\n },\n\n /** Server-managed deep link records. */\n deepLinks: {\n // <generated:deepLinks>\n get: async (shortCode: string): Promise<DeepLink> =>\n (await coreCall((c) => c.deepLinksGet(shortCode))) as DeepLink,\n // </generated:deepLinks>\n create: async (params: DeepLinkCreate): Promise<DeepLink> =>\n (await coreCall((c) => c.deepLinksCreate(encodeJson(params)))) as DeepLink,\n },\n\n /** Step-by-step user onboarding flow. */\n onboarding: {\n // <generated:onboarding>\n getStatus: async (): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingGetStatus())) as OnboardingStatus,\n nextStep: async (payload: unknown): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingNextStep(\n encodeJson(payload),\n ))) as OnboardingStatus,\n skipStep: async (): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingSkipStep())) as OnboardingStatus,\n complete: async (): Promise<OnboardingStatus> =>\n (await coreCall((c) => c.onboardingComplete())) as OnboardingStatus,\n // </generated:onboarding>\n },\n\n};\n\n/** Escape hatch — the raw SDK runtime handle, for namespaces not yet wrapped. */\nexport function getCoreWasm(): AmbaCoreWasm {\n return requireCore();\n}\n\n/**\n * Package version stamped at build time. Sent in the\n * `sdk_wrapper_version` header on every customer request.\n *\n * **Must match `package.json#version` exactly** — the\n * `verify-sdk-version-coherence` GH action gates every publish on the\n * pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).\n */\nexport const SDK_VERSION = \"4.0.11\";\n","import init, { AmbaCoreWasm } from \"@layers/amba-core/inline\";\n\nimport { setCoreRuntime } from \"./wasm-loader\";\n\nsetCoreRuntime(init, AmbaCoreWasm);\n\nexport * from \"./index\";\n"],"mappings":";;;AAYA,IAAIA;AACJ,IAAIC;AAEJ,SAAgB,eAAe,QAAgB,WAAgC;AAC7E,YAAWC;AACX,iBAAgB;;AAGlB,eAAsB,iBAGnB;AACD,KAAI,aAAa,UAAa,kBAAkB,QAAW;EACzD,MAAM,OAAO,MAAM,OAAO;AAC1B,iBAAe,KAAK,SAAS,KAAK,aAAa;;AAEjD,KAAI,aAAa,UAAa,kBAAkB,OAC9C,OAAM,IAAI,MAAM,mDAAmD;AAErE,QAAO;EACL,UAAU;EACV,WAAW;EACZ;;;;;;;;;;;;AC3BH,IAAa,0BAAb,MAAqC;CACnC,YAAY,AAAiBC,QAAgB;EAAhB;;CAE7B,IAAY,UAA0B;AACpC,MAAI;AACF,UAAO,WAAW,gBAAgB;UAC5B;AAEN,UAAO;;;CAIX,IAAI,YAAqB;AACvB,SAAO,KAAK,YAAY;;CAG1B,KAAK,KAA4B;EAC/B,MAAM,UAAU,KAAK;AACrB,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,UAAO,QAAQ,QAAQ,GAAG,KAAK,OAAO,GAAG,MAAM;UACzC;AACN,UAAO;;;CAIX,MAAM,KAAa,OAAqB;EACtC,MAAM,UAAU,KAAK;AACrB,MAAI,CAAC,QAAS;AACd,MAAI;AACF,WAAQ,QAAQ,GAAG,KAAK,OAAO,GAAG,OAAO,MAAM;UACzC;;CAKV,OAAO,KAAmB;EACxB,MAAM,UAAU,KAAK;AACrB,MAAI,CAAC,QAAS;AACd,MAAI;AACF,WAAQ,WAAW,GAAG,KAAK,OAAO,GAAG,MAAM;UACrC;;;;;;;;;;AC5CZ,SAAgB,WAAW,OAAwB;AACjD,QAAO,KAAK,UAAU,MAAM;;;;;;;;;;;;AC8B9B,SAAS,oBAAoB,KAAwC;CACnE,MAAM,MACJ,OAAO,IAAI,QAAQ,YAAY,IAAI,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,IAAI,MAAM,GAAG;CAC9E,MAAM,OACJ,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,MAAM,CAAC,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG;AACjF,KAAI,CAAC,OAAO,CAAC,KAAM,QAAO;CAC1B,MAAMC,QAAsB,EAAE,MAAM,SAAS;AAC7C,KAAI,QAAQ,OAAW,OAAM,MAAM;AACnC,KAAI,SAAS,OAAW,OAAM,OAAO;AACrC,KAAI,IAAI,SAAS,OAAW,OAAM,OAAO,IAAI;AAC7C,QAAO;;;;;;;AAQT,SAAgB,eAAe,SAA6C;CAC1E,MAAM,SAAS,QAAQ;AACvB,KAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;CAE3C,MAAM,cAAc,OACjB,IAAI,oBAAoB,CACxB,QAAQ,MAAyB,MAAM,KAAK;CAG/C,MAAM,EAAE,QAAQ,QAAS,GAAG,SAAS;AAErC,KAAI,YAAY,WAAW,EAAG,QAAO;CAErC,MAAMC,YAAyB,KAAK,YAAY,EAAE,EAAE,KAAK,OAAO,EAAE,GAAG,GAAG,EAAE;CAC1E,IAAI,cAAc;AAClB,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,KAAI,SAAS,GAAI,SAAS,QAAQ;AAChC,gBAAc;AACd;;AAGJ,KAAI,gBAAgB,IAAI;EAOtB,MAAMC,iBACJ,KAAK,aAAa,OAAO,KAAK,KAAK,UAAU,CAAC,SAAS,IACnD,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,KAAK,UAAU;GAAE,CAAC,GACxD,EAAE;AACR,WAAS,KAAK;GAAE,MAAM;GAAQ,SAAS,CAAC,GAAG,gBAAgB,GAAG,YAAY;GAAE,CAAC;QACxE;EACL,MAAM,WAAW,SAAS,aAAc;EAOxC,IAAIC;AACJ,MAAI,OAAO,aAAa,SACtB,iBAAgB,CAAC;GAAE,MAAM;GAAQ,MAAM;GAAU,CAAC;WACzC,MAAM,QAAQ,SAAS,CAChC,iBAAgB;WACP,aAAa,QAAQ,aAAa,OAC3C,iBAAgB,EAAE;MAElB,iBAAgB,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,SAAS;GAAE,CAAC;AAEpE,WAAS,eAAe;GACtB,GAAG,SAAS;GACZ,SAAS,CAAC,GAAG,eAAe,GAAG,YAAY;GAC5C;;AAEH,QAAO;EAAE,GAAG;EAAM;EAAU;;;;;ACrE9B,IAAa,eAAb,cAAkC,MAAM;CACtC,AAAS;CACT,AAAS;;;;;;;;;CAST;CACA,YACE,MACA,SACA,SACA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,OAAO;AACZ,OAAK,UAAU;AAGf,SAAO,eAAe,MAAM,IAAI,OAAO,UAAU;;;;;;;;;;;;AAarD,SAAS,gBAAgB,SAAmC;AAE1D,KAAI,QAAQ,WAAW,eAAe,CAAE,QAAO;AAC/C,KAAI,QAAQ,WAAW,YAAY,CAAE,QAAO;AAC5C,KAAI,QAAQ,WAAW,YAAY,CAAE,QAAO;AAC5C,KAAI,QAAQ,WAAW,WAAW,CAAE,QAAO;AAC3C,KAAI,QAAQ,WAAW,eAAe,CAAE,QAAO;AAC/C,KAAI,QAAQ,WAAW,mBAAmB,CAAE,QAAO;AACnD,KAAI,QAAQ,WAAW,gBAAgB,CAAE,QAAO;AAChD,KAAI,QAAQ,WAAW,aAAa,CAAE,QAAO;AAC7C,KAAI,QAAQ,WAAW,kBAAkB,CAAE,QAAO;AAClD,KAAI,QAAQ,WAAW,sBAAsB,CAAE,QAAO;AACtD,KAAI,QAAQ,SAAS,kBAAkB,CAAE,QAAO;AAChD,KAAI,QAAQ,WAAW,wBAAwB,CAAE,QAAO;AACxD,KAAI,QAAQ,WAAW,mBAAmB,CAAE,QAAO;AACnD,QAAO;;;;;;;;;;;;;AAcT,SAAgB,eAAe,KAA4B;AACzD,KAAI,eAAe,aAAc,QAAO;AACxC,KAAI,OAAO,OAAO,QAAQ,UAAU;EAClC,MAAM,MAAM;AACZ,MAAI,OAAO,IAAI,SAAS,SACtB,QAAO,IAAI,aACT,IAAI,MACJ,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,WAChD,IAAI,QACL;;AAGL,KAAI,eAAe,MACjB,QAAO,IAAI,aAAa,gBAAgB,IAAI,QAAQ,EAAE,IAAI,QAAQ;AAEpE,QAAO,IAAI,aAAa,iBAAiB,OAAO,IAAI,CAAC;;;;;;;;;;;;AC/GvD,SAAgB,YAAY,OAAuB;AACjD,KAAI,UAAU,MAAM,UAAU,OAAO,UAAU,KAC7C,OAAM,IAAI,aACR,oBACA,4BAA4B,UAAU,KAAK,UAAU,IAAI,MAAM,KAChE;AAEH,QAAO,mBAAmB,MAAM;;;;;AC0ClC,MAAM,qBAAqB;AAC3B,MAAM,iBAAiB;AAEvB,MAAMC,oBAAkB;AACxB,MAAM,uBAAuB;;;;;;;;AAS7B,SAAgB,uBAAuB,SAAyB;AAC9D,QAAO,QAAQ,QAAQ,QAAQ,GAAG,KAAKA,oBAAkB,uBAAuB;;;;;;AAOlF,IAAa,yBAAb,MAAiD;CAC/C,AAAiB,UAAmC,EAAE;CAEtD,YACE,AAAiBC,KACjB,AAAiBC,MACjB;EAFiB;EACA;;;CAInB,MAAM,QAAgB,OAAsB;AAC1C,OAAK,QAAQ,UAAU;AACvB,SAAO;;;;;;CAOT,UAAU,IAAyC,UAA4B,EAAE,EAAe;AAE9F,MAAI,CADY,KAAK,IAAI,aAAa,CAEpC,OAAM,IAAI,MACR,0GACD;EAEH,MAAM,KACJ,OAAO,KAAK,KAAK,QAAQ,CAAC,SAAS,IAC/B,UAAU,mBAAmB,KAAK,UAAU,KAAK,QAAQ,CAAC,KAC1D;AAIN,SAAO,WAAc;GACnB,KAJU,GAAG,uBAAuB,KAAK,IAAI,QAAQ,CAAC,kCAAkC,YACxF,KAAK,KACN,GAAG;GAGF,aAAa,KAAK,IAAI;GACtB,UAAU;GACV;GACD,CAAC;;;;;;;;;;;;;;AA2BN,SAAS,WAAc,MAAkC;CACvD,IAAI,UAAU;CACd,IAAIC,aAAqC;CACzC,IAAI,UAAU;CAEd,MAAM,MAAM,YAA2B;AACrC,SAAO,CAAC,SAAS;AACf,gBAAa,IAAI,iBAAiB;AAClC,OAAI;IACF,MAAM,UAAU,KAAK,aAAa;AAClC,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oBAAoB;IAClD,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK;KACjC,QAAQ;KACR,SAAS;MAAE,GAAG;MAAS,QAAQ;MAAqB;KACpD,QAAQ,WAAW;KACpB,CAAC;AACF,QAAI,CAAC,KAAK,MAAM,CAAC,KAAK,KACpB,OAAM,IAAI,MAAM,iCAAiC,KAAK,SAAS;AAOjE,UAAM,QAAQ,KAAK,MAAM,YAAY;AACnC,eAAU;AACV,UAAK,QAAQ,UAAU;MACvB;YACK,KAAK;AACZ,QAAI,QAAS;AACb,SAAK,QAAQ,UAAU,IAAI;;AAE7B,OAAI,QAAS;AACb,SAAM,MAAM,QAAQ;AACpB,aAAU,KAAK,IAAI,UAAU,GAAG,eAAe;;;AAInD,CAAK,KAAK;AAEV,cAAa;AACX,YAAU;AACV,cAAY,OAAO;;;AAIvB,eAAe,QACb,MACA,MACA,cACe;CACf,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,SAAS;CACb,IAAI,WAAW;AACf,UAAS;EACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM;AACV,YAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;EACjD,IAAIC;AACJ,UAAQ,MAAM,OAAO,QAAQ,OAAO,MAAM,IAAI;GAC5C,MAAM,QAAQ,OAAO,MAAM,GAAG,IAAI;AAClC,YAAS,OAAO,MAAM,MAAM,EAAE;AAC9B,OAAI,CAAC,UAAU;AACb,eAAW;AACX,kBAAc;;AAEhB,iBAAiB,OAAO,KAAK,SAAS;;;;AAK5C,SAAS,cAAiB,OAAe,UAAqD;CAC5F,IAAI,QAAQ;CACZ,MAAMC,YAAsB,EAAE;AAC9B,MAAK,MAAM,QAAQ,MAAM,MAAM,KAAK,EAAE;AACpC,MAAI,KAAK,WAAW,IAAI,CAAE;AAC1B,MAAI,KAAK,WAAW,SAAS,CAAE,SAAQ,KAAK,MAAM,EAAgB,CAAC,MAAM;WAChE,KAAK,WAAW,QAAQ,CAAE,WAAU,KAAK,KAAK,MAAM,EAAe,CAAC,MAAM,CAAC;;AAEtF,KAAI,UAAU,SAAU;AACxB,KAAI;EACF,MAAM,UAAU,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;AAKhD,WAAS;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,WAAW,QAAQ;GAAW,CAAC;SACtE;;AAKV,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;;;AA6B1D,IAAa,2BAAb,MAAmD;CACjD,YACE,AAAiBJ,KACjB,AAAiBK,gBACjB;EAFiB;EACA;;CAGnB,UAAU,IAAwC,UAA4B,EAAE,EAAe;AAE7F,MAAI,CADY,KAAK,IAAI,aAAa,CAEpC,OAAM,IAAI,MACR,sHACD;AAKH,SAAO,WAAc;GACnB,KAJU,GAAG,uBAAuB,KAAK,IAAI,QAAQ,CAAC,oCAAoC,YAC1F,KAAK,eACN;GAGC,aAAa,KAAK,IAAI;GACtB,UAAU;GACV;GACD,CAAC;;;;;;;;;;;;;;;;;;AA0CN,IAAa,2BAAb,MAAsC;CACpC,YAAY,AAAiBL,KAAsB;EAAtB;;CAE7B,UACE,IACA,UAA4B,EAAE,EACjB;AAEb,MAAI,CADY,KAAK,IAAI,aAAa,CAEpC,OAAM,IAAI,MACR,0GACD;AAGH,SAAO,uBAAuB;GAC5B,KAFU,GAAG,uBAAuB,KAAK,IAAI,QAAQ,CAAC;GAGtD,aAAa,KAAK,IAAI;GACtB,UAAU;GACV;GACD,CAAC;;;;;;;;;AAiBN,SAAS,uBAAuB,MAA2C;CACzE,IAAI,UAAU;CACd,IAAIE,aAAqC;CACzC,IAAI,UAAU;CAEd,MAAM,MAAM,YAA2B;AACrC,SAAO,CAAC,SAAS;AACf,gBAAa,IAAI,iBAAiB;AAClC,OAAI;IACF,MAAM,UAAU,KAAK,aAAa;AAClC,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oBAAoB;IAClD,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK;KACjC,QAAQ;KACR,SAAS;MAAE,GAAG;MAAS,QAAQ;MAAqB;KACpD,QAAQ,WAAW;KACpB,CAAC;AACF,QAAI,CAAC,KAAK,MAAM,CAAC,KAAK,KACpB,OAAM,IAAI,MAAM,iCAAiC,KAAK,SAAS;AAEjE,UAAM,oBAAoB,KAAK,MAAM,YAAY;AAC/C,eAAU;AACV,UAAK,QAAQ,UAAU;MACvB;YACK,KAAK;AACZ,QAAI,QAAS;AACb,SAAK,QAAQ,UAAU,IAAI;;AAE7B,OAAI,QAAS;AACb,SAAM,MAAM,QAAQ;AACpB,aAAU,KAAK,IAAI,UAAU,GAAG,eAAe;;;AAInD,CAAK,KAAK;AAEV,cAAa;AACX,YAAU;AACV,cAAY,OAAO;;;AAIvB,eAAe,oBACb,MACA,MACA,cACe;CACf,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,SAAS;CACb,IAAI,WAAW;AACf,UAAS;EACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM;AACV,YAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;EACjD,IAAIC;AACJ,UAAQ,MAAM,OAAO,QAAQ,OAAO,MAAM,IAAI;GAC5C,MAAM,QAAQ,OAAO,MAAM,GAAG,IAAI;AAClC,YAAS,OAAO,MAAM,MAAM,EAAE;AAC9B,OAAI,CAAC,UAAU;AACb,eAAW;AACX,kBAAc;;AAEhB,6BAA0B,OAAO,KAAK,SAAS;;;;AAKrD,SAAS,0BACP,OACA,UACM;CACN,IAAI,QAAQ;CACZ,MAAMC,YAAsB,EAAE;AAC9B,MAAK,MAAM,QAAQ,MAAM,MAAM,KAAK,EAAE;AACpC,MAAI,KAAK,WAAW,IAAI,CAAE;AAC1B,MAAI,KAAK,WAAW,SAAS,CAAE,SAAQ,KAAK,MAAM,EAAgB,CAAC,MAAM;WAChE,KAAK,WAAW,QAAQ,CAAE,WAAU,KAAK,KAAK,MAAM,EAAe,CAAC,MAAM,CAAC;;AAEtF,KAAI,UAAU,SAAU;AACxB,KAAI;EACF,MAAM,UAAU,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;AAKhD,WAAS;GAAE,MAAM,QAAQ;GAAM,MAAM,QAAQ;GAAM,WAAW,QAAQ;GAAW,CAAC;SAC5E;;;;;ACzWV,SAAS,sBAAwC;AAC/C,QAAO;EACL,cAAc;EACd,eAAe;EACf,6BAA6B;EAC7B,yBAAyB;EAC1B;;AAGH,SAAS,UAAU,KAAgC;AACjD,QAAO;EACL,cAAc,IAAI;EAClB,eAAe,IAAI;EACnB,6BAA6B,IAAI;EACjC,yBAAyB,IAAI;EAC9B;;;AAIH,SAAS,IAAI,GAAoB;AAC/B,QAAO,OAAO,MAAM,YAAY,OAAO,SAAS,EAAE,GAAG,IAAI;;;;;;;;AAS3D,SAAS,WAAW,KAAuB,OAAsC;CAC/E,MAAM,QAAQ,MAAM,mBAAmB,MAAM;CAC7C,MAAM,SAAS,MAAM,oBAAoB,MAAM;AAC/C,KAAI,UAAU,OAAW,KAAI,eAAe,KAAK,IAAI,IAAI,cAAc,IAAI,MAAM,CAAC;AAClF,KAAI,WAAW,OAAW,KAAI,gBAAgB,KAAK,IAAI,IAAI,eAAe,IAAI,OAAO,CAAC;AACtF,KAAI,MAAM,mCAAmC,OAC3C,KAAI,8BAA8B,KAAK,IACrC,IAAI,6BACJ,IAAI,MAAM,+BAA+B,CAC1C;AAEH,KAAI,MAAM,+BAA+B,OACvC,KAAI,0BAA0B,KAAK,IACjC,IAAI,yBACJ,IAAI,MAAM,2BAA2B,CACtC;;;;;;;;;;;AAaL,SAAgB,gBAAgB,OAAe,KAAsC;CACnF,MAAME,YAAsB,EAAE;AAG9B,MAAK,MAAM,QAAQ,MAAM,MAAM,aAAa,EAAE;AAC5C,MAAI,KAAK,WAAW,IAAI,CAAE;AAG1B,MAAI,KAAK,WAAW,QAAQ,CAAE,WAAU,KAAK,KAAK,MAAM,EAAe,CAAC,WAAW,CAAC;;AAEtF,KAAI,UAAU,WAAW,EAAG,QAAO;CACnC,MAAM,OAAO,UAAU,KAAK,KAAK,CAAC,MAAM;AACxC,KAAI,SAAS,MAAM,SAAS,SAAU,QAAO;CAE7C,IAAIC;AACJ,KAAI;AACF,YAAU,KAAK,MAAM,KAAK;SACpB;AACN,SAAO;;CAGT,MAAM,OAAO,OAAO,QAAQ,YAAY,WAAY,QAAQ,UAAqB;AAGjF,KAAI,SAAS,uBAAuB;EAElC,MAAM,OADQ,QAAQ,WACD;AACrB,SAAO,OAAO,SAAS,WAAW,OAAO;;AAE3C,KAAI,SAAS,iBAAiB;EAC5B,MAAM,UAAU,QAAQ;EACxB,MAAM,QAAQ,UAAU;AACxB,MAAI,SAAS,OAAO,UAAU,SAAU,YAAW,KAAK,MAAiC;AACzF,MAAI,OAAO,UAAU,aAAa,SAAU,KAAI,QAAQ,QAAQ;AAChE,SAAO;;AAET,KAAI,SAAS,iBAAiB;EAC5B,MAAM,QAAQ,QAAQ;AACtB,MAAI,SAAS,OAAO,UAAU,SAAU,YAAW,KAAK,MAAiC;EACzF,MAAM,QAAQ,QAAQ;AACtB,MAAI,OAAO,QAAQ,mBAAmB,SAAU,KAAI,aAAa,MAAM;AACvE,SAAO;;AAET,KAAI,SAAS,eAAgB,QAAO;AAKpC,KAAI,MAAM,QAAQ,QAAQ,WAAW,EAAE;EACrC,MAAM,UAAU,QAAQ;AACxB,MAAI,OAAO,QAAQ,aAAa,SAAU,KAAI,QAAQ,QAAQ;EAC9D,MAAM,QAAQ,QAAQ;AACtB,MAAI,SAAS,OAAO,UAAU,SAAU,YAAW,KAAK,MAAiC;EACzF,MAAM,QAAQ,QAAQ;AACtB,MAAI,OAAO;GACT,MAAM,SAAS,MAAM;AACrB,OAAI,OAAO,WAAW,SAAU,KAAI,aAAa;GAEjD,MAAM,UADQ,MAAM,WACI;AACxB,OAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EAAG,QAAO;;AAEhE,SAAO;;AAGT,QAAO;;;;;;;;AAST,MAAM,sBAAsB;;;;;;;;;;AAW5B,SAAgB,kBAAkB,QAAwD;CACxF,MAAM,IAAI,oBAAoB,KAAK,OAAO;AAC1C,KAAI,CAAC,EAAG,QAAO;AACf,QAAO;EACL,OAAO,OAAO,MAAM,GAAG,EAAE,MAAM;EAC/B,MAAM,OAAO,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO;EAC1C;;;;;;;;;;AAWH,gBAAuB,eACrB,MAC8C;CAC9C,IAAIC;AACJ,KAAI;AACF,SAAO,MAAM,MAAM,KAAK,KAAK;GAC3B,QAAQ;GACR,SAAS,KAAK;GACd,MAAM,KAAK;GACZ,CAAC;UACK,KAAK;AACZ,QAAM,IAAI,aAAa,iBAAiB,4BAA4B,OAAO,IAAI,GAAG;;AAGpF,KAAI,CAAC,KAAK,IAAI;EAGZ,MAAM,OAAO,MAAM,KAAK,MAAM,CAAC,YAAY,GAAG;EAC9C,IAAIC,SAAkB;AACtB,MAAI,KAAK,SAAS,EAChB,KAAI;AACF,YAAS,KAAK,MAAM,KAAK;UACnB;AACN,YAAS;;EAGb,MAAM,WACJ,UAAU,OAAO,WAAW,WACvB,SACD;AASN,QAAM,IAAI,aAPR,OAAO,UAAU,OAAO,SAAS,WAAW,SAAS,MAAM,OAAO,QAAQ,KAAK,UAE/E,OAAO,UAAU,OAAO,YAAY,WAChC,SAAS,MAAM,UACf,OAAO,WAAW,YAAY,OAAO,SAAS,IAC5C,SACA,gCAAgC,KAAK,UACP,OAAO;;AAG/C,KAAI,CAAC,KAAK,KACR,OAAM,IAAI,aACR,iBACA,oGACD;CAGH,MAAM,SAAS,KAAK,KAAK,WAAW;CACpC,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,MAAM,qBAAqB;CACjC,IAAI,SAAS;AAEb,KAAI;AACF,WAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,OAAI,MAAO,WAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;AAC5D,OAAI,KAKF,WAAU,QAAQ,QAAQ;GAE5B,IAAIC;AACJ,WAAQ,QAAQ,kBAAkB,OAAO,MAAM,MAAM;AACnD,aAAS,MAAM;IACf,MAAM,OAAO,gBAAgB,MAAM,OAAO,IAAI;AAC9C,QAAI,SAAS,QAAQ,KAAK,SAAS,GAAG;AACpC,UAAK,UAAU,KAAK;AACpB,WAAM;MAAE,MAAM;MAAS;MAAM;;;AAGjC,OAAI,MAAM;IAER,MAAM,OAAO,OAAO,MAAM;AAC1B,QAAI,KAAK,SAAS,GAAG;KACnB,MAAM,OAAO,gBAAgB,MAAM,IAAI;AACvC,SAAI,SAAS,QAAQ,KAAK,SAAS,GAAG;AACpC,WAAK,UAAU,KAAK;AACpB,YAAM;OAAE,MAAM;OAAS;OAAM;;;AAGjC;;;WAGI;AAGR,MAAI;AACF,UAAO,aAAa;UACd;;AAKV,OAAM;EACJ,MAAM;EACN,OAAO,UAAU,IAAI;EACrB,OAAO,IAAI;EACX,YAAY,IAAI,cAAc;EAC/B;;;;;;;;;;;;ACvSH,SAAgB,mBAAmB,IAAc,SAA+B;CAC9E,MAAM,QAAQ,GAAG;AACjB,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,MAAM,UAAW,QAAO,MAAM,cAAc,QAAQ;AACxD,QAAO,MAAM,gBAAgB,QAAQ,MAAM,gBAAgB,QAAQ;;;;;;;AA0FrE,SAAgB,WAAoB;CAClC,MAAM,MACJ,OAAO,cAAc,cAChB,YACD;AACN,KAAI,OAAO,OAAO,IAAI,WAAW,UAAW,QAAO,IAAI;AACvD,QAAO;;;;;;;;AAST,MAAM,4BAA4B,IAAI,IAAI;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;AAUF,SAAgB,qBAAqB,KAAuB;CAC1D,MAAM,OAAQ,KAA+C;AAC7D,QAAO,OAAO,SAAS,YAAY,0BAA0B,IAAI,KAAK;;;;;;;;;;;;;;;;;;AAmBxE,SAAgB,eAAe,KAAuB;CACpD,MAAM,OAAQ,KAA+C;AAC7D,KAAI,OAAO,SAAS,YAAY,0BAA0B,IAAI,KAAK,CACjE,QAAO;AAET,KAAI,SAAS,gBAAiB,QAAO;CAQrC,MAAM,MAAO,KAAkD;AAM/D,KAJE,OAAO,QAAQ,YACf,2EAA2E,KACzE,IACD,CACqB,QAAO;AAI/B,QAAO,CAAC,UAAU;;;AAIpB,SAAgB,oBAA4B;CAC1C,MAAM,IACJ,OAAO,eAAe,cACjB,WAAW,SACZ;AACN,KAAI,KAAK,OAAO,EAAE,eAAe,WAAY,QAAO,YAAY,EAAE,YAAY;AAG9E,QAAO,YAAY,KAAK,KAAK,CAAC,SAAS,GAAG,CAAC,GAAG,KAAK,QAAQ,CACxD,SAAS,GAAG,CACZ,MAAM,GAAG,GAAG;;;;;;;AAQjB,IAAa,eAAb,MAA0B;CACxB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;;;;;;CAMjB,AAAiB;;;;;CAKjB,AAAiB;CACjB,AAAQ,WAAwC;;;;;CAKhD,AAAQ,kBAAkB;;;;;;;;;CAS1B,AAAQ,OAAyB,QAAQ,SAAS;;CAElD,AAAQ,eAAoC;;;;;;;CAO5C,AAAQ,WAAW;CAEnB,YAAY,MAYT;AACD,OAAK,QAAQ,KAAK;AAClB,OAAK,WAAW,KAAK;AACrB,OAAK,aAAa,KAAK,cAAc;AACrC,OAAK,YAAY,KAAK;AACtB,OAAK,eAAe,KAAK;;;;;;CAO3B,AAAQ,QAAQ,KAA6B;AAC3C,MAAI,CAAC,KAAK,aAAc,QAAO;EAC/B,MAAM,UAAU,KAAK,cAAc;AACnC,SAAO,IAAI,QAAQ,OAAO,mBAAmB,IAAI,QAAQ,CAAC;;;;;;;CAQ5D,AAAQ,OACN,IACA,MACY;EACZ,MAAM,MAAM,KAAK,KAAK,KAAK,YAAY;AAMrC,OAAI,KAAK,YAAY,MAAM,iBACzB,OAAM,IAAI,MAAM,qDAAqD;GAGvE,MAAM,EAAE,MAAM,WAAW,MAAM,GADnB,MAAM,KAAK,MAAM,CACS;AACtC,SAAM,KAAK,KAAK,KAAK;AACrB,UAAO;IACP;AAGF,OAAK,OAAO,IAAI,YAAY,OAAU;AACtC,SAAO;;CAGT,MAAc,OAA4B;EACxC,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI,KAAK,WAAW;AACjD,MAAI,CAAC,IAAK,QAAO,EAAE;AACnB,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,UAAO,MAAM,QAAQ,OAAO,GAAI,SAAwB,EAAE;UACpD;AAEN,UAAO,EAAE;;;CAIb,MAAc,KAAK,KAAgC;AAIjD,MAAI,KAAK,SAAU;AACnB,MAAI,IAAI,WAAW,GAAG;AACpB,SAAM,KAAK,MAAM,OAAO,KAAK,WAAW;AACxC;;AAEF,QAAM,KAAK,MAAM,IAAI,KAAK,YAAY,KAAK,UAAU,IAAI,CAAC;;;;;;;;;CAU5D,MAAM,UAAyB;AAC7B,OAAK,WAAW;AAChB,MAAI,KAAK,cAAc;AACrB,QAAK,cAAc;AACnB,QAAK,eAAe;;AAKtB,MAAI;AACF,SAAM,KAAK;UACL;AAGR,QAAM,KAAK,KAAK,YAAY,OAAU;;;CAIxC,IAAI,aAAsB;AACxB,SAAO,KAAK;;;;;;CAOd,SAAuB;AACrB,SAAO;GAAE,OAAO,KAAK,gBAAgB;GAAE,YAAY,KAAK;GAAiB;;;;;;;;CAS3E,MAAM,QACJ,IAGA,QACmB;AAMnB,MAAI,KAAK,SACP,OAAM,IAAI,MAAM,qDAAqD;EAEvE,MAAM,QAAQ,GAAG,UAAU,SAAS,OAAO,QAAQ,KAAK,gBAAgB;EACxE,MAAMC,OAAiB;GACrB,GAAG;GACH,GAAI,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE;GACxC,gBAAgB,GAAG,kBAAkB,mBAAmB;GACxD,6BAAY,IAAI,MAAM,EAAC,aAAa;GACrC;AACD,SAAO,KAAK,QACT,QAAQ;AAGP,OAAI,UAAU,OAAO,eAAe,KAAK,iBAAiB;IACxD,MAAM,sBAAM,IAAI,MACd,8EACD;AACD,QAAI,OAAO;AACX,UAAM;;AAER,UAAO;IAAE,MAAM,CAAC,GAAG,KAAK,KAAK;IAAE,QAAQ;IAAM;KAE/C,EAAE,kBAAkB,MAAM,CAC3B;;;CAIH,MAAM,eAAgC;AACpC,SAAO,KAAK,QAAQ,MAAM,KAAK,MAAM,CAAC,CAAC;;;;;;CAOzC,MAAM,UAA+B;AACnC,SAAO,CAAC,GAAG,KAAK,QAAQ,MAAM,KAAK,MAAM,CAAC,CAAC;;;CAI7C,MAAM,QAAuB;AAC3B,QAAM,KAAK,cAAc;GAAE,MAAM,EAAE;GAAE,QAAQ;GAAW,EAAE;;;;;;;CAQ5D,MAAM,QAAuB;AAC3B,OAAK,mBAAmB;AACxB,QAAM,KAAK,OAAO;;;;;;;;;;;;CAapB,MAAM,QAA8B;AAClC,MAAI,KAAK,SAAU,QAAO;GAAE,SAAS;GAAG,SAAS;GAAG,WAAW;GAAG;AAClE,MAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,OAAK,WAAW,KAAK,UAAU,CAAC,cAAc;AAC5C,QAAK,WAAW;IAChB;AACF,SAAO,KAAK;;CAGd,MAAc,WAAiC;EAC7C,IAAI,UAAU;EACd,IAAI,UAAU;AAed,WAAS;AAIP,OAAI,KAAK,SAAU;GACnB,MAAM,MAAM,MAAM,KAAK,MAAM;AAC7B,OAAI,KAAK,cAAc;IAKrB,MAAM,UAAU,KAAK,cAAc;IACnC,MAAM,SAAS,IAAI,MAChB,MACC,CAAC,EAAE,SACF,EAAE,MAAM,cAAc,QACrB,QAAQ,gBAAgB,QACxB,EAAE,MAAM,gBAAgB,QAAQ,YACrC;AACD,QAAI,QAAQ;AAEV,SAAI,CADkB,MAAM,KAAK,SAAS,OAAO,CAC7B;AACpB,gBAAW;AACX,UAAK,cACH,QACA,OAAO,QACH;MACE,MAAM;MACN,SAAS;MACV,GACD;MACE,MAAM;MACN,SAAS;MACV,CACN;AACD;;;GAGJ,MAAM,OAAO,KAAK,QAAQ,IAAI,CAAC;AAC/B,OAAI,CAAC,KAAM;GACX,MAAM,aAAa,KAAK;GACxB,IAAIC,cAAuB;GAC3B,IAAI,KAAK;AACT,OAAI;AACF,UAAM,KAAK,SAAS,KAAK;AACzB,SAAK;YACE,KAAK;AACZ,kBAAc;;AAGhB,OAAI,CAAC,MAAM,CAAC,qBAAqB,YAAY,EAAE;AAgB7C,UAAM,KAAK,QAAQ,QAAQ;AAKzB,YAAO;MACL,MAFc,CAHA,IAAI,MACjB,MAAM,EAAE,mBAAmB,KAAK,eAClC,IAC2B,eAAe,KAAK,kBAE9B,CAAC,MAAM,GAAG,IAAI,GAAG;MACjC,QAAQ;MACT;MACD;AACF;;AAUF,OAAI,CADY,MAAM,KAAK,SAAS,KAAK,CAC3B;AACd,OAAI,GACF,YAAW;QACN;AACL,eAAW;AACX,SAAK,cAAc,MAAM,YAAY;;;EAGzC,MAAM,YAAY,KAAK,QAAQ,MAAM,KAAK,MAAM,CAAC,CAAC;AAClD,SAAO;GAAE;GAAS;GAAS;GAAW;;;;;;;CAQxC,AAAQ,SAAS,IAAgC;AAC/C,SAAO,KAAK,QAAQ,QAAQ;GAC1B,MAAM,MAAM,IAAI,WAAW,MAAM,EAAE,mBAAmB,GAAG,eAAe;AACxE,OAAI,QAAQ,GAAI,QAAO;IAAE,MAAM;IAAK,QAAQ;IAAO;AAEnD,UAAO;IAAE,MADI,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,MAAM,EAAE,CAAC;IAC1C,QAAQ;IAAM;IAC7B;;CAGJ,AAAQ,cAAc,IAAc,KAAoB;AACtD,MAAI;AACF,QAAK,YAAY,IAAI,IAAI;UACnB;;;;;;;CAUV,iBAA6B;AAC3B,MAAI,KAAK,aAAc,QAAO,KAAK;EACnC,MAAM,SACJ,OAAO,eAAe,eACtB,OAAQ,WAA8C,qBACpD,aACG,aAID;AACN,MAAI,CAAC,QAAQ;AAGX,QAAK,qBAAqB;AAC1B,UAAO,KAAK;;EAEd,MAAM,gBAAgB;AACpB,GAAK,KAAK,OAAO;;AAEnB,SAAO,iBAAiB,UAAU,QAAQ;AAC1C,OAAK,qBAAqB;AACxB,UAAO,oBAAoB,UAAU,QAAQ;AAC7C,QAAK,eAAe;;AAEtB,SAAO,KAAK;;;;;;;;;;;;;;;;;;;;AAuBhB,eAAsB,eAAgC,MAOjC;CACnB,MAAM,UAAU,KAAK,OAAO;AAC5B,KAAI;AACF,SAAO,MAAM,KAAK,QAAQ;UACnB,KAAK;AACZ,MAAI;AACF,QAAK,SAAS,QAAQ;UAChB;AAGR,QAAM;;;;;;;;;;;;;;;;;;;;;;;AChqBV,SAAS,qBAAqB,GAAqB;AACjD,KAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;CAChD,MAAM,SAAU,WACb;AACH,KAAI,OAAO,WAAW,cAAc,aAAc,OAChD,QAAO;CAIT,MAAM,MAAO,EAAmD;AAChE,QAAO,CAAC,CAAC,OAAO,OAAO,IAAI,gBAAgB,YAAY,IAAI,gBAAgB;;AAG7E,IAAI,kBAAkB;AAEtB,SAAgB,0BAA0B,YAAoC;CAO5E,MAAM,IAAI,OAAO,SAAS,cAAe,OAAmB;AAC5D,KAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE;AAClC,MAAI,CAAC,mBAAmB,OAAO,YAAY,aAAa;AACtD,qBAAkB;AAClB,WAAQ,KACN,oLACD;;AAEH,eAAa;;CAEf,MAAM,KAAK;AAUX,KAAI,OAAO,GAAG,qBAAqB,WACjC,cAAa;CAGf,MAAM,YAAY,UAAyB;AACzC,GAAM,YAAY;AAChB,OAAI;IACF,MAAM,KAAK;IAQX,IAAI,WAAW,IAAI,iBAAiB;AACpC,QAAI,CAAC,YAAY,GAAG,cAAc,aAAa,gBAE7C,aADgB,MAAM,GAAG,aAAa,YAAY,iBAAiB,GAC/C,YAAY;AAElC,QAAI,CAAC,SAAU;IACf,MAAM,OAAO,WAAW,UAAU,MAAM;AAGxC,QAAI,OAAO,IAAI,cAAc,WAAY,IAAG,UAAU,KAAK;AAC3D,UAAM;YACC,KAAK;AACZ,QAAI,OAAO,YAAY,YACrB,SAAQ,MAAM,gDAAgD,IAAI;;MAGpE;;AAGN,IAAG,iBAAiB,0BAA0B,SAAS;AACvD,cAAa;AACX,KAAG,sBAAsB,0BAA0B,SAAS;;;;;;;;;;;;;;AC7DhE,MAAM,iBAAiB;AAgBvB,MAAM,gCAAgB,IAAI,KAA2B;AAErD,SAAS,qBAA8B;AACrC,QACE,OAAO,eAAe,eACtB,OAAQ,WAA0C,iBAChD;;AAIN,SAAS,uBAAyD;CAChE,MAAM,OAAQ,WAA8C;AAC5D,QAAO,OAAO,SAAS,aAClB,OACD;;AAGN,SAAS,QAAgB;CACvB,MAAM,IAAK,WAA0D;AACrE,KAAI,GAAG,WAAY,QAAO,EAAE,YAAY;AACxC,QAAO,cAAc,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;;;;;;;;;AAWxE,SAAS,mBAA2B;AAClC,QAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;;;;;;;;;AAW7D,SAAS,oBAAoB,OAAmC;AAC9D,KAAI,UAAU,OAAW,QAAO,OAAO;AACvC,KAAI,OAAO,UAAU,YAAY,MAAM,MAAM,KAAK,GAChD,OAAM,IAAI,aACR,oBACA,kFACD;AAEH,QAAO;;AAkBT,SAAS,aAAa,OAAsD;CAC1E,MAAM,UAAU,MAAM;AAItB,KAAI,CAAC,WAAW,OAAO,YAAY,SACjC,OAAM,IAAI,aACR,oBACA,0FACD;AAEH,KAAI,QAAQ,SAAS;EAEnB,MAAM,MADK,QAAQ,cAAc,OAAO,QAAQ,KAAK,IAAI,KAAK,QAAQ,GAAG,EAC3D,SAAS;AACvB,MAAI,OAAO,MAAM,GAAG,CAClB,OAAM,IAAI,aACR,oBACA,oEACD;AAEH,SAAO;GAAE,MAAM;GAAY,IAAI;GAAI;;AAErC,KAAI,eAAe,SAAS;AAC1B,MAAI,CAAC,OAAO,SAAS,QAAQ,UAAU,IAAI,QAAQ,YAAY,EAC7D,OAAM,IAAI,aACR,oBACA,oFACD;AAEH,SAAO;GAAE,MAAM;GAAY,SAAS,QAAQ;GAAW;;AAEzD,OAAM,IAAI,aACR,oBACA,0FACD;;;;;;;AAQH,SAAS,cAAc,QAA+B;AACpD,QAAO,OAAO,SAAS,aACnB,OAAO,KACP,KAAK,KAAK,GAAG,OAAO,UAAU;;AAGpC,SAAS,gBAAgB,OAA6C;AACpE,KAAI,CAAC,SAAS,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,MAAM,KAAK,GACtE,OAAM,IAAI,aACR,oBACA,sDACD;AAEH,KAAI,OAAO,MAAM,SAAS,SACxB,OAAM,IAAI,aACR,oBACA,0DACD;;AAIL,eAAe,mBAAkC;CAC/C,MAAM,IAAK,WAAqD;CAChE,IAAI,aAAa,EAAE;AACnB,KAAI,eAAe,UACjB,cAAa,MAAM,EAAE,mBAAmB;AAE1C,KAAI,eAAe,UACjB,OAAM,IAAI,aACR,yCACA,4EACD;;;;;;;;AAUL,MAAM,sBAAsB;;;;;;;;;;;;;;;;AAiB5B,eAAe,+BAEb;CACA,MAAM,MAAO,WAAyC;AACtD,KAAI,CAAC,OAAO,EAAE,mBAAmB,KAAM,QAAO;CAC9C,MAAM,MAAM,IAAI;CAEhB,IAAIC;AACJ,KAAI;AAEF,aAAW,MAAM,IAAI,iBAAiB;SAChC;AACN;;AAEF,KAAI,CAAC,SAAU,QAAO;AAKtB,KAAI;AAOF,SANc,MAAM,QAAQ,KAAK,CAC/B,IAAI,OACJ,IAAI,SAAoB,YACtB,iBAAiB,QAAQ,OAAU,EAAE,oBAAoB,CAC1D,CACF,CAAC,IACc;SACV;AACN,SAAO;;;;;;;;;;;AAYX,eAAe,4BACb,cACA,IACe;AACf,KAAI,CAAC,aAAc;AACnB,KAAI;EACF,MAAM,UAAU,MAAM,aAAa,iBAAiB;GAClD,KAAK;GAGL,kBAAkB;GACnB,CAA4D;AAC7D,OAAK,MAAM,KAAK,SAAS;AAKvB,QADc,EAAE,QAAgD,EAAE,EACzD,kBAAkB,GAAI;AAC/B,KAAE,OAAO;;SAEL;;;;;;AAUV,SAAS,uBACP,QACA,IACM;AACN,KAAI;EACF,MAAM,IAAK,WAAqD;AAEhE,MAAI,EAAE,OAAO,OAAO;GAClB,MAAM,OAAO;GACb,MAAM;IAAE,GAAG,OAAO;IAAM,eAAe;IAAI;GAC3C,KAAK;GACN,CAAC;SACI;;;;;;;;;;;;AAgBV,SAAS,cAAc,IAAY,OAA2B;CAC5D,MAAM,YAAY,MAAM,SAAS,KAAK,KAAK;AAC3C,KAAI,YAAY,gBAAgB;AAC9B,QAAM,SAAS,iBAAiB;AAI9B,OADgB,cAAc,IAAI,GAAG,KACrB,MAAO,eAAc,IAAI,MAAM;KAC9C,eAAe;AAClB;;AAEF,OAAM,SAAS,iBACP;AAEJ,MAAI,cAAc,IAAI,GAAG,KAAK,MAAO;AACrC,gBAAc,OAAO,GAAG;AACxB,yBAAuB,MAAM,QAAQ,GAAG;IAE1C,KAAK,IAAI,GAAG,UAAU,CACvB;;;;;;;;AASH,MAAa,gBAAgB;CAC3B,MAAM,cACJ,OAC6E;AAC7E,kBAAgB,MAAM;AACtB,MAAI,CAAC,oBAAoB,CACvB,OAAM,IAAI,aACR,mCACA,+KACD;EAQH,MAAM,gBAAgB,aAAa,MAAM;EACzC,MAAM,KAAK,oBAAoB,MAAM,GAAG;AACxC,QAAM,kBAAkB;EACxB,MAAM,SAAS,cAAc,cAAc;EAG3C,MAAM,gBAAgB,kBAAkB;EAExC,MAAM,cAAc,sBAAsB;EAC1C,MAAM,eAAe,MAAM,8BAA8B;AAmBzD,MAAI,eAAe,cAAc;AAM/B,SAAM,aAAa,iBAAiB,MAAM,OAAO;IAC/C,MAAM,MAAM;IACZ,MAAM;KAAE,GAAG,MAAM;KAAM,eAAe;KAAI,gBAAgB;KAAe;IACzE,KAAK;IAGL,aAAa,IAAI,YAAY,OAAO;IACrC,CAAmD;GAIpD,MAAMC,eAAa,cAAc,IAAI,GAAG;AACxC,OAAIA,cAAY;AACd,iBAAaA,aAAW,OAAO;AAC/B,kBAAc,OAAO,GAAG;;AAE1B,UAAO;IAAE;IAAI,cAAc;IAAc;;EAU3C,MAAM,aAAa,cAAc,IAAI,GAAG;AACxC,MAAI,YAAY;AACd,gBAAa,WAAW,OAAO;AAC/B,iBAAc,OAAO,GAAG;;AAE1B,QAAM,4BAA4B,cAAc,GAAG;EAEnD,MAAMC,QAAsB;GAE1B,QAAQ;GACR;GACA,QAAQ;IACN;IACA,OAAO,MAAM;IACb,MAAM,MAAM;IACZ,MAAM,MAAM;IACb;GACF;AAGD,gBAAc,IAAI,IAAI,MAAM;AAG5B,gBAAc,IAAI,MAAM;AACxB,SAAO;GAAE;GAAI,cAAc;GAAS;;CAGtC,MAAM,YAAY,IAA2B;AAC3C,MAAI,OAAO,OAAO,YAAY,GAAG,MAAM,KAAK,GAC1C,OAAM,IAAI,aACR,oBACA,iDACD;EAGH,MAAM,QAAQ,cAAc,IAAI,GAAG;AACnC,MAAI,OAAO;AACT,gBAAa,MAAM,OAAO;AAC1B,iBAAc,OAAO,GAAG;;AAK1B,QAAM,4BADe,MAAM,8BAA8B,EACT,GAAG;;CAGrD,MAAM,qBAA4D;EAChE,MAAMC,MAAoC,EAAE;AAG5C,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAAE,KAAI,KAAK,EAAE,GAAG,QAAQ,CAAC;EAExE,MAAM,eAAe,MAAM,8BAA8B;AACzD,MAAI,aACF,KAAI;GAOF,MAAM,CAAC,eAAe,aAAa,MAAM,QAAQ,IAAI,CACnD,aAAa,iBAAiB,EAC5B,kBAAkB,MACnB,CAA4D,EAC7D,aAAa,kBAAkB,CAChC,CAAC;GAOF,MAAM,iCAAiB,IAAI,KAAa;AACxC,QAAK,MAAM,KAAK,WAAW;IAEzB,MAAM,SADQ,EAAE,QAAgD,EAAE,EAC/C;AACnB,QAAI,MAAO,gBAAe,IAAI,MAAM;;AAEtC,QAAK,MAAM,KAAK,eAAe;IAC7B,MAAM,OAAQ,EAAE,QAAgD,EAAE;IAIlE,MAAM,KAAK,KAAK;AAChB,QAAI,CAAC,GAAI;IAIT,MAAM,QAAQ,KAAK;AACnB,QAAI,SAAS,eAAe,IAAI,MAAM,CAAE;AACxC,QAAI,IAAI,MAAM,aAAa,SAAS,OAAO,GAAG,CAAE;AAChD,QAAI,KAAK;KACP;KACA,OAAO,EAAE;KACT,MAAO,EAAwB,QAAQ;KACvC;KACD,CAAC;;UAEE;AAIV,SAAO;;CAEV;;;;ACvXD,MAAM,kBAAkB;;;;;;AAOxB,MAAM,yBAAyB;AAC/B,MAAM,qBACJ;;;;AAKF,IAAIC,eAAoC;;;;;;AAOxC,IAAIC,uBAAsC;;;;;;;;;AAU1C,IAAIC,oBAA4B;AAChC,IAAIC,uBAAsC;AAC1C,IAAIC,uBAAsC;AAC1C,IAAIC,uBAAsC;AAC1C,IAAIC,2BAA0C;AAC9C,IAAIC,eAA4B;AAEhC,MAAM,2BAA2B;AACjC,MAAM,yBAAyB,OAAU;;;;;;AAOzC,IAAIC,sBAA0C;AAC9C,MAAMC,4BAA2D,CAAC,SAAS,SAAS;AAapF,SAAS,mBAAmB,WAA2B;AACrD,QAAO,aAAa,yBAAyB,WAAW,mBAAmB,UAAU;;AAGvF,SAAS,iBAAiB,OAAiC;AACzD,QAAO,OAAO,UAAU,YAAY,MAAM,MAAM,CAAC,SAAS;;AAG5D,SAAS,yBAAyB,OAAwB;AACxD,QAAO,MAAM,KAAK,MAAM,CAAC,MAAM,SAAS;EACtC,MAAM,OAAO,KAAK,WAAW,EAAE;AAC/B,SAAO,QAAQ,MAAQ,SAAS;GAChC;;;;AAKJ,SAAS,sBACP,aACA,WACiC;CACjC,MAAM,MAAM,mBAAmB,UAAU;CACzC,MAAM,MAAM,YAAY,KAAK,IAAI;AACjC,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI;EACF,MAAM,QAAQ,KAAK,MAAM,IAAI;EAC7B,MAAM,YAAY,MAAM,iBAAiB,QAAQ,MAAM,iBAAiB;AACxE,MACE,MAAM,YAAY,4BAClB,MAAM,eAAe,aACrB,CAAC,iBAAiB,MAAM,aAAa,IACpC,aAAa,CAAC,iBAAiB,MAAM,YAAY,IACjD,MAAM,iBAAiB,QAAQ,CAAC,iBAAiB,MAAM,cAAc,IACrE,MAAM,iBAAiB,QAAQ,CAAC,iBAAiB,MAAM,cAAc,IACrE,MAAM,iBAAiB,QAAQ,CAAC,iBAAiB,MAAM,cAAc,IACrE,MAAM,cAAc,SAClB,CAAC,iBAAiB,MAAM,WAAW,IAClC,CAAC,OAAO,SAAS,KAAK,MAAM,MAAM,WAAW,CAAC,EAElD,OAAM,IAAI,MAAM,4BAA4B;EAG9C,MAAM,YAAY,iBAAiB,MAAM,WAAW,GAChD,KAAK,MAAM,MAAM,WAAW,GAC5B;EACJ,MAAM,iBACJ,MAAM,iBAAiB,QACvB,OAAO,SAAS,UAAU,IAC1B,aAAa,KAAK,KAAK;AACzB,SAAO;GACL,SAAS;GACT,YAAY;GACZ,cAAc,MAAM;GACpB,aAAa,iBAAiB,MAAM,YAAY,GAC5C,MAAM,cACN;GACJ,eAAe,iBAAiB,OAAQ,MAAM,iBAAiB;GAC/D,eAAe,MAAM,iBAAiB;GACtC,YAAY,iBAAiB,OAAQ,MAAM,cAAc;GACzD,MACE,MAAM,QAAQ,OAAO,MAAM,SAAS,YAAY,iBAAiB,MAAM,KAAK,GAAG,GAC3E,MAAM,OACN;GACP;SACK;AACN,cAAY,OAAO,IAAI;AACvB,SAAO;;;;;;;;AASX,SAAS,aAAa,WAAoD;AACxE,KAAI,CAAC,gBAAgB,yBAAyB,UAAW,QAAO;AAChE,QAAO;EACL,SAAS;EACT,YAAY;EACZ,cAAc,aAAa,aAAa;EACxC,aAAa,aAAa,WAAW,IAAI;EACzC,eAAe;EACf,eAAe;EACf,YAAY;EACZ,MAAM;EACP;;AAGH,SAAS,wBAA8B;AACrC,KACE,wBAAwB,YACxB,CAAC,cAAc,aACf,CAAC,wBACD,CAAC,aAED;CACF,MAAMC,WAAqC;EACzC,SAAS;EACT,YAAY;EACZ,cAAc,aAAa,aAAa;EACxC,aAAa,aAAa,WAAW,IAAI;EACzC,eAAe;EACf,eAAe;EACf,YAAY;EACZ,MAAM;EACP;AACD,cAAa,MAAM,mBAAmB,qBAAqB,EAAE,KAAK,UAAU,SAAS,CAAC;;;;;;;;AASxF,IAAIC,8BAA6C;AACjD,IAAIC,8BAA6C;AACjD,IAAIC,8BAAsD;;;;;;;;AAS1D,IAAIC,eAA+C;AACnD,IAAIC,gBAAqC;AACzC,IAAI,kBAAkB;;;;;;AAMtB,IAAIC,iBAAsC;;;;;;;;AAQ1C,IAAI,sBAAsB;;;;;;;;;AAU1B,eAAe,kBAAiC;AAC9C,KAAI,gBAAgB;AAClB,kBAAgB;AAChB,mBAAiB;;CAEnB,MAAM,MAAM;AACZ,iBAAgB;AAChB,mBAAkB;AAClB,wBAAuB;AACvB,KAAI,IAAK,OAAM,IAAI,SAAS;;;;;;;AAQ9B,SAAS,eAAwB;CAC/B,MAAM,IAAI;AACV,KAAI,CAAC,EACH,OAAM,IAAI,aACR,mBACA,oGACD;AAEH,QAAO;EACL,MAAM,QAAQ,EAAE,KAAK,IAAI;EACzB,MAAM,KAAK,UAAU,EAAE,MAAM,KAAK,MAAM;EACxC,SAAS,QAAQ,EAAE,OAAO,IAAI;EAC/B;;;;;;;;AASH,eAAe,eAAe,IAA6B;AACzD,SAAQ,GAAG,MAAX;EACE,KAAK;AACH,SAAM,UAAU,MACd,EAAE,kBACA,GAAG,YACH,WAAW;IAAE,GAAG,GAAG;IAAS,iBAAiB,GAAG;IAAgB,CAAC,CAClE,CACF;AACD;EACF,KAAK;AACH,SAAM,UAAU,MACd,EAAE,kBACA,GAAG,YACH,GAAG,IACH,WAAW,GAAG,WAAW,EAAE,CAAC,CAC7B,CACF;AACD;EACF,KAAK;AACH,SAAM,UAAU,MACd,EAAE,kBAAkB,GAAG,YAAsB,GAAG,GAAa,CAC9D;AACD;EACF,KAAK;AAQH,OAAI,GAAG,eAAe,OACpB,OAAM,kBAAkB,GAAG;OAE3B,OAAM,UAAU,MACd,EAAE,MACA,GAAG,OACH,GAAG,aAAa,WAAW,GAAG,WAAW,GAAG,QAC5C,GAAG,UACJ,CACF;AAEH;;;;;;;;;;;;;;;AAgBN,eAAe,kBAAkB,IAA6B;CAC5D,MAAMC,UAAkC,EAAE,gBAAgB,oBAAoB;AAC9E,KAAI,qBAAsB,SAAQ,eAAe;AACjD,KAAI,qBACF,SAAQ,mBAAmB,UAAU;CACvC,MAAMC,OAAgC;EACpC,OAAO,GAAG;EACV,YAAY,GAAG,cAAc;EAC7B,WAAW,GAAG;EACd,GAAI,GAAG,YAAY,EAAE,WAAW,MAAM,GAAG,EAAE;EAC5C;CAID,MAAM,YAAY,GAAG,kBAAkB,QAAQ,OAAO,GAAG,CAAC;CAC1D,IAAIC;AACJ,KAAI;AACF,SAAO,MAAM,MAAM,WAAW;GAC5B,QAAQ;GACR;GACA,MAAM,KAAK,UAAU,KAAK;GAC3B,CAAC;UACK,KAAK;AACZ,QAAM,IAAI,aAAa,iBAAiB,+BAA+B,OAAO,IAAI,GAAG;;AAEvF,KAAI,CAAC,KAAK,IAAI;AAMZ,MAAI,KAAK,WAAW,OAAO,KAAK,WAAW,IACzC,OAAM,IAAI,aACR,iBACA,mDAAmD,KAAK,OAAO,GAChE;EAEH,MAAM,OAAO,MAAM,KAAK,MAAM,CAAC,YAAY,GAAG;EAC9C,IAAI,OAAO,QAAQ,KAAK;AACxB,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,OAAI,OAAO,QAAQ,OAAO,SAAS,SAAU,QAAO,OAAO,MAAM;UAC3D;AAGR,QAAM,IAAI,aAAa,MAAM,mCAAmC,KAAK,UAAU,KAAK;;;AAIxF,SAAS,kBAAgC;AACvC,KAAI,CAAC,cACH,iBAAgB,IAAI,aAAa;EAC/B,OAAO,cAAc;EAKrB,YAAY,kBAAkB,wBAAwB;EACtD,UAAU;EAIV,qBAAqB;GACnB,WACE,wBAAwB,uBACnB,cAAc,WAAW,IAAI,OAC9B;GACN,aAAa,cAAc,aAAa,IAAI;GAC7C;EACD,YAAY,IAAI,QAAQ;AAItB,OAAI,OAAO,YAAY,YACrB,SAAQ,KACN,wCAAwC,GAAG,KAAK,IAAI,GAAG,cAAc,GAAG,SAAS,IAAI,KACrF,IACD;;EAGN,CAAC;AAEJ,QAAO;;;;;;;AAQT,eAAe,qBAAoC;AACjD,KAAI,CAAC,aAAc;AACnB,KAAI;AACF,QAAM,iBAAiB,CAAC,OAAO;UACxB,KAAK;AACZ,MAAI,OAAO,YAAY,YACrB,SAAQ,KAAK,qDAAqD,IAAI;;;AAW5E,eAAe,aACb,MACA,KACY;AACZ,QAAQ,MAAM,UAAU,MAAM,EAAE,kBAAkB,MAAM,WAAW,IAAI,CAAC,CAAC;;AAE3E,eAAe,aACb,MACA,IACA,KACY;AACZ,QAAQ,MAAM,UAAU,MACtB,EAAE,kBAAkB,MAAM,IAAI,WAAW,IAAI,CAAC,CAC/C;;AAEH,eAAe,aACb,MACA,IACyC;AACzC,QAAQ,MAAM,UAAU,MAAM,EAAE,kBAAkB,MAAM,GAAG,CAAC;;AAK9D,SAAS,cAA4B;AACnC,KAAI,CAAC,aACH,OAAM,IAAI,aACR,mBACA,4GACD;AAEH,QAAO;;;;;;;;;;AAWT,eAAe,SAAY,IAAiD;AAC1E,KAAI;AACF,SAAO,MAAM,GAAG,aAAa,CAAC;UACvB,KAAK;AACZ,QAAM,eAAe,IAAI;;;AAa7B,MAAM,kCAAkB,IAAI,KAAqB;;;;;;;AAQjD,SAAS,oBAAoB,IAA4C;CACvE,MAAMC,OAAgC;EAAE,YAAY,GAAG;EAAY,IAAI,GAAG;EAAI;AAC9E,KAAI,GAAG,OAAO,OAAW,MAAK,QAAQ,GAAG;AACzC,KAAI,GAAG,QAAQ,OAAW,MAAK,SAAS,GAAG;AAC3C,KAAI,GAAG,QAAQ,OAAW,MAAK,SAAS,GAAG;AAC3C,KAAI,GAAG,UAAU,OAAW,MAAK,WAAW,GAAG;AAC/C,KAAI,GAAG,aAAa,OAAW,MAAK,cAAc,GAAG;AACrD,KAAI,GAAG,eAAe,OAAW,MAAK,iBAAiB,GAAG;AAC1D,KAAI,GAAG,mBAAmB,OAAW,MAAK,qBAAqB,GAAG;AAClE,QAAO;;AAGT,SAAS,gBAAgB,MAAmC;AAC1D,KAAI,CAAC,QAAQ,CAAC,qBAAsB,QAAO;AAC3C,QAAO;EACL,cAAc;EACd,cAAc,wBAAwB;EACtC;EACA,WAAW,4BAA4B;EACxC;;AAGH,SAAS,sBAAsB,SAA+B;AAC5D,MAAK,MAAM,MAAM,gBACf,KAAI;AACF,KAAG,QAAQ;UACJ,KAAK;AAIZ,MAAI,OAAO,YAAY,YACrB,SAAQ,MAAM,2CAA2C,IAAI;;;AAMrE,eAAe,qBAAqB,QAAmC;AACrE,wBAAuB,OAAO;AAC9B,wBAAuB,OAAO;AAC9B,4BACE,OAAO,cAAc,OAAO,SAAS,KAAK,MAAM,OAAO,WAAW,CAAC,GAC/D,OAAO,aACP,IAAI,KAAK,KAAK,KAAK,GAAG,uBAAuB,CAAC,aAAa;AACjE,gBAAe,OAAO;AACtB,wBAAuB;AACvB,uBAAsB,gBAAgB,OAAO,KAAK,CAAC;;;;;;;AAQrD,SAAS,sBAAqD;AAC5D,KAAI,CAAC,wBAAwB,CAAC,qBAAsB,QAAO;AAC3D,QAAO;EACL,aAAa;EACb,eAAe,UAAU;EAC1B;;;AAIH,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;;;;;;;AAQvB,SAAS,qBACP,MACA,SACgE;AAChE,cAAa;AACb,KAAI,CAAC,qBACH,OAAM,IAAI,aACR,mBACA,8FACD;AAEH,KAAI,CAAC,QAAQ,eAAe,QAAQ,YAAY,MAAM,KAAK,GACzD,OAAM,IAAI,aAAa,oBAAoB,0BAA0B;CAEvE,MAAMH,UAAkC;EACtC,aAAa;EACb,gBAAgB;EAChB,QAAQ;EACT;AACD,KAAI,qBAAsB,SAAQ,mBAAmB,UAAU;CAE/D,MAAM,WAAW,eAAe,QAAQ;AACxC,QAAO;EACL,KAAK,GAAG,kBAAkB,QAAQ,OAAO,GAAG,GAAG;EAC/C;EACA,MAAM,KAAK,UAAU;GAAE,GAAG;GAAU,QAAQ;GAAM,CAAC;EACpD;;;;;;;;;;AAWH,eAAe,mBAAmB,MAA+B;AAC/D,KAAI,4BACF,QAAO,GAAG,4BAA4B,QAAQ,OAAO,GAAG,CAAC,GAAG,YAAY,KAAK;AAG/E,QAAO,WADW,MAAM,2BAA2B,CACvB,GAAG,uBAAuB,GAAG,YAAY,KAAK;;AAG5E,eAAe,4BAA6C;AAC1D,KAAI,4BACF,QAAO;AAET,KACE,wBACA,mBAAmB,KAAK,qBAAqB,EAC7C;AACA,gCAA8B,qBAAqB,aAAa;AAChE,SAAO;;AAET,KAAI,CAAC,6BAA6B;EAChC,MAAM,sBAAsB;EAC5B,MAAM,cAAc,YAAY;GAC9B,MAAM,OAAO,MAAM,KAAK,YAAY,MAAM;AAC1C,OAAI,CAAC,KAAK,MAAM,CAAC,KAAK,kBACpB,OAAM,IAAI,aACR,6BACA,sIACA,KACD;AAEH,OACE,yBAAyB,uBACzB,CAAC,4BAED,+BAA8B,KAAK;AAErC,UAAO,KAAK;MACV;AACJ,gCAA8B;AAC9B,EAAK,WAAW,WACR;AACJ,OAAI,gCAAgC,WAClC,+BAA8B;WAG5B;AACJ,OAAI,gCAAgC,WAClC,+BAA8B;IAGnC;;AAEH,QAAO;;;;;;;;;;;AAgCT,IAAa,wBAAb,MAAa,sBAAsB;CACjC,AAAS;CAET,YAAY,SAAiB;EAC3B,MAAM,aAAa,QAAQ,MAAM;AACjC,MAAI,CAAC,cAAc,yBAAyB,WAAW,CACrD,OAAM,IAAI,aACR,oBACA,4EACD;AAEH,OAAK,UAAU;;CAGjB,QAAQ,SAAwC;AAC9C,SAAO,IAAI,sBAAsB,QAAQ;;CAG3C,QAAQ,SAAwC;AAC9C,SAAO,KAAK,QAAQ,QAAQ;;CAG9B,WAAwB,MAAyC;EAC/D,MAAM,UAAU,KAAK;AACrB,SAAO,IAAI,uBACT;GACE,SAAS;GACT,mBAAmB;IACjB,MAAM,UAAU,qBAAqB;AACrC,WAAO,UAAU;KAAE,GAAG;KAAS,mBAAmB;KAAS,GAAG;;GAEjE,EACD,KACD;;CAGH,cAAc;EACZ,MAAM,OACJ,MACA,UAAuB,EAAE,KAExB,MAAM,UAAU,MACf,EAAE,gBAAgB,MAAM,WAAW,QAAQ,EAAE,KAAK,QAAQ,CAC3D;EAEH,SAAS,OAAoB,MAAc,OACxC,MAAM,UAAU,MACf,EAAE,mBAAmB,MAAM,IAAI,KAAK,QAAQ,CAC7C;EAEH,QAAQ,OACN,MACA,QAEC,MAAM,UAAU,MACf,EAAE,kBAAkB,MAAM,WAAW,IAAI,EAAE,KAAK,QAAQ,CACzD;EAEH,QAAQ,OACN,MACA,IACA,QAEC,MAAM,UAAU,MACf,EAAE,kBAAkB,MAAM,IAAI,WAAW,IAAI,EAAE,KAAK,QAAQ,CAC7D;EAEH,QAAQ,OACN,MACA,OAEC,MAAM,UAAU,MACf,EAAE,kBAAkB,MAAM,IAAI,KAAK,QAAQ,CAC5C;EAEH,QAAQ,OACN,MACA,KACA,YAEC,MAAM,UAAU,MACf,EAAE,kBACA,MACA,WAAW,IAAI,EACf,WAAW;GACT,aAAa,QAAQ;GACrB,iBAAiB,QAAQ;GAC1B,CAAC,EACF,KAAK,QACN,CACF;EAEH,aAAa,OACX,MACA,WAEC,MAAM,UAAU,MACf,EAAE,uBAAuB,MAAM,WAAW,OAAO,EAAE,KAAK,QAAQ,CACjE;EAEH,SAAS,OAAoB,MAAc,OACxC,MAAM,UAAU,MACf,EAAE,mBAAmB,MAAM,IAAI,KAAK,QAAQ,CAC7C;EAEH,aAAa,OACX,eACmC;AAOnC,WANa,MAAM,UAAU,MAC3B,EAAE,uBACA,WAAW,WAAW,IAAI,oBAAoB,CAAC,EAC/C,KAAK,QACN,CACF,EACU;;EAGb,aAAa,OACX,MACA,YAEC,MAAM,UAAU,MACf,EAAE,uBAAuB,MAAM,WAAW,QAAQ,EAAE,KAAK,QAAQ,CAClE;EAEH,OAAO,OAAO,MAAc,WACzB,MAAM,UAAU,MACf,EAAE,iBACA,MACA,SAAS,WAAW,OAAO,GAAG,QAC9B,KAAK,QACN,CACF;EAEH,kBAAkB,OAChB,MACA,KACA,SAEA,eAAe;GACb,OAAO,KAAK;GACZ,cACE,UAAU,MACR,EAAE,kBAAkB,MAAM,WAAW,IAAI,EAAE,KAAK,QAAQ,CACzD;GACH,UAAU,KAAK;GAChB,CAAC;EAEJ,kBAAkB,OAChB,MACA,IACA,KACA,SAEA,eAAe;GACb,OAAO,KAAK;GACZ,cACE,UAAU,MACR,EAAE,kBAAkB,MAAM,IAAI,WAAW,IAAI,EAAE,KAAK,QAAQ,CAC7D;GACH,UAAU,KAAK;GAChB,CAAC;EAEJ,kBAAkB,OAChB,MACA,IACA,SAEA,eAAe;GACb,OAAO,KAAK;GACZ,cACE,UAAU,MACR,EAAE,kBAAkB,MAAM,IAAI,KAAK,QAAQ,CAC5C;GACH,UAAU,KAAK;GAChB,CAAC;EAEJ,OAAO;GACL,KAAK,QAAgB,WAAgC;IAAE;IAAQ,IAAI;IAAM;IAAO;GAChF,KAAK,QAAgB,WAAgC;IAAE;IAAQ,IAAI;IAAM;IAAO;GAChF,KAAK,QAAgB,WAAgC;IAAE;IAAQ,IAAI;IAAM;IAAO;GAChF,MAAM,QAAgB,WAAgC;IAAE;IAAQ,IAAI;IAAO;IAAO;GAClF,KAAK,QAAgB,WAAgC;IAAE;IAAQ,IAAI;IAAM;IAAO;GAChF,MAAM,QAAgB,WAAgC;IAAE;IAAQ,IAAI;IAAO;IAAO;GAClF,KAAK,QAAgB,YAAmC;IACtD;IACA,IAAI;IACJ,OAAO;IACR;GACD,SAAS,YAA4B;IAAE;IAAQ,IAAI;IAAW;GAC9D,YAAY,YAA4B;IAAE;IAAQ,IAAI;IAAe;GACrE,MAAM,GAAG,aAA+B,EAAE,KAAK,SAAS;GACxD,KAAK,GAAG,aAA+B,EAAE,IAAI,SAAS;GACtD,MAAM,YAA4B,EAAE,KAAK,QAAQ;GAClD;EACF;;;;;;;;AASH,MAAa,OAAO;CAOlB,WAAW,OAAO,WAAsC;AACtD,MAAI,CAAC,OAAO,aAAa,OAAO,UAAU,MAAM,KAAK,GACnD,OAAM,IAAI,MAAM,wCAAwC;AAE1D,MAAI,CAAC,OAAO,aAAa,OAAO,UAAU,MAAM,KAAK,GACnD,OAAM,IAAI,MAAM,wCAAwC;AAE1D,MAAI,OAAO,YAAY,UAAa,OAAO,eAAe,OACxD,OAAM,IAAI,MAAM,yDAAyD;OACpE;GACL,MAAM,EAAE,aAAa,MAAM,gBAAgB;GAC3C,MAAM,oBAAoB,OAAO,cAAc,OAAO;AACtD,OAAI,sBAAsB,OACxB,OAAM,SAAS,EAAE,gBAAgB,mBAAmB,CAAC;OAuD9C,OAAM,UAAU;;EAG3B,MAAM,qBAAqB,OAAO,sBAAsB;AACxD,MAAI,CAAC,0BAA0B,SAAS,mBAAmB,CACzD,OAAM,IAAI,aACR,kBACA,qEACD;EAEH,MAAM,cAAc,IAAI,wBACtB,OAAO,iBAAiB,OACzB;EAID,MAAM,mBACJ,uBAAuB,WACnB,aAAa,OAAO,UAAU,GAC9B,sBAAsB,aAAa,OAAO,UAAU;EAc1D,IAAII;AACJ,MAAI;GACF,MAAM,EAAE,cAAc,MAAM,gBAAgB;AAC5C,cAAW,UAAU,KACnB,WAAW;IACT,SAAS,OAAO;IAChB,UAAU,OAAO;IACjB,YAAY,OAAO;IACnB,cAAc;IACd,qBAAqB,YAAY;IACjC,kBAAkB,OAAO;IACzB,OAAO,OAAO;IACf,CAAC,EACF,WAAW,MAAM,KAAK,WAAW,CAClC;AACD,OAAI,iBAGF,UAAS,gBACP,WAAW;IACT,cAAc,iBAAiB;IAC/B,aAAa,iBAAiB;IAC9B,eAAe,iBAAiB;IAChC,eAAe,iBAAiB;IAChC,YAAY,iBAAiB;IAC9B,CAAC,CACH;WAEI,KAAK;AACZ,SAAM,eAAe,IAAI;;AAO3B,QAAM,iBAAiB;AACvB,iBAAe;AACf,wBAAsB;AACtB,MAAI,uBAAuB,SACzB,aAAY,OAAO,mBAAmB,OAAO,UAAU,CAAC;AAE1D,iBAAe;AACf,yBAAuB,OAAO;AAC9B,sBAAoB,OAAO,UAAU;AACrC,yBAAuB,OAAO;AAC9B,yBAAuB,SAAS,iBAAiB,GAC5C,kBAAkB,iBAAiB,OACpC;AACJ,yBAAuB,kBAAkB,iBAAiB;AAC1D,6BAA2B,SAAS,iBAAiB,GAChD,kBAAkB,cAAc,OACjC;AACJ,iBAAe,SAAS,iBAAiB,GACpC,kBAAkB,QAAQ,OAC3B;AACJ,gCAA8B,OAAO,oBAAoB;AAGzD,gCAA8B;AAC9B,gCAA8B;AAC9B,MACE,CAAC,+BACD,CAAC,mBAAmB,KAAK,OAAO,UAAU,CAE1C,CAAK,2BAA2B,CAAC,YAAY,GAAG;AAIlD,yBAAuB;;CAIzB,IAAI,YAAoB;AACtB,MAAI,CAAC,qBACH,OAAM,IAAI,aACR,mBACA,4GACD;AAEH,SAAO;;CAIT,IAAI,cAAsB;AACxB,SAAO,aAAa,CAAC,aAAa;;CAIpC,IAAI,YAAgC;AAClC,SAAO,aAAa,CAAC,WAAW,IAAI;;CAItC,IAAI,kBAA2B;AAC7B,SAAO,aAAa,CAAC,iBAAiB;;CAIxC,WAAW,YAA2B;AACpC,eAAa,CAAC,SAAS,QAAQ;;CAMjC,QAAQ;EAoBN,OAAO,OACL,OACA,YACA,YACkB;GAGlB,MAAM,eAAe,kBAAkB,iBAAiB,CAAC,QAAQ,GAAG;AACpE,OAAI;AACF,UAAM,UAAU,MAAM,EAAE,MACtB,OACA,aAAa,WAAW,WAAW,GAAG,QACtC,SAAS,UACV,CAAC;YACK,KAAK;AAIZ,QAAI,mBAAmB,eAAe,IAAI,EAAE;AAM1C,WAAM,iBAAiB,CAAC,QAAQ;MAC9B,MAAM;MACN;MACA;MACA,WAAW,SAAS;MACpB,6BAAY,IAAI,MAAM,EAAC,aAAa;MACrC,EAAE,aAAa;AAChB;;AAEF,UAAM;;;EAkBV,SAAS,OACP,OACA,YACA,YACgC;AAChC,OAAI,CAAC,qBACH,OAAM,IAAI,aACR,mBACA,uGACD;AAEH,OAAI,CAAC,SAAS,MAAM,MAAM,KAAK,GAC7B,OAAM,IAAI,aACR,oBACA,kDACD;GAGH,MAAMC,YAAqC,EAAE,MAAM,OAAO;AAC1D,OAAI,eAAe,OAAW,WAAU,gBAAgB;AACxD,OAAI,WAAW,YAAY,QAAS,WAAU,aAAa,QAAQ,UAAU;GAE7E,MAAML,UAAkC;IACtC,gBAAgB;IAChB,aAAa;IACd;AACD,OAAI,qBAAsB,SAAQ,mBAAmB,UAAU;GAE/D,IAAIE;AACJ,OAAI;AACF,WAAO,MAAM,MACX,GAAG,kBAAkB,QAAQ,OAAO,GAAG,CAAC,4BACxC;KACE,QAAQ;KACR;KACA,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,CAAC;KAC3C,CACF;YACM,KAAK;AACZ,UAAM,IAAI,aACR,iBACA,wBAAwB,MAAM,oBAAoB,OAAO,IAAI,GAC9D;;GAGH,MAAM,OAAO,MAAM,KAAK,MAAM;GAC9B,IAAII,SAAkB;AACtB,OAAI,KAAK,SAAS,EAChB,KAAI;AACF,aAAS,KAAK,MAAM,KAAK;WACnB;AACN,aAAS;;AAGb,OAAI,CAAC,KAAK,IAAI;IACZ,MAAM,WACJ,UAAU,OAAO,WAAW,WACvB,SACD;AASN,UAAM,IAAI,aAPR,OAAO,UAAU,OAAO,SAAS,WAC7B,SAAS,MAAM,OACf,QAAQ,KAAK,UAEjB,OAAO,UAAU,OAAO,YAAY,WAChC,SAAS,MAAM,UACf,wBAAwB,MAAM,wBAAwB,KAAK,UAC3B,OAAO;;AAE/C,UAAQ,OAAwC;;EAEnD;CAGD,MAAM;EACJ,mBAAmB,YAAiC;GAClD,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,mBAAmB,CAAC;AACvD,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAET,iBAAiB,OACf,OACA,aACwB;GACxB,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,gBACjC,OACA,SACD,CAAC;AACF,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAET,iBAAiB,OACf,OACA,aACwB;GACxB,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,gBACjC,OACA,SACD,CAAC;AACF,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAQT,iBAAiB,OAAO,kBAA+C;GACrE,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,iBACjC,SACA,cACD,CAAC;AACF,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAQT,kBAAkB,OAAO,YAAyC;GAChE,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,iBACjC,UACA,QACD,CAAC;AACF,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAST,iBAAiB,OAAO,UAAiC;AACvD,SAAM,UAAU,MAAM,EAAE,gBAAgB,MAAM,CAAC;;EASjD,gBAAgB,OAAO,OAAe,SAAsC;GAC1E,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,eAAe,OAAO,KAAK,CAAC;AAC/D,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAWT,eAAe,OAAO,UAAiC;AACrD,SAAM,UAAU,MAAM,EAAE,cAAc,MAAM,CAAC;;EAO/C,cAAc,OAAO,OAAe,SAAsC;GACxE,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,aAAa,OAAO,KAAK,CAAC;AAC7D,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAUT,kBAAkB,OAAO,UAAiC;AACxD,SAAM,UAAU,MAAM,EAAE,iBAAiB,MAAM,CAAC;;EAOlD,iBAAiB,OAAO,UAAuC;GAC7D,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,gBAAgB,MAAM,CAAC;AAC1D,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAST,aAAa,OACX,UACA,eACwB;GACxB,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,YACjC,UACA,WACD,CAAC;AACF,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EA8BT,YAAY,OAAO,OAAe,SAAgC;AAChE,UAAQ,MAAM,UAAU,MAAM,EAAE,WAAW,OAAO,KAAK,CAAC;;EAQ1D,cAAc,OAAO,OAAe,SAAgC;AAClE,UAAQ,MAAM,UAAU,MAAM,EAAE,aAAa,OAAO,KAAK,CAAC;;EAgC5D,mBAAmB,OAAO,OAAe,aAAoC;AAC3E,UAAQ,MAAM,UAAU,MAAM,EAAE,kBAAkB,OAAO,SAAS,CAAC;;EASrE,cAAc,YAA2B;AACvC,UAAQ,MAAM,UAAU,MAAM,EAAE,cAAc,CAAC;;EAIjD,gBAAgB,YAA2B;AACzC,UAAQ,MAAM,UAAU,MAAM,EAAE,gBAAgB,CAAC;;EAInD,aAAa,YAA2B;AACtC,UAAQ,MAAM,UAAU,MAAM,EAAE,aAAa,CAAC;;EAIhD,cAAc,YAA2B;AACvC,UAAQ,MAAM,UAAU,MAAM,EAAE,cAAc,CAAC;;EAWjD,uBAAuB,YAAyC;AAC9D,UAAQ,MAAM,UAAU,MAAM,EAAE,oBAAoB,CAAC;;EAEvD,SAAS,OAAO,oBAAoB,UAAyB;AAC3D,SAAM,UAAU,MAAM,EAAE,QAAQ,kBAAkB,CAAC;AACnD,0BAAuB;AACvB,0BAAuB;AACvB,8BAA2B;AAC3B,kBAAe;AACf,0BAAuB;AACvB,SAAM,oBAAoB;AAC1B,yBAAsB,KAAK;;EAE7B,SAAS,YAAiC;GACxC,MAAM,IAAK,MAAM,UAAU,MAAM,EAAE,gBAAgB,CAAC;AACpD,SAAM,qBAAqB,EAAE;AAC7B,UAAO;;EAET,IAAI,YAA2B;GAC7B,MAAM,OAAQ,MAAM,UAAU,MAAM,EAAE,IAAI,CAAC;AAC3C,kBAAe;AACf,0BAAuB;AACvB,UAAO;;EAST,YAAY,YAAqC;AAE/C,OAAI,CADS,aAAa,CAChB,iBAAiB,CAAE,QAAO;GACpC,MAAM,OAAQ,MAAM,UAAU,MAAM,EAAE,IAAI,CAAC;AAC3C,kBAAe;AACf,0BAAuB;AACvB,UAAO,gBAAgB,KAAK;;EAQ9B,gBAAgB,YAA6B;AAC3C,UAAO,aAAa,CAAC,aAAa;;EAYpC,oBACE,OACiB;AACjB,mBAAgB,IAAI,GAAG;AACvB,gBAAa;AACX,oBAAgB,OAAO,GAAG;;;EAG/B;CAGD,OAAO;EAOL,KAAK,OAAO,WAAmC;AAC7C,UAAQ,MAAM,UAAU,MAAM,EAAE,SAC9B,UAAU,OACX,CAAC;;EAYJ,UACE,MACA,SACkB;GAClB,MAAM,SAAS,OAAO,SAAS;GAC/B,MAAM,SAAS,SAAU,OAAkB;GAC3C,MAAM,QAAQ,SAAU,QAAQ,EAAE,GAAK;AACvC,UAAO,UAAU,MAAM,EAAE,YACvB,UAAU,QACV,WAAW,MAAM,CAClB,CAAC;;EAML;CAGD,UAAU;EAGR,MAAM,YAAmC;AACvC,UAAQ,MAAM,UAAU,MAAM,EAAE,cAAc,CAAC;;EAQjD,QAAQ,OAAO,cAAqC;AAClD,SAAM,UAAU,MAAM,EAAE,eAAe,UAAU,CAAC;;EAGrD;CAQD,UAAU,YAA2C;AACnD,eAAa;AACb,SAAO,IAAI,sBAAsB,QAAQ;;CAI3C,UAAU,YAA2C;AACnD,eAAa;AACb,SAAO,IAAI,sBAAsB,QAAQ;;CAmB3C,aAA0B,SACxB,IAAI,uBACF;EAAE,SAAS;EAAmB,aAAa;EAAqB,EAChE,KACD;CAGH,aAAa;EAEX,MAAM,OACJ,MACA,UAAuB,EAAE,KACI;AAC7B,UAAQ,MAAM,UAAU,MAAM,EAAE,gBAC9B,MACA,WAAW,QAAQ,CACpB,CAAC;;EAGJ,SAAS,OAAoB,MAAc,OAA2B;AACpE,UAAQ,MAAM,UAAU,MAAM,EAAE,mBAAmB,MAAM,GAAG,CAAC;;EAE/D,QAAQ,OACN,MACA,QACe;AAKf,OAAI,iBAAiB;IACnB,MAAM,kBACH,OAAO,IAAI,uBAAuB,WAC9B,IAAI,qBACL,WAAc,mBAAmB;IACvC,MAAM,UAAU;KAAE,GAAG;KAAK,iBAAiB;KAAgB;IAG3D,MAAM,eAAe,kBAAkB,iBAAiB,CAAC,QAAQ,GAAG;AACpE,QAAI;AACF,YAAQ,MAAM,UAAU,MACtB,EAAE,kBAAkB,MAAM,WAAW,QAAQ,CAAC,CAC/C;aACM,KAAK;AACZ,SAAI,eAAe,IAAI,EAAE;AACvB,YAAM,iBAAiB,CAAC,QAAQ;OAC9B,MAAM;OACN,YAAY;OACZ,SAAS;OACT;OACD,EAAE,aAAa;AAChB,aAAO;OAAE,GAAG;OAAK,eAAe;OAAM;;AAExC,WAAM;;;AAGV,UAAQ,MAAM,UAAU,MAAM,EAAE,kBAC9B,MACA,WAAW,IAAI,CAChB,CAAC;;EAEJ,QAAQ,OACN,MACA,IACA,QACe;AACf,OAAI,iBAAiB;IAGnB,MAAM,eAAe,kBAAkB,iBAAiB,CAAC,QAAQ,GAAG;AACpE,QAAI;AACF,YAAQ,MAAM,UAAU,MACtB,EAAE,kBAAkB,MAAM,IAAI,WAAW,IAAI,CAAC,CAC/C;aACM,KAAK;AACZ,SAAI,eAAe,IAAI,EAAE;AACvB,YAAM,iBAAiB,CAAC,QAAQ;OAC9B,MAAM;OACN,YAAY;OACZ;OACA,SAAS;OACV,EAAE,aAAa;AAChB,aAAO;OAAE;OAAI,GAAG;OAAK,eAAe;OAAM;;AAE5C,WAAM;;;AAGV,UAAQ,MAAM,UAAU,MAAM,EAAE,kBAC9B,MACA,IACA,WAAW,IAAI,CAChB,CAAC;;EAEJ,QAAQ,OACN,MACA,OAC4C;AAC5C,OAAI,iBAAiB;IAGnB,MAAM,eAAe,kBAAkB,iBAAiB,CAAC,QAAQ,GAAG;AACpE,QAAI;AACF,YAAQ,MAAM,UAAU,MAAM,EAAE,kBAAkB,MAAM,GAAG,CAAC;aAGrD,KAAK;AACZ,SAAI,eAAe,IAAI,EAAE;AACvB,YAAM,iBAAiB,CAAC,QAAQ;OAC9B,MAAM;OACN,YAAY;OACZ;OACD,EAAE,aAAa;AAChB,aAAO,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE;;AAEpC,WAAM;;;AAGV,UAAQ,MAAM,UAAU,MAAM,EAAE,kBAAkB,MAAM,GAAG,CAAC;;EAmB9D,QAAQ,OACN,MACA,KACA,YACe;GAGf,MAAM,OAAO;IACX,aAAa,QAAQ;IACrB,iBAAiB,QAAQ;IAC1B;AACD,UAAQ,MAAM,UAAU,MAAM,EAAE,kBAC9B,MACA,WAAW,IAAI,EACf,WAAW,KAAK,CACjB,CAAC;;EAYJ,aAAa,OACX,MACA,WAC8B;AAC9B,UAAQ,MAAM,UAAU,MAAM,EAAE,uBAC9B,MACA,WAAW,OAAO,CACnB,CAAC;;EAOJ,SAAS,OAAoB,MAAc,OAA2B;AACpE,UAAQ,MAAM,UAAU,MAAM,EAAE,mBAAmB,MAAM,GAAG,CAAC;;EAgB/D,aAAa,OACX,eACmC;AAInC,WAHa,MAAM,UAAU,MAC3B,EAAE,uBAAuB,WAAW,WAAW,IAAI,oBAAoB,CAAC,CAAC,CAC1E,EACU;;EAcb,aAAa,OACX,MACA,YACgC;AAChC,UAAQ,MAAM,UAAU,MAAM,EAAE,uBAC9B,MACA,WAAW,QAAQ,CACpB,CAAC;;EAOJ,OAAO,OAAO,MAAc,WAAqC;AAC/D,UAAQ,MAAM,UAAU,MAAM,EAAE,iBAC9B,MACA,SAAS,WAAW,OAAO,GAAG,OAC/B,CAAC;;EAuBJ,kBAAkB,OAChB,MACA,KACA,SAEA,eAAe;GACb,OAAO,KAAK;GACZ,cAAc,aAAgB,MAAM,IAAI;GACxC,UAAU,KAAK;GAChB,CAAC;EAEJ,kBAAkB,OAChB,MACA,IACA,KACA,SAEA,eAAe;GACb,OAAO,KAAK;GACZ,cAAc,aAAgB,MAAM,IAAI,IAAI;GAC5C,UAAU,KAAK;GAChB,CAAC;EAEJ,kBAAkB,OAChB,MACA,IACA,SAEA,eAAe;GACb,OAAO,KAAK;GACZ,cAAc,aAAa,MAAM,GAAG;GACpC,UAAU,KAAK;GAChB,CAAC;EAEJ,OAAO;GACL,KAAK,QAAgB,WAAgC;IACnD;IACA,IAAI;IACJ;IACD;GACD,KAAK,QAAgB,WAAgC;IACnD;IACA,IAAI;IACJ;IACD;GACD,KAAK,QAAgB,WAAgC;IACnD;IACA,IAAI;IACJ;IACD;GACD,MAAM,QAAgB,WAAgC;IACpD;IACA,IAAI;IACJ;IACD;GACD,KAAK,QAAgB,WAAgC;IACnD;IACA,IAAI;IACJ;IACD;GACD,MAAM,QAAgB,WAAgC;IACpD;IACA,IAAI;IACJ;IACD;GACD,KAAK,QAAgB,YAAmC;IACtD;IACA,IAAI;IACJ,OAAO;IACR;GACD,QAAQ,QAAgB,YAAmC;IACzD;IACA,IAAI;IACJ,OAAO;IACR;GACD,OAAO,QAAgB,aAA6B;IAClD;IACA,IAAI;IACJ,OAAO;IACR;GACD,QAAQ,QAAgB,aAA6B;IACnD;IACA,IAAI;IACJ,OAAO;IACR;GACD,SAAS,YAA4B;IAAE;IAAQ,IAAI;IAAW;GAC9D,YAAY,YAA4B;IAAE;IAAQ,IAAI;IAAe;GACrE,MAAM,GAAG,aAA+B,EAAE,KAAK,SAAS;GACxD,KAAK,GAAG,aAA+B,EAAE,IAAI,SAAS;GACtD,MAAM,YAA4B,EAAE,KAAK,QAAQ;GAClD;EACF;CAGD,SAAS;EACP,SAAS,OAAO,WAMY;AAC1B,UAAQ,MAAM,UAAU,MAAM,EAAE,eAC9B,OAAO,QACP,OAAO,UACP,OAAO,UACP,OAAO,WACP,OAAO,iBAAiB,OACzB,CAAC;;EAMJ,QAAQ,OAAO,WAKY;GACzB,MAAM,WACJ,OAAO,aACN,UAAU,OAAO,OAAO,OAAO,KAAK,OAAO;GAC9C,MAAM,WAAW,OAAO,KAAK,QAAQ;GACrC,MAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ;IACzC,QAAQ,OAAO;IACf;IACA;IACA,WAAW,OAAO,KAAK;IACvB,eAAe,OAAO;IACvB,CAAC;GAEF,MAAMC,gBAAwC,EAAE;AAChD,QAAK,MAAM,CAAC,GAAG,MAAM,QAAQ,eAC3B,eAAc,KAAK;GAErB,MAAM,UAAU,MAAM,MAAM,QAAQ,YAAY;IAC9C,QAAQ;IACR,SAAS;IACT,MAAM,OAAO;IACd,CAAC;AACF,OAAI,CAAC,QAAQ,GACX,OAAM,IAAI,MAAM,oCAAoC,QAAQ,SAAS;AAGvE,UAAQ,MAAM,UAAU,MAAM,EAAE,cAC9B,QAAQ,WACR,QAAQ,SACT,CAAC;;EASJ,MAAM,OAAO,WAAgD;AAC3D,UAAQ,MAAM,UAAU,MAAM,EAAE,YAC9B,UAAU,OACX,CAAC;;EAOJ,QAAQ,OAAO,YAAmC;AAChD,SAAM,UAAU,MAAM,EAAE,cAAc,QAAQ,CAAC;;EAWjD,UAAU,OAAO,YAAmC;GAClD,MAAM,QAAS,MAAM,UAAU,MAAM,EAAE,gBACrC,QACD,CAAC;AAUF,UAAO,IAAI,KAAK,CAAC,MAA6B,CAAC;;EAElD;CAGD,OAAO,EASL,SAAS,OAAO,SAAwC;AACtD,MAAI,CAAC,qBACH,OAAM,IAAI,aACR,mBACA,yGACD;AAEH,MAAI,CAAC,QAAQ,KAAK,MAAM,KAAK,GAC3B,OAAM,IAAI,aACR,oBACA,kDACD;EAGH,MAAM,MAAM,GAAG,kBAAkB,QAAQ,OAAO,GAAG,CAAC,4BAA4B,YAC9E,KACD;EACD,IAAIL;AACJ,MAAI;AACF,UAAO,MAAM,MAAM,KAAK;IACtB,QAAQ;IACR,SAAS,EAAE,aAAa,sBAAsB;IAC/C,CAAC;WACK,KAAK;AACZ,SAAM,IAAI,aACR,iBACA,uBAAuB,KAAK,oBAAoB,OAAO,IAAI,GAC5D;;EAGH,MAAM,OAAO,MAAM,KAAK,MAAM;EAC9B,IAAII,SAAkB;AACtB,MAAI,KAAK,SAAS,EAChB,KAAI;AACF,YAAS,KAAK,MAAM,KAAK;UACnB;AACN,YAAS;;AAGb,MAAI,CAAC,KAAK,IAAI;GACZ,MAAM,WACJ,UAAU,OAAO,WAAW,WACvB,SACD;AASN,SAAM,IAAI,aAPR,OAAO,UAAU,OAAO,SAAS,WAC7B,SAAS,MAAM,OACf,QAAQ,KAAK,UAEjB,OAAO,UAAU,OAAO,YAAY,WAChC,SAAS,MAAM,UACf,uBAAuB,KAAK,wBAAwB,KAAK,UACzB,OAAO;;AAE/C,SAAQ,OAAkC;IAE7C;CAGD,MAAM;EASJ,aAAa,OACX,YAC+B;AAC/B,UAAQ,MAAM,UAAU,MAAM,EAAE,gBAC9B,WAAW,QAAQ,CACpB,CAAC;;EASJ,aAAa,OACX,UAC+B;AAC/B,UAAQ,MAAM,UAAU,MAAM,EAAE,gBAC9B,WAAW,MAAM,CAClB,CAAC;;EAEL;CAqBD,SAAS;EAaP,cAA4B;AAG1B,OAAI,gBAAgB;AAClB,oBAAgB;AAChB,qBAAiB;;AAEnB,qBAAkB;AAGlB,0BAAuB;GACvB,MAAM,QAAQ;GACd,MAAM,IAAI,iBAAiB;AAC3B,oBAAiB,EAAE,gBAAgB;AAEnC,GAAK,EAAE,OAAO;AACd,gBAAa;AAIX,QAAI,wBAAwB,MAAO;AACnC,sBAAkB;AAClB,QAAI,gBAAgB;AAClB,qBAAgB;AAChB,sBAAiB;;;;EAKvB,iBAA0B;EAE1B,cAAc,YAA6B,iBAAiB,CAAC,cAAc;EAE3E,SAAS,YAAiC,iBAAiB,CAAC,SAAS;EAKrE,OAAO,YAAkC,iBAAiB,CAAC,OAAO;EAElE,OAAO,YAA2B,iBAAiB,CAAC,OAAO;EAC5D;CAGD,MAAM;EACJ,UAAU,OACR,OACA,UACA,aACuB;AACvB,UAAQ,MAAM,UAAU,MAAM,EAAE,aAC9B,OACA,UACA,YAAY,OACb,CAAC;;EAEJ,YAAY,OAAO,UAAiC;AAClD,SAAM,UAAU,MAAM,EAAE,eAAe,MAAM,CAAC;;EAEhD,WAAW,YAAkC;AAC3C,UAAQ,MAAM,UAAU,MAAM,EAAE,eAAe,CAAC;;EAElD,WAAW,OAAO,UAAiC;AACjD,SAAM,UAAU,MAAM,EAAE,cAAc,MAAM,CAAC;;EAE/C,aAAa,OAAO,UAAiC;AACnD,SAAM,UAAU,MAAM,EAAE,gBAAgB,MAAM,CAAC;;EAqBjD,0BACE,2BAA2B,UAAU,KAAK,KAAK,SAAS,OAAO,MAAM,CAAC;EACzE;CAYD;CAGA,cAAc;EAEZ,MAAM,YAAwC;AAC5C,UAAQ,MAAM,UAAU,MAAM,EAAE,kBAAkB,CAAC;;EAIrD,KAAK,OAAO,SAAmC;AAC7C,UAAQ,MAAM,UAAU,MAAM,EAAE,gBAAgB,KAAK,CAAC;;EAUxD,SAAS,YAAoC;AAC3C,UAAQ,MAAM,UAAU,MAAM,EAAE,qBAAqB,CAAC;;EAGzD;CAOD,WAAW,OAAO,eAAkD;AAClE,SAAQ,MAAM,UAAU,MACtB,aAAa,EAAE,aAAa,WAAW,GAAG,EAAE,eAAe,CAC5D;;CAKH,IAAI;EACF,WAAW,EACT,UAAU;GACR,QAAQ,OACN,YAC+B;AAG/B,WAAQ,MAAM,UAAU,MAAM,EAAE,oBAC9B,WAAW,eAAe,QAAQ,CAAC,CACpC,CAAC;;GAmBJ,SACE,SACA,YACiD;IACjD,MAAM,EAAE,KAAK,SAAS,SAAS,qBAC7B,mBACA,QACD;AACD,WAAO,eAAe;KAAE;KAAK;KAAS;KAAM,SAAS,SAAS;KAAS,CAAC;;GAE3E,EACF;EACD,QAAQ,EACN,MAAM,EACJ,aAAa,EAOX,SACE,SACA,YACiD;GACjD,MAAM,EAAE,KAAK,SAAS,SAAS,qBAC7B,gBACA,QACD;AACD,UAAO,eAAe;IAAE;IAAK;IAAS;IAAM,SAAS,SAAS;IAAS,CAAC;KAE3E,EACF,EACF;EACF;CA8CD,WAAW,EACT,QAAQ,OACN,MACA,MACA,YACe;AAGf,eAAa;AACb,MAAI,CAAC,qBACH,OAAM,IAAI,aACR,mBACA,yGACD;AAEH,MAAI,CAAC,QAAQ,KAAK,MAAM,KAAK,GAC3B,OAAM,IAAI,aACR,oBACA,sDACD;EAGH,MAAM,MAAM,MAAM,mBAAmB,KAAK;EAC1C,MAAM,SAAS,SAAS,UAAU;EAClC,MAAMN,UAAkC;GACtC,aAAa;GACb,GAAG,SAAS;GACb;AAID,MAAI,qBACF,SAAQ,mBAAmB,UAAU;EAEvC,IAAIQ;AACJ,MAAI,SAAS,UAAa,WAAW,OAAO;AAC1C,WAAQ,kBAAkB;AAC1B,iBAAc,KAAK,UAAU,KAAK;;EAGpC,IAAIN;AACJ,MAAI;AACF,UAAO,MAAM,MAAM,KAAK;IAAE;IAAQ;IAAS,MAAM;IAAa,CAAC;WACxD,KAAK;AACZ,SAAM,IAAI,aACR,iBACA,0BAA0B,KAAK,oBAAoB,OAAO,IAAI,GAC/D;;EAGH,MAAM,OAAO,MAAM,KAAK,MAAM;EAC9B,IAAII,SAAkB;AACtB,MAAI,KAAK,SAAS,EAChB,KAAI;AACF,YAAS,KAAK,MAAM,KAAK;UACnB;AAGN,YAAS;;EAeb,MAAM,oBACJ,UAAU,OAAO,WAAW,WACtB,OAAgD,OAC9C,cAAe,OAAoC,aACvD;EACN,MAAM,UACJ,KAAK,QAAQ,IAAI,oBAAoB,KACpC,OAAO,sBAAsB,WAAW,oBAAoB;AAC/D,WAAS,YAAY,QAAQ;AAE7B,MAAI,CAAC,KAAK,IAAI;GACZ,MAAM,WACJ,UAAU,OAAO,WAAW,WACvB,SACD;GAWN,MAAM,QAAQ,IAAI,aAThB,OAAO,UAAU,OAAO,SAAS,WAC7B,SAAS,MAAM,OACf,QAAQ,KAAK,UAEjB,OAAO,UAAU,OAAO,YAAY,WAChC,SAAS,MAAM,UACf,OAAO,WAAW,YAAY,OAAO,SAAS,IAC5C,SACA,0BAA0B,KAAK,wBAAwB,KAAK,UACtB,OAAO;AACrD,OAAI,YAAY,KAAM,OAAM,UAAU;AACtC,SAAM;;AAGR,SAAO;IAEV;CAGD,QAAQ,EAEN,OAAO,YAAmC;AACxC,SAAQ,MAAM,UAAU,MAAM,EAAE,aAAa,CAAC;IAGjD;CAGD,OAAO;EAEL,OAAO,YAAuC;AAC5C,UAAQ,MAAM,UAAU,MAAM,EAAE,YAAY,CAAC;;EAO/C,KAAK,OAAO,QAAgD;AAC1D,UAAQ,MAAM,UAAU,MAAM,EAAE,SAAS,IAAI,CAAC;;EAGjD;CAiBD,aAAa,EACX,MAAM,YAAiC;AACrC,SAAQ,MAAM,UAAU,MAAM,EAAE,iBAAiB,CAAC;IAErD;CAKD,cAAc;EAEZ,QAAQ,YACL,MAAM,UAAU,MAAM,EAAE,oBAAoB,CAAC;EAChD,aAAa,YACV,MAAM,UAAU,MAAM,EAAE,yBAAyB,CAAC;EAEtD;CAGD,YAAY;EAEV,WAAW,YACR,MAAM,UAAU,MAAM,EAAE,qBAAqB,CAAC;EACjD,KAAK,OAAO,OACT,MAAM,UAAU,MAAM,EAAE,cAAc,GAAG,CAAC;EAC7C,aAAa,OAAO,OACjB,MAAM,UAAU,MAAM,EAAE,sBAAsB,GAAG,CAAC;EACrD,OAAO,OAAO,OACX,MAAM,UAAU,MAAM,EAAE,gBAAgB,GAAG,CAAC;EAEhD;CAGD,YAAY;EAEV,YAAY,YACT,MAAM,UAAU,MAAM,EAAE,sBAAsB,CAAC;EAClD,iBAAiB,OACf,gBAEC,MAAM,UAAU,MAAM,EAAE,0BACvB,YACD,CAAC;EAEL;CAGD,WAAW;EAET,UAAU,YACP,MAAM,UAAU,MAAM,EAAE,mBAAmB,CAAC;EAC/C,SAAS,OAAO,OACb,MAAM,UAAU,MAAM,EAAE,iBAAiB,GAAG,CAAC;EAChD,UAAU,OAAO,YACd,MAAM,UAAU,MAAM,EAAE,kBACvB,WAAW,QAAQ,CACpB,CAAC;EACJ,SAAS,OAAO,YACb,MAAM,UAAU,MAAM,EAAE,iBACvB,WAAW,QAAQ,CACpB,CAAC;EAEL;CAGD,cAAc;EAEZ,KAAK,OAAO,QACT,MAAM,UAAU,MAAM,EAAE,gBAAgB,IAAI,CAAC;EAChD,YAAY,OACV,KACA,UAEC,MAAM,UAAU,MAAM,EAAE,uBACvB,KACA,SAAS,OACV,CAAC;EACJ,WAAW,OAAO,QACf,MAAM,UAAU,MAAM,EAAE,sBAAsB,IAAI,CAAC;EAEvD;CAOD,SAAS;EAMP,IAAI,YAAuC;AACzC,UAAQ,MAAM,UAAU,MAAM,EAAE,WAAW,CAAC;;EAQ9C,QAAQ,YAAuC;AAC7C,UAAQ,MAAM,UAAU,MAAM,EAAE,eAAe,CAAC;;EAGnD;CAGD,QAAQ;EAEN,MAAM,YACH,MAAM,UAAU,MAAM,EAAE,YAAY,CAAC;EACxC,oBAAoB,OAAO,aACxB,MAAM,UAAU,MAAM,EAAE,yBACvB,SACD,CAAC;EACJ,UAAU,OACR,UACA,kBACA,YAMC,MAAM,UAAU,MAAM,EAAE,eACvB,UACA,kBACA,WAAW,WAAW,EAAE,CAAC,CAC1B,CAAC;EAEL;CAGD,IAAI;EAEF,YAAY,YACT,MAAM,UAAU,MAAM,EAAE,cAAc,CAAC;EAC1C,YAAY,OAAO,UAChB,MAAM,UAAU,MAAM,EAAE,aACvB,SAAS,OACV,CAAC;EACJ,OAAO,OAAO,aACX,MAAM,UAAU,MAAM,EAAE,QAAQ,SAAS,CAAC;EAE9C;CAGD,SAAS;EAEP,QAAQ,YACL,MAAM,UAAU,MAAM,EAAE,eAAe,CAAC;EAC3C,SAAS,OAAO,cACb,MAAM,UAAU,MAAM,EAAE,eAAe,UAAU,CAAC;EAEtD;CAiBD,IAAI,eAAyC;AAI3C,SAAO,IAAI,yBAAyB;GAClC,SAAS;GACT,aAAa;GACd,CAAC;;CAMJ,OAAO,EAEL,aAAa,OACX,MACA,WAEC,MAAM,UAAU,MAAM,EAAE,iBACvB,QAAQ,QACR,UAAU,OACX,CAAC,EAEL;CAGD,SAAS;EAEP,SAAS,YACN,MAAM,UAAU,MAAM,EAAE,gBAAgB,CAAC;EAC5C,YAAY,YACT,MAAM,UAAU,MAAM,EAAE,mBAAmB,CAAC;EAK/C,aAAa,OAAO,WACjB,MAAM,UAAU,MAAM,EAAE,mBAAmB,OAAO,CAAC;EAKtD,eAAe,OAAO,iBACnB,MAAM,UAAU,MAAM,EAAE,qBACvB,aACD,CAAC;EAKJ,gBAAgB,OAAO,iBAAwC;AAC7D,SAAM,UAAU,MAAM,EAAE,sBAAsB,aAAa,CAAC;;EAE9D,WAAW,OAAO,WACf,MAAM,UAAU,MAAM,EAAE,iBAAiB,OAAO,CAAC;EACpD,aAAa,OAAO,WAAkC;AACpD,SAAM,UAAU,MAAM,EAAE,mBAAmB,OAAO,CAAC;;EAErD,aAAa,OAAO,iBAAwC;AAC1D,SAAM,UAAU,MAAM,EAAE,mBAAmB,aAAa,CAAC;;EAQ3D,cAAc,OAAO,WAAkC;AACrD,SAAM,UAAU,MAAM,EAAE,oBAAoB,OAAO,CAAC;;EAGvD;CAGD,QAAQ;EAEN,QAAQ,OAAO,WACZ,MAAM,UAAU,MAAM,EAAE,aAAa,WAAW,OAAO,CAAC,CAAC;EAC5D,KAAK,OAAO,OACT,MAAM,UAAU,MAAM,EAAE,UAAU,GAAG,CAAC;EACzC,QAAQ,OAAO,IAAY,UACxB,MAAM,UAAU,MAAM,EAAE,aAAa,IAAI,WAAW,MAAM,CAAC,CAAC;EAC/D,QAAQ,OAAO,OAA8B;AAC3C,SAAM,UAAU,MAAM,EAAE,aAAa,GAAG,CAAC;;EAE3C,YAAY,OAAO,OAChB,MAAM,UAAU,MAAM,EAAE,iBAAiB,GAAG,CAAC;EAChD,MAAM,OAAO,OACV,MAAM,UAAU,MAAM,EAAE,WAAW,GAAG,CAAC;EAC1C,OAAO,OAAO,OAA8B;AAC1C,SAAM,UAAU,MAAM,EAAE,YAAY,GAAG,CAAC;;EAE1C,QAAQ,OAAO,IAAY,WACxB,MAAM,UAAU,MAAM,EAAE,aAAa,IAAI,OAAO,CAAC;EAErD;CAsBD,WAAW;EAST,oBAAoB,OAClB,YAEC,MAAM,UAAU,MAAM,EAAE,4BACvB,WAAW,QAAQ,CACpB,CAAC;EAOJ,eAAe,YACZ,MAAM,UAAU,MAAM,EAAE,2BAA2B,CAAC;EASvD,aAAa,OACX,gBACA,YAMC,MAAM,UAAU,MAAM,EAAE,qBACvB,WAAW;GAAE,GAAG;GAAS,iBAAiB;GAAgB,CAAC,CAC5D,CAAC;EAOJ,cAAc,OACZ,gBACA,SAEC,MAAM,UAAU,MAAM,EAAE,sBACvB,gBACA,MAAM,SAAS,QACf,MAAM,UAAU,OACjB,CAAC;EASJ,YAAY,OACV,gBACA,cAC4B;AAC5B,UAAQ,MAAM,UAAU,MAAM,EAAE,oBAC9B,gBACA,UACD,CAAC;;EAcJ,gBAAgB,OACd,gBACA,WAEC,MAAM,UAAU,MAAM,EAAE,wBACvB,gBACA,OACD,CAAC;EAOJ,UAAU,OAAO,mBACd,MAAM,UAAU,MAAM,EAAE,kBACvB,eACD,CAAC;EAsBJ,eAA4B,mBAC1B,IAAI,yBACF;GAAE,SAAS;GAAmB,aAAa;GAAqB,EAChE,eACD;EACJ;CAGD,YAAY;EACV,YAAY,OAAO,YAChB,MAAM,UAAU,MAAM,EAAE,qBACvB,WAAW,QAAQ,CACpB,CAAC;EACJ,eAAe,OAAO,YACnB,MAAM,UAAU,MAAM,EAAE,wBACvB,WAAW,QAAQ,CACpB,CAAC;EACJ,iBAAiB,OAAO,OACrB,MAAM,UAAU,MAAM,EAAE,0BAA0B,GAAG,CAAC;EAC1D;CAGD,SAAS;EAEP,MAAM,OAAO,YAAoB,aAC9B,MAAM,UAAU,MAAM,EAAE,YAAY,YAAY,SAAS,CAAC;EAC7D,QAAQ,OAAO,WACZ,MAAM,UAAU,MAAM,EAAE,cAAc,WAAW,OAAO,CAAC,CAAC;EAC7D,QAAQ,OAAO,IAAY,UACxB,MAAM,UAAU,MAAM,EAAE,cAAc,IAAI,WAAW,MAAM,CAAC,CAAC;EAChE,QAAQ,OAAO,OAA8B;AAC3C,SAAM,UAAU,MAAM,EAAE,cAAc,GAAG,CAAC;;EAG7C;CAGD,OAAO;EAEL,YAAY,YACT,MAAM,UAAU,MAAM,EAAE,iBAAiB,CAAC;EAC7C,eAAe,OAAO,eACnB,MAAM,UAAU,MAAM,EAAE,mBAAmB,WAAW,CAAC;EAE3D;CAGD,WAAW;EAET,iBAAiB,YACd,MAAM,UAAU,MAAM,EAAE,0BAA0B,CAAC;EACtD,eAAe,OAAO,SACnB,MAAM,UAAU,MAAM,EAAE,uBAAuB,KAAK,CAAC;EACxD,QAAQ,OACN,MACA,YAEC,MAAM,UAAU,MAAM,EAAE,gBACvB,QAAQ,QACR,WAAW,OACZ,CAAC;EAEL;CAKD,SAAS;EAEP,MAAM,YACH,MAAM,UAAU,MAAM,EAAE,aAAa,CAAC;EAKzC,KAAK,OAAO,OACT,MAAM,UAAU,MAAM,EAAE,WAAW,GAAG,CAAC;EAE3C;CAGD,SAAS;EAOP,OAAO,OAAO,UAAkB,cAC7B,MAAM,UAAU,MAAM,EAAE,gBAAgB,QAAQ,CAAC;EAOpD,SAAS,OACP,UAAkB,WAClB,YAEC,MAAM,UAAU,MAAM,EAAE,kBACvB,SACA,SAAS,SAAS,QAClB,SAAS,UAAU,OACpB,CAAC;EACJ,MAAM,OAAO,OACV,MAAM,UAAU,MAAM,EAAE,eAAe,GAAG,CAAC;EAC9C,YAAY,OAAO,IAAY,UAC5B,MAAM,UAAU,MAAM,EAAE,kBACvB,IACA,WAAW,MAAM,CAClB,CAAC;EACJ,YAAY,OACV,SACA,SAEC,MAAM,UAAU,MAAM,EAAE,kBACvB,SACA,WAAW,KAAK,CACjB,CAAC;EAEL;CAGD,WAAW;EAET,KAAK,OAAO,cACT,MAAM,UAAU,MAAM,EAAE,aAAa,UAAU,CAAC;EAEnD,QAAQ,OAAO,WACZ,MAAM,UAAU,MAAM,EAAE,gBAAgB,WAAW,OAAO,CAAC,CAAC;EAChE;CAGD,YAAY;EAEV,WAAW,YACR,MAAM,UAAU,MAAM,EAAE,qBAAqB,CAAC;EACjD,UAAU,OAAO,YACd,MAAM,UAAU,MAAM,EAAE,mBACvB,WAAW,QAAQ,CACpB,CAAC;EACJ,UAAU,YACP,MAAM,UAAU,MAAM,EAAE,oBAAoB,CAAC;EAChD,UAAU,YACP,MAAM,UAAU,MAAM,EAAE,oBAAoB,CAAC;EAEjD;CAEF;;AAGD,SAAgB,cAA4B;AAC1C,QAAO,aAAa;;;;;;;;;;AAWtB,MAAa,cAAc;;;;AC/yG3B,eAAe,MAAM,aAAa"}
|