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