@_linked/server 3.7.0 → 3.8.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 CHANGED
@@ -1,5 +1,50 @@
1
1
  # @\_linked/server
2
2
 
3
+ ## 3.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#204](https://github.com/linked-fw/server/pull/204) [`f1dc1a6`](https://github.com/linked-fw/server/commit/f1dc1a6fec0abae90c711daec51e929a9587b582) Thanks [@flyon](https://github.com/flyon)! - Graph queries through the server (named graphs and the graph registry, `@_linked/core`'s
8
+ `graphQuery`):
9
+
10
+ - `RemoteDataset.graphQuery`: graph queries (`graph_create`, `graph_replace`, `graph_patch`,
11
+ `graph_drop`) go out on the query wire like every other query, with the same token and
12
+ end-user header. A refusal comes back as core's `GraphQueryError` with its code; a server or
13
+ gateway without the method (404, 501) reads as `unsupported-query-kind`, so the ontology sync
14
+ falls back to the default graph there.
15
+ - In the url form, `RemoteDataset` expands `graphs.allOntologies` and `graphs.allShapes` in a
16
+ read's `.from()` set from its own graph-registry cache (per dataset, keyed by the url and the
17
+ dataset the server names in its `ping`, updated by its own graph queries, re-read after 30
18
+ seconds); `invalidateGraphRegistry(dataset?)` drops it. When the registry cannot be read, the
19
+ names are sent unexpanded.
20
+ - The generic plane answers `/call/@_linked/server/graphQuery`: the body is checked with core's
21
+ `parseGraphQuery`, the `graph` rule of the store its record class routes to decides
22
+ (`withAccess({graph})`; no rule refuses with 403), and `LinkedStorage.graphQuery` runs it. A
23
+ store's refusal answers with a status and its code: `{error, code}` (409 `graph-exists`, 409
24
+ `graph-not-registered`, 400 `graph-name-read-only`, 501 `unsupported-query-kind`). Any
25
+ `ServerCallError` carrying a string `code` now sends it in the error body.
26
+ - Control channel: `POST /admin/graph-registry-changed {dataset}` (with `CN_APP_ADMIN_SECRET`)
27
+ drops the process's cached graph registry, so the next expanding read re-reads it.
28
+ - The ontology phase (boot and `POST /admin/sync-shapes`) reports and logs the graphs the sync
29
+ wrote and dropped (`ontologies.graphs`, `ontologies.droppedGraphs`), and says when a store
30
+ without graph queries got the default-graph sync.
31
+
32
+ Breaking changes:
33
+
34
+ - Requires the `@_linked/core` with graph queries (`IDataset.graphQuery`, `NamedGraph`, the graph
35
+ registry cache), which in turn requires `ghcr.io/linked-fw/fuseki:6.2.0-2` or a store that
36
+ builds its hierarchy from the registry; and the `@_linked/server-utils` whose `QueryAccess` has
37
+ the `graph` operation (with an older one every graph query is refused). Both ranges are raised
38
+ at release.
39
+ - Graph queries on the generic plane are refused unless the store declares a `graph` rule with
40
+ `withAccess`.
41
+ - `.from(graphs.allOntologies)` and `.from(graphs.allShapes)` reads through a url-form
42
+ `RemoteDataset` now also select the `OntologyGraph` and `ShapesGraph` records on the data
43
+ server; its read rule decides whether they are answered (when refused, the names go
44
+ unexpanded).
45
+ - A `ServerCallError` that carries a string `code` now sends it in the error body
46
+ (`{error, code}`).
47
+
3
48
  ## 3.7.0
4
49
 
5
50
  ### Minor Changes
@@ -14,4 +14,13 @@ export default class LincdServerBackendProvider extends BackendProvider {
14
14
  updateQuery(json: any): Promise<unknown>;
15
15
  createQuery(json: any): Promise<unknown>;
16
16
  deleteQuery(json: any): Promise<import("@_linked/core/queries/DeleteQuery.js").DeleteResponse>;
17
+ /**
18
+ * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as
19
+ * `RemoteDataset.graphQuery` sends it. The query is plain data: core's `parseGraphQuery` checks
20
+ * it, the `graph` rule of the store its record class routes to decides (no rule refuses), and
21
+ * `LinkedStorage.graphQuery` runs it outside any shape catalog; no catalog is resolved for it,
22
+ * so the check resolves the same store. A store's refusal answers with its code (see
23
+ * `graphQueryCallError`).
24
+ */
25
+ graphQuery(json: any): Promise<import("@_linked/core").GraphResult>;
17
26
  }
@@ -12,7 +12,8 @@ import { callable } from '@_linked/server-utils/utils/callable';
12
12
  import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
13
13
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
14
14
  import { LocalFileStore } from './shapes/filestores/LocalFileStore.js';
15
- import { authorizeGenericQuery, resolveQueryCatalog, toQueryBuilder, } from './utils/queryPlane.js';
15
+ import { authorizeGenericQuery, authorizeGraphQuery, graphQueryCallError, resolveQueryCatalog, toGraphQuery, toQueryBuilder, } from './utils/queryPlane.js';
16
+ import { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';
16
17
  import path from 'path';
17
18
  import fs from 'fs/promises';
18
19
  export default class LincdServerBackendProvider extends BackendProvider {
@@ -82,6 +83,26 @@ export default class LincdServerBackendProvider extends BackendProvider {
82
83
  await authorizeGenericQuery('delete', json, query, 'deleteQuery');
83
84
  return LinkedStorage.deleteQuery(query);
84
85
  }
86
+ /**
87
+ * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as
88
+ * `RemoteDataset.graphQuery` sends it. The query is plain data: core's `parseGraphQuery` checks
89
+ * it, the `graph` rule of the store its record class routes to decides (no rule refuses), and
90
+ * `LinkedStorage.graphQuery` runs it outside any shape catalog; no catalog is resolved for it,
91
+ * so the check resolves the same store. A store's refusal answers with its code (see
92
+ * `graphQueryCallError`).
93
+ */
94
+ async graphQuery(json) {
95
+ const query = toGraphQuery(json);
96
+ await authorizeGraphQuery(json, query, 'graphQuery');
97
+ if (!query)
98
+ throw new ServerCallError(400, 'Invalid graph query');
99
+ try {
100
+ return await LinkedStorage.graphQuery(query);
101
+ }
102
+ catch (err) {
103
+ throw graphQueryCallError(err);
104
+ }
105
+ }
85
106
  }
86
107
  __decorate([
87
108
  callable('public'),
@@ -119,4 +140,10 @@ __decorate([
119
140
  __metadata("design:paramtypes", [Object]),
120
141
  __metadata("design:returntype", Promise)
121
142
  ], LincdServerBackendProvider.prototype, "deleteQuery", null);
143
+ __decorate([
144
+ callable('public'),
145
+ __metadata("design:type", Function),
146
+ __metadata("design:paramtypes", [Object]),
147
+ __metadata("design:returntype", Promise)
148
+ ], LincdServerBackendProvider.prototype, "graphQuery", null);
122
149
  //# sourceMappingURL=backend.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';\nimport { callable } from '@_linked/server-utils/utils/callable';\nimport { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { LocalFileStore } from './shapes/filestores/LocalFileStore.js';\nimport {\n authorizeGenericQuery,\n resolveQueryCatalog,\n toQueryBuilder,\n} from './utils/queryPlane.js';\nimport path from 'path';\nimport fs from 'fs/promises';\n\nexport default class LincdServerBackendProvider extends BackendProvider {\n async setupBeforeControllers() {\n if (!LinkedFileStorage.getDefaultStore()) {\n LinkedFileStorage.setDefaultStore(\n new LocalFileStore(process.env.NODE_ENV + '-filestore')\n );\n }\n let fileSystemUploadPath = path.join('data', 'filestores');\n try {\n await fs.access(fileSystemUploadPath);\n } catch (err) {\n await fs.mkdir(path.join(fileSystemUploadPath, 'filestores'), {\n recursive: true,\n });\n }\n }\n\n /**\n * Reachability check for a `RemoteDataset` pointed at this server: reads nothing, changes\n * nothing. A plain linked server has no token concept, so it always answers `{ok: true}`;\n * a host that serves the wire with tokens answers 401 for a refused one.\n */\n @callable('public')\n async ping(): Promise<{ ok: true }> {\n return { ok: true };\n }\n\n // ── Generic query execution ──────────────────────────────────────────────\n //\n // These answer `RemoteDataset`: a browser's store calls\n // `Server.call(packageName, {method}, json)` (the PACKAGE form), and a remote app's store\n // posts the same `{args}` body to `{url}/call/@_linked/server/<method>`. Either way the\n // request arrives here on the generic provider rather than through Shape resolution.\n //\n // A query cannot cross the wire live, so the client ships `toJSON()` and `fromJSON()`\n // rehydrates it here into a live, closed query. `LinkedStorage` then routes it by the\n // QUERY's shape, which is what decides the target dataset; the store instance never\n // participates. The host's catalog resolver (`setQueryCatalogResolver`) picks the shape\n // catalog the JSON is decoded in; the builder keeps it for both lowerings.\n //\n // Every query is checked first against the access rules of the stores it\n // maps to, declared in the app's storage config (`withAccess`; a store with\n // no rule requires a session): see utils/queryPlane. INTERIM model, to be\n // rethought. The store runs the builder that was checked. They are declared\n // 'public' because that check, not the RPC layer, decides whether a session\n // is required.\n\n @callable('public')\n async selectQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('select', json, query, 'selectQuery');\n return LinkedStorage.selectQuery(query);\n }\n\n @callable('public')\n async askQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('ask', json, query, 'askQuery');\n return LinkedStorage.askQuery(query);\n }\n\n @callable('public')\n async updateQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('update', json, query, 'updateQuery');\n return LinkedStorage.updateQuery(query);\n }\n\n @callable('public')\n async createQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('create', json, query, 'createQuery');\n return LinkedStorage.createQuery(query);\n }\n\n @callable('public')\n async deleteQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('delete', json, query, 'deleteQuery');\n return LinkedStorage.deleteQuery(query);\n }\n}\n"]}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';\nimport { callable } from '@_linked/server-utils/utils/callable';\nimport { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { LocalFileStore } from './shapes/filestores/LocalFileStore.js';\nimport {\n authorizeGenericQuery,\n authorizeGraphQuery,\n graphQueryCallError,\n resolveQueryCatalog,\n toGraphQuery,\n toQueryBuilder,\n} from './utils/queryPlane.js';\nimport { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';\nimport path from 'path';\nimport fs from 'fs/promises';\n\nexport default class LincdServerBackendProvider extends BackendProvider {\n async setupBeforeControllers() {\n if (!LinkedFileStorage.getDefaultStore()) {\n LinkedFileStorage.setDefaultStore(\n new LocalFileStore(process.env.NODE_ENV + '-filestore')\n );\n }\n let fileSystemUploadPath = path.join('data', 'filestores');\n try {\n await fs.access(fileSystemUploadPath);\n } catch (err) {\n await fs.mkdir(path.join(fileSystemUploadPath, 'filestores'), {\n recursive: true,\n });\n }\n }\n\n /**\n * Reachability check for a `RemoteDataset` pointed at this server: reads nothing, changes\n * nothing. A plain linked server has no token concept, so it always answers `{ok: true}`;\n * a host that serves the wire with tokens answers 401 for a refused one.\n */\n @callable('public')\n async ping(): Promise<{ ok: true }> {\n return { ok: true };\n }\n\n // ── Generic query execution ──────────────────────────────────────────────\n //\n // These answer `RemoteDataset`: a browser's store calls\n // `Server.call(packageName, {method}, json)` (the PACKAGE form), and a remote app's store\n // posts the same `{args}` body to `{url}/call/@_linked/server/<method>`. Either way the\n // request arrives here on the generic provider rather than through Shape resolution.\n //\n // A query cannot cross the wire live, so the client ships `toJSON()` and `fromJSON()`\n // rehydrates it here into a live, closed query. `LinkedStorage` then routes it by the\n // QUERY's shape, which is what decides the target dataset; the store instance never\n // participates. The host's catalog resolver (`setQueryCatalogResolver`) picks the shape\n // catalog the JSON is decoded in; the builder keeps it for both lowerings.\n //\n // Every query is checked first against the access rules of the stores it\n // maps to, declared in the app's storage config (`withAccess`; a store with\n // no rule requires a session): see utils/queryPlane. INTERIM model, to be\n // rethought. The store runs the builder that was checked. They are declared\n // 'public' because that check, not the RPC layer, decides whether a session\n // is required.\n\n @callable('public')\n async selectQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('select', json, query, 'selectQuery');\n return LinkedStorage.selectQuery(query);\n }\n\n @callable('public')\n async askQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('ask', json, query, 'askQuery');\n return LinkedStorage.askQuery(query);\n }\n\n @callable('public')\n async updateQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('update', json, query, 'updateQuery');\n return LinkedStorage.updateQuery(query);\n }\n\n @callable('public')\n async createQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('create', json, query, 'createQuery');\n return LinkedStorage.createQuery(query);\n }\n\n @callable('public')\n async deleteQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('delete', json, query, 'deleteQuery');\n return LinkedStorage.deleteQuery(query);\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as\n * `RemoteDataset.graphQuery` sends it. The query is plain data: core's `parseGraphQuery` checks\n * it, the `graph` rule of the store its record class routes to decides (no rule refuses), and\n * `LinkedStorage.graphQuery` runs it outside any shape catalog; no catalog is resolved for it,\n * so the check resolves the same store. A store's refusal answers with its code (see\n * `graphQueryCallError`).\n */\n @callable('public')\n async graphQuery(json: any) {\n const query = toGraphQuery(json);\n await authorizeGraphQuery(json, query, 'graphQuery');\n if (!query) throw new ServerCallError(400, 'Invalid graph query');\n try {\n return await LinkedStorage.graphQuery(query);\n } catch (err) {\n throw graphQueryCallError(err);\n }\n }\n}\n"]}
@@ -130,7 +130,7 @@ export declare class LinkedServer extends Shape {
130
130
  /** One shape sync into the default dataset; throws when it fails. */
131
131
  private syncRegisteredShapes;
132
132
  /**
133
- * Boot: sync the registered ontologies' class hierarchy into app data and run the shape
133
+ * Boot: sync the registered ontologies into their graphs in app data and run the shape
134
134
  * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
135
135
  * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
136
136
  * `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`; the in-memory class hierarchy is loaded either way.
@@ -141,7 +141,7 @@ export declare class LinkedServer extends Shape {
141
141
  * Every discovered linked package and every workspace package of the app registers its
142
142
  * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
143
143
  * `sync` is set) into the default dataset, then the shape check.
144
- * Orphaned sync nodes are pruned under the shape sync's orphan policy
144
+ * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
145
145
  * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
146
146
  */
147
147
  private runOntologyPhase;
@@ -318,7 +318,7 @@ export declare class LinkedServer extends Shape {
318
318
  private assertSessionForLevel;
319
319
  handleErrors(fn: any): (req: any, res: any, next: any) => Promise<any>;
320
320
  handleErrorsJson(fn: any): (req: any, res: any, next: any) => Promise<any>;
321
- sendError(res: any, statusCode: number, message: any, logMessage?: string): void;
321
+ sendError(res: any, statusCode: number, message: any, logMessage?: string, code?: string): void;
322
322
  /**
323
323
  * The stylesheets a dev (Vite) page render needs, inlined into
324
324
  * `<style id="ssr-css">` so the first paint is styled before Vite's client
@@ -411,7 +411,7 @@ let LinkedServer = class LinkedServer extends Shape {
411
411
  return { written: count, orphanScope };
412
412
  }
413
413
  /**
414
- * Boot: sync the registered ontologies' class hierarchy into app data and run the shape
414
+ * Boot: sync the registered ontologies into their graphs in app data and run the shape
415
415
  * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
416
416
  * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
417
417
  * `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`; the in-memory class hierarchy is loaded either way.
@@ -429,7 +429,7 @@ let LinkedServer = class LinkedServer extends Shape {
429
429
  * Every discovered linked package and every workspace package of the app registers its
430
430
  * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
431
431
  * `sync` is set) into the default dataset, then the shape check.
432
- * Orphaned sync nodes are pruned under the shape sync's orphan policy
432
+ * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
433
433
  * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
434
434
  */
435
435
  async runOntologyPhase(sync) {
@@ -1819,17 +1819,20 @@ let LinkedServer = class LinkedServer extends Shape {
1819
1819
  catch (err) {
1820
1820
  // A ServerCallError (e.g. 501 for a call no provider handles) carries
1821
1821
  // its own status and a message that is safe to send to the client.
1822
+ // A `code` on it (a graph query refusal, see utils/queryPlane) is sent along, so
1823
+ // a remote caller can tell the refusals apart.
1822
1824
  if (ServerCallError.is(err)) {
1823
- return this.sendError(res, err.status, err.message);
1825
+ const code = err.code;
1826
+ return this.sendError(res, err.status, err.message, undefined, typeof code === 'string' ? code : undefined);
1824
1827
  }
1825
1828
  this.sendError(res, 500, 'internal server error' + (isDevelopment ? ': ' + (err === null || err === void 0 ? void 0 : err.stack) : ''), 'internal server error: ' + (err === null || err === void 0 ? void 0 : err.stack));
1826
1829
  }
1827
1830
  };
1828
1831
  }
1829
- sendError(res, statusCode = 500, message, logMessage) {
1832
+ sendError(res, statusCode = 500, message, logMessage, code) {
1830
1833
  res === null || res === void 0 ? void 0 : res.status(statusCode);
1831
1834
  if (message) {
1832
- res === null || res === void 0 ? void 0 : res.send({ error: message });
1835
+ res === null || res === void 0 ? void 0 : res.send(code ? { error: message, code } : { error: message });
1833
1836
  console.warn(paint('red', logMessage || message));
1834
1837
  }
1835
1838
  }