@palbase/web 4.0.2 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/chunk-BNAGRUIQ.js +381 -0
- package/dist/chunk-BNAGRUIQ.js.map +1 -0
- package/dist/chunk-OWLTZG2V.js +24 -0
- package/dist/chunk-OWLTZG2V.js.map +1 -0
- package/dist/{chunk-TW6YN354.js → chunk-QKFPPZLF.js} +13 -373
- package/dist/chunk-QKFPPZLF.js.map +1 -0
- package/dist/gen/cli.cjs +8 -5
- package/dist/gen/cli.cjs.map +1 -1
- package/dist/gen/cli.js +8 -5
- package/dist/gen/cli.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/internal.cjs +1 -1
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.js +2 -1
- package/dist/next/client.cjs +1 -1
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +2 -1
- package/dist/next/client.js.map +1 -1
- package/dist/next/index.cjs +4 -88
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +2 -62
- package/dist/next/index.d.ts +2 -62
- package/dist/next/index.js +8 -102
- package/dist/next/index.js.map +1 -1
- package/dist/next/middleware.cjs +263 -0
- package/dist/next/middleware.cjs.map +1 -0
- package/dist/next/middleware.d.cts +101 -0
- package/dist/next/middleware.d.ts +101 -0
- package/dist/next/middleware.js +107 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +11 -1
- package/dist/chunk-TW6YN354.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/next/middleware.ts"],"sourcesContent":["/**\n * '@palbase/web/next/middleware' — proactive cookie-session refresh in\n * Next.js middleware.\n *\n * REQUIRED — not optional — for session-bearing RSC apps. Server Components\n * cannot write cookies, so pbServer's reactive refresh cannot PERSIST a\n * rotated session there: every RSC render would re-refresh from the SAME\n * cookie refresh token, and once two of those land more than 30s apart\n * (outside palauth's rotation grace) the reuse detector REVOKES THE WHOLE\n * TOKEN FAMILY — the user is force-logged-out everywhere. This middleware\n * refreshes the cookie BEFORE the RSC tree renders, so pbServer hydrates a\n * still-valid session and never burns the token itself.\n *\n * EDGE-BUNDLE RULE — why this is a SEPARATE entry taking an EXPLICIT config\n * instead of reading the process-global one like pbServer does:\n *\n * Next compiles middleware into its OWN Edge bundle, so the generated\n * palbe.gen.ts imported by app/layout.tsx does not reach it. Importing\n * palbe.gen.ts from middleware.ts to fix that would drag `__configure` →\n * buildRuntime → messaging/wasm/loader.ts into the Edge graph. Measured on\n * Next 16.2.9 with a real app: the webpack builder FAILS the build outright\n * (\"Dynamic Code Evaluation ... not allowed in Edge Runtime\", caused by\n * importing '@palbase/web/dist/internal.js' — a compilation error, not a\n * warning), and Turbopack, which does not enforce that, instead ships a\n * 2.4 MB Edge bundle where this entry ships 168 KB.\n *\n * So the middleware never touches the runtime: it takes the two published\n * identity values it actually needs and speaks the one auth wire directly.\n * Nothing on this module's import graph may reach ../runtime.js —\n * __tests__/next-middleware-bundle.test.ts locks that against the real\n * build output.\n *\n * 'next/server' is imported only lazily (the type-only NextRequest/\n * NextResponse imports erase at build time).\n *\n * ```ts\n * // middleware.ts\n * import { palbeMiddleware } from '@palbase/web/next/middleware';\n * import type { NextRequest } from 'next/server';\n *\n * export function middleware(request: NextRequest) {\n * return palbeMiddleware(request, {\n * url: 'https://abc12345m.palbase.studio',\n * apiKey: 'pb_abc12345m_c...',\n * });\n * }\n *\n * // `config` MUST be declared here, in this file: Next reads it off THIS\n * // file's AST (export const + literal initializer). A re-exported or\n * // imported config is invisible and silently degrades to a catch-all.\n * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };\n * ```\n */\nimport type { NextRequest, NextResponse } from 'next/server';\nimport { environmentRefFromPublishableApiKey } from '../api-key.js';\nimport { BackendError } from '../errors.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookiesDecoded,\n SESSION_COOKIE_ATTRS,\n type StoredSession,\n} from './cookie-codec.js';\nimport { importNextServer } from './shared.js';\n\nconst DEFAULT_REFRESH_MARGIN_MS = 60_000;\n\n/**\n * The published identity palbeMiddleware needs — the same two values\n * `palbase web link` bakes into palbe.gen.ts. Both are publishable (the\n * anon key already ships in every browser bundle); a service_role key is\n * REJECTED, it has no business in a request-path bundle.\n *\n * A `PalbeConfig` is structurally assignable, so apps that already hold one\n * can pass it straight through.\n */\nexport interface PalbeMiddlewareConfig {\n /** Environment URL, e.g. `https://abc12345m.palbase.studio`. */\n url: string;\n /** Publishable (anon) API key: `pb_<environmentRef>_c<random>`. */\n apiKey: string;\n}\n\nexport interface PalbeMiddlewareOptions {\n /** Refresh when the access token expires within this window. Default 60s. */\n refreshMarginMs?: number;\n /**\n * Pre-built response to decorate (e.g. from chained middleware) instead of\n * building `NextResponse.next({ request })`. Pass-through cases return it\n * UNMODIFIED. Caveat: a pre-built response snapshotted the request headers\n * at ITS creation, so a rotated cookie reaches the browser (Set-Cookie)\n * but not the downstream RSC render of THIS request — prefer omitting this\n * and building your response after palbeMiddleware when possible.\n */\n response?: NextResponse;\n}\n\ntype RefreshOutcome =\n | { kind: 'rotated'; session: StoredSession }\n | { kind: 'terminal' } // 400/401/403 — refresh token dead, sign out\n | { kind: 'transient' }; // network / 5xx / malformed — keep the session\n\n/**\n * Per-process single-flight: concurrent middleware invocations holding the\n * SAME refresh token (parallel tabs / prefetches) share one refresh call —\n * a per-token thundering herd would race palauth rotation. Multi-pod\n * concurrency is covered by palauth's 30s rotation grace (concurrent\n * refreshes of one token all succeed); per-process dedup is all that's\n * needed here (Studio precedent — don't over-engineer).\n */\nconst inflightRefreshes = new Map<string, Promise<RefreshOutcome>>();\n\nfunction refreshSingleFlight(\n config: PalbeMiddlewareConfig,\n refreshToken: string,\n): Promise<RefreshOutcome> {\n const existing = inflightRefreshes.get(refreshToken);\n if (existing) return existing;\n const pending = refresh(config, refreshToken).finally(() => {\n inflightRefreshes.delete(refreshToken);\n });\n inflightRefreshes.set(refreshToken, pending);\n return pending;\n}\n\n/** Direct palauth refresh call (no runtime: middleware needs only this one wire). */\nasync function refresh(\n config: PalbeMiddlewareConfig,\n refreshToken: string,\n): Promise<RefreshOutcome> {\n try {\n const res = await fetch(`${config.url}/auth/token/refresh`, {\n method: 'POST',\n headers: { apikey: config.apiKey, 'content-type': 'application/json' },\n body: JSON.stringify({ refresh_token: refreshToken }),\n });\n if (res.status === 400 || res.status === 401 || res.status === 403) {\n return { kind: 'terminal' };\n }\n if (!res.ok) return { kind: 'transient' };\n const raw: unknown = await res.json();\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n if (\n typeof obj.access_token === 'string' &&\n typeof obj.refresh_token === 'string' &&\n typeof obj.expires_in === 'number'\n ) {\n return {\n kind: 'rotated',\n session: {\n accessToken: obj.access_token,\n refreshToken: obj.refresh_token,\n expiresAt: Date.now() + obj.expires_in * 1000,\n },\n };\n }\n }\n return { kind: 'transient' }; // malformed 200 — never destroy a session over it\n } catch {\n return { kind: 'transient' }; // network failure\n }\n}\n\n/**\n * Keep the session cookie fresh for the request. No session / still-fresh\n * session → untouched pass-through (no cache-control changes). Stale or\n * expired → single-flight refresh, then the rotated (or cleared, on a dead\n * token) cookies are written onto BOTH the request (for the downstream RSC\n * render via `NextResponse.next({ request })`) and the response (for the\n * browser), with `Cache-Control: private, no-store` on touched responses.\n *\n * Cookie writes go through Next's cookie APIs with the DECODED codec values:\n * Next percent-encodes on serialization, so the wire bytes come out exactly\n * as the shared codec's (see encodeSessionCookiesDecoded). NextRequest's\n * cookie store is NOT a CookieStoreLike (its set is the 2-arg/RequestCookie\n * form), hence the direct codec calls here instead of the pbServer adapter.\n */\nexport async function palbeMiddleware(\n request: NextRequest,\n config: PalbeMiddlewareConfig,\n opts?: PalbeMiddlewareOptions,\n): Promise<NextResponse> {\n // Fail LOUDLY on a bad key. A silently empty ref would name the cookie\n // `palbe-session-` — a name the browser SDK never writes — so every\n // request would look session-less and the middleware would degrade to a\n // no-op, which is exactly the token-family revocation this exists to stop.\n const ref = environmentRefFromPublishableApiKey(config.apiKey);\n if (ref === '') {\n throw new BackendError('validation', {\n code: 'validation_error',\n message:\n 'palbeMiddleware() needs the publishable API key from palbe.gen.ts (pb_<environmentRef>_c…). Re-run `palbase web link` and copy the generated values.',\n });\n }\n const { NextResponse } = await importNextServer('palbeMiddleware');\n const passThrough = (): NextResponse => opts?.response ?? NextResponse.next({ request });\n\n const session = decodeSessionCookies((name) => request.cookies.get(name)?.value, ref);\n if (!session) return passThrough();\n if (session.expiresAt - Date.now() > (opts?.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS)) {\n return passThrough();\n }\n\n const outcome = await refreshSingleFlight(config, session.refreshToken);\n if (outcome.kind === 'transient') return passThrough(); // keep the session over blips\n\n // Session-cookie names in the incoming jar — the browser's state mirror.\n const staleNames = clearedSessionCookieNames(ref, (name) => request.cookies.has(name));\n\n if (outcome.kind === 'terminal') {\n for (const name of staleNames) request.cookies.delete(name);\n const response = opts?.response ?? NextResponse.next({ request });\n for (const name of staleNames) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n response.headers.set('cache-control', 'private, no-store');\n return response;\n }\n\n const write = encodeSessionCookiesDecoded(ref, outcome.session);\n for (const name of staleNames) request.cookies.delete(name);\n for (const { name, value } of write.set) request.cookies.set(name, value);\n for (const name of write.clear) request.cookies.delete(name);\n // Build AFTER the request-cookie mutation: NextResponse.next({ request })\n // snapshots the request headers for the downstream RSC render.\n const response = opts?.response ?? NextResponse.next({ request });\n const written = new Set(write.set.map((c) => c.name));\n for (const { name, value } of write.set) {\n response.cookies.set(name, value, SESSION_COOKIE_ATTRS);\n }\n // Browser-side cleanup: stale names not part of the new run (a shrunken /\n // grown chunk layout) + the codec's one-past-the-end overflow guard.\n for (const name of new Set([...write.clear, ...staleNames])) {\n if (!written.has(name)) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n }\n response.headers.set('cache-control', 'private, no-store');\n return response;\n}\n"],"mappings":"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","names":["response"]}
|
package/dist/react/index.cjs
CHANGED
package/dist/react/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx","../../../core/src/config.ts","../../../core/src/errors.ts","../../../core/src/http.ts","../../../core/src/platform.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","import { PalbaseError } from './errors.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 };\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 ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = this.buildHeaders(options);\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 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 return this.executeWithRetry<T>(method, path, options, attempt + 1);\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 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","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","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';\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 const attempt = async () => {\n try {\n return await rt.http.request<T>(method, path, {\n body: spec.body,\n 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 } 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 = '4.0.2';\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/http.ts","../../../core/src/platform.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","import { PalbaseError } from './errors.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 };\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 ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = this.buildHeaders(options);\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 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 return this.executeWithRetry<T>(method, path, options, attempt + 1);\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 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","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","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';\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 const attempt = async () => {\n try {\n return await rt.http.request<T>(method, path, {\n body: spec.body,\n 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 } 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 = '5.0.1';\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":[]}
|