@frenchbaas/js 0.3.1 → 0.4.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 +83 -0
- package/dist/index.cjs +145 -0
- package/dist/index.d.mts +136 -1
- package/dist/index.d.ts +136 -1
- package/dist/index.js +144 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,6 +119,87 @@ Ces permissions se configurent dans le Dashboard → collection → schéma →
|
|
|
119
119
|
|
|
120
120
|
---
|
|
121
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
|
+
### Bucket privé — stocker et retrouver un fichier
|
|
155
|
+
|
|
156
|
+
Avec un bucket privé, l'URL signée expire après 1h. Il ne faut **pas** stocker l'URL — il faut stocker l'`id` de l'objet retourné par `upload()`, puis appeler `getUrl()` à chaque affichage.
|
|
157
|
+
|
|
158
|
+
**Workflow recommandé :**
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
// 1. Upload → récupérer l'id de l'objet
|
|
162
|
+
const obj = await bucket.upload(file, 'photo.jpg')
|
|
163
|
+
const objectId = obj.id // ex: "a1b2c3d4-..."
|
|
164
|
+
|
|
165
|
+
// 2. Stocker l'id dans une collection FrenchBaas (pas l'URL)
|
|
166
|
+
await client.collection('profils').update(userId, {
|
|
167
|
+
avatar_storage_id: objectId,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// 3. À l'affichage : récupérer l'id depuis la collection, puis générer l'URL
|
|
171
|
+
const profil = await client.collection('profils').getById(userId)
|
|
172
|
+
const storedId = profil.data.avatar_storage_id
|
|
173
|
+
|
|
174
|
+
const { url } = await bucket.getUrl(storedId)
|
|
175
|
+
// url → URL signée fraîche, valide 1h
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
> ⚠️ Ne stockez jamais l'URL signée — elle expire. Stockez toujours l'`id` de l'objet.
|
|
179
|
+
|
|
180
|
+
### Visibilité des buckets
|
|
181
|
+
|
|
182
|
+
| Visibilité | listObjects | upload / delete | getUrl |
|
|
183
|
+
|------------|-------------|-----------------|--------|
|
|
184
|
+
| `public` | Sans Bearer | Bearer requis | Sans Bearer (URL permanente) |
|
|
185
|
+
| `private` | Bearer requis (ses fichiers) | Bearer requis | Bearer requis (URL signée 1h) |
|
|
186
|
+
|
|
187
|
+
### Erreurs Storage
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { ConflictError, ValidationError, QuotaError } from '@frenchbaas/js'
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await bucket.upload(file, 'photo.jpg')
|
|
194
|
+
} catch (e) {
|
|
195
|
+
if (e instanceof ConflictError) console.error('Nom déjà pris par un autre utilisateur')
|
|
196
|
+
if (e instanceof ValidationError) console.error('Fichier trop grand ou type non autorisé')
|
|
197
|
+
if (e instanceof QuotaError) console.error('Quota storage dépassé')
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
122
203
|
## Webhooks
|
|
123
204
|
|
|
124
205
|
Si une collection a un `before_create_url` ou `before_update_url` configuré,
|
|
@@ -162,6 +243,7 @@ try {
|
|
|
162
243
|
import {
|
|
163
244
|
FrenchBaasError,
|
|
164
245
|
AuthError,
|
|
246
|
+
ConflictError,
|
|
165
247
|
ValidationError,
|
|
166
248
|
NotFoundError,
|
|
167
249
|
QuotaError,
|
|
@@ -180,6 +262,7 @@ try {
|
|
|
180
262
|
console.error(e.message, e.errors)
|
|
181
263
|
}
|
|
182
264
|
else if (e instanceof AuthError) console.error('Non connecté ou session expirée')
|
|
265
|
+
else if (e instanceof ConflictError) console.error('Conflit : ressource déjà existante')
|
|
183
266
|
else if (e instanceof NotFoundError) console.error('Document ou collection introuvable')
|
|
184
267
|
else if (e instanceof QuotaError) console.error('Quota dépassé :', e.message)
|
|
185
268
|
else if (e instanceof RateLimitError) console.error('Trop de requêtes, réessayez dans quelques instants')
|
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 {
|
|
@@ -416,6 +424,8 @@ var HttpClient = class {
|
|
|
416
424
|
const details = err.errors ?? [];
|
|
417
425
|
throw new ValidationError(details.length ? details.join(", ") : msg, details);
|
|
418
426
|
}
|
|
427
|
+
case 409:
|
|
428
|
+
throw new ConflictError(msg);
|
|
419
429
|
case 429:
|
|
420
430
|
throw new RateLimitError();
|
|
421
431
|
default:
|
|
@@ -425,6 +435,139 @@ var HttpClient = class {
|
|
|
425
435
|
}
|
|
426
436
|
};
|
|
427
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
|
+
|
|
428
571
|
// src/token.ts
|
|
429
572
|
var KEYS = {
|
|
430
573
|
access: "frenchbaas_access_token",
|
|
@@ -539,6 +682,7 @@ var FrenchBaasClient = class {
|
|
|
539
682
|
this._tokenStore = new TokenStore(config.storage ?? "memory");
|
|
540
683
|
this._http = new HttpClient(baseUrl, config.apiKey, this._tokenStore);
|
|
541
684
|
this.auth = new AuthModule(this._http, this._tokenStore, baseUrl, config.apiKey);
|
|
685
|
+
this.storage = new StorageModule(this._http);
|
|
542
686
|
}
|
|
543
687
|
/**
|
|
544
688
|
* Retourne un client pour une collection spécifique.
|
|
@@ -570,6 +714,7 @@ var FrenchBaasClient = class {
|
|
|
570
714
|
// Annotate the CommonJS export names for ESM import in node:
|
|
571
715
|
0 && (module.exports = {
|
|
572
716
|
AuthError,
|
|
717
|
+
ConflictError,
|
|
573
718
|
FrenchBaas,
|
|
574
719
|
FrenchBaasError,
|
|
575
720
|
NetworkError,
|
package/dist/index.d.mts
CHANGED
|
@@ -77,6 +77,44 @@ interface CollectionSchema {
|
|
|
77
77
|
visibility: 'public' | 'authenticated' | 'private';
|
|
78
78
|
schema: SchemaField[];
|
|
79
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
|
+
}
|
|
80
118
|
interface OpenApiSpec {
|
|
81
119
|
openapi: string;
|
|
82
120
|
info: Record<string, unknown>;
|
|
@@ -283,6 +321,93 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
283
321
|
schema(): Promise<CollectionSchema>;
|
|
284
322
|
}
|
|
285
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
|
+
|
|
286
411
|
/**
|
|
287
412
|
* Client principal FrenchBaas.
|
|
288
413
|
*
|
|
@@ -303,6 +428,8 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
303
428
|
declare class FrenchBaasClient {
|
|
304
429
|
/** Module d'authentification — signUp, login, logout, getUser */
|
|
305
430
|
readonly auth: AuthModule;
|
|
431
|
+
/** Module storage — listBuckets, bucket(id).upload / listObjects / getUrl / delete */
|
|
432
|
+
readonly storage: StorageModule;
|
|
306
433
|
private readonly _http;
|
|
307
434
|
private readonly _tokenStore;
|
|
308
435
|
constructor(config: FrenchBaasConfig);
|
|
@@ -390,5 +517,13 @@ declare class RateLimitError extends FrenchBaasError {
|
|
|
390
517
|
declare class ServerError extends FrenchBaasError {
|
|
391
518
|
constructor(message?: string, status?: number);
|
|
392
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
|
+
}
|
|
393
528
|
|
|
394
|
-
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
|
@@ -77,6 +77,44 @@ interface CollectionSchema {
|
|
|
77
77
|
visibility: 'public' | 'authenticated' | 'private';
|
|
78
78
|
schema: SchemaField[];
|
|
79
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
|
+
}
|
|
80
118
|
interface OpenApiSpec {
|
|
81
119
|
openapi: string;
|
|
82
120
|
info: Record<string, unknown>;
|
|
@@ -283,6 +321,93 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
283
321
|
schema(): Promise<CollectionSchema>;
|
|
284
322
|
}
|
|
285
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
|
+
|
|
286
411
|
/**
|
|
287
412
|
* Client principal FrenchBaas.
|
|
288
413
|
*
|
|
@@ -303,6 +428,8 @@ declare class CollectionClient<T = Record<string, unknown>> {
|
|
|
303
428
|
declare class FrenchBaasClient {
|
|
304
429
|
/** Module d'authentification — signUp, login, logout, getUser */
|
|
305
430
|
readonly auth: AuthModule;
|
|
431
|
+
/** Module storage — listBuckets, bucket(id).upload / listObjects / getUrl / delete */
|
|
432
|
+
readonly storage: StorageModule;
|
|
306
433
|
private readonly _http;
|
|
307
434
|
private readonly _tokenStore;
|
|
308
435
|
constructor(config: FrenchBaasConfig);
|
|
@@ -390,5 +517,13 @@ declare class RateLimitError extends FrenchBaasError {
|
|
|
390
517
|
declare class ServerError extends FrenchBaasError {
|
|
391
518
|
constructor(message?: string, status?: number);
|
|
392
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
|
+
}
|
|
393
528
|
|
|
394
|
-
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 {
|
|
@@ -382,6 +389,8 @@ var HttpClient = class {
|
|
|
382
389
|
const details = err.errors ?? [];
|
|
383
390
|
throw new ValidationError(details.length ? details.join(", ") : msg, details);
|
|
384
391
|
}
|
|
392
|
+
case 409:
|
|
393
|
+
throw new ConflictError(msg);
|
|
385
394
|
case 429:
|
|
386
395
|
throw new RateLimitError();
|
|
387
396
|
default:
|
|
@@ -391,6 +400,139 @@ var HttpClient = class {
|
|
|
391
400
|
}
|
|
392
401
|
};
|
|
393
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
|
+
|
|
394
536
|
// src/token.ts
|
|
395
537
|
var KEYS = {
|
|
396
538
|
access: "frenchbaas_access_token",
|
|
@@ -505,6 +647,7 @@ var FrenchBaasClient = class {
|
|
|
505
647
|
this._tokenStore = new TokenStore(config.storage ?? "memory");
|
|
506
648
|
this._http = new HttpClient(baseUrl, config.apiKey, this._tokenStore);
|
|
507
649
|
this.auth = new AuthModule(this._http, this._tokenStore, baseUrl, config.apiKey);
|
|
650
|
+
this.storage = new StorageModule(this._http);
|
|
508
651
|
}
|
|
509
652
|
/**
|
|
510
653
|
* Retourne un client pour une collection spécifique.
|
|
@@ -535,6 +678,7 @@ var FrenchBaasClient = class {
|
|
|
535
678
|
};
|
|
536
679
|
export {
|
|
537
680
|
AuthError,
|
|
681
|
+
ConflictError,
|
|
538
682
|
FrenchBaasClient as FrenchBaas,
|
|
539
683
|
FrenchBaasError,
|
|
540
684
|
NetworkError,
|