@aurabase/core 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +103 -24
- package/dist/index.d.cts +56 -12
- package/dist/index.d.ts +56 -12
- package/dist/index.js +103 -24
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ function hydrateFromCookieHeader(cookieHeader: string) {
|
|
|
149
149
|
const store = hydrateFromCookieHeader(`${CLE}=%7B%22token%22%3Anull%7D`)
|
|
150
150
|
|
|
151
151
|
// Après une mutation de session, sérialiser l'état courant en Set-Cookie.
|
|
152
|
-
const setCookieValue = store.exportToCookie({ path: '/', secure: true, sameSite: '
|
|
152
|
+
const setCookieValue = store.exportToCookie({ path: '/', secure: true, sameSite: 'lax', httpOnly: true })
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
`store.persistent` indique si le store survit à un redirect pleine page (`false` pour `BaseAuthStore`, `true` pour `LocalAuthStore`) — c'est ce flag que le flux OAuth PKCE utilise pour décider s'il peut stocker un `code_verifier` de façon fiable.
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/AuraError.ts
|
|
4
|
+
var LEGACY_LOWERCASE_CODES = /* @__PURE__ */ new Set([
|
|
5
|
+
"NO_SESSION",
|
|
6
|
+
"TIMEOUT",
|
|
7
|
+
"NETWORK_ERROR",
|
|
8
|
+
"INVALID_RESPONSE",
|
|
9
|
+
"REFRESH_DISCARDED",
|
|
10
|
+
"UNVERIFIABLE_TOKEN",
|
|
11
|
+
"NO_CODE_VERIFIER",
|
|
12
|
+
"PKCE_GENERATION_FAILED",
|
|
13
|
+
"MISSING_EMAIL",
|
|
14
|
+
"JWKS_FETCH_FAILED",
|
|
15
|
+
"SIGNATURE_INVALID",
|
|
16
|
+
"TOKEN_EXPIRED",
|
|
17
|
+
"TOKEN_NOT_YET_VALID",
|
|
18
|
+
"INVALID_JWT",
|
|
19
|
+
"UNSUPPORTED_ALG",
|
|
20
|
+
"NO_MATCHING_KEY",
|
|
21
|
+
"INVALID_ISSUER",
|
|
22
|
+
"INVALID_AUDIENCE",
|
|
23
|
+
"INVALID_PATH",
|
|
24
|
+
"INVALID_CREDENTIALS",
|
|
25
|
+
"WEAK_PASSWORD",
|
|
26
|
+
"EMAIL_ALREADY_IN_USE",
|
|
27
|
+
"USER_NOT_FOUND",
|
|
28
|
+
"SESSION_EXPIRED",
|
|
29
|
+
"INVALID_OTP",
|
|
30
|
+
"SESSION_REVOKED"
|
|
31
|
+
]);
|
|
32
|
+
function normalizeErrorCode(code) {
|
|
33
|
+
return code.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
34
|
+
}
|
|
4
35
|
var AuraError = class _AuraError extends Error {
|
|
5
36
|
status;
|
|
6
37
|
code;
|
|
7
38
|
details;
|
|
8
39
|
retryAfter;
|
|
40
|
+
/**
|
|
41
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
42
|
+
*/
|
|
43
|
+
hint;
|
|
9
44
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
10
45
|
__isAuraError = true;
|
|
11
46
|
constructor(status, body, headers) {
|
|
12
|
-
|
|
47
|
+
const serverMessage = body?.error?.message ?? body?.message;
|
|
48
|
+
let rawMessage = serverMessage ?? (status === 429 ? "Too many requests, please wait before retrying." : `HTTP ${status}`);
|
|
13
49
|
let retryAfterSec;
|
|
14
50
|
if (headers) {
|
|
15
51
|
const getHeader = (name) => {
|
|
@@ -24,8 +60,8 @@ var AuraError = class _AuraError extends Error {
|
|
|
24
60
|
const sec = parseInt(ra, 10);
|
|
25
61
|
if (!isNaN(sec)) {
|
|
26
62
|
retryAfterSec = sec;
|
|
27
|
-
if (status === 429) {
|
|
28
|
-
rawMessage = `
|
|
63
|
+
if (status === 429 && !serverMessage) {
|
|
64
|
+
rawMessage = `Too many requests, please wait ${sec} second${sec > 1 ? "s" : ""} before retrying.`;
|
|
29
65
|
}
|
|
30
66
|
}
|
|
31
67
|
}
|
|
@@ -37,30 +73,47 @@ var AuraError = class _AuraError extends Error {
|
|
|
37
73
|
if (retryAfterSec !== void 0) {
|
|
38
74
|
this.retryAfter = retryAfterSec;
|
|
39
75
|
}
|
|
40
|
-
const
|
|
76
|
+
const hint = body?.error?.hint;
|
|
77
|
+
if (typeof hint === "string" && hint) {
|
|
78
|
+
this.hint = hint;
|
|
79
|
+
}
|
|
80
|
+
const rawCode = typeof body?.error?.code === "string" ? body.error.code : void 0;
|
|
81
|
+
const canonical = rawCode ? normalizeErrorCode(rawCode) : void 0;
|
|
41
82
|
const lowerMsg = rawMessage.toLowerCase();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
83
|
+
if (canonical === "USER_ALREADY_EXISTS") {
|
|
84
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
85
|
+
} else if (canonical === "TOKEN_EXPIRED" && status === 401) {
|
|
86
|
+
this.code = "SESSION_EXPIRED";
|
|
87
|
+
} else if (canonical === "RATE_LIMIT_EXCEEDED") {
|
|
88
|
+
this.code = "RATE_LIMITED";
|
|
89
|
+
} else if (canonical && !isGenericCode(canonical)) {
|
|
90
|
+
this.code = canonical;
|
|
91
|
+
} else if (status === 429) {
|
|
92
|
+
this.code = "RATE_LIMITED";
|
|
47
93
|
} else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
|
|
48
|
-
this.code = "
|
|
49
|
-
} else if (status === 401 && lowerMsg.includes("r\xE9voqu\xE9e")) {
|
|
50
|
-
this.code = "
|
|
51
|
-
} else if (status === 401 && lowerMsg.includes("expir\xE9")) {
|
|
52
|
-
this.code = "
|
|
94
|
+
this.code = "INVALID_CREDENTIALS";
|
|
95
|
+
} else if (status === 401 && (lowerMsg.includes("r\xE9voqu\xE9e") || lowerMsg.includes("revoked"))) {
|
|
96
|
+
this.code = "SESSION_REVOKED";
|
|
97
|
+
} else if (status === 401 && (lowerMsg.includes("expir\xE9") || lowerMsg.includes("expired"))) {
|
|
98
|
+
this.code = "SESSION_EXPIRED";
|
|
53
99
|
} else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
|
|
54
|
-
this.code = "
|
|
100
|
+
this.code = "WEAK_PASSWORD";
|
|
55
101
|
} else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
|
|
56
|
-
this.code = "
|
|
57
|
-
} else if (status === 404 && lowerMsg.includes("utilisateur")) {
|
|
58
|
-
this.code = "
|
|
102
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
103
|
+
} else if (status === 404 && (lowerMsg.includes("utilisateur") || lowerMsg.includes("user not found"))) {
|
|
104
|
+
this.code = "USER_NOT_FOUND";
|
|
59
105
|
} else {
|
|
60
|
-
this.code =
|
|
106
|
+
this.code = canonical ?? `HTTP_${status}`;
|
|
61
107
|
}
|
|
62
108
|
Object.setPrototypeOf(this, _AuraError.prototype);
|
|
63
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
112
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
113
|
+
*/
|
|
114
|
+
get legacyCode() {
|
|
115
|
+
return LEGACY_LOWERCASE_CODES.has(this.code) ? this.code.toLowerCase() : this.code;
|
|
116
|
+
}
|
|
64
117
|
get isNotFound() {
|
|
65
118
|
return this.status === 404;
|
|
66
119
|
}
|
|
@@ -73,7 +126,17 @@ var AuraError = class _AuraError extends Error {
|
|
|
73
126
|
get isRateLimited() {
|
|
74
127
|
return this.status === 429;
|
|
75
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
131
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
132
|
+
*/
|
|
133
|
+
get isQuotaExceeded() {
|
|
134
|
+
return this.code === "QUOTA_EXCEEDED";
|
|
135
|
+
}
|
|
76
136
|
};
|
|
137
|
+
function isGenericCode(code) {
|
|
138
|
+
return code === "UNAUTHORIZED" || code === "BAD_REQUEST" || code === "CONFLICT" || code.startsWith("HTTP_");
|
|
139
|
+
}
|
|
77
140
|
function isAuraError(error) {
|
|
78
141
|
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
79
142
|
}
|
|
@@ -338,6 +401,17 @@ var BaseAuthStore = class {
|
|
|
338
401
|
// src/fetch.ts
|
|
339
402
|
var REFRESH_MARGIN_SEC = 30;
|
|
340
403
|
var RETRY_AFTER_CAP_MS = 6e4;
|
|
404
|
+
function networkErrorMessage(err, url) {
|
|
405
|
+
const cause = err?.cause;
|
|
406
|
+
const reason = cause?.code ?? cause?.message ?? (err instanceof Error ? err.message : "request failed");
|
|
407
|
+
let target = url;
|
|
408
|
+
try {
|
|
409
|
+
const u = new URL(url);
|
|
410
|
+
target = `${u.origin}${u.pathname}`;
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
return `Cannot reach ${target} (${reason}). Check the project URL (NEXT_PUBLIC_AURABASE_URL) and your network.`;
|
|
414
|
+
}
|
|
341
415
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
342
416
|
function unwrapEnvelope(body) {
|
|
343
417
|
if (body !== null && typeof body === "object" && "data" in body) {
|
|
@@ -459,7 +533,7 @@ async function doRefresh(client) {
|
|
|
459
533
|
error: new AuraError(0, {
|
|
460
534
|
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
461
535
|
code: "network_error",
|
|
462
|
-
message: err
|
|
536
|
+
message: networkErrorMessage(err, `${client.baseUrl}/v1/auth/refresh`)
|
|
463
537
|
}
|
|
464
538
|
})
|
|
465
539
|
};
|
|
@@ -660,7 +734,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
660
734
|
error: new AuraError(0, {
|
|
661
735
|
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
662
736
|
code: "network_error",
|
|
663
|
-
message: err
|
|
737
|
+
message: networkErrorMessage(err, url)
|
|
664
738
|
}
|
|
665
739
|
})
|
|
666
740
|
};
|
|
@@ -693,7 +767,9 @@ async function fetchWithAuth(url, init, client) {
|
|
|
693
767
|
}
|
|
694
768
|
}
|
|
695
769
|
if (res.status === 204) {
|
|
696
|
-
|
|
770
|
+
const totalFromCr2 = parseContentRangeTotal(res.headers.get("content-range"));
|
|
771
|
+
if (totalFromCr2 === void 0) return { data: null, error: null };
|
|
772
|
+
return { data: null, error: null, meta: { total: totalFromCr2 }, count: totalFromCr2 };
|
|
697
773
|
}
|
|
698
774
|
if (!res.ok) {
|
|
699
775
|
let body2 = null;
|
|
@@ -807,13 +883,16 @@ function parseCookieHeader(header) {
|
|
|
807
883
|
}
|
|
808
884
|
return result;
|
|
809
885
|
}
|
|
886
|
+
function sameSiteAttribute(v) {
|
|
887
|
+
return v.charAt(0).toUpperCase() + v.slice(1);
|
|
888
|
+
}
|
|
810
889
|
function serializeCookieHeader(name, value, options = {}) {
|
|
811
890
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
812
891
|
if (options.path) parts.push(`Path=${options.path}`);
|
|
813
892
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
814
893
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
815
894
|
if (options.secure) parts.push("Secure");
|
|
816
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
895
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
817
896
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
818
897
|
return parts.join("; ");
|
|
819
898
|
}
|
|
@@ -933,7 +1012,7 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
933
1012
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
934
1013
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
935
1014
|
if (options.secure) parts.push("Secure");
|
|
936
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
1015
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
937
1016
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
938
1017
|
return parts.join("; ");
|
|
939
1018
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - storage/path.ts : invalid_path
|
|
15
15
|
* - db/QueryBuilder.ts: PGRST116 (parité PostgREST : `.single()` sur 0/plusieurs lignes)
|
|
16
16
|
*/
|
|
17
|
-
type AuraErrorCode = '
|
|
17
|
+
type AuraErrorCode = 'NO_SESSION' | 'TIMEOUT' | 'NETWORK_ERROR' | 'INVALID_RESPONSE' | 'REFRESH_DISCARDED' | 'UNVERIFIABLE_TOKEN' | 'NO_CODE_VERIFIER' | 'PKCE_GENERATION_FAILED' | 'MISSING_EMAIL' | 'JWKS_FETCH_FAILED' | 'SIGNATURE_INVALID' | 'TOKEN_EXPIRED' | 'TOKEN_NOT_YET_VALID' | 'INVALID_JWT' | 'UNSUPPORTED_ALG' | 'NO_MATCHING_KEY' | 'INVALID_ISSUER' | 'INVALID_AUDIENCE' | 'INVALID_PATH' | 'PGRST116' | 'INVALID_CREDENTIALS' | 'WEAK_PASSWORD' | 'EMAIL_ALREADY_IN_USE' | 'USER_NOT_FOUND' | 'RATE_LIMITED' | 'SESSION_EXPIRED' | 'INVALID_OTP' | 'SESSION_REVOKED' | (string & {});
|
|
18
18
|
/**
|
|
19
19
|
* Structured error thrown/returned by the Aurabase SDK.
|
|
20
20
|
* Mirrors the backend's `AuraError` JSON shape with semantic DX normalization.
|
|
@@ -24,6 +24,10 @@ declare class AuraError extends Error {
|
|
|
24
24
|
readonly code: AuraErrorCode;
|
|
25
25
|
readonly details: unknown;
|
|
26
26
|
readonly retryAfter?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
29
|
+
*/
|
|
30
|
+
readonly hint?: string;
|
|
27
31
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
28
32
|
protected readonly __isAuraError = true;
|
|
29
33
|
constructor(status: number, body?: {
|
|
@@ -33,10 +37,20 @@ declare class AuraError extends Error {
|
|
|
33
37
|
};
|
|
34
38
|
message?: string;
|
|
35
39
|
} | null, headers?: Headers | Record<string, string | null | undefined>);
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
42
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
43
|
+
*/
|
|
44
|
+
get legacyCode(): string;
|
|
36
45
|
get isNotFound(): boolean;
|
|
37
46
|
get isUnauthorized(): boolean;
|
|
38
47
|
get isForbidden(): boolean;
|
|
39
48
|
get isRateLimited(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
51
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
52
|
+
*/
|
|
53
|
+
get isQuotaExceeded(): boolean;
|
|
40
54
|
}
|
|
41
55
|
declare function isAuraError(error: unknown): error is AuraError;
|
|
42
56
|
|
|
@@ -260,12 +274,18 @@ interface AuthChangePayload {
|
|
|
260
274
|
interface SignUpRequest {
|
|
261
275
|
email: string;
|
|
262
276
|
password: string;
|
|
277
|
+
name?: string;
|
|
263
278
|
captcha_token?: string;
|
|
279
|
+
options?: {
|
|
280
|
+
emailRedirectTo?: string;
|
|
281
|
+
data?: Record<string, unknown>;
|
|
282
|
+
};
|
|
264
283
|
}
|
|
265
284
|
interface SignInRequest {
|
|
266
285
|
email: string;
|
|
267
286
|
password: string;
|
|
268
287
|
captcha_token?: string;
|
|
288
|
+
mfa?: 'required' | 'optional';
|
|
269
289
|
}
|
|
270
290
|
interface MagicLinkRequest {
|
|
271
291
|
email: string;
|
|
@@ -329,6 +349,10 @@ interface OAuthSignInOptions {
|
|
|
329
349
|
*/
|
|
330
350
|
flowType?: 'pkce' | 'implicit';
|
|
331
351
|
}
|
|
352
|
+
interface OAuthSignInObjectOptions extends OAuthSignInOptions {
|
|
353
|
+
provider: string;
|
|
354
|
+
options?: OAuthSignInOptions;
|
|
355
|
+
}
|
|
332
356
|
interface PasswordResetRequest {
|
|
333
357
|
email: string;
|
|
334
358
|
}
|
|
@@ -646,7 +670,7 @@ interface ObjectMeta {
|
|
|
646
670
|
metadata: Record<string, string>;
|
|
647
671
|
}
|
|
648
672
|
/** Événement serveur brut reçu via WebSocket (union discriminée par type) */
|
|
649
|
-
type ServerMessage = ServerSubscribedMsg | ServerEventMsg | ServerPongMsg | ServerErrorMsg | ServerTokenRefreshedMsg | ServerPresenceStateMsg | BroadcastAckMessage;
|
|
673
|
+
type ServerMessage = ServerSubscribedMsg | ServerEventMsg | ServerPongMsg | ServerErrorMsg | ServerTokenRefreshedMsg | ServerPresenceStateMsg | BroadcastAckMessage | ServerChannelRecoveredMsg;
|
|
650
674
|
interface ServerSubscribedMsg {
|
|
651
675
|
type: 'subscribed';
|
|
652
676
|
channel: string;
|
|
@@ -689,6 +713,14 @@ interface ServerPresenceStateMsg {
|
|
|
689
713
|
channel: string;
|
|
690
714
|
presences: PresenceInfo[];
|
|
691
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* L'écoute `postgres_changes` qui avait valu une erreur (`CHANNEL_ERROR`) à ce canal est rétablie :
|
|
718
|
+
* les changements reprennent. Le SDK repasse le canal en `SUBSCRIBED`.
|
|
719
|
+
*/
|
|
720
|
+
interface ServerChannelRecoveredMsg {
|
|
721
|
+
type: 'channel_recovered';
|
|
722
|
+
channel: string;
|
|
723
|
+
}
|
|
692
724
|
/** Événement realtime (CDC, broadcast, presence) */
|
|
693
725
|
interface RealtimeEvent<T = Record<string, unknown>> {
|
|
694
726
|
id: string;
|
|
@@ -741,6 +773,11 @@ interface RealtimePostgresUpdatePayload<T = Record<string, unknown>> extends Rea
|
|
|
741
773
|
eventType: 'UPDATE';
|
|
742
774
|
event: 'update';
|
|
743
775
|
new: T;
|
|
776
|
+
/**
|
|
777
|
+
* Colonnes de **clé primaire** de l'ancienne ligne seulement (`{}` si la table n'en a pas), jamais
|
|
778
|
+
* ses autres valeurs : la sécurité au niveau des lignes n'est évaluée que sur la nouvelle ligne.
|
|
779
|
+
* Si la clé a changé, ce sont les valeurs d'avant.
|
|
780
|
+
*/
|
|
744
781
|
old: Partial<T>;
|
|
745
782
|
}
|
|
746
783
|
/** Payload enrichi pour un événement CDC PostgreSQL de suppression */
|
|
@@ -748,6 +785,7 @@ interface RealtimePostgresDeletePayload<T = Record<string, unknown>> extends Rea
|
|
|
748
785
|
eventType: 'DELETE';
|
|
749
786
|
event: 'delete';
|
|
750
787
|
new: null;
|
|
788
|
+
/** Colonnes de **clé primaire** de la ligne supprimée seulement (`{}` si la table n'en a pas). */
|
|
751
789
|
old: Partial<T> | null;
|
|
752
790
|
}
|
|
753
791
|
/** Union discriminée par eventType pour les événements CDC PostgreSQL */
|
|
@@ -803,12 +841,14 @@ interface PostgresChangesFilter {
|
|
|
803
841
|
* (defense-in-depth) : il ne peut que sur-filtrer, jamais faire fuiter un
|
|
804
842
|
* event que le serveur aurait dû exclure.
|
|
805
843
|
*
|
|
844
|
+
* **INSERT / UPDATE** : le prédicat est évalué sur la **nouvelle ligne** (`record`).
|
|
845
|
+
*
|
|
806
846
|
* **DELETE** : le prédicat est évalué sur l'**ancienne ligne** (`old_record`),
|
|
807
|
-
* puisque le `record` d'un DELETE est vide côté CDC.
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
847
|
+
* puisque le `record` d'un DELETE est vide côté CDC. `old_record` ne contient
|
|
848
|
+
* que la clé primaire (UPDATE comme DELETE) — un filtre sur une autre colonne
|
|
849
|
+
* exclura systématiquement les DELETE, **sauf `is.null`/`isdistinct`** qui
|
|
850
|
+
* traitent une colonne absente comme `NULL` et peuvent donc matcher même sans
|
|
851
|
+
* cette colonne dans `old_record`.
|
|
812
852
|
*/
|
|
813
853
|
filter?: string;
|
|
814
854
|
}
|
|
@@ -847,9 +887,13 @@ interface BroadcastFilter {
|
|
|
847
887
|
* consultable pour ce canal — laisser passer serait fail-open). C'est le modèle de
|
|
848
888
|
* Phoenix, dont dérive le protocole Supabase Realtime : on rejoint un topic avant d'y
|
|
849
889
|
* pousser quoi que ce soit. Appelez `.subscribe()` — et vérifiez le statut qu'il rend.
|
|
890
|
+
* - `'invalid_payload'` — le `payload` porte lui-même une clé `event`. Le nom de l'événement
|
|
891
|
+
* voyage dans le payload du fil (`{ event, ...payload }`) : une clé `event` du payload
|
|
892
|
+
* l'écraserait et le message serait routé sous un autre nom chez les abonnés. Rien n'est
|
|
893
|
+
* émis ; renommez la clé (`eventName`, `kind`…).
|
|
850
894
|
* - `'error'` — l'envoi lui-même a levé une exception (sérialisation, transport).
|
|
851
895
|
*/
|
|
852
|
-
type BroadcastSendStatus = 'ok' | 'timeout' | 'not_connected' | 'requires_subscription' | 'error';
|
|
896
|
+
type BroadcastSendStatus = 'ok' | 'timeout' | 'not_connected' | 'requires_subscription' | 'invalid_payload' | 'error';
|
|
853
897
|
/** Config pour presence */
|
|
854
898
|
interface PresenceFilter {
|
|
855
899
|
event: 'sync' | 'join' | 'leave';
|
|
@@ -1897,7 +1941,8 @@ interface SerializeCookieOptions {
|
|
|
1897
1941
|
domain?: string;
|
|
1898
1942
|
maxAge?: number;
|
|
1899
1943
|
secure?: boolean;
|
|
1900
|
-
|
|
1944
|
+
/** Minuscules : forme acceptée par `ResponseCookie` de Next.js (`'lax' | 'strict' | 'none'`). */
|
|
1945
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
1901
1946
|
httpOnly?: boolean;
|
|
1902
1947
|
}
|
|
1903
1948
|
/**
|
|
@@ -1905,7 +1950,6 @@ interface SerializeCookieOptions {
|
|
|
1905
1950
|
*/
|
|
1906
1951
|
declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
|
|
1907
1952
|
|
|
1908
|
-
/** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
|
|
1909
1953
|
declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
1910
1954
|
/** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
|
|
1911
1955
|
declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
|
|
@@ -1980,7 +2024,7 @@ interface CookieOptions {
|
|
|
1980
2024
|
domain?: string;
|
|
1981
2025
|
maxAge?: number;
|
|
1982
2026
|
secure?: boolean;
|
|
1983
|
-
sameSite?: '
|
|
2027
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
1984
2028
|
httpOnly?: boolean;
|
|
1985
2029
|
}
|
|
1986
2030
|
/**
|
|
@@ -2035,4 +2079,4 @@ declare class LocalAuthStore extends BaseAuthStore {
|
|
|
2035
2079
|
private _removeStorage;
|
|
2036
2080
|
}
|
|
2037
2081
|
|
|
2038
|
-
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 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 OAuthSignInOptions, type ObjectMeta, 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 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 };
|
|
2082
|
+
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 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 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - storage/path.ts : invalid_path
|
|
15
15
|
* - db/QueryBuilder.ts: PGRST116 (parité PostgREST : `.single()` sur 0/plusieurs lignes)
|
|
16
16
|
*/
|
|
17
|
-
type AuraErrorCode = '
|
|
17
|
+
type AuraErrorCode = 'NO_SESSION' | 'TIMEOUT' | 'NETWORK_ERROR' | 'INVALID_RESPONSE' | 'REFRESH_DISCARDED' | 'UNVERIFIABLE_TOKEN' | 'NO_CODE_VERIFIER' | 'PKCE_GENERATION_FAILED' | 'MISSING_EMAIL' | 'JWKS_FETCH_FAILED' | 'SIGNATURE_INVALID' | 'TOKEN_EXPIRED' | 'TOKEN_NOT_YET_VALID' | 'INVALID_JWT' | 'UNSUPPORTED_ALG' | 'NO_MATCHING_KEY' | 'INVALID_ISSUER' | 'INVALID_AUDIENCE' | 'INVALID_PATH' | 'PGRST116' | 'INVALID_CREDENTIALS' | 'WEAK_PASSWORD' | 'EMAIL_ALREADY_IN_USE' | 'USER_NOT_FOUND' | 'RATE_LIMITED' | 'SESSION_EXPIRED' | 'INVALID_OTP' | 'SESSION_REVOKED' | (string & {});
|
|
18
18
|
/**
|
|
19
19
|
* Structured error thrown/returned by the Aurabase SDK.
|
|
20
20
|
* Mirrors the backend's `AuraError` JSON shape with semantic DX normalization.
|
|
@@ -24,6 +24,10 @@ declare class AuraError extends Error {
|
|
|
24
24
|
readonly code: AuraErrorCode;
|
|
25
25
|
readonly details: unknown;
|
|
26
26
|
readonly retryAfter?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
29
|
+
*/
|
|
30
|
+
readonly hint?: string;
|
|
27
31
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
28
32
|
protected readonly __isAuraError = true;
|
|
29
33
|
constructor(status: number, body?: {
|
|
@@ -33,10 +37,20 @@ declare class AuraError extends Error {
|
|
|
33
37
|
};
|
|
34
38
|
message?: string;
|
|
35
39
|
} | null, headers?: Headers | Record<string, string | null | undefined>);
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
42
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
43
|
+
*/
|
|
44
|
+
get legacyCode(): string;
|
|
36
45
|
get isNotFound(): boolean;
|
|
37
46
|
get isUnauthorized(): boolean;
|
|
38
47
|
get isForbidden(): boolean;
|
|
39
48
|
get isRateLimited(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
51
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
52
|
+
*/
|
|
53
|
+
get isQuotaExceeded(): boolean;
|
|
40
54
|
}
|
|
41
55
|
declare function isAuraError(error: unknown): error is AuraError;
|
|
42
56
|
|
|
@@ -260,12 +274,18 @@ interface AuthChangePayload {
|
|
|
260
274
|
interface SignUpRequest {
|
|
261
275
|
email: string;
|
|
262
276
|
password: string;
|
|
277
|
+
name?: string;
|
|
263
278
|
captcha_token?: string;
|
|
279
|
+
options?: {
|
|
280
|
+
emailRedirectTo?: string;
|
|
281
|
+
data?: Record<string, unknown>;
|
|
282
|
+
};
|
|
264
283
|
}
|
|
265
284
|
interface SignInRequest {
|
|
266
285
|
email: string;
|
|
267
286
|
password: string;
|
|
268
287
|
captcha_token?: string;
|
|
288
|
+
mfa?: 'required' | 'optional';
|
|
269
289
|
}
|
|
270
290
|
interface MagicLinkRequest {
|
|
271
291
|
email: string;
|
|
@@ -329,6 +349,10 @@ interface OAuthSignInOptions {
|
|
|
329
349
|
*/
|
|
330
350
|
flowType?: 'pkce' | 'implicit';
|
|
331
351
|
}
|
|
352
|
+
interface OAuthSignInObjectOptions extends OAuthSignInOptions {
|
|
353
|
+
provider: string;
|
|
354
|
+
options?: OAuthSignInOptions;
|
|
355
|
+
}
|
|
332
356
|
interface PasswordResetRequest {
|
|
333
357
|
email: string;
|
|
334
358
|
}
|
|
@@ -646,7 +670,7 @@ interface ObjectMeta {
|
|
|
646
670
|
metadata: Record<string, string>;
|
|
647
671
|
}
|
|
648
672
|
/** Événement serveur brut reçu via WebSocket (union discriminée par type) */
|
|
649
|
-
type ServerMessage = ServerSubscribedMsg | ServerEventMsg | ServerPongMsg | ServerErrorMsg | ServerTokenRefreshedMsg | ServerPresenceStateMsg | BroadcastAckMessage;
|
|
673
|
+
type ServerMessage = ServerSubscribedMsg | ServerEventMsg | ServerPongMsg | ServerErrorMsg | ServerTokenRefreshedMsg | ServerPresenceStateMsg | BroadcastAckMessage | ServerChannelRecoveredMsg;
|
|
650
674
|
interface ServerSubscribedMsg {
|
|
651
675
|
type: 'subscribed';
|
|
652
676
|
channel: string;
|
|
@@ -689,6 +713,14 @@ interface ServerPresenceStateMsg {
|
|
|
689
713
|
channel: string;
|
|
690
714
|
presences: PresenceInfo[];
|
|
691
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* L'écoute `postgres_changes` qui avait valu une erreur (`CHANNEL_ERROR`) à ce canal est rétablie :
|
|
718
|
+
* les changements reprennent. Le SDK repasse le canal en `SUBSCRIBED`.
|
|
719
|
+
*/
|
|
720
|
+
interface ServerChannelRecoveredMsg {
|
|
721
|
+
type: 'channel_recovered';
|
|
722
|
+
channel: string;
|
|
723
|
+
}
|
|
692
724
|
/** Événement realtime (CDC, broadcast, presence) */
|
|
693
725
|
interface RealtimeEvent<T = Record<string, unknown>> {
|
|
694
726
|
id: string;
|
|
@@ -741,6 +773,11 @@ interface RealtimePostgresUpdatePayload<T = Record<string, unknown>> extends Rea
|
|
|
741
773
|
eventType: 'UPDATE';
|
|
742
774
|
event: 'update';
|
|
743
775
|
new: T;
|
|
776
|
+
/**
|
|
777
|
+
* Colonnes de **clé primaire** de l'ancienne ligne seulement (`{}` si la table n'en a pas), jamais
|
|
778
|
+
* ses autres valeurs : la sécurité au niveau des lignes n'est évaluée que sur la nouvelle ligne.
|
|
779
|
+
* Si la clé a changé, ce sont les valeurs d'avant.
|
|
780
|
+
*/
|
|
744
781
|
old: Partial<T>;
|
|
745
782
|
}
|
|
746
783
|
/** Payload enrichi pour un événement CDC PostgreSQL de suppression */
|
|
@@ -748,6 +785,7 @@ interface RealtimePostgresDeletePayload<T = Record<string, unknown>> extends Rea
|
|
|
748
785
|
eventType: 'DELETE';
|
|
749
786
|
event: 'delete';
|
|
750
787
|
new: null;
|
|
788
|
+
/** Colonnes de **clé primaire** de la ligne supprimée seulement (`{}` si la table n'en a pas). */
|
|
751
789
|
old: Partial<T> | null;
|
|
752
790
|
}
|
|
753
791
|
/** Union discriminée par eventType pour les événements CDC PostgreSQL */
|
|
@@ -803,12 +841,14 @@ interface PostgresChangesFilter {
|
|
|
803
841
|
* (defense-in-depth) : il ne peut que sur-filtrer, jamais faire fuiter un
|
|
804
842
|
* event que le serveur aurait dû exclure.
|
|
805
843
|
*
|
|
844
|
+
* **INSERT / UPDATE** : le prédicat est évalué sur la **nouvelle ligne** (`record`).
|
|
845
|
+
*
|
|
806
846
|
* **DELETE** : le prédicat est évalué sur l'**ancienne ligne** (`old_record`),
|
|
807
|
-
* puisque le `record` d'un DELETE est vide côté CDC.
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
847
|
+
* puisque le `record` d'un DELETE est vide côté CDC. `old_record` ne contient
|
|
848
|
+
* que la clé primaire (UPDATE comme DELETE) — un filtre sur une autre colonne
|
|
849
|
+
* exclura systématiquement les DELETE, **sauf `is.null`/`isdistinct`** qui
|
|
850
|
+
* traitent une colonne absente comme `NULL` et peuvent donc matcher même sans
|
|
851
|
+
* cette colonne dans `old_record`.
|
|
812
852
|
*/
|
|
813
853
|
filter?: string;
|
|
814
854
|
}
|
|
@@ -847,9 +887,13 @@ interface BroadcastFilter {
|
|
|
847
887
|
* consultable pour ce canal — laisser passer serait fail-open). C'est le modèle de
|
|
848
888
|
* Phoenix, dont dérive le protocole Supabase Realtime : on rejoint un topic avant d'y
|
|
849
889
|
* pousser quoi que ce soit. Appelez `.subscribe()` — et vérifiez le statut qu'il rend.
|
|
890
|
+
* - `'invalid_payload'` — le `payload` porte lui-même une clé `event`. Le nom de l'événement
|
|
891
|
+
* voyage dans le payload du fil (`{ event, ...payload }`) : une clé `event` du payload
|
|
892
|
+
* l'écraserait et le message serait routé sous un autre nom chez les abonnés. Rien n'est
|
|
893
|
+
* émis ; renommez la clé (`eventName`, `kind`…).
|
|
850
894
|
* - `'error'` — l'envoi lui-même a levé une exception (sérialisation, transport).
|
|
851
895
|
*/
|
|
852
|
-
type BroadcastSendStatus = 'ok' | 'timeout' | 'not_connected' | 'requires_subscription' | 'error';
|
|
896
|
+
type BroadcastSendStatus = 'ok' | 'timeout' | 'not_connected' | 'requires_subscription' | 'invalid_payload' | 'error';
|
|
853
897
|
/** Config pour presence */
|
|
854
898
|
interface PresenceFilter {
|
|
855
899
|
event: 'sync' | 'join' | 'leave';
|
|
@@ -1897,7 +1941,8 @@ interface SerializeCookieOptions {
|
|
|
1897
1941
|
domain?: string;
|
|
1898
1942
|
maxAge?: number;
|
|
1899
1943
|
secure?: boolean;
|
|
1900
|
-
|
|
1944
|
+
/** Minuscules : forme acceptée par `ResponseCookie` de Next.js (`'lax' | 'strict' | 'none'`). */
|
|
1945
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
1901
1946
|
httpOnly?: boolean;
|
|
1902
1947
|
}
|
|
1903
1948
|
/**
|
|
@@ -1905,7 +1950,6 @@ interface SerializeCookieOptions {
|
|
|
1905
1950
|
*/
|
|
1906
1951
|
declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
|
|
1907
1952
|
|
|
1908
|
-
/** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
|
|
1909
1953
|
declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
1910
1954
|
/** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
|
|
1911
1955
|
declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
|
|
@@ -1980,7 +2024,7 @@ interface CookieOptions {
|
|
|
1980
2024
|
domain?: string;
|
|
1981
2025
|
maxAge?: number;
|
|
1982
2026
|
secure?: boolean;
|
|
1983
|
-
sameSite?: '
|
|
2027
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
1984
2028
|
httpOnly?: boolean;
|
|
1985
2029
|
}
|
|
1986
2030
|
/**
|
|
@@ -2035,4 +2079,4 @@ declare class LocalAuthStore extends BaseAuthStore {
|
|
|
2035
2079
|
private _removeStorage;
|
|
2036
2080
|
}
|
|
2037
2081
|
|
|
2038
|
-
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 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 OAuthSignInOptions, type ObjectMeta, 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 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 };
|
|
2082
|
+
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 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 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 };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
// src/AuraError.ts
|
|
2
|
+
var LEGACY_LOWERCASE_CODES = /* @__PURE__ */ new Set([
|
|
3
|
+
"NO_SESSION",
|
|
4
|
+
"TIMEOUT",
|
|
5
|
+
"NETWORK_ERROR",
|
|
6
|
+
"INVALID_RESPONSE",
|
|
7
|
+
"REFRESH_DISCARDED",
|
|
8
|
+
"UNVERIFIABLE_TOKEN",
|
|
9
|
+
"NO_CODE_VERIFIER",
|
|
10
|
+
"PKCE_GENERATION_FAILED",
|
|
11
|
+
"MISSING_EMAIL",
|
|
12
|
+
"JWKS_FETCH_FAILED",
|
|
13
|
+
"SIGNATURE_INVALID",
|
|
14
|
+
"TOKEN_EXPIRED",
|
|
15
|
+
"TOKEN_NOT_YET_VALID",
|
|
16
|
+
"INVALID_JWT",
|
|
17
|
+
"UNSUPPORTED_ALG",
|
|
18
|
+
"NO_MATCHING_KEY",
|
|
19
|
+
"INVALID_ISSUER",
|
|
20
|
+
"INVALID_AUDIENCE",
|
|
21
|
+
"INVALID_PATH",
|
|
22
|
+
"INVALID_CREDENTIALS",
|
|
23
|
+
"WEAK_PASSWORD",
|
|
24
|
+
"EMAIL_ALREADY_IN_USE",
|
|
25
|
+
"USER_NOT_FOUND",
|
|
26
|
+
"SESSION_EXPIRED",
|
|
27
|
+
"INVALID_OTP",
|
|
28
|
+
"SESSION_REVOKED"
|
|
29
|
+
]);
|
|
30
|
+
function normalizeErrorCode(code) {
|
|
31
|
+
return code.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
32
|
+
}
|
|
2
33
|
var AuraError = class _AuraError extends Error {
|
|
3
34
|
status;
|
|
4
35
|
code;
|
|
5
36
|
details;
|
|
6
37
|
retryAfter;
|
|
38
|
+
/**
|
|
39
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
40
|
+
*/
|
|
41
|
+
hint;
|
|
7
42
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
8
43
|
__isAuraError = true;
|
|
9
44
|
constructor(status, body, headers) {
|
|
10
|
-
|
|
45
|
+
const serverMessage = body?.error?.message ?? body?.message;
|
|
46
|
+
let rawMessage = serverMessage ?? (status === 429 ? "Too many requests, please wait before retrying." : `HTTP ${status}`);
|
|
11
47
|
let retryAfterSec;
|
|
12
48
|
if (headers) {
|
|
13
49
|
const getHeader = (name) => {
|
|
@@ -22,8 +58,8 @@ var AuraError = class _AuraError extends Error {
|
|
|
22
58
|
const sec = parseInt(ra, 10);
|
|
23
59
|
if (!isNaN(sec)) {
|
|
24
60
|
retryAfterSec = sec;
|
|
25
|
-
if (status === 429) {
|
|
26
|
-
rawMessage = `
|
|
61
|
+
if (status === 429 && !serverMessage) {
|
|
62
|
+
rawMessage = `Too many requests, please wait ${sec} second${sec > 1 ? "s" : ""} before retrying.`;
|
|
27
63
|
}
|
|
28
64
|
}
|
|
29
65
|
}
|
|
@@ -35,30 +71,47 @@ var AuraError = class _AuraError extends Error {
|
|
|
35
71
|
if (retryAfterSec !== void 0) {
|
|
36
72
|
this.retryAfter = retryAfterSec;
|
|
37
73
|
}
|
|
38
|
-
const
|
|
74
|
+
const hint = body?.error?.hint;
|
|
75
|
+
if (typeof hint === "string" && hint) {
|
|
76
|
+
this.hint = hint;
|
|
77
|
+
}
|
|
78
|
+
const rawCode = typeof body?.error?.code === "string" ? body.error.code : void 0;
|
|
79
|
+
const canonical = rawCode ? normalizeErrorCode(rawCode) : void 0;
|
|
39
80
|
const lowerMsg = rawMessage.toLowerCase();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
81
|
+
if (canonical === "USER_ALREADY_EXISTS") {
|
|
82
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
83
|
+
} else if (canonical === "TOKEN_EXPIRED" && status === 401) {
|
|
84
|
+
this.code = "SESSION_EXPIRED";
|
|
85
|
+
} else if (canonical === "RATE_LIMIT_EXCEEDED") {
|
|
86
|
+
this.code = "RATE_LIMITED";
|
|
87
|
+
} else if (canonical && !isGenericCode(canonical)) {
|
|
88
|
+
this.code = canonical;
|
|
89
|
+
} else if (status === 429) {
|
|
90
|
+
this.code = "RATE_LIMITED";
|
|
45
91
|
} else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
|
|
46
|
-
this.code = "
|
|
47
|
-
} else if (status === 401 && lowerMsg.includes("r\xE9voqu\xE9e")) {
|
|
48
|
-
this.code = "
|
|
49
|
-
} else if (status === 401 && lowerMsg.includes("expir\xE9")) {
|
|
50
|
-
this.code = "
|
|
92
|
+
this.code = "INVALID_CREDENTIALS";
|
|
93
|
+
} else if (status === 401 && (lowerMsg.includes("r\xE9voqu\xE9e") || lowerMsg.includes("revoked"))) {
|
|
94
|
+
this.code = "SESSION_REVOKED";
|
|
95
|
+
} else if (status === 401 && (lowerMsg.includes("expir\xE9") || lowerMsg.includes("expired"))) {
|
|
96
|
+
this.code = "SESSION_EXPIRED";
|
|
51
97
|
} else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
|
|
52
|
-
this.code = "
|
|
98
|
+
this.code = "WEAK_PASSWORD";
|
|
53
99
|
} else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
|
|
54
|
-
this.code = "
|
|
55
|
-
} else if (status === 404 && lowerMsg.includes("utilisateur")) {
|
|
56
|
-
this.code = "
|
|
100
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
101
|
+
} else if (status === 404 && (lowerMsg.includes("utilisateur") || lowerMsg.includes("user not found"))) {
|
|
102
|
+
this.code = "USER_NOT_FOUND";
|
|
57
103
|
} else {
|
|
58
|
-
this.code =
|
|
104
|
+
this.code = canonical ?? `HTTP_${status}`;
|
|
59
105
|
}
|
|
60
106
|
Object.setPrototypeOf(this, _AuraError.prototype);
|
|
61
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
110
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
111
|
+
*/
|
|
112
|
+
get legacyCode() {
|
|
113
|
+
return LEGACY_LOWERCASE_CODES.has(this.code) ? this.code.toLowerCase() : this.code;
|
|
114
|
+
}
|
|
62
115
|
get isNotFound() {
|
|
63
116
|
return this.status === 404;
|
|
64
117
|
}
|
|
@@ -71,7 +124,17 @@ var AuraError = class _AuraError extends Error {
|
|
|
71
124
|
get isRateLimited() {
|
|
72
125
|
return this.status === 429;
|
|
73
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
129
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
130
|
+
*/
|
|
131
|
+
get isQuotaExceeded() {
|
|
132
|
+
return this.code === "QUOTA_EXCEEDED";
|
|
133
|
+
}
|
|
74
134
|
};
|
|
135
|
+
function isGenericCode(code) {
|
|
136
|
+
return code === "UNAUTHORIZED" || code === "BAD_REQUEST" || code === "CONFLICT" || code.startsWith("HTTP_");
|
|
137
|
+
}
|
|
75
138
|
function isAuraError(error) {
|
|
76
139
|
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
77
140
|
}
|
|
@@ -336,6 +399,17 @@ var BaseAuthStore = class {
|
|
|
336
399
|
// src/fetch.ts
|
|
337
400
|
var REFRESH_MARGIN_SEC = 30;
|
|
338
401
|
var RETRY_AFTER_CAP_MS = 6e4;
|
|
402
|
+
function networkErrorMessage(err, url) {
|
|
403
|
+
const cause = err?.cause;
|
|
404
|
+
const reason = cause?.code ?? cause?.message ?? (err instanceof Error ? err.message : "request failed");
|
|
405
|
+
let target = url;
|
|
406
|
+
try {
|
|
407
|
+
const u = new URL(url);
|
|
408
|
+
target = `${u.origin}${u.pathname}`;
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
return `Cannot reach ${target} (${reason}). Check the project URL (NEXT_PUBLIC_AURABASE_URL) and your network.`;
|
|
412
|
+
}
|
|
339
413
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
340
414
|
function unwrapEnvelope(body) {
|
|
341
415
|
if (body !== null && typeof body === "object" && "data" in body) {
|
|
@@ -457,7 +531,7 @@ async function doRefresh(client) {
|
|
|
457
531
|
error: new AuraError(0, {
|
|
458
532
|
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
459
533
|
code: "network_error",
|
|
460
|
-
message: err
|
|
534
|
+
message: networkErrorMessage(err, `${client.baseUrl}/v1/auth/refresh`)
|
|
461
535
|
}
|
|
462
536
|
})
|
|
463
537
|
};
|
|
@@ -658,7 +732,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
658
732
|
error: new AuraError(0, {
|
|
659
733
|
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
660
734
|
code: "network_error",
|
|
661
|
-
message: err
|
|
735
|
+
message: networkErrorMessage(err, url)
|
|
662
736
|
}
|
|
663
737
|
})
|
|
664
738
|
};
|
|
@@ -691,7 +765,9 @@ async function fetchWithAuth(url, init, client) {
|
|
|
691
765
|
}
|
|
692
766
|
}
|
|
693
767
|
if (res.status === 204) {
|
|
694
|
-
|
|
768
|
+
const totalFromCr2 = parseContentRangeTotal(res.headers.get("content-range"));
|
|
769
|
+
if (totalFromCr2 === void 0) return { data: null, error: null };
|
|
770
|
+
return { data: null, error: null, meta: { total: totalFromCr2 }, count: totalFromCr2 };
|
|
695
771
|
}
|
|
696
772
|
if (!res.ok) {
|
|
697
773
|
let body2 = null;
|
|
@@ -805,13 +881,16 @@ function parseCookieHeader(header) {
|
|
|
805
881
|
}
|
|
806
882
|
return result;
|
|
807
883
|
}
|
|
884
|
+
function sameSiteAttribute(v) {
|
|
885
|
+
return v.charAt(0).toUpperCase() + v.slice(1);
|
|
886
|
+
}
|
|
808
887
|
function serializeCookieHeader(name, value, options = {}) {
|
|
809
888
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
810
889
|
if (options.path) parts.push(`Path=${options.path}`);
|
|
811
890
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
812
891
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
813
892
|
if (options.secure) parts.push("Secure");
|
|
814
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
893
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
815
894
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
816
895
|
return parts.join("; ");
|
|
817
896
|
}
|
|
@@ -931,7 +1010,7 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
931
1010
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
932
1011
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
933
1012
|
if (options.secure) parts.push("Secure");
|
|
934
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
1013
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
935
1014
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
936
1015
|
return parts.join("; ");
|
|
937
1016
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurabase/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"author": "Aurabase",
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/
|
|
34
|
+
"url": "git+https://github.com/Aurabase-cloud/Aurabase.git",
|
|
35
35
|
"directory": "aurabase-js/packages/core"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://aurabase.cloud",
|
|
38
38
|
"bugs": {
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/Aurabase-cloud/Aurabase/issues"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"aurabase",
|