fauxbase 0.5.5 → 0.5.7

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.d.cts CHANGED
@@ -50,6 +50,11 @@ interface LocalDriverConfig {
50
50
  type: 'local';
51
51
  persist?: 'memory' | 'localStorage' | 'indexeddb';
52
52
  dbName?: string;
53
+ latency?: number | {
54
+ min: number;
55
+ max: number;
56
+ };
57
+ errorRate?: number;
53
58
  }
54
59
  interface HttpDriverConfig {
55
60
  type: 'http';
@@ -259,8 +264,11 @@ declare class HttpDriver implements Driver {
259
264
  private defaultHeaders;
260
265
  private endpoints;
261
266
  private authProvider;
267
+ private onUnauthorized;
262
268
  constructor(config: HttpDriverOptions);
263
269
  setAuthProvider(provider: AuthProvider$1): void;
270
+ /** @internal — set callback to refresh token on 401 */
271
+ setOnUnauthorized(handler: () => Promise<boolean>): void;
264
272
  registerEndpoint(resource: string, endpoint: string): void;
265
273
  private getEndpoint;
266
274
  private buildUrl;
@@ -303,6 +311,8 @@ interface AuthState {
303
311
  userName?: string;
304
312
  role?: string;
305
313
  token: string;
314
+ refreshToken?: string;
315
+ expiresAt?: number;
306
316
  }
307
317
  interface AuthContext {
308
318
  userId: string;
@@ -313,6 +323,8 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
313
323
  private saveState;
314
324
  private httpDriver;
315
325
  private authChangeListeners;
326
+ private refreshTimer;
327
+ private _isRefreshing;
316
328
  /** @internal — called by createClient to wire persistence */
317
329
  _initAuth(loadState: () => AuthState | null, saveState: (state: AuthState | null) => void): void;
318
330
  /** @internal — called by createClient when using HttpDriver */
@@ -320,16 +332,32 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
320
332
  login(credentials: LoginCredentials): Promise<T>;
321
333
  register(data: Partial<T>): Promise<T>;
322
334
  logout(): void;
335
+ /** Manually refresh the token. Returns the new token. */
336
+ refresh(): Promise<string>;
337
+ /**
338
+ * Ensure the token is valid before making a request.
339
+ * If expired, auto-refreshes. Safe to call concurrently.
340
+ */
341
+ ensureValidToken(): Promise<void>;
323
342
  get currentUser(): T | null;
324
343
  get isLoggedIn(): boolean;
325
344
  get token(): string | null;
345
+ get refreshToken(): string | null;
346
+ get expiresAt(): number | null;
347
+ get isExpired(): boolean;
326
348
  hasRole(role: string): boolean;
327
349
  getAuthContext(): AuthContext | null;
328
350
  private localLogin;
329
351
  private localRegister;
352
+ private localRefresh;
330
353
  private httpLogin;
331
354
  private httpRegister;
355
+ private httpRefresh;
356
+ private setAuthFromResponse;
332
357
  private generateToken;
358
+ private generateRefreshToken;
359
+ private scheduleRefresh;
360
+ private clearRefreshTimer;
333
361
  /** @internal — called by createClient to listen for auth state changes */
334
362
  _onAuthChange(listener: () => void): void;
335
363
  private persistState;
@@ -378,7 +406,11 @@ declare class LocalDriver implements Driver {
378
406
  private authProvider;
379
407
  private _ready;
380
408
  private _isReady;
409
+ private latencyMs;
410
+ private errorRate;
381
411
  constructor(config: LocalDriverConfig);
412
+ private simulate;
413
+ private getLatency;
382
414
  get ready(): Promise<void>;
383
415
  get isReady(): boolean;
384
416
  setAuthProvider(provider: AuthProvider): void;
@@ -445,7 +477,10 @@ interface Preset {
445
477
  loginUrl: string;
446
478
  registerUrl: string;
447
479
  logoutUrl?: string;
480
+ refreshUrl?: string;
448
481
  tokenField: string;
482
+ refreshTokenField?: string;
483
+ expiresInField?: string;
449
484
  userField: string;
450
485
  headerFormat: string;
451
486
  };
package/dist/index.d.ts CHANGED
@@ -50,6 +50,11 @@ interface LocalDriverConfig {
50
50
  type: 'local';
51
51
  persist?: 'memory' | 'localStorage' | 'indexeddb';
52
52
  dbName?: string;
53
+ latency?: number | {
54
+ min: number;
55
+ max: number;
56
+ };
57
+ errorRate?: number;
53
58
  }
54
59
  interface HttpDriverConfig {
55
60
  type: 'http';
@@ -259,8 +264,11 @@ declare class HttpDriver implements Driver {
259
264
  private defaultHeaders;
260
265
  private endpoints;
261
266
  private authProvider;
267
+ private onUnauthorized;
262
268
  constructor(config: HttpDriverOptions);
263
269
  setAuthProvider(provider: AuthProvider$1): void;
270
+ /** @internal — set callback to refresh token on 401 */
271
+ setOnUnauthorized(handler: () => Promise<boolean>): void;
264
272
  registerEndpoint(resource: string, endpoint: string): void;
265
273
  private getEndpoint;
266
274
  private buildUrl;
@@ -303,6 +311,8 @@ interface AuthState {
303
311
  userName?: string;
304
312
  role?: string;
305
313
  token: string;
314
+ refreshToken?: string;
315
+ expiresAt?: number;
306
316
  }
307
317
  interface AuthContext {
308
318
  userId: string;
@@ -313,6 +323,8 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
313
323
  private saveState;
314
324
  private httpDriver;
315
325
  private authChangeListeners;
326
+ private refreshTimer;
327
+ private _isRefreshing;
316
328
  /** @internal — called by createClient to wire persistence */
317
329
  _initAuth(loadState: () => AuthState | null, saveState: (state: AuthState | null) => void): void;
318
330
  /** @internal — called by createClient when using HttpDriver */
@@ -320,16 +332,32 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
320
332
  login(credentials: LoginCredentials): Promise<T>;
321
333
  register(data: Partial<T>): Promise<T>;
322
334
  logout(): void;
335
+ /** Manually refresh the token. Returns the new token. */
336
+ refresh(): Promise<string>;
337
+ /**
338
+ * Ensure the token is valid before making a request.
339
+ * If expired, auto-refreshes. Safe to call concurrently.
340
+ */
341
+ ensureValidToken(): Promise<void>;
323
342
  get currentUser(): T | null;
324
343
  get isLoggedIn(): boolean;
325
344
  get token(): string | null;
345
+ get refreshToken(): string | null;
346
+ get expiresAt(): number | null;
347
+ get isExpired(): boolean;
326
348
  hasRole(role: string): boolean;
327
349
  getAuthContext(): AuthContext | null;
328
350
  private localLogin;
329
351
  private localRegister;
352
+ private localRefresh;
330
353
  private httpLogin;
331
354
  private httpRegister;
355
+ private httpRefresh;
356
+ private setAuthFromResponse;
332
357
  private generateToken;
358
+ private generateRefreshToken;
359
+ private scheduleRefresh;
360
+ private clearRefreshTimer;
333
361
  /** @internal — called by createClient to listen for auth state changes */
334
362
  _onAuthChange(listener: () => void): void;
335
363
  private persistState;
@@ -378,7 +406,11 @@ declare class LocalDriver implements Driver {
378
406
  private authProvider;
379
407
  private _ready;
380
408
  private _isReady;
409
+ private latencyMs;
410
+ private errorRate;
381
411
  constructor(config: LocalDriverConfig);
412
+ private simulate;
413
+ private getLatency;
382
414
  get ready(): Promise<void>;
383
415
  get isReady(): boolean;
384
416
  setAuthProvider(provider: AuthProvider): void;
@@ -445,7 +477,10 @@ interface Preset {
445
477
  loginUrl: string;
446
478
  registerUrl: string;
447
479
  logoutUrl?: string;
480
+ refreshUrl?: string;
448
481
  tokenField: string;
482
+ refreshTokenField?: string;
483
+ expiresInField?: string;
449
484
  userField: string;
450
485
  headerFormat: string;
451
486
  };
package/dist/index.js CHANGED
@@ -277,10 +277,15 @@ var AuthService = class extends Service {
277
277
  saveState = null;
278
278
  httpDriver = null;
279
279
  authChangeListeners = [];
280
+ refreshTimer = null;
281
+ _isRefreshing = null;
280
282
  /** @internal — called by createClient to wire persistence */
281
283
  _initAuth(loadState, saveState) {
282
284
  this.saveState = saveState;
283
285
  this.authState = loadState();
286
+ if (this.authState?.expiresAt) {
287
+ this.scheduleRefresh();
288
+ }
284
289
  }
285
290
  /** @internal — called by createClient when using HttpDriver */
286
291
  _setHttpMode(driver) {
@@ -299,9 +304,38 @@ var AuthService = class extends Service {
299
304
  return this.localRegister(data);
300
305
  }
301
306
  logout() {
307
+ this.clearRefreshTimer();
302
308
  this.authState = null;
303
309
  this.persistState();
304
310
  }
311
+ /** Manually refresh the token. Returns the new token. */
312
+ async refresh() {
313
+ if (!this.authState?.refreshToken) {
314
+ throw new ForbiddenError("No refresh token available");
315
+ }
316
+ if (this.httpDriver) {
317
+ return this.httpRefresh();
318
+ }
319
+ return this.localRefresh();
320
+ }
321
+ /**
322
+ * Ensure the token is valid before making a request.
323
+ * If expired, auto-refreshes. Safe to call concurrently.
324
+ */
325
+ async ensureValidToken() {
326
+ if (!this.authState) return;
327
+ if (!this.authState.expiresAt) return;
328
+ const buffer = 30 * 1e3;
329
+ if (Date.now() + buffer >= this.authState.expiresAt) {
330
+ if (!this._isRefreshing) {
331
+ this._isRefreshing = this.refresh().then(() => {
332
+ }).finally(() => {
333
+ this._isRefreshing = null;
334
+ });
335
+ }
336
+ await this._isRefreshing;
337
+ }
338
+ }
305
339
  get currentUser() {
306
340
  return this.authState ? { id: this.authState.userId, email: this.authState.email } : null;
307
341
  }
@@ -311,6 +345,16 @@ var AuthService = class extends Service {
311
345
  get token() {
312
346
  return this.authState?.token ?? null;
313
347
  }
348
+ get refreshToken() {
349
+ return this.authState?.refreshToken ?? null;
350
+ }
351
+ get expiresAt() {
352
+ return this.authState?.expiresAt ?? null;
353
+ }
354
+ get isExpired() {
355
+ if (!this.authState?.expiresAt) return false;
356
+ return Date.now() >= this.authState.expiresAt;
357
+ }
314
358
  hasRole(role) {
315
359
  return this.authState?.role === role;
316
360
  }
@@ -321,7 +365,7 @@ var AuthService = class extends Service {
321
365
  userName: this.authState.userName
322
366
  };
323
367
  }
324
- // --- Local mode (original implementation) ---
368
+ // --- Local mode ---
325
369
  async localLogin(credentials) {
326
370
  const { items } = await this.list({ filter: { email: credentials.email } });
327
371
  if (items.length === 0) {
@@ -331,14 +375,18 @@ var AuthService = class extends Service {
331
375
  if (user.password !== credentials.password) {
332
376
  throw new ForbiddenError("Invalid email or password");
333
377
  }
378
+ const expiresAt = Date.now() + 60 * 60 * 1e3;
334
379
  this.authState = {
335
380
  userId: user.id,
336
381
  email: user.email,
337
382
  userName: user.name || user.email,
338
383
  role: user.role,
339
- token: this.generateToken(user)
384
+ token: this.generateToken(user, expiresAt),
385
+ refreshToken: this.generateRefreshToken(user),
386
+ expiresAt
340
387
  };
341
388
  this.persistState();
389
+ this.scheduleRefresh();
342
390
  return user;
343
391
  }
344
392
  async localRegister(data) {
@@ -351,16 +399,33 @@ var AuthService = class extends Service {
351
399
  }
352
400
  const { data: user } = await this.create(data);
353
401
  const u = user;
402
+ const expiresAt = Date.now() + 60 * 60 * 1e3;
354
403
  this.authState = {
355
404
  userId: u.id,
356
405
  email: u.email,
357
406
  userName: u.name || u.email,
358
407
  role: u.role,
359
- token: this.generateToken(u)
408
+ token: this.generateToken(u, expiresAt),
409
+ refreshToken: this.generateRefreshToken(u),
410
+ expiresAt
360
411
  };
361
412
  this.persistState();
413
+ this.scheduleRefresh();
362
414
  return user;
363
415
  }
416
+ async localRefresh() {
417
+ const payload = JSON.parse(atob(this.authState.refreshToken));
418
+ const expiresAt = Date.now() + 60 * 60 * 1e3;
419
+ this.authState = {
420
+ ...this.authState,
421
+ token: this.generateToken(payload, expiresAt),
422
+ refreshToken: this.generateRefreshToken(payload),
423
+ expiresAt
424
+ };
425
+ this.persistState();
426
+ this.scheduleRefresh();
427
+ return this.authState.token;
428
+ }
364
429
  // --- HTTP mode ---
365
430
  async httpLogin(credentials) {
366
431
  const preset = this.httpDriver.preset;
@@ -382,17 +447,8 @@ var AuthService = class extends Service {
382
447
  throw new ForbiddenError(body2.message ?? "Login failed");
383
448
  }
384
449
  const body = await response.json();
385
- const token = body[preset.auth.tokenField];
386
- const user = body[preset.auth.userField] ?? body;
387
- this.authState = {
388
- userId: user.id,
389
- email: user.email ?? credentials.email,
390
- userName: user.name || user.email || credentials.email,
391
- role: user.role,
392
- token
393
- };
394
- this.persistState();
395
- return user;
450
+ this.setAuthFromResponse(body, preset, credentials.email);
451
+ return body[preset.auth.userField] ?? body;
396
452
  }
397
453
  async httpRegister(data) {
398
454
  const preset = this.httpDriver.preset;
@@ -411,27 +467,94 @@ var AuthService = class extends Service {
411
467
  throw new ForbiddenError(body2.message ?? "Registration failed");
412
468
  }
413
469
  const body = await response.json();
470
+ this.setAuthFromResponse(body, preset, data.email);
471
+ return body[preset.auth.userField] ?? body;
472
+ }
473
+ async httpRefresh() {
474
+ const preset = this.httpDriver.preset;
475
+ const baseUrl = this.httpDriver.baseUrl;
476
+ const refreshUrl = preset.auth.refreshUrl;
477
+ if (!refreshUrl) {
478
+ throw new ForbiddenError("Refresh URL not configured in preset");
479
+ }
480
+ const response = await fetch(`${baseUrl}${refreshUrl}`, {
481
+ method: "POST",
482
+ headers: { "Content-Type": "application/json" },
483
+ body: JSON.stringify({ refreshToken: this.authState.refreshToken })
484
+ });
485
+ if (!response.ok) {
486
+ this.logout();
487
+ throw new ForbiddenError("Session expired. Please log in again.");
488
+ }
489
+ const body = await response.json();
490
+ const token = body[preset.auth.tokenField];
491
+ const refreshToken = preset.auth.refreshTokenField ? body[preset.auth.refreshTokenField] : this.authState.refreshToken;
492
+ const expiresIn = preset.auth.expiresInField ? body[preset.auth.expiresInField] : null;
493
+ const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
494
+ this.authState = {
495
+ ...this.authState,
496
+ token,
497
+ refreshToken,
498
+ expiresAt
499
+ };
500
+ this.persistState();
501
+ this.scheduleRefresh();
502
+ return token;
503
+ }
504
+ setAuthFromResponse(body, preset, fallbackEmail) {
414
505
  const token = body[preset.auth.tokenField];
415
506
  const user = body[preset.auth.userField] ?? body;
507
+ const refreshToken = preset.auth.refreshTokenField ? body[preset.auth.refreshTokenField] : void 0;
508
+ const expiresIn = preset.auth.expiresInField ? body[preset.auth.expiresInField] : null;
509
+ const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
416
510
  this.authState = {
417
511
  userId: user.id,
418
- email: user.email ?? data.email,
419
- userName: user.name || user.email || data.email,
512
+ email: user.email ?? fallbackEmail,
513
+ userName: user.name || user.email || fallbackEmail,
420
514
  role: user.role,
421
- token
515
+ token,
516
+ refreshToken,
517
+ expiresAt
422
518
  };
423
519
  this.persistState();
424
- return user;
520
+ this.scheduleRefresh();
425
521
  }
426
- generateToken(user) {
522
+ // --- Token generation (local mode) ---
523
+ generateToken(user, expiresAt) {
427
524
  return btoa(JSON.stringify({
428
- userId: user.id,
525
+ userId: user.id ?? user.userId,
429
526
  email: user.email,
430
527
  role: user.role,
431
528
  iat: Date.now(),
432
- exp: Date.now() + 24 * 60 * 60 * 1e3
529
+ exp: expiresAt
530
+ }));
531
+ }
532
+ generateRefreshToken(user) {
533
+ return btoa(JSON.stringify({
534
+ userId: user.id ?? user.userId,
535
+ email: user.email,
536
+ role: user.role,
537
+ type: "refresh",
538
+ iat: Date.now()
433
539
  }));
434
540
  }
541
+ // --- Refresh scheduling ---
542
+ scheduleRefresh() {
543
+ this.clearRefreshTimer();
544
+ if (!this.authState?.expiresAt) return;
545
+ const delay = this.authState.expiresAt - Date.now() - 60 * 1e3;
546
+ if (delay <= 0) return;
547
+ this.refreshTimer = setTimeout(() => {
548
+ this.refresh().catch(() => {
549
+ });
550
+ }, delay);
551
+ }
552
+ clearRefreshTimer() {
553
+ if (this.refreshTimer) {
554
+ clearTimeout(this.refreshTimer);
555
+ this.refreshTimer = null;
556
+ }
557
+ }
435
558
  /** @internal — called by createClient to listen for auth state changes */
436
559
  _onAuthChange(listener) {
437
560
  this.authChangeListeners.push(listener);
@@ -746,7 +869,11 @@ var LocalDriver = class {
746
869
  authProvider = null;
747
870
  _ready;
748
871
  _isReady;
872
+ latencyMs;
873
+ errorRate;
749
874
  constructor(config) {
875
+ this.latencyMs = config.latency ?? 0;
876
+ this.errorRate = config.errorRate ?? 0;
750
877
  if (config.persist === "indexeddb") {
751
878
  const backend = new IndexedDBBackend(config.dbName ?? "fauxbase");
752
879
  this.storage = backend;
@@ -760,6 +887,27 @@ var LocalDriver = class {
760
887
  this._ready = Promise.resolve();
761
888
  }
762
889
  }
890
+ async simulate() {
891
+ if (this.errorRate > 0 && Math.random() < this.errorRate) {
892
+ const errors = [
893
+ () => new NetworkError("Simulated network failure"),
894
+ () => new TimeoutError("Simulated request timeout"),
895
+ () => new NetworkError("Simulated connection refused")
896
+ ];
897
+ const delay2 = this.getLatency();
898
+ if (delay2 > 0) await new Promise((r) => setTimeout(r, delay2 / 2));
899
+ throw errors[Math.floor(Math.random() * errors.length)]();
900
+ }
901
+ const delay = this.getLatency();
902
+ if (delay > 0) {
903
+ await new Promise((r) => setTimeout(r, delay));
904
+ }
905
+ }
906
+ getLatency() {
907
+ if (typeof this.latencyMs === "number") return this.latencyMs;
908
+ const { min, max } = this.latencyMs;
909
+ return Math.floor(Math.random() * (max - min + 1)) + min;
910
+ }
763
911
  get ready() {
764
912
  return this._ready;
765
913
  }
@@ -776,12 +924,14 @@ var LocalDriver = class {
776
924
  this.entityClasses.set(resource, entityClass);
777
925
  }
778
926
  async list(resource, query) {
927
+ await this.simulate();
779
928
  const items = this.storage.getAll(resource);
780
929
  const entityClass = this.entityClasses.get(resource);
781
930
  const processed = entityClass ? items.map((item) => applyComputedFields(item, entityClass)) : items;
782
931
  return executeQuery(processed, query);
783
932
  }
784
933
  async get(resource, id) {
934
+ await this.simulate();
785
935
  const item = this.storage.getById(resource, id);
786
936
  if (!item || item.deletedAt) {
787
937
  throw new NotFoundError(`${resource} with id "${id}" not found`);
@@ -791,6 +941,7 @@ var LocalDriver = class {
791
941
  return { data };
792
942
  }
793
943
  async create(resource, data) {
944
+ await this.simulate();
794
945
  const entityClass = this.entityClasses.get(resource);
795
946
  const now = (/* @__PURE__ */ new Date()).toISOString();
796
947
  const authContext = this.authProvider?.();
@@ -817,6 +968,7 @@ var LocalDriver = class {
817
968
  return { data: result };
818
969
  }
819
970
  async update(resource, id, data) {
971
+ await this.simulate();
820
972
  const existing = this.storage.getById(resource, id);
821
973
  if (!existing || existing.deletedAt) {
822
974
  throw new NotFoundError(`${resource} with id "${id}" not found`);
@@ -843,6 +995,7 @@ var LocalDriver = class {
843
995
  return { data: result };
844
996
  }
845
997
  async delete(resource, id) {
998
+ await this.simulate();
846
999
  const existing = this.storage.getById(resource, id);
847
1000
  if (!existing || existing.deletedAt) {
848
1001
  throw new NotFoundError(`${resource} with id "${id}" not found`);
@@ -865,6 +1018,7 @@ var LocalDriver = class {
865
1018
  return { data: record };
866
1019
  }
867
1020
  async count(resource, filter) {
1021
+ await this.simulate();
868
1022
  let items = this.storage.getAll(resource).filter((item) => !item.deletedAt);
869
1023
  if (filter) {
870
1024
  items = applyFilters(items, filter);
@@ -1004,7 +1158,10 @@ var springBootPreset = definePreset({
1004
1158
  auth: {
1005
1159
  loginUrl: "/api/auth/login",
1006
1160
  registerUrl: "/api/auth/register",
1161
+ refreshUrl: "/api/auth/refresh",
1007
1162
  tokenField: "token",
1163
+ refreshTokenField: "refreshToken",
1164
+ expiresInField: "expiresIn",
1008
1165
  userField: "user",
1009
1166
  headerFormat: "Bearer {token}"
1010
1167
  }
@@ -1244,6 +1401,7 @@ var HttpDriver = class {
1244
1401
  defaultHeaders;
1245
1402
  endpoints = /* @__PURE__ */ new Map();
1246
1403
  authProvider = null;
1404
+ onUnauthorized = null;
1247
1405
  constructor(config) {
1248
1406
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
1249
1407
  this.preset = typeof config.preset === "string" ? getPreset(config.preset ?? "default") : config.preset ?? getPreset("default");
@@ -1255,6 +1413,10 @@ var HttpDriver = class {
1255
1413
  setAuthProvider(provider) {
1256
1414
  this.authProvider = provider;
1257
1415
  }
1416
+ /** @internal — set callback to refresh token on 401 */
1417
+ setOnUnauthorized(handler) {
1418
+ this.onUnauthorized = handler;
1419
+ }
1258
1420
  registerEndpoint(resource, endpoint) {
1259
1421
  this.endpoints.set(resource, endpoint);
1260
1422
  }
@@ -1288,6 +1450,12 @@ var HttpDriver = class {
1288
1450
  });
1289
1451
  clearTimeout(timer);
1290
1452
  if (!response.ok) {
1453
+ if (response.status === 401 && retryCount === 0 && this.onUnauthorized) {
1454
+ const refreshed = await this.onUnauthorized();
1455
+ if (refreshed) {
1456
+ return this._fetch(url, options, 1);
1457
+ }
1458
+ }
1291
1459
  if (response.status >= 500 && retryCount < this.maxRetries) {
1292
1460
  const delay = this.baseDelay * Math.pow(2, retryCount);
1293
1461
  await new Promise((r) => setTimeout(r, delay));
@@ -1651,10 +1819,25 @@ function createClient(config) {
1651
1819
  } else if (defaultDriver instanceof HttpDriver) {
1652
1820
  authInstance._init(defaultDriver, resourceName);
1653
1821
  defaultDriver.registerEndpoint(resourceName, authInstance.endpoint);
1822
+ const hasLocalStorage = typeof localStorage !== "undefined";
1823
+ const LS_AUTH_KEY = "fauxbase:auth";
1654
1824
  let memoryAuthState = null;
1655
1825
  authInstance._initAuth(
1656
- () => memoryAuthState,
1826
+ () => {
1827
+ if (hasLocalStorage) {
1828
+ const raw = localStorage.getItem(LS_AUTH_KEY);
1829
+ return raw ? JSON.parse(raw) : null;
1830
+ }
1831
+ return memoryAuthState;
1832
+ },
1657
1833
  (state) => {
1834
+ if (hasLocalStorage) {
1835
+ if (state) {
1836
+ localStorage.setItem(LS_AUTH_KEY, JSON.stringify(state));
1837
+ } else {
1838
+ localStorage.removeItem(LS_AUTH_KEY);
1839
+ }
1840
+ }
1658
1841
  memoryAuthState = state;
1659
1842
  }
1660
1843
  );
@@ -1663,6 +1846,14 @@ function createClient(config) {
1663
1846
  const token = authInstance.token;
1664
1847
  return token ? { token } : null;
1665
1848
  });
1849
+ defaultDriver.setOnUnauthorized(async () => {
1850
+ try {
1851
+ await authInstance.refresh();
1852
+ return true;
1853
+ } catch {
1854
+ return false;
1855
+ }
1856
+ });
1666
1857
  }
1667
1858
  client.auth = authInstance;
1668
1859
  for (const driver of overrideDrivers.values()) {
@@ -1671,6 +1862,14 @@ function createClient(config) {
1671
1862
  const token = client.auth?.token;
1672
1863
  return token ? { token } : null;
1673
1864
  });
1865
+ driver.setOnUnauthorized(async () => {
1866
+ try {
1867
+ await client.auth.refresh();
1868
+ return true;
1869
+ } catch {
1870
+ return false;
1871
+ }
1872
+ });
1674
1873
  }
1675
1874
  }
1676
1875
  }