@oceanum/datamesh 0.2.0 → 0.4.2
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/README.md +6 -6
- package/package.json +6 -8
- package/src/lib/connector.ts +194 -37
- package/src/lib/datamodel.ts +322 -148
- package/src/lib/datasource.ts +35 -9
- package/src/lib/query.ts +2 -2
- package/src/lib/session.ts +171 -0
- package/src/lib/zarr.ts +93 -31
- package/src/test/dataframe.test.ts +1 -1
- package/src/test/dataset.test.ts +139 -19
- package/src/test/fixtures.ts +51 -48
- package/src/test/query.test.ts +3 -3
- package/tsconfig.vitest-temp.json +13 -2
- package/typedoc.json +1 -1
- package/vite.config.ts +1 -1
- package/dist/README.md +0 -31
- package/dist/blosc-CeItQ6qj.cjs +0 -17
- package/dist/blosc-DaK8KnI4.js +0 -719
- package/dist/browser-BDe_cnOJ.cjs +0 -1
- package/dist/browser-CJIXy_XB.js +0 -524
- package/dist/chunk-INHXZS53-DiyuLb3Z.js +0 -14
- package/dist/chunk-INHXZS53-z3BpFH8p.cjs +0 -1
- package/dist/gzip-DfmsOCZR.cjs +0 -1
- package/dist/gzip-TMN4LZ5e.js +0 -24
- package/dist/index.cjs +0 -9
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -11341
- package/dist/lib/connector.d.ts +0 -93
- package/dist/lib/connector.d.ts.map +0 -1
- package/dist/lib/datamodel.d.ts +0 -152
- package/dist/lib/datamodel.d.ts.map +0 -1
- package/dist/lib/datasource.d.ts +0 -96
- package/dist/lib/datasource.d.ts.map +0 -1
- package/dist/lib/observe.d.ts +0 -3
- package/dist/lib/observe.d.ts.map +0 -1
- package/dist/lib/query.d.ts +0 -135
- package/dist/lib/query.d.ts.map +0 -1
- package/dist/lib/zarr.d.ts +0 -20
- package/dist/lib/zarr.d.ts.map +0 -1
- package/dist/lz4-CssV0LoA.js +0 -643
- package/dist/lz4-PFaIsPAh.cjs +0 -15
- package/dist/test/fixtures.d.ts +0 -12
- package/dist/test/fixtures.d.ts.map +0 -1
- package/dist/zlib-C-RQJQaC.cjs +0 -1
- package/dist/zlib-DrihHfbK.js +0 -24
- package/dist/zstd-Cqadn9HA.js +0 -610
- package/dist/zstd-_xUhkGOV.cjs +0 -15
- package/src/docs/reverse_proxy.md +0 -0
- package/vite.config.ts.timestamp-1734584068599-c5119713c3c4e.mjs +0 -67
package/README.md
CHANGED
|
@@ -16,16 +16,16 @@ npm install @oceanum/datamesh
|
|
|
16
16
|
import { Connector } from "@oceanum/datamesh";
|
|
17
17
|
|
|
18
18
|
//Instatiate the Datamesh Connector
|
|
19
|
-
const datamesh=Connector("my_datamesh_token"); //Get your datamesh token from your Oceanum.io account
|
|
19
|
+
const datamesh = Connector("my_datamesh_token"); //Get your datamesh token from your Oceanum.io account
|
|
20
20
|
|
|
21
21
|
//Define a datamesh query
|
|
22
|
-
const query={
|
|
23
|
-
|
|
24
|
-
}
|
|
22
|
+
const query = {
|
|
23
|
+
datasource: "oceanum-sizing_giants",
|
|
24
|
+
};
|
|
25
25
|
|
|
26
26
|
//Get the data
|
|
27
|
-
const data=await datamesh.query(query);
|
|
27
|
+
const data = await datamesh.query(query);
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
[!WARNING]
|
|
31
|
-
DO NOT put your Datamesh token directly into browser code. For use in an SPA, you should forward your Datamesh request through a reverse proxy to conceal your token. Read the [library documentation](https://oceanum-js.oceanum.io/datamesh
|
|
31
|
+
DO NOT put your Datamesh token directly into browser code. For use in an SPA, you should forward your Datamesh request through a reverse proxy to conceal your token. Read the [library documentation](https://oceanum-js.oceanum.io/datamesh) to learn more.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oceanum/datamesh",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build:docs": "typedoc"
|
|
6
6
|
},
|
|
@@ -8,17 +8,15 @@
|
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@types/geojson": "^7946.0.
|
|
11
|
+
"@types/geojson": "^7946.0.16",
|
|
12
12
|
"@types/object-hash": "^3.0.6",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"@zarrita/storage": "^0.1.0-next.7",
|
|
16
|
-
"apache-arrow": "^18.1.0",
|
|
13
|
+
"apache-arrow": "^19.0.1",
|
|
14
|
+
"buffer": "^6.0.3",
|
|
17
15
|
"dayjs": "^1.11.13",
|
|
18
16
|
"idb-keyval": "^6.2.1",
|
|
19
17
|
"object-hash": "^3.0.0",
|
|
20
|
-
"wkx": "^0.
|
|
21
|
-
"zarrita": "^0.4.0-next.
|
|
18
|
+
"wkx-ts": "^1.0.1",
|
|
19
|
+
"zarrita": "^0.4.0-next.23"
|
|
22
20
|
},
|
|
23
21
|
"type": "module",
|
|
24
22
|
"main": "./dist/index.js",
|
package/src/lib/connector.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Datasource } from "./datasource";
|
|
2
2
|
import { IQuery, Stage } from "./query";
|
|
3
|
-
import { Dataset,
|
|
3
|
+
import { Dataset, HttpZarr, TempZarr } from "./datamodel";
|
|
4
4
|
import { measureTime } from "./observe";
|
|
5
5
|
import { tableFromIPC, Table } from "apache-arrow";
|
|
6
|
+
import { Session } from "./session";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Datamesh connector class.
|
|
@@ -10,54 +11,110 @@ import { tableFromIPC, Table } from "apache-arrow";
|
|
|
10
11
|
* All datamesh operations are methods of this class.
|
|
11
12
|
*
|
|
12
13
|
*/
|
|
14
|
+
const DATAMESH_SERVICE =
|
|
15
|
+
process.env.DATAMESH_SERVICE || "https://datamesh.oceanum.io";
|
|
13
16
|
|
|
14
17
|
export class Connector {
|
|
15
18
|
static LAZY_LOAD_SIZE = 1e8;
|
|
16
19
|
private _token: string;
|
|
17
|
-
private _proto: string;
|
|
18
20
|
private _host: string;
|
|
19
21
|
private _authHeaders: Record<string, string>;
|
|
20
22
|
private _gateway: string;
|
|
23
|
+
private _nocache = false;
|
|
24
|
+
private _isV1 = false;
|
|
25
|
+
private _sessionParams: Record<string, number> = {};
|
|
26
|
+
private _currentSession: Session | null = null;
|
|
27
|
+
service?: string;
|
|
28
|
+
gateway?: string;
|
|
21
29
|
|
|
22
30
|
/**
|
|
23
31
|
* Datamesh connector constructor
|
|
24
32
|
*
|
|
25
33
|
* @param token - Your datamesh access token. Defaults to environment variable DATAMESH_TOKEN is defined else as literal string "DATAMESH_TOKEN". DO NOT put your Datamesh token directly into public facing browser code.
|
|
26
|
-
* @param
|
|
27
|
-
* @param
|
|
34
|
+
* @param options - Constructor options.
|
|
35
|
+
* @param options.service - URL of datamesh service. Defaults to environment variable DATAMESH_SERVICE or "https://datamesh.oceanum.io".
|
|
36
|
+
* @param options.gateway - URL of gateway service. Defaults to "https://gateway.<datamesh_service_domain>".
|
|
37
|
+
* @param options.jwtAuth - JWT for Oceanum service.
|
|
38
|
+
* @param options.nocache - Disable caching of datamesh results.
|
|
39
|
+
* @param options.sessionDuration - The desired length of time for acquired datamesh sessions in hours. Will be 1 hour by default.
|
|
28
40
|
*
|
|
29
41
|
* @throws {Error} - If a valid token is not provided.
|
|
30
42
|
*/
|
|
31
43
|
constructor(
|
|
32
44
|
token = process.env.DATAMESH_TOKEN || "$DATAMESH_TOKEN",
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
options?: {
|
|
46
|
+
service?: string;
|
|
47
|
+
gateway?: string;
|
|
48
|
+
jwtAuth?: string;
|
|
49
|
+
nocache?: boolean;
|
|
50
|
+
sessionDuration?: number;
|
|
51
|
+
}
|
|
37
52
|
) {
|
|
38
|
-
if (!token) {
|
|
53
|
+
if (!token && !options?.jwtAuth) {
|
|
39
54
|
throw new Error(
|
|
40
55
|
"A valid datamesh token must be supplied as a connector constructor argument or defined in environment variables as DATAMESH_TOKEN"
|
|
41
56
|
);
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
this._token = token;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this._host = url.hostname
|
|
48
|
-
this._authHeaders =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
this._nocache = options?.nocache ?? false;
|
|
61
|
+
const url = new URL(options?.service || DATAMESH_SERVICE);
|
|
62
|
+
this._host = `${url.protocol}//${url.hostname}`;
|
|
63
|
+
this._authHeaders = options?.jwtAuth
|
|
64
|
+
? {
|
|
65
|
+
Authorization: `Bearer ${options.jwtAuth}`,
|
|
66
|
+
}
|
|
67
|
+
: {
|
|
68
|
+
Authorization: `Token ${this._token}`,
|
|
69
|
+
"X-DATAMESH-TOKEN": this._token,
|
|
70
|
+
};
|
|
52
71
|
|
|
53
72
|
/* This is for testing the gateway service is not always the same as the service domain */
|
|
54
|
-
this._gateway =
|
|
73
|
+
this._gateway =
|
|
74
|
+
options?.gateway || `${url.protocol}//gateway.${url.hostname}`;
|
|
55
75
|
|
|
56
76
|
if (
|
|
57
77
|
this._host.split(".").slice(-1)[0] !==
|
|
58
78
|
this._gateway.split(".").slice(-1)[0]
|
|
59
79
|
) {
|
|
60
|
-
console.warn("
|
|
80
|
+
console.warn("Datamesh gateway and service domains do not match");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Set session parameters if provided
|
|
84
|
+
if (
|
|
85
|
+
options?.sessionDuration &&
|
|
86
|
+
typeof options.sessionDuration === "number"
|
|
87
|
+
) {
|
|
88
|
+
this._sessionParams = { duration: options.sessionDuration };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if the API is v1 (supports sessions)
|
|
92
|
+
this._checkApiVersion();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if the API version supports sessions.
|
|
97
|
+
*
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
private async _checkApiVersion(): Promise<void> {
|
|
101
|
+
try {
|
|
102
|
+
// Simply check to see if we can get a session
|
|
103
|
+
const response = await fetch(`${this._gateway}/session`, {
|
|
104
|
+
headers: this._authHeaders,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (response.status === 200) {
|
|
108
|
+
this._isV1 = true;
|
|
109
|
+
console.info("Using datamesh API version 1");
|
|
110
|
+
} else {
|
|
111
|
+
this._isV1 = false;
|
|
112
|
+
console.info("Using datamesh API version 0");
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// If we can't connect to the gateway, assume it's not a v1 API
|
|
116
|
+
this._isV1 = false;
|
|
117
|
+
console.info("Using datamesh API version 0");
|
|
61
118
|
}
|
|
62
119
|
}
|
|
63
120
|
|
|
@@ -73,10 +130,10 @@ export class Connector {
|
|
|
73
130
|
/**
|
|
74
131
|
* Check the status of the metadata server.
|
|
75
132
|
*
|
|
76
|
-
* @returns True if the
|
|
133
|
+
* @returns True if the server is up, false otherwise.
|
|
77
134
|
*/
|
|
78
135
|
async status(): Promise<boolean> {
|
|
79
|
-
const response = await fetch(
|
|
136
|
+
const response = await fetch(this._host, {
|
|
80
137
|
headers: this._authHeaders,
|
|
81
138
|
});
|
|
82
139
|
return response.status === 200;
|
|
@@ -101,6 +158,56 @@ export class Connector {
|
|
|
101
158
|
}
|
|
102
159
|
}
|
|
103
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Create a new session.
|
|
163
|
+
*
|
|
164
|
+
* @param options - Session options.
|
|
165
|
+
* @param options.duration - The desired length of time for the session in hours. Defaults to the value set in the constructor or 1 hour.
|
|
166
|
+
* @returns A new session instance.
|
|
167
|
+
*/
|
|
168
|
+
async createSession(options: { duration?: number } = {}): Promise<Session> {
|
|
169
|
+
const sessionOptions = {
|
|
170
|
+
duration: options.duration || this._sessionParams.duration || 1,
|
|
171
|
+
};
|
|
172
|
+
this._currentSession = await Session.acquire(this, sessionOptions);
|
|
173
|
+
return this._currentSession;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get the current session or create a new one if none exists.
|
|
178
|
+
*
|
|
179
|
+
* @returns The current session.
|
|
180
|
+
*/
|
|
181
|
+
async getSession(): Promise<Session> {
|
|
182
|
+
if (!this._currentSession) {
|
|
183
|
+
return this.createSession();
|
|
184
|
+
}
|
|
185
|
+
return this._currentSession;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get headers with session information if available.
|
|
190
|
+
*
|
|
191
|
+
* @param additionalHeaders - Additional headers to include.
|
|
192
|
+
* @returns Headers with session information.
|
|
193
|
+
*/
|
|
194
|
+
private async getSessionHeaders(
|
|
195
|
+
additionalHeaders: Record<string, string> = {}
|
|
196
|
+
): Promise<Record<string, string>> {
|
|
197
|
+
if (this._isV1 && !this._currentSession) {
|
|
198
|
+
await this.createSession();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (this._currentSession) {
|
|
202
|
+
return this._currentSession.addHeader({
|
|
203
|
+
...this._authHeaders,
|
|
204
|
+
...additionalHeaders,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { ...this._authHeaders, ...additionalHeaders };
|
|
209
|
+
}
|
|
210
|
+
|
|
104
211
|
/**
|
|
105
212
|
* Request metadata from datamesh.
|
|
106
213
|
*
|
|
@@ -112,20 +219,17 @@ export class Connector {
|
|
|
112
219
|
datasourceId = "",
|
|
113
220
|
params = {} as Record<string, string>
|
|
114
221
|
): Promise<Response> {
|
|
115
|
-
const url = new URL(
|
|
116
|
-
`${this._proto}//${this._host}/datasource/${datasourceId}`
|
|
117
|
-
);
|
|
222
|
+
const url = new URL(`${this._host}/datasource/${datasourceId}`);
|
|
118
223
|
Object.keys(params).forEach((key) =>
|
|
119
224
|
url.searchParams.append(key, params[key])
|
|
120
225
|
);
|
|
121
226
|
|
|
227
|
+
const headers = await this.getSessionHeaders();
|
|
122
228
|
const response = await fetch(url.toString(), {
|
|
123
|
-
headers
|
|
229
|
+
headers,
|
|
124
230
|
});
|
|
125
231
|
|
|
126
|
-
if (response.status ===
|
|
127
|
-
throw new Error(`Datasource ${datasourceId} not found`);
|
|
128
|
-
} else if (response.status === 401) {
|
|
232
|
+
if (response.status === 403) {
|
|
129
233
|
throw new Error(`Datasource ${datasourceId} not authorized`);
|
|
130
234
|
}
|
|
131
235
|
|
|
@@ -144,8 +248,9 @@ export class Connector {
|
|
|
144
248
|
qhash: string,
|
|
145
249
|
dataFormat = "application/vnd.apache.arrow.file"
|
|
146
250
|
): Promise<Table> {
|
|
251
|
+
const headers = await this.getSessionHeaders({ Accept: dataFormat });
|
|
147
252
|
const response = await fetch(`${this._gateway}/oceanql/${qhash}?f=arrow`, {
|
|
148
|
-
headers
|
|
253
|
+
headers,
|
|
149
254
|
});
|
|
150
255
|
await this.validateResponse(response);
|
|
151
256
|
return tableFromIPC(await response.arrayBuffer());
|
|
@@ -158,11 +263,15 @@ export class Connector {
|
|
|
158
263
|
* @returns The staged response.
|
|
159
264
|
*/
|
|
160
265
|
@measureTime
|
|
161
|
-
|
|
266
|
+
async stageRequest(query: IQuery): Promise<Stage | null> {
|
|
162
267
|
const data = JSON.stringify(query);
|
|
268
|
+
const headers = await this.getSessionHeaders({
|
|
269
|
+
"Content-Type": "application/json",
|
|
270
|
+
});
|
|
271
|
+
|
|
163
272
|
const response = await fetch(`${this._gateway}/oceanql/stage/`, {
|
|
164
273
|
method: "POST",
|
|
165
|
-
headers
|
|
274
|
+
headers,
|
|
166
275
|
body: data,
|
|
167
276
|
});
|
|
168
277
|
if (response.status >= 400) {
|
|
@@ -179,12 +288,15 @@ export class Connector {
|
|
|
179
288
|
* Execute a query to the datamesh.
|
|
180
289
|
*
|
|
181
290
|
* @param query - The query to execute.
|
|
291
|
+
* @param options.timeout - Additional options for the query.
|
|
182
292
|
* @returns The response from the server.
|
|
183
293
|
*/
|
|
184
294
|
@measureTime
|
|
185
295
|
async query(
|
|
186
|
-
query: IQuery
|
|
187
|
-
|
|
296
|
+
query: IQuery,
|
|
297
|
+
options: { timeout?: number } = {}
|
|
298
|
+
): Promise<Dataset<HttpZarr | TempZarr> | null> {
|
|
299
|
+
//Stage the query
|
|
188
300
|
const stage = await this.stageRequest(query);
|
|
189
301
|
if (!stage) {
|
|
190
302
|
console.warn("No data found for query");
|
|
@@ -196,8 +308,40 @@ export class Connector {
|
|
|
196
308
|
const dataset = await Dataset.fromArrow(table, stage.coordkeys);
|
|
197
309
|
return dataset;
|
|
198
310
|
}
|
|
199
|
-
|
|
200
|
-
|
|
311
|
+
let url = null;
|
|
312
|
+
let params = undefined;
|
|
313
|
+
if (
|
|
314
|
+
query.timefilter ||
|
|
315
|
+
query.geofilter ||
|
|
316
|
+
query.levelfilter ||
|
|
317
|
+
query.coordfilter
|
|
318
|
+
) {
|
|
319
|
+
url = `${this._gateway}/zarr/${stage.qhash}`;
|
|
320
|
+
} else {
|
|
321
|
+
url = `${this._gateway}/zarr/${query.datasource}`;
|
|
322
|
+
params = query.parameters;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Get headers with session information if available
|
|
326
|
+
const headers = await this.getSessionHeaders();
|
|
327
|
+
|
|
328
|
+
// Pass the headers to the Dataset.zarr method
|
|
329
|
+
const dataset = await Dataset.zarr(url, headers, {
|
|
330
|
+
parameters: params,
|
|
331
|
+
timeout: options.timeout || 60000, // Default timeout value
|
|
332
|
+
nocache: this._nocache,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (query.variables) {
|
|
336
|
+
for (const v of Object.keys(dataset.variables)) {
|
|
337
|
+
if (
|
|
338
|
+
!query.variables.includes(v) &&
|
|
339
|
+
!Object.values(dataset.coordkeys).includes(v)
|
|
340
|
+
) {
|
|
341
|
+
delete dataset.variables[v];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
201
345
|
return dataset;
|
|
202
346
|
}
|
|
203
347
|
|
|
@@ -208,7 +352,7 @@ export class Connector {
|
|
|
208
352
|
* @returns The datasource instance.
|
|
209
353
|
* @throws {Error} - If the datasource cannot be found or is not authorized.
|
|
210
354
|
*/
|
|
211
|
-
|
|
355
|
+
//@measureTime
|
|
212
356
|
async getDatasource(datasourceId: string): Promise<Datasource> {
|
|
213
357
|
const meta = await this.metadataRequest(datasourceId);
|
|
214
358
|
const metaDict = await meta.json();
|
|
@@ -226,13 +370,26 @@ export class Connector {
|
|
|
226
370
|
* @param parameters - Additional datasource parameters.
|
|
227
371
|
* @returns The dataset.
|
|
228
372
|
*/
|
|
229
|
-
|
|
373
|
+
//@measureTime
|
|
230
374
|
async loadDatasource(
|
|
231
375
|
datasourceId: string,
|
|
232
376
|
parameters: Record<string, string | number> = {}
|
|
233
|
-
): Promise<Dataset
|
|
377
|
+
): Promise<Dataset<HttpZarr | TempZarr> | null> {
|
|
234
378
|
const query = { datasource: datasourceId, parameters };
|
|
235
379
|
const dataset = await this.query(query);
|
|
236
380
|
return dataset;
|
|
237
381
|
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Close the current session if one exists.
|
|
385
|
+
*
|
|
386
|
+
* @param finaliseWrite - Whether to finalise any write operations. Defaults to false.
|
|
387
|
+
* @returns A promise that resolves when the session is closed.
|
|
388
|
+
*/
|
|
389
|
+
async closeSession(finaliseWrite = false): Promise<void> {
|
|
390
|
+
if (this._currentSession) {
|
|
391
|
+
await this._currentSession.close(finaliseWrite);
|
|
392
|
+
this._currentSession = null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
238
395
|
}
|