hydrousdb 3.0.0 → 3.0.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.d.ts CHANGED
@@ -7,24 +7,58 @@ interface RequestOptions {
7
7
  }
8
8
  declare class HttpClient {
9
9
  private readonly baseUrl;
10
- private readonly securityKey;
11
- constructor(baseUrl: string, securityKey: string);
12
- request<T = unknown>(path: string, opts?: RequestOptions): Promise<T>;
13
- get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
14
- post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
15
- put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
16
- patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
17
- delete<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
10
+ 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>;
18
17
  putToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
19
18
  }
20
19
 
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
+ type StorageKeys = Record<string, string>;
21
34
  interface HydrousConfig {
22
35
  /**
23
- * Your project's Security Key (starts with `sk_`).
24
- * Obtain this from your dashboard at https://hydrousdb.com/dashboard.
25
- * This is your most important credential — keep it secret.
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.
26
39
  */
27
- securityKey: string;
40
+ authKey: string;
41
+ /**
42
+ * 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.
45
+ */
46
+ bucketSecurityKey: string;
47
+ /**
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
+ * ```
60
+ */
61
+ storageKeys: StorageKeys;
28
62
  /**
29
63
  * Override the API base URL. Defaults to the official HydrousDB endpoint.
30
64
  * You almost never need to set this.
@@ -304,175 +338,53 @@ interface CrossBucketRow {
304
338
 
305
339
  /**
306
340
  * AuthClient — full user authentication for a single bucket.
307
- *
308
- * Every method maps directly to a route in the server's `auth.js` router.
309
- * The bucket key is the name of your user bucket (e.g. "users" or "customers").
341
+ * Uses the `authKey` (`hk_auth_…`) sent via `X-Api-Key` header.
310
342
  *
311
343
  * @example
312
344
  * ```ts
313
- * const db = createClient({ securityKey: 'sk_...' });
345
+ * const db = createClient({ authKey: 'hk_auth_…', bucketSecurityKey: '…', storageKeys: { main: '…' } });
314
346
  * const auth = db.auth('my-app-users');
315
- *
316
- * const { user, session } = await auth.signup({
317
- * email: 'alice@example.com',
318
- * password: 'hunter2',
319
- * fullName: 'Alice',
320
- * });
347
+ * const { user, session } = await auth.signup({ email: 'alice@example.com', password: 'hunter2' });
321
348
  * ```
322
349
  */
323
350
  declare class AuthClient {
324
351
  private readonly http;
325
- private readonly bucketKey;
352
+ private readonly authKey;
326
353
  private readonly basePath;
327
- constructor(http: HttpClient, bucketKey: string);
328
- /**
329
- * Register a new user in this bucket.
330
- *
331
- * @example
332
- * ```ts
333
- * const { user, session } = await auth.signup({
334
- * email: 'alice@example.com',
335
- * password: 'hunter2',
336
- * fullName: 'Alice Wonderland',
337
- * // Any extra fields are stored on the user record:
338
- * plan: 'pro',
339
- * });
340
- * ```
341
- */
354
+ constructor(http: HttpClient, authKey: string, bucketKey: string);
355
+ private post;
356
+ private get;
357
+ private patch;
358
+ private delete;
342
359
  signup(options: SignupOptions): Promise<AuthResult>;
343
- /**
344
- * Authenticate an existing user and create a session.
345
- * Sessions are valid for 24 hours; use `refreshSession` to extend.
346
- *
347
- * @example
348
- * ```ts
349
- * const { user, session } = await auth.login({
350
- * email: 'alice@example.com',
351
- * password: 'hunter2',
352
- * });
353
- * // Store session.sessionId safely — you'll need it for other calls.
354
- * ```
355
- */
356
360
  login(options: LoginOptions): Promise<AuthResult>;
357
- /**
358
- * Invalidate a session (sign out).
359
- *
360
- * @example
361
- * ```ts
362
- * await auth.logout({ sessionId: session.sessionId });
363
- * ```
364
- */
365
361
  logout({ sessionId }: {
366
362
  sessionId: string;
367
363
  }): Promise<void>;
368
- /**
369
- * Extend a session using its refresh token.
370
- * Returns a new session object.
371
- *
372
- * @example
373
- * ```ts
374
- * const newSession = await auth.refreshSession({ refreshToken: session.refreshToken });
375
- * ```
376
- */
377
364
  refreshSession({ refreshToken }: {
378
365
  refreshToken: string;
379
366
  }): Promise<Session>;
380
- /**
381
- * Fetch a user by their ID.
382
- *
383
- * @example
384
- * ```ts
385
- * const user = await auth.getUser({ userId: 'usr_abc123' });
386
- * ```
387
- */
388
367
  getUser({ userId }: {
389
368
  userId: string;
390
369
  }): Promise<UserRecord>;
391
- /**
392
- * Update fields on a user record. Requires a valid session.
393
- *
394
- * @example
395
- * ```ts
396
- * const updated = await auth.updateUser({
397
- * sessionId: session.sessionId,
398
- * userId: user.id,
399
- * data: { fullName: 'Alice Smith', plan: 'enterprise' },
400
- * });
401
- * ```
402
- */
403
370
  updateUser(options: UpdateUserOptions): Promise<UserRecord>;
404
- /**
405
- * Soft-delete a user. The record is marked deleted but not removed from storage.
406
- * Requires a valid session (the user can delete themselves, or an admin can delete any user).
407
- *
408
- * @example
409
- * ```ts
410
- * await auth.deleteUser({ sessionId: session.sessionId, userId: user.id });
411
- * ```
412
- */
413
371
  deleteUser({ sessionId, userId }: {
414
372
  sessionId: string;
415
373
  userId: string;
416
374
  }): Promise<void>;
417
- /**
418
- * List all users in the bucket. **Admin session required.**
419
- *
420
- * @example
421
- * ```ts
422
- * const { users, total } = await auth.listUsers({
423
- * sessionId: adminSession.sessionId,
424
- * limit: 50,
425
- * offset: 0,
426
- * });
427
- * ```
428
- */
429
375
  listUsers(options: ListUsersOptions): Promise<ListUsersResult>;
430
- /**
431
- * Permanently hard-delete a user and all their data. **Admin session required.**
432
- * This action is irreversible.
433
- *
434
- * @example
435
- * ```ts
436
- * await auth.hardDeleteUser({ sessionId: adminSession.sessionId, userId: user.id });
437
- * ```
438
- */
439
376
  hardDeleteUser({ sessionId, userId }: {
440
377
  sessionId: string;
441
378
  userId: string;
442
379
  }): Promise<void>;
443
- /**
444
- * Bulk delete multiple users. **Admin session required.**
445
- *
446
- * @example
447
- * ```ts
448
- * const result = await auth.bulkDeleteUsers({
449
- * sessionId: adminSession.sessionId,
450
- * userIds: ['usr_a', 'usr_b'],
451
- * });
452
- * ```
453
- */
454
- bulkDeleteUsers({ sessionId, userIds, }: {
380
+ bulkDeleteUsers({ sessionId, userIds }: {
455
381
  sessionId: string;
456
382
  userIds: string[];
457
383
  }): Promise<{
458
384
  deleted: number;
459
385
  failed: string[];
460
386
  }>;
461
- /**
462
- * Lock a user account, preventing login. **Admin session required.**
463
- *
464
- * @param options.duration Lock duration in milliseconds. Defaults to 15 minutes.
465
- *
466
- * @example
467
- * ```ts
468
- * await auth.lockAccount({
469
- * sessionId: adminSession.sessionId,
470
- * userId: user.id,
471
- * duration: 60 * 60 * 1000, // 1 hour
472
- * });
473
- * ```
474
- */
475
- lockAccount({ sessionId, userId, duration, }: {
387
+ lockAccount({ sessionId, userId, duration }: {
476
388
  sessionId: string;
477
389
  userId: string;
478
390
  duration?: number;
@@ -480,392 +392,103 @@ declare class AuthClient {
480
392
  lockedUntil: number;
481
393
  unlockTime: string;
482
394
  }>;
483
- /**
484
- * Unlock a previously locked user account. **Admin session required.**
485
- *
486
- * @example
487
- * ```ts
488
- * await auth.unlockAccount({ sessionId: adminSession.sessionId, userId: user.id });
489
- * ```
490
- */
491
- unlockAccount({ sessionId, userId, }: {
395
+ unlockAccount({ sessionId, userId }: {
492
396
  sessionId: string;
493
397
  userId: string;
494
398
  }): Promise<void>;
495
- /**
496
- * Change a user's password. The user must supply their current password.
497
- *
498
- * @example
499
- * ```ts
500
- * await auth.changePassword({
501
- * sessionId: session.sessionId,
502
- * userId: user.id,
503
- * currentPassword: 'hunter2',
504
- * newPassword: 'correcthorsebatterystaple',
505
- * });
506
- * ```
507
- */
508
399
  changePassword(options: ChangePasswordOptions): Promise<void>;
509
- /**
510
- * Request a password reset email for a user.
511
- * Always returns success to prevent user enumeration.
512
- *
513
- * @example
514
- * ```ts
515
- * await auth.requestPasswordReset({ email: 'alice@example.com' });
516
- * ```
517
- */
518
400
  requestPasswordReset({ email }: {
519
401
  email: string;
520
402
  }): Promise<void>;
521
- /**
522
- * Complete a password reset using the token from the reset email.
523
- *
524
- * @example
525
- * ```ts
526
- * await auth.confirmPasswordReset({
527
- * resetToken: 'tok_from_email',
528
- * newPassword: 'correcthorsebatterystaple',
529
- * });
530
- * ```
531
- */
532
- confirmPasswordReset({ resetToken, newPassword, }: {
403
+ confirmPasswordReset({ resetToken, newPassword }: {
533
404
  resetToken: string;
534
405
  newPassword: string;
535
406
  }): Promise<void>;
536
- /**
537
- * Send (or resend) an email verification message to a user.
538
- *
539
- * @example
540
- * ```ts
541
- * await auth.requestEmailVerification({ userId: user.id });
542
- * ```
543
- */
544
407
  requestEmailVerification({ userId }: {
545
408
  userId: string;
546
409
  }): Promise<void>;
547
- /**
548
- * Confirm an email address using the token from the verification email.
549
- *
550
- * @example
551
- * ```ts
552
- * await auth.confirmEmailVerification({ verifyToken: 'tok_from_email' });
553
- * ```
554
- */
555
410
  confirmEmailVerification({ verifyToken }: {
556
411
  verifyToken: string;
557
412
  }): Promise<void>;
558
413
  }
559
414
 
560
415
  /**
561
- * RecordsClient — create, read, update, delete, and query records in a bucket.
562
- *
563
- * A bucket is simply a named collection of JSON records. You create buckets
564
- * from your HydrousDB dashboard. Each record is a JSON object with any fields
565
- * you want; HydrousDB automatically adds `id`, `createdAt`, and `updatedAt`.
416
+ * RecordsClient — CRUD + query for a single bucket.
417
+ * Uses the `bucketSecurityKey` (`hk_bucket_…`) sent via `X-Api-Key` header.
566
418
  *
567
419
  * @example
568
420
  * ```ts
569
- * const db = createClient({ securityKey: 'sk_...' });
421
+ * const db = createClient({ authKey: '', bucketSecurityKey: 'hk_bucket_…', storageKeys: { main: '…' } });
570
422
  * const posts = db.records('blog-posts');
571
- *
572
- * // Create a record
573
423
  * const post = await posts.create({ title: 'Hello World', status: 'draft' });
574
- *
575
- * // Query records
576
- * const { records } = await posts.query({
577
- * filters: [{ field: 'status', op: '==', value: 'published' }],
578
- * limit: 20,
579
- * });
580
424
  * ```
581
425
  */
582
426
  declare class RecordsClient<T extends RecordData = RecordData> {
583
427
  private readonly http;
584
428
  private readonly bucketKey;
585
429
  private readonly basePath;
586
- constructor(http: HttpClient, bucketKey: string);
587
- /**
588
- * Create a new record.
589
- *
590
- * @example
591
- * ```ts
592
- * const user = await records.create({
593
- * name: 'Alice',
594
- * email: 'alice@example.com',
595
- * score: 100,
596
- * });
597
- * console.log(user.id); // "rec_xxxxxxxx"
598
- * ```
599
- */
430
+ private readonly bucketKey_;
431
+ constructor(http: HttpClient, bucketSecurityKey: string, bucketKey: string);
432
+ private get key();
600
433
  create(data: T): Promise<T & RecordResult>;
601
- /**
602
- * Fetch a single record by ID.
603
- *
604
- * @example
605
- * ```ts
606
- * const post = await records.get('rec_abc123');
607
- * ```
608
- */
609
434
  get(id: string): Promise<T & RecordResult>;
610
- /**
611
- * Overwrite a record entirely (full replace).
612
- *
613
- * @example
614
- * ```ts
615
- * const updated = await records.set('rec_abc123', {
616
- * name: 'Alice Updated',
617
- * email: 'alice2@example.com',
618
- * });
619
- * ```
620
- */
621
435
  set(id: string, data: T): Promise<T & RecordResult>;
622
- /**
623
- * Partially update a record (merge by default).
624
- *
625
- * @example
626
- * ```ts
627
- * // Merge: only the provided fields are updated
628
- * const updated = await records.patch('rec_abc123', { score: 200 });
629
- *
630
- * // Replace: equivalent to set()
631
- * const replaced = await records.patch('rec_abc123', { score: 200 }, { merge: false });
632
- * ```
633
- */
634
436
  patch(id: string, data: Partial<T>, options?: PatchRecordOptions): Promise<T & RecordResult>;
635
- /**
636
- * Delete a record permanently.
637
- *
638
- * @example
639
- * ```ts
640
- * await records.delete('rec_abc123');
641
- * ```
642
- */
643
437
  delete(id: string): Promise<void>;
644
- /**
645
- * Create multiple records in one request.
646
- *
647
- * @example
648
- * ```ts
649
- * const created = await records.batchCreate([
650
- * { name: 'Alice', score: 100 },
651
- * { name: 'Bob', score: 200 },
652
- * ]);
653
- * ```
654
- */
655
438
  batchCreate(items: T[]): Promise<(T & RecordResult)[]>;
656
- /**
657
- * Delete multiple records by ID in one request.
658
- *
659
- * @example
660
- * ```ts
661
- * await records.batchDelete(['rec_a', 'rec_b', 'rec_c']);
662
- * ```
663
- */
664
439
  batchDelete(ids: string[]): Promise<{
665
440
  deleted: number;
666
441
  failed: string[];
667
442
  }>;
668
- /**
669
- * Query records with optional filters, sorting, and pagination.
670
- *
671
- * @example
672
- * ```ts
673
- * // Simple query
674
- * const { records } = await posts.query({ limit: 10 });
675
- *
676
- * // Filtered query with cursor pagination
677
- * const page1 = await posts.query({
678
- * filters: [
679
- * { field: 'status', op: '==', value: 'published' },
680
- * { field: 'views', op: '>', value: 1000 },
681
- * ],
682
- * orderBy: 'createdAt',
683
- * order: 'desc',
684
- * limit: 20,
685
- * });
686
- *
687
- * const page2 = await posts.query({
688
- * filters: [{ field: 'status', op: '==', value: 'published' }],
689
- * limit: 20,
690
- * startAfter: page1.nextCursor,
691
- * });
692
- * ```
693
- */
694
443
  query(options?: QueryOptions): Promise<QueryResult<T>>;
695
- /**
696
- * Convenience alias: get all records up to `limit` (default 100).
697
- *
698
- * @example
699
- * ```ts
700
- * const allPosts = await posts.getAll({ limit: 500 });
701
- * ```
702
- */
703
444
  getAll(options?: Omit<QueryOptions, 'filters'>): Promise<(T & RecordResult)[]>;
704
- /**
705
- * Count records matching optional filters.
706
- *
707
- * @example
708
- * ```ts
709
- * const total = await posts.count([{ field: 'status', op: '==', value: 'published' }]);
710
- * ```
711
- */
712
445
  count(filters?: QueryOptions['filters']): Promise<number>;
713
- /**
714
- * Retrieve the full version history of a record.
715
- * Each write creates a new version stored in GCS.
716
- *
717
- * @example
718
- * ```ts
719
- * const history = await records.getHistory('rec_abc123');
720
- * console.log(history[0].data); // latest version
721
- * ```
722
- */
723
446
  getHistory(id: string): Promise<RecordHistoryEntry[]>;
724
- /**
725
- * Restore a record to a specific historical version.
726
- *
727
- * @example
728
- * ```ts
729
- * const history = await records.getHistory('rec_abc123');
730
- * const restored = await records.restoreVersion('rec_abc123', history[2].version);
731
- * ```
732
- */
733
447
  restoreVersion(id: string, version: number): Promise<T & RecordResult>;
734
448
  }
735
449
 
736
450
  /**
737
- * AnalyticsClient — run powerful aggregation and time-series queries against
738
- * your bucket data using BigQuery under the hood.
739
- *
740
- * All query types accept an optional `dateRange` to filter by time.
741
- * The security key is sent automatically — you never pass it manually.
451
+ * AnalyticsClient — BigQuery-powered aggregations for a bucket.
452
+ * Uses the `bucketSecurityKey` (`hk_bucket_…`) sent via `X-Api-Key` header.
742
453
  *
743
454
  * @example
744
455
  * ```ts
745
- * const db = createClient({ securityKey: 'sk_...' });
456
+ * const db = createClient({ authKey: '', bucketSecurityKey: 'hk_bucket_…', storageKeys: { main: '…' } });
746
457
  * const analytics = db.analytics('orders');
747
- *
748
- * // How many orders in the last 30 days?
749
- * const { count } = await analytics.count({
750
- * dateRange: { start: Date.now() - 30 * 24 * 60 * 60 * 1000, end: Date.now() },
751
- * });
458
+ * const { count } = await analytics.count();
752
459
  * ```
753
460
  */
754
461
  declare class AnalyticsClient {
755
462
  private readonly http;
756
- private readonly bucketKey;
463
+ private readonly bucketSecurityKey;
757
464
  private readonly basePath;
758
- constructor(http: HttpClient, bucketKey: string);
759
- /** Internal dispatcher — all queries POST to the same endpoint. */
465
+ constructor(http: HttpClient, bucketSecurityKey: string, bucketKey: string);
760
466
  private run;
761
- /**
762
- * Count the total number of records in the bucket, with optional date filter.
763
- *
764
- * @example
765
- * ```ts
766
- * const { count } = await analytics.count();
767
- * // → { count: 4821 }
768
- *
769
- * // Count only this month's records
770
- * const { count } = await analytics.count({
771
- * dateRange: { start: new Date('2025-01-01').getTime(), end: Date.now() },
772
- * });
773
- * ```
774
- */
775
467
  count(opts?: {
776
468
  dateRange?: DateRange;
777
469
  }): Promise<CountResult>;
778
- /**
779
- * Count how many records have each unique value for a given field.
780
- * Great for pie charts and bar charts.
781
- *
782
- * @example
783
- * ```ts
784
- * const rows = await analytics.distribution({
785
- * field: 'status',
786
- * limit: 10,
787
- * order: 'desc',
788
- * });
789
- * // → [{ value: 'completed', count: 312 }, { value: 'pending', count: 88 }, ...]
790
- * ```
791
- */
792
470
  distribution(opts: {
793
471
  field: string;
794
472
  limit?: number;
795
473
  order?: SortOrder;
796
474
  dateRange?: DateRange;
797
475
  }): Promise<DistributionRow[]>;
798
- /**
799
- * Sum a numeric field, optionally grouped by another field.
800
- *
801
- * @example
802
- * ```ts
803
- * // Total revenue
804
- * const rows = await analytics.sum({ field: 'amount' });
805
- * // → [{ sum: 198432.50 }]
806
- *
807
- * // Revenue by country
808
- * const rows = await analytics.sum({ field: 'amount', groupBy: 'country', limit: 10 });
809
- * // → [{ group: 'US', sum: 120000 }, { group: 'UK', sum: 45000 }, ...]
810
- * ```
811
- */
812
476
  sum(opts: {
813
477
  field: string;
814
478
  groupBy?: string;
815
479
  limit?: number;
816
480
  dateRange?: DateRange;
817
481
  }): Promise<SumRow[]>;
818
- /**
819
- * Count records created over time, grouped by a time granularity.
820
- * Perfect for line charts and activity graphs.
821
- *
822
- * @example
823
- * ```ts
824
- * const rows = await analytics.timeSeries({
825
- * granularity: 'day',
826
- * dateRange: { start: Date.now() - 7 * 86400000, end: Date.now() },
827
- * });
828
- * // → [{ date: '2025-06-01', count: 42 }, { date: '2025-06-02', count: 67 }, ...]
829
- * ```
830
- */
831
482
  timeSeries(opts?: {
832
483
  granularity?: Granularity;
833
484
  dateRange?: DateRange;
834
485
  }): Promise<TimeSeriesRow[]>;
835
- /**
836
- * Aggregate a numeric field over time (e.g. daily revenue, hourly signups).
837
- *
838
- * @example
839
- * ```ts
840
- * const rows = await analytics.fieldTimeSeries({
841
- * field: 'amount',
842
- * aggregation: 'sum',
843
- * granularity: 'week',
844
- * });
845
- * // → [{ date: '2025-W22', value: 14230.50 }, ...]
846
- * ```
847
- */
848
486
  fieldTimeSeries(opts: {
849
487
  field: string;
850
488
  aggregation?: Aggregation;
851
489
  granularity?: Granularity;
852
490
  dateRange?: DateRange;
853
491
  }): Promise<FieldTimeSeriesRow[]>;
854
- /**
855
- * Get the top N values by frequency for a field.
856
- * Optionally pair with a `labelField` for human-readable labels.
857
- *
858
- * @example
859
- * ```ts
860
- * // Top 5 most purchased products
861
- * const rows = await analytics.topN({
862
- * field: 'productId',
863
- * labelField: 'productName',
864
- * n: 5,
865
- * });
866
- * // → [{ value: 'prod_123', label: 'Widget Pro', count: 892 }, ...]
867
- * ```
868
- */
869
492
  topN(opts: {
870
493
  field: string;
871
494
  n?: number;
@@ -873,34 +496,10 @@ declare class AnalyticsClient {
873
496
  order?: SortOrder;
874
497
  dateRange?: DateRange;
875
498
  }): Promise<TopNRow[]>;
876
- /**
877
- * Get statistical summary (min, max, avg, sum, count, stddev) for a numeric field.
878
- *
879
- * @example
880
- * ```ts
881
- * const stats = await analytics.stats({ field: 'orderValue' });
882
- * // → { min: 4.99, max: 9999.99, avg: 87.23, sum: 420948.27, count: 4823, stddev: 143.2 }
883
- * ```
884
- */
885
499
  stats(opts: {
886
500
  field: string;
887
501
  dateRange?: DateRange;
888
502
  }): Promise<FieldStats>;
889
- /**
890
- * Query raw records with filters, field selection, and pagination.
891
- * This is the analytics version of `records.query()` but powered by BigQuery.
892
- *
893
- * @example
894
- * ```ts
895
- * const { records } = await analytics.records({
896
- * filters: [{ field: 'status', op: '==', value: 'refunded' }],
897
- * selectFields: ['orderId', 'amount', 'createdAt'],
898
- * limit: 50,
899
- * orderBy: 'amount',
900
- * order: 'desc',
901
- * });
902
- * ```
903
- */
904
503
  records<T extends RecordData = RecordData>(opts?: {
905
504
  filters?: AnalyticsFilter[];
906
505
  selectFields?: string[];
@@ -910,100 +509,35 @@ declare class AnalyticsClient {
910
509
  order?: SortOrder;
911
510
  dateRange?: DateRange;
912
511
  }): Promise<(T & RecordResult)[]>;
913
- /**
914
- * Calculate multiple aggregations in a single query.
915
- * Ideal for dashboards that need several numbers at once.
916
- *
917
- * @example
918
- * ```ts
919
- * const result = await analytics.multiMetric({
920
- * metrics: [
921
- * { field: 'amount', name: 'totalRevenue', aggregation: 'sum' },
922
- * { field: 'amount', name: 'avgOrderValue', aggregation: 'avg' },
923
- * { field: 'userId', name: 'uniqueCustomers', aggregation: 'count' },
924
- * ],
925
- * });
926
- * // → { totalRevenue: 198432.50, avgOrderValue: 87.23, uniqueCustomers: 2275 }
927
- * ```
928
- */
929
512
  multiMetric(opts: {
930
513
  metrics: MetricDefinition[];
931
514
  dateRange?: DateRange;
932
515
  }): Promise<MultiMetricResult>;
933
- /**
934
- * Get storage statistics for this bucket: record counts, byte sizes.
935
- *
936
- * @example
937
- * ```ts
938
- * const stats = await analytics.storageStats();
939
- * // → { totalRecords: 4821, totalBytes: 48293820, avgBytes: 10015, ... }
940
- * ```
941
- */
942
516
  storageStats(opts?: {
943
517
  dateRange?: DateRange;
944
518
  }): Promise<StorageStatsResult>;
945
- /**
946
- * Compare the same field aggregation across multiple buckets in one query.
947
- * Your security key must have read access to ALL listed buckets.
948
- *
949
- * @example
950
- * ```ts
951
- * const rows = await analytics.crossBucket({
952
- * bucketKeys: ['orders-us', 'orders-eu', 'orders-apac'],
953
- * field: 'amount',
954
- * aggregation: 'sum',
955
- * });
956
- * // → [
957
- * // { bucket: 'orders-us', value: 120000 },
958
- * // { bucket: 'orders-eu', value: 45000 },
959
- * // { bucket: 'orders-apac', value: 33000 },
960
- * // ]
961
- * ```
962
- */
963
519
  crossBucket(opts: {
964
520
  bucketKeys: string[];
965
521
  field: string;
966
522
  aggregation?: Aggregation;
967
523
  dateRange?: DateRange;
968
524
  }): Promise<CrossBucketRow[]>;
969
- /**
970
- * Send a raw analytics query object. Use this when you need full control
971
- * over the query shape or want to use a queryType not covered by the helpers.
972
- *
973
- * @example
974
- * ```ts
975
- * const result = await analytics.query({
976
- * queryType: 'topN',
977
- * field: 'category',
978
- * n: 3,
979
- * order: 'asc',
980
- * });
981
- * ```
982
- */
983
525
  query<T = unknown>(query: AnalyticsQuery): Promise<AnalyticsResult<T>>;
984
526
  }
985
527
 
986
528
  /**
987
529
  * 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.
988
532
  *
989
- * Authentication is handled automatically via the X-Storage-Key header.
990
- * Files are scoped to your owner ID — you can never access another owner's files.
991
- *
992
- * **Recommended upload flow** (for progress tracking and large files):
993
- * 1. Call `getUploadUrl()` to get a signed PUT URL.
994
- * 2. Upload directly to GCS using XHR (supports progress events).
995
- * 3. Call `confirmUpload()` to finalize metadata.
996
- *
997
- * **Simple upload** (small files, no progress needed):
998
- * - Call `upload()` — it handles everything in one call.
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`.
999
535
  *
1000
536
  * @example
1001
537
  * ```ts
1002
- * const db = createClient({ securityKey: 'sk_...' });
1003
- *
1004
- * // Simple upload
1005
- * const file = await db.storage.upload(fileBlob, 'avatars/alice.jpg', { isPublic: true });
1006
- * console.log(file.publicUrl); // CDN URL, usable anywhere
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 });
1007
541
  * ```
1008
542
  */
1009
543
  declare class StorageManager {
@@ -1300,8 +834,6 @@ declare class StorageManager {
1300
834
  }>;
1301
835
  }
1302
836
 
1303
- /** Accepted data types for file uploads. */
1304
- type UploadData = Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer;
1305
837
  /**
1306
838
  * ScopedStorage — a StorageManager pre-scoped to a specific folder prefix.
1307
839
  *
@@ -1326,7 +858,7 @@ declare class ScopedStorage {
1326
858
  constructor(manager: StorageManager, prefix: string);
1327
859
  private scopedPath;
1328
860
  /** Upload a file within the scoped folder. */
1329
- upload(data: UploadData, path: string, options?: UploadOptions): Promise<UploadResult>;
861
+ upload(data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
1330
862
  /** Upload raw JSON or text within the scoped folder. */
1331
863
  uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
1332
864
  /** Get a signed upload URL for a file within the scoped folder. */
@@ -1337,8 +869,6 @@ declare class ScopedStorage {
1337
869
  isPublic?: boolean;
1338
870
  overwrite?: boolean;
1339
871
  }): Promise<UploadUrlResult>;
1340
- /** Upload data directly to a signed GCS URL with optional progress tracking. */
1341
- uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
1342
872
  /** Confirm a direct upload within the scoped folder. */
1343
873
  confirmUpload(opts: {
1344
874
  path: string;
@@ -1347,7 +877,10 @@ declare class ScopedStorage {
1347
877
  }): Promise<UploadResult>;
1348
878
  /** Download a file within the scoped folder. */
1349
879
  download(path: string): Promise<ArrayBuffer>;
1350
- /** List files within the scoped folder. */
880
+ /**
881
+ * List files within the scoped folder.
882
+ * `prefix` in options is relative to the scope.
883
+ */
1351
884
  list(opts?: ListOptions): Promise<ListResult>;
1352
885
  /** Get metadata for a file within the scoped folder. */
1353
886
  getMetadata(path: string): Promise<FileMetadata>;
@@ -1384,7 +917,7 @@ declare class ScopedStorage {
1384
917
  * @example
1385
918
  * ```ts
1386
919
  * const uploads = db.storage.scope('user-uploads');
1387
- * const images = uploads.scope('images'); // → "user-uploads/images/"
920
+ * const images = uploads.scope('images'); // → "user-uploads/images/"
1388
921
  * ```
1389
922
  */
1390
923
  scope(subPrefix: string): ScopedStorage;
@@ -1393,75 +926,73 @@ declare class ScopedStorage {
1393
926
  /**
1394
927
  * HydrousClient — the main entry point for the HydrousDB SDK.
1395
928
  *
1396
- * Create one instance per application (it is safe to share globally).
1397
- * Every sub-client (`auth`, `records`, `analytics`, `storage`) is lazily
1398
- * instantiated and cached on first access.
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')`
1399
933
  *
1400
934
  * @example
1401
935
  * ```ts
1402
936
  * import { createClient } from 'hydrousdb';
1403
937
  *
1404
- * const db = createClient({ securityKey: 'sk_live_...' });
1405
- *
1406
- * // Records
1407
- * const posts = db.records('blog-posts');
1408
- * const users = db.records('app-users');
1409
- *
1410
- * // Auth
1411
- * const auth = db.auth('app-users');
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
+ * });
1412
947
  *
1413
- * // Analytics
1414
- * const stats = db.analytics('orders');
948
+ * // Records & Analytics — use bucketSecurityKey automatically
949
+ * const posts = db.records('blog-posts');
950
+ * const analytics = db.analytics('orders');
1415
951
  *
1416
- * // Storage (flat)
1417
- * const storage = db.storage; // StorageManager
952
+ * // Auth — uses authKey automatically
953
+ * const auth = db.auth('app-users');
1418
954
  *
1419
- * // Storage (scoped to a folder)
1420
- * const avatars = db.storage.scope('avatars');
955
+ * // Storage pick which key to use by name
956
+ * const avatarStorage = db.storage('avatars');
957
+ * const documentStorage = db.storage('documents');
1421
958
  * ```
1422
959
  */
1423
960
  declare class HydrousClient {
1424
961
  private readonly http;
1425
- private readonly _storageKey;
1426
- private _storage;
962
+ private readonly authKey_;
963
+ private readonly bucketSecurityKey_;
964
+ private readonly storageKeys_;
1427
965
  private readonly _recordsCache;
1428
966
  private readonly _authCache;
1429
967
  private readonly _analyticsCache;
968
+ private readonly _storageCache;
1430
969
  constructor(config: HydrousConfig);
1431
970
  /**
1432
- * Get a typed records client for the given bucket.
1433
- *
1434
- * The generic type parameter `T` describes the shape of records in this
1435
- * bucket. Leave it unset for a generic `Record<string, unknown>` shape.
1436
- *
1437
- * @param bucketKey The name of your bucket (must match what you created in the dashboard).
971
+ * Get a typed records client for the named bucket.
972
+ * Uses your `bucketSecurityKey` automatically.
1438
973
  *
1439
974
  * @example
1440
975
  * ```ts
1441
- * interface Post { title: string; body: string; published: boolean }
976
+ * interface Post { title: string; published: boolean }
1442
977
  * const posts = db.records<Post>('blog-posts');
1443
- *
1444
- * const post = await posts.create({ title: 'Hello', body: '...', published: false });
1445
- * // post.id, post.createdAt, post.updatedAt are added automatically
978
+ * const post = await posts.create({ title: 'Hello', published: false });
1446
979
  * ```
1447
980
  */
1448
981
  records<T extends RecordData = RecordData>(bucketKey: string): RecordsClient<T>;
1449
982
  /**
1450
- * Get an auth client for the given user bucket.
1451
- *
1452
- * @param bucketKey The name of your user bucket (e.g. `"app-users"`).
983
+ * Get an auth client for the named user bucket.
984
+ * Uses your `authKey` automatically.
1453
985
  *
1454
986
  * @example
1455
987
  * ```ts
1456
988
  * const auth = db.auth('app-users');
1457
- * const { user, session } = await auth.login({ email: '...', password: '...' });
989
+ * const { user, session } = await auth.login({ email: '', password: '' });
1458
990
  * ```
1459
991
  */
1460
992
  auth(bucketKey: string): AuthClient;
1461
993
  /**
1462
- * Get an analytics client for the given bucket.
1463
- *
1464
- * @param bucketKey The name of the bucket to analyse.
994
+ * Get an analytics client for the named bucket.
995
+ * Uses your `bucketSecurityKey` automatically.
1465
996
  *
1466
997
  * @example
1467
998
  * ```ts
@@ -1471,48 +1002,57 @@ declare class HydrousClient {
1471
1002
  */
1472
1003
  analytics(bucketKey: string): AnalyticsClient;
1473
1004
  /**
1474
- * The storage manager for uploading, downloading, listing, and managing files.
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.
1475
1008
  *
1476
- * Scoped to your project you can never access another project's files.
1009
+ * @param keyName The name of the storage key (e.g. `"avatars"`, `"documents"`, `"main"`).
1477
1010
  *
1478
1011
  * @example
1479
1012
  * ```ts
1480
- * // Upload a file
1481
- * const result = await db.storage.upload(file, 'images/photo.jpg', { isPublic: true });
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 });
1482
1018
  *
1483
- * // Scope to a folder
1484
- * const avatars = db.storage.scope('user-avatars');
1485
- * await avatars.upload(blob, `${userId}.jpg`, { isPublic: true });
1019
+ * // Scope to a sub-folder
1020
+ * const userDocs = db.storage('documents').scope(`users/${userId}`);
1021
+ * await userDocs.upload(pdfBuffer, 'contract.pdf');
1486
1022
  * ```
1487
1023
  */
1488
- get storage(): StorageManager & {
1024
+ storage(keyName: string): StorageManager & {
1489
1025
  scope: (prefix: string) => ScopedStorage;
1490
1026
  };
1491
1027
  }
1492
1028
  /**
1493
1029
  * Create a new HydrousDB client.
1494
1030
  *
1495
- * This is the **only** export you need to get started.
1496
- * The API URL is pre-configured — you only need your Security Key.
1497
- *
1498
- * @param config.securityKey Your project's Security Key from https://hydrousdb.com/dashboard
1499
- * @param config.baseUrl (Optional) Override the API base URL.
1500
- *
1501
1031
  * @example
1502
1032
  * ```ts
1503
1033
  * import { createClient } from 'hydrousdb';
1504
1034
  *
1505
1035
  * const db = createClient({
1506
- * securityKey: process.env.HYDROUS_SECURITY_KEY!,
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
+ * },
1507
1043
  * });
1508
1044
  * ```
1509
1045
  */
1510
1046
  declare function createClient(config: HydrousConfig): HydrousClient;
1511
1047
 
1512
1048
  declare class HydrousError extends Error {
1049
+ /** Machine-readable error code returned by the API. */
1513
1050
  readonly code: string;
1051
+ /** HTTP status code (if applicable). */
1514
1052
  readonly status?: number;
1053
+ /** The original request ID for support tracing. */
1515
1054
  readonly requestId?: string;
1055
+ /** Additional validation details. */
1516
1056
  readonly details?: string[];
1517
1057
  constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
1518
1058
  toString(): string;