@carto/api-client 0.4.2-alpha.0 → 0.4.3

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 (48) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/build/api/query.d.ts +1 -1
  3. package/build/api-client.cjs +990 -1163
  4. package/build/api-client.cjs.map +1 -1
  5. package/build/api-client.modern.js +876 -1044
  6. package/build/api-client.modern.js.map +1 -1
  7. package/build/index.d.ts +1 -1
  8. package/build/models/model.d.ts +1 -7
  9. package/build/sources/boundary-query-source.d.ts +2 -1
  10. package/build/sources/boundary-table-source.d.ts +2 -1
  11. package/build/sources/h3-query-source.d.ts +2 -1
  12. package/build/sources/h3-table-source.d.ts +2 -1
  13. package/build/sources/h3-tileset-source.d.ts +2 -1
  14. package/build/sources/index.d.ts +13 -14
  15. package/build/sources/quadbin-query-source.d.ts +2 -1
  16. package/build/sources/quadbin-table-source.d.ts +2 -1
  17. package/build/sources/quadbin-tileset-source.d.ts +2 -1
  18. package/build/sources/raster-source.d.ts +2 -1
  19. package/build/sources/types.d.ts +57 -36
  20. package/build/sources/vector-query-source.d.ts +2 -1
  21. package/build/sources/vector-table-source.d.ts +2 -1
  22. package/build/sources/vector-tileset-source.d.ts +2 -1
  23. package/build/utils.d.ts +1 -1
  24. package/build/widget-sources/types.d.ts +1 -8
  25. package/build/widget-sources/widget-base-source.d.ts +1 -0
  26. package/package.json +1 -1
  27. package/src/api/query.ts +2 -1
  28. package/src/index.ts +1 -36
  29. package/src/models/model.ts +24 -47
  30. package/src/sources/boundary-query-source.ts +4 -2
  31. package/src/sources/boundary-table-source.ts +4 -2
  32. package/src/sources/h3-query-source.ts +4 -8
  33. package/src/sources/h3-table-source.ts +4 -7
  34. package/src/sources/h3-tileset-source.ts +4 -2
  35. package/src/sources/index.ts +54 -24
  36. package/src/sources/quadbin-query-source.ts +5 -7
  37. package/src/sources/quadbin-table-source.ts +5 -7
  38. package/src/sources/quadbin-tileset-source.ts +4 -2
  39. package/src/sources/raster-source.ts +4 -2
  40. package/src/sources/types.ts +63 -41
  41. package/src/sources/vector-query-source.ts +10 -5
  42. package/src/sources/vector-table-source.ts +10 -5
  43. package/src/sources/vector-tileset-source.ts +4 -2
  44. package/src/utils.ts +1 -1
  45. package/src/widget-sources/types.ts +1 -9
  46. package/src/widget-sources/widget-base-source.ts +21 -190
  47. package/build/spatial-index.d.ts +0 -11
  48. package/src/spatial-index.ts +0 -119
@@ -393,1082 +393,926 @@ const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
393
393
  */
394
394
  const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
395
395
 
396
- /**
397
- * Return more descriptive error from API
398
- * @internalRemarks Source: @carto/react-api
399
- */
400
- function dealWithApiError({
401
- response,
402
- data
396
+ // deck.gl
397
+ // SPDX-License-Identifier: MIT
398
+ // Copyright (c) vis.gl contributors
399
+ function joinPath(...args) {
400
+ return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
401
+ }
402
+ function buildV3Path(apiBaseUrl, version, endpoint, ...rest) {
403
+ return joinPath(apiBaseUrl, version, endpoint, ...rest);
404
+ }
405
+ /** @internal Required by fetchMap(). */
406
+ function buildPublicMapUrl({
407
+ apiBaseUrl,
408
+ cartoMapId
403
409
  }) {
404
- var _data$error, _data$error2;
405
- if (data.error === 'Column not found') {
406
- throw new InvalidColumnError(`${data.error} ${data.column_name}`);
407
- }
408
- if (typeof data.error === 'string' && (_data$error = data.error) != null && _data$error.includes('Missing columns')) {
409
- throw new InvalidColumnError(data.error);
410
- }
411
- switch (response.status) {
412
- case 401:
413
- throw new Error('Unauthorized access. Invalid credentials');
414
- case 403:
415
- throw new Error('Forbidden access to the requested data');
416
- default:
417
- const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify((data == null ? void 0 : data.hint) || ((_data$error2 = data.error) == null ? void 0 : _data$error2[0]));
418
- throw new Error(msg);
419
- }
410
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
420
411
  }
421
- /** @internalRemarks Source: @carto/react-api */
422
- async function makeCall({
423
- url,
424
- accessToken,
425
- opts
412
+ /** @internal Required by fetchMap(). */
413
+ function buildStatsUrl({
414
+ attribute,
415
+ apiBaseUrl,
416
+ connectionName,
417
+ source,
418
+ type
426
419
  }) {
427
- let response;
428
- let data;
429
- const isPost = (opts == null ? void 0 : opts.method) === 'POST';
430
- try {
431
- var _opts$abortController;
432
- response = await fetch(url.toString(), _extends({
433
- headers: _extends({
434
- Authorization: `Bearer ${accessToken}`
435
- }, isPost && {
436
- 'Content-Type': 'application/json'
437
- })
438
- }, isPost && {
439
- method: opts == null ? void 0 : opts.method,
440
- body: opts == null ? void 0 : opts.body
441
- }, {
442
- signal: opts == null || (_opts$abortController = opts.abortController) == null ? void 0 : _opts$abortController.signal
443
- }, opts == null ? void 0 : opts.otherOptions));
444
- data = await response.json();
445
- } catch (error) {
446
- if (error.name === 'AbortError') throw error;
447
- throw new Error(`Failed request: ${error}`);
448
- }
449
- if (!response.ok) {
450
- dealWithApiError({
451
- response,
452
- data
453
- });
420
+ if (type === 'query') {
421
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
454
422
  }
455
- return data;
423
+ // type === 'table'
424
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
425
+ }
426
+ function buildSourceUrl({
427
+ apiBaseUrl,
428
+ connectionName,
429
+ endpoint
430
+ }) {
431
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
432
+ }
433
+ function buildQueryUrl({
434
+ apiBaseUrl,
435
+ connectionName
436
+ }) {
437
+ return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
456
438
  }
457
439
 
458
- /** @internalRemarks Source: @carto/react-api */
459
- const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
460
- const {
461
- V3
462
- } = ApiVersion;
463
- const REQUEST_GET_MAX_URL_LENGTH = 2048;
440
+ // deck.gl
441
+ // SPDX-License-Identifier: MIT
442
+ // Copyright (c) vis.gl contributors
464
443
  /**
465
- * Execute a SQL model request.
466
- * @internalRemarks Source: @carto/react-api
444
+ *
445
+ * Custom error for reported errors in CARTO Maps API.
446
+ * Provides useful debugging information in console and context for applications.
447
+ *
467
448
  */
468
- function executeModel(props) {
469
- assert(props.source, 'executeModel: missing source');
470
- assert(props.model, 'executeModel: missing model');
471
- assert(props.params, 'executeModel: missing params');
472
- assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
473
- const {
474
- model,
475
- source,
476
- params,
477
- opts
478
- } = props;
479
- const {
480
- type,
481
- apiVersion,
482
- apiBaseUrl,
483
- accessToken,
484
- connectionName,
485
- clientId
486
- } = source;
487
- assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
488
- assert(accessToken, 'executeModel: missing accessToken');
489
- assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
490
- assert(type !== 'tileset', 'executeModel: Tilesets not supported');
491
- let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
492
- const {
493
- data,
494
- filters,
495
- filtersLogicalOperator = 'and',
496
- spatialDataType = 'geo',
497
- spatialFiltersMode = 'intersects',
498
- spatialFiltersResolution = 0
499
- } = source;
500
- const queryParams = {
501
- type,
502
- client: clientId,
503
- source: data,
504
- params,
505
- queryParameters: source.queryParameters || '',
506
- filters,
507
- filtersLogicalOperator
508
- };
509
- const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
510
- // Picking Model API requires 'spatialDataColumn'.
511
- if (model === 'pick') {
512
- queryParams.spatialDataColumn = spatialDataColumn;
513
- }
514
- // API supports multiple filters, we apply it only to spatialDataColumn
515
- const spatialFilters = source.spatialFilter ? {
516
- [spatialDataColumn]: source.spatialFilter
517
- } : undefined;
518
- if (spatialFilters) {
519
- queryParams.spatialFilters = spatialFilters; // JSON.stringify(spatialFilters);
520
- queryParams.spatialDataColumn = spatialDataColumn;
521
- queryParams.spatialDataType = spatialDataType;
522
- }
523
- if (spatialDataType !== 'geo') {
524
- if (spatialFiltersResolution > 0) {
525
- queryParams.spatialFiltersResolution = spatialFiltersResolution;
449
+ class CartoAPIError extends Error {
450
+ constructor(error, errorContext, response, responseJson) {
451
+ let responseString = 'Failed to connect';
452
+ if (response) {
453
+ responseString = 'Server returned: ';
454
+ if (response.status === 400) {
455
+ responseString += 'Bad request';
456
+ } else if (response.status === 401 || response.status === 403) {
457
+ responseString += 'Unauthorized access';
458
+ } else if (response.status === 404) {
459
+ responseString += 'Not found';
460
+ } else {
461
+ responseString += 'Error';
462
+ }
463
+ responseString += ` (${response.status}):`;
526
464
  }
527
- queryParams.spatialFiltersMode = spatialFiltersMode;
528
- }
529
- const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
530
- const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
531
- if (isGet) {
532
- url = urlWithSearchParams;
533
- }
534
- return makeCall({
535
- url,
536
- accessToken: source.accessToken,
537
- opts: _extends({}, opts, {
538
- method: isGet ? 'GET' : 'POST'
539
- }, !isGet && {
540
- body: JSON.stringify(queryParams)
541
- })
542
- });
543
- }
544
- function objectToURLSearchParams(object) {
545
- const params = new URLSearchParams();
546
- for (const key in object) {
547
- if (isPureObject(object[key])) {
548
- params.append(key, JSON.stringify(object[key]));
549
- } else if (Array.isArray(object[key])) {
550
- params.append(key, JSON.stringify(object[key]));
551
- } else if (object[key] === null) {
552
- params.append(key, 'null');
553
- } else if (object[key] !== undefined) {
554
- params.append(key, String(object[key]));
465
+ responseString += ` ${error.message || error}`;
466
+ let message = `${errorContext.requestType} API request failed`;
467
+ message += `\n${responseString}`;
468
+ for (const key of Object.keys(errorContext)) {
469
+ if (key === 'requestType') continue;
470
+ message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
555
471
  }
472
+ message += '\n';
473
+ super(message);
474
+ /** Source error from server */
475
+ this.error = void 0;
476
+ /** Context (API call & parameters) in which error occured */
477
+ this.errorContext = void 0;
478
+ /** Response from server */
479
+ this.response = void 0;
480
+ /** JSON Response from server */
481
+ this.responseJson = void 0;
482
+ this.name = 'CartoAPIError';
483
+ this.response = response;
484
+ this.responseJson = responseJson;
485
+ this.error = error;
486
+ this.errorContext = errorContext;
556
487
  }
557
- return params;
558
- }
559
-
560
- const DEFAULT_TILE_SIZE = 512;
561
- const QUADBIN_ZOOM_MAX_OFFSET = 4;
562
- function getSpatialFiltersResolution({
563
- source,
564
- viewState
565
- }) {
566
- var _source$dataResolutio, _source$aggregationRe;
567
- assert(viewState, 'viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error');
568
- const dataResolution = (_source$dataResolutio = source.dataResolution) != null ? _source$dataResolutio : Number.MAX_VALUE;
569
- const aggregationResLevel = (_source$aggregationRe = source.aggregationResLevel) != null ? _source$aggregationRe : source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN;
570
- const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
571
- const currentZoomInt = Math.ceil(viewState.zoom);
572
- if (source.spatialDataType === 'h3') {
573
- var _maxH3SpatialFiltersR, _maxH3SpatialFiltersR2;
574
- const tileSize = DEFAULT_TILE_SIZE;
575
- const maxResolutionForZoom = (_maxH3SpatialFiltersR = (_maxH3SpatialFiltersR2 = maxH3SpatialFiltersResolutions.find(([zoom]) => zoom === currentZoomInt)) == null ? void 0 : _maxH3SpatialFiltersR2[1]) != null ? _maxH3SpatialFiltersR : Math.max(0, currentZoomInt - 3);
576
- const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
577
- const hexagonResolution = getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
578
- return Math.min(hexagonResolution, maxSpatialFiltersResolution);
579
- }
580
- if (source.spatialDataType === 'quadbin') {
581
- const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
582
- const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
583
- const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
584
- return Math.min(quadsResolution, maxSpatialFiltersResolution);
585
- }
586
- return undefined;
587
488
  }
588
- const maxH3SpatialFiltersResolutions = [[20, 14], [19, 13], [18, 12], [17, 11], [16, 10], [15, 9], [14, 8], [13, 7], [12, 7], [11, 7], [10, 6], [9, 6], [8, 5], [7, 4], [6, 4], [5, 3], [4, 2], [3, 1], [2, 1], [1, 0]];
589
- // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
590
- // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
591
- const BIAS = 2;
592
- // Resolution conversion function. Takes a WebMercatorViewport and returns
593
- // a H3 resolution such that the screen space size of the hexagons is
594
- // similar
595
- function getHexagonResolution(viewport, tileSize) {
596
- // Difference in given tile size compared to deck's internal 512px tile size,
597
- // expressed as an offset to the viewport zoom.
598
- const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
599
- const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
600
- const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
601
- // Clip and bias
602
- return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
603
- }
604
-
605
- const _excluded$1 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "viewState"],
606
- _excluded2 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "viewState"],
607
- _excluded3 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "operationExp", "viewState"],
608
- _excluded4 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "viewState"],
609
- _excluded5 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "viewState"],
610
- _excluded6 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "viewState"],
611
- _excluded7 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "abortController", "viewState"],
612
- _excluded8 = ["filterOwner", "abortController", "spatialFilter", "spatialFiltersMode", "viewState"];
613
489
  /**
614
- * Source for Widget API requests on a data source defined by a SQL query.
615
- *
616
- * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
490
+ * Converts camelCase to Camel Case
617
491
  */
618
- class WidgetBaseSource {
619
- constructor(props) {
620
- this.props = void 0;
621
- this.props = _extends({}, WidgetBaseSource.defaultProps, props);
492
+ function formatErrorKey(key) {
493
+ return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
494
+ }
495
+
496
+ const DEFAULT_HEADERS = {
497
+ Accept: 'application/json',
498
+ 'Content-Type': 'application/json'
499
+ };
500
+ const DEFAULT_REQUEST_CACHE = new Map();
501
+ async function requestWithParameters({
502
+ baseUrl,
503
+ parameters = {},
504
+ headers: customHeaders = {},
505
+ errorContext,
506
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL,
507
+ localCache
508
+ }) {
509
+ // Parameters added to all requests issued with `requestWithParameters()`.
510
+ // These parameters override parameters already in the base URL, but not
511
+ // user-provided parameters.
512
+ parameters = _extends({
513
+ v: V3_MINOR_VERSION,
514
+ client: getClient()
515
+ }, typeof deck !== 'undefined' && deck.VERSION && {
516
+ deckglVersion: deck.VERSION
517
+ }, parameters);
518
+ baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
519
+ const key = createCacheKey(baseUrl, parameters, customHeaders);
520
+ const {
521
+ cache: REQUEST_CACHE,
522
+ canReadCache,
523
+ canStoreInCache
524
+ } = getCacheSettings(localCache);
525
+ if (canReadCache && REQUEST_CACHE.has(key)) {
526
+ return REQUEST_CACHE.get(key);
622
527
  }
623
- _getModelSource(owner) {
624
- const props = this.props;
625
- return {
626
- apiVersion: props.apiVersion,
627
- apiBaseUrl: props.apiBaseUrl,
628
- clientId: props.clientId,
629
- accessToken: props.accessToken,
630
- connectionName: props.connectionName,
631
- filters: getApplicableFilters(owner, props.filters),
632
- filtersLogicalOperator: props.filtersLogicalOperator,
633
- spatialDataType: props.spatialDataType,
634
- spatialDataColumn: props.spatialDataColumn,
635
- dataResolution: props.dataResolution
636
- };
637
- }
638
- /****************************************************************************
639
- * CATEGORIES
640
- */
641
- /**
642
- * Returns a list of labeled datapoints for categorical data. Suitable for
643
- * charts including grouped bar charts, pie charts, and tree charts.
644
- */
645
- async getCategories(options) {
646
- const {
647
- filterOwner,
648
- spatialFilter,
649
- spatialFiltersMode,
650
- abortController,
651
- viewState
652
- } = options,
653
- params = _objectWithoutPropertiesLoose(options, _excluded$1);
654
- const {
655
- column,
656
- operation,
657
- operationColumn
658
- } = params;
659
- const source = this.getModelSource(filterOwner);
660
- let spatialFiltersResolution;
661
- if (spatialFilter && source.spatialDataType !== 'geo') {
662
- spatialFiltersResolution = getSpatialFiltersResolution({
663
- source,
664
- viewState
665
- });
528
+ const url = createURLWithParameters(baseUrl, parameters);
529
+ const headers = _extends({}, DEFAULT_HEADERS, customHeaders);
530
+ /* global fetch */
531
+ const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
532
+ method: 'POST',
533
+ body: JSON.stringify(parameters),
534
+ headers
535
+ }) : fetch(url, {
536
+ headers
537
+ });
538
+ let response;
539
+ let responseJson;
540
+ const jsonPromise = fetchPromise.then(_response => {
541
+ response = _response;
542
+ return response.json();
543
+ }).then(json => {
544
+ responseJson = json;
545
+ if (!response || !response.ok) {
546
+ throw new Error(json.error);
666
547
  }
667
- return executeModel({
668
- model: 'category',
669
- source: _extends({}, source, {
670
- spatialFiltersResolution,
671
- spatialFiltersMode,
672
- spatialFilter
673
- }),
674
- params: {
675
- column,
676
- operation,
677
- operationColumn: operationColumn || column
678
- },
679
- opts: {
680
- abortController
681
- }
682
- }).then(res => normalizeObjectKeys(res.rows));
683
- }
684
- /****************************************************************************
685
- * FEATURES
686
- */
687
- /**
688
- * Given a list of feature IDs (as found in `_carto_feature_id`) returns all
689
- * matching features. In datasets containing features with duplicate geometries,
690
- * feature IDs may be duplicated (IDs are a hash of geometry) and so more
691
- * results may be returned than IDs in the request.
692
- * @internal
693
- * @experimental
694
- */
695
- async getFeatures(options) {
696
- const {
697
- filterOwner,
698
- spatialFilter,
699
- spatialFiltersMode,
700
- abortController,
701
- viewState
702
- } = options,
703
- params = _objectWithoutPropertiesLoose(options, _excluded2);
704
- const {
705
- columns,
706
- dataType,
707
- featureIds,
708
- z,
709
- limit,
710
- tileResolution
711
- } = params;
712
- const source = this.getModelSource(filterOwner);
713
- let spatialFiltersResolution;
714
- if (spatialFilter && source.spatialDataType !== 'geo') {
715
- spatialFiltersResolution = getSpatialFiltersResolution({
716
- source,
717
- viewState
718
- });
548
+ return json;
549
+ }).catch(error => {
550
+ if (canStoreInCache) {
551
+ REQUEST_CACHE.delete(key);
719
552
  }
720
- return executeModel({
721
- model: 'pick',
722
- source: _extends({}, source, {
723
- spatialFiltersResolution,
724
- spatialFiltersMode,
725
- spatialFilter
726
- }),
727
- params: {
728
- columns,
729
- dataType,
730
- featureIds,
731
- z,
732
- limit: limit || 1000,
733
- tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
734
- },
735
- opts: {
736
- abortController
737
- }
738
- }).then(res => ({
739
- rows: normalizeObjectKeys(res.rows)
740
- }));
553
+ throw new CartoAPIError(error, errorContext, response, responseJson);
554
+ });
555
+ if (canStoreInCache) {
556
+ REQUEST_CACHE.set(key, jsonPromise);
741
557
  }
742
- /****************************************************************************
743
- * FORMULA
744
- */
745
- /**
746
- * Returns a scalar numerical statistic over all matching data. Suitable
747
- * for 'headline' or 'scorecard' figures such as counts and sums.
748
- */
749
- async getFormula(options) {
750
- const {
751
- filterOwner,
752
- spatialFilter,
753
- spatialFiltersMode,
754
- abortController,
755
- operationExp,
756
- viewState
757
- } = options,
758
- params = _objectWithoutPropertiesLoose(options, _excluded3);
759
- const {
760
- column,
761
- operation
762
- } = params;
763
- const source = this.getModelSource(filterOwner);
764
- let spatialFiltersResolution;
765
- if (spatialFilter && source.spatialDataType !== 'geo') {
766
- spatialFiltersResolution = getSpatialFiltersResolution({
767
- source,
768
- viewState
769
- });
558
+ return jsonPromise;
559
+ }
560
+ function getCacheSettings(localCache) {
561
+ var _localCache$cacheCont, _localCache$cacheCont2;
562
+ const canReadCache = localCache != null && (_localCache$cacheCont = localCache.cacheControl) != null && _localCache$cacheCont.includes('no-cache') ? false : true;
563
+ const canStoreInCache = localCache != null && (_localCache$cacheCont2 = localCache.cacheControl) != null && _localCache$cacheCont2.includes('no-store') ? false : true;
564
+ const cache = (localCache == null ? void 0 : localCache.cache) || DEFAULT_REQUEST_CACHE;
565
+ return {
566
+ cache,
567
+ canReadCache,
568
+ canStoreInCache
569
+ };
570
+ }
571
+ function createCacheKey(baseUrl, parameters, headers) {
572
+ const parameterEntries = Object.entries(parameters).sort(([a], [b]) => a > b ? 1 : -1);
573
+ const headerEntries = Object.entries(headers).sort(([a], [b]) => a > b ? 1 : -1);
574
+ return JSON.stringify({
575
+ baseUrl,
576
+ parameters: parameterEntries,
577
+ headers: headerEntries
578
+ });
579
+ }
580
+ /**
581
+ * Appends query string parameters to a URL. Existing URL parameters are kept,
582
+ * unless there is a conflict, in which case the new parameters override
583
+ * those already in the URL.
584
+ */
585
+ function createURLWithParameters(baseUrlString, parameters) {
586
+ const baseUrl = new URL(baseUrlString);
587
+ for (const [key, value] of Object.entries(parameters)) {
588
+ if (isPureObject(value) || Array.isArray(value)) {
589
+ baseUrl.searchParams.set(key, JSON.stringify(value));
590
+ } else {
591
+ baseUrl.searchParams.set(key, value.toString());
770
592
  }
771
- return executeModel({
772
- model: 'formula',
773
- source: _extends({}, source, {
774
- spatialFiltersResolution,
775
- spatialFiltersMode,
776
- spatialFilter
777
- }),
778
- params: {
779
- column: column != null ? column : '*',
780
- operation,
781
- operationExp
782
- },
783
- opts: {
784
- abortController
785
- }
786
- }).then(res => normalizeObjectKeys(res.rows[0]));
787
593
  }
788
- /****************************************************************************
789
- * HISTOGRAM
790
- */
791
- /**
792
- * Returns a list of labeled datapoints for 'bins' of data defined as ticks
793
- * over a numerical range. Suitable for histogram charts.
794
- */
795
- async getHistogram(options) {
796
- const {
797
- filterOwner,
798
- spatialFilter,
799
- spatialFiltersMode,
800
- abortController,
801
- viewState
802
- } = options,
803
- params = _objectWithoutPropertiesLoose(options, _excluded4);
804
- const {
805
- column,
806
- operation,
807
- ticks
808
- } = params;
809
- const source = this.getModelSource(filterOwner);
810
- let spatialFiltersResolution;
811
- if (spatialFilter && source.spatialDataType !== 'geo') {
812
- spatialFiltersResolution = getSpatialFiltersResolution({
813
- source,
814
- viewState
815
- });
594
+ return baseUrl.toString();
595
+ }
596
+ /**
597
+ * Deletes query string parameters from a URL.
598
+ */
599
+ function excludeURLParameters(baseUrlString, parameters) {
600
+ const baseUrl = new URL(baseUrlString);
601
+ for (const param of parameters) {
602
+ if (baseUrl.searchParams.has(param)) {
603
+ baseUrl.searchParams.delete(param);
816
604
  }
817
- const data = await executeModel({
818
- model: 'histogram',
819
- source: _extends({}, source, {
820
- spatialFiltersResolution,
821
- spatialFiltersMode,
822
- spatialFilter
823
- }),
824
- params: {
825
- column,
826
- operation,
827
- ticks
828
- },
829
- opts: {
830
- abortController
831
- }
832
- }).then(res => normalizeObjectKeys(res.rows));
833
- if (data.length) {
834
- // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
835
- // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
836
- const result = Array(ticks.length + 1).fill(0);
837
- data.forEach(({
838
- tick,
839
- value
840
- }) => result[tick] = value);
841
- return result;
605
+ }
606
+ return baseUrl.toString();
607
+ }
608
+
609
+ const _excluded$1 = ["accessToken", "connectionName", "cache"];
610
+ const SOURCE_DEFAULTS = {
611
+ apiBaseUrl: DEFAULT_API_BASE_URL,
612
+ clientId: getClient(),
613
+ format: 'tilejson',
614
+ headers: {},
615
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
616
+ };
617
+ async function baseSource(endpoint, options, urlParameters) {
618
+ const {
619
+ accessToken,
620
+ connectionName,
621
+ cache
622
+ } = options,
623
+ optionalOptions = _objectWithoutPropertiesLoose(options, _excluded$1);
624
+ const mergedOptions = _extends({}, SOURCE_DEFAULTS, {
625
+ accessToken,
626
+ connectionName,
627
+ endpoint
628
+ });
629
+ for (const key in optionalOptions) {
630
+ if (optionalOptions[key]) {
631
+ mergedOptions[key] = optionalOptions[key];
842
632
  }
843
- return [];
844
633
  }
845
- /****************************************************************************
846
- * RANGE
847
- */
848
- /**
849
- * Returns a range (min and max) for a numerical column of matching rows.
850
- * Suitable for displaying certain 'headline' or 'scorecard' statistics,
851
- * or rendering a range slider UI for filtering.
852
- */
853
- async getRange(options) {
854
- const {
855
- filterOwner,
856
- spatialFilter,
857
- spatialFiltersMode,
858
- abortController,
859
- viewState
860
- } = options,
861
- params = _objectWithoutPropertiesLoose(options, _excluded5);
862
- const {
863
- column
864
- } = params;
865
- const source = this.getModelSource(filterOwner);
866
- let spatialFiltersResolution;
867
- if (spatialFilter && source.spatialDataType !== 'geo') {
868
- spatialFiltersResolution = getSpatialFiltersResolution({
869
- source,
870
- viewState
871
- });
872
- }
873
- return executeModel({
874
- model: 'range',
875
- source: _extends({}, source, {
876
- spatialFiltersResolution,
877
- spatialFiltersMode,
878
- spatialFilter
879
- }),
880
- params: {
881
- column
882
- },
883
- opts: {
884
- abortController
885
- }
886
- }).then(res => normalizeObjectKeys(res.rows[0]));
887
- }
888
- /****************************************************************************
889
- * SCATTER
890
- */
891
- /**
892
- * Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
893
- * values. Suitable for rendering scatter plots.
894
- */
895
- async getScatter(options) {
896
- const {
897
- filterOwner,
898
- spatialFilter,
899
- spatialFiltersMode,
900
- abortController,
901
- viewState
902
- } = options,
903
- params = _objectWithoutPropertiesLoose(options, _excluded6);
904
- const {
905
- xAxisColumn,
906
- xAxisJoinOperation,
907
- yAxisColumn,
908
- yAxisJoinOperation
909
- } = params;
910
- const source = this.getModelSource(filterOwner);
911
- let spatialFiltersResolution;
912
- if (spatialFilter && source.spatialDataType !== 'geo') {
913
- spatialFiltersResolution = getSpatialFiltersResolution({
914
- source,
915
- viewState
916
- });
917
- }
918
- // Make sure this is sync with the same constant in cloud-native/maps-api
919
- const HARD_LIMIT = 500;
920
- return executeModel({
921
- model: 'scatterplot',
922
- source: _extends({}, source, {
923
- spatialFiltersResolution,
924
- spatialFiltersMode,
925
- spatialFilter
926
- }),
927
- params: {
928
- xAxisColumn,
929
- xAxisJoinOperation,
930
- yAxisColumn,
931
- yAxisJoinOperation,
932
- limit: HARD_LIMIT
933
- },
934
- opts: {
935
- abortController
936
- }
937
- }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(({
938
- x,
939
- y
940
- }) => [x, y]));
634
+ const baseUrl = buildSourceUrl(mergedOptions);
635
+ const {
636
+ clientId,
637
+ maxLengthURL,
638
+ format,
639
+ localCache
640
+ } = mergedOptions;
641
+ const headers = _extends({
642
+ Authorization: `Bearer ${options.accessToken}`
643
+ }, options.headers);
644
+ const parameters = _extends({
645
+ client: clientId
646
+ }, urlParameters);
647
+ const errorContext = {
648
+ requestType: 'Map instantiation',
649
+ connection: options.connectionName,
650
+ type: endpoint,
651
+ source: JSON.stringify(parameters, undefined, 2)
652
+ };
653
+ const mapInstantiation = await requestWithParameters({
654
+ baseUrl,
655
+ parameters,
656
+ headers,
657
+ errorContext,
658
+ maxLengthURL,
659
+ localCache
660
+ });
661
+ const dataUrl = mapInstantiation[format].url[0];
662
+ if (cache) {
663
+ cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
941
664
  }
942
- /****************************************************************************
943
- * TABLE
944
- */
945
- /**
946
- * Returns a list of arbitrary data rows, with support for pagination and
947
- * sorting. Suitable for displaying tables and lists.
948
- */
949
- async getTable(options) {
950
- const {
951
- filterOwner,
952
- spatialFilter,
953
- spatialFiltersMode,
954
- abortController,
955
- viewState
956
- } = options,
957
- params = _objectWithoutPropertiesLoose(options, _excluded7);
958
- const {
959
- columns,
960
- sortBy,
961
- sortDirection,
962
- offset = 0,
963
- limit = 10
964
- } = params;
965
- const source = this.getModelSource(filterOwner);
966
- let spatialFiltersResolution;
967
- if (spatialFilter && source.spatialDataType !== 'geo') {
968
- spatialFiltersResolution = getSpatialFiltersResolution({
969
- source,
970
- viewState
971
- });
972
- }
973
- return executeModel({
974
- model: 'table',
975
- source: _extends({}, source, {
976
- spatialFiltersResolution,
977
- spatialFiltersMode,
978
- spatialFilter
979
- }),
980
- params: {
981
- column: columns,
982
- sortBy,
983
- sortDirection,
984
- limit,
985
- offset
986
- },
987
- opts: {
988
- abortController
989
- }
990
- }).then(res => {
991
- var _res$rows, _res$metadata$total, _res$metadata, _res$METADATA;
992
- return {
993
- // Avoid `normalizeObjectKeys()`, which changes column names.
994
- rows: (_res$rows = res.rows) != null ? _res$rows : res.ROWS,
995
- totalCount: (_res$metadata$total = (_res$metadata = res.metadata) == null ? void 0 : _res$metadata.total) != null ? _res$metadata$total : (_res$METADATA = res.METADATA) == null ? void 0 : _res$METADATA.TOTAL
996
- };
665
+ errorContext.requestType = 'Map data';
666
+ if (format === 'tilejson') {
667
+ const json = await requestWithParameters({
668
+ baseUrl: dataUrl,
669
+ headers,
670
+ errorContext,
671
+ maxLengthURL,
672
+ localCache
997
673
  });
998
- }
999
- /****************************************************************************
1000
- * TIME SERIES
1001
- */
1002
- /**
1003
- * Returns a series of labeled numerical values, grouped into equally-sized
1004
- * time intervals. Suitable for rendering time series charts.
1005
- */
1006
- async getTimeSeries(options) {
1007
- const {
1008
- filterOwner,
1009
- abortController,
1010
- spatialFilter,
1011
- spatialFiltersMode,
1012
- viewState
1013
- } = options,
1014
- params = _objectWithoutPropertiesLoose(options, _excluded8);
1015
- const {
1016
- column,
1017
- operationColumn,
1018
- joinOperation,
1019
- operation,
1020
- stepSize,
1021
- stepMultiplier,
1022
- splitByCategory,
1023
- splitByCategoryLimit,
1024
- splitByCategoryValues
1025
- } = params;
1026
- const source = this.getModelSource(filterOwner);
1027
- let spatialFiltersResolution;
1028
- if (spatialFilter && source.spatialDataType !== 'geo') {
1029
- spatialFiltersResolution = getSpatialFiltersResolution({
1030
- source,
1031
- viewState
1032
- });
674
+ if (accessToken) {
675
+ json.accessToken = accessToken;
1033
676
  }
1034
- return executeModel({
1035
- model: 'timeseries',
1036
- source: _extends({}, source, {
1037
- spatialFiltersResolution,
1038
- spatialFiltersMode,
1039
- spatialFilter
1040
- }),
1041
- params: {
1042
- column,
1043
- stepSize,
1044
- stepMultiplier,
1045
- operationColumn: operationColumn || column,
1046
- joinOperation,
1047
- operation,
1048
- splitByCategory,
1049
- splitByCategoryLimit,
1050
- splitByCategoryValues
1051
- },
1052
- opts: {
1053
- abortController
1054
- }
1055
- }).then(res => {
1056
- var _res$metadata2;
1057
- return {
1058
- rows: normalizeObjectKeys(res.rows),
1059
- categories: (_res$metadata2 = res.metadata) == null ? void 0 : _res$metadata2.categories
1060
- };
1061
- });
677
+ return json;
1062
678
  }
679
+ return await requestWithParameters({
680
+ baseUrl: dataUrl,
681
+ headers,
682
+ errorContext,
683
+ maxLengthURL,
684
+ localCache
685
+ });
1063
686
  }
1064
- WidgetBaseSource.defaultProps = {
1065
- apiVersion: ApiVersion.V3,
1066
- apiBaseUrl: DEFAULT_API_BASE_URL,
1067
- clientId: getClient(),
1068
- filters: {},
1069
- filtersLogicalOperator: 'and'
687
+
688
+ // deck.gl
689
+ const boundaryQuerySource = async function boundaryQuerySource(options) {
690
+ const {
691
+ columns,
692
+ filters,
693
+ tilesetTableName,
694
+ propertiesSqlQuery,
695
+ queryParameters
696
+ } = options;
697
+ const urlParameters = {
698
+ tilesetTableName,
699
+ propertiesSqlQuery
700
+ };
701
+ if (columns) {
702
+ urlParameters.columns = columns.join(',');
703
+ }
704
+ if (filters) {
705
+ urlParameters.filters = filters;
706
+ }
707
+ if (queryParameters) {
708
+ urlParameters.queryParameters = queryParameters;
709
+ }
710
+ return baseSource('boundary', options, urlParameters);
1070
711
  };
1071
712
 
1072
- /**
1073
- * Source for Widget API requests on a data source defined by a SQL query.
1074
- *
1075
- * Generally not intended to be constructed directly. Instead, call
1076
- * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
1077
- * which can be shared with map layers. Sources contain a `widgetSource` property,
1078
- * for use by widget implementations.
1079
- *
1080
- * Example:
1081
- *
1082
- * ```javascript
1083
- * import { vectorQuerySource } from '@carto/api-client';
1084
- *
1085
- * const data = vectorQuerySource({
1086
- * accessToken: '••••',
1087
- * connectionName: 'carto_dw',
1088
- * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
1089
- * });
1090
- *
1091
- * const { widgetSource } = await data;
1092
- * ```
1093
- */
1094
- class WidgetQuerySource extends WidgetBaseSource {
1095
- getModelSource(owner) {
1096
- return _extends({}, super._getModelSource(owner), {
1097
- type: 'query',
1098
- data: this.props.sqlQuery,
1099
- queryParameters: this.props.queryParameters
1100
- });
713
+ // deck.gl
714
+ const boundaryTableSource = async function boundaryTableSource(options) {
715
+ const {
716
+ filters,
717
+ tilesetTableName,
718
+ columns,
719
+ propertiesTableName
720
+ } = options;
721
+ const urlParameters = {
722
+ tilesetTableName,
723
+ propertiesTableName
724
+ };
725
+ if (columns) {
726
+ urlParameters.columns = columns.join(',');
1101
727
  }
1102
- }
728
+ if (filters) {
729
+ urlParameters.filters = filters;
730
+ }
731
+ return baseSource('boundary', options, urlParameters);
732
+ };
1103
733
 
1104
734
  /**
1105
- * Source for Widget API requests on a data source defined as a table.
1106
- *
1107
- * Generally not intended to be constructed directly. Instead, call
1108
- * {@link vectorTableSource}, {@link h3TableSource}, or {@link quadbinTableSource},
1109
- * which can be shared with map layers. Sources contain a `widgetSource` property,
1110
- * for use by widget implementations.
1111
- *
1112
- * Example:
1113
- *
1114
- * ```javascript
1115
- * import { vectorTableSource } from '@carto/api-client';
1116
- *
1117
- * const data = vectorTableSource({
1118
- * accessToken: '••••',
1119
- * connectionName: 'carto_dw',
1120
- * tableName: 'carto-demo-data.demo_tables.retail_stores'
1121
- * });
1122
- *
1123
- * const { widgetSource } = await data;
1124
- * ```
735
+ * Return more descriptive error from API
736
+ * @internalRemarks Source: @carto/react-api
1125
737
  */
1126
- class WidgetTableSource extends WidgetBaseSource {
1127
- getModelSource(owner) {
1128
- return _extends({}, super._getModelSource(owner), {
1129
- type: 'table',
1130
- data: this.props.tableName
738
+ function dealWithApiError({
739
+ response,
740
+ data
741
+ }) {
742
+ var _data$error, _data$error2;
743
+ if (data.error === 'Column not found') {
744
+ throw new InvalidColumnError(`${data.error} ${data.column_name}`);
745
+ }
746
+ if (typeof data.error === 'string' && (_data$error = data.error) != null && _data$error.includes('Missing columns')) {
747
+ throw new InvalidColumnError(data.error);
748
+ }
749
+ switch (response.status) {
750
+ case 401:
751
+ throw new Error('Unauthorized access. Invalid credentials');
752
+ case 403:
753
+ throw new Error('Forbidden access to the requested data');
754
+ default:
755
+ const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify((data == null ? void 0 : data.hint) || ((_data$error2 = data.error) == null ? void 0 : _data$error2[0]));
756
+ throw new Error(msg);
757
+ }
758
+ }
759
+ /** @internalRemarks Source: @carto/react-api */
760
+ async function makeCall({
761
+ url,
762
+ accessToken,
763
+ opts
764
+ }) {
765
+ let response;
766
+ let data;
767
+ const isPost = (opts == null ? void 0 : opts.method) === 'POST';
768
+ try {
769
+ var _opts$abortController;
770
+ response = await fetch(url.toString(), _extends({
771
+ headers: _extends({
772
+ Authorization: `Bearer ${accessToken}`
773
+ }, isPost && {
774
+ 'Content-Type': 'application/json'
775
+ })
776
+ }, isPost && {
777
+ method: opts == null ? void 0 : opts.method,
778
+ body: opts == null ? void 0 : opts.body
779
+ }, {
780
+ signal: opts == null || (_opts$abortController = opts.abortController) == null ? void 0 : _opts$abortController.signal
781
+ }, opts == null ? void 0 : opts.otherOptions));
782
+ data = await response.json();
783
+ } catch (error) {
784
+ if (error.name === 'AbortError') throw error;
785
+ throw new Error(`Failed request: ${error}`);
786
+ }
787
+ if (!response.ok) {
788
+ dealWithApiError({
789
+ response,
790
+ data
1131
791
  });
1132
792
  }
793
+ return data;
1133
794
  }
1134
795
 
1135
- // deck.gl
1136
- // SPDX-License-Identifier: MIT
1137
- // Copyright (c) vis.gl contributors
796
+ /** @internalRemarks Source: @carto/react-api */
797
+ const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
798
+ const {
799
+ V3
800
+ } = ApiVersion;
801
+ const REQUEST_GET_MAX_URL_LENGTH = 2048;
1138
802
  /**
1139
- *
1140
- * Custom error for reported errors in CARTO Maps API.
1141
- * Provides useful debugging information in console and context for applications.
1142
- *
803
+ * Execute a SQL model request.
804
+ * @internalRemarks Source: @carto/react-api
1143
805
  */
1144
- class CartoAPIError extends Error {
1145
- constructor(error, errorContext, response, responseJson) {
1146
- let responseString = 'Failed to connect';
1147
- if (response) {
1148
- responseString = 'Server returned: ';
1149
- if (response.status === 400) {
1150
- responseString += 'Bad request';
1151
- } else if (response.status === 401 || response.status === 403) {
1152
- responseString += 'Unauthorized access';
1153
- } else if (response.status === 404) {
1154
- responseString += 'Not found';
1155
- } else {
1156
- responseString += 'Error';
1157
- }
1158
- responseString += ` (${response.status}):`;
1159
- }
1160
- responseString += ` ${error.message || error}`;
1161
- let message = `${errorContext.requestType} API request failed`;
1162
- message += `\n${responseString}`;
1163
- for (const key of Object.keys(errorContext)) {
1164
- if (key === 'requestType') continue;
1165
- message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
806
+ function executeModel(props) {
807
+ assert(props.source, 'executeModel: missing source');
808
+ assert(props.model, 'executeModel: missing model');
809
+ assert(props.params, 'executeModel: missing params');
810
+ assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
811
+ const {
812
+ model,
813
+ source,
814
+ params,
815
+ opts
816
+ } = props;
817
+ const {
818
+ type,
819
+ apiVersion,
820
+ apiBaseUrl,
821
+ accessToken,
822
+ connectionName,
823
+ clientId
824
+ } = source;
825
+ assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
826
+ assert(accessToken, 'executeModel: missing accessToken');
827
+ assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
828
+ assert(type !== 'tileset', 'executeModel: Tilesets not supported');
829
+ let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
830
+ const {
831
+ data,
832
+ filters,
833
+ filtersLogicalOperator = 'and',
834
+ geoColumn = DEFAULT_GEO_COLUMN
835
+ } = source;
836
+ const queryParameters = source.queryParameters ? JSON.stringify(source.queryParameters) : '';
837
+ const queryParams = {
838
+ type,
839
+ client: clientId,
840
+ source: data,
841
+ params: JSON.stringify(params),
842
+ queryParameters,
843
+ filters: JSON.stringify(filters),
844
+ filtersLogicalOperator
845
+ };
846
+ // Picking Model API requires 'spatialDataColumn'.
847
+ if (model === 'pick') {
848
+ queryParams.spatialDataColumn = geoColumn;
849
+ }
850
+ // API supports multiple filters, we apply it only to geoColumn
851
+ const spatialFilters = source.spatialFilter ? {
852
+ [geoColumn]: source.spatialFilter
853
+ } : undefined;
854
+ if (spatialFilters) {
855
+ queryParams.spatialFilters = JSON.stringify(spatialFilters);
856
+ }
857
+ const urlWithSearchParams = url + '?' + new URLSearchParams(queryParams).toString();
858
+ const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
859
+ if (isGet) {
860
+ url = urlWithSearchParams;
861
+ } else {
862
+ // undo the JSON.stringify, @TODO find a better pattern
863
+ queryParams.params = params;
864
+ queryParams.filters = filters;
865
+ queryParams.queryParameters = source.queryParameters;
866
+ if (spatialFilters) {
867
+ queryParams.spatialFilters = spatialFilters;
1166
868
  }
1167
- message += '\n';
1168
- super(message);
1169
- /** Source error from server */
1170
- this.error = void 0;
1171
- /** Context (API call & parameters) in which error occured */
1172
- this.errorContext = void 0;
1173
- /** Response from server */
1174
- this.response = void 0;
1175
- /** JSON Response from server */
1176
- this.responseJson = void 0;
1177
- this.name = 'CartoAPIError';
1178
- this.response = response;
1179
- this.responseJson = responseJson;
1180
- this.error = error;
1181
- this.errorContext = errorContext;
1182
869
  }
870
+ return makeCall({
871
+ url,
872
+ accessToken: source.accessToken,
873
+ opts: _extends({}, opts, {
874
+ method: isGet ? 'GET' : 'POST'
875
+ }, !isGet && {
876
+ body: JSON.stringify(queryParams)
877
+ })
878
+ });
1183
879
  }
880
+
881
+ const _excluded = ["filterOwner", "spatialFilter", "abortController"],
882
+ _excluded2 = ["filterOwner", "spatialFilter", "abortController"],
883
+ _excluded3 = ["filterOwner", "spatialFilter", "abortController", "operationExp"],
884
+ _excluded4 = ["filterOwner", "spatialFilter", "abortController"],
885
+ _excluded5 = ["filterOwner", "spatialFilter", "abortController"],
886
+ _excluded6 = ["filterOwner", "spatialFilter", "abortController"],
887
+ _excluded7 = ["filterOwner", "spatialFilter", "abortController"],
888
+ _excluded8 = ["filterOwner", "abortController", "spatialFilter"];
1184
889
  /**
1185
- * Converts camelCase to Camel Case
890
+ * Source for Widget API requests on a data source defined by a SQL query.
891
+ *
892
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1186
893
  */
1187
- function formatErrorKey(key) {
1188
- return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
1189
- }
1190
-
1191
- // deck.gl
1192
- // SPDX-License-Identifier: MIT
1193
- // Copyright (c) vis.gl contributors
1194
- function joinPath(...args) {
1195
- return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
1196
- }
1197
- function buildV3Path(apiBaseUrl, version, endpoint, ...rest) {
1198
- return joinPath(apiBaseUrl, version, endpoint, ...rest);
1199
- }
1200
- /** @internal Required by fetchMap(). */
1201
- function buildPublicMapUrl({
1202
- apiBaseUrl,
1203
- cartoMapId
1204
- }) {
1205
- return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
1206
- }
1207
- /** @internal Required by fetchMap(). */
1208
- function buildStatsUrl({
1209
- attribute,
1210
- apiBaseUrl,
1211
- connectionName,
1212
- source,
1213
- type
1214
- }) {
1215
- if (type === 'query') {
1216
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
894
+ class WidgetBaseSource {
895
+ constructor(props) {
896
+ this.props = void 0;
897
+ this.props = _extends({}, WidgetBaseSource.defaultProps, props);
1217
898
  }
1218
- // type === 'table'
1219
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
1220
- }
1221
- function buildSourceUrl({
1222
- apiBaseUrl,
1223
- connectionName,
1224
- endpoint
1225
- }) {
1226
- return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
1227
- }
1228
- function buildQueryUrl({
1229
- apiBaseUrl,
1230
- connectionName
1231
- }) {
1232
- return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
1233
- }
1234
-
1235
- const DEFAULT_HEADERS = {
1236
- Accept: 'application/json',
1237
- 'Content-Type': 'application/json'
1238
- };
1239
- const DEFAULT_REQUEST_CACHE = new Map();
1240
- async function requestWithParameters({
1241
- baseUrl,
1242
- parameters = {},
1243
- headers: customHeaders = {},
1244
- errorContext,
1245
- maxLengthURL = DEFAULT_MAX_LENGTH_URL,
1246
- localCache
1247
- }) {
1248
- // Parameters added to all requests issued with `requestWithParameters()`.
1249
- // These parameters override parameters already in the base URL, but not
1250
- // user-provided parameters.
1251
- parameters = _extends({
1252
- v: V3_MINOR_VERSION,
1253
- client: getClient()
1254
- }, typeof deck !== 'undefined' && deck.VERSION && {
1255
- deckglVersion: deck.VERSION
1256
- }, parameters);
1257
- baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
1258
- const key = createCacheKey(baseUrl, parameters, customHeaders);
1259
- const {
1260
- cache: REQUEST_CACHE,
1261
- canReadCache,
1262
- canStoreInCache
1263
- } = getCacheSettings(localCache);
1264
- if (canReadCache && REQUEST_CACHE.has(key)) {
1265
- return REQUEST_CACHE.get(key);
899
+ _getModelSource(owner) {
900
+ const props = this.props;
901
+ return {
902
+ apiVersion: props.apiVersion,
903
+ apiBaseUrl: props.apiBaseUrl,
904
+ clientId: props.clientId,
905
+ accessToken: props.accessToken,
906
+ connectionName: props.connectionName,
907
+ filters: getApplicableFilters(owner, props.filters),
908
+ filtersLogicalOperator: props.filtersLogicalOperator,
909
+ geoColumn: props.geoColumn
910
+ };
1266
911
  }
1267
- const url = createURLWithParameters(baseUrl, parameters);
1268
- const headers = _extends({}, DEFAULT_HEADERS, customHeaders);
1269
- /* global fetch */
1270
- const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
1271
- method: 'POST',
1272
- body: JSON.stringify(parameters),
1273
- headers
1274
- }) : fetch(url, {
1275
- headers
1276
- });
1277
- let response;
1278
- let responseJson;
1279
- const jsonPromise = fetchPromise.then(_response => {
1280
- response = _response;
1281
- return response.json();
1282
- }).then(json => {
1283
- responseJson = json;
1284
- if (!response || !response.ok) {
1285
- throw new Error(json.error);
1286
- }
1287
- return json;
1288
- }).catch(error => {
1289
- if (canStoreInCache) {
1290
- REQUEST_CACHE.delete(key);
1291
- }
1292
- throw new CartoAPIError(error, errorContext, response, responseJson);
1293
- });
1294
- if (canStoreInCache) {
1295
- REQUEST_CACHE.set(key, jsonPromise);
912
+ /****************************************************************************
913
+ * CATEGORIES
914
+ */
915
+ /**
916
+ * Returns a list of labeled datapoints for categorical data. Suitable for
917
+ * charts including grouped bar charts, pie charts, and tree charts.
918
+ */
919
+ async getCategories(options) {
920
+ const {
921
+ filterOwner,
922
+ spatialFilter,
923
+ abortController
924
+ } = options,
925
+ params = _objectWithoutPropertiesLoose(options, _excluded);
926
+ const {
927
+ column,
928
+ operation,
929
+ operationColumn
930
+ } = params;
931
+ return executeModel({
932
+ model: 'category',
933
+ source: _extends({}, this.getModelSource(filterOwner), {
934
+ spatialFilter
935
+ }),
936
+ params: {
937
+ column,
938
+ operation,
939
+ operationColumn: operationColumn || column
940
+ },
941
+ opts: {
942
+ abortController
943
+ }
944
+ }).then(res => normalizeObjectKeys(res.rows));
1296
945
  }
1297
- return jsonPromise;
1298
- }
1299
- function getCacheSettings(localCache) {
1300
- var _localCache$cacheCont, _localCache$cacheCont2;
1301
- const canReadCache = localCache != null && (_localCache$cacheCont = localCache.cacheControl) != null && _localCache$cacheCont.includes('no-cache') ? false : true;
1302
- const canStoreInCache = localCache != null && (_localCache$cacheCont2 = localCache.cacheControl) != null && _localCache$cacheCont2.includes('no-store') ? false : true;
1303
- const cache = (localCache == null ? void 0 : localCache.cache) || DEFAULT_REQUEST_CACHE;
1304
- return {
1305
- cache,
1306
- canReadCache,
1307
- canStoreInCache
1308
- };
1309
- }
1310
- function createCacheKey(baseUrl, parameters, headers) {
1311
- const parameterEntries = Object.entries(parameters).sort(([a], [b]) => a > b ? 1 : -1);
1312
- const headerEntries = Object.entries(headers).sort(([a], [b]) => a > b ? 1 : -1);
1313
- return JSON.stringify({
1314
- baseUrl,
1315
- parameters: parameterEntries,
1316
- headers: headerEntries
1317
- });
1318
- }
1319
- /**
1320
- * Appends query string parameters to a URL. Existing URL parameters are kept,
1321
- * unless there is a conflict, in which case the new parameters override
1322
- * those already in the URL.
1323
- */
1324
- function createURLWithParameters(baseUrlString, parameters) {
1325
- const baseUrl = new URL(baseUrlString);
1326
- for (const [key, value] of Object.entries(parameters)) {
1327
- if (isPureObject(value) || Array.isArray(value)) {
1328
- baseUrl.searchParams.set(key, JSON.stringify(value));
1329
- } else {
1330
- baseUrl.searchParams.set(key, value.toString());
946
+ /****************************************************************************
947
+ * FEATURES
948
+ */
949
+ /**
950
+ * Given a list of feature IDs (as found in `_carto_feature_id`) returns all
951
+ * matching features. In datasets containing features with duplicate geometries,
952
+ * feature IDs may be duplicated (IDs are a hash of geometry) and so more
953
+ * results may be returned than IDs in the request.
954
+ * @internal
955
+ * @experimental
956
+ */
957
+ async getFeatures(options) {
958
+ const {
959
+ filterOwner,
960
+ spatialFilter,
961
+ abortController
962
+ } = options,
963
+ params = _objectWithoutPropertiesLoose(options, _excluded2);
964
+ const {
965
+ columns,
966
+ dataType,
967
+ featureIds,
968
+ z,
969
+ limit,
970
+ tileResolution
971
+ } = params;
972
+ return executeModel({
973
+ model: 'pick',
974
+ source: _extends({}, this.getModelSource(filterOwner), {
975
+ spatialFilter
976
+ }),
977
+ params: {
978
+ columns,
979
+ dataType,
980
+ featureIds,
981
+ z,
982
+ limit: limit || 1000,
983
+ tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
984
+ },
985
+ opts: {
986
+ abortController
987
+ }
988
+ // Avoid `normalizeObjectKeys()`, which changes column names.
989
+ }).then(({
990
+ rows
991
+ }) => ({
992
+ rows
993
+ }));
994
+ }
995
+ /****************************************************************************
996
+ * FORMULA
997
+ */
998
+ /**
999
+ * Returns a scalar numerical statistic over all matching data. Suitable
1000
+ * for 'headline' or 'scorecard' figures such as counts and sums.
1001
+ */
1002
+ async getFormula(options) {
1003
+ const {
1004
+ filterOwner,
1005
+ spatialFilter,
1006
+ abortController,
1007
+ operationExp
1008
+ } = options,
1009
+ params = _objectWithoutPropertiesLoose(options, _excluded3);
1010
+ const {
1011
+ column,
1012
+ operation
1013
+ } = params;
1014
+ return executeModel({
1015
+ model: 'formula',
1016
+ source: _extends({}, this.getModelSource(filterOwner), {
1017
+ spatialFilter
1018
+ }),
1019
+ params: {
1020
+ column: column != null ? column : '*',
1021
+ operation,
1022
+ operationExp
1023
+ },
1024
+ opts: {
1025
+ abortController
1026
+ }
1027
+ }).then(res => normalizeObjectKeys(res.rows[0]));
1028
+ }
1029
+ /****************************************************************************
1030
+ * HISTOGRAM
1031
+ */
1032
+ /**
1033
+ * Returns a list of labeled datapoints for 'bins' of data defined as ticks
1034
+ * over a numerical range. Suitable for histogram charts.
1035
+ */
1036
+ async getHistogram(options) {
1037
+ const {
1038
+ filterOwner,
1039
+ spatialFilter,
1040
+ abortController
1041
+ } = options,
1042
+ params = _objectWithoutPropertiesLoose(options, _excluded4);
1043
+ const {
1044
+ column,
1045
+ operation,
1046
+ ticks
1047
+ } = params;
1048
+ const data = await executeModel({
1049
+ model: 'histogram',
1050
+ source: _extends({}, this.getModelSource(filterOwner), {
1051
+ spatialFilter
1052
+ }),
1053
+ params: {
1054
+ column,
1055
+ operation,
1056
+ ticks
1057
+ },
1058
+ opts: {
1059
+ abortController
1060
+ }
1061
+ }).then(res => normalizeObjectKeys(res.rows));
1062
+ if (data.length) {
1063
+ // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
1064
+ // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
1065
+ const result = Array(ticks.length + 1).fill(0);
1066
+ data.forEach(({
1067
+ tick,
1068
+ value
1069
+ }) => result[tick] = value);
1070
+ return result;
1331
1071
  }
1072
+ return [];
1073
+ }
1074
+ /****************************************************************************
1075
+ * RANGE
1076
+ */
1077
+ /**
1078
+ * Returns a range (min and max) for a numerical column of matching rows.
1079
+ * Suitable for displaying certain 'headline' or 'scorecard' statistics,
1080
+ * or rendering a range slider UI for filtering.
1081
+ */
1082
+ async getRange(options) {
1083
+ const {
1084
+ filterOwner,
1085
+ spatialFilter,
1086
+ abortController
1087
+ } = options,
1088
+ params = _objectWithoutPropertiesLoose(options, _excluded5);
1089
+ const {
1090
+ column
1091
+ } = params;
1092
+ return executeModel({
1093
+ model: 'range',
1094
+ source: _extends({}, this.getModelSource(filterOwner), {
1095
+ spatialFilter
1096
+ }),
1097
+ params: {
1098
+ column
1099
+ },
1100
+ opts: {
1101
+ abortController
1102
+ }
1103
+ }).then(res => normalizeObjectKeys(res.rows[0]));
1104
+ }
1105
+ /****************************************************************************
1106
+ * SCATTER
1107
+ */
1108
+ /**
1109
+ * Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
1110
+ * values. Suitable for rendering scatter plots.
1111
+ */
1112
+ async getScatter(options) {
1113
+ const {
1114
+ filterOwner,
1115
+ spatialFilter,
1116
+ abortController
1117
+ } = options,
1118
+ params = _objectWithoutPropertiesLoose(options, _excluded6);
1119
+ const {
1120
+ xAxisColumn,
1121
+ xAxisJoinOperation,
1122
+ yAxisColumn,
1123
+ yAxisJoinOperation
1124
+ } = params;
1125
+ // Make sure this is sync with the same constant in cloud-native/maps-api
1126
+ const HARD_LIMIT = 500;
1127
+ return executeModel({
1128
+ model: 'scatterplot',
1129
+ source: _extends({}, this.getModelSource(filterOwner), {
1130
+ spatialFilter
1131
+ }),
1132
+ params: {
1133
+ xAxisColumn,
1134
+ xAxisJoinOperation,
1135
+ yAxisColumn,
1136
+ yAxisJoinOperation,
1137
+ limit: HARD_LIMIT
1138
+ },
1139
+ opts: {
1140
+ abortController
1141
+ }
1142
+ }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(({
1143
+ x,
1144
+ y
1145
+ }) => [x, y]));
1146
+ }
1147
+ /****************************************************************************
1148
+ * TABLE
1149
+ */
1150
+ /**
1151
+ * Returns a list of arbitrary data rows, with support for pagination and
1152
+ * sorting. Suitable for displaying tables and lists.
1153
+ */
1154
+ async getTable(options) {
1155
+ const {
1156
+ filterOwner,
1157
+ spatialFilter,
1158
+ abortController
1159
+ } = options,
1160
+ params = _objectWithoutPropertiesLoose(options, _excluded7);
1161
+ const {
1162
+ columns,
1163
+ sortBy,
1164
+ sortDirection,
1165
+ offset = 0,
1166
+ limit = 10
1167
+ } = params;
1168
+ return executeModel({
1169
+ model: 'table',
1170
+ source: _extends({}, this.getModelSource(filterOwner), {
1171
+ spatialFilter
1172
+ }),
1173
+ params: {
1174
+ column: columns,
1175
+ sortBy,
1176
+ sortDirection,
1177
+ limit,
1178
+ offset
1179
+ },
1180
+ opts: {
1181
+ abortController
1182
+ }
1183
+ }).then(res => {
1184
+ var _res$rows, _res$metadata$total, _res$metadata, _res$METADATA;
1185
+ return {
1186
+ // Avoid `normalizeObjectKeys()`, which changes column names.
1187
+ rows: (_res$rows = res.rows) != null ? _res$rows : res.ROWS,
1188
+ totalCount: (_res$metadata$total = (_res$metadata = res.metadata) == null ? void 0 : _res$metadata.total) != null ? _res$metadata$total : (_res$METADATA = res.METADATA) == null ? void 0 : _res$METADATA.TOTAL
1189
+ };
1190
+ });
1332
1191
  }
1333
- return baseUrl.toString();
1334
- }
1335
- /**
1336
- * Deletes query string parameters from a URL.
1337
- */
1338
- function excludeURLParameters(baseUrlString, parameters) {
1339
- const baseUrl = new URL(baseUrlString);
1340
- for (const param of parameters) {
1341
- if (baseUrl.searchParams.has(param)) {
1342
- baseUrl.searchParams.delete(param);
1343
- }
1192
+ /****************************************************************************
1193
+ * TIME SERIES
1194
+ */
1195
+ /**
1196
+ * Returns a series of labeled numerical values, grouped into equally-sized
1197
+ * time intervals. Suitable for rendering time series charts.
1198
+ */
1199
+ async getTimeSeries(options) {
1200
+ const {
1201
+ filterOwner,
1202
+ abortController,
1203
+ spatialFilter
1204
+ } = options,
1205
+ params = _objectWithoutPropertiesLoose(options, _excluded8);
1206
+ const {
1207
+ column,
1208
+ operationColumn,
1209
+ joinOperation,
1210
+ operation,
1211
+ stepSize,
1212
+ stepMultiplier,
1213
+ splitByCategory,
1214
+ splitByCategoryLimit,
1215
+ splitByCategoryValues
1216
+ } = params;
1217
+ return executeModel({
1218
+ model: 'timeseries',
1219
+ source: _extends({}, this.getModelSource(filterOwner), {
1220
+ spatialFilter
1221
+ }),
1222
+ params: {
1223
+ column,
1224
+ stepSize,
1225
+ stepMultiplier,
1226
+ operationColumn: operationColumn || column,
1227
+ joinOperation,
1228
+ operation,
1229
+ splitByCategory,
1230
+ splitByCategoryLimit,
1231
+ splitByCategoryValues
1232
+ },
1233
+ opts: {
1234
+ abortController
1235
+ }
1236
+ }).then(res => {
1237
+ var _res$metadata2;
1238
+ return {
1239
+ rows: normalizeObjectKeys(res.rows),
1240
+ categories: (_res$metadata2 = res.metadata) == null ? void 0 : _res$metadata2.categories
1241
+ };
1242
+ });
1344
1243
  }
1345
- return baseUrl.toString();
1346
1244
  }
1347
-
1348
- const _excluded = ["accessToken", "connectionName", "cache"];
1349
- const SOURCE_DEFAULTS = {
1245
+ WidgetBaseSource.defaultProps = {
1246
+ apiVersion: ApiVersion.V3,
1350
1247
  apiBaseUrl: DEFAULT_API_BASE_URL,
1351
1248
  clientId: getClient(),
1352
- format: 'tilejson',
1353
- headers: {},
1354
- maxLengthURL: DEFAULT_MAX_LENGTH_URL
1249
+ filters: {},
1250
+ filtersLogicalOperator: 'and',
1251
+ geoColumn: DEFAULT_GEO_COLUMN
1355
1252
  };
1356
- async function baseSource(endpoint, options, urlParameters) {
1357
- const {
1358
- accessToken,
1359
- connectionName,
1360
- cache
1361
- } = options,
1362
- optionalOptions = _objectWithoutPropertiesLoose(options, _excluded);
1363
- const mergedOptions = _extends({}, SOURCE_DEFAULTS, {
1364
- accessToken,
1365
- connectionName,
1366
- endpoint
1367
- });
1368
- for (const key in optionalOptions) {
1369
- if (optionalOptions[key]) {
1370
- mergedOptions[key] = optionalOptions[key];
1371
- }
1372
- }
1373
- const baseUrl = buildSourceUrl(mergedOptions);
1374
- const {
1375
- clientId,
1376
- maxLengthURL,
1377
- format,
1378
- localCache
1379
- } = mergedOptions;
1380
- const headers = _extends({
1381
- Authorization: `Bearer ${options.accessToken}`
1382
- }, options.headers);
1383
- const parameters = _extends({
1384
- client: clientId
1385
- }, urlParameters);
1386
- const errorContext = {
1387
- requestType: 'Map instantiation',
1388
- connection: options.connectionName,
1389
- type: endpoint,
1390
- source: JSON.stringify(parameters, undefined, 2)
1391
- };
1392
- const mapInstantiation = await requestWithParameters({
1393
- baseUrl,
1394
- parameters,
1395
- headers,
1396
- errorContext,
1397
- maxLengthURL,
1398
- localCache
1399
- });
1400
- const dataUrl = mapInstantiation[format].url[0];
1401
- if (cache) {
1402
- cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
1403
- }
1404
- errorContext.requestType = 'Map data';
1405
- if (format === 'tilejson') {
1406
- const json = await requestWithParameters({
1407
- baseUrl: dataUrl,
1408
- headers,
1409
- errorContext,
1410
- maxLengthURL,
1411
- localCache
1253
+
1254
+ /**
1255
+ * Source for Widget API requests on a data source defined by a SQL query.
1256
+ *
1257
+ * Generally not intended to be constructed directly. Instead, call
1258
+ * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
1259
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
1260
+ * for use by widget implementations.
1261
+ *
1262
+ * Example:
1263
+ *
1264
+ * ```javascript
1265
+ * import { vectorQuerySource } from '@carto/api-client';
1266
+ *
1267
+ * const data = vectorQuerySource({
1268
+ * accessToken: '••••',
1269
+ * connectionName: 'carto_dw',
1270
+ * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
1271
+ * });
1272
+ *
1273
+ * const { widgetSource } = await data;
1274
+ * ```
1275
+ */
1276
+ class WidgetQuerySource extends WidgetBaseSource {
1277
+ getModelSource(owner) {
1278
+ return _extends({}, super._getModelSource(owner), {
1279
+ type: 'query',
1280
+ data: this.props.sqlQuery,
1281
+ queryParameters: this.props.queryParameters
1412
1282
  });
1413
- if (accessToken) {
1414
- json.accessToken = accessToken;
1415
- }
1416
- return json;
1417
1283
  }
1418
- return await requestWithParameters({
1419
- baseUrl: dataUrl,
1420
- headers,
1421
- errorContext,
1422
- maxLengthURL,
1423
- localCache
1424
- });
1425
1284
  }
1426
1285
 
1427
- // deck.gl
1428
- const boundaryQuerySource = async function boundaryQuerySource(options) {
1429
- const {
1430
- columns,
1431
- filters,
1432
- tilesetTableName,
1433
- propertiesSqlQuery,
1434
- queryParameters
1435
- } = options;
1436
- const urlParameters = {
1437
- tilesetTableName,
1438
- propertiesSqlQuery
1439
- };
1440
- if (columns) {
1441
- urlParameters.columns = columns.join(',');
1442
- }
1443
- if (filters) {
1444
- urlParameters.filters = filters;
1445
- }
1446
- if (queryParameters) {
1447
- urlParameters.queryParameters = queryParameters;
1448
- }
1449
- return baseSource('boundary', options, urlParameters);
1450
- };
1451
-
1452
- // deck.gl
1453
- const boundaryTableSource = async function boundaryTableSource(options) {
1454
- const {
1455
- filters,
1456
- tilesetTableName,
1457
- columns,
1458
- propertiesTableName
1459
- } = options;
1460
- const urlParameters = {
1461
- tilesetTableName,
1462
- propertiesTableName
1463
- };
1464
- if (columns) {
1465
- urlParameters.columns = columns.join(',');
1466
- }
1467
- if (filters) {
1468
- urlParameters.filters = filters;
1286
+ /**
1287
+ * Source for Widget API requests on a data source defined as a table.
1288
+ *
1289
+ * Generally not intended to be constructed directly. Instead, call
1290
+ * {@link vectorTableSource}, {@link h3TableSource}, or {@link quadbinTableSource},
1291
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
1292
+ * for use by widget implementations.
1293
+ *
1294
+ * Example:
1295
+ *
1296
+ * ```javascript
1297
+ * import { vectorTableSource } from '@carto/api-client';
1298
+ *
1299
+ * const data = vectorTableSource({
1300
+ * accessToken: '••••',
1301
+ * connectionName: 'carto_dw',
1302
+ * tableName: 'carto-demo-data.demo_tables.retail_stores'
1303
+ * });
1304
+ *
1305
+ * const { widgetSource } = await data;
1306
+ * ```
1307
+ */
1308
+ class WidgetTableSource extends WidgetBaseSource {
1309
+ getModelSource(owner) {
1310
+ return _extends({}, super._getModelSource(owner), {
1311
+ type: 'table',
1312
+ data: this.props.tableName
1313
+ });
1469
1314
  }
1470
- return baseSource('boundary', options, urlParameters);
1471
- };
1315
+ }
1472
1316
 
1473
1317
  const h3QuerySource = async function h3QuerySource(options) {
1474
1318
  const {
@@ -1495,11 +1339,7 @@ const h3QuerySource = async function h3QuerySource(options) {
1495
1339
  urlParameters.filters = filters;
1496
1340
  }
1497
1341
  return baseSource('query', options, urlParameters).then(result => _extends({}, result, {
1498
- widgetSource: new WidgetQuerySource(_extends({}, options, {
1499
- // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3'
1500
- spatialDataColumn,
1501
- spatialDataType: 'h3'
1502
- }))
1342
+ widgetSource: new WidgetQuerySource(options)
1503
1343
  }));
1504
1344
  };
1505
1345
 
@@ -1524,11 +1364,7 @@ const h3TableSource = async function h3TableSource(options) {
1524
1364
  urlParameters.filters = filters;
1525
1365
  }
1526
1366
  return baseSource('table', options, urlParameters).then(result => _extends({}, result, {
1527
- widgetSource: new WidgetTableSource(_extends({}, options, {
1528
- // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3'
1529
- spatialDataColumn,
1530
- spatialDataType: 'h3'
1531
- }))
1367
+ widgetSource: new WidgetTableSource(options)
1532
1368
  }));
1533
1369
  };
1534
1370
 
@@ -1583,11 +1419,7 @@ const quadbinQuerySource = async function quadbinQuerySource(options) {
1583
1419
  urlParameters.filters = filters;
1584
1420
  }
1585
1421
  return baseSource('query', options, urlParameters).then(result => _extends({}, result, {
1586
- widgetSource: new WidgetQuerySource(_extends({}, options, {
1587
- // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin'
1588
- spatialDataColumn,
1589
- spatialDataType: 'quadbin'
1590
- }))
1422
+ widgetSource: new WidgetQuerySource(options)
1591
1423
  }));
1592
1424
  };
1593
1425
 
@@ -1612,11 +1444,7 @@ const quadbinTableSource = async function quadbinTableSource(options) {
1612
1444
  urlParameters.filters = filters;
1613
1445
  }
1614
1446
  return baseSource('table', options, urlParameters).then(result => _extends({}, result, {
1615
- widgetSource: new WidgetTableSource(_extends({}, options, {
1616
- // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin'
1617
- spatialDataColumn,
1618
- spatialDataType: 'quadbin'
1619
- }))
1447
+ widgetSource: new WidgetTableSource(options)
1620
1448
  }));
1621
1449
  };
1622
1450
 
@@ -1638,7 +1466,8 @@ const vectorQuerySource = async function vectorQuerySource(options) {
1638
1466
  spatialDataColumn = 'geom',
1639
1467
  sqlQuery,
1640
1468
  tileResolution = DEFAULT_TILE_RESOLUTION,
1641
- queryParameters
1469
+ queryParameters,
1470
+ aggregationExp
1642
1471
  } = options;
1643
1472
  const urlParameters = {
1644
1473
  spatialDataColumn,
@@ -1655,10 +1484,11 @@ const vectorQuerySource = async function vectorQuerySource(options) {
1655
1484
  if (queryParameters) {
1656
1485
  urlParameters.queryParameters = queryParameters;
1657
1486
  }
1487
+ if (aggregationExp) {
1488
+ urlParameters.aggregationExp = aggregationExp;
1489
+ }
1658
1490
  return baseSource('query', options, urlParameters).then(result => _extends({}, result, {
1659
- widgetSource: new WidgetQuerySource(_extends({}, options, {
1660
- spatialDataType: 'geo'
1661
- }))
1491
+ widgetSource: new WidgetQuerySource(options)
1662
1492
  }));
1663
1493
  };
1664
1494
 
@@ -1668,7 +1498,8 @@ const vectorTableSource = async function vectorTableSource(options) {
1668
1498
  filters,
1669
1499
  spatialDataColumn = 'geom',
1670
1500
  tableName,
1671
- tileResolution = DEFAULT_TILE_RESOLUTION
1501
+ tileResolution = DEFAULT_TILE_RESOLUTION,
1502
+ aggregationExp
1672
1503
  } = options;
1673
1504
  const urlParameters = {
1674
1505
  name: tableName,
@@ -1682,10 +1513,11 @@ const vectorTableSource = async function vectorTableSource(options) {
1682
1513
  if (filters) {
1683
1514
  urlParameters.filters = filters;
1684
1515
  }
1516
+ if (aggregationExp) {
1517
+ urlParameters.aggregationExp = aggregationExp;
1518
+ }
1685
1519
  return baseSource('table', options, urlParameters).then(result => _extends({}, result, {
1686
- widgetSource: new WidgetTableSource(_extends({}, options, {
1687
- spatialDataType: 'geo'
1688
- }))
1520
+ widgetSource: new WidgetTableSource(options)
1689
1521
  }));
1690
1522
  };
1691
1523