@palbase/core 0.2.0 → 0.4.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/index.cjs +64 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +64 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -82,15 +82,51 @@ function parseProjectRef(apiKey) {
|
|
|
82
82
|
}
|
|
83
83
|
var MAX_RETRIES = 3;
|
|
84
84
|
var INITIAL_BACKOFF_MS = 200;
|
|
85
|
-
var HttpClient = class {
|
|
85
|
+
var HttpClient = class _HttpClient {
|
|
86
86
|
apiKey;
|
|
87
87
|
options;
|
|
88
88
|
tokenManager = null;
|
|
89
|
+
/**
|
|
90
|
+
* Admin JWT used for platform admin endpoints (/admin/*).
|
|
91
|
+
* When set, takes precedence over serviceRoleKey and tokenManager access
|
|
92
|
+
* token in the Authorization header.
|
|
93
|
+
*/
|
|
94
|
+
adminToken = null;
|
|
89
95
|
interceptors = [];
|
|
90
96
|
constructor(apiKey, options) {
|
|
91
97
|
this.apiKey = apiKey;
|
|
92
98
|
this.options = options;
|
|
93
99
|
}
|
|
100
|
+
/** Set (or clear) the admin JWT used on admin endpoints. */
|
|
101
|
+
setAdminToken(token) {
|
|
102
|
+
this.adminToken = token;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a scoped HttpClient that adds the given extra headers to every
|
|
106
|
+
* request. The returned client shares the admin token and token manager
|
|
107
|
+
* with the parent at runtime — later changes on the parent propagate to
|
|
108
|
+
* the scope and vice versa.
|
|
109
|
+
*
|
|
110
|
+
* Typical use: tagging admin calls with `x-palbase-project: <ref>` so the
|
|
111
|
+
* gateway can route them to the correct project's data plane.
|
|
112
|
+
*/
|
|
113
|
+
withHeaders(extra) {
|
|
114
|
+
const mergedHeaders = { ...this.options?.headers ?? {}, ...extra };
|
|
115
|
+
const parent = this;
|
|
116
|
+
const scoped = new _HttpClient(this.apiKey, {
|
|
117
|
+
...this.options,
|
|
118
|
+
headers: mergedHeaders
|
|
119
|
+
});
|
|
120
|
+
scoped.tokenManager = this.tokenManager;
|
|
121
|
+
Object.defineProperty(scoped, "adminToken", {
|
|
122
|
+
get: () => parent.adminToken,
|
|
123
|
+
set: (v) => {
|
|
124
|
+
parent.adminToken = v;
|
|
125
|
+
},
|
|
126
|
+
configurable: true
|
|
127
|
+
});
|
|
128
|
+
return scoped;
|
|
129
|
+
}
|
|
94
130
|
/** Add a request interceptor. Runs before every request. */
|
|
95
131
|
addInterceptor(interceptor) {
|
|
96
132
|
this.interceptors.push(interceptor);
|
|
@@ -117,9 +153,11 @@ var HttpClient = class {
|
|
|
117
153
|
}
|
|
118
154
|
buildHeaders(options) {
|
|
119
155
|
const headers = {
|
|
120
|
-
"apikey": this.apiKey,
|
|
121
156
|
"Content-Type": "application/json"
|
|
122
157
|
};
|
|
158
|
+
if (this.apiKey) {
|
|
159
|
+
headers["apikey"] = this.apiKey;
|
|
160
|
+
}
|
|
123
161
|
const token = this.tokenManager?.getAccessToken();
|
|
124
162
|
if (token) {
|
|
125
163
|
headers["Authorization"] = `Bearer ${token}`;
|
|
@@ -127,6 +165,9 @@ var HttpClient = class {
|
|
|
127
165
|
if (this.options?.serviceRoleKey) {
|
|
128
166
|
headers["Authorization"] = `Bearer ${this.options.serviceRoleKey}`;
|
|
129
167
|
}
|
|
168
|
+
if (this.adminToken) {
|
|
169
|
+
headers["Authorization"] = `Bearer ${this.adminToken}`;
|
|
170
|
+
}
|
|
130
171
|
if (this.options?.headers) {
|
|
131
172
|
Object.assign(headers, this.options.headers);
|
|
132
173
|
}
|
|
@@ -176,7 +217,7 @@ var HttpClient = class {
|
|
|
176
217
|
let data = null;
|
|
177
218
|
let errorBody;
|
|
178
219
|
const contentType = response.headers.get("Content-Type");
|
|
179
|
-
if (contentType?.includes("json")) {
|
|
220
|
+
if (method !== "HEAD" && contentType?.includes("json")) {
|
|
180
221
|
const body = await response.json();
|
|
181
222
|
if (response.ok) {
|
|
182
223
|
data = body;
|
|
@@ -196,7 +237,26 @@ var HttpClient = class {
|
|
|
196
237
|
status: response.status
|
|
197
238
|
};
|
|
198
239
|
}
|
|
199
|
-
|
|
240
|
+
const contentRange = response.headers.get("Content-Range");
|
|
241
|
+
let count;
|
|
242
|
+
if (contentRange) {
|
|
243
|
+
const slash = contentRange.lastIndexOf("/");
|
|
244
|
+
if (slash >= 0) {
|
|
245
|
+
const totalPart = contentRange.slice(slash + 1);
|
|
246
|
+
if (totalPart !== "*") {
|
|
247
|
+
const parsed = Number.parseInt(totalPart, 10);
|
|
248
|
+
if (!Number.isNaN(parsed)) {
|
|
249
|
+
count = parsed;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
data,
|
|
256
|
+
error: null,
|
|
257
|
+
status: response.status,
|
|
258
|
+
...count !== void 0 ? { count } : {}
|
|
259
|
+
};
|
|
200
260
|
}
|
|
201
261
|
delay(ms) {
|
|
202
262
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/errors.ts","../src/http.ts","../src/platform.ts","../src/token.ts"],"sourcesContent":["export { ConfigFetcher } from './config.js';\nexport { PalbaseError } from './errors.js';\nexport { HttpClient } from './http.js';\nexport type { RequestInterceptor } from './http.js';\nexport type { Platform } from './platform.js';\nexport { detectPlatform } from './platform.js';\nexport { TokenManager } from './token.js';\nexport type {\n AuthStateCallback,\n AuthStateEvent,\n HttpClientOptions,\n PalbaseConfig,\n PalbaseResponse,\n ProjectConfig,\n RequestOptions,\n Session,\n Unsubscribe,\n} from './types.js';\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\nconst PALBASE_DOMAIN = 'palbase.studio';\n\n/**\n * Parse project ref from an API key.\n * Format: pb_{ref}_{random}\n * Example: pb_abc12345_xxxxxxxxxxxxxxxxxxxxxxxx\n * Returns the ref segment or null if the key doesn't match.\n */\nfunction parseProjectRef(apiKey: string): string | null {\n const parts = apiKey.split('_');\n // Format: [\"pb\", ref, random]\n const ref = parts[1];\n if (parts.length >= 3 && parts[0] === 'pb' && ref) {\n return ref;\n }\n return null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport interface RequestInterceptor {\n (request: { headers: Record<string, string>; method: string; path: string }): void | Promise<void>;\n}\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | 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 /** 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 (this.tokenManager?.isExpired() && this.tokenManager.getRefreshToken() && this.tokenManager.refreshFunction) {\n await this.tokenManager.refreshSession();\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, testing)\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Resolve from API key (format: pb_{ref}_{random})\n const ref = parseProjectRef(this.apiKey);\n if (ref) {\n return `https://${ref}.${PALBASE_DOMAIN}`;\n }\n\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected: pb_{ref}_{random}. Provide an explicit url option for custom endpoints.',\n 0,\n );\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'apikey': this.apiKey,\n 'Content-Type': 'application/json',\n };\n\n // Add token if available\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // serviceRoleKey overrides token-based Authorization\n if (this.options?.serviceRoleKey) {\n headers['Authorization'] = `Bearer ${this.options.serviceRoleKey}`;\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 const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : parsed * 1000;\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 const contentType = response.headers.get('Content-Type');\n if (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 return { data, error: null, status: response.status };\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\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 this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/errors.ts","../src/http.ts","../src/platform.ts","../src/token.ts"],"sourcesContent":["export { ConfigFetcher } from './config.js';\nexport { PalbaseError } from './errors.js';\nexport { HttpClient } from './http.js';\nexport type { RequestInterceptor } from './http.js';\nexport type { Platform } from './platform.js';\nexport { detectPlatform } from './platform.js';\nexport { TokenManager } from './token.js';\nexport type {\n AuthStateCallback,\n AuthStateEvent,\n HttpClientOptions,\n PalbaseConfig,\n PalbaseResponse,\n ProjectConfig,\n RequestOptions,\n Session,\n Unsubscribe,\n} from './types.js';\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\nconst PALBASE_DOMAIN = 'palbase.studio';\n\n/**\n * Parse project ref from an API key.\n * Format: pb_{ref}_{random}\n * Example: pb_abc12345_xxxxxxxxxxxxxxxxxxxxxxxx\n * Returns the ref segment or null if the key doesn't match.\n */\nfunction parseProjectRef(apiKey: string): string | null {\n const parts = apiKey.split('_');\n // Format: [\"pb\", ref, random]\n const ref = parts[1];\n if (parts.length >= 3 && parts[0] === 'pb' && ref) {\n return ref;\n }\n return null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport interface RequestInterceptor {\n (request: { headers: Record<string, string>; method: string; path: string }): void | Promise<void>;\n}\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 serviceRoleKey and tokenManager access\n * token in the 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 const parent = this;\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: () => parent.adminToken,\n set: (v: string | null) => {\n parent.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 (this.tokenManager?.isExpired() && this.tokenManager.getRefreshToken() && this.tokenManager.refreshFunction) {\n await this.tokenManager.refreshSession();\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, testing)\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Resolve from API key (format: pb_{ref}_{random})\n const ref = parseProjectRef(this.apiKey);\n if (ref) {\n return `https://${ref}.${PALBASE_DOMAIN}`;\n }\n\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected: pb_{ref}_{random}. Provide an explicit url option for custom endpoints.',\n 0,\n );\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n // apikey header only when an API key is configured (platform admin flows\n // may omit it — e.g. setup/login before any project exists).\n if (this.apiKey) {\n headers['apikey'] = this.apiKey;\n }\n\n // Add token if available\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // serviceRoleKey overrides token-based Authorization\n if (this.options?.serviceRoleKey) {\n headers['Authorization'] = `Bearer ${this.options.serviceRoleKey}`;\n }\n\n // adminToken (platform admin JWT) takes precedence over everything else\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 const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : parsed * 1000;\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\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 this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\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"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -75,8 +75,26 @@ declare class HttpClient {
|
|
|
75
75
|
protected readonly apiKey: string;
|
|
76
76
|
protected readonly options?: HttpClientOptions;
|
|
77
77
|
tokenManager: TokenManager | null;
|
|
78
|
+
/**
|
|
79
|
+
* Admin JWT used for platform admin endpoints (/admin/*).
|
|
80
|
+
* When set, takes precedence over serviceRoleKey and tokenManager access
|
|
81
|
+
* token in the Authorization header.
|
|
82
|
+
*/
|
|
83
|
+
adminToken: string | null;
|
|
78
84
|
private readonly interceptors;
|
|
79
85
|
constructor(apiKey: string, options?: HttpClientOptions);
|
|
86
|
+
/** Set (or clear) the admin JWT used on admin endpoints. */
|
|
87
|
+
setAdminToken(token: string | null): void;
|
|
88
|
+
/**
|
|
89
|
+
* Create a scoped HttpClient that adds the given extra headers to every
|
|
90
|
+
* request. The returned client shares the admin token and token manager
|
|
91
|
+
* with the parent at runtime — later changes on the parent propagate to
|
|
92
|
+
* the scope and vice versa.
|
|
93
|
+
*
|
|
94
|
+
* Typical use: tagging admin calls with `x-palbase-project: <ref>` so the
|
|
95
|
+
* gateway can route them to the correct project's data plane.
|
|
96
|
+
*/
|
|
97
|
+
withHeaders(extra: Record<string, string>): HttpClient;
|
|
80
98
|
/** Add a request interceptor. Runs before every request. */
|
|
81
99
|
addInterceptor(interceptor: RequestInterceptor): void;
|
|
82
100
|
request<T>(method: string, path: string, options?: RequestOptions): Promise<PalbaseResponse<T>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -75,8 +75,26 @@ declare class HttpClient {
|
|
|
75
75
|
protected readonly apiKey: string;
|
|
76
76
|
protected readonly options?: HttpClientOptions;
|
|
77
77
|
tokenManager: TokenManager | null;
|
|
78
|
+
/**
|
|
79
|
+
* Admin JWT used for platform admin endpoints (/admin/*).
|
|
80
|
+
* When set, takes precedence over serviceRoleKey and tokenManager access
|
|
81
|
+
* token in the Authorization header.
|
|
82
|
+
*/
|
|
83
|
+
adminToken: string | null;
|
|
78
84
|
private readonly interceptors;
|
|
79
85
|
constructor(apiKey: string, options?: HttpClientOptions);
|
|
86
|
+
/** Set (or clear) the admin JWT used on admin endpoints. */
|
|
87
|
+
setAdminToken(token: string | null): void;
|
|
88
|
+
/**
|
|
89
|
+
* Create a scoped HttpClient that adds the given extra headers to every
|
|
90
|
+
* request. The returned client shares the admin token and token manager
|
|
91
|
+
* with the parent at runtime — later changes on the parent propagate to
|
|
92
|
+
* the scope and vice versa.
|
|
93
|
+
*
|
|
94
|
+
* Typical use: tagging admin calls with `x-palbase-project: <ref>` so the
|
|
95
|
+
* gateway can route them to the correct project's data plane.
|
|
96
|
+
*/
|
|
97
|
+
withHeaders(extra: Record<string, string>): HttpClient;
|
|
80
98
|
/** Add a request interceptor. Runs before every request. */
|
|
81
99
|
addInterceptor(interceptor: RequestInterceptor): void;
|
|
82
100
|
request<T>(method: string, path: string, options?: RequestOptions): Promise<PalbaseResponse<T>>;
|
package/dist/index.js
CHANGED
|
@@ -52,15 +52,51 @@ function parseProjectRef(apiKey) {
|
|
|
52
52
|
}
|
|
53
53
|
var MAX_RETRIES = 3;
|
|
54
54
|
var INITIAL_BACKOFF_MS = 200;
|
|
55
|
-
var HttpClient = class {
|
|
55
|
+
var HttpClient = class _HttpClient {
|
|
56
56
|
apiKey;
|
|
57
57
|
options;
|
|
58
58
|
tokenManager = null;
|
|
59
|
+
/**
|
|
60
|
+
* Admin JWT used for platform admin endpoints (/admin/*).
|
|
61
|
+
* When set, takes precedence over serviceRoleKey and tokenManager access
|
|
62
|
+
* token in the Authorization header.
|
|
63
|
+
*/
|
|
64
|
+
adminToken = null;
|
|
59
65
|
interceptors = [];
|
|
60
66
|
constructor(apiKey, options) {
|
|
61
67
|
this.apiKey = apiKey;
|
|
62
68
|
this.options = options;
|
|
63
69
|
}
|
|
70
|
+
/** Set (or clear) the admin JWT used on admin endpoints. */
|
|
71
|
+
setAdminToken(token) {
|
|
72
|
+
this.adminToken = token;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a scoped HttpClient that adds the given extra headers to every
|
|
76
|
+
* request. The returned client shares the admin token and token manager
|
|
77
|
+
* with the parent at runtime — later changes on the parent propagate to
|
|
78
|
+
* the scope and vice versa.
|
|
79
|
+
*
|
|
80
|
+
* Typical use: tagging admin calls with `x-palbase-project: <ref>` so the
|
|
81
|
+
* gateway can route them to the correct project's data plane.
|
|
82
|
+
*/
|
|
83
|
+
withHeaders(extra) {
|
|
84
|
+
const mergedHeaders = { ...this.options?.headers ?? {}, ...extra };
|
|
85
|
+
const parent = this;
|
|
86
|
+
const scoped = new _HttpClient(this.apiKey, {
|
|
87
|
+
...this.options,
|
|
88
|
+
headers: mergedHeaders
|
|
89
|
+
});
|
|
90
|
+
scoped.tokenManager = this.tokenManager;
|
|
91
|
+
Object.defineProperty(scoped, "adminToken", {
|
|
92
|
+
get: () => parent.adminToken,
|
|
93
|
+
set: (v) => {
|
|
94
|
+
parent.adminToken = v;
|
|
95
|
+
},
|
|
96
|
+
configurable: true
|
|
97
|
+
});
|
|
98
|
+
return scoped;
|
|
99
|
+
}
|
|
64
100
|
/** Add a request interceptor. Runs before every request. */
|
|
65
101
|
addInterceptor(interceptor) {
|
|
66
102
|
this.interceptors.push(interceptor);
|
|
@@ -87,9 +123,11 @@ var HttpClient = class {
|
|
|
87
123
|
}
|
|
88
124
|
buildHeaders(options) {
|
|
89
125
|
const headers = {
|
|
90
|
-
"apikey": this.apiKey,
|
|
91
126
|
"Content-Type": "application/json"
|
|
92
127
|
};
|
|
128
|
+
if (this.apiKey) {
|
|
129
|
+
headers["apikey"] = this.apiKey;
|
|
130
|
+
}
|
|
93
131
|
const token = this.tokenManager?.getAccessToken();
|
|
94
132
|
if (token) {
|
|
95
133
|
headers["Authorization"] = `Bearer ${token}`;
|
|
@@ -97,6 +135,9 @@ var HttpClient = class {
|
|
|
97
135
|
if (this.options?.serviceRoleKey) {
|
|
98
136
|
headers["Authorization"] = `Bearer ${this.options.serviceRoleKey}`;
|
|
99
137
|
}
|
|
138
|
+
if (this.adminToken) {
|
|
139
|
+
headers["Authorization"] = `Bearer ${this.adminToken}`;
|
|
140
|
+
}
|
|
100
141
|
if (this.options?.headers) {
|
|
101
142
|
Object.assign(headers, this.options.headers);
|
|
102
143
|
}
|
|
@@ -146,7 +187,7 @@ var HttpClient = class {
|
|
|
146
187
|
let data = null;
|
|
147
188
|
let errorBody;
|
|
148
189
|
const contentType = response.headers.get("Content-Type");
|
|
149
|
-
if (contentType?.includes("json")) {
|
|
190
|
+
if (method !== "HEAD" && contentType?.includes("json")) {
|
|
150
191
|
const body = await response.json();
|
|
151
192
|
if (response.ok) {
|
|
152
193
|
data = body;
|
|
@@ -166,7 +207,26 @@ var HttpClient = class {
|
|
|
166
207
|
status: response.status
|
|
167
208
|
};
|
|
168
209
|
}
|
|
169
|
-
|
|
210
|
+
const contentRange = response.headers.get("Content-Range");
|
|
211
|
+
let count;
|
|
212
|
+
if (contentRange) {
|
|
213
|
+
const slash = contentRange.lastIndexOf("/");
|
|
214
|
+
if (slash >= 0) {
|
|
215
|
+
const totalPart = contentRange.slice(slash + 1);
|
|
216
|
+
if (totalPart !== "*") {
|
|
217
|
+
const parsed = Number.parseInt(totalPart, 10);
|
|
218
|
+
if (!Number.isNaN(parsed)) {
|
|
219
|
+
count = parsed;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
data,
|
|
226
|
+
error: null,
|
|
227
|
+
status: response.status,
|
|
228
|
+
...count !== void 0 ? { count } : {}
|
|
229
|
+
};
|
|
170
230
|
}
|
|
171
231
|
delay(ms) {
|
|
172
232
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/http.ts","../src/platform.ts","../src/token.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","import { PalbaseError } from './errors.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\nconst PALBASE_DOMAIN = 'palbase.studio';\n\n/**\n * Parse project ref from an API key.\n * Format: pb_{ref}_{random}\n * Example: pb_abc12345_xxxxxxxxxxxxxxxxxxxxxxxx\n * Returns the ref segment or null if the key doesn't match.\n */\nfunction parseProjectRef(apiKey: string): string | null {\n const parts = apiKey.split('_');\n // Format: [\"pb\", ref, random]\n const ref = parts[1];\n if (parts.length >= 3 && parts[0] === 'pb' && ref) {\n return ref;\n }\n return null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport interface RequestInterceptor {\n (request: { headers: Record<string, string>; method: string; path: string }): void | Promise<void>;\n}\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | 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 /** 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 (this.tokenManager?.isExpired() && this.tokenManager.getRefreshToken() && this.tokenManager.refreshFunction) {\n await this.tokenManager.refreshSession();\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, testing)\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Resolve from API key (format: pb_{ref}_{random})\n const ref = parseProjectRef(this.apiKey);\n if (ref) {\n return `https://${ref}.${PALBASE_DOMAIN}`;\n }\n\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected: pb_{ref}_{random}. Provide an explicit url option for custom endpoints.',\n 0,\n );\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'apikey': this.apiKey,\n 'Content-Type': 'application/json',\n };\n\n // Add token if available\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // serviceRoleKey overrides token-based Authorization\n if (this.options?.serviceRoleKey) {\n headers['Authorization'] = `Bearer ${this.options.serviceRoleKey}`;\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 const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : parsed * 1000;\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 const contentType = response.headers.get('Content-Type');\n if (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 return { data, error: null, status: response.status };\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\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 this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/http.ts","../src/platform.ts","../src/token.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","import { PalbaseError } from './errors.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\nconst PALBASE_DOMAIN = 'palbase.studio';\n\n/**\n * Parse project ref from an API key.\n * Format: pb_{ref}_{random}\n * Example: pb_abc12345_xxxxxxxxxxxxxxxxxxxxxxxx\n * Returns the ref segment or null if the key doesn't match.\n */\nfunction parseProjectRef(apiKey: string): string | null {\n const parts = apiKey.split('_');\n // Format: [\"pb\", ref, random]\n const ref = parts[1];\n if (parts.length >= 3 && parts[0] === 'pb' && ref) {\n return ref;\n }\n return null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport interface RequestInterceptor {\n (request: { headers: Record<string, string>; method: string; path: string }): void | Promise<void>;\n}\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 serviceRoleKey and tokenManager access\n * token in the 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 const parent = this;\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: () => parent.adminToken,\n set: (v: string | null) => {\n parent.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 (this.tokenManager?.isExpired() && this.tokenManager.getRefreshToken() && this.tokenManager.refreshFunction) {\n await this.tokenManager.refreshSession();\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, testing)\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Resolve from API key (format: pb_{ref}_{random})\n const ref = parseProjectRef(this.apiKey);\n if (ref) {\n return `https://${ref}.${PALBASE_DOMAIN}`;\n }\n\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected: pb_{ref}_{random}. Provide an explicit url option for custom endpoints.',\n 0,\n );\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n // apikey header only when an API key is configured (platform admin flows\n // may omit it — e.g. setup/login before any project exists).\n if (this.apiKey) {\n headers['apikey'] = this.apiKey;\n }\n\n // Add token if available\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // serviceRoleKey overrides token-based Authorization\n if (this.options?.serviceRoleKey) {\n headers['Authorization'] = `Bearer ${this.options.serviceRoleKey}`;\n }\n\n // adminToken (platform admin JWT) takes precedence over everything else\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 const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : parsed * 1000;\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\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 this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\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"],"mappings":"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","names":[]}
|