@_linked/server 3.12.1 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/lib/esm/backend.d.ts +23 -5
  3. package/lib/esm/backend.js +116 -25
  4. package/lib/esm/backend.js.map +1 -1
  5. package/lib/esm/shapes/LincdAPI.d.ts +1 -1
  6. package/lib/esm/shapes/LincdAPI.js +43 -7
  7. package/lib/esm/shapes/LincdAPI.js.map +1 -1
  8. package/lib/esm/shapes/LinkedServer.d.ts +59 -39
  9. package/lib/esm/shapes/LinkedServer.js +158 -93
  10. package/lib/esm/shapes/LinkedServer.js.map +1 -1
  11. package/lib/esm/shapes/quadstores/RemoteDataset.d.ts +52 -20
  12. package/lib/esm/shapes/quadstores/RemoteDataset.js +134 -32
  13. package/lib/esm/shapes/quadstores/RemoteDataset.js.map +1 -1
  14. package/lib/esm/utils/adminChannel.d.ts +23 -51
  15. package/lib/esm/utils/adminChannel.js +50 -38
  16. package/lib/esm/utils/adminChannel.js.map +1 -1
  17. package/lib/esm/utils/effectiveRoutes.d.ts +6 -0
  18. package/lib/esm/utils/effectiveRoutes.js +2 -1
  19. package/lib/esm/utils/effectiveRoutes.js.map +1 -1
  20. package/lib/esm/utils/forwardedTransactions.d.ts +46 -0
  21. package/lib/esm/utils/forwardedTransactions.js +155 -0
  22. package/lib/esm/utils/forwardedTransactions.js.map +1 -0
  23. package/lib/esm/utils/graphTargets.d.ts +42 -17
  24. package/lib/esm/utils/graphTargets.js +62 -69
  25. package/lib/esm/utils/graphTargets.js.map +1 -1
  26. package/lib/esm/utils/linkedDependencies.d.ts +53 -7
  27. package/lib/esm/utils/linkedDependencies.js +130 -12
  28. package/lib/esm/utils/linkedDependencies.js.map +1 -1
  29. package/lib/esm/utils/ontologySync.d.ts +55 -31
  30. package/lib/esm/utils/ontologySync.js +94 -42
  31. package/lib/esm/utils/ontologySync.js.map +1 -1
  32. package/lib/esm/utils/queryPlane.d.ts +13 -0
  33. package/lib/esm/utils/queryPlane.js +49 -2
  34. package/lib/esm/utils/queryPlane.js.map +1 -1
  35. package/lib/esm/utils/reloadSource.js +1 -1
  36. package/lib/esm/utils/reloadSource.js.map +1 -1
  37. package/lib/esm/utils/serverTransactions.d.ts +22 -0
  38. package/lib/esm/utils/serverTransactions.js +81 -0
  39. package/lib/esm/utils/serverTransactions.js.map +1 -0
  40. package/lib/esm/utils/syncShapes.d.ts +123 -6
  41. package/lib/esm/utils/syncShapes.js +166 -35
  42. package/lib/esm/utils/syncShapes.js.map +1 -1
  43. package/package.json +4 -4
  44. package/readme.md +158 -38
@@ -11,32 +11,57 @@
11
11
  * `.from(graphs.allOntologies)` reaches the backend as the portable name, and the backend's
12
12
  * store expands it under this setting.
13
13
  *
14
- * The syncs write into the default dataset only. {@link syncPinWarning} names the shapes whose
15
- * routing sends their reads elsewhere.
14
+ * The syncs follow the storage routing (core's `syncTargets`); {@link syncTargetsLine} names the
15
+ * datasets they write into when that is more than the default dataset.
16
16
  */
17
17
  import { type GraphTargets } from '@_linked/core/utils/graphTargets';
18
+ import type { GraphSyncTargets } from '@_linked/core/shapes/syncTargets';
19
+ import type { IDataset } from '@_linked/core/interfaces/IDataset';
18
20
  /**
19
21
  * Read `LINKED_ONTOLOGY_GRAPH` / `LINKED_SHAPE_GRAPH` from `env` (checked: an invalid value throws,
20
22
  * naming the variable) and set them as the process's graph targets (core's `setGraphTargets`;
21
23
  * unset ones are `'package'`). Returns the settings the env gave.
22
24
  */
23
25
  export declare function applyGraphTargetsFromEnv(env: Record<string, string | undefined>): Partial<GraphTargets>;
24
- /** The env variables as set (unset and empty ones left out), for `GET /admin/routes`. */
25
- export declare function graphTargetEnv(env: Record<string, string | undefined>): Record<string, string>;
26
- /** The classes of the shape shapes: what the shape sync writes. */
27
- export declare const SHAPE_SHAPE_CLASSES: readonly string[];
26
+ /** The variable that says who writes the app's shape and ontology graphs. */
27
+ export declare const GRAPH_WRITER_ENV = "LINKED_GRAPH_WRITER";
28
+ /**
29
+ * Who writes the app's shape and ontology graphs: `'app'` (the default) the app itself, at boot;
30
+ * `'host'` a host that runs it (Create Now), which reads the
31
+ * content through `POST /admin/graph-content` and writes it itself, so the app writes none.
32
+ */
33
+ export type GraphWriter = 'app' | 'host';
28
34
  /**
29
- * The classes of the ontology term shapes (the class and property shapes core, `@_linked/rdfs`
30
- * and `@_linked/owl` register): what the ontology sync writes and the hierarchy reads.
35
+ * `LINKED_GRAPH_WRITER` from `env`: unset or empty is `'app'`; any value other than `app` or
36
+ * `host` throws, naming the variable (the boot stops on it).
31
37
  */
32
- export declare const ONTOLOGY_TERM_CLASSES: readonly string[];
38
+ export declare function graphWriterFromEnv(env: Record<string, string | undefined>): GraphWriter;
39
+ /**
40
+ * Whether this process writes its shape and ontology graphs: the one check every write path of the
41
+ * syncs goes through (the boot's ontology and shape syncs). False
42
+ * under `LINKED_GRAPH_WRITER=host`.
43
+ */
44
+ export declare function appWritesGraphs(env?: Record<string, string | undefined>): boolean;
45
+ /** The env variables as set (unset and empty ones left out), for `GET /admin/routes`. */
46
+ export declare function graphTargetEnv(env: Record<string, string | undefined>): Record<string, string>;
47
+ /** Which boot syncs run: only the graphs a running sync writes are named. */
48
+ export interface BootSyncs {
49
+ /** The shape sync runs (`shapeSyncOn`): it writes the shape graphs and the app's shape graph. */
50
+ shapeSync: boolean;
51
+ /** The ontology sync runs (the same switch as the shape sync): it writes the ontology graphs. */
52
+ ontologySync: boolean;
53
+ }
33
54
  /**
34
- * One warning when routing pins a shape the boot syncs write (the shape shapes `NodeShape` /
35
- * `PropertyShape`, or an ontology term shape such as `rdfs:Class` / `owl:ObjectProperty`) to a
36
- * dataset other than the default one: the syncs write into the default dataset only, so reads of
37
- * those shapes on the pinned dataset miss what they wrote. Names each such shape (sub-shapes of a
38
- * watched shape included) and its dataset. Undefined when there is nothing to warn about (or no
39
- * default dataset). Reads the registered shapes outside any shape catalog (run it at boot, never
40
- * inside `withShapeCatalog`).
55
+ * One log line naming the datasets the boot syncs that run write into, per graph kind, when the
56
+ * routing sends any graph beyond the default dataset (core's `graphSyncTargets`: a package's
57
+ * shape graph goes wherever its shapes or the shape shapes route, its ontology graph also
58
+ * wherever shapes using its classes or the term shapes route, the app's graph wherever shapes
59
+ * declaring `contains`, or their sub-shapes, route). Undefined when every graph goes to the default dataset only (the
60
+ * syncs' own lines say enough), when no sync runs or without a default dataset. `core` is
61
+ * `@_linked/core/shapes/syncTargets` (server only, imported when the boot runs). Reads the
62
+ * registered shapes outside any shape catalog (run it at boot, never inside `withShapeCatalog`).
41
63
  */
42
- export declare function syncPinWarning(): string | undefined;
64
+ export declare function syncTargetsLine(syncs: BootSyncs, core: {
65
+ graphSyncTargets: () => GraphSyncTargets;
66
+ datasetLabel: (dataset: IDataset) => string;
67
+ }): string | undefined;
@@ -11,12 +11,11 @@
11
11
  * `.from(graphs.allOntologies)` reaches the backend as the portable name, and the backend's
12
12
  * store expands it under this setting.
13
13
  *
14
- * The syncs write into the default dataset only. {@link syncPinWarning} names the shapes whose
15
- * routing sends their reads elsewhere.
14
+ * The syncs follow the storage routing (core's `syncTargets`); {@link syncTargetsLine} names the
15
+ * datasets they write into when that is more than the default dataset.
16
16
  */
17
17
  import { GRAPH_TARGET_ENV, graphTargetOptionsFromEnv, setGraphTargets, } from '@_linked/core/utils/graphTargets';
18
18
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
19
- import { getAllShapeClasses } from '@_linked/core/utils/ShapeClass';
20
19
  /**
21
20
  * Read `LINKED_ONTOLOGY_GRAPH` / `LINKED_SHAPE_GRAPH` from `env` (checked: an invalid value throws,
22
21
  * naming the variable) and set them as the process's graph targets (core's `setGraphTargets`;
@@ -27,6 +26,29 @@ export function applyGraphTargetsFromEnv(env) {
27
26
  setGraphTargets(fromEnv);
28
27
  return fromEnv;
29
28
  }
29
+ /** The variable that says who writes the app's shape and ontology graphs. */
30
+ export const GRAPH_WRITER_ENV = 'LINKED_GRAPH_WRITER';
31
+ /**
32
+ * `LINKED_GRAPH_WRITER` from `env`: unset or empty is `'app'`; any value other than `app` or
33
+ * `host` throws, naming the variable (the boot stops on it).
34
+ */
35
+ export function graphWriterFromEnv(env) {
36
+ var _a;
37
+ const value = (_a = env[GRAPH_WRITER_ENV]) === null || _a === void 0 ? void 0 : _a.trim();
38
+ if (!value || value === 'app')
39
+ return 'app';
40
+ if (value === 'host')
41
+ return 'host';
42
+ throw new Error(`${GRAPH_WRITER_ENV} must be 'app' or 'host' (who writes the app's shape and ontology graphs), not ${JSON.stringify(value)}`);
43
+ }
44
+ /**
45
+ * Whether this process writes its shape and ontology graphs: the one check every write path of the
46
+ * syncs goes through (the boot's ontology and shape syncs). False
47
+ * under `LINKED_GRAPH_WRITER=host`.
48
+ */
49
+ export function appWritesGraphs(env = process.env) {
50
+ return graphWriterFromEnv(env) === 'app';
51
+ }
30
52
  /** The env variables as set (unset and empty ones left out), for `GET /admin/routes`. */
31
53
  export function graphTargetEnv(env) {
32
54
  const out = {};
@@ -37,77 +59,48 @@ export function graphTargetEnv(env) {
37
59
  }
38
60
  return out;
39
61
  }
40
- const SH = 'http://www.w3.org/ns/shacl#';
41
- const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
42
- const RDFS = 'http://www.w3.org/2000/01/rdf-schema#';
43
- const OWL = 'http://www.w3.org/2002/07/owl#';
44
- /** The classes of the shape shapes: what the shape sync writes. */
45
- export const SHAPE_SHAPE_CLASSES = [`${SH}NodeShape`, `${SH}PropertyShape`];
46
- /**
47
- * The classes of the ontology term shapes (the class and property shapes core, `@_linked/rdfs`
48
- * and `@_linked/owl` register): what the ontology sync writes and the hierarchy reads.
49
- */
50
- export const ONTOLOGY_TERM_CLASSES = [
51
- `${RDFS}Class`,
52
- `${OWL}Class`,
53
- `${RDF}Property`,
54
- `${OWL}ObjectProperty`,
55
- `${OWL}DatatypeProperty`,
56
- `${OWL}AnnotationProperty`,
57
- ];
58
- /** A dataset as `GET /admin/routes` tells it apart: its store class, and its `id` when it has one. */
59
- function datasetName(dataset) {
60
- var _a;
61
- const id = dataset.id;
62
- const store = ((_a = dataset === null || dataset === void 0 ? void 0 : dataset.constructor) === null || _a === void 0 ? void 0 : _a.name) || 'unknown';
63
- return typeof id === 'string' && id ? `${store} "${id}"` : store;
64
- }
65
62
  /**
66
- * The watched class `shapeClass` or one of its super-shapes targets (a sub-shape of `owl:Class`,
67
- * say `owl:Restriction`, goes where its parent is pinned), else undefined.
63
+ * One log line naming the datasets the boot syncs that run write into, per graph kind, when the
64
+ * routing sends any graph beyond the default dataset (core's `graphSyncTargets`: a package's
65
+ * shape graph goes wherever its shapes or the shape shapes route, its ontology graph also
66
+ * wherever shapes using its classes or the term shapes route, the app's graph wherever shapes
67
+ * declaring `contains`, or their sub-shapes, route). Undefined when every graph goes to the default dataset only (the
68
+ * syncs' own lines say enough), when no sync runs or without a default dataset. `core` is
69
+ * `@_linked/core/shapes/syncTargets` (server only, imported when the boot runs). Reads the
70
+ * registered shapes outside any shape catalog (run it at boot, never inside `withShapeCatalog`).
68
71
  */
69
- function watchedClassOf(shapeClass, watched) {
70
- var _a;
71
- for (let current = shapeClass; typeof current === 'function' && current !== Function.prototype; current = Object.getPrototypeOf(current)) {
72
- const id = (_a = current.targetClass) === null || _a === void 0 ? void 0 : _a.id;
73
- if (typeof id === 'string' && watched.has(id))
74
- return id;
75
- }
76
- return undefined;
77
- }
78
- /**
79
- * One warning when routing pins a shape the boot syncs write (the shape shapes `NodeShape` /
80
- * `PropertyShape`, or an ontology term shape such as `rdfs:Class` / `owl:ObjectProperty`) to a
81
- * dataset other than the default one: the syncs write into the default dataset only, so reads of
82
- * those shapes on the pinned dataset miss what they wrote. Names each such shape (sub-shapes of a
83
- * watched shape included) and its dataset. Undefined when there is nothing to warn about (or no
84
- * default dataset). Reads the registered shapes outside any shape catalog (run it at boot, never
85
- * inside `withShapeCatalog`).
86
- */
87
- export function syncPinWarning() {
88
- var _a;
72
+ export function syncTargetsLine(syncs, core) {
89
73
  const defaultDataset = LinkedStorage.getDefaultDataset();
90
- if (!defaultDataset)
74
+ if (!defaultDataset || (!syncs.shapeSync && !syncs.ontologySync))
91
75
  return undefined;
92
- const watched = new Set([...SHAPE_SHAPE_CLASSES, ...ONTOLOGY_TERM_CLASSES]);
93
- const pinned = new Map();
94
- for (const [iri, shapeClass] of getAllShapeClasses()) {
95
- const targetClass = watchedClassOf(shapeClass, watched);
96
- if (!targetClass)
97
- continue;
98
- const dataset = LinkedStorage.getDatasetForShapeClass(shapeClass);
99
- if (!dataset || dataset === defaultDataset)
100
- continue;
101
- const list = (_a = pinned.get(dataset)) !== null && _a !== void 0 ? _a : [];
102
- list.push(`${shapeClass.name || iri} (${targetClass})`);
103
- pinned.set(dataset, list);
76
+ const { datasetLabel } = core;
77
+ const targets = core.graphSyncTargets();
78
+ const beyondDefault = (lists) => [...lists].some((list) => list.some((ds) => ds !== defaultDataset));
79
+ const kinds = [];
80
+ const perDataset = (byPackage) => {
81
+ const all = [...byPackage.keys()];
82
+ return targets.datasets
83
+ .map((ds) => {
84
+ const names = all.filter((name) => { var _a; return (_a = byPackage.get(name)) === null || _a === void 0 ? void 0 : _a.includes(ds); }).sort();
85
+ if (!names.length)
86
+ return undefined;
87
+ return `${datasetLabel(ds)} (${names.length === all.length ? 'every package' : names.join(', ')})`;
88
+ })
89
+ .filter((part) => !!part)
90
+ .join(', ');
91
+ };
92
+ let routed = false;
93
+ if (syncs.shapeSync) {
94
+ routed || (routed = beyondDefault(targets.shapes.values()) || beyondDefault([targets.appShapes]));
95
+ kinds.push(`shape graphs → ${perDataset(targets.shapes)}`);
96
+ kinds.push(`app shape graph → ${targets.appShapes.map(datasetLabel).join(', ')}`);
97
+ }
98
+ if (syncs.ontologySync) {
99
+ routed || (routed = beyondDefault(targets.ontologies.values()));
100
+ kinds.push(`ontology graphs → ${perDataset(targets.ontologies)}`);
104
101
  }
105
- if (pinned.size === 0)
102
+ if (!routed)
106
103
  return undefined;
107
- const parts = [...pinned].map(([dataset, shapes]) => `${shapes.sort().join(', ')} → ${datasetName(dataset)}`);
108
- return (`[LinkedServer] Routing pins shapes the boot syncs write to a dataset other than the default ` +
109
- `one: ${parts.join('; ')}. The ontology and shape syncs, when they run, write into the default dataset ` +
110
- `(${datasetName(defaultDataset)}) only, so reads of these shapes on the pinned dataset do not ` +
111
- `see what the syncs wrote.`);
104
+ return `[LinkedServer] The boot syncs follow the routing: ${kinds.join('; ')}.`;
112
105
  }
113
106
  //# sourceMappingURL=graphTargets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graphTargets.js","sourceRoot":"","sources":["../../../src/utils/graphTargets.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where the boot syncs keep the registered packages' ontology data and shapes: core's graph\n * targets (`ontologyGraph`, `shapeGraph`; `@_linked/core/utils/graphTargets`), one global setting\n * of the process. `LINKED_ONTOLOGY_GRAPH` and `LINKED_SHAPE_GRAPH` each take `'package'` (the\n * default, one graph per package), `'default'` (the default graph) or the absolute IRI of one\n * shared graph.\n *\n * The server reads them once at boot, checks them (an invalid value stops the boot, naming the\n * variable) and calls core's `setGraphTargets` before the stores are initialised and before the\n * syncs. No store takes a setting of its own, and the frontend needs none: its\n * `.from(graphs.allOntologies)` reaches the backend as the portable name, and the backend's\n * store expands it under this setting.\n *\n * The syncs write into the default dataset only. {@link syncPinWarning} names the shapes whose\n * routing sends their reads elsewhere.\n */\nimport {\n GRAPH_TARGET_ENV,\n graphTargetOptionsFromEnv,\n setGraphTargets,\n type GraphTargets,\n} from '@_linked/core/utils/graphTargets';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { getAllShapeClasses } from '@_linked/core/utils/ShapeClass';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\n\n/**\n * Read `LINKED_ONTOLOGY_GRAPH` / `LINKED_SHAPE_GRAPH` from `env` (checked: an invalid value throws,\n * naming the variable) and set them as the process's graph targets (core's `setGraphTargets`;\n * unset ones are `'package'`). Returns the settings the env gave.\n */\nexport function applyGraphTargetsFromEnv(env: Record<string, string | undefined>): Partial<GraphTargets> {\n const fromEnv = graphTargetOptionsFromEnv(env);\n setGraphTargets(fromEnv);\n return fromEnv;\n}\n\n/** The env variables as set (unset and empty ones left out), for `GET /admin/routes`. */\nexport function graphTargetEnv(env: Record<string, string | undefined>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of [GRAPH_TARGET_ENV.ontologyGraph, GRAPH_TARGET_ENV.shapeGraph]) {\n const value = env[name];\n if (value) out[name] = value;\n }\n return out;\n}\n\nconst SH = 'http://www.w3.org/ns/shacl#';\nconst RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';\nconst RDFS = 'http://www.w3.org/2000/01/rdf-schema#';\nconst OWL = 'http://www.w3.org/2002/07/owl#';\n\n/** The classes of the shape shapes: what the shape sync writes. */\nexport const SHAPE_SHAPE_CLASSES: readonly string[] = [`${SH}NodeShape`, `${SH}PropertyShape`];\n\n/**\n * The classes of the ontology term shapes (the class and property shapes core, `@_linked/rdfs`\n * and `@_linked/owl` register): what the ontology sync writes and the hierarchy reads.\n */\nexport const ONTOLOGY_TERM_CLASSES: readonly string[] = [\n `${RDFS}Class`,\n `${OWL}Class`,\n `${RDF}Property`,\n `${OWL}ObjectProperty`,\n `${OWL}DatatypeProperty`,\n `${OWL}AnnotationProperty`,\n];\n\n/** A dataset as `GET /admin/routes` tells it apart: its store class, and its `id` when it has one. */\nfunction datasetName(dataset: IDataset): string {\n const id = (dataset as { id?: unknown }).id;\n const store = dataset?.constructor?.name || 'unknown';\n return typeof id === 'string' && id ? `${store} \"${id}\"` : store;\n}\n\n/**\n * The watched class `shapeClass` or one of its super-shapes targets (a sub-shape of `owl:Class`,\n * say `owl:Restriction`, goes where its parent is pinned), else undefined.\n */\nfunction watchedClassOf(shapeClass: Function, watched: ReadonlySet<string>): string | undefined {\n for (let current: unknown = shapeClass; typeof current === 'function' && current !== Function.prototype; current = Object.getPrototypeOf(current)) {\n const id = (current as { targetClass?: { id?: unknown } }).targetClass?.id;\n if (typeof id === 'string' && watched.has(id)) return id;\n }\n return undefined;\n}\n\n/**\n * One warning when routing pins a shape the boot syncs write (the shape shapes `NodeShape` /\n * `PropertyShape`, or an ontology term shape such as `rdfs:Class` / `owl:ObjectProperty`) to a\n * dataset other than the default one: the syncs write into the default dataset only, so reads of\n * those shapes on the pinned dataset miss what they wrote. Names each such shape (sub-shapes of a\n * watched shape included) and its dataset. Undefined when there is nothing to warn about (or no\n * default dataset). Reads the registered shapes outside any shape catalog (run it at boot, never\n * inside `withShapeCatalog`).\n */\nexport function syncPinWarning(): string | undefined {\n const defaultDataset = LinkedStorage.getDefaultDataset();\n if (!defaultDataset) return undefined;\n const watched = new Set([...SHAPE_SHAPE_CLASSES, ...ONTOLOGY_TERM_CLASSES]);\n const pinned = new Map<IDataset, string[]>();\n for (const [iri, shapeClass] of getAllShapeClasses()) {\n const targetClass = watchedClassOf(shapeClass, watched);\n if (!targetClass) continue;\n const dataset = LinkedStorage.getDatasetForShapeClass(shapeClass);\n if (!dataset || dataset === defaultDataset) continue;\n const list = pinned.get(dataset) ?? [];\n list.push(`${shapeClass.name || iri} (${targetClass})`);\n pinned.set(dataset, list);\n }\n if (pinned.size === 0) return undefined;\n const parts = [...pinned].map(([dataset, shapes]) => `${shapes.sort().join(', ')} → ${datasetName(dataset)}`);\n return (\n `[LinkedServer] Routing pins shapes the boot syncs write to a dataset other than the default ` +\n `one: ${parts.join('; ')}. The ontology and shape syncs, when they run, write into the default dataset ` +\n `(${datasetName(defaultDataset)}) only, so reads of these shapes on the pinned dataset do not ` +\n `see what the syncs wrote.`\n );\n}\n"]}
1
+ {"version":3,"file":"graphTargets.js","sourceRoot":"","sources":["../../../src/utils/graphTargets.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where the boot syncs keep the registered packages' ontology data and shapes: core's graph\n * targets (`ontologyGraph`, `shapeGraph`; `@_linked/core/utils/graphTargets`), one global setting\n * of the process. `LINKED_ONTOLOGY_GRAPH` and `LINKED_SHAPE_GRAPH` each take `'package'` (the\n * default, one graph per package), `'default'` (the default graph) or the absolute IRI of one\n * shared graph.\n *\n * The server reads them once at boot, checks them (an invalid value stops the boot, naming the\n * variable) and calls core's `setGraphTargets` before the stores are initialised and before the\n * syncs. No store takes a setting of its own, and the frontend needs none: its\n * `.from(graphs.allOntologies)` reaches the backend as the portable name, and the backend's\n * store expands it under this setting.\n *\n * The syncs follow the storage routing (core's `syncTargets`); {@link syncTargetsLine} names the\n * datasets they write into when that is more than the default dataset.\n */\nimport {\n GRAPH_TARGET_ENV,\n graphTargetOptionsFromEnv,\n setGraphTargets,\n type GraphTargets,\n} from '@_linked/core/utils/graphTargets';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport type { GraphSyncTargets } from '@_linked/core/shapes/syncTargets';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\n\n/**\n * Read `LINKED_ONTOLOGY_GRAPH` / `LINKED_SHAPE_GRAPH` from `env` (checked: an invalid value throws,\n * naming the variable) and set them as the process's graph targets (core's `setGraphTargets`;\n * unset ones are `'package'`). Returns the settings the env gave.\n */\nexport function applyGraphTargetsFromEnv(env: Record<string, string | undefined>): Partial<GraphTargets> {\n const fromEnv = graphTargetOptionsFromEnv(env);\n setGraphTargets(fromEnv);\n return fromEnv;\n}\n\n/** The variable that says who writes the app's shape and ontology graphs. */\nexport const GRAPH_WRITER_ENV = 'LINKED_GRAPH_WRITER';\n\n/**\n * Who writes the app's shape and ontology graphs: `'app'` (the default) the app itself, at boot;\n * `'host'` a host that runs it (Create Now), which reads the\n * content through `POST /admin/graph-content` and writes it itself, so the app writes none.\n */\nexport type GraphWriter = 'app' | 'host';\n\n/**\n * `LINKED_GRAPH_WRITER` from `env`: unset or empty is `'app'`; any value other than `app` or\n * `host` throws, naming the variable (the boot stops on it).\n */\nexport function graphWriterFromEnv(env: Record<string, string | undefined>): GraphWriter {\n const value = env[GRAPH_WRITER_ENV]?.trim();\n if (!value || value === 'app') return 'app';\n if (value === 'host') return 'host';\n throw new Error(\n `${GRAPH_WRITER_ENV} must be 'app' or 'host' (who writes the app's shape and ontology graphs), not ${JSON.stringify(value)}`,\n );\n}\n\n/**\n * Whether this process writes its shape and ontology graphs: the one check every write path of the\n * syncs goes through (the boot's ontology and shape syncs). False\n * under `LINKED_GRAPH_WRITER=host`.\n */\nexport function appWritesGraphs(env: Record<string, string | undefined> = process.env): boolean {\n return graphWriterFromEnv(env) === 'app';\n}\n\n/** The env variables as set (unset and empty ones left out), for `GET /admin/routes`. */\nexport function graphTargetEnv(env: Record<string, string | undefined>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of [GRAPH_TARGET_ENV.ontologyGraph, GRAPH_TARGET_ENV.shapeGraph]) {\n const value = env[name];\n if (value) out[name] = value;\n }\n return out;\n}\n\n/** Which boot syncs run: only the graphs a running sync writes are named. */\nexport interface BootSyncs {\n /** The shape sync runs (`shapeSyncOn`): it writes the shape graphs and the app's shape graph. */\n shapeSync: boolean;\n /** The ontology sync runs (the same switch as the shape sync): it writes the ontology graphs. */\n ontologySync: boolean;\n}\n\n/**\n * One log line naming the datasets the boot syncs that run write into, per graph kind, when the\n * routing sends any graph beyond the default dataset (core's `graphSyncTargets`: a package's\n * shape graph goes wherever its shapes or the shape shapes route, its ontology graph also\n * wherever shapes using its classes or the term shapes route, the app's graph wherever shapes\n * declaring `contains`, or their sub-shapes, route). Undefined when every graph goes to the default dataset only (the\n * syncs' own lines say enough), when no sync runs or without a default dataset. `core` is\n * `@_linked/core/shapes/syncTargets` (server only, imported when the boot runs). Reads the\n * registered shapes outside any shape catalog (run it at boot, never inside `withShapeCatalog`).\n */\nexport function syncTargetsLine(\n syncs: BootSyncs,\n core: {\n graphSyncTargets: () => GraphSyncTargets;\n datasetLabel: (dataset: IDataset) => string;\n },\n): string | undefined {\n const defaultDataset = LinkedStorage.getDefaultDataset();\n if (!defaultDataset || (!syncs.shapeSync && !syncs.ontologySync)) return undefined;\n const { datasetLabel } = core;\n const targets = core.graphSyncTargets();\n const beyondDefault = (lists: Iterable<IDataset[]>) =>\n [...lists].some((list) => list.some((ds) => ds !== defaultDataset));\n const kinds: string[] = [];\n const perDataset = (byPackage: Map<string, IDataset[]>): string => {\n const all = [...byPackage.keys()];\n return targets.datasets\n .map((ds) => {\n const names = all.filter((name) => byPackage.get(name)?.includes(ds)).sort();\n if (!names.length) return undefined;\n return `${datasetLabel(ds)} (${names.length === all.length ? 'every package' : names.join(', ')})`;\n })\n .filter((part): part is string => !!part)\n .join(', ');\n };\n let routed = false;\n if (syncs.shapeSync) {\n routed ||= beyondDefault(targets.shapes.values()) || beyondDefault([targets.appShapes]);\n kinds.push(`shape graphs → ${perDataset(targets.shapes)}`);\n kinds.push(`app shape graph → ${targets.appShapes.map(datasetLabel).join(', ')}`);\n }\n if (syncs.ontologySync) {\n routed ||= beyondDefault(targets.ontologies.values());\n kinds.push(`ontology graphs → ${perDataset(targets.ontologies)}`);\n }\n if (!routed) return undefined;\n return `[LinkedServer] The boot syncs follow the routing: ${kinds.join('; ')}.`;\n}\n"]}
@@ -20,12 +20,26 @@ export interface LinkedDependency {
20
20
  version?: string;
21
21
  }[];
22
22
  }
23
+ /** What a `package.json` is, by its `linked.kind`. */
24
+ export type LinkedKind = 'app' | 'ontology' | 'assets' | 'library';
25
+ /** Every value `linked.kind` may take. */
26
+ export declare const LINKED_KINDS: readonly LinkedKind[];
23
27
  /**
24
- * A package whose backend providers the server loads at boot: one whose
25
- * package.json declares `"linkedPackage": true`. That flag is the only test —
28
+ * THE way to tell what a `package.json` is: its `"linked": {"kind": …}`. `app` is an app (built
29
+ * by `linked build-app`); `ontology`, `assets` and `library` are linked packages (an ontology
30
+ * package may carry shapes for its own terms; `library` is framework code). Undefined for
31
+ * anything else: no `linked` object and a kind that is not one of these.
32
+ *
33
+ * Until every published package carries `linked.kind`, a `package.json` without one is still
34
+ * read by the retired flags: `linkedApp: true` is an `app`, `linkedPackage: true` a `library`.
35
+ */
36
+ export declare function linkedKind(json: unknown): LinkedKind | undefined;
37
+ /**
38
+ * A package whose backend providers the server loads at boot: a named package
39
+ * whose `linked.kind` is not `app` ({@link linkedKind}). That is the only test —
26
40
  * not the package's scope. `@_linked/localize` is in the `@_linked` scope and
27
- * is a CLI tool, not a linked package; a custom-scope package with the flag is
28
- * a linked package. The CLI's workspace discovery uses the same flag.
41
+ * is a CLI tool, not a linked package; a custom-scope package with a kind is
42
+ * a linked package. The CLI's workspace discovery uses the same test.
29
43
  *
30
44
  * The legacy `"lincd": true` flag is deliberately NOT enough. Those packages
31
45
  * import the old `lincd` core, and loading them at boot is what produced
@@ -50,7 +64,34 @@ export declare function readInstalledPackage(name: string, fromDir: string): {
50
64
  json: any;
51
65
  } | null;
52
66
  /**
53
- * Every linked package reachable from the app's `dependencies`, each one AFTER
67
+ * The npm names a `package.json` lists in `linked.unpublishedPackages` (K-unpublished): packages
68
+ * it uses that are not on npm, localized into the app's `packages-local/`. The server treats them
69
+ * as direct dependencies of whoever lists them. Read with the cli's one reader, so a malformed
70
+ * list throws rather than shrinking silently.
71
+ */
72
+ export declare function unpublishedNames(json: unknown): string[];
73
+ /**
74
+ * A package the app lists in `linked.unpublishedPackages`, found by its npm name: first the
75
+ * way Node finds it (`node_modules/<name>` from `fromDir` up, which is npm's workspace link into
76
+ * `packages-local/` once the app is installed), else among `<appDir>/packages-local/<dir>`
77
+ * by the `name` in each `package.json` (a checkout not yet installed). Null when there is no
78
+ * checkout of it.
79
+ */
80
+ export declare function findListedPackage(name: string, fromDir: string, appDir: string): {
81
+ root: string;
82
+ json: any;
83
+ } | null;
84
+ /**
85
+ * Every name in the app's `linked.unpublishedPackages`, and transitively in the lists of the
86
+ * listed packages it finds ({@link findListedPackage}): the packages the server treats as the
87
+ * app's own direct dependencies. A listed package without a checkout still counts; only its own
88
+ * list cannot be read.
89
+ */
90
+ export declare function listedPackageNames(appDir: string, appPackageJson: any): Set<string>;
91
+ /**
92
+ * Every linked package reachable from the app's `dependencies` and its
93
+ * `linked.unpublishedPackages` (transitively, through each package's own
94
+ * `dependencies` and list), each one AFTER
54
95
  * the linked packages it depends on. Providers register middleware in the
55
96
  * order they are indexed, so a package's middleware must not run before that
56
97
  * of a package it builds on (e.g. anything reading `request.linkedAuth` after
@@ -68,10 +109,15 @@ export declare function readInstalledPackage(name: string, fromDir: string): {
68
109
  * is the one the app itself resolves, so it is the same module instance the
69
110
  * app's own imports reach; when the app cannot resolve the package at all
70
111
  * (it is only installed nested) it is the first copy found.
112
+ *
113
+ * `isLinked` decides which installed packages are linked ones (default
114
+ * {@link isLinkedPackageJson}); a caller reading installs of another
115
+ * generation passes its own.
71
116
  */
72
- export declare function discoverLinkedDependencies(appDir: string, appPackageJson: any): LinkedDependency[];
117
+ export declare function discoverLinkedDependencies(appDir: string, appPackageJson: any, isLinked?: (json: any) => boolean): LinkedDependency[];
73
118
  /**
74
- * Package name → the names of its direct `dependencies` and `peerDependencies`, for the app
119
+ * Package name → the names of its direct `dependencies`, `peerDependencies` and
120
+ * `linked.unpublishedPackages`, for the app
75
121
  * (`appPackageJson`) and every package in `packageDirs` (name → package root, as discovery
76
122
  * found it). The shape check scopes its direction-I errors with it. A package whose
77
123
  * `package.json` cannot be read is left out.
@@ -1,11 +1,48 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import { unpublishedPackages } from '@_linked/cli/installed-packages';
4
+ /** Every value `linked.kind` may take. */
5
+ export const LINKED_KINDS = [
6
+ 'app',
7
+ 'ontology',
8
+ 'assets',
9
+ 'library',
10
+ ];
3
11
  /**
4
- * A package whose backend providers the server loads at boot: one whose
5
- * package.json declares `"linkedPackage": true`. That flag is the only test —
12
+ * THE way to tell what a `package.json` is: its `"linked": {"kind": …}`. `app` is an app (built
13
+ * by `linked build-app`); `ontology`, `assets` and `library` are linked packages (an ontology
14
+ * package may carry shapes for its own terms; `library` is framework code). Undefined for
15
+ * anything else: no `linked` object and a kind that is not one of these.
16
+ *
17
+ * Until every published package carries `linked.kind`, a `package.json` without one is still
18
+ * read by the retired flags: `linkedApp: true` is an `app`, `linkedPackage: true` a `library`.
19
+ */
20
+ export function linkedKind(json) {
21
+ var _a;
22
+ if (!json || typeof json !== 'object')
23
+ return undefined;
24
+ const linked = json.linked;
25
+ if (!linked || typeof linked !== 'object')
26
+ return retiredFlagKind(json);
27
+ const kind = linked.kind;
28
+ return (_a = LINKED_KINDS.find((k) => k === kind)) !== null && _a !== void 0 ? _a : retiredFlagKind(json);
29
+ }
30
+ /** The kind a `package.json` published before `linked.kind` declares by its retired flags. */
31
+ function retiredFlagKind(json) {
32
+ const flags = json;
33
+ if (flags.linkedApp === true)
34
+ return 'app';
35
+ if (flags.linkedPackage === true)
36
+ return 'library';
37
+ return undefined;
38
+ }
39
+ // end of the linkedKind twin.
40
+ /**
41
+ * A package whose backend providers the server loads at boot: a named package
42
+ * whose `linked.kind` is not `app` ({@link linkedKind}). That is the only test —
6
43
  * not the package's scope. `@_linked/localize` is in the `@_linked` scope and
7
- * is a CLI tool, not a linked package; a custom-scope package with the flag is
8
- * a linked package. The CLI's workspace discovery uses the same flag.
44
+ * is a CLI tool, not a linked package; a custom-scope package with a kind is
45
+ * a linked package. The CLI's workspace discovery uses the same test.
9
46
  *
10
47
  * The legacy `"lincd": true` flag is deliberately NOT enough. Those packages
11
48
  * import the old `lincd` core, and loading them at boot is what produced
@@ -15,7 +52,8 @@ import * as path from 'path';
15
52
  export function isLinkedPackageJson(json) {
16
53
  if (!json || typeof json.name !== 'string')
17
54
  return false;
18
- return json.linkedPackage === true;
55
+ const kind = linkedKind(json);
56
+ return kind !== undefined && kind !== 'app';
19
57
  }
20
58
  /**
21
59
  * Find an installed package the way Node does: `<dir>/node_modules/<name>`,
@@ -66,7 +104,73 @@ export function readInstalledPackage(name, fromDir) {
66
104
  }
67
105
  }
68
106
  /**
69
- * Every linked package reachable from the app's `dependencies`, each one AFTER
107
+ * The npm names a `package.json` lists in `linked.unpublishedPackages` (K-unpublished): packages
108
+ * it uses that are not on npm, localized into the app's `packages-local/`. The server treats them
109
+ * as direct dependencies of whoever lists them. Read with the cli's one reader, so a malformed
110
+ * list throws rather than shrinking silently.
111
+ */
112
+ export function unpublishedNames(json) {
113
+ return unpublishedPackages(json).map((p) => p.name);
114
+ }
115
+ /** Where a listed package's checkout lives in the app, when npm has not linked it yet. */
116
+ const LOCAL_DIR = 'packages-local';
117
+ /**
118
+ * A package the app lists in `linked.unpublishedPackages`, found by its npm name: first the
119
+ * way Node finds it (`node_modules/<name>` from `fromDir` up, which is npm's workspace link into
120
+ * `packages-local/` once the app is installed), else among `<appDir>/packages-local/<dir>`
121
+ * by the `name` in each `package.json` (a checkout not yet installed). Null when there is no
122
+ * checkout of it.
123
+ */
124
+ export function findListedPackage(name, fromDir, appDir) {
125
+ const installed = readInstalledPackage(name, fromDir);
126
+ if (installed)
127
+ return installed;
128
+ const localDir = path.join(appDir, LOCAL_DIR);
129
+ let entries = [];
130
+ try {
131
+ entries = fs.readdirSync(localDir).sort();
132
+ }
133
+ catch (_a) {
134
+ return null;
135
+ }
136
+ for (const entry of entries) {
137
+ const root = path.join(localDir, entry);
138
+ try {
139
+ const json = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8'));
140
+ if ((json === null || json === void 0 ? void 0 : json.name) === name)
141
+ return { root, json };
142
+ }
143
+ catch (_b) { }
144
+ }
145
+ return null;
146
+ }
147
+ /**
148
+ * Every name in the app's `linked.unpublishedPackages`, and transitively in the lists of the
149
+ * listed packages it finds ({@link findListedPackage}): the packages the server treats as the
150
+ * app's own direct dependencies. A listed package without a checkout still counts; only its own
151
+ * list cannot be read.
152
+ */
153
+ export function listedPackageNames(appDir, appPackageJson) {
154
+ const names = new Set();
155
+ const queue = unpublishedNames(appPackageJson).map((name) => ({ name, fromDir: appDir }));
156
+ while (queue.length) {
157
+ const { name, fromDir } = queue.shift();
158
+ if (names.has(name) || name === (appPackageJson === null || appPackageJson === void 0 ? void 0 : appPackageJson.name))
159
+ continue;
160
+ names.add(name);
161
+ const found = findListedPackage(name, fromDir, appDir);
162
+ if (!found)
163
+ continue;
164
+ const root = realpathOr(found.root);
165
+ for (const next of unpublishedNames(found.json))
166
+ queue.push({ name: next, fromDir: root });
167
+ }
168
+ return names;
169
+ }
170
+ /**
171
+ * Every linked package reachable from the app's `dependencies` and its
172
+ * `linked.unpublishedPackages` (transitively, through each package's own
173
+ * `dependencies` and list), each one AFTER
70
174
  * the linked packages it depends on. Providers register middleware in the
71
175
  * order they are indexed, so a package's middleware must not run before that
72
176
  * of a package it builds on (e.g. anything reading `request.linkedAuth` after
@@ -84,19 +188,25 @@ export function readInstalledPackage(name, fromDir) {
84
188
  * is the one the app itself resolves, so it is the same module instance the
85
189
  * app's own imports reach; when the app cannot resolve the package at all
86
190
  * (it is only installed nested) it is the first copy found.
191
+ *
192
+ * `isLinked` decides which installed packages are linked ones (default
193
+ * {@link isLinkedPackageJson}); a caller reading installs of another
194
+ * generation passes its own.
87
195
  */
88
- export function discoverLinkedDependencies(appDir, appPackageJson) {
196
+ export function discoverLinkedDependencies(appDir, appPackageJson, isLinked = isLinkedPackageJson) {
89
197
  var _a;
90
198
  const out = [];
91
199
  const byName = new Map();
92
200
  const visitedRoots = new Set();
93
201
  const appName = appPackageJson === null || appPackageJson === void 0 ? void 0 : appPackageJson.name;
94
- const visit = (name, fromDir) => {
202
+ const visit = (name, fromDir, listed = false) => {
95
203
  var _a;
96
204
  if (name === appName)
97
205
  return;
98
- const installed = readInstalledPackage(name, fromDir);
99
- if (!installed || !isLinkedPackageJson(installed.json))
206
+ const installed = listed
207
+ ? findListedPackage(name, fromDir, appDir)
208
+ : readInstalledPackage(name, fromDir);
209
+ if (!installed || !isLinked(installed.json))
100
210
  return;
101
211
  const realRoot = realpathOr(installed.root);
102
212
  if (visitedRoots.has(realRoot))
@@ -111,6 +221,9 @@ export function discoverLinkedDependencies(appDir, appPackageJson) {
111
221
  for (const dep of Object.keys((_a = installed.json.dependencies) !== null && _a !== void 0 ? _a : {})) {
112
222
  visit(dep, realRoot);
113
223
  }
224
+ for (const dep of unpublishedNames(installed.json)) {
225
+ visit(dep, realRoot, true);
226
+ }
114
227
  // Pushed after its dependencies, once per name: the first copy found
115
228
  // fixes the package's place in the order.
116
229
  if (!out.includes(entry))
@@ -119,11 +232,14 @@ export function discoverLinkedDependencies(appDir, appPackageJson) {
119
232
  for (const name of Object.keys((_a = appPackageJson === null || appPackageJson === void 0 ? void 0 : appPackageJson.dependencies) !== null && _a !== void 0 ? _a : {})) {
120
233
  visit(name, appDir);
121
234
  }
235
+ for (const name of unpublishedNames(appPackageJson)) {
236
+ visit(name, appDir, true);
237
+ }
122
238
  for (const entry of out) {
123
239
  if (entry.copies.length < 2)
124
240
  continue;
125
241
  const fromApp = readInstalledPackage(entry.packageName, appDir);
126
- if (fromApp && isLinkedPackageJson(fromApp.json)) {
242
+ if (fromApp && isLinked(fromApp.json)) {
127
243
  const appCopy = realpathOr(fromApp.root);
128
244
  if (entry.copies.some((c) => c.path === appCopy))
129
245
  entry.path = appCopy;
@@ -132,7 +248,8 @@ export function discoverLinkedDependencies(appDir, appPackageJson) {
132
248
  return out;
133
249
  }
134
250
  /**
135
- * Package name → the names of its direct `dependencies` and `peerDependencies`, for the app
251
+ * Package name → the names of its direct `dependencies`, `peerDependencies` and
252
+ * `linked.unpublishedPackages`, for the app
136
253
  * (`appPackageJson`) and every package in `packageDirs` (name → package root, as discovery
137
254
  * found it). The shape check scopes its direction-I errors with it. A package whose
138
255
  * `package.json` cannot be read is left out.
@@ -147,6 +264,7 @@ export function readDependencyLists(packageDirs, appPackageJson) {
147
264
  ...new Set([
148
265
  ...Object.keys((_a = json === null || json === void 0 ? void 0 : json.dependencies) !== null && _a !== void 0 ? _a : {}),
149
266
  ...Object.keys((_b = json === null || json === void 0 ? void 0 : json.peerDependencies) !== null && _b !== void 0 ? _b : {}),
267
+ ...unpublishedNames(json),
150
268
  ]),
151
269
  ].sort();
152
270
  };