@aurabase/core 0.7.1 → 0.7.3
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 +119 -66
- package/dist/index.d.cts +22 -17
- package/dist/index.d.ts +22 -17
- package/dist/index.js +119 -66
- package/package.json +1 -1
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;
|
|
@@ -431,14 +437,38 @@ var BaseAuthStore = class {
|
|
|
431
437
|
}
|
|
432
438
|
/**
|
|
433
439
|
* Populate the store from a raw `Cookie` header string (server-side).
|
|
434
|
-
*
|
|
440
|
+
* Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
|
|
441
|
+
* automatically reassembled in order (AUTH-010).
|
|
435
442
|
*/
|
|
436
443
|
loadFromCookie(cookieString) {
|
|
444
|
+
if (!cookieString) return;
|
|
445
|
+
const entries = cookieString.split(";").map((c) => c.trim()).filter(Boolean);
|
|
446
|
+
let encodedData = null;
|
|
437
447
|
const prefix = `${this.key}=`;
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
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;
|
|
440
470
|
try {
|
|
441
|
-
const raw = decodeURIComponent(
|
|
471
|
+
const raw = decodeURIComponent(encodedData);
|
|
442
472
|
const parsed = JSON.parse(raw);
|
|
443
473
|
const user = parsed.token ? userFromAccessToken(parsed.token) : null;
|
|
444
474
|
if (parsed.token && parsed.refreshToken && user) {
|
|
@@ -448,23 +478,47 @@ var BaseAuthStore = class {
|
|
|
448
478
|
}
|
|
449
479
|
}
|
|
450
480
|
/**
|
|
451
|
-
* Serialize the session tokens into
|
|
452
|
-
* `
|
|
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).
|
|
453
484
|
*/
|
|
454
|
-
|
|
485
|
+
exportToCookies(options = {}, maxChunkSize = 3500) {
|
|
455
486
|
const data = {
|
|
456
487
|
token: this.token,
|
|
457
488
|
refreshToken: this.refreshToken
|
|
458
489
|
};
|
|
459
490
|
const encoded = encodeURIComponent(JSON.stringify(data));
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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];
|
|
468
522
|
}
|
|
469
523
|
// -- Event emitter ----------------------------------------------------------
|
|
470
524
|
/**
|
|
@@ -509,14 +563,42 @@ function unwrapEnvelope(body) {
|
|
|
509
563
|
}
|
|
510
564
|
return body;
|
|
511
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
|
+
}
|
|
512
573
|
function parseContentRangeTotal(headerVal) {
|
|
513
|
-
|
|
574
|
+
return parseContentRangeDetails(headerVal).total;
|
|
575
|
+
}
|
|
576
|
+
function parseContentRangeDetails(headerVal) {
|
|
577
|
+
if (!headerVal) return { total: void 0, hasMore: void 0 };
|
|
514
578
|
const parts = headerVal.split("/");
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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;
|
|
518
584
|
}
|
|
519
|
-
|
|
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
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return { total, hasMore };
|
|
520
602
|
}
|
|
521
603
|
async function readTextBounded(res, timeoutMs) {
|
|
522
604
|
const body = res.body;
|
|
@@ -909,7 +991,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
909
991
|
}
|
|
910
992
|
let body;
|
|
911
993
|
try {
|
|
912
|
-
body =
|
|
994
|
+
body = parseJsonBody(raw, client.parseBigIntAsString === true);
|
|
913
995
|
} catch {
|
|
914
996
|
const ctRaw = res.headers.get("Content-Type") ?? "unknown";
|
|
915
997
|
const ct = ctRaw.length > 64 ? `${ctRaw.slice(0, 64)}\u2026` : ctRaw;
|
|
@@ -925,10 +1007,21 @@ async function fetchWithAuth(url, init, client) {
|
|
|
925
1007
|
}
|
|
926
1008
|
const unwrapped = unwrapEnvelope(body);
|
|
927
1009
|
const cr = res.headers.get("content-range") ?? res.headers.get("Content-Range");
|
|
928
|
-
const
|
|
1010
|
+
const crDetails = parseContentRangeDetails(cr);
|
|
1011
|
+
const totalFromCr = crDetails.total;
|
|
1012
|
+
const hasMore = crDetails.hasMore;
|
|
929
1013
|
const rawMeta = body !== null && typeof body === "object" && "meta" in body ? body.meta : void 0;
|
|
930
|
-
const meta = rawMeta !== void 0 ? rawMeta
|
|
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;
|
|
931
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
|
+
}
|
|
932
1025
|
if (unwrapped === null) {
|
|
933
1026
|
return { data: null, error: null, meta, count };
|
|
934
1027
|
}
|
|
@@ -1040,48 +1133,6 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
1040
1133
|
this._emitStorageBlocked("removeItem");
|
|
1041
1134
|
}
|
|
1042
1135
|
}
|
|
1043
|
-
// -- Cookie helpers (SSR) ---------------------------------------------------
|
|
1044
|
-
/**
|
|
1045
|
-
* Populate the store from a raw `Cookie` header string (server-side).
|
|
1046
|
-
* Looks for the key matching `this.key`.
|
|
1047
|
-
*
|
|
1048
|
-
* Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
|
|
1049
|
-
* claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
|
|
1050
|
-
*/
|
|
1051
|
-
loadFromCookie(cookieString) {
|
|
1052
|
-
const prefix = `${this.key}=`;
|
|
1053
|
-
const entry = cookieString.split(";").map((c) => c.trim()).find((c) => c.startsWith(prefix));
|
|
1054
|
-
if (!entry) return;
|
|
1055
|
-
try {
|
|
1056
|
-
const raw = decodeURIComponent(entry.slice(prefix.length));
|
|
1057
|
-
const parsed = JSON.parse(raw);
|
|
1058
|
-
const user = parsed.token ? userFromAccessToken(parsed.token) : null;
|
|
1059
|
-
if (parsed.token && parsed.refreshToken && user) {
|
|
1060
|
-
super.save(parsed.token, parsed.refreshToken, user);
|
|
1061
|
-
}
|
|
1062
|
-
} catch {
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
|
|
1067
|
-
* `user_metadata` is already inside the access token, and sending it twice doubled the cookie
|
|
1068
|
-
* (5 KB of metadata gave 4 cookies and 13 139 bytes).
|
|
1069
|
-
*/
|
|
1070
|
-
exportToCookie(options = {}) {
|
|
1071
|
-
const data = {
|
|
1072
|
-
token: this.token,
|
|
1073
|
-
refreshToken: this.refreshToken
|
|
1074
|
-
};
|
|
1075
|
-
const encoded = encodeURIComponent(JSON.stringify(data));
|
|
1076
|
-
const parts = [`${this.key}=${encoded}`];
|
|
1077
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
1078
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
1079
|
-
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
1080
|
-
if (options.secure) parts.push("Secure");
|
|
1081
|
-
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
1082
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
1083
|
-
return parts.join("; ");
|
|
1084
|
-
}
|
|
1085
1136
|
// -- Private ----------------------------------------------------------------
|
|
1086
1137
|
_getStorage() {
|
|
1087
1138
|
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
|
@@ -1094,7 +1145,9 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
1094
1145
|
const env = proc.env;
|
|
1095
1146
|
const isTest = !!(env?.VITEST || env?.NODE_ENV === "test" || env?.JEST_WORKER_ID);
|
|
1096
1147
|
const execArgv = proc.execArgv;
|
|
1097
|
-
const hasFlag = Array.isArray(execArgv) && execArgv.some(
|
|
1148
|
+
const hasFlag = Array.isArray(execArgv) && execArgv.some(
|
|
1149
|
+
(a) => typeof a === "string" && a.startsWith("--localstorage-file")
|
|
1150
|
+
);
|
|
1098
1151
|
if (isTest || hasFlag) {
|
|
1099
1152
|
try {
|
|
1100
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.
|
|
@@ -1572,6 +1576,12 @@ interface AuraClientOptions {
|
|
|
1572
1576
|
* Défaut `false`.
|
|
1573
1577
|
*/
|
|
1574
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;
|
|
1575
1585
|
}
|
|
1576
1586
|
interface IdentityInfo {
|
|
1577
1587
|
id: string;
|
|
@@ -1912,12 +1922,19 @@ declare class BaseAuthStore {
|
|
|
1912
1922
|
protected _emitStorageBlocked(op: string): void;
|
|
1913
1923
|
/**
|
|
1914
1924
|
* Populate the store from a raw `Cookie` header string (server-side).
|
|
1915
|
-
*
|
|
1925
|
+
* Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
|
|
1926
|
+
* automatically reassembled in order (AUTH-010).
|
|
1916
1927
|
*/
|
|
1917
1928
|
loadFromCookie(cookieString: string): void;
|
|
1918
1929
|
/**
|
|
1919
|
-
* Serialize the session tokens into
|
|
1920
|
-
* `
|
|
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).
|
|
1921
1938
|
*/
|
|
1922
1939
|
exportToCookie(options?: CookieOptions): string;
|
|
1923
1940
|
/**
|
|
@@ -1969,6 +1986,8 @@ interface AuraClientLike {
|
|
|
1969
1986
|
readonly onEvent?: (event: AuraObservabilityEvent) => void;
|
|
1970
1987
|
/** Si `true`, chaque `AuraObservabilityEvent` est aussi journalisé via `console.debug`. */
|
|
1971
1988
|
readonly debug?: boolean;
|
|
1989
|
+
/** DB-006: Preserves 64-bit integers as strings when true. */
|
|
1990
|
+
readonly parseBigIntAsString?: boolean;
|
|
1972
1991
|
}
|
|
1973
1992
|
|
|
1974
1993
|
/**
|
|
@@ -2113,20 +2132,6 @@ declare class LocalAuthStore extends BaseAuthStore {
|
|
|
2113
2132
|
setItem(key: string, value: string): void;
|
|
2114
2133
|
getItem(key: string): string | null;
|
|
2115
2134
|
removeItem(key: string): void;
|
|
2116
|
-
/**
|
|
2117
|
-
* Populate the store from a raw `Cookie` header string (server-side).
|
|
2118
|
-
* Looks for the key matching `this.key`.
|
|
2119
|
-
*
|
|
2120
|
-
* Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
|
|
2121
|
-
* claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
|
|
2122
|
-
*/
|
|
2123
|
-
loadFromCookie(cookieString: string): void;
|
|
2124
|
-
/**
|
|
2125
|
-
* Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
|
|
2126
|
-
* `user_metadata` is already inside the access token, and sending it twice doubled the cookie
|
|
2127
|
-
* (5 KB of metadata gave 4 cookies and 13 139 bytes).
|
|
2128
|
-
*/
|
|
2129
|
-
exportToCookie(options?: CookieOptions): string;
|
|
2130
2135
|
private _getStorage;
|
|
2131
2136
|
private _loadFromStorage;
|
|
2132
2137
|
private _persist;
|
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.
|
|
@@ -1572,6 +1576,12 @@ interface AuraClientOptions {
|
|
|
1572
1576
|
* Défaut `false`.
|
|
1573
1577
|
*/
|
|
1574
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;
|
|
1575
1585
|
}
|
|
1576
1586
|
interface IdentityInfo {
|
|
1577
1587
|
id: string;
|
|
@@ -1912,12 +1922,19 @@ declare class BaseAuthStore {
|
|
|
1912
1922
|
protected _emitStorageBlocked(op: string): void;
|
|
1913
1923
|
/**
|
|
1914
1924
|
* Populate the store from a raw `Cookie` header string (server-side).
|
|
1915
|
-
*
|
|
1925
|
+
* Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
|
|
1926
|
+
* automatically reassembled in order (AUTH-010).
|
|
1916
1927
|
*/
|
|
1917
1928
|
loadFromCookie(cookieString: string): void;
|
|
1918
1929
|
/**
|
|
1919
|
-
* Serialize the session tokens into
|
|
1920
|
-
* `
|
|
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).
|
|
1921
1938
|
*/
|
|
1922
1939
|
exportToCookie(options?: CookieOptions): string;
|
|
1923
1940
|
/**
|
|
@@ -1969,6 +1986,8 @@ interface AuraClientLike {
|
|
|
1969
1986
|
readonly onEvent?: (event: AuraObservabilityEvent) => void;
|
|
1970
1987
|
/** Si `true`, chaque `AuraObservabilityEvent` est aussi journalisé via `console.debug`. */
|
|
1971
1988
|
readonly debug?: boolean;
|
|
1989
|
+
/** DB-006: Preserves 64-bit integers as strings when true. */
|
|
1990
|
+
readonly parseBigIntAsString?: boolean;
|
|
1972
1991
|
}
|
|
1973
1992
|
|
|
1974
1993
|
/**
|
|
@@ -2113,20 +2132,6 @@ declare class LocalAuthStore extends BaseAuthStore {
|
|
|
2113
2132
|
setItem(key: string, value: string): void;
|
|
2114
2133
|
getItem(key: string): string | null;
|
|
2115
2134
|
removeItem(key: string): void;
|
|
2116
|
-
/**
|
|
2117
|
-
* Populate the store from a raw `Cookie` header string (server-side).
|
|
2118
|
-
* Looks for the key matching `this.key`.
|
|
2119
|
-
*
|
|
2120
|
-
* Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
|
|
2121
|
-
* claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
|
|
2122
|
-
*/
|
|
2123
|
-
loadFromCookie(cookieString: string): void;
|
|
2124
|
-
/**
|
|
2125
|
-
* Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
|
|
2126
|
-
* `user_metadata` is already inside the access token, and sending it twice doubled the cookie
|
|
2127
|
-
* (5 KB of metadata gave 4 cookies and 13 139 bytes).
|
|
2128
|
-
*/
|
|
2129
|
-
exportToCookie(options?: CookieOptions): string;
|
|
2130
2135
|
private _getStorage;
|
|
2131
2136
|
private _loadFromStorage;
|
|
2132
2137
|
private _persist;
|
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;
|
|
@@ -429,14 +435,38 @@ var BaseAuthStore = class {
|
|
|
429
435
|
}
|
|
430
436
|
/**
|
|
431
437
|
* Populate the store from a raw `Cookie` header string (server-side).
|
|
432
|
-
*
|
|
438
|
+
* Supports both single cookie `${this.key}=...` and numbered chunk cookies `${this.key}.0=...`, `${this.key}.1=...`
|
|
439
|
+
* automatically reassembled in order (AUTH-010).
|
|
433
440
|
*/
|
|
434
441
|
loadFromCookie(cookieString) {
|
|
442
|
+
if (!cookieString) return;
|
|
443
|
+
const entries = cookieString.split(";").map((c) => c.trim()).filter(Boolean);
|
|
444
|
+
let encodedData = null;
|
|
435
445
|
const prefix = `${this.key}=`;
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
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;
|
|
438
468
|
try {
|
|
439
|
-
const raw = decodeURIComponent(
|
|
469
|
+
const raw = decodeURIComponent(encodedData);
|
|
440
470
|
const parsed = JSON.parse(raw);
|
|
441
471
|
const user = parsed.token ? userFromAccessToken(parsed.token) : null;
|
|
442
472
|
if (parsed.token && parsed.refreshToken && user) {
|
|
@@ -446,23 +476,47 @@ var BaseAuthStore = class {
|
|
|
446
476
|
}
|
|
447
477
|
}
|
|
448
478
|
/**
|
|
449
|
-
* Serialize the session tokens into
|
|
450
|
-
* `
|
|
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).
|
|
451
482
|
*/
|
|
452
|
-
|
|
483
|
+
exportToCookies(options = {}, maxChunkSize = 3500) {
|
|
453
484
|
const data = {
|
|
454
485
|
token: this.token,
|
|
455
486
|
refreshToken: this.refreshToken
|
|
456
487
|
};
|
|
457
488
|
const encoded = encodeURIComponent(JSON.stringify(data));
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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];
|
|
466
520
|
}
|
|
467
521
|
// -- Event emitter ----------------------------------------------------------
|
|
468
522
|
/**
|
|
@@ -507,14 +561,42 @@ function unwrapEnvelope(body) {
|
|
|
507
561
|
}
|
|
508
562
|
return body;
|
|
509
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
|
+
}
|
|
510
571
|
function parseContentRangeTotal(headerVal) {
|
|
511
|
-
|
|
572
|
+
return parseContentRangeDetails(headerVal).total;
|
|
573
|
+
}
|
|
574
|
+
function parseContentRangeDetails(headerVal) {
|
|
575
|
+
if (!headerVal) return { total: void 0, hasMore: void 0 };
|
|
512
576
|
const parts = headerVal.split("/");
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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;
|
|
516
582
|
}
|
|
517
|
-
|
|
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
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return { total, hasMore };
|
|
518
600
|
}
|
|
519
601
|
async function readTextBounded(res, timeoutMs) {
|
|
520
602
|
const body = res.body;
|
|
@@ -907,7 +989,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
907
989
|
}
|
|
908
990
|
let body;
|
|
909
991
|
try {
|
|
910
|
-
body =
|
|
992
|
+
body = parseJsonBody(raw, client.parseBigIntAsString === true);
|
|
911
993
|
} catch {
|
|
912
994
|
const ctRaw = res.headers.get("Content-Type") ?? "unknown";
|
|
913
995
|
const ct = ctRaw.length > 64 ? `${ctRaw.slice(0, 64)}\u2026` : ctRaw;
|
|
@@ -923,10 +1005,21 @@ async function fetchWithAuth(url, init, client) {
|
|
|
923
1005
|
}
|
|
924
1006
|
const unwrapped = unwrapEnvelope(body);
|
|
925
1007
|
const cr = res.headers.get("content-range") ?? res.headers.get("Content-Range");
|
|
926
|
-
const
|
|
1008
|
+
const crDetails = parseContentRangeDetails(cr);
|
|
1009
|
+
const totalFromCr = crDetails.total;
|
|
1010
|
+
const hasMore = crDetails.hasMore;
|
|
927
1011
|
const rawMeta = body !== null && typeof body === "object" && "meta" in body ? body.meta : void 0;
|
|
928
|
-
const meta = rawMeta !== void 0 ? rawMeta
|
|
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;
|
|
929
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
|
+
}
|
|
930
1023
|
if (unwrapped === null) {
|
|
931
1024
|
return { data: null, error: null, meta, count };
|
|
932
1025
|
}
|
|
@@ -1038,48 +1131,6 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
1038
1131
|
this._emitStorageBlocked("removeItem");
|
|
1039
1132
|
}
|
|
1040
1133
|
}
|
|
1041
|
-
// -- Cookie helpers (SSR) ---------------------------------------------------
|
|
1042
|
-
/**
|
|
1043
|
-
* Populate the store from a raw `Cookie` header string (server-side).
|
|
1044
|
-
* Looks for the key matching `this.key`.
|
|
1045
|
-
*
|
|
1046
|
-
* Le cookie ne porte que `{ token, refreshToken }` (AUTH-036) : l'utilisateur est reconstruit à partir des
|
|
1047
|
-
* claims du jeton (non vérifié — `auth.getUser()` le fait vérifier par le serveur).
|
|
1048
|
-
*/
|
|
1049
|
-
loadFromCookie(cookieString) {
|
|
1050
|
-
const prefix = `${this.key}=`;
|
|
1051
|
-
const entry = cookieString.split(";").map((c) => c.trim()).find((c) => c.startsWith(prefix));
|
|
1052
|
-
if (!entry) return;
|
|
1053
|
-
try {
|
|
1054
|
-
const raw = decodeURIComponent(entry.slice(prefix.length));
|
|
1055
|
-
const parsed = JSON.parse(raw);
|
|
1056
|
-
const user = parsed.token ? userFromAccessToken(parsed.token) : null;
|
|
1057
|
-
if (parsed.token && parsed.refreshToken && user) {
|
|
1058
|
-
super.save(parsed.token, parsed.refreshToken, user);
|
|
1059
|
-
}
|
|
1060
|
-
} catch {
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
/**
|
|
1064
|
-
* Serialize the session tokens into a `Set-Cookie` value. The `user` object is NOT included: its
|
|
1065
|
-
* `user_metadata` is already inside the access token, and sending it twice doubled the cookie
|
|
1066
|
-
* (5 KB of metadata gave 4 cookies and 13 139 bytes).
|
|
1067
|
-
*/
|
|
1068
|
-
exportToCookie(options = {}) {
|
|
1069
|
-
const data = {
|
|
1070
|
-
token: this.token,
|
|
1071
|
-
refreshToken: this.refreshToken
|
|
1072
|
-
};
|
|
1073
|
-
const encoded = encodeURIComponent(JSON.stringify(data));
|
|
1074
|
-
const parts = [`${this.key}=${encoded}`];
|
|
1075
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
1076
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
1077
|
-
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
1078
|
-
if (options.secure) parts.push("Secure");
|
|
1079
|
-
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
1080
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
1081
|
-
return parts.join("; ");
|
|
1082
|
-
}
|
|
1083
1134
|
// -- Private ----------------------------------------------------------------
|
|
1084
1135
|
_getStorage() {
|
|
1085
1136
|
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
|
@@ -1092,7 +1143,9 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
1092
1143
|
const env = proc.env;
|
|
1093
1144
|
const isTest = !!(env?.VITEST || env?.NODE_ENV === "test" || env?.JEST_WORKER_ID);
|
|
1094
1145
|
const execArgv = proc.execArgv;
|
|
1095
|
-
const hasFlag = Array.isArray(execArgv) && execArgv.some(
|
|
1146
|
+
const hasFlag = Array.isArray(execArgv) && execArgv.some(
|
|
1147
|
+
(a) => typeof a === "string" && a.startsWith("--localstorage-file")
|
|
1148
|
+
);
|
|
1096
1149
|
if (isTest || hasFlag) {
|
|
1097
1150
|
try {
|
|
1098
1151
|
const s = g.localStorage;
|
package/package.json
CHANGED