From baaac48fc2658c20e2e63394e7d485246c2c5d45 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 15:12:01 -0400 Subject: [PATCH 01/26] feat: migrate sources and requests from @deck.gl/carto --- package.json | 2 +- src/api/carto-api-error.ts | 72 ++++ src/api/endpoints.ts | 82 +++++ src/api/index.ts | 17 + src/api/query.ts | 56 ++++ src/api/request-with-parameters.ts | 134 ++++++++ src/api/types.ts | 301 +++++++++++++++++ src/constants.ts | 13 + src/global.d.ts | 3 + src/index.ts | 11 +- src/sources/base-source.ts | 99 ++++++ src/sources/boundary-query-source.ts | 53 +++ src/sources/boundary-table-source.ts | 41 +++ src/sources/h3-query-source.ts | 63 ++++ src/sources/h3-table-source.ts | 59 ++++ src/sources/h3-tileset-source.ts | 26 ++ src/sources/index.ts | 54 ++- src/sources/quadbin-query-source.ts | 64 ++++ src/sources/quadbin-table-source.ts | 60 ++++ src/sources/quadbin-tileset-source.ts | 26 ++ src/sources/raster-source.ts | 34 ++ src/sources/types.ts | 316 +++++++++++++----- src/sources/vector-query-source.ts | 65 ++++ src/sources/vector-table-source.ts | 59 ++++ src/sources/vector-tileset-source.ts | 26 ++ src/utils.ts | 8 + src/widget-sources/index.ts | 5 + src/widget-sources/types.ts | 105 ++++++ .../widget-base-source.ts | 2 +- .../widget-query-source.ts | 2 +- .../widget-table-source.ts | 2 +- src/{sources => widget-sources}/wrappers.ts | 22 +- yarn.lock | 6 +- 33 files changed, 1764 insertions(+), 124 deletions(-) create mode 100644 src/api/carto-api-error.ts create mode 100644 src/api/endpoints.ts create mode 100644 src/api/index.ts create mode 100644 src/api/query.ts create mode 100644 src/api/request-with-parameters.ts create mode 100644 src/api/types.ts create mode 100644 src/sources/base-source.ts create mode 100644 src/sources/boundary-query-source.ts create mode 100644 src/sources/boundary-table-source.ts create mode 100644 src/sources/h3-query-source.ts create mode 100644 src/sources/h3-table-source.ts create mode 100644 src/sources/h3-tileset-source.ts create mode 100644 src/sources/quadbin-query-source.ts create mode 100644 src/sources/quadbin-table-source.ts create mode 100644 src/sources/quadbin-tileset-source.ts create mode 100644 src/sources/raster-source.ts create mode 100644 src/sources/vector-query-source.ts create mode 100644 src/sources/vector-table-source.ts create mode 100644 src/sources/vector-tileset-source.ts create mode 100644 src/widget-sources/index.ts create mode 100644 src/widget-sources/types.ts rename src/{sources => widget-sources}/widget-base-source.ts (99%) rename src/{sources => widget-sources}/widget-query-source.ts (98%) rename src/{sources => widget-sources}/widget-table-source.ts (97%) rename src/{sources => widget-sources}/wrappers.ts (87%) diff --git a/package.json b/package.json index 61033c0..a627c59 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/api/carto-api-error.ts b/src/api/carto-api-error.ts new file mode 100644 index 0000000..0ed403a --- /dev/null +++ b/src/api/carto-api-error.ts @@ -0,0 +1,72 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import type {APIErrorContext} from './types'; + +/** + * + * 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; // eslint-disable-line no-continue + message += `\n${formatErrorKey(key)}: ${errorContext[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) { + return key.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase()); +} diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts new file mode 100644 index 0000000..ad91c15 --- /dev/null +++ b/src/api/endpoints.ts @@ -0,0 +1,82 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {MapType} from './types'; + +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); +} + +export function buildPublicMapUrl({ + apiBaseUrl, + cartoMapId, +}: { + apiBaseUrl: string; + cartoMapId: string; +}): string { + return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId); +} + +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'); +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..7afdd01 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,17 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +export {CartoAPIError} from './carto-api-error'; +export type { + APIErrorContext, + Format, + MapType, + RequestType, + QueryParameters, + Basemap, + MapLibreBasemap, + GoogleBasemap, +} from './types'; +export {query} from './query'; +export type {QueryOptions} from './query'; diff --git a/src/api/query.ts b/src/api/query.ts new file mode 100644 index 0000000..d32f1c6 --- /dev/null +++ b/src/api/query.ts @@ -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 './types'; + +export type QueryOptions = SourceOptions & + Omit; +type UrlParameters = {q: string; queryParameters?: string}; + +export const query = async function ( + options: QueryOptions +): Promise { + 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({ + baseUrl, + parameters, + headers, + errorContext, + maxLengthURL, + }); +}; diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts new file mode 100644 index 0000000..759891e --- /dev/null +++ b/src/api/request-with-parameters.ts @@ -0,0 +1,134 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {isPureObject} from '../utils'; +import {CartoAPIError} from './carto-api-error'; +import type {APIErrorContext} from './types'; +import {DEFAULT_MAX_LENGTH_URL, V3_MINOR_VERSION} from '../constants'; + +const DEFAULT_HEADERS = { + Accept: 'application/json', + 'Content-Type': 'application/json', +}; + +const REQUEST_CACHE = new Map>(); + +export async function requestWithParameters({ + baseUrl, + parameters = {}, + headers: customHeaders = {}, + errorContext, + maxLengthURL = DEFAULT_MAX_LENGTH_URL, +}: { + baseUrl: string; + parameters?: Record; + headers?: Record; + errorContext: APIErrorContext; + maxLengthURL?: number; +}): Promise { + // Parameters added to all requests issued with `requestWithParameters()`. + // These parameters override parameters already in the base URL, but not + // user-provided parameters. + parameters = { + v: V3_MINOR_VERSION, + clientId: DEFAULT_CLIENT, + deckglVersion: deck?.VERSION, + ...parameters, + }; + + baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters)); + const key = createCacheKey(baseUrl, parameters, customHeaders); + if (REQUEST_CACHE.has(key)) { + return REQUEST_CACHE.get(key) as Promise; + } + + const url = createURLWithParameters(baseUrl, parameters); + const headers = {...DEFAULT_HEADERS, ...customHeaders}; + + /* global fetch */ + const fetchPromise = + url.length > maxLengthURL + ? fetch(baseUrl, { + method: 'POST', + body: JSON.stringify(parameters), + headers, + }) + : fetch(url, {headers}); + + let response: Response | undefined; + let responseJson: unknown; + const jsonPromise: Promise = fetchPromise + .then((_response: Response) => { + response = _response; + return response.json(); + }) + .then((json: any) => { + responseJson = json; + if (!response || !response.ok) { + throw new Error(json.error); + } + return json; + }) + .catch((error: Error) => { + REQUEST_CACHE.delete(key); + throw new CartoAPIError(error, errorContext, response, responseJson); + }); + + REQUEST_CACHE.set(key, jsonPromise); + return jsonPromise; +} + +function createCacheKey( + baseUrl: string, + parameters: Record, + headers: Record +): string { + const parameterEntries = Object.entries(parameters).sort(([a], [b]) => + a > b ? 1 : -1 + ); + const headerEntries = Object.entries(headers).sort(([a], [b]) => + a > b ? 1 : -1 + ); + return JSON.stringify({ + baseUrl, + parameters: parameterEntries, + headers: headerEntries, + }); +} + +/** + * Appends query string parameters to a URL. Existing URL parameters are kept, + * unless there is a conflict, in which case the new parameters override + * those already in the URL. + */ +function createURLWithParameters( + baseUrlString: string, + parameters: Record +): string { + const baseUrl = new URL(baseUrlString); + for (const [key, value] of Object.entries(parameters)) { + if (isPureObject(value) || Array.isArray(value)) { + baseUrl.searchParams.set(key, JSON.stringify(value)); + } else { + baseUrl.searchParams.set( + key, + (value as string | boolean | number).toString() + ); + } + } + return baseUrl.toString(); +} + +/** + * Deletes query string parameters from a URL. + */ +function excludeURLParameters(baseUrlString: string, parameters: string[]) { + const baseUrl = new URL(baseUrlString); + for (const param of parameters) { + if (baseUrl.searchParams.has(param)) { + baseUrl.searchParams.delete(param); + } + } + return baseUrl.toString(); +} diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..7461b5e --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,301 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +export type Format = 'json' | 'geojson' | 'tilejson'; +export type MapType = 'boundary' | 'query' | 'table' | 'tileset' | 'raster'; +export type RequestType = + | 'Map data' + | 'Map instantiation' + | 'Public map' + | 'Tile stats' + | 'SQL' + | 'Basemap style'; +export type ScaleType = + | 'linear' + | 'ordinal' + | 'log' + | 'point' + | 'quantile' + | 'quantize' + | 'sqrt' + | 'custom' + | 'identity'; + +export type APIErrorContext = { + requestType: RequestType; + mapId?: string; + connection?: string; + source?: string; + type?: MapType; +}; + +export enum SchemaFieldType { + Number = 'number', + Bigint = 'bigint', + String = 'string', + Geometry = 'geometry', + Timestamp = 'timestamp', + Object = 'object', + Boolean = 'boolean', + Variant = 'variant', + Unknown = 'unknown', +} +export interface SchemaField { + name: string; + type: SchemaFieldType; // Field type in the CARTO stack, common for all providers +} + +export interface MapInstantiation extends MapInstantiationFormats { + nrows: number; + size?: number; + schema: SchemaField[]; +} + +type MapInstantiationFormats = Record< + Format, + { + url: string[]; + error?: any; + } +>; + +export type QueryParameterValue = + | string + | number + | boolean + | Array + | object; + +export type NamedQueryParameter = Record; + +export type PositionalQueryParameter = QueryParameterValue[]; + +export type QueryParameters = NamedQueryParameter | PositionalQueryParameter; +export type VisualChannelField = { + name: string; + type: string; + colorColumn?: string; +}; + +export interface Filters { + [column: string]: Filter; +} + +interface Filter { + [FilterTypes.In]?: number[]; + [FilterTypes.Between]?: number[][]; + [FilterTypes.ClosedOpen]?: number[][]; + [FilterTypes.Time]?: number[][]; + [FilterTypes.StringSearch]?: string[]; +} + +export enum FilterTypes { + In = 'in', + Between = 'between', // [a, b] both are included + ClosedOpen = 'closed_open', // [a, b) a is included, b is not + Time = 'time', + StringSearch = 'stringSearch', +} + +export type VisualChannels = { + colorField?: VisualChannelField; + colorScale?: ScaleType; + + customMarkersField?: VisualChannelField; + customMarkersScale?: ScaleType; + + radiusField?: VisualChannelField; + radiusScale?: ScaleType; + + rotationScale?: ScaleType; + rotationField?: VisualChannelField; + + sizeField?: VisualChannelField; + sizeScale?: ScaleType; + + strokeColorField?: VisualChannelField; + strokeColorScale?: ScaleType; + + heightField?: VisualChannelField; + heightScale?: ScaleType; + + weightField?: VisualChannelField; +}; + +export type ColorRange = { + category: string; + colors: string[]; + colorMap: string[][] | undefined; + name: string; + type: string; +}; + +export type CustomMarkersRange = { + markerMap: { + value: string; + markerUrl?: string; + }[]; + othersMarker?: string; +}; + +export type VisConfig = { + filled?: boolean; + opacity?: number; + enable3d?: boolean; + + colorAggregation?: any; + colorRange: ColorRange; + + customMarkers?: boolean; + customMarkersRange?: CustomMarkersRange | null; + customMarkersUrl?: string | null; + + radius: number; + radiusRange?: number[]; + + sizeAggregation?: any; + sizeRange?: any; + + strokeColorAggregation?: any; + strokeOpacity?: number; + strokeColorRange?: ColorRange; + + heightRange?: any; + heightAggregation?: any; + + weightAggregation?: any; +}; + +export type TextLabel = { + field: VisualChannelField | null | undefined; + alignment?: 'center' | 'bottom' | 'top'; + anchor?: 'middle' | 'start' | 'end'; + size: number; + color?: number[]; + offset?: [number, number]; + outlineColor?: number[]; +}; + +export type MapLayerConfig = { + columns?: Record; + color?: number[]; + label?: string; + dataId: string; + textLabel: TextLabel[]; + visConfig: VisConfig; +}; + +export type MapTextSubLayerConfig = Omit & { + textLabel?: TextLabel; +}; + +export type MapConfigLayer = { + type: string; + id: string; + config: MapLayerConfig; + visualChannels: VisualChannels; +}; + +export type MapDataset = { + id: string; + data: any; + aggregationExp: string | null; + aggregationResLevel: number | null; + geoColumn: string; +}; + +export interface CustomStyle { + url?: string; + style?: any; + customAttribution?: string; +} + +export type KeplerMapConfig = { + mapState: any; + mapStyle: { + styleType: string; + visibleLayerGroups: Record; + }; + visState: { + layers: MapConfigLayer[]; + }; + layerBlending: any; + interactionConfig: any; + customBaseMaps?: { + customStyle?: CustomStyle; + }; +}; + +export type BasemapType = 'maplibre' | 'google-maps'; + +export type Basemap = MapLibreBasemap | GoogleBasemap; + +export type BasemapCommon = { + /** + * Type of basemap. + */ + type: BasemapType; + + /** + * Custom attribution for style data if not provided by style definition. + */ + attribution?: string; + + /** + * Properties of the basemap. These properties are specific to the basemap type. + */ + props: Record; +}; + +export type MapLibreBasemap = BasemapCommon & { + type: 'maplibre'; + + /** + * MapLibre map properties. + * + * Meant to be passed to directly to `maplibregl.Map` object. + */ + props: MapLibreBasemapProps; + + /** + * Layer groups to be displayed in the basemap. + */ + visibleLayerGroups?: Record; + + /** + * If `style` has been filtered by `visibleLayerGroups` then this property contains original style object, so user + * can use `applyLayerGroupFilters` again with new settings. + */ + rawStyle?: string | Record; +}; + +// Cherry-pick of maplibregl Map API props that are supported/provided by fetchMap interface +export type MapLibreBasemapProps = { + style: string | Record; + center: [number, number]; + zoom: number; + pitch?: number; + bearing?: number; +}; + +export type GoogleBasemap = BasemapCommon & { + type: 'google-maps'; + + /** + * Google map properties. + * + * Meant to be passed to directly to `google.maps.Map` object. + */ + props: GoogleBasemapProps; +}; + +// Cherry-pick of Google Map API props that are supported/provided by fetchMap interface +export type GoogleBasemapProps = { + mapTypeId: string; + mapId?: string; + center?: {lat: number; lng: number}; + zoom?: number; + tilt?: number; + heading?: number; +}; diff --git a/src/constants.ts b/src/constants.ts index a64d55c..84610e0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,19 @@ /** Current version of @carto/api-client. */ export const API_CLIENT_VERSION = __CARTO_API_CLIENT_VERSION; +export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; +export const DEFAULT_CLIENT = 'deck-gl-carto'; +export const V3_MINOR_VERSION = '3.4'; + +// Fastly default limit is 8192; leave some padding. +export const DEFAULT_MAX_LENGTH_URL = 7000; + +export const DEFAULT_TILE_SIZE = 512; +export const DEFAULT_TILE_RESOLUTION = 0.5; + +export const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4; +export const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6; + /** * Defines a comparator used when matching a column's values against given filter values. * diff --git a/src/global.d.ts b/src/global.d.ts index 3d985d1..f523574 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -5,3 +5,6 @@ * ``` */ declare const __CARTO_API_CLIENT_VERSION: string; + +/** Defined by @deck.gl/core. */ +declare const deck: {VERSION: string | undefined} | undefined; diff --git a/src/index.ts b/src/index.ts index 62db2e6..8f3d958 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,5 +2,14 @@ export * from './client.js'; export * from './constants.js'; export * from './filters.js'; export * from './geo.js'; -export * from './sources/index.js'; +export * from './widget-sources/index.js'; export * from './types.js'; + +export { + APIErrorContext, + Format, // TODO: Move to `types.ts`? + MapType, // TODO: De-duplicate? + RequestType, // TODO: Move to `types.ts`? + QueryParameters, // TODO: Move to `types.ts`? + QueryOptions, // TODO: Move to `types.ts`? +} from './api/index.js'; diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts new file mode 100644 index 0000000..39c9993 --- /dev/null +++ b/src/sources/base-source.ts @@ -0,0 +1,99 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import { + DEFAULT_API_BASE_URL, + DEFAULT_CLIENT, + DEFAULT_MAX_LENGTH_URL, +} from '../constants'; +import {buildSourceUrl} from '../api/endpoints'; +import {requestWithParameters} from '../api/request-with-parameters'; +import type {APIErrorContext, MapType} from '../api/types'; +import type { + GeojsonResult, + JsonResult, + SourceOptionalOptions, + SourceRequiredOptions, + TilejsonMapInstantiation, + TilejsonResult, +} from './types'; + +export const SOURCE_DEFAULTS: SourceOptionalOptions = { + apiBaseUrl: DEFAULT_API_BASE_URL, + clientId: DEFAULT_CLIENT, + format: 'tilejson', + headers: {}, + maxLengthURL: DEFAULT_MAX_LENGTH_URL, +}; + +export async function baseSource>( + endpoint: MapType, + options: Partial & SourceRequiredOptions, + urlParameters: UrlParameters +): Promise { + const {accessToken, connectionName, cache, ...optionalOptions} = options; + const mergedOptions = { + ...SOURCE_DEFAULTS, + accessToken, + connectionName, + endpoint, + }; + for (const key in optionalOptions) { + if (optionalOptions[key]) { + mergedOptions[key] = optionalOptions[key]; + } + } + const baseUrl = buildSourceUrl(mergedOptions); + const {clientId, maxLengthURL, format} = mergedOptions; + const headers = { + Authorization: `Bearer ${options.accessToken}`, + ...options.headers, + }; + const parameters = {client: clientId, ...urlParameters}; + + const errorContext: APIErrorContext = { + requestType: 'Map instantiation', + connection: options.connectionName, + type: endpoint, + source: JSON.stringify(parameters, undefined, 2), + }; + const mapInstantiation = + await requestWithParameters({ + baseUrl, + parameters, + headers, + errorContext, + maxLengthURL, + }); + + const dataUrl = mapInstantiation[format].url[0]; + if (cache) { + cache.value = parseInt( + new URL(dataUrl).searchParams.get('cache') || '', + 10 + ); + } + errorContext.requestType = 'Map data'; + + if (format === 'tilejson') { + const json = await requestWithParameters({ + baseUrl: dataUrl, + headers, + errorContext, + maxLengthURL, + }); + if (accessToken) { + json.accessToken = accessToken; + } + return json; + } + + return await requestWithParameters({ + baseUrl: dataUrl, + headers, + errorContext, + maxLengthURL, + }); +} diff --git a/src/sources/boundary-query-source.ts b/src/sources/boundary-query-source.ts new file mode 100644 index 0000000..01c23fc --- /dev/null +++ b/src/sources/boundary-query-source.ts @@ -0,0 +1,53 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {QueryParameters} from '../api/index'; +import {baseSource} from './base-source'; +import type {FilterOptions, SourceOptions, TilejsonResult} from './types'; + +export type BoundaryQuerySourceOptions = SourceOptions & + FilterOptions & { + columns?: string[]; + tilesetTableName: string; + propertiesSqlQuery: string; + queryParameters?: QueryParameters; + }; +type UrlParameters = { + columns?: string; + filters?: Record; + tilesetTableName: string; + propertiesSqlQuery: string; + queryParameters?: Record | unknown[]; +}; + +export const boundaryQuerySource = async function ( + options: BoundaryQuerySourceOptions +): Promise { + const { + columns, + filters, + tilesetTableName, + propertiesSqlQuery, + queryParameters, + } = options; + const urlParameters: UrlParameters = { + tilesetTableName, + propertiesSqlQuery, + }; + + if (columns) { + urlParameters.columns = columns.join(','); + } + if (filters) { + urlParameters.filters = filters; + } + if (queryParameters) { + urlParameters.queryParameters = queryParameters; + } + return baseSource( + 'boundary', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/boundary-table-source.ts b/src/sources/boundary-table-source.ts new file mode 100644 index 0000000..05d3dae --- /dev/null +++ b/src/sources/boundary-table-source.ts @@ -0,0 +1,41 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {baseSource} from './base-source'; +import type {FilterOptions, SourceOptions, TilejsonResult} from './types'; + +export type BoundaryTableSourceOptions = SourceOptions & + FilterOptions & { + tilesetTableName: string; + columns?: string[]; + propertiesTableName: string; + }; +type UrlParameters = { + filters?: Record; + tilesetTableName: string; + columns?: string; + propertiesTableName: string; +}; + +export const boundaryTableSource = async function ( + options: BoundaryTableSourceOptions +): Promise { + const {filters, tilesetTableName, columns, propertiesTableName} = options; + const urlParameters: UrlParameters = { + tilesetTableName, + propertiesTableName, + }; + + if (columns) { + urlParameters.columns = columns.join(','); + } + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'boundary', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/h3-query-source.ts b/src/sources/h3-query-source.ts new file mode 100644 index 0000000..7fafd8f --- /dev/null +++ b/src/sources/h3-query-source.ts @@ -0,0 +1,63 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants'; +import {baseSource} from './base-source'; +import type { + AggregationOptions, + FilterOptions, + QuerySourceOptions, + SourceOptions, + SpatialDataType, + TilejsonResult, +} from './types'; + +export type H3QuerySourceOptions = SourceOptions & + QuerySourceOptions & + AggregationOptions & + FilterOptions; +type UrlParameters = { + aggregationExp: string; + aggregationResLevel?: string; + spatialDataType: SpatialDataType; + spatialDataColumn?: string; + q: string; + queryParameters?: Record | unknown[]; + filters?: Record; +}; + +export const h3QuerySource = async function ( + options: H3QuerySourceOptions +): Promise { + const { + aggregationExp, + aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3, + sqlQuery, + spatialDataColumn = 'h3', + queryParameters, + filters, + } = options; + const urlParameters: UrlParameters = { + aggregationExp, + spatialDataColumn, + spatialDataType: 'h3', + q: sqlQuery, + }; + + if (aggregationResLevel) { + urlParameters.aggregationResLevel = String(aggregationResLevel); + } + if (queryParameters) { + urlParameters.queryParameters = queryParameters; + } + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'query', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/h3-table-source.ts b/src/sources/h3-table-source.ts new file mode 100644 index 0000000..f1659c3 --- /dev/null +++ b/src/sources/h3-table-source.ts @@ -0,0 +1,59 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants'; +import {baseSource} from './base-source'; +import type { + AggregationOptions, + FilterOptions, + SourceOptions, + SpatialDataType, + TableSourceOptions, + TilejsonResult, +} from './types'; + +export type H3TableSourceOptions = SourceOptions & + TableSourceOptions & + AggregationOptions & + FilterOptions; + +type UrlParameters = { + aggregationExp: string; + aggregationResLevel?: string; + spatialDataType: SpatialDataType; + spatialDataColumn?: string; + name: string; + filters?: Record; +}; + +export const h3TableSource = async function ( + options: H3TableSourceOptions +): Promise { + const { + aggregationExp, + aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3, + spatialDataColumn = 'h3', + tableName, + filters, + } = options; + const urlParameters: UrlParameters = { + aggregationExp, + name: tableName, + spatialDataColumn, + spatialDataType: 'h3', + }; + + if (aggregationResLevel) { + urlParameters.aggregationResLevel = String(aggregationResLevel); + } + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'table', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/h3-tileset-source.ts b/src/sources/h3-tileset-source.ts new file mode 100644 index 0000000..ac1752c --- /dev/null +++ b/src/sources/h3-tileset-source.ts @@ -0,0 +1,26 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {baseSource} from './base-source'; +import type { + SourceOptions, + TilejsonResult, + TilesetSourceOptions, +} from './types'; + +export type H3TilesetSourceOptions = SourceOptions & TilesetSourceOptions; +type UrlParameters = {name: string}; + +export const h3TilesetSource = async function ( + options: H3TilesetSourceOptions +): Promise { + const {tableName} = options; + const urlParameters: UrlParameters = {name: tableName}; + + return baseSource( + 'tileset', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/index.ts b/src/sources/index.ts index 7bf51ee..5d98b2b 100644 --- a/src/sources/index.ts +++ b/src/sources/index.ts @@ -1,5 +1,49 @@ -export * from './widget-base-source.js'; -export * from './widget-query-source.js'; -export * from './widget-table-source.js'; -export * from './wrappers.js'; -export * from './types.js'; +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +export {SOURCE_DEFAULTS} from './base-source'; +export type {TilejsonResult, GeojsonResult, JsonResult} from './types'; + +export {boundaryQuerySource} from './boundary-query-source'; +export type {BoundaryQuerySourceOptions} from './boundary-query-source'; + +export {boundaryTableSource} from './boundary-table-source'; +export type {BoundaryTableSourceOptions} from './boundary-table-source'; + +export {h3QuerySource} from './h3-query-source'; +export type {H3QuerySourceOptions} from './h3-query-source'; + +export {h3TableSource} from './h3-table-source'; +export type {H3TableSourceOptions} from './h3-table-source'; + +export {h3TilesetSource} from './h3-tileset-source'; +export type {H3TilesetSourceOptions} from './h3-tileset-source'; + +export {rasterSource} from './raster-source'; +export type {RasterSourceOptions} from './raster-source'; + +export {quadbinQuerySource} from './quadbin-query-source'; +export type {QuadbinQuerySourceOptions} from './quadbin-query-source'; + +export {quadbinTableSource} from './quadbin-table-source'; +export type {QuadbinTableSourceOptions} from './quadbin-table-source'; + +export {quadbinTilesetSource} from './quadbin-tileset-source'; +export type {QuadbinTilesetSourceOptions} from './quadbin-tileset-source'; + +export {vectorQuerySource} from './vector-query-source'; +export type {VectorQuerySourceOptions} from './vector-query-source'; + +export {vectorTableSource} from './vector-table-source'; +export type {VectorTableSourceOptions} from './vector-table-source'; + +export {vectorTilesetSource} from './vector-tileset-source'; +export type {VectorTilesetSourceOptions} from './vector-tileset-source'; + +export type { + SourceOptions, + QuerySourceOptions, + TableSourceOptions, + TilesetSourceOptions, +} from './types'; diff --git a/src/sources/quadbin-query-source.ts b/src/sources/quadbin-query-source.ts new file mode 100644 index 0000000..77c1013 --- /dev/null +++ b/src/sources/quadbin-query-source.ts @@ -0,0 +1,64 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants'; +import {baseSource} from './base-source'; +import type { + AggregationOptions, + FilterOptions, + QuerySourceOptions, + SourceOptions, + SpatialDataType, + TilejsonResult, +} from './types'; + +export type QuadbinQuerySourceOptions = SourceOptions & + QuerySourceOptions & + AggregationOptions & + FilterOptions; + +type UrlParameters = { + aggregationExp: string; + aggregationResLevel?: string; + spatialDataType: SpatialDataType; + spatialDataColumn?: string; + q: string; + queryParameters?: Record | unknown[]; + filters?: Record; +}; + +export const quadbinQuerySource = async function ( + options: QuadbinQuerySourceOptions +): Promise { + const { + aggregationExp, + aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN, + sqlQuery, + spatialDataColumn = 'quadbin', + queryParameters, + filters, + } = options; + const urlParameters: UrlParameters = { + aggregationExp, + q: sqlQuery, + spatialDataColumn, + spatialDataType: 'quadbin', + }; + + if (aggregationResLevel) { + urlParameters.aggregationResLevel = String(aggregationResLevel); + } + if (queryParameters) { + urlParameters.queryParameters = queryParameters; + } + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'query', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts new file mode 100644 index 0000000..d1c4163 --- /dev/null +++ b/src/sources/quadbin-table-source.ts @@ -0,0 +1,60 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants'; +import {baseSource} from './base-source'; +import type { + AggregationOptions, + FilterOptions, + SourceOptions, + SpatialDataType, + TableSourceOptions, + TilejsonResult, +} from './types'; + +export type QuadbinTableSourceOptions = SourceOptions & + TableSourceOptions & + AggregationOptions & + FilterOptions; + +type UrlParameters = { + aggregationExp: string; + aggregationResLevel?: string; + spatialDataType: SpatialDataType; + spatialDataColumn?: string; + name: string; + filters?: Record; +}; + +export const quadbinTableSource = async function ( + options: QuadbinTableSourceOptions +): Promise { + const { + aggregationExp, + aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN, + spatialDataColumn = 'quadbin', + tableName, + filters, + } = options; + + const urlParameters: UrlParameters = { + aggregationExp, + name: tableName, + spatialDataColumn, + spatialDataType: 'quadbin', + }; + + if (aggregationResLevel) { + urlParameters.aggregationResLevel = String(aggregationResLevel); + } + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'table', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/quadbin-tileset-source.ts b/src/sources/quadbin-tileset-source.ts new file mode 100644 index 0000000..db188df --- /dev/null +++ b/src/sources/quadbin-tileset-source.ts @@ -0,0 +1,26 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {baseSource} from './base-source'; +import type { + SourceOptions, + TilejsonResult, + TilesetSourceOptions, +} from './types'; + +export type QuadbinTilesetSourceOptions = SourceOptions & TilesetSourceOptions; +type UrlParameters = {name: string}; + +export const quadbinTilesetSource = async function ( + options: QuadbinTilesetSourceOptions +): Promise { + const {tableName} = options; + const urlParameters: UrlParameters = {name: tableName}; + + return baseSource( + 'tileset', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/raster-source.ts b/src/sources/raster-source.ts new file mode 100644 index 0000000..565ead4 --- /dev/null +++ b/src/sources/raster-source.ts @@ -0,0 +1,34 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {baseSource} from './base-source'; +import type { + FilterOptions, + SourceOptions, + TilejsonResult, + TilesetSourceOptions, +} from './types'; + +export type RasterSourceOptions = SourceOptions & + TilesetSourceOptions & + FilterOptions; +type UrlParameters = { + name: string; + filters?: Record; +}; + +export const rasterSource = async function ( + options: RasterSourceOptions +): Promise { + const {tableName, filters} = options; + const urlParameters: UrlParameters = {name: tableName}; + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'raster', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/types.ts b/src/sources/types.ts index 05b035c..32d28e9 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -1,105 +1,241 @@ -import { - GroupDateType, - SortColumnType, - SortDirection, - SpatialFilter, -} from '../types'; - -/****************************************************************************** - * WIDGET API REQUESTS - */ - -/** Common options for {@link WidgetBaseSource} requests. */ -interface BaseRequestOptions { - spatialFilter?: SpatialFilter; - abortController?: AbortController; - filterOwner?: string; -} +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import type {Feature} from 'geojson'; +import type { + Filters, + Format, + MapInstantiation, + QueryParameters, +} from '../api/types'; + +export type SourceRequiredOptions = { + /** Carto platform access token. */ + accessToken: string; + + /** Data warehouse connection name in Carto platform. */ + connectionName: string; +}; -/** Options for {@link WidgetBaseSource#getCategories}. */ -export interface CategoryRequestOptions extends BaseRequestOptions { - column: string; - operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; - operationColumn?: string; -} +export type SourceOptionalOptions = { + /** + * Base URL of the CARTO Maps API. + * + * Example for account located in EU-west region: `https://gcp-eu-west1.api.carto.com` + * + * @default https://gcp-us-east1.api.carto.com + */ + apiBaseUrl: string; + + /** + * Custom HTTP headers added to map instantiation and data requests. + */ + headers: Record; + + /** + * Cache buster value returned by map instantiation. + * + * Carto source saves `cache` value of map instantiation response in `cache.value`, so it can be used to + * check if underlying map data has changed between distinct source requests. + */ + cache?: {value?: number}; + + clientId: string; + /** @deprecated use `query` instead **/ + format: Format; + + /** + * Maximum URL character length. Above this limit, requests use POST. + * Used to avoid browser and CDN limits. + * @default {@link DEFAULT_MAX_LENGTH_URL} + */ + maxLengthURL?: number; +}; -/** Options for {@link WidgetBaseSource#getFormula}. */ -export interface FormulaRequestOptions extends BaseRequestOptions { - column: string; - operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; - operationExp?: string; -} +export type SourceOptions = SourceRequiredOptions & + Partial; + +export type AggregationOptions = { + /** + * Defines the aggregation expressions that will be calculated from the resulting columns on each grid cell. + * + * Example: + * + * sum(pop) as total_population, avg(rev) as average_revenue + */ + aggregationExp: string; + + /** + * Defines the tile aggregation resolution. + * + * @default 6 for quadbin and 4 for h3 sources + */ + aggregationResLevel?: number; +}; -/** Options for {@link WidgetBaseSource#getHistogram}. */ -export interface HistogramRequestOptions extends BaseRequestOptions { - column: string; - ticks: number[]; - operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; -} +export type FilterOptions = { + /** + * Filters to apply to the data source on the server + */ + filters?: Filters; +}; -/** Options for {@link WidgetBaseSource#getRange}. */ -export interface RangeRequestOptions extends BaseRequestOptions { - column: string; -} +export type QuerySourceOptions = { + /** + * The column name and the type of geospatial support. + * + * If not present, defaults to `'geom'` for generic queries, `'quadbin'` for Quadbin sources and `'h3'` for H3 sources. + */ + spatialDataColumn?: string; + + /** SQL query. */ + sqlQuery: string; + + /** + * Relative resolution of a tile. Higher values increase density and data size. At `tileResolution = 1`, tile geometry is + * quantized to a 1024x1024 grid. Increasing or decreasing the resolution will increase or decrease the dimensions of + * the quantization grid proportionately. + * + * Supported `tileResolution` values, with corresponding grid sizes: + * + * - 0.25: 256x256 + * - 0.5: 512x512 + * - 1: 1024x1024 + * - 2: 2048x2048 + * - 4: 4096x4096 + */ + tileResolution?: TileResolution; + + /** + * Values for named or positional paramteres in the query. + * + * The way query parameters are determined by data warehouse. + * + * * BigQuery has named query parameters, specified with a dictionary, and referenced by key (`@key`) + * + * ``` + * sqlQuery: "SELECT * FROM carto-demo-data.demo_tables.retail_stores WHERE storetype = ⁣@type AND revenue > ⁣@minRevenue" + * queryParameters: { type: 'Supermarket', minRevenue: 1000000 } + * ``` + * * Snowflake supports positional parameters, in the form `:1`, `:2`, etc. + * + * ``` + * sqlQuery: "SELECT * FROM demo_db.public.import_retail_stores WHERE storetype = :2 AND revenue > :1 + * queryParameters: [100000, "Supermarket"] + * ``` + * * Postgres and Redhisft supports positional parameters, but in the form `$1`, `$2`, etc. + * + * ``` + * sqlQuery: "SELECT * FROM carto_demo_data.demo_tables.retail_stores WHERE storetype = $2 AND revenue > $1 + * queryParameters: [100000, "Supermarket"] + * ``` + */ + queryParameters?: QueryParameters; +}; -/** Options for {@link WidgetBaseSource#getScatter}. */ -export interface ScatterRequestOptions extends BaseRequestOptions { - xAxisColumn: string; - xAxisJoinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; - yAxisColumn: string; - yAxisJoinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; -} +export type TableSourceOptions = { + /** + * Fully qualified name of table. + */ + tableName: string; + + /** + * The column name and the type of geospatial support. + * + * If not present, defaults to `'geom'` for generic tables, `'quadbin'` for Quadbin sources and `'h3'` for H3 sources. + */ + spatialDataColumn?: string; + + /** + * Relative resolution of a tile. Higher values increase density and data size. At `tileResolution = 1`, tile geometry is + * quantized to a 1024x1024 grid. Increasing or decreasing the resolution will increase or decrease the dimensions of + * the quantization grid proportionately. + * + * Supported `tileResolution` values, with corresponding grid sizes: + * + * - 0.25: 256x256 + * - 0.5: 512x512 + * - 1: 1024x1024 + * - 2: 2048x2048 + * - 4: 4096x4096 + */ + tileResolution?: TileResolution; +}; -/** Options for {@link WidgetBaseSource#getTable}. */ -export interface TableRequestOptions extends BaseRequestOptions { - columns: string[]; - sortBy?: string; - sortDirection?: SortDirection; - sortByColumnType?: SortColumnType; - offset?: number; - limit?: number; -} +export type TilesetSourceOptions = { + /** + * Fully qualified name of tileset. + */ + tableName: string; +}; -/** Options for {@link WidgetBaseSource#getTimeSeries}. */ -export interface TimeSeriesRequestOptions extends BaseRequestOptions { - column: string; - stepSize?: GroupDateType; - stepMultiplier?: number; - operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; - operationColumn?: string; - joinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; - splitByCategory?: string; - splitByCategoryLimit?: number; - splitByCategoryValues?: string[]; -} +export type ColumnsOption = { + /** + * Columns to retrieve from the table. + * + * If not present, all columns are returned. + */ + columns?: string[]; +}; -/****************************************************************************** - * WIDGET API RESPONSES - */ +export type SpatialDataType = 'geo' | 'h3' | 'quadbin'; -/** Response from {@link WidgetBaseSource#getFormula}. */ -export type FormulaResponse = {value: number}; +export type TilejsonMapInstantiation = MapInstantiation & { + tilejson: {url: string[]}; +}; + +export type TileResolution = 0.25 | 0.5 | 1 | 2 | 4; + +export interface Tilejson { + tilejson: string; + name: string; + description: string; + version: string; + attribution: string; + scheme: string; + tiles: string[]; + properties_tiles: string[]; + minresolution: number; + maxresolution: number; + minzoom: number; + maxzoom: number; + bounds: [number, number, number, number]; + center: [number, number, number]; + vector_layers: VectorLayer[]; + tilestats: Tilestats; + tileResolution?: TileResolution; +} -/** Response from {@link WidgetBaseSource#getCategories}. */ -export type CategoryResponse = {name: string; value: number}[]; +export interface Tilestats { + layerCount: number; + layers: Layer[]; +} -/** Response from {@link WidgetBaseSource#getRange}. */ -export type RangeResponse = {min: number; max: number}; +export interface Layer { + layer: string; + count: number; + attributeCount: number; + attributes: Attribute[]; +} -/** Response from {@link WidgetBaseSource#getTable}. */ -export type TableResponse = { - totalCount: number; - rows: Record[]; -}; +export interface Attribute { + attribute: string; + type: string; +} -/** Response from {@link WidgetBaseSource#getScatter}. */ -export type ScatterResponse = [number, number][]; +export interface VectorLayer { + id: string; + minzoom: number; + maxzoom: number; + fields: Record; +} -/** Response from {@link WidgetBaseSource#getTimeSeries}. */ -export type TimeSeriesResponse = { - rows: {name: string; value: number}[]; - categories: string[]; +export type TilejsonResult = Tilejson & {accessToken: string}; +export type GeojsonResult = {type: 'FeatureCollection'; features: Feature[]}; +export type JsonResult = any[]; +export type QueryResult = { + meta: {cacheHit: boolean; location: string; totalBytesProcessed: string}; + rows: Record[]; + schema: {name: string; type: string}[]; }; - -/** Response from {@link WidgetBaseSource#getHistogram}. */ -export type HistogramResponse = number[]; diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts new file mode 100644 index 0000000..65561e9 --- /dev/null +++ b/src/sources/vector-query-source.ts @@ -0,0 +1,65 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import {DEFAULT_TILE_RESOLUTION} from '../constants'; +import {baseSource} from './base-source'; +import type { + FilterOptions, + SourceOptions, + QuerySourceOptions, + SpatialDataType, + TilejsonResult, + ColumnsOption, +} from './types'; + +export type VectorQuerySourceOptions = SourceOptions & + QuerySourceOptions & + FilterOptions & + ColumnsOption; + +type UrlParameters = { + columns?: string; + filters?: Record; + spatialDataType: SpatialDataType; + spatialDataColumn?: string; + tileResolution?: string; + q: string; + queryParameters?: Record | unknown[]; +}; + +export const vectorQuerySource = async function ( + options: VectorQuerySourceOptions +): Promise { + const { + columns, + filters, + spatialDataColumn = 'geom', + sqlQuery, + tileResolution = DEFAULT_TILE_RESOLUTION, + queryParameters, + } = options; + + const urlParameters: UrlParameters = { + spatialDataColumn, + spatialDataType: 'geo', + tileResolution: tileResolution.toString(), + q: sqlQuery, + }; + + if (columns) { + urlParameters.columns = columns.join(','); + } + if (filters) { + urlParameters.filters = filters; + } + if (queryParameters) { + urlParameters.queryParameters = queryParameters; + } + return baseSource( + 'query', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts new file mode 100644 index 0000000..42461e2 --- /dev/null +++ b/src/sources/vector-table-source.ts @@ -0,0 +1,59 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable camelcase */ +import {DEFAULT_TILE_RESOLUTION} from '../constants'; +import {baseSource} from './base-source'; +import type { + FilterOptions, + ColumnsOption, + SourceOptions, + SpatialDataType, + TableSourceOptions, + TilejsonResult, +} from './types'; + +export type VectorTableSourceOptions = SourceOptions & + TableSourceOptions & + FilterOptions & + ColumnsOption; +type UrlParameters = { + columns?: string; + filters?: Record; + spatialDataType: SpatialDataType; + spatialDataColumn?: string; + tileResolution?: string; + name: string; +}; + +export const vectorTableSource = async function ( + options: VectorTableSourceOptions +): Promise { + const { + columns, + filters, + spatialDataColumn = 'geom', + tableName, + tileResolution = DEFAULT_TILE_RESOLUTION, + } = options; + + const urlParameters: UrlParameters = { + name: tableName, + spatialDataColumn, + spatialDataType: 'geo', + tileResolution: tileResolution.toString(), + }; + + if (columns) { + urlParameters.columns = columns.join(','); + } + if (filters) { + urlParameters.filters = filters; + } + return baseSource( + 'table', + options, + urlParameters + ) as Promise; +}; diff --git a/src/sources/vector-tileset-source.ts b/src/sources/vector-tileset-source.ts new file mode 100644 index 0000000..506f64d --- /dev/null +++ b/src/sources/vector-tileset-source.ts @@ -0,0 +1,26 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {baseSource} from './base-source'; +import type { + SourceOptions, + TilesetSourceOptions, + TilejsonResult, +} from './types'; + +export type VectorTilesetSourceOptions = SourceOptions & TilesetSourceOptions; +type UrlParameters = {name: string}; + +export const vectorTilesetSource = async function ( + options: VectorTilesetSourceOptions +): Promise { + const {tableName} = options; + const urlParameters: UrlParameters = {name: tableName}; + + return baseSource( + 'tileset', + options, + urlParameters + ) as Promise; +}; diff --git a/src/utils.ts b/src/utils.ts index fc756c0..64a689d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,3 +90,11 @@ export function isEmptyObject(object: object): boolean { } return true; } + +/** @internal */ +export const isObject: (x: unknown) => boolean = (x) => + x !== null && typeof x === 'object'; + +/** @internal */ +export const isPureObject: (x: any) => boolean = (x) => + isObject(x) && x.constructor === {}.constructor; diff --git a/src/widget-sources/index.ts b/src/widget-sources/index.ts new file mode 100644 index 0000000..7bf51ee --- /dev/null +++ b/src/widget-sources/index.ts @@ -0,0 +1,5 @@ +export * from './widget-base-source.js'; +export * from './widget-query-source.js'; +export * from './widget-table-source.js'; +export * from './wrappers.js'; +export * from './types.js'; diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts new file mode 100644 index 0000000..05b035c --- /dev/null +++ b/src/widget-sources/types.ts @@ -0,0 +1,105 @@ +import { + GroupDateType, + SortColumnType, + SortDirection, + SpatialFilter, +} from '../types'; + +/****************************************************************************** + * WIDGET API REQUESTS + */ + +/** Common options for {@link WidgetBaseSource} requests. */ +interface BaseRequestOptions { + spatialFilter?: SpatialFilter; + abortController?: AbortController; + filterOwner?: string; +} + +/** Options for {@link WidgetBaseSource#getCategories}. */ +export interface CategoryRequestOptions extends BaseRequestOptions { + column: string; + operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; + operationColumn?: string; +} + +/** Options for {@link WidgetBaseSource#getFormula}. */ +export interface FormulaRequestOptions extends BaseRequestOptions { + column: string; + operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; + operationExp?: string; +} + +/** Options for {@link WidgetBaseSource#getHistogram}. */ +export interface HistogramRequestOptions extends BaseRequestOptions { + column: string; + ticks: number[]; + operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; +} + +/** Options for {@link WidgetBaseSource#getRange}. */ +export interface RangeRequestOptions extends BaseRequestOptions { + column: string; +} + +/** Options for {@link WidgetBaseSource#getScatter}. */ +export interface ScatterRequestOptions extends BaseRequestOptions { + xAxisColumn: string; + xAxisJoinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; + yAxisColumn: string; + yAxisJoinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; +} + +/** Options for {@link WidgetBaseSource#getTable}. */ +export interface TableRequestOptions extends BaseRequestOptions { + columns: string[]; + sortBy?: string; + sortDirection?: SortDirection; + sortByColumnType?: SortColumnType; + offset?: number; + limit?: number; +} + +/** Options for {@link WidgetBaseSource#getTimeSeries}. */ +export interface TimeSeriesRequestOptions extends BaseRequestOptions { + column: string; + stepSize?: GroupDateType; + stepMultiplier?: number; + operation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; + operationColumn?: string; + joinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum'; + splitByCategory?: string; + splitByCategoryLimit?: number; + splitByCategoryValues?: string[]; +} + +/****************************************************************************** + * WIDGET API RESPONSES + */ + +/** Response from {@link WidgetBaseSource#getFormula}. */ +export type FormulaResponse = {value: number}; + +/** Response from {@link WidgetBaseSource#getCategories}. */ +export type CategoryResponse = {name: string; value: number}[]; + +/** Response from {@link WidgetBaseSource#getRange}. */ +export type RangeResponse = {min: number; max: number}; + +/** Response from {@link WidgetBaseSource#getTable}. */ +export type TableResponse = { + totalCount: number; + rows: Record[]; +}; + +/** Response from {@link WidgetBaseSource#getScatter}. */ +export type ScatterResponse = [number, number][]; + +/** Response from {@link WidgetBaseSource#getTimeSeries}. */ +export type TimeSeriesResponse = { + rows: {name: string; value: number}[]; + categories: string[]; +}; + +/** Response from {@link WidgetBaseSource#getHistogram}. */ +export type HistogramResponse = number[]; diff --git a/src/sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts similarity index 99% rename from src/sources/widget-base-source.ts rename to src/widget-sources/widget-base-source.ts index 0b3d787..aeda083 100644 --- a/src/sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -16,7 +16,6 @@ import { TimeSeriesResponse, } from './types.js'; import {FilterLogicalOperator, Filter} from '../types.js'; -import {SourceOptions} from '@deck.gl/carto'; import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import { DEFAULT_API_BASE_URL, @@ -25,6 +24,7 @@ import { } from '../constants-internal.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; +import {SourceOptions} from '../sources/index.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; diff --git a/src/sources/widget-query-source.ts b/src/widget-sources/widget-query-source.ts similarity index 98% rename from src/sources/widget-query-source.ts rename to src/widget-sources/widget-query-source.ts index 3c71998..b78f6aa 100644 --- a/src/sources/widget-query-source.ts +++ b/src/widget-sources/widget-query-source.ts @@ -2,7 +2,7 @@ import { H3QuerySourceOptions, QuadbinQuerySourceOptions, VectorQuerySourceOptions, -} from '@deck.gl/carto'; +} from '../sources/index.js'; import {MapType} from '../constants-internal.js'; import {WidgetBaseSource, WidgetBaseSourceProps} from './widget-base-source.js'; import {ModelSource} from '../models/model.js'; diff --git a/src/sources/widget-table-source.ts b/src/widget-sources/widget-table-source.ts similarity index 97% rename from src/sources/widget-table-source.ts rename to src/widget-sources/widget-table-source.ts index 8b5df16..6bf93de 100644 --- a/src/sources/widget-table-source.ts +++ b/src/widget-sources/widget-table-source.ts @@ -2,7 +2,7 @@ import { H3TableSourceOptions, QuadbinTableSourceOptions, VectorTableSourceOptions, -} from '@deck.gl/carto'; +} from '../sources/index.js'; import {WidgetBaseSource, WidgetBaseSourceProps} from './widget-base-source.js'; import {MapType} from '../constants-internal.js'; import {ModelSource} from '../models/model.js'; diff --git a/src/sources/wrappers.ts b/src/widget-sources/wrappers.ts similarity index 87% rename from src/sources/wrappers.ts rename to src/widget-sources/wrappers.ts index 9027841..e01dd64 100644 --- a/src/sources/wrappers.ts +++ b/src/widget-sources/wrappers.ts @@ -12,7 +12,7 @@ import { QuadbinTableSourceOptions as _QuadbinTableSourceOptions, QuadbinQuerySourceOptions as _QuadbinQuerySourceOptions, SourceOptions, -} from '@deck.gl/carto'; +} from '../sources/index.js'; import {WidgetBaseSourceProps} from './widget-base-source.js'; import {WidgetQuerySource} from './widget-query-source.js'; import {WidgetTableSource} from './widget-table-source.js'; @@ -55,7 +55,6 @@ export type VectorQuerySourceOptions = export async function vectorTableSource( props: VectorTableSourceOptions ): Promise { - assignDefaultProps(props); const response = await _vectorTableSource(props as _VectorTableSourceOptions); return {...response, widgetSource: new WidgetTableSource(props)}; } @@ -64,7 +63,6 @@ export async function vectorTableSource( export async function vectorQuerySource( props: VectorQuerySourceOptions ): Promise { - assignDefaultProps(props); const response = await _vectorQuerySource(props as _VectorQuerySourceOptions); return {...response, widgetSource: new WidgetQuerySource(props)}; } @@ -80,7 +78,6 @@ export type H3QuerySourceOptions = WrappedSourceOptions<_H3QuerySourceOptions>; export async function h3TableSource( props: H3TableSourceOptions ): Promise { - assignDefaultProps(props); const response = await _h3TableSource(props as _H3TableSourceOptions); return {...response, widgetSource: new WidgetTableSource(props)}; } @@ -89,7 +86,6 @@ export async function h3TableSource( export async function h3QuerySource( props: H3QuerySourceOptions ): Promise { - assignDefaultProps(props); const response = await _h3QuerySource(props as _H3QuerySourceOptions); return {...response, widgetSource: new WidgetQuerySource(props)}; } @@ -108,7 +104,6 @@ export type QuadbinQuerySourceOptions = export async function quadbinTableSource( props: QuadbinTableSourceOptions & WidgetBaseSourceProps ): Promise { - assignDefaultProps(props); const response = await _quadbinTableSource( props as _QuadbinTableSourceOptions ); @@ -119,23 +114,8 @@ export async function quadbinTableSource( export async function quadbinQuerySource( props: QuadbinQuerySourceOptions & WidgetBaseSourceProps ): Promise { - assignDefaultProps(props); const response = await _quadbinQuerySource( props as _QuadbinQuerySourceOptions ); return {...response, widgetSource: new WidgetQuerySource(props)}; } - -/****************************************************************************** - * DEFAULT PROPS - */ - -declare const deck: {VERSION?: string} | undefined; -function assignDefaultProps(props: T): void { - if (typeof deck !== 'undefined' && deck && deck.VERSION) { - props.clientId ||= 'deck-gl-carto'; - // TODO: Uncomment if/when `@deck.gl/carto` devDependency is removed, - // and source functions are moved here rather than wrapped. - // props.deckglVersion ||= deck.VERSION; - } -} diff --git a/yarn.lock b/yarn.lock index d1e4038..7dccc4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1513,8 +1513,8 @@ __metadata: linkType: hard "@deck.gl/carto@npm:^9.0.30": - version: 9.0.32 - resolution: "@deck.gl/carto@npm:9.0.32" + version: 9.0.33 + resolution: "@deck.gl/carto@npm:9.0.33" dependencies: "@loaders.gl/gis": "npm:^4.2.0" "@loaders.gl/loader-utils": "npm:^4.2.0" @@ -1544,7 +1544,7 @@ __metadata: "@deck.gl/geo-layers": ^9.0.0 "@deck.gl/layers": ^9.0.0 "@loaders.gl/core": ^4.2.0 - checksum: 10c0/8c71f148d126c1ed2e5fc770fc7a85399a3d758ba6762c2c6cd2de49402fc3bcdd566d45c98ec315c4f5e4d65afdfe0222e852805d4665b08fd2cbece5166d4a + checksum: 10c0/97a50471fdf417919bf991fa40219f887582ddd71d2b4877146aa5ebd1e4cc0779021ed0e05cf0dcefc79194ef10d47c6df8a1b770726c917ebff9fa85e73864 languageName: node linkType: hard From 74193117919a8a85db2f046b3d9a3869e5253ddd Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 15:17:49 -0400 Subject: [PATCH 02/26] fix TS issues --- src/api/carto-api-error.ts | 6 +++--- src/api/request-with-parameters.ts | 6 +++++- src/sources/base-source.ts | 5 +++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/api/carto-api-error.ts b/src/api/carto-api-error.ts index 0ed403a..d39c5f9 100644 --- a/src/api/carto-api-error.ts +++ b/src/api/carto-api-error.ts @@ -49,8 +49,8 @@ export class CartoAPIError extends Error { let message = `${errorContext.requestType} API request failed`; message += `\n${responseString}`; for (const key of Object.keys(errorContext)) { - if (key === 'requestType') continue; // eslint-disable-line no-continue - message += `\n${formatErrorKey(key)}: ${errorContext[key]}`; + if (key === 'requestType') continue; + message += `\n${formatErrorKey(key)}: ${(errorContext as any)[key]}`; } message += '\n'; @@ -67,6 +67,6 @@ export class CartoAPIError extends Error { /** * Converts camelCase to Camel Case */ -function formatErrorKey(key) { +function formatErrorKey(key: string) { return key.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase()); } diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 759891e..8f8f4c4 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -5,7 +5,11 @@ import {isPureObject} from '../utils'; import {CartoAPIError} from './carto-api-error'; import type {APIErrorContext} from './types'; -import {DEFAULT_MAX_LENGTH_URL, V3_MINOR_VERSION} from '../constants'; +import { + DEFAULT_CLIENT, + DEFAULT_MAX_LENGTH_URL, + V3_MINOR_VERSION, +} from '../constants'; const DEFAULT_HEADERS = { Accept: 'application/json', diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index 39c9993..65c92e7 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -41,8 +41,9 @@ export async function baseSource>( endpoint, }; for (const key in optionalOptions) { - if (optionalOptions[key]) { - mergedOptions[key] = optionalOptions[key]; + if (optionalOptions[key as keyof typeof optionalOptions]) { + (mergedOptions as any)[key] = + optionalOptions[key as keyof typeof optionalOptions]; } } const baseUrl = buildSourceUrl(mergedOptions); From c83bb75e823d002a932ce1e6ecf777b11177f5bc Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 15:24:06 -0400 Subject: [PATCH 03/26] Fix failure if deck not defined --- src/api/request-with-parameters.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 8f8f4c4..5b57626 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -37,7 +37,8 @@ export async function requestWithParameters({ parameters = { v: V3_MINOR_VERSION, clientId: DEFAULT_CLIENT, - deckglVersion: deck?.VERSION, + ...(typeof deck !== 'undefined' && + deck.VERSION && {deckglVersion: deck.VERSION}), ...parameters, }; From 23173e6c96db036fea1a1f2f62c2f830088f85da Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 15:26:07 -0400 Subject: [PATCH 04/26] chore(release): v0.4.0-alpha.0 --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a627c59..aa4f852 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.3.1", + "version": "0.4.0-alpha.0", "license": "MIT", "publishConfig": { "access": "public", @@ -98,5 +98,6 @@ "vite": "^5.2.10", "vitest": "1.6.0", "vue": "^3.4.27" - } + }, + "stableVersion": "0.3.1" } From 53488119ca5fc54040064764baef7755ad2cdc84 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 15:53:13 -0400 Subject: [PATCH 05/26] type fixes --- src/api/carto-api-error.ts | 18 +- src/api/endpoints.ts | 2 +- src/api/index.ts | 19 +- src/api/query.ts | 2 +- src/api/request-with-parameters.ts | 5 +- src/api/types.ts | 301 ---------------------- src/constants-internal.ts | 48 +++- src/constants.ts | 23 +- src/index.ts | 9 +- src/models/model.ts | 10 +- src/sources/base-source.ts | 5 +- src/sources/boundary-query-source.ts | 2 +- src/sources/h3-query-source.ts | 2 +- src/sources/h3-table-source.ts | 2 +- src/sources/quadbin-query-source.ts | 2 +- src/sources/quadbin-table-source.ts | 2 +- src/sources/types.ts | 8 +- src/sources/vector-query-source.ts | 2 +- src/sources/vector-table-source.ts | 2 +- src/types-internal.ts | 55 +++- src/types.ts | 16 ++ src/widget-sources/widget-base-source.ts | 2 +- src/widget-sources/widget-query-source.ts | 3 +- src/widget-sources/widget-table-source.ts | 3 +- 24 files changed, 162 insertions(+), 381 deletions(-) delete mode 100644 src/api/types.ts diff --git a/src/api/carto-api-error.ts b/src/api/carto-api-error.ts index d39c5f9..7257538 100644 --- a/src/api/carto-api-error.ts +++ b/src/api/carto-api-error.ts @@ -2,7 +2,23 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import type {APIErrorContext} from './types'; +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; +}; /** * diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index ad91c15..50ecdf4 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import {MapType} from './types'; +import {MapType} from '../types.js'; export type V3Endpoint = 'maps' | 'stats' | 'sql'; diff --git a/src/api/index.ts b/src/api/index.ts index 7afdd01..729c3c4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,16 +2,11 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -export {CartoAPIError} from './carto-api-error'; -export type { +export { + CartoAPIError, APIErrorContext, - Format, - MapType, - RequestType, - QueryParameters, - Basemap, - MapLibreBasemap, - GoogleBasemap, -} from './types'; -export {query} from './query'; -export type {QueryOptions} from './query'; + APIRequestType, +} from './carto-api-error.js'; +export {query} from './query.js'; +export type {QueryOptions} from './query.js'; +export {requestWithParameters} from './request-with-parameters.js'; diff --git a/src/api/query.ts b/src/api/query.ts index d32f1c6..3678038 100644 --- a/src/api/query.ts +++ b/src/api/query.ts @@ -10,7 +10,7 @@ import type { } from '../sources/types'; import {buildQueryUrl} from './endpoints'; import {requestWithParameters} from './request-with-parameters'; -import {APIErrorContext} from './types'; +import {APIErrorContext} from './carto-api-error'; export type QueryOptions = SourceOptions & Omit; diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 5b57626..2032a6a 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -3,13 +3,12 @@ // Copyright (c) vis.gl contributors import {isPureObject} from '../utils'; -import {CartoAPIError} from './carto-api-error'; -import type {APIErrorContext} from './types'; +import {CartoAPIError, APIErrorContext} from './carto-api-error'; import { DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL, V3_MINOR_VERSION, -} from '../constants'; +} from '../constants-internal'; const DEFAULT_HEADERS = { Accept: 'application/json', diff --git a/src/api/types.ts b/src/api/types.ts deleted file mode 100644 index 7461b5e..0000000 --- a/src/api/types.ts +++ /dev/null @@ -1,301 +0,0 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - -export type Format = 'json' | 'geojson' | 'tilejson'; -export type MapType = 'boundary' | 'query' | 'table' | 'tileset' | 'raster'; -export type RequestType = - | 'Map data' - | 'Map instantiation' - | 'Public map' - | 'Tile stats' - | 'SQL' - | 'Basemap style'; -export type ScaleType = - | 'linear' - | 'ordinal' - | 'log' - | 'point' - | 'quantile' - | 'quantize' - | 'sqrt' - | 'custom' - | 'identity'; - -export type APIErrorContext = { - requestType: RequestType; - mapId?: string; - connection?: string; - source?: string; - type?: MapType; -}; - -export enum SchemaFieldType { - Number = 'number', - Bigint = 'bigint', - String = 'string', - Geometry = 'geometry', - Timestamp = 'timestamp', - Object = 'object', - Boolean = 'boolean', - Variant = 'variant', - Unknown = 'unknown', -} -export interface SchemaField { - name: string; - type: SchemaFieldType; // Field type in the CARTO stack, common for all providers -} - -export interface MapInstantiation extends MapInstantiationFormats { - nrows: number; - size?: number; - schema: SchemaField[]; -} - -type MapInstantiationFormats = Record< - Format, - { - url: string[]; - error?: any; - } ->; - -export type QueryParameterValue = - | string - | number - | boolean - | Array - | object; - -export type NamedQueryParameter = Record; - -export type PositionalQueryParameter = QueryParameterValue[]; - -export type QueryParameters = NamedQueryParameter | PositionalQueryParameter; -export type VisualChannelField = { - name: string; - type: string; - colorColumn?: string; -}; - -export interface Filters { - [column: string]: Filter; -} - -interface Filter { - [FilterTypes.In]?: number[]; - [FilterTypes.Between]?: number[][]; - [FilterTypes.ClosedOpen]?: number[][]; - [FilterTypes.Time]?: number[][]; - [FilterTypes.StringSearch]?: string[]; -} - -export enum FilterTypes { - In = 'in', - Between = 'between', // [a, b] both are included - ClosedOpen = 'closed_open', // [a, b) a is included, b is not - Time = 'time', - StringSearch = 'stringSearch', -} - -export type VisualChannels = { - colorField?: VisualChannelField; - colorScale?: ScaleType; - - customMarkersField?: VisualChannelField; - customMarkersScale?: ScaleType; - - radiusField?: VisualChannelField; - radiusScale?: ScaleType; - - rotationScale?: ScaleType; - rotationField?: VisualChannelField; - - sizeField?: VisualChannelField; - sizeScale?: ScaleType; - - strokeColorField?: VisualChannelField; - strokeColorScale?: ScaleType; - - heightField?: VisualChannelField; - heightScale?: ScaleType; - - weightField?: VisualChannelField; -}; - -export type ColorRange = { - category: string; - colors: string[]; - colorMap: string[][] | undefined; - name: string; - type: string; -}; - -export type CustomMarkersRange = { - markerMap: { - value: string; - markerUrl?: string; - }[]; - othersMarker?: string; -}; - -export type VisConfig = { - filled?: boolean; - opacity?: number; - enable3d?: boolean; - - colorAggregation?: any; - colorRange: ColorRange; - - customMarkers?: boolean; - customMarkersRange?: CustomMarkersRange | null; - customMarkersUrl?: string | null; - - radius: number; - radiusRange?: number[]; - - sizeAggregation?: any; - sizeRange?: any; - - strokeColorAggregation?: any; - strokeOpacity?: number; - strokeColorRange?: ColorRange; - - heightRange?: any; - heightAggregation?: any; - - weightAggregation?: any; -}; - -export type TextLabel = { - field: VisualChannelField | null | undefined; - alignment?: 'center' | 'bottom' | 'top'; - anchor?: 'middle' | 'start' | 'end'; - size: number; - color?: number[]; - offset?: [number, number]; - outlineColor?: number[]; -}; - -export type MapLayerConfig = { - columns?: Record; - color?: number[]; - label?: string; - dataId: string; - textLabel: TextLabel[]; - visConfig: VisConfig; -}; - -export type MapTextSubLayerConfig = Omit & { - textLabel?: TextLabel; -}; - -export type MapConfigLayer = { - type: string; - id: string; - config: MapLayerConfig; - visualChannels: VisualChannels; -}; - -export type MapDataset = { - id: string; - data: any; - aggregationExp: string | null; - aggregationResLevel: number | null; - geoColumn: string; -}; - -export interface CustomStyle { - url?: string; - style?: any; - customAttribution?: string; -} - -export type KeplerMapConfig = { - mapState: any; - mapStyle: { - styleType: string; - visibleLayerGroups: Record; - }; - visState: { - layers: MapConfigLayer[]; - }; - layerBlending: any; - interactionConfig: any; - customBaseMaps?: { - customStyle?: CustomStyle; - }; -}; - -export type BasemapType = 'maplibre' | 'google-maps'; - -export type Basemap = MapLibreBasemap | GoogleBasemap; - -export type BasemapCommon = { - /** - * Type of basemap. - */ - type: BasemapType; - - /** - * Custom attribution for style data if not provided by style definition. - */ - attribution?: string; - - /** - * Properties of the basemap. These properties are specific to the basemap type. - */ - props: Record; -}; - -export type MapLibreBasemap = BasemapCommon & { - type: 'maplibre'; - - /** - * MapLibre map properties. - * - * Meant to be passed to directly to `maplibregl.Map` object. - */ - props: MapLibreBasemapProps; - - /** - * Layer groups to be displayed in the basemap. - */ - visibleLayerGroups?: Record; - - /** - * If `style` has been filtered by `visibleLayerGroups` then this property contains original style object, so user - * can use `applyLayerGroupFilters` again with new settings. - */ - rawStyle?: string | Record; -}; - -// Cherry-pick of maplibregl Map API props that are supported/provided by fetchMap interface -export type MapLibreBasemapProps = { - style: string | Record; - center: [number, number]; - zoom: number; - pitch?: number; - bearing?: number; -}; - -export type GoogleBasemap = BasemapCommon & { - type: 'google-maps'; - - /** - * Google map properties. - * - * Meant to be passed to directly to `google.maps.Map` object. - */ - props: GoogleBasemapProps; -}; - -// Cherry-pick of Google Map API props that are supported/provided by fetchMap interface -export type GoogleBasemapProps = { - mapTypeId: string; - mapId?: string; - center?: {lat: number; lng: number}; - zoom?: number; - tilt?: number; - heading?: number; -}; diff --git a/src/constants-internal.ts b/src/constants-internal.ts index 8e1b99e..1bbbff2 100644 --- a/src/constants-internal.ts +++ b/src/constants-internal.ts @@ -1,3 +1,16 @@ +/****************************************************************************** + * VERSIONS + */ + +/** + * Current version of @carto/api-client. + * @internal + */ +export const API_CLIENT_VERSION = __CARTO_API_CLIENT_VERSION; + +/** @internal */ +export const V3_MINOR_VERSION = '3.4'; + /****************************************************************************** * DEFAULTS */ @@ -20,26 +33,33 @@ export const DEFAULT_CLIENT = 'deck-gl-carto'; */ export const DEFAULT_GEO_COLUMN = 'geom'; -/****************************************************************************** - * ENUMS +/** + * Fastly default limit is 8192; leave some padding. + * @internalRemarks Source: @deck.gl/carto + * @internal */ +export const DEFAULT_MAX_LENGTH_URL = 7000; /** + * @internalRemarks Source: @deck.gl/carto * @internal - * @internalRemarks Source: @carto/constants */ -export enum MapType { - TABLE = 'table', - QUERY = 'query', - TILESET = 'tileset', -} +export const DEFAULT_TILE_SIZE = 512; /** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export const DEFAULT_TILE_RESOLUTION = 0.5; + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4; + +/** + * @internalRemarks Source: @deck.gl/carto * @internal - * @internalRemarks Source: @carto/constants */ -export enum ApiVersion { - V1 = 'v1', - V2 = 'v2', - V3 = 'v3', -} +export const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6; diff --git a/src/constants.ts b/src/constants.ts index 84610e0..2862528 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,19 +1,3 @@ -/** Current version of @carto/api-client. */ -export const API_CLIENT_VERSION = __CARTO_API_CLIENT_VERSION; - -export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; -export const DEFAULT_CLIENT = 'deck-gl-carto'; -export const V3_MINOR_VERSION = '3.4'; - -// Fastly default limit is 8192; leave some padding. -export const DEFAULT_MAX_LENGTH_URL = 7000; - -export const DEFAULT_TILE_SIZE = 512; -export const DEFAULT_TILE_RESOLUTION = 0.5; - -export const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4; -export const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6; - /** * Defines a comparator used when matching a column's values against given filter values. * @@ -37,3 +21,10 @@ export enum FilterType { TIME = 'time', STRING_SEARCH = 'stringSearch', } + +/** @internalRemarks Source: @carto/constants */ +export enum ApiVersion { + V1 = 'v1', + V2 = 'v2', + V3 = 'v3', +} diff --git a/src/index.ts b/src/index.ts index 8f3d958..5d27c98 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,9 +7,8 @@ export * from './types.js'; export { APIErrorContext, - Format, // TODO: Move to `types.ts`? - MapType, // TODO: De-duplicate? - RequestType, // TODO: Move to `types.ts`? - QueryParameters, // TODO: Move to `types.ts`? - QueryOptions, // TODO: Move to `types.ts`? + APIRequestType, + QueryOptions, + requestWithParameters, + query, } from './api/index.js'; diff --git a/src/models/model.ts b/src/models/model.ts index ab0c00e..08c2edd 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -1,17 +1,15 @@ -import { - ApiVersion, - DEFAULT_GEO_COLUMN, - MapType, -} from '../constants-internal.js'; +import {DEFAULT_GEO_COLUMN} from '../constants-internal.js'; import { Filter, FilterLogicalOperator, + MapType, QueryParameters, SpatialFilter, } from '../types.js'; import {$TODO} from '../types-internal.js'; import {assert} from '../utils.js'; import {ModelRequestOptions, makeCall} from './common.js'; +import {ApiVersion} from '../constants.js'; /** @internalRemarks Source: @carto/react-api */ const AVAILABLE_MODELS = [ @@ -72,7 +70,7 @@ export function executeModel(props: { assert(apiBaseUrl, 'executeModel: missing apiBaseUrl'); assert(accessToken, 'executeModel: missing accessToken'); assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+'); - assert(type !== MapType.TILESET, 'executeModel: Tilesets not supported'); + assert(type !== 'tileset', 'executeModel: Tilesets not supported'); let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`; diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index 65c92e7..d632667 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -7,10 +7,9 @@ import { DEFAULT_API_BASE_URL, DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL, -} from '../constants'; +} from '../constants-internal'; import {buildSourceUrl} from '../api/endpoints'; import {requestWithParameters} from '../api/request-with-parameters'; -import type {APIErrorContext, MapType} from '../api/types'; import type { GeojsonResult, JsonResult, @@ -19,6 +18,8 @@ import type { TilejsonMapInstantiation, TilejsonResult, } from './types'; +import {MapType} from '../types'; +import {APIErrorContext} from '../api'; export const SOURCE_DEFAULTS: SourceOptionalOptions = { apiBaseUrl: DEFAULT_API_BASE_URL, diff --git a/src/sources/boundary-query-source.ts b/src/sources/boundary-query-source.ts index 01c23fc..de2d7bf 100644 --- a/src/sources/boundary-query-source.ts +++ b/src/sources/boundary-query-source.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import {QueryParameters} from '../api/index'; +import {QueryParameters} from '../types.js'; import {baseSource} from './base-source'; import type {FilterOptions, SourceOptions, TilejsonResult} from './types'; diff --git a/src/sources/h3-query-source.ts b/src/sources/h3-query-source.ts index 7fafd8f..9b5095f 100644 --- a/src/sources/h3-query-source.ts +++ b/src/sources/h3-query-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants'; +import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants-internal'; import {baseSource} from './base-source'; import type { AggregationOptions, diff --git a/src/sources/h3-table-source.ts b/src/sources/h3-table-source.ts index f1659c3..f2c9e6b 100644 --- a/src/sources/h3-table-source.ts +++ b/src/sources/h3-table-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants'; +import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants-internal'; import {baseSource} from './base-source'; import type { AggregationOptions, diff --git a/src/sources/quadbin-query-source.ts b/src/sources/quadbin-query-source.ts index 77c1013..f1e603c 100644 --- a/src/sources/quadbin-query-source.ts +++ b/src/sources/quadbin-query-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants'; +import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants-internal'; import {baseSource} from './base-source'; import type { AggregationOptions, diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts index d1c4163..3ab95c5 100644 --- a/src/sources/quadbin-table-source.ts +++ b/src/sources/quadbin-table-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants'; +import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants-internal'; import {baseSource} from './base-source'; import type { AggregationOptions, diff --git a/src/sources/types.ts b/src/sources/types.ts index 32d28e9..b039d18 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -3,12 +3,8 @@ // Copyright (c) vis.gl contributors import type {Feature} from 'geojson'; -import type { - Filters, - Format, - MapInstantiation, - QueryParameters, -} from '../api/types'; +import {Filters, Format, QueryParameters} from '../types'; +import {MapInstantiation} from '../types-internal'; export type SourceRequiredOptions = { /** Carto platform access token. */ diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 65561e9..a680408 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_TILE_RESOLUTION} from '../constants'; +import {DEFAULT_TILE_RESOLUTION} from '../constants-internal'; import {baseSource} from './base-source'; import type { FilterOptions, diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 42461e2..868e2a1 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_TILE_RESOLUTION} from '../constants'; +import {DEFAULT_TILE_RESOLUTION} from '../constants-internal'; import {baseSource} from './base-source'; import type { FilterOptions, diff --git a/src/types-internal.ts b/src/types-internal.ts index 604a928..2bf5f4a 100644 --- a/src/types-internal.ts +++ b/src/types-internal.ts @@ -1,9 +1,62 @@ /****************************************************************************** - * INTERNAL + * COMMON */ +import {Format} from './types'; + /** @internal */ export type $TODO = any; /** @internal */ export type $IntentionalAny = any; + +/****************************************************************************** + * MAP INSTANTIATION + */ + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export enum SchemaFieldType { + Number = 'number', + Bigint = 'bigint', + String = 'string', + Geometry = 'geometry', + Timestamp = 'timestamp', + Object = 'object', + Boolean = 'boolean', + Variant = 'variant', + Unknown = 'unknown', +} + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export interface SchemaField { + name: string; + type: SchemaFieldType; // Field type in the CARTO stack, common for all providers +} + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export interface MapInstantiation extends MapInstantiationFormats { + nrows: number; + size?: number; + schema: SchemaField[]; +} + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +type MapInstantiationFormats = Record< + Format, + { + url: string[]; + error?: any; + } +>; diff --git a/src/types.ts b/src/types.ts index c2d4ffe..38f8657 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,16 @@ import type {FilterType} from './constants.js'; import type {Polygon, MultiPolygon} from 'geojson'; +/****************************************************************************** + * MAPS AND TILES + */ + +/** @internalRemarks Source: @deck.gl/carto */ +export type Format = 'json' | 'geojson' | 'tilejson'; + +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export type MapType = 'boundary' | 'query' | 'table' | 'tileset' | 'raster'; + /****************************************************************************** * AGGREGATION */ @@ -26,6 +36,12 @@ export type AggregationType = /** @internalRemarks Source: @carto/react-api */ export type SpatialFilter = Polygon | MultiPolygon; +/** @internalRemarks Source: @deck.gl/carto */ +export interface Filters { + [column: string]: Filter; +} + +// TODO: Would `{[FilterType.IN]?: number[] | string[]}` also be valid? /** @internalRemarks Source: @carto/react-api, @deck.gl/carto */ export interface Filter { [FilterType.IN]?: {owner?: string; values: number[] | string[]}; diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index aeda083..28be227 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -20,11 +20,11 @@ import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import { DEFAULT_API_BASE_URL, DEFAULT_GEO_COLUMN, - ApiVersion, } from '../constants-internal.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; import {SourceOptions} from '../sources/index.js'; +import {ApiVersion} from '../constants.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; diff --git a/src/widget-sources/widget-query-source.ts b/src/widget-sources/widget-query-source.ts index b78f6aa..5b650f3 100644 --- a/src/widget-sources/widget-query-source.ts +++ b/src/widget-sources/widget-query-source.ts @@ -3,7 +3,6 @@ import { QuadbinQuerySourceOptions, VectorQuerySourceOptions, } from '../sources/index.js'; -import {MapType} from '../constants-internal.js'; import {WidgetBaseSource, WidgetBaseSourceProps} from './widget-base-source.js'; import {ModelSource} from '../models/model.js'; @@ -40,7 +39,7 @@ export class WidgetQuerySource extends WidgetBaseSource< protected override getModelSource(owner: string): ModelSource { return { ...super._getModelSource(owner), - type: MapType.QUERY, + type: 'query', data: this.props.sqlQuery, queryParameters: this.props.queryParameters, }; diff --git a/src/widget-sources/widget-table-source.ts b/src/widget-sources/widget-table-source.ts index 6bf93de..f28f310 100644 --- a/src/widget-sources/widget-table-source.ts +++ b/src/widget-sources/widget-table-source.ts @@ -4,7 +4,6 @@ import { VectorTableSourceOptions, } from '../sources/index.js'; import {WidgetBaseSource, WidgetBaseSourceProps} from './widget-base-source.js'; -import {MapType} from '../constants-internal.js'; import {ModelSource} from '../models/model.js'; type LayerTableSourceOptions = @@ -40,7 +39,7 @@ export class WidgetTableSource extends WidgetBaseSource< protected override getModelSource(owner: string): ModelSource { return { ...super._getModelSource(owner), - type: MapType.TABLE, + type: 'table', data: this.props.tableName, }; } From 4669cc8cc5b96fc4f7a1768e63cf4cf8f5f9f147 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 16:15:09 -0400 Subject: [PATCH 06/26] iterate on required exports --- src/constants-internal.ts | 53 -------------------------------------- src/constants.ts | 25 ++++++++++++++++++ src/index.ts | 11 ++++++++ src/sources/base-source.ts | 2 +- 4 files changed, 37 insertions(+), 54 deletions(-) diff --git a/src/constants-internal.ts b/src/constants-internal.ts index 1bbbff2..8392442 100644 --- a/src/constants-internal.ts +++ b/src/constants-internal.ts @@ -10,56 +10,3 @@ export const API_CLIENT_VERSION = __CARTO_API_CLIENT_VERSION; /** @internal */ export const V3_MINOR_VERSION = '3.4'; - -/****************************************************************************** - * DEFAULTS - */ - -/** - * @internalRemarks Source: @carto/constants - * @internal - */ -export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; - -/** - * @internalRemarks Source: @carto/constants - * @internal - */ -export const DEFAULT_CLIENT = 'deck-gl-carto'; - -/** - * @internalRemarks Source: @carto/react-api - * @internal - */ -export const DEFAULT_GEO_COLUMN = 'geom'; - -/** - * Fastly default limit is 8192; leave some padding. - * @internalRemarks Source: @deck.gl/carto - * @internal - */ -export const DEFAULT_MAX_LENGTH_URL = 7000; - -/** - * @internalRemarks Source: @deck.gl/carto - * @internal - */ -export const DEFAULT_TILE_SIZE = 512; - -/** - * @internalRemarks Source: @deck.gl/carto - * @internal - */ -export const DEFAULT_TILE_RESOLUTION = 0.5; - -/** - * @internalRemarks Source: @deck.gl/carto - * @internal - */ -export const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4; - -/** - * @internalRemarks Source: @deck.gl/carto - * @internal - */ -export const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6; diff --git a/src/constants.ts b/src/constants.ts index 2862528..6cacfdf 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,3 +28,28 @@ export enum ApiVersion { V2 = 'v2', V3 = 'v3', } + +/****************************************************************************** + * DEFAULTS + */ + +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; + +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export const DEFAULT_CLIENT = 'deck-gl-carto'; + +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export const DEFAULT_GEO_COLUMN = 'geom'; + +/** + * Fastly default limit is 8192; leave some padding. + * @internalRemarks Source: @deck.gl/carto + */ +export const DEFAULT_MAX_LENGTH_URL = 7000; + +/** @internalRemarks Source: @deck.gl/carto */ +export const DEFAULT_TILE_SIZE = 512; + +/** @internalRemarks Source: @deck.gl/carto */ +export const DEFAULT_TILE_RESOLUTION = 0.5; diff --git a/src/index.ts b/src/index.ts index 5d27c98..faefb44 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,3 +12,14 @@ export { requestWithParameters, query, } from './api/index.js'; + +export { + SOURCE_DEFAULTS, + SourceOptions, + TableSourceOptions, + QuerySourceOptions, + GeojsonResult, + JsonResult, + TilejsonResult, + QueryResult, +} from './sources/index.js'; diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index d632667..943e3b8 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -7,7 +7,7 @@ import { DEFAULT_API_BASE_URL, DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL, -} from '../constants-internal'; +} from '../constants'; import {buildSourceUrl} from '../api/endpoints'; import {requestWithParameters} from '../api/request-with-parameters'; import type { From 0b8878312cbf3ce6241380e24bb7ceacc0f6fdb6 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 16:17:52 -0400 Subject: [PATCH 07/26] fixes --- src/api/request-with-parameters.ts | 7 ++----- src/constants-internal.ts | 12 ++++++++++++ src/models/model.ts | 2 +- src/sources/index.ts | 7 ++++++- src/sources/vector-query-source.ts | 2 +- src/sources/vector-table-source.ts | 2 +- src/widget-sources/widget-base-source.ts | 10 +++++----- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 2032a6a..2655834 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -4,11 +4,8 @@ import {isPureObject} from '../utils'; import {CartoAPIError, APIErrorContext} from './carto-api-error'; -import { - DEFAULT_CLIENT, - DEFAULT_MAX_LENGTH_URL, - V3_MINOR_VERSION, -} from '../constants-internal'; +import {V3_MINOR_VERSION} from '../constants-internal'; +import {DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL} from '../constants'; const DEFAULT_HEADERS = { Accept: 'application/json', diff --git a/src/constants-internal.ts b/src/constants-internal.ts index 8392442..189c472 100644 --- a/src/constants-internal.ts +++ b/src/constants-internal.ts @@ -10,3 +10,15 @@ export const API_CLIENT_VERSION = __CARTO_API_CLIENT_VERSION; /** @internal */ export const V3_MINOR_VERSION = '3.4'; + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4; + +/** + * @internalRemarks Source: @deck.gl/carto + * @internal + */ +export const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6; diff --git a/src/models/model.ts b/src/models/model.ts index 08c2edd..7691e44 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -1,4 +1,4 @@ -import {DEFAULT_GEO_COLUMN} from '../constants-internal.js'; +import {DEFAULT_GEO_COLUMN} from '../constants.js'; import { Filter, FilterLogicalOperator, diff --git a/src/sources/index.ts b/src/sources/index.ts index 5d98b2b..4be769f 100644 --- a/src/sources/index.ts +++ b/src/sources/index.ts @@ -3,7 +3,12 @@ // Copyright (c) vis.gl contributors export {SOURCE_DEFAULTS} from './base-source'; -export type {TilejsonResult, GeojsonResult, JsonResult} from './types'; +export type { + TilejsonResult, + GeojsonResult, + JsonResult, + QueryResult, +} from './types'; export {boundaryQuerySource} from './boundary-query-source'; export type {BoundaryQuerySourceOptions} from './boundary-query-source'; diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index a680408..866dfba 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_TILE_RESOLUTION} from '../constants-internal'; +import {DEFAULT_TILE_RESOLUTION} from '../constants.js'; import {baseSource} from './base-source'; import type { FilterOptions, diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 868e2a1..3ed6e44 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_TILE_RESOLUTION} from '../constants-internal'; +import {DEFAULT_TILE_RESOLUTION} from '../constants.js'; import {baseSource} from './base-source'; import type { FilterOptions, diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 28be227..3ad4307 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -17,14 +17,14 @@ import { } from './types.js'; import {FilterLogicalOperator, Filter} from '../types.js'; import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; -import { - DEFAULT_API_BASE_URL, - DEFAULT_GEO_COLUMN, -} from '../constants-internal.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; import {SourceOptions} from '../sources/index.js'; -import {ApiVersion} from '../constants.js'; +import { + ApiVersion, + DEFAULT_API_BASE_URL, + DEFAULT_GEO_COLUMN, +} from '../constants.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; From cb91da7d753bdd889a2b2f1c6e7d7776062647b7 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 16:55:24 -0400 Subject: [PATCH 08/26] fixes for @deck.gl/carto --- src/api/endpoints.ts | 2 ++ src/api/index.ts | 2 ++ src/index.ts | 28 +++++++++++++++++++++++----- src/widget-sources/wrappers.ts | 1 - 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 50ecdf4..e36a178 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -21,6 +21,7 @@ function buildV3Path( return joinPath(apiBaseUrl, version, endpoint, ...rest); } +/** @internal Required by fetchMap(). */ export function buildPublicMapUrl({ apiBaseUrl, cartoMapId, @@ -31,6 +32,7 @@ export function buildPublicMapUrl({ return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId); } +/** @internal Required by fetchMap(). */ export function buildStatsUrl({ attribute, apiBaseUrl, diff --git a/src/api/index.ts b/src/api/index.ts index 729c3c4..4531ee2 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -7,6 +7,8 @@ export { 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'; diff --git a/src/index.ts b/src/index.ts index faefb44..46414f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,18 +8,36 @@ export * from './types.js'; export { APIErrorContext, APIRequestType, + CartoAPIError, QueryOptions, - requestWithParameters, + buildPublicMapUrl, // Internal, but required for fetchMap(). + buildStatsUrl, // Internal, but required for fetchMap(). query, + requestWithParameters, } from './api/index.js'; export { + GeojsonResult, + JsonResult, + QueryResult, + QuerySourceOptions, SOURCE_DEFAULTS, SourceOptions, TableSourceOptions, - QuerySourceOptions, - GeojsonResult, - JsonResult, TilejsonResult, - QueryResult, + TilesetSourceOptions, + + // Sources not wrapped in './widget-sources/index.js'; + BoundaryQuerySourceOptions, + BoundaryTableSourceOptions, + H3TilesetSourceOptions, + QuadbinTilesetSourceOptions, + RasterSourceOptions, + VectorTilesetSourceOptions, + boundaryQuerySource, + boundaryTableSource, + h3TilesetSource, + quadbinTilesetSource, + rasterSource, + vectorTilesetSource, } from './sources/index.js'; diff --git a/src/widget-sources/wrappers.ts b/src/widget-sources/wrappers.ts index e01dd64..8461f81 100644 --- a/src/widget-sources/wrappers.ts +++ b/src/widget-sources/wrappers.ts @@ -11,7 +11,6 @@ import { H3QuerySourceOptions as _H3QuerySourceOptions, QuadbinTableSourceOptions as _QuadbinTableSourceOptions, QuadbinQuerySourceOptions as _QuadbinQuerySourceOptions, - SourceOptions, } from '../sources/index.js'; import {WidgetBaseSourceProps} from './widget-base-source.js'; import {WidgetQuerySource} from './widget-query-source.js'; From 33a3a809c473a2ff1de1ab540a9da85f1e7aa66b Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 17:08:16 -0400 Subject: [PATCH 09/26] chore(release): v0.3.2-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa4f852..5a44827 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.0-alpha.0", + "version": "0.3.2-0", "license": "MIT", "publishConfig": { "access": "public", From bee725df340ccc2defa9942c44aef28f95a9863d Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 9 Oct 2024 17:08:41 -0400 Subject: [PATCH 10/26] chore(release): v0.4.0-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a44827..2f92ad8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.3.2-0", + "version": "0.4.0-alpha.1", "license": "MIT", "publishConfig": { "access": "public", From bb7e5adb19d7bea4026b7a344b4fb80cb826fca1 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 29 Oct 2024 15:39:25 -0400 Subject: [PATCH 11/26] remove wrappers.ts, add widget sources directly in sources --- src/index.ts | 24 +++-- src/sources/h3-query-source.ts | 14 +-- src/sources/h3-table-source.ts | 14 +-- src/sources/quadbin-query-source.ts | 14 +-- src/sources/quadbin-table-source.ts | 14 +-- src/sources/vector-query-source.ts | 17 +-- src/sources/vector-table-source.ts | 17 +-- src/widget-sources/index.ts | 1 - src/widget-sources/widget-query-source.ts | 2 + src/widget-sources/widget-table-source.ts | 2 + src/widget-sources/wrappers.ts | 120 ---------------------- 11 files changed, 75 insertions(+), 164 deletions(-) delete mode 100644 src/widget-sources/wrappers.ts diff --git a/src/index.ts b/src/index.ts index 46414f2..dda6dec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,27 +17,37 @@ export { } from './api/index.js'; export { + BoundaryQuerySourceOptions, + BoundaryTableSourceOptions, GeojsonResult, + H3QuerySourceOptions, + H3TableSourceOptions, + H3TilesetSourceOptions, JsonResult, + QuadbinQuerySourceOptions, + QuadbinTableSourceOptions, + QuadbinTilesetSourceOptions, QueryResult, QuerySourceOptions, + RasterSourceOptions, SOURCE_DEFAULTS, SourceOptions, TableSourceOptions, TilejsonResult, TilesetSourceOptions, - - // Sources not wrapped in './widget-sources/index.js'; - BoundaryQuerySourceOptions, - BoundaryTableSourceOptions, - H3TilesetSourceOptions, - QuadbinTilesetSourceOptions, - RasterSourceOptions, + VectorQuerySourceOptions, + VectorTableSourceOptions, VectorTilesetSourceOptions, boundaryQuerySource, boundaryTableSource, + h3QuerySource, + h3TableSource, h3TilesetSource, + quadbinQuerySource, + quadbinTableSource, quadbinTilesetSource, rasterSource, + vectorQuerySource, + vectorTableSource, vectorTilesetSource, } from './sources/index.js'; diff --git a/src/sources/h3-query-source.ts b/src/sources/h3-query-source.ts index 9b5095f..230b357 100644 --- a/src/sources/h3-query-source.ts +++ b/src/sources/h3-query-source.ts @@ -4,6 +4,7 @@ /* eslint-disable camelcase */ import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants-internal'; +import {WidgetQuerySource, WidgetQuerySourceResult} from '../widget-sources'; import {baseSource} from './base-source'; import type { AggregationOptions, @@ -30,7 +31,7 @@ type UrlParameters = { export const h3QuerySource = async function ( options: H3QuerySourceOptions -): Promise { +): Promise { const { aggregationExp, aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3, @@ -55,9 +56,10 @@ export const h3QuerySource = async function ( if (filters) { urlParameters.filters = filters; } - return baseSource( - 'query', - options, - urlParameters - ) as Promise; + return baseSource('query', options, urlParameters).then( + (result) => ({ + ...(result as TilejsonResult), + widgetSource: new WidgetQuerySource(options), + }) + ); }; diff --git a/src/sources/h3-table-source.ts b/src/sources/h3-table-source.ts index f2c9e6b..6bad8a9 100644 --- a/src/sources/h3-table-source.ts +++ b/src/sources/h3-table-source.ts @@ -4,6 +4,7 @@ /* eslint-disable camelcase */ import {DEFAULT_AGGREGATION_RES_LEVEL_H3} from '../constants-internal'; +import {WidgetTableSource, WidgetTableSourceResult} from '../widget-sources'; import {baseSource} from './base-source'; import type { AggregationOptions, @@ -30,7 +31,7 @@ type UrlParameters = { export const h3TableSource = async function ( options: H3TableSourceOptions -): Promise { +): Promise { const { aggregationExp, aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3, @@ -51,9 +52,10 @@ export const h3TableSource = async function ( if (filters) { urlParameters.filters = filters; } - return baseSource( - 'table', - options, - urlParameters - ) as Promise; + return baseSource('table', options, urlParameters).then( + (result) => ({ + ...(result as TilejsonResult), + widgetSource: new WidgetTableSource(options), + }) + ); }; diff --git a/src/sources/quadbin-query-source.ts b/src/sources/quadbin-query-source.ts index f1e603c..0e6ea7f 100644 --- a/src/sources/quadbin-query-source.ts +++ b/src/sources/quadbin-query-source.ts @@ -4,6 +4,7 @@ /* eslint-disable camelcase */ import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants-internal'; +import {WidgetQuerySource, WidgetQuerySourceResult} from '../widget-sources'; import {baseSource} from './base-source'; import type { AggregationOptions, @@ -31,7 +32,7 @@ type UrlParameters = { export const quadbinQuerySource = async function ( options: QuadbinQuerySourceOptions -): Promise { +): Promise { const { aggregationExp, aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN, @@ -56,9 +57,10 @@ export const quadbinQuerySource = async function ( if (filters) { urlParameters.filters = filters; } - return baseSource( - 'query', - options, - urlParameters - ) as Promise; + return baseSource('query', options, urlParameters).then( + (result) => ({ + ...(result as TilejsonResult), + widgetSource: new WidgetQuerySource(options), + }) + ); }; diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts index 3ab95c5..91b7a1c 100644 --- a/src/sources/quadbin-table-source.ts +++ b/src/sources/quadbin-table-source.ts @@ -4,6 +4,7 @@ /* eslint-disable camelcase */ import {DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN} from '../constants-internal'; +import {WidgetTableSource, WidgetTableSourceResult} from '../widget-sources'; import {baseSource} from './base-source'; import type { AggregationOptions, @@ -30,7 +31,7 @@ type UrlParameters = { export const quadbinTableSource = async function ( options: QuadbinTableSourceOptions -): Promise { +): Promise { const { aggregationExp, aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN, @@ -52,9 +53,10 @@ export const quadbinTableSource = async function ( if (filters) { urlParameters.filters = filters; } - return baseSource( - 'table', - options, - urlParameters - ) as Promise; + return baseSource('table', options, urlParameters).then( + (result) => ({ + ...(result as TilejsonResult), + widgetSource: new WidgetTableSource(options), + }) + ); }; diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 866dfba..3aa6ee7 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -4,6 +4,10 @@ /* eslint-disable camelcase */ import {DEFAULT_TILE_RESOLUTION} from '../constants.js'; +import { + WidgetQuerySource, + WidgetQuerySourceResult, +} from '../widget-sources/index.js'; import {baseSource} from './base-source'; import type { FilterOptions, @@ -31,7 +35,7 @@ type UrlParameters = { export const vectorQuerySource = async function ( options: VectorQuerySourceOptions -): Promise { +): Promise { const { columns, filters, @@ -57,9 +61,10 @@ export const vectorQuerySource = async function ( if (queryParameters) { urlParameters.queryParameters = queryParameters; } - return baseSource( - 'query', - options, - urlParameters - ) as Promise; + return baseSource('query', options, urlParameters).then( + (result) => ({ + ...(result as TilejsonResult), + widgetSource: new WidgetQuerySource(options), + }) + ); }; diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 3ed6e44..0795e9c 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -4,6 +4,10 @@ /* eslint-disable camelcase */ import {DEFAULT_TILE_RESOLUTION} from '../constants.js'; +import { + WidgetTableSource, + WidgetTableSourceResult, +} from '../widget-sources/index.js'; import {baseSource} from './base-source'; import type { FilterOptions, @@ -29,7 +33,7 @@ type UrlParameters = { export const vectorTableSource = async function ( options: VectorTableSourceOptions -): Promise { +): Promise { const { columns, filters, @@ -51,9 +55,10 @@ export const vectorTableSource = async function ( if (filters) { urlParameters.filters = filters; } - return baseSource( - 'table', - options, - urlParameters - ) as Promise; + return baseSource('table', options, urlParameters).then( + (result) => ({ + ...(result as TilejsonResult), + widgetSource: new WidgetTableSource(options), + }) + ); }; diff --git a/src/widget-sources/index.ts b/src/widget-sources/index.ts index 7bf51ee..ff13155 100644 --- a/src/widget-sources/index.ts +++ b/src/widget-sources/index.ts @@ -1,5 +1,4 @@ export * from './widget-base-source.js'; export * from './widget-query-source.js'; export * from './widget-table-source.js'; -export * from './wrappers.js'; export * from './types.js'; diff --git a/src/widget-sources/widget-query-source.ts b/src/widget-sources/widget-query-source.ts index 5b650f3..9c82f24 100644 --- a/src/widget-sources/widget-query-source.ts +++ b/src/widget-sources/widget-query-source.ts @@ -11,6 +11,8 @@ type LayerQuerySourceOptions = | Omit | Omit; +export type WidgetQuerySourceResult = {widgetSource: WidgetQuerySource}; + /** * Source for Widget API requests on a data source defined by a SQL query. * diff --git a/src/widget-sources/widget-table-source.ts b/src/widget-sources/widget-table-source.ts index f28f310..28dc5d1 100644 --- a/src/widget-sources/widget-table-source.ts +++ b/src/widget-sources/widget-table-source.ts @@ -11,6 +11,8 @@ type LayerTableSourceOptions = | Omit | Omit; +export type WidgetTableSourceResult = {widgetSource: WidgetTableSource}; + /** * Source for Widget API requests on a data source defined as a table. * diff --git a/src/widget-sources/wrappers.ts b/src/widget-sources/wrappers.ts deleted file mode 100644 index 8461f81..0000000 --- a/src/widget-sources/wrappers.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { - h3TableSource as _h3TableSource, - h3QuerySource as _h3QuerySource, - vectorTableSource as _vectorTableSource, - vectorQuerySource as _vectorQuerySource, - quadbinTableSource as _quadbinTableSource, - quadbinQuerySource as _quadbinQuerySource, - VectorTableSourceOptions as _VectorTableSourceOptions, - VectorQuerySourceOptions as _VectorQuerySourceOptions, - H3TableSourceOptions as _H3TableSourceOptions, - H3QuerySourceOptions as _H3QuerySourceOptions, - QuadbinTableSourceOptions as _QuadbinTableSourceOptions, - QuadbinQuerySourceOptions as _QuadbinQuerySourceOptions, -} from '../sources/index.js'; -import {WidgetBaseSourceProps} from './widget-base-source.js'; -import {WidgetQuerySource} from './widget-query-source.js'; -import {WidgetTableSource} from './widget-table-source.js'; - -type WrappedSourceOptions = Omit & WidgetBaseSourceProps; - -/****************************************************************************** - * RESPONSE OBJECTS - */ - -type WidgetTableSourceResponse = {widgetSource: WidgetTableSource}; -type WidgetQuerySourceResponse = {widgetSource: WidgetQuerySource}; - -export type VectorTableSourceResponse = WidgetTableSourceResponse & - Awaited>; -export type VectorQuerySourceResponse = WidgetQuerySourceResponse & - Awaited>; - -export type H3TableSourceResponse = WidgetTableSourceResponse & - Awaited>; -export type H3QuerySourceResponse = WidgetQuerySourceResponse & - Awaited>; - -export type QuadbinTableSourceResponse = WidgetTableSourceResponse & - Awaited>; -export type QuadbinQuerySourceResponse = WidgetQuerySourceResponse & - Awaited>; - -/****************************************************************************** - * VECTOR SOURCES - */ - -export type VectorTableSourceOptions = - WrappedSourceOptions<_VectorTableSourceOptions>; - -export type VectorQuerySourceOptions = - WrappedSourceOptions<_VectorQuerySourceOptions>; - -/** Wrapper adding Widget API support to [vectorTableSource](https://deck.gl/docs/api-reference/carto/data-sources). */ -export async function vectorTableSource( - props: VectorTableSourceOptions -): Promise { - const response = await _vectorTableSource(props as _VectorTableSourceOptions); - return {...response, widgetSource: new WidgetTableSource(props)}; -} - -/** Wrapper adding Widget API support to [vectorQuerySource](https://deck.gl/docs/api-reference/carto/data-sources). */ -export async function vectorQuerySource( - props: VectorQuerySourceOptions -): Promise { - const response = await _vectorQuerySource(props as _VectorQuerySourceOptions); - return {...response, widgetSource: new WidgetQuerySource(props)}; -} - -/****************************************************************************** - * H3 SOURCES - */ - -export type H3TableSourceOptions = WrappedSourceOptions<_H3TableSourceOptions>; -export type H3QuerySourceOptions = WrappedSourceOptions<_H3QuerySourceOptions>; - -/** Wrapper adding Widget API support to [h3TableSource](https://deck.gl/docs/api-reference/carto/data-sources). */ -export async function h3TableSource( - props: H3TableSourceOptions -): Promise { - const response = await _h3TableSource(props as _H3TableSourceOptions); - return {...response, widgetSource: new WidgetTableSource(props)}; -} - -/** Wrapper adding Widget API support to [h3QuerySource](https://deck.gl/docs/api-reference/carto/data-sources). */ -export async function h3QuerySource( - props: H3QuerySourceOptions -): Promise { - const response = await _h3QuerySource(props as _H3QuerySourceOptions); - return {...response, widgetSource: new WidgetQuerySource(props)}; -} - -/****************************************************************************** - * QUADBIN SOURCES - */ - -export type QuadbinTableSourceOptions = - WrappedSourceOptions<_QuadbinTableSourceOptions>; - -export type QuadbinQuerySourceOptions = - WrappedSourceOptions<_QuadbinQuerySourceOptions>; - -/** Wrapper adding Widget API support to [quadbinTableSource](https://deck.gl/docs/api-reference/carto/data-sources). */ -export async function quadbinTableSource( - props: QuadbinTableSourceOptions & WidgetBaseSourceProps -): Promise { - const response = await _quadbinTableSource( - props as _QuadbinTableSourceOptions - ); - return {...response, widgetSource: new WidgetTableSource(props)}; -} - -/** Wrapper adding Widget API support to [quadbinQuerySource](https://deck.gl/docs/api-reference/carto/data-sources). */ -export async function quadbinQuerySource( - props: QuadbinQuerySourceOptions & WidgetBaseSourceProps -): Promise { - const response = await _quadbinQuerySource( - props as _QuadbinQuerySourceOptions - ); - return {...response, widgetSource: new WidgetQuerySource(props)}; -} From b6ac088684171b011d0a409d166759cee458cc28 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 29 Oct 2024 17:09:10 -0400 Subject: [PATCH 12/26] migrate sources tests --- test/__mock-fetch.ts | 147 ++++++++++++++++++ test/data/binaryTile.json | 15 ++ test/sources/boundary-query-source.test.ts | 69 ++++++++ test/sources/boundary-table-source.test.ts | 67 ++++++++ test/sources/h3-query-source.test.ts | 79 ++++++++++ test/sources/h3-table-source.test.ts | 80 ++++++++++ test/sources/h3-tileset-source.test.ts | 63 ++++++++ test/sources/quadbin-query-source.test.ts | 78 ++++++++++ test/sources/quadbin-table-source.test.ts | 78 ++++++++++ test/sources/quadbin-tileset-source.test.ts | 63 ++++++++ test/sources/raster-source.test.ts | 63 ++++++++ test/sources/vector-query-source.test.ts | 82 ++++++++++ test/sources/vector-table-source.test.ts | 78 ++++++++++ test/sources/vector-tileset-source.test.ts | 63 ++++++++ test/sources/wrappers.test.ts | 124 --------------- .../widget-base-source.test.ts | 0 .../widget-query-source.test.ts | 0 .../widget-table-source.test.ts | 0 18 files changed, 1025 insertions(+), 124 deletions(-) create mode 100644 test/__mock-fetch.ts create mode 100644 test/data/binaryTile.json create mode 100644 test/sources/boundary-query-source.test.ts create mode 100644 test/sources/boundary-table-source.test.ts create mode 100644 test/sources/h3-query-source.test.ts create mode 100644 test/sources/h3-table-source.test.ts create mode 100644 test/sources/h3-tileset-source.test.ts create mode 100644 test/sources/quadbin-query-source.test.ts create mode 100644 test/sources/quadbin-table-source.test.ts create mode 100644 test/sources/quadbin-tileset-source.test.ts create mode 100644 test/sources/raster-source.test.ts create mode 100644 test/sources/vector-query-source.test.ts create mode 100644 test/sources/vector-table-source.test.ts create mode 100644 test/sources/vector-tileset-source.test.ts delete mode 100644 test/sources/wrappers.test.ts rename test/{sources => widget-sources}/widget-base-source.test.ts (100%) rename test/{sources => widget-sources}/widget-query-source.test.ts (100%) rename test/{sources => widget-sources}/widget-table-source.test.ts (100%) diff --git a/test/__mock-fetch.ts b/test/__mock-fetch.ts new file mode 100644 index 0000000..023ffd9 --- /dev/null +++ b/test/__mock-fetch.ts @@ -0,0 +1,147 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* global Headers */ + +// See test/modules/carto/responseToJson for details for creating test data +import binaryTileData from './data/binaryTile.json'; +const BINARY_TILE = new Uint8Array(binaryTileData).buffer; + +const fetch = globalThis.fetch; +type MockFetchCall = { + url: string; + headers: Record; + method?: 'GET' | 'POST'; + body?: string; +}; + +export const TILEJSON_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: { + layers: [ + { + attributes: [ + {attribute: 'population', type: 'integer'}, + {attribute: 'category', type: 'string'}, + ], + }, + ], + }, +}; + +export const GEOJSON_RESPONSE = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [-6.7531585693359375, 37.57505900514996], + }, + }, + ], +}; + +export const TILESTATS_RESPONSE = { + attribute: 'population', + avg: 10, + min: 1, + max: 20, + quantiles: [], + sum: 100, + type: 'Number', +}; + +export const QUERY_RESPONSE = [{id: 1, value: 'string'}]; + +const createDefaultResponse = ( + url: string, + headers: HeadersInit, + cacheKey?: string +): Promise => { + return Promise.resolve({ + json: () => { + if (url.indexOf('format=tilejson') !== -1) { + return TILEJSON_RESPONSE; + } + if (url.indexOf('format=geojson') !== -1) { + return GEOJSON_RESPONSE; + } + + if (url.indexOf('tileset') !== -1) { + return { + tilejson: { + url: [`https://xyz.com?format=tilejson&cache=${cacheKey}`], + }, + }; + } + if (url.indexOf('stats') !== -1) { + return TILESTATS_RESPONSE; + } + if (url.indexOf('sql') !== -1) { + return QUERY_RESPONSE; + } + if (url.indexOf('query') !== -1 || url.indexOf('table')) { + return { + tilejson: { + url: [`https://xyz.com?format=tilejson&cache=${cacheKey}`], + }, + geojson: { + url: [`https://xyz.com?format=geojson&cache=${cacheKey}`], + }, + }; + } + return null; + }, + arrayBuffer: () => BINARY_TILE, + text: () => null, // Required to get loaders.gl to use arrayBuffer() + ok: true, + url, + headers: new Headers(headers), + }); +}; + +async function setupMockFetchMapsV3( + responseFunc = createDefaultResponse, + cacheKey = btoa(Math.random().toFixed(4)) +): Promise { + const calls: MockFetchCall[] = []; + + const mockFetch = (url: string, {headers, method, body}) => { + calls.push({url, headers, method, body}); + if (url.indexOf('formatTiles=binary') !== -1) { + headers = { + ...headers, + 'Content-Type': 'application/vnd.carto-vector-tile', + }; + } + return responseFunc(url, headers, cacheKey); + }; + + globalThis.fetch = mockFetch as unknown as typeof fetch; + + return calls; +} + +function teardownMockFetchMaps() { + globalThis.fetch = fetch; +} + +export async function withMockFetchMapsV3( + testFunc: (calls: MockFetchCall[]) => Promise, + responseFunc: ( + url: string, + headers: HeadersInit, + cacheKey?: string + ) => Promise = createDefaultResponse +): Promise { + try { + const calls = await setupMockFetchMapsV3(responseFunc); + await testFunc(calls); + } finally { + teardownMockFetchMaps(); + } +} diff --git a/test/data/binaryTile.json b/test/data/binaryTile.json new file mode 100644 index 0000000..6e1c793 --- /dev/null +++ b/test/data/binaryTile.json @@ -0,0 +1,15 @@ +[ + 10, 12, 10, 2, 16, 2, 18, 2, 16, 1, 26, 2, 16, 1, 18, 19, 10, 2, 16, 2, 18, 5, + 10, 1, 0, 16, 1, 26, 2, 16, 1, 34, 2, 16, 1, 26, 223, 1, 10, 116, 10, 112, 0, + 0, 0, 192, 43, 138, 82, 192, 0, 0, 0, 160, 231, 68, 68, 64, 0, 0, 0, 64, 43, + 138, 82, 192, 0, 0, 0, 0, 232, 68, 68, 64, 0, 0, 0, 160, 40, 138, 82, 192, 0, + 0, 0, 192, 226, 68, 68, 64, 0, 0, 0, 0, 37, 138, 82, 192, 0, 0, 0, 96, 230, + 68, 68, 64, 0, 0, 0, 64, 41, 138, 82, 192, 0, 0, 0, 64, 239, 68, 68, 64, 0, 0, + 0, 64, 45, 138, 82, 192, 0, 0, 0, 0, 235, 68, 68, 64, 0, 0, 0, 192, 43, 138, + 82, 192, 0, 0, 0, 160, 231, 68, 68, 64, 16, 2, 18, 6, 10, 2, 0, 7, 16, 1, 26, + 11, 10, 7, 0, 0, 0, 0, 0, 0, 0, 16, 1, 34, 11, 10, 7, 0, 0, 0, 0, 0, 0, 0, 16, + 1, 42, 6, 10, 2, 0, 7, 16, 1, 50, 16, 10, 12, 5, 0, 1, 1, 2, 3, 3, 4, 5, 5, 1, + 3, 16, 1, 58, 43, 10, 22, 10, 7, 97, 100, 100, 114, 101, 115, 115, 18, 11, 52, + 32, 80, 65, 82, 75, 32, 82, 79, 65, 68, 10, 17, 10, 3, 98, 98, 108, 18, 10, + 53, 48, 53, 50, 54, 56, 48, 50, 54, 52 +] diff --git a/test/sources/boundary-query-source.test.ts b/test/sources/boundary-query-source.test.ts new file mode 100644 index 0000000..ef43b1b --- /dev/null +++ b/test/sources/boundary-query-source.test.ts @@ -0,0 +1,69 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {boundaryQuerySource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'boundary-query-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('boundaryQuerySource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await boundaryQuerySource({ + connectionName: 'carto_dw', + accessToken: '', + tilesetTableName: 'a.b.tileset_table', + columns: ['column1', 'column2'], + propertiesSqlQuery: 'select * from `a.b.properties_table`', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/boundary/); + expect(initURL).toMatch(/tilesetTableName=a.b.tileset_table/); + expect(initURL).toMatch( + /propertiesSqlQuery=select\+\*\+from\+%60a.b.properties_table%60/ + ); + expect(initURL).toMatch(/columns=column1%2Ccolumn2/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); +}); diff --git a/test/sources/boundary-table-source.test.ts b/test/sources/boundary-table-source.test.ts new file mode 100644 index 0000000..9830288 --- /dev/null +++ b/test/sources/boundary-table-source.test.ts @@ -0,0 +1,67 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {boundaryTableSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'boundary-table-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('boundaryTableSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await boundaryTableSource({ + connectionName: 'carto_dw', + accessToken: '', + tilesetTableName: 'a.b.tileset_table', + columns: ['column1', 'column2'], + propertiesTableName: 'a.b.properties_table', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/boundary/); + expect(initURL).toMatch(/tilesetTableName=a.b.tileset_table/); + expect(initURL).toMatch(/propertiesTableName=a.b.properties_table/); + expect(initURL).toMatch(/columns=column1%2Ccolumn2/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); +}); diff --git a/test/sources/h3-query-source.test.ts b/test/sources/h3-query-source.test.ts new file mode 100644 index 0000000..c09380a --- /dev/null +++ b/test/sources/h3-query-source.test.ts @@ -0,0 +1,79 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {WidgetQuerySource, h3QuerySource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'h3-query-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('h3QuerySource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + test('default', async () => { + const tilejson = await h3QuerySource({ + connectionName: 'carto_dw', + clientId: 'CUSTOM_CLIENT', + accessToken: '', + sqlQuery: 'SELECT * FROM a.b.h3_table', + aggregationExp: 'SUM(population) as pop', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/query/); + expect(initURL).toMatch(/aggregationExp=SUM%28population%29\+as\+pop/); + expect(initURL).toMatch(/spatialDataColumn=h3/); + expect(initURL).toMatch(/spatialDataType=h3/); + expect(initURL).toMatch(/q=SELECT\+\*\+FROM\+a\.b\.h3_table/); + expect(initURL).toMatch(/client\=CUSTOM_CLIENT/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); + + test('widgetSource', async () => { + const {widgetSource} = await h3QuerySource({ + accessToken: '', + connectionName: 'carto_dw', + sqlQuery: 'SELECT *', + aggregationExp: 'COUNT (*)', + }); + + expect(widgetSource).toBeInstanceOf(WidgetQuerySource); + }); +}); diff --git a/test/sources/h3-table-source.test.ts b/test/sources/h3-table-source.test.ts new file mode 100644 index 0000000..dd37abe --- /dev/null +++ b/test/sources/h3-table-source.test.ts @@ -0,0 +1,80 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {WidgetTableSource, h3TableSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'h3-table-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('h3TableSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await h3TableSource({ + connectionName: 'carto_dw', + clientId: 'CUSTOM_CLIENT', + accessToken: '', + tableName: 'a.b.h3_table', + aggregationExp: 'SUM(population) as pop', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/table/); + expect(initURL).toMatch(/aggregationExp=SUM%28population%29\+as\+pop/); + expect(initURL).toMatch(/spatialDataColumn=h3/); + expect(initURL).toMatch(/spatialDataType=h3/); + expect(initURL).toMatch(/name=a.b.h3_table/); + expect(initURL).toMatch(/client\=CUSTOM_CLIENT/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); + + test('widgetSource', async () => { + const {widgetSource} = await h3TableSource({ + accessToken: '', + connectionName: 'carto_dw', + tableName: 'my-table', + aggregationExp: 'COUNT (*)', + }); + + expect(widgetSource).toBeInstanceOf(WidgetTableSource); + }); +}); diff --git a/test/sources/h3-tileset-source.test.ts b/test/sources/h3-tileset-source.test.ts new file mode 100644 index 0000000..dfe7d90 --- /dev/null +++ b/test/sources/h3-tileset-source.test.ts @@ -0,0 +1,63 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {h3TilesetSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'h3-tileset-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('h3TilesetSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await h3TilesetSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.h3_tileset', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/tileset/); + expect(initURL).toMatch(/name=a.b.h3_tileset/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); +}); diff --git a/test/sources/quadbin-query-source.test.ts b/test/sources/quadbin-query-source.test.ts new file mode 100644 index 0000000..cfe315e --- /dev/null +++ b/test/sources/quadbin-query-source.test.ts @@ -0,0 +1,78 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {WidgetQuerySource, quadbinQuerySource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'quadbin-query-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('quadbinQuerySource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await quadbinQuerySource({ + connectionName: 'carto_dw', + accessToken: '', + sqlQuery: 'SELECT * FROM a.b.quadbin_table', + aggregationExp: 'SUM(population) as pop', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/query/); + expect(initURL).toMatch(/aggregationExp=SUM%28population%29\+as\+pop/); + expect(initURL).toMatch(/spatialDataColumn=quadbin/); + expect(initURL).toMatch(/spatialDataType=quadbin/); + expect(initURL).toMatch(/q=SELECT\+\*\+FROM\+a\.b\.quadbin_table/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); + + test('widgetSource', async () => { + const {widgetSource} = await quadbinQuerySource({ + accessToken: '', + connectionName: 'carto_dw', + sqlQuery: 'SELECT *', + aggregationExp: 'COUNT (*)', + }); + + expect(widgetSource).toBeInstanceOf(WidgetQuerySource); + }); +}); diff --git a/test/sources/quadbin-table-source.test.ts b/test/sources/quadbin-table-source.test.ts new file mode 100644 index 0000000..cad9c70 --- /dev/null +++ b/test/sources/quadbin-table-source.test.ts @@ -0,0 +1,78 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {WidgetTableSource, quadbinTableSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'quadbin-table-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('quadbinTableSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await quadbinTableSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.quadbin_table', + aggregationExp: 'SUM(population) as pop', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/table/); + expect(initURL).toMatch(/aggregationExp=SUM%28population%29\+as\+pop/); + expect(initURL).toMatch(/spatialDataColumn=quadbin/); + expect(initURL).toMatch(/spatialDataType=quadbin/); + expect(initURL).toMatch(/name=a.b.quadbin_table/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); + + test('widgetSource', async () => { + const {widgetSource} = await quadbinTableSource({ + accessToken: '', + connectionName: 'carto_dw', + tableName: 'my-table', + aggregationExp: 'COUNT (*)', + }); + + expect(widgetSource).toBeInstanceOf(WidgetTableSource); + }); +}); diff --git a/test/sources/quadbin-tileset-source.test.ts b/test/sources/quadbin-tileset-source.test.ts new file mode 100644 index 0000000..c2bd6ee --- /dev/null +++ b/test/sources/quadbin-tileset-source.test.ts @@ -0,0 +1,63 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {quadbinTilesetSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'quadbin-tileset-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('quadbinTilesetSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await quadbinTilesetSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.quadbin_tileset', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/tileset/); + expect(initURL).toMatch(/name=a.b.quadbin_tileset/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); +}); diff --git a/test/sources/raster-source.test.ts b/test/sources/raster-source.test.ts new file mode 100644 index 0000000..6d19c60 --- /dev/null +++ b/test/sources/raster-source.test.ts @@ -0,0 +1,63 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {rasterSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'vector-tileset-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('rasterSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await rasterSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.raster_table', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/raster/); + expect(initURL).toMatch(/name=a\.b\.raster_table/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); +}); diff --git a/test/sources/vector-query-source.test.ts b/test/sources/vector-query-source.test.ts new file mode 100644 index 0000000..4ec9497 --- /dev/null +++ b/test/sources/vector-query-source.test.ts @@ -0,0 +1,82 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {WidgetQuerySource, vectorQuerySource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'vector-query-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('vectorQuerySource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await vectorQuerySource({ + connectionName: 'carto_dw', + accessToken: '', + sqlQuery: 'SELECT * FROM a.b.vector_table', + columns: ['a', 'b'], + spatialDataColumn: 'mygeom', + queryParameters: {type: 'Supermarket', minRevenue: 1000000}, + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/query/); + expect(initURL).toMatch(/q=SELECT\+\*\+FROM\+a\.b\.vector_table/); + expect(initURL).toMatch(/columns=a%2Cb/); + expect(initURL).toMatch(/spatialDataColumn=mygeom/); + expect(initURL).toMatch(/spatialDataType=geo/); + expect(initURL).toMatch( + /queryParameters=%7B%22type%22%3A%22Supermarket%22%2C%22minRevenue%22%3A1000000%7D/ + ); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); + + test('widgetSource', async () => { + const {widgetSource} = await vectorQuerySource({ + accessToken: '', + connectionName: 'carto_dw', + sqlQuery: 'SELECT *', + }); + + expect(widgetSource).toBeInstanceOf(WidgetQuerySource); + }); +}); diff --git a/test/sources/vector-table-source.test.ts b/test/sources/vector-table-source.test.ts new file mode 100644 index 0000000..bf1436b --- /dev/null +++ b/test/sources/vector-table-source.test.ts @@ -0,0 +1,78 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {WidgetTableSource, vectorTableSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'vector-table-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('vectorTableSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await vectorTableSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.vector_table', + columns: ['a', 'b'], + spatialDataColumn: 'mygeom', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/table/); + expect(initURL).toMatch(/name=a\.b\.vector_table/); + expect(initURL).toMatch(/columns=a%2Cb/); + expect(initURL).toMatch(/spatialDataColumn=mygeom/); + expect(initURL).toMatch(/spatialDataType=geo/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); + + test('widgetSource', async () => { + const {widgetSource} = await vectorTableSource({ + accessToken: '', + connectionName: 'carto_dw', + tableName: 'my-table', + }); + + expect(widgetSource).toBeInstanceOf(WidgetTableSource); + }); +}); diff --git a/test/sources/vector-tileset-source.test.ts b/test/sources/vector-tileset-source.test.ts new file mode 100644 index 0000000..41f1deb --- /dev/null +++ b/test/sources/vector-tileset-source.test.ts @@ -0,0 +1,63 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {vectorTilesetSource} from '@carto/api-client'; +import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; + +const CACHE = 'vector-tileset-source-test'; + +const INIT_RESPONSE = { + tilejson: {url: [`https://xyz.com?format=tilejson&cache=${CACHE}`]}, +}; + +const TILESET_RESPONSE = { + tilejson: '2.2.0', + tiles: ['https://xyz.com/{z}/{x}/{y}?formatTiles=binary'], + tilestats: {layers: []}, +}; + +describe('vectorTilesetSource', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValueOnce( + Promise.resolve({ok: true, json: () => Promise.resolve(INIT_RESPONSE)}) + ) + .mockReturnValueOnce( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(TILESET_RESPONSE), + }) + ); + + vi.stubGlobal('fetch', mockFetch); + }); + + afterEach(() => void vi.restoreAllMocks()); + + test('default', async () => { + const tilejson = await vectorTilesetSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.vector_tileset', + }); + + expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); + + const [[initURL], [tilesetURL]] = vi.mocked(fetch).mock.calls; + + expect(initURL).toMatch(/v3\/maps\/carto_dw\/tileset/); + expect(initURL).toMatch(/name=a\.b\.vector_tileset/); + + expect(tilesetURL).toMatch( + /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ + ); + + expect(tilejson).toBeTruthy(); + expect(tilejson.tiles).toEqual([ + 'https://xyz.com/{z}/{x}/{y}?formatTiles=binary', + ]); + expect(tilejson.accessToken).toBe(''); + }); +}); diff --git a/test/sources/wrappers.test.ts b/test/sources/wrappers.test.ts deleted file mode 100644 index 1f165b1..0000000 --- a/test/sources/wrappers.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import {afterEach, expect, test, vi} from 'vitest'; -import { - vectorQuerySource, - vectorTableSource, - h3QuerySource, - h3TableSource, - quadbinQuerySource, - quadbinTableSource, - WidgetQuerySource, - WidgetTableSource, -} from '@carto/api-client'; - -const createMockFetchForTileJSON = () => - vi - .fn() - .mockResolvedValueOnce( - createMockFetchResponse({ - label: 'mapInit', - tilejson: {url: ['https://example.com']}, - }) - ) - .mockResolvedValueOnce( - createMockFetchResponse({ - label: 'tilejson', - tilejson: {url: ['https://example.com']}, - }) - ); - -const createMockFetchResponse = (data: unknown) => ({ - ok: true, - json: () => new Promise((resolve) => resolve(data)), -}); - -afterEach(() => { - vi.unstubAllGlobals(); -}); - -/****************************************************************************** - * VECTOR SOURCES - */ - -test('vectorQuerySource', async () => { - vi.stubGlobal('fetch', createMockFetchForTileJSON()); - - const {widgetSource} = await vectorQuerySource({ - accessToken: '', - connectionName: 'carto_dw', - sqlQuery: 'SELECT *', - }); - - expect(widgetSource).toBeInstanceOf(WidgetQuerySource); -}); - -test('vectorTableSource', async () => { - vi.stubGlobal('fetch', createMockFetchForTileJSON()); - - const {widgetSource} = await vectorTableSource({ - accessToken: '', - connectionName: 'carto_dw', - tableName: 'my-table', - }); - - expect(widgetSource).toBeInstanceOf(WidgetTableSource); -}); - -/****************************************************************************** - * H3 SOURCES - */ - -test('h3QuerySource', async () => { - vi.stubGlobal('fetch', createMockFetchForTileJSON()); - - const {widgetSource} = await h3QuerySource({ - accessToken: '', - connectionName: 'carto_dw', - sqlQuery: 'SELECT *', - aggregationExp: 'COUNT (*)', - }); - - expect(widgetSource).toBeInstanceOf(WidgetQuerySource); -}); - -test('h3TableSource', async () => { - vi.stubGlobal('fetch', createMockFetchForTileJSON()); - - const {widgetSource} = await h3TableSource({ - accessToken: '', - connectionName: 'carto_dw', - tableName: 'my-table', - aggregationExp: 'COUNT (*)', - }); - - expect(widgetSource).toBeInstanceOf(WidgetTableSource); -}); - -/****************************************************************************** - * QUADBIN SOURCES - */ - -test('quadbinQuerySource', async () => { - vi.stubGlobal('fetch', createMockFetchForTileJSON()); - - const {widgetSource} = await quadbinQuerySource({ - accessToken: '', - connectionName: 'carto_dw', - sqlQuery: 'SELECT *', - aggregationExp: 'COUNT (*)', - }); - - expect(widgetSource).toBeInstanceOf(WidgetQuerySource); -}); - -test('quadbinTableSource', async () => { - vi.stubGlobal('fetch', createMockFetchForTileJSON()); - - const {widgetSource} = await quadbinTableSource({ - accessToken: '', - connectionName: 'carto_dw', - tableName: 'my-table', - aggregationExp: 'COUNT (*)', - }); - - expect(widgetSource).toBeInstanceOf(WidgetTableSource); -}); diff --git a/test/sources/widget-base-source.test.ts b/test/widget-sources/widget-base-source.test.ts similarity index 100% rename from test/sources/widget-base-source.test.ts rename to test/widget-sources/widget-base-source.test.ts diff --git a/test/sources/widget-query-source.test.ts b/test/widget-sources/widget-query-source.test.ts similarity index 100% rename from test/sources/widget-query-source.test.ts rename to test/widget-sources/widget-query-source.test.ts diff --git a/test/sources/widget-table-source.test.ts b/test/widget-sources/widget-table-source.test.ts similarity index 100% rename from test/sources/widget-table-source.test.ts rename to test/widget-sources/widget-table-source.test.ts From c7a7c7eeb3365011c0c89d9b7a3a569b62cb9655 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 29 Oct 2024 17:49:18 -0400 Subject: [PATCH 13/26] migrate requestWithParameters tests --- test/api/request-with-parameters.test.ts | 289 ++++++++++++++++++++ test/data/binaryTile.json | 15 - test/sources/boundary-query-source.test.ts | 4 - test/sources/boundary-table-source.test.ts | 4 - test/sources/h3-query-source.test.ts | 4 - test/sources/h3-table-source.test.ts | 4 - test/sources/h3-tileset-source.test.ts | 4 - test/sources/quadbin-query-source.test.ts | 4 - test/sources/quadbin-table-source.test.ts | 4 - test/sources/quadbin-tileset-source.test.ts | 4 - test/sources/raster-source.test.ts | 4 - test/sources/vector-query-source.test.ts | 4 - test/sources/vector-table-source.test.ts | 4 - test/sources/vector-tileset-source.test.ts | 4 - 14 files changed, 289 insertions(+), 63 deletions(-) create mode 100644 test/api/request-with-parameters.test.ts delete mode 100644 test/data/binaryTile.json diff --git a/test/api/request-with-parameters.test.ts b/test/api/request-with-parameters.test.ts new file mode 100644 index 0000000..7e34fec --- /dev/null +++ b/test/api/request-with-parameters.test.ts @@ -0,0 +1,289 @@ +import { + describe, + test, + expect, + vi, + afterEach, + beforeEach, + assert, +} from 'vitest'; +import { + APIRequestType, + CartoAPIError, + requestWithParameters, +} from '@carto/api-client'; + +const errorContext = {requestType: 'test' as APIRequestType}; + +describe('requestWithParameters', () => { + beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValue( + Promise.resolve({ok: true, json: () => Promise.resolve({data: 12345})}) + ); + + vi.stubGlobal('fetch', mockFetch); + vi.stubGlobal('deck', {VERSION: 'untranspiled source'}); + }); + afterEach(() => void vi.restoreAllMocks()); + + test('cache baseURL', async () => { + const mockFetch = vi.mocked(fetch); + + expect(mockFetch).not.toHaveBeenCalled(); + + await Promise.all([ + requestWithParameters({ + baseUrl: 'https://example.com/v1/baseURL', + headers: {}, + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v2/baseURL', + headers: {}, + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v2/baseURL', + headers: {}, + errorContext, + }), + ]); + + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + test('cache headers', async () => { + const mockFetch = vi.mocked(fetch); + + expect(mockFetch).not.toHaveBeenCalled(); + + await Promise.all([ + requestWithParameters({ + baseUrl: 'https://example.com/v1/headers', + headers: {key: '1'}, + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v1/headers', + headers: {key: '1'}, + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v1/headers', + headers: {key: '2'}, + errorContext, + }), + ]); + + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + test('cache parameters', async () => { + const mockFetch = vi.mocked(fetch); + + expect(mockFetch).not.toHaveBeenCalled(); + + await Promise.all([ + requestWithParameters({ + baseUrl: 'https://example.com/v1/params', + headers: {}, + parameters: {}, + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v1/params', + headers: {}, + parameters: {}, + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v1/params', + headers: {}, + parameters: {a: 1}, + errorContext, + }), + ]); + + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + test('no cache error context', async () => { + const mockFetch = vi + .mocked(fetch) + .mockReset() + .mockReturnValue( + // @ts-ignore + Promise.resolve({ + ok: false, + json: () => + Promise.resolve({error: 'CustomError', customData: {abc: 'def'}}), + }) + ); + + expect(mockFetch).not.toHaveBeenCalled(); + + let error1: Error | undefined; + let error2: Error | undefined; + + try { + await requestWithParameters({ + baseUrl: 'https://example.com/v1/errorContext', + errorContext: {requestType: 'Map data'}, + }); + assert.fail('request #1 should fail, but did not'); + } catch (error) { + error1 = error as Error; + } + + try { + await requestWithParameters({ + baseUrl: 'https://example.com/v1/errorContext', + errorContext: {requestType: 'SQL'}, + }); + assert.fail('request #2 should fail, but did not'); + } catch (error) { + error2 = error as Error; + } + + // 2 unique requests, failures not cached + expect(mockFetch).toHaveBeenCalledTimes(2); + + expect((error1 as CartoAPIError).responseJson).toMatchObject({ + error: 'CustomError', + customData: {abc: 'def'}, + }); + + expect(error1 instanceof CartoAPIError).toBeTruthy(); + expect((error1 as CartoAPIError).errorContext.requestType).toBe('Map data'); + expect(error2 instanceof CartoAPIError).toBeTruthy(); + expect((error2 as CartoAPIError).errorContext.requestType).toBe('SQL'); + }); + + test('method GET or POST', async () => { + const mockFetch = vi.mocked(fetch); + + expect(mockFetch).not.toHaveBeenCalled(); + + await Promise.all([ + requestWithParameters({ + baseUrl: 'https://example.com/v1/params', + headers: {}, + parameters: {object: {a: 1, b: 2}, array: [1, 2, 3], string: 'short'}, + errorContext, + }), + requestWithParameters({ + baseUrl: `https://example.com/v1/params`, + headers: {}, + parameters: { + object: {a: 1, b: 2}, + array: [1, 2, 3], + string: 'long'.padEnd(10_000, 'g'), + }, + errorContext, + }), + ]); + + expect(mockFetch).toHaveBeenCalledTimes(2); + + const calls = mockFetch.mock.calls; + + // GET + expect(calls[0][0]).toMatch(/^https:\/\/example\.com\/v1\/params\?/); + expect(calls[0][1].method).toBe(undefined); + expect(calls[0][1].body).toBe(undefined); + expect( + Array.from(new URL(calls[0][0] as string).searchParams.entries()) + ).toEqual([ + ['v', '3.4'], + ['clientId', 'deck-gl-carto'], + ['deckglVersion', 'untranspiled source'], + ['object', '{"a":1,"b":2}'], + ['array', '[1,2,3]'], + ['string', 'short'], + ]); + + // POST + const postBody = JSON.parse(calls[1][1].body as string); + expect(calls[1][1].method).toBe('POST'); + expect(postBody.v).toBe('3.4'); + expect(postBody.deckglVersion).toBe('untranspiled source'); + expect(postBody.object).toEqual({a: 1, b: 2}); + expect(postBody.array).toEqual([1, 2, 3]); + expect(postBody.string).toMatch(/^longgg/); + expect(calls[1][0]).toBe('https://example.com/v1/params'); + }); + + test('parameter precedence', async () => { + const mockFetch = vi.mocked(fetch); + + expect(mockFetch).not.toHaveBeenCalled(); + + await Promise.all([ + requestWithParameters({ + baseUrl: 'https://example.com/v1/params?test=1', + headers: {}, + parameters: {}, + errorContext, + }), + requestWithParameters({ + baseUrl: `https://example.com/v1/params?test=2&v=3.0`, + headers: {}, + parameters: {}, + errorContext, + }), + requestWithParameters({ + baseUrl: `https://example.com/v1/params?test=3&v=3.0`, + headers: {}, + parameters: {v: '3.2'}, + errorContext, + }), + ]); + + expect(mockFetch).toHaveBeenCalledTimes(3); + + const calls = mockFetch.mock.calls; + const [url1, url2, url3] = calls.map((call) => new URL(call[0] as string)); + + expect(url1.searchParams.get('v')).toBe('3.4'); // unset + expect(url2.searchParams.get('v')).toBe('3.4'); // default overrides url + expect(url3.searchParams.get('v')).toBe('3.2'); // param overrides default + }); + + test('maxLengthURL', async (t) => { + const mockFetch = vi.mocked(fetch); + + expect(mockFetch).not.toHaveBeenCalled(); + + await Promise.all([ + requestWithParameters({ + baseUrl: 'https://example.com/v1/item/1', + errorContext, + }), + requestWithParameters({ + baseUrl: 'https://example.com/v1/item/2', + maxLengthURL: 10, + errorContext, + }), + requestWithParameters({ + baseUrl: `https://example.com/v1/item/3`, + parameters: {content: 'long'.padEnd(10_000, 'g')}, // > default limit + errorContext, + }), + requestWithParameters({ + baseUrl: `https://example.com/v1/item/4`, + parameters: {content: 'long'.padEnd(10_000, 'g')}, + maxLengthURL: 15_000, + errorContext, + }), + ]); + + expect(mockFetch).toHaveBeenCalledTimes(4); + + const calls = mockFetch.mock.calls; + const methods = calls.map(([_, {method}]) => method ?? 'GET'); + + expect(methods).toEqual(['GET', 'POST', 'POST', 'GET']); + }); +}); diff --git a/test/data/binaryTile.json b/test/data/binaryTile.json deleted file mode 100644 index 6e1c793..0000000 --- a/test/data/binaryTile.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - 10, 12, 10, 2, 16, 2, 18, 2, 16, 1, 26, 2, 16, 1, 18, 19, 10, 2, 16, 2, 18, 5, - 10, 1, 0, 16, 1, 26, 2, 16, 1, 34, 2, 16, 1, 26, 223, 1, 10, 116, 10, 112, 0, - 0, 0, 192, 43, 138, 82, 192, 0, 0, 0, 160, 231, 68, 68, 64, 0, 0, 0, 64, 43, - 138, 82, 192, 0, 0, 0, 0, 232, 68, 68, 64, 0, 0, 0, 160, 40, 138, 82, 192, 0, - 0, 0, 192, 226, 68, 68, 64, 0, 0, 0, 0, 37, 138, 82, 192, 0, 0, 0, 96, 230, - 68, 68, 64, 0, 0, 0, 64, 41, 138, 82, 192, 0, 0, 0, 64, 239, 68, 68, 64, 0, 0, - 0, 64, 45, 138, 82, 192, 0, 0, 0, 0, 235, 68, 68, 64, 0, 0, 0, 192, 43, 138, - 82, 192, 0, 0, 0, 160, 231, 68, 68, 64, 16, 2, 18, 6, 10, 2, 0, 7, 16, 1, 26, - 11, 10, 7, 0, 0, 0, 0, 0, 0, 0, 16, 1, 34, 11, 10, 7, 0, 0, 0, 0, 0, 0, 0, 16, - 1, 42, 6, 10, 2, 0, 7, 16, 1, 50, 16, 10, 12, 5, 0, 1, 1, 2, 3, 3, 4, 5, 5, 1, - 3, 16, 1, 58, 43, 10, 22, 10, 7, 97, 100, 100, 114, 101, 115, 115, 18, 11, 52, - 32, 80, 65, 82, 75, 32, 82, 79, 65, 68, 10, 17, 10, 3, 98, 98, 108, 18, 10, - 53, 48, 53, 50, 54, 56, 48, 50, 54, 52 -] diff --git a/test/sources/boundary-query-source.test.ts b/test/sources/boundary-query-source.test.ts index ef43b1b..480af3f 100644 --- a/test/sources/boundary-query-source.test.ts +++ b/test/sources/boundary-query-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {boundaryQuerySource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/boundary-table-source.test.ts b/test/sources/boundary-table-source.test.ts index 9830288..785e3eb 100644 --- a/test/sources/boundary-table-source.test.ts +++ b/test/sources/boundary-table-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {boundaryTableSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/h3-query-source.test.ts b/test/sources/h3-query-source.test.ts index c09380a..9fd1993 100644 --- a/test/sources/h3-query-source.test.ts +++ b/test/sources/h3-query-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {WidgetQuerySource, h3QuerySource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/h3-table-source.test.ts b/test/sources/h3-table-source.test.ts index dd37abe..17866b6 100644 --- a/test/sources/h3-table-source.test.ts +++ b/test/sources/h3-table-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {WidgetTableSource, h3TableSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/h3-tileset-source.test.ts b/test/sources/h3-tileset-source.test.ts index dfe7d90..f7373fb 100644 --- a/test/sources/h3-tileset-source.test.ts +++ b/test/sources/h3-tileset-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {h3TilesetSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/quadbin-query-source.test.ts b/test/sources/quadbin-query-source.test.ts index cfe315e..3b3555a 100644 --- a/test/sources/quadbin-query-source.test.ts +++ b/test/sources/quadbin-query-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {WidgetQuerySource, quadbinQuerySource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/quadbin-table-source.test.ts b/test/sources/quadbin-table-source.test.ts index cad9c70..e16ea99 100644 --- a/test/sources/quadbin-table-source.test.ts +++ b/test/sources/quadbin-table-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {WidgetTableSource, quadbinTableSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/quadbin-tileset-source.test.ts b/test/sources/quadbin-tileset-source.test.ts index c2bd6ee..d1d2963 100644 --- a/test/sources/quadbin-tileset-source.test.ts +++ b/test/sources/quadbin-tileset-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {quadbinTilesetSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/raster-source.test.ts b/test/sources/raster-source.test.ts index 6d19c60..d602bd0 100644 --- a/test/sources/raster-source.test.ts +++ b/test/sources/raster-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {rasterSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/vector-query-source.test.ts b/test/sources/vector-query-source.test.ts index 4ec9497..4e18e47 100644 --- a/test/sources/vector-query-source.test.ts +++ b/test/sources/vector-query-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {WidgetQuerySource, vectorQuerySource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/vector-table-source.test.ts b/test/sources/vector-table-source.test.ts index bf1436b..45665aa 100644 --- a/test/sources/vector-table-source.test.ts +++ b/test/sources/vector-table-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {WidgetTableSource, vectorTableSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; diff --git a/test/sources/vector-tileset-source.test.ts b/test/sources/vector-tileset-source.test.ts index 41f1deb..1050e01 100644 --- a/test/sources/vector-tileset-source.test.ts +++ b/test/sources/vector-tileset-source.test.ts @@ -1,7 +1,3 @@ -// deck.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - import {vectorTilesetSource} from '@carto/api-client'; import {describe, afterEach, vi, test, expect, beforeEach} from 'vitest'; From dc718da01bfc63662c564dae2fd88ccfef79ece9 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 29 Oct 2024 17:54:07 -0400 Subject: [PATCH 14/26] migrate query tests --- test/api/query.test.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/api/query.test.ts diff --git a/test/api/query.test.ts b/test/api/query.test.ts new file mode 100644 index 0000000..f9880ef --- /dev/null +++ b/test/api/query.test.ts @@ -0,0 +1,37 @@ +import {test, vi, expect, beforeEach, afterEach} from 'vitest'; +import {query} from '@carto/api-client'; + +const QUERY_RESPONSE = [{id: 1, value: 'string'}]; + +beforeEach(() => { + const mockFetch = vi + .fn() + .mockReturnValue( + Promise.resolve({ok: true, json: () => Promise.resolve(QUERY_RESPONSE)}) + ); + + vi.stubGlobal('fetch', mockFetch); +}); + +afterEach(() => void vi.restoreAllMocks()); + +test('query', async (t) => { + const mockFetch = vi.mocked(fetch); + + const response = await query({ + connectionName: 'carto_dw', + clientId: 'CUSTOM_CLIENT', + accessToken: '', + sqlQuery: 'SELECT * FROM a.b.h3_table', + }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + + const [queryCall] = mockFetch.mock.calls; + + expect(queryCall[0]).toMatch(/v3\/sql\/carto_dw\/query/); + expect(queryCall[0]).toMatch(/q=SELECT\+\*\+FROM\+a\.b\.h3_table/); + expect(queryCall[0]).toMatch(/client\=CUSTOM_CLIENT/); + + expect(response).toEqual(QUERY_RESPONSE); +}); From ab7215108780b83b68921c012e0173b88760b57d Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 29 Oct 2024 17:58:29 -0400 Subject: [PATCH 15/26] migrate CartoAPIError tests --- test/api/carto-api-error.test.ts | 117 +++++++++++++++++++++++++++++++ test/api/query.test.ts | 2 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 test/api/carto-api-error.test.ts diff --git a/test/api/carto-api-error.test.ts b/test/api/carto-api-error.test.ts new file mode 100644 index 0000000..b9fa8f7 --- /dev/null +++ b/test/api/carto-api-error.test.ts @@ -0,0 +1,117 @@ +import {expect, test} from 'vitest'; +import {APIErrorContext, CartoAPIError} from '@carto/api-client'; + +[ + { + title: '400 error', + error: new Error('Bad'), + errorContext: {requestType: 'Map data'}, + response: {status: 400}, + message: ` +Map data API request failed +Server returned: Bad request (400): Bad +`, + }, + { + title: '401 error', + error: new Error('Unauthorized'), + errorContext: {requestType: 'Map data'}, + response: {status: 401}, + message: ` +Map data API request failed +Server returned: Unauthorized access (401): Unauthorized +`, + }, + { + title: '403 error', + error: new Error('Forbidden'), + errorContext: {requestType: 'Map data'}, + response: {status: 403}, + message: ` +Map data API request failed +Server returned: Unauthorized access (403): Forbidden +`, + }, + { + title: '404 error', + error: new Error('Not found'), + errorContext: {requestType: 'Map data'}, + response: {status: 404}, + message: ` +Map data API request failed +Server returned: Not found (404): Not found +`, + }, + { + title: '500 error', + error: new Error('Source error'), + errorContext: {requestType: 'Map data'}, + response: {status: 500}, + message: ` +Map data API request failed +Server returned: Error (500): Source error +`, + }, + { + title: 'Full error context: instantiation', + error: new Error('Source error'), + errorContext: { + requestType: 'Map instantiation', + connection: 'connectionName', + source: 'sourceName', + type: 'query', + }, + response: {status: 500}, + message: ` +Map instantiation API request failed +Server returned: Error (500): Source error +Connection: connectionName +Source: sourceName +Type: query +`, + }, + { + title: 'Full error context: public map', + error: new Error('Source error'), + errorContext: { + requestType: 'Public map', + mapId: 'abcd', + }, + response: {status: 500}, + message: ` +Public map API request failed +Server returned: Error (500): Source error +Map Id: abcd +`, + }, + { + title: 'Full error context: custom value', + error: new Error('Source error'), + errorContext: { + requestType: 'Tile stats', + connection: 'connectionName', + source: 'sourceName', + type: 'tileset', + customKey: 'customValue', + }, + response: {status: 500}, + message: ` +Tile stats API request failed +Server returned: Error (500): Source error +Connection: connectionName +Source: sourceName +Type: tileset +Custom Key: customValue +`, + }, +].forEach(({title, error, errorContext, response, message}) => { + test(`CartoAPIError: ${title}`, () => { + const cartoAPIError = new CartoAPIError( + error, + errorContext as APIErrorContext, + response as Response + ); + expect(cartoAPIError).toBeTruthy(); + expect(cartoAPIError.message).toBe(message.slice(1)); + }); +}); diff --git a/test/api/query.test.ts b/test/api/query.test.ts index f9880ef..fc2d08a 100644 --- a/test/api/query.test.ts +++ b/test/api/query.test.ts @@ -15,7 +15,7 @@ beforeEach(() => { afterEach(() => void vi.restoreAllMocks()); -test('query', async (t) => { +test('query', async () => { const mockFetch = vi.mocked(fetch); const response = await query({ From 2db57801ba35de429add69c8c0a074eb2f22498f Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Tue, 29 Oct 2024 18:00:26 -0400 Subject: [PATCH 16/26] chore(release): v0.4.0-alpha.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f92ad8..046a62e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.0-alpha.1", + "version": "0.4.0-alpha.2", "license": "MIT", "publishConfig": { "access": "public", From 271f59f80dd5902fe88f897c880143b525cf8501 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 30 Oct 2024 14:12:05 -0400 Subject: [PATCH 17/26] fix duplicate client/clientID params --- src/api/request-with-parameters.ts | 2 +- test/api/request-with-parameters.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 2655834..c44e97d 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -32,7 +32,7 @@ export async function requestWithParameters({ // user-provided parameters. parameters = { v: V3_MINOR_VERSION, - clientId: DEFAULT_CLIENT, + client: DEFAULT_CLIENT, ...(typeof deck !== 'undefined' && deck.VERSION && {deckglVersion: deck.VERSION}), ...parameters, diff --git a/test/api/request-with-parameters.test.ts b/test/api/request-with-parameters.test.ts index 7e34fec..0a31236 100644 --- a/test/api/request-with-parameters.test.ts +++ b/test/api/request-with-parameters.test.ts @@ -197,7 +197,7 @@ describe('requestWithParameters', () => { Array.from(new URL(calls[0][0] as string).searchParams.entries()) ).toEqual([ ['v', '3.4'], - ['clientId', 'deck-gl-carto'], + ['client', 'deck-gl-carto'], ['deckglVersion', 'untranspiled source'], ['object', '{"a":1,"b":2}'], ['array', '[1,2,3]'], From 9ce9987ca7c9bc597fba32cad260273035e7147d Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 30 Oct 2024 14:27:41 -0400 Subject: [PATCH 18/26] fix coverage --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 046a62e..238509c 100644 --- a/package.json +++ b/package.json @@ -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/*", From a40056f519e4be95444a0e768d078c0927377ee3 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 30 Oct 2024 14:31:32 -0400 Subject: [PATCH 19/26] chore(release): v0.4.0-alpha.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 238509c..9533208 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.0-alpha.2", + "version": "0.4.0-alpha.3", "license": "MIT", "publishConfig": { "access": "public", From ec67f5a9cefd7aa71a5732305f8f414bbdd2f436 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 30 Oct 2024 21:15:35 -0400 Subject: [PATCH 20/26] fix(metrics): Consistent 'deck-gl-carto' client ID --- src/api/request-with-parameters.ts | 5 +++-- src/client.ts | 4 ++-- src/constants.ts | 3 --- src/sources/base-source.ts | 9 +++------ test/client.test.ts | 2 +- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index c44e97d..4aff8f9 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -5,7 +5,8 @@ import {isPureObject} from '../utils'; import {CartoAPIError, APIErrorContext} from './carto-api-error'; import {V3_MINOR_VERSION} from '../constants-internal'; -import {DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL} from '../constants'; +import {DEFAULT_MAX_LENGTH_URL} from '../constants'; +import {getClient} from '../client'; const DEFAULT_HEADERS = { Accept: 'application/json', @@ -32,7 +33,7 @@ export async function requestWithParameters({ // user-provided parameters. parameters = { v: V3_MINOR_VERSION, - client: DEFAULT_CLIENT, + client: getClient(), ...(typeof deck !== 'undefined' && deck.VERSION && {deckglVersion: deck.VERSION}), ...parameters, diff --git a/src/client.ts b/src/client.ts index eefe1c8..8e4d502 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,8 +1,8 @@ /** * @internal - * @internalRemarks Source: @carto/react-core + * @internalRemarks Source: @carto/react-core, @carto/constants, @deck.gl/carto */ -let client = 'carto-api-client'; +let client = 'deck-gl-carto'; /** * Returns current client ID, used to categorize API requests. For internal use only. diff --git a/src/constants.ts b/src/constants.ts index 6cacfdf..40970ee 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,9 +36,6 @@ export enum ApiVersion { /** @internalRemarks Source: @carto/constants, @deck.gl/carto */ export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; -/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ -export const DEFAULT_CLIENT = 'deck-gl-carto'; - /** @internalRemarks Source: @carto/constants, @deck.gl/carto */ export const DEFAULT_GEO_COLUMN = 'geom'; diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index 943e3b8..bac84d9 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -3,11 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import { - DEFAULT_API_BASE_URL, - DEFAULT_CLIENT, - DEFAULT_MAX_LENGTH_URL, -} from '../constants'; +import {DEFAULT_API_BASE_URL, DEFAULT_MAX_LENGTH_URL} from '../constants'; import {buildSourceUrl} from '../api/endpoints'; import {requestWithParameters} from '../api/request-with-parameters'; import type { @@ -20,10 +16,11 @@ import type { } from './types'; import {MapType} from '../types'; import {APIErrorContext} from '../api'; +import {getClient} from '../client'; export const SOURCE_DEFAULTS: SourceOptionalOptions = { apiBaseUrl: DEFAULT_API_BASE_URL, - clientId: DEFAULT_CLIENT, + clientId: getClient(), format: 'tilejson', headers: {}, maxLengthURL: DEFAULT_MAX_LENGTH_URL, diff --git a/test/client.test.ts b/test/client.test.ts index c0d6240..0d4e0ce 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -2,7 +2,7 @@ import {expect, test} from 'vitest'; import {getClient, setClient} from '@carto/api-client'; // Source: src/client.ts -const CLIENT_ID = 'carto-api-client'; +const CLIENT_ID = 'deck-gl-carto'; test('client', () => { expect(getClient()).toBe(CLIENT_ID); From 0853d096167456504098229f6e539c916cc730aa Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 31 Oct 2024 10:16:39 -0700 Subject: [PATCH 21/26] revert coverage change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9533208..8ed49a6 100644 --- a/package.json +++ b/package.json @@ -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.enabled --coverage.all false", + "coverage": "vitest run --coverage", "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/*", From dedfdb4f54d60fa0aecf8d42efa9728c89917bc5 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 31 Oct 2024 11:42:22 -0700 Subject: [PATCH 22/26] reorganize constants (internal & public) --- src/api/request-with-parameters.ts | 2 +- src/constants-internal.ts | 17 ++++++++++++++--- src/constants.ts | 22 ---------------------- src/models/model.ts | 2 +- src/sources/base-source.ts | 5 ++++- src/sources/vector-query-source.ts | 2 +- src/sources/vector-table-source.ts | 2 +- src/widget-sources/widget-base-source.ts | 4 ++-- 8 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 4aff8f9..0cffe1a 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -5,7 +5,7 @@ import {isPureObject} from '../utils'; import {CartoAPIError, APIErrorContext} from './carto-api-error'; import {V3_MINOR_VERSION} from '../constants-internal'; -import {DEFAULT_MAX_LENGTH_URL} from '../constants'; +import {DEFAULT_MAX_LENGTH_URL} from '../constants-internal'; import {getClient} from '../client'; const DEFAULT_HEADERS = { diff --git a/src/constants-internal.ts b/src/constants-internal.ts index 189c472..d307fa8 100644 --- a/src/constants-internal.ts +++ b/src/constants-internal.ts @@ -1,6 +1,5 @@ -/****************************************************************************** - * VERSIONS - */ +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; /** * Current version of @carto/api-client. @@ -11,6 +10,18 @@ export const API_CLIENT_VERSION = __CARTO_API_CLIENT_VERSION; /** @internal */ export const V3_MINOR_VERSION = '3.4'; +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export const DEFAULT_GEO_COLUMN = 'geom'; + +/** + * Fastly default limit is 8192; leave some padding. + * @internalRemarks Source: @deck.gl/carto + */ +export const DEFAULT_MAX_LENGTH_URL = 7000; + +/** @internalRemarks Source: @deck.gl/carto */ +export const DEFAULT_TILE_RESOLUTION = 0.5; + /** * @internalRemarks Source: @deck.gl/carto * @internal diff --git a/src/constants.ts b/src/constants.ts index 40970ee..2862528 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,25 +28,3 @@ export enum ApiVersion { V2 = 'v2', V3 = 'v3', } - -/****************************************************************************** - * DEFAULTS - */ - -/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ -export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; - -/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ -export const DEFAULT_GEO_COLUMN = 'geom'; - -/** - * Fastly default limit is 8192; leave some padding. - * @internalRemarks Source: @deck.gl/carto - */ -export const DEFAULT_MAX_LENGTH_URL = 7000; - -/** @internalRemarks Source: @deck.gl/carto */ -export const DEFAULT_TILE_SIZE = 512; - -/** @internalRemarks Source: @deck.gl/carto */ -export const DEFAULT_TILE_RESOLUTION = 0.5; diff --git a/src/models/model.ts b/src/models/model.ts index 7691e44..08c2edd 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -1,4 +1,4 @@ -import {DEFAULT_GEO_COLUMN} from '../constants.js'; +import {DEFAULT_GEO_COLUMN} from '../constants-internal.js'; import { Filter, FilterLogicalOperator, diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index bac84d9..44249fd 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -3,7 +3,10 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_API_BASE_URL, DEFAULT_MAX_LENGTH_URL} from '../constants'; +import { + DEFAULT_API_BASE_URL, + DEFAULT_MAX_LENGTH_URL, +} from '../constants-internal'; import {buildSourceUrl} from '../api/endpoints'; import {requestWithParameters} from '../api/request-with-parameters'; import type { diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 3aa6ee7..93db9a4 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_TILE_RESOLUTION} from '../constants.js'; +import {DEFAULT_TILE_RESOLUTION} from '../constants-internal.js'; import { WidgetQuerySource, WidgetQuerySourceResult, diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 0795e9c..85dc4db 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors /* eslint-disable camelcase */ -import {DEFAULT_TILE_RESOLUTION} from '../constants.js'; +import {DEFAULT_TILE_RESOLUTION} from '../constants-internal.js'; import { WidgetTableSource, WidgetTableSourceResult, diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 3ad4307..b9bbef8 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -20,11 +20,11 @@ import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; import {SourceOptions} from '../sources/index.js'; +import {ApiVersion} from '../constants.js'; import { - ApiVersion, DEFAULT_API_BASE_URL, DEFAULT_GEO_COLUMN, -} from '../constants.js'; +} from '../constants-internal.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; From 946a3d522b2b9d2501526287db8568669624a421 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 31 Oct 2024 12:06:49 -0700 Subject: [PATCH 23/26] stricter types --- src/models/common.ts | 14 +++++++++++--- src/types.ts | 1 - src/utils.ts | 3 +-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/models/common.ts b/src/models/common.ts index ed5b9c6..b108924 100644 --- a/src/models/common.ts +++ b/src/models/common.ts @@ -1,4 +1,3 @@ -import {$TODO} from '../types-internal.js'; import {InvalidColumnError} from '../utils.js'; /** @internalRemarks Source: @carto/react-api */ @@ -9,6 +8,12 @@ export interface ModelRequestOptions { body?: string; } +interface ModelErrorResponse { + error?: string | string[]; + hint?: string; + column_name?: string; +} + /** * Return more descriptive error from API * @internalRemarks Source: @carto/react-api @@ -18,13 +23,16 @@ export function dealWithApiError({ data, }: { response: Response; - data: $TODO; + data: ModelErrorResponse; }) { if (data.error === 'Column not found') { throw new InvalidColumnError(`${data.error} ${data.column_name}`); } - if (data.error?.includes('Missing columns')) { + if ( + typeof data.error === 'string' && + data.error?.includes('Missing columns') + ) { throw new InvalidColumnError(data.error); } diff --git a/src/types.ts b/src/types.ts index 38f8657..984b07d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,7 +41,6 @@ export interface Filters { [column: string]: Filter; } -// TODO: Would `{[FilterType.IN]?: number[] | string[]}` also be valid? /** @internalRemarks Source: @carto/react-api, @deck.gl/carto */ export interface Filter { [FilterType.IN]?: {owner?: string; values: number[] | string[]}; diff --git a/src/utils.ts b/src/utils.ts index 64a689d..e4048fc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,5 @@ import {Filter} from './types.js'; import {FilterType} from './constants.js'; -import {$TODO} from './types-internal.js'; const FILTER_TYPES = new Set(Object.values(FilterType)); const isFilterType = (type: string): type is FilterType => @@ -26,7 +25,7 @@ export function getApplicableFilters( const isApplicable = !owner || !filter?.owner || filter?.owner !== owner; if (filter && isApplicable) { applicableFilters[column] ||= {}; - applicableFilters[column][type] = filter as $TODO; + (applicableFilters[column][type] as typeof filter) = filter; } } } From ea9f615b77d9f0cd9c33f31bb4a19e50c4311464 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 31 Oct 2024 12:07:08 -0700 Subject: [PATCH 24/26] chore(release): v0.4.0-alpha.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ed49a6..83b0bb1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.0-alpha.3", + "version": "0.4.0-alpha.4", "license": "MIT", "publishConfig": { "access": "public", From 0fa0d9c36e60f907b622ef7fbb92ea1f715af3b6 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Mon, 4 Nov 2024 11:00:43 -0800 Subject: [PATCH 25/26] feat(constants): Export DEFAULT_API_BASE_URL --- src/constants-internal.ts | 3 --- src/constants.ts | 3 +++ src/sources/base-source.ts | 7 ++----- src/widget-sources/widget-base-source.ts | 7 ++----- test/constants.test.ts | 6 +++++- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/constants-internal.ts b/src/constants-internal.ts index d307fa8..811dd34 100644 --- a/src/constants-internal.ts +++ b/src/constants-internal.ts @@ -1,6 +1,3 @@ -/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ -export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; - /** * Current version of @carto/api-client. * @internal diff --git a/src/constants.ts b/src/constants.ts index 2862528..a67256d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,3 +28,6 @@ export enum ApiVersion { V2 = 'v2', V3 = 'v3', } + +/** @internalRemarks Source: @carto/constants, @deck.gl/carto */ +export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com'; diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index 44249fd..3b2f530 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -2,11 +2,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -/* eslint-disable camelcase */ -import { - DEFAULT_API_BASE_URL, - DEFAULT_MAX_LENGTH_URL, -} from '../constants-internal'; +import {DEFAULT_API_BASE_URL} from '../constants'; +import {DEFAULT_MAX_LENGTH_URL} from '../constants-internal'; import {buildSourceUrl} from '../api/endpoints'; import {requestWithParameters} from '../api/request-with-parameters'; import type { diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index b9bbef8..3dde07d 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -20,11 +20,8 @@ import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; import {SourceOptions} from '../sources/index.js'; -import {ApiVersion} from '../constants.js'; -import { - DEFAULT_API_BASE_URL, - DEFAULT_GEO_COLUMN, -} from '../constants-internal.js'; +import {ApiVersion, DEFAULT_API_BASE_URL} from '../constants.js'; +import {DEFAULT_GEO_COLUMN} from '../constants-internal.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; diff --git a/test/constants.test.ts b/test/constants.test.ts index 77015e6..0c45544 100644 --- a/test/constants.test.ts +++ b/test/constants.test.ts @@ -1,8 +1,12 @@ import {expect, test} from 'vitest'; -import {FilterType} from '@carto/api-client'; +import {FilterType, DEFAULT_API_BASE_URL} from '@carto/api-client'; test('FilterType', () => { expect(FilterType.IN).toBe('in'); expect(FilterType.STRING_SEARCH).toBe('stringSearch'); expect(FilterType.CLOSED_OPEN).toBe('closed_open'); }); + +test('DEFAULT_API_BASE_URL', () => { + expect(DEFAULT_API_BASE_URL).toMatch(/^https:\/\//); +}); From 101650ad387d3fe3dc4101e9e257aacb1cb228dc Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Mon, 4 Nov 2024 11:03:11 -0800 Subject: [PATCH 26/26] chore(release): v0.4.0-alpha.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83b0bb1..8e2f3be 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.0-alpha.4", + "version": "0.4.0-alpha.5", "license": "MIT", "publishConfig": { "access": "public",