@palbase/web 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics-facade-CAKBIH_U.d.cts +1302 -0
- package/dist/analytics-facade-DLH-KivI.d.ts +1302 -0
- package/dist/chunk-XVLR3HGD.js +6843 -0
- package/dist/chunk-XVLR3HGD.js.map +1 -0
- package/dist/index.cjs +3617 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -7
- package/dist/index.d.ts +208 -7
- package/dist/index.js +7 -1
- package/dist/internal.cjs +3613 -167
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +5 -6
- package/dist/internal.d.ts +5 -6
- package/dist/internal.js +1 -1
- package/dist/next/client.cjs +3611 -177
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +1 -1
- package/dist/next/index.cjs +3616 -170
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +4 -11
- package/dist/next/index.d.ts +4 -11
- package/dist/next/index.js +14 -5
- package/dist/next/index.js.map +1 -1
- package/dist/{pb-Cudze7Kb.d.cts → pb-DioxNuEV.d.cts} +3 -1
- package/dist/{pb-BmgkAe97.d.ts → pb-HegMSSk-.d.ts} +3 -1
- package/dist/pkg/palbe_mls_bg.wasm +0 -0
- package/dist/react/index.cjs +97 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +30 -6
- package/dist/react/index.d.ts +30 -6
- package/dist/react/index.js +86 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +8 -7
- package/dist/analytics-facade-DkOwkEpi.d.ts +0 -454
- package/dist/analytics-facade-t6UrFdn7.d.cts +0 -454
- package/dist/chunk-JVT65V4E.js +0 -3384
- package/dist/chunk-JVT65V4E.js.map +0 -1
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/request.ts","../../src/state.ts","../../src/namespaces.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 { 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","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 (all traffic lands on one host and\n * Kong stamps `X-Project-Ref` from the key-auth consumer) is the only\n * supported production path — subdomain-per-tenant routing was\n * considered but retired in favour of a single gateway.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse project ref from a palbase API key.\n *\n * Canonical shape: `pb_{ref}_{scope}{random}` where `ref` is exactly\n * 8 base62 chars and `scope` is `c` (publishable client key). See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the 8-char ref on match; `null` otherwise — callers surface\n * that as an `invalid_api_key` error.\n */\nconst REF_LEN = 8;\nconst BASE62_RE = /^[0-9A-Za-z]+$/;\n\nfunction parseProjectRef(apiKey: string): string | null {\n // pb_ (3) + ref (8) + _ (1) + scope (1) = 13 min chars\n if (apiKey.length < 13) return null;\n if (!apiKey.startsWith('pb_')) return null;\n if (apiKey[11] !== '_') return null;\n const ref = apiKey.slice(3, 11);\n if (ref.length !== REF_LEN) return null;\n if (!BASE62_RE.test(ref)) return null;\n const scope = apiKey[12];\n if (scope !== 'c') return null;\n return ref;\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: tagging admin calls with `x-palbase-project: <ref>` so the\n * gateway can route them to the correct project's data plane.\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 && parseProjectRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{ref}_{scope}{random}. 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 project 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 // Defense-in-depth X-Project-Ref. Kong's pre-function plugin\n // always overrides this with the consumer's `custom_id` (see\n // docs/MODULE_HEADER_CONTRACT.md §\"pre-function\") so any\n // client-sent value is discarded before it reaches the\n // upstream module — but emitting one here keeps non-Kong\n // test rigs and apex-routed admin paths correct.\n const ref = parseProjectRef(effectiveKey);\n if (ref) {\n headers['X-Project-Ref'] = ref;\n }\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","import { asPalbaseError, fromPalbaseError, unwrap } from './errors.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\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 let res = await attempt();\n\n // Reactive 401: refresh once, retry once with the same Idempotency-Key.\n if (\n res.error?.status === 401 &&\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 throw fromPalbaseError(res.error);\n }\n // Transient (network/5xx): keep the session, surface the refresh failure.\n throw pe ? fromPalbaseError(pe) : refreshErr;\n }\n res = await attempt();\n }\n\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// Fixed surface names on pb (call/upload/auth/flags/realtime/analytics) — cannot be shadowed at the top level.\nconst FIXED_SURFACE = new Set(['call', 'upload', 'auth', 'flags', 'realtime', 'analytics']);\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 (FIXED_SURFACE.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","// AUTO-GENERATED from package.json — do not edit\nexport const VERSION = '1.0.0';\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 { 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 // X-Project-Ref is deliberately NOT set here: uploads bypass @palbase/core's\n // HttpClient (which stamps it from the apikey) to stream multipart bodies, and\n // Kong already derives endpoint_ref from the apikey/host on every request, so\n // the header is redundant on the wire. This matches the iOS SDK, whose upload\n // path likewise omits it.\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 return { ...headers, ...extra };\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 { PalbeFlags } from './flags-facade.js';\nimport { getRuntime } from './internal.js';\nimport { boundNamespaceAccessor, getNamespace, getRegistry } from './namespaces.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}\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 };\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/request.ts","../../src/state.ts","../../src/namespaces.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 (all traffic lands on one host and\n * Kong stamps `X-Project-Ref` from the key-auth consumer) is the only\n * supported production path — subdomain-per-tenant routing was\n * considered but retired in favour of a single gateway.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse project ref from a palbase API key.\n *\n * Canonical shape: `pb_{ref}_{scope}{random}` where `ref` is exactly\n * 8 base62 chars and `scope` is `c` (publishable client key). See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the 8-char ref on match; `null` otherwise — callers surface\n * that as an `invalid_api_key` error.\n */\nconst REF_LEN = 8;\nconst BASE62_RE = /^[0-9A-Za-z]+$/;\n\nfunction parseProjectRef(apiKey: string): string | null {\n // pb_ (3) + ref (8) + _ (1) + scope (1) = 13 min chars\n if (apiKey.length < 13) return null;\n if (!apiKey.startsWith('pb_')) return null;\n if (apiKey[11] !== '_') return null;\n const ref = apiKey.slice(3, 11);\n if (ref.length !== REF_LEN) return null;\n if (!BASE62_RE.test(ref)) return null;\n const scope = apiKey[12];\n if (scope !== 'c') return null;\n return ref;\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: tagging admin calls with `x-palbase-project: <ref>` so the\n * gateway can route them to the correct project's data plane.\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 && parseProjectRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{ref}_{scope}{random}. 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 project 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 // Defense-in-depth X-Project-Ref. Kong's pre-function plugin\n // always overrides this with the consumer's `custom_id` (see\n // docs/MODULE_HEADER_CONTRACT.md §\"pre-function\") so any\n // client-sent value is discarded before it reaches the\n // upstream module — but emitting one here keeps non-Kong\n // test rigs and apex-routed admin paths correct.\n const ref = parseProjectRef(effectiveKey);\n if (ref) {\n headers['X-Project-Ref'] = ref;\n }\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","import { asPalbaseError, fromPalbaseError, unwrap } from './errors.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\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 let res = await attempt();\n\n // Reactive 401: refresh once, retry once with the same Idempotency-Key.\n if (\n res.error?.status === 401 &&\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 throw fromPalbaseError(res.error);\n }\n // Transient (network/5xx): keep the session, surface the refresh failure.\n throw pe ? fromPalbaseError(pe) : refreshErr;\n }\n res = await attempt();\n }\n\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// Fixed surface names on pb — cannot be shadowed at the top level by codegen namespaces.\nconst FIXED_SURFACE = new Set([\n 'call',\n 'upload',\n 'auth',\n 'flags',\n 'realtime',\n 'analytics',\n 'calls',\n 'messaging',\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 (FIXED_SURFACE.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","// AUTO-GENERATED from package.json — do not edit\nexport const VERSION = '1.1.0';\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 { 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 // X-Project-Ref is deliberately NOT set here: uploads bypass @palbase/core's\n // HttpClient (which stamps it from the apikey) to stream multipart bodies, and\n // Kong already derives endpoint_ref from the apikey/host on every request, so\n // the header is redundant on the wire. This matches the iOS SDK, whose upload\n // path likewise omits it.\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 return { ...headers, ...extra };\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 { 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}\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 };\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":[]}
|
package/dist/react/index.d.cts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import '
|
|
4
|
-
import '@palbase/auth';
|
|
1
|
+
import { B as RealtimeConnectionState, d as AuthUser, E as RealtimePayload, h as Chat, l as ChatMember, F as FlagsView, m as ChatMessage } from '../analytics-facade-CAKBIH_U.cjs';
|
|
2
|
+
import { F } from './pooled-flags-Bwq4usn0.js';
|
|
3
|
+
import 'livekit-client';
|
|
5
4
|
import '../storage-BPaeSG8K.cjs';
|
|
6
5
|
|
|
7
6
|
/** The session slice `useSession` exposes. */
|
|
@@ -48,7 +47,7 @@ declare function useSession(): SessionState;
|
|
|
48
47
|
*
|
|
49
48
|
* Self-heals when `__configure` runs after mount (same as `useUser`).
|
|
50
49
|
*/
|
|
51
|
-
declare function useFlag<T extends
|
|
50
|
+
declare function useFlag<T extends F>(key: string, fallback: T): T;
|
|
52
51
|
/**
|
|
53
52
|
* Subscribe to the whole flag set. Re-renders on any change. `pb.flags.all()`
|
|
54
53
|
* returns a frozen, identity-stable view (a NEW frozen object only when the set
|
|
@@ -82,5 +81,30 @@ type ChannelStatus = RealtimeConnectionState | 'unavailable';
|
|
|
82
81
|
declare function useChannel(name: string, event: string, handler: (payload: RealtimePayload) => void): {
|
|
83
82
|
status: ChannelStatus;
|
|
84
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* The observable chat list (DMs + groups, active only). Re-renders when a chat
|
|
86
|
+
* is added (you start one / a peer DMs you and the Welcome drains), removed, or
|
|
87
|
+
* the list hydrates from the durable catalog on launch.
|
|
88
|
+
*
|
|
89
|
+
* Self-heals when `__configure` runs after mount (same as `useUser`).
|
|
90
|
+
*/
|
|
91
|
+
declare function useChats(): readonly Chat[];
|
|
92
|
+
/**
|
|
93
|
+
* Bind to one `Chat`'s changes. Re-renders whenever the chat's observable state
|
|
94
|
+
* changes (a new message, members refresh, typing, materialize draft→active).
|
|
95
|
+
* Returns the SAME `chat` instance for ergonomic access to `chat.messages`,
|
|
96
|
+
* `chat.send(...)`, etc.
|
|
97
|
+
*/
|
|
98
|
+
declare function useChat(chat: Chat): Chat;
|
|
99
|
+
/**
|
|
100
|
+
* The ordered message transcript for a chat (newest last). Re-renders on a new
|
|
101
|
+
* message / history page / own-send echo. A cached snapshot keeps the array
|
|
102
|
+
* reference stable until the list actually changes.
|
|
103
|
+
*/
|
|
104
|
+
declare function useMessages(chat: Chat): readonly ChatMessage[];
|
|
105
|
+
/** The members of a chat (users). Re-renders when the roster changes. */
|
|
106
|
+
declare function useChatMembers(chat: Chat): readonly ChatMember[];
|
|
107
|
+
/** The users currently typing in a chat. Re-renders on typing start/stop. */
|
|
108
|
+
declare function useTyping(chat: Chat): readonly ChatMember[];
|
|
85
109
|
|
|
86
|
-
export { type ChannelStatus, type SessionState, useChannel, useFlag, useFlags, useSession, useUser };
|
|
110
|
+
export { type ChannelStatus, type SessionState, useChannel, useChat, useChatMembers, useChats, useFlag, useFlags, useMessages, useSession, useTyping, useUser };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import '
|
|
4
|
-
import '@palbase/auth';
|
|
1
|
+
import { B as RealtimeConnectionState, d as AuthUser, E as RealtimePayload, h as Chat, l as ChatMember, F as FlagsView, m as ChatMessage } from '../analytics-facade-DLH-KivI.js';
|
|
2
|
+
import { F } from './pooled-flags-Bwq4usn0.js';
|
|
3
|
+
import 'livekit-client';
|
|
5
4
|
import '../storage-BPaeSG8K.js';
|
|
6
5
|
|
|
7
6
|
/** The session slice `useSession` exposes. */
|
|
@@ -48,7 +47,7 @@ declare function useSession(): SessionState;
|
|
|
48
47
|
*
|
|
49
48
|
* Self-heals when `__configure` runs after mount (same as `useUser`).
|
|
50
49
|
*/
|
|
51
|
-
declare function useFlag<T extends
|
|
50
|
+
declare function useFlag<T extends F>(key: string, fallback: T): T;
|
|
52
51
|
/**
|
|
53
52
|
* Subscribe to the whole flag set. Re-renders on any change. `pb.flags.all()`
|
|
54
53
|
* returns a frozen, identity-stable view (a NEW frozen object only when the set
|
|
@@ -82,5 +81,30 @@ type ChannelStatus = RealtimeConnectionState | 'unavailable';
|
|
|
82
81
|
declare function useChannel(name: string, event: string, handler: (payload: RealtimePayload) => void): {
|
|
83
82
|
status: ChannelStatus;
|
|
84
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* The observable chat list (DMs + groups, active only). Re-renders when a chat
|
|
86
|
+
* is added (you start one / a peer DMs you and the Welcome drains), removed, or
|
|
87
|
+
* the list hydrates from the durable catalog on launch.
|
|
88
|
+
*
|
|
89
|
+
* Self-heals when `__configure` runs after mount (same as `useUser`).
|
|
90
|
+
*/
|
|
91
|
+
declare function useChats(): readonly Chat[];
|
|
92
|
+
/**
|
|
93
|
+
* Bind to one `Chat`'s changes. Re-renders whenever the chat's observable state
|
|
94
|
+
* changes (a new message, members refresh, typing, materialize draft→active).
|
|
95
|
+
* Returns the SAME `chat` instance for ergonomic access to `chat.messages`,
|
|
96
|
+
* `chat.send(...)`, etc.
|
|
97
|
+
*/
|
|
98
|
+
declare function useChat(chat: Chat): Chat;
|
|
99
|
+
/**
|
|
100
|
+
* The ordered message transcript for a chat (newest last). Re-renders on a new
|
|
101
|
+
* message / history page / own-send echo. A cached snapshot keeps the array
|
|
102
|
+
* reference stable until the list actually changes.
|
|
103
|
+
*/
|
|
104
|
+
declare function useMessages(chat: Chat): readonly ChatMessage[];
|
|
105
|
+
/** The members of a chat (users). Re-renders when the roster changes. */
|
|
106
|
+
declare function useChatMembers(chat: Chat): readonly ChatMember[];
|
|
107
|
+
/** The users currently typing in a chat. Re-renders on typing start/stop. */
|
|
108
|
+
declare function useTyping(chat: Chat): readonly ChatMember[];
|
|
85
109
|
|
|
86
|
-
export { type ChannelStatus, type SessionState, useChannel, useFlag, useFlags, useSession, useUser };
|
|
110
|
+
export { type ChannelStatus, type SessionState, useChannel, useChat, useChatMembers, useChats, useFlag, useFlags, useMessages, useSession, useTyping, useUser };
|
package/dist/react/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
onConfigured,
|
|
4
4
|
pb
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-XVLR3HGD.js";
|
|
6
6
|
|
|
7
7
|
// src/react/index.tsx
|
|
8
8
|
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
@@ -146,11 +146,96 @@ function useChannel(name, event, handler) {
|
|
|
146
146
|
}, [name, event, configEpoch]);
|
|
147
147
|
return { status };
|
|
148
148
|
}
|
|
149
|
+
var EMPTY_CHATS = Object.freeze([]);
|
|
150
|
+
var EMPTY_MESSAGES = Object.freeze([]);
|
|
151
|
+
var EMPTY_MEMBERS = Object.freeze([]);
|
|
152
|
+
var EMPTY_TYPING = Object.freeze([]);
|
|
153
|
+
function useChats() {
|
|
154
|
+
const lastRef = useRef(EMPTY_CHATS);
|
|
155
|
+
const getSnapshot = useCallback(() => {
|
|
156
|
+
try {
|
|
157
|
+
const next = pb.messaging.chats;
|
|
158
|
+
if (next.length !== lastRef.current.length || next.some((c, i) => c !== lastRef.current[i])) {
|
|
159
|
+
lastRef.current = next.slice();
|
|
160
|
+
}
|
|
161
|
+
return lastRef.current;
|
|
162
|
+
} catch {
|
|
163
|
+
return EMPTY_CHATS;
|
|
164
|
+
}
|
|
165
|
+
}, []);
|
|
166
|
+
return useSyncExternalStore(subscribeChatsWithConfig, getSnapshot, getEmptyChats);
|
|
167
|
+
}
|
|
168
|
+
var subscribeChatsWithConfig = subscribeWithConfig(
|
|
169
|
+
(onStoreChange) => pb.messaging.onChatsChange(onStoreChange)
|
|
170
|
+
);
|
|
171
|
+
function getEmptyChats() {
|
|
172
|
+
return EMPTY_CHATS;
|
|
173
|
+
}
|
|
174
|
+
function useChat(chat) {
|
|
175
|
+
const subscribe = useCallback(
|
|
176
|
+
(onStoreChange) => chat.onChange(onStoreChange),
|
|
177
|
+
[chat]
|
|
178
|
+
);
|
|
179
|
+
const getSnapshot = useCallback(() => chat, [chat]);
|
|
180
|
+
const [, force] = useState(0);
|
|
181
|
+
useEffect(() => chat.onChange(() => force((n) => n + 1)), [chat]);
|
|
182
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
183
|
+
}
|
|
184
|
+
function useMessages(chat) {
|
|
185
|
+
const lastRef = useRef(EMPTY_MESSAGES);
|
|
186
|
+
const subscribe = useCallback(
|
|
187
|
+
(onStoreChange) => chat.onChange(onStoreChange),
|
|
188
|
+
[chat]
|
|
189
|
+
);
|
|
190
|
+
const getSnapshot = useCallback(() => {
|
|
191
|
+
const next = chat.messages;
|
|
192
|
+
if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {
|
|
193
|
+
lastRef.current = next.slice();
|
|
194
|
+
}
|
|
195
|
+
return lastRef.current;
|
|
196
|
+
}, [chat]);
|
|
197
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MESSAGES);
|
|
198
|
+
}
|
|
199
|
+
function useChatMembers(chat) {
|
|
200
|
+
const lastRef = useRef(EMPTY_MEMBERS);
|
|
201
|
+
const subscribe = useCallback(
|
|
202
|
+
(onStoreChange) => chat.onChange(onStoreChange),
|
|
203
|
+
[chat]
|
|
204
|
+
);
|
|
205
|
+
const getSnapshot = useCallback(() => {
|
|
206
|
+
const next = chat.members;
|
|
207
|
+
if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {
|
|
208
|
+
lastRef.current = next.slice();
|
|
209
|
+
}
|
|
210
|
+
return lastRef.current;
|
|
211
|
+
}, [chat]);
|
|
212
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_MEMBERS);
|
|
213
|
+
}
|
|
214
|
+
function useTyping(chat) {
|
|
215
|
+
const lastRef = useRef(EMPTY_TYPING);
|
|
216
|
+
const subscribe = useCallback(
|
|
217
|
+
(onStoreChange) => chat.onChange(onStoreChange),
|
|
218
|
+
[chat]
|
|
219
|
+
);
|
|
220
|
+
const getSnapshot = useCallback(() => {
|
|
221
|
+
const next = chat.typing;
|
|
222
|
+
if (next.length !== lastRef.current.length || next.some((m, i) => m !== lastRef.current[i])) {
|
|
223
|
+
lastRef.current = next.slice();
|
|
224
|
+
}
|
|
225
|
+
return lastRef.current;
|
|
226
|
+
}, [chat]);
|
|
227
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_TYPING);
|
|
228
|
+
}
|
|
149
229
|
export {
|
|
150
230
|
useChannel,
|
|
231
|
+
useChat,
|
|
232
|
+
useChatMembers,
|
|
233
|
+
useChats,
|
|
151
234
|
useFlag,
|
|
152
235
|
useFlags,
|
|
236
|
+
useMessages,
|
|
153
237
|
useSession,
|
|
238
|
+
useTyping,
|
|
154
239
|
useUser
|
|
155
240
|
};
|
|
156
241
|
//# sourceMappingURL=index.js.map
|