@carto/api-client 0.4.2 → 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 (36) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/api-client.cjs +933 -925
  3. package/build/api-client.cjs.map +1 -1
  4. package/build/api-client.modern.js +873 -865
  5. package/build/api-client.modern.js.map +1 -1
  6. package/build/index.d.ts +1 -1
  7. package/build/sources/boundary-query-source.d.ts +2 -1
  8. package/build/sources/boundary-table-source.d.ts +2 -1
  9. package/build/sources/h3-query-source.d.ts +2 -1
  10. package/build/sources/h3-table-source.d.ts +2 -1
  11. package/build/sources/h3-tileset-source.d.ts +2 -1
  12. package/build/sources/index.d.ts +13 -14
  13. package/build/sources/quadbin-query-source.d.ts +2 -1
  14. package/build/sources/quadbin-table-source.d.ts +2 -1
  15. package/build/sources/quadbin-tileset-source.d.ts +2 -1
  16. package/build/sources/raster-source.d.ts +2 -1
  17. package/build/sources/types.d.ts +16 -0
  18. package/build/sources/vector-query-source.d.ts +2 -1
  19. package/build/sources/vector-table-source.d.ts +2 -1
  20. package/build/sources/vector-tileset-source.d.ts +2 -1
  21. package/package.json +1 -1
  22. package/src/index.ts +1 -36
  23. package/src/sources/boundary-query-source.ts +4 -2
  24. package/src/sources/boundary-table-source.ts +4 -2
  25. package/src/sources/h3-query-source.ts +3 -1
  26. package/src/sources/h3-table-source.ts +3 -1
  27. package/src/sources/h3-tileset-source.ts +4 -2
  28. package/src/sources/index.ts +54 -24
  29. package/src/sources/quadbin-query-source.ts +4 -1
  30. package/src/sources/quadbin-table-source.ts +4 -1
  31. package/src/sources/quadbin-tileset-source.ts +4 -2
  32. package/src/sources/raster-source.ts +4 -2
  33. package/src/sources/types.ts +18 -0
  34. package/src/sources/vector-query-source.ts +9 -1
  35. package/src/sources/vector-table-source.ts +10 -1
  36. package/src/sources/vector-tileset-source.ts +4 -2
@@ -393,926 +393,926 @@ const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
393
393
  */
394
394
  const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
395
395
 
396
- /**
397
- * Return more descriptive error from API
398
- * @internalRemarks Source: @carto/react-api
399
- */
400
- function dealWithApiError({
401
- response,
402
- data
396
+ // deck.gl
397
+ // SPDX-License-Identifier: MIT
398
+ // Copyright (c) vis.gl contributors
399
+ function joinPath(...args) {
400
+ return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
401
+ }
402
+ function buildV3Path(apiBaseUrl, version, endpoint, ...rest) {
403
+ return joinPath(apiBaseUrl, version, endpoint, ...rest);
404
+ }
405
+ /** @internal Required by fetchMap(). */
406
+ function buildPublicMapUrl({
407
+ apiBaseUrl,
408
+ cartoMapId
403
409
  }) {
404
- var _data$error, _data$error2;
405
- if (data.error === 'Column not found') {
406
- throw new InvalidColumnError(`${data.error} ${data.column_name}`);
407
- }
408
- if (typeof data.error === 'string' && (_data$error = data.error) != null && _data$error.includes('Missing columns')) {
409
- throw new InvalidColumnError(data.error);
410
- }
411
- switch (response.status) {
412
- case 401:
413
- throw new Error('Unauthorized access. Invalid credentials');
414
- case 403:
415
- throw new Error('Forbidden access to the requested data');
416
- default:
417
- const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify((data == null ? void 0 : data.hint) || ((_data$error2 = data.error) == null ? void 0 : _data$error2[0]));
418
- throw new Error(msg);
419
- }
410
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
420
411
  }
421
- /** @internalRemarks Source: @carto/react-api */
422
- async function makeCall({
423
- url,
424
- accessToken,
425
- opts
412
+ /** @internal Required by fetchMap(). */
413
+ function buildStatsUrl({
414
+ attribute,
415
+ apiBaseUrl,
416
+ connectionName,
417
+ source,
418
+ type
426
419
  }) {
427
- let response;
428
- let data;
429
- const isPost = (opts == null ? void 0 : opts.method) === 'POST';
430
- try {
431
- var _opts$abortController;
432
- response = await fetch(url.toString(), _extends({
433
- headers: _extends({
434
- Authorization: `Bearer ${accessToken}`
435
- }, isPost && {
436
- 'Content-Type': 'application/json'
437
- })
438
- }, isPost && {
439
- method: opts == null ? void 0 : opts.method,
440
- body: opts == null ? void 0 : opts.body
441
- }, {
442
- signal: opts == null || (_opts$abortController = opts.abortController) == null ? void 0 : _opts$abortController.signal
443
- }, opts == null ? void 0 : opts.otherOptions));
444
- data = await response.json();
445
- } catch (error) {
446
- if (error.name === 'AbortError') throw error;
447
- throw new Error(`Failed request: ${error}`);
448
- }
449
- if (!response.ok) {
450
- dealWithApiError({
451
- response,
452
- data
453
- });
420
+ if (type === 'query') {
421
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
454
422
  }
455
- return data;
423
+ // type === 'table'
424
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
425
+ }
426
+ function buildSourceUrl({
427
+ apiBaseUrl,
428
+ connectionName,
429
+ endpoint
430
+ }) {
431
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
432
+ }
433
+ function buildQueryUrl({
434
+ apiBaseUrl,
435
+ connectionName
436
+ }) {
437
+ return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
456
438
  }
457
439
 
458
- /** @internalRemarks Source: @carto/react-api */
459
- const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
460
- const {
461
- V3
462
- } = ApiVersion;
463
- const REQUEST_GET_MAX_URL_LENGTH = 2048;
440
+ // deck.gl
441
+ // SPDX-License-Identifier: MIT
442
+ // Copyright (c) vis.gl contributors
464
443
  /**
465
- * Execute a SQL model request.
466
- * @internalRemarks Source: @carto/react-api
444
+ *
445
+ * Custom error for reported errors in CARTO Maps API.
446
+ * Provides useful debugging information in console and context for applications.
447
+ *
467
448
  */
468
- function executeModel(props) {
469
- assert(props.source, 'executeModel: missing source');
470
- assert(props.model, 'executeModel: missing model');
471
- assert(props.params, 'executeModel: missing params');
472
- assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
473
- const {
474
- model,
475
- source,
476
- params,
477
- opts
478
- } = props;
479
- const {
480
- type,
481
- apiVersion,
482
- apiBaseUrl,
483
- accessToken,
484
- connectionName,
485
- clientId
486
- } = source;
487
- assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
488
- assert(accessToken, 'executeModel: missing accessToken');
489
- assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
490
- assert(type !== 'tileset', 'executeModel: Tilesets not supported');
491
- let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
492
- const {
493
- data,
494
- filters,
495
- filtersLogicalOperator = 'and',
496
- 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);
560
- }
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));
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);
607
527
  }
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
- }));
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);
656
557
  }
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]));
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
+ }
690
593
  }
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;
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);
733
604
  }
734
- return [];
735
605
  }
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]));
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];
632
+ }
808
633
  }
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
914
- };
915
687
 
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
- });
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(',');
945
703
  }
946
- }
947
-
948
- /**
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
- * ```
969
- */
970
- class WidgetTableSource extends WidgetBaseSource {
971
- getModelSource(owner) {
972
- return _extends({}, super._getModelSource(owner), {
973
- type: 'table',
974
- data: this.props.tableName
975
- });
704
+ if (filters) {
705
+ urlParameters.filters = filters;
976
706
  }
977
- }
707
+ if (queryParameters) {
708
+ urlParameters.queryParameters = queryParameters;
709
+ }
710
+ return baseSource('boundary', options, urlParameters);
711
+ };
978
712
 
979
713
  // deck.gl
980
- // SPDX-License-Identifier: MIT
981
- // Copyright (c) vis.gl contributors
982
- /**
983
- *
984
- * Custom error for reported errors in CARTO Maps API.
985
- * Provides useful debugging information in console and context for applications.
986
- *
987
- */
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]}`;
1010
- }
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;
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(',');
1026
727
  }
1027
- }
728
+ if (filters) {
729
+ urlParameters.filters = filters;
730
+ }
731
+ return baseSource('boundary', options, urlParameters);
732
+ };
733
+
1028
734
  /**
1029
- * Converts camelCase to Camel Case
735
+ * Return more descriptive error from API
736
+ * @internalRemarks Source: @carto/react-api
1030
737
  */
1031
- function formatErrorKey(key) {
1032
- return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
1033
- }
1034
-
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);
1050
- }
1051
- /** @internal Required by fetchMap(). */
1052
- function buildStatsUrl({
1053
- attribute,
1054
- apiBaseUrl,
1055
- connectionName,
1056
- source,
1057
- type
738
+ function dealWithApiError({
739
+ response,
740
+ data
1058
741
  }) {
1059
- if (type === 'query') {
1060
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
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);
1061
757
  }
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
758
  }
1072
- function buildQueryUrl({
1073
- apiBaseUrl,
1074
- connectionName
759
+ /** @internalRemarks Source: @carto/react-api */
760
+ async function makeCall({
761
+ url,
762
+ accessToken,
763
+ opts
1075
764
  }) {
1076
- return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
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
791
+ });
792
+ }
793
+ return data;
1077
794
  }
1078
795
 
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);
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;
802
+ /**
803
+ * Execute a SQL model request.
804
+ * @internalRemarks Source: @carto/react-api
805
+ */
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(', ')}`);
1103
811
  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);
812
+ model,
813
+ source,
814
+ params,
815
+ opts
816
+ } = props;
817
+ const {
818
+ type,
819
+ apiVersion,
820
+ apiBaseUrl,
821
+ accessToken,
822
+ connectionName,
823
+ clientId
824
+ } = source;
825
+ assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
826
+ assert(accessToken, 'executeModel: missing accessToken');
827
+ assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
828
+ assert(type !== 'tileset', 'executeModel: Tilesets not supported');
829
+ let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
830
+ const {
831
+ data,
832
+ filters,
833
+ filtersLogicalOperator = 'and',
834
+ geoColumn = DEFAULT_GEO_COLUMN
835
+ } = source;
836
+ const queryParameters = source.queryParameters ? JSON.stringify(source.queryParameters) : '';
837
+ const queryParams = {
838
+ type,
839
+ client: clientId,
840
+ source: data,
841
+ params: JSON.stringify(params),
842
+ queryParameters,
843
+ filters: JSON.stringify(filters),
844
+ filtersLogicalOperator
845
+ };
846
+ // Picking Model API requires 'spatialDataColumn'.
847
+ if (model === 'pick') {
848
+ queryParams.spatialDataColumn = geoColumn;
1110
849
  }
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);
1130
- }
1131
- return json;
1132
- }).catch(error => {
1133
- if (canStoreInCache) {
1134
- REQUEST_CACHE.delete(key);
850
+ // API supports multiple filters, we apply it only to geoColumn
851
+ const spatialFilters = source.spatialFilter ? {
852
+ [geoColumn]: source.spatialFilter
853
+ } : undefined;
854
+ if (spatialFilters) {
855
+ queryParams.spatialFilters = JSON.stringify(spatialFilters);
856
+ }
857
+ const urlWithSearchParams = url + '?' + new URLSearchParams(queryParams).toString();
858
+ const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
859
+ if (isGet) {
860
+ url = urlWithSearchParams;
861
+ } else {
862
+ // undo the JSON.stringify, @TODO find a better pattern
863
+ queryParams.params = params;
864
+ queryParams.filters = filters;
865
+ queryParams.queryParameters = source.queryParameters;
866
+ if (spatialFilters) {
867
+ queryParams.spatialFilters = spatialFilters;
1135
868
  }
1136
- throw new CartoAPIError(error, errorContext, response, responseJson);
1137
- });
1138
- if (canStoreInCache) {
1139
- REQUEST_CACHE.set(key, jsonPromise);
1140
869
  }
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
870
+ return makeCall({
871
+ url,
872
+ accessToken: source.accessToken,
873
+ opts: _extends({}, opts, {
874
+ method: isGet ? 'GET' : 'POST'
875
+ }, !isGet && {
876
+ body: JSON.stringify(queryParams)
877
+ })
1161
878
  });
1162
879
  }
880
+
881
+ const _excluded = ["filterOwner", "spatialFilter", "abortController"],
882
+ _excluded2 = ["filterOwner", "spatialFilter", "abortController"],
883
+ _excluded3 = ["filterOwner", "spatialFilter", "abortController", "operationExp"],
884
+ _excluded4 = ["filterOwner", "spatialFilter", "abortController"],
885
+ _excluded5 = ["filterOwner", "spatialFilter", "abortController"],
886
+ _excluded6 = ["filterOwner", "spatialFilter", "abortController"],
887
+ _excluded7 = ["filterOwner", "spatialFilter", "abortController"],
888
+ _excluded8 = ["filterOwner", "abortController", "spatialFilter"];
1163
889
  /**
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.
890
+ * Source for Widget API requests on a data source defined by a SQL query.
891
+ *
892
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1167
893
  */
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());
894
+ class WidgetBaseSource {
895
+ constructor(props) {
896
+ this.props = void 0;
897
+ this.props = _extends({}, WidgetBaseSource.defaultProps, props);
898
+ }
899
+ _getModelSource(owner) {
900
+ const props = this.props;
901
+ return {
902
+ apiVersion: props.apiVersion,
903
+ apiBaseUrl: props.apiBaseUrl,
904
+ clientId: props.clientId,
905
+ accessToken: props.accessToken,
906
+ connectionName: props.connectionName,
907
+ filters: getApplicableFilters(owner, props.filters),
908
+ filtersLogicalOperator: props.filtersLogicalOperator,
909
+ geoColumn: props.geoColumn
910
+ };
911
+ }
912
+ /****************************************************************************
913
+ * CATEGORIES
914
+ */
915
+ /**
916
+ * Returns a list of labeled datapoints for categorical data. Suitable for
917
+ * charts including grouped bar charts, pie charts, and tree charts.
918
+ */
919
+ async getCategories(options) {
920
+ const {
921
+ filterOwner,
922
+ spatialFilter,
923
+ abortController
924
+ } = options,
925
+ params = _objectWithoutPropertiesLoose(options, _excluded);
926
+ const {
927
+ column,
928
+ operation,
929
+ operationColumn
930
+ } = params;
931
+ return executeModel({
932
+ model: 'category',
933
+ source: _extends({}, this.getModelSource(filterOwner), {
934
+ spatialFilter
935
+ }),
936
+ params: {
937
+ column,
938
+ operation,
939
+ operationColumn: operationColumn || column
940
+ },
941
+ opts: {
942
+ abortController
943
+ }
944
+ }).then(res => normalizeObjectKeys(res.rows));
945
+ }
946
+ /****************************************************************************
947
+ * FEATURES
948
+ */
949
+ /**
950
+ * Given a list of feature IDs (as found in `_carto_feature_id`) returns all
951
+ * matching features. In datasets containing features with duplicate geometries,
952
+ * feature IDs may be duplicated (IDs are a hash of geometry) and so more
953
+ * results may be returned than IDs in the request.
954
+ * @internal
955
+ * @experimental
956
+ */
957
+ async getFeatures(options) {
958
+ const {
959
+ filterOwner,
960
+ spatialFilter,
961
+ abortController
962
+ } = options,
963
+ params = _objectWithoutPropertiesLoose(options, _excluded2);
964
+ const {
965
+ columns,
966
+ dataType,
967
+ featureIds,
968
+ z,
969
+ limit,
970
+ tileResolution
971
+ } = params;
972
+ return executeModel({
973
+ model: 'pick',
974
+ source: _extends({}, this.getModelSource(filterOwner), {
975
+ spatialFilter
976
+ }),
977
+ params: {
978
+ columns,
979
+ dataType,
980
+ featureIds,
981
+ z,
982
+ limit: limit || 1000,
983
+ tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
984
+ },
985
+ opts: {
986
+ abortController
987
+ }
988
+ // Avoid `normalizeObjectKeys()`, which changes column names.
989
+ }).then(({
990
+ rows
991
+ }) => ({
992
+ rows
993
+ }));
994
+ }
995
+ /****************************************************************************
996
+ * FORMULA
997
+ */
998
+ /**
999
+ * Returns a scalar numerical statistic over all matching data. Suitable
1000
+ * for 'headline' or 'scorecard' figures such as counts and sums.
1001
+ */
1002
+ async getFormula(options) {
1003
+ const {
1004
+ filterOwner,
1005
+ spatialFilter,
1006
+ abortController,
1007
+ operationExp
1008
+ } = options,
1009
+ params = _objectWithoutPropertiesLoose(options, _excluded3);
1010
+ const {
1011
+ column,
1012
+ operation
1013
+ } = params;
1014
+ return executeModel({
1015
+ model: 'formula',
1016
+ source: _extends({}, this.getModelSource(filterOwner), {
1017
+ spatialFilter
1018
+ }),
1019
+ params: {
1020
+ column: column != null ? column : '*',
1021
+ operation,
1022
+ operationExp
1023
+ },
1024
+ opts: {
1025
+ abortController
1026
+ }
1027
+ }).then(res => normalizeObjectKeys(res.rows[0]));
1028
+ }
1029
+ /****************************************************************************
1030
+ * HISTOGRAM
1031
+ */
1032
+ /**
1033
+ * Returns a list of labeled datapoints for 'bins' of data defined as ticks
1034
+ * over a numerical range. Suitable for histogram charts.
1035
+ */
1036
+ async getHistogram(options) {
1037
+ const {
1038
+ filterOwner,
1039
+ spatialFilter,
1040
+ abortController
1041
+ } = options,
1042
+ params = _objectWithoutPropertiesLoose(options, _excluded4);
1043
+ const {
1044
+ column,
1045
+ operation,
1046
+ ticks
1047
+ } = params;
1048
+ const data = await executeModel({
1049
+ model: 'histogram',
1050
+ source: _extends({}, this.getModelSource(filterOwner), {
1051
+ spatialFilter
1052
+ }),
1053
+ params: {
1054
+ column,
1055
+ operation,
1056
+ ticks
1057
+ },
1058
+ opts: {
1059
+ abortController
1060
+ }
1061
+ }).then(res => normalizeObjectKeys(res.rows));
1062
+ if (data.length) {
1063
+ // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
1064
+ // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
1065
+ const result = Array(ticks.length + 1).fill(0);
1066
+ data.forEach(({
1067
+ tick,
1068
+ value
1069
+ }) => result[tick] = value);
1070
+ return result;
1175
1071
  }
1072
+ return [];
1073
+ }
1074
+ /****************************************************************************
1075
+ * RANGE
1076
+ */
1077
+ /**
1078
+ * Returns a range (min and max) for a numerical column of matching rows.
1079
+ * Suitable for displaying certain 'headline' or 'scorecard' statistics,
1080
+ * or rendering a range slider UI for filtering.
1081
+ */
1082
+ async getRange(options) {
1083
+ const {
1084
+ filterOwner,
1085
+ spatialFilter,
1086
+ abortController
1087
+ } = options,
1088
+ params = _objectWithoutPropertiesLoose(options, _excluded5);
1089
+ const {
1090
+ column
1091
+ } = params;
1092
+ return executeModel({
1093
+ model: 'range',
1094
+ source: _extends({}, this.getModelSource(filterOwner), {
1095
+ spatialFilter
1096
+ }),
1097
+ params: {
1098
+ column
1099
+ },
1100
+ opts: {
1101
+ abortController
1102
+ }
1103
+ }).then(res => normalizeObjectKeys(res.rows[0]));
1104
+ }
1105
+ /****************************************************************************
1106
+ * SCATTER
1107
+ */
1108
+ /**
1109
+ * Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
1110
+ * values. Suitable for rendering scatter plots.
1111
+ */
1112
+ async getScatter(options) {
1113
+ const {
1114
+ filterOwner,
1115
+ spatialFilter,
1116
+ abortController
1117
+ } = options,
1118
+ params = _objectWithoutPropertiesLoose(options, _excluded6);
1119
+ const {
1120
+ xAxisColumn,
1121
+ xAxisJoinOperation,
1122
+ yAxisColumn,
1123
+ yAxisJoinOperation
1124
+ } = params;
1125
+ // Make sure this is sync with the same constant in cloud-native/maps-api
1126
+ const HARD_LIMIT = 500;
1127
+ return executeModel({
1128
+ model: 'scatterplot',
1129
+ source: _extends({}, this.getModelSource(filterOwner), {
1130
+ spatialFilter
1131
+ }),
1132
+ params: {
1133
+ xAxisColumn,
1134
+ xAxisJoinOperation,
1135
+ yAxisColumn,
1136
+ yAxisJoinOperation,
1137
+ limit: HARD_LIMIT
1138
+ },
1139
+ opts: {
1140
+ abortController
1141
+ }
1142
+ }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(({
1143
+ x,
1144
+ y
1145
+ }) => [x, y]));
1146
+ }
1147
+ /****************************************************************************
1148
+ * TABLE
1149
+ */
1150
+ /**
1151
+ * Returns a list of arbitrary data rows, with support for pagination and
1152
+ * sorting. Suitable for displaying tables and lists.
1153
+ */
1154
+ async getTable(options) {
1155
+ const {
1156
+ filterOwner,
1157
+ spatialFilter,
1158
+ abortController
1159
+ } = options,
1160
+ params = _objectWithoutPropertiesLoose(options, _excluded7);
1161
+ const {
1162
+ columns,
1163
+ sortBy,
1164
+ sortDirection,
1165
+ offset = 0,
1166
+ limit = 10
1167
+ } = params;
1168
+ return executeModel({
1169
+ model: 'table',
1170
+ source: _extends({}, this.getModelSource(filterOwner), {
1171
+ spatialFilter
1172
+ }),
1173
+ params: {
1174
+ column: columns,
1175
+ sortBy,
1176
+ sortDirection,
1177
+ limit,
1178
+ offset
1179
+ },
1180
+ opts: {
1181
+ abortController
1182
+ }
1183
+ }).then(res => {
1184
+ var _res$rows, _res$metadata$total, _res$metadata, _res$METADATA;
1185
+ return {
1186
+ // Avoid `normalizeObjectKeys()`, which changes column names.
1187
+ rows: (_res$rows = res.rows) != null ? _res$rows : res.ROWS,
1188
+ totalCount: (_res$metadata$total = (_res$metadata = res.metadata) == null ? void 0 : _res$metadata.total) != null ? _res$metadata$total : (_res$METADATA = res.METADATA) == null ? void 0 : _res$METADATA.TOTAL
1189
+ };
1190
+ });
1176
1191
  }
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
- }
1192
+ /****************************************************************************
1193
+ * TIME SERIES
1194
+ */
1195
+ /**
1196
+ * Returns a series of labeled numerical values, grouped into equally-sized
1197
+ * time intervals. Suitable for rendering time series charts.
1198
+ */
1199
+ async getTimeSeries(options) {
1200
+ const {
1201
+ filterOwner,
1202
+ abortController,
1203
+ spatialFilter
1204
+ } = options,
1205
+ params = _objectWithoutPropertiesLoose(options, _excluded8);
1206
+ const {
1207
+ column,
1208
+ operationColumn,
1209
+ joinOperation,
1210
+ operation,
1211
+ stepSize,
1212
+ stepMultiplier,
1213
+ splitByCategory,
1214
+ splitByCategoryLimit,
1215
+ splitByCategoryValues
1216
+ } = params;
1217
+ return executeModel({
1218
+ model: 'timeseries',
1219
+ source: _extends({}, this.getModelSource(filterOwner), {
1220
+ spatialFilter
1221
+ }),
1222
+ params: {
1223
+ column,
1224
+ stepSize,
1225
+ stepMultiplier,
1226
+ operationColumn: operationColumn || column,
1227
+ joinOperation,
1228
+ operation,
1229
+ splitByCategory,
1230
+ splitByCategoryLimit,
1231
+ splitByCategoryValues
1232
+ },
1233
+ opts: {
1234
+ abortController
1235
+ }
1236
+ }).then(res => {
1237
+ var _res$metadata2;
1238
+ return {
1239
+ rows: normalizeObjectKeys(res.rows),
1240
+ categories: (_res$metadata2 = res.metadata) == null ? void 0 : _res$metadata2.categories
1241
+ };
1242
+ });
1188
1243
  }
1189
- return baseUrl.toString();
1190
1244
  }
1191
-
1192
- const _excluded = ["accessToken", "connectionName", "cache"];
1193
- const SOURCE_DEFAULTS = {
1245
+ WidgetBaseSource.defaultProps = {
1246
+ apiVersion: ApiVersion.V3,
1194
1247
  apiBaseUrl: DEFAULT_API_BASE_URL,
1195
1248
  clientId: getClient(),
1196
- format: 'tilejson',
1197
- headers: {},
1198
- maxLengthURL: DEFAULT_MAX_LENGTH_URL
1249
+ filters: {},
1250
+ filtersLogicalOperator: 'and',
1251
+ geoColumn: DEFAULT_GEO_COLUMN
1199
1252
  };
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
1253
+
1254
+ /**
1255
+ * Source for Widget API requests on a data source defined by a SQL query.
1256
+ *
1257
+ * Generally not intended to be constructed directly. Instead, call
1258
+ * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
1259
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
1260
+ * for use by widget implementations.
1261
+ *
1262
+ * Example:
1263
+ *
1264
+ * ```javascript
1265
+ * import { vectorQuerySource } from '@carto/api-client';
1266
+ *
1267
+ * const data = vectorQuerySource({
1268
+ * accessToken: '••••',
1269
+ * connectionName: 'carto_dw',
1270
+ * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
1271
+ * });
1272
+ *
1273
+ * const { widgetSource } = await data;
1274
+ * ```
1275
+ */
1276
+ class WidgetQuerySource extends WidgetBaseSource {
1277
+ getModelSource(owner) {
1278
+ return _extends({}, super._getModelSource(owner), {
1279
+ type: 'query',
1280
+ data: this.props.sqlQuery,
1281
+ queryParameters: this.props.queryParameters
1256
1282
  });
1257
- if (accessToken) {
1258
- json.accessToken = accessToken;
1259
- }
1260
- return json;
1261
1283
  }
1262
- return await requestWithParameters({
1263
- baseUrl: dataUrl,
1264
- headers,
1265
- errorContext,
1266
- maxLengthURL,
1267
- localCache
1268
- });
1269
1284
  }
1270
1285
 
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;
1286
+ /**
1287
+ * Source for Widget API requests on a data source defined as a table.
1288
+ *
1289
+ * Generally not intended to be constructed directly. Instead, call
1290
+ * {@link vectorTableSource}, {@link h3TableSource}, or {@link quadbinTableSource},
1291
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
1292
+ * for use by widget implementations.
1293
+ *
1294
+ * Example:
1295
+ *
1296
+ * ```javascript
1297
+ * import { vectorTableSource } from '@carto/api-client';
1298
+ *
1299
+ * const data = vectorTableSource({
1300
+ * accessToken: '••••',
1301
+ * connectionName: 'carto_dw',
1302
+ * tableName: 'carto-demo-data.demo_tables.retail_stores'
1303
+ * });
1304
+ *
1305
+ * const { widgetSource } = await data;
1306
+ * ```
1307
+ */
1308
+ class WidgetTableSource extends WidgetBaseSource {
1309
+ getModelSource(owner) {
1310
+ return _extends({}, super._getModelSource(owner), {
1311
+ type: 'table',
1312
+ data: this.props.tableName
1313
+ });
1313
1314
  }
1314
- return baseSource('boundary', options, urlParameters);
1315
- };
1315
+ }
1316
1316
 
1317
1317
  const h3QuerySource = async function h3QuerySource(options) {
1318
1318
  const {
@@ -1466,7 +1466,8 @@ const vectorQuerySource = async function vectorQuerySource(options) {
1466
1466
  spatialDataColumn = 'geom',
1467
1467
  sqlQuery,
1468
1468
  tileResolution = DEFAULT_TILE_RESOLUTION,
1469
- queryParameters
1469
+ queryParameters,
1470
+ aggregationExp
1470
1471
  } = options;
1471
1472
  const urlParameters = {
1472
1473
  spatialDataColumn,
@@ -1483,6 +1484,9 @@ const vectorQuerySource = async function vectorQuerySource(options) {
1483
1484
  if (queryParameters) {
1484
1485
  urlParameters.queryParameters = queryParameters;
1485
1486
  }
1487
+ if (aggregationExp) {
1488
+ urlParameters.aggregationExp = aggregationExp;
1489
+ }
1486
1490
  return baseSource('query', options, urlParameters).then(result => _extends({}, result, {
1487
1491
  widgetSource: new WidgetQuerySource(options)
1488
1492
  }));
@@ -1494,7 +1498,8 @@ const vectorTableSource = async function vectorTableSource(options) {
1494
1498
  filters,
1495
1499
  spatialDataColumn = 'geom',
1496
1500
  tableName,
1497
- tileResolution = DEFAULT_TILE_RESOLUTION
1501
+ tileResolution = DEFAULT_TILE_RESOLUTION,
1502
+ aggregationExp
1498
1503
  } = options;
1499
1504
  const urlParameters = {
1500
1505
  name: tableName,
@@ -1508,6 +1513,9 @@ const vectorTableSource = async function vectorTableSource(options) {
1508
1513
  if (filters) {
1509
1514
  urlParameters.filters = filters;
1510
1515
  }
1516
+ if (aggregationExp) {
1517
+ urlParameters.aggregationExp = aggregationExp;
1518
+ }
1511
1519
  return baseSource('table', options, urlParameters).then(result => _extends({}, result, {
1512
1520
  widgetSource: new WidgetTableSource(options)
1513
1521
  }));