@luxdb/sdk 1.3.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @luxdb/sdk
2
+
3
+ Official TypeScript SDK for Lux.
4
+
5
+ Use the project client for browser, server, and SSR app code. Use the direct client when you want low-level Redis-compatible access to a Lux instance.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun i @luxdb/sdk
11
+ ```
12
+
13
+ ## Browser app client
14
+
15
+ Use a publishable key in browser code. The browser client persists auth sessions in browser storage by default.
16
+
17
+ ```ts
18
+ import { createBrowserClient } from "@luxdb/sdk";
19
+
20
+ const lux = createBrowserClient(
21
+ "https://api.luxdb.dev/v1/my-project",
22
+ "lux_pub_..."
23
+ );
24
+
25
+ const { data: session, error } = await lux.auth.signInWithPassword({
26
+ email: "user@example.com",
27
+ password: "correct horse battery staple",
28
+ });
29
+
30
+ if (error) throw error;
31
+ ```
32
+
33
+ ## Tables
34
+
35
+ Queries and mutations return a Supabase-style result object:
36
+
37
+ ```ts
38
+ const { data: users, error } = await lux
39
+ .table<{ id: number; email: string; age: number }>("users")
40
+ .select()
41
+ .gt("age", 25)
42
+ .order("age", { ascending: false })
43
+ .limit(10);
44
+
45
+ if (error) throw error;
46
+ console.log(users);
47
+ ```
48
+
49
+ ```ts
50
+ const { data: inserted, error: insertError } = await lux
51
+ .table("messages")
52
+ .insert({ body: "hello", channel: "general" });
53
+
54
+ const { data: updated, error: updateError } = await lux
55
+ .table("messages")
56
+ .update({ body: "edited" })
57
+ .eq("id", inserted?.id);
58
+
59
+ const { data: deleted, error: deleteError } = await lux
60
+ .table("messages")
61
+ .delete()
62
+ .eq("id", inserted?.id);
63
+ ```
64
+
65
+ ## OAuth
66
+
67
+ ```ts
68
+ const { data, error } = await lux.auth.signInWithOAuth({
69
+ provider: "google",
70
+ redirectTo: "https://app.example.com/auth/callback",
71
+ });
72
+
73
+ if (error) throw error;
74
+ ```
75
+
76
+ On your callback page:
77
+
78
+ ```ts
79
+ const { data, error } = await lux.auth.consumeOAuthRedirect();
80
+
81
+ if (error) throw error;
82
+ console.log(data.user);
83
+ ```
84
+
85
+ ## Server client
86
+
87
+ Use a secret key only from trusted server code.
88
+
89
+ ```ts
90
+ import { createClient } from "@luxdb/sdk";
91
+
92
+ const admin = createClient(
93
+ "https://api.luxdb.dev/v1/my-project",
94
+ process.env.LUX_SECRET_KEY!
95
+ );
96
+
97
+ const { data: users, error } = await admin.auth.listUsers();
98
+ ```
99
+
100
+ ## SSR client
101
+
102
+ Use `createServerClient` with your framework's cookie methods to persist sessions on the server.
103
+
104
+ ```ts
105
+ import { createServerClient } from "@luxdb/sdk";
106
+
107
+ const lux = createServerClient(
108
+ "https://api.luxdb.dev/v1/my-project",
109
+ "lux_pub_...",
110
+ { cookies }
111
+ );
112
+ ```
113
+
114
+ ## Direct Lux/Redis-compatible access
115
+
116
+ Use direct access for trusted infrastructure that needs RESP commands, low-level primitives, or compatibility with Redis workflows. Do not ship database passwords to browsers.
117
+
118
+ ```ts
119
+ import Lux from "@luxdb/sdk";
120
+
121
+ const lux = new Lux("lux://:password@localhost:6379");
122
+
123
+ await lux.set("hello", "world");
124
+ const value = await lux.get("hello");
125
+ ```
126
+
127
+ ## Access model
128
+
129
+ - `lux_pub_...` keys are safe for browser app calls.
130
+ - `lux_sec_...` keys are server-only.
131
+ - User sessions issue JWT access tokens.
132
+ - Direct `lux://` or `rediss://` database access uses the database password and is for trusted infrastructure.
@@ -0,0 +1,504 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LuxAuthClient = void 0;
4
+ const utils_1 = require("./utils");
5
+ class LuxAuthClient {
6
+ constructor(options = {}) {
7
+ this.currentSession = null;
8
+ this.loadedSession = false;
9
+ this.refreshTimer = null;
10
+ this.listeners = new Set();
11
+ this.httpUrl = options.httpUrl?.replace(/\/+$/, '');
12
+ this.apiKey = options.apiKey;
13
+ this.authToken = options.authToken;
14
+ this.fetchImpl = resolveFetch(options.fetch);
15
+ this.persistSession = options.persistSession ?? false;
16
+ this.autoRefreshToken = options.autoRefreshToken ?? this.persistSession;
17
+ this.storage = options.storage === undefined ? defaultBrowserStorage() : options.storage;
18
+ this.storageKey = options.storageKey ?? 'lux.auth.session';
19
+ this.refreshMarginSeconds = options.refreshMarginSeconds ?? 60;
20
+ if (this.authToken) {
21
+ this.currentSession = null;
22
+ }
23
+ }
24
+ async getSession() {
25
+ try {
26
+ return (0, utils_1.ok)({ session: await this.getSessionValue() });
27
+ }
28
+ catch (error) {
29
+ return (0, utils_1.err)('LUX_AUTH_SESSION_ERROR', 'Failed to get auth session', (0, utils_1.toLuxError)(error));
30
+ }
31
+ }
32
+ async getSessionValue() {
33
+ await this.loadStoredSession();
34
+ if (this.currentSession && isExpired(this.currentSession, this.refreshMarginSeconds)) {
35
+ if (this.autoRefreshToken && this.currentSession.refresh_token) {
36
+ const refreshed = await this.refreshSession(this.currentSession.refresh_token);
37
+ return refreshed.data?.session ?? null;
38
+ }
39
+ await this.clearSessionValue();
40
+ return null;
41
+ }
42
+ return this.currentSession;
43
+ }
44
+ async getAccessToken() {
45
+ const session = await this.getSessionValue();
46
+ return session?.access_token ?? this.authToken;
47
+ }
48
+ async setSession(session) {
49
+ try {
50
+ return (0, utils_1.ok)({ session: await this.setSessionValue(session) });
51
+ }
52
+ catch (error) {
53
+ return (0, utils_1.err)('LUX_AUTH_SESSION_ERROR', 'Failed to set auth session', (0, utils_1.toLuxError)(error));
54
+ }
55
+ }
56
+ async setSessionValue(session) {
57
+ if (typeof session === 'string') {
58
+ this.authToken = session;
59
+ this.currentSession = null;
60
+ return null;
61
+ }
62
+ if (!session) {
63
+ await this.clearSessionValue();
64
+ return null;
65
+ }
66
+ await this.saveSession(normalizeSession(session), 'SESSION_UPDATED');
67
+ return this.currentSession;
68
+ }
69
+ async clearSession() {
70
+ try {
71
+ await this.clearSessionValue();
72
+ return (0, utils_1.ok)(null);
73
+ }
74
+ catch (error) {
75
+ return (0, utils_1.err)('LUX_AUTH_SESSION_ERROR', 'Failed to clear auth session', (0, utils_1.toLuxError)(error));
76
+ }
77
+ }
78
+ async clearSessionValue() {
79
+ this.authToken = undefined;
80
+ this.currentSession = null;
81
+ this.loadedSession = true;
82
+ this.clearRefreshTimer();
83
+ if (this.persistSession && this.storage) {
84
+ await this.storage.removeItem(this.storageKey);
85
+ }
86
+ this.emit('SIGNED_OUT', null);
87
+ }
88
+ onAuthStateChange(callback) {
89
+ this.listeners.add(callback);
90
+ void this.getSessionValue().then((session) => callback('INITIAL_SESSION', session));
91
+ return {
92
+ unsubscribe: () => {
93
+ this.listeners.delete(callback);
94
+ },
95
+ };
96
+ }
97
+ async signUp(options) {
98
+ try {
99
+ const session = await this.requestRaw('/auth/v1/signup', {
100
+ method: 'POST',
101
+ body: JSON.stringify({
102
+ email: options.email,
103
+ password: options.password,
104
+ data: options.data,
105
+ }),
106
+ apiKey: true,
107
+ });
108
+ await this.saveSession(normalizeSession(session), 'SIGNED_IN');
109
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession.user });
110
+ }
111
+ catch (error) {
112
+ return (0, utils_1.err)('LUX_AUTH_SIGNUP_ERROR', 'Failed to sign up', (0, utils_1.toLuxError)(error));
113
+ }
114
+ }
115
+ async signInWithPassword(options) {
116
+ try {
117
+ const session = await this.requestRaw('/auth/v1/token', {
118
+ method: 'POST',
119
+ body: JSON.stringify({
120
+ grant_type: 'password',
121
+ email: options.email,
122
+ password: options.password,
123
+ }),
124
+ apiKey: true,
125
+ });
126
+ await this.saveSession(normalizeSession(session), 'SIGNED_IN');
127
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession.user });
128
+ }
129
+ catch (error) {
130
+ return (0, utils_1.err)('LUX_AUTH_SIGNIN_ERROR', 'Failed to sign in', (0, utils_1.toLuxError)(error));
131
+ }
132
+ }
133
+ async signInWithOAuth(options) {
134
+ try {
135
+ if (!this.httpUrl) {
136
+ throw new Error('Lux auth requires httpUrl');
137
+ }
138
+ const redirectTo = options.redirectTo ?? browserLocation();
139
+ const url = new URL(`${this.httpUrl}/auth/v1/authorize`);
140
+ url.searchParams.set('provider', options.provider);
141
+ if (redirectTo)
142
+ url.searchParams.set('redirect_to', redirectTo);
143
+ const target = url.toString();
144
+ if (!options.skipRedirect && typeof globalThis !== 'undefined') {
145
+ const location = globalThis.location;
146
+ if (location?.assign)
147
+ location.assign(target);
148
+ }
149
+ return (0, utils_1.ok)({ url: target });
150
+ }
151
+ catch (error) {
152
+ return (0, utils_1.err)('LUX_AUTH_OAUTH_ERROR', 'Failed to start OAuth sign in', (0, utils_1.toLuxError)(error));
153
+ }
154
+ }
155
+ async consumeOAuthRedirect(url = browserLocation()) {
156
+ try {
157
+ if (!url)
158
+ return (0, utils_1.ok)({ session: null, user: null });
159
+ const parsed = new URL(url);
160
+ const params = new URLSearchParams(parsed.hash.replace(/^#/, ''));
161
+ const accessToken = params.get('access_token');
162
+ const refreshToken = params.get('refresh_token');
163
+ if (!accessToken || !refreshToken)
164
+ return (0, utils_1.ok)({ session: null, user: null });
165
+ const session = normalizeSession({
166
+ access_token: accessToken,
167
+ refresh_token: refreshToken,
168
+ expires_in: Number(params.get('expires_in') || 0),
169
+ token_type: 'bearer',
170
+ user: { id: '', email: '' },
171
+ });
172
+ if (this.httpUrl) {
173
+ const user = await this.getUser(accessToken);
174
+ if (user.error)
175
+ return user;
176
+ if (!user.data?.user) {
177
+ return (0, utils_1.err)('LUX_AUTH_USER_ERROR', 'OAuth redirect token did not resolve to a user');
178
+ }
179
+ session.user = user.data.user;
180
+ }
181
+ await this.saveSession(session, 'SIGNED_IN');
182
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession?.user ?? null });
183
+ }
184
+ catch (error) {
185
+ return (0, utils_1.err)('LUX_AUTH_OAUTH_ERROR', 'Failed to consume OAuth redirect', (0, utils_1.toLuxError)(error));
186
+ }
187
+ }
188
+ async refreshSession(refreshToken) {
189
+ try {
190
+ const session = await this.requestRaw('/auth/v1/token', {
191
+ method: 'POST',
192
+ body: JSON.stringify({
193
+ grant_type: 'refresh_token',
194
+ refresh_token: refreshToken,
195
+ }),
196
+ apiKey: true,
197
+ });
198
+ await this.saveSession(normalizeSession(session), 'TOKEN_REFRESHED');
199
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession.user });
200
+ }
201
+ catch (error) {
202
+ return (0, utils_1.err)('LUX_AUTH_REFRESH_ERROR', 'Failed to refresh auth session', (0, utils_1.toLuxError)(error));
203
+ }
204
+ }
205
+ async getUser(session) {
206
+ if (!session) {
207
+ await this.getSessionValue();
208
+ }
209
+ try {
210
+ return (0, utils_1.ok)(await this.requestRaw('/auth/v1/user', {
211
+ method: 'GET',
212
+ token: this.tokenFrom(session),
213
+ }));
214
+ }
215
+ catch (error) {
216
+ return (0, utils_1.err)('LUX_AUTH_USER_ERROR', 'Failed to get auth user', (0, utils_1.toLuxError)(error));
217
+ }
218
+ }
219
+ async logout(sessionOrRefreshToken) {
220
+ if (!sessionOrRefreshToken) {
221
+ sessionOrRefreshToken = await this.getSessionValue() ?? undefined;
222
+ }
223
+ const token = typeof sessionOrRefreshToken === 'string'
224
+ ? sessionOrRefreshToken
225
+ : sessionOrRefreshToken?.access_token;
226
+ const refreshToken = typeof sessionOrRefreshToken === 'string'
227
+ ? undefined
228
+ : sessionOrRefreshToken?.refresh_token;
229
+ try {
230
+ await this.requestRaw('/auth/v1/logout', {
231
+ method: 'POST',
232
+ token,
233
+ body: JSON.stringify(refreshToken ? { refresh_token: refreshToken } : {}),
234
+ });
235
+ await this.clearSessionValue();
236
+ return (0, utils_1.ok)(null);
237
+ }
238
+ catch (error) {
239
+ return (0, utils_1.err)('LUX_AUTH_LOGOUT_ERROR', 'Failed to log out', (0, utils_1.toLuxError)(error));
240
+ }
241
+ }
242
+ async signOut() {
243
+ return this.logout();
244
+ }
245
+ async listUsers() {
246
+ try {
247
+ const result = await this.requestRaw('/auth/v1/admin/users', {
248
+ method: 'GET',
249
+ secret: true,
250
+ });
251
+ return (0, utils_1.ok)(result.users);
252
+ }
253
+ catch (error) {
254
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list auth users', (0, utils_1.toLuxError)(error));
255
+ }
256
+ }
257
+ async grantCapability(userId, capability) {
258
+ try {
259
+ const result = await this.requestRaw('/auth/v1/admin/grants', {
260
+ method: 'POST',
261
+ secret: true,
262
+ body: JSON.stringify({ user_id: userId, capability }),
263
+ });
264
+ return (0, utils_1.ok)(result.grant);
265
+ }
266
+ catch (error) {
267
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to grant capability', (0, utils_1.toLuxError)(error));
268
+ }
269
+ }
270
+ async listUserGrants(userId) {
271
+ try {
272
+ const result = await this.requestRaw(`/auth/v1/admin/users/${encodeURIComponent(userId)}/grants`, {
273
+ method: 'GET',
274
+ secret: true,
275
+ });
276
+ return (0, utils_1.ok)(result.grants);
277
+ }
278
+ catch (error) {
279
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list user grants', (0, utils_1.toLuxError)(error));
280
+ }
281
+ }
282
+ async revokeGrant(grantId) {
283
+ try {
284
+ await this.requestRaw(`/auth/v1/admin/grants/${encodeURIComponent(grantId)}`, {
285
+ method: 'DELETE',
286
+ secret: true,
287
+ });
288
+ return (0, utils_1.ok)(null);
289
+ }
290
+ catch (error) {
291
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to revoke grant', (0, utils_1.toLuxError)(error));
292
+ }
293
+ }
294
+ async listApiKeys() {
295
+ try {
296
+ const result = await this.requestRaw('/auth/v1/admin/keys', {
297
+ method: 'GET',
298
+ secret: true,
299
+ });
300
+ return (0, utils_1.ok)(result.keys);
301
+ }
302
+ catch (error) {
303
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list API keys', (0, utils_1.toLuxError)(error));
304
+ }
305
+ }
306
+ async listProviders() {
307
+ try {
308
+ const result = await this.requestRaw('/auth/v1/admin/providers', {
309
+ method: 'GET',
310
+ secret: true,
311
+ });
312
+ return (0, utils_1.ok)(result.providers);
313
+ }
314
+ catch (error) {
315
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list auth providers', (0, utils_1.toLuxError)(error));
316
+ }
317
+ }
318
+ async upsertProvider(options) {
319
+ try {
320
+ const result = await this.requestRaw(`/auth/v1/admin/providers/${encodeURIComponent(options.provider)}`, {
321
+ method: 'PUT',
322
+ secret: true,
323
+ body: JSON.stringify(options),
324
+ });
325
+ return (0, utils_1.ok)(result.provider);
326
+ }
327
+ catch (error) {
328
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to update auth provider', (0, utils_1.toLuxError)(error));
329
+ }
330
+ }
331
+ async createApiKey(options) {
332
+ try {
333
+ return (0, utils_1.ok)(await this.requestRaw('/auth/v1/admin/keys', {
334
+ method: 'POST',
335
+ secret: true,
336
+ body: JSON.stringify(options),
337
+ }));
338
+ }
339
+ catch (error) {
340
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to create API key', (0, utils_1.toLuxError)(error));
341
+ }
342
+ }
343
+ async revokeApiKey(keyId) {
344
+ try {
345
+ await this.requestRaw(`/auth/v1/admin/keys/${encodeURIComponent(keyId)}`, {
346
+ method: 'DELETE',
347
+ secret: true,
348
+ });
349
+ return (0, utils_1.ok)(null);
350
+ }
351
+ catch (error) {
352
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to revoke API key', (0, utils_1.toLuxError)(error));
353
+ }
354
+ }
355
+ async get(path, session) {
356
+ return this.requestResult(path, { method: 'GET', token: this.tokenFrom(session) });
357
+ }
358
+ async post(path, body, session) {
359
+ return this.requestResult(path, {
360
+ method: 'POST',
361
+ token: this.tokenFrom(session),
362
+ body: body == null ? undefined : JSON.stringify(body),
363
+ });
364
+ }
365
+ async put(path, body, session) {
366
+ return this.requestResult(path, {
367
+ method: 'PUT',
368
+ token: this.tokenFrom(session),
369
+ body: body == null ? undefined : JSON.stringify(body),
370
+ });
371
+ }
372
+ async delete(path, session) {
373
+ return this.requestResult(path, { method: 'DELETE', token: this.tokenFrom(session) });
374
+ }
375
+ tokenFrom(session) {
376
+ return typeof session === 'string' ? session : session?.access_token ?? this.authToken;
377
+ }
378
+ async loadStoredSession() {
379
+ if (this.loadedSession)
380
+ return;
381
+ this.loadedSession = true;
382
+ if (!this.persistSession || !this.storage)
383
+ return;
384
+ const raw = await this.storage.getItem(this.storageKey);
385
+ if (!raw)
386
+ return;
387
+ try {
388
+ const session = normalizeSession(JSON.parse(raw));
389
+ this.currentSession = session;
390
+ this.authToken = session.access_token;
391
+ this.scheduleRefresh(session);
392
+ }
393
+ catch {
394
+ await this.storage.removeItem(this.storageKey);
395
+ }
396
+ }
397
+ async saveSession(session, event) {
398
+ this.currentSession = session;
399
+ this.authToken = session.access_token;
400
+ this.loadedSession = true;
401
+ if (this.persistSession && this.storage) {
402
+ await this.storage.setItem(this.storageKey, JSON.stringify(session));
403
+ }
404
+ this.scheduleRefresh(session);
405
+ this.emit(event, session);
406
+ }
407
+ scheduleRefresh(session) {
408
+ this.clearRefreshTimer();
409
+ if (!this.autoRefreshToken || !session.refresh_token || !session.expires_at)
410
+ return;
411
+ const delayMs = Math.max(0, (session.expires_at - this.refreshMarginSeconds) * 1000 - Date.now());
412
+ this.refreshTimer = setTimeout(() => {
413
+ void this.refreshSession(session.refresh_token).catch(() => {
414
+ void this.clearSessionValue();
415
+ });
416
+ }, delayMs);
417
+ }
418
+ clearRefreshTimer() {
419
+ if (this.refreshTimer) {
420
+ clearTimeout(this.refreshTimer);
421
+ this.refreshTimer = null;
422
+ }
423
+ }
424
+ emit(event, session) {
425
+ for (const listener of this.listeners) {
426
+ listener(event, session);
427
+ }
428
+ }
429
+ async requestResult(path, init) {
430
+ try {
431
+ return (0, utils_1.ok)(await this.requestRaw(path, init));
432
+ }
433
+ catch (error) {
434
+ return (0, utils_1.err)('LUX_AUTH_REQUEST_ERROR', 'Lux auth request failed', (0, utils_1.toLuxError)(error));
435
+ }
436
+ }
437
+ async requestRaw(path, init) {
438
+ if (!this.httpUrl) {
439
+ throw new Error('Lux auth requires httpUrl');
440
+ }
441
+ const headers = {
442
+ Accept: 'application/json',
443
+ };
444
+ if (init.body != null) {
445
+ headers['Content-Type'] = 'application/json';
446
+ }
447
+ const token = init.token ?? this.authToken;
448
+ if (token) {
449
+ headers.Authorization = `Bearer ${token}`;
450
+ }
451
+ if ((init.apiKey || init.secret) && this.apiKey) {
452
+ headers.apikey = this.apiKey;
453
+ }
454
+ const response = await this.fetchImpl(`${this.httpUrl}${path}`, {
455
+ method: init.method,
456
+ headers,
457
+ body: init.body,
458
+ });
459
+ const text = await response.text();
460
+ const payload = text ? JSON.parse(text) : {};
461
+ if (!response.ok) {
462
+ const message = payload?.error || `Lux auth request failed with HTTP ${response.status}`;
463
+ throw new Error(message);
464
+ }
465
+ return payload;
466
+ }
467
+ }
468
+ exports.LuxAuthClient = LuxAuthClient;
469
+ function normalizeSession(session) {
470
+ return {
471
+ ...session,
472
+ expires_at: session.expires_at ?? Math.floor(Date.now() / 1000) + Number(session.expires_in || 0),
473
+ };
474
+ }
475
+ function isExpired(session, marginSeconds = 0) {
476
+ return Boolean(session.expires_at && session.expires_at <= Math.floor(Date.now() / 1000) + marginSeconds);
477
+ }
478
+ function defaultBrowserStorage() {
479
+ if (typeof globalThis === 'undefined')
480
+ return null;
481
+ const storage = globalThis.localStorage;
482
+ if (!storage)
483
+ return null;
484
+ return {
485
+ getItem: (key) => storage.getItem(key),
486
+ setItem: (key, value) => storage.setItem(key, value),
487
+ removeItem: (key) => storage.removeItem(key),
488
+ };
489
+ }
490
+ function resolveFetch(fetchImpl) {
491
+ const candidate = fetchImpl ?? globalThis.fetch;
492
+ if (!candidate) {
493
+ throw new Error('Lux auth requires a fetch implementation');
494
+ }
495
+ if (typeof globalThis !== 'undefined' && candidate === globalThis.fetch) {
496
+ return candidate.bind(globalThis);
497
+ }
498
+ return candidate;
499
+ }
500
+ function browserLocation() {
501
+ if (typeof globalThis === 'undefined')
502
+ return '';
503
+ return String(globalThis.location?.href || '');
504
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBrowserClient = createBrowserClient;
4
+ const project_1 = require("./project");
5
+ function createBrowserClient(url, key, options = {}) {
6
+ return (0, project_1.createClient)(url, key, {
7
+ ...options,
8
+ auth: {
9
+ persistSession: true,
10
+ autoRefreshToken: true,
11
+ ...(options.auth ?? {}),
12
+ },
13
+ });
14
+ }