@aurabase/core 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aurabase
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # @aurabase/core
2
+
3
+ Cœur d'infrastructure du SDK Aurabase : type de réponse (`AuraResponse`), erreurs structurées (`AuraError`), stores d'authentification, couche fetch résiliente et le contrat `AuraClientLike`/`BaseService` pour composer un service ou un client alternatif.
4
+
5
+ ![npm](https://img.shields.io/npm/v/@aurabase/core) ![license](https://img.shields.io/badge/license-MIT-blue)
6
+
7
+ > Pre-release — pas encore publié sur npm.
8
+
9
+ Fait partie du SDK **[aurabase](../aurabase)**. Installé automatiquement comme dépendance transitive de tous les autres packages `@aurabase/*` ; ce package n'est utile en installation directe que pour écrire une extension ou un client alternatif (voir Usage).
10
+
11
+ Ce README s'adresse aux **auteurs d'extensions** (service additionnel, client SSR maison) — pas aux utilisateurs finaux d'une application, qui doivent passer par le méta-package [`aurabase`](../aurabase).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm i @aurabase/core
17
+ ```
18
+
19
+ > `@aurabase/core` n'a aucune dépendance runtime Aurabase — c'est la fondation. Si vous utilisez `aurabase` ou n'importe quel package `@aurabase/*` (auth, db, storage, realtime, functions, ai, notifications), il est déjà présent en tant que dépendance transitive.
20
+
21
+ ## Usage
22
+
23
+ ### `AuraResponse` / `AuraError`
24
+
25
+ Toutes les méthodes de service renvoient une `Promise<AuraResponse<T>>` — jamais un throw sur une erreur métier/HTTP.
26
+
27
+ ```ts
28
+ import type { AuraResponse } from '@aurabase/core'
29
+
30
+ interface Widget {
31
+ id: string
32
+ name: string
33
+ }
34
+
35
+ function handle(res: AuraResponse<Widget>) {
36
+ if (res.error) {
37
+ if (res.error.isNotFound) console.warn('introuvable')
38
+ else if (res.error.isRateLimited) console.warn('rate limited')
39
+ else console.error(res.error.status, res.error.code, res.error.message)
40
+ return
41
+ }
42
+ // `data` peut être `null` même en succès (204 / corps vide) : tester `data`
43
+ // plutôt que `error` évite l'assertion `data!`, et TypeScript réduit alors
44
+ // `res.data` à `Widget`.
45
+ if (res.data === null) {
46
+ console.log('succès sans contenu')
47
+ return
48
+ }
49
+ console.log(res.data.name)
50
+ }
51
+ ```
52
+
53
+ #### `AuraResponse` est une union à trois membres
54
+
55
+ ```ts
56
+ type AuraResponse<T> =
57
+ | AuraSuccess<T> // { data: T, error: null, meta?: Meta }
58
+ | AuraNoContent // { data: null, error: null, meta?: Meta }
59
+ | AuraFailure // { data: null, error: AuraError, meta?: Meta }
60
+ ```
61
+
62
+ Conséquences utiles :
63
+
64
+ - la combinaison **impossible** `{ data: T, error: AuraError }` n'est plus
65
+ constructible — aucun producteur du SDK ne la produit ;
66
+ - `data` non nul ⇒ TypeScript sait que `error` est `null` ;
67
+ - `error` non nul ⇒ TypeScript sait que `data` est `null`.
68
+
69
+ Le membre `AuraNoContent` n'est pas un ornement : `fetchWithAuth` le renvoie sur
70
+ un **204** et sur tout **2xx à corps vide**, pour n'importe quel `T`. Le cas
71
+ emblématique est `db.from('t').delete().eq(...)` **sans** `.select()`, typé
72
+ `Row[]` mais auquel PostgREST répond 204. Le fondre dans le membre succès
73
+ rendrait le type menteur : c'est pourquoi `if (error) return` ne réduit
74
+ volontairement **pas** `data` à `T`.
75
+
76
+ #### Corps de réponse invalide (`invalid_response`)
77
+
78
+ Un **2xx dont le corps n'est pas du JSON** (page HTML d'un proxy mal configuré,
79
+ réponse tronquée, corps illisible) produit un `AuraError` structuré de code
80
+ `invalid_response` et de `status: 0` (erreur détectée côté client, comme
81
+ `network_error` et `timeout`). Le statut HTTP réel, le content-type annoncé et
82
+ la taille du corps figurent dans le `message` ; le corps lui-même n'est jamais
83
+ recopié. Un 2xx **vide** reste au contraire un succès sans contenu.
84
+
85
+ `isAuraError()` détecte une instance `AuraError` même à travers une frontière de bundle/realm différente (deux copies de `@aurabase/core` chargées séparément) :
86
+
87
+ ```ts
88
+ import { isAuraError } from '@aurabase/core'
89
+
90
+ function report(err: unknown) {
91
+ if (isAuraError(err)) {
92
+ console.error(err.code, err.status)
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### Construire un service (`BaseService` + `AuraClientLike`)
98
+
99
+ `BaseService` est la classe de base utilisée par tous les services officiels (`AuthService`, `DatabaseService`, `FunctionsService`, ...). `AuraClientLike` est un contrat structurel : n'importe quel objet qui le satisfait (le `AuraClient` concret du méta-package, ou un client maison) peut être passé à un service.
100
+
101
+ ```ts
102
+ import { BaseService } from '@aurabase/core'
103
+ import type { AuraClientLike, AuraResponse } from '@aurabase/core'
104
+
105
+ interface Widget {
106
+ id: string
107
+ name: string
108
+ }
109
+
110
+ class WidgetsService extends BaseService {
111
+ constructor(client: AuraClientLike) {
112
+ super(client)
113
+ }
114
+
115
+ private get prefix(): string {
116
+ return `/v1/widgets/${this.client.projectId}`
117
+ }
118
+
119
+ async list(): Promise<AuraResponse<Widget[]>> {
120
+ return this.request('GET', this.prefix)
121
+ }
122
+
123
+ async get(id: string): Promise<AuraResponse<Widget>> {
124
+ return this.request('GET', `${this.prefix}/${id}`)
125
+ }
126
+ }
127
+ ```
128
+
129
+ `this.request(method, path, body?, options?)` délègue à la couche fetch partagée (`fetchWithAuth`) : injection `apikey`/`Authorization`, refresh proactif du token, retry transitoire et dé-wrapping `{data, meta}` — un service maison en bénéficie gratuitement sans en réimplémenter la logique.
130
+
131
+ ### Stores d'authentification (dont SSR)
132
+
133
+ `BaseAuthStore` est en mémoire (event-emitter `onChange`, stockage clé/valeur générique pour le PKCE `code_verifier`). `LocalAuthStore` l'étend avec persistance `localStorage` et des helpers cookie pour le rendu serveur.
134
+
135
+ ```ts
136
+ import { LocalAuthStore } from '@aurabase/core'
137
+
138
+ function hydrateFromCookieHeader(cookieHeader: string) {
139
+ const store = new LocalAuthStore('aurabase_auth')
140
+ store.loadFromCookie(cookieHeader) // hydrate depuis l'en-tête Cookie de la requête entrante
141
+ return store
142
+ }
143
+
144
+ const store = hydrateFromCookieHeader('aurabase_auth=%7B%22token%22%3Anull%7D')
145
+
146
+ // Après une mutation de session, sérialiser l'état courant en Set-Cookie.
147
+ const setCookieValue = store.exportToCookie({ path: '/', secure: true, sameSite: 'Lax', httpOnly: true })
148
+ ```
149
+
150
+ `store.persistent` indique si le store survit à un redirect pleine page (`false` pour `BaseAuthStore`, `true` pour `LocalAuthStore`) — c'est ce flag que le flux OAuth PKCE utilise pour décider s'il peut stocker un `code_verifier` de façon fiable.
151
+
152
+ ### Résilience de la couche fetch (interne, consommée automatiquement)
153
+
154
+ `BaseService.request()` passe par un pipeline de fetch partagé qui gère, sans configuration de votre part :
155
+
156
+ - retry transitoire (full-jitter) sur `GET`/`HEAD` pour les statuts `408/429/502/503/504`, avec respect d'un `Retry-After` plafonné ;
157
+ - timeout par tentative (chaque rejeu repart avec un budget neuf) ;
158
+ - refresh de token single-flight (une seule requête de refresh en vol par client, dédupliquée) avec un commit-guard anti-race si une déconnexion/reconnexion s'intercale pendant l'attente.
159
+
160
+ Cette couche (`fetchWithAuth`, `refreshOnce`) est exportée mais reste un détail d'implémentation consommé par `BaseService` : son usage direct n'est pas le chemin recommandé et son contrat n'est pas garanti stable entre versions mineures.
161
+
162
+ ## Limitations
163
+
164
+ - Package d'infrastructure : pas d'API « produit ». Pour construire une application, utilisez le méta-package [`aurabase`](../aurabase), qui assemble tous les services derrière un seul client.
165
+ - `BaseAuthStore` est en mémoire — l'état est perdu au reload ; `LocalAuthStore` nécessite un `localStorage` disponible (sinon retombe silencieusement sur le comportement en mémoire, sauf via les helpers cookie explicites).
166
+ - `AuraClientLike` est un contrat minimal (`baseUrl`, `projectId`, `apiKey`, `fetchFn`, `authStore`, `auth.refreshSession`, `retryConfig?`, `requestTimeoutMs?`) : un client maison doit l'implémenter intégralement pour rester compatible avec `BaseService`.
167
+ - La couche fetch bas niveau (`fetchWithAuth`/`refreshOnce`) est un détail d'implémentation interne, pas une API publique stabilisée.
168
+
169
+ ## Documentation
170
+
171
+ Référence complète du SDK (tous les services, types partagés, SSR) : [README racine](../../README.md).