@frenchbaas/js 0.3.0 → 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/README.md +77 -5
- package/dist/index.cjs +159 -2
- package/dist/index.d.mts +149 -7
- package/dist/index.d.ts +149 -7
- package/dist/index.js +158 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,11 +24,11 @@ const client = new FrenchBaas({
|
|
|
24
24
|
## Auth
|
|
25
25
|
|
|
26
26
|
```js
|
|
27
|
-
// Inscription
|
|
28
|
-
const { user, access_token } = await client.auth.signUp({ email: 'a@b.com', password: '
|
|
27
|
+
// Inscription (mot de passe : 8 caractères minimum)
|
|
28
|
+
const { user, access_token } = await client.auth.signUp({ email: 'a@b.com', password: 'monMotDePasse' })
|
|
29
29
|
|
|
30
30
|
// Connexion
|
|
31
|
-
const { user, access_token } = await client.auth.login({ email: 'a@b.com', password: '
|
|
31
|
+
const { user, access_token } = await client.auth.login({ email: 'a@b.com', password: 'monMotDePasse' })
|
|
32
32
|
|
|
33
33
|
// Déconnexion
|
|
34
34
|
await client.auth.logout()
|
|
@@ -49,12 +49,23 @@ const col = client.collection('uuid-de-la-collection')
|
|
|
49
49
|
const { data, meta } = await col.get({ page: 1, perPage: 20 })
|
|
50
50
|
// meta → { total, page, per_page, total_pages }
|
|
51
51
|
|
|
52
|
-
// Filtrer par champ
|
|
52
|
+
// Filtrer par champ — valeur unique
|
|
53
53
|
const { data: pending } = await col.get({ filter: { status: 'pending' } })
|
|
54
54
|
|
|
55
|
-
// Filtrer
|
|
55
|
+
// Filtrer OR — plusieurs valeurs (status pending OU delivered)
|
|
56
|
+
const { data: multi } = await col.get({ filter: { status: ['pending', 'delivered'] } })
|
|
57
|
+
|
|
58
|
+
// Filtrer par references — valeur unique (contient l'UUID)
|
|
56
59
|
const { data: products } = await col.get({ filter: { category_ids: 'uuid-categorie' } })
|
|
57
60
|
|
|
61
|
+
// Filtrer references OR — catégorie A OU B
|
|
62
|
+
const { data: orProducts } = await col.get({ filter: { category_ids: ['uuid-cat-a', 'uuid-cat-b'] } })
|
|
63
|
+
|
|
64
|
+
// Filtrer references AND — dans catégorie A ET B (uniquement pour references/array)
|
|
65
|
+
const { data: andProducts } = await col.get({ filter: { category_ids: { and: ['uuid-cat-a', 'uuid-cat-b'] } } })
|
|
66
|
+
|
|
67
|
+
// ⚠ Maximum 20 valeurs par filtre (OR ou AND)
|
|
68
|
+
|
|
58
69
|
// Récupérer un document par ID
|
|
59
70
|
const doc = await col.getById('doc-uuid')
|
|
60
71
|
// doc → { id, data, created_at, updated_at }
|
|
@@ -108,6 +119,61 @@ Ces permissions se configurent dans le Dashboard → collection → schéma →
|
|
|
108
119
|
|
|
109
120
|
---
|
|
110
121
|
|
|
122
|
+
## Storage
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
// Lister les buckets du projet (X-Api-Key uniquement, pas de Bearer)
|
|
126
|
+
const buckets = await client.storage.listBuckets()
|
|
127
|
+
// → [{ id, name, visibility, public, created_at }, ...]
|
|
128
|
+
|
|
129
|
+
// Obtenir un client bucket
|
|
130
|
+
const bucket = client.storage.bucket('uuid-bucket')
|
|
131
|
+
|
|
132
|
+
// Lister les fichiers (paginé, filtre préfixe optionnel)
|
|
133
|
+
const { data, meta } = await bucket.listObjects({ page: 1, perPage: 20, prefix: 'users/123/' })
|
|
134
|
+
// → data: [{ id, name, content_type, size, created_by, created_at, url? }, ...]
|
|
135
|
+
// → meta: { total, page, per_page, pages }
|
|
136
|
+
|
|
137
|
+
// Upload un fichier (3 étapes gérées automatiquement : présignature → PUT OVH → confirmation)
|
|
138
|
+
// Bearer requis.
|
|
139
|
+
const file = document.querySelector('input[type=file]').files[0]
|
|
140
|
+
const obj = await bucket.upload(file, 'photo.jpg', {
|
|
141
|
+
onProgress: pct => console.log(`${pct}%`), // progression optionnelle (navigateur uniquement)
|
|
142
|
+
})
|
|
143
|
+
// → { id, name, content_type, size, created_by, created_at, url? }
|
|
144
|
+
|
|
145
|
+
// Récupérer l'URL d'un fichier
|
|
146
|
+
const { url, expires_in } = await bucket.getUrl(obj.id)
|
|
147
|
+
// Bucket public → url permanente, expires_in absent
|
|
148
|
+
// Bucket privé → url signée (TTL 3600 s), créateur uniquement
|
|
149
|
+
|
|
150
|
+
// Supprimer un fichier (Bearer requis, créateur uniquement)
|
|
151
|
+
await bucket.delete(obj.id)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Visibilité des buckets
|
|
155
|
+
|
|
156
|
+
| Visibilité | listObjects | upload / delete | getUrl |
|
|
157
|
+
|------------|-------------|-----------------|--------|
|
|
158
|
+
| `public` | Sans Bearer | Bearer requis | Sans Bearer (URL permanente) |
|
|
159
|
+
| `private` | Bearer requis (ses fichiers) | Bearer requis | Bearer requis (URL signée 1h) |
|
|
160
|
+
|
|
161
|
+
### Erreurs Storage
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { ConflictError, ValidationError, QuotaError } from '@frenchbaas/js'
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await bucket.upload(file, 'photo.jpg')
|
|
168
|
+
} catch (e) {
|
|
169
|
+
if (e instanceof ConflictError) console.error('Nom déjà pris par un autre utilisateur')
|
|
170
|
+
if (e instanceof ValidationError) console.error('Fichier trop grand ou type non autorisé')
|
|
171
|
+
if (e instanceof QuotaError) console.error('Quota storage dépassé')
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
111
177
|
## Webhooks
|
|
112
178
|
|
|
113
179
|
Si une collection a un `before_create_url` ou `before_update_url` configuré,
|
|
@@ -151,6 +217,7 @@ try {
|
|
|
151
217
|
import {
|
|
152
218
|
FrenchBaasError,
|
|
153
219
|
AuthError,
|
|
220
|
+
ConflictError,
|
|
154
221
|
ValidationError,
|
|
155
222
|
NotFoundError,
|
|
156
223
|
QuotaError,
|
|
@@ -169,6 +236,7 @@ try {
|
|
|
169
236
|
console.error(e.message, e.errors)
|
|
170
237
|
}
|
|
171
238
|
else if (e instanceof AuthError) console.error('Non connecté ou session expirée')
|
|
239
|
+
else if (e instanceof ConflictError) console.error('Conflit : ressource déjà existante')
|
|
172
240
|
else if (e instanceof NotFoundError) console.error('Document ou collection introuvable')
|
|
173
241
|
else if (e instanceof QuotaError) console.error('Quota dépassé :', e.message)
|
|
174
242
|
else if (e instanceof RateLimitError) console.error('Trop de requêtes, réessayez dans quelques instants')
|
|
@@ -206,6 +274,10 @@ const client = new FrenchBaas({
|
|
|
206
274
|
Utilisez `localStorage` pour que la session survive au rechargement de page
|
|
207
275
|
(applications web). Utilisez `memory` pour les environnements serveur (Node.js).
|
|
208
276
|
|
|
277
|
+
> **Sécurité** : l'option `localStorage` expose les tokens à toute attaque XSS sur votre page.
|
|
278
|
+
> Assurez-vous que votre application ne charge pas de scripts tiers non maîtrisés
|
|
279
|
+
> et qu'elle applique une politique CSP stricte si vous utilisez ce mode.
|
|
280
|
+
|
|
209
281
|
---
|
|
210
282
|
|
|
211
283
|
## Fonctionnalités automatiques
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AuthError: () => AuthError,
|
|
24
|
+
ConflictError: () => ConflictError,
|
|
24
25
|
FrenchBaas: () => FrenchBaasClient,
|
|
25
26
|
FrenchBaasError: () => FrenchBaasError,
|
|
26
27
|
NetworkError: () => NetworkError,
|
|
@@ -91,6 +92,13 @@ var ServerError = class extends FrenchBaasError {
|
|
|
91
92
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
92
93
|
}
|
|
93
94
|
};
|
|
95
|
+
var ConflictError = class extends FrenchBaasError {
|
|
96
|
+
constructor(message = "Conflit : la ressource existe d\xE9j\xE0.") {
|
|
97
|
+
super(message, 409);
|
|
98
|
+
this.name = "ConflictError";
|
|
99
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
94
102
|
|
|
95
103
|
// src/auth.ts
|
|
96
104
|
var AuthModule = class {
|
|
@@ -211,7 +219,14 @@ var CollectionClient = class {
|
|
|
211
219
|
};
|
|
212
220
|
if (options.filter) {
|
|
213
221
|
for (const [key, val] of Object.entries(options.filter)) {
|
|
214
|
-
if (val
|
|
222
|
+
if (val === void 0) continue;
|
|
223
|
+
if (typeof val === "string") {
|
|
224
|
+
params[`filter[${key}]`] = val;
|
|
225
|
+
} else if (Array.isArray(val)) {
|
|
226
|
+
params[`filter[${key}][]`] = val;
|
|
227
|
+
} else {
|
|
228
|
+
params[`filter[${key}][and][]`] = val.and;
|
|
229
|
+
}
|
|
215
230
|
}
|
|
216
231
|
}
|
|
217
232
|
const res = await this._http.get(
|
|
@@ -314,7 +329,12 @@ var HttpClient = class {
|
|
|
314
329
|
const url = new URL(this._baseUrl + path);
|
|
315
330
|
if (params) {
|
|
316
331
|
for (const [k, v] of Object.entries(params)) {
|
|
317
|
-
if (v
|
|
332
|
+
if (v === void 0) continue;
|
|
333
|
+
if (Array.isArray(v)) {
|
|
334
|
+
for (const item of v) url.searchParams.append(k, item);
|
|
335
|
+
} else {
|
|
336
|
+
url.searchParams.set(k, String(v));
|
|
337
|
+
}
|
|
318
338
|
}
|
|
319
339
|
}
|
|
320
340
|
return this._request("GET", url.toString(), void 0, true);
|
|
@@ -404,6 +424,8 @@ var HttpClient = class {
|
|
|
404
424
|
const details = err.errors ?? [];
|
|
405
425
|
throw new ValidationError(details.length ? details.join(", ") : msg, details);
|
|
406
426
|
}
|
|
427
|
+
case 409:
|
|
428
|
+
throw new ConflictError(msg);
|
|
407
429
|
case 429:
|
|
408
430
|
throw new RateLimitError();
|
|
409
431
|
default:
|
|
@@ -413,6 +435,139 @@ var HttpClient = class {
|
|
|
413
435
|
}
|
|
414
436
|
};
|
|
415
437
|
|
|
438
|
+
// src/storage.ts
|
|
439
|
+
var StorageModule = class {
|
|
440
|
+
constructor(_http) {
|
|
441
|
+
this._http = _http;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Liste tous les buckets du projet.
|
|
445
|
+
*
|
|
446
|
+
* Authentification par API key uniquement — pas de Bearer requis.
|
|
447
|
+
* Usage principal : découverte des IDs pendant la construction de l'app.
|
|
448
|
+
*/
|
|
449
|
+
async listBuckets() {
|
|
450
|
+
const res = await this._http.get(
|
|
451
|
+
"/sdk/storage/buckets"
|
|
452
|
+
);
|
|
453
|
+
return res.data;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Retourne un client pour opérer sur un bucket spécifique.
|
|
457
|
+
*
|
|
458
|
+
* @param bucketId UUID du bucket (visible dans Dashboard → Storage)
|
|
459
|
+
*/
|
|
460
|
+
bucket(bucketId) {
|
|
461
|
+
if (!bucketId || typeof bucketId !== "string") {
|
|
462
|
+
throw new Error("[FrenchBaas] storage.bucket() requiert un UUID de bucket valide.");
|
|
463
|
+
}
|
|
464
|
+
return new StorageBucketClient(bucketId, this._http);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
var StorageBucketClient = class {
|
|
468
|
+
constructor(_bucketId, _http) {
|
|
469
|
+
this._bucketId = _bucketId;
|
|
470
|
+
this._http = _http;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Liste les fichiers du bucket avec pagination et filtre préfixe.
|
|
474
|
+
*
|
|
475
|
+
* - Bucket **public** : Bearer non requis — retourne tous les fichiers.
|
|
476
|
+
* - Bucket **privé** : Bearer requis — retourne uniquement les fichiers du user connecté.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* const { data, meta } = await bucket.listObjects({ prefix: 'users/123/', perPage: 20 })
|
|
480
|
+
*/
|
|
481
|
+
async listObjects(options = {}) {
|
|
482
|
+
const params = {};
|
|
483
|
+
if (options.page !== void 0) params["page"] = options.page;
|
|
484
|
+
if (options.perPage !== void 0) params["per_page"] = options.perPage;
|
|
485
|
+
if (options.prefix) params["prefix"] = options.prefix;
|
|
486
|
+
return this._http.get(
|
|
487
|
+
`/sdk/storage/buckets/${this._bucketId}/objects`,
|
|
488
|
+
params
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Upload un fichier en 3 étapes : présignature → PUT direct vers OVH → confirmation.
|
|
493
|
+
*
|
|
494
|
+
* Bearer requis. La progression PUT (0–100 %) peut être suivie via `options.onProgress`.
|
|
495
|
+
*
|
|
496
|
+
* @param file Fichier ou Blob à uploader
|
|
497
|
+
* @param name Nom dans le bucket (ex: "photo.jpg", "users/123/avatar.jpg")
|
|
498
|
+
* @param options contentType (override), onProgress
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* const obj = await bucket.upload(file, 'avatar.jpg', {
|
|
502
|
+
* onProgress: pct => console.log(`${pct}%`)
|
|
503
|
+
* })
|
|
504
|
+
*/
|
|
505
|
+
async upload(file, name, options = {}) {
|
|
506
|
+
const contentType = options.contentType ?? ((file instanceof File ? file.type : "") || "application/octet-stream");
|
|
507
|
+
const presign = await this._http.post(`/sdk/storage/buckets/${this._bucketId}/upload`, {
|
|
508
|
+
name,
|
|
509
|
+
content_type: contentType,
|
|
510
|
+
size: file.size
|
|
511
|
+
});
|
|
512
|
+
await this._putToOvh(presign.upload_url, file, contentType, options.onProgress);
|
|
513
|
+
const confirm = await this._http.post(
|
|
514
|
+
`/sdk/storage/buckets/${this._bucketId}/confirm`,
|
|
515
|
+
{ name, key: presign.key, content_type: contentType }
|
|
516
|
+
);
|
|
517
|
+
return confirm.data;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Retourne l'URL d'accès à un fichier.
|
|
521
|
+
*
|
|
522
|
+
* - Bucket **public** : URL publique permanente, Bearer non requis.
|
|
523
|
+
* - Bucket **privé** : URL signée (TTL 3600 s), Bearer requis, créateur uniquement.
|
|
524
|
+
*/
|
|
525
|
+
async getUrl(objectId) {
|
|
526
|
+
return this._http.get(
|
|
527
|
+
`/sdk/storage/buckets/${this._bucketId}/objects/${objectId}/url`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Supprime un fichier du bucket.
|
|
532
|
+
* Bearer requis. Seul le créateur du fichier peut le supprimer.
|
|
533
|
+
*/
|
|
534
|
+
async delete(objectId) {
|
|
535
|
+
await this._http.delete(`/sdk/storage/buckets/${this._bucketId}/objects/${objectId}`);
|
|
536
|
+
}
|
|
537
|
+
// ── Private ──────────────────────────────────────────────────────────────
|
|
538
|
+
/**
|
|
539
|
+
* PUT direct vers l'URL présignée OVH.
|
|
540
|
+
* Utilise XHR (si disponible) pour le suivi de progression, sinon fetch.
|
|
541
|
+
*/
|
|
542
|
+
async _putToOvh(url, file, contentType, onProgress) {
|
|
543
|
+
if (typeof onProgress === "function" && typeof XMLHttpRequest !== "undefined") {
|
|
544
|
+
return new Promise((resolve, reject) => {
|
|
545
|
+
const xhr = new XMLHttpRequest();
|
|
546
|
+
xhr.open("PUT", url);
|
|
547
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
548
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
549
|
+
if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100));
|
|
550
|
+
});
|
|
551
|
+
xhr.addEventListener("load", () => {
|
|
552
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve();
|
|
553
|
+
else reject(new NetworkError(`Upload OVH \xE9chou\xE9 (${xhr.status})`));
|
|
554
|
+
});
|
|
555
|
+
xhr.addEventListener(
|
|
556
|
+
"error",
|
|
557
|
+
() => reject(new NetworkError("Erreur r\xE9seau lors de l'upload OVH."))
|
|
558
|
+
);
|
|
559
|
+
xhr.send(file);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
const res = await fetch(url, {
|
|
563
|
+
method: "PUT",
|
|
564
|
+
headers: { "Content-Type": contentType },
|
|
565
|
+
body: file
|
|
566
|
+
});
|
|
567
|
+
if (!res.ok) throw new NetworkError(`Upload OVH \xE9chou\xE9 (${res.status})`);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
416
571
|
// src/token.ts
|
|
417
572
|
var KEYS = {
|
|
418
573
|
access: "frenchbaas_access_token",
|
|
@@ -527,6 +682,7 @@ var FrenchBaasClient = class {
|
|
|
527
682
|
this._tokenStore = new TokenStore(config.storage ?? "memory");
|
|
528
683
|
this._http = new HttpClient(baseUrl, config.apiKey, this._tokenStore);
|
|
529
684
|
this.auth = new AuthModule(this._http, this._tokenStore, baseUrl, config.apiKey);
|
|
685
|
+
this.storage = new StorageModule(this._http);
|
|
530
686
|
}
|
|
531
687
|
/**
|
|
532
688
|
* Retourne un client pour une collection spécifique.
|
|
@@ -558,6 +714,7 @@ var FrenchBaasClient = class {
|
|
|
558
714
|
// Annotate the CommonJS export names for ESM import in node:
|
|
559
715
|
0 && (module.exports = {
|
|
560
716
|
AuthError,
|
|
717
|
+
ConflictError,
|
|
561
718
|
FrenchBaas,
|
|
562
719
|
FrenchBaasError,
|
|
563
720
|
NetworkError,
|
package/dist/index.d.mts
CHANGED
|
@@ -49,13 +49,20 @@ interface GetOptions {
|
|
|
49
49
|
perPage?: number;
|
|
50
50
|
/**
|
|
51
51
|
* Filtres sur les champs du document.
|
|
52
|
-
* - Champ string/number/boolean : égalité exacte
|
|
53
|
-
* - Champ references : l'UUID doit être présent dans le tableau
|
|
54
52
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* - Valeur unique (string) → égalité exacte ou contient (references/array)
|
|
54
|
+
* - Tableau string[] → OR : field = v1 OU v2 / contient v1 OU v2
|
|
55
|
+
* - { and: string[] } → AND : uniquement pour references/array — contient tous les éléments
|
|
56
|
+
*
|
|
57
|
+
* @example { status: 'active' }
|
|
58
|
+
* @example { status: ['active', 'pending'] }
|
|
59
|
+
* @example { category_ids: 'uuid1' }
|
|
60
|
+
* @example { category_ids: ['uuid1', 'uuid2'] }
|
|
61
|
+
* @example { category_ids: { and: ['uuid1', 'uuid2'] } }
|
|
57
62
|
*/
|
|
58
|
-
filter?: Record<string, string
|
|
63
|
+
filter?: Record<string, string | string[] | {
|
|
64
|
+
and: string[];
|
|
65
|
+
}>;
|
|
59
66
|
}
|
|
60
67
|
type FieldType = 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object' | 'datetime';
|
|
61
68
|
interface SchemaField {
|
|
@@ -70,6 +77,44 @@ interface CollectionSchema {
|
|
|
70
77
|
visibility: 'public' | 'authenticated' | 'private';
|
|
71
78
|
schema: SchemaField[];
|
|
72
79
|
}
|
|
80
|
+
interface StorageBucket {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
visibility: 'public' | 'private';
|
|
84
|
+
public: boolean;
|
|
85
|
+
created_at: string;
|
|
86
|
+
}
|
|
87
|
+
interface StorageObject {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
content_type: string;
|
|
91
|
+
size: number;
|
|
92
|
+
created_by: string | null;
|
|
93
|
+
created_at: string;
|
|
94
|
+
/** URL publique — présente uniquement si le bucket est public */
|
|
95
|
+
url?: string;
|
|
96
|
+
}
|
|
97
|
+
interface StorageListOptions {
|
|
98
|
+
/** Numéro de page (défaut: 1) */
|
|
99
|
+
page?: number;
|
|
100
|
+
/** Fichiers par page — max 100 (défaut: 50) */
|
|
101
|
+
perPage?: number;
|
|
102
|
+
/** Filtre les fichiers dont le nom commence par ce préfixe */
|
|
103
|
+
prefix?: string;
|
|
104
|
+
}
|
|
105
|
+
interface StorageListResult {
|
|
106
|
+
data: StorageObject[];
|
|
107
|
+
meta: PaginationMeta;
|
|
108
|
+
}
|
|
109
|
+
interface StorageUploadOptions {
|
|
110
|
+
/** Force le Content-Type (sinon déduit de file.type) */
|
|
111
|
+
contentType?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Callback appelé pendant le PUT vers OVH (0–100).
|
|
114
|
+
* Nécessite XMLHttpRequest (navigateur). Ignoré côté Node/SSR.
|
|
115
|
+
*/
|
|
116
|
+
onProgress?: (pct: number) => void;
|
|
117
|
+
}
|
|
73
118
|
interface OpenApiSpec {
|
|
74
119
|
openapi: string;
|
|
75
120
|
info: Record<string, unknown>;
|
|
@@ -134,7 +179,7 @@ declare class HttpClient {
|
|
|
134
179
|
constructor(_baseUrl: string, _apiKey: string, _tokenStore: TokenStore);
|
|
135
180
|
/** Enregistre la fonction de refresh (injectée par AuthModule). */
|
|
136
181
|
setRefreshFn(fn: RefreshFn): void;
|
|
137
|
-
get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
|
|
182
|
+
get<T>(path: string, params?: Record<string, string | number | string[] | undefined>): Promise<T>;
|
|
138
183
|
post<T>(path: string, body?: unknown): Promise<T>;
|
|
139
184
|
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
140
185
|
delete<T>(path: string): Promise<T>;
|
|
@@ -276,6 +321,93 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
276
321
|
schema(): Promise<CollectionSchema>;
|
|
277
322
|
}
|
|
278
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Module storage — découverte des buckets et accès aux opérations par bucket.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* // Lister les buckets (pas de Bearer requis)
|
|
330
|
+
* const buckets = await client.storage.listBuckets()
|
|
331
|
+
*
|
|
332
|
+
* // Opérations sur un bucket spécifique
|
|
333
|
+
* const bucket = client.storage.bucket('bucket-uuid')
|
|
334
|
+
* const obj = await bucket.upload(file, 'photo.jpg')
|
|
335
|
+
* const list = await bucket.listObjects({ prefix: 'users/123/' })
|
|
336
|
+
* const { url } = await bucket.getUrl(obj.id)
|
|
337
|
+
* await bucket.delete(obj.id)
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
declare class StorageModule {
|
|
341
|
+
private readonly _http;
|
|
342
|
+
constructor(_http: HttpClient);
|
|
343
|
+
/**
|
|
344
|
+
* Liste tous les buckets du projet.
|
|
345
|
+
*
|
|
346
|
+
* Authentification par API key uniquement — pas de Bearer requis.
|
|
347
|
+
* Usage principal : découverte des IDs pendant la construction de l'app.
|
|
348
|
+
*/
|
|
349
|
+
listBuckets(): Promise<StorageBucket[]>;
|
|
350
|
+
/**
|
|
351
|
+
* Retourne un client pour opérer sur un bucket spécifique.
|
|
352
|
+
*
|
|
353
|
+
* @param bucketId UUID du bucket (visible dans Dashboard → Storage)
|
|
354
|
+
*/
|
|
355
|
+
bucket(bucketId: string): StorageBucketClient;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Client pour les opérations sur un bucket spécifique.
|
|
359
|
+
*/
|
|
360
|
+
declare class StorageBucketClient {
|
|
361
|
+
private readonly _bucketId;
|
|
362
|
+
private readonly _http;
|
|
363
|
+
constructor(_bucketId: string, _http: HttpClient);
|
|
364
|
+
/**
|
|
365
|
+
* Liste les fichiers du bucket avec pagination et filtre préfixe.
|
|
366
|
+
*
|
|
367
|
+
* - Bucket **public** : Bearer non requis — retourne tous les fichiers.
|
|
368
|
+
* - Bucket **privé** : Bearer requis — retourne uniquement les fichiers du user connecté.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* const { data, meta } = await bucket.listObjects({ prefix: 'users/123/', perPage: 20 })
|
|
372
|
+
*/
|
|
373
|
+
listObjects(options?: StorageListOptions): Promise<StorageListResult>;
|
|
374
|
+
/**
|
|
375
|
+
* Upload un fichier en 3 étapes : présignature → PUT direct vers OVH → confirmation.
|
|
376
|
+
*
|
|
377
|
+
* Bearer requis. La progression PUT (0–100 %) peut être suivie via `options.onProgress`.
|
|
378
|
+
*
|
|
379
|
+
* @param file Fichier ou Blob à uploader
|
|
380
|
+
* @param name Nom dans le bucket (ex: "photo.jpg", "users/123/avatar.jpg")
|
|
381
|
+
* @param options contentType (override), onProgress
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* const obj = await bucket.upload(file, 'avatar.jpg', {
|
|
385
|
+
* onProgress: pct => console.log(`${pct}%`)
|
|
386
|
+
* })
|
|
387
|
+
*/
|
|
388
|
+
upload(file: File | Blob, name: string, options?: StorageUploadOptions): Promise<StorageObject>;
|
|
389
|
+
/**
|
|
390
|
+
* Retourne l'URL d'accès à un fichier.
|
|
391
|
+
*
|
|
392
|
+
* - Bucket **public** : URL publique permanente, Bearer non requis.
|
|
393
|
+
* - Bucket **privé** : URL signée (TTL 3600 s), Bearer requis, créateur uniquement.
|
|
394
|
+
*/
|
|
395
|
+
getUrl(objectId: string): Promise<{
|
|
396
|
+
url: string;
|
|
397
|
+
expires_in?: number;
|
|
398
|
+
}>;
|
|
399
|
+
/**
|
|
400
|
+
* Supprime un fichier du bucket.
|
|
401
|
+
* Bearer requis. Seul le créateur du fichier peut le supprimer.
|
|
402
|
+
*/
|
|
403
|
+
delete(objectId: string): Promise<void>;
|
|
404
|
+
/**
|
|
405
|
+
* PUT direct vers l'URL présignée OVH.
|
|
406
|
+
* Utilise XHR (si disponible) pour le suivi de progression, sinon fetch.
|
|
407
|
+
*/
|
|
408
|
+
private _putToOvh;
|
|
409
|
+
}
|
|
410
|
+
|
|
279
411
|
/**
|
|
280
412
|
* Client principal FrenchBaas.
|
|
281
413
|
*
|
|
@@ -296,6 +428,8 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
296
428
|
declare class FrenchBaasClient {
|
|
297
429
|
/** Module d'authentification — signUp, login, logout, getUser */
|
|
298
430
|
readonly auth: AuthModule;
|
|
431
|
+
/** Module storage — listBuckets, bucket(id).upload / listObjects / getUrl / delete */
|
|
432
|
+
readonly storage: StorageModule;
|
|
299
433
|
private readonly _http;
|
|
300
434
|
private readonly _tokenStore;
|
|
301
435
|
constructor(config: FrenchBaasConfig);
|
|
@@ -383,5 +517,13 @@ declare class RateLimitError extends FrenchBaasError {
|
|
|
383
517
|
declare class ServerError extends FrenchBaasError {
|
|
384
518
|
constructor(message?: string, status?: number);
|
|
385
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Conflit de ressource — 409.
|
|
522
|
+
* Levée sur storage : un fichier avec ce nom existe déjà dans le bucket,
|
|
523
|
+
* déposé par un autre utilisateur.
|
|
524
|
+
*/
|
|
525
|
+
declare class ConflictError extends FrenchBaasError {
|
|
526
|
+
constructor(message?: string);
|
|
527
|
+
}
|
|
386
528
|
|
|
387
|
-
export { AuthError, type AuthResult, type CollectionSchema, type Document, type DocumentsPage, type FieldType, FrenchBaasClient as FrenchBaas, type FrenchBaasConfig, FrenchBaasError, type GetOptions, NetworkError, NotFoundError, type OpenApiSpec, type PaginationMeta, QuotaError, RateLimitError, type RefreshResult, type SchemaField, ServerError, type User, ValidationError };
|
|
529
|
+
export { AuthError, type AuthResult, type CollectionSchema, ConflictError, type Document, type DocumentsPage, type FieldType, FrenchBaasClient as FrenchBaas, type FrenchBaasConfig, FrenchBaasError, type GetOptions, NetworkError, NotFoundError, type OpenApiSpec, type PaginationMeta, QuotaError, RateLimitError, type RefreshResult, type SchemaField, ServerError, type StorageBucket, type StorageListOptions, type StorageListResult, type StorageObject, type StorageUploadOptions, type User, ValidationError };
|
package/dist/index.d.ts
CHANGED
|
@@ -49,13 +49,20 @@ interface GetOptions {
|
|
|
49
49
|
perPage?: number;
|
|
50
50
|
/**
|
|
51
51
|
* Filtres sur les champs du document.
|
|
52
|
-
* - Champ string/number/boolean : égalité exacte
|
|
53
|
-
* - Champ references : l'UUID doit être présent dans le tableau
|
|
54
52
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* - Valeur unique (string) → égalité exacte ou contient (references/array)
|
|
54
|
+
* - Tableau string[] → OR : field = v1 OU v2 / contient v1 OU v2
|
|
55
|
+
* - { and: string[] } → AND : uniquement pour references/array — contient tous les éléments
|
|
56
|
+
*
|
|
57
|
+
* @example { status: 'active' }
|
|
58
|
+
* @example { status: ['active', 'pending'] }
|
|
59
|
+
* @example { category_ids: 'uuid1' }
|
|
60
|
+
* @example { category_ids: ['uuid1', 'uuid2'] }
|
|
61
|
+
* @example { category_ids: { and: ['uuid1', 'uuid2'] } }
|
|
57
62
|
*/
|
|
58
|
-
filter?: Record<string, string
|
|
63
|
+
filter?: Record<string, string | string[] | {
|
|
64
|
+
and: string[];
|
|
65
|
+
}>;
|
|
59
66
|
}
|
|
60
67
|
type FieldType = 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object' | 'datetime';
|
|
61
68
|
interface SchemaField {
|
|
@@ -70,6 +77,44 @@ interface CollectionSchema {
|
|
|
70
77
|
visibility: 'public' | 'authenticated' | 'private';
|
|
71
78
|
schema: SchemaField[];
|
|
72
79
|
}
|
|
80
|
+
interface StorageBucket {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
visibility: 'public' | 'private';
|
|
84
|
+
public: boolean;
|
|
85
|
+
created_at: string;
|
|
86
|
+
}
|
|
87
|
+
interface StorageObject {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
content_type: string;
|
|
91
|
+
size: number;
|
|
92
|
+
created_by: string | null;
|
|
93
|
+
created_at: string;
|
|
94
|
+
/** URL publique — présente uniquement si le bucket est public */
|
|
95
|
+
url?: string;
|
|
96
|
+
}
|
|
97
|
+
interface StorageListOptions {
|
|
98
|
+
/** Numéro de page (défaut: 1) */
|
|
99
|
+
page?: number;
|
|
100
|
+
/** Fichiers par page — max 100 (défaut: 50) */
|
|
101
|
+
perPage?: number;
|
|
102
|
+
/** Filtre les fichiers dont le nom commence par ce préfixe */
|
|
103
|
+
prefix?: string;
|
|
104
|
+
}
|
|
105
|
+
interface StorageListResult {
|
|
106
|
+
data: StorageObject[];
|
|
107
|
+
meta: PaginationMeta;
|
|
108
|
+
}
|
|
109
|
+
interface StorageUploadOptions {
|
|
110
|
+
/** Force le Content-Type (sinon déduit de file.type) */
|
|
111
|
+
contentType?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Callback appelé pendant le PUT vers OVH (0–100).
|
|
114
|
+
* Nécessite XMLHttpRequest (navigateur). Ignoré côté Node/SSR.
|
|
115
|
+
*/
|
|
116
|
+
onProgress?: (pct: number) => void;
|
|
117
|
+
}
|
|
73
118
|
interface OpenApiSpec {
|
|
74
119
|
openapi: string;
|
|
75
120
|
info: Record<string, unknown>;
|
|
@@ -134,7 +179,7 @@ declare class HttpClient {
|
|
|
134
179
|
constructor(_baseUrl: string, _apiKey: string, _tokenStore: TokenStore);
|
|
135
180
|
/** Enregistre la fonction de refresh (injectée par AuthModule). */
|
|
136
181
|
setRefreshFn(fn: RefreshFn): void;
|
|
137
|
-
get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
|
|
182
|
+
get<T>(path: string, params?: Record<string, string | number | string[] | undefined>): Promise<T>;
|
|
138
183
|
post<T>(path: string, body?: unknown): Promise<T>;
|
|
139
184
|
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
140
185
|
delete<T>(path: string): Promise<T>;
|
|
@@ -276,6 +321,93 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
276
321
|
schema(): Promise<CollectionSchema>;
|
|
277
322
|
}
|
|
278
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Module storage — découverte des buckets et accès aux opérations par bucket.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* // Lister les buckets (pas de Bearer requis)
|
|
330
|
+
* const buckets = await client.storage.listBuckets()
|
|
331
|
+
*
|
|
332
|
+
* // Opérations sur un bucket spécifique
|
|
333
|
+
* const bucket = client.storage.bucket('bucket-uuid')
|
|
334
|
+
* const obj = await bucket.upload(file, 'photo.jpg')
|
|
335
|
+
* const list = await bucket.listObjects({ prefix: 'users/123/' })
|
|
336
|
+
* const { url } = await bucket.getUrl(obj.id)
|
|
337
|
+
* await bucket.delete(obj.id)
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
declare class StorageModule {
|
|
341
|
+
private readonly _http;
|
|
342
|
+
constructor(_http: HttpClient);
|
|
343
|
+
/**
|
|
344
|
+
* Liste tous les buckets du projet.
|
|
345
|
+
*
|
|
346
|
+
* Authentification par API key uniquement — pas de Bearer requis.
|
|
347
|
+
* Usage principal : découverte des IDs pendant la construction de l'app.
|
|
348
|
+
*/
|
|
349
|
+
listBuckets(): Promise<StorageBucket[]>;
|
|
350
|
+
/**
|
|
351
|
+
* Retourne un client pour opérer sur un bucket spécifique.
|
|
352
|
+
*
|
|
353
|
+
* @param bucketId UUID du bucket (visible dans Dashboard → Storage)
|
|
354
|
+
*/
|
|
355
|
+
bucket(bucketId: string): StorageBucketClient;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Client pour les opérations sur un bucket spécifique.
|
|
359
|
+
*/
|
|
360
|
+
declare class StorageBucketClient {
|
|
361
|
+
private readonly _bucketId;
|
|
362
|
+
private readonly _http;
|
|
363
|
+
constructor(_bucketId: string, _http: HttpClient);
|
|
364
|
+
/**
|
|
365
|
+
* Liste les fichiers du bucket avec pagination et filtre préfixe.
|
|
366
|
+
*
|
|
367
|
+
* - Bucket **public** : Bearer non requis — retourne tous les fichiers.
|
|
368
|
+
* - Bucket **privé** : Bearer requis — retourne uniquement les fichiers du user connecté.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* const { data, meta } = await bucket.listObjects({ prefix: 'users/123/', perPage: 20 })
|
|
372
|
+
*/
|
|
373
|
+
listObjects(options?: StorageListOptions): Promise<StorageListResult>;
|
|
374
|
+
/**
|
|
375
|
+
* Upload un fichier en 3 étapes : présignature → PUT direct vers OVH → confirmation.
|
|
376
|
+
*
|
|
377
|
+
* Bearer requis. La progression PUT (0–100 %) peut être suivie via `options.onProgress`.
|
|
378
|
+
*
|
|
379
|
+
* @param file Fichier ou Blob à uploader
|
|
380
|
+
* @param name Nom dans le bucket (ex: "photo.jpg", "users/123/avatar.jpg")
|
|
381
|
+
* @param options contentType (override), onProgress
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* const obj = await bucket.upload(file, 'avatar.jpg', {
|
|
385
|
+
* onProgress: pct => console.log(`${pct}%`)
|
|
386
|
+
* })
|
|
387
|
+
*/
|
|
388
|
+
upload(file: File | Blob, name: string, options?: StorageUploadOptions): Promise<StorageObject>;
|
|
389
|
+
/**
|
|
390
|
+
* Retourne l'URL d'accès à un fichier.
|
|
391
|
+
*
|
|
392
|
+
* - Bucket **public** : URL publique permanente, Bearer non requis.
|
|
393
|
+
* - Bucket **privé** : URL signée (TTL 3600 s), Bearer requis, créateur uniquement.
|
|
394
|
+
*/
|
|
395
|
+
getUrl(objectId: string): Promise<{
|
|
396
|
+
url: string;
|
|
397
|
+
expires_in?: number;
|
|
398
|
+
}>;
|
|
399
|
+
/**
|
|
400
|
+
* Supprime un fichier du bucket.
|
|
401
|
+
* Bearer requis. Seul le créateur du fichier peut le supprimer.
|
|
402
|
+
*/
|
|
403
|
+
delete(objectId: string): Promise<void>;
|
|
404
|
+
/**
|
|
405
|
+
* PUT direct vers l'URL présignée OVH.
|
|
406
|
+
* Utilise XHR (si disponible) pour le suivi de progression, sinon fetch.
|
|
407
|
+
*/
|
|
408
|
+
private _putToOvh;
|
|
409
|
+
}
|
|
410
|
+
|
|
279
411
|
/**
|
|
280
412
|
* Client principal FrenchBaas.
|
|
281
413
|
*
|
|
@@ -296,6 +428,8 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
296
428
|
declare class FrenchBaasClient {
|
|
297
429
|
/** Module d'authentification — signUp, login, logout, getUser */
|
|
298
430
|
readonly auth: AuthModule;
|
|
431
|
+
/** Module storage — listBuckets, bucket(id).upload / listObjects / getUrl / delete */
|
|
432
|
+
readonly storage: StorageModule;
|
|
299
433
|
private readonly _http;
|
|
300
434
|
private readonly _tokenStore;
|
|
301
435
|
constructor(config: FrenchBaasConfig);
|
|
@@ -383,5 +517,13 @@ declare class RateLimitError extends FrenchBaasError {
|
|
|
383
517
|
declare class ServerError extends FrenchBaasError {
|
|
384
518
|
constructor(message?: string, status?: number);
|
|
385
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Conflit de ressource — 409.
|
|
522
|
+
* Levée sur storage : un fichier avec ce nom existe déjà dans le bucket,
|
|
523
|
+
* déposé par un autre utilisateur.
|
|
524
|
+
*/
|
|
525
|
+
declare class ConflictError extends FrenchBaasError {
|
|
526
|
+
constructor(message?: string);
|
|
527
|
+
}
|
|
386
528
|
|
|
387
|
-
export { AuthError, type AuthResult, type CollectionSchema, type Document, type DocumentsPage, type FieldType, FrenchBaasClient as FrenchBaas, type FrenchBaasConfig, FrenchBaasError, type GetOptions, NetworkError, NotFoundError, type OpenApiSpec, type PaginationMeta, QuotaError, RateLimitError, type RefreshResult, type SchemaField, ServerError, type User, ValidationError };
|
|
529
|
+
export { AuthError, type AuthResult, type CollectionSchema, ConflictError, type Document, type DocumentsPage, type FieldType, FrenchBaasClient as FrenchBaas, type FrenchBaasConfig, FrenchBaasError, type GetOptions, NetworkError, NotFoundError, type OpenApiSpec, type PaginationMeta, QuotaError, RateLimitError, type RefreshResult, type SchemaField, ServerError, type StorageBucket, type StorageListOptions, type StorageListResult, type StorageObject, type StorageUploadOptions, type User, ValidationError };
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,13 @@ var ServerError = class extends FrenchBaasError {
|
|
|
57
57
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
+
var ConflictError = class extends FrenchBaasError {
|
|
61
|
+
constructor(message = "Conflit : la ressource existe d\xE9j\xE0.") {
|
|
62
|
+
super(message, 409);
|
|
63
|
+
this.name = "ConflictError";
|
|
64
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
60
67
|
|
|
61
68
|
// src/auth.ts
|
|
62
69
|
var AuthModule = class {
|
|
@@ -177,7 +184,14 @@ var CollectionClient = class {
|
|
|
177
184
|
};
|
|
178
185
|
if (options.filter) {
|
|
179
186
|
for (const [key, val] of Object.entries(options.filter)) {
|
|
180
|
-
if (val
|
|
187
|
+
if (val === void 0) continue;
|
|
188
|
+
if (typeof val === "string") {
|
|
189
|
+
params[`filter[${key}]`] = val;
|
|
190
|
+
} else if (Array.isArray(val)) {
|
|
191
|
+
params[`filter[${key}][]`] = val;
|
|
192
|
+
} else {
|
|
193
|
+
params[`filter[${key}][and][]`] = val.and;
|
|
194
|
+
}
|
|
181
195
|
}
|
|
182
196
|
}
|
|
183
197
|
const res = await this._http.get(
|
|
@@ -280,7 +294,12 @@ var HttpClient = class {
|
|
|
280
294
|
const url = new URL(this._baseUrl + path);
|
|
281
295
|
if (params) {
|
|
282
296
|
for (const [k, v] of Object.entries(params)) {
|
|
283
|
-
if (v
|
|
297
|
+
if (v === void 0) continue;
|
|
298
|
+
if (Array.isArray(v)) {
|
|
299
|
+
for (const item of v) url.searchParams.append(k, item);
|
|
300
|
+
} else {
|
|
301
|
+
url.searchParams.set(k, String(v));
|
|
302
|
+
}
|
|
284
303
|
}
|
|
285
304
|
}
|
|
286
305
|
return this._request("GET", url.toString(), void 0, true);
|
|
@@ -370,6 +389,8 @@ var HttpClient = class {
|
|
|
370
389
|
const details = err.errors ?? [];
|
|
371
390
|
throw new ValidationError(details.length ? details.join(", ") : msg, details);
|
|
372
391
|
}
|
|
392
|
+
case 409:
|
|
393
|
+
throw new ConflictError(msg);
|
|
373
394
|
case 429:
|
|
374
395
|
throw new RateLimitError();
|
|
375
396
|
default:
|
|
@@ -379,6 +400,139 @@ var HttpClient = class {
|
|
|
379
400
|
}
|
|
380
401
|
};
|
|
381
402
|
|
|
403
|
+
// src/storage.ts
|
|
404
|
+
var StorageModule = class {
|
|
405
|
+
constructor(_http) {
|
|
406
|
+
this._http = _http;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Liste tous les buckets du projet.
|
|
410
|
+
*
|
|
411
|
+
* Authentification par API key uniquement — pas de Bearer requis.
|
|
412
|
+
* Usage principal : découverte des IDs pendant la construction de l'app.
|
|
413
|
+
*/
|
|
414
|
+
async listBuckets() {
|
|
415
|
+
const res = await this._http.get(
|
|
416
|
+
"/sdk/storage/buckets"
|
|
417
|
+
);
|
|
418
|
+
return res.data;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Retourne un client pour opérer sur un bucket spécifique.
|
|
422
|
+
*
|
|
423
|
+
* @param bucketId UUID du bucket (visible dans Dashboard → Storage)
|
|
424
|
+
*/
|
|
425
|
+
bucket(bucketId) {
|
|
426
|
+
if (!bucketId || typeof bucketId !== "string") {
|
|
427
|
+
throw new Error("[FrenchBaas] storage.bucket() requiert un UUID de bucket valide.");
|
|
428
|
+
}
|
|
429
|
+
return new StorageBucketClient(bucketId, this._http);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
var StorageBucketClient = class {
|
|
433
|
+
constructor(_bucketId, _http) {
|
|
434
|
+
this._bucketId = _bucketId;
|
|
435
|
+
this._http = _http;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Liste les fichiers du bucket avec pagination et filtre préfixe.
|
|
439
|
+
*
|
|
440
|
+
* - Bucket **public** : Bearer non requis — retourne tous les fichiers.
|
|
441
|
+
* - Bucket **privé** : Bearer requis — retourne uniquement les fichiers du user connecté.
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* const { data, meta } = await bucket.listObjects({ prefix: 'users/123/', perPage: 20 })
|
|
445
|
+
*/
|
|
446
|
+
async listObjects(options = {}) {
|
|
447
|
+
const params = {};
|
|
448
|
+
if (options.page !== void 0) params["page"] = options.page;
|
|
449
|
+
if (options.perPage !== void 0) params["per_page"] = options.perPage;
|
|
450
|
+
if (options.prefix) params["prefix"] = options.prefix;
|
|
451
|
+
return this._http.get(
|
|
452
|
+
`/sdk/storage/buckets/${this._bucketId}/objects`,
|
|
453
|
+
params
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Upload un fichier en 3 étapes : présignature → PUT direct vers OVH → confirmation.
|
|
458
|
+
*
|
|
459
|
+
* Bearer requis. La progression PUT (0–100 %) peut être suivie via `options.onProgress`.
|
|
460
|
+
*
|
|
461
|
+
* @param file Fichier ou Blob à uploader
|
|
462
|
+
* @param name Nom dans le bucket (ex: "photo.jpg", "users/123/avatar.jpg")
|
|
463
|
+
* @param options contentType (override), onProgress
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* const obj = await bucket.upload(file, 'avatar.jpg', {
|
|
467
|
+
* onProgress: pct => console.log(`${pct}%`)
|
|
468
|
+
* })
|
|
469
|
+
*/
|
|
470
|
+
async upload(file, name, options = {}) {
|
|
471
|
+
const contentType = options.contentType ?? ((file instanceof File ? file.type : "") || "application/octet-stream");
|
|
472
|
+
const presign = await this._http.post(`/sdk/storage/buckets/${this._bucketId}/upload`, {
|
|
473
|
+
name,
|
|
474
|
+
content_type: contentType,
|
|
475
|
+
size: file.size
|
|
476
|
+
});
|
|
477
|
+
await this._putToOvh(presign.upload_url, file, contentType, options.onProgress);
|
|
478
|
+
const confirm = await this._http.post(
|
|
479
|
+
`/sdk/storage/buckets/${this._bucketId}/confirm`,
|
|
480
|
+
{ name, key: presign.key, content_type: contentType }
|
|
481
|
+
);
|
|
482
|
+
return confirm.data;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Retourne l'URL d'accès à un fichier.
|
|
486
|
+
*
|
|
487
|
+
* - Bucket **public** : URL publique permanente, Bearer non requis.
|
|
488
|
+
* - Bucket **privé** : URL signée (TTL 3600 s), Bearer requis, créateur uniquement.
|
|
489
|
+
*/
|
|
490
|
+
async getUrl(objectId) {
|
|
491
|
+
return this._http.get(
|
|
492
|
+
`/sdk/storage/buckets/${this._bucketId}/objects/${objectId}/url`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Supprime un fichier du bucket.
|
|
497
|
+
* Bearer requis. Seul le créateur du fichier peut le supprimer.
|
|
498
|
+
*/
|
|
499
|
+
async delete(objectId) {
|
|
500
|
+
await this._http.delete(`/sdk/storage/buckets/${this._bucketId}/objects/${objectId}`);
|
|
501
|
+
}
|
|
502
|
+
// ── Private ──────────────────────────────────────────────────────────────
|
|
503
|
+
/**
|
|
504
|
+
* PUT direct vers l'URL présignée OVH.
|
|
505
|
+
* Utilise XHR (si disponible) pour le suivi de progression, sinon fetch.
|
|
506
|
+
*/
|
|
507
|
+
async _putToOvh(url, file, contentType, onProgress) {
|
|
508
|
+
if (typeof onProgress === "function" && typeof XMLHttpRequest !== "undefined") {
|
|
509
|
+
return new Promise((resolve, reject) => {
|
|
510
|
+
const xhr = new XMLHttpRequest();
|
|
511
|
+
xhr.open("PUT", url);
|
|
512
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
513
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
514
|
+
if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100));
|
|
515
|
+
});
|
|
516
|
+
xhr.addEventListener("load", () => {
|
|
517
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve();
|
|
518
|
+
else reject(new NetworkError(`Upload OVH \xE9chou\xE9 (${xhr.status})`));
|
|
519
|
+
});
|
|
520
|
+
xhr.addEventListener(
|
|
521
|
+
"error",
|
|
522
|
+
() => reject(new NetworkError("Erreur r\xE9seau lors de l'upload OVH."))
|
|
523
|
+
);
|
|
524
|
+
xhr.send(file);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
const res = await fetch(url, {
|
|
528
|
+
method: "PUT",
|
|
529
|
+
headers: { "Content-Type": contentType },
|
|
530
|
+
body: file
|
|
531
|
+
});
|
|
532
|
+
if (!res.ok) throw new NetworkError(`Upload OVH \xE9chou\xE9 (${res.status})`);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
382
536
|
// src/token.ts
|
|
383
537
|
var KEYS = {
|
|
384
538
|
access: "frenchbaas_access_token",
|
|
@@ -493,6 +647,7 @@ var FrenchBaasClient = class {
|
|
|
493
647
|
this._tokenStore = new TokenStore(config.storage ?? "memory");
|
|
494
648
|
this._http = new HttpClient(baseUrl, config.apiKey, this._tokenStore);
|
|
495
649
|
this.auth = new AuthModule(this._http, this._tokenStore, baseUrl, config.apiKey);
|
|
650
|
+
this.storage = new StorageModule(this._http);
|
|
496
651
|
}
|
|
497
652
|
/**
|
|
498
653
|
* Retourne un client pour une collection spécifique.
|
|
@@ -523,6 +678,7 @@ var FrenchBaasClient = class {
|
|
|
523
678
|
};
|
|
524
679
|
export {
|
|
525
680
|
AuthError,
|
|
681
|
+
ConflictError,
|
|
526
682
|
FrenchBaasClient as FrenchBaas,
|
|
527
683
|
FrenchBaasError,
|
|
528
684
|
NetworkError,
|