hydrousdb 2.0.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs DELETED
@@ -1,1038 +0,0 @@
1
- // src/utils/errors.ts
2
- var HydrousDBError = class extends Error {
3
- constructor(message, code = "SDK_ERROR", status) {
4
- super(message);
5
- this.name = "HydrousDBError";
6
- this.code = code;
7
- this.status = status;
8
- }
9
- };
10
- function toHydrousError(err) {
11
- if (err instanceof HydrousDBError) {
12
- return { message: err.message, code: err.code, status: err.status };
13
- }
14
- if (err instanceof Error) {
15
- return { message: err.message, code: "UNKNOWN_ERROR" };
16
- }
17
- return { message: String(err), code: "UNKNOWN_ERROR" };
18
- }
19
- function isHydrousError(err) {
20
- return err instanceof HydrousDBError;
21
- }
22
-
23
- // src/utils/http.ts
24
- async function parseResponse(res) {
25
- let body;
26
- try {
27
- body = await res.json();
28
- } catch (e) {
29
- if (!res.ok) throw new HydrousDBError(`HTTP ${res.status}`, "HTTP_ERROR", res.status);
30
- return void 0;
31
- }
32
- if (!res.ok) {
33
- const e = body;
34
- throw new HydrousDBError(
35
- e.error || e.message || `HTTP ${res.status}`,
36
- e.code || "HTTP_ERROR",
37
- res.status
38
- );
39
- }
40
- return body;
41
- }
42
- function buildUrl(base, path, params) {
43
- const url = new URL(path, base.endsWith("/") ? base : base + "/");
44
- if (params) {
45
- for (const [k, v] of Object.entries(params)) {
46
- if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
47
- }
48
- }
49
- return url.toString();
50
- }
51
- function mergeHeaders(a, b) {
52
- return { ...a, ...b };
53
- }
54
- async function readSSEStream(response, onEvent) {
55
- if (!response.body) return;
56
- const reader = response.body.getReader();
57
- const decoder = new TextDecoder();
58
- let buf = "";
59
- const flush = (chunk) => {
60
- var _a;
61
- buf += chunk;
62
- const blocks = buf.split("\n\n");
63
- buf = (_a = blocks.pop()) != null ? _a : "";
64
- for (const block of blocks) {
65
- if (!block.trim()) continue;
66
- let eventType = "message";
67
- let dataLine = null;
68
- for (const line of block.split("\n")) {
69
- if (line.startsWith("event:")) eventType = line.slice(6).trim();
70
- if (line.startsWith("data:")) dataLine = line.slice(5).trim();
71
- }
72
- if (dataLine === null) continue;
73
- try {
74
- onEvent(eventType, JSON.parse(dataLine));
75
- } catch (e) {
76
- }
77
- }
78
- };
79
- while (true) {
80
- const { done, value } = await reader.read();
81
- if (done) break;
82
- flush(decoder.decode(value, { stream: true }));
83
- }
84
- if (buf.trim()) flush("");
85
- }
86
- function parseSSEText(text, onEvent) {
87
- const blocks = text.split("\n\n");
88
- for (const block of blocks) {
89
- if (!block.trim()) continue;
90
- let eventType = "message";
91
- let dataLine = null;
92
- for (const line of block.split("\n")) {
93
- if (line.startsWith("event:")) eventType = line.slice(6).trim();
94
- if (line.startsWith("data:")) dataLine = line.slice(5).trim();
95
- }
96
- if (dataLine === null) continue;
97
- try {
98
- onEvent(eventType, JSON.parse(dataLine));
99
- } catch (e) {
100
- }
101
- }
102
- }
103
- function xhrUpload(url, body, headers, onProgress) {
104
- return new Promise((resolve, reject) => {
105
- const xhr = new XMLHttpRequest();
106
- xhr.open("POST", url);
107
- for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);
108
- xhr.responseType = "text";
109
- if (onProgress) {
110
- xhr.upload.onprogress = (e) => {
111
- if (e.lengthComputable) onProgress(e.loaded, e.total);
112
- };
113
- }
114
- xhr.onload = () => {
115
- var _a;
116
- if (xhr.status >= 200 && xhr.status < 300) {
117
- resolve(xhr.responseText);
118
- } else {
119
- try {
120
- const d = JSON.parse(xhr.responseText);
121
- reject(new HydrousDBError((_a = d.error) != null ? _a : `HTTP ${xhr.status}`, "HTTP_ERROR", xhr.status));
122
- } catch (e) {
123
- reject(new HydrousDBError(`HTTP ${xhr.status}`, "HTTP_ERROR", xhr.status));
124
- }
125
- }
126
- };
127
- xhr.onerror = () => reject(new HydrousDBError("Network error", "NETWORK_ERROR"));
128
- xhr.onabort = () => reject(new HydrousDBError("Upload aborted", "UPLOAD_ABORTED"));
129
- xhr.ontimeout = () => reject(new HydrousDBError("Upload timed out", "UPLOAD_TIMEOUT"));
130
- xhr.send(body);
131
- });
132
- }
133
-
134
- // src/auth/client.ts
135
- var AuthClient = class {
136
- constructor(config) {
137
- this.session = null;
138
- this.baseUrl = config.url;
139
- this.headers = {
140
- "Content-Type": "application/json",
141
- "Authorization": `Bearer ${config.authKey}`
142
- };
143
- }
144
- /** Create a new user account */
145
- async signUp(options) {
146
- try {
147
- const res = await fetch(buildUrl(this.baseUrl, "auth/signup"), {
148
- method: "POST",
149
- headers: this.headers,
150
- body: JSON.stringify(options)
151
- });
152
- const json = await parseResponse(res);
153
- this.session = json.data;
154
- return { data: json.data, error: null };
155
- } catch (err) {
156
- return { data: null, error: toHydrousError(err) };
157
- }
158
- }
159
- /** Sign in with email and password */
160
- async signIn(options) {
161
- try {
162
- const res = await fetch(buildUrl(this.baseUrl, "auth/signin"), {
163
- method: "POST",
164
- headers: this.headers,
165
- body: JSON.stringify(options)
166
- });
167
- const json = await parseResponse(res);
168
- this.session = json.data;
169
- return { data: json.data, error: null };
170
- } catch (err) {
171
- return { data: null, error: toHydrousError(err) };
172
- }
173
- }
174
- /** Sign out and invalidate the current session */
175
- async signOut() {
176
- try {
177
- const res = await fetch(buildUrl(this.baseUrl, "auth/signout"), {
178
- method: "POST",
179
- headers: mergeHeaders(this.headers, this._sessionHeader())
180
- });
181
- await parseResponse(res);
182
- this.session = null;
183
- return { data: void 0, error: null };
184
- } catch (err) {
185
- return { data: null, error: toHydrousError(err) };
186
- }
187
- }
188
- /** Get the currently authenticated user */
189
- async getUser() {
190
- try {
191
- const res = await fetch(buildUrl(this.baseUrl, "auth/user"), {
192
- headers: mergeHeaders(this.headers, this._sessionHeader())
193
- });
194
- const json = await parseResponse(res);
195
- return { data: json.data, error: null };
196
- } catch (err) {
197
- return { data: null, error: toHydrousError(err) };
198
- }
199
- }
200
- /** Refresh the access token using the stored refresh token */
201
- async refreshSession() {
202
- var _a;
203
- if (!((_a = this.session) == null ? void 0 : _a.refreshToken)) {
204
- return { data: null, error: { message: "No active session", code: "NO_SESSION" } };
205
- }
206
- try {
207
- const res = await fetch(buildUrl(this.baseUrl, "auth/refresh"), {
208
- method: "POST",
209
- headers: this.headers,
210
- body: JSON.stringify({ refreshToken: this.session.refreshToken })
211
- });
212
- const json = await parseResponse(res);
213
- this.session = json.data;
214
- return { data: json.data, error: null };
215
- } catch (err) {
216
- return { data: null, error: toHydrousError(err) };
217
- }
218
- }
219
- /** Return the current in-memory session (may be null) */
220
- getSession() {
221
- return this.session;
222
- }
223
- _sessionHeader() {
224
- var _a;
225
- return ((_a = this.session) == null ? void 0 : _a.accessToken) ? { "X-Session-Token": this.session.accessToken } : {};
226
- }
227
- };
228
-
229
- // src/utils/query.ts
230
- function serialiseQuery(opts = {}) {
231
- var _a;
232
- const params = {};
233
- if (opts.limit !== void 0) params["limit"] = String(opts.limit);
234
- if (opts.offset !== void 0) params["offset"] = String(opts.offset);
235
- if (opts.select && opts.select.length > 0) params["select"] = opts.select.join(",");
236
- if (opts.orderBy) {
237
- params["orderBy"] = opts.orderBy.field;
238
- params["direction"] = (_a = opts.orderBy.direction) != null ? _a : "asc";
239
- }
240
- const filters = opts.where ? Array.isArray(opts.where) ? opts.where : [opts.where] : [];
241
- if (filters.length > 0) params["where"] = JSON.stringify(filters);
242
- return params;
243
- }
244
- var eq = (field, value) => ({ field, operator: "eq", value });
245
- var neq = (field, value) => ({ field, operator: "neq", value });
246
- var gt = (field, value) => ({ field, operator: "gt", value });
247
- var lt = (field, value) => ({ field, operator: "lt", value });
248
- var gte = (field, value) => ({ field, operator: "gte", value });
249
- var lte = (field, value) => ({ field, operator: "lte", value });
250
- var inArray = (field, value) => ({ field, operator: "in", value });
251
-
252
- // src/records/client.ts
253
- var RecordsClient = class {
254
- constructor(config) {
255
- this.baseUrl = config.url;
256
- this.headers = {
257
- "Content-Type": "application/json",
258
- "Authorization": `Bearer ${config.bucketSecurityKey}`
259
- };
260
- }
261
- /** Query records from a collection */
262
- async select(collection, options = {}) {
263
- try {
264
- const url = buildUrl(this.baseUrl, `records/${collection}`, serialiseQuery(options));
265
- const res = await fetch(url, { headers: this.headers });
266
- const json = await parseResponse(res);
267
- return { data: json.data, count: json.count, error: null };
268
- } catch (err) {
269
- return { data: [], count: 0, error: toHydrousError(err) };
270
- }
271
- }
272
- /** Fetch a single record by ID */
273
- async get(collection, id) {
274
- try {
275
- const res = await fetch(buildUrl(this.baseUrl, `records/${collection}/${id}`), { headers: this.headers });
276
- const json = await parseResponse(res);
277
- return { data: json.data, error: null };
278
- } catch (err) {
279
- return { data: null, error: toHydrousError(err) };
280
- }
281
- }
282
- /** Insert one or more records */
283
- async insert(collection, payload) {
284
- try {
285
- const res = await fetch(buildUrl(this.baseUrl, `records/${collection}`), {
286
- method: "POST",
287
- headers: this.headers,
288
- body: JSON.stringify(payload)
289
- });
290
- const json = await parseResponse(res);
291
- return { data: json.data, count: json.count, error: null };
292
- } catch (err) {
293
- return { data: [], count: 0, error: toHydrousError(err) };
294
- }
295
- }
296
- /** Update a record by ID */
297
- async update(collection, id, payload) {
298
- try {
299
- const res = await fetch(buildUrl(this.baseUrl, `records/${collection}/${id}`), {
300
- method: "PATCH",
301
- headers: this.headers,
302
- body: JSON.stringify(payload)
303
- });
304
- const json = await parseResponse(res);
305
- return { data: json.data, error: null };
306
- } catch (err) {
307
- return { data: null, error: toHydrousError(err) };
308
- }
309
- }
310
- /** Delete a record by ID */
311
- async delete(collection, id) {
312
- try {
313
- const res = await fetch(buildUrl(this.baseUrl, `records/${collection}/${id}`), {
314
- method: "DELETE",
315
- headers: this.headers
316
- });
317
- await parseResponse(res);
318
- return { data: void 0, error: null };
319
- } catch (err) {
320
- return { data: null, error: toHydrousError(err) };
321
- }
322
- }
323
- };
324
-
325
- // src/analytics/client.ts
326
- var AnalyticsClient = class {
327
- constructor(config) {
328
- this.baseUrl = config.url;
329
- this.headers = {
330
- "Content-Type": "application/json",
331
- "Authorization": `Bearer ${config.bucketSecurityKey}`
332
- };
333
- }
334
- /** Track a single analytics event */
335
- async track(options) {
336
- var _a;
337
- try {
338
- const res = await fetch(buildUrl(this.baseUrl, "analytics/track"), {
339
- method: "POST",
340
- headers: this.headers,
341
- body: JSON.stringify({ ...options, timestamp: (_a = options.timestamp) != null ? _a : Date.now() })
342
- });
343
- await parseResponse(res);
344
- return { data: void 0, error: null };
345
- } catch (err) {
346
- return { data: null, error: toHydrousError(err) };
347
- }
348
- }
349
- /** Track many events in one request */
350
- async trackBatch(events) {
351
- try {
352
- const stamped = events.map((e) => {
353
- var _a;
354
- return { ...e, timestamp: (_a = e.timestamp) != null ? _a : Date.now() };
355
- });
356
- const res = await fetch(buildUrl(this.baseUrl, "analytics/track/batch"), {
357
- method: "POST",
358
- headers: this.headers,
359
- body: JSON.stringify({ events: stamped })
360
- });
361
- await parseResponse(res);
362
- return { data: void 0, error: null };
363
- } catch (err) {
364
- return { data: null, error: toHydrousError(err) };
365
- }
366
- }
367
- /** Query recorded analytics events */
368
- async query(options = {}) {
369
- try {
370
- const params = {};
371
- if (options.event) params["event"] = options.event;
372
- if (options.from) params["from"] = options.from;
373
- if (options.to) params["to"] = options.to;
374
- if (options.limit) params["limit"] = String(options.limit);
375
- if (options.groupBy) params["groupBy"] = options.groupBy;
376
- const url = buildUrl(this.baseUrl, "analytics/events", params);
377
- const res = await fetch(url, { headers: this.headers });
378
- const json = await parseResponse(res);
379
- return { data: json.data, count: json.count, error: null };
380
- } catch (err) {
381
- return { data: [], count: 0, error: toHydrousError(err) };
382
- }
383
- }
384
- };
385
-
386
- // src/storage/scoped.ts
387
- var isBrowser = typeof window !== "undefined" && typeof XMLHttpRequest !== "undefined";
388
- function storageBase(url, bucketKey) {
389
- return `${url.replace(/\/$/, "")}/storage/${encodeURIComponent(bucketKey)}`;
390
- }
391
- function storageHeaders(bucketKey) {
392
- return { "X-Storage-Key": bucketKey };
393
- }
394
- function jsonHeaders(bucketKey) {
395
- return { "X-Storage-Key": bucketKey, "Content-Type": "application/json" };
396
- }
397
- function drainSSE(raw, onProgress) {
398
- const results = [];
399
- const errors = [];
400
- parseSSEText(raw, (eventType, data) => {
401
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
402
- const d = data;
403
- if (eventType === "progress" && onProgress) {
404
- onProgress({
405
- index: (_a = d["index"]) != null ? _a : 0,
406
- total: (_b = d["total"]) != null ? _b : 1,
407
- path: (_c = d["path"]) != null ? _c : "",
408
- stage: (_d = d["stage"]) != null ? _d : "uploading",
409
- bytesUploaded: (_e = d["bytesUploaded"]) != null ? _e : 0,
410
- totalBytes: (_f = d["totalBytes"]) != null ? _f : 0,
411
- percent: (_g = d["percent"]) != null ? _g : 0,
412
- bytesPerSecond: (_h = d["bytesPerSecond"]) != null ? _h : null,
413
- eta: (_i = d["eta"]) != null ? _i : null,
414
- result: d["result"],
415
- error: d["error"],
416
- code: d["code"]
417
- });
418
- }
419
- if (eventType === "done") {
420
- if (d["path"]) {
421
- results.push(d);
422
- } else if (Array.isArray(d["succeeded"])) {
423
- results.push(...d["succeeded"]);
424
- errors.push(...(_j = d["errors"]) != null ? _j : []);
425
- }
426
- }
427
- if (eventType === "error") {
428
- errors.push({
429
- path: "",
430
- error: (_k = d["error"]) != null ? _k : "Unknown error",
431
- code: (_l = d["code"]) != null ? _l : "UNKNOWN"
432
- });
433
- }
434
- });
435
- return { results, errors };
436
- }
437
- var ScopedStorageClient = class {
438
- constructor(baseUrl, keyName, bucketKey) {
439
- this.base = storageBase(baseUrl, bucketKey);
440
- this.key = bucketKey;
441
- this.keyName = keyName;
442
- }
443
- // ══════════════════════════════════════════════════════════════════════════
444
- // UPLOAD — single file
445
- // ══════════════════════════════════════════════════════════════════════════
446
- /**
447
- * Upload a single file.
448
- *
449
- * Supply `onProgress` to receive live upload ticks including bytes
450
- * transferred, speed (bytes/sec), ETA, and lifecycle stage.
451
- *
452
- * **Stage sequence:**
453
- * `pending → compressing → uploading → done | error`
454
- *
455
- * In browsers the progress is tracked at the network level via XHR, so
456
- * `percent` reflects actual bytes leaving the device. `done` only fires
457
- * after the server confirms the write to cloud storage, so 100% is real.
458
- *
459
- * @example
460
- * const { data, error } = await db.storage('avatars').upload(file, {
461
- * path: 'users/alice.jpg',
462
- * overwrite: true,
463
- * onProgress: (p) => {
464
- * setProgress(p.percent); // e.g. drive a <progress> bar
465
- * setSpeed(`${p.bytesPerSecond} B/s`);
466
- * setEta(`${p.eta}s remaining`);
467
- * },
468
- * });
469
- */
470
- async upload(file, options = {}) {
471
- var _a, _b;
472
- const { path, overwrite = false, onProgress } = options;
473
- try {
474
- const url = `${this.base}/upload`;
475
- const form = new FormData();
476
- if (file instanceof Uint8Array) {
477
- form.append("file", new Blob([file.buffer]), path != null ? path : "file");
478
- } else if (file instanceof ArrayBuffer) {
479
- form.append("file", new Blob([file]), path != null ? path : "file");
480
- } else {
481
- form.append("file", file, path != null ? path : file instanceof File ? file.name : "file");
482
- }
483
- if (path) form.append("path", path);
484
- if (overwrite) form.append("overwrite", "true");
485
- const headers = storageHeaders(this.key);
486
- if (isBrowser) {
487
- const totalBytes = file instanceof Blob ? file.size : file instanceof Uint8Array ? file.byteLength : file.byteLength;
488
- const rawBody = await xhrUpload(url, form, headers, (loaded, total) => {
489
- onProgress == null ? void 0 : onProgress({
490
- index: 0,
491
- total: 1,
492
- path: path != null ? path : "",
493
- stage: "uploading",
494
- bytesUploaded: loaded,
495
- totalBytes: total || totalBytes,
496
- percent: Math.min(99, Math.round(loaded / (total || totalBytes) * 100)),
497
- bytesPerSecond: null,
498
- eta: null
499
- });
500
- });
501
- const { results, errors } = drainSSE(rawBody, onProgress);
502
- if (errors.length > 0 && results.length === 0) {
503
- return { data: null, error: { message: errors[0].error, code: errors[0].code } };
504
- }
505
- const result = (_a = results[0]) != null ? _a : null;
506
- if (result && onProgress) {
507
- onProgress({
508
- index: 0,
509
- total: 1,
510
- path: result.path,
511
- stage: "done",
512
- bytesUploaded: totalBytes,
513
- totalBytes,
514
- percent: 100,
515
- bytesPerSecond: null,
516
- eta: 0,
517
- result
518
- });
519
- }
520
- return { data: result, error: null };
521
- }
522
- const res = await fetch(url, { method: "POST", headers, body: form });
523
- if (!res.ok) {
524
- const e = await res.json().catch(() => ({}));
525
- throw new HydrousDBError((_b = e.error) != null ? _b : `HTTP ${res.status}`, "HTTP_ERROR", res.status);
526
- }
527
- let finalResult = null;
528
- await readSSEStream(res, (eventType, data) => {
529
- var _a2, _b2, _c, _d, _e, _f, _g, _h;
530
- const d = data;
531
- if (eventType === "progress" && onProgress) {
532
- onProgress({
533
- index: 0,
534
- total: 1,
535
- path: path != null ? path : "",
536
- stage: (_a2 = d["stage"]) != null ? _a2 : "uploading",
537
- bytesUploaded: (_b2 = d["bytesUploaded"]) != null ? _b2 : 0,
538
- totalBytes: (_c = d["totalBytes"]) != null ? _c : 0,
539
- percent: (_d = d["percent"]) != null ? _d : 0,
540
- bytesPerSecond: (_e = d["bytesPerSecond"]) != null ? _e : null,
541
- eta: (_f = d["eta"]) != null ? _f : null,
542
- result: d["result"]
543
- });
544
- }
545
- if (eventType === "done") finalResult = data;
546
- if (eventType === "error") {
547
- throw new HydrousDBError(
548
- (_g = d["error"]) != null ? _g : "Upload failed",
549
- (_h = d["code"]) != null ? _h : "UPLOAD_ERROR"
550
- );
551
- }
552
- });
553
- return { data: finalResult, error: null };
554
- } catch (err) {
555
- return { data: null, error: toHydrousError(err) };
556
- }
557
- }
558
- // ══════════════════════════════════════════════════════════════════════════
559
- // UPLOAD TEXT / JSON
560
- // ══════════════════════════════════════════════════════════════════════════
561
- /**
562
- * Upload raw text or JSON content directly — no File object needed.
563
- *
564
- * @example
565
- * // Save a JSON config
566
- * await db.storage('configs').uploadText(
567
- * 'settings/app.json',
568
- * JSON.stringify({ theme: 'dark' }),
569
- * { mimeType: 'application/json' }
570
- * );
571
- */
572
- async uploadText(path, content, options = {}) {
573
- var _a;
574
- const { mimeType = "text/plain", overwrite = false, onProgress } = options;
575
- try {
576
- const res = await fetch(`${this.base}/upload-raw`, {
577
- method: "POST",
578
- headers: jsonHeaders(this.key),
579
- body: JSON.stringify({ path, content, mimeType, overwrite })
580
- });
581
- if (!res.ok) {
582
- const e = await res.json().catch(() => ({}));
583
- throw new HydrousDBError((_a = e.error) != null ? _a : `HTTP ${res.status}`, "HTTP_ERROR", res.status);
584
- }
585
- let finalResult = null;
586
- await readSSEStream(res, (eventType, data) => {
587
- var _a2, _b, _c, _d, _e, _f;
588
- const d = data;
589
- if (eventType === "progress" && onProgress) {
590
- onProgress({
591
- index: 0,
592
- total: 1,
593
- path,
594
- stage: (_a2 = d["stage"]) != null ? _a2 : "uploading",
595
- bytesUploaded: (_b = d["bytesUploaded"]) != null ? _b : 0,
596
- totalBytes: (_c = d["totalBytes"]) != null ? _c : 0,
597
- percent: (_d = d["percent"]) != null ? _d : 0,
598
- bytesPerSecond: (_e = d["bytesPerSecond"]) != null ? _e : null,
599
- eta: (_f = d["eta"]) != null ? _f : null
600
- });
601
- }
602
- if (eventType === "done") finalResult = data;
603
- });
604
- return { data: finalResult, error: null };
605
- } catch (err) {
606
- return { data: null, error: toHydrousError(err) };
607
- }
608
- }
609
- // ══════════════════════════════════════════════════════════════════════════
610
- // BATCH UPLOAD
611
- // ══════════════════════════════════════════════════════════════════════════
612
- /**
613
- * Upload multiple files in one request.
614
- *
615
- * `onProgress` fires per file — use `p.index` to identify which file.
616
- * All files receive a `pending` event upfront so you can render progress
617
- * bars immediately before any data is sent.
618
- *
619
- * @example
620
- * await db.storage('documents').batchUpload(files, {
621
- * prefix: 'reports/2024/',
622
- * onProgress: (p) => updateBar(p.index, p.percent),
623
- * });
624
- */
625
- async batchUpload(files, options = {}) {
626
- var _a;
627
- const { prefix = "", paths, overwrite = false, onProgress } = options;
628
- try {
629
- const url = `${this.base}/batch-upload`;
630
- const resolvedPaths = files.map((f, i) => {
631
- var _a2;
632
- return (_a2 = paths == null ? void 0 : paths[i]) != null ? _a2 : `${prefix}${f.name}`;
633
- });
634
- const form = new FormData();
635
- files.forEach((f) => form.append("files", f, f.name));
636
- form.append("paths", JSON.stringify(resolvedPaths));
637
- if (overwrite) form.append("overwrite", "true");
638
- const headers = storageHeaders(this.key);
639
- if (isBrowser) {
640
- const totalBytes = files.reduce((s, f) => s + f.size, 0);
641
- const rawBody = await xhrUpload(url, form, headers, (loaded, total) => {
642
- if (!onProgress) return;
643
- let cursor = 0;
644
- for (let i = 0; i < files.length; i++) {
645
- const share = files[i].size / (totalBytes || 1);
646
- const fileLoaded = Math.max(0, Math.min(
647
- files[i].size,
648
- (loaded / (total || totalBytes) - cursor) / share * files[i].size
649
- ));
650
- onProgress({
651
- index: i,
652
- total: files.length,
653
- path: resolvedPaths[i],
654
- stage: "uploading",
655
- bytesUploaded: Math.round(fileLoaded),
656
- totalBytes: files[i].size,
657
- percent: Math.min(99, Math.round(fileLoaded / files[i].size * 100)),
658
- bytesPerSecond: null,
659
- eta: null
660
- });
661
- cursor += share;
662
- }
663
- });
664
- const { results, errors } = drainSSE(rawBody, onProgress);
665
- return { data: { succeeded: results, failed: errors }, error: null };
666
- }
667
- const res = await fetch(url, { method: "POST", headers, body: form });
668
- if (!res.ok) {
669
- const e = await res.json().catch(() => ({}));
670
- throw new HydrousDBError((_a = e.error) != null ? _a : `HTTP ${res.status}`, "HTTP_ERROR", res.status);
671
- }
672
- const succeeded = [];
673
- const failed = [];
674
- await readSSEStream(res, (eventType, data) => {
675
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
676
- const d = data;
677
- if (eventType === "progress" && onProgress) {
678
- onProgress({
679
- index: (_a2 = d["index"]) != null ? _a2 : 0,
680
- total: (_b = d["total"]) != null ? _b : files.length,
681
- path: (_c = d["path"]) != null ? _c : "",
682
- stage: (_d = d["stage"]) != null ? _d : "uploading",
683
- bytesUploaded: (_e = d["bytesUploaded"]) != null ? _e : 0,
684
- totalBytes: (_f = d["totalBytes"]) != null ? _f : 0,
685
- percent: (_g = d["percent"]) != null ? _g : 0,
686
- bytesPerSecond: (_h = d["bytesPerSecond"]) != null ? _h : null,
687
- eta: (_i = d["eta"]) != null ? _i : null
688
- });
689
- }
690
- if (eventType === "done" && d["succeeded"]) {
691
- succeeded.push(...d["succeeded"]);
692
- failed.push(...(_j = d["errors"]) != null ? _j : []);
693
- }
694
- });
695
- return { data: { succeeded, failed }, error: null };
696
- } catch (err) {
697
- return { data: null, error: toHydrousError(err) };
698
- }
699
- }
700
- // ══════════════════════════════════════════════════════════════════════════
701
- // DOWNLOAD
702
- // ══════════════════════════════════════════════════════════════════════════
703
- /**
704
- * Download a single file and return its content as an `ArrayBuffer`.
705
- *
706
- * @example
707
- * const { data } = await db.storage('avatars').download('users/alice.jpg');
708
- * const blob = new Blob([data!]);
709
- * img.src = URL.createObjectURL(blob);
710
- */
711
- async download(filePath) {
712
- var _a;
713
- try {
714
- const res = await fetch(`${this.base}/download/${filePath}`, {
715
- headers: storageHeaders(this.key)
716
- });
717
- if (!res.ok) {
718
- const e = await res.json().catch(() => ({}));
719
- throw new HydrousDBError((_a = e.error) != null ? _a : `HTTP ${res.status}`, "HTTP_ERROR", res.status);
720
- }
721
- return { data: await res.arrayBuffer(), error: null };
722
- } catch (err) {
723
- return { data: null, error: toHydrousError(err) };
724
- }
725
- }
726
- // ══════════════════════════════════════════════════════════════════════════
727
- // BATCH DOWNLOAD
728
- // ══════════════════════════════════════════════════════════════════════════
729
- /**
730
- * Download multiple files in one request.
731
- *
732
- * Set `autoSave: true` (browser only) to trigger a Save dialog per file.
733
- *
734
- * @example
735
- * const { data } = await db.storage('reports').batchDownload(
736
- * ['jan.pdf', 'feb.pdf'],
737
- * { autoSave: true, onProgress: (p) => console.log(p.path, p.status) }
738
- * );
739
- */
740
- async batchDownload(filePaths, options = {}) {
741
- var _a;
742
- const { concurrency = 5, onProgress, autoSave = false } = options;
743
- try {
744
- const res = await fetch(`${this.base}/batch-download`, {
745
- method: "POST",
746
- headers: jsonHeaders(this.key),
747
- body: JSON.stringify({ paths: filePaths, concurrency })
748
- });
749
- if (!res.ok) {
750
- const e = await res.json().catch(() => ({}));
751
- throw new HydrousDBError((_a = e.error) != null ? _a : `HTTP ${res.status}`, "HTTP_ERROR", res.status);
752
- }
753
- const downloadedFiles = [];
754
- await readSSEStream(res, (eventType, data) => {
755
- var _a2, _b, _c, _d, _e, _f, _g, _h;
756
- const d = data;
757
- if (eventType === "file") {
758
- const base64 = d["content"];
759
- const mimeType = (_a2 = d["mimeType"]) != null ? _a2 : "application/octet-stream";
760
- const path = (_b = d["path"]) != null ? _b : "";
761
- const size = (_c = d["size"]) != null ? _c : 0;
762
- const index = (_d = d["index"]) != null ? _d : 0;
763
- const binary = atob(base64);
764
- const bytes = new Uint8Array(binary.length);
765
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
766
- downloadedFiles.push({ path, content: bytes.buffer, mimeType, size });
767
- onProgress == null ? void 0 : onProgress({ index, total: filePaths.length, path, status: "success", size, mimeType });
768
- if (autoSave && isBrowser) {
769
- const blob = new Blob([bytes.buffer], { type: mimeType });
770
- const blobUrl = URL.createObjectURL(blob);
771
- const a = document.createElement("a");
772
- a.href = blobUrl;
773
- a.download = (_e = path.split("/").pop()) != null ? _e : "download";
774
- a.click();
775
- setTimeout(() => URL.revokeObjectURL(blobUrl), 5e3);
776
- }
777
- }
778
- if (eventType === "error" && onProgress) {
779
- const index = (_f = d["index"]) != null ? _f : 0;
780
- onProgress({
781
- index,
782
- total: filePaths.length,
783
- path: (_g = filePaths[index]) != null ? _g : "",
784
- status: "error",
785
- error: (_h = d["error"]) != null ? _h : "Download failed"
786
- });
787
- }
788
- });
789
- return { data: downloadedFiles, error: null };
790
- } catch (err) {
791
- return { data: null, error: toHydrousError(err) };
792
- }
793
- }
794
- // ══════════════════════════════════════════════════════════════════════════
795
- // LIST
796
- // ══════════════════════════════════════════════════════════════════════════
797
- /**
798
- * List files and folders (paginated).
799
- *
800
- * @example
801
- * const { data } = await db.storage('avatars').list({ prefix: 'users/' });
802
- * for (const item of data!.items) {
803
- * console.log(item.type, item.path, item.size);
804
- * }
805
- */
806
- async list(options = {}) {
807
- const { prefix = "", limit = 50, cursor } = options;
808
- try {
809
- const url = buildUrl(this.base, "list", {
810
- prefix: prefix || void 0,
811
- limit,
812
- cursor: cursor || void 0
813
- });
814
- const res = await fetch(url.replace(this.base + "/list", `${this.base}/list`), {
815
- headers: storageHeaders(this.key)
816
- });
817
- const u = `${this.base}/list?${new URLSearchParams(
818
- Object.entries({ prefix: prefix || "", limit: String(limit), ...cursor ? { cursor } : {} }).filter(([, v]) => v !== "")
819
- ).toString()}`;
820
- const r = await fetch(u, { headers: storageHeaders(this.key) });
821
- const json = await parseResponse(r);
822
- return { data: json, error: null };
823
- } catch (err) {
824
- return { data: null, error: toHydrousError(err) };
825
- }
826
- }
827
- // ══════════════════════════════════════════════════════════════════════════
828
- // METADATA
829
- // ══════════════════════════════════════════════════════════════════════════
830
- /**
831
- * Get metadata for a file (size, MIME type, compression, etc.)
832
- *
833
- * @example
834
- * const { data: meta } = await db.storage('docs').metadata('report.pdf');
835
- * console.log(meta!.size, meta!.mimeType, meta!.isCompressed);
836
- */
837
- async metadata(filePath) {
838
- try {
839
- const res = await fetch(`${this.base}/metadata/${filePath}`, {
840
- headers: storageHeaders(this.key)
841
- });
842
- const json = await parseResponse(res);
843
- return { data: json.data, error: null };
844
- } catch (err) {
845
- return { data: null, error: toHydrousError(err) };
846
- }
847
- }
848
- // ══════════════════════════════════════════════════════════════════════════
849
- // DELETE
850
- // ══════════════════════════════════════════════════════════════════════════
851
- /** Delete a single file */
852
- async deleteFile(filePath) {
853
- try {
854
- const res = await fetch(`${this.base}/file`, {
855
- method: "DELETE",
856
- headers: jsonHeaders(this.key),
857
- body: JSON.stringify({ path: filePath })
858
- });
859
- await parseResponse(res);
860
- return { data: void 0, error: null };
861
- } catch (err) {
862
- return { data: null, error: toHydrousError(err) };
863
- }
864
- }
865
- /** Recursively delete a folder and all its contents */
866
- async deleteFolder(folderPath) {
867
- try {
868
- const res = await fetch(`${this.base}/folder`, {
869
- method: "DELETE",
870
- headers: jsonHeaders(this.key),
871
- body: JSON.stringify({ path: folderPath })
872
- });
873
- await parseResponse(res);
874
- return { data: void 0, error: null };
875
- } catch (err) {
876
- return { data: null, error: toHydrousError(err) };
877
- }
878
- }
879
- /** Create an empty folder */
880
- async createFolder(folderPath) {
881
- try {
882
- const res = await fetch(`${this.base}/folder`, {
883
- method: "POST",
884
- headers: jsonHeaders(this.key),
885
- body: JSON.stringify({ path: folderPath })
886
- });
887
- await parseResponse(res);
888
- return { data: void 0, error: null };
889
- } catch (err) {
890
- return { data: null, error: toHydrousError(err) };
891
- }
892
- }
893
- // ══════════════════════════════════════════════════════════════════════════
894
- // MOVE & COPY
895
- // ══════════════════════════════════════════════════════════════════════════
896
- /** Move (rename) a file */
897
- async move(fromPath, toPath) {
898
- try {
899
- const res = await fetch(`${this.base}/move`, {
900
- method: "POST",
901
- headers: jsonHeaders(this.key),
902
- body: JSON.stringify({ from: fromPath, to: toPath })
903
- });
904
- await parseResponse(res);
905
- return { data: void 0, error: null };
906
- } catch (err) {
907
- return { data: null, error: toHydrousError(err) };
908
- }
909
- }
910
- /** Copy a file (original is kept) */
911
- async copy(fromPath, toPath) {
912
- try {
913
- const res = await fetch(`${this.base}/copy`, {
914
- method: "POST",
915
- headers: jsonHeaders(this.key),
916
- body: JSON.stringify({ from: fromPath, to: toPath })
917
- });
918
- await parseResponse(res);
919
- return { data: void 0, error: null };
920
- } catch (err) {
921
- return { data: null, error: toHydrousError(err) };
922
- }
923
- }
924
- // ══════════════════════════════════════════════════════════════════════════
925
- // SIGNED URL
926
- // ══════════════════════════════════════════════════════════════════════════
927
- /**
928
- * Generate a time-limited public URL for a private file.
929
- *
930
- * @example
931
- * const { data } = await db.storage('contracts').signedUrl('nda.pdf', { expiresIn: 300 });
932
- * console.log(data!.signedUrl); // share this
933
- */
934
- async signedUrl(filePath, options = {}) {
935
- const { expiresIn = 3600 } = options;
936
- try {
937
- const res = await fetch(`${this.base}/signed-url`, {
938
- method: "POST",
939
- headers: jsonHeaders(this.key),
940
- body: JSON.stringify({ path: filePath, expiresInSeconds: expiresIn })
941
- });
942
- const json = await parseResponse(res);
943
- return { data: json, error: null };
944
- } catch (err) {
945
- return { data: null, error: toHydrousError(err) };
946
- }
947
- }
948
- // ══════════════════════════════════════════════════════════════════════════
949
- // STATS
950
- // ══════════════════════════════════════════════════════════════════════════
951
- /**
952
- * Get usage and billing stats for this storage key.
953
- *
954
- * @example
955
- * const { data } = await db.storage('main').stats();
956
- * console.log(data!.totalFiles, data!.totalSizeBytes);
957
- */
958
- async stats() {
959
- try {
960
- const res = await fetch(`${this.base}/stats`, {
961
- headers: storageHeaders(this.key)
962
- });
963
- const json = await parseResponse(res);
964
- return { data: json.data, error: null };
965
- } catch (err) {
966
- return { data: null, error: toHydrousError(err) };
967
- }
968
- }
969
- };
970
-
971
- // src/storage/manager.ts
972
- var StorageManager = class {
973
- constructor(config) {
974
- this.cache = /* @__PURE__ */ new Map();
975
- this.baseUrl = config.url;
976
- this._keys = config.storageKeys;
977
- }
978
- /**
979
- * Get a storage client scoped to a named key.
980
- *
981
- * @param keyName - Must match a property you declared in `storageKeys`
982
- *
983
- * @example
984
- * const avatarStore = db.storage('avatars');
985
- * const documentStore = db.storage('documents');
986
- *
987
- * await avatarStore.upload(file, { path: 'users/alice.jpg' });
988
- * const list = await documentStore.list({ prefix: 'invoices/' });
989
- */
990
- use(keyName) {
991
- const bucketKey = this._keys[keyName];
992
- if (!bucketKey) {
993
- const available = Object.keys(this._keys).join(", ");
994
- throw new HydrousDBError(
995
- `Storage key "${keyName}" is not defined.
996
- Available: ${available || "(none)"}`,
997
- "UNKNOWN_STORAGE_KEY"
998
- );
999
- }
1000
- if (!this.cache.has(keyName)) {
1001
- this.cache.set(keyName, new ScopedStorageClient(this.baseUrl, keyName, bucketKey));
1002
- }
1003
- return this.cache.get(keyName);
1004
- }
1005
- /** Return the names of all configured storage keys */
1006
- keyNames() {
1007
- return Object.keys(this._keys);
1008
- }
1009
- };
1010
-
1011
- // src/client.ts
1012
- var HydrousClient = class {
1013
- constructor(config) {
1014
- if (!config.url) throw new Error("[HydrousDB] config.url is required");
1015
- if (!config.authKey) throw new Error("[HydrousDB] config.authKey is required");
1016
- if (!config.bucketSecurityKey) throw new Error("[HydrousDB] config.bucketSecurityKey is required");
1017
- if (!config.storageKeys || typeof config.storageKeys !== "object") {
1018
- throw new Error("[HydrousDB] config.storageKeys must be an object of named keys");
1019
- }
1020
- this.auth = new AuthClient(config);
1021
- this.records = new RecordsClient(config);
1022
- this.analytics = new AnalyticsClient(config);
1023
- const manager = new StorageManager(config);
1024
- const fn = (keyName) => manager.use(keyName);
1025
- fn.use = (keyName) => manager.use(keyName);
1026
- fn.keyNames = () => manager.keyNames();
1027
- this.storage = fn;
1028
- }
1029
- };
1030
-
1031
- // src/index.ts
1032
- function createClient(config) {
1033
- return new HydrousClient(config);
1034
- }
1035
-
1036
- export { AnalyticsClient, AuthClient, HydrousClient, HydrousDBError, RecordsClient, ScopedStorageClient, StorageManager, createClient, eq, gt, gte, inArray, isHydrousError, lt, lte, neq };
1037
- //# sourceMappingURL=index.mjs.map
1038
- //# sourceMappingURL=index.mjs.map