@_linked/server 3.6.1 → 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,77 @@
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
+
48
+ ## 3.7.0
49
+
50
+ ### Minor Changes
51
+
52
+ - [#202](https://github.com/linked-fw/server/pull/202) [`9e9cf47`](https://github.com/linked-fw/server/commit/9e9cf479c42c82a287a640c6f02c300186653176) Thanks [@flyon](https://github.com/flyon)! - Ontology data: labels, descriptions and types for every term.
53
+
54
+ - `src/data/server.ttl` gains the ontology node (`dcterms:title`, `dcterms:description`,
55
+ `owl:versionInfo`) and the seven terms it lacked (`LincdWebApp`, `LincdAPI`, `hasAPI`,
56
+ `N3FileStore`, `BackendAPIStore`, `ownPackage`, `maintainsPackage`), and is written in the
57
+ deterministic Turtle form. All 11 terms are typed (`owl:Class`, was `rdfs:Class`, or
58
+ `owl:ObjectProperty`) with an English label and comment; the labels say "Linked", not "LINCD".
59
+ - Two axioms: `server:BackendFileStore` and `server:BackendAPIStore` are `rdfs:subClassOf`
60
+ `server:BackendStore`. No shape targets these classes.
61
+
62
+ - [#202](https://github.com/linked-fw/server/pull/202) [`4e992f4`](https://github.com/linked-fw/server/commit/4e992f458ab3366bbd673a74d12105217c289190) Thanks [@flyon](https://github.com/flyon)! - The server syncs the class hierarchy of every registered ontology into its app data and runs core's shape check before its shape sync, at boot and on `POST /admin/sync-shapes`, whose answer gains `ontologies: {written, skipped}` and `shapeCheck: {errors, warnings}`. The boot's ontology sync follows the shape sync's switches; `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false` turns off only it. The server's own ontology data is Turtle (`data/server.ttl`), and the server no longer calls `autoLoadOntologyData`.
63
+
64
+ - [#202](https://github.com/linked-fw/server/pull/202) [`4fcafde`](https://github.com/linked-fw/server/commit/4fcafde29dca73f34d45ce5054f24dba79f0d89b) Thanks [@flyon](https://github.com/flyon)! - At boot and on `POST /admin/sync-shapes`, every linked package the server discovered in the app's
65
+ dependency tree registers its ontologies (core `registerInstalledOntologies`) before the ontology
66
+ sync and the shape check, so a package the app only deep-imports shape files from
67
+ (`@_linked/schema/shapes/Person`) is synced and checked too.
68
+
69
+ - [#202](https://github.com/linked-fw/server/pull/202) [`49a02be`](https://github.com/linked-fw/server/commit/49a02bea25b9526bf732a01db20cd1a33b9560e0) Thanks [@flyon](https://github.com/flyon)! - The ontology registration before the sync and the shape check also covers the app's own workspace
70
+ packages (`linkedPackage: true` under its `workspaces`), which the dependency walk misses when no
71
+ package imports them (a Create Now clone's `packages/<slug>-ont`). In dev, a package without a
72
+ built `lib/` registers from `src/ontologies/*.register.ts` through Vite's `ssrLoadModule` (core
73
+ `registerInstalledOntologies`' `importModule`); a built app skips it.
74
+
3
75
  ## 3.6.1
4
76
 
5
77
  ### Patch 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"]}
@@ -0,0 +1,75 @@
1
+ @prefix server: <https://linked.cm/ont/server/> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
6
+ @prefix dcterms: <http://purl.org/dc/terms/> .
7
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
8
+ @prefix schema: <http://schema.org/> .
9
+
10
+ server:
11
+ a owl:Ontology ;
12
+ dcterms:description "Terms of the Linked Node.js server: the server, the web app it serves and its data API."@en ;
13
+ dcterms:title "Linked server"@en ;
14
+ owl:versionInfo "3.7.0" .
15
+
16
+ server:BackendAPIStore
17
+ a owl:Class ;
18
+ rdfs:comment "A frontend store that sends every query to the Linked server's data API instead of holding quads locally."@en ;
19
+ rdfs:label "backend API store"@en ;
20
+ rdfs:subClassOf server:BackendStore .
21
+
22
+ server:BackendFileStore
23
+ a owl:Class ;
24
+ rdfs:comment "A frontend quad store that loads and updates its quads through API calls to the Linked server's backend."@en ;
25
+ rdfs:isDefinedBy server: ;
26
+ rdfs:label "backend file store"@en ;
27
+ rdfs:subClassOf server:BackendStore .
28
+
29
+ server:BackendStore
30
+ a owl:Class ;
31
+ rdfs:comment "The base class of frontend quad stores that are loaded and updated through the Linked server's backend API."@en ;
32
+ rdfs:isDefinedBy server: ;
33
+ rdfs:label "backend store"@en .
34
+
35
+ server:LincdAPI
36
+ a owl:Class ;
37
+ rdfs:comment "The server's data API endpoint, which answers select, update, create, delete and raw SPARQL calls from the client (LincdAPI.process)."@en ;
38
+ rdfs:label "Linked data API"@en .
39
+
40
+ server:LincdServer
41
+ a owl:Class ;
42
+ rdfs:comment "The Node.js HTTP server that serves a Linked app, its assets and its /call and data API routes (LinkedServer)."@en ;
43
+ rdfs:isDefinedBy server: ;
44
+ rdfs:label "Linked server"@en .
45
+
46
+ server:LincdWebApp
47
+ a owl:Class ;
48
+ rdfs:comment "A Linked web app as the server knows it: the site at SITE_ROOT, with the data API it exposes."@en ;
49
+ rdfs:label "Linked web app"@en .
50
+
51
+ server:N3FileStore
52
+ a owl:Class ;
53
+ rdfs:comment "A quad store that keeps its quads in an N3/Turtle file on a Node.js server."@en ;
54
+ rdfs:label "N3 file store"@en .
55
+
56
+ server:NodeFileStore
57
+ a owl:Class ;
58
+ rdfs:comment "A quad store that keeps its quads in a JSON-LD file on a Node.js server."@en ;
59
+ rdfs:isDefinedBy server: ;
60
+ rdfs:label "Node file store"@en .
61
+
62
+ server:hasAPI
63
+ a owl:ObjectProperty ;
64
+ rdfs:comment "Links a web app to the client of its data API."@en ;
65
+ rdfs:label "has API"@en .
66
+
67
+ server:maintainsPackage
68
+ a owl:ObjectProperty ;
69
+ rdfs:comment "Links a web app to a package it maintains."@en ;
70
+ rdfs:label "maintains package"@en .
71
+
72
+ server:ownPackage
73
+ a owl:ObjectProperty ;
74
+ rdfs:comment "Links a web app to the npm package that is its own code."@en ;
75
+ rdfs:label "own package"@en .
@@ -1,25 +1,7 @@
1
- /**
2
- * Load the data of this ontology.
3
- * In @_linked/core, loadData returns the raw JSON import — no JSONLD.parse() needed.
4
- */
5
- export declare var loadData: () => Promise<{
6
- "@context": {
7
- dc: string;
8
- owl: string;
9
- rdf: string;
10
- rdfs: string;
11
- server: string;
12
- };
13
- "@graph": {
14
- "@id": string;
15
- "@type": string;
16
- "rdfs:comment": string;
17
- "rdfs:isDefinedBy": {
18
- "@id": string;
19
- };
20
- "rdfs:label": string;
21
- }[];
22
- }>;
1
+ /** This ontology's data files, relative to this module (`linked build` copies them to `lib/esm/data/`). */
2
+ export declare const dataFiles: string[];
3
+ /** Read this ontology's Turtle data (server only). */
4
+ export declare const loadData: () => Promise<string[]>;
23
5
  /**
24
6
  * The namespace of this ontology.
25
7
  *
@@ -1,15 +1,9 @@
1
1
  import { createNameSpace } from '@_linked/core/utils/NameSpace';
2
- /**
3
- * Load the data of this ontology.
4
- * In @_linked/core, loadData returns the raw JSON import — no JSONLD.parse() needed.
5
- */
6
- export var loadData = async () => {
7
- //@ts-ignore
8
- const data = await import('../data/server.json', {
9
- with: { type: 'json' },
10
- });
11
- return data.default || data;
12
- };
2
+ import { loadOntologyFiles } from '@_linked/core/ontology/loadOntologyFiles';
3
+ /** This ontology's data files, relative to this module (`linked build` copies them to `lib/esm/data/`). */
4
+ export const dataFiles = ['../data/server.ttl'];
5
+ /** Read this ontology's Turtle data (server only). */
6
+ export const loadData = () => loadOntologyFiles(import.meta.url, dataFiles);
13
7
  /**
14
8
  * The namespace of this ontology.
15
9
  *
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/ontologies/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,IAAI,QAAQ,GAAG,KAAK,IAAI,EAAE;IAC/B,YAAY;IACZ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,qBAAqB,EAAE;QAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;KACvB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;AAC9B,CAAC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,IAAI,EAAE,GAAG,eAAe,CAAC,+BAA+B,CAAC,CAAC;AAEjE,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAE1B,uEAAuE;AACvE,0EAA0E;AAC1E,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;AAC1C,MAAM,YAAY,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;AACxC,MAAM,eAAe,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;AACpC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;AAChC,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAE5B,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,WAAW;IACX,gBAAgB;IAChB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,eAAe;IACf,WAAW;IACX,UAAU;IACV,gBAAgB;IAChB,QAAQ;IACR,MAAM;CACP,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC","sourcesContent":["import { createNameSpace } from '@_linked/core/utils/NameSpace';\n\n/**\n * Load the data of this ontology.\n * In @_linked/core, loadData returns the raw JSON import — no JSONLD.parse() needed.\n */\nexport var loadData = async () => {\n //@ts-ignore\n const data = await import('../data/server.json', {\n with: { type: 'json' },\n });\n return data.default || data;\n};\n\n/**\n * The namespace of this ontology.\n *\n * First-party ontologies live on linked.cm: `https://linked.cm/ont/{ontologySlug}/`, and a\n * package's own ontology takes the package's publicSlug (`@_linked/server` → `server`), the same\n * slug its shapes use under `https://linked.cm/shape/server/`.\n *\n * Its prefix label is the same slug, `server` (`server:LincdAPI`). It used to be `lincd-server`,\n * which is still registered as a deprecated alias so `lincd-server:` names keep expanding.\n *\n * Until an earlier release the namespace was `http://lincd.org/ont/lincd-server/`. No stored\n * data is typed with these terms; the only store triples that carried them were the synced shape descriptions of\n * `LinkedServer`, `LincdAPI` and `LincdWebApp` (`sh:targetClass`, `sh:path`), which boot sync\n * rewrites (delete, then recreate) the next time the server starts.\n */\nexport var ns = createNameSpace('https://linked.cm/ont/server/');\n\nexport var _self = ns('');\n\n// Terms are properties of the namespace object below, not module-scope\n// exports. Most of these names are also SHAPE CLASS names in this package\n// (BackendAPIStore, LincdWebApp, LincdAPI, ...). Two bindings of the same name\n// in one bundle scope make the bundler rename one of them, and when the loser\n// is the class, `constructor.name` becomes `BackendAPIStore2` -- which is the\n// shape's IRI and the name `Server.call` routes on. The client then asks a\n// backend that has no such shape, and every store round-trip answers 501.\nconst LincdServer = ns('LincdServer');\nconst BackendFileStore = ns('BackendFileStore');\nconst NodeFileStore = ns('NodeFileStore');\nconst BackendStore = ns('BackendStore');\nconst BackendAPIStore = ns('BackendAPIStore');\nconst LincdWebApp = ns('LincdWebApp');\nconst ownPackage = ns('ownPackage');\nconst maintainsPackage = ns('maintainsPackage');\nconst N3FileStore = ns('N3FileStore');\nconst LincdAPI = ns('LincdAPI');\nconst hasAPI = ns('hasAPI');\n\nexport const server = {\n LincdServer,\n BackendFileStore,\n NodeFileStore,\n BackendStore,\n N3FileStore,\n BackendAPIStore,\n LincdWebApp,\n ownPackage,\n maintainsPackage,\n LincdAPI,\n hasAPI,\n};\n\n/** @deprecated use `server` — same object, kept under its old name. */\nexport const lincdServer = server;\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/ontologies/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E,2GAA2G;AAC3G,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAEhD,sDAAsD;AACtD,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AAE5E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,IAAI,EAAE,GAAG,eAAe,CAAC,+BAA+B,CAAC,CAAC;AAEjE,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAE1B,uEAAuE;AACvE,0EAA0E;AAC1E,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;AAC1C,MAAM,YAAY,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;AACxC,MAAM,eAAe,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;AACpC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;AAChC,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAE5B,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,WAAW;IACX,gBAAgB;IAChB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,eAAe;IACf,WAAW;IACX,UAAU;IACV,gBAAgB;IAChB,QAAQ;IACR,MAAM;CACP,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC","sourcesContent":["import { createNameSpace } from '@_linked/core/utils/NameSpace';\nimport { loadOntologyFiles } from '@_linked/core/ontology/loadOntologyFiles';\n\n/** This ontology's data files, relative to this module (`linked build` copies them to `lib/esm/data/`). */\nexport const dataFiles = ['../data/server.ttl'];\n\n/** Read this ontology's Turtle data (server only). */\nexport const loadData = () => loadOntologyFiles(import.meta.url, dataFiles);\n\n/**\n * The namespace of this ontology.\n *\n * First-party ontologies live on linked.cm: `https://linked.cm/ont/{ontologySlug}/`, and a\n * package's own ontology takes the package's publicSlug (`@_linked/server` → `server`), the same\n * slug its shapes use under `https://linked.cm/shape/server/`.\n *\n * Its prefix label is the same slug, `server` (`server:LincdAPI`). It used to be `lincd-server`,\n * which is still registered as a deprecated alias so `lincd-server:` names keep expanding.\n *\n * Until an earlier release the namespace was `http://lincd.org/ont/lincd-server/`. No stored\n * data is typed with these terms; the only store triples that carried them were the synced shape descriptions of\n * `LinkedServer`, `LincdAPI` and `LincdWebApp` (`sh:targetClass`, `sh:path`), which boot sync\n * rewrites (delete, then recreate) the next time the server starts.\n */\nexport var ns = createNameSpace('https://linked.cm/ont/server/');\n\nexport var _self = ns('');\n\n// Terms are properties of the namespace object below, not module-scope\n// exports. Most of these names are also SHAPE CLASS names in this package\n// (BackendAPIStore, LincdWebApp, LincdAPI, ...). Two bindings of the same name\n// in one bundle scope make the bundler rename one of them, and when the loser\n// is the class, `constructor.name` becomes `BackendAPIStore2` -- which is the\n// shape's IRI and the name `Server.call` routes on. The client then asks a\n// backend that has no such shape, and every store round-trip answers 501.\nconst LincdServer = ns('LincdServer');\nconst BackendFileStore = ns('BackendFileStore');\nconst NodeFileStore = ns('NodeFileStore');\nconst BackendStore = ns('BackendStore');\nconst BackendAPIStore = ns('BackendAPIStore');\nconst LincdWebApp = ns('LincdWebApp');\nconst ownPackage = ns('ownPackage');\nconst maintainsPackage = ns('maintainsPackage');\nconst N3FileStore = ns('N3FileStore');\nconst LincdAPI = ns('LincdAPI');\nconst hasAPI = ns('hasAPI');\n\nexport const server = {\n LincdServer,\n BackendFileStore,\n NodeFileStore,\n BackendStore,\n N3FileStore,\n BackendAPIStore,\n LincdWebApp,\n ownPackage,\n maintainsPackage,\n LincdAPI,\n hasAPI,\n};\n\n/** @deprecated use `server` — same object, kept under its old name. */\nexport const lincdServer = server;\n"]}
@@ -11,7 +11,7 @@
11
11
  * From a sibling module the same import is ordinary and survives.
12
12
  */
13
13
  import * as terms from './server.js';
14
- import { loadData, ns } from './server.js';
14
+ import { dataFiles, loadData, ns } from './server.js';
15
15
  import { linkedOntology } from '../package.js';
16
16
  import { Prefix } from '@_linked/core/utils/Prefix';
17
17
  // Deprecated prefix label, kept so `lincd-server:X` names still expand. It is added
@@ -19,6 +19,6 @@ import { Prefix } from '@_linked/core/utils/Prefix';
19
19
  // last add wins: registering `server` second makes compaction emit `server:X`.
20
20
  Prefix.add('lincd-server', ns('').id);
21
21
  // The prefix is also the key this ontology is registered under in the package tree,
22
- // and the name of its data file: `server` → `../data/server.json`.
23
- linkedOntology(terms, ns, 'server', loadData, '../data/server.json');
22
+ // and the name of its data file: `server` → `../data/server.ttl`.
23
+ linkedOntology(terms, ns, 'server', loadData, dataFiles);
24
24
  //# sourceMappingURL=server.register.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.register.js","sourceRoot":"","sources":["../../../src/ontologies/server.register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,KAAK,MAAM,aAAa,CAAC;AACrC,OAAO,EAAC,QAAQ,EAAE,EAAE,EAAC,MAAM,aAAa,CAAC;AACzC,OAAO,EAAC,cAAc,EAAC,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAElD,oFAAoF;AACpF,sFAAsF;AACtF,+EAA+E;AAC/E,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAEtC,oFAAoF;AACpF,mEAAmE;AACnE,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["/**\n * Registers this ontology.\n *\n * Kept out of `server.ts` because registration needs that module's whole export\n * namespace, and a module cannot import itself once a bundler is involved: Rollup\n * treats a static self-reference as a circular import and elides it, so the binding is\n * undefined at runtime and the consuming app dies at boot with `_this is not\n * defined`. `tsc` preserves it, which is why the pattern survived for as long as\n * packages were built with `tsc` alone.\n *\n * From a sibling module the same import is ordinary and survives.\n */\nimport * as terms from './server.js';\nimport {loadData, ns} from './server.js';\nimport {linkedOntology} from '../package.js';\nimport {Prefix} from '@_linked/core/utils/Prefix';\n\n// Deprecated prefix label, kept so `lincd-server:X` names still expand. It is added\n// BEFORE the registration below because Prefix keeps one prefix per namespace and the\n// last add wins: registering `server` second makes compaction emit `server:X`.\nPrefix.add('lincd-server', ns('').id);\n\n// The prefix is also the key this ontology is registered under in the package tree,\n// and the name of its data file: `server` → `../data/server.json`.\nlinkedOntology(terms, ns, 'server', loadData, '../data/server.json');\n"]}
1
+ {"version":3,"file":"server.register.js","sourceRoot":"","sources":["../../../src/ontologies/server.register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,KAAK,MAAM,aAAa,CAAC;AACrC,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAC,MAAM,aAAa,CAAC;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAElD,oFAAoF;AACpF,sFAAsF;AACtF,+EAA+E;AAC/E,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAEtC,oFAAoF;AACpF,kEAAkE;AAClE,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC","sourcesContent":["/**\n * Registers this ontology.\n *\n * Kept out of `server.ts` because registration needs that module's whole export\n * namespace, and a module cannot import itself once a bundler is involved: Rollup\n * treats a static self-reference as a circular import and elides it, so the binding is\n * undefined at runtime and the consuming app dies at boot with `_this is not\n * defined`. `tsc` preserves it, which is why the pattern survived for as long as\n * packages were built with `tsc` alone.\n *\n * From a sibling module the same import is ordinary and survives.\n */\nimport * as terms from './server.js';\nimport {dataFiles, loadData, ns} from './server.js';\nimport {linkedOntology} from '../package.js';\nimport {Prefix} from '@_linked/core/utils/Prefix';\n\n// Deprecated prefix label, kept so `lincd-server:X` names still expand. It is added\n// BEFORE the registration below because Prefix keeps one prefix per namespace and the\n// last add wins: registering `server` second makes compaction emit `server:X`.\nPrefix.add('lincd-server', ns('').id);\n\n// The prefix is also the key this ontology is registered under in the package tree,\n// and the name of its data file: `server` → `../data/server.ttl`.\nlinkedOntology(terms, ns, 'server', loadData, dataFiles);\n"]}
@@ -1,4 +1,4 @@
1
1
  export declare const linkedShape: {
2
2
  <T extends typeof import("@_linked/core").Shape>(constructor: T): void;
3
3
  <T extends typeof import("@_linked/core").Shape>(config?: import("@_linked/core/utils/Package").ShapeConfig): (constructor: T) => void;
4
- }, linkedUtil: (constructor: any) => any, linkedOntology: (allFileExports: any, nameSpace: (term: string) => import("@_linked/core/utils/NodeReference.js").NodeReferenceValue, suggestedPrefixAndFileName: string, loadDataFunction?: () => Promise<any>, dataSource?: string | string[]) => void, packageExports: any, packageName: string;
4
+ }, linkedUtil: (constructor: any) => any, linkedOntology: (allFileExports: any, nameSpace: (term: string) => import("@_linked/core/utils/NodeReference.js").NodeReferenceValue, suggestedPrefixAndFileName: string, loadDataFunction?: () => Promise<unknown>, dataSource?: string | string[]) => void, packageExports: any, packageName: string;
@@ -82,6 +82,8 @@ export declare class LinkedServer extends Shape {
82
82
  * could not be loaded at all.
83
83
  */
84
84
  private linkedPackageDirs;
85
+ /** The app's own workspace packages (`linkedPackage: true`), by real path. */
86
+ private workspacePackageDirs;
85
87
  private warnedDuplicateInstalls;
86
88
  private resizePathsMap;
87
89
  private api;
@@ -127,11 +129,29 @@ export declare class LinkedServer extends Shape {
127
129
  private materializeShapesIntoStore;
128
130
  /** One shape sync into the default dataset; throws when it fails. */
129
131
  private syncRegisteredShapes;
132
+ /**
133
+ * Boot: sync the registered ontologies into their graphs in app data and run the shape
134
+ * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
135
+ * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
136
+ * `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`; the in-memory class hierarchy is loaded either way.
137
+ * Never stops the boot: failures and findings are logged.
138
+ */
139
+ private syncOntologiesOnBoot;
140
+ /**
141
+ * Every discovered linked package and every workspace package of the app registers its
142
+ * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
143
+ * `sync` is set) into the default dataset, then the shape check.
144
+ * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
145
+ * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
146
+ */
147
+ private runOntologyPhase;
130
148
  /** Tail of the {@link resyncShapes} queue: one re-sync at a time. */
131
149
  private shapeSyncQueue;
132
150
  /**
133
- * Re-sync the registered shapes into app data, as the boot does, and report
134
- * what was written. The host calls it (`POST /admin/sync-shapes`) after it
151
+ * Re-sync the registered ontologies and shapes into app data, as the boot does,
152
+ * and report what was written: the ontology sync (every registered package,
153
+ * hash-skipped), the shape check's counts, then the shape sync. A failed ontology
154
+ * sync does not stop the shape sync; the run then fails with the ontology error. The host calls it (`POST /admin/sync-shapes`) after it
135
155
  * changed the app's shape code, so the app — the only process that loads that
136
156
  * code — writes its own shapes and their hashes.
137
157
  *
@@ -298,7 +318,7 @@ export declare class LinkedServer extends Shape {
298
318
  private assertSessionForLevel;
299
319
  handleErrors(fn: any): (req: any, res: any, next: any) => Promise<any>;
300
320
  handleErrorsJson(fn: any): (req: any, res: any, next: any) => Promise<any>;
301
- sendError(res: any, statusCode: number, message: any, logMessage?: string): void;
321
+ sendError(res: any, statusCode: number, message: any, logMessage?: string, code?: string): void;
302
322
  /**
303
323
  * The stylesheets a dev (Vite) page render needs, inlined into
304
324
  * `<style id="ssr-css">` so the first paint is styled before Vite's client
@@ -36,12 +36,11 @@ import { paint } from '../utils/paint.js';
36
36
  import { nativeImport } from '../utils/nativeImport.js';
37
37
  import { createJsonBodyParser } from '../utils/jsonBodyParser.js';
38
38
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
39
- import { autoLoadOntologyData } from '@_linked/core/utils/Package';
40
39
  import { getAllShapeClasses, getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
41
40
  import path from 'path';
42
41
  import { pathToFileURL } from 'node:url';
43
42
  import { isFatalError } from '../utils/fatalError.js';
44
- import { discoverLinkedDependencies, hasModuleEntry, isBundledBySsr, NODE_IMPORT_CONDITIONS, readInstalledPackage, resolveBackendEntry, resolveDevBackendEntry, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
43
+ import { discoverLinkedDependencies, hasModuleEntry, isBundledBySsr, NODE_IMPORT_CONDITIONS, readDependencyLists, readInstalledPackage, resolveBackendEntry, resolveDevBackendEntry, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
45
44
  /**
46
45
  * `instanceof ShapeProvider`, tolerating a second copy of server-utils.
47
46
  *
@@ -138,7 +137,8 @@ import { resolveStaticAccessURL, staticAssetURL, } from '../utils/releaseManifes
138
137
  import { resolveBootstrapEntry, resolveViteMainEntry, resolveViteServingMode, } from '../utils/bootstrapEntry.js';
139
138
  import { resolveRouteAssets } from '../utils/routeAssets.js';
140
139
  import { resolveRouteComponent, ssrCssInlineUrls, ssrCssScope, } from '../utils/ssrCss.js';
141
- import { materializeShapes } from '../utils/syncShapes.js';
140
+ import { bootOrphanScope, materializeShapes } from '../utils/syncShapes.js';
141
+ import { bootSyncsOntologies, loadOntologyCore, runOntologyPhase, ontologyPackageDirs, sourceModuleImporter, shapeSyncOn, syncOntologiesThenShapes, } from '../utils/ontologySync.js';
142
142
  import { AdminRequestError, assertReloadablePackages, registerAdminRoutes, } from '../utils/adminChannel.js';
143
143
  import { envMs, withTimeout } from '../utils/withTimeout.js';
144
144
  import { invalidateSourceUnder } from '../utils/reloadSource.js';
@@ -192,7 +192,6 @@ function installProcessSetup() {
192
192
  //
193
193
  // global['document'] = document;
194
194
  global['reactStaticRenderer'] = renderToStaticMarkup;
195
- autoLoadOntologyData(true);
196
195
  /**
197
196
  * How RPC arguments treat a `{__sc}` (live Shape class) marker. No call takes a
198
197
  * Shape class from the client: logged in 'warn', refused (400) in 'enforce'.
@@ -285,6 +284,8 @@ let LinkedServer = class LinkedServer extends Shape {
285
284
  * could not be loaded at all.
286
285
  */
287
286
  this.linkedPackageDirs = new Map();
287
+ /** The app's own workspace packages (`linkedPackage: true`), by real path. */
288
+ this.workspacePackageDirs = [];
288
289
  this.warnedDuplicateInstalls = new Set();
289
290
  //from resizedFileName to full resized path (CDN or similar)
290
291
  this.resizePathsMap = new Map();
@@ -360,6 +361,7 @@ let LinkedServer = class LinkedServer extends Shape {
360
361
  this.initPackage();
361
362
  this.server = express();
362
363
  await this.initBackendProviders();
364
+ await this.syncOntologiesOnBoot();
363
365
  await this.materializeShapesIntoStore();
364
366
  return this;
365
367
  }
@@ -396,9 +398,7 @@ let LinkedServer = class LinkedServer extends Shape {
396
398
  /** One shape sync into the default dataset; throws when it fails. */
397
399
  async syncRegisteredShapes() {
398
400
  var _a;
399
- const configFlag = (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot;
400
- const envFlag = process.env.LINKED_SYNC_SHAPES_ON_BOOT;
401
- if (configFlag === false || envFlag === 'false') {
401
+ if (!shapeSyncOn(process.env, (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot)) {
402
402
  return { written: 0, skipped: 'disabled' };
403
403
  }
404
404
  const appData = LinkedStorage.getDefaultDataset();
@@ -411,8 +411,43 @@ let LinkedServer = class LinkedServer extends Shape {
411
411
  return { written: count, orphanScope };
412
412
  }
413
413
  /**
414
- * Re-sync the registered shapes into app data, as the boot does, and report
415
- * what was written. The host calls it (`POST /admin/sync-shapes`) after it
414
+ * Boot: sync the registered ontologies into their graphs in app data and run the shape
415
+ * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
416
+ * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
417
+ * `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`; the in-memory class hierarchy is loaded either way.
418
+ * Never stops the boot: failures and findings are logged.
419
+ */
420
+ async syncOntologiesOnBoot() {
421
+ try {
422
+ await runAsSystem(() => { var _a; return this.runOntologyPhase(bootSyncsOntologies(process.env, (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot)); }, 'ontology sync at boot');
423
+ }
424
+ catch (err) {
425
+ console.warn('[LinkedServer] ontology sync failed (non-fatal):', err);
426
+ }
427
+ }
428
+ /**
429
+ * Every discovered linked package and every workspace package of the app registers its
430
+ * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
431
+ * `sync` is set) into the default dataset, then the shape check.
432
+ * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
433
+ * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
434
+ */
435
+ async runOntologyPhase(sync) {
436
+ var _a, _b, _c, _d;
437
+ return runOntologyPhase(LinkedStorage.getDefaultDataset(), {
438
+ core: await loadOntologyCore(),
439
+ sync,
440
+ orphanScope: bootOrphanScope(),
441
+ dependencies: readDependencyLists((_a = this.linkedPackageDirs) !== null && _a !== void 0 ? _a : new Map(), this.package),
442
+ packageDirs: ontologyPackageDirs(((_b = this.linkedPackageDirs) !== null && _b !== void 0 ? _b : new Map()).values(), (_c = this.workspacePackageDirs) !== null && _c !== void 0 ? _c : []),
443
+ importModule: sourceModuleImporter((_d = this.config.server) === null || _d === void 0 ? void 0 : _d.vite),
444
+ });
445
+ }
446
+ /**
447
+ * Re-sync the registered ontologies and shapes into app data, as the boot does,
448
+ * and report what was written: the ontology sync (every registered package,
449
+ * hash-skipped), the shape check's counts, then the shape sync. A failed ontology
450
+ * sync does not stop the shape sync; the run then fails with the ontology error. The host calls it (`POST /admin/sync-shapes`) after it
416
451
  * changed the app's shape code, so the app — the only process that loads that
417
452
  * code — writes its own shapes and their hashes.
418
453
  *
@@ -435,7 +470,7 @@ let LinkedServer = class LinkedServer extends Shape {
435
470
  if (await this.reloadPackageFromDisk(pkg))
436
471
  reloaded.push(pkg);
437
472
  }
438
- const result = await this.syncRegisteredShapes();
473
+ const result = await syncOntologiesThenShapes(() => { var _a; return this.runOntologyPhase(shapeSyncOn(process.env, (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot)); }, () => this.syncRegisteredShapes());
439
474
  return { ...result, reloaded, shapes: [...getAllShapeClasses().keys()].sort() };
440
475
  }, 'shape sync requested by the host'), timeoutMs, `the shape sync did not finish within ${timeoutMs} ms`));
441
476
  this.shapeSyncQueue = run.catch(() => undefined);
@@ -622,6 +657,7 @@ let LinkedServer = class LinkedServer extends Shape {
622
657
  // });
623
658
  // before controllers
624
659
  await this.initBackendProviders();
660
+ await this.syncOntologiesOnBoot();
625
661
  await this.materializeShapesIntoStore();
626
662
  //START OF EXPRESS ROUTES AND MIDDLEWARE
627
663
  //use cors
@@ -793,6 +829,14 @@ let LinkedServer = class LinkedServer extends Shape {
793
829
  // Local workspace packages in this app's dependency tree. These are also
794
830
  // imported whole at boot (indexLincdPackage), not only their /backend.
795
831
  const allLocalPackages = getLincdPackages();
832
+ this.workspacePackageDirs = allLocalPackages.map((pkg) => {
833
+ try {
834
+ return fsNative.realpathSync(pkg.path);
835
+ }
836
+ catch (_a) {
837
+ return pkg.path;
838
+ }
839
+ });
796
840
  const localPackageMap = new Map(allLocalPackages.map((pkg) => [pkg.packageName, pkg]));
797
841
  const relevantPackages = this.filterPackagesByDependencyTree(localPackageMap, this.package);
798
842
  // Every linked package in the dependency tree, installed or local, each
@@ -1775,17 +1819,20 @@ let LinkedServer = class LinkedServer extends Shape {
1775
1819
  catch (err) {
1776
1820
  // A ServerCallError (e.g. 501 for a call no provider handles) carries
1777
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.
1778
1824
  if (ServerCallError.is(err)) {
1779
- 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);
1780
1827
  }
1781
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));
1782
1829
  }
1783
1830
  };
1784
1831
  }
1785
- sendError(res, statusCode = 500, message, logMessage) {
1832
+ sendError(res, statusCode = 500, message, logMessage, code) {
1786
1833
  res === null || res === void 0 ? void 0 : res.status(statusCode);
1787
1834
  if (message) {
1788
- 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 });
1789
1836
  console.warn(paint('red', logMessage || message));
1790
1837
  }
1791
1838
  }