hydrousdb 1.1.1 → 2.0.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.js CHANGED
@@ -33,7 +33,7 @@ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504])
33
33
  var HttpClient = class {
34
34
  constructor(config) {
35
35
  this.authKey = config.authKey;
36
- this.bucketKey = config.bucketKey;
36
+ this.securityKey = config.securityKey;
37
37
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
38
38
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
39
39
  this.retries = config.retries ?? DEFAULT_RETRIES;
@@ -168,31 +168,34 @@ var RecordsClient = class {
168
168
  constructor(http) {
169
169
  this.http = http;
170
170
  }
171
- get path() {
172
- return `/api/${this.http.bucketKey}`;
171
+ /** Builds the base path for a given bucket: /api/:bucketKey/:securityKey */
172
+ path(bucketKey) {
173
+ return `/api/${bucketKey}/${this.http.securityKey}`;
173
174
  }
174
175
  // ── GET — single record ────────────────────────────────────────────────────
175
176
  /**
176
177
  * Fetch a single record by ID.
177
178
  *
178
179
  * @example
179
- * const { data } = await db.records.get('rec_abc123');
180
- * const { data, history } = await db.records.get('rec_abc123', { showHistory: true });
180
+ * const { data } = await db.records.get('rec_abc123', { bucketKey: 'users' });
181
+ * const { data, history } = await db.records.get('rec_abc123', { bucketKey: 'users', showHistory: true });
181
182
  */
182
183
  async get(recordId, options) {
184
+ const { bucketKey, showHistory, ...rest } = options;
183
185
  const params = { recordId };
184
- if (options?.showHistory) params["showHistory"] = "true";
185
- return this.http.get(this.path, params, options);
186
+ if (showHistory) params["showHistory"] = "true";
187
+ return this.http.get(this.path(bucketKey), params, rest);
186
188
  }
187
189
  // ── GET — historical snapshot ──────────────────────────────────────────────
188
190
  /**
189
191
  * Fetch a specific historical version (generation) of a record.
190
192
  *
191
193
  * @example
192
- * const { data } = await db.records.getSnapshot('rec_abc123', '1700000000000000');
194
+ * const { data } = await db.records.getSnapshot('rec_abc123', '1700000000000000', { bucketKey: 'users' });
193
195
  */
194
- async getSnapshot(recordId, generation, opts) {
195
- return this.http.get(this.path, { recordId, generation }, opts);
196
+ async getSnapshot(recordId, generation, options) {
197
+ const { bucketKey, ...rest } = options;
198
+ return this.http.get(this.path(bucketKey), { recordId, generation }, rest);
196
199
  }
197
200
  // ── GET — collection query ─────────────────────────────────────────────────
198
201
  /**
@@ -200,10 +203,11 @@ var RecordsClient = class {
200
203
  *
201
204
  * @example
202
205
  * // Simple query
203
- * const { data, meta } = await db.records.query({ limit: 50, sortOrder: 'desc' });
206
+ * const { data, meta } = await db.records.query({ bucketKey: 'orders', limit: 50 });
204
207
  *
205
208
  * // Filtered query
206
209
  * const { data } = await db.records.query({
210
+ * bucketKey: 'orders',
207
211
  * filters: [{ field: 'status', op: '==', value: 'active' }],
208
212
  * timeScope: '7d',
209
213
  * });
@@ -211,52 +215,54 @@ var RecordsClient = class {
211
215
  * // Paginated
212
216
  * let cursor: string | null = null;
213
217
  * do {
214
- * const result = await db.records.query({ limit: 100, cursor: cursor ?? undefined });
218
+ * const result = await db.records.query({ bucketKey: 'orders', limit: 100, cursor: cursor ?? undefined });
215
219
  * cursor = result.meta.nextCursor;
216
220
  * } while (cursor);
217
221
  */
218
222
  async query(options) {
219
- if (options?.filters) validateFilters(options.filters);
220
- const params = buildQueryParams(options ?? {});
221
- return this.http.get(this.path, params, options);
223
+ const { bucketKey, ...rest } = options;
224
+ if (rest.filters) validateFilters(rest.filters);
225
+ const params = buildQueryParams(rest);
226
+ return this.http.get(this.path(bucketKey), params, rest);
222
227
  }
223
228
  // ── POST — insert ──────────────────────────────────────────────────────────
224
229
  /**
225
- * Insert a new record.
230
+ * Insert a new record into the specified bucket.
226
231
  *
227
232
  * @example
228
- * const { data, meta } = await db.records.insert({
229
- * values: { name: 'Alice', score: 99 },
230
- * queryableFields: ['name'],
231
- * userEmail: 'alice@example.com',
232
- * });
233
+ * const { data, meta } = await db.records.insert(
234
+ * { values: { name: 'Alice', score: 99 }, queryableFields: ['name'] },
235
+ * { bucketKey: 'users' }
236
+ * );
233
237
  */
234
- async insert(payload, opts) {
235
- return this.http.post(this.path, payload, opts);
238
+ async insert(payload, options) {
239
+ const { bucketKey, ...rest } = options;
240
+ return this.http.post(this.path(bucketKey), payload, rest);
236
241
  }
237
242
  // ── PATCH — update ─────────────────────────────────────────────────────────
238
243
  /**
239
244
  * Update an existing record.
240
245
  *
241
246
  * @example
242
- * await db.records.update({
243
- * recordId: 'rec_abc123',
244
- * values: { score: 100 },
245
- * track_record_history: true,
246
- * });
247
+ * await db.records.update(
248
+ * { recordId: 'rec_abc123', values: { score: 100 }, track_record_history: true },
249
+ * { bucketKey: 'users' }
250
+ * );
247
251
  */
248
- async update(payload, opts) {
249
- return this.http.patch(this.path, payload, opts);
252
+ async update(payload, options) {
253
+ const { bucketKey, ...rest } = options;
254
+ return this.http.patch(this.path(bucketKey), payload, rest);
250
255
  }
251
256
  // ── DELETE ─────────────────────────────────────────────────────────────────
252
257
  /**
253
258
  * Delete a record permanently.
254
259
  *
255
260
  * @example
256
- * await db.records.delete('rec_abc123');
261
+ * await db.records.delete('rec_abc123', { bucketKey: 'users' });
257
262
  */
258
- async delete(recordId, opts) {
259
- return this.http.delete(this.path, { recordId }, opts);
263
+ async delete(recordId, options) {
264
+ const { bucketKey, ...rest } = options;
265
+ return this.http.delete(this.path(bucketKey), { recordId }, rest);
260
266
  }
261
267
  // ── HEAD — existence check ─────────────────────────────────────────────────
262
268
  /**
@@ -264,11 +270,12 @@ var RecordsClient = class {
264
270
  * Returns `null` if the record is not found.
265
271
  *
266
272
  * @example
267
- * const info = await db.records.exists('rec_abc123');
273
+ * const info = await db.records.exists('rec_abc123', { bucketKey: 'users' });
268
274
  * if (info?.exists) console.log('found at', info.updatedAt);
269
275
  */
270
- async exists(recordId, opts) {
271
- const res = await this.http.head(this.path, { recordId }, opts);
276
+ async exists(recordId, options) {
277
+ const { bucketKey, ...rest } = options;
278
+ const res = await this.http.head(this.path(bucketKey), { recordId }, rest);
272
279
  if (res.status === 404) return null;
273
280
  if (!res.ok) return null;
274
281
  return {
@@ -284,33 +291,28 @@ var RecordsClient = class {
284
291
  * Update up to 500 records in a single request.
285
292
  *
286
293
  * @example
287
- * await db.records.batchUpdate({
288
- * updates: [
289
- * { recordId: 'rec_1', values: { status: 'archived' } },
290
- * { recordId: 'rec_2', values: { status: 'archived' } },
291
- * ],
292
- * });
294
+ * await db.records.batchUpdate(
295
+ * { updates: [{ recordId: 'rec_1', values: { status: 'archived' } }] },
296
+ * { bucketKey: 'orders' }
297
+ * );
293
298
  */
294
- async batchUpdate(payload, opts) {
295
- return this.http.post(
296
- `${this.path}/batch/update`,
297
- payload,
298
- opts
299
- );
299
+ async batchUpdate(payload, options) {
300
+ const { bucketKey, ...rest } = options;
301
+ return this.http.post(`${this.path(bucketKey)}/batch/update`, payload, rest);
300
302
  }
301
303
  // ── Batch — delete ─────────────────────────────────────────────────────────
302
304
  /**
303
305
  * Delete up to 500 records in a single request.
304
306
  *
305
307
  * @example
306
- * await db.records.batchDelete({ recordIds: ['rec_1', 'rec_2', 'rec_3'] });
308
+ * await db.records.batchDelete(
309
+ * { recordIds: ['rec_1', 'rec_2'] },
310
+ * { bucketKey: 'orders' }
311
+ * );
307
312
  */
308
- async batchDelete(payload, opts) {
309
- return this.http.post(
310
- `${this.path}/batch/delete`,
311
- payload,
312
- opts
313
- );
313
+ async batchDelete(payload, options) {
314
+ const { bucketKey, ...rest } = options;
315
+ return this.http.post(`${this.path(bucketKey)}/batch/delete`, payload, rest);
314
316
  }
315
317
  // ── Batch — insert ─────────────────────────────────────────────────────────
316
318
  /**
@@ -318,17 +320,14 @@ var RecordsClient = class {
318
320
  * Returns HTTP 207 (multi-status) — check `meta.failed` for partial failures.
319
321
  *
320
322
  * @example
321
- * const result = await db.records.batchInsert({
322
- * records: [{ name: 'Alice' }, { name: 'Bob' }],
323
- * queryableFields: ['name'],
324
- * });
323
+ * const result = await db.records.batchInsert(
324
+ * { records: [{ name: 'Alice' }, { name: 'Bob' }], queryableFields: ['name'] },
325
+ * { bucketKey: 'users' }
326
+ * );
325
327
  */
326
- async batchInsert(payload, opts) {
327
- return this.http.post(
328
- `${this.path}/batch/insert`,
329
- payload,
330
- opts
331
- );
328
+ async batchInsert(payload, options) {
329
+ const { bucketKey, ...rest } = options;
330
+ return this.http.post(`${this.path(bucketKey)}/batch/insert`, payload, rest);
332
331
  }
333
332
  // ── Helpers ────────────────────────────────────────────────────────────────
334
333
  /**
@@ -337,6 +336,7 @@ var RecordsClient = class {
337
336
  *
338
337
  * @example
339
338
  * const allRecords = await db.records.queryAll({
339
+ * bucketKey: 'orders',
340
340
  * filters: [{ field: 'type', op: '==', value: 'invoice' }],
341
341
  * });
342
342
  */
@@ -344,7 +344,9 @@ var RecordsClient = class {
344
344
  const all = [];
345
345
  let cursor = null;
346
346
  do {
347
- const result = await this.query(cursor ? { ...options, cursor } : { ...options });
347
+ const result = await this.query(
348
+ cursor ? { ...options, cursor } : { ...options }
349
+ );
348
350
  all.push(...result.data);
349
351
  cursor = result.meta.nextCursor ?? null;
350
352
  } while (cursor);
@@ -572,214 +574,177 @@ var AnalyticsClient = class {
572
574
  constructor(http) {
573
575
  this.http = http;
574
576
  }
575
- get path() {
576
- return `/api/analytics/${this.http.bucketKey}/${this.http.bucketKey}`;
577
+ /** Builds the path for a given bucket: /api/analytics/:bucketKey/:securityKey */
578
+ path(bucketKey) {
579
+ return `/api/analytics/${bucketKey}/${this.http.securityKey}`;
577
580
  }
578
581
  // ── Raw query ─────────────────────────────────────────────────────────────
579
582
  /**
580
583
  * Run any analytics query with full control over the payload.
581
584
  * Prefer the typed convenience methods below for everyday use.
582
585
  */
583
- async query(payload, opts) {
584
- return this.http.post(this.path, payload, opts);
586
+ async query(payload, options) {
587
+ const { bucketKey, ...rest } = options;
588
+ return this.http.post(this.path(bucketKey), payload, rest);
585
589
  }
586
590
  // ── count ──────────────────────────────────────────────────────────────────
587
591
  /**
588
592
  * Total record count, optionally scoped to a date range.
589
- * Server `queryType`: **"count"**
590
593
  *
591
594
  * @example
592
- * const { data } = await db.analytics.count();
593
- * console.log(data.count); // 1234
595
+ * const { data } = await db.analytics.count({ bucketKey: 'orders' });
596
+ * console.log(data.count);
594
597
  *
595
- * // With date range
596
598
  * const { data } = await db.analytics.count({
597
- * dateRange: { startDate: '2025-01-01', endDate: '2025-12-31' }
599
+ * bucketKey: 'orders',
600
+ * dateRange: { startDate: '2025-01-01', endDate: '2025-12-31' },
598
601
  * });
599
602
  */
600
603
  async count(options) {
601
- return this.query({ queryType: "count", dateRange: options?.dateRange }, options);
604
+ const { dateRange, ...rest } = options;
605
+ return this.query({ queryType: "count", dateRange }, rest);
602
606
  }
603
607
  // ── distribution ───────────────────────────────────────────────────────────
604
608
  /**
605
609
  * Value distribution (histogram) for a field.
606
- * Server `queryType`: **"distribution"**
607
610
  *
608
611
  * @example
609
- * const { data } = await db.analytics.distribution('status');
612
+ * const { data } = await db.analytics.distribution('status', { bucketKey: 'orders' });
610
613
  * // [{ value: 'active', count: 80 }, { value: 'archived', count: 20 }]
611
614
  */
612
615
  async distribution(field, options) {
613
- return this.query({
614
- queryType: "distribution",
615
- field,
616
- limit: options?.limit,
617
- order: options?.order,
618
- dateRange: options?.dateRange
619
- }, options);
616
+ const { limit, order, dateRange, ...rest } = options;
617
+ return this.query({ queryType: "distribution", field, limit, order, dateRange }, rest);
620
618
  }
621
619
  // ── sum ────────────────────────────────────────────────────────────────────
622
620
  /**
623
621
  * Sum a numeric field, with optional group-by.
624
- * Server `queryType`: **"sum"**
625
622
  *
626
623
  * @example
627
- * const { data } = await db.analytics.sum('revenue', { groupBy: 'region' });
624
+ * const { data } = await db.analytics.sum('revenue', { bucketKey: 'orders', groupBy: 'region' });
628
625
  */
629
626
  async sum(field, options) {
630
- return this.query({
631
- queryType: "sum",
632
- field,
633
- groupBy: options?.groupBy,
634
- limit: options?.limit,
635
- dateRange: options?.dateRange
636
- }, options);
627
+ const { groupBy, limit, dateRange, ...rest } = options;
628
+ return this.query({ queryType: "sum", field, groupBy, limit, dateRange }, rest);
637
629
  }
638
630
  // ── timeSeries ─────────────────────────────────────────────────────────────
639
631
  /**
640
632
  * Record count grouped over time.
641
- * Server `queryType`: **"timeSeries"**
642
633
  *
643
634
  * @example
644
- * const { data } = await db.analytics.timeSeries({ granularity: 'day' });
635
+ * const { data } = await db.analytics.timeSeries({ bucketKey: 'orders', granularity: 'day' });
645
636
  * // [{ date: '2025-01-01', count: 42 }, ...]
646
637
  */
647
638
  async timeSeries(options) {
648
- return this.query({
649
- queryType: "timeSeries",
650
- granularity: options?.granularity,
651
- dateRange: options?.dateRange
652
- }, options);
639
+ const { granularity, dateRange, ...rest } = options;
640
+ return this.query({ queryType: "timeSeries", granularity, dateRange }, rest);
653
641
  }
654
642
  // ── fieldTimeSeries ────────────────────────────────────────────────────────
655
643
  /**
656
644
  * Aggregate a numeric field over time.
657
- * Server `queryType`: **"fieldTimeSeries"**
658
645
  *
659
646
  * @example
660
647
  * const { data } = await db.analytics.fieldTimeSeries('revenue', {
648
+ * bucketKey: 'orders',
661
649
  * granularity: 'month',
662
650
  * aggregation: 'sum',
663
651
  * });
664
652
  */
665
653
  async fieldTimeSeries(field, options) {
666
- return this.query({
667
- queryType: "fieldTimeSeries",
668
- field,
669
- aggregation: options?.aggregation,
670
- granularity: options?.granularity,
671
- dateRange: options?.dateRange
672
- }, options);
654
+ const { aggregation, granularity, dateRange, ...rest } = options;
655
+ return this.query({ queryType: "fieldTimeSeries", field, aggregation, granularity, dateRange }, rest);
673
656
  }
674
657
  // ── topN ───────────────────────────────────────────────────────────────────
675
658
  /**
676
659
  * Top N most frequent values for a field.
677
- * Server `queryType`: **"topN"**
678
660
  *
679
661
  * @example
680
- * const { data } = await db.analytics.topN('country', 5);
662
+ * const { data } = await db.analytics.topN('country', 5, { bucketKey: 'users' });
681
663
  * // [{ label: 'US', value: 'US', count: 500 }, ...]
682
664
  */
683
665
  async topN(field, n = 10, options) {
684
- return this.query({
685
- queryType: "topN",
686
- field,
687
- n,
688
- labelField: options?.labelField,
689
- order: options?.order,
690
- dateRange: options?.dateRange
691
- }, options);
666
+ const { labelField, order, dateRange, ...rest } = options;
667
+ return this.query({ queryType: "topN", field, n, labelField, order, dateRange }, rest);
692
668
  }
693
669
  // ── stats ──────────────────────────────────────────────────────────────────
694
670
  /**
695
671
  * Statistical summary for a numeric field: min, max, avg, stddev, p50, p90, p99.
696
- * Server `queryType`: **"stats"**
697
672
  *
698
673
  * @example
699
- * const { data } = await db.analytics.stats('score');
674
+ * const { data } = await db.analytics.stats('score', { bucketKey: 'users' });
700
675
  * console.log(data.avg, data.p99);
701
676
  */
702
677
  async stats(field, options) {
703
- return this.query({ queryType: "stats", field, dateRange: options?.dateRange }, options);
678
+ const { dateRange, ...rest } = options;
679
+ return this.query({ queryType: "stats", field, dateRange }, rest);
704
680
  }
705
681
  // ── records ────────────────────────────────────────────────────────────────
706
682
  /**
707
683
  * Filtered, paginated raw records with optional field projection.
708
684
  * Supports filter ops: == != > < >= <= CONTAINS
709
- * Server `queryType`: **"records"**
710
685
  *
711
686
  * @example
712
687
  * const { data } = await db.analytics.records({
688
+ * bucketKey: 'users',
713
689
  * filters: [{ field: 'role', op: '==', value: 'admin' }],
714
690
  * selectFields: ['email', 'createdAt'],
715
691
  * limit: 25,
716
692
  * });
717
- * console.log(data.data, data.hasMore);
718
693
  */
719
694
  async records(options) {
720
- return this.query({
721
- queryType: "records",
722
- filters: options?.filters,
723
- selectFields: options?.selectFields,
724
- limit: options?.limit,
725
- offset: options?.offset,
726
- orderBy: options?.orderBy,
727
- order: options?.order,
728
- dateRange: options?.dateRange
729
- }, options);
695
+ const { filters, selectFields, limit, offset, orderBy, order, dateRange, ...rest } = options;
696
+ return this.query(
697
+ { queryType: "records", filters, selectFields, limit, offset, orderBy, order, dateRange },
698
+ rest
699
+ );
730
700
  }
731
701
  // ── multiMetric ────────────────────────────────────────────────────────────
732
702
  /**
733
- * Multiple aggregations in a single BigQuery call — ideal for dashboard stat cards.
734
- * Server `queryType`: **"multiMetric"**
703
+ * Multiple aggregations in a single call — ideal for dashboard stat cards.
735
704
  *
736
705
  * @example
737
- * const { data } = await db.analytics.multiMetric([
738
- * { name: 'totalRevenue', field: 'amount', aggregation: 'sum' },
739
- * { name: 'avgScore', field: 'score', aggregation: 'avg' },
740
- * { name: 'userCount', field: 'userId', aggregation: 'count' },
741
- * ]);
742
- * console.log(data.totalRevenue, data.avgScore, data.userCount);
706
+ * const { data } = await db.analytics.multiMetric(
707
+ * [
708
+ * { name: 'totalRevenue', field: 'amount', aggregation: 'sum' },
709
+ * { name: 'avgScore', field: 'score', aggregation: 'avg' },
710
+ * ],
711
+ * { bucketKey: 'orders' }
712
+ * );
713
+ * console.log(data.totalRevenue, data.avgScore);
743
714
  */
744
715
  async multiMetric(metrics, options) {
745
- return this.query({
746
- queryType: "multiMetric",
747
- metrics,
748
- dateRange: options?.dateRange
749
- }, options);
716
+ const { dateRange, ...rest } = options;
717
+ return this.query({ queryType: "multiMetric", metrics, dateRange }, rest);
750
718
  }
751
719
  // ── storageStats ───────────────────────────────────────────────────────────
752
720
  /**
753
- * Storage statistics for the bucket — total records, bytes, avg/min/max size.
754
- * Server `queryType`: **"storageStats"**
721
+ * Storage statistics for a bucket — total records, bytes, avg/min/max size.
755
722
  *
756
723
  * @example
757
- * const { data } = await db.analytics.storageStats();
724
+ * const { data } = await db.analytics.storageStats({ bucketKey: 'orders' });
758
725
  * console.log(data.totalRecords, data.totalBytes);
759
726
  */
760
727
  async storageStats(options) {
761
- return this.query({ queryType: "storageStats", dateRange: options?.dateRange }, options);
728
+ const { dateRange, ...rest } = options;
729
+ return this.query({ queryType: "storageStats", dateRange }, rest);
762
730
  }
763
731
  // ── crossBucket ────────────────────────────────────────────────────────────
764
732
  /**
765
733
  * Compare a metric across multiple buckets in one query.
766
- * Server `queryType`: **"crossBucket"**
734
+ * Note: `bucketKey` here is used only for auth — the actual buckets
735
+ * compared are specified via `bucketKeys`.
767
736
  *
768
737
  * @example
769
738
  * const { data } = await db.analytics.crossBucket({
770
- * bucketKeys: ['sales', 'refunds', 'trials'],
739
+ * bucketKey: 'sales-2025', // auth bucket
740
+ * bucketKeys: ['sales-2024', 'sales-2025'],
771
741
  * field: 'amount',
772
742
  * aggregation: 'sum',
773
743
  * });
774
744
  */
775
745
  async crossBucket(options) {
776
- return this.query({
777
- queryType: "crossBucket",
778
- bucketKeys: options.bucketKeys,
779
- field: options.field,
780
- aggregation: options.aggregation,
781
- dateRange: options.dateRange
782
- }, options);
746
+ const { bucketKeys, field, aggregation, dateRange, ...rest } = options;
747
+ return this.query({ queryType: "crossBucket", bucketKeys, field, aggregation, dateRange }, rest);
783
748
  }
784
749
  };
785
750
 
@@ -787,7 +752,7 @@ var AnalyticsClient = class {
787
752
  var HydrousClient = class {
788
753
  constructor(config) {
789
754
  if (!config.authKey) throw new Error("[hydrousdb] authKey is required");
790
- if (!config.bucketKey) throw new Error("[hydrousdb] bucketKey is required");
755
+ if (!config.securityKey) throw new Error("[hydrousdb] securityKey is required");
791
756
  this.http = new HttpClient(config);
792
757
  this.records = new RecordsClient(this.http);
793
758
  this.auth = new AuthClient(this.http);