@oceanum/datamesh 0.2.0 → 0.4.3

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.
Files changed (50) hide show
  1. package/README.md +6 -6
  2. package/package.json +6 -8
  3. package/src/lib/connector.ts +194 -37
  4. package/src/lib/datamodel.ts +322 -148
  5. package/src/lib/datasource.ts +35 -9
  6. package/src/lib/query.ts +2 -2
  7. package/src/lib/session.ts +171 -0
  8. package/src/lib/zarr.ts +93 -31
  9. package/src/test/dataframe.test.ts +1 -1
  10. package/src/test/dataset.test.ts +139 -19
  11. package/src/test/fixtures.ts +51 -48
  12. package/src/test/query.test.ts +3 -3
  13. package/tsconfig.vitest-temp.json +13 -2
  14. package/typedoc.json +1 -1
  15. package/vite.config.ts +1 -1
  16. package/dist/README.md +0 -31
  17. package/dist/blosc-CeItQ6qj.cjs +0 -17
  18. package/dist/blosc-DaK8KnI4.js +0 -719
  19. package/dist/browser-BDe_cnOJ.cjs +0 -1
  20. package/dist/browser-CJIXy_XB.js +0 -524
  21. package/dist/chunk-INHXZS53-DiyuLb3Z.js +0 -14
  22. package/dist/chunk-INHXZS53-z3BpFH8p.cjs +0 -1
  23. package/dist/gzip-DfmsOCZR.cjs +0 -1
  24. package/dist/gzip-TMN4LZ5e.js +0 -24
  25. package/dist/index.cjs +0 -9
  26. package/dist/index.d.ts +0 -5
  27. package/dist/index.d.ts.map +0 -1
  28. package/dist/index.js +0 -11341
  29. package/dist/lib/connector.d.ts +0 -93
  30. package/dist/lib/connector.d.ts.map +0 -1
  31. package/dist/lib/datamodel.d.ts +0 -152
  32. package/dist/lib/datamodel.d.ts.map +0 -1
  33. package/dist/lib/datasource.d.ts +0 -96
  34. package/dist/lib/datasource.d.ts.map +0 -1
  35. package/dist/lib/observe.d.ts +0 -3
  36. package/dist/lib/observe.d.ts.map +0 -1
  37. package/dist/lib/query.d.ts +0 -135
  38. package/dist/lib/query.d.ts.map +0 -1
  39. package/dist/lib/zarr.d.ts +0 -20
  40. package/dist/lib/zarr.d.ts.map +0 -1
  41. package/dist/lz4-CssV0LoA.js +0 -643
  42. package/dist/lz4-PFaIsPAh.cjs +0 -15
  43. package/dist/test/fixtures.d.ts +0 -12
  44. package/dist/test/fixtures.d.ts.map +0 -1
  45. package/dist/zlib-C-RQJQaC.cjs +0 -1
  46. package/dist/zlib-DrihHfbK.js +0 -24
  47. package/dist/zstd-Cqadn9HA.js +0 -610
  48. package/dist/zstd-_xUhkGOV.cjs +0 -15
  49. package/src/docs/reverse_proxy.md +0 -0
  50. 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
- "datasource":"oceanum-sizing_giants"
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/reverse_proxy) to learn more.
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.0",
3
+ "version": "0.4.3",
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.15",
11
+ "@types/geojson": "^7946.0.16",
12
12
  "@types/object-hash": "^3.0.6",
13
- "@zarrita/core": "^0.1.0-next.15",
14
- "@zarrita/indexing": "^0.1.0-next.17",
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.5.0",
21
- "zarrita": "^0.4.0-next.17"
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",
@@ -1,8 +1,9 @@
1
1
  import { Datasource } from "./datasource";
2
2
  import { IQuery, Stage } from "./query";
3
- import { Dataset, DatameshStore, TempStore } from "./datamodel";
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 service - URL of datamesh service. Defaults to environment variable DATAMESH_SERVICE or "https://datamesh.oceanum.io".
27
- * @param _gateway - URL of gateway service. Defaults to "https://gateway.datamesh.oceanum.io".
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
- service = process.env.DATAMESH_SERVICE || "https://datamesh.oceanum.io",
34
- // @ignore //
35
- _gateway = process.env.DATAMESH_GATEWAY ||
36
- "https://gateway.datamesh.oceanum.io"
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
- const url = new URL(service);
46
- this._proto = url.protocol;
47
- this._host = url.hostname;
48
- this._authHeaders = {
49
- Authorization: `Token ${this._token}`,
50
- "X-DATAMESH-TOKEN": this._token,
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 = _gateway || `${this._proto}//gateway.${this._host}`;
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("Gateway and service domain do not match");
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 metadata server is up, false otherwise.
133
+ * @returns True if the server is up, false otherwise.
77
134
  */
78
135
  async status(): Promise<boolean> {
79
- const response = await fetch(`${this._proto}//${this._host}`, {
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: this._authHeaders,
229
+ headers,
124
230
  });
125
231
 
126
- if (response.status === 404) {
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: { Accept: dataFormat, ...this._authHeaders },
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
- private async stageRequest(query: IQuery): Promise<Stage | null> {
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: { "Content-Type": "application/json", ...this._authHeaders },
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
- ): Promise<Dataset</** @ignore */ DatameshStore | TempStore> | null> {
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
- const url = `${this._gateway}/zarr/${stage.qhash}`;
200
- const dataset = await Dataset.zarr(url, this._authHeaders);
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
- @measureTime
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
- @measureTime
373
+ //@measureTime
230
374
  async loadDatasource(
231
375
  datasourceId: string,
232
376
  parameters: Record<string, string | number> = {}
233
- ): Promise<Dataset</** @ignore */ DatameshStore> | null> {
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
  }