@aurabase/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -41,6 +41,42 @@ declare function isAuraError(error: unknown): error is AuraError;
41
41
 
42
42
  interface Meta {
43
43
  total?: number;
44
+ /**
45
+ * Numéro de page, quand le backend pagine par page (services non-CRUD).
46
+ * Absent sur le CRUD, qui pagine par `limit`/`offset` ou par curseur.
47
+ */
48
+ page?: number;
49
+ /**
50
+ * A27 — limite **réellement appliquée** par le serveur à cette réponse, plafond compris.
51
+ *
52
+ * C'est le seul moyen de détecter une troncature : une lecture sans `.limit()` est bornée
53
+ * (1000 lignes sur le CRUD aura-db, 1000 objets sur un listing storage), et une `.limit(n)`
54
+ * au-delà du plafond est rabattue en silence. Le champ était rendu par le backend mais
55
+ * **absent de ce type** : le plafond restait donc invisible côté TypeScript alors que la
56
+ * donnée était dans la réponse.
57
+ *
58
+ * Motif de détection : `data.length >= meta.per_page` ⇒ il y a probablement une suite —
59
+ * enchaînez avec `.range()`/`.offset()`, ou `cursor()`/`meta.next_cursor` là où il existe.
60
+ *
61
+ * ⚠️ N15 — **N'EST PAS ÉMIS SUR LE CHEMIN PostgREST**, c.-à-d. sur le CRUD d'un projet
62
+ * PostgreSQL, qui est le moteur NOMINAL. Le champ n'existe que sur les chemins qui
63
+ * connaissent la limite qu'ils ont eux-mêmes appliquée : aura-db natif (MongoDB, ou
64
+ * PostgreSQL avec PostgREST désactivé), listings storage, analytics notifications.
65
+ *
66
+ * Ce n'est pas un oubli du gateway : PostgREST ne rend que `Content-Range` (`0-4/*`), qui
67
+ * décrit les lignes **rendues**, jamais la limite appliquée. Mesuré en réel (PostgREST
68
+ * v12.2.3, `db-max-rows=5`, table de 10 lignes) : une requête `limit=50` rabattue à 5
69
+ * produit exactement le même en-tête qu'une table qui ne contient que 5 lignes. Émettre
70
+ * `per_page = limit demandé` serait donc faux précisément dans le cas de troncature que ce
71
+ * champ sert à détecter — le gateway s'abstient plutôt que de mentir.
72
+ *
73
+ * Sur ce chemin, le motif de détection qui FONCTIONNE est le comptage explicite :
74
+ * demandez `count: 'exact'` (`.count('exact')`) et comparez `data.length` à `meta.total`
75
+ * (`data.length < meta.total` ⇒ il reste des lignes) — mesuré : `{"total":10}` pour
76
+ * 5 lignes rendues. Sans `count`, `meta` vaut `{}` et **aucune** troncature n'est
77
+ * détectable côté client.
78
+ */
79
+ per_page?: number;
44
80
  /**
45
81
  * Curseur opaque de pagination keyset (voir `QueryBuilder.cursor()`, `@aurabase/db`).
46
82
  * SPÉCIFIQUE AURA-DB : n'est émis QUE sur le chemin aura-db natif (MongoDB, ou
@@ -197,8 +233,51 @@ interface MagicLinkRequest {
197
233
  redirect_url?: string;
198
234
  }
199
235
  interface OAuthSignInOptions {
236
+ /**
237
+ * URL d'atterrissage après la connexion (parité Supabase).
238
+ *
239
+ * **Connexion aboutie** — le navigateur est redirigé sur cette URL MÊME, augmentée du seul
240
+ * fragment `#access_token=…&refresh_token=…&expires_in=…&token_type=bearer&user=…`.
241
+ * Aucun segment n'est ajouté.
242
+ *
243
+ * **⚠️ EXCEPTION — utilisateur avec MFA actif** : la connexion n'est pas encore aboutie, il
244
+ * reste un défi à relever. L'atterrissage est alors le sous-chemin dédié
245
+ * `{redirectTo}/oauth/mfa#mfa_token=…` (et `{site_url}/oauth/mfa#mfa_token=…` si `redirectTo`
246
+ * est omise). C'est délibéré : `#mfa_token` n'est pas une session, et l'envoyer sur la page
247
+ * d'accueil de l'application y déposerait un jeton que rien n'attend. Votre application DOIT
248
+ * donc servir une page sur `{redirectTo}/oauth/mfa` : elle y lit `mfa_token` dans le
249
+ * fragment, demande le code TOTP, puis appelle `mfaVerify({ mfa_token, code })`.
250
+ * `detectSessionInUrl` reconnaît ce fragment et émet `MFA_CHALLENGE` avec `mfaToken`
251
+ * (aucune session n'est posée). Le sous-chemin reste sous l'origine et le préfixe de chemin
252
+ * validés : il n'échappe pas à l'allowlist.
253
+ *
254
+ * Contraintes (N25) :
255
+ * - elle doit figurer dans l'allowlist `redirect_urls` du projet — c'est l'URL
256
+ * validée, et c'est exactement celle qui sera servie ;
257
+ * - elle ne doit PAS contenir de fragment (`#…`) : le fragment final porte les
258
+ * jetons et une URL ne peut pas en avoir deux — le démarrage répond `400`.
259
+ *
260
+ * **Divergence Supabase assumée** : une `redirectTo` HORS allowlist fait échouer le
261
+ * démarrage (`400`). Supabase, lui, l'ignore en silence et retombe sur le Site URL.
262
+ * Aurabase préfère l'erreur explicite — une redirection silencieuse vers une autre
263
+ * destination que celle demandée est indétectable côté application.
264
+ *
265
+ * Omise, le retour se fait sur `{site_url du projet}/oauth/callback` (convention
266
+ * historique d'Aurabase, inchangée : `site_url` est une base, pas une page).
267
+ */
200
268
  redirectTo?: string;
201
269
  scopes?: string;
270
+ /**
271
+ * Paramètres d'expérience de connexion transmis au provider (`prompt`, `login_hint`,
272
+ * `display`, `ui_locales`, `max_age`, `acr_values`, `claims_locales`, `access_type`,
273
+ * `include_granted_scopes`, `hd`).
274
+ *
275
+ * Toute autre clé est **refusée** (`error.code === 'invalid_oauth_query_param'`),
276
+ * jamais transmise ni ignorée en silence : les paramètres du protocole (`state`,
277
+ * `nonce`, `client_id`, `scope`, `code_challenge`…) sont calculés par le serveur, et
278
+ * ceux de la requête (`project_id`, `redirect_uri`, `scopes`) ont leur option dédiée.
279
+ * Le backend applique le même contrat et répond `400` — cf. `signInWithOAuth`.
280
+ */
202
281
  queryParams?: Record<string, string>;
203
282
  /**
204
283
  * Active le flux PKCE (génère + stocke un `code_verifier`, envoie
@@ -213,8 +292,16 @@ interface PasswordResetRequest {
213
292
  email: string;
214
293
  }
215
294
  interface UpdateUserRequest {
295
+ /**
296
+ * Nouvelle adresse email.
297
+ *
298
+ * **Ne change PAS l'email immédiatement** (A24) : le serveur envoie un lien de
299
+ * confirmation à la NOUVELLE adresse et répond `200` avec l'utilisateur **inchangé**
300
+ * — l'ancien email est donc normal dans la réponse. Le changement ne prend effet
301
+ * qu'une fois le lien suivi, via `verifyOtp({ type: 'email_change', token })`.
302
+ * Tant que la confirmation n'a pas eu lieu, `user.email` reste l'ancienne valeur.
303
+ */
216
304
  email?: string;
217
- phone?: string;
218
305
  password?: string;
219
306
  current_password?: string;
220
307
  data?: Record<string, unknown>;
@@ -297,6 +384,146 @@ interface SchemaEnum {
297
384
  name: string;
298
385
  values: string[];
299
386
  }
387
+ /**
388
+ * Rôles acceptés dans `grant_execute_to` à la CRÉATION d'une fonction.
389
+ *
390
+ * Miroir exact de l'allowlist backend `ALLOWED_ROLES` (`format_target_roles`,
391
+ * `services/aura-db/src/handlers/schema.rs`) : toute autre valeur est refusée en 400 —
392
+ * y compris `PUBLIC` et le rôle propriétaire `tenant_<uuid>` que la LECTURE
393
+ * (`PgFunctionInfo.grant_execute_to`) peut pourtant rendre. Union fermée à dessein :
394
+ * une faute de frappe se voit à la compilation plutôt qu'en 400 à l'exécution.
395
+ */
396
+ type PgExecutableRole = 'anon' | 'authenticated' | 'service_role' | 'aura_anon' | 'aura_authenticated' | 'aura_service_role';
397
+ /**
398
+ * Corps de `POST /v1/db/functions` — miroir de `CreateFunctionRequest`.
399
+ *
400
+ * Les défauts de sécurité du backend sont documentés champ par champ ci-dessous : ils
401
+ * sont SÛRS (aucune élévation, aucun droit d'exécution) tant qu'on ne les change pas.
402
+ */
403
+ interface CreatePgFunctionRequest {
404
+ /**
405
+ * Nom de la fonction, pris TEL QUEL (la casse est conservée : le DDL le pose entre
406
+ * guillemets). Le backend REFUSE — il n'assainit pas en silence — tout nom qui n'est
407
+ * pas `[A-Za-z0-9_]+` : `calc-total` rend 400, il ne devient pas `calctotal`.
408
+ */
409
+ name: string;
410
+ /**
411
+ * Arguments, dans l'ordre. Même forme que ceux rendus par `GET /schema`
412
+ * (`SchemaFunctionArg`) — le backend désérialise les deux depuis la MÊME structure Rust
413
+ * `FunctionArg { name, data_type, mode }`.
414
+ *
415
+ * Les trois champs sont REQUIS (aucun `serde(default)` côté serveur : un `mode` absent
416
+ * fait échouer la désérialisation). `mode` doit valoir `'IN'` — le seul mode accepté à
417
+ * la création aujourd'hui ; `OUT`/`INOUT`/`VARIADIC` se lisent mais ne s'écrivent pas.
418
+ * `data_type` passe par une ALLOWLIST de formes (`text`, `uuid[]`, `numeric(10,2)`,
419
+ * `timestamp with time zone`…), pas par une denylist.
420
+ */
421
+ args: SchemaFunctionArg[];
422
+ /** Type de retour, même allowlist que `args[].data_type`, `setof x` compris. */
423
+ returns: string;
424
+ /** `plv8` n'est jamais installé : le backend n'accepte que ces deux langages. */
425
+ language: 'plpgsql' | 'sql';
426
+ /**
427
+ * Corps de la fonction. Soumis à la MÊME denylist d'isolation multi-tenant que
428
+ * `sql()` (cf. son docstring), en version PLUS stricte : ni `EXECUTE` (SQL dynamique),
429
+ * ni bascule de RLS — un corps est persistant et potentiellement GRANTable à `anon`.
430
+ */
431
+ body: string;
432
+ /**
433
+ * ⚠️ Défaut SÛR : `'invoker'` quand le champ est omis — la fonction s'exécute avec les
434
+ * droits de son APPELANT, aucune élévation de privilège implicite. `'definer'` la fait
435
+ * tourner avec ceux de son propriétaire (rôle admin du tenant) : à ne poser qu'en
436
+ * connaissance de cause. La casse est repliée côté serveur.
437
+ */
438
+ security?: 'invoker' | 'definer';
439
+ /**
440
+ * ⚠️ Défaut SÛR : AUCUN GRANT quand le champ est omis. Postgres accorde `EXECUTE` à
441
+ * `PUBLIC` sur toute fonction neuve (piège connu) ; le backend le RÉVOQUE
442
+ * systématiquement, de `PUBLIC` comme de tous les rôles de l'allowlist, AVANT
443
+ * d'appliquer ce champ. Une fonction créée sans `grant_execute_to` existe donc mais
444
+ * n'est appelable par personne — pas même en `rpc()` — tant qu'un GRANT explicite
445
+ * n'est pas posé. C'est voulu : un `CREATE OR REPLACE` qui RESTREINT restreint
446
+ * réellement, il n'hérite pas des droits de la version précédente.
447
+ */
448
+ grant_execute_to?: PgExecutableRole[];
449
+ }
450
+ /**
451
+ * Une fonction du schéma du projet, telle que la rend `GET /v1/db/functions`
452
+ * — miroir de `FunctionInfo`.
453
+ *
454
+ * Les quatre premiers champs sont historiques ; `args`, `security` et `grant_execute_to`
455
+ * ont été ajoutés côté serveur (la liste taisait jusque-là ce que la création écrivait
456
+ * pourtant réellement).
457
+ */
458
+ interface PgFunctionInfo {
459
+ name: string;
460
+ /** Type de retour CANONIQUE Postgres : `text`, `text[]`, `SETOF uuid`, `TABLE(x integer)`. */
461
+ return_type: string;
462
+ /** Langage en MAJUSCULES, tel que le rend le catalogue (`SQL`, `PLPGSQL`). */
463
+ language: string;
464
+ /** Corps de la fonction. */
465
+ body: string;
466
+ /** `[]` si la fonction n'a aucun argument. `name` peut être vide (argument anonyme). */
467
+ args: SchemaFunctionArg[];
468
+ /**
469
+ * `'INVOKER'` ou `'DEFINER'` — MAJUSCULES à la lecture, alors que l'écriture attend des
470
+ * minuscules. Typé `string` et non une union fermée : c'est une valeur de fil, un
471
+ * affichage ne doit pas se croire exhaustif.
472
+ */
473
+ security: string;
474
+ /**
475
+ * Rôles portant `EXECUTE`, triés. Peut contenir `'PUBLIC'` et le rôle propriétaire
476
+ * (`tenant_<uuid>`), que Postgres matérialise dans l'ACL.
477
+ *
478
+ * ⚠️ LECTURE D'AUDIT UNIQUEMENT — ces valeurs ne sont PAS valides en écriture (cf.
479
+ * `PgExecutableRole`). Ne jamais les réinjecter telles quelles dans un
480
+ * `createFunction()`.
481
+ */
482
+ grant_execute_to: string[];
483
+ }
484
+ /**
485
+ * Corps de `POST /v1/db/:table/triggers` — miroir de `CreateTriggerRequest`.
486
+ *
487
+ * Le trigger BRANCHE une fonction déjà créée (`createFunction()` avec
488
+ * `returns: 'trigger'`) ; il n'en crée aucune. Les trois identifiants (`name`,
489
+ * `function_name`, et la table) sont pris tels quels ou refusés — jamais assainis en
490
+ * silence.
491
+ */
492
+ interface CreatePgTriggerRequest {
493
+ name: string;
494
+ timing: 'before' | 'after' | 'instead_of';
495
+ /**
496
+ * Au moins un événement, sans DOUBLON (le backend refuse `['insert','insert']` — que
497
+ * Postgres rejetterait de toute façon). `truncate` exige `for_each: 'statement'`.
498
+ */
499
+ events: Array<'insert' | 'update' | 'delete' | 'truncate'>;
500
+ /** Fonction du MÊME schéma, appelée sans argument (`EXECUTE FUNCTION fn()`). */
501
+ function_name: string;
502
+ for_each: 'row' | 'statement';
503
+ }
504
+ /**
505
+ * Un trigger tel que le rend `GET /v1/db/:table/triggers`.
506
+ *
507
+ * ⚠️ La source est `information_schema.triggers`, qui rend UNE LIGNE PAR ÉVÉNEMENT : un
508
+ * trigger déclaré `INSERT OR UPDATE` ressort en DEUX entrées de même `name`, l'une avec
509
+ * `events: 'INSERT'`, l'autre `'UPDATE'`. Regrouper par `name` si l'affichage doit
510
+ * refléter le trigger et non ses événements.
511
+ */
512
+ interface PgTriggerInfo {
513
+ name: string;
514
+ table_name: string;
515
+ /** `'BEFORE'`, `'AFTER'` ou `'INSTEAD OF'` — MAJUSCULES, contrairement à l'écriture. */
516
+ timing: string;
517
+ /** UN SEUL événement par entrée (`'INSERT'`, `'UPDATE'`, …) — voir la note ci-dessus. */
518
+ events: string;
519
+ /**
520
+ * ⚠️ Ce n'est PAS un nom de fonction mais l'`action_statement` complet du catalogue,
521
+ * p. ex. `EXECUTE FUNCTION project_xxx.set_updated_at()`. Le nom d'alias vient du
522
+ * serveur ; il est conservé tel quel plutôt que corrigé côté client, pour que ce type
523
+ * reste un miroir exact de la réponse.
524
+ */
525
+ function_name: string;
526
+ }
300
527
  /**
301
528
  * Options de transformation d'image (vocabulaire façon Supabase) appliquées
302
529
  * côté rendu backend (`GET /v1/storage/{pid}/{bucket}/{key}?width=&height=&
@@ -353,7 +580,19 @@ interface FileEntry {
353
580
  content_type: string;
354
581
  etag?: string | null;
355
582
  last_modified?: string | null;
356
- metadata: Record<string, unknown>;
583
+ /**
584
+ * ⚠️ TOUJOURS `{}` sur le retour de `list()` (C1).
585
+ *
586
+ * `ListObjectsV2` ne renvoie pas les métadonnées d'objet : le backend pose un objet vide
587
+ * (`storage/s3.rs`) et n'enrichit ensuite que `content_type` depuis la base
588
+ * (`handlers/manage.rs`). Le champ était déclaré NON optionnel, ce qui laissait écrire
589
+ * `files.find(f => f.metadata.owner === 'x')` — une recherche qui ne matche jamais, sans
590
+ * erreur ni erreur de type.
591
+ *
592
+ * Pour lire les métadonnées réelles d'un objet, appelez `getMetadata(bucket, key)`, qui
593
+ * fait un `HEAD` et les renvoie peuplées.
594
+ */
595
+ metadata?: Record<string, unknown>;
357
596
  }
358
597
  /** Métadonnées d'un objet storage — forme réelle retournée par getMetadata() */
359
598
  interface ObjectMeta {
@@ -386,9 +625,20 @@ interface ServerEventMsg {
386
625
  interface ServerPongMsg {
387
626
  type: 'pong';
388
627
  }
628
+ /**
629
+ * Erreur serveur.
630
+ *
631
+ * `channel` (B9) distingue une erreur PROPRE À UN CANAL d'une erreur GLOBALE de session :
632
+ * - présent → l'erreur ne concerne QUE ce canal (refus RLS/write policy, canal hors projet,
633
+ * quota de canaux, join/s, présence sans abonnement, retard de messages). Le SDK ne
634
+ * notifie que lui ; si le canal est inconnu du client, personne n'est notifié.
635
+ * - absent → erreur globale (heartbeat, token de session, message illisible, quota de
636
+ * violations) : tous les canaux sont légitimement concernés.
637
+ */
389
638
  interface ServerErrorMsg {
390
639
  type: 'error';
391
640
  message: string;
641
+ channel?: string;
392
642
  }
393
643
  interface ServerTokenRefreshedMsg {
394
644
  type: 'token_refreshed';
@@ -480,6 +730,40 @@ interface PostgresChangesFilter {
480
730
  interface BroadcastFilter {
481
731
  event: string;
482
732
  }
733
+ /**
734
+ * Résultat de `AuraChannel.send()` — un statut TYPÉ par cause.
735
+ *
736
+ * Avant C18/N24, trois échecs sans rapport partageaient la même valeur `'error'` :
737
+ * l'appelant ne pouvait ni les distinguer ni réagir différemment. Chaque cause a
738
+ * désormais son propre code, exhaustivement vérifiable par TypeScript.
739
+ *
740
+ * - `'ok'`
741
+ * — sans `ack` : le message a été **remis au transport** (aucune confirmation
742
+ * serveur n'est demandée dans ce mode) ;
743
+ * — avec `ack` : le serveur a répondu `broadcast_ack` (confirmation de bout en bout).
744
+ * - `'timeout'` — `ack` demandé sur un canal abonné, mais aucun `broadcast_ack` reçu
745
+ * dans les 5 s. Le message est parti ; c'est la réponse qui manque (lien coupé, accusé
746
+ * perdu, serveur saturé). Ce statut ne recouvre QUE cette cause : le cas « le serveur a
747
+ * refusé l'écriture faute d'abonnement » se résout désormais tout de suite en
748
+ * `'requires_subscription'` (C18), au lieu d'être maquillé en attente expirée.
749
+ * - `'not_connected'` — le message n'a **pas quitté le process** : aucun transport lié
750
+ * (canal jamais connecté) ou socket non-OPEN (`RealtimeService._send` jette
751
+ * silencieusement ce qu'il ne peut pas émettre). À retenter après reconnexion.
752
+ * - `'requires_subscription'` — le SERVEUR n'a **pas d'abonnement** pour ce canal : rien
753
+ * n'a été émis. Trois chemins y mènent :
754
+ * 1. `.subscribe()` n'a jamais été appelé ;
755
+ * 2. il a été appelé mais le serveur l'a **refusé** (`Accès refusé au canal privé (RLS
756
+ * policy)`, `private_only`, quota de canaux…) — le canal a alors reçu `CHANNEL_ERROR` ;
757
+ * 3. l'abonnement a été **révoqué** ensuite (`Accès révoqué au channel '…' après token
758
+ * refresh`), ou le serveur a répondu `Non abonné au canal '…'` à une écriture.
759
+ * Le serveur exige l'abonnement avant toute écriture (`handlers/websocket.rs`, bras
760
+ * `ClientMessage::Broadcast` : sans entrée d'abonnement, aucune policy RLS n'est
761
+ * consultable pour ce canal — laisser passer serait fail-open). C'est le modèle de
762
+ * Phoenix, dont dérive le protocole Supabase Realtime : on rejoint un topic avant d'y
763
+ * pousser quoi que ce soit. Appelez `.subscribe()` — et vérifiez le statut qu'il rend.
764
+ * - `'error'` — l'envoi lui-même a levé une exception (sérialisation, transport).
765
+ */
766
+ type BroadcastSendStatus = 'ok' | 'timeout' | 'not_connected' | 'requires_subscription' | 'error';
483
767
  /** Config pour presence */
484
768
  interface PresenceFilter {
485
769
  event: 'sync' | 'join' | 'leave';
@@ -489,7 +773,18 @@ interface ChannelConfig {
489
773
  private?: boolean;
490
774
  /** Configuration broadcast pour ce channel */
491
775
  broadcast?: {
492
- /** Si true, le serveur ACK chaque send() (résout la Promise) */
776
+ /**
777
+ * Si `true`, le serveur accuse réception (`broadcast_ack`) des `send()` émis sur ce
778
+ * canal **une fois qu'il est abonné**, et `send()` ne résout qu'à cet accusé.
779
+ *
780
+ * **Prérequis — `.subscribe()` d'abord.** L'option `ack` voyage dans le message
781
+ * `subscribe` (`SubscribeConfig.broadcast.ack`) et le serveur ne la relit que dans la
782
+ * subscription de la connexion (`handlers/websocket.rs`, bras `Broadcast` :
783
+ * `subscriptions.get(&channel)` → `entry.config.broadcast.ack`). Sans abonnement, il
784
+ * n'a aucune config à consulter et **n'émettra jamais** d'accusé : `send()` sort donc
785
+ * immédiatement en `'requires_subscription'`, sans rien émettre ni attendre en vain
786
+ * (cf. {@link BroadcastSendStatus}).
787
+ */
493
788
  ack?: boolean;
494
789
  /** Si true, le client reçoit ses propres broadcasts (self-echo) */
495
790
  self?: boolean;
@@ -528,6 +823,31 @@ interface ChatResponse {
528
823
  content: string;
529
824
  input_tokens?: number;
530
825
  output_tokens?: number;
826
+ /**
827
+ * Part des tokens de sortie consommée par le raisonnement du modèle
828
+ * (sous-ensemble d'`output_tokens`). Absent si le provider ne la rapporte pas.
829
+ */
830
+ reasoning_tokens?: number;
831
+ /**
832
+ * Motif d'arrêt NORMALISÉ par le serveur : `'stop' | 'length' | 'content_filter' |
833
+ * 'tool_use' | 'other'`.
834
+ *
835
+ * **A25** — sans lui, une réponse coupée par `max_tokens` (`'length'`) est indiscernable
836
+ * d'une réponse complète (`'stop'`) : le texte arrive tronqué, sans erreur ni signal. Le
837
+ * serveur le calcule et le renvoie déjà (`services/aura-ai/src/handlers/chat.rs`), et le
838
+ * chemin streamé le typait ; seul `chat()` le masquait.
839
+ *
840
+ * Le champ reste optionnel : un provider qui ne rapporte pas de motif, ou un serveur
841
+ * antérieur, renverra `undefined`.
842
+ */
843
+ finish_reason?: string;
844
+ /** Provider ayant effectivement répondu (`'openai'`, `'anthropic'`, `'google'`…). */
845
+ provider?: string;
846
+ /**
847
+ * `true` si une bascule de fallback a eu lieu — le provider effectif n'est pas celui
848
+ * demandé. Permet de tracer une dégradation silencieuse de qualité ou de coût.
849
+ */
850
+ fallback_used?: boolean;
531
851
  }
532
852
  interface EmbedRequest {
533
853
  text: string;
@@ -639,10 +959,30 @@ interface Notification {
639
959
  }
640
960
  interface SendNotificationRequest {
641
961
  channel: 'email' | 'sms' | 'push' | 'webhook';
962
+ /** Adresse email, numéro E.164, jeton d'appareil ou URL `https://`, selon le canal. */
642
963
  recipient: string;
964
+ /** Corps du message. Ignoré quand `template_id` est fourni. */
643
965
  body?: string;
966
+ /**
967
+ * ⚠️ Sémantique dépendante du canal (A28) — ce n'est pas partout un « sujet » :
968
+ * - `email` → en-tête `Subject` du message. Absent ⇒ `"Notification Aurabase"` ;
969
+ * - `push` → **titre** de la notification affichée. Absent ⇒ `"Notification"` ;
970
+ * - `webhook` → champ **`"event"`** du corps JSON POSTé. Absent ⇒ `"notification"` ; il
971
+ * participe en outre à la clé d'idempotence de livraison ;
972
+ * - `sms` → **ignoré** (Twilio ne reçoit que `To`, `From` et `Body`).
973
+ *
974
+ * Avec `template_id` : à la différence de `body`, ce champ n'est PAS ignoré — un sujet non
975
+ * vide **surcharge** celui du template et est interpolé avec les mêmes `variables` ; vide ou
976
+ * absent, le sujet du template s'applique.
977
+ */
644
978
  subject?: string;
979
+ /** Le `body` fourni est ignoré : c'est le corps du template qui est rendu. */
645
980
  template_id?: string;
981
+ /**
982
+ * Valeurs interpolées dans `body`/`subject` (`{{ var }}`), avec ou sans `template_id`.
983
+ * Une variable manquante refuse l'envoi (400). Les variables se fournissent ICI, jamais
984
+ * en déclaration sur le template.
985
+ */
646
986
  variables?: Record<string, unknown>;
647
987
  metadata?: Record<string, unknown>;
648
988
  idempotency_key?: string;
@@ -658,11 +998,30 @@ interface BatchSendResponse {
658
998
  }
659
999
  interface NotificationTemplate {
660
1000
  id: string;
1001
+ /**
1002
+ * N53 — le serveur renvoie ce champ sur TOUTES les routes template (`POST /templates`,
1003
+ * `GET /templates`, `GET /templates/{id}`, `PATCH /templates/{id}`) : il est sérialisé
1004
+ * depuis `NotificationTemplate` côté Rust. Il manquait ici — miroir exact de C24, où un
1005
+ * champ DÉCLARÉ (`variables`) n'existait pas côté serveur ; ici un champ qui EXISTE
1006
+ * n'était pas déclaré, donc invisible aux appelants typés.
1007
+ */
1008
+ project_id: string;
661
1009
  name: string;
1010
+ /**
1011
+ * Le canal est FIXÉ à la création : il conditionne le rendu (échappement HTML pour
1012
+ * `email`, texte brut sinon) et la validation à l'envoi. Le serveur refuse de le modifier
1013
+ * (400) — créez un autre template pour un autre canal.
1014
+ */
662
1015
  channel: 'email' | 'sms' | 'push' | 'webhook';
1016
+ /**
1017
+ * ⚠️ Sémantique dépendante du canal (A28) :
1018
+ * - `email` → en-tête Subject du message ;
1019
+ * - `push` → **titre** de la notification ;
1020
+ * - `webhook` → champ **`"event"`** du corps JSON envoyé au destinataire ;
1021
+ * - `sms` → **ignoré** (un SMS n'a pas de sujet).
1022
+ */
663
1023
  subject?: string | null;
664
1024
  body: string;
665
- variables?: string[];
666
1025
  created_at: string;
667
1026
  updated_at: string;
668
1027
  }
@@ -841,7 +1200,6 @@ interface AuraClientOptions {
841
1200
  */
842
1201
  detectSessionInUrl?: boolean;
843
1202
  };
844
- apiKey?: string;
845
1203
  fetch?: typeof globalThis.fetch;
846
1204
  /**
847
1205
  * SEC-F5 : si une clé `service_role` (`aura_sk_…`) est fournie en contexte NAVIGATEUR
@@ -885,8 +1243,7 @@ interface IdentityInfo {
885
1243
  auto_linked: boolean;
886
1244
  created_at: string;
887
1245
  }
888
- interface AdminUser extends User {
889
- }
1246
+ type AdminUser = User;
890
1247
  interface AuditLogEntry {
891
1248
  id: string;
892
1249
  user_id?: string | null;
@@ -916,6 +1273,24 @@ interface AdminSessionInfo {
916
1273
  expires_at: string;
917
1274
  last_used_at: string;
918
1275
  }
1276
+ /**
1277
+ * Valeur d'un provider dans `oauth_providers`.
1278
+ *
1279
+ * Le backend stocke ce champ en JSONB libre et l'interprète de DEUX façons :
1280
+ * - un booléen — simple interrupteur (défaut du schéma : `{"github": true, "google": true}`) ;
1281
+ * - un objet de configuration — `client_id`/`client_secret`/`scopes` pour un provider intégré,
1282
+ * ou `discovery_url`+`client_id`+`client_secret` sous une clé `oidc:<nom>` pour un OIDC
1283
+ * personnalisé (cf. `load_oidc_config`, services/aura-auth/src/handlers/oauth.rs).
1284
+ */
1285
+ type OAuthProviderSetting = boolean | {
1286
+ enabled?: boolean;
1287
+ client_id?: string;
1288
+ client_secret?: string;
1289
+ scopes?: string[];
1290
+ /** OIDC personnalisé (clé `oidc:<nom>`) : endpoint de découverte OpenID Connect. */
1291
+ discovery_url?: string;
1292
+ [key: string]: unknown;
1293
+ };
919
1294
  interface AuthSettings {
920
1295
  allow_signup: boolean;
921
1296
  email_confirmation_required: boolean;
@@ -925,11 +1300,7 @@ interface AuthSettings {
925
1300
  jwt_expiry_seconds: number;
926
1301
  refresh_token_expiry_days: number;
927
1302
  site_url?: string | null;
928
- oauth_providers: Record<string, {
929
- enabled?: boolean;
930
- client_id?: string;
931
- client_secret?: string;
932
- }>;
1303
+ oauth_providers: Record<string, OAuthProviderSetting>;
933
1304
  new_device_alerts_enabled: boolean;
934
1305
  webhook_url?: string | null;
935
1306
  smtp_host?: string | null;
@@ -953,13 +1324,142 @@ interface AuthSettings {
953
1324
  hook_send_email_url?: string | null;
954
1325
  hook_mfa_verification_url?: string | null;
955
1326
  hook_password_verification_url?: string | null;
1327
+ /** Hook BLOQUANT avant inscription : peut refuser le signup. */
1328
+ hook_before_signup_url?: string | null;
1329
+ /** Hook de notification après inscription (fire-and-forget). */
1330
+ hook_after_signup_url?: string | null;
1331
+ /** Hook d'enrichissement des claims du JWT à chaque émission de jeton. */
1332
+ hook_custom_access_token_url?: string | null;
1333
+ /**
1334
+ * Le secret HMAC qui SIGNE tous les hooks est posé (write-only : jamais relu en clair).
1335
+ * Sans lui, aucun hook n'est signé — le destinataire ne peut pas authentifier l'appel.
1336
+ */
1337
+ hook_secret_set?: boolean;
956
1338
  auto_link_by_email: boolean;
1339
+ password_require_uppercase: boolean;
1340
+ password_require_number: boolean;
1341
+ password_require_special: boolean;
957
1342
  revoke_on_new_device: boolean;
958
1343
  enforce_ip_binding: boolean;
959
1344
  allowed_email_domains: string[];
960
1345
  blocked_email_domains: string[];
1346
+ rate_limit_login_per_hour: number;
1347
+ rate_limit_emails_per_hour: number;
1348
+ rate_limit_signup_per_hour: number;
961
1349
  updated_at: string;
962
1350
  }
1351
+ /**
1352
+ * Champs RÉELLEMENT acceptés en écriture par `PUT /v1/auth/{project_id}/admin/settings`.
1353
+ *
1354
+ * Miroir exact de `UpdateSettingsRequest` (services/aura-auth/src/handlers/settings.rs) —
1355
+ * un test anti-divergence relit la struct Rust et compare. Ne PAS y ajouter un champ que
1356
+ * le backend ne désérialise pas : `UpdateSettingsRequest` porte `deny_unknown_fields`,
1357
+ * l'appel entier serait REFUSÉ (`400`, le champ fautif est nommé).
1358
+ */
1359
+ declare const AUTH_SETTINGS_WRITABLE_FIELDS: readonly ["allow_signup", "email_confirmation_required", "password_min_length", "mfa_enabled", "allow_anonymous_users", "jwt_expiry_seconds", "refresh_token_expiry_days", "site_url", "oauth_providers", "new_device_alerts_enabled", "webhook_url", "webhook_secret", "smtp_host", "smtp_port", "smtp_username", "smtp_password", "smtp_from_email", "smtp_from_name", "smtp_clear", "oauth_credentials", "oauth_credentials_clear", "captcha_enabled", "captcha_provider", "captcha_site_key", "captcha_secret_key", "password_check_hibp", "sms_provider", "sms_api_key", "sms_api_secret", "sms_from_number", "sms_clear", "redirect_urls", "session_inactivity_timeout_seconds", "hook_send_sms_url", "hook_send_email_url", "hook_mfa_verification_url", "hook_password_verification_url", "auto_link_by_email", "rate_limit_login_per_hour", "rate_limit_emails_per_hour", "rate_limit_signup_per_hour", "revoke_on_new_device", "enforce_ip_binding", "allowed_email_domains", "blocked_email_domains", "password_require_uppercase", "password_require_number", "password_require_special", "hook_before_signup_url", "hook_after_signup_url", "hook_custom_access_token_url", "hook_secret"];
1360
+ /**
1361
+ * Corps de `authAdmin.updateSettings()`.
1362
+ *
1363
+ * Distinct de `Partial<AuthSettings>` À DESSEIN, dans les deux sens :
1364
+ * - il AJOUTE les champs write-only que la vue de lecture ne peut pas porter (secrets
1365
+ * `webhook_secret`/`smtp_password`/`captcha_secret_key`/`sms_api_key`/`sms_api_secret`,
1366
+ * drapeaux d'effacement `smtp_clear`/`sms_clear`/`oauth_credentials_clear`,
1367
+ * `oauth_credentials` en clair) ;
1368
+ * - il RETIRE les projections de lecture (`smtp_password_set`, `captcha_secret_key_set`,
1369
+ * `sms_api_key_set`, `hook_secret_set`, `updated_at`) : le backend les REFUSE.
1370
+ *
1371
+ * Ces deux refus sont ceux du SERVEUR, pas seulement du compilateur (C26, rectifié) :
1372
+ * - champ inconnu → `400` nommant le champ (`deny_unknown_fields` sur `UpdateSettingsRequest`) ;
1373
+ * - `null` explicite → `400` : la mise à jour est un `COALESCE($n, colonne)`, donc `null`
1374
+ * ne remet RIEN à zéro — il ne ferait rien. Pour effacer, utiliser les drapeaux `*_clear`
1375
+ * (SMTP/SMS/OAuth) ou la valeur vide (`""`, `[]`) selon le champ.
1376
+ *
1377
+ * Conséquence pratique : ne PAS renvoyer tel quel un objet lu par `getSettings()`
1378
+ * (`{...settings, mfa_enabled: true}`) — il porte `updated_at` et les `*_set`, l'appel serait
1379
+ * refusé. N'envoyez que les champs à modifier.
1380
+ */
1381
+ interface AuthSettingsUpdate {
1382
+ allow_signup?: boolean;
1383
+ email_confirmation_required?: boolean;
1384
+ /** Entre 8 et 128 (refusé hors bornes par le backend). */
1385
+ password_min_length?: number;
1386
+ mfa_enabled?: boolean;
1387
+ allow_anonymous_users?: boolean;
1388
+ /** Entre 300 et 3600 secondes. */
1389
+ jwt_expiry_seconds?: number;
1390
+ /** Entre 1 et 90 jours. */
1391
+ refresh_token_expiry_days?: number;
1392
+ site_url?: string;
1393
+ oauth_providers?: Record<string, OAuthProviderSetting>;
1394
+ new_device_alerts_enabled?: boolean;
1395
+ webhook_url?: string;
1396
+ /** Write-only : jamais relu par `getSettings()`. */
1397
+ webhook_secret?: string;
1398
+ smtp_host?: string;
1399
+ smtp_port?: number;
1400
+ smtp_username?: string;
1401
+ /** Write-only ; `getSettings()` n'expose que `smtp_password_set`. */
1402
+ smtp_password?: string;
1403
+ smtp_from_email?: string;
1404
+ smtp_from_name?: string;
1405
+ /** `true` = efface TOUTE la config SMTP du projet (retour au SMTP global). */
1406
+ smtp_clear?: boolean;
1407
+ /** Fusion partielle ; `getSettings()` ne les renvoie que MASQUÉS. */
1408
+ oauth_credentials?: Record<string, Record<string, unknown>>;
1409
+ /** `true` = efface tous les identifiants OAuth du projet (retour aux variables globales). */
1410
+ oauth_credentials_clear?: boolean;
1411
+ captcha_enabled?: boolean;
1412
+ captcha_provider?: string;
1413
+ captcha_site_key?: string;
1414
+ /** Write-only ; `getSettings()` n'expose que `captcha_secret_key_set`. */
1415
+ captcha_secret_key?: string;
1416
+ password_check_hibp?: boolean;
1417
+ sms_provider?: string;
1418
+ /** Write-only ; `getSettings()` n'expose que `sms_api_key_set`. */
1419
+ sms_api_key?: string;
1420
+ /** Write-only : jamais relu. */
1421
+ sms_api_secret?: string;
1422
+ sms_from_number?: string;
1423
+ /** `true` = efface toute la config SMS. */
1424
+ sms_clear?: boolean;
1425
+ redirect_urls?: string[];
1426
+ /** 0 (désactivé) ou >= 300 secondes. */
1427
+ session_inactivity_timeout_seconds?: number;
1428
+ hook_send_sms_url?: string;
1429
+ hook_send_email_url?: string;
1430
+ hook_mfa_verification_url?: string;
1431
+ hook_password_verification_url?: string;
1432
+ /** Hook BLOQUANT avant inscription : un refus du hook annule le signup. */
1433
+ hook_before_signup_url?: string;
1434
+ /** Hook de notification après inscription (fire-and-forget). */
1435
+ hook_after_signup_url?: string;
1436
+ /** Hook d'enrichissement des claims du JWT (fusionnés dans `app_metadata`). */
1437
+ hook_custom_access_token_url?: string;
1438
+ /**
1439
+ * Secret HMAC signant TOUS les hooks (en-tête `X-Aurabase-Signature`).
1440
+ *
1441
+ * Write-only ; `getSettings()` n'expose que `hook_secret_set`. Tant qu'il est absent,
1442
+ * les hooks partent NON SIGNÉS : le destinataire ne peut pas distinguer un appel
1443
+ * Aurabase d'un appel forgé par un tiers.
1444
+ */
1445
+ hook_secret?: string;
1446
+ auto_link_by_email?: boolean;
1447
+ /** Exige au moins une majuscule (inscription + changement de mot de passe). */
1448
+ password_require_uppercase?: boolean;
1449
+ /** Exige au moins un chiffre. */
1450
+ password_require_number?: boolean;
1451
+ /** Exige au moins un caractère spécial. */
1452
+ password_require_special?: boolean;
1453
+ rate_limit_login_per_hour?: number;
1454
+ rate_limit_emails_per_hour?: number;
1455
+ rate_limit_signup_per_hour?: number;
1456
+ revoke_on_new_device?: boolean;
1457
+ enforce_ip_binding?: boolean;
1458
+ /** Remplacement complet (sémantique de liste, pas de fusion). */
1459
+ allowed_email_domains?: string[];
1460
+ /** Remplacement complet. */
1461
+ blocked_email_domains?: string[];
1462
+ }
963
1463
  interface EmailTemplateUpdate {
964
1464
  subject: string;
965
1465
  html_body: string;
@@ -1068,8 +1568,12 @@ declare class BaseAuthStore {
1068
1568
  * dépendance core → meta (les services n'importent plus la classe concrète).
1069
1569
  */
1070
1570
  interface AuraClientLike {
1571
+ /**
1572
+ * URL du projet (`https://<slug>.aurabase.cloud`). C'est elle — et elle seule — qui
1573
+ * désigne le projet : la passerelle résout le tenant depuis l'hôte et injecte le
1574
+ * `project_id` dans le chemin. Aucun identifiant de projet ne transite côté client.
1575
+ */
1071
1576
  readonly baseUrl: string;
1072
- readonly projectId: string;
1073
1577
  readonly apiKey: string | null;
1074
1578
  readonly fetchFn: typeof globalThis.fetch;
1075
1579
  readonly authStore: BaseAuthStore;
@@ -1098,6 +1602,44 @@ interface AuraClientLike {
1098
1602
  readonly debug?: boolean;
1099
1603
  }
1100
1604
 
1605
+ /**
1606
+ * Abstract base class for all service modules.
1607
+ * Provides the shared HTTP helper and URL builder.
1608
+ */
1609
+ declare abstract class BaseService {
1610
+ protected readonly client: AuraClientLike;
1611
+ constructor(client: AuraClientLike);
1612
+ /** Build a full URL for the given path. */
1613
+ protected url(path: string): string;
1614
+ /** Convenience HTTP helper that delegates to fetchWithAuth. */
1615
+ protected request<T>(method: string, path: string, body?: unknown, options?: {
1616
+ headers?: Record<string, string>;
1617
+ signal?: AbortSignal;
1618
+ skipAuthRefresh?: boolean;
1619
+ /** Override explicite du Bearer (LOT D) — valide un token arbitraire. */
1620
+ authToken?: string;
1621
+ }): Promise<AuraResponse<T>>;
1622
+ }
1623
+
1624
+ /**
1625
+ * Parse a raw Cookie header string into key-value pairs.
1626
+ */
1627
+ declare function parseCookieHeader(header: string): Record<string, string>;
1628
+ interface SerializeCookieOptions {
1629
+ path?: string;
1630
+ domain?: string;
1631
+ maxAge?: number;
1632
+ secure?: boolean;
1633
+ sameSite?: 'Strict' | 'Lax' | 'None';
1634
+ httpOnly?: boolean;
1635
+ }
1636
+ /**
1637
+ * Serialize a cookie name + value + options into a Set-Cookie header value.
1638
+ */
1639
+ declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
1640
+
1641
+ /** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
1642
+ declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
1101
1643
  /** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
1102
1644
  declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
1103
1645
  interface FetchInit {
@@ -1128,23 +1670,43 @@ interface FetchInit {
1128
1670
  declare function fetchWithAuth<T>(url: string, init: FetchInit, client: AuraClientLike): Promise<AuraResponse<T>>;
1129
1671
 
1130
1672
  /**
1131
- * Abstract base class for all service modules.
1132
- * Provides the shared HTTP helper and URL builder.
1673
+ * Primitives de résilience réseau (module pur, sans I/O).
1674
+ * `Math.random`/`Date.now` sont injectables pour des tests déterministes.
1133
1675
  */
1134
- declare abstract class BaseService {
1135
- protected readonly client: AuraClientLike;
1136
- constructor(client: AuraClientLike);
1137
- /** Build a full URL for the given path. */
1138
- protected url(path: string): string;
1139
- /** Convenience HTTP helper that delegates to fetchWithAuth. */
1140
- protected request<T>(method: string, path: string, body?: unknown, options?: {
1141
- headers?: Record<string, string>;
1142
- signal?: AbortSignal;
1143
- skipAuthRefresh?: boolean;
1144
- /** Override explicite du Bearer (LOT D) — valide un token arbitraire. */
1145
- authToken?: string;
1146
- }): Promise<AuraResponse<T>>;
1147
- }
1676
+ declare const RETRYABLE_STATUS: Set<number>;
1677
+ declare const RETRYABLE_METHODS: Set<string>;
1678
+ declare const DEFAULT_MAX_RETRIES = 3;
1679
+ declare function isRetryableStatus(status: number): boolean;
1680
+ declare function isRetryableMethod(method: string): boolean;
1681
+ /** Full jitter : random(0, min(BASE·2^attempt, CAP)). `attempt` commence à 0. */
1682
+ declare function computeBackoff(attempt: number, rand?: () => number): number;
1683
+ /** Parse un header `Retry-After` (secondes OU HTTP-date) → délai en ms, ou null. */
1684
+ declare function parseRetryAfter(header: string | null, now?: number): number | null;
1685
+ /** `sleep(ms)` qui résout tôt (sans rejeter) si `signal` s'annule. */
1686
+ declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>;
1687
+ /** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
1688
+ declare function isAbortError(err: unknown): boolean;
1689
+
1690
+ /**
1691
+ * Portée d'un client, dérivée de son URL de projet.
1692
+ *
1693
+ * Chaque projet est adressé par son PROPRE hôte (`https://<slug>.aurabase.cloud`), et le
1694
+ * SDK ne connaît plus d'identifiant de projet : l'URL est la seule chose qui distingue
1695
+ * deux projets. Tout ce qui devait auparavant être « scopé projet » côté client — clé
1696
+ * BroadcastChannel de synchro multi-onglets, `code_verifier` PKCE, marqueur d'initiation
1697
+ * OAuth, nom de cookie SSR, nom de verrou de refresh — se scope donc par cette portée.
1698
+ *
1699
+ * Ce n'est PAS cosmétique : une page (`app.example.com`) peut parfaitement instancier deux
1700
+ * clients pointant sur deux projets. Sans portée, leurs clés de stockage se marcheraient
1701
+ * dessus dans le MÊME `localStorage`, celui de la page.
1702
+ *
1703
+ * Le résultat est réduit à `[a-z0-9-]` : il sert aussi de nom de cookie, et un nom de
1704
+ * cookie est un `token` RFC 6265 — un `:` de port (`localhost:8080`) y est interdit.
1705
+ */
1706
+ declare function clientScope(baseUrl: string): string;
1707
+
1708
+ /** JSON.stringify avec support des bigint (sérialisés en string). */
1709
+ declare function serializeBody(body: unknown): string;
1148
1710
 
1149
1711
  interface CookieOptions {
1150
1712
  path?: string;
@@ -1156,21 +1718,36 @@ interface CookieOptions {
1156
1718
  }
1157
1719
  /**
1158
1720
  * Auth store backed by `localStorage` (browser) with cookie helpers for SSR.
1721
+ *
1722
+ * `key` est OBLIGATOIRE, et c'est le correctif K3 : la session est le sixième état
1723
+ * local du SDK, et le seul qui n'était pas scopé par projet. Un nom par défaut
1724
+ * constant faisait que deux clients pointant sur deux projets dans la MÊME page
1725
+ * écrivaient sous la même clé du MÊME `localStorage` (celui de la page, pas celui du
1726
+ * projet) et s'écrasaient mutuellement. Le nom canonique est
1727
+ * `aura-${clientScope(baseUrl)}-auth` — celui que posent `AuraClient` (navigateur) et
1728
+ * `createServerClient` (cookie SSR), et la même convention que les cinq autres états
1729
+ * (`aura-<portée>-pkce-code-verifier`, `-oauth-implicit-initiated`, canal de synchro,
1730
+ * verrou de refresh). Voir `packages/core/src/scope.ts`.
1159
1731
  */
1160
1732
  declare class LocalAuthStore extends BaseAuthStore {
1161
1733
  /** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
1162
1734
  readonly persistent = true;
1163
1735
  private readonly key;
1164
- constructor(key?: string);
1736
+ /**
1737
+ * @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
1738
+ * `aura-${clientScope(baseUrl)}-auth`.
1739
+ */
1740
+ constructor(key: string);
1165
1741
  save(token: string, refreshToken: string, user: User): void;
1166
1742
  clear(): void;
1167
1743
  /**
1168
1744
  * Supprime de localStorage tous les artefacts d'auth éphémères sur disque :
1169
- * - `code_verifier` PKCE (`aura-<projectId>-pkce-code-verifier`),
1170
- * - marqueur d'initiation OAuth implicit (`aura-<projectId>-oauth-implicit-initiated`,
1745
+ * - `code_verifier` PKCE (`aura-<portée>-pkce-code-verifier`),
1746
+ * - marqueur d'initiation OAuth implicit (`aura-<portée>-oauth-implicit-initiated`,
1171
1747
  * SEC-F3). Un `signOut`/`clear()` ne doit laisser aucun résidu réutilisable (parité
1172
1748
  * `@supabase/ssr` : ces artefacts sont éphémères). On itère les clés préfixées pour
1173
- * couvrir un store partagé entre plusieurs projets, sans dépendre du `projectId` courant.
1749
+ * couvrir un store partagé entre plusieurs projets, sans dépendre de la portée courante
1750
+ * (`clientScope`, dérivée de l'URL du projet).
1174
1751
  */
1175
1752
  private _removePkceVerifiers;
1176
1753
  setItem(key: string, value: string): void;
@@ -1190,42 +1767,4 @@ declare class LocalAuthStore extends BaseAuthStore {
1190
1767
  private _removeStorage;
1191
1768
  }
1192
1769
 
1193
- /** JSON.stringify avec support des bigint (sérialisés en string). */
1194
- declare function serializeBody(body: unknown): string;
1195
-
1196
- /**
1197
- * Parse a raw Cookie header string into key-value pairs.
1198
- */
1199
- declare function parseCookieHeader(header: string): Record<string, string>;
1200
- interface SerializeCookieOptions {
1201
- path?: string;
1202
- domain?: string;
1203
- maxAge?: number;
1204
- secure?: boolean;
1205
- sameSite?: 'Strict' | 'Lax' | 'None';
1206
- httpOnly?: boolean;
1207
- }
1208
- /**
1209
- * Serialize a cookie name + value + options into a Set-Cookie header value.
1210
- */
1211
- declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
1212
-
1213
- /**
1214
- * Primitives de résilience réseau (module pur, sans I/O).
1215
- * `Math.random`/`Date.now` sont injectables pour des tests déterministes.
1216
- */
1217
- declare const RETRYABLE_STATUS: Set<number>;
1218
- declare const RETRYABLE_METHODS: Set<string>;
1219
- declare const DEFAULT_MAX_RETRIES = 3;
1220
- declare function isRetryableStatus(status: number): boolean;
1221
- declare function isRetryableMethod(method: string): boolean;
1222
- /** Full jitter : random(0, min(BASE·2^attempt, CAP)). `attempt` commence à 0. */
1223
- declare function computeBackoff(attempt: number, rand?: () => number): number;
1224
- /** Parse un header `Retry-After` (secondes OU HTTP-date) → délai en ms, ou null. */
1225
- declare function parseRetryAfter(header: string | null, now?: number): number | null;
1226
- /** `sleep(ms)` qui résout tôt (sans rejeter) si `signal` s'annule. */
1227
- declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>;
1228
- /** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
1229
- declare function isAbortError(err: unknown): boolean;
1230
-
1231
- export { type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, 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, BaseAuthStore, BaseService, type BatchSendItemResult, type BatchSendResponse, type BroadcastAckMessage, type BroadcastFilter, type BroadcastPayload, type CdcPayload, type ChannelConfig, type ChatMessage, type ChatResponse, type CountMode, type CronJob, DEFAULT_MAX_RETRIES, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, 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 NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationStats, type NotificationTemplate, type NotificationTemplatePreview, type OAuthSignInOptions, type ObjectMeta, type PasswordResetRequest, type PostgresChangesFilter, type PresenceFilter, type PresenceInfo, type PresenceState, type QueryFilter, type QueryOptions, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeEvent, type RealtimeListenType, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationRequest, type SerializeCookieOptions, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type SessionInfo, type SignInRequest, type SignUpRequest, type StorageBucket, type SubscribeCallback, type TokenPair, type UpdateUserRequest, type UploadResult, type User, type UserStats, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
1770
+ export { AUTH_SETTINGS_WRITABLE_FIELDS, type AdminCreateUserRequest, type AdminSessionInfo, type AdminUpdateUserRequest, type AdminUser, 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 CreatePgFunctionRequest, type CreatePgTriggerRequest, type CronJob, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, type EmailTemplateUpdate, type EmbedRequest, type EmbedResponse, type FetchInit, type FileEntry, type FilterOp, type FunctionDef, 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 NotificationChannelStats, type NotificationDeadLetter, type NotificationEvent, type NotificationProviderLog, type NotificationQueueStats, type NotificationRule, type NotificationSettings, type NotificationStats, 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, RETRYABLE_METHODS, RETRYABLE_STATUS, type RagIngestRequest, type RagIngestResponse, type RagRequest, type RagResponse, type RealtimeEvent, type RealtimeListenType, type RealtimeSubscribeStatus, type SchemaColumn, type SchemaEnum, type SchemaFunction, type SchemaFunctionArg, type SchemaIndex, type SchemaTable, type SemanticSearchResult, type SendNotificationRequest, type SerializeCookieOptions, type ServerErrorMsg, type ServerEventMsg, type ServerMessage, type ServerPongMsg, type ServerPresenceStateMsg, type ServerSubscribedMsg, type ServerTokenRefreshedMsg, type SessionInfo, type SignInRequest, type SignUpRequest, type StorageBucket, type SubscribeCallback, type TokenPair, type UpdateUserRequest, type UploadResult, type User, type UserStats, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };