@carto/api-client 0.5.15 → 0.5.16

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/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "homepage": "https://github.com/CartoDB/carto-api-client#readme",
9
9
  "author": "Don McCurdy <donmccurdy@carto.com>",
10
10
  "packageManager": "yarn@4.3.1",
11
- "version": "0.5.15",
11
+ "version": "0.5.16",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -121,7 +121,7 @@
121
121
  "rimraf": "^6.0.1",
122
122
  "semver": "^7.7.1",
123
123
  "thenby": "^1.3.4",
124
- "tinybench": "^4.0.1",
124
+ "tinybench": "^5.0.1",
125
125
  "tsup": "^8.3.6",
126
126
  "typescript": "~5.9.2",
127
127
  "typescript-eslint": "^8.26.1",
@@ -24,6 +24,7 @@ const AVAILABLE_MODELS = [
24
24
  'range',
25
25
  'scatterplot',
26
26
  'table',
27
+ 'aggregations',
27
28
  ] as const;
28
29
 
29
30
  export type Model = (typeof AVAILABLE_MODELS)[number];
@@ -207,6 +207,26 @@ export interface TimeSeriesRequestOptions extends BaseRequestOptions {
207
207
  splitByCategoryValues?: string[];
208
208
  }
209
209
 
210
+ /**
211
+ * Examples:
212
+ * * aggregations with array syntax
213
+ * * aggregations: [{column: 'pop_high', operation: 'sum', alias: 'high_pop'}, {column: 'pop_low', operation: 'avg'}]
214
+ * * aggregations with string syntax
215
+ * * aggregations: 'sum(pop_high) as high_pop, avg(pop_low) as avg_low'
216
+ *
217
+ * Options for {@link WidgetRemoteSource#getAggregations}.
218
+ */
219
+ export interface AggregationsRequestOptions extends BaseRequestOptions {
220
+ /** Aggregations to compute. Can be an array of objects or a SQL string expression. */
221
+ aggregations:
222
+ | {
223
+ column: string;
224
+ operation: Exclude<AggregationType, 'custom'>;
225
+ alias: string;
226
+ }[]
227
+ | string;
228
+ }
229
+
210
230
  /** @experimental */
211
231
  export type ExtentRequestOptions = BaseRequestOptions;
212
232
 
@@ -264,5 +284,10 @@ export type TimeSeriesResponse = {
264
284
  /** Response from {@link WidgetRemoteSource#getHistogram}. */
265
285
  export type HistogramResponse = number[];
266
286
 
287
+ /** Response from {@link WidgetRemoteSource#getAggregations}. */
288
+ export type AggregationsResponse = {
289
+ rows: Record<string, number | string | null>[];
290
+ };
291
+
267
292
  /** @experimental */
268
293
  export type ExtentResponse = {bbox: BBox};
@@ -1,5 +1,7 @@
1
1
  import {executeModel, type ModelSource} from '../models/index.js';
2
2
  import type {
3
+ AggregationsRequestOptions,
4
+ AggregationsResponse,
3
5
  CategoryRequestOptions,
4
6
  CategoryResponse,
5
7
  ExtentRequestOptions,
@@ -394,6 +396,34 @@ export abstract class WidgetRemoteSource<
394
396
  }));
395
397
  }
396
398
 
399
+ async getAggregations(
400
+ options: AggregationsRequestOptions
401
+ ): Promise<AggregationsResponse> {
402
+ const {
403
+ signal,
404
+ filters = this.props.filters,
405
+ filterOwner,
406
+ spatialFilter,
407
+ spatialFiltersMode,
408
+ aggregations,
409
+ } = options;
410
+
411
+ return executeModel({
412
+ model: 'aggregations',
413
+ source: {
414
+ ...this.getModelSource(filters, filterOwner),
415
+ spatialFiltersMode,
416
+ spatialFilter,
417
+ },
418
+ params: {
419
+ aggregations,
420
+ },
421
+ opts: {signal, headers: this.props.headers},
422
+ }).then((res: AggregationsResponse) => ({
423
+ rows: res.rows.map((row) => normalizeObjectKeys(row)),
424
+ }));
425
+ }
426
+
397
427
  /** @experimental */
398
428
  async getExtent(options: ExtentRequestOptions = {}): Promise<ExtentResponse> {
399
429
  const {signal, filters = this.props.filters, filterOwner} = options;
@@ -1,4 +1,6 @@
1
1
  import type {
2
+ AggregationsRequestOptions,
3
+ AggregationsResponse,
2
4
  CategoryRequestOptions,
3
5
  CategoryResponse,
4
6
  ExtentRequestOptions,
@@ -127,6 +129,15 @@ export abstract class WidgetSource<
127
129
  options: TimeSeriesRequestOptions
128
130
  ): Promise<TimeSeriesResponse>;
129
131
 
132
+ /**
133
+ * Returns multiple aggregated values computed over matching data. Suitable
134
+ * for aggregated statistics from pivoted tables, such as H3 tables with
135
+ * pre-computed aggregations across multiple columns.
136
+ */
137
+ abstract getAggregations(
138
+ options: AggregationsRequestOptions
139
+ ): Promise<AggregationsResponse>;
140
+
130
141
  /** @experimental */
131
142
  abstract getExtent(options?: ExtentRequestOptions): Promise<ExtentResponse>;
132
143
  }
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/require-await */
2
2
  import type {
3
+ AggregationsRequestOptions,
4
+ AggregationsResponse,
3
5
  CategoryRequestOptions,
4
6
  CategoryResponse,
5
7
  ExtentResponse,
@@ -148,6 +150,8 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
148
150
  }
149
151
 
150
152
  const targetOperation = aggregationFunctions[operation];
153
+ assert(targetOperation, `Unsupported aggregation operation: ${operation}`);
154
+
151
155
  return {
152
156
  value: targetOperation(filteredFeatures, column, joinOperation),
153
157
  };
@@ -391,6 +395,51 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
391
395
  };
392
396
  }
393
397
 
398
+ async getAggregations({
399
+ aggregations,
400
+ filters,
401
+ filterOwner,
402
+ spatialFilter,
403
+ }: AggregationsRequestOptions): Promise<AggregationsResponse> {
404
+ const filteredFeatures = this._getFilteredFeatures(
405
+ spatialFilter,
406
+ filters,
407
+ filterOwner
408
+ );
409
+
410
+ if (!this._features.length) {
411
+ return {rows: []};
412
+ }
413
+
414
+ // SQL aggregations require remote execution, and are not supported for tilesets.
415
+ assert(
416
+ typeof aggregations !== 'string',
417
+ 'Unsupported tileset SQL aggregation'
418
+ );
419
+
420
+ // Handle array-based aggregations
421
+ const result: Record<string, number> = {};
422
+ const usedAliases = new Set<string>();
423
+
424
+ for (const {column, operation, alias} of aggregations) {
425
+ // Column is required except when operation is 'count'.
426
+ if ((column && column !== '*') || operation !== AggregationTypes.Count) {
427
+ assertColumn(this._features, column);
428
+ }
429
+
430
+ const aliasKey = alias.toLowerCase();
431
+ assert(!usedAliases.has(aliasKey), `Duplicate alias: ${aliasKey}`);
432
+ usedAliases.add(aliasKey);
433
+
434
+ const targetOperation = aggregationFunctions[operation];
435
+ assert(targetOperation, `Unsupported operation: ${operation}`);
436
+
437
+ result[alias] = targetOperation(filteredFeatures, column);
438
+ }
439
+
440
+ return {rows: [result]};
441
+ }
442
+
394
443
  /** @experimental */
395
444
  async getExtent(): Promise<ExtentResponse> {
396
445
  return Promise.reject(new Error('not implemented'));
@@ -1,4 +1,6 @@
1
1
  import type {
2
+ AggregationsRequestOptions,
3
+ AggregationsResponse,
2
4
  CategoryRequestOptions,
3
5
  CategoryResponse,
4
6
  ExtentResponse,
@@ -326,6 +328,17 @@ export class WidgetTilesetSource<
326
328
  return this._executeWorkerMethod(Method.GET_RANGE, [options], signal);
327
329
  }
328
330
 
331
+ async getAggregations({
332
+ signal,
333
+ ...options
334
+ }: AggregationsRequestOptions): Promise<AggregationsResponse> {
335
+ return this._executeWorkerMethod(
336
+ Method.GET_AGGREGATIONS,
337
+ [options],
338
+ signal
339
+ );
340
+ }
341
+
329
342
  /** @experimental */
330
343
  async getExtent(): Promise<ExtentResponse> {
331
344
  return Promise.resolve({
@@ -10,4 +10,5 @@ export enum Method {
10
10
  GET_TABLE = 'getTable',
11
11
  GET_TIME_SERIES = 'getTimeSeries',
12
12
  GET_RANGE = 'getRange',
13
+ GET_AGGREGATIONS = 'getAggregations',
13
14
  }