@_linked/server 3.13.0 → 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 +33 -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 +50 -37
- package/lib/esm/shapes/LinkedServer.js +121 -76
- package/lib/esm/shapes/LinkedServer.js.map +1 -1
- package/lib/esm/shapes/quadstores/RemoteDataset.d.ts +35 -6
- package/lib/esm/shapes/quadstores/RemoteDataset.js +77 -12
- package/lib/esm/shapes/quadstores/RemoteDataset.js.map +1 -1
- package/lib/esm/utils/adminChannel.d.ts +22 -51
- package/lib/esm/utils/adminChannel.js +48 -36
- 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 +37 -20
- package/lib/esm/utils/graphTargets.js +61 -82
- package/lib/esm/utils/graphTargets.js.map +1 -1
- package/lib/esm/utils/ontologySync.d.ts +40 -23
- package/lib/esm/utils/ontologySync.js +59 -37
- 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 +120 -8
- package/lib/esm/utils/syncShapes.js +151 -32
- package/lib/esm/utils/syncShapes.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +134 -24
|
@@ -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
|
|
@@ -94,6 +93,14 @@ export type RemoteDatasetPingInfo = Readonly<Record<string, unknown>>;
|
|
|
94
93
|
* `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must
|
|
95
94
|
* fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves
|
|
96
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).
|
|
97
104
|
*/
|
|
98
105
|
export declare class RemoteDataset implements IDataset {
|
|
99
106
|
/**
|
|
@@ -108,6 +115,8 @@ export declare class RemoteDataset implements IDataset {
|
|
|
108
115
|
readonly forwardUserToken: boolean;
|
|
109
116
|
private initialized?;
|
|
110
117
|
private pingInfo?;
|
|
118
|
+
/** Whether the server's last `ping` listed `'transactions'` in its `supports`. */
|
|
119
|
+
private serverHoldsTransactions;
|
|
111
120
|
private readonly instanceRegistryKey;
|
|
112
121
|
private warnedRegistryRead;
|
|
113
122
|
constructor(config?: RemoteDatasetConfig | string);
|
|
@@ -116,13 +125,32 @@ export declare class RemoteDataset implements IDataset {
|
|
|
116
125
|
* always in the no-url form.
|
|
117
126
|
*/
|
|
118
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;
|
|
119
143
|
/**
|
|
120
144
|
* In the url form, asks the server's `ping` and resolves its body (also kept as
|
|
121
145
|
* {@link info}); resolves undefined in the no-url form.
|
|
122
146
|
*/
|
|
123
147
|
init(): Promise<RemoteDatasetPingInfo | undefined>;
|
|
124
|
-
|
|
125
|
-
|
|
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>>;
|
|
126
154
|
updateQuery(query: UpdateQuery): Promise<UpdateResult>;
|
|
127
155
|
createQuery(query: CreateQuery): Promise<CreateResult>;
|
|
128
156
|
deleteQuery(query: DeleteQuery): Promise<DeleteResponse>;
|
|
@@ -164,6 +192,7 @@ export declare class RemoteDataset implements IDataset {
|
|
|
164
192
|
*/
|
|
165
193
|
private withReadGraphs;
|
|
166
194
|
private call;
|
|
195
|
+
private callMethod;
|
|
167
196
|
/**
|
|
168
197
|
* Opts in to `rejectOnError`, so a failed call (HTTP error status, or no
|
|
169
198
|
* provider on the backend) rejects with a `ServerCallError` carrying the
|
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
@@ -155,11 +156,21 @@ const PING_UNREACHABLE_STATUSES = new Set([404, 501, 502, 503, 504]);
|
|
|
155
156
|
* `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must
|
|
156
157
|
* fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves
|
|
157
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).
|
|
158
167
|
*/
|
|
159
168
|
export class RemoteDataset {
|
|
160
169
|
constructor(config) {
|
|
161
170
|
/** Whether the url form sends {@link USER_TOKEN_HEADER}; see `RemoteDatasetConfig.forwardUserToken`. */
|
|
162
171
|
this.forwardUserToken = true;
|
|
172
|
+
/** Whether the server's last `ping` listed `'transactions'` in its `supports`. */
|
|
173
|
+
this.serverHoldsTransactions = false;
|
|
163
174
|
this.instanceRegistryKey = `remote-dataset:${nextRegistryKey++}`;
|
|
164
175
|
this.warnedRegistryRead = false;
|
|
165
176
|
if (!config)
|
|
@@ -192,16 +203,54 @@ export class RemoteDataset {
|
|
|
192
203
|
get info() {
|
|
193
204
|
return this.pingInfo;
|
|
194
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
|
+
}
|
|
195
237
|
/**
|
|
196
238
|
* In the url form, asks the server's `ping` and resolves its body (also kept as
|
|
197
239
|
* {@link info}); resolves undefined in the no-url form.
|
|
198
240
|
*/
|
|
199
241
|
init() {
|
|
200
|
-
|
|
201
|
-
if (this.url === undefined)
|
|
202
|
-
|
|
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
|
+
}
|
|
203
251
|
if (!this.initialized) {
|
|
204
252
|
this.initialized = this.post('ping', []).then((body) => {
|
|
253
|
+
this.notePing(body);
|
|
205
254
|
const info = Object.freeze(body && typeof body === 'object' && !Array.isArray(body)
|
|
206
255
|
? { ...body }
|
|
207
256
|
: {});
|
|
@@ -218,11 +267,24 @@ export class RemoteDataset {
|
|
|
218
267
|
// Queries serialize to DSL-JSON for the wire: the live (closed) query can't cross a call
|
|
219
268
|
// as-is, so we ship `toJSON()` and the server's generic provider rehydrates it with
|
|
220
269
|
// `fromJSON()`.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
+
}
|
|
226
288
|
}
|
|
227
289
|
updateQuery(query) {
|
|
228
290
|
return this.call('updateQuery', query.toJSON());
|
|
@@ -338,9 +400,12 @@ export class RemoteDataset {
|
|
|
338
400
|
return { ...json, graphs: out };
|
|
339
401
|
}
|
|
340
402
|
call(method, json) {
|
|
403
|
+
return this.callMethod(method, [json]);
|
|
404
|
+
}
|
|
405
|
+
callMethod(method, args) {
|
|
341
406
|
if (this.url === undefined)
|
|
342
|
-
return this.callBackend(method,
|
|
343
|
-
return this.post(method,
|
|
407
|
+
return this.callBackend(method, ...args);
|
|
408
|
+
return this.post(method, args);
|
|
344
409
|
}
|
|
345
410
|
/**
|
|
346
411
|
* Opts in to `rejectOnError`, so a failed call (HTTP error status, or no
|
|
@@ -348,12 +413,12 @@ export class RemoteDataset {
|
|
|
348
413
|
* status and the server's message instead of reading as an empty result.
|
|
349
414
|
* Whatever a successful call returns, `undefined` included, resolves as is.
|
|
350
415
|
*/
|
|
351
|
-
callBackend(method,
|
|
416
|
+
callBackend(method, ...args) {
|
|
352
417
|
// The PACKAGE form, not the shape form. It posts to
|
|
353
418
|
// `/call/<packageName>/<method>` with a body of `{args}` only — no `shapeURI`,
|
|
354
419
|
// no `instanceNode` — and the backend answers from this package's generic
|
|
355
420
|
// provider without resolving or instantiating any Shape.
|
|
356
|
-
return Server.call(packageName, { method, rejectOnError: true },
|
|
421
|
+
return Server.call(packageName, { method, rejectOnError: true }, ...args);
|
|
357
422
|
}
|
|
358
423
|
/**
|
|
359
424
|
* The url form: one POST to `{url}/call/@_linked/server/<method>`. Resolves the parsed
|
|
@@ -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 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 | '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 USER_TOKEN_HEADER}; see `RemoteDatasetConfig.forwardUserToken`. */\n readonly forwardUserToken: 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.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 * 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.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"]}
|
|
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"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type express from 'express';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { GraphContents } from './syncShapes.js';
|
|
3
|
+
import type { GraphTargets } from '@_linked/core/utils/graphTargets';
|
|
4
4
|
import { type EffectiveRoutes } from './effectiveRoutes.js';
|
|
5
5
|
/** The environment variable that holds the control channel's secret. */
|
|
6
6
|
export declare const ADMIN_SECRET_ENV = "APP_ADMIN_SECRET";
|
|
@@ -8,47 +8,6 @@ export declare const ADMIN_SECRET_ENV = "APP_ADMIN_SECRET";
|
|
|
8
8
|
export declare const ADMIN_SECRET_HEADER = "x-app-admin-secret";
|
|
9
9
|
/** The control channel's secret: `APP_ADMIN_SECRET`, else `undefined` (no control channel). */
|
|
10
10
|
export declare function resolveAdminSecret(env?: Record<string, string | undefined>): string | undefined;
|
|
11
|
-
/** What one shape sync did. */
|
|
12
|
-
export interface ShapeSyncResult {
|
|
13
|
-
/**
|
|
14
|
-
* Shape graphs written (package graphs and the app's own graph) because their stored hash
|
|
15
|
-
* differed; on a store without graph queries, the default-graph sync's writes.
|
|
16
|
-
*/
|
|
17
|
-
written: number;
|
|
18
|
-
/** The orphan policy the sync ran with (see utils/syncShapes). */
|
|
19
|
-
orphanScope?: 'all' | 'none';
|
|
20
|
-
/** Set when no sync ran: shape sync is turned off, or there is no default dataset. */
|
|
21
|
-
skipped?: 'disabled' | 'no-default-dataset';
|
|
22
|
-
/**
|
|
23
|
-
* The shape graph sync: packages written and skipped, the graphs written, the app's own shape
|
|
24
|
-
* graph, the registered packages without `shapes.ttl` (so without a shape graph) and the
|
|
25
|
-
* default-graph shapes removed. Absent when no sync ran.
|
|
26
|
-
*/
|
|
27
|
-
shapeGraphs?: ShapeGraphsReport;
|
|
28
|
-
/**
|
|
29
|
-
* The requested packages whose source was reloaded before the sync (dev only: a built
|
|
30
|
-
* app reloads nothing). Set by `POST /admin/sync-shapes`.
|
|
31
|
-
*/
|
|
32
|
-
reloaded?: string[];
|
|
33
|
-
/** The node shape IRIs registered in the process after the reload. Set by `POST /admin/sync-shapes`. */
|
|
34
|
-
shapes?: string[];
|
|
35
|
-
/**
|
|
36
|
-
* The ontology sync: packages whose ontology graph (or, on a store without graph queries,
|
|
37
|
-
* class hierarchy) was written, packages left as they were (unchanged, or their data could not
|
|
38
|
-
* be read), and the graphs written and dropped. Set by `POST /admin/sync-shapes`.
|
|
39
|
-
*/
|
|
40
|
-
ontologies?: {
|
|
41
|
-
written: string[];
|
|
42
|
-
skipped: string[];
|
|
43
|
-
graphs?: string[];
|
|
44
|
-
droppedGraphs?: string[];
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* The shape check's findings, counted by severity (each is logged), and the message of the
|
|
48
|
-
* error the check threw, if it did (`failed`). Set by `POST /admin/sync-shapes`.
|
|
49
|
-
*/
|
|
50
|
-
shapeCheck?: ShapeCheckSummary;
|
|
51
|
-
}
|
|
52
11
|
/** Whether `name` is a valid npm package name (and so can never be a path like `..`). */
|
|
53
12
|
export declare function isValidPackageName(name: string): boolean;
|
|
54
13
|
/**
|
|
@@ -60,13 +19,24 @@ export declare function assertReloadablePackages(packages: string[], allowed: Re
|
|
|
60
19
|
/** Thrown by a handler for a request the channel refuses with 400. */
|
|
61
20
|
export declare class AdminRequestError extends Error {
|
|
62
21
|
}
|
|
22
|
+
/** Thrown by a handler for a request that conflicts with how the process runs: answered 409. */
|
|
23
|
+
export declare class AdminConflictError extends Error {
|
|
24
|
+
}
|
|
25
|
+
/** The answer of `POST /admin/graph-content`. */
|
|
26
|
+
export interface GraphContentAnswer extends GraphContents {
|
|
27
|
+
/** The process's graph targets (both `'package'`, or the route answers 409). */
|
|
28
|
+
graphTargets: GraphTargets;
|
|
29
|
+
/** The requested packages whose source was reloaded first (dev only). */
|
|
30
|
+
reloaded: string[];
|
|
31
|
+
}
|
|
32
|
+
/** The body of a graph-content request: `packages` limits the answer, `reload` is reloaded first. */
|
|
33
|
+
export declare function parseGraphContentBody(body: unknown): {
|
|
34
|
+
packages?: string[];
|
|
35
|
+
reload: string[];
|
|
36
|
+
};
|
|
63
37
|
export interface AdminChannelHandlers {
|
|
64
38
|
/** Exit the process (the host restarts it). Called after the 202 is sent. */
|
|
65
39
|
exit: () => void;
|
|
66
|
-
/** Reload `packages` (when given) and sync the registered shapes into app data. */
|
|
67
|
-
syncShapes: (request: {
|
|
68
|
-
packages: string[];
|
|
69
|
-
}) => Promise<ShapeSyncResult>;
|
|
70
40
|
/**
|
|
71
41
|
* Drop the cached graph registry for `dataset` (see `POST /admin/graph-registry-changed`).
|
|
72
42
|
* Default: {@link dropGraphRegistryCache}.
|
|
@@ -74,6 +44,11 @@ export interface AdminChannelHandlers {
|
|
|
74
44
|
graphRegistryChanged?: (dataset: string) => void;
|
|
75
45
|
/** The effective graph routes (see `GET /admin/routes`). Default: {@link effectiveGraphRoutes}. */
|
|
76
46
|
routes?: () => EffectiveRoutes;
|
|
47
|
+
/** Reload `reload`, then answer the content of the app's graphs (see `POST /admin/graph-content`); without it the route answers 404. */
|
|
48
|
+
graphContent?: (request: {
|
|
49
|
+
packages?: string[];
|
|
50
|
+
reload: string[];
|
|
51
|
+
}) => Promise<GraphContentAnswer>;
|
|
77
52
|
}
|
|
78
53
|
/**
|
|
79
54
|
* Drop the process's cached graph registry after the host changed the graphs of `dataset`.
|
|
@@ -94,9 +69,5 @@ export declare function parseGraphRegistryChangedBody(body: unknown): {
|
|
|
94
69
|
* first, so the comparison does not reveal the secret's length either.
|
|
95
70
|
*/
|
|
96
71
|
export declare function adminSecretMatches(secret: string | undefined, provided: string | undefined): boolean;
|
|
97
|
-
/** The `packages` of a sync-shapes body: a list of package names, or none. */
|
|
98
|
-
export declare function parseSyncShapesBody(body: unknown): {
|
|
99
|
-
packages: string[];
|
|
100
|
-
};
|
|
101
72
|
/** Mount the control channel's routes on `app`. */
|
|
102
73
|
export declare function registerAdminRoutes(app: express.Express, secret: string | undefined, handlers: AdminChannelHandlers): void;
|