@aurabase/core 0.6.1 → 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 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: 'Lax', httpOnly: true })
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
- let rawMessage = body?.error?.message ?? body?.message ?? (status === 429 ? "Trop de requ\xEAtes, veuillez patienter avant de r\xE9essayer." : `HTTP ${status}`);
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 = `Trop de requ\xEAtes, veuillez patienter ${sec} seconde${sec > 1 ? "s" : ""} avant de r\xE9essayer.`;
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,46 +73,47 @@ var AuraError = class _AuraError extends Error {
37
73
  if (retryAfterSec !== void 0) {
38
74
  this.retryAfter = retryAfterSec;
39
75
  }
40
- const rawCode = body?.error?.code;
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
- if (rawCode === "INVALID_CREDENTIALS" || rawCode === "invalid_credentials") {
43
- this.code = "invalid_credentials";
44
- } else if (rawCode === "EMAIL_ALREADY_IN_USE" || rawCode === "USER_ALREADY_EXISTS" || rawCode === "email_already_in_use") {
45
- this.code = "email_already_in_use";
46
- } else if (rawCode === "WEAK_PASSWORD" || rawCode === "weak_password") {
47
- this.code = "weak_password";
48
- } else if (rawCode === "USER_NOT_FOUND" || rawCode === "user_not_found") {
49
- this.code = "user_not_found";
50
- } else if (rawCode === "SESSION_EXPIRED" || rawCode === "session_expired") {
51
- this.code = "session_expired";
52
- } else if (rawCode === "SESSION_REVOKED" || rawCode === "session_revoked") {
53
- this.code = "session_revoked";
54
- } else if (rawCode === "INVALID_OTP" || rawCode === "invalid_otp") {
55
- this.code = "invalid_otp";
56
- } else if (rawCode === "RATE_LIMITED" || rawCode === "rate_limit_exceeded" || status === 429) {
57
- this.code = "rate_limit_exceeded";
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";
93
+ } else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
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";
99
+ } else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
100
+ this.code = "WEAK_PASSWORD";
101
+ } else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
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";
58
105
  } else {
59
- const isGenericHttpCode = typeof rawCode !== "string" || rawCode === "UNAUTHORIZED" || rawCode === "BAD_REQUEST" || rawCode === "CONFLICT" || rawCode.startsWith("http_");
60
- if (!isGenericHttpCode) {
61
- this.code = rawCode;
62
- } else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
63
- this.code = "invalid_credentials";
64
- } else if (status === 401 && lowerMsg.includes("r\xE9voqu\xE9e")) {
65
- this.code = "session_revoked";
66
- } else if (status === 401 && lowerMsg.includes("expir\xE9")) {
67
- this.code = "session_expired";
68
- } else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
69
- this.code = "weak_password";
70
- } else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
71
- this.code = "email_already_in_use";
72
- } else if (status === 404 && lowerMsg.includes("utilisateur")) {
73
- this.code = "user_not_found";
74
- } else {
75
- this.code = rawCode ?? `http_${status}`;
76
- }
106
+ this.code = canonical ?? `HTTP_${status}`;
77
107
  }
78
108
  Object.setPrototypeOf(this, _AuraError.prototype);
79
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
+ }
80
117
  get isNotFound() {
81
118
  return this.status === 404;
82
119
  }
@@ -89,7 +126,17 @@ var AuraError = class _AuraError extends Error {
89
126
  get isRateLimited() {
90
127
  return this.status === 429;
91
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
+ }
92
136
  };
137
+ function isGenericCode(code) {
138
+ return code === "UNAUTHORIZED" || code === "BAD_REQUEST" || code === "CONFLICT" || code.startsWith("HTTP_");
139
+ }
93
140
  function isAuraError(error) {
94
141
  return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
95
142
  }
@@ -354,6 +401,17 @@ var BaseAuthStore = class {
354
401
  // src/fetch.ts
355
402
  var REFRESH_MARGIN_SEC = 30;
356
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
+ }
357
415
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
358
416
  function unwrapEnvelope(body) {
359
417
  if (body !== null && typeof body === "object" && "data" in body) {
@@ -475,7 +533,7 @@ async function doRefresh(client) {
475
533
  error: new AuraError(0, {
476
534
  error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
477
535
  code: "network_error",
478
- message: err instanceof Error ? err.message : "Network request failed"
536
+ message: networkErrorMessage(err, `${client.baseUrl}/v1/auth/refresh`)
479
537
  }
480
538
  })
481
539
  };
@@ -676,7 +734,7 @@ async function fetchWithAuth(url, init, client) {
676
734
  error: new AuraError(0, {
677
735
  error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
678
736
  code: "network_error",
679
- message: err instanceof Error ? err.message : "Network request failed"
737
+ message: networkErrorMessage(err, url)
680
738
  }
681
739
  })
682
740
  };
@@ -825,13 +883,16 @@ function parseCookieHeader(header) {
825
883
  }
826
884
  return result;
827
885
  }
886
+ function sameSiteAttribute(v) {
887
+ return v.charAt(0).toUpperCase() + v.slice(1);
888
+ }
828
889
  function serializeCookieHeader(name, value, options = {}) {
829
890
  const parts = [`${name}=${encodeURIComponent(value)}`];
830
891
  if (options.path) parts.push(`Path=${options.path}`);
831
892
  if (options.domain) parts.push(`Domain=${options.domain}`);
832
893
  if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
833
894
  if (options.secure) parts.push("Secure");
834
- if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
895
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
835
896
  if (options.httpOnly) parts.push("HttpOnly");
836
897
  return parts.join("; ");
837
898
  }
@@ -951,7 +1012,7 @@ var LocalAuthStore = class extends BaseAuthStore {
951
1012
  if (options.domain) parts.push(`Domain=${options.domain}`);
952
1013
  if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
953
1014
  if (options.secure) parts.push("Secure");
954
- if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
1015
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
955
1016
  if (options.httpOnly) parts.push("HttpOnly");
956
1017
  return parts.join("; ");
957
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 = '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_limit_exceeded' | 'session_expired' | 'invalid_otp' | 'session_revoked' | (string & {});
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
 
@@ -1927,7 +1941,8 @@ interface SerializeCookieOptions {
1927
1941
  domain?: string;
1928
1942
  maxAge?: number;
1929
1943
  secure?: boolean;
1930
- sameSite?: 'Strict' | 'Lax' | 'None';
1944
+ /** Minuscules : forme acceptée par `ResponseCookie` de Next.js (`'lax' | 'strict' | 'none'`). */
1945
+ sameSite?: 'strict' | 'lax' | 'none';
1931
1946
  httpOnly?: boolean;
1932
1947
  }
1933
1948
  /**
@@ -1935,7 +1950,6 @@ interface SerializeCookieOptions {
1935
1950
  */
1936
1951
  declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
1937
1952
 
1938
- /** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
1939
1953
  declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
1940
1954
  /** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
1941
1955
  declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
@@ -2010,7 +2024,7 @@ interface CookieOptions {
2010
2024
  domain?: string;
2011
2025
  maxAge?: number;
2012
2026
  secure?: boolean;
2013
- sameSite?: 'Strict' | 'Lax' | 'None';
2027
+ sameSite?: 'strict' | 'lax' | 'none';
2014
2028
  httpOnly?: boolean;
2015
2029
  }
2016
2030
  /**
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 = '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_limit_exceeded' | 'session_expired' | 'invalid_otp' | 'session_revoked' | (string & {});
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
 
@@ -1927,7 +1941,8 @@ interface SerializeCookieOptions {
1927
1941
  domain?: string;
1928
1942
  maxAge?: number;
1929
1943
  secure?: boolean;
1930
- sameSite?: 'Strict' | 'Lax' | 'None';
1944
+ /** Minuscules : forme acceptée par `ResponseCookie` de Next.js (`'lax' | 'strict' | 'none'`). */
1945
+ sameSite?: 'strict' | 'lax' | 'none';
1931
1946
  httpOnly?: boolean;
1932
1947
  }
1933
1948
  /**
@@ -1935,7 +1950,6 @@ interface SerializeCookieOptions {
1935
1950
  */
1936
1951
  declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
1937
1952
 
1938
- /** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
1939
1953
  declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
1940
1954
  /** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
1941
1955
  declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
@@ -2010,7 +2024,7 @@ interface CookieOptions {
2010
2024
  domain?: string;
2011
2025
  maxAge?: number;
2012
2026
  secure?: boolean;
2013
- sameSite?: 'Strict' | 'Lax' | 'None';
2027
+ sameSite?: 'strict' | 'lax' | 'none';
2014
2028
  httpOnly?: boolean;
2015
2029
  }
2016
2030
  /**
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
- let rawMessage = body?.error?.message ?? body?.message ?? (status === 429 ? "Trop de requ\xEAtes, veuillez patienter avant de r\xE9essayer." : `HTTP ${status}`);
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 = `Trop de requ\xEAtes, veuillez patienter ${sec} seconde${sec > 1 ? "s" : ""} avant de r\xE9essayer.`;
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,46 +71,47 @@ var AuraError = class _AuraError extends Error {
35
71
  if (retryAfterSec !== void 0) {
36
72
  this.retryAfter = retryAfterSec;
37
73
  }
38
- const rawCode = body?.error?.code;
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
- if (rawCode === "INVALID_CREDENTIALS" || rawCode === "invalid_credentials") {
41
- this.code = "invalid_credentials";
42
- } else if (rawCode === "EMAIL_ALREADY_IN_USE" || rawCode === "USER_ALREADY_EXISTS" || rawCode === "email_already_in_use") {
43
- this.code = "email_already_in_use";
44
- } else if (rawCode === "WEAK_PASSWORD" || rawCode === "weak_password") {
45
- this.code = "weak_password";
46
- } else if (rawCode === "USER_NOT_FOUND" || rawCode === "user_not_found") {
47
- this.code = "user_not_found";
48
- } else if (rawCode === "SESSION_EXPIRED" || rawCode === "session_expired") {
49
- this.code = "session_expired";
50
- } else if (rawCode === "SESSION_REVOKED" || rawCode === "session_revoked") {
51
- this.code = "session_revoked";
52
- } else if (rawCode === "INVALID_OTP" || rawCode === "invalid_otp") {
53
- this.code = "invalid_otp";
54
- } else if (rawCode === "RATE_LIMITED" || rawCode === "rate_limit_exceeded" || status === 429) {
55
- this.code = "rate_limit_exceeded";
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";
91
+ } else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
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";
97
+ } else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
98
+ this.code = "WEAK_PASSWORD";
99
+ } else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
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";
56
103
  } else {
57
- const isGenericHttpCode = typeof rawCode !== "string" || rawCode === "UNAUTHORIZED" || rawCode === "BAD_REQUEST" || rawCode === "CONFLICT" || rawCode.startsWith("http_");
58
- if (!isGenericHttpCode) {
59
- this.code = rawCode;
60
- } else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
61
- this.code = "invalid_credentials";
62
- } else if (status === 401 && lowerMsg.includes("r\xE9voqu\xE9e")) {
63
- this.code = "session_revoked";
64
- } else if (status === 401 && lowerMsg.includes("expir\xE9")) {
65
- this.code = "session_expired";
66
- } else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
67
- this.code = "weak_password";
68
- } else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
69
- this.code = "email_already_in_use";
70
- } else if (status === 404 && lowerMsg.includes("utilisateur")) {
71
- this.code = "user_not_found";
72
- } else {
73
- this.code = rawCode ?? `http_${status}`;
74
- }
104
+ this.code = canonical ?? `HTTP_${status}`;
75
105
  }
76
106
  Object.setPrototypeOf(this, _AuraError.prototype);
77
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
+ }
78
115
  get isNotFound() {
79
116
  return this.status === 404;
80
117
  }
@@ -87,7 +124,17 @@ var AuraError = class _AuraError extends Error {
87
124
  get isRateLimited() {
88
125
  return this.status === 429;
89
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
+ }
90
134
  };
135
+ function isGenericCode(code) {
136
+ return code === "UNAUTHORIZED" || code === "BAD_REQUEST" || code === "CONFLICT" || code.startsWith("HTTP_");
137
+ }
91
138
  function isAuraError(error) {
92
139
  return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
93
140
  }
@@ -352,6 +399,17 @@ var BaseAuthStore = class {
352
399
  // src/fetch.ts
353
400
  var REFRESH_MARGIN_SEC = 30;
354
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
+ }
355
413
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
356
414
  function unwrapEnvelope(body) {
357
415
  if (body !== null && typeof body === "object" && "data" in body) {
@@ -473,7 +531,7 @@ async function doRefresh(client) {
473
531
  error: new AuraError(0, {
474
532
  error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
475
533
  code: "network_error",
476
- message: err instanceof Error ? err.message : "Network request failed"
534
+ message: networkErrorMessage(err, `${client.baseUrl}/v1/auth/refresh`)
477
535
  }
478
536
  })
479
537
  };
@@ -674,7 +732,7 @@ async function fetchWithAuth(url, init, client) {
674
732
  error: new AuraError(0, {
675
733
  error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
676
734
  code: "network_error",
677
- message: err instanceof Error ? err.message : "Network request failed"
735
+ message: networkErrorMessage(err, url)
678
736
  }
679
737
  })
680
738
  };
@@ -823,13 +881,16 @@ function parseCookieHeader(header) {
823
881
  }
824
882
  return result;
825
883
  }
884
+ function sameSiteAttribute(v) {
885
+ return v.charAt(0).toUpperCase() + v.slice(1);
886
+ }
826
887
  function serializeCookieHeader(name, value, options = {}) {
827
888
  const parts = [`${name}=${encodeURIComponent(value)}`];
828
889
  if (options.path) parts.push(`Path=${options.path}`);
829
890
  if (options.domain) parts.push(`Domain=${options.domain}`);
830
891
  if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
831
892
  if (options.secure) parts.push("Secure");
832
- if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
893
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
833
894
  if (options.httpOnly) parts.push("HttpOnly");
834
895
  return parts.join("; ");
835
896
  }
@@ -949,7 +1010,7 @@ var LocalAuthStore = class extends BaseAuthStore {
949
1010
  if (options.domain) parts.push(`Domain=${options.domain}`);
950
1011
  if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
951
1012
  if (options.secure) parts.push("Secure");
952
- if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
1013
+ if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
953
1014
  if (options.httpOnly) parts.push("HttpOnly");
954
1015
  return parts.join("; ");
955
1016
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurabase/core",
3
- "version": "0.6.1",
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/daylami555/aurabase.git",
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/daylami555/aurabase/issues"
39
+ "url": "https://github.com/Aurabase-cloud/Aurabase/issues"
40
40
  },
41
41
  "keywords": [
42
42
  "aurabase",