@palbase/web 4.0.2 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +20 -3
  2. package/dist/chunk-BNAGRUIQ.js +381 -0
  3. package/dist/chunk-BNAGRUIQ.js.map +1 -0
  4. package/dist/chunk-OWLTZG2V.js +24 -0
  5. package/dist/chunk-OWLTZG2V.js.map +1 -0
  6. package/dist/{chunk-TW6YN354.js → chunk-QKFPPZLF.js} +13 -373
  7. package/dist/chunk-QKFPPZLF.js.map +1 -0
  8. package/dist/gen/cli.cjs +8 -5
  9. package/dist/gen/cli.cjs.map +1 -1
  10. package/dist/gen/cli.js +8 -5
  11. package/dist/gen/cli.js.map +1 -1
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +5 -3
  17. package/dist/internal.cjs +1 -1
  18. package/dist/internal.cjs.map +1 -1
  19. package/dist/internal.js +2 -1
  20. package/dist/next/client.cjs +1 -1
  21. package/dist/next/client.cjs.map +1 -1
  22. package/dist/next/client.js +2 -1
  23. package/dist/next/client.js.map +1 -1
  24. package/dist/next/index.cjs +4 -88
  25. package/dist/next/index.cjs.map +1 -1
  26. package/dist/next/index.d.cts +2 -62
  27. package/dist/next/index.d.ts +2 -62
  28. package/dist/next/index.js +8 -102
  29. package/dist/next/index.js.map +1 -1
  30. package/dist/next/middleware.cjs +263 -0
  31. package/dist/next/middleware.cjs.map +1 -0
  32. package/dist/next/middleware.d.cts +101 -0
  33. package/dist/next/middleware.d.ts +101 -0
  34. package/dist/next/middleware.js +107 -0
  35. package/dist/next/middleware.js.map +1 -0
  36. package/dist/react/index.cjs +1 -1
  37. package/dist/react/index.cjs.map +1 -1
  38. package/dist/react/index.js +2 -1
  39. package/dist/react/index.js.map +1 -1
  40. package/package.json +11 -1
  41. package/dist/chunk-TW6YN354.js.map +0 -1
package/README.md CHANGED
@@ -229,17 +229,34 @@ cookie the server can read.
229
229
 
230
230
  ```ts
231
231
  // middleware.ts
232
- import './palbe.gen';
233
- import { palbeMiddleware } from '@palbase/web/next';
232
+ import { palbeMiddleware } from '@palbase/web/next/middleware';
234
233
  import type { NextRequest } from 'next/server';
235
234
 
236
235
  export function middleware(request: NextRequest) {
237
- return palbeMiddleware(request);
236
+ return palbeMiddleware(request, {
237
+ url: 'https://<environmentRef>.palbase.studio',
238
+ apiKey: 'pb_<environmentRef>_c...',
239
+ });
238
240
  }
239
241
 
240
242
  export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };
241
243
  ```
242
244
 
245
+ `palbase web link` writes this file for you, with your `url` and publishable
246
+ `apiKey` filled in — the same pair it bakes into `palbe.gen.ts`.
247
+
248
+ Two rules the shape above encodes:
249
+
250
+ - **Import `@palbase/web/next/middleware`, not `@palbase/web/next`.** Next
251
+ compiles middleware into a separate Edge bundle, and the full adapter graph
252
+ reaches `WebAssembly.compile`, which Next rejects there with a build error.
253
+ This entry reaches none of it.
254
+ - **Pass the config explicitly; do not `import './palbe.gen'` here.** That
255
+ import would pull the same graph back in. It also means `config`/`matcher`
256
+ must be declared in this file — Next reads `export const config` off this
257
+ file's own AST, so a re-exported or imported one is silently ignored and the
258
+ middleware degrades to matching every request.
259
+
243
260
  **3. Read data in Server Components** with `pbServer()` — a per-request,
244
261
  session-isolated client:
245
262
 
@@ -0,0 +1,381 @@
1
+ // ../core/dist/index.js
2
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
3
+ var PalbaseError = class extends Error {
4
+ code;
5
+ status;
6
+ details;
7
+ constructor(code, message, status, details) {
8
+ super(message);
9
+ this.name = "PalbaseError";
10
+ this.code = code;
11
+ this.status = status;
12
+ this.details = details;
13
+ }
14
+ };
15
+ var PALBASE_DEFAULT_HOST = "api.palbase.studio";
16
+ var API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;
17
+ function parseEnvironmentRef(apiKey) {
18
+ return API_KEY_RE.exec(apiKey)?.[1] ?? null;
19
+ }
20
+ var MAX_RETRIES = 3;
21
+ var INITIAL_BACKOFF_MS = 200;
22
+ var MAX_RETRY_DELAY_MS = 1e4;
23
+ var HttpClient = class _HttpClient {
24
+ apiKey;
25
+ options;
26
+ tokenManager = null;
27
+ /**
28
+ * Admin JWT used for platform admin endpoints (/admin/*).
29
+ * When set, takes precedence over tokenManager access token in the
30
+ * Authorization header.
31
+ */
32
+ adminToken = null;
33
+ interceptors = [];
34
+ constructor(apiKey, options) {
35
+ this.apiKey = apiKey;
36
+ this.options = options;
37
+ }
38
+ /** Set (or clear) the admin JWT used on admin endpoints. */
39
+ setAdminToken(token) {
40
+ this.adminToken = token;
41
+ }
42
+ /**
43
+ * Create a scoped HttpClient that adds the given extra headers to every
44
+ * request. The returned client shares the admin token and token manager
45
+ * with the parent at runtime — later changes on the parent propagate to
46
+ * the scope and vice versa.
47
+ *
48
+ * Typical use: adding an Environment-routing header for an admin call.
49
+ */
50
+ withHeaders(extra) {
51
+ const mergedHeaders = { ...this.options?.headers ?? {}, ...extra };
52
+ const scoped = new _HttpClient(this.apiKey, {
53
+ ...this.options,
54
+ headers: mergedHeaders
55
+ });
56
+ scoped.tokenManager = this.tokenManager;
57
+ Object.defineProperty(scoped, "adminToken", {
58
+ get: () => this.adminToken,
59
+ set: (v) => {
60
+ this.adminToken = v;
61
+ },
62
+ configurable: true
63
+ });
64
+ return scoped;
65
+ }
66
+ /** Add a request interceptor. Runs before every request. */
67
+ addInterceptor(interceptor) {
68
+ this.interceptors.push(interceptor);
69
+ }
70
+ async request(method, path, options) {
71
+ if (this.tokenManager?.isExpired() && this.tokenManager.getRefreshToken() && this.tokenManager.refreshFunction) {
72
+ try {
73
+ await this.tokenManager.refreshSession();
74
+ } catch (e) {
75
+ const status = e instanceof PalbaseError ? e.status : 0;
76
+ if (status === 400 || status === 401 || status === 403) {
77
+ this.tokenManager.clearSession();
78
+ } else {
79
+ throw e;
80
+ }
81
+ }
82
+ }
83
+ return this.executeWithRetry(method, path, options, 0);
84
+ }
85
+ getBaseUrl() {
86
+ if (this.options?.url) {
87
+ return this.options.url;
88
+ }
89
+ if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {
90
+ throw new PalbaseError(
91
+ "invalid_api_key",
92
+ 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: "https://api.dev.palbase.studio"` via options.',
93
+ 0
94
+ );
95
+ }
96
+ return `https://${PALBASE_DEFAULT_HOST}`;
97
+ }
98
+ buildHeaders(options) {
99
+ const headers = {
100
+ "Content-Type": "application/json"
101
+ };
102
+ const effectiveKey = this.apiKey;
103
+ if (effectiveKey) {
104
+ headers["apikey"] = effectiveKey;
105
+ }
106
+ const token = this.tokenManager?.getAccessToken();
107
+ if (token) {
108
+ headers["Authorization"] = `Bearer ${token}`;
109
+ }
110
+ if (this.adminToken) {
111
+ headers["Authorization"] = `Bearer ${this.adminToken}`;
112
+ }
113
+ if (this.options?.headers) {
114
+ Object.assign(headers, this.options.headers);
115
+ }
116
+ if (options?.headers) {
117
+ Object.assign(headers, options.headers);
118
+ }
119
+ return headers;
120
+ }
121
+ async executeWithRetry(method, path, options, attempt) {
122
+ const url = `${this.getBaseUrl()}${path}`;
123
+ const headers = this.buildHeaders(options);
124
+ for (const interceptor of this.interceptors) {
125
+ await interceptor({ headers, method, path });
126
+ }
127
+ const fetchOptions = {
128
+ method,
129
+ headers,
130
+ signal: options?.signal
131
+ };
132
+ if (options?.body !== void 0) {
133
+ fetchOptions.body = JSON.stringify(options.body);
134
+ }
135
+ let response;
136
+ try {
137
+ response = await fetch(url, fetchOptions);
138
+ } catch (error) {
139
+ if (attempt < MAX_RETRIES - 1) {
140
+ const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;
141
+ await this.delay(backoff);
142
+ return this.executeWithRetry(method, path, options, attempt + 1);
143
+ }
144
+ throw new PalbaseError(
145
+ "network_error",
146
+ error instanceof Error ? error.message : "Network request failed",
147
+ 0
148
+ );
149
+ }
150
+ if (response.status === 429) {
151
+ if (attempt < MAX_RETRIES - 1) {
152
+ const retryAfter = response.headers.get("Retry-After");
153
+ const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;
154
+ const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : Math.min(parsed * 1e3, MAX_RETRY_DELAY_MS);
155
+ await this.delay(delayMs);
156
+ return this.executeWithRetry(method, path, options, attempt + 1);
157
+ }
158
+ }
159
+ let data = null;
160
+ let errorBody;
161
+ const contentType = response.headers.get("Content-Type");
162
+ if (method !== "HEAD" && contentType?.includes("json")) {
163
+ const body = await response.json();
164
+ if (response.ok) {
165
+ data = body;
166
+ } else {
167
+ errorBody = body;
168
+ }
169
+ }
170
+ if (!response.ok) {
171
+ return {
172
+ data: null,
173
+ error: new PalbaseError(
174
+ errorBody?.error ?? "unknown_error",
175
+ errorBody?.error_description ?? response.statusText,
176
+ response.status,
177
+ errorBody
178
+ ),
179
+ status: response.status
180
+ };
181
+ }
182
+ const contentRange = response.headers.get("Content-Range");
183
+ let count;
184
+ if (contentRange) {
185
+ const slash = contentRange.lastIndexOf("/");
186
+ if (slash >= 0) {
187
+ const totalPart = contentRange.slice(slash + 1);
188
+ if (totalPart !== "*") {
189
+ const parsed = Number.parseInt(totalPart, 10);
190
+ if (!Number.isNaN(parsed)) {
191
+ count = parsed;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ return {
197
+ data,
198
+ error: null,
199
+ status: response.status,
200
+ ...count !== void 0 ? { count } : {}
201
+ };
202
+ }
203
+ delay(ms) {
204
+ return new Promise((resolve) => setTimeout(resolve, ms));
205
+ }
206
+ };
207
+ var TokenManager = class {
208
+ session = null;
209
+ listeners = /* @__PURE__ */ new Set();
210
+ refreshPromise = null;
211
+ refreshing = false;
212
+ refreshFunction = null;
213
+ setSession(session) {
214
+ this.session = session;
215
+ this.notify("SESSION_SET", session);
216
+ }
217
+ getAccessToken() {
218
+ return this.session?.accessToken ?? null;
219
+ }
220
+ getRefreshToken() {
221
+ return this.session?.refreshToken ?? null;
222
+ }
223
+ clearSession() {
224
+ this.session = null;
225
+ this.notify("SESSION_CLEARED", null);
226
+ }
227
+ isExpired() {
228
+ if (!this.session) return true;
229
+ return Date.now() >= this.session.expiresAt;
230
+ }
231
+ async refreshSession() {
232
+ if (!this.session?.refreshToken || !this.refreshFunction) {
233
+ return;
234
+ }
235
+ if (this.refreshPromise) {
236
+ return this.refreshPromise;
237
+ }
238
+ if (this.refreshing) {
239
+ return;
240
+ }
241
+ this.refreshing = true;
242
+ this.refreshPromise = this.executeRefresh(this.session.refreshToken);
243
+ try {
244
+ await this.refreshPromise;
245
+ } finally {
246
+ this.refreshPromise = null;
247
+ this.refreshing = false;
248
+ }
249
+ }
250
+ onAuthStateChange(callback) {
251
+ this.listeners.add(callback);
252
+ return () => {
253
+ this.listeners.delete(callback);
254
+ };
255
+ }
256
+ async executeRefresh(refreshToken) {
257
+ if (!this.refreshFunction) return;
258
+ const newSession = await this.refreshFunction(refreshToken);
259
+ this.setSession(newSession);
260
+ }
261
+ notify(event, session) {
262
+ for (const listener of this.listeners) {
263
+ listener(event, session);
264
+ }
265
+ }
266
+ };
267
+
268
+ // src/errors.ts
269
+ var BackendError = class _BackendError extends Error {
270
+ kind;
271
+ code;
272
+ status;
273
+ requestId;
274
+ fields;
275
+ retryAfter;
276
+ data;
277
+ constructor(kind, params) {
278
+ super(params.message);
279
+ this.name = "BackendError";
280
+ this.kind = kind;
281
+ this.code = params.code;
282
+ this.status = params.status ?? 0;
283
+ this.requestId = params.requestId;
284
+ this.fields = params.fields;
285
+ this.retryAfter = params.retryAfter;
286
+ this.data = params.data;
287
+ }
288
+ static notConfigured() {
289
+ return new _BackendError("notConfigured", {
290
+ code: "not_configured",
291
+ message: "Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup."
292
+ });
293
+ }
294
+ };
295
+ function isFieldErrorArray(value) {
296
+ return Array.isArray(value) && value.length > 0 && value.every(
297
+ (v) => typeof v === "object" && v !== null && typeof v.field === "string" && typeof v.message === "string"
298
+ );
299
+ }
300
+ function pickField(value, key) {
301
+ if (typeof value === "object" && value !== null) {
302
+ return value[key];
303
+ }
304
+ return void 0;
305
+ }
306
+ function pickNumber(value, key) {
307
+ const n = pickField(value, key);
308
+ return typeof n === "number" ? n : void 0;
309
+ }
310
+ function pickString(value, key) {
311
+ const s = pickField(value, key);
312
+ return typeof s === "string" ? s : void 0;
313
+ }
314
+ function fromPalbaseError(err) {
315
+ const base = {
316
+ code: err.code,
317
+ message: err.message,
318
+ status: err.status,
319
+ requestId: pickString(err.details, "request_id"),
320
+ data: pickField(err.details, "data")
321
+ };
322
+ if (err.code === "network_error") return new BackendError("network", base);
323
+ if (err.status === 401) return new BackendError("unauthorized", base);
324
+ if (err.status === 429)
325
+ return new BackendError("rateLimited", {
326
+ ...base,
327
+ retryAfter: pickNumber(err.details, "retry_after")
328
+ });
329
+ const nested = pickField(err.details, "details");
330
+ if (err.status === 400 && isFieldErrorArray(nested))
331
+ return new BackendError("validation", { ...base, fields: nested });
332
+ return new BackendError("server", base);
333
+ }
334
+ function fromEnvelope(status, body) {
335
+ const code = pickString(body, "error") ?? "http_error";
336
+ const message = pickString(body, "error_description") ?? `HTTP ${status}`;
337
+ const requestId = pickString(body, "request_id");
338
+ const details = pickField(body, "details");
339
+ const params = {
340
+ code,
341
+ message,
342
+ status,
343
+ requestId,
344
+ data: pickField(body, "data")
345
+ };
346
+ if (status === 401) return new BackendError("unauthorized", params);
347
+ if (status === 429)
348
+ return new BackendError("rateLimited", {
349
+ ...params,
350
+ // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.
351
+ retryAfter: pickNumber(body, "retry_after") ?? pickNumber(details, "retry_after")
352
+ });
353
+ if (status === 400 && isFieldErrorArray(details))
354
+ return new BackendError("validation", { ...params, fields: details });
355
+ return new BackendError("server", params);
356
+ }
357
+ function isBackendError(e) {
358
+ return e instanceof BackendError || typeof e === "object" && e !== null && e.name === "BackendError" && typeof e.kind === "string";
359
+ }
360
+ function asPalbaseError(e) {
361
+ if (e instanceof PalbaseError) return e;
362
+ if (e instanceof Error && e.name === "PalbaseError") return e;
363
+ return null;
364
+ }
365
+ function unwrap(res) {
366
+ if (res.error) throw fromPalbaseError(res.error);
367
+ return res.data;
368
+ }
369
+
370
+ export {
371
+ PalbaseError,
372
+ HttpClient,
373
+ TokenManager,
374
+ BackendError,
375
+ fromPalbaseError,
376
+ fromEnvelope,
377
+ isBackendError,
378
+ asPalbaseError,
379
+ unwrap
380
+ };
381
+ //# sourceMappingURL=chunk-BNAGRUIQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/config.ts","../../core/src/errors.ts","../../core/src/http.ts","../../core/src/platform.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","import { PalbaseError } from './errors.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment\n * ref is 4-24 lowercase ASCII alphanumeric characters and random is 20 base62\n * chars. See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = this.buildHeaders(options);\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, details?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', {\n ...base,\n retryAfter: pickNumber(err.details, 'retry_after'),\n });\n // Field-error array lives at the envelope's nested `details` key.\n const nested = pickField(err.details, 'details');\n if (err.status === 400 && isFieldErrorArray(nested))\n return new BackendError('validation', { ...base, fields: nested });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const details = pickField(body, 'details');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', {\n ...params,\n // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.\n retryAfter: pickNumber(body, 'retry_after') ?? pickNumber(details, 'retry_after'),\n });\n if (status === 400 && isFieldErrorArray(details))\n return new BackendError('validation', { ...params, fields: details });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,24 @@
1
+ import {
2
+ BackendError
3
+ } from "./chunk-BNAGRUIQ.js";
4
+
5
+ // src/next/shared.ts
6
+ var nextServerModule;
7
+ async function importNextServer(caller) {
8
+ nextServerModule ??= import("next/server");
9
+ try {
10
+ return await nextServerModule;
11
+ } catch (cause) {
12
+ nextServerModule = void 0;
13
+ const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : "";
14
+ throw new BackendError("validation", {
15
+ code: "next_required",
16
+ message: `${caller}() requires Next.js \u2014 'next/server' could not be resolved${detail}.`
17
+ });
18
+ }
19
+ }
20
+
21
+ export {
22
+ importNextServer
23
+ };
24
+ //# sourceMappingURL=chunk-OWLTZG2V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/next/shared.ts"],"sourcesContent":["/**\n * The lazy 'next/server' import, shared by every palbe/next surface that\n * builds a NextResponse (palbeMiddleware, handleAuthCallback).\n *\n * MODULE-GRAPH RULE: this module must stay reachable from the middleware\n * entry, which Next compiles into its own Edge bundle — so it may import\n * NOTHING that leads to ../runtime.js. Keep it at errors.js and below.\n * (Global-config lookup, which does reach the runtime, lives in\n * ./global-config.ts.)\n */\nimport { BackendError } from '../errors.js';\n\nlet nextServerModule: Promise<typeof import('next/server')> | undefined;\n\n/**\n * Lazy 'next/server' import (MODULE-GRAPH RULE: never top-level — importing\n * palbe/next must work without next installed) with a guided failure.\n * The module promise is memoized: bundler dynamic-import machinery can take\n * macrotask-scale hops PER CALL, so without the cache two near-simultaneous\n * middleware invocations could straddle a refresh — every call after the\n * first now resolves in pure microtasks.\n */\nexport async function importNextServer(caller: string): Promise<typeof import('next/server')> {\n nextServerModule ??= import('next/server');\n try {\n return await nextServerModule;\n } catch (cause) {\n nextServerModule = undefined; // never poison later calls with a cached rejection\n const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : '';\n throw new BackendError('validation', {\n code: 'next_required',\n message: `${caller}() requires Next.js — 'next/server' could not be resolved${detail}.`,\n });\n }\n}\n"],"mappings":";;;;;AAYA,IAAI;AAUJ,eAAsB,iBAAiB,QAAuD;AAC5F,uBAAqB,OAAO,aAAa;AACzC,MAAI;AACF,WAAO,MAAM;AAAA,EACf,SAAS,OAAO;AACd,uBAAmB;AACnB,UAAM,SAAS,iBAAiB,SAAS,MAAM,UAAU,KAAK,MAAM,OAAO,MAAM;AACjF,UAAM,IAAI,aAAa,cAAc;AAAA,MACnC,MAAM;AAAA,MACN,SAAS,GAAG,MAAM,iEAA4D,MAAM;AAAA,IACtF,CAAC;AAAA,EACH;AACF;","names":[]}