@_linked/server 3.12.1 → 3.13.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.
@@ -25,7 +25,7 @@
25
25
  * this server depends on has all of them. Kept apart from `LinkedServer` so the order and the failure handling can be tested without
26
26
  * booting a server.
27
27
  */
28
- import type { BootOrphanScope } from './syncShapes.js';
28
+ import { type BootOrphanScope } from './syncShapes.js';
29
29
  /** The subset of core's ontology and shape-check API this module calls. */
30
30
  export interface OntologyCore {
31
31
  syncOntologies(ds: unknown, options: {
@@ -35,7 +35,6 @@ export interface OntologyCore {
35
35
  skipped: string[];
36
36
  pruned: string[];
37
37
  target?: string;
38
- fallback?: true;
39
38
  }>;
40
39
  checkShapeHierarchy(options: {
41
40
  dependencies?: Record<string, string[]>;
@@ -64,6 +63,12 @@ export interface OntologyCore {
64
63
  importModule?: (file: string) => Promise<unknown>;
65
64
  }) => Promise<string[]>;
66
65
  }
66
+ /** The shape check's findings by severity; `failed` holds the message of a check that threw. */
67
+ export interface ShapeCheckSummary {
68
+ errors: number;
69
+ warnings: number;
70
+ failed?: string;
71
+ }
67
72
  /** What the ontology sync and the shape check did, as `POST /admin/sync-shapes` reports it. */
68
73
  export interface OntologyPhaseResult {
69
74
  /**
@@ -77,12 +82,12 @@ export interface OntologyPhaseResult {
77
82
  graphs?: string[];
78
83
  droppedGraphs?: string[];
79
84
  };
80
- /** The shape check's findings, counted by severity. */
81
- shapeCheck: {
82
- errors: number;
83
- warnings: number;
84
- };
85
- /** The first error the sync or the check threw; the phase went on without it. */
85
+ /**
86
+ * The shape check's findings, counted by severity, and the message of the error the check
87
+ * threw, if it did (`failed`). A thrown check is reported here and never fails the phase.
88
+ */
89
+ shapeCheck: ShapeCheckSummary;
90
+ /** The error the ontology sync threw; the phase went on without it. */
86
91
  failure?: Error;
87
92
  }
88
93
  /**
@@ -119,8 +124,9 @@ export declare function sourceModuleImporter(vite: {
119
124
  /**
120
125
  * Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync
121
126
  * the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the
122
- * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync is logged and returned as `failure`, and the
123
- * check still runs; a failed check likewise. Logs one line for the sync, one summary line for
127
+ * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync
128
+ * is logged and returned as `failure`, and the check still runs; a check that throws is logged and
129
+ * returned as `shapeCheck.failed`. Logs one line for the sync, one summary line for
124
130
  * the check and one line per finding.
125
131
  */
126
132
  export declare function runOntologyPhase(ds: unknown, { core, sync, orphanScope, dependencies, packageDirs, importModule, log, warn, }: {
@@ -152,7 +158,8 @@ export declare function graphsWrittenAndDropped(ops: Array<{
152
158
  };
153
159
  /**
154
160
  * The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology
155
- * phase failed. The ontology phase's failure is then thrown, so the route answers with it, as
156
- * it does for a failed shape sync.
161
+ * sync failed. The ontology sync's failure is then thrown, so the route answers with it, as it
162
+ * does for a failed shape sync. A shape check that threw fails nothing: the answer reports it as
163
+ * `shapeCheck.failed`.
157
164
  */
158
165
  export declare function syncOntologiesThenShapes<T extends object>(ontologyPhase: () => Promise<OntologyPhaseResult>, shapeSync: () => Promise<T>): Promise<T & Pick<OntologyPhaseResult, 'ontologies' | 'shapeCheck'>>;
@@ -1,3 +1,31 @@
1
+ /**
2
+ * Ontology sync and the shape check, as the server runs them before its shape sync: at boot and
3
+ * when the host asks for a re-sync (`POST /admin/sync-shapes`).
4
+ *
5
+ * - The ontology sync (core `syncOntologies`) writes each registered package's ontology data into
6
+ * its own named graph of the app's default dataset (`{package IRI}/graph/ontology`, with a
7
+ * record in the graph registry that marks it as a hierarchy source), skipping a package whose
8
+ * stored hash matches, so the store's RDFS view infers the class hierarchy. A store without
9
+ * graph queries (it answers `unsupported-query-kind`, or has no `graphQuery`) gets core's
10
+ * earlier sync of the `rdfs:subClassOf` triples into the default graph instead. The graphs the
11
+ * sync wrote or dropped are observed on core's query dispatch and reported by name.
12
+ * - The shape check (core `checkShapeHierarchy`) compares the registered shapes with that
13
+ * hierarchy. A running app is never stopped by it: its findings are logged and counted.
14
+ * - The in-memory class hierarchy (core `loadOntologyHierarchy`) is loaded also when the sync
15
+ * is off; the sync loads it itself when it runs.
16
+ * - Before either, every linked package directory the server discovered registers its ontologies
17
+ * (core `registerInstalledOntologies`): a package the app only deep-imports shape files from
18
+ * never evaluates its register modules, so its data would be missing from both. A source
19
+ * package without built register modules (a workspace package in dev) registers through
20
+ * `importModule` (Vite's `ssrLoadModule`), when the caller passes one.
21
+ *
22
+ * The four core modules (`ontology/syncOntologies`, `shapes/checkShapeHierarchy`,
23
+ * `ontology/hierarchy`, `ontology/registerInstalled`) are server only and not in core's index, so
24
+ * {@link loadOntologyCore} imports them by their fixed subpaths when the phase runs; the core
25
+ * this server depends on has all of them. Kept apart from `LinkedServer` so the order and the failure handling can be tested without
26
+ * booting a server.
27
+ */
28
+ import { writtenAndUnchanged } from './syncShapes.js';
1
29
  /**
2
30
  * Whether the server syncs shapes (and so ontologies) into its app data: off when `linked.config`
3
31
  * sets `syncShapesOnBoot: false` or the environment sets `LINKED_SYNC_SHAPES_ON_BOOT=false`. One
@@ -57,12 +85,13 @@ const list = (names) => (names.length ? names.join(', ') : 'none');
57
85
  /**
58
86
  * Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync
59
87
  * the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the
60
- * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync is logged and returned as `failure`, and the
61
- * check still runs; a failed check likewise. Logs one line for the sync, one summary line for
88
+ * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync
89
+ * is logged and returned as `failure`, and the check still runs; a check that throws is logged and
90
+ * returned as `shapeCheck.failed`. Logs one line for the sync, one summary line for
62
91
  * the check and one line per finding.
63
92
  */
64
93
  export async function runOntologyPhase(ds, { core, sync, orphanScope, dependencies, packageDirs = [], importModule, log = console.log, warn = console.warn, }) {
65
- var _a, _b;
94
+ var _a;
66
95
  const result = {
67
96
  ontologies: { written: [], skipped: [], graphs: [], droppedGraphs: [] },
68
97
  shapeCheck: { errors: 0, warnings: 0 },
@@ -94,17 +123,15 @@ export async function runOntologyPhase(ds, { core, sync, orphanScope, dependenci
94
123
  const { graphs, droppedGraphs } = graphsWrittenAndDropped(ops.filter((op) => op.ok));
95
124
  result.ontologies = { written: sync.written, skipped: sync.skipped, graphs, droppedGraphs };
96
125
  hierarchyLoaded = true;
97
- // Core ≥ 2.41 says where it wrote (the global `ontologyGraph`); an older core is told
98
- // apart by a write that sent no graph query.
126
+ // Core says where it wrote (the global `ontologyGraph`). A store without graph queries makes
127
+ // the sync throw in the 'package' and shared modes, reported below as a failed sync.
99
128
  const where = sync.target === 'default'
100
129
  ? " (default graph: ontologyGraph is 'default')"
101
- : sync.fallback || (sync.target === undefined && !!stop && sync.written.length > 0 && graphs.length === 0)
102
- ? ' (default graph: the store has no graph queries)'
103
- : sync.target && sync.target !== 'package'
104
- ? ` (one shared graph <${sync.target}>)`
105
- : '';
130
+ : sync.target && sync.target !== 'package'
131
+ ? ` (one shared graph <${sync.target}>)`
132
+ : '';
106
133
  log(`[LinkedServer] ontology sync${where}: ` +
107
- `written ${list(sync.written)}; skipped ${list(sync.skipped)}` +
134
+ writtenAndUnchanged(sync.written, sync.skipped) +
108
135
  (orphanScope === 'all' ? `; pruned ${list(sync.pruned)}` : '') +
109
136
  (graphs.length ? `; graphs written ${list(graphs)}` : '') +
110
137
  (droppedGraphs.length ? `; graphs dropped ${list(droppedGraphs)}` : ''));
@@ -129,12 +156,14 @@ export async function runOntologyPhase(ds, { core, sync, orphanScope, dependenci
129
156
  const findings = await core.checkShapeHierarchy({ dependencies });
130
157
  const errors = findings.filter((f) => f.severity === 'error').length;
131
158
  result.shapeCheck = { errors, warnings: findings.length - errors };
132
- log(`[LinkedServer] shape check: ${errors} error(s), ${findings.length - errors} warning(s)`);
159
+ if (findings.length) {
160
+ log(`[LinkedServer] shape check: ${errors} error(s), ${findings.length - errors} warning(s)`);
161
+ }
133
162
  for (const f of findings)
134
163
  warn(`[LinkedServer] shape check ${f.severity} (${f.kind}): ${f.message}`);
135
164
  }
136
165
  catch (err) {
137
- (_b = result.failure) !== null && _b !== void 0 ? _b : (result.failure = asError(err));
166
+ result.shapeCheck = { errors: 0, warnings: 0, failed: asError(err).message };
138
167
  warn('[LinkedServer] shape check failed:', err);
139
168
  }
140
169
  return result;
@@ -165,8 +194,9 @@ export function graphsWrittenAndDropped(ops) {
165
194
  }
166
195
  /**
167
196
  * The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology
168
- * phase failed. The ontology phase's failure is then thrown, so the route answers with it, as
169
- * it does for a failed shape sync.
197
+ * sync failed. The ontology sync's failure is then thrown, so the route answers with it, as it
198
+ * does for a failed shape sync. A shape check that threw fails nothing: the answer reports it as
199
+ * `shapeCheck.failed`.
170
200
  */
171
201
  export async function syncOntologiesThenShapes(ontologyPhase, shapeSync) {
172
202
  const { ontologies, shapeCheck, failure } = await ontologyPhase();
@@ -1 +1 @@
1
- {"version":3,"file":"ontologySync.js","sourceRoot":"","sources":["../../../src/utils/ontologySync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ontology sync and the shape check, as the server runs them before its shape sync: at boot and\n * when the host asks for a re-sync (`POST /admin/sync-shapes`).\n *\n * - The ontology sync (core `syncOntologies`) writes each registered package's ontology data into\n * its own named graph of the app's default dataset (`{package IRI}/graph/ontology`, with a\n * record in the graph registry that marks it as a hierarchy source), skipping a package whose\n * stored hash matches, so the store's RDFS view infers the class hierarchy. A store without\n * graph queries (it answers `unsupported-query-kind`, or has no `graphQuery`) gets core's\n * earlier sync of the `rdfs:subClassOf` triples into the default graph instead. The graphs the\n * sync wrote or dropped are observed on core's query dispatch and reported by name.\n * - The shape check (core `checkShapeHierarchy`) compares the registered shapes with that\n * hierarchy. A running app is never stopped by it: its findings are logged and counted.\n * - The in-memory class hierarchy (core `loadOntologyHierarchy`) is loaded also when the sync\n * is off; the sync loads it itself when it runs.\n * - Before either, every linked package directory the server discovered registers its ontologies\n * (core `registerInstalledOntologies`): a package the app only deep-imports shape files from\n * never evaluates its register modules, so its data would be missing from both. A source\n * package without built register modules (a workspace package in dev) registers through\n * `importModule` (Vite's `ssrLoadModule`), when the caller passes one.\n *\n * The four core modules (`ontology/syncOntologies`, `shapes/checkShapeHierarchy`,\n * `ontology/hierarchy`, `ontology/registerInstalled`) are server only and not in core's index, so\n * {@link loadOntologyCore} imports them by their fixed subpaths when the phase runs; the core\n * this server depends on has all of them. Kept apart from `LinkedServer` so the order and the failure handling can be tested without\n * booting a server.\n */\nimport type {BootOrphanScope} from './syncShapes.js';\n\n/** The subset of core's ontology and shape-check API this module calls. */\nexport interface OntologyCore {\n syncOntologies(\n ds: unknown,\n options: {orphanScope: BootOrphanScope},\n ): Promise<{written: string[]; skipped: string[]; pruned: string[]; target?: string; fallback?: true}>;\n checkShapeHierarchy(options: {dependencies?: Record<string, string[]>}): Promise<\n Array<{kind: string; severity: 'error' | 'warning'; shape?: string; iri?: string; message: string}>\n >;\n /** Loads the in-memory class hierarchy. Optional so a test double can leave it out. */\n loadOntologyHierarchy?: () => Promise<unknown>;\n /**\n * Calls `onGraph` for every graph query on `ds` that succeeds until the returned function is\n * called (core's `subscribeQueryDispatch`). Optional so a test double can leave it out.\n */\n observeGraphQueries?: (\n ds: unknown,\n onGraph: (query: {kind: string; graph: string}, settled: Promise<unknown>) => void,\n ) => () => void;\n /**\n * Registers the ontologies of the installed packages in the given directories. Optional so a\n * test double can leave it out.\n */\n registerInstalledOntologies?: (\n packageDirs: string[],\n options?: {importModule?: (file: string) => Promise<unknown>},\n ) => Promise<string[]>;\n}\n\n/** What the ontology sync and the shape check did, as `POST /admin/sync-shapes` reports it. */\nexport interface OntologyPhaseResult {\n /**\n * Packages whose ontology graph (or, on a store without graph queries, hierarchy) was written,\n * packages left as they were (unchanged or unreadable), and the graphs the sync wrote and\n * dropped (empty on a store without graph queries).\n */\n ontologies: {written: string[]; skipped: string[]; graphs?: string[]; droppedGraphs?: string[]};\n /** The shape check's findings, counted by severity. */\n shapeCheck: {errors: number; warnings: number};\n /** The first error the sync or the check threw; the phase went on without it. */\n failure?: Error;\n}\n\n/**\n * Whether the server syncs shapes (and so ontologies) into its app data: off when `linked.config`\n * sets `syncShapesOnBoot: false` or the environment sets `LINKED_SYNC_SHAPES_ON_BOOT=false`. One\n * switch for both syncs, at boot and on the host's request.\n */\nexport function shapeSyncOn(\n env: Record<string, string | undefined>,\n configFlag: unknown,\n): boolean {\n return configFlag !== false && env.LINKED_SYNC_SHAPES_ON_BOOT !== 'false';\n}\n\n/**\n * Whether the boot syncs ontologies: when shape sync is on ({@link shapeSyncOn}) and the\n * environment does not opt out with `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`.\n */\nexport function bootSyncsOntologies(\n env: Record<string, string | undefined>,\n configFlag?: unknown,\n): boolean {\n return shapeSyncOn(env, configFlag) && env.LINKED_SYNC_ONTOLOGIES_ON_BOOT !== 'false';\n}\n\n/**\n * Import core's ontology sync, shape check, hierarchy loader and installed packages'\n * registration.\n */\nexport async function loadOntologyCore(): Promise<OntologyCore> {\n const sync = await import('@_linked/core/ontology/syncOntologies');\n const check = await import('@_linked/core/shapes/checkShapeHierarchy');\n const hierarchy = await import('@_linked/core/ontology/hierarchy');\n const registerInstalled = await import('@_linked/core/ontology/registerInstalled');\n const dispatch = await import('@_linked/core/queries/queryDispatch');\n return {\n syncOntologies: (ds, options) =>\n sync.syncOntologies(ds as Parameters<typeof sync.syncOntologies>[0], options),\n checkShapeHierarchy: (options) => check.checkShapeHierarchy(options),\n loadOntologyHierarchy: () => hierarchy.loadOntologyHierarchy(),\n registerInstalledOntologies: (packageDirs, options) =>\n registerInstalled.registerInstalledOntologies(packageDirs, options),\n observeGraphQueries: (ds, onGraph) =>\n dispatch.subscribeQueryDispatch((event) => {\n if (event.kind === 'graph' && event.target === ds) onGraph(event.query, event.result);\n }),\n };\n}\n\n/**\n * The package directories whose ontologies register before the sync: every linked package\n * discovered in the app's dependency tree, then the app's own workspace packages (an ontology\n * package in `packages/` that no package imports is one, so the dependency walk never reaches it).\n * Order kept, duplicates dropped.\n */\nexport function ontologyPackageDirs(\n linkedPackageDirs: Iterable<string>,\n workspacePackageDirs: Iterable<string>,\n): string[] {\n return [...new Set([...linkedPackageDirs, ...workspacePackageDirs])];\n}\n\n/**\n * In dev, the loader for source register modules: Vite's `ssrLoadModule`, so a workspace package\n * that was never built registers on the core the SSR graph uses. Undefined without Vite (a built\n * app), where source packages are skipped.\n */\nexport function sourceModuleImporter(\n vite: {ssrLoadModule?: (url: string) => Promise<unknown>} | undefined | null,\n): ((file: string) => Promise<unknown>) | undefined {\n const ssrLoadModule = vite?.ssrLoadModule;\n return typeof ssrLoadModule === 'function' ? (file) => ssrLoadModule.call(vite, file) : undefined;\n}\n\nconst asError = (err: unknown): Error => (err instanceof Error ? err : new Error(String(err)));\nconst list = (names: string[]) => (names.length ? names.join(', ') : 'none');\n\n/**\n * Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync\n * the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the\n * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync is logged and returned as `failure`, and the\n * check still runs; a failed check likewise. Logs one line for the sync, one summary line for\n * the check and one line per finding.\n */\nexport async function runOntologyPhase(\n ds: unknown,\n {\n core,\n sync,\n orphanScope,\n dependencies,\n packageDirs = [],\n importModule,\n log = console.log,\n warn = console.warn,\n }: {\n core: OntologyCore;\n sync: boolean;\n orphanScope: BootOrphanScope;\n dependencies?: Record<string, string[]>;\n /** Every linked package directory the server discovered (transitively). */\n packageDirs?: string[];\n /**\n * Imports a source register module (`src/ontologies/*.register.ts`) of a package directory\n * without built ones; in dev, Vite's `ssrLoadModule`.\n */\n importModule?: (file: string) => Promise<unknown>;\n log?: (message: string) => void;\n warn?: (message: string, err?: unknown) => void;\n },\n): Promise<OntologyPhaseResult> {\n const result: OntologyPhaseResult = {\n ontologies: {written: [], skipped: [], graphs: [], droppedGraphs: []},\n shapeCheck: {errors: 0, warnings: 0},\n };\n\n if (core.registerInstalledOntologies && packageDirs.length) {\n try {\n if (importModule) await core.registerInstalledOntologies(packageDirs, {importModule});\n else await core.registerInstalledOntologies(packageDirs);\n } catch (err) {\n warn(\"[LinkedServer] registering the installed packages' ontologies failed:\", err);\n }\n }\n\n let hierarchyLoaded = false;\n if (sync && ds) {\n // Every graph query that succeeded, in the order the sync sent them.\n const ops: Array<{kind: string; graph: string; ok?: boolean}> = [];\n const pending: Promise<unknown>[] = [];\n const stop = core.observeGraphQueries?.(ds, (query, settled) => {\n const op: {kind: string; graph: string; ok?: boolean} = {kind: query.kind, graph: query.graph};\n ops.push(op);\n pending.push(\n settled.then(\n () => (op.ok = true),\n () => undefined,\n ),\n );\n });\n try {\n const sync = await core.syncOntologies(ds, {orphanScope});\n await Promise.all(pending);\n const {graphs, droppedGraphs} = graphsWrittenAndDropped(ops.filter((op) => op.ok));\n result.ontologies = {written: sync.written, skipped: sync.skipped, graphs, droppedGraphs};\n hierarchyLoaded = true;\n // Core ≥ 2.41 says where it wrote (the global `ontologyGraph`); an older core is told\n // apart by a write that sent no graph query.\n const where =\n sync.target === 'default'\n ? \" (default graph: ontologyGraph is 'default')\"\n : sync.fallback || (sync.target === undefined && !!stop && sync.written.length > 0 && graphs.length === 0)\n ? ' (default graph: the store has no graph queries)'\n : sync.target && sync.target !== 'package'\n ? ` (one shared graph <${sync.target}>)`\n : '';\n log(\n `[LinkedServer] ontology sync${where}: ` +\n `written ${list(sync.written)}; skipped ${list(sync.skipped)}` +\n (orphanScope === 'all' ? `; pruned ${list(sync.pruned)}` : '') +\n (graphs.length ? `; graphs written ${list(graphs)}` : '') +\n (droppedGraphs.length ? `; graphs dropped ${list(droppedGraphs)}` : ''),\n );\n } catch (err) {\n result.failure = asError(err);\n warn('[LinkedServer] ontology sync failed:', err);\n } finally {\n stop?.();\n }\n }\n if (!hierarchyLoaded && core.loadOntologyHierarchy) {\n try {\n await core.loadOntologyHierarchy();\n } catch (err) {\n warn('[LinkedServer] loading the class hierarchy failed:', err);\n }\n }\n\n try {\n const findings = await core.checkShapeHierarchy({dependencies});\n const errors = findings.filter((f) => f.severity === 'error').length;\n result.shapeCheck = {errors, warnings: findings.length - errors};\n log(`[LinkedServer] shape check: ${errors} error(s), ${findings.length - errors} warning(s)`);\n for (const f of findings) warn(`[LinkedServer] shape check ${f.severity} (${f.kind}): ${f.message}`);\n } catch (err) {\n result.failure ??= asError(err);\n warn('[LinkedServer] shape check failed:', err);\n }\n return result;\n}\n\n/**\n * The graphs a sync left written and those it left dropped, from its successful graph queries in\n * order. A graph the run created and then dropped (the inference probe's temporary graphs) is in\n * neither list.\n */\nexport function graphsWrittenAndDropped(ops: Array<{kind: string; graph: string}>): {\n graphs: string[];\n droppedGraphs: string[];\n} {\n const byGraph = new Map<string, string[]>();\n for (const {kind, graph} of ops) {\n const kinds = byGraph.get(graph) ?? [];\n kinds.push(kind);\n byGraph.set(graph, kinds);\n }\n const graphs: string[] = [];\n const droppedGraphs: string[] = [];\n for (const [graph, kinds] of byGraph) {\n const last = kinds[kinds.length - 1];\n if (last !== 'graph_drop') graphs.push(graph);\n else if (kinds[0] !== 'graph_create') droppedGraphs.push(graph);\n }\n return {graphs: graphs.sort(), droppedGraphs: droppedGraphs.sort()};\n}\n\n/**\n * The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology\n * phase failed. The ontology phase's failure is then thrown, so the route answers with it, as\n * it does for a failed shape sync.\n */\nexport async function syncOntologiesThenShapes<T extends object>(\n ontologyPhase: () => Promise<OntologyPhaseResult>,\n shapeSync: () => Promise<T>,\n): Promise<T & Pick<OntologyPhaseResult, 'ontologies' | 'shapeCheck'>> {\n const {ontologies, shapeCheck, failure} = await ontologyPhase();\n const shapes = await shapeSync();\n if (failure) throw failure;\n return {...shapes, ontologies, shapeCheck};\n}\n"]}
1
+ {"version":3,"file":"ontologySync.js","sourceRoot":"","sources":["../../../src/utils/ontologySync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ontology sync and the shape check, as the server runs them before its shape sync: at boot and\n * when the host asks for a re-sync (`POST /admin/sync-shapes`).\n *\n * - The ontology sync (core `syncOntologies`) writes each registered package's ontology data into\n * its own named graph of the app's default dataset (`{package IRI}/graph/ontology`, with a\n * record in the graph registry that marks it as a hierarchy source), skipping a package whose\n * stored hash matches, so the store's RDFS view infers the class hierarchy. A store without\n * graph queries (it answers `unsupported-query-kind`, or has no `graphQuery`) gets core's\n * earlier sync of the `rdfs:subClassOf` triples into the default graph instead. The graphs the\n * sync wrote or dropped are observed on core's query dispatch and reported by name.\n * - The shape check (core `checkShapeHierarchy`) compares the registered shapes with that\n * hierarchy. A running app is never stopped by it: its findings are logged and counted.\n * - The in-memory class hierarchy (core `loadOntologyHierarchy`) is loaded also when the sync\n * is off; the sync loads it itself when it runs.\n * - Before either, every linked package directory the server discovered registers its ontologies\n * (core `registerInstalledOntologies`): a package the app only deep-imports shape files from\n * never evaluates its register modules, so its data would be missing from both. A source\n * package without built register modules (a workspace package in dev) registers through\n * `importModule` (Vite's `ssrLoadModule`), when the caller passes one.\n *\n * The four core modules (`ontology/syncOntologies`, `shapes/checkShapeHierarchy`,\n * `ontology/hierarchy`, `ontology/registerInstalled`) are server only and not in core's index, so\n * {@link loadOntologyCore} imports them by their fixed subpaths when the phase runs; the core\n * this server depends on has all of them. Kept apart from `LinkedServer` so the order and the failure handling can be tested without\n * booting a server.\n */\nimport {writtenAndUnchanged, type BootOrphanScope} from './syncShapes.js';\n\n/** The subset of core's ontology and shape-check API this module calls. */\nexport interface OntologyCore {\n syncOntologies(\n ds: unknown,\n options: {orphanScope: BootOrphanScope},\n ): Promise<{written: string[]; skipped: string[]; pruned: string[]; target?: string}>;\n checkShapeHierarchy(options: {dependencies?: Record<string, string[]>}): Promise<\n Array<{kind: string; severity: 'error' | 'warning'; shape?: string; iri?: string; message: string}>\n >;\n /** Loads the in-memory class hierarchy. Optional so a test double can leave it out. */\n loadOntologyHierarchy?: () => Promise<unknown>;\n /**\n * Calls `onGraph` for every graph query on `ds` that succeeds until the returned function is\n * called (core's `subscribeQueryDispatch`). Optional so a test double can leave it out.\n */\n observeGraphQueries?: (\n ds: unknown,\n onGraph: (query: {kind: string; graph: string}, settled: Promise<unknown>) => void,\n ) => () => void;\n /**\n * Registers the ontologies of the installed packages in the given directories. Optional so a\n * test double can leave it out.\n */\n registerInstalledOntologies?: (\n packageDirs: string[],\n options?: {importModule?: (file: string) => Promise<unknown>},\n ) => Promise<string[]>;\n}\n\n/** The shape check's findings by severity; `failed` holds the message of a check that threw. */\nexport interface ShapeCheckSummary {\n errors: number;\n warnings: number;\n failed?: string;\n}\n\n/** What the ontology sync and the shape check did, as `POST /admin/sync-shapes` reports it. */\nexport interface OntologyPhaseResult {\n /**\n * Packages whose ontology graph (or, on a store without graph queries, hierarchy) was written,\n * packages left as they were (unchanged or unreadable), and the graphs the sync wrote and\n * dropped (empty on a store without graph queries).\n */\n ontologies: {written: string[]; skipped: string[]; graphs?: string[]; droppedGraphs?: string[]};\n /**\n * The shape check's findings, counted by severity, and the message of the error the check\n * threw, if it did (`failed`). A thrown check is reported here and never fails the phase.\n */\n shapeCheck: ShapeCheckSummary;\n /** The error the ontology sync threw; the phase went on without it. */\n failure?: Error;\n}\n\n/**\n * Whether the server syncs shapes (and so ontologies) into its app data: off when `linked.config`\n * sets `syncShapesOnBoot: false` or the environment sets `LINKED_SYNC_SHAPES_ON_BOOT=false`. One\n * switch for both syncs, at boot and on the host's request.\n */\nexport function shapeSyncOn(\n env: Record<string, string | undefined>,\n configFlag: unknown,\n): boolean {\n return configFlag !== false && env.LINKED_SYNC_SHAPES_ON_BOOT !== 'false';\n}\n\n/**\n * Whether the boot syncs ontologies: when shape sync is on ({@link shapeSyncOn}) and the\n * environment does not opt out with `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`.\n */\nexport function bootSyncsOntologies(\n env: Record<string, string | undefined>,\n configFlag?: unknown,\n): boolean {\n return shapeSyncOn(env, configFlag) && env.LINKED_SYNC_ONTOLOGIES_ON_BOOT !== 'false';\n}\n\n/**\n * Import core's ontology sync, shape check, hierarchy loader and installed packages'\n * registration.\n */\nexport async function loadOntologyCore(): Promise<OntologyCore> {\n const sync = await import('@_linked/core/ontology/syncOntologies');\n const check = await import('@_linked/core/shapes/checkShapeHierarchy');\n const hierarchy = await import('@_linked/core/ontology/hierarchy');\n const registerInstalled = await import('@_linked/core/ontology/registerInstalled');\n const dispatch = await import('@_linked/core/queries/queryDispatch');\n return {\n syncOntologies: (ds, options) =>\n sync.syncOntologies(ds as Parameters<typeof sync.syncOntologies>[0], options),\n checkShapeHierarchy: (options) => check.checkShapeHierarchy(options),\n loadOntologyHierarchy: () => hierarchy.loadOntologyHierarchy(),\n registerInstalledOntologies: (packageDirs, options) =>\n registerInstalled.registerInstalledOntologies(packageDirs, options),\n observeGraphQueries: (ds, onGraph) =>\n dispatch.subscribeQueryDispatch((event) => {\n if (event.kind === 'graph' && event.target === ds) onGraph(event.query, event.result);\n }),\n };\n}\n\n/**\n * The package directories whose ontologies register before the sync: every linked package\n * discovered in the app's dependency tree, then the app's own workspace packages (an ontology\n * package in `packages/` that no package imports is one, so the dependency walk never reaches it).\n * Order kept, duplicates dropped.\n */\nexport function ontologyPackageDirs(\n linkedPackageDirs: Iterable<string>,\n workspacePackageDirs: Iterable<string>,\n): string[] {\n return [...new Set([...linkedPackageDirs, ...workspacePackageDirs])];\n}\n\n/**\n * In dev, the loader for source register modules: Vite's `ssrLoadModule`, so a workspace package\n * that was never built registers on the core the SSR graph uses. Undefined without Vite (a built\n * app), where source packages are skipped.\n */\nexport function sourceModuleImporter(\n vite: {ssrLoadModule?: (url: string) => Promise<unknown>} | undefined | null,\n): ((file: string) => Promise<unknown>) | undefined {\n const ssrLoadModule = vite?.ssrLoadModule;\n return typeof ssrLoadModule === 'function' ? (file) => ssrLoadModule.call(vite, file) : undefined;\n}\n\nconst asError = (err: unknown): Error => (err instanceof Error ? err : new Error(String(err)));\nconst list = (names: string[]) => (names.length ? names.join(', ') : 'none');\n\n/**\n * Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync\n * the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the\n * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync\n * is logged and returned as `failure`, and the check still runs; a check that throws is logged and\n * returned as `shapeCheck.failed`. Logs one line for the sync, one summary line for\n * the check and one line per finding.\n */\nexport async function runOntologyPhase(\n ds: unknown,\n {\n core,\n sync,\n orphanScope,\n dependencies,\n packageDirs = [],\n importModule,\n log = console.log,\n warn = console.warn,\n }: {\n core: OntologyCore;\n sync: boolean;\n orphanScope: BootOrphanScope;\n dependencies?: Record<string, string[]>;\n /** Every linked package directory the server discovered (transitively). */\n packageDirs?: string[];\n /**\n * Imports a source register module (`src/ontologies/*.register.ts`) of a package directory\n * without built ones; in dev, Vite's `ssrLoadModule`.\n */\n importModule?: (file: string) => Promise<unknown>;\n log?: (message: string) => void;\n warn?: (message: string, err?: unknown) => void;\n },\n): Promise<OntologyPhaseResult> {\n const result: OntologyPhaseResult = {\n ontologies: {written: [], skipped: [], graphs: [], droppedGraphs: []},\n shapeCheck: {errors: 0, warnings: 0},\n };\n\n if (core.registerInstalledOntologies && packageDirs.length) {\n try {\n if (importModule) await core.registerInstalledOntologies(packageDirs, {importModule});\n else await core.registerInstalledOntologies(packageDirs);\n } catch (err) {\n warn(\"[LinkedServer] registering the installed packages' ontologies failed:\", err);\n }\n }\n\n let hierarchyLoaded = false;\n if (sync && ds) {\n // Every graph query that succeeded, in the order the sync sent them.\n const ops: Array<{kind: string; graph: string; ok?: boolean}> = [];\n const pending: Promise<unknown>[] = [];\n const stop = core.observeGraphQueries?.(ds, (query, settled) => {\n const op: {kind: string; graph: string; ok?: boolean} = {kind: query.kind, graph: query.graph};\n ops.push(op);\n pending.push(\n settled.then(\n () => (op.ok = true),\n () => undefined,\n ),\n );\n });\n try {\n const sync = await core.syncOntologies(ds, {orphanScope});\n await Promise.all(pending);\n const {graphs, droppedGraphs} = graphsWrittenAndDropped(ops.filter((op) => op.ok));\n result.ontologies = {written: sync.written, skipped: sync.skipped, graphs, droppedGraphs};\n hierarchyLoaded = true;\n // Core says where it wrote (the global `ontologyGraph`). A store without graph queries makes\n // the sync throw in the 'package' and shared modes, reported below as a failed sync.\n const where =\n sync.target === 'default'\n ? \" (default graph: ontologyGraph is 'default')\"\n : sync.target && sync.target !== 'package'\n ? ` (one shared graph <${sync.target}>)`\n : '';\n log(\n `[LinkedServer] ontology sync${where}: ` +\n writtenAndUnchanged(sync.written, sync.skipped) +\n (orphanScope === 'all' ? `; pruned ${list(sync.pruned)}` : '') +\n (graphs.length ? `; graphs written ${list(graphs)}` : '') +\n (droppedGraphs.length ? `; graphs dropped ${list(droppedGraphs)}` : ''),\n );\n } catch (err) {\n result.failure = asError(err);\n warn('[LinkedServer] ontology sync failed:', err);\n } finally {\n stop?.();\n }\n }\n if (!hierarchyLoaded && core.loadOntologyHierarchy) {\n try {\n await core.loadOntologyHierarchy();\n } catch (err) {\n warn('[LinkedServer] loading the class hierarchy failed:', err);\n }\n }\n\n try {\n const findings = await core.checkShapeHierarchy({dependencies});\n const errors = findings.filter((f) => f.severity === 'error').length;\n result.shapeCheck = {errors, warnings: findings.length - errors};\n if (findings.length) {\n log(`[LinkedServer] shape check: ${errors} error(s), ${findings.length - errors} warning(s)`);\n }\n for (const f of findings) warn(`[LinkedServer] shape check ${f.severity} (${f.kind}): ${f.message}`);\n } catch (err) {\n result.shapeCheck = {errors: 0, warnings: 0, failed: asError(err).message};\n warn('[LinkedServer] shape check failed:', err);\n }\n return result;\n}\n\n/**\n * The graphs a sync left written and those it left dropped, from its successful graph queries in\n * order. A graph the run created and then dropped (the inference probe's temporary graphs) is in\n * neither list.\n */\nexport function graphsWrittenAndDropped(ops: Array<{kind: string; graph: string}>): {\n graphs: string[];\n droppedGraphs: string[];\n} {\n const byGraph = new Map<string, string[]>();\n for (const {kind, graph} of ops) {\n const kinds = byGraph.get(graph) ?? [];\n kinds.push(kind);\n byGraph.set(graph, kinds);\n }\n const graphs: string[] = [];\n const droppedGraphs: string[] = [];\n for (const [graph, kinds] of byGraph) {\n const last = kinds[kinds.length - 1];\n if (last !== 'graph_drop') graphs.push(graph);\n else if (kinds[0] !== 'graph_create') droppedGraphs.push(graph);\n }\n return {graphs: graphs.sort(), droppedGraphs: droppedGraphs.sort()};\n}\n\n/**\n * The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology\n * sync failed. The ontology sync's failure is then thrown, so the route answers with it, as it\n * does for a failed shape sync. A shape check that threw fails nothing: the answer reports it as\n * `shapeCheck.failed`.\n */\nexport async function syncOntologiesThenShapes<T extends object>(\n ontologyPhase: () => Promise<OntologyPhaseResult>,\n shapeSync: () => Promise<T>,\n): Promise<T & Pick<OntologyPhaseResult, 'ontologies' | 'shapeCheck'>> {\n const {ontologies, shapeCheck, failure} = await ontologyPhase();\n const shapes = await shapeSync();\n if (failure) throw failure;\n return {...shapes, ontologies, shapeCheck};\n}\n"]}
@@ -114,6 +114,11 @@ export interface ShapeGraphsReport {
114
114
  /** Set when the store has no graph queries, so the shapes went into the default graph instead of `target`. */
115
115
  fallback?: true;
116
116
  }
117
+ /**
118
+ * The head of a sync's log line: the packages written, then how many were unchanged (`skipped`
119
+ * in the syncs' results): `written a, b; 17 unchanged`, `written a, b`, or `19 unchanged`.
120
+ */
121
+ export declare function writtenAndUnchanged(written: string[], unchanged: string[]): string;
117
122
  /**
118
123
  * Sync the registered shapes into `ds`, one shape graph per package (see the module doc), and log
119
124
  * one line naming what was written and skipped, the graphs written, the packages without
@@ -100,6 +100,18 @@ export function viteShapesTurtleGenerator(vite) {
100
100
  };
101
101
  }
102
102
  const list = (names) => (names.length ? names.join(', ') : 'none');
103
+ /**
104
+ * The head of a sync's log line: the packages written, then how many were unchanged (`skipped`
105
+ * in the syncs' results): `written a, b; 17 unchanged`, `written a, b`, or `19 unchanged`.
106
+ */
107
+ export function writtenAndUnchanged(written, unchanged) {
108
+ const parts = written.length ? [`written ${written.join(', ')}`] : [];
109
+ if (unchanged.length || !written.length)
110
+ parts.push(`${unchanged.length} unchanged`);
111
+ return parts.join('; ');
112
+ }
113
+ /** An app graph's status in a log line: core's `skipped` is an unchanged graph. */
114
+ const appGraphStatus = (status) => (status === 'skipped' ? 'unchanged' : status);
103
115
  /**
104
116
  * Sync the registered shapes into `ds`, one shape graph per package (see the module doc), and log
105
117
  * one line naming what was written and skipped, the graphs written, the packages without
@@ -177,9 +189,9 @@ export async function syncShapeGraphsInto(ds, { core, orphanScope, isOwn, dev, p
177
189
  log(`[LinkedServer] shape sync (default graph: ${why}): ${report.defaultGraph} shape(s) written`);
178
190
  }
179
191
  else if (shared) {
180
- log(`[LinkedServer] shape graph sync (one shared graph <${shared}>): written ${list(report.written)}; ` +
181
- `skipped ${list(report.skipped)}` +
182
- (report.appGraph ? `; app shapes ${report.appGraph.status}` : '') +
192
+ log(`[LinkedServer] shape graph sync (one shared graph <${shared}>): ` +
193
+ writtenAndUnchanged(report.written, report.skipped) +
194
+ (report.appGraph ? `; app shapes ${appGraphStatus(report.appGraph.status)}` : '') +
183
195
  (report.pruned.length ? `; dropped from the graph ${list(report.pruned)}` : '') +
184
196
  (withoutTurtle.length ? `; no shapes.ttl (not in the graph): ${list(withoutTurtle)}` : ''));
185
197
  // Unlike package graphs, which stay when a package goes missing, the shared graph is rebuilt
@@ -190,8 +202,8 @@ export async function syncShapeGraphsInto(ds, { core, orphanScope, isOwn, dev, p
190
202
  }
191
203
  }
192
204
  else {
193
- log(`[LinkedServer] shape graph sync: written ${list(report.written)}; skipped ${list(report.skipped)}` +
194
- (report.appGraph ? `; app graph ${report.appGraph.status}` : '; no app graph (DATA_ROOT not set)') +
205
+ log(`[LinkedServer] shape graph sync: ${writtenAndUnchanged(report.written, report.skipped)}` +
206
+ (report.appGraph ? `; app graph ${appGraphStatus(report.appGraph.status)}` : '; no app graph (DATA_ROOT not set)') +
195
207
  (report.graphs.length ? `; graphs written ${list(report.graphs)}` : '') +
196
208
  (orphanScope === 'all' ? `; pruned ${list(report.pruned)}` : '') +
197
209
  (report.cleaned ? `; ${report.cleaned} default-graph shape(s) removed` : '') +
@@ -1 +1 @@
1
- {"version":3,"file":"syncShapes.js","sourceRoot":"","sources":["../../../src/utils/syncShapes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shape sync, as the server runs it at boot and when the host asks for a re-sync\n * (`POST /admin/sync-shapes`): core's `syncShapeGraphs` writes each package's shapes into its own\n * named graph of the app's default dataset (`{package IRI}/graph/shapes`, a `ShapesGraph` record\n * in the graph registry), skipped when the stored content hash matches, and the app's generated\n * single-owner shapes into the app's own shape graph, `{DATA_ROOT}/graph/shapes`. Shapes an\n * earlier sync wrote into the default graph are removed there once their package's graph holds\n * them. A store without graph queries gets core's default-graph shape sync instead.\n *\n * Where a package's Turtle comes from ({@link shapeTurtleSource}):\n * - an installed package: its built `lib/esm/data/shapes.ttl` (written by `linked build`); a\n * package without that file gets no shape graph, and nothing is generated for it;\n * - the app's own packages (the app and its workspace packages): in dev, generated in process\n * from their source shapes, through Vite's loader, so the core the SSR graph registered the\n * shapes on writes them (the same bytes as `packageShapesTurtle`, which `linked build` writes);\n * in a built app, their `lib/esm/data/shapes.ttl` when there is one, else generated in process.\n *\n * Kept apart from `LinkedServer` so the choices and the log can be tested without booting a\n * server.\n */\nimport fs from 'fs';\nimport path from 'path';\n\n/** The orphan policies a booting server may run with. */\nexport type BootOrphanScope = 'all' | 'none';\n\n/**\n * The orphan policy for the shape sync.\n *\n * `'none'` unless the operator sets `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true`, which drops the\n * shape graph of every package that is no longer registered in this process (never the app's own\n * graph).\n *\n * The registry is routinely a subset of what the dataset holds: shapes are deep-importable one\n * file at a time, an app's backend may import no shapes at all, and an app-data dataset also holds\n * shapes other writers put there. So nothing is pruned by default.\n */\nexport function bootOrphanScope(\n env: Record<string, string | undefined> = process.env,\n): BootOrphanScope {\n return env.LINKED_SYNC_SHAPES_PRUNE_ORPHANS === 'true' ? 'all' : 'none';\n}\n\n/** What core's `syncShapeGraphs` reports (`@_linked/core/shapes/syncShapeGraphs`). */\nexport interface ShapeGraphSyncResult {\n written: string[];\n skipped: string[];\n pruned: string[];\n cleaned: number;\n appGraph?: 'written' | 'skipped' | 'dropped';\n defaultGraph?: number;\n /** The global `shapeGraph` target the run wrote in (core ≥ 2.41): `'package'`, `'default'` or an IRI. */\n target?: string;\n /** Set when a store without graph queries took the default-graph sync instead of `target`. */\n fallback?: true;\n}\n\n/** The subset of core's shape graph API this module calls. */\nexport interface ShapeGraphCore {\n syncShapeGraphs(\n ds: unknown,\n options: {\n orphanScope: BootOrphanScope;\n packages?: string[];\n turtleFor: (packageName: string) => Promise<string | undefined>;\n appShapes?: {graph: string; package?: string};\n },\n ): Promise<ShapeGraphSyncResult>;\n /** Every package with registered shapes (core counted for its built-ins). */\n packagesWithShapes(): string[];\n /** A package's shape graph IRI (`ShapesGraph.iriFor`). */\n shapesGraphFor(packageName: string): string;\n}\n\n/** Import core's shape graph sync. */\nexport async function loadShapeGraphCore(): Promise<ShapeGraphCore> {\n const sync = await import('@_linked/core/shapes/syncShapeGraphs');\n const turtle = await import('@_linked/core/shapes/shapesToTurtle');\n const graphs = await import('@_linked/core/shapes/NamedGraph');\n return {\n syncShapeGraphs: (ds, options) =>\n sync.syncShapeGraphs(ds as Parameters<typeof sync.syncShapeGraphs>[0], options),\n packagesWithShapes: () => turtle.packagesWithShapes(),\n shapesGraphFor: (packageName) => graphs.ShapesGraph.iriFor(packageName),\n };\n}\n\n/** Where `linked build` writes a package's shapes, relative to the package root. */\nexport const SHAPES_TURTLE_FILE = path.join('lib', 'esm', 'data', 'shapes.ttl');\n\n/** A package's built `shapes.ttl`, or undefined when the package root has none. */\nexport function readShapesTurtle(packageDir: string | undefined): string | undefined {\n if (!packageDir) return undefined;\n try {\n return fs.readFileSync(path.join(packageDir, SHAPES_TURTLE_FILE), 'utf-8');\n } catch {\n return undefined;\n }\n}\n\n/** Where one package's Turtle comes from. */\nexport type ShapeTurtleSource =\n | {source: 'file'; text: string}\n | {source: 'generated'}\n | {source: 'none'};\n\n/**\n * Where a registered package's Turtle comes from: in dev an own package is generated; otherwise\n * its built file when it has one; else an own package is generated and an installed one has\n * none (no shape graph, no generation).\n */\nexport function shapeTurtleSource(\n packageName: string,\n {\n own,\n dev,\n packageDir,\n read = readShapesTurtle,\n }: {\n /** Whether the package is the app or one of its workspace packages. */\n own: boolean;\n /** Whether the server runs with Vite (dev). */\n dev: boolean;\n /** The package root, when the server knows it. */\n packageDir: string | undefined;\n read?: (packageDir: string | undefined) => string | undefined;\n },\n): ShapeTurtleSource {\n if (own && dev) return {source: 'generated'};\n const text = read(packageDir);\n if (text !== undefined) return {source: 'file', text};\n return own ? {source: 'generated'} : {source: 'none'};\n}\n\n/**\n * The app's own shape graph, `{DATA_ROOT}/graph/shapes`, or undefined when `DATA_ROOT` is not\n * an absolute IRI.\n */\nexport function appShapesGraph(\n env: Record<string, string | undefined> = process.env,\n): string | undefined {\n const root = env.DATA_ROOT?.trim().replace(/\\/+$/, '');\n if (!root || !/^[a-z][a-z0-9+.-]*:\\/\\/[^/]/i.test(root)) return undefined;\n return `${root}/graph/shapes`;\n}\n\n/**\n * In dev, the generator for the app's own packages: core's `packageShapesTurtle` imported through\n * Vite's `ssrLoadModule`, the core the SSR graph registered the source shapes on. Undefined\n * without Vite.\n */\nexport function viteShapesTurtleGenerator(\n vite: {ssrLoadModule?: (url: string) => Promise<unknown>} | undefined | null,\n): ((packageName: string) => Promise<string | undefined>) | undefined {\n const ssrLoadModule = vite?.ssrLoadModule;\n if (typeof ssrLoadModule !== 'function') return undefined;\n return async (packageName) => {\n const mod = (await ssrLoadModule.call(vite, '@_linked/core/shapes/shapesToTurtle')) as {\n packageShapesTurtle?: (name: string) => string | undefined;\n };\n if (typeof mod?.packageShapesTurtle !== 'function') {\n throw new Error('@_linked/core/shapes/shapesToTurtle has no packageShapesTurtle');\n }\n return mod.packageShapesTurtle(packageName);\n };\n}\n\n/** What one shape sync did, as the boot logs it and `POST /admin/sync-shapes` reports it. */\nexport interface ShapeGraphsReport {\n /** Packages whose shape graph was written, and those left as they were. */\n written: string[];\n skipped: string[];\n /** Packages no longer registered whose shape graph was dropped (`orphanScope: 'all'`). */\n pruned: string[];\n /** The shape graphs written (package graphs, then the app graph), by IRI. */\n graphs: string[];\n /** The app's own shape graph and what the sync did with it; absent without `DATA_ROOT`. */\n appGraph?: {graph: string; status: 'written' | 'skipped' | 'dropped'};\n /**\n * Registered packages with no Turtle, so with no shape graph: an installed package without\n * `shapes.ttl`, or an own package whose shapes could not be generated.\n */\n withoutTurtle: string[];\n /** Shapes an earlier sync had written into the default graph, removed now. */\n cleaned: number;\n /**\n * Set when the shapes went into the default graph (the global `shapeGraph` is `'default'`, or\n * it has no graph queries): the default-graph sync's writes.\n */\n defaultGraph?: number;\n /** The global `shapeGraph` target (`'package'`, `'default'` or a shared graph IRI), when core reports it. */\n target?: string;\n /** Set when the store has no graph queries, so the shapes went into the default graph instead of `target`. */\n fallback?: true;\n}\n\nconst list = (names: string[]) => (names.length ? names.join(', ') : 'none');\n\n/**\n * Sync the registered shapes into `ds`, one shape graph per package (see the module doc), and log\n * one line naming what was written and skipped, the graphs written, the packages without\n * `shapes.ttl` and the default-graph shapes removed. Throws when the sync fails.\n */\nexport async function syncShapeGraphsInto(\n ds: unknown,\n {\n core,\n orphanScope,\n isOwn,\n dev,\n packageDirOf,\n generate,\n appGraph,\n appPackage,\n read = readShapesTurtle,\n log = console.log,\n warn = console.warn,\n }: {\n core: ShapeGraphCore;\n orphanScope: BootOrphanScope;\n /** Whether a package is the app or one of its workspace packages. */\n isOwn: (packageName: string) => boolean;\n /** Whether the server runs with Vite (dev). */\n dev: boolean;\n /** A package's root directory, when the server knows it. */\n packageDirOf: (packageName: string) => string | undefined;\n /**\n * Generates an own package's Turtle (in dev through Vite's loader); undefined, or a result of\n * undefined, leaves the generation to the core that runs the sync.\n */\n generate?: (packageName: string) => Promise<string | undefined>;\n /** The app's own shape graph (`{DATA_ROOT}/graph/shapes`). */\n appGraph?: string;\n /** The app's package name, recorded on its shape graph's record. */\n appPackage?: string;\n read?: (packageDir: string | undefined) => string | undefined;\n log?: (message: string) => void;\n warn?: (message: string, err?: unknown) => void;\n },\n): Promise<ShapeGraphsReport> {\n const sources = new Map<string, ShapeTurtleSource>();\n for (const name of core.packagesWithShapes()) {\n sources.set(name, shapeTurtleSource(name, {own: isOwn(name), dev, packageDir: packageDirOf(name), read}));\n }\n const noFile = [...sources].filter(([, s]) => s.source === 'none').map(([name]) => name);\n\n const turtleFor = async (name: string): Promise<string | undefined> => {\n const source = sources.get(name);\n if (source?.source === 'file') return source.text;\n if (source?.source !== 'generated' || !generate) return undefined;\n try {\n return await generate(name);\n } catch (err) {\n warn(`[LinkedServer] generating the shapes of ${name} through the dev loader failed; trying the server's core instead:`, err);\n return undefined;\n }\n };\n\n const result = await core.syncShapeGraphs(ds, {\n orphanScope,\n // A package without Turtle is left out: core would generate it otherwise.\n ...(noFile.length ? {packages: [...sources.keys()].filter((n) => !noFile.includes(n))} : {}),\n turtleFor,\n ...(appGraph ? {appShapes: {graph: appGraph, ...(appPackage ? {package: appPackage} : {})}} : {}),\n });\n\n // A package core neither wrote nor skipped got no Turtle at all: generation produced nothing\n // (in dev, the server's core may not hold the source shapes the dev loader registered). The\n // default-graph sync (a store without graph queries) reports no packages, so it is not checked.\n const covered = new Set([...result.written, ...result.skipped]);\n const notGenerated =\n result.defaultGraph !== undefined\n ? []\n : [...sources.keys()].filter((n) => !noFile.includes(n) && !covered.has(n));\n if (notGenerated.length) {\n warn(\n `[LinkedServer] no shapes could be generated for ${list(notGenerated.sort())}, so ` +\n `${notGenerated.length === 1 ? 'it has' : 'they have'} no shape graph this sync`,\n );\n }\n const withoutTurtle = [...noFile, ...notGenerated].sort();\n\n // A shared graph (the global `shapeGraph` is an IRI) holds every package and the app's shapes.\n const shared = result.target && result.target !== 'package' && result.target !== 'default' ? result.target : undefined;\n const report: ShapeGraphsReport = {\n written: result.written,\n skipped: result.skipped,\n pruned: result.pruned,\n // A shared graph is written whole or not at all: listed when the run wrote anything into it.\n graphs: shared\n ? result.written.length || result.appGraph === 'written' || result.pruned.length\n ? [shared]\n : []\n : result.written.map((name) => core.shapesGraphFor(name)),\n withoutTurtle,\n cleaned: result.cleaned,\n };\n if (result.target) report.target = result.target;\n if (result.fallback) report.fallback = true;\n if (appGraph && result.appGraph) {\n report.appGraph = {graph: shared ?? appGraph, status: result.appGraph};\n if (result.appGraph === 'written' && !shared) report.graphs.push(appGraph);\n }\n if (result.defaultGraph !== undefined) report.defaultGraph = result.defaultGraph;\n\n if (report.defaultGraph !== undefined) {\n const why =\n result.target === 'default' && !result.fallback\n ? \"shapeGraph is 'default'\"\n : 'the store has no graph queries';\n log(`[LinkedServer] shape sync (default graph: ${why}): ${report.defaultGraph} shape(s) written`);\n } else if (shared) {\n log(\n `[LinkedServer] shape graph sync (one shared graph <${shared}>): written ${list(report.written)}; ` +\n `skipped ${list(report.skipped)}` +\n (report.appGraph ? `; app shapes ${report.appGraph.status}` : '') +\n (report.pruned.length ? `; dropped from the graph ${list(report.pruned)}` : '') +\n (withoutTurtle.length ? `; no shapes.ttl (not in the graph): ${list(withoutTurtle)}` : ''),\n );\n // Unlike package graphs, which stay when a package goes missing, the shared graph is rebuilt\n // from the packages the run covers.\n if (withoutTurtle.length) {\n warn(\n `[LinkedServer] the shared shape graph <${shared}> holds no shapes of ${list(withoutTurtle)}: ` +\n 'a package without Turtle in a run is left out of the rebuilt graph',\n );\n }\n } else {\n log(\n `[LinkedServer] shape graph sync: written ${list(report.written)}; skipped ${list(report.skipped)}` +\n (report.appGraph ? `; app graph ${report.appGraph.status}` : '; no app graph (DATA_ROOT not set)') +\n (report.graphs.length ? `; graphs written ${list(report.graphs)}` : '') +\n (orphanScope === 'all' ? `; pruned ${list(report.pruned)}` : '') +\n (report.cleaned ? `; ${report.cleaned} default-graph shape(s) removed` : '') +\n (withoutTurtle.length ? `; no shapes.ttl (no shape graph): ${list(withoutTurtle)}` : ''),\n );\n }\n if (orphanScope === 'all' && withoutTurtle.length && !shared) {\n warn(\n '[LinkedServer] orphaned shape graphs were not pruned: core prunes only when the sync covers ' +\n `every registered package, and ${list(withoutTurtle)} has no Turtle`,\n );\n }\n return report;\n}\n\n/**\n * @deprecated The default-graph shape sync's callback type; the server now syncs shape graphs with\n * {@link syncShapeGraphsInto}. Kept so code built against an earlier server still compiles.\n */\nexport type SyncShapesFn = (\n ds: unknown,\n options: {orphanScope: BootOrphanScope},\n) => Promise<Array<() => Promise<void>>>;\n\n/**\n * @deprecated Use {@link syncShapeGraphsInto}, which writes each package's shapes into its own\n * shape graph. This runs the old default-graph sync: it plans every registered shape with\n * `syncShapes` (the orphan policy from {@link bootOrphanScope}) and runs the writes in batches\n * of 8. Kept so code built against an earlier server keeps working.\n */\nexport async function materializeShapes(\n ds: unknown,\n syncShapes: SyncShapesFn,\n {\n env = process.env,\n log = console.log,\n }: {\n env?: Record<string, string | undefined>;\n log?: (message: string) => void;\n } = {},\n): Promise<{count: number; orphanScope: BootOrphanScope}> {\n const orphanScope = bootOrphanScope(env);\n const thunks = await syncShapes(ds, {orphanScope});\n for (let i = 0; i < thunks.length; i += 8) {\n await Promise.all(thunks.slice(i, i + 8).map((run) => run()));\n }\n log(\n `[LinkedServer] synced shapes into the default graph with orphanScope '${orphanScope}': ` +\n `${thunks.length} operation(s)`,\n );\n return {count: thunks.length, orphanScope};\n}\n"]}
1
+ {"version":3,"file":"syncShapes.js","sourceRoot":"","sources":["../../../src/utils/syncShapes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shape sync, as the server runs it at boot and when the host asks for a re-sync\n * (`POST /admin/sync-shapes`): core's `syncShapeGraphs` writes each package's shapes into its own\n * named graph of the app's default dataset (`{package IRI}/graph/shapes`, a `ShapesGraph` record\n * in the graph registry), skipped when the stored content hash matches, and the app's generated\n * single-owner shapes into the app's own shape graph, `{DATA_ROOT}/graph/shapes`. Shapes an\n * earlier sync wrote into the default graph are removed there once their package's graph holds\n * them. A store without graph queries gets core's default-graph shape sync instead.\n *\n * Where a package's Turtle comes from ({@link shapeTurtleSource}):\n * - an installed package: its built `lib/esm/data/shapes.ttl` (written by `linked build`); a\n * package without that file gets no shape graph, and nothing is generated for it;\n * - the app's own packages (the app and its workspace packages): in dev, generated in process\n * from their source shapes, through Vite's loader, so the core the SSR graph registered the\n * shapes on writes them (the same bytes as `packageShapesTurtle`, which `linked build` writes);\n * in a built app, their `lib/esm/data/shapes.ttl` when there is one, else generated in process.\n *\n * Kept apart from `LinkedServer` so the choices and the log can be tested without booting a\n * server.\n */\nimport fs from 'fs';\nimport path from 'path';\n\n/** The orphan policies a booting server may run with. */\nexport type BootOrphanScope = 'all' | 'none';\n\n/**\n * The orphan policy for the shape sync.\n *\n * `'none'` unless the operator sets `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true`, which drops the\n * shape graph of every package that is no longer registered in this process (never the app's own\n * graph).\n *\n * The registry is routinely a subset of what the dataset holds: shapes are deep-importable one\n * file at a time, an app's backend may import no shapes at all, and an app-data dataset also holds\n * shapes other writers put there. So nothing is pruned by default.\n */\nexport function bootOrphanScope(\n env: Record<string, string | undefined> = process.env,\n): BootOrphanScope {\n return env.LINKED_SYNC_SHAPES_PRUNE_ORPHANS === 'true' ? 'all' : 'none';\n}\n\n/** What core's `syncShapeGraphs` reports (`@_linked/core/shapes/syncShapeGraphs`). */\nexport interface ShapeGraphSyncResult {\n written: string[];\n skipped: string[];\n pruned: string[];\n cleaned: number;\n appGraph?: 'written' | 'skipped' | 'dropped';\n defaultGraph?: number;\n /** The global `shapeGraph` target the run wrote in (core ≥ 2.41): `'package'`, `'default'` or an IRI. */\n target?: string;\n /** Set when a store without graph queries took the default-graph sync instead of `target`. */\n fallback?: true;\n}\n\n/** The subset of core's shape graph API this module calls. */\nexport interface ShapeGraphCore {\n syncShapeGraphs(\n ds: unknown,\n options: {\n orphanScope: BootOrphanScope;\n packages?: string[];\n turtleFor: (packageName: string) => Promise<string | undefined>;\n appShapes?: {graph: string; package?: string};\n },\n ): Promise<ShapeGraphSyncResult>;\n /** Every package with registered shapes (core counted for its built-ins). */\n packagesWithShapes(): string[];\n /** A package's shape graph IRI (`ShapesGraph.iriFor`). */\n shapesGraphFor(packageName: string): string;\n}\n\n/** Import core's shape graph sync. */\nexport async function loadShapeGraphCore(): Promise<ShapeGraphCore> {\n const sync = await import('@_linked/core/shapes/syncShapeGraphs');\n const turtle = await import('@_linked/core/shapes/shapesToTurtle');\n const graphs = await import('@_linked/core/shapes/NamedGraph');\n return {\n syncShapeGraphs: (ds, options) =>\n sync.syncShapeGraphs(ds as Parameters<typeof sync.syncShapeGraphs>[0], options),\n packagesWithShapes: () => turtle.packagesWithShapes(),\n shapesGraphFor: (packageName) => graphs.ShapesGraph.iriFor(packageName),\n };\n}\n\n/** Where `linked build` writes a package's shapes, relative to the package root. */\nexport const SHAPES_TURTLE_FILE = path.join('lib', 'esm', 'data', 'shapes.ttl');\n\n/** A package's built `shapes.ttl`, or undefined when the package root has none. */\nexport function readShapesTurtle(packageDir: string | undefined): string | undefined {\n if (!packageDir) return undefined;\n try {\n return fs.readFileSync(path.join(packageDir, SHAPES_TURTLE_FILE), 'utf-8');\n } catch {\n return undefined;\n }\n}\n\n/** Where one package's Turtle comes from. */\nexport type ShapeTurtleSource =\n | {source: 'file'; text: string}\n | {source: 'generated'}\n | {source: 'none'};\n\n/**\n * Where a registered package's Turtle comes from: in dev an own package is generated; otherwise\n * its built file when it has one; else an own package is generated and an installed one has\n * none (no shape graph, no generation).\n */\nexport function shapeTurtleSource(\n packageName: string,\n {\n own,\n dev,\n packageDir,\n read = readShapesTurtle,\n }: {\n /** Whether the package is the app or one of its workspace packages. */\n own: boolean;\n /** Whether the server runs with Vite (dev). */\n dev: boolean;\n /** The package root, when the server knows it. */\n packageDir: string | undefined;\n read?: (packageDir: string | undefined) => string | undefined;\n },\n): ShapeTurtleSource {\n if (own && dev) return {source: 'generated'};\n const text = read(packageDir);\n if (text !== undefined) return {source: 'file', text};\n return own ? {source: 'generated'} : {source: 'none'};\n}\n\n/**\n * The app's own shape graph, `{DATA_ROOT}/graph/shapes`, or undefined when `DATA_ROOT` is not\n * an absolute IRI.\n */\nexport function appShapesGraph(\n env: Record<string, string | undefined> = process.env,\n): string | undefined {\n const root = env.DATA_ROOT?.trim().replace(/\\/+$/, '');\n if (!root || !/^[a-z][a-z0-9+.-]*:\\/\\/[^/]/i.test(root)) return undefined;\n return `${root}/graph/shapes`;\n}\n\n/**\n * In dev, the generator for the app's own packages: core's `packageShapesTurtle` imported through\n * Vite's `ssrLoadModule`, the core the SSR graph registered the source shapes on. Undefined\n * without Vite.\n */\nexport function viteShapesTurtleGenerator(\n vite: {ssrLoadModule?: (url: string) => Promise<unknown>} | undefined | null,\n): ((packageName: string) => Promise<string | undefined>) | undefined {\n const ssrLoadModule = vite?.ssrLoadModule;\n if (typeof ssrLoadModule !== 'function') return undefined;\n return async (packageName) => {\n const mod = (await ssrLoadModule.call(vite, '@_linked/core/shapes/shapesToTurtle')) as {\n packageShapesTurtle?: (name: string) => string | undefined;\n };\n if (typeof mod?.packageShapesTurtle !== 'function') {\n throw new Error('@_linked/core/shapes/shapesToTurtle has no packageShapesTurtle');\n }\n return mod.packageShapesTurtle(packageName);\n };\n}\n\n/** What one shape sync did, as the boot logs it and `POST /admin/sync-shapes` reports it. */\nexport interface ShapeGraphsReport {\n /** Packages whose shape graph was written, and those left as they were. */\n written: string[];\n skipped: string[];\n /** Packages no longer registered whose shape graph was dropped (`orphanScope: 'all'`). */\n pruned: string[];\n /** The shape graphs written (package graphs, then the app graph), by IRI. */\n graphs: string[];\n /** The app's own shape graph and what the sync did with it; absent without `DATA_ROOT`. */\n appGraph?: {graph: string; status: 'written' | 'skipped' | 'dropped'};\n /**\n * Registered packages with no Turtle, so with no shape graph: an installed package without\n * `shapes.ttl`, or an own package whose shapes could not be generated.\n */\n withoutTurtle: string[];\n /** Shapes an earlier sync had written into the default graph, removed now. */\n cleaned: number;\n /**\n * Set when the shapes went into the default graph (the global `shapeGraph` is `'default'`, or\n * it has no graph queries): the default-graph sync's writes.\n */\n defaultGraph?: number;\n /** The global `shapeGraph` target (`'package'`, `'default'` or a shared graph IRI), when core reports it. */\n target?: string;\n /** Set when the store has no graph queries, so the shapes went into the default graph instead of `target`. */\n fallback?: true;\n}\n\nconst list = (names: string[]) => (names.length ? names.join(', ') : 'none');\n\n/**\n * The head of a sync's log line: the packages written, then how many were unchanged (`skipped`\n * in the syncs' results): `written a, b; 17 unchanged`, `written a, b`, or `19 unchanged`.\n */\nexport function writtenAndUnchanged(written: string[], unchanged: string[]): string {\n const parts = written.length ? [`written ${written.join(', ')}`] : [];\n if (unchanged.length || !written.length) parts.push(`${unchanged.length} unchanged`);\n return parts.join('; ');\n}\n\n/** An app graph's status in a log line: core's `skipped` is an unchanged graph. */\nconst appGraphStatus = (status: 'written' | 'skipped' | 'dropped') => (status === 'skipped' ? 'unchanged' : status);\n\n/**\n * Sync the registered shapes into `ds`, one shape graph per package (see the module doc), and log\n * one line naming what was written and skipped, the graphs written, the packages without\n * `shapes.ttl` and the default-graph shapes removed. Throws when the sync fails.\n */\nexport async function syncShapeGraphsInto(\n ds: unknown,\n {\n core,\n orphanScope,\n isOwn,\n dev,\n packageDirOf,\n generate,\n appGraph,\n appPackage,\n read = readShapesTurtle,\n log = console.log,\n warn = console.warn,\n }: {\n core: ShapeGraphCore;\n orphanScope: BootOrphanScope;\n /** Whether a package is the app or one of its workspace packages. */\n isOwn: (packageName: string) => boolean;\n /** Whether the server runs with Vite (dev). */\n dev: boolean;\n /** A package's root directory, when the server knows it. */\n packageDirOf: (packageName: string) => string | undefined;\n /**\n * Generates an own package's Turtle (in dev through Vite's loader); undefined, or a result of\n * undefined, leaves the generation to the core that runs the sync.\n */\n generate?: (packageName: string) => Promise<string | undefined>;\n /** The app's own shape graph (`{DATA_ROOT}/graph/shapes`). */\n appGraph?: string;\n /** The app's package name, recorded on its shape graph's record. */\n appPackage?: string;\n read?: (packageDir: string | undefined) => string | undefined;\n log?: (message: string) => void;\n warn?: (message: string, err?: unknown) => void;\n },\n): Promise<ShapeGraphsReport> {\n const sources = new Map<string, ShapeTurtleSource>();\n for (const name of core.packagesWithShapes()) {\n sources.set(name, shapeTurtleSource(name, {own: isOwn(name), dev, packageDir: packageDirOf(name), read}));\n }\n const noFile = [...sources].filter(([, s]) => s.source === 'none').map(([name]) => name);\n\n const turtleFor = async (name: string): Promise<string | undefined> => {\n const source = sources.get(name);\n if (source?.source === 'file') return source.text;\n if (source?.source !== 'generated' || !generate) return undefined;\n try {\n return await generate(name);\n } catch (err) {\n warn(`[LinkedServer] generating the shapes of ${name} through the dev loader failed; trying the server's core instead:`, err);\n return undefined;\n }\n };\n\n const result = await core.syncShapeGraphs(ds, {\n orphanScope,\n // A package without Turtle is left out: core would generate it otherwise.\n ...(noFile.length ? {packages: [...sources.keys()].filter((n) => !noFile.includes(n))} : {}),\n turtleFor,\n ...(appGraph ? {appShapes: {graph: appGraph, ...(appPackage ? {package: appPackage} : {})}} : {}),\n });\n\n // A package core neither wrote nor skipped got no Turtle at all: generation produced nothing\n // (in dev, the server's core may not hold the source shapes the dev loader registered). The\n // default-graph sync (a store without graph queries) reports no packages, so it is not checked.\n const covered = new Set([...result.written, ...result.skipped]);\n const notGenerated =\n result.defaultGraph !== undefined\n ? []\n : [...sources.keys()].filter((n) => !noFile.includes(n) && !covered.has(n));\n if (notGenerated.length) {\n warn(\n `[LinkedServer] no shapes could be generated for ${list(notGenerated.sort())}, so ` +\n `${notGenerated.length === 1 ? 'it has' : 'they have'} no shape graph this sync`,\n );\n }\n const withoutTurtle = [...noFile, ...notGenerated].sort();\n\n // A shared graph (the global `shapeGraph` is an IRI) holds every package and the app's shapes.\n const shared = result.target && result.target !== 'package' && result.target !== 'default' ? result.target : undefined;\n const report: ShapeGraphsReport = {\n written: result.written,\n skipped: result.skipped,\n pruned: result.pruned,\n // A shared graph is written whole or not at all: listed when the run wrote anything into it.\n graphs: shared\n ? result.written.length || result.appGraph === 'written' || result.pruned.length\n ? [shared]\n : []\n : result.written.map((name) => core.shapesGraphFor(name)),\n withoutTurtle,\n cleaned: result.cleaned,\n };\n if (result.target) report.target = result.target;\n if (result.fallback) report.fallback = true;\n if (appGraph && result.appGraph) {\n report.appGraph = {graph: shared ?? appGraph, status: result.appGraph};\n if (result.appGraph === 'written' && !shared) report.graphs.push(appGraph);\n }\n if (result.defaultGraph !== undefined) report.defaultGraph = result.defaultGraph;\n\n if (report.defaultGraph !== undefined) {\n const why =\n result.target === 'default' && !result.fallback\n ? \"shapeGraph is 'default'\"\n : 'the store has no graph queries';\n log(`[LinkedServer] shape sync (default graph: ${why}): ${report.defaultGraph} shape(s) written`);\n } else if (shared) {\n log(\n `[LinkedServer] shape graph sync (one shared graph <${shared}>): ` +\n writtenAndUnchanged(report.written, report.skipped) +\n (report.appGraph ? `; app shapes ${appGraphStatus(report.appGraph.status)}` : '') +\n (report.pruned.length ? `; dropped from the graph ${list(report.pruned)}` : '') +\n (withoutTurtle.length ? `; no shapes.ttl (not in the graph): ${list(withoutTurtle)}` : ''),\n );\n // Unlike package graphs, which stay when a package goes missing, the shared graph is rebuilt\n // from the packages the run covers.\n if (withoutTurtle.length) {\n warn(\n `[LinkedServer] the shared shape graph <${shared}> holds no shapes of ${list(withoutTurtle)}: ` +\n 'a package without Turtle in a run is left out of the rebuilt graph',\n );\n }\n } else {\n log(\n `[LinkedServer] shape graph sync: ${writtenAndUnchanged(report.written, report.skipped)}` +\n (report.appGraph ? `; app graph ${appGraphStatus(report.appGraph.status)}` : '; no app graph (DATA_ROOT not set)') +\n (report.graphs.length ? `; graphs written ${list(report.graphs)}` : '') +\n (orphanScope === 'all' ? `; pruned ${list(report.pruned)}` : '') +\n (report.cleaned ? `; ${report.cleaned} default-graph shape(s) removed` : '') +\n (withoutTurtle.length ? `; no shapes.ttl (no shape graph): ${list(withoutTurtle)}` : ''),\n );\n }\n if (orphanScope === 'all' && withoutTurtle.length && !shared) {\n warn(\n '[LinkedServer] orphaned shape graphs were not pruned: core prunes only when the sync covers ' +\n `every registered package, and ${list(withoutTurtle)} has no Turtle`,\n );\n }\n return report;\n}\n\n/**\n * @deprecated The default-graph shape sync's callback type; the server now syncs shape graphs with\n * {@link syncShapeGraphsInto}. Kept so code built against an earlier server still compiles.\n */\nexport type SyncShapesFn = (\n ds: unknown,\n options: {orphanScope: BootOrphanScope},\n) => Promise<Array<() => Promise<void>>>;\n\n/**\n * @deprecated Use {@link syncShapeGraphsInto}, which writes each package's shapes into its own\n * shape graph. This runs the old default-graph sync: it plans every registered shape with\n * `syncShapes` (the orphan policy from {@link bootOrphanScope}) and runs the writes in batches\n * of 8. Kept so code built against an earlier server keeps working.\n */\nexport async function materializeShapes(\n ds: unknown,\n syncShapes: SyncShapesFn,\n {\n env = process.env,\n log = console.log,\n }: {\n env?: Record<string, string | undefined>;\n log?: (message: string) => void;\n } = {},\n): Promise<{count: number; orphanScope: BootOrphanScope}> {\n const orphanScope = bootOrphanScope(env);\n const thunks = await syncShapes(ds, {orphanScope});\n for (let i = 0; i < thunks.length; i += 8) {\n await Promise.all(thunks.slice(i, i + 8).map((run) => run()));\n }\n log(\n `[LinkedServer] synced shapes into the default graph with orphanScope '${orphanScope}': ` +\n `${thunks.length} operation(s)`,\n );\n return {count: thunks.length, orphanScope};\n}\n"]}
package/package.json CHANGED
@@ -10,9 +10,8 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/server.git"
12
12
  },
13
- "version": "3.12.1",
13
+ "version": "3.13.0",
14
14
  "packageManager": "npm@12.2.0",
15
- "linkedPackage": true,
16
15
  "type": "module",
17
16
  "main": "lib/esm/index.js",
18
17
  "types": "index.d.ts",
@@ -39,8 +38,8 @@
39
38
  "web3"
40
39
  ],
41
40
  "dependencies": {
42
- "@_linked/cli": "^1.54.0",
43
- "@_linked/core": "^2.42.0",
41
+ "@_linked/cli": "^1.58.0",
42
+ "@_linked/core": "^2.45.0",
44
43
  "@_linked/server-utils": "^1.16.1",
45
44
  "autoprefixer": "^10.4.18",
46
45
  "compression": "^1.7.4",
@@ -91,6 +90,7 @@
91
90
  }
92
91
  },
93
92
  "linked": {
93
+ "kind": "library",
94
94
  "serverOnly": [
95
95
  ".",
96
96
  "./backend",
package/readme.md CHANGED
@@ -323,7 +323,7 @@ export interface RemoteDatasetConfig {
323
323
  token?: string; // sent as Authorization: Bearer <token> when set and url is set
324
324
  name?: string; // id = ${DATA_ROOT}/backend-api-store/<name>; does not affect routing
325
325
  id?: string;
326
- forwardEndUser?: boolean | string; // url form: send the signed-in user's WebID (default true)
326
+ forwardUserToken?: boolean | string; // url form: send the signed-in user's access token (default true)
327
327
  }
328
328
  ```
329
329
 
@@ -353,22 +353,32 @@ semantics are under [Error semantics](#error-semantics). `token` is ignored.
353
353
  `deleteQuery`) with `Content-Type` and `Accept` set to `application/json` and, when `token` is
354
354
  non-empty, `Authorization: Bearer <token>`. It resolves the parsed body; `null` is a valid
355
355
  result. Nothing is retried, so a write is never applied twice.
356
- - **The end user.** When the query is made while a signed-in user's HTTP request is being
357
- answered (the call context is `http` and its request has
358
- `linkedAuth.userAccount.accountOf.id`), the post also carries `x-linked-end-user: <WebID>`
359
- (exported as `END_USER_HEADER`). A system context (`runAsSystem`), an anonymous request and
360
- code outside any call send no such header. The token still decides which data the store
361
- reaches; the data server decides what to do with the end user. `forwardEndUser: false` (or the
362
- string `"false"`) turns it off. No other headers are sent.
363
-
364
- > **Warning: on by default.** Every query of a signed-in user's request sends that user's WebID
365
- > to the data server at `url`. Point `url` only at a server you would give your users'
366
- > identities to (for an app CN hosts, CN's gateway). To send none, set it off in the dataset's
367
- > config:
356
+ - **The user's token.** When the query is made while a signed-in user's HTTP request is being
357
+ answered (the call context is `http`), the post asks the hook on
358
+ `globalThis[Symbol.for('@_linked/auth:userTokenHeaders')]` (exported as
359
+ `USER_TOKEN_HEADERS_HOOK`; `@_linked/auth`'s backend installs it) with the request and
360
+ `{method: 'POST', url}` (the exact URL posted to). When it answers, the post also carries
361
+ `x-linked-user-token: <the user's Solid-OIDC access token>` (exported as `USER_TOKEN_HEADER`) and
362
+ `DPoP: <a proof for this one request>`, beside the bearer. auth answers for a session signed in
363
+ through an issuer that gave a DPoP-bound JWT access token (webid.email does), while the token has
364
+ more than 30 seconds left; other sessions, a system context (`runAsSystem`), an anonymous request
365
+ and code outside any call send neither. A hook that throws or answers something else sends
366
+ neither (one warning per process) and the query still goes. The token still decides which data
367
+ the store reaches; the data server verifies the user's token and proof (`@_linked/auth`'s
368
+ `verifySolidAccessToken`) and acts as that user. `forwardUserToken: false` (or the string
369
+ `"false"`) turns it off. No other headers are sent.
370
+
371
+ > **Warning: on by default.** Every query of a signed-in user's request sends that user's access
372
+ > token to the data server at `url`. Each proof is for one method and URL and the server cannot
373
+ > make new ones (the key stays with the app), but it learns who the user is. Point `url` only at
374
+ > a server you trust with your users' identities (for an app CN hosts, CN's gateway). To send
375
+ > none, set it off in the dataset's config:
368
376
  >
369
377
  > ```json
370
- > "config": { "url": "${DATA_SERVER_URL:-}", "token": "${DATA_SERVER_TOKEN:-}", "forwardEndUser": false }
378
+ > "config": { "url": "${DATA_SERVER_URL:-}", "token": "${DATA_SERVER_TOKEN:-}", "forwardUserToken": false }
371
379
  > ```
380
+ - A redirect is never followed (`redirect: 'manual'`): a 3xx answer is a `RemoteDatasetError`
381
+ with its status, so the bearer and the user's token go to `url` only.
372
382
  - Trailing slashes are trimmed from `url`. A `url` that is present but empty (an unset
373
383
  `${DATA_SERVER_URL:-}`) never falls back to the no-url form: `init()` and every method throw
374
384
  `RemoteDatasetUnreachableError` with `url: ''`.