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