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