@_linked/server 3.12.1 → 3.14.0
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/CHANGELOG.md +72 -0
- package/lib/esm/backend.d.ts +23 -5
- package/lib/esm/backend.js +116 -25
- package/lib/esm/backend.js.map +1 -1
- package/lib/esm/shapes/LincdAPI.d.ts +1 -1
- package/lib/esm/shapes/LincdAPI.js +43 -7
- package/lib/esm/shapes/LincdAPI.js.map +1 -1
- package/lib/esm/shapes/LinkedServer.d.ts +59 -39
- package/lib/esm/shapes/LinkedServer.js +158 -93
- package/lib/esm/shapes/LinkedServer.js.map +1 -1
- package/lib/esm/shapes/quadstores/RemoteDataset.d.ts +52 -20
- package/lib/esm/shapes/quadstores/RemoteDataset.js +134 -32
- package/lib/esm/shapes/quadstores/RemoteDataset.js.map +1 -1
- package/lib/esm/utils/adminChannel.d.ts +23 -51
- package/lib/esm/utils/adminChannel.js +50 -38
- package/lib/esm/utils/adminChannel.js.map +1 -1
- package/lib/esm/utils/effectiveRoutes.d.ts +6 -0
- package/lib/esm/utils/effectiveRoutes.js +2 -1
- package/lib/esm/utils/effectiveRoutes.js.map +1 -1
- package/lib/esm/utils/forwardedTransactions.d.ts +46 -0
- package/lib/esm/utils/forwardedTransactions.js +155 -0
- package/lib/esm/utils/forwardedTransactions.js.map +1 -0
- package/lib/esm/utils/graphTargets.d.ts +42 -17
- package/lib/esm/utils/graphTargets.js +62 -69
- package/lib/esm/utils/graphTargets.js.map +1 -1
- package/lib/esm/utils/linkedDependencies.d.ts +53 -7
- package/lib/esm/utils/linkedDependencies.js +130 -12
- package/lib/esm/utils/linkedDependencies.js.map +1 -1
- package/lib/esm/utils/ontologySync.d.ts +55 -31
- package/lib/esm/utils/ontologySync.js +94 -42
- package/lib/esm/utils/ontologySync.js.map +1 -1
- package/lib/esm/utils/queryPlane.d.ts +13 -0
- package/lib/esm/utils/queryPlane.js +49 -2
- package/lib/esm/utils/queryPlane.js.map +1 -1
- package/lib/esm/utils/reloadSource.js +1 -1
- package/lib/esm/utils/reloadSource.js.map +1 -1
- package/lib/esm/utils/serverTransactions.d.ts +22 -0
- package/lib/esm/utils/serverTransactions.js +81 -0
- package/lib/esm/utils/serverTransactions.js.map +1 -0
- package/lib/esm/utils/syncShapes.d.ts +123 -6
- package/lib/esm/utils/syncShapes.js +166 -35
- package/lib/esm/utils/syncShapes.js.map +1 -1
- package/package.json +4 -4
- package/readme.md +158 -38
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { IDataset } from '@_linked/core/interfaces/IDataset';
|
|
2
|
-
import type {
|
|
3
|
-
import type
|
|
4
|
-
import type { AskQuery } from '@_linked/core/queries/AskQuery';
|
|
2
|
+
import type { QueryFeature } from '@_linked/core/queries/queryFeatures';
|
|
3
|
+
import { type ReadQuery, type ReadResult } from '@_linked/core/queries/readQuery';
|
|
5
4
|
import type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';
|
|
6
5
|
import type { CreateQuery } from '@_linked/core/queries/CreateQuery';
|
|
7
6
|
import type { DeleteQuery, DeleteResponse } from '@_linked/core/queries/DeleteQuery';
|
|
8
|
-
import type {
|
|
7
|
+
import type { UpdateResult, CreateResult, GraphQuery, GraphResult } from '@_linked/core/queries/IntermediateRepresentation';
|
|
9
8
|
import { type GraphRegistryRow } from '@_linked/core/utils/graphRegistryCache';
|
|
10
9
|
/**
|
|
11
10
|
* Constructor argument for `new RemoteDataset(config)`: the `config` object of a
|
|
@@ -25,24 +24,27 @@ export interface RemoteDatasetConfig {
|
|
|
25
24
|
/** Or pass a fully-qualified URI directly. */
|
|
26
25
|
id?: string;
|
|
27
26
|
/**
|
|
28
|
-
* In the url form, send the signed-in user's
|
|
29
|
-
*
|
|
30
|
-
* string `'false'`, as an interpolated config gives it) turns it off
|
|
27
|
+
* In the url form, send the signed-in user's own access token with each request made
|
|
28
|
+
* while their HTTP request is answered (see {@link USER_TOKEN_HEADER}). Default true;
|
|
29
|
+
* `false` (or the string `'false'`, as an interpolated config gives it) turns it off: do so
|
|
30
|
+
* for a data server you do not trust with your users' tokens.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
forwardUserToken?: boolean | string;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* The header the url form sends
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* The header the url form sends the signed-in user's Solid-OIDC access token in, with a
|
|
36
|
+
* `DPoP` proof for that request beside it. Both come from {@link USER_TOKEN_HEADERS_HOOK};
|
|
37
|
+
* the data server verifies them and acts as that user. The bearer token still decides which
|
|
38
|
+
* data the store reaches.
|
|
38
39
|
*/
|
|
39
|
-
export declare const
|
|
40
|
+
export declare const USER_TOKEN_HEADER = "x-linked-user-token";
|
|
40
41
|
/**
|
|
41
|
-
* The
|
|
42
|
-
* `request
|
|
43
|
-
*
|
|
42
|
+
* The `globalThis` key of the hook that answers the user token headers for a request:
|
|
43
|
+
* `(request, {method, url}) => Promise<{'x-linked-user-token': string; DPoP: string} | undefined>`.
|
|
44
|
+
* `@_linked/auth`'s backend installs it; read here instead of importing auth, so this store
|
|
45
|
+
* stays browser-safe and free of auth.
|
|
44
46
|
*/
|
|
45
|
-
export declare
|
|
47
|
+
export declare const USER_TOKEN_HEADERS_HOOK: unique symbol;
|
|
46
48
|
/**
|
|
47
49
|
* A failed call to the data server at `url`. `status` is the HTTP status (0 when no HTTP
|
|
48
50
|
* answer came back); `code` is the error body's `code` when the server sent one.
|
|
@@ -91,6 +93,14 @@ export type RemoteDatasetPingInfo = Readonly<Record<string, unknown>>;
|
|
|
91
93
|
* `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must
|
|
92
94
|
* fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves
|
|
93
95
|
* the ping body (also readable as `info`), so a boot can log which dataset the server serves.
|
|
96
|
+
*
|
|
97
|
+
* **Store transactions** (core's `LinkedStorage.begin()`): the store declares `'transactions'`
|
|
98
|
+
* in `supports` only once the server's `ping` listed it (`supports: ['transactions']`), so a
|
|
99
|
+
* server that does not hold them is never sent a txId; until then a transaction warns once and
|
|
100
|
+
* runs its queries directly. `beginTransaction()`, `commit(txId)` and `rollback(txId)` post to
|
|
101
|
+
* the methods of the same names, never retried; a query's txId rides inside its `toJSON()`. In
|
|
102
|
+
* the no-url form `init()` asks the backend's `ping` for this alone (a failed one leaves
|
|
103
|
+
* transactions off, and `init()` still resolves undefined).
|
|
94
104
|
*/
|
|
95
105
|
export declare class RemoteDataset implements IDataset {
|
|
96
106
|
/**
|
|
@@ -101,10 +111,12 @@ export declare class RemoteDataset implements IDataset {
|
|
|
101
111
|
/** The data server base url, trailing slashes trimmed; undefined in the no-url form. */
|
|
102
112
|
readonly url?: string;
|
|
103
113
|
private readonly token?;
|
|
104
|
-
/** Whether the url form sends {@link
|
|
105
|
-
readonly
|
|
114
|
+
/** Whether the url form sends {@link USER_TOKEN_HEADER}; see `RemoteDatasetConfig.forwardUserToken`. */
|
|
115
|
+
readonly forwardUserToken: boolean;
|
|
106
116
|
private initialized?;
|
|
107
117
|
private pingInfo?;
|
|
118
|
+
/** Whether the server's last `ping` listed `'transactions'` in its `supports`. */
|
|
119
|
+
private serverHoldsTransactions;
|
|
108
120
|
private readonly instanceRegistryKey;
|
|
109
121
|
private warnedRegistryRead;
|
|
110
122
|
constructor(config?: RemoteDatasetConfig | string);
|
|
@@ -113,13 +125,32 @@ export declare class RemoteDataset implements IDataset {
|
|
|
113
125
|
* always in the no-url form.
|
|
114
126
|
*/
|
|
115
127
|
get info(): RemoteDatasetPingInfo | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* `'ask'` (the server answers an ask itself), plus `'transactions'` once the server's `ping`
|
|
130
|
+
* listed them. The select features (grouping, min/max) are not declared, so core refuses such a
|
|
131
|
+
* select before it is sent; nor are `'triples'` and `'triplesTerms'`, which the server's
|
|
132
|
+
* `readQuery` does not answer.
|
|
133
|
+
*/
|
|
134
|
+
get supports(): readonly QueryFeature[];
|
|
135
|
+
/** Begin a store transaction on the server; resolves its txId. */
|
|
136
|
+
beginTransaction(): Promise<string>;
|
|
137
|
+
/** Commit the transaction `txId` on the server. */
|
|
138
|
+
commit(txId: string): Promise<void>;
|
|
139
|
+
/** Roll back the transaction `txId` on the server (an unknown one is a no-op there). */
|
|
140
|
+
rollback(txId: string): Promise<void>;
|
|
141
|
+
/** Note what a `ping` body says of transactions. */
|
|
142
|
+
private notePing;
|
|
116
143
|
/**
|
|
117
144
|
* In the url form, asks the server's `ping` and resolves its body (also kept as
|
|
118
145
|
* {@link info}); resolves undefined in the no-url form.
|
|
119
146
|
*/
|
|
120
147
|
init(): Promise<RemoteDatasetPingInfo | undefined>;
|
|
121
|
-
|
|
122
|
-
|
|
148
|
+
/**
|
|
149
|
+
* A select, count or ask, posted to the server's one read method, `readQuery`; the server
|
|
150
|
+
* tells the kinds apart by the DSL-JSON `op`. Triples and terms reads are not declared in
|
|
151
|
+
* {@link supports} and are refused here, unsent (the server does not answer them).
|
|
152
|
+
*/
|
|
153
|
+
readQuery<Q extends ReadQuery>(query: Q): Promise<ReadResult<Q>>;
|
|
123
154
|
updateQuery(query: UpdateQuery): Promise<UpdateResult>;
|
|
124
155
|
createQuery(query: CreateQuery): Promise<CreateResult>;
|
|
125
156
|
deleteQuery(query: DeleteQuery): Promise<DeleteResponse>;
|
|
@@ -161,6 +192,7 @@ export declare class RemoteDataset implements IDataset {
|
|
|
161
192
|
*/
|
|
162
193
|
private withReadGraphs;
|
|
163
194
|
private call;
|
|
195
|
+
private callMethod;
|
|
164
196
|
/**
|
|
165
197
|
* Opts in to `rejectOnError`, so a failed call (HTTP error status, or no
|
|
166
198
|
* provider on the backend) rejects with a `ServerCallError` carrying the
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { packageName } from '../../package.js';
|
|
2
2
|
import { brandLinkedError } from '@_linked/core/utils/linkedError';
|
|
3
3
|
import { Server } from '@_linked/server-utils/utils/Server';
|
|
4
|
+
import { readKindOf } from '@_linked/core/queries/readQuery';
|
|
4
5
|
import { GraphQueryError, isGraphQueryError, unsupportedGraphQuery, } from '@_linked/core/queries/graphQuery';
|
|
5
6
|
import { invalidateGraphRegistry, noteGraphQuery, readGraphRegistry, } from '@_linked/core/utils/graphRegistryCache';
|
|
6
7
|
import { graphs } from '@_linked/core/ontologies/graphs';
|
|
7
8
|
import { OntologyGraph, ShapesGraph } from '@_linked/core/shapes/NamedGraph';
|
|
8
9
|
/**
|
|
9
|
-
* The header the url form sends
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* The header the url form sends the signed-in user's Solid-OIDC access token in, with a
|
|
11
|
+
* `DPoP` proof for that request beside it. Both come from {@link USER_TOKEN_HEADERS_HOOK};
|
|
12
|
+
* the data server verifies them and acts as that user. The bearer token still decides which
|
|
13
|
+
* data the store reaches.
|
|
12
14
|
*/
|
|
13
|
-
export const
|
|
15
|
+
export const USER_TOKEN_HEADER = 'x-linked-user-token';
|
|
16
|
+
/**
|
|
17
|
+
* The `globalThis` key of the hook that answers the user token headers for a request:
|
|
18
|
+
* `(request, {method, url}) => Promise<{'x-linked-user-token': string; DPoP: string} | undefined>`.
|
|
19
|
+
* `@_linked/auth`'s backend installs it; read here instead of importing auth, so this store
|
|
20
|
+
* stays browser-safe and free of auth.
|
|
21
|
+
*/
|
|
22
|
+
export const USER_TOKEN_HEADERS_HOOK = Symbol.for('@_linked/auth:userTokenHeaders');
|
|
14
23
|
// The store `@_linked/server-utils/utils/CallContext` keeps the call context in, shared on
|
|
15
24
|
// `globalThis` under this key. Read here instead of importing that module: it imports
|
|
16
25
|
// `node:async_hooks`, and this store also runs in the browser (the no-url form). In a browser,
|
|
@@ -20,18 +29,43 @@ function currentCallContext() {
|
|
|
20
29
|
const store = globalThis[CALL_CONTEXT_KEY];
|
|
21
30
|
return typeof (store === null || store === void 0 ? void 0 : store.getStore) === 'function' ? store.getStore() : undefined;
|
|
22
31
|
}
|
|
32
|
+
let warnedUserTokenHook = false;
|
|
33
|
+
const warnUserTokenHook = (problem) => {
|
|
34
|
+
if (warnedUserTokenHook)
|
|
35
|
+
return;
|
|
36
|
+
warnedUserTokenHook = true;
|
|
37
|
+
console.warn(`RemoteDataset: the user token hook ${problem}; queries go without the user's token`);
|
|
38
|
+
};
|
|
23
39
|
/**
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
40
|
+
* The user token headers for a request to `url`, made in the current call: the hook's
|
|
41
|
+
* answer for the http context's request. Undefined in a system context (`runAsSystem`),
|
|
42
|
+
* outside any call, without the hook, and when it answers nothing. A hook that throws or
|
|
43
|
+
* answers something else than two non-empty strings sends nothing (one warning per process).
|
|
27
44
|
*/
|
|
28
|
-
|
|
29
|
-
var _a, _b, _c, _d;
|
|
45
|
+
async function userTokenHeadersFor(url) {
|
|
30
46
|
const ctx = currentCallContext();
|
|
31
|
-
if (!ctx || ctx.kind !== 'http')
|
|
47
|
+
if (!ctx || ctx.kind !== 'http' || !ctx.request)
|
|
48
|
+
return undefined;
|
|
49
|
+
const hook = globalThis[USER_TOKEN_HEADERS_HOOK];
|
|
50
|
+
if (typeof hook !== 'function')
|
|
51
|
+
return undefined;
|
|
52
|
+
let answer;
|
|
53
|
+
try {
|
|
54
|
+
answer = await hook(ctx.request, { method: 'POST', url });
|
|
55
|
+
}
|
|
56
|
+
catch (_a) {
|
|
57
|
+
warnUserTokenHook('failed');
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (answer === undefined || answer === null)
|
|
61
|
+
return undefined;
|
|
62
|
+
const token = answer[USER_TOKEN_HEADER];
|
|
63
|
+
const proof = answer.DPoP;
|
|
64
|
+
if (typeof token !== 'string' || !token || typeof proof !== 'string' || !proof) {
|
|
65
|
+
warnUserTokenHook('answered something else than a token and a proof');
|
|
32
66
|
return undefined;
|
|
33
|
-
|
|
34
|
-
return
|
|
67
|
+
}
|
|
68
|
+
return { [USER_TOKEN_HEADER]: token, DPoP: proof };
|
|
35
69
|
}
|
|
36
70
|
/**
|
|
37
71
|
* A failed call to the data server at `url`. `status` is the HTTP status (0 when no HTTP
|
|
@@ -122,11 +156,21 @@ const PING_UNREACHABLE_STATUSES = new Set([404, 501, 502, 503, 504]);
|
|
|
122
156
|
* `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must
|
|
123
157
|
* fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves
|
|
124
158
|
* the ping body (also readable as `info`), so a boot can log which dataset the server serves.
|
|
159
|
+
*
|
|
160
|
+
* **Store transactions** (core's `LinkedStorage.begin()`): the store declares `'transactions'`
|
|
161
|
+
* in `supports` only once the server's `ping` listed it (`supports: ['transactions']`), so a
|
|
162
|
+
* server that does not hold them is never sent a txId; until then a transaction warns once and
|
|
163
|
+
* runs its queries directly. `beginTransaction()`, `commit(txId)` and `rollback(txId)` post to
|
|
164
|
+
* the methods of the same names, never retried; a query's txId rides inside its `toJSON()`. In
|
|
165
|
+
* the no-url form `init()` asks the backend's `ping` for this alone (a failed one leaves
|
|
166
|
+
* transactions off, and `init()` still resolves undefined).
|
|
125
167
|
*/
|
|
126
168
|
export class RemoteDataset {
|
|
127
169
|
constructor(config) {
|
|
128
|
-
/** Whether the url form sends {@link
|
|
129
|
-
this.
|
|
170
|
+
/** Whether the url form sends {@link USER_TOKEN_HEADER}; see `RemoteDatasetConfig.forwardUserToken`. */
|
|
171
|
+
this.forwardUserToken = true;
|
|
172
|
+
/** Whether the server's last `ping` listed `'transactions'` in its `supports`. */
|
|
173
|
+
this.serverHoldsTransactions = false;
|
|
130
174
|
this.instanceRegistryKey = `remote-dataset:${nextRegistryKey++}`;
|
|
131
175
|
this.warnedRegistryRead = false;
|
|
132
176
|
if (!config)
|
|
@@ -141,8 +185,8 @@ export class RemoteDataset {
|
|
|
141
185
|
if (token)
|
|
142
186
|
this.token = token;
|
|
143
187
|
}
|
|
144
|
-
if (config.
|
|
145
|
-
this.
|
|
188
|
+
if (config.forwardUserToken === false || config.forwardUserToken === 'false') {
|
|
189
|
+
this.forwardUserToken = false;
|
|
146
190
|
}
|
|
147
191
|
if (config.id) {
|
|
148
192
|
this.id = config.id;
|
|
@@ -159,16 +203,54 @@ export class RemoteDataset {
|
|
|
159
203
|
get info() {
|
|
160
204
|
return this.pingInfo;
|
|
161
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* `'ask'` (the server answers an ask itself), plus `'transactions'` once the server's `ping`
|
|
208
|
+
* listed them. The select features (grouping, min/max) are not declared, so core refuses such a
|
|
209
|
+
* select before it is sent; nor are `'triples'` and `'triplesTerms'`, which the server's
|
|
210
|
+
* `readQuery` does not answer.
|
|
211
|
+
*/
|
|
212
|
+
get supports() {
|
|
213
|
+
return this.serverHoldsTransactions ? ['ask', 'transactions'] : ['ask'];
|
|
214
|
+
}
|
|
215
|
+
/** Begin a store transaction on the server; resolves its txId. */
|
|
216
|
+
async beginTransaction() {
|
|
217
|
+
var _a;
|
|
218
|
+
const txId = await this.callMethod('beginTransaction', []);
|
|
219
|
+
if (typeof txId !== 'string' || !txId) {
|
|
220
|
+
throw new RemoteDatasetError(`RemoteDataset: the data server${this.url ? ` at ${this.url}` : ''} answered beginTransaction without a transaction id`, (_a = this.url) !== null && _a !== void 0 ? _a : '', 200);
|
|
221
|
+
}
|
|
222
|
+
return txId;
|
|
223
|
+
}
|
|
224
|
+
/** Commit the transaction `txId` on the server. */
|
|
225
|
+
async commit(txId) {
|
|
226
|
+
await this.callMethod('commit', [txId]);
|
|
227
|
+
}
|
|
228
|
+
/** Roll back the transaction `txId` on the server (an unknown one is a no-op there). */
|
|
229
|
+
async rollback(txId) {
|
|
230
|
+
await this.callMethod('rollback', [txId]);
|
|
231
|
+
}
|
|
232
|
+
/** Note what a `ping` body says of transactions. */
|
|
233
|
+
notePing(body) {
|
|
234
|
+
const supports = body && typeof body === 'object' ? body.supports : undefined;
|
|
235
|
+
this.serverHoldsTransactions = Array.isArray(supports) && supports.includes('transactions');
|
|
236
|
+
}
|
|
162
237
|
/**
|
|
163
238
|
* In the url form, asks the server's `ping` and resolves its body (also kept as
|
|
164
239
|
* {@link info}); resolves undefined in the no-url form.
|
|
165
240
|
*/
|
|
166
241
|
init() {
|
|
167
|
-
|
|
168
|
-
if (this.url === undefined)
|
|
169
|
-
|
|
242
|
+
var _a;
|
|
243
|
+
if (this.url === undefined) {
|
|
244
|
+
// Queries go to the app's own backend; its ping only says whether it holds transactions.
|
|
245
|
+
(_a = this.initialized) !== null && _a !== void 0 ? _a : (this.initialized = this.callBackend('ping').then((body) => {
|
|
246
|
+
this.notePing(body);
|
|
247
|
+
return undefined;
|
|
248
|
+
}, () => undefined));
|
|
249
|
+
return this.initialized;
|
|
250
|
+
}
|
|
170
251
|
if (!this.initialized) {
|
|
171
252
|
this.initialized = this.post('ping', []).then((body) => {
|
|
253
|
+
this.notePing(body);
|
|
172
254
|
const info = Object.freeze(body && typeof body === 'object' && !Array.isArray(body)
|
|
173
255
|
? { ...body }
|
|
174
256
|
: {});
|
|
@@ -185,11 +267,24 @@ export class RemoteDataset {
|
|
|
185
267
|
// Queries serialize to DSL-JSON for the wire: the live (closed) query can't cross a call
|
|
186
268
|
// as-is, so we ship `toJSON()` and the server's generic provider rehydrates it with
|
|
187
269
|
// `fromJSON()`.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
270
|
+
/**
|
|
271
|
+
* A select, count or ask, posted to the server's one read method, `readQuery`; the server
|
|
272
|
+
* tells the kinds apart by the DSL-JSON `op`. Triples and terms reads are not declared in
|
|
273
|
+
* {@link supports} and are refused here, unsent (the server does not answer them).
|
|
274
|
+
*/
|
|
275
|
+
async readQuery(query) {
|
|
276
|
+
const kind = readKindOf(query);
|
|
277
|
+
switch (kind) {
|
|
278
|
+
case 'select':
|
|
279
|
+
case 'count':
|
|
280
|
+
case 'ask': {
|
|
281
|
+
const live = query;
|
|
282
|
+
return this.call('readQuery', await this.withReadGraphs(live.toJSON()));
|
|
283
|
+
}
|
|
284
|
+
case 'triples':
|
|
285
|
+
case 'triples_terms':
|
|
286
|
+
throw new Error(`RemoteDataset does not answer ${kind === 'triples' ? 'a triples' : 'a terms'} read: the data server's readQuery has none.`);
|
|
287
|
+
}
|
|
193
288
|
}
|
|
194
289
|
updateQuery(query) {
|
|
195
290
|
return this.call('updateQuery', query.toJSON());
|
|
@@ -305,9 +400,12 @@ export class RemoteDataset {
|
|
|
305
400
|
return { ...json, graphs: out };
|
|
306
401
|
}
|
|
307
402
|
call(method, json) {
|
|
403
|
+
return this.callMethod(method, [json]);
|
|
404
|
+
}
|
|
405
|
+
callMethod(method, args) {
|
|
308
406
|
if (this.url === undefined)
|
|
309
|
-
return this.callBackend(method,
|
|
310
|
-
return this.post(method,
|
|
407
|
+
return this.callBackend(method, ...args);
|
|
408
|
+
return this.post(method, args);
|
|
311
409
|
}
|
|
312
410
|
/**
|
|
313
411
|
* Opts in to `rejectOnError`, so a failed call (HTTP error status, or no
|
|
@@ -315,12 +413,12 @@ export class RemoteDataset {
|
|
|
315
413
|
* status and the server's message instead of reading as an empty result.
|
|
316
414
|
* Whatever a successful call returns, `undefined` included, resolves as is.
|
|
317
415
|
*/
|
|
318
|
-
callBackend(method,
|
|
416
|
+
callBackend(method, ...args) {
|
|
319
417
|
// The PACKAGE form, not the shape form. It posts to
|
|
320
418
|
// `/call/<packageName>/<method>` with a body of `{args}` only — no `shapeURI`,
|
|
321
419
|
// no `instanceNode` — and the backend answers from this package's generic
|
|
322
420
|
// provider without resolving or instantiating any Shape.
|
|
323
|
-
return Server.call(packageName, { method, rejectOnError: true },
|
|
421
|
+
return Server.call(packageName, { method, rejectOnError: true }, ...args);
|
|
324
422
|
}
|
|
325
423
|
/**
|
|
326
424
|
* The url form: one POST to `{url}/call/@_linked/server/<method>`. Resolves the parsed
|
|
@@ -340,10 +438,11 @@ export class RemoteDataset {
|
|
|
340
438
|
};
|
|
341
439
|
if (this.token)
|
|
342
440
|
headers.Authorization = `Bearer ${this.token}`;
|
|
343
|
-
if (this.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
441
|
+
if (this.forwardUserToken) {
|
|
442
|
+
// a fresh proof for every request: never cached (each has its own `jti`)
|
|
443
|
+
const userHeaders = await userTokenHeadersFor(target);
|
|
444
|
+
if (userHeaders)
|
|
445
|
+
Object.assign(headers, userHeaders);
|
|
347
446
|
}
|
|
348
447
|
let response;
|
|
349
448
|
try {
|
|
@@ -351,6 +450,9 @@ export class RemoteDataset {
|
|
|
351
450
|
method: 'POST',
|
|
352
451
|
headers,
|
|
353
452
|
body: JSON.stringify({ args }),
|
|
453
|
+
// A redirect is an answer, never followed: the bearer and the user's token and proof
|
|
454
|
+
// go to the data server at `url` and nowhere else.
|
|
455
|
+
redirect: 'manual',
|
|
354
456
|
});
|
|
355
457
|
}
|
|
356
458
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteDataset.js","sourceRoot":"","sources":["../../../../src/shapes/quadstores/RemoteDataset.ts"],"names":[],"mappings":"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","sourcesContent":["import { packageName } from '../../package.js';\nimport { brandLinkedError } from '@_linked/core/utils/linkedError';\nimport { Server } from '@_linked/server-utils/utils/Server';\nimport type { CallContext } from '@_linked/server-utils/utils/CallContext';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\nimport type { SelectQuery } from '@_linked/core/queries/SelectQuery';\nimport type { CountQuery } from '@_linked/core/queries/CountQuery';\nimport type { AskQuery } from '@_linked/core/queries/AskQuery';\nimport type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';\nimport type { CreateQuery } from '@_linked/core/queries/CreateQuery';\nimport type {\n DeleteQuery,\n DeleteResponse,\n} from '@_linked/core/queries/DeleteQuery';\nimport type {\n SelectResult,\n UpdateResult,\n CreateResult,\n GraphQuery,\n GraphResult,\n} from '@_linked/core/queries/IntermediateRepresentation';\nimport {\n GraphQueryError,\n isGraphQueryError,\n unsupportedGraphQuery,\n type GraphQueryErrorCode,\n} from '@_linked/core/queries/graphQuery';\nimport {\n invalidateGraphRegistry,\n noteGraphQuery,\n readGraphRegistry,\n type GraphRegistryRow,\n} from '@_linked/core/utils/graphRegistryCache';\nimport { graphs } from '@_linked/core/ontologies/graphs';\nimport { OntologyGraph, ShapesGraph } from '@_linked/core/shapes/NamedGraph';\n\n/**\n * Constructor argument for `new RemoteDataset(config)`: the `config` object of a\n * `linked.{frontend,backend}.datasets.json` entry, passed verbatim.\n */\nexport interface RemoteDatasetConfig {\n /**\n * Base URL of the data server, e.g. `http://localhost:4040/gateway`; routes are appended\n * to it. Without it, queries go to this process's own backend through `Server.call`.\n * Present but empty is an error, never the no-url form.\n */\n url?: string;\n /** Sent as `Authorization: Bearer <token>` when set and `url` is set. */\n token?: string;\n /** Stable name; becomes the suffix of the store URI: ${DATA_ROOT}/backend-api-store/<name>. */\n name?: string;\n /** Or pass a fully-qualified URI directly. */\n id?: string;\n /**\n * In the url form, send the signed-in user's WebID of the current call as\n * `x-linked-end-user` (see {@link END_USER_HEADER}). Default true; `false` (or the\n * string `'false'`, as an interpolated config gives it) turns it off.\n */\n forwardEndUser?: boolean | string;\n}\n\n/**\n * The header the url form sends with a query made while a signed-in user's HTTP request is\n * being answered: that user's WebID. The data server decides what to do with it; the bearer\n * token still decides which data the store reaches.\n */\nexport const END_USER_HEADER = 'x-linked-end-user';\n\n// The store `@_linked/server-utils/utils/CallContext` keeps the call context in, shared on\n// `globalThis` under this key. Read here instead of importing that module: it imports\n// `node:async_hooks`, and this store also runs in the browser (the no-url form). In a browser,\n// and in a server before any call ran, the key is unset and there is no context.\nconst CALL_CONTEXT_KEY = Symbol.for('@_linked/server-utils:callContext');\n\nfunction currentCallContext(): CallContext | undefined {\n const store = (globalThis as Record<symbol, { getStore?: () => unknown } | undefined>)[CALL_CONTEXT_KEY];\n return typeof store?.getStore === 'function' ? (store.getStore() as CallContext | undefined) : undefined;\n}\n\n/**\n * The WebID of the signed-in user behind the current call: the http context's\n * `request.linkedAuth.userAccount.accountOf.id`. Undefined in a system context\n * (`runAsSystem`), outside any call, and for a request without a session.\n */\nexport function currentEndUserWebId(): string | undefined {\n const ctx = currentCallContext();\n if (!ctx || ctx.kind !== 'http') return undefined;\n const webId = ctx.request?.linkedAuth?.userAccount?.accountOf?.id;\n return typeof webId === 'string' && webId ? webId : undefined;\n}\n\n/**\n * A failed call to the data server at `url`. `status` is the HTTP status (0 when no HTTP\n * answer came back); `code` is the error body's `code` when the server sent one.\n */\nexport class RemoteDatasetError extends Error {\n /** The data server base url ('' when the configured url is empty). */\n readonly url: string;\n /** The HTTP status; 0 = no HTTP answer. */\n readonly status: number;\n /** The error body's `code`, when the server sent one. */\n readonly code?: string;\n\n constructor(message: string, url: string, status: number, code?: string) {\n super(message);\n this.name = new.target.name;\n this.url = url;\n this.status = status;\n if (code !== undefined) this.code = code;\n }\n}\n\n/**\n * Nothing at `url` speaks the query wire: no HTTP answer, an empty url, or (on `ping`) an\n * answer that is not the server's. A boot that gets this should stop.\n */\nexport class RemoteDatasetUnreachableError extends RemoteDatasetError {}\n\n/** The data server answered 401 or 403: it refused this dataset's token. */\nexport class RemoteDatasetUnauthorizedError extends RemoteDatasetError {}\n\n// Branded, so `isLinkedError(err, RemoteDatasetUnreachableError)` from `@_linked/core` matches an\n// error thrown by another copy of this module, where `instanceof` does not. See\n// https://github.com/linked-fw/cli/blob/main/docs/architecture/shared-state-and-error-brands.md.\nbrandLinkedError(RemoteDatasetError, '@_linked/server', 'RemoteDatasetError');\nbrandLinkedError(RemoteDatasetUnreachableError, '@_linked/server', 'RemoteDatasetUnreachableError');\nbrandLinkedError(RemoteDatasetUnauthorizedError, '@_linked/server', 'RemoteDatasetUnauthorizedError');\n\n/**\n * What the data server's `ping` answered, as is. The generic `@_linked/server` ping answers\n * `{ok: true}`; a gateway may add fields that say which dataset it serves, such as\n * `{ok, projectId, branchName, dataset, forked}`. A body that is not a JSON object reads as `{}`.\n */\nexport type RemoteDatasetPingInfo = Readonly<Record<string, unknown>>;\n\ntype Method =\n | 'ping'\n | 'selectQuery'\n | 'askQuery'\n | 'updateQuery'\n | 'createQuery'\n | 'deleteQuery'\n | 'graphQuery';\n\n/** The refusal codes a data server sends back for a graph query (`{error, code}`). */\nconst GRAPH_REFUSAL_CODES: ReadonlySet<string> = new Set<GraphQueryErrorCode>([\n 'unsupported-query-kind',\n 'graph-not-registered',\n 'graph-name-read-only',\n 'graph-exists',\n]);\n\n/**\n * A failed graph query as the caller expects it: a {@link GraphQueryError} when the data server\n * refused it with one of the graph refusal codes, or when it has no graph method at all (404 or\n * 501 without any code: an older server or gateway), which reads as `unsupported-query-kind`\n * so the ontology sync falls back to the default graph. Anything else is passed on as is.\n */\nfunction graphQueryFailure(err: unknown, query: GraphQuery): unknown {\n if (isGraphQueryError(err)) return err;\n const code = (err as { code?: unknown } | null)?.code;\n const message = err instanceof Error ? err.message : String(err);\n let refusal: GraphQueryError | undefined;\n if (typeof code === 'string' && GRAPH_REFUSAL_CODES.has(code)) {\n refusal = new GraphQueryError(code as GraphQueryErrorCode, message, query.graph);\n } else if (code === undefined) {\n const status = (err as { status?: unknown } | null)?.status;\n if (status === 404 || status === 501) refusal = unsupportedGraphQuery(query, message);\n }\n if (!refusal) return err;\n (refusal as { cause?: unknown }).cause = err;\n return refusal;\n}\n\n/** The portable names a remote store expands from its own registry cache, with their record class. */\nconst REGISTRY_EXPANDED: ReadonlyMap<string, string> = new Map([\n [graphs.allOntologies, OntologyGraph.targetClass.id],\n [graphs.allShapes, ShapesGraph.targetClass.id],\n]);\n\nlet nextRegistryKey = 0;\n\n/** On `ping`, these statuses mean nothing at that url serves the query wire. */\nconst PING_UNREACHABLE_STATUSES = new Set([404, 501, 502, 503, 504]);\n\n/**\n * An {@link IDataset} whose queries are answered by a linked server.\n *\n * - **Without `url`** the server is this app's own backend: every query goes through\n * `Server.call`'s package form (`/call/@_linked/server/<method>` with a body of `{args}`),\n * and on a backend `Server`'s local shortcut answers it in process. This is the store a\n * browser uses to talk to its backend.\n * - **With `url`** the server is a remote data server: every query posts\n * `{\"args\": [query.toJSON()]}` to `{url}/call/@_linked/server/<method>` with the bearer\n * `token`, through `fetch`, never through `Server` (whose local shortcut would answer the\n * app's own provider) and never retried (a retried write could apply twice). `init()` asks\n * the server's `ping` and throws {@link RemoteDatasetUnreachableError} or\n * {@link RemoteDatasetUnauthorizedError} when it cannot be used.\n *\n * A store is not a Shape: it addresses the backend by package name, so nothing about the\n * store instance crosses the wire. Query routing is decided by the query's shape.\n *\n * `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must\n * fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves\n * the ping body (also readable as `info`), so a boot can log which dataset the server serves.\n */\nexport class RemoteDataset implements IDataset {\n /**\n * Kept so the config form is still accepted and a store can be named; it is not an\n * addressing mechanism, nothing transmits or resolves it.\n */\n readonly id?: string;\n /** The data server base url, trailing slashes trimmed; undefined in the no-url form. */\n readonly url?: string;\n private readonly token?: string;\n /** Whether the url form sends {@link END_USER_HEADER}; see `RemoteDatasetConfig.forwardEndUser`. */\n readonly forwardEndUser: boolean = true;\n private initialized?: Promise<RemoteDatasetPingInfo | undefined>;\n private pingInfo?: RemoteDatasetPingInfo;\n private readonly instanceRegistryKey = `remote-dataset:${nextRegistryKey++}`;\n private warnedRegistryRead = false;\n\n constructor(config?: RemoteDatasetConfig | string) {\n if (!config) return;\n if (typeof config === 'string') {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config}`;\n return;\n }\n if (typeof config.url === 'string') {\n this.url = config.url.trim().replace(/\\/+$/, '');\n const token = typeof config.token === 'string' ? config.token.trim() : '';\n if (token) this.token = token;\n }\n if (config.forwardEndUser === false || config.forwardEndUser === 'false') {\n this.forwardEndUser = false;\n }\n if (config.id) {\n this.id = config.id;\n return;\n }\n if (config.name) {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config.name}`;\n }\n }\n\n /**\n * The body of the last successful `ping` (see {@link init}); undefined before one, and\n * always in the no-url form.\n */\n get info(): RemoteDatasetPingInfo | undefined {\n return this.pingInfo;\n }\n\n /**\n * In the url form, asks the server's `ping` and resolves its body (also kept as\n * {@link info}); resolves undefined in the no-url form.\n */\n init(): Promise<RemoteDatasetPingInfo | undefined> {\n // No initialization needed when queries are routed to the app's own backend.\n if (this.url === undefined) return Promise.resolve(undefined);\n if (!this.initialized) {\n this.initialized = this.post('ping', []).then(\n (body) => {\n const info: RemoteDatasetPingInfo = Object.freeze(\n body && typeof body === 'object' && !Array.isArray(body)\n ? { ...(body as Record<string, unknown>) }\n : {},\n );\n this.pingInfo = info;\n return info;\n },\n (err) => {\n // Not memoized: the next init() asks again.\n this.initialized = undefined;\n throw err;\n },\n );\n }\n return this.initialized;\n }\n\n // Queries serialize to DSL-JSON for the wire: the live (closed) query can't cross a call\n // as-is, so we ship `toJSON()` and the server's generic provider rehydrates it with\n // `fromJSON()`.\n async selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number> {\n return this.call('selectQuery', await this.withReadGraphs(query.toJSON()));\n }\n\n async askQuery(query: AskQuery): Promise<boolean> {\n return this.call('askQuery', await this.withReadGraphs(query.toJSON()));\n }\n\n updateQuery(query: UpdateQuery): Promise<UpdateResult> {\n return this.call('updateQuery', query.toJSON());\n }\n\n createQuery(query: CreateQuery): Promise<CreateResult> {\n return this.call('createQuery', query.toJSON());\n }\n\n deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n return this.call('deleteQuery', query.toJSON());\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`): the\n * query is plain data, so it is sent as is, the same way and with the same headers as every\n * other query. On success this store's registry cache notes it in the same step (a create adds\n * the graph, a drop removes it), so its next `.from(graphs.allOntologies)` read sees the change.\n * A refusal rejects with a `GraphQueryError` (see `graphQueryFailure`): a server or gateway\n * without the method answers `unsupported-query-kind`.\n */\n async graphQuery(query: GraphQuery): Promise<GraphResult> {\n let result: GraphResult;\n try {\n result = await this.call<GraphResult>('graphQuery', query);\n } catch (err) {\n throw graphQueryFailure(err, query);\n }\n if (this.url !== undefined) noteGraphQuery(this.graphRegistryKey(), query);\n return result;\n }\n\n /**\n * The key this store's graph-registry cache is kept under: the data server's url and the\n * dataset it named in its `ping` answer (a gateway answers `{dataset}`), so stores of the\n * same dataset share one entry; else one key per instance. (`POST /admin/graph-registry-changed`\n * drops every entry of the process, whatever its key.)\n */\n protected graphRegistryKey(): string {\n const dataset = this.pingInfo?.dataset;\n return typeof dataset === 'string' && dataset && this.url\n ? `${this.url}#${dataset}`\n : this.instanceRegistryKey;\n }\n\n /**\n * Drop the cached graph registry of `dataset` (default: this store's, see `graphRegistryKey`):\n * the next read that expands `graphs.allOntologies` re-reads it.\n */\n invalidateGraphRegistry(dataset?: string): void {\n invalidateGraphRegistry(dataset ?? this.graphRegistryKey());\n }\n\n /**\n * Read the data server's graph registry: the records of the ontology and shapes graphs, by\n * class, through the same query wire (the record shapes read the registry graph themselves).\n */\n protected async loadGraphRegistry(): Promise<GraphRegistryRow[]> {\n const rows: GraphRegistryRow[] = [];\n const kinds = [OntologyGraph, ShapesGraph] as const;\n const results = await Promise.all(kinds.map((kind) => kind.select().exec(this)));\n results.forEach((records, i) => {\n const recordClass = kinds[i].targetClass.id;\n for (const record of (records as Array<{ id?: unknown }> | null) ?? []) {\n if (typeof record?.id === 'string' && record.id) rows.push({ graph: record.id, recordClass });\n }\n });\n return rows;\n }\n\n /**\n * In the url form, expand `graphs.allOntologies` and `graphs.allShapes` in a read's `graphs`\n * from this store's registry cache (per dataset, updated by its own graph queries, dropped by a\n * pushed invalidation, re-read after 30 seconds), so the data server reads exactly the graphs\n * this process knows of. Other names (graph IRIs, `graphs.default`, `graphs.union`) go as they\n * are, for the data server's store to resolve. A name that expands to no graph is sent as is,\n * so the data server answers from its own registry; so is every name when the registry cannot\n * be read (the data server refused or failed the record selects), with one warning per store.\n * The no-url form sends `graphs` unchanged: the backend's store expands them.\n */\n private async withReadGraphs(json: unknown): Promise<unknown> {\n if (this.url === undefined || !json || typeof json !== 'object') return json;\n const names = (json as { graphs?: unknown }).graphs;\n if (!Array.isArray(names) || !names.some((name) => REGISTRY_EXPANDED.has(name))) return json;\n let registry: Awaited<ReturnType<typeof readGraphRegistry>>;\n try {\n registry = await readGraphRegistry(this.graphRegistryKey(), () => this.loadGraphRegistry());\n } catch (err) {\n if (!this.warnedRegistryRead) {\n this.warnedRegistryRead = true;\n console.warn(\n `RemoteDataset: could not read the graph registry at ${this.url}; reads send graph names unexpanded:`,\n err instanceof Error ? err.message : err,\n );\n }\n return json;\n }\n const out: string[] = [];\n const add = (iri: string) => {\n if (!out.includes(iri)) out.push(iri);\n };\n for (const name of names as string[]) {\n const recordClass = REGISTRY_EXPANDED.get(name);\n if (!recordClass) {\n add(name);\n continue;\n }\n const listed = [...registry].filter(([, classes]) => classes.has(recordClass)).map(([graph]) => graph);\n if (listed.length) listed.sort().forEach(add);\n else add(name);\n }\n return { ...(json as Record<string, unknown>), graphs: out };\n }\n\n private call<T>(method: Method, json: unknown): Promise<T> {\n if (this.url === undefined) return this.callBackend(method, json);\n return this.post(method, [json]) as Promise<T>;\n }\n\n /**\n * Opts in to `rejectOnError`, so a failed call (HTTP error status, or no\n * provider on the backend) rejects with a `ServerCallError` carrying the\n * status and the server's message instead of reading as an empty result.\n * Whatever a successful call returns, `undefined` included, resolves as is.\n */\n private callBackend<T>(method: string, json: unknown): Promise<T> {\n // The PACKAGE form, not the shape form. It posts to\n // `/call/<packageName>/<method>` with a body of `{args}` only — no `shapeURI`,\n // no `instanceNode` — and the backend answers from this package's generic\n // provider without resolving or instantiating any Shape.\n return Server.call(packageName, { method, rejectOnError: true }, json);\n }\n\n /**\n * The url form: one POST to `{url}/call/@_linked/server/<method>`. Resolves the parsed\n * body (`null` is a valid result: a method that returned `undefined`). Messages name the\n * url and the method, never the token.\n */\n private async post(method: Method, args: unknown[]): Promise<unknown> {\n const base = this.url ?? '';\n if (!base) {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: no data server url is set (the configured url is empty); cannot ${method}`,\n '',\n 0,\n );\n }\n const target = `${base}/call/${packageName}/${method}`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (this.token) headers.Authorization = `Bearer ${this.token}`;\n if (this.forwardEndUser) {\n const endUser = currentEndUserWebId();\n if (endUser) headers[END_USER_HEADER] = endUser;\n }\n\n let response: Response;\n try {\n response = await globalThis.fetch(target, {\n method: 'POST',\n headers,\n body: JSON.stringify({ args }),\n });\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: cannot reach the data server at ${base} (${method}): ${cause}`,\n base,\n 0,\n );\n }\n\n const text = await response.text();\n let body: unknown = undefined;\n let isJson = false;\n if (text) {\n try {\n body = JSON.parse(text);\n isJson = true;\n } catch {\n body = text;\n }\n }\n\n if (response.ok) {\n if (isJson) return body;\n if (method === 'ping') {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: the data server at ${base} answered ping with a body that is not JSON`,\n base,\n response.status,\n );\n }\n if (!text) return undefined;\n throw new RemoteDatasetError(\n `RemoteDataset: the data server at ${base} answered ${method} with a body that is not JSON`,\n base,\n response.status,\n );\n }\n\n const record = body && typeof body === 'object' ? (body as Record<string, unknown>) : undefined;\n const code = typeof record?.code === 'string' ? record.code : undefined;\n const detail =\n typeof record?.error === 'string'\n ? record.error\n : typeof body === 'string' && body\n ? body\n : response.statusText;\n const message = `RemoteDataset: the data server at ${base} answered ${method} with ${response.status}${\n code ? ` (${code})` : ''\n }${detail ? `: ${detail}` : ''}`;\n\n if (response.status === 401 || response.status === 403) {\n throw new RemoteDatasetUnauthorizedError(message, base, response.status, code);\n }\n if (method === 'ping' && (PING_UNREACHABLE_STATUSES.has(response.status) || !isJson)) {\n throw new RemoteDatasetUnreachableError(message, base, response.status, code);\n }\n throw new RemoteDatasetError(message, base, response.status, code);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RemoteDataset.js","sourceRoot":"","sources":["../../../../src/shapes/quadstores/RemoteDataset.ts"],"names":[],"mappings":"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","sourcesContent":["import { packageName } from '../../package.js';\nimport { brandLinkedError } from '@_linked/core/utils/linkedError';\nimport { Server } from '@_linked/server-utils/utils/Server';\nimport type { CallContext } from '@_linked/server-utils/utils/CallContext';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\nimport type { QueryFeature } from '@_linked/core/queries/queryFeatures';\nimport type { SelectQuery } from '@_linked/core/queries/SelectQuery';\nimport type { CountQuery } from '@_linked/core/queries/CountQuery';\nimport type { AskQuery } from '@_linked/core/queries/AskQuery';\nimport { readKindOf, type ReadQuery, type ReadResult } from '@_linked/core/queries/readQuery';\nimport type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';\nimport type { CreateQuery } from '@_linked/core/queries/CreateQuery';\nimport type {\n DeleteQuery,\n DeleteResponse,\n} from '@_linked/core/queries/DeleteQuery';\nimport type {\n UpdateResult,\n CreateResult,\n GraphQuery,\n GraphResult,\n} from '@_linked/core/queries/IntermediateRepresentation';\nimport {\n GraphQueryError,\n isGraphQueryError,\n unsupportedGraphQuery,\n type GraphQueryErrorCode,\n} from '@_linked/core/queries/graphQuery';\nimport {\n invalidateGraphRegistry,\n noteGraphQuery,\n readGraphRegistry,\n type GraphRegistryRow,\n} from '@_linked/core/utils/graphRegistryCache';\nimport { graphs } from '@_linked/core/ontologies/graphs';\nimport { OntologyGraph, ShapesGraph } from '@_linked/core/shapes/NamedGraph';\n\n/**\n * Constructor argument for `new RemoteDataset(config)`: the `config` object of a\n * `linked.{frontend,backend}.datasets.json` entry, passed verbatim.\n */\nexport interface RemoteDatasetConfig {\n /**\n * Base URL of the data server, e.g. `http://localhost:4040/gateway`; routes are appended\n * to it. Without it, queries go to this process's own backend through `Server.call`.\n * Present but empty is an error, never the no-url form.\n */\n url?: string;\n /** Sent as `Authorization: Bearer <token>` when set and `url` is set. */\n token?: string;\n /** Stable name; becomes the suffix of the store URI: ${DATA_ROOT}/backend-api-store/<name>. */\n name?: string;\n /** Or pass a fully-qualified URI directly. */\n id?: string;\n /**\n * In the url form, send the signed-in user's own access token with each request made\n * while their HTTP request is answered (see {@link USER_TOKEN_HEADER}). Default true;\n * `false` (or the string `'false'`, as an interpolated config gives it) turns it off: do so\n * for a data server you do not trust with your users' tokens.\n */\n forwardUserToken?: boolean | string;\n}\n\n/**\n * The header the url form sends the signed-in user's Solid-OIDC access token in, with a\n * `DPoP` proof for that request beside it. Both come from {@link USER_TOKEN_HEADERS_HOOK};\n * the data server verifies them and acts as that user. The bearer token still decides which\n * data the store reaches.\n */\nexport const USER_TOKEN_HEADER = 'x-linked-user-token';\n\n/**\n * The `globalThis` key of the hook that answers the user token headers for a request:\n * `(request, {method, url}) => Promise<{'x-linked-user-token': string; DPoP: string} | undefined>`.\n * `@_linked/auth`'s backend installs it; read here instead of importing auth, so this store\n * stays browser-safe and free of auth.\n */\nexport const USER_TOKEN_HEADERS_HOOK = Symbol.for('@_linked/auth:userTokenHeaders');\n\ntype UserTokenHeadersHook = (\n request: unknown,\n target: { method: string; url: string },\n) => Promise<unknown>;\n\n// The store `@_linked/server-utils/utils/CallContext` keeps the call context in, shared on\n// `globalThis` under this key. Read here instead of importing that module: it imports\n// `node:async_hooks`, and this store also runs in the browser (the no-url form). In a browser,\n// and in a server before any call ran, the key is unset and there is no context.\nconst CALL_CONTEXT_KEY = Symbol.for('@_linked/server-utils:callContext');\n\nfunction currentCallContext(): CallContext | undefined {\n const store = (globalThis as Record<symbol, { getStore?: () => unknown } | undefined>)[CALL_CONTEXT_KEY];\n return typeof store?.getStore === 'function' ? (store.getStore() as CallContext | undefined) : undefined;\n}\n\nlet warnedUserTokenHook = false;\nconst warnUserTokenHook = (problem: string): void => {\n if (warnedUserTokenHook) return;\n warnedUserTokenHook = true;\n console.warn(`RemoteDataset: the user token hook ${problem}; queries go without the user's token`);\n};\n\n/**\n * The user token headers for a request to `url`, made in the current call: the hook's\n * answer for the http context's request. Undefined in a system context (`runAsSystem`),\n * outside any call, without the hook, and when it answers nothing. A hook that throws or\n * answers something else than two non-empty strings sends nothing (one warning per process).\n */\nasync function userTokenHeadersFor(url: string): Promise<Record<string, string> | undefined> {\n const ctx = currentCallContext();\n if (!ctx || ctx.kind !== 'http' || !ctx.request) return undefined;\n const hook = (globalThis as Record<symbol, unknown>)[USER_TOKEN_HEADERS_HOOK];\n if (typeof hook !== 'function') return undefined;\n let answer: unknown;\n try {\n answer = await (hook as UserTokenHeadersHook)(ctx.request, { method: 'POST', url });\n } catch {\n warnUserTokenHook('failed');\n return undefined;\n }\n if (answer === undefined || answer === null) return undefined;\n const token = (answer as Record<string, unknown>)[USER_TOKEN_HEADER];\n const proof = (answer as Record<string, unknown>).DPoP;\n if (typeof token !== 'string' || !token || typeof proof !== 'string' || !proof) {\n warnUserTokenHook('answered something else than a token and a proof');\n return undefined;\n }\n return { [USER_TOKEN_HEADER]: token, DPoP: proof };\n}\n\n/**\n * A failed call to the data server at `url`. `status` is the HTTP status (0 when no HTTP\n * answer came back); `code` is the error body's `code` when the server sent one.\n */\nexport class RemoteDatasetError extends Error {\n /** The data server base url ('' when the configured url is empty). */\n readonly url: string;\n /** The HTTP status; 0 = no HTTP answer. */\n readonly status: number;\n /** The error body's `code`, when the server sent one. */\n readonly code?: string;\n\n constructor(message: string, url: string, status: number, code?: string) {\n super(message);\n this.name = new.target.name;\n this.url = url;\n this.status = status;\n if (code !== undefined) this.code = code;\n }\n}\n\n/**\n * Nothing at `url` speaks the query wire: no HTTP answer, an empty url, or (on `ping`) an\n * answer that is not the server's. A boot that gets this should stop.\n */\nexport class RemoteDatasetUnreachableError extends RemoteDatasetError {}\n\n/** The data server answered 401 or 403: it refused this dataset's token. */\nexport class RemoteDatasetUnauthorizedError extends RemoteDatasetError {}\n\n// Branded, so `isLinkedError(err, RemoteDatasetUnreachableError)` from `@_linked/core` matches an\n// error thrown by another copy of this module, where `instanceof` does not. See\n// https://github.com/linked-fw/cli/blob/main/docs/architecture/shared-state-and-error-brands.md.\nbrandLinkedError(RemoteDatasetError, '@_linked/server', 'RemoteDatasetError');\nbrandLinkedError(RemoteDatasetUnreachableError, '@_linked/server', 'RemoteDatasetUnreachableError');\nbrandLinkedError(RemoteDatasetUnauthorizedError, '@_linked/server', 'RemoteDatasetUnauthorizedError');\n\n/**\n * What the data server's `ping` answered, as is. The generic `@_linked/server` ping answers\n * `{ok: true}`; a gateway may add fields that say which dataset it serves, such as\n * `{ok, projectId, branchName, dataset, forked}`. A body that is not a JSON object reads as `{}`.\n */\nexport type RemoteDatasetPingInfo = Readonly<Record<string, unknown>>;\n\ntype Method =\n | 'ping'\n | 'readQuery'\n | 'updateQuery'\n | 'createQuery'\n | 'deleteQuery'\n | 'graphQuery'\n | 'beginTransaction'\n | 'commit'\n | 'rollback';\n\n/** The refusal codes a data server sends back for a graph query (`{error, code}`). */\nconst GRAPH_REFUSAL_CODES: ReadonlySet<string> = new Set<GraphQueryErrorCode>([\n 'unsupported-query-kind',\n 'graph-not-registered',\n 'graph-name-read-only',\n 'graph-exists',\n]);\n\n/**\n * A failed graph query as the caller expects it: a {@link GraphQueryError} when the data server\n * refused it with one of the graph refusal codes, or when it has no graph method at all (404 or\n * 501 without any code: an older server or gateway), which reads as `unsupported-query-kind`\n * so the ontology sync falls back to the default graph. Anything else is passed on as is.\n */\nfunction graphQueryFailure(err: unknown, query: GraphQuery): unknown {\n if (isGraphQueryError(err)) return err;\n const code = (err as { code?: unknown } | null)?.code;\n const message = err instanceof Error ? err.message : String(err);\n let refusal: GraphQueryError | undefined;\n if (typeof code === 'string' && GRAPH_REFUSAL_CODES.has(code)) {\n refusal = new GraphQueryError(code as GraphQueryErrorCode, message, query.graph);\n } else if (code === undefined) {\n const status = (err as { status?: unknown } | null)?.status;\n if (status === 404 || status === 501) refusal = unsupportedGraphQuery(query, message);\n }\n if (!refusal) return err;\n (refusal as { cause?: unknown }).cause = err;\n return refusal;\n}\n\n/** The portable names a remote store expands from its own registry cache, with their record class. */\nconst REGISTRY_EXPANDED: ReadonlyMap<string, string> = new Map([\n [graphs.allOntologies, OntologyGraph.targetClass.id],\n [graphs.allShapes, ShapesGraph.targetClass.id],\n]);\n\nlet nextRegistryKey = 0;\n\n/** On `ping`, these statuses mean nothing at that url serves the query wire. */\nconst PING_UNREACHABLE_STATUSES = new Set([404, 501, 502, 503, 504]);\n\n/**\n * An {@link IDataset} whose queries are answered by a linked server.\n *\n * - **Without `url`** the server is this app's own backend: every query goes through\n * `Server.call`'s package form (`/call/@_linked/server/<method>` with a body of `{args}`),\n * and on a backend `Server`'s local shortcut answers it in process. This is the store a\n * browser uses to talk to its backend.\n * - **With `url`** the server is a remote data server: every query posts\n * `{\"args\": [query.toJSON()]}` to `{url}/call/@_linked/server/<method>` with the bearer\n * `token`, through `fetch`, never through `Server` (whose local shortcut would answer the\n * app's own provider) and never retried (a retried write could apply twice). `init()` asks\n * the server's `ping` and throws {@link RemoteDatasetUnreachableError} or\n * {@link RemoteDatasetUnauthorizedError} when it cannot be used.\n *\n * A store is not a Shape: it addresses the backend by package name, so nothing about the\n * store instance crosses the wire. Query routing is decided by the query's shape.\n *\n * `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must\n * fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves\n * the ping body (also readable as `info`), so a boot can log which dataset the server serves.\n *\n * **Store transactions** (core's `LinkedStorage.begin()`): the store declares `'transactions'`\n * in `supports` only once the server's `ping` listed it (`supports: ['transactions']`), so a\n * server that does not hold them is never sent a txId; until then a transaction warns once and\n * runs its queries directly. `beginTransaction()`, `commit(txId)` and `rollback(txId)` post to\n * the methods of the same names, never retried; a query's txId rides inside its `toJSON()`. In\n * the no-url form `init()` asks the backend's `ping` for this alone (a failed one leaves\n * transactions off, and `init()` still resolves undefined).\n */\nexport class RemoteDataset implements IDataset {\n /**\n * Kept so the config form is still accepted and a store can be named; it is not an\n * addressing mechanism, nothing transmits or resolves it.\n */\n readonly id?: string;\n /** The data server base url, trailing slashes trimmed; undefined in the no-url form. */\n readonly url?: string;\n private readonly token?: string;\n /** Whether the url form sends {@link USER_TOKEN_HEADER}; see `RemoteDatasetConfig.forwardUserToken`. */\n readonly forwardUserToken: boolean = true;\n private initialized?: Promise<RemoteDatasetPingInfo | undefined>;\n private pingInfo?: RemoteDatasetPingInfo;\n /** Whether the server's last `ping` listed `'transactions'` in its `supports`. */\n private serverHoldsTransactions = false;\n private readonly instanceRegistryKey = `remote-dataset:${nextRegistryKey++}`;\n private warnedRegistryRead = false;\n\n constructor(config?: RemoteDatasetConfig | string) {\n if (!config) return;\n if (typeof config === 'string') {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config}`;\n return;\n }\n if (typeof config.url === 'string') {\n this.url = config.url.trim().replace(/\\/+$/, '');\n const token = typeof config.token === 'string' ? config.token.trim() : '';\n if (token) this.token = token;\n }\n if (config.forwardUserToken === false || config.forwardUserToken === 'false') {\n this.forwardUserToken = false;\n }\n if (config.id) {\n this.id = config.id;\n return;\n }\n if (config.name) {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config.name}`;\n }\n }\n\n /**\n * The body of the last successful `ping` (see {@link init}); undefined before one, and\n * always in the no-url form.\n */\n get info(): RemoteDatasetPingInfo | undefined {\n return this.pingInfo;\n }\n\n /**\n * `'ask'` (the server answers an ask itself), plus `'transactions'` once the server's `ping`\n * listed them. The select features (grouping, min/max) are not declared, so core refuses such a\n * select before it is sent; nor are `'triples'` and `'triplesTerms'`, which the server's\n * `readQuery` does not answer.\n */\n get supports(): readonly QueryFeature[] {\n return this.serverHoldsTransactions ? ['ask', 'transactions'] : ['ask'];\n }\n\n /** Begin a store transaction on the server; resolves its txId. */\n async beginTransaction(): Promise<string> {\n const txId = await this.callMethod<unknown>('beginTransaction', []);\n if (typeof txId !== 'string' || !txId) {\n throw new RemoteDatasetError(\n `RemoteDataset: the data server${this.url ? ` at ${this.url}` : ''} answered beginTransaction without a transaction id`,\n this.url ?? '',\n 200,\n );\n }\n return txId;\n }\n\n /** Commit the transaction `txId` on the server. */\n async commit(txId: string): Promise<void> {\n await this.callMethod('commit', [txId]);\n }\n\n /** Roll back the transaction `txId` on the server (an unknown one is a no-op there). */\n async rollback(txId: string): Promise<void> {\n await this.callMethod('rollback', [txId]);\n }\n\n /** Note what a `ping` body says of transactions. */\n private notePing(body: unknown): void {\n const supports = body && typeof body === 'object' ? (body as { supports?: unknown }).supports : undefined;\n this.serverHoldsTransactions = Array.isArray(supports) && supports.includes('transactions');\n }\n\n /**\n * In the url form, asks the server's `ping` and resolves its body (also kept as\n * {@link info}); resolves undefined in the no-url form.\n */\n init(): Promise<RemoteDatasetPingInfo | undefined> {\n if (this.url === undefined) {\n // Queries go to the app's own backend; its ping only says whether it holds transactions.\n this.initialized ??= this.callBackend<unknown>('ping').then(\n (body) => {\n this.notePing(body);\n return undefined;\n },\n () => undefined,\n );\n return this.initialized;\n }\n if (!this.initialized) {\n this.initialized = this.post('ping', []).then(\n (body) => {\n this.notePing(body);\n const info: RemoteDatasetPingInfo = Object.freeze(\n body && typeof body === 'object' && !Array.isArray(body)\n ? { ...(body as Record<string, unknown>) }\n : {},\n );\n this.pingInfo = info;\n return info;\n },\n (err) => {\n // Not memoized: the next init() asks again.\n this.initialized = undefined;\n throw err;\n },\n );\n }\n return this.initialized;\n }\n\n // Queries serialize to DSL-JSON for the wire: the live (closed) query can't cross a call\n // as-is, so we ship `toJSON()` and the server's generic provider rehydrates it with\n // `fromJSON()`.\n\n /**\n * A select, count or ask, posted to the server's one read method, `readQuery`; the server\n * tells the kinds apart by the DSL-JSON `op`. Triples and terms reads are not declared in\n * {@link supports} and are refused here, unsent (the server does not answer them).\n */\n async readQuery<Q extends ReadQuery>(query: Q): Promise<ReadResult<Q>> {\n const kind = readKindOf(query);\n switch (kind) {\n case 'select':\n case 'count':\n case 'ask': {\n const live = query as SelectQuery | CountQuery | AskQuery;\n return this.call<ReadResult<Q>>('readQuery', await this.withReadGraphs(live.toJSON()));\n }\n case 'triples':\n case 'triples_terms':\n throw new Error(\n `RemoteDataset does not answer ${kind === 'triples' ? 'a triples' : 'a terms'} read: the data server's readQuery has none.`,\n );\n }\n }\n\n updateQuery(query: UpdateQuery): Promise<UpdateResult> {\n return this.call('updateQuery', query.toJSON());\n }\n\n createQuery(query: CreateQuery): Promise<CreateResult> {\n return this.call('createQuery', query.toJSON());\n }\n\n deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n return this.call('deleteQuery', query.toJSON());\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`): the\n * query is plain data, so it is sent as is, the same way and with the same headers as every\n * other query. On success this store's registry cache notes it in the same step (a create adds\n * the graph, a drop removes it), so its next `.from(graphs.allOntologies)` read sees the change.\n * A refusal rejects with a `GraphQueryError` (see `graphQueryFailure`): a server or gateway\n * without the method answers `unsupported-query-kind`.\n */\n async graphQuery(query: GraphQuery): Promise<GraphResult> {\n let result: GraphResult;\n try {\n result = await this.call<GraphResult>('graphQuery', query);\n } catch (err) {\n throw graphQueryFailure(err, query);\n }\n if (this.url !== undefined) noteGraphQuery(this.graphRegistryKey(), query);\n return result;\n }\n\n /**\n * The key this store's graph-registry cache is kept under: the data server's url and the\n * dataset it named in its `ping` answer (a gateway answers `{dataset}`), so stores of the\n * same dataset share one entry; else one key per instance. (`POST /admin/graph-registry-changed`\n * drops every entry of the process, whatever its key.)\n */\n protected graphRegistryKey(): string {\n const dataset = this.pingInfo?.dataset;\n return typeof dataset === 'string' && dataset && this.url\n ? `${this.url}#${dataset}`\n : this.instanceRegistryKey;\n }\n\n /**\n * Drop the cached graph registry of `dataset` (default: this store's, see `graphRegistryKey`):\n * the next read that expands `graphs.allOntologies` re-reads it.\n */\n invalidateGraphRegistry(dataset?: string): void {\n invalidateGraphRegistry(dataset ?? this.graphRegistryKey());\n }\n\n /**\n * Read the data server's graph registry: the records of the ontology and shapes graphs, by\n * class, through the same query wire (the record shapes read the registry graph themselves).\n */\n protected async loadGraphRegistry(): Promise<GraphRegistryRow[]> {\n const rows: GraphRegistryRow[] = [];\n const kinds = [OntologyGraph, ShapesGraph] as const;\n const results = await Promise.all(kinds.map((kind) => kind.select().exec(this)));\n results.forEach((records, i) => {\n const recordClass = kinds[i].targetClass.id;\n for (const record of (records as Array<{ id?: unknown }> | null) ?? []) {\n if (typeof record?.id === 'string' && record.id) rows.push({ graph: record.id, recordClass });\n }\n });\n return rows;\n }\n\n /**\n * In the url form, expand `graphs.allOntologies` and `graphs.allShapes` in a read's `graphs`\n * from this store's registry cache (per dataset, updated by its own graph queries, dropped by a\n * pushed invalidation, re-read after 30 seconds), so the data server reads exactly the graphs\n * this process knows of. Other names (graph IRIs, `graphs.default`, `graphs.union`) go as they\n * are, for the data server's store to resolve. A name that expands to no graph is sent as is,\n * so the data server answers from its own registry; so is every name when the registry cannot\n * be read (the data server refused or failed the record selects), with one warning per store.\n * The no-url form sends `graphs` unchanged: the backend's store expands them.\n */\n private async withReadGraphs(json: unknown): Promise<unknown> {\n if (this.url === undefined || !json || typeof json !== 'object') return json;\n const names = (json as { graphs?: unknown }).graphs;\n if (!Array.isArray(names) || !names.some((name) => REGISTRY_EXPANDED.has(name))) return json;\n let registry: Awaited<ReturnType<typeof readGraphRegistry>>;\n try {\n registry = await readGraphRegistry(this.graphRegistryKey(), () => this.loadGraphRegistry());\n } catch (err) {\n if (!this.warnedRegistryRead) {\n this.warnedRegistryRead = true;\n console.warn(\n `RemoteDataset: could not read the graph registry at ${this.url}; reads send graph names unexpanded:`,\n err instanceof Error ? err.message : err,\n );\n }\n return json;\n }\n const out: string[] = [];\n const add = (iri: string) => {\n if (!out.includes(iri)) out.push(iri);\n };\n for (const name of names as string[]) {\n const recordClass = REGISTRY_EXPANDED.get(name);\n if (!recordClass) {\n add(name);\n continue;\n }\n const listed = [...registry].filter(([, classes]) => classes.has(recordClass)).map(([graph]) => graph);\n if (listed.length) listed.sort().forEach(add);\n else add(name);\n }\n return { ...(json as Record<string, unknown>), graphs: out };\n }\n\n private call<T>(method: Method, json: unknown): Promise<T> {\n return this.callMethod<T>(method, [json]);\n }\n\n private callMethod<T>(method: Method, args: unknown[]): Promise<T> {\n if (this.url === undefined) return this.callBackend(method, ...args);\n return this.post(method, args) as Promise<T>;\n }\n\n /**\n * Opts in to `rejectOnError`, so a failed call (HTTP error status, or no\n * provider on the backend) rejects with a `ServerCallError` carrying the\n * status and the server's message instead of reading as an empty result.\n * Whatever a successful call returns, `undefined` included, resolves as is.\n */\n private callBackend<T>(method: string, ...args: unknown[]): Promise<T> {\n // The PACKAGE form, not the shape form. It posts to\n // `/call/<packageName>/<method>` with a body of `{args}` only — no `shapeURI`,\n // no `instanceNode` — and the backend answers from this package's generic\n // provider without resolving or instantiating any Shape.\n return Server.call(packageName, { method, rejectOnError: true }, ...args);\n }\n\n /**\n * The url form: one POST to `{url}/call/@_linked/server/<method>`. Resolves the parsed\n * body (`null` is a valid result: a method that returned `undefined`). Messages name the\n * url and the method, never the token.\n */\n private async post(method: Method, args: unknown[]): Promise<unknown> {\n const base = this.url ?? '';\n if (!base) {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: no data server url is set (the configured url is empty); cannot ${method}`,\n '',\n 0,\n );\n }\n const target = `${base}/call/${packageName}/${method}`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (this.token) headers.Authorization = `Bearer ${this.token}`;\n if (this.forwardUserToken) {\n // a fresh proof for every request: never cached (each has its own `jti`)\n const userHeaders = await userTokenHeadersFor(target);\n if (userHeaders) Object.assign(headers, userHeaders);\n }\n\n let response: Response;\n try {\n response = await globalThis.fetch(target, {\n method: 'POST',\n headers,\n body: JSON.stringify({ args }),\n // A redirect is an answer, never followed: the bearer and the user's token and proof\n // go to the data server at `url` and nowhere else.\n redirect: 'manual',\n });\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: cannot reach the data server at ${base} (${method}): ${cause}`,\n base,\n 0,\n );\n }\n\n const text = await response.text();\n let body: unknown = undefined;\n let isJson = false;\n if (text) {\n try {\n body = JSON.parse(text);\n isJson = true;\n } catch {\n body = text;\n }\n }\n\n if (response.ok) {\n if (isJson) return body;\n if (method === 'ping') {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: the data server at ${base} answered ping with a body that is not JSON`,\n base,\n response.status,\n );\n }\n if (!text) return undefined;\n throw new RemoteDatasetError(\n `RemoteDataset: the data server at ${base} answered ${method} with a body that is not JSON`,\n base,\n response.status,\n );\n }\n\n const record = body && typeof body === 'object' ? (body as Record<string, unknown>) : undefined;\n const code = typeof record?.code === 'string' ? record.code : undefined;\n const detail =\n typeof record?.error === 'string'\n ? record.error\n : typeof body === 'string' && body\n ? body\n : response.statusText;\n const message = `RemoteDataset: the data server at ${base} answered ${method} with ${response.status}${\n code ? ` (${code})` : ''\n }${detail ? `: ${detail}` : ''}`;\n\n if (response.status === 401 || response.status === 403) {\n throw new RemoteDatasetUnauthorizedError(message, base, response.status, code);\n }\n if (method === 'ping' && (PING_UNREACHABLE_STATUSES.has(response.status) || !isJson)) {\n throw new RemoteDatasetUnreachableError(message, base, response.status, code);\n }\n throw new RemoteDatasetError(message, base, response.status, code);\n }\n}\n"]}
|