hydrousdb 3.0.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,67 +1,49 @@
1
+ /**
2
+ * The server base URL — no trailing slash, no /api suffix.
3
+ * Routes in routes.ts already include /api, /api/analytics, /api/auth, /storage.
4
+ */
5
+ declare const DEFAULT_BASE_URL = "https://db-api-82687684612.us-central1.run.app";
1
6
  interface RequestOptions {
2
- method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
7
+ method: string;
3
8
  body?: unknown;
9
+ rawBody?: FormData | Uint8Array | ArrayBuffer;
4
10
  headers?: Record<string, string>;
5
- rawBody?: string | FormData | Uint8Array<ArrayBuffer>;
6
- contentType?: string;
7
11
  }
8
12
  declare class HttpClient {
9
13
  private readonly baseUrl;
10
14
  constructor(baseUrl: string);
11
- request<T = unknown>(path: string, apiKeyOrOpts?: string | RequestOptions, opts?: RequestOptions): Promise<T>;
12
- get<T = unknown>(path: string, apiKey?: string, headers?: Record<string, string>): Promise<T>;
13
- post<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
14
- put<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
15
- patch<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
16
- delete<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
17
- putToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
15
+ request<T>(path: string, options: RequestOptions): Promise<T>;
16
+ get<T>(path: string, apiKey?: string, extraHeaders?: Record<string, string>): Promise<T>;
17
+ post<T>(path: string, apiKey: string, body?: unknown): Promise<T>;
18
+ put<T>(path: string, apiKey: string, body?: unknown): Promise<T>;
19
+ patch<T>(path: string, apiKey: string, body?: unknown): Promise<T>;
20
+ delete<T>(path: string, apiKey: string, body?: unknown): Promise<T>;
21
+ /**
22
+ * PUT directly to a signed GCS URL.
23
+ * Uses XHR in browsers for onprogress support; fetch in Node.
24
+ */
25
+ putToSignedUrl(signedUrl: string, data: Blob | Uint8Array | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
18
26
  }
19
27
 
20
- /**
21
- * Storage keys map — one named key per storage bucket/permission scope.
22
- * Keys start with `ssk_`. You can define as many as you need.
23
- *
24
- * @example
25
- * ```ts
26
- * storageKeys: {
27
- * main: 'ssk_main_…',
28
- * avatars: 'ssk_avatars_…',
29
- * documents: 'ssk_docs_…',
30
- * }
31
- * ```
32
- */
33
28
  type StorageKeys = Record<string, string>;
34
29
  interface HydrousConfig {
35
- /**
36
- * Auth service key (starts with `hk_auth_`).
37
- * Used exclusively for all `/auth/*` routes — signup, login, sessions, etc.
38
- * Obtain from your dashboard at https://hydrousdb.com/dashboard.
39
- */
30
+ /** Auth service key (starts with `hk_auth_`). Required for auth routes. */
40
31
  authKey: string;
41
32
  /**
42
33
  * Bucket security key (starts with `hk_bucket_`).
43
- * Used for all `/records/*` and `/analytics/*` routes.
44
- * Obtain from your dashboard at https://hydrousdb.com/dashboard.
34
+ * Used for records and analytics routes.
45
35
  */
46
36
  bucketSecurityKey: string;
47
37
  /**
48
- * Named storage keys (each starts with `ssk_`).
49
- * Define one key per storage bucket/permission scope.
50
- * Pass the name of the key you want when calling `db.storage('keyName')`.
51
- *
52
- * @example
53
- * ```ts
54
- * storageKeys: {
55
- * main: 'ssk_main_…', // default general-purpose bucket
56
- * avatars: 'ssk_avatars_…', // user avatar uploads
57
- * documents: 'ssk_docs_…', // private documents
58
- * }
59
- * ```
38
+ * Named storage keys each value starts with `ssk_`.
39
+ * Key names are arbitrary labels you choose; pass the same label to `db.storage('label')`.
40
+ * Example: { avatars: 'ssk_…', documents: 'ssk_…' }
60
41
  */
61
42
  storageKeys: StorageKeys;
62
43
  /**
63
- * Override the API base URL. Defaults to the official HydrousDB endpoint.
64
- * You almost never need to set this.
44
+ * Override the API base URL.
45
+ * Defaults to the official HydrousDB endpoint.
46
+ * Should NOT include /api — route paths are fully qualified in routes.ts.
65
47
  */
66
48
  baseUrl?: string;
67
49
  }
@@ -103,24 +85,26 @@ interface AuthResult {
103
85
  interface UpdateUserOptions {
104
86
  sessionId: string;
105
87
  userId: string;
106
- data: Partial<Omit<UserRecord, 'id' | 'email' | 'createdAt'>>;
88
+ /** Fields to update. Sent as `updates: {...}` to the server. */
89
+ updates: Partial<Omit<UserRecord, 'id' | 'email' | 'createdAt'>>;
107
90
  }
108
91
  interface ChangePasswordOptions {
109
92
  sessionId: string;
110
93
  userId: string;
94
+ /** Sent to server as `oldPassword`. */
111
95
  currentPassword: string;
112
96
  newPassword: string;
113
97
  }
114
98
  interface ListUsersOptions {
115
99
  sessionId: string;
116
100
  limit?: number;
117
- offset?: number;
101
+ /** URL-encoded cursor returned by the previous page. */
102
+ cursor?: string;
118
103
  }
119
104
  interface ListUsersResult {
120
105
  users: UserRecord[];
121
- total: number;
122
- limit: number;
123
- offset: number;
106
+ hasMore: boolean;
107
+ nextCursor: string | null;
124
108
  }
125
109
  type RecordData = Record<string, unknown>;
126
110
  interface RecordResult {
@@ -131,7 +115,7 @@ interface RecordResult {
131
115
  }
132
116
  interface QueryFilter {
133
117
  field: string;
134
- op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'CONTAINS';
118
+ op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'contains';
135
119
  value: string | number | boolean;
136
120
  }
137
121
  interface QueryOptions {
@@ -141,6 +125,10 @@ interface QueryOptions {
141
125
  offset?: number;
142
126
  orderBy?: string;
143
127
  order?: 'asc' | 'desc';
128
+ /**
129
+ * Pagination cursor — pass the `nextCursor` from the previous page.
130
+ * Maps to `?cursor=` on the server.
131
+ */
144
132
  startAfter?: string;
145
133
  startAt?: string;
146
134
  endAt?: string;
@@ -156,19 +144,36 @@ interface PatchRecordOptions {
156
144
  merge?: boolean;
157
145
  }
158
146
  interface RecordHistoryEntry {
159
- id: string;
160
- version: number;
161
- createdAt: number;
162
- data: RecordData;
147
+ generation: number | null;
148
+ savedAt: number | null;
149
+ savedBy: string | null;
150
+ sizeBytes: number | null;
151
+ }
152
+ /** Options for `records.create()`. */
153
+ interface CreateRecordOptions {
154
+ /**
155
+ * Fields to index for server-side filtering.
156
+ * Only listed fields will be queryable via `query({ filters: [...] })`.
157
+ */
158
+ queryableFields?: string[];
159
+ /** User email for audit trails. */
160
+ userEmail?: string;
161
+ /**
162
+ * Custom record ID for upsert behaviour.
163
+ * Format: `YYMMDD-segment1__segment2` (e.g. `260307-user_alice__post_1`).
164
+ * If the ID already exists the record is updated in-place.
165
+ */
166
+ customRecordId?: string;
167
+ }
168
+ /** Options for `records.batchCreate()`. */
169
+ interface BatchCreateOptions {
170
+ queryableFields?: string[];
171
+ userEmail?: string;
163
172
  }
164
173
  interface UploadOptions {
165
- /** Set to true to make the file publicly accessible without authentication. */
166
174
  isPublic?: boolean;
167
- /** Set to true to overwrite an existing file at the same path. */
168
175
  overwrite?: boolean;
169
- /** MIME type of the file. Auto-detected if omitted. */
170
176
  mimeType?: string;
171
- /** TTL in seconds for the signed upload URL (default 900 = 15 min). */
172
177
  expiresInSeconds?: number;
173
178
  }
174
179
  interface UploadResult {
@@ -195,6 +200,7 @@ interface ListOptions {
195
200
  interface FileEntry {
196
201
  name: string;
197
202
  path: string;
203
+ type?: 'file' | 'folder';
198
204
  size?: number;
199
205
  mimeType?: string;
200
206
  isPublic?: boolean;
@@ -243,14 +249,27 @@ interface BatchUploadUrlResult {
243
249
  index: number;
244
250
  }>;
245
251
  }
252
+ interface BatchDownloadResult {
253
+ succeeded: Array<{
254
+ index: number;
255
+ path: string;
256
+ mimeType: string;
257
+ size: number;
258
+ content: string;
259
+ }>;
260
+ failed: Array<{
261
+ index: number;
262
+ path: string;
263
+ error: string;
264
+ code?: string;
265
+ }>;
266
+ }
246
267
  type QueryType = 'count' | 'distribution' | 'sum' | 'timeSeries' | 'fieldTimeSeries' | 'topN' | 'stats' | 'records' | 'multiMetric' | 'storageStats' | 'crossBucket';
247
268
  type Aggregation = 'sum' | 'avg' | 'min' | 'max' | 'count';
248
269
  type Granularity = 'hour' | 'day' | 'week' | 'month' | 'year';
249
270
  type SortOrder = 'asc' | 'desc';
250
271
  interface DateRange {
251
- /** Start timestamp in milliseconds (Unix epoch). */
252
272
  start?: number;
253
- /** End timestamp in milliseconds (Unix epoch). */
254
273
  end?: number;
255
274
  }
256
275
  interface AnalyticsFilter {
@@ -259,11 +278,8 @@ interface AnalyticsFilter {
259
278
  value: string | number | boolean;
260
279
  }
261
280
  interface MetricDefinition {
262
- /** Field name in your records to aggregate. */
263
281
  field: string;
264
- /** Alias used in the result object. Must be a safe identifier. */
265
282
  name: string;
266
- /** Aggregation function. Defaults to 'count'. */
267
283
  aggregation?: Aggregation;
268
284
  }
269
285
  interface AnalyticsQuery {
@@ -282,7 +298,6 @@ interface AnalyticsQuery {
282
298
  order?: SortOrder;
283
299
  n?: number;
284
300
  metrics?: MetricDefinition[];
285
- /** For crossBucket queries: list of bucket names to compare. */
286
301
  bucketKeys?: string[];
287
302
  }
288
303
  interface AnalyticsResult<T = unknown> {
@@ -337,54 +352,127 @@ interface CrossBucketRow {
337
352
  }
338
353
 
339
354
  /**
340
- * AuthClient — full user authentication for a single bucket.
341
- * Uses the `authKey` (`hk_auth_…`) sent via `X-Api-Key` header.
355
+ * AuthClient — user authentication for a project.
356
+ *
357
+ * All routes are under `/api/auth` (NOT `/api/auth/:bucketKey`).
358
+ * The server resolves which user bucket to use from the API key itself.
359
+ * Uses `X-Api-Key: <authKey>`.
342
360
  *
343
361
  * @example
344
362
  * ```ts
345
- * const db = createClient({ authKey: 'hk_auth_…', bucketSecurityKey: '…', storageKeys: { main: '…' } });
346
- * const auth = db.auth('my-app-users');
363
+ * const auth = db.auth();
347
364
  * const { user, session } = await auth.signup({ email: 'alice@example.com', password: 'hunter2' });
348
365
  * ```
349
366
  */
350
367
  declare class AuthClient {
351
368
  private readonly http;
352
369
  private readonly authKey;
353
- private readonly basePath;
354
- constructor(http: HttpClient, authKey: string, bucketKey: string);
370
+ constructor(http: HttpClient, authKey: string);
355
371
  private post;
356
372
  private get;
357
373
  private patch;
358
- private delete;
374
+ /**
375
+ * Register a new user account.
376
+ *
377
+ * Server: POST /api/auth/signup
378
+ * Body: { email, password, fullName?, ...extraData }
379
+ */
359
380
  signup(options: SignupOptions): Promise<AuthResult>;
381
+ /**
382
+ * Sign in with email + password.
383
+ *
384
+ * Server: POST /api/auth/signin (NOT /login)
385
+ * Body: { email, password }
386
+ */
360
387
  login(options: LoginOptions): Promise<AuthResult>;
361
- logout({ sessionId }: {
388
+ /**
389
+ * Sign out — revoke a session (or all sessions with `allDevices: true`).
390
+ *
391
+ * Server: POST /api/auth/signout
392
+ * Body: { sessionId, allDevices? }
393
+ */
394
+ logout(options: {
362
395
  sessionId: string;
396
+ allDevices?: boolean;
363
397
  }): Promise<void>;
364
- refreshSession({ refreshToken }: {
365
- refreshToken: string;
366
- }): Promise<Session>;
367
- getUser({ userId }: {
368
- userId: string;
369
- }): Promise<UserRecord>;
398
+ /**
399
+ * Validate an existing session and retrieve the current user.
400
+ *
401
+ * Server: POST /api/auth/session/validate
402
+ * Body: { sessionId }
403
+ */
404
+ validateSession(sessionId: string): Promise<{
405
+ user: UserRecord;
406
+ session: {
407
+ sessionId: string;
408
+ expiresAt: number;
409
+ };
410
+ }>;
411
+ /**
412
+ * Rotate a refresh token to get a new session.
413
+ *
414
+ * Server: POST /api/auth/session/refresh
415
+ * Body: { refreshToken }
416
+ */
417
+ refreshSession(refreshToken: string): Promise<Session>;
418
+ /**
419
+ * Fetch a user by ID.
420
+ *
421
+ * Server: GET /api/auth/user?userId=:userId
422
+ */
423
+ getUser(userId: string): Promise<UserRecord>;
424
+ /**
425
+ * Update a user's profile fields.
426
+ * Regular users can only update themselves; admins can update any user.
427
+ *
428
+ * Server: PATCH /api/auth/user
429
+ * Body: { sessionId, userId, updates: { ...fields } }
430
+ */
370
431
  updateUser(options: UpdateUserOptions): Promise<UserRecord>;
371
- deleteUser({ sessionId, userId }: {
372
- sessionId: string;
373
- userId: string;
374
- }): Promise<void>;
432
+ /**
433
+ * Soft-delete a user account.
434
+ * Regular users can only delete themselves; admins can delete any user.
435
+ *
436
+ * Server: DELETE /api/auth/user?userId=:userId
437
+ * Body: { sessionId }
438
+ */
439
+ deleteUser(sessionId: string, userId: string): Promise<void>;
440
+ /**
441
+ * List all users (paginated). Admin only.
442
+ *
443
+ * Server: GET /api/auth/users?limit=&cursor=
444
+ * The sessionId is passed via the X-Session-Id header (GET body is unreliable).
445
+ */
375
446
  listUsers(options: ListUsersOptions): Promise<ListUsersResult>;
376
- hardDeleteUser({ sessionId, userId }: {
377
- sessionId: string;
378
- userId: string;
379
- }): Promise<void>;
380
- bulkDeleteUsers({ sessionId, userIds }: {
447
+ /**
448
+ * Permanently delete a user (hard delete — cannot be undone).
449
+ * Admin only. Charged at 1 HC per deletion.
450
+ *
451
+ * Server: DELETE /api/auth/user/hard?userId=:userId
452
+ * Body: { sessionId }
453
+ */
454
+ hardDeleteUser(sessionId: string, userId: string): Promise<void>;
455
+ /**
456
+ * Delete multiple users at once (soft or hard). Admin only.
457
+ *
458
+ * Server: DELETE /api/auth/users/bulk
459
+ * Body: { userIds, hard?, sessionId }
460
+ */
461
+ bulkDeleteUsers(options: {
381
462
  sessionId: string;
382
463
  userIds: string[];
464
+ hard?: boolean;
383
465
  }): Promise<{
384
- deleted: number;
385
- failed: string[];
466
+ succeeded: number;
467
+ failed: number;
386
468
  }>;
387
- lockAccount({ sessionId, userId, duration }: {
469
+ /**
470
+ * Lock a user account for a specified duration. Admin only.
471
+ *
472
+ * Server: POST /api/auth/account/lock
473
+ * Body: { sessionId, userId, duration? } — duration in ms, default 15 min
474
+ */
475
+ lockAccount(options: {
388
476
  sessionId: string;
389
477
  userId: string;
390
478
  duration?: number;
@@ -392,103 +480,252 @@ declare class AuthClient {
392
480
  lockedUntil: number;
393
481
  unlockTime: string;
394
482
  }>;
395
- unlockAccount({ sessionId, userId }: {
396
- sessionId: string;
397
- userId: string;
398
- }): Promise<void>;
483
+ /**
484
+ * Unlock a locked user account. Admin only.
485
+ *
486
+ * Server: POST /api/auth/account/unlock
487
+ * Body: { sessionId, userId }
488
+ */
489
+ unlockAccount(sessionId: string, userId: string): Promise<void>;
490
+ /**
491
+ * Change a user's password. Requires both a valid session AND the old password.
492
+ *
493
+ * Server: POST /api/auth/password/change
494
+ * Body: { sessionId, userId, oldPassword, newPassword }
495
+ */
399
496
  changePassword(options: ChangePasswordOptions): Promise<void>;
400
- requestPasswordReset({ email }: {
401
- email: string;
402
- }): Promise<void>;
403
- confirmPasswordReset({ resetToken, newPassword }: {
404
- resetToken: string;
405
- newPassword: string;
406
- }): Promise<void>;
407
- requestEmailVerification({ userId }: {
408
- userId: string;
409
- }): Promise<void>;
410
- confirmEmailVerification({ verifyToken }: {
411
- verifyToken: string;
412
- }): Promise<void>;
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
+ requestPasswordReset(email: string): Promise<void>;
505
+ /**
506
+ * Confirm a password reset using the token from the reset email.
507
+ *
508
+ * Server: POST /api/auth/password/reset/confirm
509
+ * Body: { resetToken, newPassword }
510
+ */
511
+ confirmPasswordReset(resetToken: string, newPassword: string): Promise<void>;
512
+ /**
513
+ * Request a verification email be sent to a user.
514
+ *
515
+ * Server: POST /api/auth/email/verify/request
516
+ * Body: { userId }
517
+ */
518
+ requestEmailVerification(userId: string): Promise<void>;
519
+ /**
520
+ * Confirm email ownership using the token from the verification email.
521
+ *
522
+ * Server: POST /api/auth/email/verify/confirm
523
+ * Body: { verifyToken }
524
+ */
525
+ confirmEmailVerification(verifyToken: string): Promise<void>;
526
+ private _buildAuthResult;
413
527
  }
414
528
 
415
529
  /**
416
- * RecordsClient — CRUD + query for a single bucket.
417
- * Uses the `bucketSecurityKey` (`hk_bucket_…`) sent via `X-Api-Key` header.
530
+ * RecordsClient — typed CRUD + batch + query for a single bucket.
531
+ *
532
+ * All requests use `X-Api-Key: <bucketSecurityKey>`.
533
+ * Routes: GET|POST|PATCH|DELETE /api/:bucketKey
418
534
  *
419
535
  * @example
420
536
  * ```ts
421
- * const db = createClient({ authKey: '…', bucketSecurityKey: 'hk_bucket_…', storageKeys: { main: '…' } });
422
- * const posts = db.records('blog-posts');
423
- * const post = await posts.create({ title: 'Hello World', status: 'draft' });
537
+ * interface Post { title: string; published: boolean }
538
+ * const posts = db.records<Post>('blog-posts');
539
+ * const post = await posts.create({ title: 'Hello', published: false });
424
540
  * ```
425
541
  */
426
542
  declare class RecordsClient<T extends RecordData = RecordData> {
427
543
  private readonly http;
428
544
  private readonly bucketKey;
429
- private readonly basePath;
430
- private readonly bucketKey_;
545
+ private readonly apiKey;
431
546
  constructor(http: HttpClient, bucketSecurityKey: string, bucketKey: string);
432
- private get key();
433
- create(data: T): Promise<T & RecordResult>;
547
+ /**
548
+ * Create a new record (auto-generated ID) or upsert by `customRecordId`.
549
+ *
550
+ * Server: POST /api/:bucketKey
551
+ * Body: { values, queryableFields?, userEmail?, customRecordId? }
552
+ * Returns 201 for new records, 200 for upserts.
553
+ *
554
+ * @example
555
+ * ```ts
556
+ * const post = await posts.create(
557
+ * { title: 'Hello', status: 'draft', authorId: 'u1' },
558
+ * { queryableFields: ['status', 'authorId'], userEmail: 'alice@example.com' },
559
+ * );
560
+ * ```
561
+ */
562
+ create(data: T, options?: CreateRecordOptions): Promise<T & RecordResult>;
563
+ /**
564
+ * Get a single record by ID.
565
+ *
566
+ * Server: GET /api/:bucketKey?recordId=:id
567
+ */
434
568
  get(id: string): Promise<T & RecordResult>;
435
- set(id: string, data: T): Promise<T & RecordResult>;
436
- patch(id: string, data: Partial<T>, options?: PatchRecordOptions): Promise<T & RecordResult>;
569
+ /**
570
+ * Partially update an existing record (PATCH semantics).
571
+ * Supports write-filter sentinels: `{ __op: 'increment', delta: 1 }` etc.
572
+ *
573
+ * Server: PATCH /api/:bucketKey
574
+ * Body: { recordId, values, userEmail?, track_record_history? }
575
+ */
576
+ patch(id: string, data: Partial<T>, options?: PatchRecordOptions & {
577
+ userEmail?: string;
578
+ trackHistory?: boolean;
579
+ }): Promise<{
580
+ id: string;
581
+ updatedAt?: number;
582
+ }>;
583
+ /**
584
+ * Delete a record permanently.
585
+ *
586
+ * Server: DELETE /api/:bucketKey?recordId=:id
587
+ */
437
588
  delete(id: string): Promise<void>;
438
- batchCreate(items: T[]): Promise<(T & RecordResult)[]>;
439
- batchDelete(ids: string[]): Promise<{
440
- deleted: number;
589
+ /**
590
+ * Check whether a record exists (HEAD request — very lightweight).
591
+ *
592
+ * Server: HEAD /api/:bucketKey?recordId=:id
593
+ * Returns true if the record exists, false if 404.
594
+ */
595
+ exists(id: string): Promise<boolean>;
596
+ /**
597
+ * Get a historical snapshot of a record at a specific generation.
598
+ *
599
+ * Server: GET /api/:bucketKey?recordId=:id&generation=:gen
600
+ */
601
+ getVersion(id: string, generation: string | number): Promise<T & RecordResult>;
602
+ /**
603
+ * Get the version history of a record.
604
+ *
605
+ * Server: GET /api/:bucketKey?recordId=:id&showHistory=true
606
+ */
607
+ getHistory(id: string): Promise<RecordHistoryEntry[]>;
608
+ /**
609
+ * Create up to 500 records in one request.
610
+ * Each record may include `_customRecordId` for upsert behaviour.
611
+ *
612
+ * Server: POST /api/:bucketKey/batch/insert
613
+ * Body: { records, queryableFields?, userEmail? }
614
+ *
615
+ * @example
616
+ * ```ts
617
+ * const results = await posts.batchCreate(
618
+ * [{ title: 'A' }, { title: 'B' }],
619
+ * { queryableFields: ['title'] },
620
+ * );
621
+ * ```
622
+ */
623
+ batchCreate(items: T[], options?: BatchCreateOptions): Promise<{
624
+ results: (T & RecordResult)[];
625
+ errors: unknown[];
626
+ successful: number;
627
+ failed: number;
628
+ }>;
629
+ /**
630
+ * Update up to 500 records in one request.
631
+ *
632
+ * Server: POST /api/:bucketKey/batch/update
633
+ * Body: { updates: [{ recordId, values }], userEmail? }
634
+ */
635
+ batchUpdate(updates: Array<{
636
+ recordId: string;
637
+ values: Partial<T>;
638
+ }>, userEmail?: string): Promise<{
639
+ successful: number;
441
640
  failed: string[];
442
641
  }>;
642
+ /**
643
+ * Delete up to 500 records in one request.
644
+ *
645
+ * Server: POST /api/:bucketKey/batch/delete
646
+ * Body: { recordIds, userEmail? }
647
+ */
648
+ batchDelete(ids: string[], userEmail?: string): Promise<{
649
+ successful: number;
650
+ failed: string[];
651
+ }>;
652
+ /**
653
+ * Query records with filters, sorting, and cursor-based pagination.
654
+ *
655
+ * Server: GET /api/:bucketKey?field=value&field[op]=value&limit=&sortBy=&sortOrder=&cursor=
656
+ *
657
+ * @example
658
+ * ```ts
659
+ * const { records, hasMore, nextCursor } = await posts.query({
660
+ * filters: [{ field: 'status', op: '==', value: 'published' }],
661
+ * orderBy: 'createdAt',
662
+ * order: 'desc',
663
+ * limit: 20,
664
+ * });
665
+ * ```
666
+ */
443
667
  query(options?: QueryOptions): Promise<QueryResult<T>>;
668
+ /**
669
+ * Retrieve all records matching options (no filter support — use `query()` for filters).
670
+ */
444
671
  getAll(options?: Omit<QueryOptions, 'filters'>): Promise<(T & RecordResult)[]>;
445
- count(filters?: QueryOptions['filters']): Promise<number>;
446
- getHistory(id: string): Promise<RecordHistoryEntry[]>;
447
- restoreVersion(id: string, version: number): Promise<T & RecordResult>;
448
672
  }
449
673
 
450
674
  /**
451
- * AnalyticsClient — BigQuery-powered aggregations for a bucket.
452
- * Uses the `bucketSecurityKey` (`hk_bucket_…`) sent via `X-Api-Key` header.
675
+ * AnalyticsClient — BigQuery-powered aggregations for a single bucket.
676
+ *
677
+ * All methods POST a `queryType` body to `POST /api/analytics/:bucketKey`.
678
+ * The `dateRange` is passed as `{ start, end }` ms timestamps — the server
679
+ * converts them to ISO date strings internally.
680
+ * Uses `X-Api-Key: <bucketSecurityKey>`.
453
681
  *
454
682
  * @example
455
683
  * ```ts
456
- * const db = createClient({ authKey: '…', bucketSecurityKey: 'hk_bucket_…', storageKeys: { main: '…' } });
457
684
  * const analytics = db.analytics('orders');
458
685
  * const { count } = await analytics.count();
686
+ * const top5 = await analytics.topN({ field: 'country', n: 5 });
459
687
  * ```
460
688
  */
461
689
  declare class AnalyticsClient {
462
690
  private readonly http;
463
691
  private readonly bucketSecurityKey;
464
- private readonly basePath;
692
+ private readonly bucketKey;
465
693
  constructor(http: HttpClient, bucketSecurityKey: string, bucketKey: string);
466
694
  private run;
695
+ /** Count all records, optionally within a date range. */
467
696
  count(opts?: {
468
697
  dateRange?: DateRange;
469
698
  }): Promise<CountResult>;
699
+ /**
700
+ * Get value distribution for a field (e.g. records per status).
701
+ * `order` maps to `sortBy` on the wire (the server param name).
702
+ */
470
703
  distribution(opts: {
471
704
  field: string;
472
705
  limit?: number;
473
706
  order?: SortOrder;
474
707
  dateRange?: DateRange;
475
708
  }): Promise<DistributionRow[]>;
709
+ /** Sum a numeric field, optionally grouped by another field. */
476
710
  sum(opts: {
477
711
  field: string;
478
712
  groupBy?: string;
479
713
  limit?: number;
480
714
  dateRange?: DateRange;
481
715
  }): Promise<SumRow[]>;
716
+ /** Count of records over time, bucketed by granularity. */
482
717
  timeSeries(opts?: {
483
718
  granularity?: Granularity;
484
719
  dateRange?: DateRange;
485
720
  }): Promise<TimeSeriesRow[]>;
721
+ /** Aggregate a numeric field over time. */
486
722
  fieldTimeSeries(opts: {
487
723
  field: string;
488
724
  aggregation?: Aggregation;
489
725
  granularity?: Granularity;
490
726
  dateRange?: DateRange;
491
727
  }): Promise<FieldTimeSeriesRow[]>;
728
+ /** Top N values for a field by count. */
492
729
  topN(opts: {
493
730
  field: string;
494
731
  n?: number;
@@ -496,10 +733,15 @@ declare class AnalyticsClient {
496
733
  order?: SortOrder;
497
734
  dateRange?: DateRange;
498
735
  }): Promise<TopNRow[]>;
736
+ /** Statistical summary (min, max, avg, sum, count, stddev) for a numeric field. */
499
737
  stats(opts: {
500
738
  field: string;
501
739
  dateRange?: DateRange;
502
740
  }): Promise<FieldStats>;
741
+ /**
742
+ * Fetch filtered records via the analytics engine (BigQuery).
743
+ * Bypasses Firestore pagination — useful for large result sets.
744
+ */
503
745
  records<T extends RecordData = RecordData>(opts?: {
504
746
  filters?: AnalyticsFilter[];
505
747
  selectFields?: string[];
@@ -509,97 +751,96 @@ declare class AnalyticsClient {
509
751
  order?: SortOrder;
510
752
  dateRange?: DateRange;
511
753
  }): Promise<(T & RecordResult)[]>;
754
+ /**
755
+ * Compute multiple aggregations in a single request.
756
+ * `metrics` must have valid `field` and `name` (used as BigQuery column aliases).
757
+ */
512
758
  multiMetric(opts: {
513
759
  metrics: MetricDefinition[];
514
760
  dateRange?: DateRange;
515
761
  }): Promise<MultiMetricResult>;
762
+ /** Storage usage stats for the bucket (record count, bytes, avg/min/max size). */
516
763
  storageStats(opts?: {
517
764
  dateRange?: DateRange;
518
765
  }): Promise<StorageStatsResult>;
766
+ /**
767
+ * Compare a metric across multiple buckets in one query.
768
+ * The caller's key must have read access to EVERY bucket in `bucketKeys`.
769
+ * System buckets (`users`, `_sys_*`) are blocked server-side.
770
+ */
519
771
  crossBucket(opts: {
520
772
  bucketKeys: string[];
521
773
  field: string;
522
774
  aggregation?: Aggregation;
523
775
  dateRange?: DateRange;
524
776
  }): Promise<CrossBucketRow[]>;
777
+ /**
778
+ * Raw query — escape hatch when the typed helpers don't cover your case.
779
+ */
525
780
  query<T = unknown>(query: AnalyticsQuery): Promise<AnalyticsResult<T>>;
526
781
  }
527
782
 
528
783
  /**
529
784
  * StorageManager — upload, download, list, move, copy, and delete files.
530
- * Uses an `X-Storage-Key` (`ssk_…`) header — separate from auth and bucket keys.
531
- * Each key is scoped to a specific storage bucket and permission set.
532
785
  *
533
- * Get a StorageManager via `db.storage('keyName')` where the key name
534
- * matches one of the keys you defined in `storageKeys` when calling `createClient`.
786
+ * Uses `X-Storage-Key: <ssk_…>` header separate from auth and bucket keys.
787
+ * Files are scoped server-side to `hydrous-storage/{ownerId}/{userPath}`.
788
+ * You only ever deal with your own `userPath` — the server handles scoping.
789
+ *
790
+ * Get a StorageManager via `db.storage('keyName')` where the name matches
791
+ * one of the keys defined in `storageKeys` when calling `createClient`.
535
792
  *
536
793
  * @example
537
794
  * ```ts
538
- * const db = createClient({ authKey: '…', bucketSecurityKey: '…', storageKeys: { avatars: 'ssk_avatars_…' } });
539
- * const avatars = db.storage('avatars');
540
- * const result = await avatars.upload(file, 'alice.jpg', { isPublic: true });
795
+ * const storage = db.storage('avatars');
796
+ * const result = await storage.upload(file, 'alice.jpg', { isPublic: true });
797
+ * console.log(result.publicUrl);
541
798
  * ```
542
799
  */
543
800
  declare class StorageManager {
544
801
  private readonly http;
545
802
  private readonly storageKey;
546
- private readonly basePath;
547
803
  constructor(http: HttpClient, storageKey: string);
548
- /** Headers for all storage requests — uses X-Storage-Key, not X-Api-Key. */
549
804
  private get authHeaders();
550
805
  /**
551
- * Upload a file to storage in one step (server-buffered, up to 500 MB).
552
- * For files >10 MB or when you need upload progress, use `getUploadUrl()` instead.
806
+ * Upload a file in one step (server-buffered, up to 500 MB).
807
+ * For files >10 MB or when upload progress tracking is needed, use the
808
+ * signed URL flow: `getUploadUrl()` → `uploadToSignedUrl()` → `confirmUpload()`.
553
809
  *
554
- * @param data File data as a Blob, Buffer, Uint8Array, or ArrayBuffer.
555
- * @param path Destination path in your storage (e.g. `"avatars/alice.jpg"`).
556
- * @param options Upload options: isPublic, overwrite, mimeType.
810
+ * Server: POST /storage/upload (multipart/form-data)
557
811
  *
558
812
  * @example
559
813
  * ```ts
560
- * // Upload a public avatar
561
814
  * const result = await storage.upload(file, 'avatars/alice.jpg', { isPublic: true });
562
- * console.log(result.publicUrl); // → https://...
563
- *
564
- * // Upload a private document
565
- * const result = await storage.upload(pdfBuffer, 'docs/contract.pdf');
566
- * console.log(result.downloadUrl); // → /storage/download/docs/contract.pdf
815
+ * console.log(result.publicUrl);
567
816
  * ```
568
817
  */
569
- upload(data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
818
+ upload(data: Blob | Uint8Array | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
570
819
  /**
571
- * Upload raw JSON or plain text data as a file.
820
+ * Upload raw string or JSON data directly as a file.
821
+ *
822
+ * Server: POST /storage/upload-raw
823
+ * Body: { path, content, mimeType?, isPublic?, overwrite? }
572
824
  *
573
825
  * @example
574
826
  * ```ts
575
- * const result = await storage.uploadRaw(
576
- * { config: { theme: 'dark' } },
577
- * 'settings/user-config.json',
578
- * { isPublic: false },
579
- * );
827
+ * await storage.uploadRaw({ theme: 'dark' }, 'settings/config.json');
580
828
  * ```
581
829
  */
582
830
  uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
583
831
  /**
584
- * Step 1 of the recommended upload flow.
585
- * Get a signed URL to upload directly to GCS from the client (supports progress).
832
+ * Step 1 get a signed GCS PUT URL for direct client-to-GCS upload.
833
+ *
834
+ * Server: POST /storage/upload-url
835
+ * Body: { path, mimeType, size, isPublic?, overwrite?, expiresIn? }
586
836
  *
587
837
  * @example
588
838
  * ```ts
589
- * const { uploadUrl, path: confirmedPath } = await storage.getUploadUrl({
590
- * path: 'videos/intro.mp4',
591
- * mimeType: 'video/mp4',
592
- * size: file.size,
593
- * isPublic: true,
594
- * });
595
- *
596
- * // Upload using XHR for progress tracking
597
- * await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', (pct) => {
598
- * console.log(`${pct}% uploaded`);
839
+ * const { uploadUrl, path: p } = await storage.getUploadUrl({
840
+ * path: 'videos/intro.mp4', mimeType: 'video/mp4', size: file.size,
599
841
  * });
600
- *
601
- * // Step 3: confirm
602
- * const result = await storage.confirmUpload({ path: confirmedPath, mimeType: 'video/mp4', isPublic: true });
842
+ * await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', pct => setProgress(pct));
843
+ * const result = await storage.confirmUpload({ path: p, mimeType: 'video/mp4' });
603
844
  * ```
604
845
  */
605
846
  getUploadUrl(opts: {
@@ -611,28 +852,15 @@ declare class StorageManager {
611
852
  expiresInSeconds?: number;
612
853
  }): Promise<UploadUrlResult>;
613
854
  /**
614
- * Upload data directly to a signed GCS URL (no auth headers needed).
615
- * Optionally tracks progress via a callback.
616
- *
617
- * @param signedUrl The URL returned by `getUploadUrl()`.
618
- * @param data File data.
619
- * @param mimeType Must match what was used in `getUploadUrl()`.
620
- * @param onProgress Optional callback called with 0–100 progress percentage.
855
+ * Step 2 — upload data directly to the signed GCS URL.
856
+ * Supports progress tracking in browser environments via XHR.
621
857
  */
622
- uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
858
+ uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
623
859
  /**
624
- * Step 3 of the recommended upload flow.
625
- * Confirm a direct upload and register metadata on the server.
860
+ * Step 3 confirm a direct upload and register metadata server-side.
626
861
  *
627
- * @example
628
- * ```ts
629
- * const result = await storage.confirmUpload({
630
- * path: 'videos/intro.mp4',
631
- * mimeType: 'video/mp4',
632
- * isPublic: true,
633
- * });
634
- * console.log(result.publicUrl);
635
- * ```
862
+ * Server: POST /storage/confirm
863
+ * Body: { path, mimeType, isPublic? }
636
864
  */
637
865
  confirmUpload(opts: {
638
866
  path: string;
@@ -640,112 +868,79 @@ declare class StorageManager {
640
868
  isPublic?: boolean;
641
869
  }): Promise<UploadResult>;
642
870
  /**
643
- * Get signed upload URLs for multiple files at once.
871
+ * Get signed upload URLs for up to 50 files at once.
644
872
  *
645
- * @example
646
- * ```ts
647
- * const { files } = await storage.getBatchUploadUrls([
648
- * { path: 'images/photo1.jpg', mimeType: 'image/jpeg', size: 204800 },
649
- * { path: 'images/photo2.jpg', mimeType: 'image/jpeg', size: 153600 },
650
- * ]);
651
- *
652
- * // Upload each file and confirm
653
- * for (const f of files) {
654
- * await storage.uploadToSignedUrl(f.uploadUrl, blobs[f.index], f.mimeType);
655
- * await storage.confirmUpload({ path: f.path, mimeType: f.mimeType });
656
- * }
657
- * ```
873
+ * Server: POST /storage/batch-upload-urls
874
+ * Body: { files: [...], expiresIn? }
658
875
  */
659
876
  getBatchUploadUrls(files: BatchUploadItem[]): Promise<BatchUploadUrlResult>;
660
877
  /**
661
878
  * Confirm multiple direct uploads at once.
879
+ *
880
+ * Server: POST /storage/batch-confirm
881
+ * Body: { files: [{ path, mimeType, isPublic? }] }
662
882
  */
663
883
  batchConfirmUploads(items: Array<{
664
884
  path: string;
665
885
  mimeType: string;
666
886
  isPublic?: boolean;
667
- }>): Promise<UploadResult[]>;
887
+ }>): Promise<{
888
+ succeeded: UploadResult[];
889
+ failed: Array<{
890
+ path: string;
891
+ error: string;
892
+ }>;
893
+ }>;
668
894
  /**
669
895
  * Download a private file as an ArrayBuffer.
670
896
  * For public files, use the `publicUrl` directly — no SDK needed.
671
897
  *
672
- * @example
673
- * ```ts
674
- * const buffer = await storage.download('docs/contract.pdf');
675
- * const blob = new Blob([buffer], { type: 'application/pdf' });
676
- * // Open in browser:
677
- * window.open(URL.createObjectURL(blob));
678
- * ```
898
+ * Server: GET /storage/download/:path (requires X-Storage-Key)
679
899
  */
680
900
  download(path: string): Promise<ArrayBuffer>;
681
901
  /**
682
- * Download multiple files at once, returned as a JSON map of `{ path: base64 }`.
902
+ * Download up to 20 files at once. Returns base64-encoded content.
683
903
  *
684
- * @example
685
- * ```ts
686
- * const files = await storage.batchDownload(['docs/a.pdf', 'docs/b.pdf']);
687
- * ```
904
+ * Server: POST /storage/batch-download
905
+ * Body: { paths, concurrency? }
688
906
  */
689
- batchDownload(paths: string[]): Promise<Record<string, string>>;
907
+ batchDownload(paths: string[], concurrency?: number): Promise<BatchDownloadResult>;
690
908
  /**
691
909
  * List files and folders at a given path prefix.
692
910
  *
911
+ * Server: GET /storage/list?prefix=&limit=&cursor=
912
+ *
693
913
  * @example
694
914
  * ```ts
695
- * // List everything in the root
696
- * const { files, folders } = await storage.list();
697
- *
698
- * // List a specific folder
699
- * const { files, folders, hasMore, nextCursor } = await storage.list({
700
- * prefix: 'avatars/',
701
- * limit: 20,
702
- * });
703
- *
704
- * // Next page
915
+ * const { files, folders, hasMore, nextCursor } = await storage.list({ prefix: 'avatars/', limit: 20 });
705
916
  * const page2 = await storage.list({ prefix: 'avatars/', cursor: nextCursor });
706
917
  * ```
707
918
  */
708
919
  list(opts?: ListOptions): Promise<ListResult>;
709
920
  /**
710
- * Get metadata for a file (size, MIME type, visibility, URLs).
921
+ * Get file metadata: size, MIME type, visibility, URLs.
711
922
  *
712
- * @example
713
- * ```ts
714
- * const meta = await storage.getMetadata('avatars/alice.jpg');
715
- * console.log(meta.size, meta.isPublic, meta.publicUrl);
716
- * ```
923
+ * Server: GET /storage/metadata/:path
717
924
  */
718
925
  getMetadata(path: string): Promise<FileMetadata>;
719
926
  /**
720
927
  * Generate a time-limited download URL for a private file.
721
- * The URL can be shared externally without requiring an `X-Storage-Key`.
928
+ * Can be shared externally no X-Storage-Key required to access.
722
929
  *
723
- * > **Note:** Downloads via signed URLs bypass the server, so download stats
724
- * > are NOT tracked. Use `downloadUrl` for tracked downloads.
930
+ * Note: Downloads via signed URLs bypass the server stats are NOT tracked.
725
931
  *
726
- * @param path Path to the file.
727
- * @param expiresIn URL lifetime in seconds (default 3600 = 1 hour).
932
+ * Server: POST /storage/signed-url
933
+ * Body: { path, expiresIn? }
728
934
  *
729
- * @example
730
- * ```ts
731
- * const { signedUrl, expiresAt } = await storage.getSignedUrl('docs/invoice.pdf', 1800);
732
- * // Share signedUrl with the recipient — it expires in 30 minutes.
733
- * ```
935
+ * @param path File path.
936
+ * @param expiresIn URL lifetime in seconds (default 3600 = 1 hour).
734
937
  */
735
938
  getSignedUrl(path: string, expiresIn?: number): Promise<SignedUrlResult>;
736
939
  /**
737
- * Change a file's visibility between public and private after upload.
940
+ * Change a file's visibility between public and private.
738
941
  *
739
- * @example
740
- * ```ts
741
- * // Make a file public
742
- * const result = await storage.setVisibility('avatars/alice.jpg', true);
743
- * console.log(result.publicUrl); // CDN URL
744
- *
745
- * // Make a file private
746
- * const result = await storage.setVisibility('avatars/alice.jpg', false);
747
- * console.log(result.downloadUrl); // Auth-required URL
748
- * ```
942
+ * Server: PATCH /storage/visibility
943
+ * Body: { path, isPublic }
749
944
  */
750
945
  setVisibility(path: string, isPublic: boolean): Promise<{
751
946
  path: string;
@@ -754,44 +949,33 @@ declare class StorageManager {
754
949
  downloadUrl: string | null;
755
950
  }>;
756
951
  /**
757
- * Create a folder (a GCS prefix placeholder).
952
+ * Create a folder (empty GCS object used as a prefix marker).
758
953
  *
759
- * @example
760
- * ```ts
761
- * await storage.createFolder('uploads/2025/');
762
- * ```
954
+ * Server: POST /storage/folder
955
+ * Body: { path }
763
956
  */
764
957
  createFolder(path: string): Promise<{
765
958
  path: string;
766
959
  }>;
767
960
  /**
768
- * Delete a single file.
961
+ * Permanently delete a file.
769
962
  *
770
- * @example
771
- * ```ts
772
- * await storage.deleteFile('avatars/old-avatar.jpg');
773
- * ```
963
+ * Server: DELETE /storage/file
964
+ * Body: { path }
774
965
  */
775
966
  deleteFile(path: string): Promise<void>;
776
967
  /**
777
- * Delete a folder and all its contents recursively.
968
+ * Recursively delete a folder and all its contents.
778
969
  *
779
- * @example
780
- * ```ts
781
- * await storage.deleteFolder('temp/');
782
- * ```
970
+ * Server: DELETE /storage/folder
971
+ * Body: { path }
783
972
  */
784
973
  deleteFolder(path: string): Promise<void>;
785
974
  /**
786
- * Move or rename a file.
975
+ * Move (rename) a file.
787
976
  *
788
- * @example
789
- * ```ts
790
- * // Rename
791
- * await storage.move('docs/draft.pdf', 'docs/final.pdf');
792
- * // Move to a different folder
793
- * await storage.move('inbox/report.xlsx', 'archive/2025/report.xlsx');
794
- * ```
977
+ * Server: POST /storage/move
978
+ * Body: { from, to }
795
979
  */
796
980
  move(from: string, to: string): Promise<{
797
981
  from: string;
@@ -800,33 +984,23 @@ declare class StorageManager {
800
984
  /**
801
985
  * Copy a file to a new path.
802
986
  *
803
- * @example
804
- * ```ts
805
- * await storage.copy('templates/base.html', 'sites/my-site/index.html');
806
- * ```
987
+ * Server: POST /storage/copy
988
+ * Body: { from, to }
807
989
  */
808
990
  copy(from: string, to: string): Promise<{
809
991
  from: string;
810
992
  to: string;
811
993
  }>;
812
994
  /**
813
- * Get storage statistics for your key: total files, bytes, operation counts.
995
+ * Get usage statistics for this storage key.
814
996
  *
815
- * @example
816
- * ```ts
817
- * const stats = await storage.getStats();
818
- * console.log(`${stats.totalFiles} files, ${(stats.totalBytes / 1e6).toFixed(1)} MB`);
819
- * ```
997
+ * Server: GET /storage/stats
820
998
  */
821
999
  getStats(): Promise<StorageStats>;
822
1000
  /**
823
- * Ping the storage service. No authentication required.
1001
+ * Get server info (no auth required).
824
1002
  *
825
- * @example
826
- * ```ts
827
- * const info = await storage.info();
828
- * // → { ok: true, storageRoot: 'hydrous-storage' }
829
- * ```
1003
+ * Server: GET /storage/info
830
1004
  */
831
1005
  info(): Promise<{
832
1006
  ok: boolean;
@@ -835,190 +1009,159 @@ declare class StorageManager {
835
1009
  }
836
1010
 
837
1011
  /**
838
- * ScopedStorage — a StorageManager pre-scoped to a specific folder prefix.
1012
+ * ScopedStorage — a path-prefixed view over a StorageManager.
839
1013
  *
840
- * All paths are automatically prepended with the scope, so you never
841
- * have to repeat the folder name.
1014
+ * Every path you pass is automatically prefixed with the scope.
1015
+ * Obtain via `db.storage('keyName').scope('prefix/')`.
842
1016
  *
843
1017
  * @example
844
1018
  * ```ts
845
- * const db = createClient({ securityKey: 'sk_...' });
1019
+ * const userDocs = db.storage('documents').scope(`users/${userId}/`);
846
1020
  *
847
- * // All operations in the "avatars/" folder
848
- * const avatars = db.storage.scope('avatars');
1021
+ * // Uploads to: users/{userId}/contract.pdf
1022
+ * await userDocs.upload(pdfBuffer, 'contract.pdf');
849
1023
  *
850
- * await avatars.upload(file, 'alice.jpg'); // "avatars/alice.jpg"
851
- * const list = await avatars.list(); // → files under "avatars/"
852
- * await avatars.deleteFile('alice.jpg'); // → deletes "avatars/alice.jpg"
1024
+ * // Lists: users/{userId}/
1025
+ * const { files } = await userDocs.list();
853
1026
  * ```
854
1027
  */
855
1028
  declare class ScopedStorage {
856
1029
  private readonly manager;
857
1030
  private readonly prefix;
858
1031
  constructor(manager: StorageManager, prefix: string);
859
- private scopedPath;
860
- /** Upload a file within the scoped folder. */
861
- upload(data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
862
- /** Upload raw JSON or text within the scoped folder. */
1032
+ private p;
1033
+ upload(data: Blob | Uint8Array | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
863
1034
  uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
864
- /** Get a signed upload URL for a file within the scoped folder. */
865
1035
  getUploadUrl(opts: {
866
1036
  path: string;
867
1037
  mimeType: string;
868
1038
  size: number;
869
1039
  isPublic?: boolean;
870
1040
  overwrite?: boolean;
1041
+ expiresInSeconds?: number;
871
1042
  }): Promise<UploadUrlResult>;
872
- /** Confirm a direct upload within the scoped folder. */
1043
+ uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
873
1044
  confirmUpload(opts: {
874
1045
  path: string;
875
1046
  mimeType: string;
876
1047
  isPublic?: boolean;
877
1048
  }): Promise<UploadResult>;
878
- /** Download a file within the scoped folder. */
1049
+ getBatchUploadUrls(files: BatchUploadItem[]): Promise<BatchUploadUrlResult>;
1050
+ batchConfirmUploads(items: Array<{
1051
+ path: string;
1052
+ mimeType: string;
1053
+ isPublic?: boolean;
1054
+ }>): Promise<{
1055
+ succeeded: UploadResult[];
1056
+ failed: Array<{
1057
+ path: string;
1058
+ error: string;
1059
+ }>;
1060
+ }>;
879
1061
  download(path: string): Promise<ArrayBuffer>;
1062
+ batchDownload(paths: string[]): Promise<BatchDownloadResult>;
880
1063
  /**
881
- * List files within the scoped folder.
882
- * `prefix` in options is relative to the scope.
1064
+ * List files within this scope.
1065
+ * The `prefix` option is relative to the scope root.
1066
+ *
1067
+ * @example
1068
+ * ```ts
1069
+ * const userDocs = storage.scope('users/alice/');
1070
+ * // Lists users/alice/docs/
1071
+ * const { files } = await userDocs.list({ prefix: 'docs/' });
1072
+ * ```
883
1073
  */
884
1074
  list(opts?: ListOptions): Promise<ListResult>;
885
- /** Get metadata for a file within the scoped folder. */
886
1075
  getMetadata(path: string): Promise<FileMetadata>;
887
- /** Get a time-limited signed URL for a file within the scoped folder. */
888
1076
  getSignedUrl(path: string, expiresIn?: number): Promise<SignedUrlResult>;
889
- /** Change visibility of a file within the scoped folder. */
890
1077
  setVisibility(path: string, isPublic: boolean): Promise<{
891
1078
  path: string;
892
1079
  isPublic: boolean;
893
1080
  publicUrl: string | null;
894
1081
  downloadUrl: string | null;
895
1082
  }>;
896
- /** Delete a file within the scoped folder. */
1083
+ createFolder(path: string): Promise<{
1084
+ path: string;
1085
+ }>;
897
1086
  deleteFile(path: string): Promise<void>;
898
- /** Delete a sub-folder within the scoped folder. */
899
1087
  deleteFolder(path: string): Promise<void>;
900
- /** Move a file within the scoped folder. */
901
1088
  move(from: string, to: string): Promise<{
902
1089
  from: string;
903
1090
  to: string;
904
1091
  }>;
905
- /** Copy a file within the scoped folder. */
906
1092
  copy(from: string, to: string): Promise<{
907
1093
  from: string;
908
1094
  to: string;
909
1095
  }>;
910
- /** Create a sub-folder within the scoped folder. */
911
- createFolder(path: string): Promise<{
912
- path: string;
913
- }>;
1096
+ getStats(): Promise<StorageStats>;
914
1097
  /**
915
- * Create a further-scoped instance nested within this scope.
1098
+ * Create a deeper scope within this one.
916
1099
  *
917
1100
  * @example
918
1101
  * ```ts
919
- * const uploads = db.storage.scope('user-uploads');
920
- * const images = uploads.scope('images'); // → "user-uploads/images/"
1102
+ * const user = storage.scope('users/alice/');
1103
+ * const userDocs = user.scope('docs/');
1104
+ * // Effective prefix: users/alice/docs/
921
1105
  * ```
922
1106
  */
923
1107
  scope(subPrefix: string): ScopedStorage;
924
1108
  }
925
1109
 
926
- /**
927
- * HydrousClient — the main entry point for the HydrousDB SDK.
928
- *
929
- * Each service uses its own dedicated key:
930
- * - `authKey` (`hk_auth_…`) → `db.auth('bucket')`
931
- * - `bucketSecurityKey` (`hk_bucket_…`) → `db.records('bucket')` + `db.analytics('bucket')`
932
- * - `storageKeys` (`ssk_…`) → `db.storage('keyName')`
933
- *
934
- * @example
935
- * ```ts
936
- * import { createClient } from 'hydrousdb';
937
- *
938
- * const db = createClient({
939
- * authKey: 'hk_auth_…',
940
- * bucketSecurityKey: 'hk_bucket_…',
941
- * storageKeys: {
942
- * main: 'ssk_main_…',
943
- * avatars: 'ssk_avatars_…',
944
- * documents: 'ssk_docs_…',
945
- * },
946
- * });
947
- *
948
- * // Records & Analytics — use bucketSecurityKey automatically
949
- * const posts = db.records('blog-posts');
950
- * const analytics = db.analytics('orders');
951
- *
952
- * // Auth — uses authKey automatically
953
- * const auth = db.auth('app-users');
954
- *
955
- * // Storage — pick which key to use by name
956
- * const avatarStorage = db.storage('avatars');
957
- * const documentStorage = db.storage('documents');
958
- * ```
959
- */
960
1110
  declare class HydrousClient {
961
1111
  private readonly http;
962
1112
  private readonly authKey_;
963
1113
  private readonly bucketSecurityKey_;
964
1114
  private readonly storageKeys_;
965
1115
  private readonly _recordsCache;
966
- private readonly _authCache;
967
1116
  private readonly _analyticsCache;
968
1117
  private readonly _storageCache;
1118
+ private _authClient?;
969
1119
  constructor(config: HydrousConfig);
970
1120
  /**
971
- * Get a typed records client for the named bucket.
972
- * Uses your `bucketSecurityKey` automatically.
1121
+ * Get a typed RecordsClient for a bucket.
973
1122
  *
974
1123
  * @example
975
1124
  * ```ts
976
1125
  * interface Post { title: string; published: boolean }
977
1126
  * const posts = db.records<Post>('blog-posts');
978
- * const post = await posts.create({ title: 'Hello', published: false });
1127
+ * const post = await posts.create({ title: 'Hello', published: false });
979
1128
  * ```
980
1129
  */
981
1130
  records<T extends RecordData = RecordData>(bucketKey: string): RecordsClient<T>;
982
1131
  /**
983
- * Get an auth client for the named user bucket.
984
- * Uses your `authKey` automatically.
1132
+ * Get the AuthClient.
1133
+ * Auth routes are NOT bucket-scoped in the URL — the bucket is determined
1134
+ * server-side from the API key itself.
985
1135
  *
986
1136
  * @example
987
1137
  * ```ts
988
- * const auth = db.auth('app-users');
989
- * const { user, session } = await auth.login({ email: '…', password: '…' });
1138
+ * const { user, session } = await db.auth().signup({ email: 'alice@example.com', password: 'pw' });
990
1139
  * ```
991
1140
  */
992
- auth(bucketKey: string): AuthClient;
1141
+ auth(): AuthClient;
993
1142
  /**
994
- * Get an analytics client for the named bucket.
995
- * Uses your `bucketSecurityKey` automatically.
1143
+ * Get an AnalyticsClient for a bucket.
996
1144
  *
997
1145
  * @example
998
1146
  * ```ts
999
- * const analytics = db.analytics('orders');
1000
- * const { count } = await analytics.count();
1147
+ * const { count } = await db.analytics('orders').count();
1001
1148
  * ```
1002
1149
  */
1003
1150
  analytics(bucketKey: string): AnalyticsClient;
1004
1151
  /**
1005
- * Get a storage manager for the named storage key.
1006
- * The name must match a key you defined in `storageKeys` when calling `createClient`.
1007
- * Uses the corresponding `ssk_…` key automatically via `X-Storage-Key` header.
1152
+ * Get a StorageManager for a named storage key.
1153
+ * The name must match a key in the `storageKeys` config object.
1008
1154
  *
1009
- * @param keyName The name of the storage key (e.g. `"avatars"`, `"documents"`, `"main"`).
1155
+ * Attach `.scope('prefix/')` to get a path-prefixed ScopedStorage.
1010
1156
  *
1011
1157
  * @example
1012
1158
  * ```ts
1013
- * const avatars = db.storage('avatars');
1014
- * const documents = db.storage('documents');
1015
- *
1016
- * // Upload to avatars bucket
1017
- * await avatars.upload(file, `${userId}.jpg`, { isPublic: true });
1159
+ * const avatars = db.storage('avatars');
1160
+ * const result = await avatars.upload(file, 'alice.jpg', { isPublic: true });
1018
1161
  *
1019
- * // Scope to a sub-folder
1020
- * const userDocs = db.storage('documents').scope(`users/${userId}`);
1021
- * await userDocs.upload(pdfBuffer, 'contract.pdf');
1162
+ * // Scoped:
1163
+ * const userFiles = db.storage('documents').scope(`users/${userId}/`);
1164
+ * await userFiles.upload(pdf, 'contract.pdf');
1022
1165
  * ```
1023
1166
  */
1024
1167
  storage(keyName: string): StorageManager & {
@@ -1026,33 +1169,135 @@ declare class HydrousClient {
1026
1169
  };
1027
1170
  }
1028
1171
  /**
1029
- * Create a new HydrousDB client.
1172
+ * Create a HydrousDB client.
1030
1173
  *
1031
1174
  * @example
1032
1175
  * ```ts
1033
- * import { createClient } from 'hydrousdb';
1176
+ * import { createClient } from 'hydrous-sdk';
1034
1177
  *
1035
1178
  * const db = createClient({
1036
- * authKey: process.env.HYDROUS_AUTH_KEY!,
1037
- * bucketSecurityKey: process.env.HYDROUS_BUCKET_KEY!,
1038
- * storageKeys: {
1039
- * main: process.env.HYDROUS_STORAGE_MAIN!,
1040
- * avatars: process.env.HYDROUS_STORAGE_AVATARS!,
1041
- * documents: process.env.HYDROUS_STORAGE_DOCS!,
1042
- * },
1179
+ * authKey: 'hk_auth_…',
1180
+ * bucketSecurityKey: 'hk_bucket_…',
1181
+ * storageKeys: { avatars: 'ssk_…' },
1043
1182
  * });
1044
1183
  * ```
1045
1184
  */
1046
1185
  declare function createClient(config: HydrousConfig): HydrousClient;
1047
1186
 
1187
+ /**
1188
+ * routes.ts — Single source of truth for every API path in the HydrousDB SDK.
1189
+ *
1190
+ * Base URL: https://db-api-82687684612.us-central1.run.app
1191
+ *
1192
+ * Mount points (from server.js):
1193
+ * /api → records router (X-Api-Key: bucketSecurityKey)
1194
+ * /api/analytics → analytics router (X-Api-Key: bucketSecurityKey)
1195
+ * /api/auth → auth router (X-Api-Key: authKey)
1196
+ * /storage → storage router (X-Storage-Key: ssk_…)
1197
+ *
1198
+ * ⚠️ Keys MUST be sent in headers — NEVER in URLs or query strings.
1199
+ * Records + Analytics: X-Api-Key (or Authorization: Bearer …)
1200
+ * Storage: X-Storage-Key (or Authorization: Bearer …)
1201
+ */
1202
+ declare const RECORDS: {
1203
+ /** GET|POST|PATCH|DELETE|HEAD /api/:bucketKey */
1204
+ readonly bucket: (bucketKey: string) => string;
1205
+ /** POST /api/:bucketKey/batch/insert */
1206
+ readonly batchInsert: (bucketKey: string) => string;
1207
+ /** POST /api/:bucketKey/batch/update */
1208
+ readonly batchUpdate: (bucketKey: string) => string;
1209
+ /** POST /api/:bucketKey/batch/delete */
1210
+ readonly batchDelete: (bucketKey: string) => string;
1211
+ };
1212
+ declare const ANALYTICS: {
1213
+ /** POST /api/analytics/:bucketKey */
1214
+ readonly query: (bucketKey: string) => string;
1215
+ };
1216
+ declare const AUTH: {
1217
+ /** POST /api/auth/signup body: { email, password, fullName?, ...extra } */
1218
+ readonly signup: "/api/auth/signup";
1219
+ /** POST /api/auth/signin body: { email, password } */
1220
+ readonly signin: "/api/auth/signin";
1221
+ /** POST /api/auth/signout body: { sessionId, allDevices? } */
1222
+ readonly signout: "/api/auth/signout";
1223
+ /** POST /api/auth/session/validate body: { sessionId } */
1224
+ readonly sessionValidate: "/api/auth/session/validate";
1225
+ /** POST /api/auth/session/refresh body: { refreshToken } */
1226
+ readonly sessionRefresh: "/api/auth/session/refresh";
1227
+ /** GET /api/auth/user?userId=... */
1228
+ readonly getUser: "/api/auth/user";
1229
+ /** GET /api/auth/users?limit=&cursor= */
1230
+ readonly listUsers: "/api/auth/users";
1231
+ /** PATCH /api/auth/user body: { sessionId, userId, updates: {...} } */
1232
+ readonly updateUser: "/api/auth/user";
1233
+ /** DELETE /api/auth/user?userId=... body: { sessionId } */
1234
+ readonly deleteUser: "/api/auth/user";
1235
+ /** DELETE /api/auth/user/hard?userId=... body: { sessionId } */
1236
+ readonly hardDeleteUser: "/api/auth/user/hard";
1237
+ /** DELETE /api/auth/users/bulk body: { userIds, hard?, sessionId } */
1238
+ readonly bulkDeleteUsers: "/api/auth/users/bulk";
1239
+ /** POST /api/auth/password/change body: { sessionId, userId, oldPassword, newPassword } */
1240
+ readonly passwordChange: "/api/auth/password/change";
1241
+ /** POST /api/auth/password/reset/request body: { email } */
1242
+ readonly passwordResetRequest: "/api/auth/password/reset/request";
1243
+ /** POST /api/auth/password/reset/confirm body: { resetToken, newPassword } */
1244
+ readonly passwordResetConfirm: "/api/auth/password/reset/confirm";
1245
+ /** POST /api/auth/email/verify/request body: { userId } */
1246
+ readonly emailVerifyRequest: "/api/auth/email/verify/request";
1247
+ /** POST /api/auth/email/verify/confirm body: { verifyToken } */
1248
+ readonly emailVerifyConfirm: "/api/auth/email/verify/confirm";
1249
+ /** POST /api/auth/account/lock body: { sessionId, userId, duration? } */
1250
+ readonly accountLock: "/api/auth/account/lock";
1251
+ /** POST /api/auth/account/unlock body: { sessionId, userId } */
1252
+ readonly accountUnlock: "/api/auth/account/unlock";
1253
+ };
1254
+ declare const STORAGE: {
1255
+ /** GET /storage/info — no auth required */
1256
+ readonly info: "/storage/info";
1257
+ /** GET /storage/public/:fullScopedPath — no auth required */
1258
+ readonly publicFile: (fullScopedPath: string) => string;
1259
+ /** POST /storage/upload-url body: { path, mimeType, size, isPublic?, overwrite?, expiresIn? } */
1260
+ readonly uploadUrl: "/storage/upload-url";
1261
+ /** POST /storage/batch-upload-urls body: { files: [...], expiresIn? } */
1262
+ readonly batchUploadUrls: "/storage/batch-upload-urls";
1263
+ /** POST /storage/confirm body: { path, mimeType, isPublic? } */
1264
+ readonly confirm: "/storage/confirm";
1265
+ /** POST /storage/batch-confirm body: { files: [...] } */
1266
+ readonly batchConfirm: "/storage/batch-confirm";
1267
+ /** POST /storage/upload multipart/form-data: file, path, mimeType, isPublic, overwrite */
1268
+ readonly upload: "/storage/upload";
1269
+ /** POST /storage/upload-raw body: { path, content, mimeType?, isPublic?, overwrite? } */
1270
+ readonly uploadRaw: "/storage/upload-raw";
1271
+ /** GET /storage/list?prefix=&limit=&cursor= */
1272
+ readonly list: "/storage/list";
1273
+ /** GET /storage/download/:path — requires X-Storage-Key */
1274
+ readonly download: (filePath: string) => string;
1275
+ /** POST /storage/batch-download body: { paths: [...], concurrency? } */
1276
+ readonly batchDownload: "/storage/batch-download";
1277
+ /** GET /storage/metadata/:path */
1278
+ readonly metadata: (filePath: string) => string;
1279
+ /** POST /storage/signed-url body: { path, expiresIn? } */
1280
+ readonly signedUrl: "/storage/signed-url";
1281
+ /** PATCH /storage/visibility body: { path, isPublic } */
1282
+ readonly visibility: "/storage/visibility";
1283
+ /** POST /storage/folder body: { path } */
1284
+ readonly folder: "/storage/folder";
1285
+ /** DELETE /storage/file body: { path } */
1286
+ readonly file: "/storage/file";
1287
+ /** DELETE /storage/folder body: { path } */
1288
+ readonly folderDelete: "/storage/folder";
1289
+ /** POST /storage/move body: { from, to } */
1290
+ readonly move: "/storage/move";
1291
+ /** POST /storage/copy body: { from, to } */
1292
+ readonly copy: "/storage/copy";
1293
+ /** GET /storage/stats */
1294
+ readonly stats: "/storage/stats";
1295
+ };
1296
+
1048
1297
  declare class HydrousError extends Error {
1049
- /** Machine-readable error code returned by the API. */
1050
1298
  readonly code: string;
1051
- /** HTTP status code (if applicable). */
1052
1299
  readonly status?: number;
1053
- /** The original request ID for support tracing. */
1054
1300
  readonly requestId?: string;
1055
- /** Additional validation details. */
1056
1301
  readonly details?: string[];
1057
1302
  constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
1058
1303
  toString(): string;
@@ -1077,4 +1322,4 @@ declare class NetworkError extends HydrousError {
1077
1322
  constructor(message: string, cause?: unknown);
1078
1323
  }
1079
1324
 
1080
- export { type Aggregation, AnalyticsClient, AnalyticsError, type AnalyticsFilter, type AnalyticsQuery, type AnalyticsResult, AuthClient, AuthError, type AuthResult, type BatchUploadItem, type BatchUploadUrlResult, type ChangePasswordOptions, type CountResult, type CrossBucketRow, type DateRange, type DistributionRow, type FieldStats, type FieldTimeSeriesRow, type FileEntry, type FileMetadata, type Granularity, HydrousClient, type HydrousConfig, HydrousError, type ListOptions, type ListResult, type ListUsersOptions, type ListUsersResult, type LoginOptions, type MetricDefinition, type MultiMetricResult, NetworkError, type PatchRecordOptions, type QueryFilter, type QueryOptions, type QueryResult, type QueryType, type RecordData, RecordError, type RecordHistoryEntry, type RecordResult, RecordsClient, ScopedStorage, type Session, type SignedUrlResult, type SignupOptions, type SortOrder, StorageError, StorageManager, type StorageStats, type StorageStatsResult, type SumRow, type TimeSeriesRow, type TopNRow, type UpdateUserOptions, type UploadOptions, type UploadResult, type UploadUrlResult, type UserRecord, ValidationError, createClient };
1325
+ export { ANALYTICS, AUTH, type Aggregation, AnalyticsClient, AnalyticsError, type AnalyticsFilter, type AnalyticsQuery, type AnalyticsResult, AuthClient, AuthError, type AuthResult, type BatchCreateOptions, type BatchDownloadResult, type BatchUploadItem, type BatchUploadUrlResult, type ChangePasswordOptions, type CountResult, type CreateRecordOptions, type CrossBucketRow, DEFAULT_BASE_URL, type DateRange, type DistributionRow, type FieldStats, type FieldTimeSeriesRow, type FileEntry, type FileMetadata, type Granularity, HttpClient, HydrousClient, type HydrousConfig, HydrousError, type ListOptions, type ListResult, type ListUsersOptions, type ListUsersResult, type LoginOptions, type MetricDefinition, type MultiMetricResult, NetworkError, type PatchRecordOptions, type QueryFilter, type QueryOptions, type QueryResult, type QueryType, RECORDS, type RecordData, RecordError, type RecordHistoryEntry, type RecordResult, RecordsClient, STORAGE, ScopedStorage, type Session, type SignedUrlResult, type SignupOptions, type SortOrder, StorageError, type StorageKeys, StorageManager, type StorageStats, type StorageStatsResult, type SumRow, type TimeSeriesRow, type TopNRow, type UpdateUserOptions, type UploadOptions, type UploadResult, type UploadUrlResult, type UserRecord, ValidationError, createClient };