@aurabase/core 0.7.0 → 0.7.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/dist/index.cjs CHANGED
@@ -76,6 +76,12 @@ var AuraError = class _AuraError extends Error {
76
76
  const hint = body?.error?.hint;
77
77
  if (typeof hint === "string" && hint) {
78
78
  this.hint = hint;
79
+ } else {
80
+ const serverDetails = JSON.stringify(body ?? "");
81
+ const isJsonCastError = rawMessage.includes("invalid input syntax for type json") || serverDetails.includes("22P02") && serverDetails.includes("json");
82
+ if (isJsonCastError) {
83
+ this.hint = "To filter on a text value inside a JSON column, use the '->>' operator instead of '->' (e.g. .eq('metadata->>theme', 'dark')).";
84
+ }
79
85
  }
80
86
  const rawCode = typeof body?.error?.code === "string" ? body.error.code : void 0;
81
87
  const canonical = rawCode ? normalizeErrorCode(rawCode) : void 0;
@@ -96,8 +102,12 @@ var AuraError = class _AuraError extends Error {
96
102
  this.code = "SESSION_REVOKED";
97
103
  } else if (status === 401 && lowerMsg.includes("expired")) {
98
104
  this.code = "SESSION_EXPIRED";
99
- } else if (status === 400 && lowerMsg.includes("password")) {
105
+ } else if (status === 400 && (lowerMsg.includes("current password") || lowerMsg.includes("old password") || lowerMsg.includes("password") && lowerMsg.includes("required"))) {
106
+ this.code = lowerMsg.includes("incorrect") || lowerMsg.includes("invalid") ? "INVALID_CURRENT_PASSWORD" : "CURRENT_PASSWORD_REQUIRED";
107
+ } else if (status === 400 && lowerMsg.includes("password") && (lowerMsg.includes("weak") || lowerMsg.includes("length") || lowerMsg.includes("strength") || lowerMsg.includes("character") || lowerMsg.includes("digit") || lowerMsg.includes("pwned") || lowerMsg.includes("too short") || lowerMsg.includes("too common"))) {
100
108
  this.code = "WEAK_PASSWORD";
109
+ } else if (status === 400 && lowerMsg.includes("password")) {
110
+ this.code = "INVALID_PASSWORD";
101
111
  } else if (status === 409 && (lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
102
112
  this.code = "EMAIL_ALREADY_IN_USE";
103
113
  } else if (status === 404 && lowerMsg.includes("user not found")) {
@@ -279,6 +289,33 @@ var AUTH_SETTINGS_WRITABLE_FIELDS = [
279
289
  "hook_secret"
280
290
  ];
281
291
 
292
+ // src/cookie-utils.ts
293
+ function parseCookieHeader(header) {
294
+ const result = {};
295
+ if (!header) return result;
296
+ for (const pair of header.split(";")) {
297
+ const idx = pair.indexOf("=");
298
+ if (idx === -1) continue;
299
+ const key = pair.slice(0, idx).trim();
300
+ const value = pair.slice(idx + 1).trim();
301
+ if (key) result[key] = decodeURIComponent(value);
302
+ }
303
+ return result;
304
+ }
305
+ function sameSiteAttribute(v) {
306
+ return v.charAt(0).toUpperCase() + v.slice(1);
307
+ }
308
+ function serializeCookieHeader(name, value, options = {}) {
309
+ const parts = [`${name}=${encodeURIComponent(value)}`];
310
+ if (options.path) parts.push(`Path=${options.path}`);
311
+ if (options.domain) parts.push(`Domain=${options.domain}`);
312
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
313
+ if (options.secure) parts.push("Secure");
314
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
315
+ if (options.httpOnly) parts.push("HttpOnly");
316
+ return parts.join("; ");
317
+ }
318
+
282
319
  // src/stores/BaseAuthStore.ts
283
320
  function decodeJwtPayload(token) {
284
321
  try {
@@ -323,6 +360,7 @@ var BaseAuthStore = class {
323
360
  * le flux PKCE : le `code_verifier` doit survivre au redirect OAuth.
324
361
  */
325
362
  persistent = false;
363
+ key;
326
364
  _token = null;
327
365
  _refreshToken = null;
328
366
  _user = null;
@@ -333,6 +371,9 @@ var BaseAuthStore = class {
333
371
  /** Hook d'observabilité (LOT 3) — câblé par `AuraClient` via `setObservabilityHook`. */
334
372
  _onEvent;
335
373
  _debug = false;
374
+ constructor(key = "aura-auth") {
375
+ this.key = key;
376
+ }
336
377
  // -- Getters ----------------------------------------------------------------
337
378
  /** Compteur incrémenté à chaque `clear()` — sert au commit-guard anti-race du refresh. */
338
379
  get removalEpoch() {
@@ -394,6 +435,91 @@ var BaseAuthStore = class {
394
435
  _emitStorageBlocked(op) {
395
436
  emitAuraEvent(this._onEvent, this._debug, { kind: "storage-blocked", op });
396
437
  }
438
+ /**
439
+ * Populate the store from a raw `Cookie` header string (server-side).
440
+ * Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
441
+ * automatically reassembled in order (AUTH-010).
442
+ */
443
+ loadFromCookie(cookieString) {
444
+ if (!cookieString) return;
445
+ const entries = cookieString.split(";").map((c) => c.trim()).filter(Boolean);
446
+ let encodedData = null;
447
+ const prefix = `${this.key}=`;
448
+ const singleEntry = entries.find((c) => c.startsWith(prefix));
449
+ if (singleEntry) {
450
+ encodedData = singleEntry.slice(prefix.length);
451
+ } else {
452
+ const escapedKey = this.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
453
+ const chunkRegex = new RegExp(`^${escapedKey}\\.(\\d+)=(.*)$`);
454
+ const chunks = [];
455
+ for (const entry of entries) {
456
+ const match = entry.match(chunkRegex);
457
+ if (match) {
458
+ chunks.push({
459
+ index: parseInt(match[1], 10),
460
+ value: match[2]
461
+ });
462
+ }
463
+ }
464
+ if (chunks.length > 0) {
465
+ chunks.sort((a, b) => a.index - b.index);
466
+ encodedData = chunks.map((c) => c.value).join("");
467
+ }
468
+ }
469
+ if (!encodedData) return;
470
+ try {
471
+ const raw = decodeURIComponent(encodedData);
472
+ const parsed = JSON.parse(raw);
473
+ const user = parsed.token ? userFromAccessToken(parsed.token) : null;
474
+ if (parsed.token && parsed.refreshToken && user) {
475
+ this.save(parsed.token, parsed.refreshToken, user);
476
+ }
477
+ } catch {
478
+ }
479
+ }
480
+ /**
481
+ * Serialize the session tokens into an array of `Set-Cookie` header strings.
482
+ * If the payload exceeds `maxChunkSize` (default 3500 bytes), it is automatically split into
483
+ * numbered chunks `${key}.0`, `${key}.1`, etc. to avoid browser cookie size limits (AUTH-010).
484
+ */
485
+ exportToCookies(options = {}, maxChunkSize = 3500) {
486
+ const data = {
487
+ token: this.token,
488
+ refreshToken: this.refreshToken
489
+ };
490
+ const encoded = encodeURIComponent(JSON.stringify(data));
491
+ const buildCookie = (name, value) => {
492
+ const parts = [`${name}=${value}`];
493
+ if (options.path) parts.push(`Path=${options.path}`);
494
+ if (options.domain) parts.push(`Domain=${options.domain}`);
495
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
496
+ if (options.secure) parts.push("Secure");
497
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
498
+ if (options.httpOnly) parts.push("HttpOnly");
499
+ return parts.join("; ");
500
+ };
501
+ const limit = Math.max(1, maxChunkSize);
502
+ if (encoded.length <= limit) {
503
+ return [buildCookie(this.key, encoded)];
504
+ }
505
+ const cookies = [];
506
+ let offset = 0;
507
+ let index = 0;
508
+ while (offset < encoded.length) {
509
+ const chunk = encoded.slice(offset, offset + limit);
510
+ cookies.push(buildCookie(`${this.key}.${index}`, chunk));
511
+ offset += limit;
512
+ index++;
513
+ }
514
+ return cookies;
515
+ }
516
+ /**
517
+ * Serialize the session tokens into a single `Set-Cookie` value.
518
+ * Note: If the token payload may exceed 3.5KB, use `exportToCookies()` instead for automatic chunking (AUTH-010).
519
+ */
520
+ exportToCookie(options = {}) {
521
+ return this.exportToCookies(options, Infinity)[0];
522
+ }
397
523
  // -- Event emitter ----------------------------------------------------------
398
524
  /**
399
525
  * Register a callback that fires whenever the auth state changes.
@@ -437,14 +563,42 @@ function unwrapEnvelope(body) {
437
563
  }
438
564
  return body;
439
565
  }
566
+ function parseJsonBody(raw, parseBigIntAsString) {
567
+ if (parseBigIntAsString) {
568
+ const safe = raw.replace(/([:\[,]\s*)(-?\d{16,})(?=\s*[,}\]])/g, '$1"$2"');
569
+ return JSON.parse(safe);
570
+ }
571
+ return JSON.parse(raw);
572
+ }
440
573
  function parseContentRangeTotal(headerVal) {
441
- if (!headerVal) return void 0;
574
+ return parseContentRangeDetails(headerVal).total;
575
+ }
576
+ function parseContentRangeDetails(headerVal) {
577
+ if (!headerVal) return { total: void 0, hasMore: void 0 };
442
578
  const parts = headerVal.split("/");
443
- if (parts.length === 2 && parts[1] !== "*") {
444
- const n = parseInt(parts[1], 10);
445
- if (!Number.isNaN(n)) return n;
579
+ const totalStr = parts[1];
580
+ let total;
581
+ if (totalStr && totalStr !== "*") {
582
+ const n = parseInt(totalStr, 10);
583
+ if (!Number.isNaN(n)) total = n;
584
+ }
585
+ let hasMore;
586
+ const rangePart = parts[0];
587
+ if (rangePart) {
588
+ const rangeBounds = rangePart.split("-");
589
+ if (rangeBounds.length === 2) {
590
+ const start = parseInt(rangeBounds[0], 10);
591
+ const end = parseInt(rangeBounds[1], 10);
592
+ if (!Number.isNaN(start) && !Number.isNaN(end)) {
593
+ if (total !== void 0) {
594
+ hasMore = end + 1 < total;
595
+ } else if (end - start + 1 >= 1e3) {
596
+ hasMore = true;
597
+ }
598
+ }
599
+ }
446
600
  }
447
- return void 0;
601
+ return { total, hasMore };
448
602
  }
449
603
  async function readTextBounded(res, timeoutMs) {
450
604
  const body = res.body;
@@ -837,7 +991,7 @@ async function fetchWithAuth(url, init, client) {
837
991
  }
838
992
  let body;
839
993
  try {
840
- body = JSON.parse(raw);
994
+ body = parseJsonBody(raw, client.parseBigIntAsString === true);
841
995
  } catch {
842
996
  const ctRaw = res.headers.get("Content-Type") ?? "unknown";
843
997
  const ct = ctRaw.length > 64 ? `${ctRaw.slice(0, 64)}\u2026` : ctRaw;
@@ -853,10 +1007,21 @@ async function fetchWithAuth(url, init, client) {
853
1007
  }
854
1008
  const unwrapped = unwrapEnvelope(body);
855
1009
  const cr = res.headers.get("content-range") ?? res.headers.get("Content-Range");
856
- const totalFromCr = parseContentRangeTotal(cr);
1010
+ const crDetails = parseContentRangeDetails(cr);
1011
+ const totalFromCr = crDetails.total;
1012
+ const hasMore = crDetails.hasMore;
857
1013
  const rawMeta = body !== null && typeof body === "object" && "meta" in body ? body.meta : void 0;
858
- const meta = rawMeta !== void 0 ? rawMeta : totalFromCr !== void 0 ? { total: totalFromCr } : void 0;
1014
+ const meta = rawMeta !== void 0 ? { ...rawMeta, ...hasMore !== void 0 ? { has_more: hasMore } : {} } : totalFromCr !== void 0 || hasMore !== void 0 ? {
1015
+ ...totalFromCr !== void 0 ? { total: totalFromCr } : {},
1016
+ ...hasMore !== void 0 ? { has_more: hasMore } : {}
1017
+ } : void 0;
859
1018
  const count = meta?.total !== void 0 ? meta.total : totalFromCr ?? null;
1019
+ const isDev = Boolean(client.debug) || typeof globalThis !== "undefined" && globalThis.process?.env?.NODE_ENV === "development";
1020
+ if (hasMore && isDev) {
1021
+ console.warn(
1022
+ "[Aurabase] Query result reached pagination limit of 1,000 rows. Use .range() to paginate further results."
1023
+ );
1024
+ }
860
1025
  if (unwrapped === null) {
861
1026
  return { data: null, error: null, meta, count };
862
1027
  }
@@ -888,45 +1053,16 @@ var BaseService = class {
888
1053
  }
889
1054
  };
890
1055
 
891
- // src/cookie-utils.ts
892
- function parseCookieHeader(header) {
893
- const result = {};
894
- if (!header) return result;
895
- for (const pair of header.split(";")) {
896
- const idx = pair.indexOf("=");
897
- if (idx === -1) continue;
898
- const key = pair.slice(0, idx).trim();
899
- const value = pair.slice(idx + 1).trim();
900
- if (key) result[key] = decodeURIComponent(value);
901
- }
902
- return result;
903
- }
904
- function sameSiteAttribute(v) {
905
- return v.charAt(0).toUpperCase() + v.slice(1);
906
- }
907
- function serializeCookieHeader(name, value, options = {}) {
908
- const parts = [`${name}=${encodeURIComponent(value)}`];
909
- if (options.path) parts.push(`Path=${options.path}`);
910
- if (options.domain) parts.push(`Domain=${options.domain}`);
911
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
912
- if (options.secure) parts.push("Secure");
913
- if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
914
- if (options.httpOnly) parts.push("HttpOnly");
915
- return parts.join("; ");
916
- }
917
-
918
1056
  // src/stores/LocalAuthStore.ts
919
1057
  var LocalAuthStore = class extends BaseAuthStore {
920
1058
  /** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
921
1059
  persistent = true;
922
- key;
923
1060
  /**
924
1061
  * @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
925
1062
  * `aura-${clientScope(baseUrl)}-auth`.
926
1063
  */
927
1064
  constructor(key) {
928
- super();
929
- this.key = key;
1065
+ super(key);
930
1066
  this._loadFromStorage();
931
1067
  }
932
1068
  // -- Overrides --------------------------------------------------------------
@@ -997,48 +1133,6 @@ var LocalAuthStore = class extends BaseAuthStore {
997
1133
  this._emitStorageBlocked("removeItem");
998
1134
  }
999
1135
  }
1000
- // -- Cookie helpers (SSR) ---------------------------------------------------
1001
- /**
1002
- * Populate the store from a raw `Cookie` header string (server-side).
1003
- * Looks for the key matching `this.key`.
1004
- *
1005
- * Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
1006
- * claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
1007
- */
1008
- loadFromCookie(cookieString) {
1009
- const prefix = `${this.key}=`;
1010
- const entry = cookieString.split(";").map((c) => c.trim()).find((c) => c.startsWith(prefix));
1011
- if (!entry) return;
1012
- try {
1013
- const raw = decodeURIComponent(entry.slice(prefix.length));
1014
- const parsed = JSON.parse(raw);
1015
- const user = parsed.token ? userFromAccessToken(parsed.token) : null;
1016
- if (parsed.token && parsed.refreshToken && user) {
1017
- super.save(parsed.token, parsed.refreshToken, user);
1018
- }
1019
- } catch {
1020
- }
1021
- }
1022
- /**
1023
- * Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
1024
- * `user_metadata` is already inside the access token, and sending it twice doubled the cookie
1025
- * (5 KB of metadata gave 4 cookies and 13 139 bytes).
1026
- */
1027
- exportToCookie(options = {}) {
1028
- const data = {
1029
- token: this.token,
1030
- refreshToken: this.refreshToken
1031
- };
1032
- const encoded = encodeURIComponent(JSON.stringify(data));
1033
- const parts = [`${this.key}=${encoded}`];
1034
- if (options.path) parts.push(`Path=${options.path}`);
1035
- if (options.domain) parts.push(`Domain=${options.domain}`);
1036
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
1037
- if (options.secure) parts.push("Secure");
1038
- if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
1039
- if (options.httpOnly) parts.push("HttpOnly");
1040
- return parts.join("; ");
1041
- }
1042
1136
  // -- Private ----------------------------------------------------------------
1043
1137
  _getStorage() {
1044
1138
  if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
@@ -1051,7 +1145,9 @@ var LocalAuthStore = class extends BaseAuthStore {
1051
1145
  const env = proc.env;
1052
1146
  const isTest = !!(env?.VITEST || env?.NODE_ENV === "test" || env?.JEST_WORKER_ID);
1053
1147
  const execArgv = proc.execArgv;
1054
- const hasFlag = Array.isArray(execArgv) && execArgv.some((a) => typeof a === "string" && a.startsWith("--localstorage-file"));
1148
+ const hasFlag = Array.isArray(execArgv) && execArgv.some(
1149
+ (a) => typeof a === "string" && a.startsWith("--localstorage-file")
1150
+ );
1055
1151
  if (isTest || hasFlag) {
1056
1152
  try {
1057
1153
  const s = g.localStorage;
package/dist/index.d.cts CHANGED
@@ -102,6 +102,10 @@ interface Meta {
102
102
  * échoue avec un 400 explicite plutôt que d'émettre ce champ.
103
103
  */
104
104
  next_cursor?: string;
105
+ /**
106
+ * DB-005 : Indique si la requête a été tronquée par le serveur (ex: limite 1 000 lignes de PostgREST).
107
+ */
108
+ has_more?: boolean;
105
109
  }
106
110
  /**
107
111
  * Réponse en SUCCÈS porteuse de contenu. `error` y est `null` par construction.
@@ -203,6 +207,8 @@ interface User {
203
207
  last_sign_in_at?: string | null;
204
208
  }
205
209
  interface Session {
210
+ /** Identifiant unique de la session (UUID ou 'sess_...'), dérivé du claim `session_id` du JWT. */
211
+ id?: string;
206
212
  access_token: string;
207
213
  refresh_token: string;
208
214
  token_type: string;
@@ -267,6 +273,7 @@ interface SignUpRequest {
267
273
  options?: {
268
274
  emailRedirectTo?: string;
269
275
  data?: Record<string, unknown>;
276
+ preventEmailEnumeration?: boolean;
270
277
  };
271
278
  }
272
279
  interface SignInRequest {
@@ -369,14 +376,25 @@ interface MfaEnrollResponse {
369
376
  * Pour une passkey, utilisez `auth.mfa.webauthn.verify`.
370
377
  */
371
378
  type MfaVerifyRequest = {
372
- mfa_token: string;
379
+ mfa_token?: string;
380
+ mfaToken?: string;
381
+ challenge_id?: string;
382
+ challengeId?: string;
373
383
  code: string;
374
384
  } | {
375
- mfa_token: string;
385
+ mfa_token?: string;
386
+ mfaToken?: string;
387
+ challenge_id?: string;
388
+ challengeId?: string;
376
389
  recovery_code: string;
390
+ recoveryCode?: string;
377
391
  } | {
378
- mfa_token: string;
392
+ mfa_token?: string;
393
+ mfaToken?: string;
394
+ challenge_id?: string;
395
+ challengeId?: string;
379
396
  sms_code: string;
397
+ smsCode?: string;
380
398
  };
381
399
  /** Facteur MFA utilisable pour relever un défi (`auth.mfa.factors`). */
382
400
  type MfaFactorType = 'totp' | 'webauthn' | 'sms';
@@ -1558,6 +1576,12 @@ interface AuraClientOptions {
1558
1576
  * Défaut `false`.
1559
1577
  */
1560
1578
  debug?: boolean;
1579
+ /**
1580
+ * DB-006: Preserves 64-bit integer precision by parsing BIGINT values as strings
1581
+ * instead of JavaScript IEEE 754 floats when exceeding Number.MAX_SAFE_INTEGER (9007199254740991).
1582
+ * Default: false (native JSON.parse).
1583
+ */
1584
+ parseBigIntAsString?: boolean;
1561
1585
  }
1562
1586
  interface IdentityInfo {
1563
1587
  id: string;
@@ -1839,6 +1863,19 @@ type AuthChangeCallback = (token: string | null, user: User | null) => void;
1839
1863
  * Check whether a JWT has expired (or will expire within `marginSec` seconds).
1840
1864
  */
1841
1865
  declare function isTokenExpired(token: string, marginSec?: number): boolean;
1866
+ interface CookieOptions {
1867
+ path?: string;
1868
+ domain?: string;
1869
+ maxAge?: number;
1870
+ secure?: boolean;
1871
+ sameSite?: 'strict' | 'lax' | 'none';
1872
+ httpOnly?: boolean;
1873
+ }
1874
+ interface StoredAuth {
1875
+ token: string | null;
1876
+ refreshToken: string | null;
1877
+ user: User | null;
1878
+ }
1842
1879
  /**
1843
1880
  * In-memory auth store with an event-emitter pattern.
1844
1881
  * Can be extended (see LocalAuthStore) for persistence.
@@ -1850,6 +1887,7 @@ declare class BaseAuthStore {
1850
1887
  * le flux PKCE : le `code_verifier` doit survivre au redirect OAuth.
1851
1888
  */
1852
1889
  readonly persistent: boolean;
1890
+ protected readonly key: string;
1853
1891
  private _token;
1854
1892
  private _refreshToken;
1855
1893
  private _user;
@@ -1860,6 +1898,7 @@ declare class BaseAuthStore {
1860
1898
  /** Hook d'observabilité (LOT 3) — câblé par `AuraClient` via `setObservabilityHook`. */
1861
1899
  private _onEvent?;
1862
1900
  private _debug;
1901
+ constructor(key?: string);
1863
1902
  /** Compteur incrémenté à chaque `clear()` — sert au commit-guard anti-race du refresh. */
1864
1903
  get removalEpoch(): number;
1865
1904
  get token(): string | null;
@@ -1881,6 +1920,23 @@ declare class BaseAuthStore {
1881
1920
  setObservabilityHook(onEvent: ((event: AuraObservabilityEvent) => void) | undefined, debug: boolean | undefined): void;
1882
1921
  /** Émet `{kind:'storage-blocked', op}` — ne throw jamais (voir `emitAuraEvent`). */
1883
1922
  protected _emitStorageBlocked(op: string): void;
1923
+ /**
1924
+ * Populate the store from a raw `Cookie` header string (server-side).
1925
+ * Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
1926
+ * automatically reassembled in order (AUTH-010).
1927
+ */
1928
+ loadFromCookie(cookieString: string): void;
1929
+ /**
1930
+ * Serialize the session tokens into an array of `Set-Cookie` header strings.
1931
+ * If the payload exceeds `maxChunkSize` (default 3500 bytes), it is automatically split into
1932
+ * numbered chunks `${key}.0`, `${key}.1`, etc. to avoid browser cookie size limits (AUTH-010).
1933
+ */
1934
+ exportToCookies(options?: CookieOptions, maxChunkSize?: number): string[];
1935
+ /**
1936
+ * Serialize the session tokens into a single `Set-Cookie` value.
1937
+ * Note: If the token payload may exceed 3.5KB, use `exportToCookies()` instead for automatic chunking (AUTH-010).
1938
+ */
1939
+ exportToCookie(options?: CookieOptions): string;
1884
1940
  /**
1885
1941
  * Register a callback that fires whenever the auth state changes.
1886
1942
  * Returns an unsubscribe function.
@@ -1930,6 +1986,8 @@ interface AuraClientLike {
1930
1986
  readonly onEvent?: (event: AuraObservabilityEvent) => void;
1931
1987
  /** Si `true`, chaque `AuraObservabilityEvent` est aussi journalisé via `console.debug`. */
1932
1988
  readonly debug?: boolean;
1989
+ /** DB-006: Preserves 64-bit integers as strings when true. */
1990
+ readonly parseBigIntAsString?: boolean;
1933
1991
  }
1934
1992
 
1935
1993
  /**
@@ -2038,14 +2096,6 @@ declare function clientScope(baseUrl: string): string;
2038
2096
  /** JSON.stringify avec support des bigint (sérialisés en string). */
2039
2097
  declare function serializeBody(body: unknown): string;
2040
2098
 
2041
- interface CookieOptions {
2042
- path?: string;
2043
- domain?: string;
2044
- maxAge?: number;
2045
- secure?: boolean;
2046
- sameSite?: 'strict' | 'lax' | 'none';
2047
- httpOnly?: boolean;
2048
- }
2049
2099
  /**
2050
2100
  * Auth store backed by `localStorage` (browser) with cookie helpers for SSR.
2051
2101
  *
@@ -2062,7 +2112,6 @@ interface CookieOptions {
2062
2112
  declare class LocalAuthStore extends BaseAuthStore {
2063
2113
  /** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
2064
2114
  readonly persistent = true;
2065
- private readonly key;
2066
2115
  /**
2067
2116
  * @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
2068
2117
  * `aura-${clientScope(baseUrl)}-auth`.
@@ -2083,24 +2132,10 @@ declare class LocalAuthStore extends BaseAuthStore {
2083
2132
  setItem(key: string, value: string): void;
2084
2133
  getItem(key: string): string | null;
2085
2134
  removeItem(key: string): void;
2086
- /**
2087
- * Populate the store from a raw `Cookie` header string (server-side).
2088
- * Looks for the key matching `this.key`.
2089
- *
2090
- * Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
2091
- * claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
2092
- */
2093
- loadFromCookie(cookieString: string): void;
2094
- /**
2095
- * Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
2096
- * `user_metadata` is already inside the access token, and sending it twice doubled the cookie
2097
- * (5 KB of metadata gave 4 cookies and 13 139 bytes).
2098
- */
2099
- exportToCookie(options?: CookieOptions): string;
2100
2135
  private _getStorage;
2101
2136
  private _loadFromStorage;
2102
2137
  private _persist;
2103
2138
  private _removeStorage;
2104
2139
  }
2105
2140
 
2106
- export { AUTH_SETTINGS_WRITABLE_FIELDS, type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type ApnsPublicSettings, type ApnsSettingsPatch, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, type AuthSettingsUpdate, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type BroadcastSendStatus, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CreateNotificationRuleRequest, type CreatePgFunctionRequest, type CreatePgTriggerRequest, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FcmPublicSettings, type FcmSettingsPatch, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type FunctionPermissions, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaFactorType, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationAttemptStatus, type NotificationChannel, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationSettingsPatch, type NotificationSettingsPublic, type NotificationStats, type NotificationStatsWindow, type NotificationStatus, type NotificationTemplate, type NotificationTemplatePreview, type OAuthProviderSetting, type OAuthSignInObjectOptions, type OAuthSignInOptions, type ObjectMeta, type PasskeyInfo, type PasskeyRegisterResult, type PasswordResetRequest, type PgExecutableRole, type PgFunctionInfo, type PgTriggerInfo, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, type QuotaPublicSettings, type QuotaSettingsPatch, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeBroadcastEvent, type RealtimeEvent, type RealtimeListenType, type RealtimePostgresChangesBasePayload, type RealtimePostgresChangesPayload, type RealtimePostgresDeletePayload, type RealtimePostgresInsertPayload, type RealtimePostgresUpdatePayload, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationItem, type SendNotificationOptions, type SendNotificationRequest, type SerializeCookieOptions, type ServerChannelRecoveredMsg, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type Session, type SessionInfo, type SettingsSource, type SignInRequest, type SignUpRequest, type SmtpPublicSettings, type SmtpSettingsPatch, type StorageBucket, type SubscribeCallback, type TokenPair, type TwilioPublicSettings, type TwilioSettingsPatch, type UpdateNotificationRuleRequest, type UpdateUserRequest, type UploadResult, type User, type UserStats, type WebhookPublicSettings, type WebhookSettingsPatch, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
2141
+ export { AUTH_SETTINGS_WRITABLE_FIELDS, type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type ApnsPublicSettings, type ApnsSettingsPatch, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, type AuthSettingsUpdate, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type BroadcastSendStatus, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CookieOptions, type CountMode, type CreateNotificationRuleRequest, type CreatePgFunctionRequest, type CreatePgTriggerRequest, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FcmPublicSettings, type FcmSettingsPatch, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type FunctionPermissions, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaFactorType, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationAttemptStatus, type NotificationChannel, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationSettingsPatch, type NotificationSettingsPublic, type NotificationStats, type NotificationStatsWindow, type NotificationStatus, type NotificationTemplate, type NotificationTemplatePreview, type OAuthProviderSetting, type OAuthSignInObjectOptions, type OAuthSignInOptions, type ObjectMeta, type PasskeyInfo, type PasskeyRegisterResult, type PasswordResetRequest, type PgExecutableRole, type PgFunctionInfo, type PgTriggerInfo, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, type QuotaPublicSettings, type QuotaSettingsPatch, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeBroadcastEvent, type RealtimeEvent, type RealtimeListenType, type RealtimePostgresChangesBasePayload, type RealtimePostgresChangesPayload, type RealtimePostgresDeletePayload, type RealtimePostgresInsertPayload, type RealtimePostgresUpdatePayload, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationItem, type SendNotificationOptions, type SendNotificationRequest, type SerializeCookieOptions, type ServerChannelRecoveredMsg, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type Session, type SessionInfo, type SettingsSource, type SignInRequest, type SignUpRequest, type SmtpPublicSettings, type SmtpSettingsPatch, type StorageBucket, type StoredAuth, type SubscribeCallback, type TokenPair, type TwilioPublicSettings, type TwilioSettingsPatch, type UpdateNotificationRuleRequest, type UpdateUserRequest, type UploadResult, type User, type UserStats, type WebhookPublicSettings, type WebhookSettingsPatch, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
package/dist/index.d.ts CHANGED
@@ -102,6 +102,10 @@ interface Meta {
102
102
  * échoue avec un 400 explicite plutôt que d'émettre ce champ.
103
103
  */
104
104
  next_cursor?: string;
105
+ /**
106
+ * DB-005 : Indique si la requête a été tronquée par le serveur (ex: limite 1 000 lignes de PostgREST).
107
+ */
108
+ has_more?: boolean;
105
109
  }
106
110
  /**
107
111
  * Réponse en SUCCÈS porteuse de contenu. `error` y est `null` par construction.
@@ -203,6 +207,8 @@ interface User {
203
207
  last_sign_in_at?: string | null;
204
208
  }
205
209
  interface Session {
210
+ /** Identifiant unique de la session (UUID ou 'sess_...'), dérivé du claim `session_id` du JWT. */
211
+ id?: string;
206
212
  access_token: string;
207
213
  refresh_token: string;
208
214
  token_type: string;
@@ -267,6 +273,7 @@ interface SignUpRequest {
267
273
  options?: {
268
274
  emailRedirectTo?: string;
269
275
  data?: Record<string, unknown>;
276
+ preventEmailEnumeration?: boolean;
270
277
  };
271
278
  }
272
279
  interface SignInRequest {
@@ -369,14 +376,25 @@ interface MfaEnrollResponse {
369
376
  * Pour une passkey, utilisez `auth.mfa.webauthn.verify`.
370
377
  */
371
378
  type MfaVerifyRequest = {
372
- mfa_token: string;
379
+ mfa_token?: string;
380
+ mfaToken?: string;
381
+ challenge_id?: string;
382
+ challengeId?: string;
373
383
  code: string;
374
384
  } | {
375
- mfa_token: string;
385
+ mfa_token?: string;
386
+ mfaToken?: string;
387
+ challenge_id?: string;
388
+ challengeId?: string;
376
389
  recovery_code: string;
390
+ recoveryCode?: string;
377
391
  } | {
378
- mfa_token: string;
392
+ mfa_token?: string;
393
+ mfaToken?: string;
394
+ challenge_id?: string;
395
+ challengeId?: string;
379
396
  sms_code: string;
397
+ smsCode?: string;
380
398
  };
381
399
  /** Facteur MFA utilisable pour relever un défi (`auth.mfa.factors`). */
382
400
  type MfaFactorType = 'totp' | 'webauthn' | 'sms';
@@ -1558,6 +1576,12 @@ interface AuraClientOptions {
1558
1576
  * Défaut `false`.
1559
1577
  */
1560
1578
  debug?: boolean;
1579
+ /**
1580
+ * DB-006: Preserves 64-bit integer precision by parsing BIGINT values as strings
1581
+ * instead of JavaScript IEEE 754 floats when exceeding Number.MAX_SAFE_INTEGER (9007199254740991).
1582
+ * Default: false (native JSON.parse).
1583
+ */
1584
+ parseBigIntAsString?: boolean;
1561
1585
  }
1562
1586
  interface IdentityInfo {
1563
1587
  id: string;
@@ -1839,6 +1863,19 @@ type AuthChangeCallback = (token: string | null, user: User | null) => void;
1839
1863
  * Check whether a JWT has expired (or will expire within `marginSec` seconds).
1840
1864
  */
1841
1865
  declare function isTokenExpired(token: string, marginSec?: number): boolean;
1866
+ interface CookieOptions {
1867
+ path?: string;
1868
+ domain?: string;
1869
+ maxAge?: number;
1870
+ secure?: boolean;
1871
+ sameSite?: 'strict' | 'lax' | 'none';
1872
+ httpOnly?: boolean;
1873
+ }
1874
+ interface StoredAuth {
1875
+ token: string | null;
1876
+ refreshToken: string | null;
1877
+ user: User | null;
1878
+ }
1842
1879
  /**
1843
1880
  * In-memory auth store with an event-emitter pattern.
1844
1881
  * Can be extended (see LocalAuthStore) for persistence.
@@ -1850,6 +1887,7 @@ declare class BaseAuthStore {
1850
1887
  * le flux PKCE : le `code_verifier` doit survivre au redirect OAuth.
1851
1888
  */
1852
1889
  readonly persistent: boolean;
1890
+ protected readonly key: string;
1853
1891
  private _token;
1854
1892
  private _refreshToken;
1855
1893
  private _user;
@@ -1860,6 +1898,7 @@ declare class BaseAuthStore {
1860
1898
  /** Hook d'observabilité (LOT 3) — câblé par `AuraClient` via `setObservabilityHook`. */
1861
1899
  private _onEvent?;
1862
1900
  private _debug;
1901
+ constructor(key?: string);
1863
1902
  /** Compteur incrémenté à chaque `clear()` — sert au commit-guard anti-race du refresh. */
1864
1903
  get removalEpoch(): number;
1865
1904
  get token(): string | null;
@@ -1881,6 +1920,23 @@ declare class BaseAuthStore {
1881
1920
  setObservabilityHook(onEvent: ((event: AuraObservabilityEvent) => void) | undefined, debug: boolean | undefined): void;
1882
1921
  /** Émet `{kind:'storage-blocked', op}` — ne throw jamais (voir `emitAuraEvent`). */
1883
1922
  protected _emitStorageBlocked(op: string): void;
1923
+ /**
1924
+ * Populate the store from a raw `Cookie` header string (server-side).
1925
+ * Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
1926
+ * automatically reassembled in order (AUTH-010).
1927
+ */
1928
+ loadFromCookie(cookieString: string): void;
1929
+ /**
1930
+ * Serialize the session tokens into an array of `Set-Cookie` header strings.
1931
+ * If the payload exceeds `maxChunkSize` (default 3500 bytes), it is automatically split into
1932
+ * numbered chunks `${key}.0`, `${key}.1`, etc. to avoid browser cookie size limits (AUTH-010).
1933
+ */
1934
+ exportToCookies(options?: CookieOptions, maxChunkSize?: number): string[];
1935
+ /**
1936
+ * Serialize the session tokens into a single `Set-Cookie` value.
1937
+ * Note: If the token payload may exceed 3.5KB, use `exportToCookies()` instead for automatic chunking (AUTH-010).
1938
+ */
1939
+ exportToCookie(options?: CookieOptions): string;
1884
1940
  /**
1885
1941
  * Register a callback that fires whenever the auth state changes.
1886
1942
  * Returns an unsubscribe function.
@@ -1930,6 +1986,8 @@ interface AuraClientLike {
1930
1986
  readonly onEvent?: (event: AuraObservabilityEvent) => void;
1931
1987
  /** Si `true`, chaque `AuraObservabilityEvent` est aussi journalisé via `console.debug`. */
1932
1988
  readonly debug?: boolean;
1989
+ /** DB-006: Preserves 64-bit integers as strings when true. */
1990
+ readonly parseBigIntAsString?: boolean;
1933
1991
  }
1934
1992
 
1935
1993
  /**
@@ -2038,14 +2096,6 @@ declare function clientScope(baseUrl: string): string;
2038
2096
  /** JSON.stringify avec support des bigint (sérialisés en string). */
2039
2097
  declare function serializeBody(body: unknown): string;
2040
2098
 
2041
- interface CookieOptions {
2042
- path?: string;
2043
- domain?: string;
2044
- maxAge?: number;
2045
- secure?: boolean;
2046
- sameSite?: 'strict' | 'lax' | 'none';
2047
- httpOnly?: boolean;
2048
- }
2049
2099
  /**
2050
2100
  * Auth store backed by `localStorage` (browser) with cookie helpers for SSR.
2051
2101
  *
@@ -2062,7 +2112,6 @@ interface CookieOptions {
2062
2112
  declare class LocalAuthStore extends BaseAuthStore {
2063
2113
  /** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
2064
2114
  readonly persistent = true;
2065
- private readonly key;
2066
2115
  /**
2067
2116
  * @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
2068
2117
  * `aura-${clientScope(baseUrl)}-auth`.
@@ -2083,24 +2132,10 @@ declare class LocalAuthStore extends BaseAuthStore {
2083
2132
  setItem(key: string, value: string): void;
2084
2133
  getItem(key: string): string | null;
2085
2134
  removeItem(key: string): void;
2086
- /**
2087
- * Populate the store from a raw `Cookie` header string (server-side).
2088
- * Looks for the key matching `this.key`.
2089
- *
2090
- * Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
2091
- * claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
2092
- */
2093
- loadFromCookie(cookieString: string): void;
2094
- /**
2095
- * Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
2096
- * `user_metadata` is already inside the access token, and sending it twice doubled the cookie
2097
- * (5 KB of metadata gave 4 cookies and 13 139 bytes).
2098
- */
2099
- exportToCookie(options?: CookieOptions): string;
2100
2135
  private _getStorage;
2101
2136
  private _loadFromStorage;
2102
2137
  private _persist;
2103
2138
  private _removeStorage;
2104
2139
  }
2105
2140
 
2106
- export { AUTH_SETTINGS_WRITABLE_FIELDS, type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type ApnsPublicSettings, type ApnsSettingsPatch, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, type AuthSettingsUpdate, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type BroadcastSendStatus, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CreateNotificationRuleRequest, type CreatePgFunctionRequest, type CreatePgTriggerRequest, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FcmPublicSettings, type FcmSettingsPatch, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type FunctionPermissions, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaFactorType, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationAttemptStatus, type NotificationChannel, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationSettingsPatch, type NotificationSettingsPublic, type NotificationStats, type NotificationStatsWindow, type NotificationStatus, type NotificationTemplate, type NotificationTemplatePreview, type OAuthProviderSetting, type OAuthSignInObjectOptions, type OAuthSignInOptions, type ObjectMeta, type PasskeyInfo, type PasskeyRegisterResult, type PasswordResetRequest, type PgExecutableRole, type PgFunctionInfo, type PgTriggerInfo, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, type QuotaPublicSettings, type QuotaSettingsPatch, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeBroadcastEvent, type RealtimeEvent, type RealtimeListenType, type RealtimePostgresChangesBasePayload, type RealtimePostgresChangesPayload, type RealtimePostgresDeletePayload, type RealtimePostgresInsertPayload, type RealtimePostgresUpdatePayload, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationItem, type SendNotificationOptions, type SendNotificationRequest, type SerializeCookieOptions, type ServerChannelRecoveredMsg, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type Session, type SessionInfo, type SettingsSource, type SignInRequest, type SignUpRequest, type SmtpPublicSettings, type SmtpSettingsPatch, type StorageBucket, type SubscribeCallback, type TokenPair, type TwilioPublicSettings, type TwilioSettingsPatch, type UpdateNotificationRuleRequest, type UpdateUserRequest, type UploadResult, type User, type UserStats, type WebhookPublicSettings, type WebhookSettingsPatch, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
2141
+ export { AUTH_SETTINGS_WRITABLE_FIELDS, type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, type ApnsPublicSettings, type ApnsSettingsPatch, type AuditLogEntry, type AuraClientLike, type AuraClientOptions, AuraError, type AuraErrorCode, type AuraFailure, type AuraNoContent, type AuraObservabilityEvent, type AuraResponse, type AuraSuccess, type AuthChangePayload, type AuthEvent, type AuthSettings, type AuthSettingsUpdate, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type BroadcastSendStatus, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CookieOptions, type CountMode, type CreateNotificationRuleRequest, type CreatePgFunctionRequest, type CreatePgTriggerRequest, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FcmPublicSettings, type FcmSettingsPatch, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, type FunctionPermissions, type GenerateLinkResult, type GenerateLinkType, type GetClaimsResult, type IdentityInfo, type ImageTransform, type Job, type JwtClaims, LocalAuthStore, type LoginResponse, type MagicLinkRequest, type Meta, type MfaChallenge, type MfaEnrollResponse, type MfaFactorType, type MfaVerifyRequest, type Notification, type NotificationAttempt, type NotificationAttemptStatus, type NotificationChannel, type NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationSettingsPatch, type NotificationSettingsPublic, type NotificationStats, type NotificationStatsWindow, type NotificationStatus, type NotificationTemplate, type NotificationTemplatePreview, type OAuthProviderSetting, type OAuthSignInObjectOptions, type OAuthSignInOptions, type ObjectMeta, type PasskeyInfo, type PasskeyRegisterResult, type PasswordResetRequest, type PgExecutableRole, type PgFunctionInfo, type PgTriggerInfo, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, type QuotaPublicSettings, type QuotaSettingsPatch, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeBroadcastEvent, type RealtimeEvent, type RealtimeListenType, type RealtimePostgresChangesBasePayload, type RealtimePostgresChangesPayload, type RealtimePostgresDeletePayload, type RealtimePostgresInsertPayload, type RealtimePostgresUpdatePayload, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationItem, type SendNotificationOptions, type SendNotificationRequest, type SerializeCookieOptions, type ServerChannelRecoveredMsg, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type Session, type SessionInfo, type SettingsSource, type SignInRequest, type SignUpRequest, type SmtpPublicSettings, type SmtpSettingsPatch, type StorageBucket, type StoredAuth, type SubscribeCallback, type TokenPair, type TwilioPublicSettings, type TwilioSettingsPatch, type UpdateNotificationRuleRequest, type UpdateUserRequest, type UploadResult, type User, type UserStats, type WebhookPublicSettings, type WebhookSettingsPatch, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
package/dist/index.js CHANGED
@@ -74,6 +74,12 @@ var AuraError = class _AuraError extends Error {
74
74
  const hint = body?.error?.hint;
75
75
  if (typeof hint === "string" && hint) {
76
76
  this.hint = hint;
77
+ } else {
78
+ const serverDetails = JSON.stringify(body ?? "");
79
+ const isJsonCastError = rawMessage.includes("invalid input syntax for type json") || serverDetails.includes("22P02") && serverDetails.includes("json");
80
+ if (isJsonCastError) {
81
+ this.hint = "To filter on a text value inside a JSON column, use the '->>' operator instead of '->' (e.g. .eq('metadata->>theme', 'dark')).";
82
+ }
77
83
  }
78
84
  const rawCode = typeof body?.error?.code === "string" ? body.error.code : void 0;
79
85
  const canonical = rawCode ? normalizeErrorCode(rawCode) : void 0;
@@ -94,8 +100,12 @@ var AuraError = class _AuraError extends Error {
94
100
  this.code = "SESSION_REVOKED";
95
101
  } else if (status === 401 && lowerMsg.includes("expired")) {
96
102
  this.code = "SESSION_EXPIRED";
97
- } else if (status === 400 && lowerMsg.includes("password")) {
103
+ } else if (status === 400 && (lowerMsg.includes("current password") || lowerMsg.includes("old password") || lowerMsg.includes("password") && lowerMsg.includes("required"))) {
104
+ this.code = lowerMsg.includes("incorrect") || lowerMsg.includes("invalid") ? "INVALID_CURRENT_PASSWORD" : "CURRENT_PASSWORD_REQUIRED";
105
+ } else if (status === 400 && lowerMsg.includes("password") && (lowerMsg.includes("weak") || lowerMsg.includes("length") || lowerMsg.includes("strength") || lowerMsg.includes("character") || lowerMsg.includes("digit") || lowerMsg.includes("pwned") || lowerMsg.includes("too short") || lowerMsg.includes("too common"))) {
98
106
  this.code = "WEAK_PASSWORD";
107
+ } else if (status === 400 && lowerMsg.includes("password")) {
108
+ this.code = "INVALID_PASSWORD";
99
109
  } else if (status === 409 && (lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
100
110
  this.code = "EMAIL_ALREADY_IN_USE";
101
111
  } else if (status === 404 && lowerMsg.includes("user not found")) {
@@ -277,6 +287,33 @@ var AUTH_SETTINGS_WRITABLE_FIELDS = [
277
287
  "hook_secret"
278
288
  ];
279
289
 
290
+ // src/cookie-utils.ts
291
+ function parseCookieHeader(header) {
292
+ const result = {};
293
+ if (!header) return result;
294
+ for (const pair of header.split(";")) {
295
+ const idx = pair.indexOf("=");
296
+ if (idx === -1) continue;
297
+ const key = pair.slice(0, idx).trim();
298
+ const value = pair.slice(idx + 1).trim();
299
+ if (key) result[key] = decodeURIComponent(value);
300
+ }
301
+ return result;
302
+ }
303
+ function sameSiteAttribute(v) {
304
+ return v.charAt(0).toUpperCase() + v.slice(1);
305
+ }
306
+ function serializeCookieHeader(name, value, options = {}) {
307
+ const parts = [`${name}=${encodeURIComponent(value)}`];
308
+ if (options.path) parts.push(`Path=${options.path}`);
309
+ if (options.domain) parts.push(`Domain=${options.domain}`);
310
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
311
+ if (options.secure) parts.push("Secure");
312
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
313
+ if (options.httpOnly) parts.push("HttpOnly");
314
+ return parts.join("; ");
315
+ }
316
+
280
317
  // src/stores/BaseAuthStore.ts
281
318
  function decodeJwtPayload(token) {
282
319
  try {
@@ -321,6 +358,7 @@ var BaseAuthStore = class {
321
358
  * le flux PKCE : le `code_verifier` doit survivre au redirect OAuth.
322
359
  */
323
360
  persistent = false;
361
+ key;
324
362
  _token = null;
325
363
  _refreshToken = null;
326
364
  _user = null;
@@ -331,6 +369,9 @@ var BaseAuthStore = class {
331
369
  /** Hook d'observabilité (LOT 3) — câblé par `AuraClient` via `setObservabilityHook`. */
332
370
  _onEvent;
333
371
  _debug = false;
372
+ constructor(key = "aura-auth") {
373
+ this.key = key;
374
+ }
334
375
  // -- Getters ----------------------------------------------------------------
335
376
  /** Compteur incrémenté à chaque `clear()` — sert au commit-guard anti-race du refresh. */
336
377
  get removalEpoch() {
@@ -392,6 +433,91 @@ var BaseAuthStore = class {
392
433
  _emitStorageBlocked(op) {
393
434
  emitAuraEvent(this._onEvent, this._debug, { kind: "storage-blocked", op });
394
435
  }
436
+ /**
437
+ * Populate the store from a raw `Cookie` header string (server-side).
438
+ * Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
439
+ * automatically reassembled in order (AUTH-010).
440
+ */
441
+ loadFromCookie(cookieString) {
442
+ if (!cookieString) return;
443
+ const entries = cookieString.split(";").map((c) => c.trim()).filter(Boolean);
444
+ let encodedData = null;
445
+ const prefix = `${this.key}=`;
446
+ const singleEntry = entries.find((c) => c.startsWith(prefix));
447
+ if (singleEntry) {
448
+ encodedData = singleEntry.slice(prefix.length);
449
+ } else {
450
+ const escapedKey = this.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
451
+ const chunkRegex = new RegExp(`^${escapedKey}\\.(\\d+)=(.*)$`);
452
+ const chunks = [];
453
+ for (const entry of entries) {
454
+ const match = entry.match(chunkRegex);
455
+ if (match) {
456
+ chunks.push({
457
+ index: parseInt(match[1], 10),
458
+ value: match[2]
459
+ });
460
+ }
461
+ }
462
+ if (chunks.length > 0) {
463
+ chunks.sort((a, b) => a.index - b.index);
464
+ encodedData = chunks.map((c) => c.value).join("");
465
+ }
466
+ }
467
+ if (!encodedData) return;
468
+ try {
469
+ const raw = decodeURIComponent(encodedData);
470
+ const parsed = JSON.parse(raw);
471
+ const user = parsed.token ? userFromAccessToken(parsed.token) : null;
472
+ if (parsed.token && parsed.refreshToken && user) {
473
+ this.save(parsed.token, parsed.refreshToken, user);
474
+ }
475
+ } catch {
476
+ }
477
+ }
478
+ /**
479
+ * Serialize the session tokens into an array of `Set-Cookie` header strings.
480
+ * If the payload exceeds `maxChunkSize` (default 3500 bytes), it is automatically split into
481
+ * numbered chunks `${key}.0`, `${key}.1`, etc. to avoid browser cookie size limits (AUTH-010).
482
+ */
483
+ exportToCookies(options = {}, maxChunkSize = 3500) {
484
+ const data = {
485
+ token: this.token,
486
+ refreshToken: this.refreshToken
487
+ };
488
+ const encoded = encodeURIComponent(JSON.stringify(data));
489
+ const buildCookie = (name, value) => {
490
+ const parts = [`${name}=${value}`];
491
+ if (options.path) parts.push(`Path=${options.path}`);
492
+ if (options.domain) parts.push(`Domain=${options.domain}`);
493
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
494
+ if (options.secure) parts.push("Secure");
495
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
496
+ if (options.httpOnly) parts.push("HttpOnly");
497
+ return parts.join("; ");
498
+ };
499
+ const limit = Math.max(1, maxChunkSize);
500
+ if (encoded.length <= limit) {
501
+ return [buildCookie(this.key, encoded)];
502
+ }
503
+ const cookies = [];
504
+ let offset = 0;
505
+ let index = 0;
506
+ while (offset < encoded.length) {
507
+ const chunk = encoded.slice(offset, offset + limit);
508
+ cookies.push(buildCookie(`${this.key}.${index}`, chunk));
509
+ offset += limit;
510
+ index++;
511
+ }
512
+ return cookies;
513
+ }
514
+ /**
515
+ * Serialize the session tokens into a single `Set-Cookie` value.
516
+ * Note: If the token payload may exceed 3.5KB, use `exportToCookies()` instead for automatic chunking (AUTH-010).
517
+ */
518
+ exportToCookie(options = {}) {
519
+ return this.exportToCookies(options, Infinity)[0];
520
+ }
395
521
  // -- Event emitter ----------------------------------------------------------
396
522
  /**
397
523
  * Register a callback that fires whenever the auth state changes.
@@ -435,14 +561,42 @@ function unwrapEnvelope(body) {
435
561
  }
436
562
  return body;
437
563
  }
564
+ function parseJsonBody(raw, parseBigIntAsString) {
565
+ if (parseBigIntAsString) {
566
+ const safe = raw.replace(/([:\[,]\s*)(-?\d{16,})(?=\s*[,}\]])/g, '$1"$2"');
567
+ return JSON.parse(safe);
568
+ }
569
+ return JSON.parse(raw);
570
+ }
438
571
  function parseContentRangeTotal(headerVal) {
439
- if (!headerVal) return void 0;
572
+ return parseContentRangeDetails(headerVal).total;
573
+ }
574
+ function parseContentRangeDetails(headerVal) {
575
+ if (!headerVal) return { total: void 0, hasMore: void 0 };
440
576
  const parts = headerVal.split("/");
441
- if (parts.length === 2 && parts[1] !== "*") {
442
- const n = parseInt(parts[1], 10);
443
- if (!Number.isNaN(n)) return n;
577
+ const totalStr = parts[1];
578
+ let total;
579
+ if (totalStr && totalStr !== "*") {
580
+ const n = parseInt(totalStr, 10);
581
+ if (!Number.isNaN(n)) total = n;
582
+ }
583
+ let hasMore;
584
+ const rangePart = parts[0];
585
+ if (rangePart) {
586
+ const rangeBounds = rangePart.split("-");
587
+ if (rangeBounds.length === 2) {
588
+ const start = parseInt(rangeBounds[0], 10);
589
+ const end = parseInt(rangeBounds[1], 10);
590
+ if (!Number.isNaN(start) && !Number.isNaN(end)) {
591
+ if (total !== void 0) {
592
+ hasMore = end + 1 < total;
593
+ } else if (end - start + 1 >= 1e3) {
594
+ hasMore = true;
595
+ }
596
+ }
597
+ }
444
598
  }
445
- return void 0;
599
+ return { total, hasMore };
446
600
  }
447
601
  async function readTextBounded(res, timeoutMs) {
448
602
  const body = res.body;
@@ -835,7 +989,7 @@ async function fetchWithAuth(url, init, client) {
835
989
  }
836
990
  let body;
837
991
  try {
838
- body = JSON.parse(raw);
992
+ body = parseJsonBody(raw, client.parseBigIntAsString === true);
839
993
  } catch {
840
994
  const ctRaw = res.headers.get("Content-Type") ?? "unknown";
841
995
  const ct = ctRaw.length > 64 ? `${ctRaw.slice(0, 64)}\u2026` : ctRaw;
@@ -851,10 +1005,21 @@ async function fetchWithAuth(url, init, client) {
851
1005
  }
852
1006
  const unwrapped = unwrapEnvelope(body);
853
1007
  const cr = res.headers.get("content-range") ?? res.headers.get("Content-Range");
854
- const totalFromCr = parseContentRangeTotal(cr);
1008
+ const crDetails = parseContentRangeDetails(cr);
1009
+ const totalFromCr = crDetails.total;
1010
+ const hasMore = crDetails.hasMore;
855
1011
  const rawMeta = body !== null && typeof body === "object" && "meta" in body ? body.meta : void 0;
856
- const meta = rawMeta !== void 0 ? rawMeta : totalFromCr !== void 0 ? { total: totalFromCr } : void 0;
1012
+ const meta = rawMeta !== void 0 ? { ...rawMeta, ...hasMore !== void 0 ? { has_more: hasMore } : {} } : totalFromCr !== void 0 || hasMore !== void 0 ? {
1013
+ ...totalFromCr !== void 0 ? { total: totalFromCr } : {},
1014
+ ...hasMore !== void 0 ? { has_more: hasMore } : {}
1015
+ } : void 0;
857
1016
  const count = meta?.total !== void 0 ? meta.total : totalFromCr ?? null;
1017
+ const isDev = Boolean(client.debug) || typeof globalThis !== "undefined" && globalThis.process?.env?.NODE_ENV === "development";
1018
+ if (hasMore && isDev) {
1019
+ console.warn(
1020
+ "[Aurabase] Query result reached pagination limit of 1,000 rows. Use .range() to paginate further results."
1021
+ );
1022
+ }
858
1023
  if (unwrapped === null) {
859
1024
  return { data: null, error: null, meta, count };
860
1025
  }
@@ -886,45 +1051,16 @@ var BaseService = class {
886
1051
  }
887
1052
  };
888
1053
 
889
- // src/cookie-utils.ts
890
- function parseCookieHeader(header) {
891
- const result = {};
892
- if (!header) return result;
893
- for (const pair of header.split(";")) {
894
- const idx = pair.indexOf("=");
895
- if (idx === -1) continue;
896
- const key = pair.slice(0, idx).trim();
897
- const value = pair.slice(idx + 1).trim();
898
- if (key) result[key] = decodeURIComponent(value);
899
- }
900
- return result;
901
- }
902
- function sameSiteAttribute(v) {
903
- return v.charAt(0).toUpperCase() + v.slice(1);
904
- }
905
- function serializeCookieHeader(name, value, options = {}) {
906
- const parts = [`${name}=${encodeURIComponent(value)}`];
907
- if (options.path) parts.push(`Path=${options.path}`);
908
- if (options.domain) parts.push(`Domain=${options.domain}`);
909
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
910
- if (options.secure) parts.push("Secure");
911
- if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
912
- if (options.httpOnly) parts.push("HttpOnly");
913
- return parts.join("; ");
914
- }
915
-
916
1054
  // src/stores/LocalAuthStore.ts
917
1055
  var LocalAuthStore = class extends BaseAuthStore {
918
1056
  /** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
919
1057
  persistent = true;
920
- key;
921
1058
  /**
922
1059
  * @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
923
1060
  * `aura-${clientScope(baseUrl)}-auth`.
924
1061
  */
925
1062
  constructor(key) {
926
- super();
927
- this.key = key;
1063
+ super(key);
928
1064
  this._loadFromStorage();
929
1065
  }
930
1066
  // -- Overrides --------------------------------------------------------------
@@ -995,48 +1131,6 @@ var LocalAuthStore = class extends BaseAuthStore {
995
1131
  this._emitStorageBlocked("removeItem");
996
1132
  }
997
1133
  }
998
- // -- Cookie helpers (SSR) ---------------------------------------------------
999
- /**
1000
- * Populate the store from a raw `Cookie` header string (server-side).
1001
- * Looks for the key matching `this.key`.
1002
- *
1003
- * Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
1004
- * claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
1005
- */
1006
- loadFromCookie(cookieString) {
1007
- const prefix = `${this.key}=`;
1008
- const entry = cookieString.split(";").map((c) => c.trim()).find((c) => c.startsWith(prefix));
1009
- if (!entry) return;
1010
- try {
1011
- const raw = decodeURIComponent(entry.slice(prefix.length));
1012
- const parsed = JSON.parse(raw);
1013
- const user = parsed.token ? userFromAccessToken(parsed.token) : null;
1014
- if (parsed.token && parsed.refreshToken && user) {
1015
- super.save(parsed.token, parsed.refreshToken, user);
1016
- }
1017
- } catch {
1018
- }
1019
- }
1020
- /**
1021
- * Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
1022
- * `user_metadata` is already inside the access token, and sending it twice doubled the cookie
1023
- * (5 KB of metadata gave 4 cookies and 13 139 bytes).
1024
- */
1025
- exportToCookie(options = {}) {
1026
- const data = {
1027
- token: this.token,
1028
- refreshToken: this.refreshToken
1029
- };
1030
- const encoded = encodeURIComponent(JSON.stringify(data));
1031
- const parts = [`${this.key}=${encoded}`];
1032
- if (options.path) parts.push(`Path=${options.path}`);
1033
- if (options.domain) parts.push(`Domain=${options.domain}`);
1034
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
1035
- if (options.secure) parts.push("Secure");
1036
- if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
1037
- if (options.httpOnly) parts.push("HttpOnly");
1038
- return parts.join("; ");
1039
- }
1040
1134
  // -- Private ----------------------------------------------------------------
1041
1135
  _getStorage() {
1042
1136
  if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
@@ -1049,7 +1143,9 @@ var LocalAuthStore = class extends BaseAuthStore {
1049
1143
  const env = proc.env;
1050
1144
  const isTest = !!(env?.VITEST || env?.NODE_ENV === "test" || env?.JEST_WORKER_ID);
1051
1145
  const execArgv = proc.execArgv;
1052
- const hasFlag = Array.isArray(execArgv) && execArgv.some((a) => typeof a === "string" && a.startsWith("--localstorage-file"));
1146
+ const hasFlag = Array.isArray(execArgv) && execArgv.some(
1147
+ (a) => typeof a === "string" && a.startsWith("--localstorage-file")
1148
+ );
1053
1149
  if (isTest || hasFlag) {
1054
1150
  try {
1055
1151
  const s = g.localStorage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurabase/core",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Cœur du SDK Aurabase : fetch résilient, erreurs, stores auth, types de réponse (infrastructure interne)",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",