@carto/api-client 0.4.2 → 0.4.4

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