@carto/api-client 0.4.7-0 → 0.4.7-alpha.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/build/api/carto-api-error.d.ts +26 -0
  3. package/build/api/endpoints.d.ts +24 -0
  4. package/build/api/index.d.ts +5 -0
  5. package/build/api/query.d.ts +3 -0
  6. package/build/api/request-with-parameters.d.ts +10 -0
  7. package/build/api-client.cjs +2910 -2741
  8. package/build/api-client.cjs.map +1 -1
  9. package/build/api-client.modern.js +3718 -0
  10. package/build/api-client.modern.js.map +1 -0
  11. package/build/client.d.ts +14 -0
  12. package/build/constants-internal.d.ts +26 -0
  13. package/build/constants.d.ts +53 -0
  14. package/build/deck/get-data-filter-extension-props.d.ts +28 -0
  15. package/build/deck/index.d.ts +1 -0
  16. package/build/filters/Filter.d.ts +25 -0
  17. package/build/filters/FilterTypes.d.ts +3 -0
  18. package/build/filters/geosjonFeatures.d.ts +8 -0
  19. package/build/filters/index.d.ts +6 -0
  20. package/build/filters/tileFeatures.d.ts +20 -0
  21. package/build/filters/tileFeaturesGeometries.d.ts +13 -0
  22. package/build/filters/tileFeaturesSpatialIndex.d.ts +10 -0
  23. package/build/filters.d.ts +39 -0
  24. package/build/geo.d.ts +19 -0
  25. package/build/index.d.ts +16 -0
  26. package/build/models/common.d.ts +28 -0
  27. package/build/models/index.d.ts +3 -0
  28. package/build/models/model.d.ts +37 -0
  29. package/build/operations/aggregation.d.ts +8 -0
  30. package/build/operations/applySorting.d.ts +20 -0
  31. package/build/operations/groupBy.d.ts +15 -0
  32. package/build/operations/groupByDate.d.ts +11 -0
  33. package/build/operations/histogram.d.ts +13 -0
  34. package/build/operations/index.d.ts +6 -0
  35. package/build/operations/scatterPlot.d.ts +14 -0
  36. package/build/sources/base-source.d.ts +4 -0
  37. package/build/sources/boundary-query-source.d.ts +10 -0
  38. package/build/sources/boundary-table-source.d.ts +8 -0
  39. package/build/sources/h3-query-source.d.ts +5 -0
  40. package/build/sources/h3-table-source.d.ts +5 -0
  41. package/build/sources/h3-tileset-source.d.ts +4 -0
  42. package/build/sources/index.d.ts +26 -0
  43. package/build/sources/quadbin-query-source.d.ts +5 -0
  44. package/build/sources/quadbin-table-source.d.ts +5 -0
  45. package/build/sources/quadbin-tileset-source.d.ts +4 -0
  46. package/build/sources/raster-source.d.ts +4 -0
  47. package/build/sources/types.d.ts +366 -0
  48. package/build/sources/vector-query-source.d.ts +5 -0
  49. package/build/sources/vector-table-source.d.ts +5 -0
  50. package/build/sources/vector-tileset-source.d.ts +4 -0
  51. package/build/spatial-index.d.ts +14 -0
  52. package/build/types-internal.d.ts +56 -0
  53. package/build/types.d.ts +140 -0
  54. package/build/utils/dateUtils.d.ts +10 -0
  55. package/build/utils/getTileFormat.d.ts +3 -0
  56. package/build/utils/makeIntervalComplete.d.ts +2 -0
  57. package/build/utils/transformTileCoordsToWGS84.d.ts +8 -0
  58. package/build/utils/transformToTileCoords.d.ts +9 -0
  59. package/build/utils.d.ts +32 -0
  60. package/build/widget-sources/index.d.ts +6 -0
  61. package/build/widget-sources/types.d.ts +162 -0
  62. package/build/widget-sources/widget-query-source.d.ts +34 -0
  63. package/build/widget-sources/widget-remote-source.d.ts +18 -0
  64. package/build/widget-sources/widget-source.d.ts +74 -0
  65. package/build/widget-sources/widget-table-source.d.ts +34 -0
  66. package/build/widget-sources/widget-tileset-source.d.ts +75 -0
  67. package/package.json +18 -19
  68. package/src/constants-internal.ts +6 -0
  69. package/src/deck/get-data-filter-extension-props.ts +27 -9
  70. package/src/filters/tileFeatures.ts +0 -1
  71. package/src/global.d.ts +8 -3
  72. package/src/sources/h3-tileset-source.ts +6 -18
  73. package/src/sources/quadbin-tileset-source.ts +6 -18
  74. package/src/sources/types.ts +0 -6
  75. package/src/sources/vector-tileset-source.ts +6 -19
  76. package/src/widget-sources/types.ts +2 -0
  77. package/src/widget-sources/widget-remote-source.ts +16 -42
  78. package/src/widget-sources/widget-source.ts +40 -12
  79. package/src/widget-sources/widget-tileset-source.ts +341 -196
  80. package/build/api-client.d.cts +0 -1389
  81. package/build/api-client.d.ts +0 -1389
  82. package/build/api-client.js +0 -3676
  83. package/build/api-client.js.map +0 -1
  84. package/build/worker.d.ts +0 -2
  85. package/build/worker.js +0 -1949
  86. package/build/worker.js.map +0 -1
  87. package/src/widget-sources/widget-tileset-source-impl.ts +0 -417
  88. package/src/workers/constants.ts +0 -13
  89. package/src/workers/types.ts +0 -19
  90. package/src/workers/widget-tileset-worker.ts +0 -40
@@ -1,3 +1,6 @@
1
+ /* eslint-disable @typescript-eslint/require-await */
2
+ import {TilesetSourceOptions} from '../sources/index.js';
3
+ import type {ModelSource} from '../models/index.js';
1
4
  import {
2
5
  CategoryRequestOptions,
3
6
  CategoryResponse,
@@ -15,15 +18,33 @@ import {
15
18
  TimeSeriesRequestOptions,
16
19
  TimeSeriesResponse,
17
20
  } from './types.js';
18
- import {SpatialFilter, Tile} from '../types.js';
19
- import {TileFeatureExtractOptions} from '../filters/index.js';
21
+ import {InvalidColumnError, assert, getApplicableFilters} from '../utils.js';
22
+ import {TileFormat} from '../constants.js';
23
+ import {Filter, Filters, SpatialFilter, Tile} from '../types.js';
24
+ import {
25
+ TileFeatureExtractOptions,
26
+ applyFilters,
27
+ geojsonFeatures,
28
+ tileFeatures,
29
+ } from '../filters/index.js';
30
+ import {
31
+ aggregationFunctions,
32
+ applySorting,
33
+ groupValuesByColumn,
34
+ groupValuesByDateColumn,
35
+ histogram,
36
+ scatterPlot,
37
+ } from '../operations/index.js';
38
+ import {FeatureData} from '../types-internal.js';
20
39
  import {FeatureCollection} from 'geojson';
40
+ import {SpatialDataType} from '../sources/types.js';
21
41
  import {WidgetSource, WidgetSourceProps} from './widget-source.js';
22
- import {Method} from '../workers/constants.js';
23
- import {WorkerRequest, WorkerResponse} from '../workers/types.js';
24
- import {SpatialDataType, TilesetSourceOptions} from '../sources/types.js';
25
- import {TileFormat} from '../constants.js';
26
- import {WidgetTilesetSourceImpl} from './widget-tileset-source-impl.js';
42
+ import {booleanEqual} from '@turf/boolean-equal';
43
+
44
+ // TODO(cleanup): Parameter defaults in source functions and widget API calls are
45
+ // currently duplicated and possibly inconsistent. Consider consolidating and
46
+ // operating on Required<T> objects. See:
47
+ // https://github.com/CartoDB/carto-api-client/issues/39
27
48
 
28
49
  export type WidgetTilesetSourceProps = WidgetSourceProps &
29
50
  Omit<TilesetSourceOptions, 'filters'> & {
@@ -56,176 +77,62 @@ export type WidgetTilesetSourceResult = {widgetSource: WidgetTilesetSource};
56
77
  * ```
57
78
  */
58
79
  export class WidgetTilesetSource extends WidgetSource<WidgetTilesetSourceProps> {
59
- protected _localImpl: WidgetTilesetSourceImpl | null = null;
60
-
61
- protected _workerImpl: Worker | null = null;
62
- protected _workerEnabled: boolean;
63
- protected _workerNextRequestId = 1;
64
-
65
- constructor(props: WidgetTilesetSourceProps) {
66
- super(props);
67
-
68
- this._workerEnabled =
69
- (props.widgetWorker ?? true) &&
70
- TSUP_FORMAT !== 'cjs' &&
71
- typeof Worker !== 'undefined';
72
-
73
- if (!this._workerEnabled) {
74
- this._localImpl = new WidgetTilesetSourceImpl(this.props);
75
- }
76
- }
77
-
78
- destroy() {
79
- this._localImpl?.destroy();
80
- this._localImpl = null;
81
-
82
- this._workerImpl?.terminate();
83
- this._workerImpl = null;
84
-
85
- super.destroy();
80
+ private _tiles: Tile[] = [];
81
+ private _features: FeatureData[] = [];
82
+ private _tileFeatureExtractOptions: TileFeatureExtractOptions = {};
83
+ private _tileFeatureExtractPreviousInputs: {spatialFilter?: SpatialFilter} =
84
+ {};
85
+
86
+ protected override getModelSource(
87
+ filters: Filters | undefined,
88
+ filterOwner: string
89
+ ): ModelSource {
90
+ return {
91
+ ...super._getModelSource(filters, filterOwner),
92
+ type: 'tileset',
93
+ data: this.props.tableName,
94
+ };
86
95
  }
87
96
 
88
- /////////////////////////////////////////////////////////////////////////////
89
- // WEB WORKER MANAGEMENT
90
-
91
- /**
92
- * Returns an initialized Worker, to be reused for the lifecycle of this
93
- * source instance.
94
- */
95
- protected _getWorker(): Worker {
96
- if (this._workerImpl) {
97
- return this._workerImpl;
98
- }
99
-
100
- this._workerImpl = new Worker(
101
- new URL('@carto/api-client/worker', import.meta.url),
102
- {
103
- type: 'module',
104
- name: 'cartowidgettileset',
105
- }
106
- );
107
-
108
- this._workerImpl.postMessage({
109
- method: Method.INIT,
110
- params: [this.props],
111
- } as WorkerRequest);
112
-
113
- return this._workerImpl;
114
- }
115
-
116
- /** Executes a given method on the worker. */
117
- protected _executeWorkerMethod<T>(
118
- method: Method,
119
- params: unknown[],
120
- signal?: AbortSignal
121
- ): Promise<T> {
122
- if (!this._workerEnabled) {
123
- // @ts-expect-error No type-checking dynamic method name.
124
- return this._localImpl[method](...params);
125
- }
126
-
127
- const worker = this._getWorker();
128
- const requestId = this._workerNextRequestId++;
129
-
130
- // TODO: ViewState may contain non-serializable data, which we do not need.
131
- // Remove this sanitization after sc-469614 is fixed.
132
- const options = params[0] as any;
133
- if (options?.spatialIndexReferenceViewState) {
134
- const {zoom, latitude, longitude} =
135
- options.spatialIndexReferenceViewState;
136
- options.spatialIndexReferenceViewState = {zoom, latitude, longitude};
137
- }
138
-
139
- let resolve: ((value: T) => void) | null = null;
140
- let reject: ((reason: any) => void) | null = null;
141
-
142
- // If worker sends message to main process, check whether it's a response
143
- // to this request, and whether the request can been aborted. Then resolve
144
- // or reject the Promise.
145
- function onMessage(e: MessageEvent) {
146
- const response = e.data as WorkerResponse;
147
- if (response.requestId !== requestId) return;
148
-
149
- if (signal?.aborted) {
150
- onAbort();
151
- } else if (response.ok) {
152
- resolve!(response.result as T);
153
- } else {
154
- reject!(new Error(response.error));
155
- }
156
- }
157
-
158
- // If request is aborted by user, immediately reject the Promise.
159
- function onAbort() {
160
- // https://developer.mozilla.org/en-US/docs/Web/API/DOMException#aborterror
161
- const abortError = new Error(signal!.reason);
162
- abortError.name = 'AbortError';
163
- reject!(abortError);
164
- }
165
-
166
- worker.addEventListener('message', onMessage);
167
- signal?.addEventListener('abort', onAbort);
168
-
169
- // Send the task to the worker, creating a Promise to resolve/reject later.
170
- const promise = new Promise<T>((_resolve, _reject) => {
171
- resolve = _resolve;
172
- reject = _reject;
173
-
174
- worker.postMessage({
175
- requestId,
176
- method,
177
- params,
178
- } as WorkerRequest);
179
- });
180
-
181
- // Whether the task completes, fails, or aborts: clean up afterward.
182
- void promise.finally(() => {
183
- worker.removeEventListener('message', onMessage);
184
- signal?.removeEventListener('abort', onAbort);
185
- });
186
-
187
- return promise;
188
- }
189
-
190
- /////////////////////////////////////////////////////////////////////////////
191
- // DATA LOADING
192
-
193
97
  /**
194
98
  * Loads features as a list of tiles (typically provided by deck.gl).
195
99
  * After tiles are loaded, {@link extractTileFeatures} must be called
196
100
  * before computing statistics on the tiles.
197
101
  */
198
102
  loadTiles(tiles: unknown[]) {
199
- if (!this._workerEnabled) {
200
- return this._localImpl!.loadTiles(tiles);
201
- }
202
-
203
- const worker = this._getWorker();
204
-
205
- tiles = (tiles as Tile[]).map(({id, bbox, data}) => ({
206
- id,
207
- bbox,
208
- data,
209
- }));
210
-
211
- worker.postMessage({
212
- method: Method.LOAD_TILES,
213
- params: [tiles],
214
- } as WorkerRequest);
103
+ this._tiles = tiles as Tile[];
104
+ this._features.length = 0;
215
105
  }
216
106
 
217
107
  /** Configures options used to extract features from tiles. */
218
108
  setTileFeatureExtractOptions(options: TileFeatureExtractOptions) {
219
- if (!this._workerEnabled) {
220
- return this._localImpl?.setTileFeatureExtractOptions(options);
109
+ this._tileFeatureExtractOptions = options;
110
+ this._features.length = 0;
111
+ }
112
+
113
+ protected _extractTileFeatures(spatialFilter: SpatialFilter) {
114
+ // When spatial filter has not changed, don't redo extraction. If tiles or
115
+ // tile extract options change, features will have been cleared already.
116
+ const prevInputs = this._tileFeatureExtractPreviousInputs;
117
+ if (
118
+ this._features.length &&
119
+ prevInputs.spatialFilter &&
120
+ booleanEqual(prevInputs.spatialFilter, spatialFilter)
121
+ ) {
122
+ return;
221
123
  }
222
124
 
223
- const worker = this._getWorker();
125
+ this._features = tileFeatures({
126
+ tiles: this._tiles,
127
+ tileFormat: this.props.tileFormat,
128
+ ...this._tileFeatureExtractOptions,
224
129
 
225
- worker.postMessage({
226
- type: Method.SET_TILE_FEATURE_EXTRACT_OPTIONS,
227
- params: [options],
130
+ spatialFilter,
131
+ spatialDataColumn: this.props.spatialDataColumn,
132
+ spatialDataType: this.props.spatialDataType,
228
133
  });
134
+
135
+ prevInputs.spatialFilter = spatialFilter;
229
136
  }
230
137
 
231
138
  /**
@@ -240,72 +147,310 @@ export class WidgetTilesetSource extends WidgetSource<WidgetTilesetSourceProps>
240
147
  geojson: FeatureCollection;
241
148
  spatialFilter: SpatialFilter;
242
149
  }) {
243
- if (!this._workerEnabled) {
244
- return this._localImpl!.loadGeoJSON({geojson, spatialFilter});
245
- }
246
-
247
- const worker = this._getWorker();
248
-
249
- worker.postMessage({
250
- method: Method.LOAD_GEOJSON,
251
- params: [{geojson, spatialFilter}],
252
- } as WorkerRequest);
150
+ this._features = geojsonFeatures({
151
+ geojson,
152
+ spatialFilter,
153
+ ...this._tileFeatureExtractOptions,
154
+ });
155
+ this._tileFeatureExtractPreviousInputs.spatialFilter = spatialFilter;
253
156
  }
254
157
 
255
- /////////////////////////////////////////////////////////////////////////////
256
- // WIDGETS API
257
-
258
- // eslint-disable-next-line @typescript-eslint/require-await
259
158
  override async getFeatures(): Promise<FeaturesResponse> {
260
159
  throw new Error('getFeatures not supported for tilesets');
261
160
  }
262
161
 
263
162
  async getFormula({
264
- signal,
265
- ...options
163
+ column = '*',
164
+ operation = 'count',
165
+ joinOperation,
166
+ filters,
167
+ filterOwner,
168
+ spatialFilter,
266
169
  }: FormulaRequestOptions): Promise<FormulaResponse> {
267
- return this._executeWorkerMethod(Method.GET_FORMULA, [options], signal);
170
+ if (operation === 'custom') {
171
+ throw new Error('Custom aggregation not supported for tilesets');
172
+ }
173
+
174
+ // Column is required except when operation is 'count'.
175
+ if ((column && column !== '*') || operation !== 'count') {
176
+ assertColumn(this._features, column);
177
+ }
178
+
179
+ const filteredFeatures = this._getFilteredFeatures(
180
+ spatialFilter,
181
+ filters,
182
+ filterOwner
183
+ );
184
+
185
+ if (filteredFeatures.length === 0 && operation !== 'count') {
186
+ return {value: null};
187
+ }
188
+
189
+ const targetOperation = aggregationFunctions[operation];
190
+ return {
191
+ value: targetOperation(filteredFeatures, column, joinOperation),
192
+ };
268
193
  }
269
194
 
270
195
  override async getHistogram({
271
- signal,
272
- ...options
196
+ operation = 'count',
197
+ ticks,
198
+ column,
199
+ joinOperation,
200
+ filters,
201
+ filterOwner,
202
+ spatialFilter,
273
203
  }: HistogramRequestOptions): Promise<HistogramResponse> {
274
- return this._executeWorkerMethod(Method.GET_HISTOGRAM, [options], signal);
204
+ const filteredFeatures = this._getFilteredFeatures(
205
+ spatialFilter,
206
+ filters,
207
+ filterOwner
208
+ );
209
+
210
+ assertColumn(this._features, column);
211
+
212
+ if (!this._features.length) {
213
+ return [];
214
+ }
215
+
216
+ return histogram({
217
+ data: filteredFeatures,
218
+ valuesColumns: normalizeColumns(column),
219
+ joinOperation,
220
+ ticks,
221
+ operation,
222
+ });
275
223
  }
276
224
 
277
225
  override async getCategories({
278
- signal,
279
- ...options
226
+ column,
227
+ operation = 'count',
228
+ operationColumn,
229
+ joinOperation,
230
+ filters,
231
+ filterOwner,
232
+ spatialFilter,
280
233
  }: CategoryRequestOptions): Promise<CategoryResponse> {
281
- return this._executeWorkerMethod(Method.GET_CATEGORIES, [options], signal);
234
+ const filteredFeatures = this._getFilteredFeatures(
235
+ spatialFilter,
236
+ filters,
237
+ filterOwner
238
+ );
239
+
240
+ if (!filteredFeatures.length) {
241
+ return [];
242
+ }
243
+
244
+ assertColumn(this._features, column, operationColumn as string);
245
+
246
+ const groups = groupValuesByColumn({
247
+ data: filteredFeatures,
248
+ valuesColumns: normalizeColumns(operationColumn || column),
249
+ joinOperation,
250
+ keysColumn: column,
251
+ operation,
252
+ });
253
+
254
+ return groups || [];
282
255
  }
283
256
 
284
257
  override async getScatter({
285
- signal,
286
- ...options
258
+ xAxisColumn,
259
+ yAxisColumn,
260
+ xAxisJoinOperation,
261
+ yAxisJoinOperation,
262
+ filters,
263
+ filterOwner,
264
+ spatialFilter,
287
265
  }: ScatterRequestOptions): Promise<ScatterResponse> {
288
- return this._executeWorkerMethod(Method.GET_SCATTER, [options], signal);
266
+ const filteredFeatures = this._getFilteredFeatures(
267
+ spatialFilter,
268
+ filters,
269
+ filterOwner
270
+ );
271
+
272
+ if (!filteredFeatures.length) {
273
+ return [];
274
+ }
275
+
276
+ assertColumn(this._features, xAxisColumn, yAxisColumn);
277
+
278
+ return scatterPlot({
279
+ data: filteredFeatures,
280
+ xAxisColumns: normalizeColumns(xAxisColumn),
281
+ xAxisJoinOperation,
282
+ yAxisColumns: normalizeColumns(yAxisColumn),
283
+ yAxisJoinOperation,
284
+ });
289
285
  }
290
286
 
291
287
  override async getTable({
292
- signal,
293
- ...options
288
+ columns,
289
+ searchFilterColumn,
290
+ searchFilterText,
291
+ sortBy,
292
+ sortDirection,
293
+ sortByColumnType,
294
+ offset = 0,
295
+ limit = 10,
296
+ filters,
297
+ filterOwner,
298
+ spatialFilter,
294
299
  }: TableRequestOptions): Promise<TableResponse> {
295
- return this._executeWorkerMethod(Method.GET_TABLE, [options], signal);
300
+ // Filter.
301
+ let filteredFeatures = this._getFilteredFeatures(
302
+ spatialFilter,
303
+ filters,
304
+ filterOwner
305
+ );
306
+
307
+ if (!filteredFeatures.length) {
308
+ return {rows: [], totalCount: 0};
309
+ }
310
+
311
+ // Search.
312
+ if (searchFilterColumn && searchFilterText) {
313
+ filteredFeatures = filteredFeatures.filter(
314
+ (row) =>
315
+ row[searchFilterColumn] &&
316
+ String(row[searchFilterColumn] as unknown)
317
+ .toLowerCase()
318
+ .includes(String(searchFilterText).toLowerCase())
319
+ );
320
+ }
321
+
322
+ // Sort.
323
+ let rows = applySorting(filteredFeatures, {
324
+ sortBy,
325
+ sortByDirection: sortDirection,
326
+ sortByColumnType,
327
+ });
328
+ const totalCount = rows.length;
329
+
330
+ // Offset and limit.
331
+ rows = rows.slice(
332
+ Math.min(offset, totalCount),
333
+ Math.min(offset + limit, totalCount)
334
+ );
335
+
336
+ // Select columns.
337
+ rows = rows.map((srcRow: FeatureData) => {
338
+ const dstRow: FeatureData = {};
339
+ for (const column of columns) {
340
+ dstRow[column] = srcRow[column];
341
+ }
342
+ return dstRow;
343
+ });
344
+
345
+ return {rows, totalCount} as TableResponse;
296
346
  }
297
347
 
298
348
  override async getTimeSeries({
299
- signal,
300
- ...options
349
+ column,
350
+ stepSize,
351
+ operation,
352
+ operationColumn,
353
+ joinOperation,
354
+ filters,
355
+ filterOwner,
356
+ spatialFilter,
301
357
  }: TimeSeriesRequestOptions): Promise<TimeSeriesResponse> {
302
- return this._executeWorkerMethod(Method.GET_TIME_SERIES, [options], signal);
358
+ const filteredFeatures = this._getFilteredFeatures(
359
+ spatialFilter,
360
+ filters,
361
+ filterOwner
362
+ );
363
+
364
+ if (!filteredFeatures.length) {
365
+ return {rows: []};
366
+ }
367
+
368
+ assertColumn(this._features, column, operationColumn as string);
369
+
370
+ const rows =
371
+ groupValuesByDateColumn({
372
+ data: filteredFeatures,
373
+ valuesColumns: normalizeColumns(operationColumn || column),
374
+ keysColumn: column,
375
+ groupType: stepSize,
376
+ operation,
377
+ joinOperation,
378
+ }) || [];
379
+
380
+ return {rows};
303
381
  }
304
382
 
305
383
  override async getRange({
306
- signal,
307
- ...options
384
+ column,
385
+ filters,
386
+ filterOwner,
387
+ spatialFilter,
308
388
  }: RangeRequestOptions): Promise<RangeResponse> {
309
- return this._executeWorkerMethod(Method.GET_RANGE, [options], signal);
389
+ assertColumn(this._features, column);
390
+
391
+ const filteredFeatures = this._getFilteredFeatures(
392
+ spatialFilter,
393
+ filters,
394
+ filterOwner
395
+ );
396
+
397
+ if (!this._features.length) {
398
+ // TODO: Is this the only nullable response in the Widgets API? If so,
399
+ // can we do something more consistent?
400
+ return null;
401
+ }
402
+
403
+ return {
404
+ min: aggregationFunctions.min(filteredFeatures, column),
405
+ max: aggregationFunctions.max(filteredFeatures, column),
406
+ };
407
+ }
408
+
409
+ /****************************************************************************
410
+ * INTERNAL
411
+ */
412
+
413
+ private _getFilteredFeatures(
414
+ spatialFilter?: SpatialFilter,
415
+ filters?: Record<string, Filter>,
416
+ filterOwner?: string
417
+ ): FeatureData[] {
418
+ assert(spatialFilter, 'spatialFilter required for tilesets');
419
+ this._extractTileFeatures(spatialFilter);
420
+ return applyFilters(
421
+ this._features,
422
+ getApplicableFilters(filterOwner, filters || this.props.filters),
423
+ this.props.filtersLogicalOperator || 'and'
424
+ );
425
+ }
426
+ }
427
+
428
+ function assertColumn(
429
+ features: FeatureData[],
430
+ ...columnArgs: string[] | string[][]
431
+ ) {
432
+ // TODO(cleanup): Can drop support for multiple column shapes here?
433
+
434
+ // Due to the multiple column shape, we normalise it as an array with normalizeColumns
435
+ const columns = Array.from(new Set(columnArgs.map(normalizeColumns).flat()));
436
+
437
+ const featureKeys = Object.keys(features[0]);
438
+
439
+ const invalidColumns = columns.filter(
440
+ (column) => !featureKeys.includes(column)
441
+ );
442
+
443
+ if (invalidColumns.length) {
444
+ throw new InvalidColumnError(
445
+ `Missing column(s): ${invalidColumns.join(', ')}`
446
+ );
310
447
  }
311
448
  }
449
+
450
+ function normalizeColumns(columns: string | string[]): string[] {
451
+ return Array.isArray(columns)
452
+ ? columns
453
+ : typeof columns === 'string'
454
+ ? [columns]
455
+ : [];
456
+ }