@bluecopa/core 0.1.58 → 0.1.59

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/README.md CHANGED
@@ -24,6 +24,7 @@ The core package provides essential API utilities and functions for data managem
24
24
  - [Quick Start](#quick-start)
25
25
  - [CRUD](#crud)
26
26
  - [Query Operators](#query-operators)
27
+ - [Aggregate Queries](#aggregate-queries)
27
28
  - [Framework Integration](#framework-integration)
28
29
  - [WebSocket Provider (optional)](#websocket-provider-optional)
29
30
  - [Cleanup](#cleanup)
@@ -248,6 +249,70 @@ const unsub = copaInputTableDb.collection("invoices").count((n) => console.log(n
248
249
  | `in` | in array |
249
250
  | `not-in` | not in array |
250
251
 
252
+ ### Aggregate Queries
253
+
254
+ Compute server-side aggregates (`sum`, `avg`, `count`, `min`, `max`) without fetching all rows. Combines with `where()`, `limit()`, and `skip()` filters.
255
+
256
+ ```typescript
257
+ // Column aggregates
258
+ const result = await copaInputTableDb
259
+ .collection("invoices")
260
+ .where("status", "==", "active")
261
+ .aggregate({ amount: ["sum", "avg"], price: ["min", "max"] });
262
+ // => { amount: { sum: 1234.56, avg: 123.45 }, price: { min: 10, max: 999 } }
263
+
264
+ // Row count
265
+ const result = await copaInputTableDb
266
+ .collection("invoices")
267
+ .aggregate({ _count: true });
268
+ // => { _count: 42 }
269
+
270
+ // Column count (non-null values) + row count
271
+ const result = await copaInputTableDb
272
+ .collection("invoices")
273
+ .aggregate({ name: ["count"], _count: true });
274
+ // => { name: { count: 38 }, _count: 42 }
275
+ ```
276
+
277
+ `.aggregate()` is a **terminal method** — it bypasses local RxDB and hits PostgREST directly. Errors throw `InputTableError`. An empty spec `{}` returns `{}` without calling the API.
278
+
279
+ ### Grouped Aggregates
280
+
281
+ Add a `{ groupBy: [...columns] }` second argument to get per-group breakdowns. Returns an array instead of a single object.
282
+
283
+ ```typescript
284
+ // Sum per status group
285
+ const rows = await copaInputTableDb
286
+ .collection("invoices")
287
+ .aggregate({ amount: ["sum"] }, { groupBy: ["status"] });
288
+ // => [{ status: "active", amount: { sum: 1234 } }, { status: "draft", amount: { sum: 100 } }]
289
+
290
+ // Multi-column groupBy with filters and ordering
291
+ const rows = await copaInputTableDb
292
+ .collection("invoices")
293
+ .where("year", "==", 2024)
294
+ .orderBy("order_date", "asc")
295
+ .aggregate({ amount: ["sum", "avg"] }, { groupBy: ["order_date", "status"] });
296
+
297
+ // Count per group
298
+ const rows = await copaInputTableDb
299
+ .collection("invoices")
300
+ .aggregate({ _count: true }, { groupBy: ["status"] });
301
+ // => [{ status: "active", _count: 10 }, { status: "draft", _count: 4 }]
302
+
303
+ // Distinct values (no aggregate functions)
304
+ const rows = await copaInputTableDb
305
+ .collection("invoices")
306
+ .aggregate({}, { groupBy: ["status"] });
307
+ // => [{ status: "active" }, { status: "draft" }]
308
+ ```
309
+
310
+ **Notes:**
311
+ - `orderBy()` is forwarded to PostgREST when `groupBy` is present (ignored otherwise)
312
+ - `limit()`/`skip()` apply to the number of _groups_, not input rows
313
+ - A column cannot appear in both the aggregate spec and `groupBy` — throws `InputTableError`
314
+ - Empty `groupBy: []` behaves like no groupBy — returns a single object
315
+
251
316
  ### Framework Integration
252
317
 
253
318
  **Svelte 5**
package/dist/config.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface Config {
6
6
  workspaceId: string;
7
7
  userId: string;
8
8
  solutionId?: string;
9
+ solutionBranch?: string;
10
+ solutionBranchType?: string;
9
11
  websocketProvider?: IWebsocketProvider;
10
12
  }
11
13
  declare class ConfigSingleton {
package/dist/index.es.js CHANGED
@@ -37,6 +37,8 @@ class ConfigSingleton {
37
37
  workspaceId: "",
38
38
  userId: "",
39
39
  solutionId: void 0,
40
+ solutionBranch: void 0,
41
+ solutionBranchType: void 0,
40
42
  websocketProvider: void 0
41
43
  };
42
44
  }
@@ -54,6 +56,22 @@ const createApiClient = () => {
54
56
  // Increased to 120 seconds for audit log queries
55
57
  headers: {
56
58
  "Content-Type": "application/json"
59
+ },
60
+ // PostgREST requires repeated params for multi-value filters: amount=gt.10&amount=lt.100
61
+ // Axios's default qs serializer uses indices format (amount[0]=gt.10) which PostgREST
62
+ // does not understand. This custom serializer produces the correct repeated-param format.
63
+ paramsSerializer: (params) => {
64
+ const parts = [];
65
+ for (const [key, value] of Object.entries(params)) {
66
+ if (Array.isArray(value)) {
67
+ for (const v of value) {
68
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`);
69
+ }
70
+ } else if (value !== null && value !== void 0) {
71
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
72
+ }
73
+ }
74
+ return parts.join("&");
57
75
  }
58
76
  });
59
77
  client.interceptors.request.use(
@@ -71,11 +89,6 @@ const createApiClient = () => {
71
89
  if (copaConfig.solutionId && config.headers) {
72
90
  config.headers["x-bluecopa-solution-id"] = copaConfig.solutionId;
73
91
  }
74
- console.log("API Request Config:", {
75
- baseURL: config.baseURL,
76
- hasToken: !!copaConfig.accessToken,
77
- hasWorkspaceId: !!copaConfig.workspaceId
78
- });
79
92
  return config;
80
93
  },
81
94
  (error) => {
@@ -12260,6 +12273,75 @@ var InputTableColumnType = /* @__PURE__ */ ((InputTableColumnType2) => {
12260
12273
  InputTableColumnType2["JSONB"] = "jsonb";
12261
12274
  return InputTableColumnType2;
12262
12275
  })(InputTableColumnType || {});
12276
+ const IDENTIFIER_PATTERN = /^[a-zA-Z0-9_-]+$/;
12277
+ const SOLUTION_BRANCH_COOKIE_NAME = "x-bluecopa-solution-branch";
12278
+ const SOLUTION_BRANCH_TYPE_COOKIE_NAME = "x-bluecopa-solution-branch-type";
12279
+ function readCookie(name) {
12280
+ try {
12281
+ const cookie = document.cookie.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${name}=`));
12282
+ return cookie ? decodeURIComponent(cookie.split("=").slice(1).join("=")) : null;
12283
+ } catch {
12284
+ return null;
12285
+ }
12286
+ }
12287
+ function getSolutionBranchHeaders() {
12288
+ const config = getConfig();
12289
+ const branch = config.solutionBranch ?? readCookie(SOLUTION_BRANCH_COOKIE_NAME);
12290
+ const branchType = config.solutionBranchType ?? readCookie(SOLUTION_BRANCH_TYPE_COOKIE_NAME);
12291
+ const headers = {};
12292
+ if (branch) headers["x-bluecopa-solution-branch"] = branch;
12293
+ if (branchType) headers["x-bluecopa-solution-branch-type"] = branchType;
12294
+ return headers;
12295
+ }
12296
+ const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
12297
+ class InputTableError extends Error {
12298
+ constructor(message, status) {
12299
+ super(message);
12300
+ this.status = status;
12301
+ this.name = "InputTableError";
12302
+ }
12303
+ }
12304
+ function validateIdentifier(value, label) {
12305
+ const trimmed = value.trim();
12306
+ if (!trimmed || !IDENTIFIER_PATTERN.test(trimmed)) {
12307
+ throw new InputTableError(`Invalid ${label}: "${value}"`, 400);
12308
+ }
12309
+ return trimmed;
12310
+ }
12311
+ function validateCheckpointTimestamp(value) {
12312
+ if (!ISO_TIMESTAMP_PATTERN.test(value)) {
12313
+ throw new InputTableError(`Invalid checkpoint timestamp: "${value}"`, 400);
12314
+ }
12315
+ return value;
12316
+ }
12317
+ function validateCheckpointId(value) {
12318
+ return validateIdentifier(value, "checkpoint id");
12319
+ }
12320
+ function stripNullsAndCoercePk(row, primaryKeyField) {
12321
+ const doc = {};
12322
+ for (const [key, value] of Object.entries(row)) {
12323
+ if (value === null) continue;
12324
+ doc[key] = value;
12325
+ }
12326
+ doc[primaryKeyField] = String(row[primaryKeyField]);
12327
+ return doc;
12328
+ }
12329
+ function deferSubscription(resolve, callback, fallback) {
12330
+ let innerUnsub = null;
12331
+ let cancelled = false;
12332
+ resolve().then((subscribeFn) => {
12333
+ if (!cancelled) {
12334
+ innerUnsub = subscribeFn(callback);
12335
+ }
12336
+ }).catch((err) => {
12337
+ console.error("[copaInputTableDb]", err);
12338
+ if (!cancelled) callback(fallback);
12339
+ });
12340
+ return () => {
12341
+ cancelled = true;
12342
+ innerUnsub == null ? void 0 : innerUnsub();
12343
+ };
12344
+ }
12263
12345
  const OP_MAP = {
12264
12346
  "==": "$eq",
12265
12347
  "!=": "$ne",
@@ -12271,8 +12353,9 @@ const OP_MAP = {
12271
12353
  "not-in": "$nin"
12272
12354
  };
12273
12355
  class QueryBuilderImpl {
12274
- constructor(collection, initialWhere, initialOrder, initialLimit, initialSkip) {
12356
+ constructor(collection, initialWhere, initialOrder, initialLimit, initialSkip, aggregateExecutor) {
12275
12357
  this.collection = collection;
12358
+ this.aggregateExecutor = aggregateExecutor;
12276
12359
  this.wheres = [];
12277
12360
  this.orders = [];
12278
12361
  if (initialWhere) this.wheres.push(initialWhere);
@@ -12308,6 +12391,22 @@ class QueryBuilderImpl {
12308
12391
  const docs = await query.exec();
12309
12392
  return docs.map((d) => d.toJSON());
12310
12393
  }
12394
+ async aggregate(spec, options) {
12395
+ if (!this.aggregateExecutor) {
12396
+ throw new InputTableError(
12397
+ "[copaInputTableDb] aggregate() is not available on this QueryBuilder instance",
12398
+ 500
12399
+ );
12400
+ }
12401
+ return this.aggregateExecutor(
12402
+ this.wheres,
12403
+ this.orders,
12404
+ this.limitCount,
12405
+ this.skipCount,
12406
+ spec,
12407
+ options == null ? void 0 : options.groupBy
12408
+ );
12409
+ }
12311
12410
  buildQuery() {
12312
12411
  const selector = {};
12313
12412
  for (const { field, op, value } of this.wheres) {
@@ -12333,56 +12432,6 @@ const MAX_COLLECTIONS = 14;
12333
12432
  const REPLICATION_RETRY_TIME = 5e3;
12334
12433
  const PULL_BATCH_SIZE = 100;
12335
12434
  const PUSH_BATCH_SIZE = 10;
12336
- const IDENTIFIER_PATTERN = /^[a-zA-Z0-9_-]+$/;
12337
- const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
12338
- class InputTableError extends Error {
12339
- constructor(message, status) {
12340
- super(message);
12341
- this.status = status;
12342
- this.name = "InputTableError";
12343
- }
12344
- }
12345
- function validateIdentifier(value, label) {
12346
- const trimmed = value.trim();
12347
- if (!trimmed || !IDENTIFIER_PATTERN.test(trimmed)) {
12348
- throw new InputTableError(`Invalid ${label}: "${value}"`, 400);
12349
- }
12350
- return trimmed;
12351
- }
12352
- function validateCheckpointTimestamp(value) {
12353
- if (!ISO_TIMESTAMP_PATTERN.test(value)) {
12354
- throw new InputTableError(`Invalid checkpoint timestamp: "${value}"`, 400);
12355
- }
12356
- return value;
12357
- }
12358
- function validateCheckpointId(value) {
12359
- return validateIdentifier(value, "checkpoint id");
12360
- }
12361
- function stripNullsAndCoercePk(row, primaryKeyField) {
12362
- const doc = {};
12363
- for (const [key, value] of Object.entries(row)) {
12364
- if (value === null) continue;
12365
- doc[key] = value;
12366
- }
12367
- doc[primaryKeyField] = String(row[primaryKeyField]);
12368
- return doc;
12369
- }
12370
- function deferSubscription(resolve, callback, fallback) {
12371
- let innerUnsub = null;
12372
- let cancelled = false;
12373
- resolve().then((subscribeFn) => {
12374
- if (!cancelled) {
12375
- innerUnsub = subscribeFn(callback);
12376
- }
12377
- }).catch((err) => {
12378
- console.error("[copaInputTableDb]", err);
12379
- if (!cancelled) callback(fallback);
12380
- });
12381
- return () => {
12382
- cancelled = true;
12383
- innerUnsub == null ? void 0 : innerUnsub();
12384
- };
12385
- }
12386
12435
  class DocRefImpl {
12387
12436
  constructor(collection, id, solutionId, tableName, primaryKeyField, websocketProvider) {
12388
12437
  this.collection = collection;
@@ -12422,6 +12471,168 @@ class DocRefImpl {
12422
12471
  return () => rxSub.unsubscribe();
12423
12472
  }
12424
12473
  }
12474
+ const POSTGREST_OP_MAP = {
12475
+ "==": "eq",
12476
+ "!=": "neq",
12477
+ "<": "lt",
12478
+ "<=": "lte",
12479
+ ">": "gt",
12480
+ ">=": "gte",
12481
+ in: "in",
12482
+ "not-in": "not.in"
12483
+ };
12484
+ const VALID_AGGREGATE_FUNCTIONS = /* @__PURE__ */ new Set([
12485
+ "sum",
12486
+ "avg",
12487
+ "count",
12488
+ "min",
12489
+ "max"
12490
+ ]);
12491
+ function validateAggregateSpec(spec) {
12492
+ for (const [key, value] of Object.entries(spec)) {
12493
+ if (key === "_count") {
12494
+ if (value !== true) {
12495
+ throw new InputTableError(
12496
+ `"_count" must be exactly \`true\`, got: ${JSON.stringify(value)}`,
12497
+ 400
12498
+ );
12499
+ }
12500
+ continue;
12501
+ }
12502
+ validateIdentifier(key, `aggregate column "${key}"`);
12503
+ if (!Array.isArray(value) || value.length === 0) {
12504
+ throw new InputTableError(
12505
+ `Aggregate spec column "${key}" must have a non-empty array of functions`,
12506
+ 400
12507
+ );
12508
+ }
12509
+ for (const fn of value) {
12510
+ if (!VALID_AGGREGATE_FUNCTIONS.has(fn)) {
12511
+ throw new InputTableError(
12512
+ `Unknown aggregate function "${fn}" for column "${key}". Allowed: sum, avg, count, min, max`,
12513
+ 400
12514
+ );
12515
+ }
12516
+ }
12517
+ }
12518
+ }
12519
+ function validateGroupBySpec(groupBy, spec) {
12520
+ const specKeys = new Set(Object.keys(spec).filter((k) => k !== "_count"));
12521
+ if (specKeys.size === 0 && !spec._count) {
12522
+ throw new InputTableError(
12523
+ "groupBy requires at least one aggregate function or `_count: true` in the spec",
12524
+ 400
12525
+ );
12526
+ }
12527
+ for (const col of groupBy) {
12528
+ validateIdentifier(col, `groupBy column "${col}"`);
12529
+ if (specKeys.has(col)) {
12530
+ throw new InputTableError(
12531
+ `groupBy column "${col}" overlaps with aggregate spec key. Use a different column name or separate the queries.`,
12532
+ 400
12533
+ );
12534
+ }
12535
+ }
12536
+ }
12537
+ function buildAggregateSelect(spec, groupBy) {
12538
+ const parts = [];
12539
+ if (groupBy && groupBy.length > 0) {
12540
+ for (const col of groupBy) {
12541
+ parts.push(col);
12542
+ }
12543
+ }
12544
+ for (const [key, value] of Object.entries(spec)) {
12545
+ if (key === "_count") continue;
12546
+ for (const fn of value) {
12547
+ parts.push(`${key}_${fn}:${key}.${fn}()`);
12548
+ }
12549
+ }
12550
+ if (spec._count) {
12551
+ parts.push("count()");
12552
+ }
12553
+ return parts.join(",");
12554
+ }
12555
+ function buildPostgrestOrder(orders) {
12556
+ for (const { field } of orders) {
12557
+ validateIdentifier(field, `order field "${field}"`);
12558
+ }
12559
+ return orders.map(({ field, direction }) => `${field}.${direction}`).join(",");
12560
+ }
12561
+ const RESERVED_PARAMS = /* @__PURE__ */ new Set(["select", "order", "limit", "offset", "on_conflict", "columns", "and", "or", "not"]);
12562
+ function buildPostgrestFilters(wheres) {
12563
+ const params = {};
12564
+ for (const { field, op, value } of wheres) {
12565
+ validateIdentifier(field, `where field "${field}"`);
12566
+ if (RESERVED_PARAMS.has(field)) {
12567
+ throw new InputTableError(
12568
+ `Cannot filter on reserved PostgREST param name "${field}"`,
12569
+ 400
12570
+ );
12571
+ }
12572
+ const pgOp = POSTGREST_OP_MAP[op];
12573
+ let filterStr;
12574
+ if (op === "in" || op === "not-in") {
12575
+ const arr = Array.isArray(value) ? value : [value];
12576
+ filterStr = `${pgOp}.(${arr.map(quotePostgrestValue).join(",")})`;
12577
+ } else {
12578
+ filterStr = `${pgOp}.${quotePostgrestValue(value)}`;
12579
+ }
12580
+ const existing = params[field];
12581
+ if (existing === void 0) {
12582
+ params[field] = filterStr;
12583
+ } else if (Array.isArray(existing)) {
12584
+ existing.push(filterStr);
12585
+ } else {
12586
+ params[field] = [existing, filterStr];
12587
+ }
12588
+ }
12589
+ return params;
12590
+ }
12591
+ function quotePostgrestValue(value) {
12592
+ if (value === null || value === void 0) {
12593
+ throw new InputTableError("Filter value must not be null or undefined", 400);
12594
+ }
12595
+ if (typeof value === "object") {
12596
+ throw new InputTableError(
12597
+ `Filter value must be a primitive, got: ${Array.isArray(value) ? "array" : "object"}`,
12598
+ 400
12599
+ );
12600
+ }
12601
+ const str = String(value);
12602
+ if (/[,.:()\\"']/.test(str)) {
12603
+ const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
12604
+ return `"${escaped}"`;
12605
+ }
12606
+ return str;
12607
+ }
12608
+ function parseRowAggregates(spec, row) {
12609
+ const result = {};
12610
+ for (const [key, value] of Object.entries(spec)) {
12611
+ if (key === "_count") continue;
12612
+ const nested = {};
12613
+ for (const fn of value) {
12614
+ const alias = `${key}_${fn}`;
12615
+ nested[fn] = alias in row ? row[alias] : null;
12616
+ }
12617
+ result[key] = nested;
12618
+ }
12619
+ if (spec._count) {
12620
+ result["_count"] = row["count"] ?? 0;
12621
+ }
12622
+ return result;
12623
+ }
12624
+ function parseAggregateResponse(spec, data) {
12625
+ return parseRowAggregates(spec, data[0] ?? {});
12626
+ }
12627
+ function parseGroupedAggregateResponse(spec, groupBy, data) {
12628
+ return data.map((row) => {
12629
+ const result = parseRowAggregates(spec, row);
12630
+ for (const col of groupBy) {
12631
+ result[col] = row[col];
12632
+ }
12633
+ return result;
12634
+ });
12635
+ }
12425
12636
  class CollectionRefImpl {
12426
12637
  constructor(collection, solutionId, tableName, primaryKeyField, pkColumn, replicationState, getWebsocketProvider) {
12427
12638
  this.collection = collection;
@@ -12432,17 +12643,27 @@ class CollectionRefImpl {
12432
12643
  this.replicationState = replicationState;
12433
12644
  this.getWebsocketProvider = getWebsocketProvider;
12434
12645
  }
12646
+ makeQueryBuilder(initialWhere, initialOrder, initialLimit, initialSkip) {
12647
+ return new QueryBuilderImpl(
12648
+ this.collection,
12649
+ initialWhere,
12650
+ initialOrder,
12651
+ initialLimit,
12652
+ initialSkip,
12653
+ (wheres, orders, limit, skip, spec, groupBy) => this.executeAggregate(wheres, orders, limit, skip, spec, groupBy)
12654
+ );
12655
+ }
12435
12656
  where(field, op, value) {
12436
- return new QueryBuilderImpl(this.collection, { field, op, value });
12657
+ return this.makeQueryBuilder({ field, op, value });
12437
12658
  }
12438
12659
  orderBy(field, direction = "asc") {
12439
- return new QueryBuilderImpl(this.collection, void 0, { field, direction });
12660
+ return this.makeQueryBuilder(void 0, { field, direction });
12440
12661
  }
12441
12662
  limit(count) {
12442
- return new QueryBuilderImpl(this.collection, void 0, void 0, count);
12663
+ return this.makeQueryBuilder(void 0, void 0, count);
12443
12664
  }
12444
12665
  skip(count) {
12445
- return new QueryBuilderImpl(this.collection, void 0, void 0, void 0, count);
12666
+ return this.makeQueryBuilder(void 0, void 0, void 0, count);
12446
12667
  }
12447
12668
  doc(id) {
12448
12669
  return new DocRefImpl(
@@ -12498,6 +12719,58 @@ class CollectionRefImpl {
12498
12719
  });
12499
12720
  return () => rxSub.unsubscribe();
12500
12721
  }
12722
+ async aggregate(spec, options) {
12723
+ return this.executeAggregate([], [], void 0, void 0, spec, options == null ? void 0 : options.groupBy);
12724
+ }
12725
+ async executeAggregate(wheres, orders, limit, skip, spec, groupBy) {
12726
+ var _a, _b, _c;
12727
+ validateAggregateSpec(spec);
12728
+ const activeGroupBy = groupBy && groupBy.length > 0 ? groupBy : void 0;
12729
+ if (activeGroupBy) {
12730
+ validateGroupBySpec(activeGroupBy, spec);
12731
+ }
12732
+ const selectParam = buildAggregateSelect(spec, activeGroupBy);
12733
+ if (!selectParam) {
12734
+ return activeGroupBy ? [] : {};
12735
+ }
12736
+ const filterParams = buildPostgrestFilters(wheres);
12737
+ const params = {
12738
+ select: selectParam,
12739
+ ...filterParams
12740
+ };
12741
+ if (limit !== void 0) params["limit"] = limit;
12742
+ if (skip !== void 0) params["offset"] = skip;
12743
+ if (activeGroupBy && orders.length > 0) {
12744
+ const groupBySet = new Set(activeGroupBy);
12745
+ for (const { field } of orders) {
12746
+ if (!groupBySet.has(field)) {
12747
+ throw new InputTableError(
12748
+ `orderBy field "${field}" must be one of the groupBy columns: [${activeGroupBy.join(", ")}]`,
12749
+ 400
12750
+ );
12751
+ }
12752
+ }
12753
+ params["order"] = buildPostgrestOrder(orders);
12754
+ }
12755
+ let response;
12756
+ try {
12757
+ response = await apiClient.get(
12758
+ `/input-table-v2/${this.solutionId}/rows/${this.tableName}`,
12759
+ { params, headers: getSolutionBranchHeaders() }
12760
+ );
12761
+ } catch (err) {
12762
+ const axiosErr = err;
12763
+ throw new InputTableError(
12764
+ ((_b = (_a = axiosErr.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? axiosErr.message ?? "Aggregate query failed",
12765
+ ((_c = axiosErr.response) == null ? void 0 : _c.status) ?? 500
12766
+ );
12767
+ }
12768
+ const data = Array.isArray(response.data) ? response.data : [response.data];
12769
+ if (activeGroupBy) {
12770
+ return parseGroupedAggregateResponse(spec, activeGroupBy, data);
12771
+ }
12772
+ return parseAggregateResponse(spec, data);
12773
+ }
12501
12774
  }
12502
12775
  let dbPromise = null;
12503
12776
  function getInputTableDatabase() {
@@ -12850,18 +13123,22 @@ async function evictLeastRecentlyUsed() {
12850
13123
  }
12851
13124
  }
12852
13125
  const SOLUTION_COOKIE_NAME = "x-bluecopa-solution-id";
13126
+ function getCookieValue(name) {
13127
+ try {
13128
+ const cookie = document.cookie.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${name}=`));
13129
+ return cookie ? decodeURIComponent(cookie.split("=").slice(1).join("=")) : null;
13130
+ } catch {
13131
+ return null;
13132
+ }
13133
+ }
12853
13134
  function getSolutionId() {
12854
13135
  const config = getConfig();
12855
13136
  if (config.solutionId) {
12856
13137
  return validateIdentifier(config.solutionId, "solutionId");
12857
13138
  }
12858
- try {
12859
- const cookie = document.cookie.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${SOLUTION_COOKIE_NAME}=`));
12860
- if (cookie) {
12861
- const value = decodeURIComponent(cookie.split("=").slice(1).join("="));
12862
- return validateIdentifier(value, "solutionId (cookie)");
12863
- }
12864
- } catch {
13139
+ const cookieValue = getCookieValue(SOLUTION_COOKIE_NAME);
13140
+ if (cookieValue) {
13141
+ return validateIdentifier(cookieValue, "solutionId (cookie)");
12865
13142
  }
12866
13143
  throw new InputTableError(
12867
13144
  "[copaInputTableDb] No solutionId configured. Set via copaSetConfig({ solutionId }) or cookie.",
@@ -12933,7 +13210,8 @@ const _InputTableDBImpl = class _InputTableDBImpl {
12933
13210
  if (this.schemaPending.has(solutionId)) {
12934
13211
  return this.schemaPending.get(solutionId);
12935
13212
  }
12936
- const pending = apiClient.get(`/input-table-v2/${solutionId}/tables`).then((res) => {
13213
+ const headers = getSolutionBranchHeaders();
13214
+ const pending = apiClient.get(`/input-table-v2/${solutionId}/tables`, { headers }).then((res) => {
12937
13215
  const tables = res.data ?? [];
12938
13216
  this.schemaCache.set(solutionId, { tables, fetchedAt: Date.now() });
12939
13217
  this.schemaPending.delete(solutionId);
@@ -13013,6 +13291,13 @@ class LazyCollectionRef {
13013
13291
  0
13014
13292
  );
13015
13293
  }
13294
+ async aggregate(spec, options) {
13295
+ const ref = await this.getRef();
13296
+ if (options) {
13297
+ return ref.aggregate(spec, options);
13298
+ }
13299
+ return ref.aggregate(spec);
13300
+ }
13016
13301
  }
13017
13302
  class LazyQueryBuilder {
13018
13303
  constructor(resolveBase) {
@@ -13058,6 +13343,13 @@ class LazyQueryBuilder {
13058
13343
  return [];
13059
13344
  }
13060
13345
  }
13346
+ async aggregate(spec, options) {
13347
+ const qb = await this.resolveWithOps();
13348
+ if (options) {
13349
+ return qb.aggregate(spec, options);
13350
+ }
13351
+ return qb.aggregate(spec);
13352
+ }
13061
13353
  }
13062
13354
  class LazyDocRef {
13063
13355
  constructor(id, resolve) {