@aurabase/core 0.4.1 → 0.5.1
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 +10 -5
- package/dist/index.cjs +183 -92
- package/dist/index.d.cts +620 -74
- package/dist/index.d.ts +620 -74
- package/dist/index.js +182 -93
- package/package.json +10 -9
package/dist/index.d.ts
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
|
-
|
|
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,17 +773,37 @@ interface ChannelConfig {
|
|
|
489
773
|
private?: boolean;
|
|
490
774
|
/** Configuration broadcast pour ce channel */
|
|
491
775
|
broadcast?: {
|
|
492
|
-
/**
|
|
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;
|
|
496
791
|
};
|
|
497
|
-
/** Configuration présence */
|
|
792
|
+
/** Configuration présence directe */
|
|
793
|
+
presence?: {
|
|
794
|
+
/** Clé de présence — permet de regrouper plusieurs onglets/devices du même user */
|
|
795
|
+
key?: string;
|
|
796
|
+
};
|
|
797
|
+
/** Configuration compatible Supabase sous `config` */
|
|
498
798
|
config?: {
|
|
799
|
+
broadcast?: {
|
|
800
|
+
ack?: boolean;
|
|
801
|
+
self?: boolean;
|
|
802
|
+
};
|
|
499
803
|
presence?: {
|
|
500
|
-
/** Clé de présence — permet de regrouper plusieurs onglets/devices du même user */
|
|
501
804
|
key?: string;
|
|
502
805
|
};
|
|
806
|
+
private?: boolean;
|
|
503
807
|
};
|
|
504
808
|
}
|
|
505
809
|
/** Payload d'un broadcast à envoyer */
|
|
@@ -528,6 +832,31 @@ interface ChatResponse {
|
|
|
528
832
|
content: string;
|
|
529
833
|
input_tokens?: number;
|
|
530
834
|
output_tokens?: number;
|
|
835
|
+
/**
|
|
836
|
+
* Part des tokens de sortie consommée par le raisonnement du modèle
|
|
837
|
+
* (sous-ensemble d'`output_tokens`). Absent si le provider ne la rapporte pas.
|
|
838
|
+
*/
|
|
839
|
+
reasoning_tokens?: number;
|
|
840
|
+
/**
|
|
841
|
+
* Motif d'arrêt NORMALISÉ par le serveur : `'stop' | 'length' | 'content_filter' |
|
|
842
|
+
* 'tool_use' | 'other'`.
|
|
843
|
+
*
|
|
844
|
+
* **A25** — sans lui, une réponse coupée par `max_tokens` (`'length'`) est indiscernable
|
|
845
|
+
* d'une réponse complète (`'stop'`) : le texte arrive tronqué, sans erreur ni signal. Le
|
|
846
|
+
* serveur le calcule et le renvoie déjà (`services/aura-ai/src/handlers/chat.rs`), et le
|
|
847
|
+
* chemin streamé le typait ; seul `chat()` le masquait.
|
|
848
|
+
*
|
|
849
|
+
* Le champ reste optionnel : un provider qui ne rapporte pas de motif, ou un serveur
|
|
850
|
+
* antérieur, renverra `undefined`.
|
|
851
|
+
*/
|
|
852
|
+
finish_reason?: string;
|
|
853
|
+
/** Provider ayant effectivement répondu (`'openai'`, `'anthropic'`, `'google'`…). */
|
|
854
|
+
provider?: string;
|
|
855
|
+
/**
|
|
856
|
+
* `true` si une bascule de fallback a eu lieu — le provider effectif n'est pas celui
|
|
857
|
+
* demandé. Permet de tracer une dégradation silencieuse de qualité ou de coût.
|
|
858
|
+
*/
|
|
859
|
+
fallback_used?: boolean;
|
|
531
860
|
}
|
|
532
861
|
interface EmbedRequest {
|
|
533
862
|
text: string;
|
|
@@ -639,10 +968,30 @@ interface Notification {
|
|
|
639
968
|
}
|
|
640
969
|
interface SendNotificationRequest {
|
|
641
970
|
channel: 'email' | 'sms' | 'push' | 'webhook';
|
|
971
|
+
/** Adresse email, numéro E.164, jeton d'appareil ou URL `https://`, selon le canal. */
|
|
642
972
|
recipient: string;
|
|
973
|
+
/** Corps du message. Ignoré quand `template_id` est fourni. */
|
|
643
974
|
body?: string;
|
|
975
|
+
/**
|
|
976
|
+
* ⚠️ Sémantique dépendante du canal (A28) — ce n'est pas partout un « sujet » :
|
|
977
|
+
* - `email` → en-tête `Subject` du message. Absent ⇒ `"Notification Aurabase"` ;
|
|
978
|
+
* - `push` → **titre** de la notification affichée. Absent ⇒ `"Notification"` ;
|
|
979
|
+
* - `webhook` → champ **`"event"`** du corps JSON POSTé. Absent ⇒ `"notification"` ; il
|
|
980
|
+
* participe en outre à la clé d'idempotence de livraison ;
|
|
981
|
+
* - `sms` → **ignoré** (Twilio ne reçoit que `To`, `From` et `Body`).
|
|
982
|
+
*
|
|
983
|
+
* Avec `template_id` : à la différence de `body`, ce champ n'est PAS ignoré — un sujet non
|
|
984
|
+
* vide **surcharge** celui du template et est interpolé avec les mêmes `variables` ; vide ou
|
|
985
|
+
* absent, le sujet du template s'applique.
|
|
986
|
+
*/
|
|
644
987
|
subject?: string;
|
|
988
|
+
/** Le `body` fourni est ignoré : c'est le corps du template qui est rendu. */
|
|
645
989
|
template_id?: string;
|
|
990
|
+
/**
|
|
991
|
+
* Valeurs interpolées dans `body`/`subject` (`{{ var }}`), avec ou sans `template_id`.
|
|
992
|
+
* Une variable manquante refuse l'envoi (400). Les variables se fournissent ICI, jamais
|
|
993
|
+
* en déclaration sur le template.
|
|
994
|
+
*/
|
|
646
995
|
variables?: Record<string, unknown>;
|
|
647
996
|
metadata?: Record<string, unknown>;
|
|
648
997
|
idempotency_key?: string;
|
|
@@ -658,11 +1007,30 @@ interface BatchSendResponse {
|
|
|
658
1007
|
}
|
|
659
1008
|
interface NotificationTemplate {
|
|
660
1009
|
id: string;
|
|
1010
|
+
/**
|
|
1011
|
+
* N53 — le serveur renvoie ce champ sur TOUTES les routes template (`POST /templates`,
|
|
1012
|
+
* `GET /templates`, `GET /templates/{id}`, `PATCH /templates/{id}`) : il est sérialisé
|
|
1013
|
+
* depuis `NotificationTemplate` côté Rust. Il manquait ici — miroir exact de C24, où un
|
|
1014
|
+
* champ DÉCLARÉ (`variables`) n'existait pas côté serveur ; ici un champ qui EXISTE
|
|
1015
|
+
* n'était pas déclaré, donc invisible aux appelants typés.
|
|
1016
|
+
*/
|
|
1017
|
+
project_id: string;
|
|
661
1018
|
name: string;
|
|
1019
|
+
/**
|
|
1020
|
+
* Le canal est FIXÉ à la création : il conditionne le rendu (échappement HTML pour
|
|
1021
|
+
* `email`, texte brut sinon) et la validation à l'envoi. Le serveur refuse de le modifier
|
|
1022
|
+
* (400) — créez un autre template pour un autre canal.
|
|
1023
|
+
*/
|
|
662
1024
|
channel: 'email' | 'sms' | 'push' | 'webhook';
|
|
1025
|
+
/**
|
|
1026
|
+
* ⚠️ Sémantique dépendante du canal (A28) :
|
|
1027
|
+
* - `email` → en-tête Subject du message ;
|
|
1028
|
+
* - `push` → **titre** de la notification ;
|
|
1029
|
+
* - `webhook` → champ **`"event"`** du corps JSON envoyé au destinataire ;
|
|
1030
|
+
* - `sms` → **ignoré** (un SMS n'a pas de sujet).
|
|
1031
|
+
*/
|
|
663
1032
|
subject?: string | null;
|
|
664
1033
|
body: string;
|
|
665
|
-
variables?: string[];
|
|
666
1034
|
created_at: string;
|
|
667
1035
|
updated_at: string;
|
|
668
1036
|
}
|
|
@@ -841,7 +1209,6 @@ interface AuraClientOptions {
|
|
|
841
1209
|
*/
|
|
842
1210
|
detectSessionInUrl?: boolean;
|
|
843
1211
|
};
|
|
844
|
-
apiKey?: string;
|
|
845
1212
|
fetch?: typeof globalThis.fetch;
|
|
846
1213
|
/**
|
|
847
1214
|
* SEC-F5 : si une clé `service_role` (`aura_sk_…`) est fournie en contexte NAVIGATEUR
|
|
@@ -885,8 +1252,7 @@ interface IdentityInfo {
|
|
|
885
1252
|
auto_linked: boolean;
|
|
886
1253
|
created_at: string;
|
|
887
1254
|
}
|
|
888
|
-
|
|
889
|
-
}
|
|
1255
|
+
type AdminUser = User;
|
|
890
1256
|
interface AuditLogEntry {
|
|
891
1257
|
id: string;
|
|
892
1258
|
user_id?: string | null;
|
|
@@ -916,6 +1282,24 @@ interface AdminSessionInfo {
|
|
|
916
1282
|
expires_at: string;
|
|
917
1283
|
last_used_at: string;
|
|
918
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Valeur d'un provider dans `oauth_providers`.
|
|
1287
|
+
*
|
|
1288
|
+
* Le backend stocke ce champ en JSONB libre et l'interprète de DEUX façons :
|
|
1289
|
+
* - un booléen — simple interrupteur (défaut du schéma : `{"github": true, "google": true}`) ;
|
|
1290
|
+
* - un objet de configuration — `client_id`/`client_secret`/`scopes` pour un provider intégré,
|
|
1291
|
+
* ou `discovery_url`+`client_id`+`client_secret` sous une clé `oidc:<nom>` pour un OIDC
|
|
1292
|
+
* personnalisé (cf. `load_oidc_config`, services/aura-auth/src/handlers/oauth.rs).
|
|
1293
|
+
*/
|
|
1294
|
+
type OAuthProviderSetting = boolean | {
|
|
1295
|
+
enabled?: boolean;
|
|
1296
|
+
client_id?: string;
|
|
1297
|
+
client_secret?: string;
|
|
1298
|
+
scopes?: string[];
|
|
1299
|
+
/** OIDC personnalisé (clé `oidc:<nom>`) : endpoint de découverte OpenID Connect. */
|
|
1300
|
+
discovery_url?: string;
|
|
1301
|
+
[key: string]: unknown;
|
|
1302
|
+
};
|
|
919
1303
|
interface AuthSettings {
|
|
920
1304
|
allow_signup: boolean;
|
|
921
1305
|
email_confirmation_required: boolean;
|
|
@@ -925,11 +1309,7 @@ interface AuthSettings {
|
|
|
925
1309
|
jwt_expiry_seconds: number;
|
|
926
1310
|
refresh_token_expiry_days: number;
|
|
927
1311
|
site_url?: string | null;
|
|
928
|
-
oauth_providers: Record<string,
|
|
929
|
-
enabled?: boolean;
|
|
930
|
-
client_id?: string;
|
|
931
|
-
client_secret?: string;
|
|
932
|
-
}>;
|
|
1312
|
+
oauth_providers: Record<string, OAuthProviderSetting>;
|
|
933
1313
|
new_device_alerts_enabled: boolean;
|
|
934
1314
|
webhook_url?: string | null;
|
|
935
1315
|
smtp_host?: string | null;
|
|
@@ -953,13 +1333,142 @@ interface AuthSettings {
|
|
|
953
1333
|
hook_send_email_url?: string | null;
|
|
954
1334
|
hook_mfa_verification_url?: string | null;
|
|
955
1335
|
hook_password_verification_url?: string | null;
|
|
1336
|
+
/** Hook BLOQUANT avant inscription : peut refuser le signup. */
|
|
1337
|
+
hook_before_signup_url?: string | null;
|
|
1338
|
+
/** Hook de notification après inscription (fire-and-forget). */
|
|
1339
|
+
hook_after_signup_url?: string | null;
|
|
1340
|
+
/** Hook d'enrichissement des claims du JWT à chaque émission de jeton. */
|
|
1341
|
+
hook_custom_access_token_url?: string | null;
|
|
1342
|
+
/**
|
|
1343
|
+
* Le secret HMAC qui SIGNE tous les hooks est posé (write-only : jamais relu en clair).
|
|
1344
|
+
* Sans lui, aucun hook n'est signé — le destinataire ne peut pas authentifier l'appel.
|
|
1345
|
+
*/
|
|
1346
|
+
hook_secret_set?: boolean;
|
|
956
1347
|
auto_link_by_email: boolean;
|
|
1348
|
+
password_require_uppercase: boolean;
|
|
1349
|
+
password_require_number: boolean;
|
|
1350
|
+
password_require_special: boolean;
|
|
957
1351
|
revoke_on_new_device: boolean;
|
|
958
1352
|
enforce_ip_binding: boolean;
|
|
959
1353
|
allowed_email_domains: string[];
|
|
960
1354
|
blocked_email_domains: string[];
|
|
1355
|
+
rate_limit_login_per_hour: number;
|
|
1356
|
+
rate_limit_emails_per_hour: number;
|
|
1357
|
+
rate_limit_signup_per_hour: number;
|
|
961
1358
|
updated_at: string;
|
|
962
1359
|
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Champs RÉELLEMENT acceptés en écriture par `PUT /v1/auth/{project_id}/admin/settings`.
|
|
1362
|
+
*
|
|
1363
|
+
* Miroir exact de `UpdateSettingsRequest` (services/aura-auth/src/handlers/settings.rs) —
|
|
1364
|
+
* un test anti-divergence relit la struct Rust et compare. Ne PAS y ajouter un champ que
|
|
1365
|
+
* le backend ne désérialise pas : `UpdateSettingsRequest` porte `deny_unknown_fields`,
|
|
1366
|
+
* l'appel entier serait REFUSÉ (`400`, le champ fautif est nommé).
|
|
1367
|
+
*/
|
|
1368
|
+
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"];
|
|
1369
|
+
/**
|
|
1370
|
+
* Corps de `authAdmin.updateSettings()`.
|
|
1371
|
+
*
|
|
1372
|
+
* Distinct de `Partial<AuthSettings>` À DESSEIN, dans les deux sens :
|
|
1373
|
+
* - il AJOUTE les champs write-only que la vue de lecture ne peut pas porter (secrets
|
|
1374
|
+
* `webhook_secret`/`smtp_password`/`captcha_secret_key`/`sms_api_key`/`sms_api_secret`,
|
|
1375
|
+
* drapeaux d'effacement `smtp_clear`/`sms_clear`/`oauth_credentials_clear`,
|
|
1376
|
+
* `oauth_credentials` en clair) ;
|
|
1377
|
+
* - il RETIRE les projections de lecture (`smtp_password_set`, `captcha_secret_key_set`,
|
|
1378
|
+
* `sms_api_key_set`, `hook_secret_set`, `updated_at`) : le backend les REFUSE.
|
|
1379
|
+
*
|
|
1380
|
+
* Ces deux refus sont ceux du SERVEUR, pas seulement du compilateur (C26, rectifié) :
|
|
1381
|
+
* - champ inconnu → `400` nommant le champ (`deny_unknown_fields` sur `UpdateSettingsRequest`) ;
|
|
1382
|
+
* - `null` explicite → `400` : la mise à jour est un `COALESCE($n, colonne)`, donc `null`
|
|
1383
|
+
* ne remet RIEN à zéro — il ne ferait rien. Pour effacer, utiliser les drapeaux `*_clear`
|
|
1384
|
+
* (SMTP/SMS/OAuth) ou la valeur vide (`""`, `[]`) selon le champ.
|
|
1385
|
+
*
|
|
1386
|
+
* Conséquence pratique : ne PAS renvoyer tel quel un objet lu par `getSettings()`
|
|
1387
|
+
* (`{...settings, mfa_enabled: true}`) — il porte `updated_at` et les `*_set`, l'appel serait
|
|
1388
|
+
* refusé. N'envoyez que les champs à modifier.
|
|
1389
|
+
*/
|
|
1390
|
+
interface AuthSettingsUpdate {
|
|
1391
|
+
allow_signup?: boolean;
|
|
1392
|
+
email_confirmation_required?: boolean;
|
|
1393
|
+
/** Entre 8 et 128 (refusé hors bornes par le backend). */
|
|
1394
|
+
password_min_length?: number;
|
|
1395
|
+
mfa_enabled?: boolean;
|
|
1396
|
+
allow_anonymous_users?: boolean;
|
|
1397
|
+
/** Entre 300 et 3600 secondes. */
|
|
1398
|
+
jwt_expiry_seconds?: number;
|
|
1399
|
+
/** Entre 1 et 90 jours. */
|
|
1400
|
+
refresh_token_expiry_days?: number;
|
|
1401
|
+
site_url?: string;
|
|
1402
|
+
oauth_providers?: Record<string, OAuthProviderSetting>;
|
|
1403
|
+
new_device_alerts_enabled?: boolean;
|
|
1404
|
+
webhook_url?: string;
|
|
1405
|
+
/** Write-only : jamais relu par `getSettings()`. */
|
|
1406
|
+
webhook_secret?: string;
|
|
1407
|
+
smtp_host?: string;
|
|
1408
|
+
smtp_port?: number;
|
|
1409
|
+
smtp_username?: string;
|
|
1410
|
+
/** Write-only ; `getSettings()` n'expose que `smtp_password_set`. */
|
|
1411
|
+
smtp_password?: string;
|
|
1412
|
+
smtp_from_email?: string;
|
|
1413
|
+
smtp_from_name?: string;
|
|
1414
|
+
/** `true` = efface TOUTE la config SMTP du projet (retour au SMTP global). */
|
|
1415
|
+
smtp_clear?: boolean;
|
|
1416
|
+
/** Fusion partielle ; `getSettings()` ne les renvoie que MASQUÉS. */
|
|
1417
|
+
oauth_credentials?: Record<string, Record<string, unknown>>;
|
|
1418
|
+
/** `true` = efface tous les identifiants OAuth du projet (retour aux variables globales). */
|
|
1419
|
+
oauth_credentials_clear?: boolean;
|
|
1420
|
+
captcha_enabled?: boolean;
|
|
1421
|
+
captcha_provider?: string;
|
|
1422
|
+
captcha_site_key?: string;
|
|
1423
|
+
/** Write-only ; `getSettings()` n'expose que `captcha_secret_key_set`. */
|
|
1424
|
+
captcha_secret_key?: string;
|
|
1425
|
+
password_check_hibp?: boolean;
|
|
1426
|
+
sms_provider?: string;
|
|
1427
|
+
/** Write-only ; `getSettings()` n'expose que `sms_api_key_set`. */
|
|
1428
|
+
sms_api_key?: string;
|
|
1429
|
+
/** Write-only : jamais relu. */
|
|
1430
|
+
sms_api_secret?: string;
|
|
1431
|
+
sms_from_number?: string;
|
|
1432
|
+
/** `true` = efface toute la config SMS. */
|
|
1433
|
+
sms_clear?: boolean;
|
|
1434
|
+
redirect_urls?: string[];
|
|
1435
|
+
/** 0 (désactivé) ou >= 300 secondes. */
|
|
1436
|
+
session_inactivity_timeout_seconds?: number;
|
|
1437
|
+
hook_send_sms_url?: string;
|
|
1438
|
+
hook_send_email_url?: string;
|
|
1439
|
+
hook_mfa_verification_url?: string;
|
|
1440
|
+
hook_password_verification_url?: string;
|
|
1441
|
+
/** Hook BLOQUANT avant inscription : un refus du hook annule le signup. */
|
|
1442
|
+
hook_before_signup_url?: string;
|
|
1443
|
+
/** Hook de notification après inscription (fire-and-forget). */
|
|
1444
|
+
hook_after_signup_url?: string;
|
|
1445
|
+
/** Hook d'enrichissement des claims du JWT (fusionnés dans `app_metadata`). */
|
|
1446
|
+
hook_custom_access_token_url?: string;
|
|
1447
|
+
/**
|
|
1448
|
+
* Secret HMAC signant TOUS les hooks (en-tête `X-Aurabase-Signature`).
|
|
1449
|
+
*
|
|
1450
|
+
* Write-only ; `getSettings()` n'expose que `hook_secret_set`. Tant qu'il est absent,
|
|
1451
|
+
* les hooks partent NON SIGNÉS : le destinataire ne peut pas distinguer un appel
|
|
1452
|
+
* Aurabase d'un appel forgé par un tiers.
|
|
1453
|
+
*/
|
|
1454
|
+
hook_secret?: string;
|
|
1455
|
+
auto_link_by_email?: boolean;
|
|
1456
|
+
/** Exige au moins une majuscule (inscription + changement de mot de passe). */
|
|
1457
|
+
password_require_uppercase?: boolean;
|
|
1458
|
+
/** Exige au moins un chiffre. */
|
|
1459
|
+
password_require_number?: boolean;
|
|
1460
|
+
/** Exige au moins un caractère spécial. */
|
|
1461
|
+
password_require_special?: boolean;
|
|
1462
|
+
rate_limit_login_per_hour?: number;
|
|
1463
|
+
rate_limit_emails_per_hour?: number;
|
|
1464
|
+
rate_limit_signup_per_hour?: number;
|
|
1465
|
+
revoke_on_new_device?: boolean;
|
|
1466
|
+
enforce_ip_binding?: boolean;
|
|
1467
|
+
/** Remplacement complet (sémantique de liste, pas de fusion). */
|
|
1468
|
+
allowed_email_domains?: string[];
|
|
1469
|
+
/** Remplacement complet. */
|
|
1470
|
+
blocked_email_domains?: string[];
|
|
1471
|
+
}
|
|
963
1472
|
interface EmailTemplateUpdate {
|
|
964
1473
|
subject: string;
|
|
965
1474
|
html_body: string;
|
|
@@ -1068,8 +1577,12 @@ declare class BaseAuthStore {
|
|
|
1068
1577
|
* dépendance core → meta (les services n'importent plus la classe concrète).
|
|
1069
1578
|
*/
|
|
1070
1579
|
interface AuraClientLike {
|
|
1580
|
+
/**
|
|
1581
|
+
* URL du projet (`https://<slug>.aurabase.cloud`). C'est elle — et elle seule — qui
|
|
1582
|
+
* désigne le projet : la passerelle résout le tenant depuis l'hôte et injecte le
|
|
1583
|
+
* `project_id` dans le chemin. Aucun identifiant de projet ne transite côté client.
|
|
1584
|
+
*/
|
|
1071
1585
|
readonly baseUrl: string;
|
|
1072
|
-
readonly projectId: string;
|
|
1073
1586
|
readonly apiKey: string | null;
|
|
1074
1587
|
readonly fetchFn: typeof globalThis.fetch;
|
|
1075
1588
|
readonly authStore: BaseAuthStore;
|
|
@@ -1098,6 +1611,42 @@ interface AuraClientLike {
|
|
|
1098
1611
|
readonly debug?: boolean;
|
|
1099
1612
|
}
|
|
1100
1613
|
|
|
1614
|
+
/**
|
|
1615
|
+
* Abstract base class for all service modules.
|
|
1616
|
+
* Provides the shared HTTP helper and URL builder.
|
|
1617
|
+
*/
|
|
1618
|
+
declare abstract class BaseService {
|
|
1619
|
+
protected readonly client: AuraClientLike;
|
|
1620
|
+
constructor(client: AuraClientLike);
|
|
1621
|
+
/** Build a full URL for the given path. */
|
|
1622
|
+
protected url(path: string): string;
|
|
1623
|
+
/** Convenience HTTP helper that delegates to fetchWithAuth. */
|
|
1624
|
+
protected request<T>(method: string, path: string, body?: unknown, options?: {
|
|
1625
|
+
headers?: Record<string, string>;
|
|
1626
|
+
signal?: AbortSignal;
|
|
1627
|
+
skipAuthRefresh?: boolean;
|
|
1628
|
+
/** Override explicite du Bearer (LOT D) — valide un token arbitraire. */
|
|
1629
|
+
authToken?: string;
|
|
1630
|
+
}): Promise<AuraResponse<T>>;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Parse a raw Cookie header string into key-value pairs.
|
|
1635
|
+
*/
|
|
1636
|
+
declare function parseCookieHeader(header: string): Record<string, string>;
|
|
1637
|
+
interface SerializeCookieOptions {
|
|
1638
|
+
path?: string;
|
|
1639
|
+
domain?: string;
|
|
1640
|
+
maxAge?: number;
|
|
1641
|
+
secure?: boolean;
|
|
1642
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
1643
|
+
httpOnly?: boolean;
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Serialize a cookie name + value + options into a Set-Cookie header value.
|
|
1647
|
+
*/
|
|
1648
|
+
declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
|
|
1649
|
+
|
|
1101
1650
|
/** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
|
|
1102
1651
|
declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
1103
1652
|
/** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
|
|
@@ -1130,23 +1679,43 @@ interface FetchInit {
|
|
|
1130
1679
|
declare function fetchWithAuth<T>(url: string, init: FetchInit, client: AuraClientLike): Promise<AuraResponse<T>>;
|
|
1131
1680
|
|
|
1132
1681
|
/**
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1682
|
+
* Primitives de résilience réseau (module pur, sans I/O).
|
|
1683
|
+
* `Math.random`/`Date.now` sont injectables pour des tests déterministes.
|
|
1135
1684
|
*/
|
|
1136
|
-
declare
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1685
|
+
declare const RETRYABLE_STATUS: Set<number>;
|
|
1686
|
+
declare const RETRYABLE_METHODS: Set<string>;
|
|
1687
|
+
declare const DEFAULT_MAX_RETRIES = 3;
|
|
1688
|
+
declare function isRetryableStatus(status: number): boolean;
|
|
1689
|
+
declare function isRetryableMethod(method: string): boolean;
|
|
1690
|
+
/** Full jitter : random(0, min(BASE·2^attempt, CAP)). `attempt` commence à 0. */
|
|
1691
|
+
declare function computeBackoff(attempt: number, rand?: () => number): number;
|
|
1692
|
+
/** Parse un header `Retry-After` (secondes OU HTTP-date) → délai en ms, ou null. */
|
|
1693
|
+
declare function parseRetryAfter(header: string | null, now?: number): number | null;
|
|
1694
|
+
/** `sleep(ms)` qui résout tôt (sans rejeter) si `signal` s'annule. */
|
|
1695
|
+
declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
1696
|
+
/** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
|
|
1697
|
+
declare function isAbortError(err: unknown): boolean;
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Portée d'un client, dérivée de son URL de projet.
|
|
1701
|
+
*
|
|
1702
|
+
* Chaque projet est adressé par son PROPRE hôte (`https://<slug>.aurabase.cloud`), et le
|
|
1703
|
+
* SDK ne connaît plus d'identifiant de projet : l'URL est la seule chose qui distingue
|
|
1704
|
+
* deux projets. Tout ce qui devait auparavant être « scopé projet » côté client — clé
|
|
1705
|
+
* BroadcastChannel de synchro multi-onglets, `code_verifier` PKCE, marqueur d'initiation
|
|
1706
|
+
* OAuth, nom de cookie SSR, nom de verrou de refresh — se scope donc par cette portée.
|
|
1707
|
+
*
|
|
1708
|
+
* Ce n'est PAS cosmétique : une page (`app.example.com`) peut parfaitement instancier deux
|
|
1709
|
+
* clients pointant sur deux projets. Sans portée, leurs clés de stockage se marcheraient
|
|
1710
|
+
* dessus dans le MÊME `localStorage`, celui de la page.
|
|
1711
|
+
*
|
|
1712
|
+
* Le résultat est réduit à `[a-z0-9-]` : il sert aussi de nom de cookie, et un nom de
|
|
1713
|
+
* cookie est un `token` RFC 6265 — un `:` de port (`localhost:8080`) y est interdit.
|
|
1714
|
+
*/
|
|
1715
|
+
declare function clientScope(baseUrl: string): string;
|
|
1716
|
+
|
|
1717
|
+
/** JSON.stringify avec support des bigint (sérialisés en string). */
|
|
1718
|
+
declare function serializeBody(body: unknown): string;
|
|
1150
1719
|
|
|
1151
1720
|
interface CookieOptions {
|
|
1152
1721
|
path?: string;
|
|
@@ -1158,21 +1727,36 @@ interface CookieOptions {
|
|
|
1158
1727
|
}
|
|
1159
1728
|
/**
|
|
1160
1729
|
* Auth store backed by `localStorage` (browser) with cookie helpers for SSR.
|
|
1730
|
+
*
|
|
1731
|
+
* `key` est OBLIGATOIRE, et c'est le correctif K3 : la session est le sixième état
|
|
1732
|
+
* local du SDK, et le seul qui n'était pas scopé par projet. Un nom par défaut
|
|
1733
|
+
* constant faisait que deux clients pointant sur deux projets dans la MÊME page
|
|
1734
|
+
* écrivaient sous la même clé du MÊME `localStorage` (celui de la page, pas celui du
|
|
1735
|
+
* projet) et s'écrasaient mutuellement. Le nom canonique est
|
|
1736
|
+
* `aura-${clientScope(baseUrl)}-auth` — celui que posent `AuraClient` (navigateur) et
|
|
1737
|
+
* `createServerClient` (cookie SSR), et la même convention que les cinq autres états
|
|
1738
|
+
* (`aura-<portée>-pkce-code-verifier`, `-oauth-implicit-initiated`, canal de synchro,
|
|
1739
|
+
* verrou de refresh). Voir `packages/core/src/scope.ts`.
|
|
1161
1740
|
*/
|
|
1162
1741
|
declare class LocalAuthStore extends BaseAuthStore {
|
|
1163
1742
|
/** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
|
|
1164
1743
|
readonly persistent = true;
|
|
1165
1744
|
private readonly key;
|
|
1166
|
-
|
|
1745
|
+
/**
|
|
1746
|
+
* @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
|
|
1747
|
+
* `aura-${clientScope(baseUrl)}-auth`.
|
|
1748
|
+
*/
|
|
1749
|
+
constructor(key: string);
|
|
1167
1750
|
save(token: string, refreshToken: string, user: User): void;
|
|
1168
1751
|
clear(): void;
|
|
1169
1752
|
/**
|
|
1170
1753
|
* Supprime de localStorage tous les artefacts d'auth éphémères sur disque :
|
|
1171
|
-
* - `code_verifier` PKCE (`aura-<
|
|
1172
|
-
* - marqueur d'initiation OAuth implicit (`aura-<
|
|
1754
|
+
* - `code_verifier` PKCE (`aura-<portée>-pkce-code-verifier`),
|
|
1755
|
+
* - marqueur d'initiation OAuth implicit (`aura-<portée>-oauth-implicit-initiated`,
|
|
1173
1756
|
* SEC-F3). Un `signOut`/`clear()` ne doit laisser aucun résidu réutilisable (parité
|
|
1174
1757
|
* `@supabase/ssr` : ces artefacts sont éphémères). On itère les clés préfixées pour
|
|
1175
|
-
* couvrir un store partagé entre plusieurs projets, sans dépendre
|
|
1758
|
+
* couvrir un store partagé entre plusieurs projets, sans dépendre de la portée courante
|
|
1759
|
+
* (`clientScope`, dérivée de l'URL du projet).
|
|
1176
1760
|
*/
|
|
1177
1761
|
private _removePkceVerifiers;
|
|
1178
1762
|
setItem(key: string, value: string): void;
|
|
@@ -1192,42 +1776,4 @@ declare class LocalAuthStore extends BaseAuthStore {
|
|
|
1192
1776
|
private _removeStorage;
|
|
1193
1777
|
}
|
|
1194
1778
|
|
|
1195
|
-
|
|
1196
|
-
declare function serializeBody(body: unknown): string;
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Parse a raw Cookie header string into key-value pairs.
|
|
1200
|
-
*/
|
|
1201
|
-
declare function parseCookieHeader(header: string): Record<string, string>;
|
|
1202
|
-
interface SerializeCookieOptions {
|
|
1203
|
-
path?: string;
|
|
1204
|
-
domain?: string;
|
|
1205
|
-
maxAge?: number;
|
|
1206
|
-
secure?: boolean;
|
|
1207
|
-
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
1208
|
-
httpOnly?: boolean;
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Serialize a cookie name + value + options into a Set-Cookie header value.
|
|
1212
|
-
*/
|
|
1213
|
-
declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
|
|
1214
|
-
|
|
1215
|
-
/**
|
|
1216
|
-
* Primitives de résilience réseau (module pur, sans I/O).
|
|
1217
|
-
* `Math.random`/`Date.now` sont injectables pour des tests déterministes.
|
|
1218
|
-
*/
|
|
1219
|
-
declare const RETRYABLE_STATUS: Set<number>;
|
|
1220
|
-
declare const RETRYABLE_METHODS: Set<string>;
|
|
1221
|
-
declare const DEFAULT_MAX_RETRIES = 3;
|
|
1222
|
-
declare function isRetryableStatus(status: number): boolean;
|
|
1223
|
-
declare function isRetryableMethod(method: string): boolean;
|
|
1224
|
-
/** Full jitter : random(0, min(BASE·2^attempt, CAP)). `attempt` commence à 0. */
|
|
1225
|
-
declare function computeBackoff(attempt: number, rand?: () => number): number;
|
|
1226
|
-
/** Parse un header `Retry-After` (secondes OU HTTP-date) → délai en ms, ou null. */
|
|
1227
|
-
declare function parseRetryAfter(header: string | null, now?: number): number | null;
|
|
1228
|
-
/** `sleep(ms)` qui résout tôt (sans rejeter) si `signal` s'annule. */
|
|
1229
|
-
declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
1230
|
-
/** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
|
|
1231
|
-
declare function isAbortError(err: unknown): boolean;
|
|
1232
|
-
|
|
1233
|
-
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, 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 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 };
|
|
1779
|
+
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 };
|