hydrousdb 3.2.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +832 -1124
- package/dist/index.cjs +618 -321
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +519 -222
- package/dist/index.d.ts +519 -222
- package/dist/index.mjs +613 -322
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -57,7 +57,7 @@ var HttpClient = class {
|
|
|
57
57
|
constructor(baseUrl) {
|
|
58
58
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
59
59
|
}
|
|
60
|
-
// ─── Core request
|
|
60
|
+
// ─── Core request ──────────────────────────────────────────────────────────
|
|
61
61
|
async request(path, options) {
|
|
62
62
|
const url = `${this.baseUrl}${path}`;
|
|
63
63
|
const headers = { ...options.headers };
|
|
@@ -96,11 +96,7 @@ var HttpClient = class {
|
|
|
96
96
|
try {
|
|
97
97
|
data = await response.json();
|
|
98
98
|
} catch {
|
|
99
|
-
throw new HydrousError(
|
|
100
|
-
`Failed to parse response JSON`,
|
|
101
|
-
"PARSE_ERROR",
|
|
102
|
-
response.status
|
|
103
|
-
);
|
|
99
|
+
throw new HydrousError("Failed to parse response JSON", "PARSE_ERROR", response.status);
|
|
104
100
|
}
|
|
105
101
|
if (!response.ok) {
|
|
106
102
|
throw new HydrousError(
|
|
@@ -149,8 +145,8 @@ var HttpClient = class {
|
|
|
149
145
|
});
|
|
150
146
|
}
|
|
151
147
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
148
|
+
* PUT directly to a signed GCS URL.
|
|
149
|
+
* Uses XHR in browsers for onprogress support; fetch in Node.
|
|
154
150
|
*/
|
|
155
151
|
async putToSignedUrl(signedUrl, data, mimeType, onProgress) {
|
|
156
152
|
const body = data instanceof Blob ? data : data instanceof Uint8Array ? data.buffer : data;
|
|
@@ -179,135 +175,262 @@ var HttpClient = class {
|
|
|
179
175
|
}
|
|
180
176
|
};
|
|
181
177
|
|
|
178
|
+
// src/routes.ts
|
|
179
|
+
var RECORDS = {
|
|
180
|
+
/** GET|POST|PATCH|DELETE|HEAD /api/:bucketKey */
|
|
181
|
+
bucket: (bucketKey) => `/api/${bucketKey}`,
|
|
182
|
+
/** POST /api/:bucketKey/batch/insert */
|
|
183
|
+
batchInsert: (bucketKey) => `/api/${bucketKey}/batch/insert`,
|
|
184
|
+
/** POST /api/:bucketKey/batch/update */
|
|
185
|
+
batchUpdate: (bucketKey) => `/api/${bucketKey}/batch/update`,
|
|
186
|
+
/** POST /api/:bucketKey/batch/delete */
|
|
187
|
+
batchDelete: (bucketKey) => `/api/${bucketKey}/batch/delete`
|
|
188
|
+
};
|
|
189
|
+
var ANALYTICS = {
|
|
190
|
+
/** POST /api/analytics/:bucketKey */
|
|
191
|
+
query: (bucketKey) => `/api/analytics/${bucketKey}`
|
|
192
|
+
};
|
|
193
|
+
var AUTH = {
|
|
194
|
+
/** POST /api/auth/signup body: { email, password, fullName?, ...extra } */
|
|
195
|
+
signup: "/api/auth/signup",
|
|
196
|
+
/** POST /api/auth/signin body: { email, password } */
|
|
197
|
+
signin: "/api/auth/signin",
|
|
198
|
+
/** POST /api/auth/signout body: { sessionId, allDevices? } */
|
|
199
|
+
signout: "/api/auth/signout",
|
|
200
|
+
/** POST /api/auth/session/validate body: { sessionId } */
|
|
201
|
+
sessionValidate: "/api/auth/session/validate",
|
|
202
|
+
/** POST /api/auth/session/refresh body: { refreshToken } */
|
|
203
|
+
sessionRefresh: "/api/auth/session/refresh",
|
|
204
|
+
/** GET /api/auth/user?userId=... */
|
|
205
|
+
getUser: "/api/auth/user",
|
|
206
|
+
/** GET /api/auth/users?limit=&cursor= */
|
|
207
|
+
listUsers: "/api/auth/users",
|
|
208
|
+
/** PATCH /api/auth/user body: { sessionId, userId, updates: {...} } */
|
|
209
|
+
updateUser: "/api/auth/user",
|
|
210
|
+
/** DELETE /api/auth/user?userId=... body: { sessionId } */
|
|
211
|
+
deleteUser: "/api/auth/user",
|
|
212
|
+
/** DELETE /api/auth/user/hard?userId=... body: { sessionId } */
|
|
213
|
+
hardDeleteUser: "/api/auth/user/hard",
|
|
214
|
+
/** DELETE /api/auth/users/bulk body: { userIds, hard?, sessionId } */
|
|
215
|
+
bulkDeleteUsers: "/api/auth/users/bulk",
|
|
216
|
+
/** POST /api/auth/password/change body: { sessionId, userId, oldPassword, newPassword } */
|
|
217
|
+
passwordChange: "/api/auth/password/change",
|
|
218
|
+
/** POST /api/auth/password/reset/request body: { email } */
|
|
219
|
+
passwordResetRequest: "/api/auth/password/reset/request",
|
|
220
|
+
/** POST /api/auth/password/reset/confirm body: { resetToken, newPassword } */
|
|
221
|
+
passwordResetConfirm: "/api/auth/password/reset/confirm",
|
|
222
|
+
/** POST /api/auth/email/verify/request body: { userId } */
|
|
223
|
+
emailVerifyRequest: "/api/auth/email/verify/request",
|
|
224
|
+
/** POST /api/auth/email/verify/confirm body: { verifyToken } */
|
|
225
|
+
emailVerifyConfirm: "/api/auth/email/verify/confirm",
|
|
226
|
+
/** POST /api/auth/account/lock body: { sessionId, userId, duration? } */
|
|
227
|
+
accountLock: "/api/auth/account/lock",
|
|
228
|
+
/** POST /api/auth/account/unlock body: { sessionId, userId } */
|
|
229
|
+
accountUnlock: "/api/auth/account/unlock"
|
|
230
|
+
};
|
|
231
|
+
var STORAGE = {
|
|
232
|
+
/** GET /storage/info — no auth required */
|
|
233
|
+
info: "/storage/info",
|
|
234
|
+
/** GET /storage/public/:fullScopedPath — no auth required */
|
|
235
|
+
publicFile: (fullScopedPath) => `/storage/public/${fullScopedPath}`,
|
|
236
|
+
/** POST /storage/upload-url body: { path, mimeType, size, isPublic?, overwrite?, expiresIn? } */
|
|
237
|
+
uploadUrl: "/storage/upload-url",
|
|
238
|
+
/** POST /storage/batch-upload-urls body: { files: [...], expiresIn? } */
|
|
239
|
+
batchUploadUrls: "/storage/batch-upload-urls",
|
|
240
|
+
/** POST /storage/confirm body: { path, mimeType, isPublic? } */
|
|
241
|
+
confirm: "/storage/confirm",
|
|
242
|
+
/** POST /storage/batch-confirm body: { files: [...] } */
|
|
243
|
+
batchConfirm: "/storage/batch-confirm",
|
|
244
|
+
/** POST /storage/upload multipart/form-data: file, path, mimeType, isPublic, overwrite */
|
|
245
|
+
upload: "/storage/upload",
|
|
246
|
+
/** POST /storage/upload-raw body: { path, content, mimeType?, isPublic?, overwrite? } */
|
|
247
|
+
uploadRaw: "/storage/upload-raw",
|
|
248
|
+
/** GET /storage/list?prefix=&limit=&cursor= */
|
|
249
|
+
list: "/storage/list",
|
|
250
|
+
/** GET /storage/download/:path — requires X-Storage-Key */
|
|
251
|
+
download: (filePath) => `/storage/download/${filePath}`,
|
|
252
|
+
/** POST /storage/batch-download body: { paths: [...], concurrency? } */
|
|
253
|
+
batchDownload: "/storage/batch-download",
|
|
254
|
+
/** GET /storage/metadata/:path */
|
|
255
|
+
metadata: (filePath) => `/storage/metadata/${filePath}`,
|
|
256
|
+
/** POST /storage/signed-url body: { path, expiresIn? } */
|
|
257
|
+
signedUrl: "/storage/signed-url",
|
|
258
|
+
/** PATCH /storage/visibility body: { path, isPublic } */
|
|
259
|
+
visibility: "/storage/visibility",
|
|
260
|
+
/** POST /storage/folder body: { path } */
|
|
261
|
+
folder: "/storage/folder",
|
|
262
|
+
/** DELETE /storage/file body: { path } */
|
|
263
|
+
file: "/storage/file",
|
|
264
|
+
/** DELETE /storage/folder body: { path } */
|
|
265
|
+
folderDelete: "/storage/folder",
|
|
266
|
+
/** POST /storage/move body: { from, to } */
|
|
267
|
+
move: "/storage/move",
|
|
268
|
+
/** POST /storage/copy body: { from, to } */
|
|
269
|
+
copy: "/storage/copy",
|
|
270
|
+
/** GET /storage/stats */
|
|
271
|
+
stats: "/storage/stats"
|
|
272
|
+
};
|
|
273
|
+
|
|
182
274
|
// src/auth/client.ts
|
|
183
275
|
var AuthClient = class {
|
|
184
|
-
constructor(http, authKey
|
|
276
|
+
constructor(http, authKey) {
|
|
185
277
|
this.http = http;
|
|
186
278
|
this.authKey = authKey;
|
|
187
|
-
this.basePath = `/auth/${bucketKey}`;
|
|
188
279
|
}
|
|
189
280
|
post(path, body) {
|
|
190
281
|
return this.http.post(path, this.authKey, body);
|
|
191
282
|
}
|
|
192
|
-
get(path, query) {
|
|
283
|
+
get(path, query, extraHeaders) {
|
|
193
284
|
const qs = query && Object.keys(query).length ? "?" + new URLSearchParams(query).toString() : "";
|
|
194
|
-
return this.http.get(`${path}${qs}`, this.authKey);
|
|
285
|
+
return this.http.get(`${path}${qs}`, this.authKey, extraHeaders);
|
|
195
286
|
}
|
|
196
287
|
patch(path, body) {
|
|
197
288
|
return this.http.patch(path, this.authKey, body);
|
|
198
289
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
290
|
+
// ─── Signup / Login / Logout ──────────────────────────────────────────────
|
|
291
|
+
/**
|
|
292
|
+
* Register a new user account.
|
|
293
|
+
*
|
|
294
|
+
* Server: POST /api/auth/signup
|
|
295
|
+
* Body: { email, password, fullName?, ...extraData }
|
|
296
|
+
*/
|
|
206
297
|
async signup(options) {
|
|
207
|
-
const result = await this.post(
|
|
208
|
-
const user = result.
|
|
209
|
-
return
|
|
210
|
-
user,
|
|
211
|
-
session: {
|
|
212
|
-
sessionId: result.session.sessionId,
|
|
213
|
-
userId: user.id,
|
|
214
|
-
bucketId: this.basePath.split("/")[2],
|
|
215
|
-
createdAt: Date.now(),
|
|
216
|
-
expiresAt: result.session.expiresAt,
|
|
217
|
-
refreshToken: result.session.refreshToken,
|
|
218
|
-
refreshExpiresAt: result.session.expiresAt
|
|
219
|
-
}
|
|
220
|
-
};
|
|
298
|
+
const result = await this.post(AUTH.signup, options);
|
|
299
|
+
const user = result.data;
|
|
300
|
+
return this._buildAuthResult(user, result.session);
|
|
221
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Sign in with email + password.
|
|
304
|
+
*
|
|
305
|
+
* Server: POST /api/auth/signin (NOT /login)
|
|
306
|
+
* Body: { email, password }
|
|
307
|
+
*/
|
|
222
308
|
async login(options) {
|
|
223
|
-
const result = await this.post(
|
|
224
|
-
const user = result.
|
|
225
|
-
return
|
|
226
|
-
user,
|
|
227
|
-
session: {
|
|
228
|
-
sessionId: result.session.sessionId,
|
|
229
|
-
userId: user.id,
|
|
230
|
-
bucketId: this.basePath.split("/")[2],
|
|
231
|
-
createdAt: Date.now(),
|
|
232
|
-
expiresAt: result.session.expiresAt,
|
|
233
|
-
refreshToken: result.session.refreshToken,
|
|
234
|
-
refreshExpiresAt: result.session.expiresAt
|
|
235
|
-
}
|
|
236
|
-
};
|
|
309
|
+
const result = await this.post(AUTH.signin, options);
|
|
310
|
+
const user = result.data;
|
|
311
|
+
return this._buildAuthResult(user, result.session);
|
|
237
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Sign out — revoke a session (or all sessions with `allDevices: true`).
|
|
315
|
+
*
|
|
316
|
+
* Server: POST /api/auth/signout
|
|
317
|
+
* Body: { sessionId, allDevices? }
|
|
318
|
+
*/
|
|
238
319
|
async logout(options) {
|
|
239
|
-
await this.post(
|
|
320
|
+
await this.post(AUTH.signout, options);
|
|
240
321
|
}
|
|
241
|
-
|
|
322
|
+
// ─── Session Management ───────────────────────────────────────────────────
|
|
323
|
+
/**
|
|
324
|
+
* Validate an existing session and retrieve the current user.
|
|
325
|
+
*
|
|
326
|
+
* Server: POST /api/auth/session/validate
|
|
327
|
+
* Body: { sessionId }
|
|
328
|
+
*/
|
|
329
|
+
async validateSession(sessionId) {
|
|
242
330
|
const result = await this.post(
|
|
243
|
-
|
|
331
|
+
AUTH.sessionValidate,
|
|
332
|
+
{ sessionId }
|
|
333
|
+
);
|
|
334
|
+
return { user: result.data, session: result.session };
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Rotate a refresh token to get a new session.
|
|
338
|
+
*
|
|
339
|
+
* Server: POST /api/auth/session/refresh
|
|
340
|
+
* Body: { refreshToken }
|
|
341
|
+
*/
|
|
342
|
+
async refreshSession(refreshToken) {
|
|
343
|
+
const result = await this.post(
|
|
344
|
+
AUTH.sessionRefresh,
|
|
244
345
|
{ refreshToken }
|
|
245
346
|
);
|
|
246
347
|
const s = result.session;
|
|
247
348
|
return {
|
|
248
349
|
sessionId: s.sessionId,
|
|
249
350
|
userId: result.data?.id ?? "",
|
|
250
|
-
bucketId:
|
|
351
|
+
bucketId: "",
|
|
251
352
|
createdAt: Date.now(),
|
|
252
353
|
expiresAt: s.expiresAt,
|
|
253
354
|
refreshToken: s.refreshToken,
|
|
254
355
|
refreshExpiresAt: s.expiresAt
|
|
255
356
|
};
|
|
256
357
|
}
|
|
257
|
-
async validateSession({ sessionId }) {
|
|
258
|
-
const result = await this.post(
|
|
259
|
-
`${this.basePath}/session/validate`,
|
|
260
|
-
{ sessionId }
|
|
261
|
-
);
|
|
262
|
-
return { user: result.data, session: result.session };
|
|
263
|
-
}
|
|
264
358
|
// ─── User Profile ─────────────────────────────────────────────────────────
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Fetch a user by ID.
|
|
361
|
+
*
|
|
362
|
+
* Server: GET /api/auth/user?userId=:userId
|
|
363
|
+
*/
|
|
364
|
+
async getUser(userId) {
|
|
365
|
+
const result = await this.get(AUTH.getUser, { userId });
|
|
270
366
|
return result.data;
|
|
271
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Update a user's profile fields.
|
|
370
|
+
* Regular users can only update themselves; admins can update any user.
|
|
371
|
+
*
|
|
372
|
+
* Server: PATCH /api/auth/user
|
|
373
|
+
* Body: { sessionId, userId, updates: { ...fields } }
|
|
374
|
+
*/
|
|
272
375
|
async updateUser(options) {
|
|
273
376
|
const { sessionId, userId, updates } = options;
|
|
274
377
|
const result = await this.patch(
|
|
275
|
-
|
|
378
|
+
AUTH.updateUser,
|
|
276
379
|
{ sessionId, userId, updates }
|
|
277
380
|
);
|
|
278
381
|
return result.data;
|
|
279
382
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Soft-delete a user account.
|
|
385
|
+
* Regular users can only delete themselves; admins can delete any user.
|
|
386
|
+
*
|
|
387
|
+
* Server: DELETE /api/auth/user?userId=:userId
|
|
388
|
+
* Body: { sessionId }
|
|
389
|
+
*/
|
|
390
|
+
async deleteUser(sessionId, userId) {
|
|
391
|
+
await this.http.request(
|
|
392
|
+
`${AUTH.deleteUser}?userId=${encodeURIComponent(userId)}`,
|
|
393
|
+
{
|
|
394
|
+
method: "DELETE",
|
|
395
|
+
body: { sessionId },
|
|
396
|
+
headers: { "X-Api-Key": this.authKey }
|
|
397
|
+
}
|
|
398
|
+
);
|
|
286
399
|
}
|
|
287
400
|
// ─── Admin Operations ─────────────────────────────────────────────────────
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
401
|
+
/**
|
|
402
|
+
* List all users (paginated). Admin only.
|
|
403
|
+
*
|
|
404
|
+
* Server: GET /api/auth/users?limit=&cursor=
|
|
405
|
+
* The sessionId is passed via the X-Session-Id header (GET body is unreliable).
|
|
406
|
+
*/
|
|
291
407
|
async listUsers(options) {
|
|
292
408
|
const { sessionId, limit = 50, cursor } = options;
|
|
293
409
|
const params = { limit: String(limit) };
|
|
294
410
|
if (cursor) params["cursor"] = cursor;
|
|
295
411
|
const result = await this.http.request(
|
|
296
|
-
`${
|
|
412
|
+
`${AUTH.listUsers}?${new URLSearchParams(params)}`,
|
|
297
413
|
{
|
|
298
414
|
method: "GET",
|
|
299
415
|
headers: { "X-Api-Key": this.authKey, "X-Session-Id": sessionId }
|
|
300
416
|
}
|
|
301
417
|
);
|
|
302
418
|
return {
|
|
303
|
-
users: result.data ??
|
|
419
|
+
users: result.data ?? [],
|
|
304
420
|
hasMore: result.meta?.hasMore ?? result.hasMore ?? false,
|
|
305
421
|
nextCursor: result.meta?.nextCursor ?? result.nextCursor ?? null
|
|
306
422
|
};
|
|
307
423
|
}
|
|
308
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Permanently delete a user (hard delete — cannot be undone).
|
|
426
|
+
* Admin only. Charged at 1 HC per deletion.
|
|
427
|
+
*
|
|
428
|
+
* Server: DELETE /api/auth/user/hard?userId=:userId
|
|
429
|
+
* Body: { sessionId }
|
|
430
|
+
*/
|
|
431
|
+
async hardDeleteUser(sessionId, userId) {
|
|
309
432
|
await this.http.request(
|
|
310
|
-
`${
|
|
433
|
+
`${AUTH.hardDeleteUser}?userId=${encodeURIComponent(userId)}`,
|
|
311
434
|
{
|
|
312
435
|
method: "DELETE",
|
|
313
436
|
body: { sessionId },
|
|
@@ -315,54 +438,114 @@ var AuthClient = class {
|
|
|
315
438
|
}
|
|
316
439
|
);
|
|
317
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Delete multiple users at once (soft or hard). Admin only.
|
|
443
|
+
*
|
|
444
|
+
* Server: DELETE /api/auth/users/bulk
|
|
445
|
+
* Body: { userIds, hard?, sessionId }
|
|
446
|
+
*/
|
|
318
447
|
async bulkDeleteUsers(options) {
|
|
319
448
|
const result = await this.http.request(
|
|
320
|
-
|
|
449
|
+
AUTH.bulkDeleteUsers,
|
|
321
450
|
{
|
|
322
451
|
method: "DELETE",
|
|
323
|
-
body: {
|
|
452
|
+
body: {
|
|
453
|
+
userIds: options.userIds,
|
|
454
|
+
hard: options.hard ?? false,
|
|
455
|
+
sessionId: options.sessionId
|
|
456
|
+
},
|
|
324
457
|
headers: { "X-Api-Key": this.authKey }
|
|
325
458
|
}
|
|
326
459
|
);
|
|
327
460
|
return { succeeded: result.meta.succeeded, failed: result.meta.failed };
|
|
328
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Lock a user account for a specified duration. Admin only.
|
|
464
|
+
*
|
|
465
|
+
* Server: POST /api/auth/account/lock
|
|
466
|
+
* Body: { sessionId, userId, duration? } — duration in ms, default 15 min
|
|
467
|
+
*/
|
|
329
468
|
async lockAccount(options) {
|
|
330
|
-
const result = await this.post(
|
|
331
|
-
`${this.basePath}/account/lock`,
|
|
332
|
-
options
|
|
333
|
-
);
|
|
469
|
+
const result = await this.post(AUTH.accountLock, options);
|
|
334
470
|
return result.data;
|
|
335
471
|
}
|
|
336
|
-
|
|
337
|
-
|
|
472
|
+
/**
|
|
473
|
+
* Unlock a locked user account. Admin only.
|
|
474
|
+
*
|
|
475
|
+
* Server: POST /api/auth/account/unlock
|
|
476
|
+
* Body: { sessionId, userId }
|
|
477
|
+
*/
|
|
478
|
+
async unlockAccount(sessionId, userId) {
|
|
479
|
+
await this.post(AUTH.accountUnlock, { sessionId, userId });
|
|
338
480
|
}
|
|
339
481
|
// ─── Password Management ──────────────────────────────────────────────────
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Change a user's password. Requires both a valid session AND the old password.
|
|
484
|
+
*
|
|
485
|
+
* Server: POST /api/auth/password/change
|
|
486
|
+
* Body: { sessionId, userId, oldPassword, newPassword }
|
|
487
|
+
*/
|
|
343
488
|
async changePassword(options) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
oldPassword
|
|
349
|
-
newPassword
|
|
489
|
+
await this.post(AUTH.passwordChange, {
|
|
490
|
+
sessionId: options.sessionId,
|
|
491
|
+
userId: options.userId,
|
|
492
|
+
oldPassword: options.currentPassword,
|
|
493
|
+
// server field is `oldPassword`
|
|
494
|
+
newPassword: options.newPassword
|
|
350
495
|
});
|
|
351
496
|
}
|
|
352
|
-
|
|
353
|
-
|
|
497
|
+
/**
|
|
498
|
+
* Request a password reset email.
|
|
499
|
+
* Always returns success regardless of whether the email exists (prevents enumeration).
|
|
500
|
+
*
|
|
501
|
+
* Server: POST /api/auth/password/reset/request
|
|
502
|
+
* Body: { email }
|
|
503
|
+
*/
|
|
504
|
+
async requestPasswordReset(email) {
|
|
505
|
+
await this.post(AUTH.passwordResetRequest, { email });
|
|
354
506
|
}
|
|
355
|
-
|
|
356
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Confirm a password reset using the token from the reset email.
|
|
509
|
+
*
|
|
510
|
+
* Server: POST /api/auth/password/reset/confirm
|
|
511
|
+
* Body: { resetToken, newPassword }
|
|
512
|
+
*/
|
|
513
|
+
async confirmPasswordReset(resetToken, newPassword) {
|
|
514
|
+
await this.post(AUTH.passwordResetConfirm, { resetToken, newPassword });
|
|
357
515
|
}
|
|
358
516
|
// ─── Email Verification ───────────────────────────────────────────────────
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Request a verification email be sent to a user.
|
|
519
|
+
*
|
|
520
|
+
* Server: POST /api/auth/email/verify/request
|
|
521
|
+
* Body: { userId }
|
|
522
|
+
*/
|
|
523
|
+
async requestEmailVerification(userId) {
|
|
524
|
+
await this.post(AUTH.emailVerifyRequest, { userId });
|
|
363
525
|
}
|
|
364
|
-
|
|
365
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Confirm email ownership using the token from the verification email.
|
|
528
|
+
*
|
|
529
|
+
* Server: POST /api/auth/email/verify/confirm
|
|
530
|
+
* Body: { verifyToken }
|
|
531
|
+
*/
|
|
532
|
+
async confirmEmailVerification(verifyToken) {
|
|
533
|
+
await this.post(AUTH.emailVerifyConfirm, { verifyToken });
|
|
534
|
+
}
|
|
535
|
+
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
536
|
+
_buildAuthResult(user, session) {
|
|
537
|
+
return {
|
|
538
|
+
user,
|
|
539
|
+
session: {
|
|
540
|
+
sessionId: session.sessionId,
|
|
541
|
+
userId: user.id,
|
|
542
|
+
bucketId: "",
|
|
543
|
+
createdAt: Date.now(),
|
|
544
|
+
expiresAt: session.expiresAt,
|
|
545
|
+
refreshToken: session.refreshToken,
|
|
546
|
+
refreshExpiresAt: session.expiresAt
|
|
547
|
+
}
|
|
548
|
+
};
|
|
366
549
|
}
|
|
367
550
|
};
|
|
368
551
|
|
|
@@ -371,18 +554,22 @@ function buildQueryParams(options = {}) {
|
|
|
371
554
|
const params = new URLSearchParams();
|
|
372
555
|
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
373
556
|
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
374
|
-
if (options.orderBy !== void 0) params.set("
|
|
375
|
-
if (options.order !== void 0) params.set("
|
|
557
|
+
if (options.orderBy !== void 0) params.set("sortBy", options.orderBy);
|
|
558
|
+
if (options.order !== void 0) params.set("sortOrder", options.order);
|
|
376
559
|
if (options.fields !== void 0) params.set("fields", options.fields);
|
|
377
|
-
if (options.startAfter !== void 0) params.set("
|
|
378
|
-
if (options.startAt !== void 0) params.set("
|
|
379
|
-
if (options.
|
|
560
|
+
if (options.startAfter !== void 0) params.set("cursor", options.startAfter);
|
|
561
|
+
if (options.startAt !== void 0) params.set("cursor", options.startAt);
|
|
562
|
+
if (options.timeScope !== void 0) params.set("timeScope", options.timeScope);
|
|
380
563
|
if (options.dateRange?.start !== void 0)
|
|
381
564
|
params.set("startDate", new Date(options.dateRange.start).toISOString().split("T")[0]);
|
|
382
565
|
if (options.dateRange?.end !== void 0)
|
|
383
566
|
params.set("endDate", new Date(options.dateRange.end).toISOString().split("T")[0]);
|
|
384
|
-
if (options.filters && options.filters.length > 0)
|
|
385
|
-
|
|
567
|
+
if (options.filters && options.filters.length > 0) {
|
|
568
|
+
for (const f of options.filters) {
|
|
569
|
+
const key = f.op === "==" ? f.field : `${f.field}[${f.op}]`;
|
|
570
|
+
params.set(key, String(f.value));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
386
573
|
const str = params.toString();
|
|
387
574
|
return str ? `?${str}` : "";
|
|
388
575
|
}
|
|
@@ -404,10 +591,13 @@ function guessMimeType(filename) {
|
|
|
404
591
|
html: "text/html",
|
|
405
592
|
css: "text/css",
|
|
406
593
|
js: "application/javascript",
|
|
594
|
+
ts: "application/typescript",
|
|
407
595
|
json: "application/json",
|
|
408
596
|
xml: "application/xml",
|
|
409
597
|
zip: "application/zip",
|
|
410
|
-
csv: "text/csv"
|
|
598
|
+
csv: "text/csv",
|
|
599
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
600
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
411
601
|
};
|
|
412
602
|
return map[ext ?? ""] ?? "application/octet-stream";
|
|
413
603
|
}
|
|
@@ -426,15 +616,14 @@ var RecordsClient = class {
|
|
|
426
616
|
this.http = http;
|
|
427
617
|
this.bucketKey = bucketKey;
|
|
428
618
|
this.apiKey = bucketSecurityKey;
|
|
429
|
-
this.basePath = `/records/${bucketKey}`;
|
|
430
619
|
}
|
|
431
620
|
// ─── Single Record Operations ─────────────────────────────────────────────
|
|
432
621
|
/**
|
|
433
|
-
* Create a new record
|
|
622
|
+
* Create a new record (auto-generated ID) or upsert by `customRecordId`.
|
|
434
623
|
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
624
|
+
* Server: POST /api/:bucketKey
|
|
625
|
+
* Body: { values, queryableFields?, userEmail?, customRecordId? }
|
|
626
|
+
* Returns 201 for new records, 200 for upserts.
|
|
438
627
|
*
|
|
439
628
|
* @example
|
|
440
629
|
* ```ts
|
|
@@ -450,63 +639,110 @@ var RecordsClient = class {
|
|
|
450
639
|
if (queryableFields?.length) body["queryableFields"] = queryableFields;
|
|
451
640
|
if (userEmail) body["userEmail"] = userEmail;
|
|
452
641
|
if (customRecordId) body["customRecordId"] = customRecordId;
|
|
453
|
-
const result = await this.http.post(
|
|
454
|
-
|
|
642
|
+
const result = await this.http.post(
|
|
643
|
+
RECORDS.bucket(this.bucketKey),
|
|
644
|
+
this.apiKey,
|
|
645
|
+
body
|
|
646
|
+
);
|
|
647
|
+
return result.data;
|
|
455
648
|
}
|
|
456
649
|
/**
|
|
457
650
|
* Get a single record by ID.
|
|
651
|
+
*
|
|
652
|
+
* Server: GET /api/:bucketKey?recordId=:id
|
|
458
653
|
*/
|
|
459
654
|
async get(id) {
|
|
460
655
|
const result = await this.http.get(
|
|
461
|
-
`${this.
|
|
656
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
|
|
462
657
|
this.apiKey
|
|
463
658
|
);
|
|
464
|
-
return result.data
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Fully replace a record (PUT semantics).
|
|
468
|
-
*/
|
|
469
|
-
async set(id, data) {
|
|
470
|
-
const result = await this.http.put(
|
|
471
|
-
`${this.basePath}/${encodeURIComponent(id)}`,
|
|
472
|
-
this.apiKey,
|
|
473
|
-
data
|
|
474
|
-
);
|
|
475
|
-
return result.data ?? result.record;
|
|
659
|
+
return result.data;
|
|
476
660
|
}
|
|
477
661
|
/**
|
|
478
|
-
* Partially update
|
|
479
|
-
* By default merges with existing fields; set `merge: false` to replace.
|
|
662
|
+
* Partially update an existing record (PATCH semantics).
|
|
480
663
|
* Supports write-filter sentinels: `{ __op: 'increment', delta: 1 }` etc.
|
|
664
|
+
*
|
|
665
|
+
* Server: PATCH /api/:bucketKey
|
|
666
|
+
* Body: { recordId, values, userEmail?, track_record_history? }
|
|
481
667
|
*/
|
|
482
668
|
async patch(id, data, options = {}) {
|
|
483
|
-
const {
|
|
669
|
+
const { userEmail, trackHistory } = options;
|
|
670
|
+
const body = { recordId: id, values: data };
|
|
671
|
+
if (userEmail) body["userEmail"] = userEmail;
|
|
672
|
+
if (trackHistory) body["track_record_history"] = true;
|
|
484
673
|
const result = await this.http.patch(
|
|
485
|
-
|
|
674
|
+
RECORDS.bucket(this.bucketKey),
|
|
486
675
|
this.apiKey,
|
|
487
|
-
|
|
676
|
+
body
|
|
488
677
|
);
|
|
489
|
-
return result.
|
|
678
|
+
return { id: result.meta?.id ?? id, updatedAt: result.meta?.updatedAt };
|
|
490
679
|
}
|
|
491
680
|
/**
|
|
492
681
|
* Delete a record permanently.
|
|
682
|
+
*
|
|
683
|
+
* Server: DELETE /api/:bucketKey?recordId=:id
|
|
493
684
|
*/
|
|
494
685
|
async delete(id) {
|
|
495
686
|
await this.http.delete(
|
|
496
|
-
`${this.
|
|
687
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
|
|
688
|
+
this.apiKey
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Check whether a record exists (HEAD request — very lightweight).
|
|
693
|
+
*
|
|
694
|
+
* Server: HEAD /api/:bucketKey?recordId=:id
|
|
695
|
+
* Returns true if the record exists, false if 404.
|
|
696
|
+
*/
|
|
697
|
+
async exists(id) {
|
|
698
|
+
try {
|
|
699
|
+
await this.http.request(
|
|
700
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
|
|
701
|
+
{ method: "HEAD", headers: { "X-Api-Key": this.apiKey } }
|
|
702
|
+
);
|
|
703
|
+
return true;
|
|
704
|
+
} catch (err) {
|
|
705
|
+
if (err && typeof err === "object" && "status" in err && err.status === 404) return false;
|
|
706
|
+
throw err;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Get a historical snapshot of a record at a specific generation.
|
|
711
|
+
*
|
|
712
|
+
* Server: GET /api/:bucketKey?recordId=:id&generation=:gen
|
|
713
|
+
*/
|
|
714
|
+
async getVersion(id, generation) {
|
|
715
|
+
const result = await this.http.get(
|
|
716
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}&generation=${encodeURIComponent(String(generation))}`,
|
|
717
|
+
this.apiKey
|
|
718
|
+
);
|
|
719
|
+
return result.data;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Get the version history of a record.
|
|
723
|
+
*
|
|
724
|
+
* Server: GET /api/:bucketKey?recordId=:id&showHistory=true
|
|
725
|
+
*/
|
|
726
|
+
async getHistory(id) {
|
|
727
|
+
const result = await this.http.get(
|
|
728
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}&showHistory=true`,
|
|
497
729
|
this.apiKey
|
|
498
730
|
);
|
|
731
|
+
return result.history ?? [];
|
|
499
732
|
}
|
|
500
733
|
// ─── Batch Operations ─────────────────────────────────────────────────────
|
|
501
734
|
/**
|
|
502
|
-
* Create
|
|
503
|
-
* Each record
|
|
735
|
+
* Create up to 500 records in one request.
|
|
736
|
+
* Each record may include `_customRecordId` for upsert behaviour.
|
|
737
|
+
*
|
|
738
|
+
* Server: POST /api/:bucketKey/batch/insert
|
|
739
|
+
* Body: { records, queryableFields?, userEmail? }
|
|
504
740
|
*
|
|
505
741
|
* @example
|
|
506
742
|
* ```ts
|
|
507
|
-
* const
|
|
743
|
+
* const results = await posts.batchCreate(
|
|
508
744
|
* [{ title: 'A' }, { title: 'B' }],
|
|
509
|
-
* { queryableFields: ['title']
|
|
745
|
+
* { queryableFields: ['title'] },
|
|
510
746
|
* );
|
|
511
747
|
* ```
|
|
512
748
|
*/
|
|
@@ -516,29 +752,60 @@ var RecordsClient = class {
|
|
|
516
752
|
if (queryableFields?.length) body["queryableFields"] = queryableFields;
|
|
517
753
|
if (userEmail) body["userEmail"] = userEmail;
|
|
518
754
|
const result = await this.http.post(
|
|
519
|
-
|
|
755
|
+
RECORDS.batchInsert(this.bucketKey),
|
|
520
756
|
this.apiKey,
|
|
521
757
|
body
|
|
522
758
|
);
|
|
523
|
-
return
|
|
759
|
+
return {
|
|
760
|
+
results: result.data ?? [],
|
|
761
|
+
errors: result.errors ?? [],
|
|
762
|
+
successful: result.meta?.successful ?? (result.data?.length ?? 0),
|
|
763
|
+
failed: result.meta?.failed ?? 0
|
|
764
|
+
};
|
|
524
765
|
}
|
|
525
766
|
/**
|
|
526
|
-
*
|
|
767
|
+
* Update up to 500 records in one request.
|
|
768
|
+
*
|
|
769
|
+
* Server: POST /api/:bucketKey/batch/update
|
|
770
|
+
* Body: { updates: [{ recordId, values }], userEmail? }
|
|
527
771
|
*/
|
|
528
|
-
async
|
|
772
|
+
async batchUpdate(updates, userEmail) {
|
|
773
|
+
const body = { updates };
|
|
774
|
+
if (userEmail) body["userEmail"] = userEmail;
|
|
529
775
|
const result = await this.http.post(
|
|
530
|
-
|
|
776
|
+
RECORDS.batchUpdate(this.bucketKey),
|
|
531
777
|
this.apiKey,
|
|
532
|
-
|
|
778
|
+
body
|
|
533
779
|
);
|
|
534
780
|
return {
|
|
535
|
-
|
|
536
|
-
failed: result.data?.failed ??
|
|
781
|
+
successful: result.data?.successful ?? result.meta?.count ?? 0,
|
|
782
|
+
failed: result.data?.failed ?? []
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Delete up to 500 records in one request.
|
|
787
|
+
*
|
|
788
|
+
* Server: POST /api/:bucketKey/batch/delete
|
|
789
|
+
* Body: { recordIds, userEmail? }
|
|
790
|
+
*/
|
|
791
|
+
async batchDelete(ids, userEmail) {
|
|
792
|
+
const body = { recordIds: ids };
|
|
793
|
+
if (userEmail) body["userEmail"] = userEmail;
|
|
794
|
+
const result = await this.http.post(
|
|
795
|
+
RECORDS.batchDelete(this.bucketKey),
|
|
796
|
+
this.apiKey,
|
|
797
|
+
body
|
|
798
|
+
);
|
|
799
|
+
return {
|
|
800
|
+
successful: result.data?.successful ?? result.meta?.count ?? 0,
|
|
801
|
+
failed: result.data?.failed ?? []
|
|
537
802
|
};
|
|
538
803
|
}
|
|
539
804
|
// ─── Querying ─────────────────────────────────────────────────────────────
|
|
540
805
|
/**
|
|
541
|
-
* Query records with
|
|
806
|
+
* Query records with filters, sorting, and cursor-based pagination.
|
|
807
|
+
*
|
|
808
|
+
* Server: GET /api/:bucketKey?field=value&field[op]=value&limit=&sortBy=&sortOrder=&cursor=
|
|
542
809
|
*
|
|
543
810
|
* @example
|
|
544
811
|
* ```ts
|
|
@@ -552,52 +819,23 @@ var RecordsClient = class {
|
|
|
552
819
|
*/
|
|
553
820
|
async query(options = {}) {
|
|
554
821
|
const qs = buildQueryParams(options);
|
|
555
|
-
const result = await this.http.get(`${this.basePath}${qs}`, this.apiKey);
|
|
556
|
-
const records = result.data ?? result.records ?? [];
|
|
557
|
-
const hasMore = result.meta?.hasMore ?? result.hasMore ?? false;
|
|
558
|
-
const total = result.meta?.total ?? result.total;
|
|
559
|
-
const nextCursor = result.meta?.nextCursor ?? result.nextCursor ?? void 0;
|
|
560
|
-
return { records, total, hasMore, nextCursor: nextCursor ?? void 0 };
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Get all records matching the given options (no filter support — use `query()` for filters).
|
|
564
|
-
*/
|
|
565
|
-
async getAll(options = {}) {
|
|
566
|
-
const { records } = await this.query(options);
|
|
567
|
-
return records;
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Count records matching the given filters.
|
|
571
|
-
*/
|
|
572
|
-
async count(filters = []) {
|
|
573
|
-
const result = await this.http.post(
|
|
574
|
-
`${this.basePath}/count`,
|
|
575
|
-
this.apiKey,
|
|
576
|
-
{ filters }
|
|
577
|
-
);
|
|
578
|
-
return result.data?.count ?? result.count ?? 0;
|
|
579
|
-
}
|
|
580
|
-
// ─── Version History ──────────────────────────────────────────────────────
|
|
581
|
-
/**
|
|
582
|
-
* Get the version history of a record.
|
|
583
|
-
*/
|
|
584
|
-
async getHistory(id) {
|
|
585
822
|
const result = await this.http.get(
|
|
586
|
-
`${this.
|
|
823
|
+
`${RECORDS.bucket(this.bucketKey)}${qs}`,
|
|
587
824
|
this.apiKey
|
|
588
825
|
);
|
|
589
|
-
return
|
|
826
|
+
return {
|
|
827
|
+
records: result.data ?? [],
|
|
828
|
+
total: result.meta?.total ?? result.total,
|
|
829
|
+
hasMore: result.meta?.hasMore ?? result.hasMore ?? false,
|
|
830
|
+
nextCursor: result.meta?.nextCursor ?? result.nextCursor ?? void 0
|
|
831
|
+
};
|
|
590
832
|
}
|
|
591
833
|
/**
|
|
592
|
-
*
|
|
834
|
+
* Retrieve all records matching options (no filter support — use `query()` for filters).
|
|
593
835
|
*/
|
|
594
|
-
async
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
this.apiKey,
|
|
598
|
-
{ version }
|
|
599
|
-
);
|
|
600
|
-
return result.data ?? result.record;
|
|
836
|
+
async getAll(options = {}) {
|
|
837
|
+
const { records } = await this.query(options);
|
|
838
|
+
return records;
|
|
601
839
|
}
|
|
602
840
|
};
|
|
603
841
|
|
|
@@ -607,11 +845,11 @@ var AnalyticsClient = class {
|
|
|
607
845
|
assertSafeName(bucketKey, "bucketKey");
|
|
608
846
|
this.http = http;
|
|
609
847
|
this.bucketSecurityKey = bucketSecurityKey;
|
|
610
|
-
this.
|
|
848
|
+
this.bucketKey = bucketKey;
|
|
611
849
|
}
|
|
612
850
|
async run(query) {
|
|
613
851
|
const result = await this.http.post(
|
|
614
|
-
this.
|
|
852
|
+
ANALYTICS.query(this.bucketKey),
|
|
615
853
|
this.bucketSecurityKey,
|
|
616
854
|
query
|
|
617
855
|
);
|
|
@@ -622,7 +860,10 @@ var AnalyticsClient = class {
|
|
|
622
860
|
async count(opts = {}) {
|
|
623
861
|
return this.run({ queryType: "count", ...opts });
|
|
624
862
|
}
|
|
625
|
-
/**
|
|
863
|
+
/**
|
|
864
|
+
* Get value distribution for a field (e.g. records per status).
|
|
865
|
+
* `order` maps to `sortBy` on the wire (the server param name).
|
|
866
|
+
*/
|
|
626
867
|
async distribution(opts) {
|
|
627
868
|
assertSafeName(opts.field, "field");
|
|
628
869
|
return this.run({ queryType: "distribution", ...opts });
|
|
@@ -658,13 +899,19 @@ var AnalyticsClient = class {
|
|
|
658
899
|
assertSafeName(opts.field, "field");
|
|
659
900
|
return this.run({ queryType: "stats", ...opts });
|
|
660
901
|
}
|
|
661
|
-
/**
|
|
902
|
+
/**
|
|
903
|
+
* Fetch filtered records via the analytics engine (BigQuery).
|
|
904
|
+
* Bypasses Firestore pagination — useful for large result sets.
|
|
905
|
+
*/
|
|
662
906
|
async records(opts = {}) {
|
|
663
907
|
if (opts.orderBy) assertSafeName(opts.orderBy, "orderBy");
|
|
664
908
|
if (opts.selectFields) opts.selectFields.forEach((f) => assertSafeName(f, "selectField"));
|
|
665
909
|
return this.run({ queryType: "records", limit: 100, order: "desc", ...opts });
|
|
666
910
|
}
|
|
667
|
-
/**
|
|
911
|
+
/**
|
|
912
|
+
* Compute multiple aggregations in a single request.
|
|
913
|
+
* `metrics` must have valid `field` and `name` (used as BigQuery column aliases).
|
|
914
|
+
*/
|
|
668
915
|
async multiMetric(opts) {
|
|
669
916
|
opts.metrics.forEach((m) => {
|
|
670
917
|
assertSafeName(m.field, "metric.field");
|
|
@@ -678,7 +925,8 @@ var AnalyticsClient = class {
|
|
|
678
925
|
}
|
|
679
926
|
/**
|
|
680
927
|
* Compare a metric across multiple buckets in one query.
|
|
681
|
-
* The caller's key must have read access to
|
|
928
|
+
* The caller's key must have read access to EVERY bucket in `bucketKeys`.
|
|
929
|
+
* System buckets (`users`, `_sys_*`) are blocked server-side.
|
|
682
930
|
*/
|
|
683
931
|
async crossBucket(opts) {
|
|
684
932
|
assertSafeName(opts.field, "field");
|
|
@@ -686,7 +934,7 @@ var AnalyticsClient = class {
|
|
|
686
934
|
return this.run({ queryType: "crossBucket", aggregation: "sum", ...opts });
|
|
687
935
|
}
|
|
688
936
|
/**
|
|
689
|
-
* Raw query —
|
|
937
|
+
* Raw query — escape hatch when the typed helpers don't cover your case.
|
|
690
938
|
*/
|
|
691
939
|
async query(query) {
|
|
692
940
|
const data = await this.run(query);
|
|
@@ -707,17 +955,19 @@ function toUploadResult(r) {
|
|
|
707
955
|
}
|
|
708
956
|
var StorageManager = class {
|
|
709
957
|
constructor(http, storageKey) {
|
|
710
|
-
this.basePath = "/storage";
|
|
711
958
|
this.http = http;
|
|
712
959
|
this.storageKey = storageKey;
|
|
713
960
|
}
|
|
714
961
|
get authHeaders() {
|
|
715
962
|
return { "X-Storage-Key": this.storageKey };
|
|
716
963
|
}
|
|
717
|
-
// ─── Upload:
|
|
964
|
+
// ─── Upload: Server-buffered ──────────────────────────────────────────────
|
|
718
965
|
/**
|
|
719
|
-
* Upload a file
|
|
720
|
-
* For files >10 MB or when upload progress is needed, use
|
|
966
|
+
* Upload a file in one step (server-buffered, up to 500 MB).
|
|
967
|
+
* For files >10 MB or when upload progress tracking is needed, use the
|
|
968
|
+
* signed URL flow: `getUploadUrl()` → `uploadToSignedUrl()` → `confirmUpload()`.
|
|
969
|
+
*
|
|
970
|
+
* Server: POST /storage/upload (multipart/form-data)
|
|
721
971
|
*
|
|
722
972
|
* @example
|
|
723
973
|
* ```ts
|
|
@@ -735,7 +985,7 @@ var StorageManager = class {
|
|
|
735
985
|
formData.append("mimeType", mime);
|
|
736
986
|
formData.append("isPublic", String(isPublic));
|
|
737
987
|
formData.append("overwrite", String(overwrite));
|
|
738
|
-
const result = await this.http.request(
|
|
988
|
+
const result = await this.http.request(STORAGE.upload, {
|
|
739
989
|
method: "POST",
|
|
740
990
|
rawBody: formData,
|
|
741
991
|
headers: this.authHeaders
|
|
@@ -743,7 +993,10 @@ var StorageManager = class {
|
|
|
743
993
|
return toUploadResult(result);
|
|
744
994
|
}
|
|
745
995
|
/**
|
|
746
|
-
* Upload raw
|
|
996
|
+
* Upload raw string or JSON data directly as a file.
|
|
997
|
+
*
|
|
998
|
+
* Server: POST /storage/upload-raw
|
|
999
|
+
* Body: { path, content, mimeType?, isPublic?, overwrite? }
|
|
747
1000
|
*
|
|
748
1001
|
* @example
|
|
749
1002
|
* ```ts
|
|
@@ -753,33 +1006,32 @@ var StorageManager = class {
|
|
|
753
1006
|
async uploadRaw(data, path, options = {}) {
|
|
754
1007
|
const { isPublic = false, overwrite = false, mimeType = "application/json" } = options;
|
|
755
1008
|
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
756
|
-
const result = await this.http.request(
|
|
1009
|
+
const result = await this.http.request(STORAGE.uploadRaw, {
|
|
757
1010
|
method: "POST",
|
|
758
1011
|
body: { path, content, mimeType, isPublic, overwrite },
|
|
759
1012
|
headers: this.authHeaders
|
|
760
1013
|
});
|
|
761
1014
|
return toUploadResult(result);
|
|
762
1015
|
}
|
|
763
|
-
// ─── Upload: Direct-to-GCS (recommended for large files)
|
|
1016
|
+
// ─── Upload: Direct-to-GCS (recommended for large files / progress) ───────
|
|
764
1017
|
/**
|
|
765
|
-
* Step 1 — get a signed GCS URL to upload
|
|
766
|
-
*
|
|
1018
|
+
* Step 1 — get a signed GCS PUT URL for direct client-to-GCS upload.
|
|
1019
|
+
*
|
|
1020
|
+
* Server: POST /storage/upload-url
|
|
1021
|
+
* Body: { path, mimeType, size, isPublic?, overwrite?, expiresIn? }
|
|
767
1022
|
*
|
|
768
1023
|
* @example
|
|
769
1024
|
* ```ts
|
|
770
|
-
* const { uploadUrl, path:
|
|
771
|
-
* path:
|
|
772
|
-
* mimeType: 'video/mp4',
|
|
773
|
-
* size: file.size,
|
|
774
|
-
* isPublic: true,
|
|
1025
|
+
* const { uploadUrl, path: p } = await storage.getUploadUrl({
|
|
1026
|
+
* path: 'videos/intro.mp4', mimeType: 'video/mp4', size: file.size,
|
|
775
1027
|
* });
|
|
776
|
-
* await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', pct =>
|
|
777
|
-
* const result = await storage.confirmUpload({ path:
|
|
1028
|
+
* await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', pct => setProgress(pct));
|
|
1029
|
+
* const result = await storage.confirmUpload({ path: p, mimeType: 'video/mp4' });
|
|
778
1030
|
* ```
|
|
779
1031
|
*/
|
|
780
1032
|
async getUploadUrl(opts) {
|
|
781
1033
|
const { expiresInSeconds, ...rest } = opts;
|
|
782
|
-
const result = await this.http.request(
|
|
1034
|
+
const result = await this.http.request(STORAGE.uploadUrl, {
|
|
783
1035
|
method: "POST",
|
|
784
1036
|
body: { isPublic: false, overwrite: false, expiresIn: expiresInSeconds ?? 900, ...rest },
|
|
785
1037
|
headers: this.authHeaders
|
|
@@ -793,17 +1045,20 @@ var StorageManager = class {
|
|
|
793
1045
|
};
|
|
794
1046
|
}
|
|
795
1047
|
/**
|
|
796
|
-
* Step 2 — upload data directly to
|
|
797
|
-
* Supports progress tracking in browser environments.
|
|
1048
|
+
* Step 2 — upload data directly to the signed GCS URL.
|
|
1049
|
+
* Supports progress tracking in browser environments via XHR.
|
|
798
1050
|
*/
|
|
799
1051
|
async uploadToSignedUrl(signedUrl, data, mimeType, onProgress) {
|
|
800
1052
|
await this.http.putToSignedUrl(signedUrl, data, mimeType, onProgress);
|
|
801
1053
|
}
|
|
802
1054
|
/**
|
|
803
|
-
* Step 3 — confirm a direct upload and register metadata
|
|
1055
|
+
* Step 3 — confirm a direct upload and register metadata server-side.
|
|
1056
|
+
*
|
|
1057
|
+
* Server: POST /storage/confirm
|
|
1058
|
+
* Body: { path, mimeType, isPublic? }
|
|
804
1059
|
*/
|
|
805
1060
|
async confirmUpload(opts) {
|
|
806
|
-
const result = await this.http.request(
|
|
1061
|
+
const result = await this.http.request(STORAGE.confirm, {
|
|
807
1062
|
method: "POST",
|
|
808
1063
|
body: { isPublic: false, ...opts },
|
|
809
1064
|
headers: this.authHeaders
|
|
@@ -812,26 +1067,17 @@ var StorageManager = class {
|
|
|
812
1067
|
}
|
|
813
1068
|
// ─── Batch Upload ─────────────────────────────────────────────────────────
|
|
814
1069
|
/**
|
|
815
|
-
* Get signed upload URLs for
|
|
816
|
-
* Server returns `{ succeeded, failed }` — only succeeded items have URLs.
|
|
1070
|
+
* Get signed upload URLs for up to 50 files at once.
|
|
817
1071
|
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
* const { files } = await storage.getBatchUploadUrls([
|
|
821
|
-
* { path: 'images/a.jpg', mimeType: 'image/jpeg', size: 204800 },
|
|
822
|
-
* { path: 'images/b.jpg', mimeType: 'image/jpeg', size: 153600 },
|
|
823
|
-
* ]);
|
|
824
|
-
* for (const f of files) {
|
|
825
|
-
* await storage.uploadToSignedUrl(f.uploadUrl, blobs[f.index], f.mimeType);
|
|
826
|
-
* await storage.confirmUpload({ path: f.path, mimeType: f.mimeType });
|
|
827
|
-
* }
|
|
828
|
-
* ```
|
|
1072
|
+
* Server: POST /storage/batch-upload-urls
|
|
1073
|
+
* Body: { files: [...], expiresIn? }
|
|
829
1074
|
*/
|
|
830
1075
|
async getBatchUploadUrls(files) {
|
|
831
|
-
const result = await this.http.request(
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1076
|
+
const result = await this.http.request(STORAGE.batchUploadUrls, {
|
|
1077
|
+
method: "POST",
|
|
1078
|
+
body: { files },
|
|
1079
|
+
headers: this.authHeaders
|
|
1080
|
+
});
|
|
835
1081
|
return {
|
|
836
1082
|
files: result.succeeded.map((f) => ({
|
|
837
1083
|
index: f.index,
|
|
@@ -845,13 +1091,16 @@ var StorageManager = class {
|
|
|
845
1091
|
}
|
|
846
1092
|
/**
|
|
847
1093
|
* Confirm multiple direct uploads at once.
|
|
848
|
-
*
|
|
1094
|
+
*
|
|
1095
|
+
* Server: POST /storage/batch-confirm
|
|
1096
|
+
* Body: { files: [{ path, mimeType, isPublic? }] }
|
|
849
1097
|
*/
|
|
850
1098
|
async batchConfirmUploads(items) {
|
|
851
|
-
const result = await this.http.request(
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1099
|
+
const result = await this.http.request(STORAGE.batchConfirm, {
|
|
1100
|
+
method: "POST",
|
|
1101
|
+
body: { files: items },
|
|
1102
|
+
headers: this.authHeaders
|
|
1103
|
+
});
|
|
855
1104
|
return {
|
|
856
1105
|
succeeded: result.succeeded.map(toUploadResult),
|
|
857
1106
|
failed: result.failed
|
|
@@ -861,32 +1110,28 @@ var StorageManager = class {
|
|
|
861
1110
|
/**
|
|
862
1111
|
* Download a private file as an ArrayBuffer.
|
|
863
1112
|
* For public files, use the `publicUrl` directly — no SDK needed.
|
|
1113
|
+
*
|
|
1114
|
+
* Server: GET /storage/download/:path (requires X-Storage-Key)
|
|
864
1115
|
*/
|
|
865
1116
|
async download(path) {
|
|
866
1117
|
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
867
|
-
return this.http.request(
|
|
1118
|
+
return this.http.request(STORAGE.download(encoded), {
|
|
868
1119
|
method: "GET",
|
|
869
1120
|
headers: this.authHeaders
|
|
870
1121
|
});
|
|
871
1122
|
}
|
|
872
1123
|
/**
|
|
873
|
-
* Download
|
|
874
|
-
* Returns a structured result with succeeded and failed items.
|
|
875
|
-
* Each succeeded item includes the file content as a base64 string.
|
|
1124
|
+
* Download up to 20 files at once. Returns base64-encoded content.
|
|
876
1125
|
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
* const { succeeded, failed } = await storage.batchDownload(['docs/a.pdf', 'docs/b.pdf']);
|
|
880
|
-
* for (const f of succeeded) {
|
|
881
|
-
* const bytes = Buffer.from(f.content, 'base64');
|
|
882
|
-
* }
|
|
883
|
-
* ```
|
|
1126
|
+
* Server: POST /storage/batch-download
|
|
1127
|
+
* Body: { paths, concurrency? }
|
|
884
1128
|
*/
|
|
885
|
-
async batchDownload(paths) {
|
|
886
|
-
const result = await this.http.request(
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1129
|
+
async batchDownload(paths, concurrency = 5) {
|
|
1130
|
+
const result = await this.http.request(STORAGE.batchDownload, {
|
|
1131
|
+
method: "POST",
|
|
1132
|
+
body: { paths, concurrency },
|
|
1133
|
+
headers: this.authHeaders
|
|
1134
|
+
});
|
|
890
1135
|
return {
|
|
891
1136
|
succeeded: result.succeeded.map((f) => ({
|
|
892
1137
|
index: f.index,
|
|
@@ -906,7 +1151,8 @@ var StorageManager = class {
|
|
|
906
1151
|
// ─── List ─────────────────────────────────────────────────────────────────
|
|
907
1152
|
/**
|
|
908
1153
|
* List files and folders at a given path prefix.
|
|
909
|
-
*
|
|
1154
|
+
*
|
|
1155
|
+
* Server: GET /storage/list?prefix=&limit=&cursor=
|
|
910
1156
|
*
|
|
911
1157
|
* @example
|
|
912
1158
|
* ```ts
|
|
@@ -920,7 +1166,7 @@ var StorageManager = class {
|
|
|
920
1166
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
921
1167
|
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
922
1168
|
const qs = params.toString() ? `?${params}` : "";
|
|
923
|
-
const result = await this.http.request(`${
|
|
1169
|
+
const result = await this.http.request(`${STORAGE.list}${qs}`, {
|
|
924
1170
|
method: "GET",
|
|
925
1171
|
headers: this.authHeaders
|
|
926
1172
|
});
|
|
@@ -928,11 +1174,8 @@ var StorageManager = class {
|
|
|
928
1174
|
const files = [];
|
|
929
1175
|
const folders = [];
|
|
930
1176
|
for (const item of result.items) {
|
|
931
|
-
if (item.type === "folder")
|
|
932
|
-
|
|
933
|
-
} else {
|
|
934
|
-
files.push(item);
|
|
935
|
-
}
|
|
1177
|
+
if (item.type === "folder") folders.push(item.path);
|
|
1178
|
+
else files.push(item);
|
|
936
1179
|
}
|
|
937
1180
|
return {
|
|
938
1181
|
files,
|
|
@@ -950,14 +1193,16 @@ var StorageManager = class {
|
|
|
950
1193
|
}
|
|
951
1194
|
// ─── Metadata ─────────────────────────────────────────────────────────────
|
|
952
1195
|
/**
|
|
953
|
-
* Get metadata
|
|
1196
|
+
* Get file metadata: size, MIME type, visibility, URLs.
|
|
1197
|
+
*
|
|
1198
|
+
* Server: GET /storage/metadata/:path
|
|
954
1199
|
*/
|
|
955
1200
|
async getMetadata(path) {
|
|
956
1201
|
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
957
|
-
const result = await this.http.request(
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
);
|
|
1202
|
+
const result = await this.http.request(STORAGE.metadata(encoded), {
|
|
1203
|
+
method: "GET",
|
|
1204
|
+
headers: this.authHeaders
|
|
1205
|
+
});
|
|
961
1206
|
return {
|
|
962
1207
|
path: result.path,
|
|
963
1208
|
size: result.size,
|
|
@@ -972,15 +1217,18 @@ var StorageManager = class {
|
|
|
972
1217
|
// ─── Signed URL ───────────────────────────────────────────────────────────
|
|
973
1218
|
/**
|
|
974
1219
|
* Generate a time-limited download URL for a private file.
|
|
975
|
-
* Can be shared externally
|
|
1220
|
+
* Can be shared externally — no X-Storage-Key required to access.
|
|
976
1221
|
*
|
|
977
|
-
*
|
|
1222
|
+
* Note: Downloads via signed URLs bypass the server — stats are NOT tracked.
|
|
1223
|
+
*
|
|
1224
|
+
* Server: POST /storage/signed-url
|
|
1225
|
+
* Body: { path, expiresIn? }
|
|
978
1226
|
*
|
|
979
1227
|
* @param path File path.
|
|
980
1228
|
* @param expiresIn URL lifetime in seconds (default 3600 = 1 hour).
|
|
981
1229
|
*/
|
|
982
1230
|
async getSignedUrl(path, expiresIn = 3600) {
|
|
983
|
-
const result = await this.http.request(
|
|
1231
|
+
const result = await this.http.request(STORAGE.signedUrl, {
|
|
984
1232
|
method: "POST",
|
|
985
1233
|
body: { path, expiresIn },
|
|
986
1234
|
headers: this.authHeaders
|
|
@@ -994,10 +1242,13 @@ var StorageManager = class {
|
|
|
994
1242
|
}
|
|
995
1243
|
// ─── Visibility ───────────────────────────────────────────────────────────
|
|
996
1244
|
/**
|
|
997
|
-
* Change a file's visibility between public and private
|
|
1245
|
+
* Change a file's visibility between public and private.
|
|
1246
|
+
*
|
|
1247
|
+
* Server: PATCH /storage/visibility
|
|
1248
|
+
* Body: { path, isPublic }
|
|
998
1249
|
*/
|
|
999
1250
|
async setVisibility(path, isPublic) {
|
|
1000
|
-
const result = await this.http.request(
|
|
1251
|
+
const result = await this.http.request(STORAGE.visibility, {
|
|
1001
1252
|
method: "PATCH",
|
|
1002
1253
|
body: { path, isPublic },
|
|
1003
1254
|
headers: this.authHeaders
|
|
@@ -1010,53 +1261,95 @@ var StorageManager = class {
|
|
|
1010
1261
|
};
|
|
1011
1262
|
}
|
|
1012
1263
|
// ─── Folder Operations ────────────────────────────────────────────────────
|
|
1264
|
+
/**
|
|
1265
|
+
* Create a folder (empty GCS object used as a prefix marker).
|
|
1266
|
+
*
|
|
1267
|
+
* Server: POST /storage/folder
|
|
1268
|
+
* Body: { path }
|
|
1269
|
+
*/
|
|
1013
1270
|
async createFolder(path) {
|
|
1014
|
-
const result = await this.http.request(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1271
|
+
const result = await this.http.request(STORAGE.folder, {
|
|
1272
|
+
method: "POST",
|
|
1273
|
+
body: { path },
|
|
1274
|
+
headers: this.authHeaders
|
|
1275
|
+
});
|
|
1018
1276
|
return { path: result.path };
|
|
1019
1277
|
}
|
|
1020
1278
|
// ─── File Operations ──────────────────────────────────────────────────────
|
|
1279
|
+
/**
|
|
1280
|
+
* Permanently delete a file.
|
|
1281
|
+
*
|
|
1282
|
+
* Server: DELETE /storage/file
|
|
1283
|
+
* Body: { path }
|
|
1284
|
+
*/
|
|
1021
1285
|
async deleteFile(path) {
|
|
1022
|
-
await this.http.request(
|
|
1286
|
+
await this.http.request(STORAGE.file, {
|
|
1023
1287
|
method: "DELETE",
|
|
1024
1288
|
body: { path },
|
|
1025
1289
|
headers: this.authHeaders
|
|
1026
1290
|
});
|
|
1027
1291
|
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Recursively delete a folder and all its contents.
|
|
1294
|
+
*
|
|
1295
|
+
* Server: DELETE /storage/folder
|
|
1296
|
+
* Body: { path }
|
|
1297
|
+
*/
|
|
1028
1298
|
async deleteFolder(path) {
|
|
1029
|
-
await this.http.request(
|
|
1299
|
+
await this.http.request(STORAGE.folderDelete, {
|
|
1030
1300
|
method: "DELETE",
|
|
1031
1301
|
body: { path },
|
|
1032
1302
|
headers: this.authHeaders
|
|
1033
1303
|
});
|
|
1034
1304
|
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Move (rename) a file.
|
|
1307
|
+
*
|
|
1308
|
+
* Server: POST /storage/move
|
|
1309
|
+
* Body: { from, to }
|
|
1310
|
+
*/
|
|
1035
1311
|
async move(from, to) {
|
|
1036
|
-
const result = await this.http.request(
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1312
|
+
const result = await this.http.request(STORAGE.move, {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
body: { from, to },
|
|
1315
|
+
headers: this.authHeaders
|
|
1316
|
+
});
|
|
1040
1317
|
return { from: result.from, to: result.to };
|
|
1041
1318
|
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Copy a file to a new path.
|
|
1321
|
+
*
|
|
1322
|
+
* Server: POST /storage/copy
|
|
1323
|
+
* Body: { from, to }
|
|
1324
|
+
*/
|
|
1042
1325
|
async copy(from, to) {
|
|
1043
|
-
const result = await this.http.request(
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1326
|
+
const result = await this.http.request(STORAGE.copy, {
|
|
1327
|
+
method: "POST",
|
|
1328
|
+
body: { from, to },
|
|
1329
|
+
headers: this.authHeaders
|
|
1330
|
+
});
|
|
1047
1331
|
return { from: result.from, to: result.to };
|
|
1048
1332
|
}
|
|
1049
1333
|
// ─── Stats ────────────────────────────────────────────────────────────────
|
|
1334
|
+
/**
|
|
1335
|
+
* Get usage statistics for this storage key.
|
|
1336
|
+
*
|
|
1337
|
+
* Server: GET /storage/stats
|
|
1338
|
+
*/
|
|
1050
1339
|
async getStats() {
|
|
1051
|
-
const result = await this.http.request(
|
|
1340
|
+
const result = await this.http.request(STORAGE.stats, {
|
|
1052
1341
|
method: "GET",
|
|
1053
1342
|
headers: this.authHeaders
|
|
1054
1343
|
});
|
|
1055
1344
|
return result.stats;
|
|
1056
1345
|
}
|
|
1057
|
-
|
|
1346
|
+
/**
|
|
1347
|
+
* Get server info (no auth required).
|
|
1348
|
+
*
|
|
1349
|
+
* Server: GET /storage/info
|
|
1350
|
+
*/
|
|
1058
1351
|
async info() {
|
|
1059
|
-
return this.http.request(
|
|
1352
|
+
return this.http.request(STORAGE.info, {
|
|
1060
1353
|
method: "GET"
|
|
1061
1354
|
});
|
|
1062
1355
|
}
|
|
@@ -1106,7 +1399,8 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1106
1399
|
}
|
|
1107
1400
|
// ─── List ─────────────────────────────────────────────────────────────────
|
|
1108
1401
|
/**
|
|
1109
|
-
* List files within this scope.
|
|
1402
|
+
* List files within this scope.
|
|
1403
|
+
* The `prefix` option is relative to the scope root.
|
|
1110
1404
|
*
|
|
1111
1405
|
* @example
|
|
1112
1406
|
* ```ts
|
|
@@ -1158,7 +1452,7 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1158
1452
|
*
|
|
1159
1453
|
* @example
|
|
1160
1454
|
* ```ts
|
|
1161
|
-
* const user
|
|
1455
|
+
* const user = storage.scope('users/alice/');
|
|
1162
1456
|
* const userDocs = user.scope('docs/');
|
|
1163
1457
|
* // Effective prefix: users/alice/docs/
|
|
1164
1458
|
* ```
|
|
@@ -1172,17 +1466,16 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1172
1466
|
var HydrousClient = class {
|
|
1173
1467
|
constructor(config) {
|
|
1174
1468
|
this._recordsCache = /* @__PURE__ */ new Map();
|
|
1175
|
-
this._authCache = /* @__PURE__ */ new Map();
|
|
1176
1469
|
this._analyticsCache = /* @__PURE__ */ new Map();
|
|
1177
1470
|
this._storageCache = /* @__PURE__ */ new Map();
|
|
1178
1471
|
if (!config.authKey) {
|
|
1179
|
-
throw new Error("[HydrousDB] authKey is required.
|
|
1472
|
+
throw new Error("[HydrousDB] authKey is required.");
|
|
1180
1473
|
}
|
|
1181
1474
|
if (!config.bucketSecurityKey) {
|
|
1182
|
-
throw new Error("[HydrousDB] bucketSecurityKey is required.
|
|
1475
|
+
throw new Error("[HydrousDB] bucketSecurityKey is required.");
|
|
1183
1476
|
}
|
|
1184
1477
|
if (!config.storageKeys || Object.keys(config.storageKeys).length === 0) {
|
|
1185
|
-
throw new Error("[HydrousDB] storageKeys
|
|
1478
|
+
throw new Error("[HydrousDB] storageKeys must define at least one key.");
|
|
1186
1479
|
}
|
|
1187
1480
|
this.http = new HttpClient(config.baseUrl ?? DEFAULT_BASE_URL);
|
|
1188
1481
|
this.authKey_ = config.authKey;
|
|
@@ -1191,16 +1484,13 @@ var HydrousClient = class {
|
|
|
1191
1484
|
}
|
|
1192
1485
|
// ─── Records ──────────────────────────────────────────────────────────────
|
|
1193
1486
|
/**
|
|
1194
|
-
* Get a typed
|
|
1487
|
+
* Get a typed RecordsClient for a bucket.
|
|
1195
1488
|
*
|
|
1196
1489
|
* @example
|
|
1197
1490
|
* ```ts
|
|
1198
1491
|
* interface Post { title: string; published: boolean }
|
|
1199
1492
|
* const posts = db.records<Post>('blog-posts');
|
|
1200
|
-
* const post = await posts.create(
|
|
1201
|
-
* { title: 'Hello', published: false },
|
|
1202
|
-
* { queryableFields: ['published'] },
|
|
1203
|
-
* );
|
|
1493
|
+
* const post = await posts.create({ title: 'Hello', published: false });
|
|
1204
1494
|
* ```
|
|
1205
1495
|
*/
|
|
1206
1496
|
records(bucketKey) {
|
|
@@ -1214,28 +1504,28 @@ var HydrousClient = class {
|
|
|
1214
1504
|
}
|
|
1215
1505
|
// ─── Auth ─────────────────────────────────────────────────────────────────
|
|
1216
1506
|
/**
|
|
1217
|
-
* Get
|
|
1507
|
+
* Get the AuthClient.
|
|
1508
|
+
* Auth routes are NOT bucket-scoped in the URL — the bucket is determined
|
|
1509
|
+
* server-side from the API key itself.
|
|
1218
1510
|
*
|
|
1219
1511
|
* @example
|
|
1220
1512
|
* ```ts
|
|
1221
|
-
* const
|
|
1222
|
-
* const { user, session } = await auth.login({ email: '…', password: '…' });
|
|
1513
|
+
* const { user, session } = await db.auth().signup({ email: 'alice@example.com', password: 'pw' });
|
|
1223
1514
|
* ```
|
|
1224
1515
|
*/
|
|
1225
|
-
auth(
|
|
1226
|
-
if (!this.
|
|
1227
|
-
this.
|
|
1516
|
+
auth() {
|
|
1517
|
+
if (!this._authClient) {
|
|
1518
|
+
this._authClient = new AuthClient(this.http, this.authKey_);
|
|
1228
1519
|
}
|
|
1229
|
-
return this.
|
|
1520
|
+
return this._authClient;
|
|
1230
1521
|
}
|
|
1231
1522
|
// ─── Analytics ────────────────────────────────────────────────────────────
|
|
1232
1523
|
/**
|
|
1233
|
-
* Get an
|
|
1524
|
+
* Get an AnalyticsClient for a bucket.
|
|
1234
1525
|
*
|
|
1235
1526
|
* @example
|
|
1236
1527
|
* ```ts
|
|
1237
|
-
* const
|
|
1238
|
-
* const { count } = await analytics.count();
|
|
1528
|
+
* const { count } = await db.analytics('orders').count();
|
|
1239
1529
|
* ```
|
|
1240
1530
|
*/
|
|
1241
1531
|
analytics(bucketKey) {
|
|
@@ -1249,18 +1539,19 @@ var HydrousClient = class {
|
|
|
1249
1539
|
}
|
|
1250
1540
|
// ─── Storage ──────────────────────────────────────────────────────────────
|
|
1251
1541
|
/**
|
|
1252
|
-
* Get a
|
|
1253
|
-
* The name must match a key
|
|
1542
|
+
* Get a StorageManager for a named storage key.
|
|
1543
|
+
* The name must match a key in the `storageKeys` config object.
|
|
1254
1544
|
*
|
|
1255
|
-
* Attach `.scope(prefix)` to
|
|
1545
|
+
* Attach `.scope('prefix/')` to get a path-prefixed ScopedStorage.
|
|
1256
1546
|
*
|
|
1257
1547
|
* @example
|
|
1258
1548
|
* ```ts
|
|
1259
|
-
* const avatars
|
|
1260
|
-
* const
|
|
1549
|
+
* const avatars = db.storage('avatars');
|
|
1550
|
+
* const result = await avatars.upload(file, 'alice.jpg', { isPublic: true });
|
|
1261
1551
|
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1552
|
+
* // Scoped:
|
|
1553
|
+
* const userFiles = db.storage('documents').scope(`users/${userId}/`);
|
|
1554
|
+
* await userFiles.upload(pdf, 'contract.pdf');
|
|
1264
1555
|
* ```
|
|
1265
1556
|
*/
|
|
1266
1557
|
storage(keyName) {
|
|
@@ -1286,6 +1577,6 @@ function createClient(config) {
|
|
|
1286
1577
|
return new HydrousClient(config);
|
|
1287
1578
|
}
|
|
1288
1579
|
|
|
1289
|
-
export { AnalyticsClient, AnalyticsError, AuthClient, AuthError, HydrousClient, HydrousError, NetworkError, RecordError, RecordsClient, ScopedStorage, StorageError, StorageManager, ValidationError, createClient };
|
|
1580
|
+
export { ANALYTICS, AUTH, AnalyticsClient, AnalyticsError, AuthClient, AuthError, DEFAULT_BASE_URL, HttpClient, HydrousClient, HydrousError, NetworkError, RECORDS, RecordError, RecordsClient, STORAGE, ScopedStorage, StorageError, StorageManager, ValidationError, createClient };
|
|
1290
1581
|
//# sourceMappingURL=index.mjs.map
|
|
1291
1582
|
//# sourceMappingURL=index.mjs.map
|