Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sources): Move sources from @deck.gl/carto #28

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"repository": "github:CartoDB/carto-api-client",
"author": "Don McCurdy <[email protected]>",
"packageManager": "[email protected]",
"version": "0.3.1",
"version": "0.4.0-alpha.3",
"license": "MIT",
"publishConfig": {
"access": "public",
Expand Down Expand Up @@ -34,7 +34,7 @@
"dev": "concurrently \"yarn build:watch\" \"vite --config examples/vite.config.ts --open\"",
"test": "vitest run --typecheck",
"test:watch": "vitest watch --typecheck",
"coverage": "vitest run --coverage",
"coverage": "vitest run --coverage.enabled --coverage.all false",
"lint": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --check",
"format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --write",
"clean": "rimraf build/*",
Expand All @@ -52,7 +52,6 @@
"LICENSE.md"
],
"dependencies": {
"@deck.gl/carto": "^9.0.30",
"@turf/bbox-clip": "^7.1.0",
"@turf/bbox-polygon": "^7.1.0",
"@turf/helpers": "^7.1.0",
Expand All @@ -62,6 +61,7 @@
},
"devDependencies": {
"@deck.gl/aggregation-layers": "^9.0.30",
"@deck.gl/carto": "^9.0.30",
"@deck.gl/core": "^9.0.30",
"@deck.gl/extensions": "^9.0.30",
"@deck.gl/geo-layers": "^9.0.30",
Expand Down Expand Up @@ -98,5 +98,6 @@
"vite": "^5.2.10",
"vitest": "1.6.0",
"vue": "^3.4.27"
}
},
"stableVersion": "0.3.1"
}
88 changes: 88 additions & 0 deletions src/api/carto-api-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {MapType} from '../types';

export type APIRequestType =
| 'Map data'
| 'Map instantiation'
| 'Public map'
| 'Tile stats'
| 'SQL'
| 'Basemap style';

export type APIErrorContext = {
requestType: APIRequestType;
mapId?: string;
connection?: string;
source?: string;
type?: MapType;
};

/**
*
* Custom error for reported errors in CARTO Maps API.
* Provides useful debugging information in console and context for applications.
*
*/
export class CartoAPIError extends Error {
/** Source error from server */
error: Error;

/** Context (API call & parameters) in which error occured */
errorContext: APIErrorContext;

/** Response from server */
response?: Response;

/** JSON Response from server */
responseJson?: any;

constructor(
error: Error,
errorContext: APIErrorContext,
response?: Response,
responseJson?: any
) {
let responseString = 'Failed to connect';
if (response) {
responseString = 'Server returned: ';
if (response.status === 400) {
responseString += 'Bad request';
} else if (response.status === 401 || response.status === 403) {
responseString += 'Unauthorized access';
} else if (response.status === 404) {
responseString += 'Not found';
} else {
responseString += 'Error';
}

responseString += ` (${response.status}):`;
}
responseString += ` ${error.message || error}`;

let message = `${errorContext.requestType} API request failed`;
message += `\n${responseString}`;
for (const key of Object.keys(errorContext)) {
if (key === 'requestType') continue;
message += `\n${formatErrorKey(key)}: ${(errorContext as any)[key]}`;
}
message += '\n';

super(message);

this.name = 'CartoAPIError';
this.response = response;
this.responseJson = responseJson;
this.error = error;
this.errorContext = errorContext;
}
}

/**
* Converts camelCase to Camel Case
*/
function formatErrorKey(key: string) {
return key.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase());
}
84 changes: 84 additions & 0 deletions src/api/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {MapType} from '../types.js';

export type V3Endpoint = 'maps' | 'stats' | 'sql';

function joinPath(...args: string[]): string {
return args
.map((part) => (part.endsWith('/') ? part.slice(0, -1) : part))
.join('/');
}

function buildV3Path(
apiBaseUrl: string,
version: 'v3',
endpoint: V3Endpoint,
...rest: string[]
): string {
return joinPath(apiBaseUrl, version, endpoint, ...rest);
}

/** @internal Required by fetchMap(). */
export function buildPublicMapUrl({
apiBaseUrl,
cartoMapId,
}: {
apiBaseUrl: string;
cartoMapId: string;
}): string {
return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
}

/** @internal Required by fetchMap(). */
export function buildStatsUrl({
attribute,
apiBaseUrl,
connectionName,
source,
type,
}: {
attribute: string;
apiBaseUrl: string;
connectionName: string;
source: string;
type: MapType;
}): string {
if (type === 'query') {
return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
}

// type === 'table'
return buildV3Path(
apiBaseUrl,
'v3',
'stats',
connectionName,
source,
attribute
);
}

export function buildSourceUrl({
apiBaseUrl,
connectionName,
endpoint,
}: {
apiBaseUrl: string;
connectionName: string;
endpoint: MapType;
}): string {
return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
}

export function buildQueryUrl({
apiBaseUrl,
connectionName,
}: {
apiBaseUrl: string;
connectionName: string;
}): string {
return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
}
14 changes: 14 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

export {
CartoAPIError,
APIErrorContext,
APIRequestType,
} from './carto-api-error.js';
// Internal, but required for fetchMap().
export {buildPublicMapUrl, buildStatsUrl} from './endpoints.js';
export {query} from './query.js';
export type {QueryOptions} from './query.js';
export {requestWithParameters} from './request-with-parameters.js';
56 changes: 56 additions & 0 deletions src/api/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {SOURCE_DEFAULTS} from '../sources/index';
import type {
SourceOptions,
QuerySourceOptions,
QueryResult,
} from '../sources/types';
import {buildQueryUrl} from './endpoints';
import {requestWithParameters} from './request-with-parameters';
import {APIErrorContext} from './carto-api-error';

export type QueryOptions = SourceOptions &
Omit<QuerySourceOptions, 'spatialDataColumn'>;
type UrlParameters = {q: string; queryParameters?: string};

export const query = async function (
options: QueryOptions
): Promise<QueryResult> {
const {
apiBaseUrl = SOURCE_DEFAULTS.apiBaseUrl,
clientId = SOURCE_DEFAULTS.clientId,
maxLengthURL = SOURCE_DEFAULTS.maxLengthURL,
connectionName,
sqlQuery,
queryParameters,
} = options;
const urlParameters: UrlParameters = {q: sqlQuery};

if (queryParameters) {
urlParameters.queryParameters = JSON.stringify(queryParameters);
}

const baseUrl = buildQueryUrl({apiBaseUrl, connectionName});
const headers = {
Authorization: `Bearer ${options.accessToken}`,
...options.headers,
};
const parameters = {client: clientId, ...urlParameters};

const errorContext: APIErrorContext = {
requestType: 'SQL',
connection: options.connectionName,
type: 'query',
source: JSON.stringify(parameters, undefined, 2),
};
return await requestWithParameters<QueryResult>({
baseUrl,
parameters,
headers,
errorContext,
maxLengthURL,
});
};
Loading