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.mjs CHANGED
@@ -57,7 +57,7 @@ var HttpClient = class {
57
57
  constructor(baseUrl) {
58
58
  this.baseUrl = baseUrl.replace(/\/$/, "");
59
59
  }
60
- // ─── Core request method ──────────────────────────────────────────────────
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
- * Upload directly to a signed GCS URL (no auth headers — signed URL is self-authenticating).
153
- * Supports optional progress tracking via XHR in browser environments.
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, bucketKey) {
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
- delete(path, body) {
200
- return this.http.delete(path, this.authKey, body);
201
- }
202
- // ─── Registration & Login ─────────────────────────────────────────────────
203
- // Server: POST /auth/:bucket/signup → { user, session }
204
- // Server: POST /auth/:bucket/signin → { user, session }
205
- // Server: POST /auth/:bucket/signout → { success, message }
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(`${this.basePath}/signup`, options);
208
- const user = result.user ?? result.data;
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(`${this.basePath}/signin`, options);
224
- const user = result.user ?? result.data;
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(`${this.basePath}/signout`, options);
320
+ await this.post(AUTH.signout, options);
240
321
  }
241
- async refreshSession({ refreshToken }) {
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
- `${this.basePath}/session/refresh`,
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: this.basePath.split("/")[2],
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
- // Server: GET /auth/:bucket/user?userId=... → { success, data: UserRecord }
266
- // Server: PATCH /auth/:bucket/user body: { sessionId, userId, updates: {...} }
267
- // Server: DELETE /auth/:bucket/user?userId=... header/body: sessionId
268
- async getUser({ userId }) {
269
- const result = await this.get(`${this.basePath}/user`, { userId });
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
- `${this.basePath}/user`,
378
+ AUTH.updateUser,
276
379
  { sessionId, userId, updates }
277
380
  );
278
381
  return result.data;
279
382
  }
280
- async deleteUser({ sessionId, userId }) {
281
- await this.http.request(`${this.basePath}/user?userId=${encodeURIComponent(userId)}`, {
282
- method: "DELETE",
283
- body: { sessionId },
284
- headers: { "X-Api-Key": this.authKey }
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
- // Server: GET /auth/:bucket/users?limit=&cursor= → { data: UserRecord[], meta: { hasMore, nextCursor } }
289
- // Server: DELETE /auth/:bucket/users/bulk body: { userIds, hard?, sessionId }
290
- // Server: DELETE /auth/:bucket/user/hard?userId=... body: { sessionId }
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
- `${this.basePath}/users?${new URLSearchParams(params)}`,
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 ?? result.users ?? [],
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
- async hardDeleteUser({ sessionId, userId }) {
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
- `${this.basePath}/user/hard?userId=${encodeURIComponent(userId)}`,
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
- `${this.basePath}/users/bulk`,
449
+ AUTH.bulkDeleteUsers,
321
450
  {
322
451
  method: "DELETE",
323
- body: { userIds: options.userIds, hard: options.hard ?? false, sessionId: options.sessionId },
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
- async unlockAccount({ sessionId, userId }) {
337
- await this.post(`${this.basePath}/account/unlock`, { sessionId, userId });
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
- // Server: POST /auth/:bucket/password/change body: { sessionId, userId, oldPassword, newPassword }
341
- // Server: POST /auth/:bucket/password/reset/request body: { email }
342
- // Server: POST /auth/:bucket/password/reset/confirm body: { resetToken, newPassword }
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
- const { sessionId, userId, currentPassword, newPassword } = options;
345
- await this.post(`${this.basePath}/password/change`, {
346
- sessionId,
347
- userId,
348
- oldPassword: currentPassword,
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
- async requestPasswordReset({ email }) {
353
- await this.post(`${this.basePath}/password/reset/request`, { email });
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
- async confirmPasswordReset({ resetToken, newPassword }) {
356
- await this.post(`${this.basePath}/password/reset/confirm`, { resetToken, newPassword });
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
- // Server: POST /auth/:bucket/email/verify/request body: { userId }
360
- // Server: POST /auth/:bucket/email/verify/confirm body: { verifyToken }
361
- async requestEmailVerification({ userId }) {
362
- await this.post(`${this.basePath}/email/verify/request`, { userId });
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
- async confirmEmailVerification({ verifyToken }) {
365
- await this.post(`${this.basePath}/email/verify/confirm`, { verifyToken });
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("orderBy", options.orderBy);
375
- if (options.order !== void 0) params.set("order", options.order);
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("startAfter", options.startAfter);
378
- if (options.startAt !== void 0) params.set("startAt", options.startAt);
379
- if (options.endAt !== void 0) params.set("endAt", options.endAt);
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
- params.set("filters", JSON.stringify(options.filters));
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
- * @param data The record fields to store.
436
- * @param options Optional: queryableFields (for server-side filtering),
437
- * userEmail (audit trail), customRecordId (upsert by ID).
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(this.basePath, this.apiKey, body);
454
- return result.data ?? result.record;
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.basePath}/${encodeURIComponent(id)}`,
656
+ `${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
462
657
  this.apiKey
463
658
  );
464
- return result.data ?? result.record;
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 a record (PATCH semantics).
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 { merge = true } = options;
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
- `${this.basePath}/${encodeURIComponent(id)}`,
674
+ RECORDS.bucket(this.bucketKey),
486
675
  this.apiKey,
487
- { ...data, _merge: merge }
676
+ body
488
677
  );
489
- return result.data ?? result.record;
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.basePath}/${encodeURIComponent(id)}`,
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 multiple records in one request (max 500).
503
- * Each record can include a `_customRecordId` field for upsert behaviour.
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 created = await posts.batchCreate(
743
+ * const results = await posts.batchCreate(
508
744
  * [{ title: 'A' }, { title: 'B' }],
509
- * { queryableFields: ['title'], userEmail: 'alice@example.com' },
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
- `${this.basePath}/batch/insert`,
755
+ RECORDS.batchInsert(this.bucketKey),
520
756
  this.apiKey,
521
757
  body
522
758
  );
523
- return result.data ?? result.records ?? [];
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
- * Delete multiple records in one request (max 500).
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 batchDelete(ids) {
772
+ async batchUpdate(updates, userEmail) {
773
+ const body = { updates };
774
+ if (userEmail) body["userEmail"] = userEmail;
529
775
  const result = await this.http.post(
530
- `${this.basePath}/batch/delete`,
776
+ RECORDS.batchUpdate(this.bucketKey),
531
777
  this.apiKey,
532
- { recordIds: ids }
778
+ body
533
779
  );
534
780
  return {
535
- deleted: result.data?.successful ?? result.deleted ?? 0,
536
- failed: result.data?.failed ?? result.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 optional filters, sorting, and pagination.
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.basePath}/${encodeURIComponent(id)}?showHistory=true`,
823
+ `${RECORDS.bucket(this.bucketKey)}${qs}`,
587
824
  this.apiKey
588
825
  );
589
- return result.history;
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
- * Restore a record to a previous version.
834
+ * Retrieve all records matching options (no filter support — use `query()` for filters).
593
835
  */
594
- async restoreVersion(id, version) {
595
- const result = await this.http.post(
596
- `${this.basePath}/${encodeURIComponent(id)}/restore`,
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.basePath = `/analytics/${bucketKey}`;
848
+ this.bucketKey = bucketKey;
611
849
  }
612
850
  async run(query) {
613
851
  const result = await this.http.post(
614
- this.basePath,
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
- /** Get value distribution for a field (e.g. how many records per status). */
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
- /** Fetch filtered records via the analytics engine (bypasses Firestore pagination). */
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
- /** Compute multiple aggregations in a single request. */
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 every bucket in `bucketKeys`.
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 — use this when none of the typed helpers cover your use case.
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: Simple (server-buffered) ────────────────────────────────────
964
+ // ─── Upload: Server-buffered ──────────────────────────────────────────────
718
965
  /**
719
- * Upload a file to storage in one step (server-buffered, up to 500 MB).
720
- * For files >10 MB or when upload progress is needed, use `getUploadUrl()`.
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(`${this.basePath}/upload`, {
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 JSON or plain-text data as a file.
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(`${this.basePath}/upload-raw`, {
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 directly from the client.
766
- * Supports upload progress via `uploadToSignedUrl()`.
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: confirmedPath } = await storage.getUploadUrl({
771
- * path: 'videos/intro.mp4',
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 => console.log(pct + '%'));
777
- * const result = await storage.confirmUpload({ path: confirmedPath, mimeType: 'video/mp4', isPublic: true });
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(`${this.basePath}/upload-url`, {
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 a signed GCS URL.
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 on the server.
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(`${this.basePath}/confirm`, {
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 multiple files at once (max 50).
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
- * @example
819
- * ```ts
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
- `${this.basePath}/batch-upload-urls`,
833
- { method: "POST", body: { files }, headers: this.authHeaders }
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
- * Returns both succeeded and failed results.
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
- `${this.basePath}/batch-confirm`,
853
- { method: "POST", body: { files: items }, headers: this.authHeaders }
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(`${this.basePath}/download/${encoded}`, {
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 multiple files at once (max 20).
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
- * @example
878
- * ```ts
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
- `${this.basePath}/batch-download`,
888
- { method: "POST", body: { paths }, headers: this.authHeaders }
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
- * Returns separate `files` and `folders` arrays for easy consumption.
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(`${this.basePath}/list${qs}`, {
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
- folders.push(item.path);
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 for a file: size, MIME type, visibility, URLs.
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
- `${this.basePath}/metadata/${encoded}`,
959
- { method: "GET", headers: this.authHeaders }
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 without requiring an `X-Storage-Key`.
1220
+ * Can be shared externally no X-Storage-Key required to access.
976
1221
  *
977
- * > Note: Downloads via signed URLs bypass the server — download stats are NOT tracked.
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(`${this.basePath}/signed-url`, {
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 after upload.
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(`${this.basePath}/visibility`, {
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
- `${this.basePath}/folder`,
1016
- { method: "POST", body: { path }, headers: this.authHeaders }
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(`${this.basePath}/file`, {
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(`${this.basePath}/folder`, {
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
- `${this.basePath}/move`,
1038
- { method: "POST", body: { from, to }, headers: this.authHeaders }
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
- `${this.basePath}/copy`,
1045
- { method: "POST", body: { from, to }, headers: this.authHeaders }
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(`${this.basePath}/stats`, {
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
- // ─── Info (no auth) ───────────────────────────────────────────────────────
1346
+ /**
1347
+ * Get server info (no auth required).
1348
+ *
1349
+ * Server: GET /storage/info
1350
+ */
1058
1351
  async info() {
1059
- return this.http.request(`${this.basePath}/info`, {
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. The `prefix` option is relative to the scope root.
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 = storage.scope('users/alice/');
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. Get yours from https://hydrousdb.com/dashboard.");
1472
+ throw new Error("[HydrousDB] authKey is required.");
1180
1473
  }
1181
1474
  if (!config.bucketSecurityKey) {
1182
- throw new Error("[HydrousDB] bucketSecurityKey is required. Get yours from https://hydrousdb.com/dashboard.");
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 is required. Define at least one storage key from https://hydrousdb.com/dashboard.");
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 records client for the named bucket.
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 an auth client for the named user bucket.
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 auth = db.auth('app-users');
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(bucketKey) {
1226
- if (!this._authCache.has(bucketKey)) {
1227
- this._authCache.set(bucketKey, new AuthClient(this.http, this.authKey_, bucketKey));
1516
+ auth() {
1517
+ if (!this._authClient) {
1518
+ this._authClient = new AuthClient(this.http, this.authKey_);
1228
1519
  }
1229
- return this._authCache.get(bucketKey);
1520
+ return this._authClient;
1230
1521
  }
1231
1522
  // ─── Analytics ────────────────────────────────────────────────────────────
1232
1523
  /**
1233
- * Get an analytics client for the named bucket.
1524
+ * Get an AnalyticsClient for a bucket.
1234
1525
  *
1235
1526
  * @example
1236
1527
  * ```ts
1237
- * const analytics = db.analytics('orders');
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 storage manager for the named storage key.
1253
- * The name must match a key defined in `storageKeys` when calling `createClient`.
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 namespace all operations under a path prefix.
1545
+ * Attach `.scope('prefix/')` to get a path-prefixed ScopedStorage.
1256
1546
  *
1257
1547
  * @example
1258
1548
  * ```ts
1259
- * const avatars = db.storage('avatars');
1260
- * const userDocs = db.storage('documents').scope(`users/${userId}/`);
1549
+ * const avatars = db.storage('avatars');
1550
+ * const result = await avatars.upload(file, 'alice.jpg', { isPublic: true });
1261
1551
  *
1262
- * await avatars.upload(file, `${userId}.jpg`, { isPublic: true });
1263
- * await userDocs.upload(pdfBuffer, 'contract.pdf');
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