@carto/api-client 0.0.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@carto/api-client",
3
+ "version": "0.0.1-0",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "tag": "alpha"
7
+ },
8
+ "packageManager": "yarn@4.2.2",
9
+ "author": "Don McCurdy <donmccurdy@carto.com>",
10
+ "license": "UNLICENSED",
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "source": "src/index.ts",
14
+ "types": "./build/index.d.ts",
15
+ "main": "./build/api-client.cjs",
16
+ "module": "./build/api-client.modern.js",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./build/index.d.ts",
20
+ "require": "./build/api-client.cjs",
21
+ "default": "./build/api-client.modern.js"
22
+ }
23
+ },
24
+ "browserslist": [
25
+ "defaults",
26
+ "not IE 11",
27
+ "node >= 18"
28
+ ],
29
+ "scripts": {
30
+ "build": "microbundle --format cjs,modern --no-compress --define VERSION=$npm_package_version",
31
+ "build:watch": "microbundle watch --format cjs,modern --no-compress --define VERSION=$npm_package_version",
32
+ "dev": "concurrently \"yarn build:watch\" \"vite --config examples/vite.config.ts --open\"",
33
+ "test": "vitest run --typecheck",
34
+ "test:watch": "vitest watch --typecheck",
35
+ "coverage": "vitest run --coverage",
36
+ "lint": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --check",
37
+ "format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --write",
38
+ "clean": "rimraf build/*",
39
+ "version": "git add -u",
40
+ "prepack": "yarn clean && yarn build",
41
+ "prepublish": "yarn lint && yarn test",
42
+ "postpublish": "git push && git push --tags"
43
+ },
44
+ "files": [
45
+ "build",
46
+ "src",
47
+ "README.md"
48
+ ],
49
+ "devDependencies": {
50
+ "@deck.gl/aggregation-layers": "^9.0.14",
51
+ "@deck.gl/carto": "^9.0.14",
52
+ "@deck.gl/core": "^9.0.14",
53
+ "@deck.gl/extensions": "^9.0.14",
54
+ "@deck.gl/geo-layers": "^9.0.14",
55
+ "@deck.gl/layers": "^9.0.12",
56
+ "@deck.gl/mesh-layers": "^9.0.12",
57
+ "@deck.gl/react": "^9.0.17",
58
+ "@lit/react": "^1.0.5",
59
+ "@lit/task": "^1.0.1",
60
+ "@loaders.gl/core": "^4.2.1",
61
+ "@luma.gl/core": "^9.0.12",
62
+ "@luma.gl/engine": "^9.0.12",
63
+ "@sveltejs/vite-plugin-svelte": "^3.1.1",
64
+ "@types/json-schema": "^7.0.15",
65
+ "@types/react": "^18.3.3",
66
+ "@types/react-dom": "^18.3.0",
67
+ "@types/semver": "^7.5.8",
68
+ "@vitejs/plugin-vue": "^5.0.5",
69
+ "@vitest/coverage-istanbul": "^1.6.0",
70
+ "@webcomponents/webcomponentsjs": "^2.8.0",
71
+ "concurrently": "^8.2.2",
72
+ "echarts": "^5.5.0",
73
+ "lit": "^3.1.4",
74
+ "lit-analyzer": "^1.2.1",
75
+ "maplibre-gl": "^4.1.3",
76
+ "microbundle": "^0.15.1",
77
+ "prettier": "^2.6.2",
78
+ "react": "^18.3.1",
79
+ "react-dom": "^18.3.1",
80
+ "react-map-gl": "^7.1.7",
81
+ "rimraf": "^3.0.2",
82
+ "svelte": "^4.2.17",
83
+ "typescript": "~5.3.3",
84
+ "vite": "^5.2.10",
85
+ "vitest": "1.6.0",
86
+ "vue": "^3.4.27"
87
+ },
88
+ "stableVersion": "0.0.0"
89
+ }
package/src/client.ts ADDED
@@ -0,0 +1,17 @@
1
+ import {CLIENT_ID} from './constants.js';
2
+
3
+ /**
4
+ * Default client
5
+ * @internalRemarks Source: @carto/react-core
6
+ */
7
+ let client = CLIENT_ID;
8
+
9
+ /** @internalRemarks Source: @carto/react-core */
10
+ export function getClient() {
11
+ return client;
12
+ }
13
+
14
+ /** @internalRemarks Source: @carto/react-core */
15
+ export function setClient(c: string) {
16
+ client = c;
17
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Threshold to use GET requests, vs POST
3
+ * @internalRemarks Source: @carto/constants
4
+ * @internal
5
+ */
6
+ export const REQUEST_GET_MAX_URL_LENGTH = 2048;
7
+
8
+ /**
9
+ * @internalRemarks Source: @carto/constants
10
+ * @internal
11
+ */
12
+ export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
13
+
14
+ /**
15
+ * @internalRemarks Source: @carto/constants
16
+ * @internal
17
+ */
18
+ export const DEFAULT_CLIENT = 'deck-gl-carto';
19
+
20
+ /**
21
+ * @internalRemarks Source: @carto/react-api
22
+ * @internal
23
+ */
24
+ export const DEFAULT_GEO_COLUMN = 'geom';
@@ -0,0 +1,37 @@
1
+ export const CLIENT_ID = 'carto-api-client';
2
+
3
+ /** @internalRemarks Source: @carto/constants */
4
+ export enum MapType {
5
+ TABLE = 'table',
6
+ QUERY = 'query',
7
+ TILESET = 'tileset',
8
+ }
9
+
10
+ /** @internalRemarks Source: @carto/constants */
11
+ export enum ApiVersion {
12
+ V1 = 'v1',
13
+ V2 = 'v2',
14
+ V3 = 'v3',
15
+ }
16
+
17
+ /** @internalRemarks Source: @carto/react-core */
18
+ export enum GroupDateType {
19
+ YEARS = 'year',
20
+ MONTHS = 'month',
21
+ WEEKS = 'week',
22
+ DAYS = 'day',
23
+ HOURS = 'hour',
24
+ MINUTES = 'minute',
25
+ SECONDS = 'second',
26
+ }
27
+
28
+ /** @internalRemarks Source: @carto/react-api, @deck.gl/carto */
29
+ export enum FilterType {
30
+ IN = 'in',
31
+ /** [a, b] both are included. */
32
+ BETWEEN = 'between',
33
+ /** [a, b) a is included, b is not. */
34
+ CLOSED_OPEN = 'closed_open',
35
+ TIME = 'time',
36
+ STRING_SEARCH = 'stringSearch',
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './client.js';
2
+ export * from './constants.js';
3
+ export * from './models/index.js';
4
+ export * from './sources/index.js';
5
+ export * from './types.js';
@@ -0,0 +1,93 @@
1
+ import {$TODO} from '../types-internal.js';
2
+ import {Credentials} from '../types.js';
3
+ import {InvalidColumnError} from '../utils.js';
4
+
5
+ /** @internalRemarks Source: @carto/react-api */
6
+ export interface ModelRequestOptions {
7
+ method: 'GET' | 'POST';
8
+ abortController?: AbortController;
9
+ otherOptions?: Record<string, unknown>;
10
+ body?: string;
11
+ }
12
+
13
+ /**
14
+ * Return more descriptive error from API
15
+ * @internalRemarks Source: @carto/react-api
16
+ */
17
+ export function dealWithApiError({
18
+ response,
19
+ data,
20
+ }: {
21
+ response: Response;
22
+ data: $TODO;
23
+ }) {
24
+ if (data.error === 'Column not found') {
25
+ throw new InvalidColumnError(`${data.error} ${data.column_name}`);
26
+ }
27
+
28
+ if (data.error?.includes('Missing columns')) {
29
+ throw new InvalidColumnError(data.error);
30
+ }
31
+
32
+ switch (response.status) {
33
+ case 401:
34
+ throw new Error('Unauthorized access. Invalid credentials');
35
+ case 403:
36
+ throw new Error('Forbidden access to the requested data');
37
+ default:
38
+ const msg =
39
+ data && data.error && typeof data.error === 'string'
40
+ ? data.error
41
+ : JSON.stringify(data?.hint || data.error?.[0]);
42
+ throw new Error(msg);
43
+ }
44
+ }
45
+
46
+ /** @internalRemarks Source: @carto/react-api */
47
+ export function checkCredentials(credentials: Credentials) {
48
+ if (!credentials || !credentials.apiBaseUrl || !credentials.accessToken) {
49
+ throw new Error('Missing or bad credentials provided');
50
+ }
51
+ }
52
+
53
+ /** @internalRemarks Source: @carto/react-api */
54
+ export async function makeCall({
55
+ url,
56
+ credentials,
57
+ opts,
58
+ }: {
59
+ url: string;
60
+ credentials: Credentials;
61
+ opts: ModelRequestOptions;
62
+ }) {
63
+ let response;
64
+ let data;
65
+ const isPost = opts?.method === 'POST';
66
+ try {
67
+ response = await fetch(url.toString(), {
68
+ headers: {
69
+ Authorization: `Bearer ${credentials.accessToken}`,
70
+ ...(isPost ? {'Content-Type': 'application/json'} : {}),
71
+ },
72
+ ...(isPost
73
+ ? {
74
+ method: opts?.method,
75
+ body: opts?.body,
76
+ }
77
+ : {}),
78
+ signal: opts?.abortController?.signal,
79
+ ...opts?.otherOptions,
80
+ });
81
+ data = await response.json();
82
+ } catch (error) {
83
+ if ((error as Error).name === 'AbortError') throw error;
84
+
85
+ throw new Error(`Failed request: ${error}`);
86
+ }
87
+
88
+ if (!response.ok) {
89
+ dealWithApiError({response, data});
90
+ }
91
+
92
+ return data;
93
+ }
@@ -0,0 +1,3 @@
1
+ export {executeModel} from './model.js';
2
+ export type {Model} from './model.js';
3
+ export type {ModelRequestOptions} from './common.js';
@@ -0,0 +1,112 @@
1
+ import {getClient} from '../client';
2
+ import {ApiVersion, MapType} from '../constants';
3
+ import {
4
+ DEFAULT_GEO_COLUMN,
5
+ REQUEST_GET_MAX_URL_LENGTH,
6
+ } from '../constants-internal';
7
+ import {Source, SpatialFilter} from '../types';
8
+ import {$TODO} from '../types-internal';
9
+ import {assert} from '../utils';
10
+ import {ModelRequestOptions, checkCredentials, makeCall} from './common';
11
+
12
+ /** @internalRemarks Source: @carto/react-api */
13
+ const AVAILABLE_MODELS = [
14
+ 'category',
15
+ 'histogram',
16
+ 'formula',
17
+ 'timeseries',
18
+ 'range',
19
+ 'scatterplot',
20
+ 'table',
21
+ ] as const;
22
+
23
+ export type Model = (typeof AVAILABLE_MODELS)[number];
24
+
25
+ /**
26
+ * Execute a SQL model request.
27
+ * @internalRemarks Source: @carto/react-api
28
+ */
29
+ export function executeModel(props: {
30
+ model: Model;
31
+ source: Source;
32
+ params: Record<string, unknown>;
33
+ spatialFilter?: SpatialFilter;
34
+ opts?: Partial<ModelRequestOptions>;
35
+ }) {
36
+ assert(props.source, 'executeModel: missing source');
37
+ assert(props.model, 'executeModel: missing model');
38
+ assert(props.params, 'executeModel: missing params');
39
+
40
+ assert(
41
+ AVAILABLE_MODELS.indexOf(props.model) !== -1,
42
+ `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(
43
+ ', '
44
+ )}`
45
+ );
46
+
47
+ const {source, model, params, spatialFilter, opts} = props;
48
+
49
+ checkCredentials(source.credentials);
50
+
51
+ assert(
52
+ source.credentials.apiVersion === ApiVersion.V3,
53
+ 'SQL Model API is a feature only available in CARTO 3.'
54
+ );
55
+ assert(
56
+ source.type !== MapType.TILESET,
57
+ 'executeModel: Tileset not supported'
58
+ );
59
+
60
+ let url = `${source.credentials.apiBaseUrl}/v3/sql/${source.connection}/model/${model}`;
61
+
62
+ const {filters, filtersLogicalOperator = 'and', data, type} = source;
63
+ const queryParameters = source.queryParameters
64
+ ? JSON.stringify(source.queryParameters)
65
+ : '';
66
+
67
+ const queryParams: Record<string, string> = {
68
+ type,
69
+ client: getClient(),
70
+ source: data,
71
+ params: JSON.stringify(params),
72
+ queryParameters,
73
+ filters: JSON.stringify(filters),
74
+ filtersLogicalOperator,
75
+ };
76
+
77
+ // API supports multiple filters, we apply it only to geoColumn
78
+ const spatialFilters = spatialFilter
79
+ ? {
80
+ [source.geoColumn ? source.geoColumn : DEFAULT_GEO_COLUMN]:
81
+ spatialFilter,
82
+ }
83
+ : undefined;
84
+
85
+ if (spatialFilters) {
86
+ queryParams.spatialFilters = JSON.stringify(spatialFilters);
87
+ }
88
+
89
+ const urlWithSearchParams =
90
+ url + '?' + new URLSearchParams(queryParams).toString();
91
+ const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
92
+ if (isGet) {
93
+ url = urlWithSearchParams;
94
+ } else {
95
+ // undo the JSON.stringify, @TODO find a better pattern
96
+ queryParams.params = params as $TODO;
97
+ queryParams.filters = filters as $TODO;
98
+ queryParams.queryParameters = source.queryParameters as $TODO;
99
+ if (spatialFilters) {
100
+ queryParams.spatialFilters = spatialFilters as $TODO;
101
+ }
102
+ }
103
+ return makeCall({
104
+ url,
105
+ credentials: source.credentials,
106
+ opts: {
107
+ ...opts,
108
+ method: isGet ? 'GET' : 'POST',
109
+ ...(!isGet && {body: JSON.stringify(queryParams)}),
110
+ },
111
+ });
112
+ }
@@ -0,0 +1,5 @@
1
+ export * from './widget-base-source.js';
2
+ export * from './widget-query-source.js';
3
+ export * from './widget-table-source.js';
4
+ export * from './wrappers.js';
5
+ export * from './types.js';
@@ -0,0 +1,90 @@
1
+ import {
2
+ AggregationType,
3
+ SortColumnType,
4
+ SortDirection,
5
+ SpatialFilter,
6
+ } from '../types';
7
+
8
+ /******************************************************************************
9
+ * WIDGET API REQUESTS
10
+ */
11
+
12
+ interface BaseRequestOptions {
13
+ spatialFilter?: SpatialFilter;
14
+ abortController?: AbortController;
15
+ filterOwner?: string;
16
+ }
17
+
18
+ export interface FormulaRequestOptions extends BaseRequestOptions {
19
+ column: string;
20
+ operation?: AggregationType;
21
+ operationExp?: string;
22
+ }
23
+
24
+ export interface CategoryRequestOptions extends BaseRequestOptions {
25
+ column: string;
26
+ operation?: AggregationType;
27
+ operationColumn?: string;
28
+ }
29
+
30
+ export interface RangeRequestOptions extends BaseRequestOptions {
31
+ column: string;
32
+ }
33
+
34
+ export interface TableRequestOptions extends BaseRequestOptions {
35
+ columns: string[];
36
+ sortBy?: string;
37
+ sortDirection?: SortDirection;
38
+ sortByColumnType?: SortColumnType;
39
+ page?: number;
40
+ rowsPerPage?: number;
41
+ }
42
+
43
+ export interface ScatterRequestOptions extends BaseRequestOptions {
44
+ xAxisColumn: string;
45
+ xAxisJoinOperation?: AggregationType;
46
+ yAxisColumn: string;
47
+ yAxisJoinOperation?: AggregationType;
48
+ }
49
+
50
+ export interface TimeSeriesRequestOptions extends BaseRequestOptions {
51
+ column: string;
52
+ stepSize?: number;
53
+ stepMultiplier?: number;
54
+ operation?: AggregationType;
55
+ operationColumn?: string;
56
+ joinOperation?: AggregationType;
57
+ splitByCategory?: string;
58
+ splitByCategoryLimit?: number;
59
+ splitByCategoryValues?: string[];
60
+ }
61
+
62
+ export interface HistogramRequestOptions extends BaseRequestOptions {
63
+ column: string;
64
+ ticks: number[];
65
+ operation?: AggregationType;
66
+ }
67
+
68
+ /******************************************************************************
69
+ * WIDGET API RESPONSES
70
+ */
71
+
72
+ export type FormulaResponse = {value: number};
73
+
74
+ export type CategoryResponse = {name: string; value: number}[];
75
+
76
+ export type RangeResponse = {min: number; max: number};
77
+
78
+ export type TableResponse = {
79
+ totalCount: number;
80
+ rows: Record<string, number | string>[];
81
+ };
82
+
83
+ export type ScatterResponse = [number, number][];
84
+
85
+ export type TimeSeriesResponse = {
86
+ rows: {name: string; value: number}[];
87
+ categories: string[];
88
+ };
89
+
90
+ export type HistogramResponse = number[];