@palbase/web 7.3.1 → 7.3.3
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/{chunk-53TN7KYR.js → chunk-43BGZCNU.js} +16 -4
- package/dist/chunk-43BGZCNU.js.map +1 -0
- package/dist/{chunk-CBN3PWNL.js → chunk-IRPS637F.js} +2 -2
- package/dist/{chunk-RRHJV6YD.js → chunk-XF7NM6HF.js} +3 -3
- package/dist/{chunk-RRHJV6YD.js.map → chunk-XF7NM6HF.js.map} +1 -1
- package/dist/index.cjs +14 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/internal.cjs +16 -4
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/next/client.cjs +16 -4
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +2 -2
- package/dist/next/index.cjs +16 -4
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js +3 -3
- package/dist/next/proxy.js +2 -2
- package/dist/react/index.cjs +14 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-53TN7KYR.js.map +0 -1
- /package/dist/{chunk-CBN3PWNL.js.map → chunk-IRPS637F.js.map} +0 -0
package/dist/react/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../core/src/config.ts","../../core/src/errors.ts","../../core/src/pow.ts","../../core/src/platform.ts","../../core/src/http.ts","../../core/src/token.ts","../src/errors.ts"],"sourcesContent":["import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * `maxIterations` bounds the search so a difficulty nobody can pay fails as an\n * error rather than as a hang. The default carries the server's default (16)\n * with room to spare — the expected cost is 2^difficulty and the tail is long,\n * so the bound is generous rather than tight.\n */\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = 1 << 24,\n): Promise<Record<string, string>> {\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(challenge.prefix + nonce));\n if (leadingZeroBits(new Uint8Array(digest)) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment\n * ref is 4-24 lowercase ASCII alphanumeric characters and random is 20 base62\n * chars. See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\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, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, details?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', {\n ...base,\n retryAfter: pickNumber(err.details, 'retry_after'),\n });\n // Field-error array lives at the envelope's nested `details` key.\n const nested = pickField(err.details, 'details');\n if (err.status === 400 && isFieldErrorArray(nested))\n return new BackendError('validation', { ...base, fields: nested });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const details = pickField(body, 'details');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', {\n ...params,\n // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.\n retryAfter: pickNumber(body, 'retry_after') ?? pickNumber(details, 'retry_after'),\n });\n if (status === 400 && isFieldErrorArray(details))\n return new BackendError('validation', { ...params, fields: details });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|