@carto/api-client 0.5.0-alpha.5 → 0.5.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api-client.cjs +104 -122
- package/build/api-client.d.cts +24 -13
- package/build/api-client.d.ts +24 -13
- package/build/api-client.js +104 -122
- package/build/worker.js +5 -7
- package/package.json +1 -5
- package/src/models/common.ts +2 -2
- package/src/widget-sources/types.ts +1 -1
- package/src/widget-sources/widget-remote-source.ts +16 -16
- package/src/widget-sources/widget-tileset-source.ts +2 -2
- package/src/widget-sources/widget-tileset-worker-source.ts +123 -121
- package/src/workers/types.ts +0 -1
- package/src/workers/widget-tileset-worker.ts +11 -8
- package/build/{chunk-V3E7BKVR.js → chunk-LEI5PI5X.js} +1 -1
|
@@ -195,12 +195,12 @@ export class WidgetTilesetSource extends WidgetSource<WidgetTilesetSourceProps>
|
|
|
195
195
|
filterOwner
|
|
196
196
|
);
|
|
197
197
|
|
|
198
|
-
assertColumn(this._features, column);
|
|
199
|
-
|
|
200
198
|
if (!this._features.length) {
|
|
201
199
|
return [];
|
|
202
200
|
}
|
|
203
201
|
|
|
202
|
+
assertColumn(this._features, column);
|
|
203
|
+
|
|
204
204
|
return histogram({
|
|
205
205
|
data: filteredFeatures,
|
|
206
206
|
valuesColumns: normalizeColumns(column),
|
|
@@ -24,20 +24,119 @@ import {Method} from '../workers/constants.js';
|
|
|
24
24
|
import {WorkerRequest, WorkerResponse} from '../workers/types.js';
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Wrapper for {@link WidgetTilesetSource}, moving calculations to Web Workers.
|
|
28
|
+
* When supported, use of both classes is identical.
|
|
29
|
+
*
|
|
30
|
+
* To use this wrapper, the application and environment must support ESM Web
|
|
31
|
+
* Workers. For older build systems based on CommonJS, or in environments like
|
|
32
|
+
* Node.js, it may be necessary to use {@link WidgetTilesetSource} directly,
|
|
33
|
+
* and to (optionally) create workers manually in the application.
|
|
28
34
|
*/
|
|
29
35
|
export class WidgetTilesetWorkerSource extends WidgetSource<WidgetTilesetSourceProps> {
|
|
30
36
|
constructor(props: WidgetTilesetSourceProps) {
|
|
31
37
|
super(props);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
41
|
+
// WEB WORKER MANAGEMENT
|
|
42
|
+
|
|
43
|
+
protected _worker: Worker | null = null;
|
|
44
|
+
protected _workerNextRequestId = 1;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns an initialized Worker, to be reused for the lifecycle of this
|
|
48
|
+
* source instance.
|
|
49
|
+
*/
|
|
50
|
+
_getWorker() {
|
|
51
|
+
if (this._worker) {
|
|
52
|
+
return this._worker;
|
|
53
|
+
}
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
this._worker = new Worker(
|
|
56
|
+
new URL('@carto/api-client/worker', import.meta.url),
|
|
57
|
+
{
|
|
58
|
+
type: 'module',
|
|
59
|
+
name: 'cartowidgettileset',
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
this._worker.postMessage({
|
|
36
64
|
method: Method.INIT,
|
|
37
65
|
params: [this.props],
|
|
38
66
|
} as WorkerRequest);
|
|
67
|
+
|
|
68
|
+
return this._worker;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Executes a given method on the worker. */
|
|
72
|
+
_executeWorkerMethod<T>(
|
|
73
|
+
method: Method,
|
|
74
|
+
params: unknown[],
|
|
75
|
+
signal?: AbortSignal
|
|
76
|
+
): Promise<T> {
|
|
77
|
+
const worker = this._getWorker();
|
|
78
|
+
const requestId = this._workerNextRequestId++;
|
|
79
|
+
|
|
80
|
+
// TODO: ViewState may contain non-serializable data, which we do not need.
|
|
81
|
+
// Remove this sanitization after sc-469614 is fixed.
|
|
82
|
+
const options = params[0] as any;
|
|
83
|
+
if (options?.spatialIndexReferenceViewState) {
|
|
84
|
+
const {zoom, latitude, longitude} =
|
|
85
|
+
options.spatialIndexReferenceViewState;
|
|
86
|
+
options.spatialIndexReferenceViewState = {zoom, latitude, longitude};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let resolve: ((value: T) => void) | null = null;
|
|
90
|
+
let reject: ((reason: any) => void) | null = null;
|
|
91
|
+
|
|
92
|
+
// If worker sends message to main process, check whether it's a response
|
|
93
|
+
// to this request, and whether the request can been aborted. Then resolve
|
|
94
|
+
// or reject the Promise.
|
|
95
|
+
function onMessage(e: MessageEvent) {
|
|
96
|
+
const response = e.data as WorkerResponse;
|
|
97
|
+
if (response.requestId !== requestId) return;
|
|
98
|
+
|
|
99
|
+
if (signal?.aborted) {
|
|
100
|
+
reject!(new Error(signal.reason));
|
|
101
|
+
} else if (response.ok) {
|
|
102
|
+
resolve!(response.result as T);
|
|
103
|
+
} else {
|
|
104
|
+
reject!(new Error(response.error));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If request is aborted by user, immediately reject the Promise.
|
|
109
|
+
function onAbort() {
|
|
110
|
+
reject!(new Error(signal!.reason));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
worker.addEventListener('message', onMessage);
|
|
114
|
+
signal?.addEventListener('abort', onAbort);
|
|
115
|
+
|
|
116
|
+
// Send the task to the worker, creating a Promise to resolve/reject later.
|
|
117
|
+
const promise = new Promise<T>((_resolve, _reject) => {
|
|
118
|
+
resolve = _resolve;
|
|
119
|
+
reject = _reject;
|
|
120
|
+
|
|
121
|
+
worker.postMessage({
|
|
122
|
+
requestId,
|
|
123
|
+
method,
|
|
124
|
+
params,
|
|
125
|
+
} as WorkerRequest);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Whether the task completes, fails, or aborts: clean up afterward.
|
|
129
|
+
void promise.finally(() => {
|
|
130
|
+
worker.removeEventListener('message', onMessage);
|
|
131
|
+
signal?.removeEventListener('abort', onAbort);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return promise;
|
|
39
135
|
}
|
|
40
136
|
|
|
137
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
138
|
+
// DATA LOADING
|
|
139
|
+
|
|
41
140
|
/**
|
|
42
141
|
* Loads features as a list of tiles (typically provided by deck.gl).
|
|
43
142
|
* After tiles are loaded, {@link extractTileFeatures} must be called
|
|
@@ -50,8 +149,7 @@ export class WidgetTilesetWorkerSource extends WidgetSource<WidgetTilesetSourceP
|
|
|
50
149
|
data,
|
|
51
150
|
}));
|
|
52
151
|
|
|
53
|
-
|
|
54
|
-
tableName: this.props.tableName,
|
|
152
|
+
this._getWorker().postMessage({
|
|
55
153
|
method: Method.LOAD_TILES,
|
|
56
154
|
params: [tiles],
|
|
57
155
|
} as WorkerRequest);
|
|
@@ -59,8 +157,7 @@ export class WidgetTilesetWorkerSource extends WidgetSource<WidgetTilesetSourceP
|
|
|
59
157
|
|
|
60
158
|
/** Configures options used to extract features from tiles. */
|
|
61
159
|
setTileFeatureExtractOptions(options: TileFeatureExtractOptions) {
|
|
62
|
-
|
|
63
|
-
tableName: this.props.tableName,
|
|
160
|
+
this._getWorker().postMessage({
|
|
64
161
|
type: Method.SET_TILE_FEATURE_EXTRACT_OPTIONS,
|
|
65
162
|
params: [options],
|
|
66
163
|
});
|
|
@@ -78,161 +175,66 @@ export class WidgetTilesetWorkerSource extends WidgetSource<WidgetTilesetSourceP
|
|
|
78
175
|
geojson: FeatureCollection;
|
|
79
176
|
spatialFilter: SpatialFilter;
|
|
80
177
|
}) {
|
|
81
|
-
|
|
82
|
-
tableName: this.props.tableName,
|
|
178
|
+
this._getWorker().postMessage({
|
|
83
179
|
method: Method.LOAD_GEOJSON,
|
|
84
180
|
params: [{geojson, spatialFilter}],
|
|
85
181
|
} as WorkerRequest);
|
|
86
182
|
}
|
|
87
183
|
|
|
184
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
185
|
+
// WIDGETS API
|
|
186
|
+
|
|
88
187
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
89
188
|
override async getFeatures(): Promise<FeaturesResponse> {
|
|
90
189
|
throw new Error('getFeatures not supported for tilesets');
|
|
91
190
|
}
|
|
92
191
|
|
|
93
192
|
async getFormula({
|
|
94
|
-
|
|
193
|
+
signal,
|
|
95
194
|
...options
|
|
96
195
|
}: FormulaRequestOptions): Promise<FormulaResponse> {
|
|
97
|
-
return this._executeWorkerMethod(
|
|
98
|
-
this.props.tableName,
|
|
99
|
-
Method.GET_FORMULA,
|
|
100
|
-
[options],
|
|
101
|
-
abortController?.signal
|
|
102
|
-
);
|
|
196
|
+
return this._executeWorkerMethod(Method.GET_FORMULA, [options], signal);
|
|
103
197
|
}
|
|
104
198
|
|
|
105
199
|
override async getHistogram({
|
|
106
|
-
|
|
200
|
+
signal,
|
|
107
201
|
...options
|
|
108
202
|
}: HistogramRequestOptions): Promise<HistogramResponse> {
|
|
109
|
-
return this._executeWorkerMethod(
|
|
110
|
-
this.props.tableName,
|
|
111
|
-
Method.GET_HISTOGRAM,
|
|
112
|
-
[options],
|
|
113
|
-
abortController?.signal
|
|
114
|
-
);
|
|
203
|
+
return this._executeWorkerMethod(Method.GET_HISTOGRAM, [options], signal);
|
|
115
204
|
}
|
|
116
205
|
|
|
117
206
|
override async getCategories({
|
|
118
|
-
|
|
207
|
+
signal,
|
|
119
208
|
...options
|
|
120
209
|
}: CategoryRequestOptions): Promise<CategoryResponse> {
|
|
121
|
-
return this._executeWorkerMethod(
|
|
122
|
-
this.props.tableName,
|
|
123
|
-
Method.GET_CATEGORIES,
|
|
124
|
-
[options],
|
|
125
|
-
abortController?.signal
|
|
126
|
-
);
|
|
210
|
+
return this._executeWorkerMethod(Method.GET_CATEGORIES, [options], signal);
|
|
127
211
|
}
|
|
128
212
|
|
|
129
213
|
override async getScatter({
|
|
130
|
-
|
|
214
|
+
signal,
|
|
131
215
|
...options
|
|
132
216
|
}: ScatterRequestOptions): Promise<ScatterResponse> {
|
|
133
|
-
return this._executeWorkerMethod(
|
|
134
|
-
this.props.tableName,
|
|
135
|
-
Method.GET_SCATTER,
|
|
136
|
-
[options],
|
|
137
|
-
abortController?.signal
|
|
138
|
-
);
|
|
217
|
+
return this._executeWorkerMethod(Method.GET_SCATTER, [options], signal);
|
|
139
218
|
}
|
|
140
219
|
|
|
141
220
|
override async getTable({
|
|
142
|
-
|
|
221
|
+
signal,
|
|
143
222
|
...options
|
|
144
223
|
}: TableRequestOptions): Promise<TableResponse> {
|
|
145
|
-
return this._executeWorkerMethod(
|
|
146
|
-
this.props.tableName,
|
|
147
|
-
Method.GET_TABLE,
|
|
148
|
-
[options],
|
|
149
|
-
abortController?.signal
|
|
150
|
-
);
|
|
224
|
+
return this._executeWorkerMethod(Method.GET_TABLE, [options], signal);
|
|
151
225
|
}
|
|
152
226
|
|
|
153
227
|
override async getTimeSeries({
|
|
154
|
-
|
|
228
|
+
signal,
|
|
155
229
|
...options
|
|
156
230
|
}: TimeSeriesRequestOptions): Promise<TimeSeriesResponse> {
|
|
157
|
-
return this._executeWorkerMethod(
|
|
158
|
-
this.props.tableName,
|
|
159
|
-
Method.GET_TIME_SERIES,
|
|
160
|
-
[options],
|
|
161
|
-
abortController?.signal
|
|
162
|
-
);
|
|
231
|
+
return this._executeWorkerMethod(Method.GET_TIME_SERIES, [options], signal);
|
|
163
232
|
}
|
|
164
233
|
|
|
165
234
|
override async getRange({
|
|
166
|
-
|
|
235
|
+
signal,
|
|
167
236
|
...options
|
|
168
237
|
}: RangeRequestOptions): Promise<RangeResponse> {
|
|
169
|
-
return this._executeWorkerMethod(
|
|
170
|
-
this.props.tableName,
|
|
171
|
-
Method.GET_RANGE,
|
|
172
|
-
[options],
|
|
173
|
-
abortController?.signal
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
178
|
-
// WEB WORKER MANAGEMENT
|
|
179
|
-
|
|
180
|
-
// TODO: Singleton? Pool shared among datasets? One per dataset?
|
|
181
|
-
protected static WORKER: Worker;
|
|
182
|
-
protected static _nextRequestID = 1;
|
|
183
|
-
|
|
184
|
-
static init() {
|
|
185
|
-
WidgetTilesetWorkerSource.WORKER = new Worker(
|
|
186
|
-
new URL('@carto/api-client/worker', import.meta.url),
|
|
187
|
-
{
|
|
188
|
-
type: 'module',
|
|
189
|
-
name: 'cartowidgettileset',
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
_executeWorkerMethod<T>(
|
|
195
|
-
tableName: string,
|
|
196
|
-
method: Method,
|
|
197
|
-
params: unknown[],
|
|
198
|
-
signal?: AbortSignal
|
|
199
|
-
): Promise<T> {
|
|
200
|
-
const worker = WidgetTilesetWorkerSource.WORKER;
|
|
201
|
-
const requestId = WidgetTilesetWorkerSource._nextRequestID++;
|
|
202
|
-
|
|
203
|
-
// TODO: ViewState may contain non-serializable data, which we do not need.
|
|
204
|
-
// Remove this sanitization after sc-469614 is fixed.
|
|
205
|
-
const options = params[0] as any;
|
|
206
|
-
if (options?.spatialIndexReferenceViewState) {
|
|
207
|
-
const {zoom, latitude, longitude} =
|
|
208
|
-
options.spatialIndexReferenceViewState;
|
|
209
|
-
options.spatialIndexReferenceViewState = {zoom, latitude, longitude};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
worker.postMessage({
|
|
213
|
-
requestId,
|
|
214
|
-
tableName,
|
|
215
|
-
method,
|
|
216
|
-
params,
|
|
217
|
-
} as WorkerRequest);
|
|
218
|
-
|
|
219
|
-
return new Promise((resolve, reject) => {
|
|
220
|
-
function listener(e: MessageEvent) {
|
|
221
|
-
const response = e.data as WorkerResponse;
|
|
222
|
-
if (response.requestId !== requestId) return;
|
|
223
|
-
|
|
224
|
-
worker.removeEventListener('message', listener);
|
|
225
|
-
|
|
226
|
-
if (signal?.aborted) {
|
|
227
|
-
reject(new Error(signal.reason));
|
|
228
|
-
} else if (response.ok) {
|
|
229
|
-
resolve(response.result as T);
|
|
230
|
-
} else {
|
|
231
|
-
reject(new Error(response.error));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
worker.addEventListener('message', listener);
|
|
236
|
-
});
|
|
238
|
+
return this._executeWorkerMethod(Method.GET_RANGE, [options], signal);
|
|
237
239
|
}
|
|
238
240
|
}
|
package/src/workers/types.ts
CHANGED
|
@@ -5,22 +5,25 @@ import {
|
|
|
5
5
|
import {Method} from './constants.js';
|
|
6
6
|
import type {WorkerRequest, WorkerResponse} from './types.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/*
|
|
9
|
+
* Web Worker, compiled as a separate `@carto/api-client/worker` entrypoint.
|
|
10
|
+
*
|
|
11
|
+
* Workers are scoped to the lifecycle of a single WidgetTilesetWorkerSource
|
|
12
|
+
* instance, representing and executing calculations on a single datasource.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
let source: WidgetTilesetSource;
|
|
10
16
|
|
|
11
17
|
addEventListener('message', (e) => {
|
|
12
|
-
const {
|
|
18
|
+
const {method, params, requestId} = e.data as WorkerRequest;
|
|
13
19
|
|
|
14
20
|
if (method === Method.INIT) {
|
|
15
|
-
|
|
16
|
-
SOURCES_BY_NAME.set(tableName, new WidgetTilesetSource(props));
|
|
21
|
+
source = new WidgetTilesetSource(params[0] as WidgetTilesetSourceProps);
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
const source = SOURCES_BY_NAME.get(tableName);
|
|
21
|
-
|
|
22
25
|
if (!source) {
|
|
23
|
-
const error = `
|
|
26
|
+
const error = `Cannot execute "${method}" on uninitialized source.`;
|
|
24
27
|
postMessage({ok: false, error, requestId} as WorkerResponse);
|
|
25
28
|
return;
|
|
26
29
|
}
|
|
@@ -1829,10 +1829,10 @@ var WidgetTilesetSource = class extends WidgetSource {
|
|
|
1829
1829
|
filters,
|
|
1830
1830
|
filterOwner
|
|
1831
1831
|
);
|
|
1832
|
-
assertColumn(this._features, column);
|
|
1833
1832
|
if (!this._features.length) {
|
|
1834
1833
|
return [];
|
|
1835
1834
|
}
|
|
1835
|
+
assertColumn(this._features, column);
|
|
1836
1836
|
return histogram({
|
|
1837
1837
|
data: filteredFeatures,
|
|
1838
1838
|
valuesColumns: normalizeColumns(column),
|