@_linked/core 2.31.0 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +61 -61
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/ontologies/linked-core.d.ts +1 -0
- package/lib/esm/ontologies/linked-core.js +8 -2
- package/lib/esm/ontologies/linked-core.js.map +1 -1
- package/lib/esm/ontologies/shacl.d.ts +3 -0
- package/lib/esm/ontologies/shacl.js +6 -0
- package/lib/esm/ontologies/shacl.js.map +1 -1
- package/lib/esm/shapes/List.class.d.ts +3 -3
- package/lib/esm/shapes/List.class.js +3 -3
- package/lib/esm/shapes/List.class.js.map +1 -1
- package/lib/esm/shapes/PathList.d.ts +5 -8
- package/lib/esm/shapes/PathList.js +6 -9
- package/lib/esm/shapes/PathList.js.map +1 -1
- package/lib/esm/shapes/PathNode.d.ts +1 -3
- package/lib/esm/shapes/PathNode.js +6 -8
- package/lib/esm/shapes/PathNode.js.map +1 -1
- package/lib/esm/shapes/SHACL.d.ts +15 -13
- package/lib/esm/shapes/SHACL.js +8 -0
- package/lib/esm/shapes/SHACL.js.map +1 -1
- package/lib/esm/shapes/nodeShapeData.d.ts +4 -1
- package/lib/esm/shapes/nodeShapeData.js.map +1 -1
- package/lib/esm/shapes/singleOwnerConstraint.d.ts +5 -7
- package/lib/esm/shapes/singleOwnerConstraint.js +65 -21
- package/lib/esm/shapes/singleOwnerConstraint.js.map +1 -1
- package/lib/esm/shapes/syncShapes.d.ts +54 -3
- package/lib/esm/shapes/syncShapes.js +141 -13
- package/lib/esm/shapes/syncShapes.js.map +1 -1
- package/lib/esm/sparql/irToAlgebra.d.ts +15 -38
- package/lib/esm/sparql/irToAlgebra.js +406 -391
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/lib/esm/sparql/ownedLinkCheck.d.ts +6 -10
- package/lib/esm/sparql/ownedLinkCheck.js +10 -11
- package/lib/esm/sparql/ownedLinkCheck.js.map +1 -1
- package/lib/esm/utils/Package.d.ts +3 -2
- package/lib/esm/utils/Package.js +21 -8
- package/lib/esm/utils/Package.js.map +1 -1
- package/lib/esm/utils/sha256.d.ts +8 -0
- package/lib/esm/utils/sha256.js +80 -0
- package/lib/esm/utils/sha256.js.map +1 -0
- package/package.json +1 -1
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
import { getPropertyShapes } from './nodeShapeData.js';
|
|
7
7
|
import { getSimplePathId } from '../paths/normalizePropertyPath.js';
|
|
8
8
|
import { serializePathToNodeData } from './serializePathToNodeData.js';
|
|
9
|
+
import { getSubShapes } from '../utils/ShapeClass.js';
|
|
10
|
+
import { NodeShape } from './SHACL.js';
|
|
11
|
+
import { PathList } from './PathList.js';
|
|
12
|
+
import { rdf } from '../ontologies/rdf.js';
|
|
9
13
|
/**
|
|
10
14
|
* The single-owner SHACL constraints generated from `contains` properties.
|
|
11
15
|
*
|
|
12
|
-
* A `contains` property says the subject owns the value node
|
|
13
|
-
*
|
|
14
|
-
*
|
|
16
|
+
* A `contains` property says the subject owns the value node, and only that property does: a
|
|
17
|
+
* plain property on the same predicate is a reference. For every such property, shape sync
|
|
18
|
+
* writes one generated NodeShape stating the matching store-side rule: every object of the
|
|
19
|
+
* property's predicate has at most one subject pointing at it through that predicate *that is
|
|
20
|
+
* an instance of the declaring class or of a registered sub-shape's class*.
|
|
15
21
|
*
|
|
16
22
|
* ```turtle
|
|
17
23
|
* <…/Thing/image.singleOwner> a sh:NodeShape ;
|
|
@@ -19,19 +25,26 @@ import { serializePathToNodeData } from './serializePathToNodeData.js';
|
|
|
19
25
|
* sh:targetObjectsOf schema:image ;
|
|
20
26
|
* sh:property <…/Thing/image.singleOwner/owners> .
|
|
21
27
|
* <…/Thing/image.singleOwner/owners> a sh:PropertyShape ;
|
|
22
|
-
* sh:
|
|
23
|
-
* sh:
|
|
28
|
+
* sh:path [ sh:inversePath schema:image ] ;
|
|
29
|
+
* sh:qualifiedValueShape [ sh:or ( [ sh:class schema:Thing ] [ sh:class ex:Project ] ) ] ;
|
|
30
|
+
* sh:qualifiedMaxCount 1 .
|
|
24
31
|
* ```
|
|
25
32
|
*
|
|
33
|
+
* The owner classes are enumerated rather than left to `sh:class` alone, because `sh:class`
|
|
34
|
+
* matches a subclass instance only through `rdfs:subClassOf` triples, which a dataset without
|
|
35
|
+
* inference does not have. With one class the qualified shape is `[ sh:class C ]`. Sub-shapes
|
|
36
|
+
* registered only in another process are missing until a process that knows them syncs.
|
|
37
|
+
*
|
|
26
38
|
* One constraint per accessor, written only by the class that declares it: subclasses inherit
|
|
27
|
-
* the property
|
|
28
|
-
*
|
|
29
|
-
* (`NodeShape.generatedShapes`, `contains`): shape sync
|
|
30
|
-
* create and deletes them with the shape
|
|
39
|
+
* the property and are counted as owners, but add no constraint. A declaring shape without a
|
|
40
|
+
* `targetClass` gets none (its owners cannot be named). The declaring shape owns its
|
|
41
|
+
* constraints through `core:generatedShape` (`NodeShape.generatedShapes`, `contains`): shape sync
|
|
42
|
+
* writes them nested in the shape's create and deletes them with the shape; their qualified
|
|
43
|
+
* shape and `sh:or` list are blank nodes, deleted with the property shape that holds them.
|
|
31
44
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
45
|
+
* Ownership through two different `contains` predicates is not counted (a documented
|
|
46
|
+
* limitation). The shapes are built only at sync time and are never registered in the
|
|
47
|
+
* in-memory shape registry.
|
|
35
48
|
*/
|
|
36
49
|
/** Suffix that turns a property shape's IRI into its generated single-owner shape's IRI. */
|
|
37
50
|
const SINGLE_OWNER_SUFFIX = '.singleOwner';
|
|
@@ -43,26 +56,56 @@ export function singleOwnerShapeIri(propertyShapeIri) {
|
|
|
43
56
|
return `${propertyShapeIri}${SINGLE_OWNER_SUFFIX}`;
|
|
44
57
|
}
|
|
45
58
|
/**
|
|
46
|
-
* The shape's OWN `contains` properties that get a single-owner constraint: those
|
|
47
|
-
*
|
|
59
|
+
* The shape's OWN `contains` properties that get a single-owner constraint: those with a simple
|
|
60
|
+
* path (every decorated `contains` property has a value shape), in declaration order.
|
|
48
61
|
*
|
|
49
62
|
* Inherited properties are not included; their declaring shape writes their constraint.
|
|
50
|
-
* Complex paths are skipped, as the
|
|
51
|
-
* without a value shape is a polymorphic slot (like `sh:path`) whose value may be a shared IRI,
|
|
52
|
-
* so the replace, the subject delete and the class check do not treat it as owning that value,
|
|
53
|
-
* and neither does the single-owner rule.
|
|
63
|
+
* Complex paths are skipped, as the owned walk skips them.
|
|
54
64
|
*/
|
|
55
65
|
export function singleOwnerProperties(nodeShape) {
|
|
56
66
|
return getPropertyShapes(nodeShape, false).filter((ps) => ps.contains && !!ps.valueShape && !!getSimplePathId(ps.path));
|
|
57
67
|
}
|
|
68
|
+
/** The classes whose instances own through the shape's properties: its own and every sub-shape's. */
|
|
69
|
+
function ownerClasses(nodeShape) {
|
|
70
|
+
var _a;
|
|
71
|
+
const classes = new Set();
|
|
72
|
+
for (const shape of [nodeShape, ...getSubShapes(nodeShape)]) {
|
|
73
|
+
if ((_a = shape.targetClass) === null || _a === void 0 ? void 0 : _a.id)
|
|
74
|
+
classes.add(shape.targetClass.id);
|
|
75
|
+
}
|
|
76
|
+
return [...classes].sort();
|
|
77
|
+
}
|
|
78
|
+
/** `[ sh:class C ]`, or `[ sh:or ( [ sh:class A ] … ) ]` for several classes. Blank once written. */
|
|
79
|
+
function ownerShapeData(classes) {
|
|
80
|
+
const classShape = (id) => ({ shape: NodeShape, class: { id } });
|
|
81
|
+
if (classes.length === 1)
|
|
82
|
+
return classShape(classes[0]);
|
|
83
|
+
const list = classes.reduceRight((rest, id) => ({ shape: PathList, first: classShape(id), rest }), { id: rdf.nil.id });
|
|
84
|
+
return { shape: NodeShape, orShapes: list };
|
|
85
|
+
}
|
|
86
|
+
const warnedWithoutTargetClass = new Set();
|
|
58
87
|
/**
|
|
59
88
|
* Create-pipeline data for the generated single-owner NodeShapes of `nodeShape`, one per
|
|
60
89
|
* {@link singleOwnerProperties} entry. Each is stored at
|
|
61
90
|
* {@link singleOwnerShapeIri}`(${shapeIri}/${label})`: the property shape's IRI exactly as shape
|
|
62
|
-
* sync writes it (`buildPropertyShapeData`).
|
|
91
|
+
* sync writes it (`buildPropertyShapeData`). Empty, with one warning per shape, when the shape
|
|
92
|
+
* has `contains` properties but no `targetClass`.
|
|
63
93
|
*/
|
|
64
94
|
export function buildSingleOwnerConstraints(nodeShape, shapeIri) {
|
|
65
|
-
|
|
95
|
+
var _a;
|
|
96
|
+
const properties = singleOwnerProperties(nodeShape);
|
|
97
|
+
if (properties.length === 0)
|
|
98
|
+
return [];
|
|
99
|
+
if (!((_a = nodeShape.targetClass) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
100
|
+
if (!warnedWithoutTargetClass.has(nodeShape.id)) {
|
|
101
|
+
warnedWithoutTargetClass.add(nodeShape.id);
|
|
102
|
+
console.warn(`[linked] Shape '${nodeShape.id}' has contains properties but no targetClass, so shape ` +
|
|
103
|
+
'sync writes no single-owner constraint for them: its owners cannot be named.');
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const owners = ownerShapeData(ownerClasses(nodeShape));
|
|
108
|
+
return properties.flatMap((ps) => {
|
|
66
109
|
// Always set: `singleOwnerProperties` keeps simple paths only. The guard narrows the type.
|
|
67
110
|
const predicate = getSimplePathId(ps.path);
|
|
68
111
|
if (!predicate)
|
|
@@ -78,8 +121,9 @@ export function buildSingleOwnerConstraints(nodeShape, shapeIri) {
|
|
|
78
121
|
{
|
|
79
122
|
__id: psIri,
|
|
80
123
|
label: 'owners',
|
|
81
|
-
maxCount: 1,
|
|
82
124
|
path: serializePathToNodeData({ inv: { id: predicate } }),
|
|
125
|
+
qualifiedValueShape: owners,
|
|
126
|
+
qualifiedMaxCount: 1,
|
|
83
127
|
},
|
|
84
128
|
],
|
|
85
129
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"singleOwnerConstraint.js","sourceRoot":"","sources":["../../../src/shapes/singleOwnerConstraint.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAC,iBAAiB,EAA6C,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAC,eAAe,EAAC,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"singleOwnerConstraint.js","sourceRoot":"","sources":["../../../src/shapes/singleOwnerConstraint.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\nimport {getPropertyShapes, type NodeShapeData, type PropertyShapeData} from './nodeShapeData.js';\nimport {getSimplePathId} from '../paths/normalizePropertyPath.js';\nimport {serializePathToNodeData} from './serializePathToNodeData.js';\nimport {getSubShapes} from '../utils/ShapeClass.js';\nimport {NodeShape} from './SHACL.js';\nimport {PathList} from './PathList.js';\nimport {rdf} from '../ontologies/rdf.js';\n\n/**\n * The single-owner SHACL constraints generated from `contains` properties.\n *\n * A `contains` property says the subject owns the value node, and only that property does: a\n * plain property on the same predicate is a reference. For every such property, shape sync\n * writes one generated NodeShape stating the matching store-side rule: every object of the\n * property's predicate has at most one subject pointing at it through that predicate *that is\n * an instance of the declaring class or of a registered sub-shape's class*.\n *\n * ```turtle\n * <…/Thing/image.singleOwner> a sh:NodeShape ;\n * core:generated true ;\n * sh:targetObjectsOf schema:image ;\n * sh:property <…/Thing/image.singleOwner/owners> .\n * <…/Thing/image.singleOwner/owners> a sh:PropertyShape ;\n * sh:path [ sh:inversePath schema:image ] ;\n * sh:qualifiedValueShape [ sh:or ( [ sh:class schema:Thing ] [ sh:class ex:Project ] ) ] ;\n * sh:qualifiedMaxCount 1 .\n * ```\n *\n * The owner classes are enumerated rather than left to `sh:class` alone, because `sh:class`\n * matches a subclass instance only through `rdfs:subClassOf` triples, which a dataset without\n * inference does not have. With one class the qualified shape is `[ sh:class C ]`. Sub-shapes\n * registered only in another process are missing until a process that knows them syncs.\n *\n * One constraint per accessor, written only by the class that declares it: subclasses inherit\n * the property and are counted as owners, but add no constraint. A declaring shape without a\n * `targetClass` gets none (its owners cannot be named). The declaring shape owns its\n * constraints through `core:generatedShape` (`NodeShape.generatedShapes`, `contains`): shape sync\n * writes them nested in the shape's create and deletes them with the shape; their qualified\n * shape and `sh:or` list are blank nodes, deleted with the property shape that holds them.\n *\n * Ownership through two different `contains` predicates is not counted (a documented\n * limitation). The shapes are built only at sync time and are never registered in the\n * in-memory shape registry.\n */\n\n/** Suffix that turns a property shape's IRI into its generated single-owner shape's IRI. */\nconst SINGLE_OWNER_SUFFIX = '.singleOwner';\n\n/**\n * The deterministic IRI of the generated single-owner NodeShape of one property shape:\n * `${propertyShapeIri}.singleOwner`, e.g. `…/shape/my-pkg/Thing/image.singleOwner`.\n */\nexport function singleOwnerShapeIri(propertyShapeIri: string): string {\n return `${propertyShapeIri}${SINGLE_OWNER_SUFFIX}`;\n}\n\n/**\n * The shape's OWN `contains` properties that get a single-owner constraint: those with a simple\n * path (every decorated `contains` property has a value shape), in declaration order.\n *\n * Inherited properties are not included; their declaring shape writes their constraint.\n * Complex paths are skipped, as the owned walk skips them.\n */\nexport function singleOwnerProperties(nodeShape: NodeShapeData): PropertyShapeData[] {\n return getPropertyShapes(nodeShape, false).filter(\n (ps) => ps.contains && !!ps.valueShape && !!getSimplePathId(ps.path),\n );\n}\n\n/** The classes whose instances own through the shape's properties: its own and every sub-shape's. */\nfunction ownerClasses(nodeShape: NodeShapeData): string[] {\n const classes = new Set<string>();\n for (const shape of [nodeShape, ...getSubShapes(nodeShape)]) {\n if (shape.targetClass?.id) classes.add(shape.targetClass.id);\n }\n return [...classes].sort();\n}\n\n/** `[ sh:class C ]`, or `[ sh:or ( [ sh:class A ] … ) ]` for several classes. Blank once written. */\nfunction ownerShapeData(classes: string[]): Record<string, unknown> {\n const classShape = (id: string) => ({shape: NodeShape, class: {id}});\n if (classes.length === 1) return classShape(classes[0]);\n const list = classes.reduceRight<unknown>(\n (rest, id) => ({shape: PathList, first: classShape(id), rest}),\n {id: rdf.nil.id},\n );\n return {shape: NodeShape, orShapes: list};\n}\n\nconst warnedWithoutTargetClass = new Set<string>();\n\n/**\n * Create-pipeline data for the generated single-owner NodeShapes of `nodeShape`, one per\n * {@link singleOwnerProperties} entry. Each is stored at\n * {@link singleOwnerShapeIri}`(${shapeIri}/${label})`: the property shape's IRI exactly as shape\n * sync writes it (`buildPropertyShapeData`). Empty, with one warning per shape, when the shape\n * has `contains` properties but no `targetClass`.\n */\nexport function buildSingleOwnerConstraints(\n nodeShape: NodeShapeData,\n shapeIri: string,\n): Array<{iri: string; data: Record<string, unknown>}> {\n const properties = singleOwnerProperties(nodeShape);\n if (properties.length === 0) return [];\n if (!nodeShape.targetClass?.id) {\n if (!warnedWithoutTargetClass.has(nodeShape.id)) {\n warnedWithoutTargetClass.add(nodeShape.id);\n console.warn(\n `[linked] Shape '${nodeShape.id}' has contains properties but no targetClass, so shape ` +\n 'sync writes no single-owner constraint for them: its owners cannot be named.',\n );\n }\n return [];\n }\n const owners = ownerShapeData(ownerClasses(nodeShape));\n return properties.flatMap((ps) => {\n // Always set: `singleOwnerProperties` keeps simple paths only. The guard narrows the type.\n const predicate = getSimplePathId(ps.path);\n if (!predicate) return [];\n const iri = singleOwnerShapeIri(`${shapeIri}/${ps.label}`);\n const psIri = `${iri}/owners`;\n return [{\n iri,\n data: {\n generated: true,\n targetObjectsOf: [{id: predicate}],\n properties: [\n {\n __id: psIri,\n label: 'owners',\n path: serializePathToNodeData({inv: {id: predicate}}),\n qualifiedValueShape: owners,\n qualifiedMaxCount: 1,\n },\n ],\n },\n }];\n });\n}\n"]}
|
|
@@ -7,6 +7,33 @@ import type { IDataset } from '../interfaces/IDataset.js';
|
|
|
7
7
|
* @internal Exported for tests only — not part of the public API.
|
|
8
8
|
*/
|
|
9
9
|
export declare function buildPropertyShapeData(ps: PropertyShapeData, shapeIri: string): Record<string, unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Version of the {@link shapeSyncHash} input. Bump it whenever core changes the SHACL it writes
|
|
12
|
+
* for a shape (a serialization change that the create data itself does not show), so every stored
|
|
13
|
+
* shape is rewritten once by the next sync. Without a bump, stores keep the old triples: their
|
|
14
|
+
* stored hashes still match, so the sync skips them.
|
|
15
|
+
*
|
|
16
|
+
* Enforced by `shape-sync-output-equivalence.test.ts`: its golden SHACL output is recorded in
|
|
17
|
+
* `SHAPE_HASH_LEDGER` with the version it was generated for, and regenerating the golden without
|
|
18
|
+
* bumping this constant fails that test. To change the output: bump this by one, update the
|
|
19
|
+
* golden, and append `{version, goldenDigest}` to the ledger (never edit an existing entry).
|
|
20
|
+
*
|
|
21
|
+
* @internal Exported for tests only — not part of the public API.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SHAPE_HASH_VERSION = 1;
|
|
24
|
+
/**
|
|
25
|
+
* The content hash shape sync stores on a NodeShape as `core:shapeHash`: SHA-256 (hex) of the
|
|
26
|
+
* canonical form of the shape IRI and the exact create data the sync writes for it — the
|
|
27
|
+
* NodeShape, its flattened own + inherited property shapes, their `sh:in` lists and paths, and
|
|
28
|
+
* the generated single-owner shapes it owns (whose owner classes come from the registered
|
|
29
|
+
* sub-shapes, so registering a new sub-shape changes the hash of the declaring shape).
|
|
30
|
+
*
|
|
31
|
+
* Deterministic across processes, runtimes and Node versions: no blank-node labels, no object key
|
|
32
|
+
* order, no array order (see {@link canonicalize}).
|
|
33
|
+
*
|
|
34
|
+
* @internal Exported for tests only — not part of the public API.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shapeSyncHash(data: Record<string, unknown>, shapeIri: string): string;
|
|
10
37
|
/** Options controlling how {@link syncShapes} treats store shapes that are not in code. */
|
|
11
38
|
export interface SyncShapesOptions {
|
|
12
39
|
/**
|
|
@@ -27,6 +54,18 @@ export interface SyncShapesOptions {
|
|
|
27
54
|
* `'all'` and `'ownedNamespaces'` are destructive and must be requested explicitly.
|
|
28
55
|
*/
|
|
29
56
|
orphanScope?: 'all' | 'ownedNamespaces' | 'none';
|
|
57
|
+
/**
|
|
58
|
+
* Rewrite every code shape, even one whose stored `core:shapeHash` matches. Default `false`:
|
|
59
|
+
* a shape is rewritten only when its hash differs or is missing. Use it to repair a shape
|
|
60
|
+
* whose stored subtree was edited or partly deleted outside shape sync (the hash describes
|
|
61
|
+
* what sync last wrote, not what is in the store now).
|
|
62
|
+
*/
|
|
63
|
+
force?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Options for {@link syncShape}. */
|
|
66
|
+
export interface SyncShapeOptions {
|
|
67
|
+
/** Rewrite the shape even when its stored `core:shapeHash` matches. See {@link SyncShapesOptions.force}. */
|
|
68
|
+
force?: boolean;
|
|
30
69
|
}
|
|
31
70
|
/**
|
|
32
71
|
* Plan an idempotent sync of all code-registered (non-framework) NodeShapes into the store as
|
|
@@ -39,6 +78,15 @@ export interface SyncShapesOptions {
|
|
|
39
78
|
* Each in-code shape's thunk runs `delete → create` in order (the delete cascade-cleans the old
|
|
40
79
|
* property shapes / list / path subtrees via the containment cascade, the create rebuilds them).
|
|
41
80
|
*
|
|
81
|
+
* **Unchanged shapes are skipped.** The create stores a content hash of everything it writes for
|
|
82
|
+
* the shape on the NodeShape (`core:shapeHash`, see {@link shapeSyncHash}). The plan reads the
|
|
83
|
+
* stored hashes of all code shapes in one select and builds a thunk only for a shape whose hash
|
|
84
|
+
* is missing (never synced, or synced before hashes existed) or differs. Syncing an unchanged
|
|
85
|
+
* app therefore returns no write thunks, and the length of the plan is the number of shapes
|
|
86
|
+
* actually written plus the pruned orphans. The hash records what sync last wrote, not what the
|
|
87
|
+
* store holds now: a subtree edited or partly deleted by another writer while the NodeShape and
|
|
88
|
+
* its hash survive is not detected. `options.force` rewrites every code shape.
|
|
89
|
+
*
|
|
42
90
|
* **Shapes in the store but not registered in this process are left alone by default**
|
|
43
91
|
* (`orphanScope: 'none'`). The registry only holds what this process happened to import, and
|
|
44
92
|
* that is routinely a subset of what the dataset holds: shapes are deep-importable one file at a
|
|
@@ -55,8 +103,8 @@ export interface SyncShapesOptions {
|
|
|
55
103
|
* with `'none'` no read is made.
|
|
56
104
|
*
|
|
57
105
|
* `ds` (optional) targets an explicit dataset instead of the global router. It is a **plan-time**
|
|
58
|
-
* parameter: it feeds
|
|
59
|
-
* store they'll be
|
|
106
|
+
* parameter: it feeds the stored-hash read and the orphan-detection read (so both are computed
|
|
107
|
+
* against the same store they'll be written to) and every delete/create thunk. Omitted → the global router;
|
|
60
108
|
* the returned thunks stay nullary either way.
|
|
61
109
|
*/
|
|
62
110
|
export declare function syncShapes(ds?: IDataset, options?: SyncShapesOptions): Promise<Array<() => Promise<void>>>;
|
|
@@ -71,9 +119,12 @@ export declare function syncShapes(ds?: IDataset, options?: SyncShapesOptions):
|
|
|
71
119
|
* its registered class via `getShapeClass`; the shape must be code-registered.
|
|
72
120
|
* @param ds optional explicit dataset to materialize into (a store or a router) — both the delete
|
|
73
121
|
* and the create run against it instead of the global router. Omitted → global behavior.
|
|
122
|
+
* @param options `force: true` rewrites the shape even when its stored hash matches.
|
|
74
123
|
* @returns a single unexecuted thunk (consistent with {@link syncShapes}), so callers can batch
|
|
75
124
|
* several together — e.g. a shape plus its referenced object-property shapes:
|
|
76
125
|
* `await Promise.all([syncShape(Person), syncShape(Address)].map((run) => run()))`.
|
|
126
|
+
* Unless `force` is set, the thunk first reads the shape's stored `core:shapeHash` and writes
|
|
127
|
+
* nothing when it equals the hash of what it would write (see {@link syncShapes}).
|
|
77
128
|
* @throws if `target` is a framework/meta shape, is not registered, or has no `.shape`.
|
|
78
129
|
*/
|
|
79
|
-
export declare function syncShape(target: typeof Shape | string, ds?: IDataset): () => Promise<void>;
|
|
130
|
+
export declare function syncShape(target: typeof Shape | string, ds?: IDataset, options?: SyncShapeOptions): () => Promise<void>;
|
|
@@ -19,6 +19,8 @@ import { DeleteBuilder } from '../queries/DeleteBuilder.js';
|
|
|
19
19
|
import { rdfList } from './List.js';
|
|
20
20
|
import { serializePathToNodeData } from './serializePathToNodeData.js';
|
|
21
21
|
import { buildSingleOwnerConstraints } from './singleOwnerConstraint.js';
|
|
22
|
+
import { QueryBuilder } from '../queries/QueryBuilder.js';
|
|
23
|
+
import { sha256Hex } from '../utils/sha256.js';
|
|
22
24
|
/**
|
|
23
25
|
* The npm scope of framework shapes (NodeShape/PropertyShape/List/PathNode/Shape) that
|
|
24
26
|
* describe the meta-model itself and must never be synced as user data.
|
|
@@ -129,16 +131,112 @@ function buildNodeShapeData(nodeShape, shapeIri) {
|
|
|
129
131
|
}
|
|
130
132
|
return d;
|
|
131
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Version of the {@link shapeSyncHash} input. Bump it whenever core changes the SHACL it writes
|
|
136
|
+
* for a shape (a serialization change that the create data itself does not show), so every stored
|
|
137
|
+
* shape is rewritten once by the next sync. Without a bump, stores keep the old triples: their
|
|
138
|
+
* stored hashes still match, so the sync skips them.
|
|
139
|
+
*
|
|
140
|
+
* Enforced by `shape-sync-output-equivalence.test.ts`: its golden SHACL output is recorded in
|
|
141
|
+
* `SHAPE_HASH_LEDGER` with the version it was generated for, and regenerating the golden without
|
|
142
|
+
* bumping this constant fails that test. To change the output: bump this by one, update the
|
|
143
|
+
* golden, and append `{version, goldenDigest}` to the ledger (never edit an existing entry).
|
|
144
|
+
*
|
|
145
|
+
* @internal Exported for tests only — not part of the public API.
|
|
146
|
+
*/
|
|
147
|
+
export const SHAPE_HASH_VERSION = 1;
|
|
148
|
+
/**
|
|
149
|
+
* A canonical string for create-pipeline data: equal for equal data, independent of object key
|
|
150
|
+
* order and of the order of array values.
|
|
151
|
+
*
|
|
152
|
+
* Arrays are sorted because in create data an array is always a multi-valued property (a set of
|
|
153
|
+
* RDF values: `properties`, `generatedShapes`, `targetObjectsOf`, …). Ordered `rdf:List`s
|
|
154
|
+
* (`sh:in`, sequence and alternative paths, `sh:or`) are nested `first`/`rest` objects, so their
|
|
155
|
+
* order is kept by the structure. A shape class value (`shape: List`) is written as its NodeShape
|
|
156
|
+
* IRI. Values without ids (blank nodes once written) are compared by structure, so no blank-node
|
|
157
|
+
* label is ever part of the input.
|
|
158
|
+
*/
|
|
159
|
+
function canonicalize(value) {
|
|
160
|
+
var _a, _b;
|
|
161
|
+
if (value === null || value === undefined)
|
|
162
|
+
return 'null';
|
|
163
|
+
switch (typeof value) {
|
|
164
|
+
case 'string':
|
|
165
|
+
return JSON.stringify(value);
|
|
166
|
+
case 'number':
|
|
167
|
+
return `n:${Object.is(value, -0) ? '0' : String(value)}`;
|
|
168
|
+
case 'boolean':
|
|
169
|
+
return `b:${value}`;
|
|
170
|
+
case 'bigint':
|
|
171
|
+
return `i:${value.toString()}`;
|
|
172
|
+
case 'function': {
|
|
173
|
+
const shapeId = (_a = value.shape) === null || _a === void 0 ? void 0 : _a.id;
|
|
174
|
+
return `c:${JSON.stringify((_b = shapeId !== null && shapeId !== void 0 ? shapeId : value.name) !== null && _b !== void 0 ? _b : '')}`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
return `[${value.map(canonicalize).sort().join(',')}]`;
|
|
179
|
+
}
|
|
180
|
+
if (value instanceof Date)
|
|
181
|
+
return `d:${value.toISOString()}`;
|
|
182
|
+
if (value instanceof RegExp)
|
|
183
|
+
return `r:${JSON.stringify(value.source)}/${value.flags}`;
|
|
184
|
+
const record = value;
|
|
185
|
+
const keys = Object.keys(record)
|
|
186
|
+
.filter((k) => record[k] !== undefined)
|
|
187
|
+
.sort();
|
|
188
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(record[k])}`).join(',')}}`;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The content hash shape sync stores on a NodeShape as `core:shapeHash`: SHA-256 (hex) of the
|
|
192
|
+
* canonical form of the shape IRI and the exact create data the sync writes for it — the
|
|
193
|
+
* NodeShape, its flattened own + inherited property shapes, their `sh:in` lists and paths, and
|
|
194
|
+
* the generated single-owner shapes it owns (whose owner classes come from the registered
|
|
195
|
+
* sub-shapes, so registering a new sub-shape changes the hash of the declaring shape).
|
|
196
|
+
*
|
|
197
|
+
* Deterministic across processes, runtimes and Node versions: no blank-node labels, no object key
|
|
198
|
+
* order, no array order (see {@link canonicalize}).
|
|
199
|
+
*
|
|
200
|
+
* @internal Exported for tests only — not part of the public API.
|
|
201
|
+
*/
|
|
202
|
+
export function shapeSyncHash(data, shapeIri) {
|
|
203
|
+
return sha256Hex(`linked-shape-sync/${SHAPE_HASH_VERSION}\n${canonicalize({ iri: shapeIri, data })}`);
|
|
204
|
+
}
|
|
205
|
+
/** The create data and its content hash for one shape, as the sync writes it. */
|
|
206
|
+
function buildSyncData(nodeShape, iri) {
|
|
207
|
+
const data = buildNodeShapeData(nodeShape, iri);
|
|
208
|
+
return { data, hash: shapeSyncHash(data, iri) };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The stored `core:shapeHash` of each of `iris` that exists as a NodeShape, in one select. A shape
|
|
212
|
+
* that is absent, or stored without a hash (written before hashes existed), is missing from the
|
|
213
|
+
* map. `ds` targets an explicit dataset, as for the writes.
|
|
214
|
+
*/
|
|
215
|
+
function readStoredHashes(iris, ds) {
|
|
216
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
217
|
+
var _a;
|
|
218
|
+
const stored = new Map();
|
|
219
|
+
if (iris.length === 0)
|
|
220
|
+
return stored;
|
|
221
|
+
const rows = (_a = (yield QueryBuilder.from(NodeShape).select(['shapeHash']).forAll(iris).exec(ds))) !== null && _a !== void 0 ? _a : [];
|
|
222
|
+
for (const row of rows) {
|
|
223
|
+
if (row && typeof row.shapeHash === 'string')
|
|
224
|
+
stored.set(row.id, row.shapeHash);
|
|
225
|
+
}
|
|
226
|
+
return stored;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
132
229
|
/**
|
|
133
230
|
* The per-shape `delete → create` thunk shared by `syncShapes()` and `syncShape()`: exactly two
|
|
134
231
|
* requests. The delete cascade-cleans the shape's old property-shape / list / path subtrees; the
|
|
135
|
-
* create rebuilds them
|
|
232
|
+
* create rebuilds them, and stores the content hash (`core:shapeHash`) of what it wrote on the
|
|
233
|
+
* NodeShape so a later sync can skip the shape while it is unchanged.
|
|
136
234
|
*
|
|
137
235
|
* The single-owner constraints generated from the shape's own `contains` properties
|
|
138
236
|
* (`${iri}/${label}.singleOwner`, see `singleOwnerConstraint.ts`) are owned by the shape through
|
|
139
237
|
* `core:generatedShape` (a `contains` property with value shape NodeShape). The delete removes
|
|
140
|
-
*
|
|
141
|
-
* nodes
|
|
238
|
+
* them and their property shapes through the owned walk, and their blank path and qualified-shape
|
|
239
|
+
* nodes through the blank-node cascade; the create writes the current ones nested, in the same request
|
|
142
240
|
* as the shape. A removed or renamed accessor therefore loses its constraint on the next sync,
|
|
143
241
|
* and a NodeShape the shape does not own is never touched.
|
|
144
242
|
*
|
|
@@ -146,12 +244,16 @@ function buildNodeShapeData(nodeShape, shapeIri) {
|
|
|
146
244
|
* global router. Omitted → today's global behavior.
|
|
147
245
|
*/
|
|
148
246
|
function buildSyncThunk(nodeShape, iri, ds) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
247
|
+
// `writeShape` builds the data fresh on each invocation, so every run writes from the current
|
|
248
|
+
// shape metadata, and the hash stored with it always describes what was written.
|
|
249
|
+
return () => writeShape(nodeShape, iri, ds);
|
|
250
|
+
}
|
|
251
|
+
/** Delete (cascade) then create one shape, storing the hash of the written data. */
|
|
252
|
+
function writeShape(nodeShape, iri, ds) {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
const { data, hash } = buildSyncData(nodeShape, iri);
|
|
153
255
|
yield DeleteBuilder.from(NodeShape, { id: iri }).exec(ds);
|
|
154
|
-
yield NodeShape.create(data).withId(iri).exec(ds);
|
|
256
|
+
yield NodeShape.create(Object.assign(Object.assign({}, data), { shapeHash: hash })).withId(iri).exec(ds);
|
|
155
257
|
});
|
|
156
258
|
}
|
|
157
259
|
/** The generated single-owner shape IRIs a code shape writes. */
|
|
@@ -182,6 +284,15 @@ function shapeNamespace(iri) {
|
|
|
182
284
|
* Each in-code shape's thunk runs `delete → create` in order (the delete cascade-cleans the old
|
|
183
285
|
* property shapes / list / path subtrees via the containment cascade, the create rebuilds them).
|
|
184
286
|
*
|
|
287
|
+
* **Unchanged shapes are skipped.** The create stores a content hash of everything it writes for
|
|
288
|
+
* the shape on the NodeShape (`core:shapeHash`, see {@link shapeSyncHash}). The plan reads the
|
|
289
|
+
* stored hashes of all code shapes in one select and builds a thunk only for a shape whose hash
|
|
290
|
+
* is missing (never synced, or synced before hashes existed) or differs. Syncing an unchanged
|
|
291
|
+
* app therefore returns no write thunks, and the length of the plan is the number of shapes
|
|
292
|
+
* actually written plus the pruned orphans. The hash records what sync last wrote, not what the
|
|
293
|
+
* store holds now: a subtree edited or partly deleted by another writer while the NodeShape and
|
|
294
|
+
* its hash survive is not detected. `options.force` rewrites every code shape.
|
|
295
|
+
*
|
|
185
296
|
* **Shapes in the store but not registered in this process are left alone by default**
|
|
186
297
|
* (`orphanScope: 'none'`). The registry only holds what this process happened to import, and
|
|
187
298
|
* that is routinely a subset of what the dataset holds: shapes are deep-importable one file at a
|
|
@@ -198,8 +309,8 @@ function shapeNamespace(iri) {
|
|
|
198
309
|
* with `'none'` no read is made.
|
|
199
310
|
*
|
|
200
311
|
* `ds` (optional) targets an explicit dataset instead of the global router. It is a **plan-time**
|
|
201
|
-
* parameter: it feeds
|
|
202
|
-
* store they'll be
|
|
312
|
+
* parameter: it feeds the stored-hash read and the orphan-detection read (so both are computed
|
|
313
|
+
* against the same store they'll be written to) and every delete/create thunk. Omitted → the global router;
|
|
203
314
|
* the returned thunks stay nullary either way.
|
|
204
315
|
*/
|
|
205
316
|
export function syncShapes(ds, options) {
|
|
@@ -233,8 +344,15 @@ export function syncShapes(ds, options) {
|
|
|
233
344
|
// Namespaces this process actually writes — the ownership scope for `'ownedNamespaces'` pruning.
|
|
234
345
|
const ownedNamespaces = new Set(userShapes.map((s) => shapeNamespace(s.iri)));
|
|
235
346
|
const thunks = [];
|
|
236
|
-
// 2. Per in-code shape: delete (cascade) then
|
|
347
|
+
// 2. Per in-code shape whose stored content hash is missing or differs: delete (cascade) then
|
|
348
|
+
// recreate. An unchanged shape gets no thunk, so syncing an unchanged app writes nothing.
|
|
349
|
+
// The stored hashes are read in one select, against the same `ds` the thunks target.
|
|
350
|
+
const storedHashes = (options === null || options === void 0 ? void 0 : options.force)
|
|
351
|
+
? new Map()
|
|
352
|
+
: yield readStoredHashes(userShapes.map((s) => s.iri), ds);
|
|
237
353
|
for (const { iri, nodeShape } of userShapes) {
|
|
354
|
+
if (!(options === null || options === void 0 ? void 0 : options.force) && storedHashes.get(iri) === buildSyncData(nodeShape, iri).hash)
|
|
355
|
+
continue;
|
|
238
356
|
thunks.push(buildSyncThunk(nodeShape, iri, ds));
|
|
239
357
|
}
|
|
240
358
|
// 3. Orphan sweep (unless disabled). Identity-read existing NodeShape IRIs (ids only). Must hit
|
|
@@ -267,12 +385,15 @@ export function syncShapes(ds, options) {
|
|
|
267
385
|
* its registered class via `getShapeClass`; the shape must be code-registered.
|
|
268
386
|
* @param ds optional explicit dataset to materialize into (a store or a router) — both the delete
|
|
269
387
|
* and the create run against it instead of the global router. Omitted → global behavior.
|
|
388
|
+
* @param options `force: true` rewrites the shape even when its stored hash matches.
|
|
270
389
|
* @returns a single unexecuted thunk (consistent with {@link syncShapes}), so callers can batch
|
|
271
390
|
* several together — e.g. a shape plus its referenced object-property shapes:
|
|
272
391
|
* `await Promise.all([syncShape(Person), syncShape(Address)].map((run) => run()))`.
|
|
392
|
+
* Unless `force` is set, the thunk first reads the shape's stored `core:shapeHash` and writes
|
|
393
|
+
* nothing when it equals the hash of what it would write (see {@link syncShapes}).
|
|
273
394
|
* @throws if `target` is a framework/meta shape, is not registered, or has no `.shape`.
|
|
274
395
|
*/
|
|
275
|
-
export function syncShape(target, ds) {
|
|
396
|
+
export function syncShape(target, ds, options) {
|
|
276
397
|
// Normalize target → its registered shape class.
|
|
277
398
|
const cls = typeof target === 'string' ? getShapeClass(target) : target;
|
|
278
399
|
if (typeof target === 'string' && !cls) {
|
|
@@ -292,6 +413,13 @@ export function syncShape(target, ds) {
|
|
|
292
413
|
throw new Error(`syncShape: refusing to sync framework/meta shape ${iri}`);
|
|
293
414
|
}
|
|
294
415
|
assertNoGeneratedIriCollision(nodeShape, iri, (id) => !!getShapeClass(id));
|
|
295
|
-
|
|
416
|
+
if (options === null || options === void 0 ? void 0 : options.force)
|
|
417
|
+
return buildSyncThunk(nodeShape, iri, ds);
|
|
418
|
+
return () => __awaiter(this, void 0, void 0, function* () {
|
|
419
|
+
const stored = yield readStoredHashes([iri], ds);
|
|
420
|
+
if (stored.get(iri) === buildSyncData(nodeShape, iri).hash)
|
|
421
|
+
return;
|
|
422
|
+
yield writeShape(nodeShape, iri, ds);
|
|
423
|
+
});
|
|
296
424
|
}
|
|
297
425
|
//# sourceMappingURL=syncShapes.js.map
|