@palbase/web 7.3.3 → 7.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.tsx","../../../core/src/config.ts","../../../core/src/errors.ts","../../../core/src/pow.ts","../../../core/src/platform.ts","../../../core/src/http.ts","../../../core/src/token.ts","../../src/errors.ts","../../src/perf/url-redactor.ts","../../src/request.ts","../../src/state.ts","../../src/namespaces.ts","../../src/runtime-metadata.ts","../../src/version.ts","../../src/call.ts","../../src/upload.ts","../../src/pb.ts","../../src/internal.ts"],"sourcesContent":["'use client';\n\n// `palbe/react` — thin React hooks over the observable `pb.*` facades. Each\n// value hook is a `useSyncExternalStore` (the React 18+ concurrent-safe\n// primitive) binding: `subscribe` is the facade's Unsubscribe-returning\n// listener, `getSnapshot` is the matching getter. NO new SDK behaviour lives\n// here — the hooks re-render exactly when (and only when) their slice of state\n// changes, mirroring the iOS `@Observable` granularity.\n//\n// `react` is an OPTIONAL peer dependency: importing `palbe` (the core entry)\n// never pulls React; only `palbe/react` does.\n//\n// Client-only: the module starts with `'use client'` (Next.js App Router). SSR\n// initial values come from the `getServerSnapshot` arms (null user, empty flag\n// set) so a server render never touches the runtime singleton.\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { AuthUser, Unsubscribe } from '../auth-facade.js';\nimport type { FlagsView, FlagValue } from '../flags-facade.js';\nimport { onConfigured } from '../internal.js';\nimport type { Chat } from '../messaging/chat.js';\nimport type { ChatMember, ChatMessage } from '../messaging/types.js';\nimport { pb } from '../pb.js';\nimport type { RealtimeConnectionState, RealtimePayload } from '../realtime/facade.js';\n\n/** The session slice `useSession` exposes. */\nexport interface SessionState {\n signedIn: boolean;\n user: AuthUser | null;\n}\n\n/** Frozen empty flag view — the stable server/unconfigured fallback for\n * `useFlags` (a fresh `{}` each call would loop `useSyncExternalStore`). */\nconst EMPTY_FLAGS: FlagsView = Object.freeze({});\n\n/** The signed-out session snapshot — a single frozen instance so the\n * unconfigured/server snapshot is referentially stable across calls. */\nconst SIGNED_OUT: SessionState = Object.freeze({ signedIn: false, user: null });\n\n/**\n * Wraps a facade-listener factory so hooks that mount BEFORE `__configure`\n * runs self-heal when configuration arrives (or changes on re-configure /\n * watch-mode regen).\n *\n * Behaviour:\n * - If already configured at subscribe time, attaches the facade listener\n * immediately AND registers an `onConfigured` watcher for future re-configures.\n * - If NOT yet configured at subscribe time, only registers `onConfigured`;\n * the facade listener is attached when configuration arrives.\n * - On every configure event: detaches the old facade listener (if any),\n * attaches a fresh one against the new runtime, then calls `onStoreChange`\n * so React re-reads the snapshot from the new runtime.\n * - The returned unsubscribe tears down both the onConfigured registration\n * and the current facade listener.\n *\n * @param getFacadeListener - factory called when a runtime is available;\n * must return an Unsubscribe; called with `onStoreChange` as its argument.\n */\nfunction subscribeWithConfig(\n getFacadeListener: (onStoreChange: () => void) => Unsubscribe,\n): (onStoreChange: () => void) => Unsubscribe {\n return (onStoreChange: () => void): Unsubscribe => {\n let currentFacadeUnsub: Unsubscribe | null = null;\n\n function attachFacade(): void {\n currentFacadeUnsub?.();\n try {\n currentFacadeUnsub = getFacadeListener(onStoreChange);\n } catch {\n // notConfigured — will be retried when onConfigured fires\n currentFacadeUnsub = null;\n }\n }\n\n // Register for future (re-)configure events.\n const offConfigured = onConfigured(() => {\n attachFacade();\n // Notify React to re-read the snapshot from the new runtime.\n onStoreChange();\n });\n\n // If already configured right now, attach immediately too.\n attachFacade();\n\n return () => {\n offConfigured();\n currentFacadeUnsub?.();\n currentFacadeUnsub = null;\n };\n };\n}\n\n/**\n * Current authenticated user, or `null` when signed out. Re-renders on BOTH\n * auth-state transitions (sign-in adopts a user, sign-out clears it) and\n * user-profile changes (e.g. an email-verified flip via `refreshUser`).\n *\n * `getSnapshot` is guarded: before the generated `palbe.gen.ts` runs\n * `__configure`, `pb.auth` throws `notConfigured` — a tree rendered that early\n * gets `null`, never a render crash. Once `__configure` runs the hook\n * self-heals: it re-subscribes to the new runtime's auth facade and notifies\n * React to re-read the snapshot.\n */\nexport function useUser(): AuthUser | null {\n return useSyncExternalStore(subscribeUserWithConfig, getUserSnapshot, getNullUser);\n}\n\nconst subscribeUserWithConfig = subscribeWithConfig((onStoreChange) => {\n // Two sources, one composite unsubscribe:\n // onAuthStateChange → sign-in (new user) and sign-out (null)\n // onUserChange → in-place profile edits (email-verified, …)\n // Both fire immediately on subscribe (iOS parity), which simply re-reads the\n // already-current snapshot — harmless.\n const offState = pb.auth.onAuthStateChange(onStoreChange);\n const offUser = pb.auth.onUserChange(onStoreChange);\n return () => {\n offState();\n offUser();\n };\n});\n\nfunction getUserSnapshot(): AuthUser | null {\n try {\n return pb.auth.currentUser;\n } catch {\n return null;\n }\n}\n\nfunction getNullUser(): AuthUser | null {\n return null;\n}\n\n/**\n * Session slice: `{ signedIn, user }`. Re-renders on auth-state transitions.\n *\n * `getSnapshot` MUST be referentially stable when nothing changed — returning a\n * fresh object every call would put `useSyncExternalStore` into an infinite\n * render loop. A per-component ref caches the last value and returns the SAME\n * object until `signedIn` or `user` actually changes.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useSession(): SessionState {\n const lastRef = useRef<SessionState>(SIGNED_OUT);\n const getSnapshot = useCallback((): SessionState => {\n const next = readSession();\n const prev = lastRef.current;\n if (prev.signedIn === next.signedIn && prev.user === next.user) {\n return prev; // unchanged — keep the stable reference\n }\n lastRef.current = next;\n return next;\n }, []);\n return useSyncExternalStore(subscribeSessionWithConfig, getSnapshot, getSignedOut);\n}\n\nconst subscribeSessionWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.auth.onAuthStateChange(onStoreChange),\n);\n\nfunction readSession(): SessionState {\n try {\n const user = pb.auth.currentUser;\n const signedIn = pb.auth.isSignedIn;\n if (!signedIn && user === null) return SIGNED_OUT;\n return { signedIn, user };\n } catch {\n return SIGNED_OUT;\n }\n}\n\nfunction getSignedOut(): SessionState {\n return SIGNED_OUT;\n}\n\n/**\n * Subscribe to ONE flag. Re-renders only when THIS key's value changes\n * (`pb.flags.subscribeKey` is the per-key source — structural compare for\n * objects), returning `fallback` until the key has a value.\n *\n * The value is read at the dynamic flag boundary where the concrete type `T`\n * is caller-asserted (the cached value is whatever the server sent); the\n * contained `as T` is the documented escape hatch — flags are untyped on the\n * wire, and the caller owns the `fallback`'s type.\n *\n * NOTE: passing an inline-object `fallback` for an absent key defeats snapshot\n * caching (a new object reference each render re-enters the last-value ref).\n * Memoize the fallback (e.g. a module-level const or `useMemo`) if it matters.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlag<T extends FlagValue>(key: string, fallback: T): T {\n // Subscribe identity must change when `key` changes so useSyncExternalStore\n // re-subscribes to the new key. subscribeWithConfig wraps the per-key\n // factory so the hook also self-heals on (re-)configure.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `key` is a runtime param, not an outer-scope value — dependency IS required\n const subscribe = useCallback(\n subscribeWithConfig((onStoreChange) => pb.flags.subscribeKey(key, onStoreChange)),\n [key],\n );\n\n // Cache the last value so getSnapshot is referentially stable (objects come\n // back as the frozen pooled reference — stable identity until a real change).\n const lastRef = useRef<T>(fallback);\n const getSnapshot = useCallback((): T => {\n let next: T;\n try {\n const value = pb.flags.get(key);\n next = value === undefined ? fallback : (value as T);\n } catch {\n next = fallback;\n }\n if (!Object.is(next, lastRef.current)) {\n lastRef.current = next;\n }\n return lastRef.current;\n }, [key, fallback]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to the whole flag set. Re-renders on any change. `pb.flags.all()`\n * returns a frozen, identity-stable view (a NEW frozen object only when the set\n * actually changes), so it is `useSyncExternalStore`-safe as-is.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlags(): FlagsView {\n return useSyncExternalStore(subscribeFlagsWithConfig, getFlagsSnapshot, getEmptyFlags);\n}\n\nconst subscribeFlagsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.flags.onChange(onStoreChange),\n);\n\nfunction getFlagsSnapshot(): FlagsView {\n try {\n return pb.flags.all();\n } catch {\n return EMPTY_FLAGS;\n }\n}\n\nfunction getEmptyFlags(): FlagsView {\n return EMPTY_FLAGS;\n}\n\n/** The reported channel status: the shared-socket connection state, plus\n * `'unavailable'` when `pb.realtime.channel()` is not usable in this\n * environment (no WebSocket / SSR). */\nexport type ChannelStatus = RealtimeConnectionState | 'unavailable';\n\n/**\n * Subscribe a `handler` to one realtime `event` on `channel(name)` for the\n * lifetime of the component. Returns the live connection `{ status }`.\n *\n * The handler is held in a ref and refreshed every render, so passing a fresh\n * closure each render (the common case — callers needn't `useCallback`) does\n * NOT tear down and re-create the subscription. The subscription itself is\n * keyed on `[name, event]`: only a name/event change re-subscribes.\n *\n * StrictMode-safe: the facade refcounts joins, so React's mount→cleanup→\n * remount (double-invoke in dev) nets to exactly one live subscription via the\n * effect's `cancel()` cleanup — NO once-guard (a once-guard would leave the\n * subscription dead after the first cleanup; that was the P4 live-smoke bug).\n *\n * SSR / no-WebSocket: `pb.realtime.channel()` throws; the effect catches it and\n * reports `status: 'unavailable'` instead of crashing. (Effects don't run on\n * the server, so the initial server status is the idle default.)\n */\nexport function useChannel(\n name: string,\n event: string,\n handler: (payload: RealtimePayload) => void,\n): { status: ChannelStatus } {\n const handlerRef = useRef(handler);\n // Refresh the handler every render WITHOUT re-subscribing — the effect below\n // calls `handlerRef.current`, so the latest closure always runs.\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const [status, setStatus] = useState<ChannelStatus>('idle');\n\n // A configure-epoch counter: bumped by onConfigured so the channel effect\n // re-runs when a runtime arrives (handles the pre-config mount case and\n // watch-mode runtime swaps). Does not re-subscribe on every render.\n const [configEpoch, setConfigEpoch] = useState(0);\n useEffect(() => {\n return onConfigured(() => setConfigEpoch((n) => n + 1));\n }, []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: `configEpoch` is intentionally a re-run trigger for configure-arrive / watch-mode\n useEffect(() => {\n let channel: ReturnType<typeof pb.realtime.channel>;\n try {\n channel = pb.realtime.channel(name);\n } catch {\n // notConfigured or no-WebSocket / server environment — degrade gracefully.\n setStatus('unavailable');\n return;\n }\n const sub = channel.on(event, (payload) => handlerRef.current(payload));\n // Seed from the current connection state, then track transitions.\n setStatus(pb.realtime.status.state);\n const offStatus = pb.realtime.status.onChange((snapshot) => setStatus(snapshot.state));\n return () => {\n offStatus();\n sub.cancel();\n };\n }, [name, event, configEpoch]);\n\n return { status };\n}\n\n// ─── Messaging hooks (Web-MLS Phase 2) ───────────────────────────────────────\n\nconst EMPTY_CHATS: readonly Chat[] = Object.freeze([]);\nconst EMPTY_MESSAGES: readonly ChatMessage[] = Object.freeze([]);\nconst EMPTY_MEMBERS: readonly ChatMember[] = Object.freeze([]);\nconst EMPTY_TYPING: readonly ChatMember[] = Object.freeze([]);\n\n/**\n * The observable chat list (DMs + groups, active only). Re-renders when a chat\n * is added (you start one / a peer DMs you and the Welcome drains), removed, or\n * the list hydrates from the durable catalog on launch.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useChats(): readonly Chat[] {\n const lastRef = useRef<readonly Chat[]>(EMPTY_CHATS);\n const getSnapshot = useCallback((): readonly Chat[] => {\n try {\n const next = pb.messaging.chats;\n // Identity-stable: only swap the cached array when the set changed.\n if (next.length !== lastRef.current.length || next.some((c, i) => c !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n } catch {\n return EMPTY_CHATS;\n }\n }, []);\n return useSyncExternalStore(subscribeChatsWithConfig, getSnapshot, getEmptyChats);\n}\n\nconst subscribeChatsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.messaging.onChatsChange(onStoreChange),\n);\n\nfunction getEmptyChats(): readonly Chat[] {\n return EMPTY_CHATS;\n}\n\n/**\n * Bind to one `Chat`'s changes. Re-renders whenever the chat's observable state\n * changes (a new message, members refresh, typing, materialize draft→active).\n * Returns the SAME `chat` instance for ergonomic access to `chat.messages`,\n * `chat.send(...)`, etc.\n */\nexport function useChat(chat: Chat): Chat {\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n // The chat instance is pointer-stable; a per-render counter forces re-render\n // on each onChange. Snapshot returns the instance itself (stable identity).\n const getSnapshot = useCallback(() => chat, [chat]);\n // Tick on every change so consumers reading chat.messages re-render.\n const [, force] = useState(0);\n useEffect(() => chat.onChange(() => force((n) => n + 1)), [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * The ordered message transcript for a chat (newest last). Re-renders on a new\n * message / history page / own-send echo. A cached snapshot keeps the array\n * reference stable until the list actually changes.\n */\nexport function useMessages(chat: Chat): readonly ChatMessage[] {\n const lastRef = useRef<readonly ChatMessage[]>(EMPTY_MESSAGES);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMessage[] => {\n const next = chat.messages;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MESSAGES);\n}\n\n/** The members of a chat (users). Re-renders when the roster changes. */\nexport function useChatMembers(chat: Chat): readonly ChatMember[] {\n const lastRef = useRef<readonly ChatMember[]>(EMPTY_MEMBERS);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMember[] => {\n const next = chat.members;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MEMBERS);\n}\n\n/** The users currently typing in a chat. Re-renders on typing start/stop. */\nexport function useTyping(chat: Chat): readonly ChatMember[] {\n const lastRef = useRef<readonly ChatMember[]>(EMPTY_TYPING);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMember[] => {\n const next = chat.typing;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_TYPING);\n}\n","import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * The hardest challenge this client will attempt.\n *\n * Not a taste: it is the server's own ceiling. palauth maps a risk score to a\n * difficulty and its worst case is 24 (`DifficultyForRisk`, bot/pow.go:156-166).\n * Anything above that cannot have come from a stack behaving as designed, and\n * the cost of humouring it falls entirely on this side — each step up DOUBLES\n * the work, so difficulty 30 is sixty-four times a legitimate worst case and, on\n * the web, sixty-four times a frozen main thread. Refused immediately, by name.\n */\nexport const MAX_POW_DIFFICULTY = 24;\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * THE BUDGET SCALES WITH THE CHALLENGE, and the first version of this did not.\n * It bounded the search at a flat `1 << 24` — which is not a generous bound for\n * difficulty 24, it is the EXPECTED number of attempts. Finding a nonce is a\n * geometric process: the chance of needing more than 2^d attempts is 1/e, so a\n * flat 2^24 would have failed roughly 37% of legitimate hardest-risk challenges,\n * and failed them for precisely the users the gate exists to slow down — who\n * would have been unable to sign in at all rather than made to wait.\n *\n * Eight times expected puts that at e^-8, about three in ten thousand, while\n * leaving the common case (the server's default 16, and 12 for an unremarkable\n * caller) exactly as cheap as it was.\n *\n * `powBudget` is exported and separate so the RELATIONSHIP can be asserted\n * directly. A test that only watches a cheap challenge succeed cannot tell this\n * budget from the flat one it replaced — measured: reinstating `1 << 24` left\n * such a test green.\n */\nexport function powBudget(difficulty: number): number {\n return 8 * 2 ** difficulty;\n}\n\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = powBudget(challenge.difficulty),\n // The caller's AbortSignal, honoured INSIDE the loop and not only around the\n // fetch it precedes. At the server's hardest difficulty this is ~17 million\n // digests: the Go comment beside DifficultyForRisk calls that \"~2-3s\", which\n // is a native tight loop — one `crypto.subtle.digest` per nonce runs at\n // roughly 200k/s, so the same work is more than a minute of a browser's main\n // thread. A person who navigates away, or a caller that times out, must be\n // able to stop it; without this the signal governed the request and not the\n // work done on its behalf.\n signal?: AbortSignal,\n): Promise<Record<string, string>> {\n if (challenge.difficulty > MAX_POW_DIFFICULTY) {\n throw new Error(\n `proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`,\n );\n }\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n // Checked in batches: reading `signal.aborted` is cheap but not free, and a\n // 1024-digest granularity bounds the delay at about five milliseconds.\n if ((nonce & 1023) === 0 && signal?.aborted) {\n throw new DOMException('proof-of-work solve aborted', 'AbortError');\n }\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(challenge.prefix + nonce));\n if (leadingZeroBits(new Uint8Array(digest)) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment\n * ref is 4-24 lowercase ASCII alphanumeric characters and random is 20 base62\n * chars. See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n // WITHOUT `earned`, and that is the whole point of this line.\n //\n // A network error means the response was lost, not that the request\n // was. If it reached the server, the challenge is already SPENT —\n // palauth's VerifyChallenge reads and deletes in one step\n // (bot/pow.go:96-104), deliberately, because a proof presented twice is\n // not proof. Replaying the nonce would then answer `pow_invalid`, and\n // the one-solve guard below would refuse to try again: a request one\n // fresh solve away from succeeding, failed. Dropping it costs nothing\n // in the other case — if the server never saw the request, a fresh\n // challenge works exactly as well as the old one.\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n // WITH `earned`, unlike the network path above: a 429 is a refusal the\n // server issued INSTEAD of doing the work, so the challenge was never\n // consumed. The edge's rate limiter answers before palsvc, and on the\n // auth routes palauth's own limiter runs BEFORE the proof-of-work\n // middleware (auth/internal/server/server.go: rl.LoginByIP, then powMW).\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge, undefined, options?.signal),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, details?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', {\n ...base,\n retryAfter: pickNumber(err.details, 'retry_after'),\n });\n // Field-error array lives at the envelope's nested `details` key.\n const nested = pickField(err.details, 'details');\n if (err.status === 400 && isFieldErrorArray(nested))\n return new BackendError('validation', { ...base, fields: nested });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const details = pickField(body, 'details');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', {\n ...params,\n // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.\n retryAfter: pickNumber(body, 'retry_after') ?? pickNumber(details, 'retry_after'),\n });\n if (status === 400 && isFieldErrorArray(details))\n return new BackendError('validation', { ...params, fields: details });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n","const ID_SEGMENT =\n /^(?:\\d+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n\n// An email lands in a path segment as `jane@acme.com` or percent-encoded\n// `jane%40acme.com`. Either form is PII and must never survive into a metric\n// name, so a segment carrying an `@` (encoded or raw) is masked like an id.\nconst EMAIL_SEGMENT = /(?:@|%40)/i;\n\nfunction isSensitiveSegment(seg: string): boolean {\n return ID_SEGMENT.test(seg) || EMAIL_SEGMENT.test(seg);\n}\n\n/**\n * Capture-time URL redaction (§4.1 privacy, client half). Collapses a request\n * URL to a PII-free, low-cardinality path pattern BEFORE it leaves the browser:\n * query string dropped wholesale, id-like segments masked. The ingest scrubber\n * re-applies server-side (defense in depth).\n *\n * Rules:\n * - absolute URL → keep only `pathname` (scheme/host/query/hash dropped);\n * - relative URL → drop everything from the first `?` onward;\n * - any segment that is purely numeric, a UUID, or carries an email (`@` /\n * `%40`) → `:id`;\n * - credentials/tokens live in the query (dropped wholesale), emails in a\n * path segment (masked), so neither survives into the metric name.\n */\nexport function redactUrl(rawUrl: string): string {\n let path = rawUrl;\n try {\n // Absolute URLs: take the pathname (drops scheme/host/query/hash).\n path = new URL(rawUrl).pathname;\n } catch {\n const q = path.indexOf('?');\n if (q >= 0) path = path.slice(0, q);\n }\n return path\n .split('/')\n .map((seg) => (isSensitiveSegment(seg) ? ':id' : seg))\n .join('/');\n}\n","import { asPalbaseError, fromPalbaseError, unwrap } from './errors.js';\nimport { redactUrl } from './perf/url-redactor.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { asPowChallenge, solvePowChallenge } from '@palbase/core';\n\nexport interface RequestSpec {\n body?: unknown;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport interface CallOptions {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nconst MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\n\n// The perf flush (`POST /v1/analytics/perf`) and analytics event ingest\n// (`/v1/analytics/*`) BOTH funnel through `palbeRequest`. Tracing them would\n// make every flush emit another `network` item that the next flush ships,\n// then traces again — a self-measurement loop. Excluding the whole\n// `/v1/analytics/` prefix breaks it. This is the SAME self-exclusion shape as\n// the iOS network trace (1b-3), keyed on the identical ingest prefix.\nconst PERF_EXCLUDED_PREFIX = '/v1/analytics/';\n\nfunction isSelfTraced(path: string): boolean {\n return path.startsWith(PERF_EXCLUDED_PREFIX);\n}\n\n/**\n * Codes meaning the server-side session is GONE — a token refresh cannot help\n * and the client must tear its local session down. `subject_fenced` is palauth's\n * wire-true erasure-fence code (`bridge.go`); `subject_erased` is accepted for\n * forward-compat. `session_revoked` is palauth's revoked-session 401.\n * A PLAIN 403 (ordinary authz / RLS denial) is intentionally NOT here — matching\n * all 403s would sign users out on routine permission errors.\n */\nfunction isSessionKilled(status: number, code: string): boolean {\n if (status === 401) return code === 'session_revoked';\n if (status === 403) return code === 'subject_fenced' || code === 'subject_erased';\n return false;\n}\n\n/** Monotonic millisecond clock — `performance.now()` when present, else\n * `Date.now()`. Mirrors `perf-facade.ts`'s `nowMs()` so network durations and\n * custom-trace durations share one time base. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/** An `AbortError` (caller-cancelled request) is NOT a completed network call,\n * so it must never produce a trace. */\nfunction isAbort(e: unknown): boolean {\n return e instanceof Error && e.name === 'AbortError';\n}\n\nexport async function palbeRequest<T>(\n rt: PalbeRuntime,\n method: string,\n path: string,\n spec: RequestSpec = {},\n): Promise<T> {\n const headers: Record<string, string> = { ...spec.headers };\n // Case-insensitive scan: a caller-supplied `idempotency-key` must suppress\n // generation, otherwise Headers merges both casings and dedup breaks.\n const callerHasKey = Object.keys(headers).some((k) => k.toLowerCase() === 'idempotency-key');\n if (MUTATING.has(method) && !callerHasKey) {\n headers['Idempotency-Key'] = crypto.randomUUID();\n }\n\n // `extra` carries the proof-of-work headers on the one retry that needs them.\n // The base `headers` are reused rather than rebuilt, so a retry keeps the same\n // Idempotency-Key and the server still sees one logical request.\n const attempt = async (extra?: Record<string, string>) => {\n try {\n return await rt.http.request<T>(method, path, {\n body: spec.body,\n headers: extra ? { ...headers, ...extra } : headers,\n signal: spec.signal,\n });\n } catch (e) {\n const pe = asPalbaseError(e);\n throw pe ? fromPalbaseError(pe) : e;\n }\n };\n\n // The single network-trace choke point: every SDK fetch funnels through\n // `palbeRequest`, so timing it here records one `network` PerfItem per call —\n // redacted URL + method + final HTTP status + wall-clock duration. The\n // `/v1/analytics/` ingest paths are self-excluded (no recursion) and aborts\n // are skipped (not a completed call).\n // `rt.perf` is guarded too: `buildRuntime` always wires it, but a hand-built\n // runtime (tests, embedders) may omit it — a missing facade must never break\n // the request path.\n const traced = !isSelfTraced(path) && rt.perf !== undefined;\n const startedAt = traced ? nowMs() : 0;\n // Exactly ONE trace per `palbeRequest`: the inner refresh-catch records with\n // the precise status and rethrows; that rethrow is then caught by the outer\n // catch, so the guard keeps the second `record()` a no-op (no double-count).\n let recorded = false;\n const record = (status: number): void => {\n if (!traced || recorded) return;\n recorded = true;\n rt.perf.recordNetwork(method, redactUrl(path), status, nowMs() - startedAt);\n };\n\n let res: Awaited<ReturnType<typeof attempt>>;\n try {\n res = await attempt();\n\n // Reactive 401: refresh once, retry once with the same Idempotency-Key.\n // A revoked session (401 session_revoked) is terminal — a refresh cannot\n // resurrect it, so skip straight to the teardown check below.\n if (\n res.error?.status === 401 &&\n res.error.code !== 'session_revoked' &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (refreshErr) {\n const pe = asPalbaseError(refreshErr);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead. Sign out and surface the\n // ORIGINAL 401 (mirrors HttpClient's pre-flight terminal split).\n rt.tokenManager.clearSession();\n record(res.error.status);\n throw fromPalbaseError(res.error);\n }\n // Transient (network/5xx): keep the session, surface the refresh failure.\n record(pe?.status ?? 0);\n throw pe ? fromPalbaseError(pe) : refreshErr;\n }\n res = await attempt();\n }\n\n // Reactive proof-of-work: signup and login sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it once and repeat the request with the headers; the caller never learns\n // the gate is there.\n //\n // Placed after the 401 branch so it sees whichever response is current — a\n // refresh-and-retry can land on the gate just as a first attempt can.\n //\n // Only ONE retry, and only when the body really carries a challenge. A 403\n // that merely says pow_required without one is a server the client cannot\n // satisfy, and looping on it would turn a broken gate into a hang.\n if (res.error?.status === 403 && res.error.code === 'pow_required') {\n const challenge = asPowChallenge(res.error.details);\n if (challenge) {\n res = await attempt(await solvePowChallenge(challenge));\n }\n }\n } catch (e) {\n // A thrown error here is a network/transport failure (the `{data,error}`\n // envelope never throws). Record it as a completed-with-error call unless\n // the caller aborted — an abort is a cancellation, not a measurement.\n if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);\n throw e;\n }\n\n // Reactive session teardown: a server-side session kill (subject erased /\n // session revoked) means the local session is dead regardless of which facade\n // issued this call — tear it down + emit signedOut{sessionInvalid} once. Scoped\n // to the exact codes so an ordinary 403 authz denial does NOT sign the user out.\n if (res.error && isSessionKilled(res.error.status, res.error.code)) {\n rt.auth.reactiveTeardown();\n }\n\n // Response resolved (2xx or an error envelope): record the real HTTP status\n // BEFORE `unwrap` may throw, so a 4xx/5xx is measured with its true status.\n record(res.error?.status ?? 200);\n return unwrap(res);\n}\n","import type { NamespaceTree } from './namespaces.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport interface PalbeGlobalState {\n runtime: PalbeRuntime | null;\n registry: NamespaceTree;\n /** Memoized materialized namespaces (per top-level key); reset on (re-)register. */\n nsCache: Record<string, unknown>;\n /** Listeners fired synchronously after every successful __configure call. */\n configuredListeners: Set<() => void>;\n}\n\n/**\n * Module-level state would split between the ESM and CJS bundles of the two\n * entries (esbuild only code-splits ESM) and between dual-package copies in\n * one process. A globalThis slot keyed by Symbol.for converges them all.\n */\nconst STATE_KEY: unique symbol = Symbol.for('palbe.state.v1');\n\ninterface PalbeGlobals {\n [STATE_KEY]?: PalbeGlobalState;\n}\n\nexport function palbeState(): PalbeGlobalState {\n // Single direct widening view of globalThis — typed via the unique-symbol\n // key above so reads/writes of the slot stay fully typed.\n const g = globalThis as PalbeGlobals;\n let state = g[STATE_KEY];\n if (!state) {\n state = { runtime: null, registry: {}, nsCache: {}, configuredListeners: new Set() };\n g[STATE_KEY] = state;\n }\n return state;\n}\n","import { BackendError, isBackendError } from './errors.js';\nimport { getRuntime } from './internal.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\nexport interface EndpointDescriptor {\n method: string;\n path: string;\n /** `{name}` segments consumed as leading call args, in order. */\n pathParams?: string[];\n /**\n * 'body' (default for non-GET) | 'query' (serialize input into the URL) |\n * 'none' (no input arg — options come straight after path params).\n */\n input?: 'body' | 'query' | 'none';\n /** wire error code → lift fn; the materializer rethrows the lifted error. */\n errors?: Record<string, (e: BackendError) => Error>;\n}\n\nexport type NamespaceTree = { [key: string]: NamespaceTree | EndpointDescriptor };\n\n// Reserved surface names on pb — cannot be shadowed at the top level by codegen\n// namespaces. `perf` sits next to `analytics`: `pb.perf` is the PalPerf facade,\n// so a generated controller named `perf` must NOT collide with it.\nexport const RESERVED = new Set([\n 'call',\n 'upload',\n 'auth',\n 'flags',\n 'realtime',\n 'analytics',\n 'calls',\n 'messaging',\n 'perf',\n]);\n\nfunction reservedNamespaceError(key: string): BackendError {\n return new BackendError('validation', {\n code: 'reserved_namespace',\n message: `Cannot register endpoint namespace '${key}': the name is reserved on pb.`,\n });\n}\n\nfunction isDescriptor(node: NamespaceTree | EndpointDescriptor): node is EndpointDescriptor {\n return (\n typeof (node as EndpointDescriptor).path === 'string' &&\n typeof (node as EndpointDescriptor).method === 'string'\n );\n}\n\n/**\n * Validate + normalize one tree level, recursively:\n * - plain-object guard (a literal `__proto__:` in a hand-written tree would\n * swap the node's prototype — cheap proto-pollution insurance);\n * - reject 'then' at EVERY level (a nested then makes that namespace object\n * thenable → `await pb.rooms` hangs) and every Object.prototype member\n * name (`'valueOf' in {}` is true — covers toString, __proto__, …);\n * - uppercase descriptor methods (request.ts matches mutating methods\n * against an uppercase set; a codegen 'get' would otherwise send a body\n * and an Idempotency-Key).\n */\nfunction validateDescriptorExtensions(d: EndpointDescriptor, path: string): void {\n if (d.pathParams !== undefined) {\n if (!Array.isArray(d.pathParams) || !d.pathParams.every((p) => typeof p === 'string')) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams must be an array of strings.`,\n });\n }\n // Cross-check: a pathParams entry without a matching placeholder would\n // silently consume a call arg and substitute nothing — fail at registration.\n for (const p of d.pathParams) {\n if (!d.path.includes(`{${p}}`)) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams entry '${p}' has no '{${p}}' placeholder in the path.`,\n });\n }\n }\n }\n if (d.input !== undefined && d.input !== 'body' && d.input !== 'query' && d.input !== 'none') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': input must be 'body', 'query' or 'none'.`,\n });\n }\n if (d.errors !== undefined) {\n for (const [code, fn] of Object.entries(d.errors)) {\n if (typeof fn !== 'function') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': errors['${code}'] must be a function.`,\n });\n }\n }\n }\n}\n\nfunction validateTree(node: NamespaceTree): void {\n const proto = Object.getPrototypeOf(node) as unknown;\n if (proto !== Object.prototype && proto !== null) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: 'Namespace tree nodes must be plain objects.',\n });\n }\n for (const [key, child] of Object.entries(node)) {\n if (key === 'then' || key in {}) throw reservedNamespaceError(key);\n if (isDescriptor(child)) {\n child.method = child.method.toUpperCase();\n validateDescriptorExtensions(child, child.path);\n } else {\n validateTree(child);\n }\n }\n}\n\nexport function __registerNamespaces(tree: NamespaceTree): void {\n for (const key of Object.keys(tree)) {\n if (RESERVED.has(key)) throw reservedNamespaceError(key);\n }\n validateTree(tree);\n const state = palbeState();\n state.registry = tree;\n state.nsCache = {};\n}\n\nexport function resetNamespaces(): void {\n const state = palbeState();\n state.registry = {};\n state.nsCache = {};\n}\n\nexport function getRegistry(): NamespaceTree {\n return palbeState().registry;\n}\n\nfunction serializeQuery(input: unknown): string {\n if (input === undefined || input === null) return '';\n const entries = Object.entries(input as Record<string, unknown>)\n .filter(([, v]) => v !== undefined && v !== null)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n if (entries.length === 0) return '';\n const parts = entries.map(([k, v]) => {\n if (typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {\n // Objects/arrays would stringify to '[object Object]' on the wire — fail loud.\n throw new BackendError('validation', {\n code: 'invalid_query_value',\n message: `Query parameter '${k}' must be a string, number, or boolean.`,\n });\n }\n return `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`;\n });\n return `?${parts.join('&')}`;\n}\n\nasync function invokeDescriptor(\n rt: PalbeRuntime,\n d: EndpointDescriptor,\n args: unknown[],\n): Promise<unknown> {\n const params = d.pathParams ?? [];\n let path = d.path;\n for (let i = 0; i < params.length; i++) {\n const v = args[i];\n if (typeof v !== 'string' && typeof v !== 'number') {\n throw new BackendError('validation', {\n code: 'missing_path_param',\n message: `${d.path}: path parameter '${params[i]}' must be a string or number`,\n });\n }\n path = path.replace(`{${params[i]}}`, encodeURIComponent(String(v)));\n }\n // input:'none' ops have NO input slot — options come straight after the\n // path params (a generated `get(id, options?)` would otherwise drop its\n // options into the input slot: GET throws, POST sends them as the body).\n if (d.input === 'none' && args.length > params.length + 1) {\n throw new BackendError('validation', {\n code: 'unexpected_argument',\n message: `${d.path}: endpoint takes no input — pass only path params and options`,\n });\n }\n const input = d.input === 'none' ? undefined : args[params.length];\n const optionsSlot = d.input === 'none' ? params.length : params.length + 1;\n const options = args[optionsSlot] as CallOptions | undefined;\n let body: unknown;\n if (d.input === 'query') {\n path += serializeQuery(input);\n } else if (d.input !== 'none') {\n if (d.method === 'GET') {\n if (input !== undefined) {\n throw new BackendError('validation', {\n code: 'unsupported_get_input',\n message: `${d.path}: GET endpoints take no body input`,\n });\n }\n } else {\n body = input;\n }\n }\n try {\n return await palbeRequest(rt, d.method, path, {\n body,\n headers: options?.headers,\n signal: options?.signal,\n });\n } catch (e) {\n if (d.errors && isBackendError(e)) {\n const lift = d.errors[e.code];\n if (lift) throw lift(e);\n }\n throw e;\n }\n}\n\n/**\n * The runtime is resolved AT CALL TIME via `resolveRt` (late binding): the\n * global path passes `getRuntime` so a `__configure` swap is picked up by\n * already-materialized namespaces; bound clients pass `() => rt` so the\n * captured runtime never changes.\n */\nfunction materialize(\n node: NamespaceTree | EndpointDescriptor,\n resolveRt: () => PalbeRuntime,\n): unknown {\n if (isDescriptor(node)) {\n // async wrapper: resolveRt() may throw (notConfigured) — endpoint methods\n // must ALWAYS reject, never throw synchronously (callers use .catch()).\n return async (...args: unknown[]) => invokeDescriptor(resolveRt(), node, args);\n }\n const out: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(node)) {\n out[key] = materialize(child, resolveRt);\n }\n return out;\n}\n\n/** Global path: resolves the global runtime slot, memoizes in the global nsCache. */\nexport function getNamespace(name: string): unknown {\n const state = palbeState();\n const cached = state.nsCache[name];\n if (cached !== undefined) return cached;\n const node = state.registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, getRuntime);\n state.nsCache[name] = ns;\n return ns;\n}\n\n/**\n * Bound path: namespaces materialize against ONE fixed runtime, memoized in a\n * per-client closure cache (the global nsCache stays global-only — a bound\n * entry there would leak one request's runtime into every other client).\n * The cache is invalidated when the registry object is swapped\n * (__registerNamespaces replaces it wholesale, e.g. watch-mode regen).\n */\nexport function boundNamespaceAccessor(rt: PalbeRuntime): (name: string) => unknown {\n let cacheRegistry = palbeState().registry;\n let cache: Record<string, unknown> = {};\n return (name: string): unknown => {\n const registry = palbeState().registry;\n if (registry !== cacheRegistry) {\n cacheRegistry = registry;\n cache = {};\n }\n const cached = cache[name];\n if (cached !== undefined) return cached;\n const node = registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, () => rt);\n cache[name] = ns;\n return ns;\n };\n}\n","const APP_METADATA_HEADER = 'X-Palbase-Bundle';\n\n/**\n * Stamp the live browser origin metadata on an app-key request. Final headers\n * are inspected case-insensitively so an explicit caller/config value wins.\n * The authenticated app identity is the app-bound API key plus its app_id;\n * this header is diagnostic metadata, not an authorization proof. Server\n * runtimes have no browser origin and intentionally add nothing.\n */\nexport function applyBrowserOriginHeader(headers: Record<string, string>): void {\n if (Object.keys(headers).some((key) => key.toLowerCase() === 'x-palbase-bundle')) return;\n const origin =\n typeof window !== 'undefined' && typeof window.location !== 'undefined'\n ? window.location.origin\n : '';\n if (origin !== '') headers[APP_METADATA_HEADER] = origin;\n}\n","// AUTO-GENERATED from package.json — do not edit\nexport const VERSION = '7.3.3';\n","import { BackendError } from './errors.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/**\n * Runtime resolution is deferred behind `resolveRt` (global pb passes\n * `getRuntime`, bound clients pass `() => rt`) — but name validation runs\n * FIRST so an empty name fails as `validation` even before `notConfigured`.\n */\nexport async function callEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n input?: unknown,\n options?: CallOptions,\n): Promise<O> {\n if (!name || name === '/') {\n throw new BackendError('validation', {\n code: 'invalid_endpoint_name',\n message: 'Endpoint name must be a non-empty path like \"todos/create\"',\n });\n }\n const rt = resolveRt();\n const path = name.startsWith('/') ? name : `/${name}`;\n return palbeRequest<O>(rt, 'POST', path, {\n body: input,\n headers: options?.headers,\n signal: options?.signal,\n });\n}\n","import { asPalbaseError, BackendError, fromEnvelope, fromPalbaseError } from './errors.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { applyBrowserOriginHeader } from './runtime-metadata.js';\nimport { VERSION } from './version.js';\n\nexport interface UploadProgress {\n /** Bytes sent so far. */\n sent: number;\n /**\n * Total bytes. Always > 0 when delivered — progress events whose length is\n * not computable are skipped entirely.\n */\n total: number;\n}\n\nexport interface UploadOptions {\n file: Blob;\n filename?: string;\n contentType?: string;\n fields?: Record<string, string>;\n onProgress?: (progress: UploadProgress) => void;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n /** Client-side pre-send validation (iOS parity). */\n constraints?: { maxSize?: number; allowedTypes?: string[] };\n}\n\nfunction abortError(): BackendError {\n return new BackendError('network', { code: 'aborted', message: 'Upload aborted' });\n}\n\nfunction checkConstraints(options: UploadOptions): void {\n const c = options.constraints;\n if (!c) return;\n if (c.maxSize !== undefined && options.file.size > c.maxSize) {\n const message = `File size ${options.file.size} exceeds max ${c.maxSize} bytes`;\n throw new BackendError('validation', {\n code: 'file_too_large',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n // Empty allowedTypes = no type check (iOS contract). Validate the EFFECTIVE\n // type: a contentType override wins over file.type (it is what gets sent).\n const effType = options.contentType ?? options.file.type;\n if (c.allowedTypes && c.allowedTypes.length > 0 && !c.allowedTypes.includes(effType)) {\n const message = `File type '${effType}' is not allowed`;\n throw new BackendError('validation', {\n code: 'file_type_not_allowed',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n}\n\nasync function buildHeaders(\n rt: PalbeRuntime,\n extra?: Record<string, string>,\n): Promise<Record<string, string>> {\n // Pre-flight refresh: mirrors the pattern in request.ts / HttpClient.\n if (\n rt.tokenManager.isExpired() &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (e) {\n const pe = asPalbaseError(e);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n rt.tokenManager.clearSession(); // terminal — proceed unauthenticated\n } else {\n throw pe ? fromPalbaseError(pe) : e;\n }\n }\n }\n\n // Uploads bypass @palbase/core to stream multipart bodies. Kong derives the\n // Environment identity from the API key, matching the iOS upload path.\n const headers: Record<string, string> = {\n apikey: rt.config.apiKey,\n 'X-Client-Info': `palbe-web/${VERSION}`,\n ...rt.config.headers,\n };\n const token = rt.tokenManager.getAccessToken();\n if (token) headers.Authorization = `Bearer ${token}`;\n // Case-insensitive scan (mirrors request.ts): a caller-supplied\n // `idempotency-key` in any casing must suppress generation.\n const callerHasKey = Object.keys(extra ?? {}).some((k) => k.toLowerCase() === 'idempotency-key');\n if (!callerHasKey) headers['Idempotency-Key'] = crypto.randomUUID();\n // extra spreads LAST so a caller-pinned Authorization wins (HttpClient/iOS precedence).\n const merged = { ...headers, ...extra };\n applyBrowserOriginHeader(merged);\n return merged;\n}\n\nfunction buildForm(options: UploadOptions): FormData {\n const form = new FormData();\n for (const [k, v] of Object.entries(options.fields ?? {})) form.append(k, v);\n const file =\n options.contentType && options.file.type !== options.contentType\n ? new Blob([options.file], { type: options.contentType })\n : options.file;\n const filename =\n options.filename ??\n (typeof File !== 'undefined' && options.file instanceof File ? options.file.name : 'file');\n form.append('file', file, filename);\n return form;\n}\n\nfunction decode<O>(status: number, text: string): O {\n let body: unknown;\n try {\n body = text === '' ? null : JSON.parse(text);\n } catch {\n if (status >= 200 && status < 300) {\n throw new BackendError('decode', {\n code: 'decode_error',\n message: 'Invalid JSON in response',\n status,\n });\n }\n body = text;\n }\n if (status < 200 || status >= 300) throw fromEnvelope(status, body);\n return body as O;\n}\n\nfunction uploadViaXHR<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n return new Promise<O>((resolve, reject) => {\n // Pre-aborted signal: never send. (xhr.abort() before send() fires no\n // abort event per spec, so routing this through onabort would hang.)\n if (options.signal?.aborted) {\n reject(abortError());\n return;\n }\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);\n xhr.upload.onprogress = (e: { lengthComputable: boolean; loaded: number; total: number }) => {\n if (!e.lengthComputable) return;\n options.onProgress?.({ sent: e.loaded, total: e.total });\n };\n const onAbort = () => xhr.abort();\n const cleanup = () => options.signal?.removeEventListener('abort', onAbort);\n xhr.onload = () => {\n cleanup();\n try {\n resolve(decode<O>(xhr.status, xhr.responseText));\n } catch (err) {\n reject(err);\n }\n };\n xhr.onerror = () => {\n cleanup();\n reject(new BackendError('network', { code: 'network_error', message: 'Upload failed' }));\n };\n xhr.onabort = () => {\n cleanup();\n reject(abortError());\n };\n options.signal?.addEventListener('abort', onAbort, { once: true });\n xhr.send(form);\n });\n}\n\nasync function uploadViaFetch<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n let res: Response;\n try {\n res = await fetch(url, { method: 'POST', body: form, headers, signal: options.signal });\n } catch (e) {\n // DOMException extends Error in modern runtimes — name check covers both.\n if (e instanceof Error && e.name === 'AbortError') throw abortError();\n throw new BackendError('network', {\n code: 'network_error',\n message: e instanceof Error ? e.message : 'Upload failed',\n });\n }\n return decode<O>(res.status, await res.text());\n}\n\n/** Runtime resolution deferred behind `resolveRt` — see call.ts for the pattern. */\nexport async function uploadEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n options: UploadOptions,\n): Promise<O> {\n const rt = resolveRt();\n checkConstraints(options); // pre-send guard — rejects before any network I/O\n const path = name.startsWith('/') ? name : `/${name}`;\n const url = `${rt.config.url}${path}`;\n const headers = await buildHeaders(rt, options.headers);\n const form = buildForm(options);\n const useXHR = options.onProgress !== undefined && typeof XMLHttpRequest !== 'undefined';\n return useXHR\n ? uploadViaXHR<O>(url, form, headers, options)\n : uploadViaFetch<O>(url, form, headers, options);\n}\n","import type { PalbeAnalytics } from './analytics-facade.js';\nimport type { PalbeAuth } from './auth-facade.js';\nimport { callEndpoint } from './call.js';\nimport type { PalbeCalls } from './calls/facade.js';\nimport type { PalbeFlags } from './flags-facade.js';\nimport { getRuntime } from './internal.js';\nimport type { PalbeMessaging } from './messaging/facade.js';\nimport { boundNamespaceAccessor, getNamespace, getRegistry } from './namespaces.js';\nimport type { PalbePerf } from './perf/perf-facade.js';\nimport type { PalbeRealtime } from './realtime/facade.js';\nimport type { CallOptions } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport type { UploadOptions } from './upload.js';\nimport { uploadEndpoint } from './upload.js';\n\n/**\n * The Palbe surface. Codegen augments this interface with typed endpoint\n * namespaces: declare module '@palbase/web' { interface PB { rooms: ... } }\n */\nexport interface PB {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O>;\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O>;\n readonly auth: PalbeAuth;\n readonly flags: PalbeFlags;\n readonly realtime: PalbeRealtime;\n readonly analytics: PalbeAnalytics;\n readonly calls: PalbeCalls;\n readonly messaging: PalbeMessaging;\n /** The PalPerf surface: custom traces (`startTrace`) + the buffered flush of\n * network/app-start/custom items. `pb.perf.record(...)` is also how the\n * runtime feeds the cold-start measurement in. */\n readonly perf: PalbePerf;\n /** Mark (or unmark) this client's traffic as test — flips the facade's flag\n * so every subsequent perf flush carries `X-Palbase-Test-Device: 1` (the\n * SAME header the iOS SDK sends; the server reads it → `is_test_traffic`). */\n setTestDevice(on: boolean): void;\n}\n\n/**\n * One proxy construction for BOTH client flavors:\n * - global `pb`: resolveRt = getRuntime (late-bound — a __configure swap is\n * visible to every subsequent call), nsAccessor = the globally-cached path;\n * - bound clients: resolveRt = () => rt (captured once, immune to global\n * swaps), nsAccessor = a per-client cache (see namespaces.ts).\n */\nfunction createClientProxy(\n resolveRt: () => PalbeRuntime,\n nsAccessor: (name: string) => unknown,\n): PB {\n const base = {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O> {\n return callEndpoint<O>(resolveRt, name, input, options);\n },\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O> {\n return uploadEndpoint<O>(resolveRt, name, options);\n },\n get auth(): PalbeAuth {\n return resolveRt().auth;\n },\n get flags(): PalbeFlags {\n return resolveRt().flags;\n },\n get realtime(): PalbeRealtime {\n return resolveRt().realtime;\n },\n get analytics(): PalbeAnalytics {\n return resolveRt().analytics;\n },\n get calls(): PalbeCalls {\n return resolveRt().calls;\n },\n get messaging(): PalbeMessaging {\n return resolveRt().messaging;\n },\n get perf(): PalbePerf {\n return resolveRt().perf;\n },\n setTestDevice(on: boolean): void {\n resolveRt().perf.setTestDevice(on);\n },\n };\n return new Proxy(base, {\n get(target, prop, receiver) {\n if (prop in target) return Reflect.get(target, prop, receiver);\n // `then` must NEVER resolve to a namespace: a thenable pb would make\n // `await pb` (or Promise.resolve(pb)) hang on the proxy. Registration\n // also rejects it (see namespaces.ts) — this is defense in depth.\n if (prop === 'then') return undefined;\n if (typeof prop === 'string') {\n const ns = nsAccessor(prop);\n if (ns !== undefined) return ns;\n }\n return undefined;\n },\n has(target, prop) {\n if (prop in target) return true;\n return typeof prop === 'string' && prop !== 'then' && getRegistry()[prop] !== undefined;\n },\n }) as PB;\n}\n\nexport const pb: PB = createClientProxy(getRuntime, getNamespace);\n\n/**\n * A `PB` client permanently bound to ONE runtime — typed namespaces, `call`,\n * `upload` and `auth` all resolve `rt`, NEVER the global slot. This is the\n * per-request isolation primitive for server adapters (palbe/next pbServer):\n * two bound clients serving concurrent requests cannot leak each other's\n * sessions, and global `__configure` swaps don't touch them.\n */\nexport function createBoundClient(rt: PalbeRuntime): PB {\n return createClientProxy(() => rt, boundNamespaceAccessor(rt));\n}\n","import type { PalbeConfig } from './config.js';\nimport { BackendError } from './errors.js';\nimport { __registerNamespaces, resetNamespaces } from './namespaces.js';\nimport { buildRuntime, type PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\n// Runtime lives in a process-global slot (see state.ts) so every build copy\n// (ESM/CJS, palbe + palbe/internal entries) converges on one singleton.\n// This is the BROWSER path (one runtime per tab). Server-side per-request\n// runtimes arrive with palbe/next (P3); do not sign in from shared server\n// code against this singleton.\n\n/**\n * Called by the generated palbe.gen.ts on import. REPLACES the runtime\n * (re-runs storage hydration) — safe to call repeatedly, e.g. watch-mode\n * regen. The namespace registry intentionally survives re-configure: the\n * gen file re-registers its static descriptors right after.\n */\nexport function __configure(config: PalbeConfig): void {\n const state = palbeState();\n // Destroy the old realtime facade if it was constructed — closes the socket\n // and clears heartbeat timers so they do not outlive the replaced runtime.\n state.runtime?.destroyRealtime();\n state.runtime = buildRuntime(config);\n // Fire after the runtime is in place so listeners that read pb.* immediately\n // (e.g. useSyncExternalStore getSnapshot) see the new runtime.\n for (const cb of state.configuredListeners) {\n try {\n cb();\n } catch {\n // consumer errors must not abort delivery to other listeners\n }\n }\n}\n\n/**\n * Register a callback that fires synchronously after every successful\n * `__configure` call (including re-configure / watch-mode regen).\n *\n * Returns an unsubscribe function. Safe to call before the first `__configure`.\n *\n * @internal — consumed by `palbe/react`; not part of the public `palbe` surface.\n */\nexport function onConfigured(cb: () => void): () => void {\n const state = palbeState();\n state.configuredListeners.add(cb);\n return () => state.configuredListeners.delete(cb);\n}\n\n/** @internal */\nexport function getRuntime(): PalbeRuntime {\n const rt = palbeState().runtime;\n if (!rt) throw BackendError.notConfigured();\n return rt;\n}\n\n/** Test-only: drop runtime + registered namespaces. */\nexport function __reset(): void {\n const state = palbeState();\n state.runtime?.destroyRealtime();\n state.runtime = null;\n state.configuredListeners.clear();\n resetNamespaces();\n}\n\nexport type { EndpointDescriptor, NamespaceTree } from './namespaces.js';\nexport type { PB } from './pb.js';\nexport { createBoundClient } from './pb.js';\nexport type { PalbeConfig, PalbeRuntime };\n// Server-adapter seams (palbe/next): per-request runtime + bound client.\nexport { __registerNamespaces, buildRuntime };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/react/index.tsx","../../../core/src/config.ts","../../../core/src/errors.ts","../../../core/src/pow.ts","../../../core/src/platform.ts","../../../core/src/http.ts","../../../core/src/token.ts","../../src/errors.ts","../../src/perf/url-redactor.ts","../../src/request.ts","../../src/state.ts","../../src/namespaces.ts","../../src/runtime-metadata.ts","../../src/version.ts","../../src/call.ts","../../src/upload.ts","../../src/pb.ts","../../src/internal.ts"],"sourcesContent":["'use client';\n\n// `palbe/react` — thin React hooks over the observable `pb.*` facades. Each\n// value hook is a `useSyncExternalStore` (the React 18+ concurrent-safe\n// primitive) binding: `subscribe` is the facade's Unsubscribe-returning\n// listener, `getSnapshot` is the matching getter. NO new SDK behaviour lives\n// here — the hooks re-render exactly when (and only when) their slice of state\n// changes, mirroring the iOS `@Observable` granularity.\n//\n// `react` is an OPTIONAL peer dependency: importing `palbe` (the core entry)\n// never pulls React; only `palbe/react` does.\n//\n// Client-only: the module starts with `'use client'` (Next.js App Router). SSR\n// initial values come from the `getServerSnapshot` arms (null user, empty flag\n// set) so a server render never touches the runtime singleton.\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { AuthUser, Unsubscribe } from '../auth-facade.js';\nimport type { FlagsView, FlagValue } from '../flags-facade.js';\nimport { onConfigured } from '../internal.js';\nimport type { Chat } from '../messaging/chat.js';\nimport type { ChatMember, ChatMessage } from '../messaging/types.js';\nimport { pb } from '../pb.js';\nimport type { RealtimeConnectionState, RealtimePayload } from '../realtime/facade.js';\n\n/** The session slice `useSession` exposes. */\nexport interface SessionState {\n signedIn: boolean;\n user: AuthUser | null;\n}\n\n/** Frozen empty flag view — the stable server/unconfigured fallback for\n * `useFlags` (a fresh `{}` each call would loop `useSyncExternalStore`). */\nconst EMPTY_FLAGS: FlagsView = Object.freeze({});\n\n/** The signed-out session snapshot — a single frozen instance so the\n * unconfigured/server snapshot is referentially stable across calls. */\nconst SIGNED_OUT: SessionState = Object.freeze({ signedIn: false, user: null });\n\n/**\n * Wraps a facade-listener factory so hooks that mount BEFORE `__configure`\n * runs self-heal when configuration arrives (or changes on re-configure /\n * watch-mode regen).\n *\n * Behaviour:\n * - If already configured at subscribe time, attaches the facade listener\n * immediately AND registers an `onConfigured` watcher for future re-configures.\n * - If NOT yet configured at subscribe time, only registers `onConfigured`;\n * the facade listener is attached when configuration arrives.\n * - On every configure event: detaches the old facade listener (if any),\n * attaches a fresh one against the new runtime, then calls `onStoreChange`\n * so React re-reads the snapshot from the new runtime.\n * - The returned unsubscribe tears down both the onConfigured registration\n * and the current facade listener.\n *\n * @param getFacadeListener - factory called when a runtime is available;\n * must return an Unsubscribe; called with `onStoreChange` as its argument.\n */\nfunction subscribeWithConfig(\n getFacadeListener: (onStoreChange: () => void) => Unsubscribe,\n): (onStoreChange: () => void) => Unsubscribe {\n return (onStoreChange: () => void): Unsubscribe => {\n let currentFacadeUnsub: Unsubscribe | null = null;\n\n function attachFacade(): void {\n currentFacadeUnsub?.();\n try {\n currentFacadeUnsub = getFacadeListener(onStoreChange);\n } catch {\n // notConfigured — will be retried when onConfigured fires\n currentFacadeUnsub = null;\n }\n }\n\n // Register for future (re-)configure events.\n const offConfigured = onConfigured(() => {\n attachFacade();\n // Notify React to re-read the snapshot from the new runtime.\n onStoreChange();\n });\n\n // If already configured right now, attach immediately too.\n attachFacade();\n\n return () => {\n offConfigured();\n currentFacadeUnsub?.();\n currentFacadeUnsub = null;\n };\n };\n}\n\n/**\n * Current authenticated user, or `null` when signed out. Re-renders on BOTH\n * auth-state transitions (sign-in adopts a user, sign-out clears it) and\n * user-profile changes (e.g. an email-verified flip via `refreshUser`).\n *\n * `getSnapshot` is guarded: before the generated `palbe.gen.ts` runs\n * `__configure`, `pb.auth` throws `notConfigured` — a tree rendered that early\n * gets `null`, never a render crash. Once `__configure` runs the hook\n * self-heals: it re-subscribes to the new runtime's auth facade and notifies\n * React to re-read the snapshot.\n */\nexport function useUser(): AuthUser | null {\n return useSyncExternalStore(subscribeUserWithConfig, getUserSnapshot, getNullUser);\n}\n\nconst subscribeUserWithConfig = subscribeWithConfig((onStoreChange) => {\n // Two sources, one composite unsubscribe:\n // onAuthStateChange → sign-in (new user) and sign-out (null)\n // onUserChange → in-place profile edits (email-verified, …)\n // Both fire immediately on subscribe (iOS parity), which simply re-reads the\n // already-current snapshot — harmless.\n const offState = pb.auth.onAuthStateChange(onStoreChange);\n const offUser = pb.auth.onUserChange(onStoreChange);\n return () => {\n offState();\n offUser();\n };\n});\n\nfunction getUserSnapshot(): AuthUser | null {\n try {\n return pb.auth.currentUser;\n } catch {\n return null;\n }\n}\n\nfunction getNullUser(): AuthUser | null {\n return null;\n}\n\n/**\n * Session slice: `{ signedIn, user }`. Re-renders on auth-state transitions.\n *\n * `getSnapshot` MUST be referentially stable when nothing changed — returning a\n * fresh object every call would put `useSyncExternalStore` into an infinite\n * render loop. A per-component ref caches the last value and returns the SAME\n * object until `signedIn` or `user` actually changes.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useSession(): SessionState {\n const lastRef = useRef<SessionState>(SIGNED_OUT);\n const getSnapshot = useCallback((): SessionState => {\n const next = readSession();\n const prev = lastRef.current;\n if (prev.signedIn === next.signedIn && prev.user === next.user) {\n return prev; // unchanged — keep the stable reference\n }\n lastRef.current = next;\n return next;\n }, []);\n return useSyncExternalStore(subscribeSessionWithConfig, getSnapshot, getSignedOut);\n}\n\nconst subscribeSessionWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.auth.onAuthStateChange(onStoreChange),\n);\n\nfunction readSession(): SessionState {\n try {\n const user = pb.auth.currentUser;\n const signedIn = pb.auth.isSignedIn;\n if (!signedIn && user === null) return SIGNED_OUT;\n return { signedIn, user };\n } catch {\n return SIGNED_OUT;\n }\n}\n\nfunction getSignedOut(): SessionState {\n return SIGNED_OUT;\n}\n\n/**\n * Subscribe to ONE flag. Re-renders only when THIS key's value changes\n * (`pb.flags.subscribeKey` is the per-key source — structural compare for\n * objects), returning `fallback` until the key has a value.\n *\n * The value is read at the dynamic flag boundary where the concrete type `T`\n * is caller-asserted (the cached value is whatever the server sent); the\n * contained `as T` is the documented escape hatch — flags are untyped on the\n * wire, and the caller owns the `fallback`'s type.\n *\n * NOTE: passing an inline-object `fallback` for an absent key defeats snapshot\n * caching (a new object reference each render re-enters the last-value ref).\n * Memoize the fallback (e.g. a module-level const or `useMemo`) if it matters.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlag<T extends FlagValue>(key: string, fallback: T): T {\n // Subscribe identity must change when `key` changes so useSyncExternalStore\n // re-subscribes to the new key. subscribeWithConfig wraps the per-key\n // factory so the hook also self-heals on (re-)configure.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `key` is a runtime param, not an outer-scope value — dependency IS required\n const subscribe = useCallback(\n subscribeWithConfig((onStoreChange) => pb.flags.subscribeKey(key, onStoreChange)),\n [key],\n );\n\n // Cache the last value so getSnapshot is referentially stable (objects come\n // back as the frozen pooled reference — stable identity until a real change).\n const lastRef = useRef<T>(fallback);\n const getSnapshot = useCallback((): T => {\n let next: T;\n try {\n const value = pb.flags.get(key);\n next = value === undefined ? fallback : (value as T);\n } catch {\n next = fallback;\n }\n if (!Object.is(next, lastRef.current)) {\n lastRef.current = next;\n }\n return lastRef.current;\n }, [key, fallback]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to the whole flag set. Re-renders on any change. `pb.flags.all()`\n * returns a frozen, identity-stable view (a NEW frozen object only when the set\n * actually changes), so it is `useSyncExternalStore`-safe as-is.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useFlags(): FlagsView {\n return useSyncExternalStore(subscribeFlagsWithConfig, getFlagsSnapshot, getEmptyFlags);\n}\n\nconst subscribeFlagsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.flags.onChange(onStoreChange),\n);\n\nfunction getFlagsSnapshot(): FlagsView {\n try {\n return pb.flags.all();\n } catch {\n return EMPTY_FLAGS;\n }\n}\n\nfunction getEmptyFlags(): FlagsView {\n return EMPTY_FLAGS;\n}\n\n/** The reported channel status: the shared-socket connection state, plus\n * `'unavailable'` when `pb.realtime.channel()` is not usable in this\n * environment (no WebSocket / SSR). */\nexport type ChannelStatus = RealtimeConnectionState | 'unavailable';\n\n/**\n * Subscribe a `handler` to one realtime `event` on `channel(name)` for the\n * lifetime of the component. Returns the live connection `{ status }`.\n *\n * The handler is held in a ref and refreshed every render, so passing a fresh\n * closure each render (the common case — callers needn't `useCallback`) does\n * NOT tear down and re-create the subscription. The subscription itself is\n * keyed on `[name, event]`: only a name/event change re-subscribes.\n *\n * StrictMode-safe: the facade refcounts joins, so React's mount→cleanup→\n * remount (double-invoke in dev) nets to exactly one live subscription via the\n * effect's `cancel()` cleanup — NO once-guard (a once-guard would leave the\n * subscription dead after the first cleanup; that was the P4 live-smoke bug).\n *\n * SSR / no-WebSocket: `pb.realtime.channel()` throws; the effect catches it and\n * reports `status: 'unavailable'` instead of crashing. (Effects don't run on\n * the server, so the initial server status is the idle default.)\n */\nexport function useChannel(\n name: string,\n event: string,\n handler: (payload: RealtimePayload) => void,\n): { status: ChannelStatus } {\n const handlerRef = useRef(handler);\n // Refresh the handler every render WITHOUT re-subscribing — the effect below\n // calls `handlerRef.current`, so the latest closure always runs.\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const [status, setStatus] = useState<ChannelStatus>('idle');\n\n // A configure-epoch counter: bumped by onConfigured so the channel effect\n // re-runs when a runtime arrives (handles the pre-config mount case and\n // watch-mode runtime swaps). Does not re-subscribe on every render.\n const [configEpoch, setConfigEpoch] = useState(0);\n useEffect(() => {\n return onConfigured(() => setConfigEpoch((n) => n + 1));\n }, []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: `configEpoch` is intentionally a re-run trigger for configure-arrive / watch-mode\n useEffect(() => {\n let channel: ReturnType<typeof pb.realtime.channel>;\n try {\n channel = pb.realtime.channel(name);\n } catch {\n // notConfigured or no-WebSocket / server environment — degrade gracefully.\n setStatus('unavailable');\n return;\n }\n const sub = channel.on(event, (payload) => handlerRef.current(payload));\n // Seed from the current connection state, then track transitions.\n setStatus(pb.realtime.status.state);\n const offStatus = pb.realtime.status.onChange((snapshot) => setStatus(snapshot.state));\n return () => {\n offStatus();\n sub.cancel();\n };\n }, [name, event, configEpoch]);\n\n return { status };\n}\n\n// ─── Messaging hooks (Web-MLS Phase 2) ───────────────────────────────────────\n\nconst EMPTY_CHATS: readonly Chat[] = Object.freeze([]);\nconst EMPTY_MESSAGES: readonly ChatMessage[] = Object.freeze([]);\nconst EMPTY_MEMBERS: readonly ChatMember[] = Object.freeze([]);\nconst EMPTY_TYPING: readonly ChatMember[] = Object.freeze([]);\n\n/**\n * The observable chat list (DMs + groups, active only). Re-renders when a chat\n * is added (you start one / a peer DMs you and the Welcome drains), removed, or\n * the list hydrates from the durable catalog on launch.\n *\n * Self-heals when `__configure` runs after mount (same as `useUser`).\n */\nexport function useChats(): readonly Chat[] {\n const lastRef = useRef<readonly Chat[]>(EMPTY_CHATS);\n const getSnapshot = useCallback((): readonly Chat[] => {\n try {\n const next = pb.messaging.chats;\n // Identity-stable: only swap the cached array when the set changed.\n if (next.length !== lastRef.current.length || next.some((c, i) => c !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n } catch {\n return EMPTY_CHATS;\n }\n }, []);\n return useSyncExternalStore(subscribeChatsWithConfig, getSnapshot, getEmptyChats);\n}\n\nconst subscribeChatsWithConfig = subscribeWithConfig((onStoreChange) =>\n pb.messaging.onChatsChange(onStoreChange),\n);\n\nfunction getEmptyChats(): readonly Chat[] {\n return EMPTY_CHATS;\n}\n\n/**\n * Bind to one `Chat`'s changes. Re-renders whenever the chat's observable state\n * changes (a new message, members refresh, typing, materialize draft→active).\n * Returns the SAME `chat` instance for ergonomic access to `chat.messages`,\n * `chat.send(...)`, etc.\n */\nexport function useChat(chat: Chat): Chat {\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n // The chat instance is pointer-stable; a per-render counter forces re-render\n // on each onChange. Snapshot returns the instance itself (stable identity).\n const getSnapshot = useCallback(() => chat, [chat]);\n // Tick on every change so consumers reading chat.messages re-render.\n const [, force] = useState(0);\n useEffect(() => chat.onChange(() => force((n) => n + 1)), [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * The ordered message transcript for a chat (newest last). Re-renders on a new\n * message / history page / own-send echo. A cached snapshot keeps the array\n * reference stable until the list actually changes.\n */\nexport function useMessages(chat: Chat): readonly ChatMessage[] {\n const lastRef = useRef<readonly ChatMessage[]>(EMPTY_MESSAGES);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMessage[] => {\n const next = chat.messages;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MESSAGES);\n}\n\n/** The members of a chat (users). Re-renders when the roster changes. */\nexport function useChatMembers(chat: Chat): readonly ChatMember[] {\n const lastRef = useRef<readonly ChatMember[]>(EMPTY_MEMBERS);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMember[] => {\n const next = chat.members;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MEMBERS);\n}\n\n/** The users currently typing in a chat. Re-renders on typing start/stop. */\nexport function useTyping(chat: Chat): readonly ChatMember[] {\n const lastRef = useRef<readonly ChatMember[]>(EMPTY_TYPING);\n const subscribe = useCallback(\n (onStoreChange: () => void) => chat.onChange(onStoreChange),\n [chat],\n );\n const getSnapshot = useCallback((): readonly ChatMember[] => {\n const next = chat.typing;\n if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {\n lastRef.current = next.slice();\n }\n return lastRef.current;\n }, [chat]);\n return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_TYPING);\n}\n","import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * One SHA-256, by the fastest route this runtime offers.\n *\n * Awaiting `crypto.subtle.digest` once per nonce is what made this expensive,\n * and the cost is the await rather than the hashing. Measured on one machine,\n * 200k digests of a 40-byte input:\n *\n *\tawaited crypto.subtle.digest 105,597 digests/s\n *\tsync node:crypto createHash 1,324,503 digests/s — 12.5x\n *\n * That is the difference between difficulty 24 taking 159 seconds and taking\n * 13. Node, Bun and Deno all have the sync one; a browser has only WebCrypto,\n * and there it stays async.\n *\n * The specifier is assembled at runtime so a browser bundler does not try to\n * resolve `node:crypto` and fail the build over a branch that never runs there.\n */\ntype Hasher = (input: string) => Uint8Array | Promise<Uint8Array>;\n\nlet hasher: Hasher | null = null;\n\nasync function digester(): Promise<Hasher> {\n if (hasher) return hasher;\n // Read off globalThis with an inline shape rather than by naming `process`,\n // which needs @types/node — a dependency this package does not have and should\n // not grow for one branch. It typechecked locally only because those types\n // were hoisted into node_modules by a sibling package; the publish workflow's\n // clean checkout is what said so, which is exactly what it is for.\n const runtime = globalThis as {\n process?: { versions?: { node?: string; bun?: string } };\n };\n const nodeish =\n runtime.process?.versions?.node !== undefined ||\n runtime.process?.versions?.bun !== undefined;\n if (nodeish) {\n try {\n const mod = (await import(/* @vite-ignore */ `${'node:'}crypto`)) as {\n createHash?: (alg: string) => { update(s: string): { digest(): Uint8Array } };\n };\n if (typeof mod.createHash === 'function') {\n const createHash = mod.createHash;\n hasher = (input: string) => new Uint8Array(createHash('sha256').update(input).digest());\n return hasher;\n }\n } catch {\n // No node:crypto here. WebCrypto below is not a fallback in the apologetic\n // sense — it is the only hash a browser has, and it is correct.\n }\n }\n hasher = async (input: string) =>\n new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(input)));\n return hasher;\n}\n\n/** Monotonic where it exists, wall-clock where it does not. */\nconst now = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * The hardest challenge this client will attempt.\n *\n * Not a taste: it is the server's own ceiling. palauth maps a risk score to a\n * difficulty and its worst case is 24 (`DifficultyForRisk`, bot/pow.go:156-166).\n * Anything above that cannot have come from a stack behaving as designed, and\n * the cost of humouring it falls entirely on this side — each step up DOUBLES\n * the work, so difficulty 30 is sixty-four times a legitimate worst case and, on\n * the web, sixty-four times a frozen main thread. Refused immediately, by name.\n */\nexport const MAX_POW_DIFFICULTY = 24;\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * THE BUDGET SCALES WITH THE CHALLENGE, and the first version of this did not.\n * It bounded the search at a flat `1 << 24` — which is not a generous bound for\n * difficulty 24, it is the EXPECTED number of attempts. Finding a nonce is a\n * geometric process: the chance of needing more than 2^d attempts is 1/e, so a\n * flat 2^24 would have failed roughly 37% of legitimate hardest-risk challenges,\n * and failed them for precisely the users the gate exists to slow down — who\n * would have been unable to sign in at all rather than made to wait.\n *\n * Eight times expected puts that at e^-8, about three in ten thousand, while\n * leaving the common case (the server's default 16, and 12 for an unremarkable\n * caller) exactly as cheap as it was.\n *\n * `powBudget` is exported and separate so the RELATIONSHIP can be asserted\n * directly. A test that only watches a cheap challenge succeed cannot tell this\n * budget from the flat one it replaced — measured: reinstating `1 << 24` left\n * such a test green.\n */\nexport function powBudget(difficulty: number): number {\n return 8 * 2 ** difficulty;\n}\n\n/**\n * The longest a solve may be ALLOWED to take, and the difference from a\n * deadline is the whole point.\n *\n * The first version of this was a flat 120s deadline, and it was measured to be\n * worse than the flat iteration budget it was meant to backstop: at ~105k\n * digests/s, difficulty 24 EXPECTS 159 seconds, so a 120s clock killed the\n * majority of legitimate hardest-risk solves — reintroducing, larger, exactly\n * the class of defect that replacing `1 << 24` had removed. Guessing a number\n * for an unknown machine cannot work: the same difficulty is 13 seconds on a\n * runtime with a sync hasher and 159 on one without.\n *\n * So the machine is MEASURED, and the decision moves to the front. A short\n * calibration gives the rate this process actually hashes at; if the whole\n * iteration budget cannot fit in this window at that rate, the solve is refused\n * IMMEDIATELY, naming the numbers. A caller then learns in milliseconds that\n * this difficulty is unpayable here, instead of after two minutes of work\n * thrown away.\n *\n * What remains after that is a guarantee rather than a gamble: a solve that\n * starts can always finish inside its budget, so the only failure left is the\n * budget's own e^-8.\n */\nexport const POW_TIME_BUDGET_MS = 120_000;\n\n/**\n * The window the rate is measured over, and the warm-up it deliberately skips.\n *\n * All of these are REAL attempts — the search starts at nonce 0 and never\n * restarts — so calibration costs nothing but the reading. The first 1024 are\n * excluded from the timing because they include this loop's own JIT warm-up:\n * measured, timing from zero reported 747k digests/s on a machine whose steady\n * rate is 1.32M, and the decision below would have refused a difficulty this\n * machine can pay in half the allowance.\n */\nconst CALIBRATION_WARMUP = 1024;\nconst CALIBRATION_END = 9216;\n\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = powBudget(challenge.difficulty),\n // The caller's AbortSignal, honoured INSIDE the loop rather than only around\n // the fetch it precedes — for the callers that have one. `pb.auth.signIn` does\n // NOT: it reaches the network through @palbase/auth's client, which takes\n // credentials and nothing else. So it is the extra a caller can opt into, and\n // POW_TIME_BUDGET_MS below is what actually bounds the work.\n signal?: AbortSignal,\n): Promise<Record<string, string>> {\n if (challenge.difficulty > MAX_POW_DIFFICULTY) {\n throw new Error(\n `proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`,\n );\n }\n\n const digest = await digester();\n let warmedAt = 0;\n let calibrated = false;\n // Armed by the calibration below, never before it: until the rate is known\n // there is no honest number to put here.\n let deadline = Number.POSITIVE_INFINITY;\n\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n // Checked in batches: reading them is cheap but not free, and a\n // 1024-digest granularity bounds the delay at a few milliseconds.\n if ((nonce & 1023) === 0) {\n if (signal?.aborted) {\n throw new DOMException('proof-of-work solve aborted', 'AbortError');\n }\n if (now() > deadline) {\n throw new Error(\n `proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1000}s ` +\n `and ${nonce.toLocaleString()} attempts — the tail this run drew is longer than the allowance`,\n );\n }\n }\n\n // THE DECISION, TAKEN ONCE AND TAKEN EARLY.\n //\n // After CALIBRATION_DIGESTS real attempts the rate of THIS process is\n // known, so the question \"can this machine pay this difficulty\" has an\n // answer instead of an assumption. If the whole budget cannot fit in the\n // time budget, refuse here — milliseconds in, with the numbers — rather\n // than spend two minutes and throw them away. If it fits, everything after\n // this point is guaranteed to finish inside the window, so the only\n // remaining failure is the budget's own e^-8.\n if (nonce === CALIBRATION_WARMUP) {\n warmedAt = now();\n }\n if (!calibrated && nonce === CALIBRATION_END) {\n calibrated = true;\n const elapsed = Math.max(now() - warmedAt, 0.001);\n const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1000);\n // EXPECTED, not worst case, and the difference is the whole judgement.\n //\n // Finding a nonce is geometric: 2^difficulty attempts on average, with a\n // long tail the 8x budget covers. Refusing because the TAIL will not fit\n // would turn away work whose expected cost is seventeen seconds — measured\n // exactly that on this machine at difficulty 24. Refusing on the EXPECTED\n // cost turns away only what is genuinely unpayable here, and what it lets\n // through is then cut by the clock with probability e^-(budget/expected):\n // at 120s against a 17s expectation that is one run in a thousand, and at\n // difficulty 20 on a browser it is one in a hundred and fifty thousand.\n const expectedMs = (2 ** challenge.difficulty / rate) * 1000;\n if (expectedMs > POW_TIME_BUDGET_MS) {\n throw new Error(\n `proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1000)}s here ` +\n `(${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1000}s; ` +\n `refusing before spending the time rather than after`,\n );\n }\n deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));\n }\n\n const hash = await digest(challenge.prefix + nonce);\n if (leadingZeroBits(hash) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment\n * ref is 4-24 lowercase ASCII alphanumeric characters and random is 20 base62\n * chars. See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n // WITHOUT `earned`, and that is the whole point of this line.\n //\n // A network error means the response was lost, not that the request\n // was. If it reached the server, the challenge is already SPENT —\n // palauth's VerifyChallenge reads and deletes in one step\n // (bot/pow.go:96-104), deliberately, because a proof presented twice is\n // not proof. Replaying the nonce would then answer `pow_invalid`, and\n // the one-solve guard below would refuse to try again: a request one\n // fresh solve away from succeeding, failed. Dropping it costs nothing\n // in the other case — if the server never saw the request, a fresh\n // challenge works exactly as well as the old one.\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n // WITH `earned`, unlike the network path above: a 429 is a refusal the\n // server issued INSTEAD of doing the work, so the challenge was never\n // consumed. The edge's rate limiter answers before palsvc, and on the\n // auth routes palauth's own limiter runs BEFORE the proof-of-work\n // middleware (auth/internal/server/server.go: rl.LoginByIP, then powMW).\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge, undefined, options?.signal),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, details?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', {\n ...base,\n retryAfter: pickNumber(err.details, 'retry_after'),\n });\n // Field-error array lives at the envelope's nested `details` key.\n const nested = pickField(err.details, 'details');\n if (err.status === 400 && isFieldErrorArray(nested))\n return new BackendError('validation', { ...base, fields: nested });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const details = pickField(body, 'details');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', {\n ...params,\n // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.\n retryAfter: pickNumber(body, 'retry_after') ?? pickNumber(details, 'retry_after'),\n });\n if (status === 400 && isFieldErrorArray(details))\n return new BackendError('validation', { ...params, fields: details });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n","const ID_SEGMENT =\n /^(?:\\d+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n\n// An email lands in a path segment as `jane@acme.com` or percent-encoded\n// `jane%40acme.com`. Either form is PII and must never survive into a metric\n// name, so a segment carrying an `@` (encoded or raw) is masked like an id.\nconst EMAIL_SEGMENT = /(?:@|%40)/i;\n\nfunction isSensitiveSegment(seg: string): boolean {\n return ID_SEGMENT.test(seg) || EMAIL_SEGMENT.test(seg);\n}\n\n/**\n * Capture-time URL redaction (§4.1 privacy, client half). Collapses a request\n * URL to a PII-free, low-cardinality path pattern BEFORE it leaves the browser:\n * query string dropped wholesale, id-like segments masked. The ingest scrubber\n * re-applies server-side (defense in depth).\n *\n * Rules:\n * - absolute URL → keep only `pathname` (scheme/host/query/hash dropped);\n * - relative URL → drop everything from the first `?` onward;\n * - any segment that is purely numeric, a UUID, or carries an email (`@` /\n * `%40`) → `:id`;\n * - credentials/tokens live in the query (dropped wholesale), emails in a\n * path segment (masked), so neither survives into the metric name.\n */\nexport function redactUrl(rawUrl: string): string {\n let path = rawUrl;\n try {\n // Absolute URLs: take the pathname (drops scheme/host/query/hash).\n path = new URL(rawUrl).pathname;\n } catch {\n const q = path.indexOf('?');\n if (q >= 0) path = path.slice(0, q);\n }\n return path\n .split('/')\n .map((seg) => (isSensitiveSegment(seg) ? ':id' : seg))\n .join('/');\n}\n","import { asPalbaseError, fromPalbaseError, unwrap } from './errors.js';\nimport { redactUrl } from './perf/url-redactor.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { asPowChallenge, solvePowChallenge } from '@palbase/core';\n\nexport interface RequestSpec {\n body?: unknown;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport interface CallOptions {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nconst MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\n\n// The perf flush (`POST /v1/analytics/perf`) and analytics event ingest\n// (`/v1/analytics/*`) BOTH funnel through `palbeRequest`. Tracing them would\n// make every flush emit another `network` item that the next flush ships,\n// then traces again — a self-measurement loop. Excluding the whole\n// `/v1/analytics/` prefix breaks it. This is the SAME self-exclusion shape as\n// the iOS network trace (1b-3), keyed on the identical ingest prefix.\nconst PERF_EXCLUDED_PREFIX = '/v1/analytics/';\n\nfunction isSelfTraced(path: string): boolean {\n return path.startsWith(PERF_EXCLUDED_PREFIX);\n}\n\n/**\n * Codes meaning the server-side session is GONE — a token refresh cannot help\n * and the client must tear its local session down. `subject_fenced` is palauth's\n * wire-true erasure-fence code (`bridge.go`); `subject_erased` is accepted for\n * forward-compat. `session_revoked` is palauth's revoked-session 401.\n * A PLAIN 403 (ordinary authz / RLS denial) is intentionally NOT here — matching\n * all 403s would sign users out on routine permission errors.\n */\nfunction isSessionKilled(status: number, code: string): boolean {\n if (status === 401) return code === 'session_revoked';\n if (status === 403) return code === 'subject_fenced' || code === 'subject_erased';\n return false;\n}\n\n/** Monotonic millisecond clock — `performance.now()` when present, else\n * `Date.now()`. Mirrors `perf-facade.ts`'s `nowMs()` so network durations and\n * custom-trace durations share one time base. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/** An `AbortError` (caller-cancelled request) is NOT a completed network call,\n * so it must never produce a trace. */\nfunction isAbort(e: unknown): boolean {\n return e instanceof Error && e.name === 'AbortError';\n}\n\nexport async function palbeRequest<T>(\n rt: PalbeRuntime,\n method: string,\n path: string,\n spec: RequestSpec = {},\n): Promise<T> {\n const headers: Record<string, string> = { ...spec.headers };\n // Case-insensitive scan: a caller-supplied `idempotency-key` must suppress\n // generation, otherwise Headers merges both casings and dedup breaks.\n const callerHasKey = Object.keys(headers).some((k) => k.toLowerCase() === 'idempotency-key');\n if (MUTATING.has(method) && !callerHasKey) {\n headers['Idempotency-Key'] = crypto.randomUUID();\n }\n\n // `extra` carries the proof-of-work headers on the one retry that needs them.\n // The base `headers` are reused rather than rebuilt, so a retry keeps the same\n // Idempotency-Key and the server still sees one logical request.\n const attempt = async (extra?: Record<string, string>) => {\n try {\n return await rt.http.request<T>(method, path, {\n body: spec.body,\n headers: extra ? { ...headers, ...extra } : headers,\n signal: spec.signal,\n });\n } catch (e) {\n const pe = asPalbaseError(e);\n throw pe ? fromPalbaseError(pe) : e;\n }\n };\n\n // The single network-trace choke point: every SDK fetch funnels through\n // `palbeRequest`, so timing it here records one `network` PerfItem per call —\n // redacted URL + method + final HTTP status + wall-clock duration. The\n // `/v1/analytics/` ingest paths are self-excluded (no recursion) and aborts\n // are skipped (not a completed call).\n // `rt.perf` is guarded too: `buildRuntime` always wires it, but a hand-built\n // runtime (tests, embedders) may omit it — a missing facade must never break\n // the request path.\n const traced = !isSelfTraced(path) && rt.perf !== undefined;\n const startedAt = traced ? nowMs() : 0;\n // Exactly ONE trace per `palbeRequest`: the inner refresh-catch records with\n // the precise status and rethrows; that rethrow is then caught by the outer\n // catch, so the guard keeps the second `record()` a no-op (no double-count).\n let recorded = false;\n const record = (status: number): void => {\n if (!traced || recorded) return;\n recorded = true;\n rt.perf.recordNetwork(method, redactUrl(path), status, nowMs() - startedAt);\n };\n\n let res: Awaited<ReturnType<typeof attempt>>;\n try {\n res = await attempt();\n\n // Reactive 401: refresh once, retry once with the same Idempotency-Key.\n // A revoked session (401 session_revoked) is terminal — a refresh cannot\n // resurrect it, so skip straight to the teardown check below.\n if (\n res.error?.status === 401 &&\n res.error.code !== 'session_revoked' &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (refreshErr) {\n const pe = asPalbaseError(refreshErr);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead. Sign out and surface the\n // ORIGINAL 401 (mirrors HttpClient's pre-flight terminal split).\n rt.tokenManager.clearSession();\n record(res.error.status);\n throw fromPalbaseError(res.error);\n }\n // Transient (network/5xx): keep the session, surface the refresh failure.\n record(pe?.status ?? 0);\n throw pe ? fromPalbaseError(pe) : refreshErr;\n }\n res = await attempt();\n }\n\n // Reactive proof-of-work: signup and login sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it once and repeat the request with the headers; the caller never learns\n // the gate is there.\n //\n // Placed after the 401 branch so it sees whichever response is current — a\n // refresh-and-retry can land on the gate just as a first attempt can.\n //\n // Only ONE retry, and only when the body really carries a challenge. A 403\n // that merely says pow_required without one is a server the client cannot\n // satisfy, and looping on it would turn a broken gate into a hang.\n if (res.error?.status === 403 && res.error.code === 'pow_required') {\n const challenge = asPowChallenge(res.error.details);\n if (challenge) {\n res = await attempt(await solvePowChallenge(challenge));\n }\n }\n } catch (e) {\n // A thrown error here is a network/transport failure (the `{data,error}`\n // envelope never throws). Record it as a completed-with-error call unless\n // the caller aborted — an abort is a cancellation, not a measurement.\n if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);\n throw e;\n }\n\n // Reactive session teardown: a server-side session kill (subject erased /\n // session revoked) means the local session is dead regardless of which facade\n // issued this call — tear it down + emit signedOut{sessionInvalid} once. Scoped\n // to the exact codes so an ordinary 403 authz denial does NOT sign the user out.\n if (res.error && isSessionKilled(res.error.status, res.error.code)) {\n rt.auth.reactiveTeardown();\n }\n\n // Response resolved (2xx or an error envelope): record the real HTTP status\n // BEFORE `unwrap` may throw, so a 4xx/5xx is measured with its true status.\n record(res.error?.status ?? 200);\n return unwrap(res);\n}\n","import type { NamespaceTree } from './namespaces.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport interface PalbeGlobalState {\n runtime: PalbeRuntime | null;\n registry: NamespaceTree;\n /** Memoized materialized namespaces (per top-level key); reset on (re-)register. */\n nsCache: Record<string, unknown>;\n /** Listeners fired synchronously after every successful __configure call. */\n configuredListeners: Set<() => void>;\n}\n\n/**\n * Module-level state would split between the ESM and CJS bundles of the two\n * entries (esbuild only code-splits ESM) and between dual-package copies in\n * one process. A globalThis slot keyed by Symbol.for converges them all.\n */\nconst STATE_KEY: unique symbol = Symbol.for('palbe.state.v1');\n\ninterface PalbeGlobals {\n [STATE_KEY]?: PalbeGlobalState;\n}\n\nexport function palbeState(): PalbeGlobalState {\n // Single direct widening view of globalThis — typed via the unique-symbol\n // key above so reads/writes of the slot stay fully typed.\n const g = globalThis as PalbeGlobals;\n let state = g[STATE_KEY];\n if (!state) {\n state = { runtime: null, registry: {}, nsCache: {}, configuredListeners: new Set() };\n g[STATE_KEY] = state;\n }\n return state;\n}\n","import { BackendError, isBackendError } from './errors.js';\nimport { getRuntime } from './internal.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\nexport interface EndpointDescriptor {\n method: string;\n path: string;\n /** `{name}` segments consumed as leading call args, in order. */\n pathParams?: string[];\n /**\n * 'body' (default for non-GET) | 'query' (serialize input into the URL) |\n * 'none' (no input arg — options come straight after path params).\n */\n input?: 'body' | 'query' | 'none';\n /** wire error code → lift fn; the materializer rethrows the lifted error. */\n errors?: Record<string, (e: BackendError) => Error>;\n}\n\nexport type NamespaceTree = { [key: string]: NamespaceTree | EndpointDescriptor };\n\n// Reserved surface names on pb — cannot be shadowed at the top level by codegen\n// namespaces. `perf` sits next to `analytics`: `pb.perf` is the PalPerf facade,\n// so a generated controller named `perf` must NOT collide with it.\nexport const RESERVED = new Set([\n 'call',\n 'upload',\n 'auth',\n 'flags',\n 'realtime',\n 'analytics',\n 'calls',\n 'messaging',\n 'perf',\n]);\n\nfunction reservedNamespaceError(key: string): BackendError {\n return new BackendError('validation', {\n code: 'reserved_namespace',\n message: `Cannot register endpoint namespace '${key}': the name is reserved on pb.`,\n });\n}\n\nfunction isDescriptor(node: NamespaceTree | EndpointDescriptor): node is EndpointDescriptor {\n return (\n typeof (node as EndpointDescriptor).path === 'string' &&\n typeof (node as EndpointDescriptor).method === 'string'\n );\n}\n\n/**\n * Validate + normalize one tree level, recursively:\n * - plain-object guard (a literal `__proto__:` in a hand-written tree would\n * swap the node's prototype — cheap proto-pollution insurance);\n * - reject 'then' at EVERY level (a nested then makes that namespace object\n * thenable → `await pb.rooms` hangs) and every Object.prototype member\n * name (`'valueOf' in {}` is true — covers toString, __proto__, …);\n * - uppercase descriptor methods (request.ts matches mutating methods\n * against an uppercase set; a codegen 'get' would otherwise send a body\n * and an Idempotency-Key).\n */\nfunction validateDescriptorExtensions(d: EndpointDescriptor, path: string): void {\n if (d.pathParams !== undefined) {\n if (!Array.isArray(d.pathParams) || !d.pathParams.every((p) => typeof p === 'string')) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams must be an array of strings.`,\n });\n }\n // Cross-check: a pathParams entry without a matching placeholder would\n // silently consume a call arg and substitute nothing — fail at registration.\n for (const p of d.pathParams) {\n if (!d.path.includes(`{${p}}`)) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams entry '${p}' has no '{${p}}' placeholder in the path.`,\n });\n }\n }\n }\n if (d.input !== undefined && d.input !== 'body' && d.input !== 'query' && d.input !== 'none') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': input must be 'body', 'query' or 'none'.`,\n });\n }\n if (d.errors !== undefined) {\n for (const [code, fn] of Object.entries(d.errors)) {\n if (typeof fn !== 'function') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': errors['${code}'] must be a function.`,\n });\n }\n }\n }\n}\n\nfunction validateTree(node: NamespaceTree): void {\n const proto = Object.getPrototypeOf(node) as unknown;\n if (proto !== Object.prototype && proto !== null) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: 'Namespace tree nodes must be plain objects.',\n });\n }\n for (const [key, child] of Object.entries(node)) {\n if (key === 'then' || key in {}) throw reservedNamespaceError(key);\n if (isDescriptor(child)) {\n child.method = child.method.toUpperCase();\n validateDescriptorExtensions(child, child.path);\n } else {\n validateTree(child);\n }\n }\n}\n\nexport function __registerNamespaces(tree: NamespaceTree): void {\n for (const key of Object.keys(tree)) {\n if (RESERVED.has(key)) throw reservedNamespaceError(key);\n }\n validateTree(tree);\n const state = palbeState();\n state.registry = tree;\n state.nsCache = {};\n}\n\nexport function resetNamespaces(): void {\n const state = palbeState();\n state.registry = {};\n state.nsCache = {};\n}\n\nexport function getRegistry(): NamespaceTree {\n return palbeState().registry;\n}\n\nfunction serializeQuery(input: unknown): string {\n if (input === undefined || input === null) return '';\n const entries = Object.entries(input as Record<string, unknown>)\n .filter(([, v]) => v !== undefined && v !== null)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n if (entries.length === 0) return '';\n const parts = entries.map(([k, v]) => {\n if (typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {\n // Objects/arrays would stringify to '[object Object]' on the wire — fail loud.\n throw new BackendError('validation', {\n code: 'invalid_query_value',\n message: `Query parameter '${k}' must be a string, number, or boolean.`,\n });\n }\n return `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`;\n });\n return `?${parts.join('&')}`;\n}\n\nasync function invokeDescriptor(\n rt: PalbeRuntime,\n d: EndpointDescriptor,\n args: unknown[],\n): Promise<unknown> {\n const params = d.pathParams ?? [];\n let path = d.path;\n for (let i = 0; i < params.length; i++) {\n const v = args[i];\n if (typeof v !== 'string' && typeof v !== 'number') {\n throw new BackendError('validation', {\n code: 'missing_path_param',\n message: `${d.path}: path parameter '${params[i]}' must be a string or number`,\n });\n }\n path = path.replace(`{${params[i]}}`, encodeURIComponent(String(v)));\n }\n // input:'none' ops have NO input slot — options come straight after the\n // path params (a generated `get(id, options?)` would otherwise drop its\n // options into the input slot: GET throws, POST sends them as the body).\n if (d.input === 'none' && args.length > params.length + 1) {\n throw new BackendError('validation', {\n code: 'unexpected_argument',\n message: `${d.path}: endpoint takes no input — pass only path params and options`,\n });\n }\n const input = d.input === 'none' ? undefined : args[params.length];\n const optionsSlot = d.input === 'none' ? params.length : params.length + 1;\n const options = args[optionsSlot] as CallOptions | undefined;\n let body: unknown;\n if (d.input === 'query') {\n path += serializeQuery(input);\n } else if (d.input !== 'none') {\n if (d.method === 'GET') {\n if (input !== undefined) {\n throw new BackendError('validation', {\n code: 'unsupported_get_input',\n message: `${d.path}: GET endpoints take no body input`,\n });\n }\n } else {\n body = input;\n }\n }\n try {\n return await palbeRequest(rt, d.method, path, {\n body,\n headers: options?.headers,\n signal: options?.signal,\n });\n } catch (e) {\n if (d.errors && isBackendError(e)) {\n const lift = d.errors[e.code];\n if (lift) throw lift(e);\n }\n throw e;\n }\n}\n\n/**\n * The runtime is resolved AT CALL TIME via `resolveRt` (late binding): the\n * global path passes `getRuntime` so a `__configure` swap is picked up by\n * already-materialized namespaces; bound clients pass `() => rt` so the\n * captured runtime never changes.\n */\nfunction materialize(\n node: NamespaceTree | EndpointDescriptor,\n resolveRt: () => PalbeRuntime,\n): unknown {\n if (isDescriptor(node)) {\n // async wrapper: resolveRt() may throw (notConfigured) — endpoint methods\n // must ALWAYS reject, never throw synchronously (callers use .catch()).\n return async (...args: unknown[]) => invokeDescriptor(resolveRt(), node, args);\n }\n const out: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(node)) {\n out[key] = materialize(child, resolveRt);\n }\n return out;\n}\n\n/** Global path: resolves the global runtime slot, memoizes in the global nsCache. */\nexport function getNamespace(name: string): unknown {\n const state = palbeState();\n const cached = state.nsCache[name];\n if (cached !== undefined) return cached;\n const node = state.registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, getRuntime);\n state.nsCache[name] = ns;\n return ns;\n}\n\n/**\n * Bound path: namespaces materialize against ONE fixed runtime, memoized in a\n * per-client closure cache (the global nsCache stays global-only — a bound\n * entry there would leak one request's runtime into every other client).\n * The cache is invalidated when the registry object is swapped\n * (__registerNamespaces replaces it wholesale, e.g. watch-mode regen).\n */\nexport function boundNamespaceAccessor(rt: PalbeRuntime): (name: string) => unknown {\n let cacheRegistry = palbeState().registry;\n let cache: Record<string, unknown> = {};\n return (name: string): unknown => {\n const registry = palbeState().registry;\n if (registry !== cacheRegistry) {\n cacheRegistry = registry;\n cache = {};\n }\n const cached = cache[name];\n if (cached !== undefined) return cached;\n const node = registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, () => rt);\n cache[name] = ns;\n return ns;\n };\n}\n","const APP_METADATA_HEADER = 'X-Palbase-Bundle';\n\n/**\n * Stamp the live browser origin metadata on an app-key request. Final headers\n * are inspected case-insensitively so an explicit caller/config value wins.\n * The authenticated app identity is the app-bound API key plus its app_id;\n * this header is diagnostic metadata, not an authorization proof. Server\n * runtimes have no browser origin and intentionally add nothing.\n */\nexport function applyBrowserOriginHeader(headers: Record<string, string>): void {\n if (Object.keys(headers).some((key) => key.toLowerCase() === 'x-palbase-bundle')) return;\n const origin =\n typeof window !== 'undefined' && typeof window.location !== 'undefined'\n ? window.location.origin\n : '';\n if (origin !== '') headers[APP_METADATA_HEADER] = origin;\n}\n","// AUTO-GENERATED from package.json — do not edit\nexport const VERSION = '7.3.5';\n","import { BackendError } from './errors.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/**\n * Runtime resolution is deferred behind `resolveRt` (global pb passes\n * `getRuntime`, bound clients pass `() => rt`) — but name validation runs\n * FIRST so an empty name fails as `validation` even before `notConfigured`.\n */\nexport async function callEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n input?: unknown,\n options?: CallOptions,\n): Promise<O> {\n if (!name || name === '/') {\n throw new BackendError('validation', {\n code: 'invalid_endpoint_name',\n message: 'Endpoint name must be a non-empty path like \"todos/create\"',\n });\n }\n const rt = resolveRt();\n const path = name.startsWith('/') ? name : `/${name}`;\n return palbeRequest<O>(rt, 'POST', path, {\n body: input,\n headers: options?.headers,\n signal: options?.signal,\n });\n}\n","import { asPalbaseError, BackendError, fromEnvelope, fromPalbaseError } from './errors.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { applyBrowserOriginHeader } from './runtime-metadata.js';\nimport { VERSION } from './version.js';\n\nexport interface UploadProgress {\n /** Bytes sent so far. */\n sent: number;\n /**\n * Total bytes. Always > 0 when delivered — progress events whose length is\n * not computable are skipped entirely.\n */\n total: number;\n}\n\nexport interface UploadOptions {\n file: Blob;\n filename?: string;\n contentType?: string;\n fields?: Record<string, string>;\n onProgress?: (progress: UploadProgress) => void;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n /** Client-side pre-send validation (iOS parity). */\n constraints?: { maxSize?: number; allowedTypes?: string[] };\n}\n\nfunction abortError(): BackendError {\n return new BackendError('network', { code: 'aborted', message: 'Upload aborted' });\n}\n\nfunction checkConstraints(options: UploadOptions): void {\n const c = options.constraints;\n if (!c) return;\n if (c.maxSize !== undefined && options.file.size > c.maxSize) {\n const message = `File size ${options.file.size} exceeds max ${c.maxSize} bytes`;\n throw new BackendError('validation', {\n code: 'file_too_large',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n // Empty allowedTypes = no type check (iOS contract). Validate the EFFECTIVE\n // type: a contentType override wins over file.type (it is what gets sent).\n const effType = options.contentType ?? options.file.type;\n if (c.allowedTypes && c.allowedTypes.length > 0 && !c.allowedTypes.includes(effType)) {\n const message = `File type '${effType}' is not allowed`;\n throw new BackendError('validation', {\n code: 'file_type_not_allowed',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n}\n\nasync function buildHeaders(\n rt: PalbeRuntime,\n extra?: Record<string, string>,\n): Promise<Record<string, string>> {\n // Pre-flight refresh: mirrors the pattern in request.ts / HttpClient.\n if (\n rt.tokenManager.isExpired() &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (e) {\n const pe = asPalbaseError(e);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n rt.tokenManager.clearSession(); // terminal — proceed unauthenticated\n } else {\n throw pe ? fromPalbaseError(pe) : e;\n }\n }\n }\n\n // Uploads bypass @palbase/core to stream multipart bodies. Kong derives the\n // Environment identity from the API key, matching the iOS upload path.\n const headers: Record<string, string> = {\n apikey: rt.config.apiKey,\n 'X-Client-Info': `palbe-web/${VERSION}`,\n ...rt.config.headers,\n };\n const token = rt.tokenManager.getAccessToken();\n if (token) headers.Authorization = `Bearer ${token}`;\n // Case-insensitive scan (mirrors request.ts): a caller-supplied\n // `idempotency-key` in any casing must suppress generation.\n const callerHasKey = Object.keys(extra ?? {}).some((k) => k.toLowerCase() === 'idempotency-key');\n if (!callerHasKey) headers['Idempotency-Key'] = crypto.randomUUID();\n // extra spreads LAST so a caller-pinned Authorization wins (HttpClient/iOS precedence).\n const merged = { ...headers, ...extra };\n applyBrowserOriginHeader(merged);\n return merged;\n}\n\nfunction buildForm(options: UploadOptions): FormData {\n const form = new FormData();\n for (const [k, v] of Object.entries(options.fields ?? {})) form.append(k, v);\n const file =\n options.contentType && options.file.type !== options.contentType\n ? new Blob([options.file], { type: options.contentType })\n : options.file;\n const filename =\n options.filename ??\n (typeof File !== 'undefined' && options.file instanceof File ? options.file.name : 'file');\n form.append('file', file, filename);\n return form;\n}\n\nfunction decode<O>(status: number, text: string): O {\n let body: unknown;\n try {\n body = text === '' ? null : JSON.parse(text);\n } catch {\n if (status >= 200 && status < 300) {\n throw new BackendError('decode', {\n code: 'decode_error',\n message: 'Invalid JSON in response',\n status,\n });\n }\n body = text;\n }\n if (status < 200 || status >= 300) throw fromEnvelope(status, body);\n return body as O;\n}\n\nfunction uploadViaXHR<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n return new Promise<O>((resolve, reject) => {\n // Pre-aborted signal: never send. (xhr.abort() before send() fires no\n // abort event per spec, so routing this through onabort would hang.)\n if (options.signal?.aborted) {\n reject(abortError());\n return;\n }\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);\n xhr.upload.onprogress = (e: { lengthComputable: boolean; loaded: number; total: number }) => {\n if (!e.lengthComputable) return;\n options.onProgress?.({ sent: e.loaded, total: e.total });\n };\n const onAbort = () => xhr.abort();\n const cleanup = () => options.signal?.removeEventListener('abort', onAbort);\n xhr.onload = () => {\n cleanup();\n try {\n resolve(decode<O>(xhr.status, xhr.responseText));\n } catch (err) {\n reject(err);\n }\n };\n xhr.onerror = () => {\n cleanup();\n reject(new BackendError('network', { code: 'network_error', message: 'Upload failed' }));\n };\n xhr.onabort = () => {\n cleanup();\n reject(abortError());\n };\n options.signal?.addEventListener('abort', onAbort, { once: true });\n xhr.send(form);\n });\n}\n\nasync function uploadViaFetch<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n let res: Response;\n try {\n res = await fetch(url, { method: 'POST', body: form, headers, signal: options.signal });\n } catch (e) {\n // DOMException extends Error in modern runtimes — name check covers both.\n if (e instanceof Error && e.name === 'AbortError') throw abortError();\n throw new BackendError('network', {\n code: 'network_error',\n message: e instanceof Error ? e.message : 'Upload failed',\n });\n }\n return decode<O>(res.status, await res.text());\n}\n\n/** Runtime resolution deferred behind `resolveRt` — see call.ts for the pattern. */\nexport async function uploadEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n options: UploadOptions,\n): Promise<O> {\n const rt = resolveRt();\n checkConstraints(options); // pre-send guard — rejects before any network I/O\n const path = name.startsWith('/') ? name : `/${name}`;\n const url = `${rt.config.url}${path}`;\n const headers = await buildHeaders(rt, options.headers);\n const form = buildForm(options);\n const useXHR = options.onProgress !== undefined && typeof XMLHttpRequest !== 'undefined';\n return useXHR\n ? uploadViaXHR<O>(url, form, headers, options)\n : uploadViaFetch<O>(url, form, headers, options);\n}\n","import type { PalbeAnalytics } from './analytics-facade.js';\nimport type { PalbeAuth } from './auth-facade.js';\nimport { callEndpoint } from './call.js';\nimport type { PalbeCalls } from './calls/facade.js';\nimport type { PalbeFlags } from './flags-facade.js';\nimport { getRuntime } from './internal.js';\nimport type { PalbeMessaging } from './messaging/facade.js';\nimport { boundNamespaceAccessor, getNamespace, getRegistry } from './namespaces.js';\nimport type { PalbePerf } from './perf/perf-facade.js';\nimport type { PalbeRealtime } from './realtime/facade.js';\nimport type { CallOptions } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport type { UploadOptions } from './upload.js';\nimport { uploadEndpoint } from './upload.js';\n\n/**\n * The Palbe surface. Codegen augments this interface with typed endpoint\n * namespaces: declare module '@palbase/web' { interface PB { rooms: ... } }\n */\nexport interface PB {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O>;\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O>;\n readonly auth: PalbeAuth;\n readonly flags: PalbeFlags;\n readonly realtime: PalbeRealtime;\n readonly analytics: PalbeAnalytics;\n readonly calls: PalbeCalls;\n readonly messaging: PalbeMessaging;\n /** The PalPerf surface: custom traces (`startTrace`) + the buffered flush of\n * network/app-start/custom items. `pb.perf.record(...)` is also how the\n * runtime feeds the cold-start measurement in. */\n readonly perf: PalbePerf;\n /** Mark (or unmark) this client's traffic as test — flips the facade's flag\n * so every subsequent perf flush carries `X-Palbase-Test-Device: 1` (the\n * SAME header the iOS SDK sends; the server reads it → `is_test_traffic`). */\n setTestDevice(on: boolean): void;\n}\n\n/**\n * One proxy construction for BOTH client flavors:\n * - global `pb`: resolveRt = getRuntime (late-bound — a __configure swap is\n * visible to every subsequent call), nsAccessor = the globally-cached path;\n * - bound clients: resolveRt = () => rt (captured once, immune to global\n * swaps), nsAccessor = a per-client cache (see namespaces.ts).\n */\nfunction createClientProxy(\n resolveRt: () => PalbeRuntime,\n nsAccessor: (name: string) => unknown,\n): PB {\n const base = {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O> {\n return callEndpoint<O>(resolveRt, name, input, options);\n },\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O> {\n return uploadEndpoint<O>(resolveRt, name, options);\n },\n get auth(): PalbeAuth {\n return resolveRt().auth;\n },\n get flags(): PalbeFlags {\n return resolveRt().flags;\n },\n get realtime(): PalbeRealtime {\n return resolveRt().realtime;\n },\n get analytics(): PalbeAnalytics {\n return resolveRt().analytics;\n },\n get calls(): PalbeCalls {\n return resolveRt().calls;\n },\n get messaging(): PalbeMessaging {\n return resolveRt().messaging;\n },\n get perf(): PalbePerf {\n return resolveRt().perf;\n },\n setTestDevice(on: boolean): void {\n resolveRt().perf.setTestDevice(on);\n },\n };\n return new Proxy(base, {\n get(target, prop, receiver) {\n if (prop in target) return Reflect.get(target, prop, receiver);\n // `then` must NEVER resolve to a namespace: a thenable pb would make\n // `await pb` (or Promise.resolve(pb)) hang on the proxy. Registration\n // also rejects it (see namespaces.ts) — this is defense in depth.\n if (prop === 'then') return undefined;\n if (typeof prop === 'string') {\n const ns = nsAccessor(prop);\n if (ns !== undefined) return ns;\n }\n return undefined;\n },\n has(target, prop) {\n if (prop in target) return true;\n return typeof prop === 'string' && prop !== 'then' && getRegistry()[prop] !== undefined;\n },\n }) as PB;\n}\n\nexport const pb: PB = createClientProxy(getRuntime, getNamespace);\n\n/**\n * A `PB` client permanently bound to ONE runtime — typed namespaces, `call`,\n * `upload` and `auth` all resolve `rt`, NEVER the global slot. This is the\n * per-request isolation primitive for server adapters (palbe/next pbServer):\n * two bound clients serving concurrent requests cannot leak each other's\n * sessions, and global `__configure` swaps don't touch them.\n */\nexport function createBoundClient(rt: PalbeRuntime): PB {\n return createClientProxy(() => rt, boundNamespaceAccessor(rt));\n}\n","import type { PalbeConfig } from './config.js';\nimport { BackendError } from './errors.js';\nimport { __registerNamespaces, resetNamespaces } from './namespaces.js';\nimport { buildRuntime, type PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\n// Runtime lives in a process-global slot (see state.ts) so every build copy\n// (ESM/CJS, palbe + palbe/internal entries) converges on one singleton.\n// This is the BROWSER path (one runtime per tab). Server-side per-request\n// runtimes arrive with palbe/next (P3); do not sign in from shared server\n// code against this singleton.\n\n/**\n * Called by the generated palbe.gen.ts on import. REPLACES the runtime\n * (re-runs storage hydration) — safe to call repeatedly, e.g. watch-mode\n * regen. The namespace registry intentionally survives re-configure: the\n * gen file re-registers its static descriptors right after.\n */\nexport function __configure(config: PalbeConfig): void {\n const state = palbeState();\n // Destroy the old realtime facade if it was constructed — closes the socket\n // and clears heartbeat timers so they do not outlive the replaced runtime.\n state.runtime?.destroyRealtime();\n state.runtime = buildRuntime(config);\n // Fire after the runtime is in place so listeners that read pb.* immediately\n // (e.g. useSyncExternalStore getSnapshot) see the new runtime.\n for (const cb of state.configuredListeners) {\n try {\n cb();\n } catch {\n // consumer errors must not abort delivery to other listeners\n }\n }\n}\n\n/**\n * Register a callback that fires synchronously after every successful\n * `__configure` call (including re-configure / watch-mode regen).\n *\n * Returns an unsubscribe function. Safe to call before the first `__configure`.\n *\n * @internal — consumed by `palbe/react`; not part of the public `palbe` surface.\n */\nexport function onConfigured(cb: () => void): () => void {\n const state = palbeState();\n state.configuredListeners.add(cb);\n return () => state.configuredListeners.delete(cb);\n}\n\n/** @internal */\nexport function getRuntime(): PalbeRuntime {\n const rt = palbeState().runtime;\n if (!rt) throw BackendError.notConfigured();\n return rt;\n}\n\n/** Test-only: drop runtime + registered namespaces. */\nexport function __reset(): void {\n const state = palbeState();\n state.runtime?.destroyRealtime();\n state.runtime = null;\n state.configuredListeners.clear();\n resetNamespaces();\n}\n\nexport type { EndpointDescriptor, NamespaceTree } from './namespaces.js';\nexport type { PB } from './pb.js';\nexport { createBoundClient } from './pb.js';\nexport type { PalbeConfig, PalbeRuntime };\n// Server-adapter seams (palbe/next): per-request runtime + bound client.\nexport { __registerNamespaces, buildRuntime };\n"],"mappings":"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","names":[]}
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  onConfigured,
4
4
  pb
5
- } from "../chunk-XF7NM6HF.js";
6
- import "../chunk-43BGZCNU.js";
5
+ } from "../chunk-2VURERR2.js";
6
+ import "../chunk-6DGCUHSO.js";
7
7
  import "../chunk-CFDU23TB.js";
8
8
  import "../chunk-PZ5AY32C.js";
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palbase/web",
3
- "version": "7.3.3",
3
+ "version": "7.3.5",
4
4
  "description": "Palbe — the Palbase client SDK for web. One entry point: pb.",
5
5
  "license": "MIT",
6
6
  "private": false,