@_linked/core 2.30.1 → 2.32.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 (59) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +107 -60
  3. package/lib/esm/ontologies/linked-core.js +3 -2
  4. package/lib/esm/ontologies/linked-core.js.map +1 -1
  5. package/lib/esm/ontologies/shacl.d.ts +3 -0
  6. package/lib/esm/ontologies/shacl.js +6 -0
  7. package/lib/esm/ontologies/shacl.js.map +1 -1
  8. package/lib/esm/queries/IRMutation.js +1 -1
  9. package/lib/esm/queries/IRMutation.js.map +1 -1
  10. package/lib/esm/queries/IntermediateRepresentation.d.ts +0 -5
  11. package/lib/esm/queries/IntermediateRepresentation.js.map +1 -1
  12. package/lib/esm/queries/MutationQuery.js +0 -15
  13. package/lib/esm/queries/MutationQuery.js.map +1 -1
  14. package/lib/esm/queries/MutationSerialization.d.ts +6 -5
  15. package/lib/esm/queries/MutationSerialization.js +1 -9
  16. package/lib/esm/queries/MutationSerialization.js.map +1 -1
  17. package/lib/esm/queries/QueryFactory.d.ts +0 -6
  18. package/lib/esm/queries/QueryFactory.js.map +1 -1
  19. package/lib/esm/queries/lowerMutationJSON.js +1 -6
  20. package/lib/esm/queries/lowerMutationJSON.js.map +1 -1
  21. package/lib/esm/shapes/List.class.d.ts +3 -3
  22. package/lib/esm/shapes/List.class.js +3 -3
  23. package/lib/esm/shapes/List.class.js.map +1 -1
  24. package/lib/esm/shapes/List.d.ts +0 -1
  25. package/lib/esm/shapes/List.js.map +1 -1
  26. package/lib/esm/shapes/PathList.d.ts +24 -0
  27. package/lib/esm/shapes/PathList.js +60 -0
  28. package/lib/esm/shapes/PathList.js.map +1 -0
  29. package/lib/esm/shapes/PathNode.d.ts +8 -9
  30. package/lib/esm/shapes/PathNode.js +20 -14
  31. package/lib/esm/shapes/PathNode.js.map +1 -1
  32. package/lib/esm/shapes/SHACL.d.ts +15 -13
  33. package/lib/esm/shapes/SHACL.js +8 -0
  34. package/lib/esm/shapes/SHACL.js.map +1 -1
  35. package/lib/esm/shapes/index.d.ts +1 -0
  36. package/lib/esm/shapes/index.js +1 -0
  37. package/lib/esm/shapes/index.js.map +1 -1
  38. package/lib/esm/shapes/nodeShapeData.d.ts +4 -1
  39. package/lib/esm/shapes/nodeShapeData.js.map +1 -1
  40. package/lib/esm/shapes/serializePathToNodeData.d.ts +12 -11
  41. package/lib/esm/shapes/serializePathToNodeData.js +12 -11
  42. package/lib/esm/shapes/serializePathToNodeData.js.map +1 -1
  43. package/lib/esm/shapes/singleOwnerConstraint.d.ts +5 -7
  44. package/lib/esm/shapes/singleOwnerConstraint.js +65 -21
  45. package/lib/esm/shapes/singleOwnerConstraint.js.map +1 -1
  46. package/lib/esm/shapes/syncShapes.js +2 -2
  47. package/lib/esm/shapes/syncShapes.js.map +1 -1
  48. package/lib/esm/shapes/validation.js +2 -2
  49. package/lib/esm/shapes/validation.js.map +1 -1
  50. package/lib/esm/sparql/irToAlgebra.d.ts +15 -33
  51. package/lib/esm/sparql/irToAlgebra.js +680 -431
  52. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  53. package/lib/esm/sparql/ownedLinkCheck.d.ts +6 -10
  54. package/lib/esm/sparql/ownedLinkCheck.js +10 -11
  55. package/lib/esm/sparql/ownedLinkCheck.js.map +1 -1
  56. package/lib/esm/utils/Package.d.ts +3 -2
  57. package/lib/esm/utils/Package.js +21 -8
  58. package/lib/esm/utils/Package.js.map +1 -1
  59. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"nodeShapeData.js","sourceRoot":"","sources":["../../../src/shapes/nodeShapeData.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 type {NodeReferenceValue} from '../utils/NodeReference.js';\nimport type {PathExpr} from '../paths/PropertyPathExpr.js';\nimport {getAllNodeShapes, getRegistryVersion, getSuperShapes} from '../utils/ShapeClass.js';\n\n/**\n * Plain-object SHACL metadata — the QResult-like shape of a `sh:PropertyShape`.\n *\n * Shapes are metadata, not data: a shape class's `.shape` and its property shapes\n * are plain objects (no class instance, no methods). Operations that used to be\n * instance methods now live as free functions in this module.\n */\nexport interface PropertyShapeData {\n id: string;\n label: string;\n path: PathExpr;\n nodeKind?: NodeReferenceValue;\n datatype?: NodeReferenceValue;\n minCount?: number;\n maxCount?: number;\n name?: string;\n description?: string;\n order?: number;\n group?: string;\n /**\n * `core:displayRank` — a single linear importance rank, lower = more\n * important. Truncated display contexts (\"the top 3 properties\") derive from it.\n * Distinct from `order`, which is arrangement rather than importance.\n */\n displayRank?: number;\n /** `core:displayHidden` — omit from generic rendering. */\n displayHidden?: boolean;\n class?: NodeReferenceValue;\n in?: (NodeReferenceValue | string | number | boolean)[];\n equalsConstraint?: NodeReferenceValue;\n disjoint?: NodeReferenceValue;\n lessThan?: NodeReferenceValue;\n lessThanOrEquals?: NodeReferenceValue;\n /** Value-range constraints (sh:minInclusive / sh:maxInclusive / sh:minExclusive / sh:maxExclusive). */\n minInclusive?: number;\n maxInclusive?: number;\n minExclusive?: number | string;\n maxExclusive?: number;\n /** String-length constraints (sh:minLength / sh:maxLength). */\n minLength?: number;\n maxLength?: number;\n /** Regex constraint (sh:pattern); serialized as its source string. */\n pattern?: RegExp;\n hasValueConstraint?: NodeReferenceValue | string | number | boolean;\n defaultValue?: unknown;\n sortBy?: PathExpr;\n valueShape?: NodeReferenceValue;\n /** Composition marker: the value(s) of this property are owned by the subject. */\n contains?: boolean;\n parentNodeShape?: NodeShapeData;\n}\n\n/**\n * Plain-object SHACL metadata — the QResult-like shape of a `sh:NodeShape`.\n */\nexport interface NodeShapeData {\n id: string;\n label?: string;\n description?: string;\n targetClass?: NodeReferenceValue;\n extends?: NodeReferenceValue;\n /** Composition marker: instances are dependent (cascade-deletable via `contains`). */\n dependent?: boolean;\n /** sh:closed — target nodes with undeclared properties are invalid. */\n closed?: boolean;\n /** sh:ignoredProperties — extra properties permitted when the shape is closed. */\n ignoredProperties?: NodeReferenceValue[];\n propertyShapes: PropertyShapeData[];\n}\n\n/**\n * Result object produced by `propertyShapeToResult()` (SHACL projection).\n * @deprecated No production callers remain (the `NodeShape.properties` getter and\n * `PropertyShape.getResult()` were removed with the plain-object conversion). Read\n * the plain `PropertyShapeData` fields directly. Scheduled for removal.\n */\nexport interface PropertyShapeResult {\n id: string;\n label: string;\n path: PathExpr;\n nodeKind?: NodeReferenceValue;\n datatype?: NodeReferenceValue;\n minCount?: number;\n maxCount?: number;\n name?: string;\n description?: string;\n order?: number;\n group?: string;\n class?: NodeReferenceValue;\n in?: (NodeReferenceValue | string | number | boolean)[];\n equals?: NodeReferenceValue;\n disjoint?: NodeReferenceValue;\n lessThan?: NodeReferenceValue;\n lessThanOrEquals?: NodeReferenceValue;\n hasValue?: NodeReferenceValue | string | number | boolean;\n defaultValue?: unknown;\n sortBy?: PathExpr;\n valueShape?: NodeReferenceValue;\n}\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create an empty NodeShape metadata object for the given shape IRI. */\nexport function createNodeShapeData(id: string): NodeShapeData {\n return {id, propertyShapes: []};\n}\n\n/** Create a blank PropertyShape metadata object (fields filled in by the caller). */\nexport function createPropertyShapeData(): PropertyShapeData {\n return {id: '', label: '', path: null as unknown as PathExpr};\n}\n\n// ---------------------------------------------------------------------------\n// Free functions (formerly NodeShape/PropertyShape instance methods)\n// ---------------------------------------------------------------------------\n\n/** One-time warning keys for shapes whose `propertyShapes` is missing/invalid. */\nconst warnedMissingPropertyShapes = new Set<string>();\n\n/**\n * Read a node shape's own property shapes, tolerating a missing/invalid array.\n *\n * A non-array `propertyShapes` almost always means a duplicate `@_linked/core`\n * install or a non-normalized static shape on a superclass; warn once per shape id\n * (diagnostic parity with the former `NodeShape.listPropertyShapesSafe`) and treat\n * it as empty.\n */\nfunction ownPropertyShapes(nodeShape: NodeShapeData): PropertyShapeData[] {\n const own = (nodeShape as {propertyShapes?: PropertyShapeData[]}).propertyShapes;\n if (Array.isArray(own)) {\n return own;\n }\n const id = (nodeShape as {id?: string}).id ?? '';\n if (!warnedMissingPropertyShapes.has(id)) {\n warnedMissingPropertyShapes.add(id);\n console.warn(\n `[@_linked/core] static shape ${id ? `'${id}'` : '(unknown id)'} has missing ` +\n `or invalid propertyShapes. Treating as []. Often caused by duplicate ` +\n `@_linked/core installs or a non-normalized static shape on a superclass.`,\n );\n }\n return [];\n}\n\n/**\n * Property shapes declared on this NodeShape. With `includeSuperClasses`, walks the\n * inheritance chain via `getSuperShapes` and concatenates each ancestor's own property\n * shapes — the prototype chain for a class-backed shape, `extends` for one known only\n * as data.\n */\nexport function getPropertyShapes(\n nodeShape: NodeShapeData,\n includeSuperClasses: boolean = false,\n): PropertyShapeData[] {\n if (!includeSuperClasses) {\n return [...ownPropertyShapes(nodeShape)];\n }\n // One inheritance walk, shared with getSuperShapes: the prototype chain for a\n // class-backed shape (which includes the framework `Shape` root and its `label` /\n // `type` properties), or `extends` through the registry for a shape that exists only\n // as data. Previously this walked the prototype chain directly and returned ONLY own\n // properties when no class existed — so a project-authored shape silently lost\n // everything it inherited.\n const res: PropertyShapeData[] = [...ownPropertyShapes(nodeShape)];\n for (const superShape of getSuperShapes(nodeShape)) {\n res.push(...ownPropertyShapes(superShape));\n }\n return res;\n}\n\n// ---------------------------------------------------------------------------\n// Property-shape lookup by id\n// ---------------------------------------------------------------------------\n\n// The registry scan behind predicate, datatype and dependency resolution, cached on\n// successful lookups only and invalidated by the registry version.\nconst propertyShapeCache = new Map<string, PropertyShapeData>();\nlet propertyShapeCacheVersion = -1;\n\n/**\n * Find the property shape declared under a property-shape IRI, across every\n * registered node shape.\n *\n * Scans the METAMODEL registry, which holds every shape — authored or data-only.\n * Scanning the class registry meant a data-only property was never found, and the\n * caller then fell through to emitting the PROPERTY SHAPE's IRI as the SPARQL\n * predicate: a silently wrong query that matched nothing, with no error.\n *\n * Cache keyed on the registration version rather than the registry SIZE. Size does not\n * change when a shape is re-registered in place, which is exactly what happens when a\n * shape is edited and its metadata replaced. Only successful resolutions are cached — a\n * not-found is never stored, so a property resolved before its shape registers can\n * still resolve correctly afterwards.\n */\nexport function findPropertyShapeById(propertyId: string): PropertyShapeData | undefined {\n const version = getRegistryVersion();\n if (version !== propertyShapeCacheVersion) {\n propertyShapeCache.clear();\n propertyShapeCacheVersion = version;\n }\n const cached = propertyShapeCache.get(propertyId);\n if (cached) return cached;\n\n for (const nodeShape of getAllNodeShapes().values()) {\n const propertyShape = getPropertyShapes(nodeShape, true).find(\n (prop: {id?: string}) => prop.id === propertyId,\n );\n if (propertyShape) {\n propertyShapeCache.set(propertyId, propertyShape);\n return propertyShape;\n }\n }\n return undefined;\n}\n\n/** Property shapes across the inheritance chain, deduped by label (most specific wins). */\nexport function getUniquePropertyShapes(\n nodeShape: NodeShapeData,\n): PropertyShapeData[] {\n const unique: PropertyShapeData[] = [];\n const seen = new Set<string>();\n for (const ps of getPropertyShapes(nodeShape, true)) {\n if (!seen.has(ps.label)) {\n seen.add(ps.label);\n unique.push(ps);\n }\n }\n return unique;\n}\n\n/**\n * Find a property shape by label. With `checkSubShapes` (default true), ascends the\n * inheritance chain if the label isn't found locally.\n *\n * Shares `getSuperShapes` with {@link getPropertyShapes}, so the singular and plural\n * lookups can never disagree about what a shape inherits — and so a shape registered\n * from data resolves inherited properties through `extends` rather than stopping at its\n * own. This is what `getPropertyShapeByLabel` delegates to (PR #211), so closing the gap\n * here closes it for the query proxies too. See docs/backlog/040.\n */\nexport function getPropertyShape(\n nodeShape: NodeShapeData,\n label: string,\n checkSubShapes: boolean = true,\n): PropertyShapeData | undefined {\n const own = ownPropertyShapes(nodeShape).find((ps) => ps.label === label);\n if (own || !checkSubShapes) return own;\n for (const superShape of getSuperShapes(nodeShape)) {\n const inherited = ownPropertyShapes(superShape).find(\n (ps) => ps.label === label,\n );\n if (inherited) return inherited;\n }\n return undefined;\n}\n\n/** Two node shapes are equal when they share the same IRI. */\nexport function nodeShapeEquals(a: NodeShapeData, b?: NodeShapeData): boolean {\n return !!b && a?.id === b.id;\n}\n\n/** Append a property shape to a node shape, wiring the back-reference. */\nexport function addPropertyShape(\n nodeShape: NodeShapeData,\n propertyShape: PropertyShapeData,\n): void {\n propertyShape.parentNodeShape = nodeShape;\n if (!Array.isArray(nodeShape.propertyShapes)) {\n nodeShape.propertyShapes = [];\n }\n nodeShape.propertyShapes.push(propertyShape);\n}\n\n/** Shallow-clone a property shape (used by property override / disallow). */\nexport function clonePropertyShape(\n propertyShape: PropertyShapeData,\n): PropertyShapeData {\n return {...propertyShape};\n}\n\n/**\n * Project a property shape to its SHACL result object.\n * @deprecated No production callers remain. Read the plain `PropertyShapeData`\n * fields directly (note this projection renames `equalsConstraint`→`equals`,\n * `hasValueConstraint`→`hasValue`, and serializes `pattern` to its source string).\n * Scheduled for removal.\n */\nexport function propertyShapeToResult(ps: PropertyShapeData): PropertyShapeResult {\n const result: Record<string, unknown> & {id: string; label: string; path: PathExpr} = {\n id: ps.id,\n label: ps.label,\n path: ps.path,\n };\n if (ps.nodeKind) result.nodeKind = ps.nodeKind;\n if (ps.datatype) result.datatype = ps.datatype;\n if (typeof ps.minCount === 'number') result.minCount = ps.minCount;\n if (typeof ps.maxCount === 'number') result.maxCount = ps.maxCount;\n if (ps.name) result.name = ps.name;\n if (ps.description) result.description = ps.description;\n if (typeof ps.order === 'number') result.order = ps.order;\n if (ps.group) result.group = ps.group;\n if (ps.class) result.class = ps.class;\n if (ps.in) result.in = ps.in;\n if (ps.equalsConstraint) result.equals = ps.equalsConstraint;\n if (ps.disjoint) result.disjoint = ps.disjoint;\n if (ps.lessThan) result.lessThan = ps.lessThan;\n if (ps.lessThanOrEquals) result.lessThanOrEquals = ps.lessThanOrEquals;\n if (ps.minInclusive !== undefined) result.minInclusive = ps.minInclusive;\n if (ps.maxInclusive !== undefined) result.maxInclusive = ps.maxInclusive;\n if (ps.minExclusive !== undefined) result.minExclusive = ps.minExclusive;\n if (ps.maxExclusive !== undefined) result.maxExclusive = ps.maxExclusive;\n if (typeof ps.minLength === 'number') result.minLength = ps.minLength;\n if (typeof ps.maxLength === 'number') result.maxLength = ps.maxLength;\n if (ps.pattern) result.pattern = ps.pattern.source;\n if (ps.hasValueConstraint !== undefined) result.hasValue = ps.hasValueConstraint;\n if (ps.defaultValue !== undefined) result.defaultValue = ps.defaultValue;\n if (ps.sortBy) result.sortBy = ps.sortBy;\n if (ps.valueShape) result.valueShape = ps.valueShape;\n return result as PropertyShapeResult;\n}\n"]}
1
+ {"version":3,"file":"nodeShapeData.js","sourceRoot":"","sources":["../../../src/shapes/nodeShapeData.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 type {NodeReferenceValue} from '../utils/NodeReference.js';\nimport type {PathExpr} from '../paths/PropertyPathExpr.js';\nimport {getAllNodeShapes, getRegistryVersion, getSuperShapes} from '../utils/ShapeClass.js';\n\n/**\n * Plain-object SHACL metadata — the QResult-like shape of a `sh:PropertyShape`.\n *\n * Shapes are metadata, not data: a shape class's `.shape` and its property shapes\n * are plain objects (no class instance, no methods). Operations that used to be\n * instance methods now live as free functions in this module.\n */\nexport interface PropertyShapeData {\n id: string;\n label: string;\n path: PathExpr;\n nodeKind?: NodeReferenceValue;\n datatype?: NodeReferenceValue;\n minCount?: number;\n maxCount?: number;\n name?: string;\n description?: string;\n order?: number;\n group?: string;\n /**\n * `core:displayRank` — a single linear importance rank, lower = more\n * important. Truncated display contexts (\"the top 3 properties\") derive from it.\n * Distinct from `order`, which is arrangement rather than importance.\n */\n displayRank?: number;\n /** `core:displayHidden` — omit from generic rendering. */\n displayHidden?: boolean;\n class?: NodeReferenceValue;\n in?: (NodeReferenceValue | string | number | boolean)[];\n equalsConstraint?: NodeReferenceValue;\n disjoint?: NodeReferenceValue;\n lessThan?: NodeReferenceValue;\n lessThanOrEquals?: NodeReferenceValue;\n /** Value-range constraints (sh:minInclusive / sh:maxInclusive / sh:minExclusive / sh:maxExclusive). */\n minInclusive?: number;\n maxInclusive?: number;\n minExclusive?: number | string;\n maxExclusive?: number;\n /** String-length constraints (sh:minLength / sh:maxLength). */\n minLength?: number;\n maxLength?: number;\n /** Regex constraint (sh:pattern); serialized as its source string. */\n pattern?: RegExp;\n hasValueConstraint?: NodeReferenceValue | string | number | boolean;\n defaultValue?: unknown;\n sortBy?: PathExpr;\n valueShape?: NodeReferenceValue;\n /** Composition marker: the value(s) of this property are owned by the subject. */\n contains?: boolean;\n parentNodeShape?: NodeShapeData;\n}\n\n/**\n * Plain-object SHACL metadata — the QResult-like shape of a `sh:NodeShape`.\n */\nexport interface NodeShapeData {\n id: string;\n label?: string;\n description?: string;\n targetClass?: NodeReferenceValue;\n extends?: NodeReferenceValue;\n /**\n * @deprecated No effect: every node a `contains` property reaches is deleted with its owner.\n * Kept for shapes that declare it and persisted as `core:dependent`.\n */\n dependent?: boolean;\n /** sh:closed — target nodes with undeclared properties are invalid. */\n closed?: boolean;\n /** sh:ignoredProperties — extra properties permitted when the shape is closed. */\n ignoredProperties?: NodeReferenceValue[];\n propertyShapes: PropertyShapeData[];\n}\n\n/**\n * Result object produced by `propertyShapeToResult()` (SHACL projection).\n * @deprecated No production callers remain (the `NodeShape.properties` getter and\n * `PropertyShape.getResult()` were removed with the plain-object conversion). Read\n * the plain `PropertyShapeData` fields directly. Scheduled for removal.\n */\nexport interface PropertyShapeResult {\n id: string;\n label: string;\n path: PathExpr;\n nodeKind?: NodeReferenceValue;\n datatype?: NodeReferenceValue;\n minCount?: number;\n maxCount?: number;\n name?: string;\n description?: string;\n order?: number;\n group?: string;\n class?: NodeReferenceValue;\n in?: (NodeReferenceValue | string | number | boolean)[];\n equals?: NodeReferenceValue;\n disjoint?: NodeReferenceValue;\n lessThan?: NodeReferenceValue;\n lessThanOrEquals?: NodeReferenceValue;\n hasValue?: NodeReferenceValue | string | number | boolean;\n defaultValue?: unknown;\n sortBy?: PathExpr;\n valueShape?: NodeReferenceValue;\n}\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create an empty NodeShape metadata object for the given shape IRI. */\nexport function createNodeShapeData(id: string): NodeShapeData {\n return {id, propertyShapes: []};\n}\n\n/** Create a blank PropertyShape metadata object (fields filled in by the caller). */\nexport function createPropertyShapeData(): PropertyShapeData {\n return {id: '', label: '', path: null as unknown as PathExpr};\n}\n\n// ---------------------------------------------------------------------------\n// Free functions (formerly NodeShape/PropertyShape instance methods)\n// ---------------------------------------------------------------------------\n\n/** One-time warning keys for shapes whose `propertyShapes` is missing/invalid. */\nconst warnedMissingPropertyShapes = new Set<string>();\n\n/**\n * Read a node shape's own property shapes, tolerating a missing/invalid array.\n *\n * A non-array `propertyShapes` almost always means a duplicate `@_linked/core`\n * install or a non-normalized static shape on a superclass; warn once per shape id\n * (diagnostic parity with the former `NodeShape.listPropertyShapesSafe`) and treat\n * it as empty.\n */\nfunction ownPropertyShapes(nodeShape: NodeShapeData): PropertyShapeData[] {\n const own = (nodeShape as {propertyShapes?: PropertyShapeData[]}).propertyShapes;\n if (Array.isArray(own)) {\n return own;\n }\n const id = (nodeShape as {id?: string}).id ?? '';\n if (!warnedMissingPropertyShapes.has(id)) {\n warnedMissingPropertyShapes.add(id);\n console.warn(\n `[@_linked/core] static shape ${id ? `'${id}'` : '(unknown id)'} has missing ` +\n `or invalid propertyShapes. Treating as []. Often caused by duplicate ` +\n `@_linked/core installs or a non-normalized static shape on a superclass.`,\n );\n }\n return [];\n}\n\n/**\n * Property shapes declared on this NodeShape. With `includeSuperClasses`, walks the\n * inheritance chain via `getSuperShapes` and concatenates each ancestor's own property\n * shapes — the prototype chain for a class-backed shape, `extends` for one known only\n * as data.\n */\nexport function getPropertyShapes(\n nodeShape: NodeShapeData,\n includeSuperClasses: boolean = false,\n): PropertyShapeData[] {\n if (!includeSuperClasses) {\n return [...ownPropertyShapes(nodeShape)];\n }\n // One inheritance walk, shared with getSuperShapes: the prototype chain for a\n // class-backed shape (which includes the framework `Shape` root and its `label` /\n // `type` properties), or `extends` through the registry for a shape that exists only\n // as data. Previously this walked the prototype chain directly and returned ONLY own\n // properties when no class existed — so a project-authored shape silently lost\n // everything it inherited.\n const res: PropertyShapeData[] = [...ownPropertyShapes(nodeShape)];\n for (const superShape of getSuperShapes(nodeShape)) {\n res.push(...ownPropertyShapes(superShape));\n }\n return res;\n}\n\n// ---------------------------------------------------------------------------\n// Property-shape lookup by id\n// ---------------------------------------------------------------------------\n\n// The registry scan behind predicate, datatype and dependency resolution, cached on\n// successful lookups only and invalidated by the registry version.\nconst propertyShapeCache = new Map<string, PropertyShapeData>();\nlet propertyShapeCacheVersion = -1;\n\n/**\n * Find the property shape declared under a property-shape IRI, across every\n * registered node shape.\n *\n * Scans the METAMODEL registry, which holds every shape — authored or data-only.\n * Scanning the class registry meant a data-only property was never found, and the\n * caller then fell through to emitting the PROPERTY SHAPE's IRI as the SPARQL\n * predicate: a silently wrong query that matched nothing, with no error.\n *\n * Cache keyed on the registration version rather than the registry SIZE. Size does not\n * change when a shape is re-registered in place, which is exactly what happens when a\n * shape is edited and its metadata replaced. Only successful resolutions are cached — a\n * not-found is never stored, so a property resolved before its shape registers can\n * still resolve correctly afterwards.\n */\nexport function findPropertyShapeById(propertyId: string): PropertyShapeData | undefined {\n const version = getRegistryVersion();\n if (version !== propertyShapeCacheVersion) {\n propertyShapeCache.clear();\n propertyShapeCacheVersion = version;\n }\n const cached = propertyShapeCache.get(propertyId);\n if (cached) return cached;\n\n for (const nodeShape of getAllNodeShapes().values()) {\n const propertyShape = getPropertyShapes(nodeShape, true).find(\n (prop: {id?: string}) => prop.id === propertyId,\n );\n if (propertyShape) {\n propertyShapeCache.set(propertyId, propertyShape);\n return propertyShape;\n }\n }\n return undefined;\n}\n\n/** Property shapes across the inheritance chain, deduped by label (most specific wins). */\nexport function getUniquePropertyShapes(\n nodeShape: NodeShapeData,\n): PropertyShapeData[] {\n const unique: PropertyShapeData[] = [];\n const seen = new Set<string>();\n for (const ps of getPropertyShapes(nodeShape, true)) {\n if (!seen.has(ps.label)) {\n seen.add(ps.label);\n unique.push(ps);\n }\n }\n return unique;\n}\n\n/**\n * Find a property shape by label. With `checkSubShapes` (default true), ascends the\n * inheritance chain if the label isn't found locally.\n *\n * Shares `getSuperShapes` with {@link getPropertyShapes}, so the singular and plural\n * lookups can never disagree about what a shape inherits — and so a shape registered\n * from data resolves inherited properties through `extends` rather than stopping at its\n * own. This is what `getPropertyShapeByLabel` delegates to (PR #211), so closing the gap\n * here closes it for the query proxies too. See docs/backlog/040.\n */\nexport function getPropertyShape(\n nodeShape: NodeShapeData,\n label: string,\n checkSubShapes: boolean = true,\n): PropertyShapeData | undefined {\n const own = ownPropertyShapes(nodeShape).find((ps) => ps.label === label);\n if (own || !checkSubShapes) return own;\n for (const superShape of getSuperShapes(nodeShape)) {\n const inherited = ownPropertyShapes(superShape).find(\n (ps) => ps.label === label,\n );\n if (inherited) return inherited;\n }\n return undefined;\n}\n\n/** Two node shapes are equal when they share the same IRI. */\nexport function nodeShapeEquals(a: NodeShapeData, b?: NodeShapeData): boolean {\n return !!b && a?.id === b.id;\n}\n\n/** Append a property shape to a node shape, wiring the back-reference. */\nexport function addPropertyShape(\n nodeShape: NodeShapeData,\n propertyShape: PropertyShapeData,\n): void {\n propertyShape.parentNodeShape = nodeShape;\n if (!Array.isArray(nodeShape.propertyShapes)) {\n nodeShape.propertyShapes = [];\n }\n nodeShape.propertyShapes.push(propertyShape);\n}\n\n/** Shallow-clone a property shape (used by property override / disallow). */\nexport function clonePropertyShape(\n propertyShape: PropertyShapeData,\n): PropertyShapeData {\n return {...propertyShape};\n}\n\n/**\n * Project a property shape to its SHACL result object.\n * @deprecated No production callers remain. Read the plain `PropertyShapeData`\n * fields directly (note this projection renames `equalsConstraint`→`equals`,\n * `hasValueConstraint`→`hasValue`, and serializes `pattern` to its source string).\n * Scheduled for removal.\n */\nexport function propertyShapeToResult(ps: PropertyShapeData): PropertyShapeResult {\n const result: Record<string, unknown> & {id: string; label: string; path: PathExpr} = {\n id: ps.id,\n label: ps.label,\n path: ps.path,\n };\n if (ps.nodeKind) result.nodeKind = ps.nodeKind;\n if (ps.datatype) result.datatype = ps.datatype;\n if (typeof ps.minCount === 'number') result.minCount = ps.minCount;\n if (typeof ps.maxCount === 'number') result.maxCount = ps.maxCount;\n if (ps.name) result.name = ps.name;\n if (ps.description) result.description = ps.description;\n if (typeof ps.order === 'number') result.order = ps.order;\n if (ps.group) result.group = ps.group;\n if (ps.class) result.class = ps.class;\n if (ps.in) result.in = ps.in;\n if (ps.equalsConstraint) result.equals = ps.equalsConstraint;\n if (ps.disjoint) result.disjoint = ps.disjoint;\n if (ps.lessThan) result.lessThan = ps.lessThan;\n if (ps.lessThanOrEquals) result.lessThanOrEquals = ps.lessThanOrEquals;\n if (ps.minInclusive !== undefined) result.minInclusive = ps.minInclusive;\n if (ps.maxInclusive !== undefined) result.maxInclusive = ps.maxInclusive;\n if (ps.minExclusive !== undefined) result.minExclusive = ps.minExclusive;\n if (ps.maxExclusive !== undefined) result.maxExclusive = ps.maxExclusive;\n if (typeof ps.minLength === 'number') result.minLength = ps.minLength;\n if (typeof ps.maxLength === 'number') result.maxLength = ps.maxLength;\n if (ps.pattern) result.pattern = ps.pattern.source;\n if (ps.hasValueConstraint !== undefined) result.hasValue = ps.hasValueConstraint;\n if (ps.defaultValue !== undefined) result.defaultValue = ps.defaultValue;\n if (ps.sortBy) result.sortBy = ps.sortBy;\n if (ps.valueShape) result.valueShape = ps.valueShape;\n return result as PropertyShapeResult;\n}\n"]}
@@ -1,34 +1,35 @@
1
1
  import type { PathExpr } from '../paths/PropertyPathExpr.js';
2
2
  import type { NodeReferenceValue } from '../utils/NodeReference.js';
3
- import { List } from './List.js';
3
+ import { PathList } from './PathList.js';
4
4
  import { PathNode } from './PathNode.js';
5
5
  /**
6
6
  * Node-data the create pipeline accepts for a `sh:path` value: an IRI reference (simple path),
7
- * an `rdf:List` (sequence / alternative members), or a `PathNode` operator node.
7
+ * a `PathList` (sequence / alternative members), or a `PathNode` operator node.
8
8
  */
9
9
  export type PathNodeData = NodeReferenceValue | {
10
- shape: typeof List | typeof PathNode;
11
- __blank: true;
10
+ shape: typeof PathList | typeof PathNode;
12
11
  [k: string]: unknown;
13
12
  };
14
13
  /**
15
14
  * Translate a `PathExpr` into create-pipeline node-data for `sh:path`.
16
15
  *
17
16
  * - simple `PathRef` → `{id}` (a shared predicate IRI; NOT owned)
18
- * - sequence → an `rdf:List` of segments
19
- * - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is an `rdf:List`)
17
+ * - sequence → a `PathList` (an `rdf:List`) of segments
18
+ * - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is a `PathList`)
20
19
  *
21
- * Every operator node and list cell is written as an untyped blank node (`__blank`), the only
22
- * form SHACL §2.3.1 defines: an IRI in `sh:path` is a predicate path, and an inverse,
23
- * alternative or cardinality path is a blank node that is the subject of exactly one triple.
20
+ * Operator nodes and list cells carry no id. `PropertyShape.path`, the `PathNode` slots and
21
+ * `PathList.first` / `.rest` are `sh:BlankNodeOrIRI`, so the create writes each of them as an
22
+ * untyped blank node, the only form SHACL §2.3.1 defines: an IRI in `sh:path` is a predicate path,
23
+ * and an inverse, alternative or cardinality path is a blank node that is the subject of exactly
24
+ * one triple.
24
25
  *
25
26
  * ```turtle
26
27
  * <ps> sh:path [ sh:inversePath schema:image ] .
27
28
  * <ps> sh:path ( schema:knows [ sh:zeroOrMorePath schema:parent ] ) .
28
29
  * ```
29
30
  *
30
- * The structure is owned through `contains` edges, so a re-sync's delete removes it (the
31
- * blank-node block of the containment cascade) before the create writes it again.
31
+ * A re-sync's delete removes the structure (the blank-node cascade follows the blank-capable
32
+ * properties) before the create writes it again.
32
33
  *
33
34
  * @param _base ignored. It seeded the IRIs of the nodes this used to mint (`{base}/inv`, …);
34
35
  * blank nodes have none. Kept so existing two-argument calls still compile.
@@ -1,25 +1,27 @@
1
1
  import { isPathRef } from '../paths/PropertyPathExpr.js';
2
- import { List } from './List.js';
2
+ import { PathList } from './PathList.js';
3
3
  import { rdf } from '../ontologies/rdf.js';
4
4
  import { PathNode } from './PathNode.js';
5
5
  /**
6
6
  * Translate a `PathExpr` into create-pipeline node-data for `sh:path`.
7
7
  *
8
8
  * - simple `PathRef` → `{id}` (a shared predicate IRI; NOT owned)
9
- * - sequence → an `rdf:List` of segments
10
- * - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is an `rdf:List`)
9
+ * - sequence → a `PathList` (an `rdf:List`) of segments
10
+ * - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is a `PathList`)
11
11
  *
12
- * Every operator node and list cell is written as an untyped blank node (`__blank`), the only
13
- * form SHACL §2.3.1 defines: an IRI in `sh:path` is a predicate path, and an inverse,
14
- * alternative or cardinality path is a blank node that is the subject of exactly one triple.
12
+ * Operator nodes and list cells carry no id. `PropertyShape.path`, the `PathNode` slots and
13
+ * `PathList.first` / `.rest` are `sh:BlankNodeOrIRI`, so the create writes each of them as an
14
+ * untyped blank node, the only form SHACL §2.3.1 defines: an IRI in `sh:path` is a predicate path,
15
+ * and an inverse, alternative or cardinality path is a blank node that is the subject of exactly
16
+ * one triple.
15
17
  *
16
18
  * ```turtle
17
19
  * <ps> sh:path [ sh:inversePath schema:image ] .
18
20
  * <ps> sh:path ( schema:knows [ sh:zeroOrMorePath schema:parent ] ) .
19
21
  * ```
20
22
  *
21
- * The structure is owned through `contains` edges, so a re-sync's delete removes it (the
22
- * blank-node block of the containment cascade) before the create writes it again.
23
+ * A re-sync's delete removes the structure (the blank-node cascade follows the blank-capable
24
+ * properties) before the create writes it again.
23
25
  *
24
26
  * @param _base ignored. It seeded the IRIs of the nodes this used to mint (`{base}/inv`, …);
25
27
  * blank nodes have none. Kept so existing two-argument calls still compile.
@@ -27,7 +29,6 @@ import { PathNode } from './PathNode.js';
27
29
  export function serializePathToNodeData(path, _base) {
28
30
  const operator = (key, operand) => ({
29
31
  shape: PathNode,
30
- __blank: true,
31
32
  [key]: operand,
32
33
  });
33
34
  const serialize = (e) => {
@@ -71,8 +72,8 @@ export function serializePathToNodeData(path, _base) {
71
72
  };
72
73
  return serialize(path);
73
74
  }
74
- /** An `rdf:List` of blank cells (`rdf:first`/`rdf:rest` only), terminated by `rdf:nil`. */
75
+ /** A `PathList` of cells without ids (blank once written), terminated by `rdf:nil`. */
75
76
  function blankList(items) {
76
- return items.reduceRight((rest, first) => ({ shape: List, __blank: true, first, rest }), { id: rdf.nil.id });
77
+ return items.reduceRight((rest, first) => ({ shape: PathList, first, rest }), { id: rdf.nil.id });
77
78
  }
78
79
  //# sourceMappingURL=serializePathToNodeData.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serializePathToNodeData.js","sourceRoot":"","sources":["../../../src/shapes/serializePathToNodeData.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 type {PathExpr} from '../paths/PropertyPathExpr.js';\nimport {isPathRef} from '../paths/PropertyPathExpr.js';\nimport type {NodeReferenceValue} from '../utils/NodeReference.js';\nimport {List, type RdfListNodeData} from './List.js';\nimport {rdf} from '../ontologies/rdf.js';\nimport {PathNode} from './PathNode.js';\n\n/**\n * Node-data the create pipeline accepts for a `sh:path` value: an IRI reference (simple path),\n * an `rdf:List` (sequence / alternative members), or a `PathNode` operator node.\n */\nexport type PathNodeData =\n | NodeReferenceValue\n | {shape: typeof List | typeof PathNode; __blank: true; [k: string]: unknown};\n\n/**\n * Translate a `PathExpr` into create-pipeline node-data for `sh:path`.\n *\n * - simple `PathRef` → `{id}` (a shared predicate IRI; NOT owned)\n * - sequence → an `rdf:List` of segments\n * - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is an `rdf:List`)\n *\n * Every operator node and list cell is written as an untyped blank node (`__blank`), the only\n * form SHACL §2.3.1 defines: an IRI in `sh:path` is a predicate path, and an inverse,\n * alternative or cardinality path is a blank node that is the subject of exactly one triple.\n *\n * ```turtle\n * <ps> sh:path [ sh:inversePath schema:image ] .\n * <ps> sh:path ( schema:knows [ sh:zeroOrMorePath schema:parent ] ) .\n * ```\n *\n * The structure is owned through `contains` edges, so a re-sync's delete removes it (the\n * blank-node block of the containment cascade) before the create writes it again.\n *\n * @param _base ignored. It seeded the IRIs of the nodes this used to mint (`{base}/inv`, …);\n * blank nodes have none. Kept so existing two-argument calls still compile.\n */\nexport function serializePathToNodeData(path: PathExpr, _base?: string): PathNodeData {\n const operator = (key: string, operand: unknown): PathNodeData => ({\n shape: PathNode,\n __blank: true,\n [key]: operand,\n });\n const serialize = (e: PathExpr): PathNodeData => {\n if (isPathRef(e)) {\n return {id: typeof e === 'string' ? e : e.id};\n }\n if ('seq' in e) {\n // SHACL §2.3.1: a sequence path is a list with **at least two** members.\n // A 0/1-member sequence is not a list — collapse a singleton to its bare\n // member, reject an empty one. (normalizePropertyPath already collapses\n // these upstream; this keeps the serializer independently spec-correct.)\n if (e.seq.length === 0) {\n throw new Error('Cannot serialize an empty sequence path to SHACL sh:path.');\n }\n if (e.seq.length === 1) return serialize(e.seq[0]);\n return blankList(e.seq.map(serialize)) as PathNodeData;\n }\n if ('inv' in e) return operator('inversePath', serialize(e.inv));\n if ('alt' in e) {\n // SHACL §2.3.2: sh:alternativePath is a list with **at least two** members.\n // Collapse a singleton alternative to its bare member, reject an empty one.\n if (e.alt.length === 0) {\n throw new Error('Cannot serialize an empty alternative path to SHACL sh:path.');\n }\n if (e.alt.length === 1) return serialize(e.alt[0]);\n return operator('alternativePath', blankList(e.alt.map(serialize)));\n }\n if ('zeroOrMore' in e) return operator('zeroOrMorePath', serialize(e.zeroOrMore));\n if ('oneOrMore' in e) return operator('oneOrMorePath', serialize(e.oneOrMore));\n if ('zeroOrOne' in e) return operator('zeroOrOnePath', serialize(e.zeroOrOne));\n if ('negatedPropertySet' in e) {\n throw new Error(\n 'negatedPropertySet cannot be serialized to SHACL sh:path — no SHACL representation.',\n );\n }\n throw new Error(`Unknown PathExpr shape: ${JSON.stringify(e)}`);\n };\n return serialize(path);\n}\n\n/** An `rdf:List` of blank cells (`rdf:first`/`rdf:rest` only), terminated by `rdf:nil`. */\nfunction blankList(items: PathNodeData[]): RdfListNodeData {\n return items.reduceRight<RdfListNodeData>(\n (rest, first) => ({shape: List, __blank: true, first, rest}),\n {id: rdf.nil.id},\n );\n}\n"]}
1
+ {"version":3,"file":"serializePathToNodeData.js","sourceRoot":"","sources":["../../../src/shapes/serializePathToNodeData.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 type {PathExpr} from '../paths/PropertyPathExpr.js';\nimport {isPathRef} from '../paths/PropertyPathExpr.js';\nimport type {NodeReferenceValue} from '../utils/NodeReference.js';\nimport {PathList} from './PathList.js';\nimport {rdf} from '../ontologies/rdf.js';\nimport {PathNode} from './PathNode.js';\n\n/**\n * Node-data the create pipeline accepts for a `sh:path` value: an IRI reference (simple path),\n * a `PathList` (sequence / alternative members), or a `PathNode` operator node.\n */\nexport type PathNodeData =\n | NodeReferenceValue\n | {shape: typeof PathList | typeof PathNode; [k: string]: unknown};\n\n/**\n * Translate a `PathExpr` into create-pipeline node-data for `sh:path`.\n *\n * - simple `PathRef` → `{id}` (a shared predicate IRI; NOT owned)\n * - sequence → a `PathList` (an `rdf:List`) of segments\n * - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is a `PathList`)\n *\n * Operator nodes and list cells carry no id. `PropertyShape.path`, the `PathNode` slots and\n * `PathList.first` / `.rest` are `sh:BlankNodeOrIRI`, so the create writes each of them as an\n * untyped blank node, the only form SHACL §2.3.1 defines: an IRI in `sh:path` is a predicate path,\n * and an inverse, alternative or cardinality path is a blank node that is the subject of exactly\n * one triple.\n *\n * ```turtle\n * <ps> sh:path [ sh:inversePath schema:image ] .\n * <ps> sh:path ( schema:knows [ sh:zeroOrMorePath schema:parent ] ) .\n * ```\n *\n * A re-sync's delete removes the structure (the blank-node cascade follows the blank-capable\n * properties) before the create writes it again.\n *\n * @param _base ignored. It seeded the IRIs of the nodes this used to mint (`{base}/inv`, …);\n * blank nodes have none. Kept so existing two-argument calls still compile.\n */\nexport function serializePathToNodeData(path: PathExpr, _base?: string): PathNodeData {\n const operator = (key: string, operand: unknown): PathNodeData => ({\n shape: PathNode,\n [key]: operand,\n });\n const serialize = (e: PathExpr): PathNodeData => {\n if (isPathRef(e)) {\n return {id: typeof e === 'string' ? e : e.id};\n }\n if ('seq' in e) {\n // SHACL §2.3.1: a sequence path is a list with **at least two** members.\n // A 0/1-member sequence is not a list — collapse a singleton to its bare\n // member, reject an empty one. (normalizePropertyPath already collapses\n // these upstream; this keeps the serializer independently spec-correct.)\n if (e.seq.length === 0) {\n throw new Error('Cannot serialize an empty sequence path to SHACL sh:path.');\n }\n if (e.seq.length === 1) return serialize(e.seq[0]);\n return blankList(e.seq.map(serialize));\n }\n if ('inv' in e) return operator('inversePath', serialize(e.inv));\n if ('alt' in e) {\n // SHACL §2.3.2: sh:alternativePath is a list with **at least two** members.\n // Collapse a singleton alternative to its bare member, reject an empty one.\n if (e.alt.length === 0) {\n throw new Error('Cannot serialize an empty alternative path to SHACL sh:path.');\n }\n if (e.alt.length === 1) return serialize(e.alt[0]);\n return operator('alternativePath', blankList(e.alt.map(serialize)));\n }\n if ('zeroOrMore' in e) return operator('zeroOrMorePath', serialize(e.zeroOrMore));\n if ('oneOrMore' in e) return operator('oneOrMorePath', serialize(e.oneOrMore));\n if ('zeroOrOne' in e) return operator('zeroOrOnePath', serialize(e.zeroOrOne));\n if ('negatedPropertySet' in e) {\n throw new Error(\n 'negatedPropertySet cannot be serialized to SHACL sh:path — no SHACL representation.',\n );\n }\n throw new Error(`Unknown PathExpr shape: ${JSON.stringify(e)}`);\n };\n return serialize(path);\n}\n\n/** A `PathList` of cells without ids (blank once written), terminated by `rdf:nil`. */\nfunction blankList(items: PathNodeData[]): PathNodeData {\n return items.reduceRight<PathNodeData>(\n (rest, first) => ({shape: PathList, first, rest}),\n {id: rdf.nil.id},\n );\n}\n"]}
@@ -5,21 +5,19 @@ import { type NodeShapeData, type PropertyShapeData } from './nodeShapeData.js';
5
5
  */
6
6
  export declare function singleOwnerShapeIri(propertyShapeIri: string): string;
7
7
  /**
8
- * The shape's OWN `contains` properties that get a single-owner constraint: those that declare
9
- * a value shape and have a simple path, in declaration order.
8
+ * The shape's OWN `contains` properties that get a single-owner constraint: those with a simple
9
+ * path (every decorated `contains` property has a value shape), in declaration order.
10
10
  *
11
11
  * Inherited properties are not included; their declaring shape writes their constraint.
12
- * Complex paths are skipped, as the containment cascade skips them. A `contains` property
13
- * without a value shape is a polymorphic slot (like `sh:path`) whose value may be a shared IRI,
14
- * so the replace, the subject delete and the class check do not treat it as owning that value,
15
- * and neither does the single-owner rule.
12
+ * Complex paths are skipped, as the owned walk skips them.
16
13
  */
17
14
  export declare function singleOwnerProperties(nodeShape: NodeShapeData): PropertyShapeData[];
18
15
  /**
19
16
  * Create-pipeline data for the generated single-owner NodeShapes of `nodeShape`, one per
20
17
  * {@link singleOwnerProperties} entry. Each is stored at
21
18
  * {@link singleOwnerShapeIri}`(${shapeIri}/${label})`: the property shape's IRI exactly as shape
22
- * sync writes it (`buildPropertyShapeData`).
19
+ * sync writes it (`buildPropertyShapeData`). Empty, with one warning per shape, when the shape
20
+ * has `contains` properties but no `targetClass`.
23
21
  */
24
22
  export declare function buildSingleOwnerConstraints(nodeShape: NodeShapeData, shapeIri: string): Array<{
25
23
  iri: string;
@@ -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. For every such property, shape
13
- * sync writes one generated NodeShape stating the matching store-side rule: every object of the
14
- * property's predicate has at most one subject pointing at it through that predicate.
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:maxCount 1 ;
23
- * sh:path [ sh:inversePath schema:image ] .
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 but add no constraint, since the rule targets the predicate's objects whoever the
28
- * subject is. The declaring shape owns its constraints through `core:generatedShape`
29
- * (`NodeShape.generatedShapes`, `contains`): shape sync writes them nested in the shape's
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
- * Non-owning references through other predicates are not counted, and neither is ownership
33
- * through two different `contains` predicates (a documented limitation). The shapes are built
34
- * only at sync time and are never registered in the in-memory shape registry.
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 that declare
47
- * a value shape and have a simple path, in declaration order.
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 containment cascade skips them. A `contains` property
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
- return singleOwnerProperties(nodeShape).flatMap((ps) => {
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;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,4FAA4F;AAC5F,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,gBAAwB;IAC1D,OAAO,GAAG,gBAAgB,GAAG,mBAAmB,EAAE,CAAC;AACrD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAwB;IAC5D,OAAO,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,MAAM,CAC/C,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,UAAU,IAAI,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,CACrE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,SAAwB,EACxB,QAAgB;IAEhB,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACrD,2FAA2F;QAC3F,MAAM,SAAS,GAAG,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,mBAAmB,CAAC,GAAG,QAAQ,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,GAAG,GAAG,SAAS,CAAC;QAC9B,OAAO,CAAC;gBACN,GAAG;gBACH,IAAI,EAAE;oBACJ,SAAS,EAAE,IAAI;oBACf,eAAe,EAAE,CAAC,EAAC,EAAE,EAAE,SAAS,EAAC,CAAC;oBAClC,UAAU,EAAE;wBACV;4BACE,IAAI,EAAE,KAAK;4BACX,KAAK,EAAE,QAAQ;4BACf,QAAQ,EAAE,CAAC;4BACX,IAAI,EAAE,uBAAuB,CAAC,EAAC,GAAG,EAAE,EAAC,EAAE,EAAE,SAAS,EAAC,EAAC,CAAC;yBACtD;qBACF;iBACF;aACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","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';\n\n/**\n * The single-owner SHACL constraints generated from `contains` properties.\n *\n * A `contains` property says the subject owns the value node. For every such property, shape\n * sync 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.\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:maxCount 1 ;\n * sh:path [ sh:inversePath schema:image ] .\n * ```\n *\n * One constraint per accessor, written only by the class that declares it: subclasses inherit\n * the property but add no constraint, since the rule targets the predicate's objects whoever the\n * subject is. The declaring shape owns its constraints through `core:generatedShape`\n * (`NodeShape.generatedShapes`, `contains`): shape sync writes them nested in the shape's\n * create and deletes them with the shape.\n *\n * Non-owning references through other predicates are not counted, and neither is ownership\n * through two different `contains` predicates (a documented limitation). The shapes are built\n * only at sync time and are never registered in the 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 that declare\n * a value shape and have a simple path, in declaration order.\n *\n * Inherited properties are not included; their declaring shape writes their constraint.\n * Complex paths are skipped, as the containment cascade skips them. A `contains` property\n * without a value shape is a polymorphic slot (like `sh:path`) whose value may be a shared IRI,\n * so the replace, the subject delete and the class check do not treat it as owning that value,\n * and neither does the single-owner rule.\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/**\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`).\n */\nexport function buildSingleOwnerConstraints(\n nodeShape: NodeShapeData,\n shapeIri: string,\n): Array<{iri: string; data: Record<string, unknown>}> {\n return singleOwnerProperties(nodeShape).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 maxCount: 1,\n path: serializePathToNodeData({inv: {id: predicate}}),\n },\n ],\n },\n }];\n });\n}\n"]}
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"]}
@@ -137,8 +137,8 @@ function buildNodeShapeData(nodeShape, shapeIri) {
137
137
  * The single-owner constraints generated from the shape's own `contains` properties
138
138
  * (`${iri}/${label}.singleOwner`, see `singleOwnerConstraint.ts`) are owned by the shape through
139
139
  * `core:generatedShape` (a `contains` property with value shape NodeShape). The delete removes
140
- * each one's own triples (the immediate owned-child block) and their property shapes and path
141
- * nodes (the dependent cascade); the create writes the current ones nested, in the same request
140
+ * them and their property shapes through the owned walk, and their blank path and qualified-shape
141
+ * nodes through the blank-node cascade; the create writes the current ones nested, in the same request
142
142
  * as the shape. A removed or renamed accessor therefore loses its constraint on the next sync,
143
143
  * and a NodeShape the shape does not own is never touched.
144
144
  *
@@ -1 +1 @@
1
- {"version":3,"file":"syncShapes.js","sourceRoot":"","sources":["../../../src/shapes/syncShapes.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 {getAllShapeClasses, getShapeClass} from '../utils/ShapeClass.js';\nimport {NodeShape} from './SHACL.js';\nimport {\n getUniquePropertyShapes,\n type NodeShapeData,\n type PropertyShapeData,\n} from './nodeShapeData.js';\nimport {Shape} from './Shape.js';\nimport {DeleteBuilder} from '../queries/DeleteBuilder.js';\nimport type {IDataset} from '../interfaces/IDataset.js';\nimport {rdfList} from './List.js';\nimport {serializePathToNodeData} from './serializePathToNodeData.js';\nimport {buildSingleOwnerConstraints} from './singleOwnerConstraint.js';\n\n/**\n * The npm scope of framework shapes (NodeShape/PropertyShape/List/PathNode/Shape) that\n * describe the meta-model itself and must never be synced as user data.\n */\nconst FRAMEWORK_PACKAGE = '@_linked/core';\n\n/**\n * Build the create-pipeline data object for a single PropertyShape (flattened under shapeIri).\n *\n * @internal Exported for tests only — not part of the public API.\n */\nexport function buildPropertyShapeData(ps: PropertyShapeData, shapeIri: string): Record<string, unknown> {\n const psIri = `${shapeIri}/${ps.label}`;\n const d: Record<string, unknown> = {\n __id: psIri,\n path: serializePathToNodeData(ps.path),\n };\n // `rdfs:label` — the DSL label, i.e. the accessor name the property is reached\n // by in code (`person.memberships`). It is NOT `sh:name`, written below, which\n // is a human-readable display string.\n //\n // Without this the label is only ever implied by `psIri`, and a reader has to\n // re-derive one from the path's local part. That works right up until the two\n // differ — `@_linked/org` declares `memberships` on the path `org:hasMembership`\n // — at which point the catalog reports `hasMembership`, the query proxy indexes\n // the shape with it, and the class (which only knows `memberships`) throws.\n // Readers already prefer a stored label and fall back to the path, so writing it\n // is the whole fix; old catalogs keep working via that fallback.\n if (ps.label) d.label = ps.label;\n if (ps.nodeKind) d.nodeKind = ps.nodeKind;\n if (ps.datatype) d.datatype = ps.datatype;\n if (typeof ps.minCount === 'number') d.minCount = ps.minCount;\n if (typeof ps.maxCount === 'number') d.maxCount = ps.maxCount;\n if (ps.name) d.name = ps.name;\n if (ps.description) d.description = ps.description;\n if (typeof ps.order === 'number') d.order = ps.order;\n if (ps.group) d.group = ps.group;\n if (typeof ps.displayRank === 'number') d.displayRank = ps.displayRank;\n if (ps.displayHidden !== undefined) d.displayHidden = ps.displayHidden;\n if (ps.class) d.class = ps.class;\n if (ps.in) d.in = rdfList(ps.in, {base: `${psIri}/in`});\n if (ps.equalsConstraint) d.equalsConstraint = ps.equalsConstraint;\n if (ps.disjoint) d.disjoint = ps.disjoint;\n if (ps.lessThan) d.lessThan = ps.lessThan;\n if (ps.lessThanOrEquals) d.lessThanOrEquals = ps.lessThanOrEquals;\n if (ps.minInclusive !== undefined) d.minInclusive = ps.minInclusive;\n if (ps.maxInclusive !== undefined) d.maxInclusive = ps.maxInclusive;\n if (ps.minExclusive !== undefined) d.minExclusive = ps.minExclusive;\n if (ps.maxExclusive !== undefined) d.maxExclusive = ps.maxExclusive;\n if (typeof ps.minLength === 'number') d.minLength = ps.minLength;\n if (typeof ps.maxLength === 'number') d.maxLength = ps.maxLength;\n // sh:pattern is serialized as its regex source string.\n if (ps.pattern) d.pattern = ps.pattern.source;\n if (ps.hasValueConstraint !== undefined) d.hasValue = ps.hasValueConstraint;\n if (ps.valueShape) d.valueShape = ps.valueShape;\n if (ps.contains) d.contains = true;\n return d;\n}\n\n/** Build the create-pipeline data object for a NodeShape and its (flattened) property shapes. */\nfunction buildNodeShapeData(nodeShape: NodeShapeData, shapeIri: string): Record<string, unknown> {\n const d: Record<string, unknown> = {};\n if (nodeShape.targetClass) d.targetClass = nodeShape.targetClass;\n if (nodeShape.description) d.description = nodeShape.description;\n if (nodeShape.extends) d.extends = nodeShape.extends;\n if (nodeShape.dependent) d.dependent = true;\n if (nodeShape.closed !== undefined) d.closed = nodeShape.closed;\n if (nodeShape.ignoredProperties && nodeShape.ignoredProperties.length) {\n d.ignoredProperties = nodeShape.ignoredProperties;\n }\n // Flatten: emit own + inherited property shapes (deduped by label) under this shape.\n d.properties = getUniquePropertyShapes(nodeShape).map((ps) =>\n buildPropertyShapeData(ps, shapeIri),\n );\n // The single-owner constraints of the shape's own `contains` properties, owned through\n // `core:generatedShape` and written nested in the same create with their fixed IRIs.\n const generated = buildSingleOwnerConstraints(nodeShape, shapeIri);\n if (generated.length > 0) {\n d.generatedShapes = generated.map(({iri, data}) => ({__id: iri, ...data}));\n }\n return d;\n}\n\n/**\n * The per-shape `delete → create` thunk shared by `syncShapes()` and `syncShape()`: exactly two\n * requests. The delete cascade-cleans the shape's old property-shape / list / path subtrees; the\n * create rebuilds them.\n *\n * The single-owner constraints generated from the shape's own `contains` properties\n * (`${iri}/${label}.singleOwner`, see `singleOwnerConstraint.ts`) are owned by the shape through\n * `core:generatedShape` (a `contains` property with value shape NodeShape). The delete removes\n * each one's own triples (the immediate owned-child block) and their property shapes and path\n * nodes (the dependent cascade); the create writes the current ones nested, in the same request\n * as the shape. A removed or renamed accessor therefore loses its constraint on the next sync,\n * and a NodeShape the shape does not own is never touched.\n *\n * `ds` (optional) targets an explicit dataset — both requests run against it instead of the\n * global router. Omitted → today's global behavior.\n */\nfunction buildSyncThunk(nodeShape: NodeShapeData, iri: string, ds?: IDataset): () => Promise<void> {\n return async () => {\n // Build the data fresh on each invocation, so every run writes from the current\n // shape metadata.\n const data = buildNodeShapeData(nodeShape, iri);\n await DeleteBuilder.from(NodeShape, {id: iri}).exec(ds);\n await (NodeShape.create(data as never).withId(iri) as unknown as {\n exec: (target?: IDataset) => Promise<unknown>;\n }).exec(ds);\n };\n}\n\n/** The generated single-owner shape IRIs a code shape writes. */\nfunction generatedIrisOf(nodeShape: NodeShapeData, iri: string): string[] {\n return buildSingleOwnerConstraints(nodeShape, iri).map((c) => c.iri);\n}\n\n/** Throw when a registered shape IRI equals one of a shape's generated single-owner IRIs. */\nfunction assertNoGeneratedIriCollision(\n nodeShape: NodeShapeData,\n iri: string,\n isRegistered: (iri: string) => boolean,\n): void {\n for (const generated of generatedIrisOf(nodeShape, iri)) {\n if (isRegistered(generated)) {\n throw new Error(`Shape IRI ${generated} collides with a generated single-owner shape`);\n }\n }\n}\n\n/** Options controlling how {@link syncShapes} treats store shapes that are not in code. */\nexport interface SyncShapesOptions {\n /**\n * How to treat NodeShapes present in the store but not in the current process's code:\n * - `'none'` (default) — never prune; purely additive (delete→recreate the code shapes, touch\n * nothing else). The only mode that is safe when the process has loaded a *subset* of the\n * shapes in the dataset, or when the dataset has more than one writer.\n * - `'all'` — prune every store-only shape as an orphan. Only correct when this process has\n * loaded **every** shape the dataset should hold and is its **only** writer; \"not registered\n * here\" is then taken to mean \"deleted from code\".\n * - `'ownedNamespaces'` — prune a store-only shape ONLY if its IRI falls within a namespace\n * this process actually writes (the package-namespace of one of its code-registered shapes,\n * i.e. the IRI prefix up to the last `/` or `#`). Protects other writers' packages (arch-04:\n * one app-data dataset, several writers), but NOT the rest of a package the process only\n * partially loaded — the namespace is the whole package, so a deep import of one shape file\n * still claims all of it.\n *\n * `'all'` and `'ownedNamespaces'` are destructive and must be requested explicitly.\n */\n orphanScope?: 'all' | 'ownedNamespaces' | 'none';\n}\n\n/** The owning namespace of a shape IRI — everything up to and including the last `/` or `#`. */\nfunction shapeNamespace(iri: string): string {\n const cut = Math.max(iri.lastIndexOf('/'), iri.lastIndexOf('#'));\n return cut >= 0 ? iri.slice(0, cut + 1) : iri;\n}\n\n/**\n * Plan an idempotent sync of all code-registered (non-framework) NodeShapes into the store as\n * SHACL data — the forward (code → graph) materialization of arch-05 \"code is canonical\".\n *\n * Returns built-but-unexecuted thunks; the caller controls execution and batching:\n * ```ts\n * await Promise.all((await syncShapes()).map((run) => run()));\n * ```\n * Each in-code shape's thunk runs `delete → create` in order (the delete cascade-cleans the old\n * property shapes / list / path subtrees via the containment cascade, the create rebuilds them).\n *\n * **Shapes in the store but not registered in this process are left alone by default**\n * (`orphanScope: 'none'`). The registry only holds what this process happened to import, and\n * that is routinely a subset of what the dataset holds: shapes are deep-importable one file at a\n * time, and an app-data dataset also holds shapes other writers materialized (CN's\n * `bindShape` / `enableCapability`). \"Not registered here\" therefore does not mean \"deleted from\n * code\". Measured against a dataset holding the 36 `@_linked/schema` shapes: a process that\n * imported only `@_linked/schema/shapes/Thing` (11 shapes registered) deleted the other 25 under\n * both `'all'` and `'ownedNamespaces'`, and a process that imported no shapes deleted all 36.\n *\n * Pruning is opt-in via `options.orphanScope` (see {@link SyncShapesOptions}): pass `'all'` only\n * from a process that has loaded every shape the dataset should hold and is its sole writer, and\n * `'ownedNamespaces'` only when it has loaded every shape of each package it registers. Pruned\n * orphans are deleted with their owned subtree. Pruning reads the existing shape IRIs first;\n * with `'none'` no read is made.\n *\n * `ds` (optional) targets an explicit dataset instead of the global router. It is a **plan-time**\n * parameter: it feeds both the orphan-detection read (so orphans are computed against the same\n * store they'll be pruned from) and every delete/create thunk. Omitted → the global router;\n * the returned thunks stay nullary either way.\n */\nexport async function syncShapes(\n ds?: IDataset,\n options?: SyncShapesOptions,\n): Promise<Array<() => Promise<void>>> {\n const orphanScope = options?.orphanScope ?? 'none';\n // 1. Enumerate code-registered user shapes (exclude framework/meta shapes).\n const userShapes: Array<{iri: string; nodeShape: NodeShapeData}> = [];\n for (const [iri, shapeClass] of getAllShapeClasses()) {\n if (!shapeClass?.shape) continue;\n const pkg = (shapeClass as {packageName?: string}).packageName;\n // Skip framework/meta shapes: the core package, and the base `Shape` which is registered\n // directly (no applyLinkedShape) and therefore carries no packageName.\n if (!pkg || pkg === FRAMEWORK_PACKAGE) continue;\n userShapes.push({iri, nodeShape: shapeClass.shape});\n }\n const localShapeIris = new Set(userShapes.map((s) => s.iri));\n for (const {iri, nodeShape} of userShapes) {\n assertNoGeneratedIriCollision(nodeShape, iri, (id) => localShapeIris.has(id));\n }\n // The generated single-owner shapes of in-code shapes are written by the same thunks, so they\n // are never orphans. Derived in memory from the same function that builds them; a stale\n // generated shape (removed accessor or shape) is not protected and goes with its owner's\n // delete, through the `core:generatedShape` cascade.\n for (const {iri, nodeShape} of userShapes) {\n for (const generated of generatedIrisOf(nodeShape, iri)) localShapeIris.add(generated);\n }\n // Namespaces this process actually writes — the ownership scope for `'ownedNamespaces'` pruning.\n const ownedNamespaces = new Set(userShapes.map((s) => shapeNamespace(s.iri)));\n\n const thunks: Array<() => Promise<void>> = [];\n\n // 2. Per in-code shape: delete (cascade) then recreate.\n for (const {iri, nodeShape} of userShapes) {\n thunks.push(buildSyncThunk(nodeShape, iri, ds));\n }\n\n // 3. Orphan sweep (unless disabled). Identity-read existing NodeShape IRIs (ids only). Must hit\n // the same `ds` the thunks target, or orphans get computed against the wrong store.\n if (orphanScope !== 'none') {\n const existingRows = (await (NodeShape as unknown as {\n select: () => {exec: (target?: IDataset) => Promise<Array<{id: string}>>};\n }).select().exec(ds)) as Array<{id: string}>;\n\n // Orphan shapes (in store, not in code) → delete (cascade cleans their owned subtree). In\n // `'ownedNamespaces'` mode, only shapes within a namespace this process writes are eligible —\n // other writers' shapes (different package namespace) are left untouched.\n for (const {id: iri} of existingRows) {\n if (localShapeIris.has(iri)) continue;\n if (orphanScope === 'ownedNamespaces' && !ownedNamespaces.has(shapeNamespace(iri))) {\n continue;\n }\n thunks.push(() =>\n (DeleteBuilder.from(NodeShape, {id: iri}).exec(ds) as Promise<unknown>).then(\n () => undefined,\n ),\n );\n }\n }\n\n return thunks;\n}\n\n/**\n * Plan an idempotent sync of ONE code-registered NodeShape into the store as SHACL data.\n *\n * Scoped counterpart to {@link syncShapes}: materializes a single shape (delete → recreate, so the\n * delete cascade-cleans the old property-shape / list / path subtrees and the create rebuilds them)\n * and does NOT run the store-wide orphan sweep — other shapes in the store are untouched.\n *\n * @param target a shape class (e.g. `Person`) or its NodeShape IRI string. An IRI is resolved to\n * its registered class via `getShapeClass`; the shape must be code-registered.\n * @param ds optional explicit dataset to materialize into (a store or a router) — both the delete\n * and the create run against it instead of the global router. Omitted → global behavior.\n * @returns a single unexecuted thunk (consistent with {@link syncShapes}), so callers can batch\n * several together — e.g. a shape plus its referenced object-property shapes:\n * `await Promise.all([syncShape(Person), syncShape(Address)].map((run) => run()))`.\n * @throws if `target` is a framework/meta shape, is not registered, or has no `.shape`.\n */\nexport function syncShape(target: typeof Shape | string, ds?: IDataset): () => Promise<void> {\n // Normalize target → its registered shape class.\n const cls =\n typeof target === 'string' ? getShapeClass(target) : (target as typeof Shape);\n if (typeof target === 'string' && !cls) {\n throw new Error(`syncShape: no registered shape for IRI ${target}`);\n }\n const nodeShape = cls?.shape;\n if (!nodeShape) {\n throw new Error(\n typeof target === 'string'\n ? `syncShape: no registered shape for IRI ${target}`\n : `syncShape: shape class has no static .shape`,\n );\n }\n const iri = nodeShape.id;\n\n // Never materialize a framework/meta shape as user data — same invariant syncShapes() enforces\n // by skipping (the base Shape carries no packageName).\n const pkg = (cls as {packageName?: string}).packageName;\n if (!pkg || pkg === FRAMEWORK_PACKAGE) {\n throw new Error(`syncShape: refusing to sync framework/meta shape ${iri}`);\n }\n\n assertNoGeneratedIriCollision(nodeShape, iri, (id) => !!getShapeClass(id));\n return buildSyncThunk(nodeShape, iri, ds);\n}\n"]}
1
+ {"version":3,"file":"syncShapes.js","sourceRoot":"","sources":["../../../src/shapes/syncShapes.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;;GAIG;AACH,OAAO,EAAC,kBAAkB,EAAE,aAAa,EAAC,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AACrC,OAAO,EACL,uBAAuB,GAGxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAE1D,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAClC,OAAO,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAC,2BAA2B,EAAC,MAAM,4BAA4B,CAAC;AAEvE;;;GAGG;AACH,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAqB,EAAE,QAAgB;IAC5E,MAAM,KAAK,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;IACxC,MAAM,CAAC,GAA4B;QACjC,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,uBAAuB,CAAC,EAAE,CAAC,IAAI,CAAC;KACvC,CAAC;IACF,+EAA+E;IAC/E,+EAA+E;IAC/E,sCAAsC;IACtC,EAAE;IACF,8EAA8E;IAC9E,8EAA8E;IAC9E,iFAAiF;IACjF,gFAAgF;IAChF,4EAA4E;IAC5E,iFAAiF;IACjF,iEAAiE;IACjE,IAAI,EAAE,CAAC,KAAK;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACjC,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,OAAO,EAAE,CAAC,QAAQ,KAAK,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC9D,IAAI,OAAO,EAAE,CAAC,QAAQ,KAAK,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC9D,IAAI,EAAE,CAAC,IAAI;QAAE,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,WAAW;QAAE,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC;IACnD,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACrD,IAAI,EAAE,CAAC,KAAK;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC,WAAW,KAAK,QAAQ;QAAE,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC;IACvE,IAAI,EAAE,CAAC,aAAa,KAAK,SAAS;QAAE,CAAC,CAAC,aAAa,GAAG,EAAE,CAAC,aAAa,CAAC;IACvE,IAAI,EAAE,CAAC,KAAK;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACjC,IAAI,EAAE,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,EAAC,IAAI,EAAE,GAAG,KAAK,KAAK,EAAC,CAAC,CAAC;IACxD,IAAI,EAAE,CAAC,gBAAgB;QAAE,CAAC,CAAC,gBAAgB,GAAG,EAAE,CAAC,gBAAgB,CAAC;IAClE,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,EAAE,CAAC,gBAAgB;QAAE,CAAC,CAAC,gBAAgB,GAAG,EAAE,CAAC,gBAAgB,CAAC;IAClE,IAAI,EAAE,CAAC,YAAY,KAAK,SAAS;QAAE,CAAC,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;IACpE,IAAI,EAAE,CAAC,YAAY,KAAK,SAAS;QAAE,CAAC,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;IACpE,IAAI,EAAE,CAAC,YAAY,KAAK,SAAS;QAAE,CAAC,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;IACpE,IAAI,EAAE,CAAC,YAAY,KAAK,SAAS;QAAE,CAAC,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;IACpE,IAAI,OAAO,EAAE,CAAC,SAAS,KAAK,QAAQ;QAAE,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC;IACjE,IAAI,OAAO,EAAE,CAAC,SAAS,KAAK,QAAQ;QAAE,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC;IACjE,uDAAuD;IACvD,IAAI,EAAE,CAAC,OAAO;QAAE,CAAC,CAAC,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,CAAC,kBAAkB,KAAK,SAAS;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,kBAAkB,CAAC;IAC5E,IAAI,EAAE,CAAC,UAAU;QAAE,CAAC,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC;IAChD,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iGAAiG;AACjG,SAAS,kBAAkB,CAAC,SAAwB,EAAE,QAAgB;IACpE,MAAM,CAAC,GAA4B,EAAE,CAAC;IACtC,IAAI,SAAS,CAAC,WAAW;QAAE,CAAC,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;IACjE,IAAI,SAAS,CAAC,WAAW;QAAE,CAAC,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;IACjE,IAAI,SAAS,CAAC,OAAO;QAAE,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;IACrD,IAAI,SAAS,CAAC,SAAS;QAAE,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC;IAC5C,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS;QAAE,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAChE,IAAI,SAAS,CAAC,iBAAiB,IAAI,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC;QACtE,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,iBAAiB,CAAC;IACpD,CAAC;IACD,qFAAqF;IACrF,CAAC,CAAC,UAAU,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAC3D,sBAAsB,CAAC,EAAE,EAAE,QAAQ,CAAC,CACrC,CAAC;IACF,uFAAuF;IACvF,qFAAqF;IACrF,MAAM,SAAS,GAAG,2BAA2B,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,CAAC,CAAC,eAAe,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,EAAC,GAAG,EAAE,IAAI,EAAC,EAAE,EAAE,CAAC,iBAAE,IAAI,EAAE,GAAG,IAAK,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,cAAc,CAAC,SAAwB,EAAE,GAAW,EAAE,EAAa;IAC1E,OAAO,GAAS,EAAE;QAChB,gFAAgF;QAChF,kBAAkB;QAClB,MAAM,IAAI,GAAG,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAChD,MAAM,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,EAAC,EAAE,EAAE,GAAG,EAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxD,MAAO,SAAS,CAAC,MAAM,CAAC,IAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAE/C,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACd,CAAC,CAAA,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,SAAS,eAAe,CAAC,SAAwB,EAAE,GAAW;IAC5D,OAAO,2BAA2B,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACvE,CAAC;AAED,6FAA6F;AAC7F,SAAS,6BAA6B,CACpC,SAAwB,EACxB,GAAW,EACX,YAAsC;IAEtC,KAAK,MAAM,SAAS,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;QACxD,IAAI,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,aAAa,SAAS,+CAA+C,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;AACH,CAAC;AAwBD,gGAAgG;AAChG,SAAS,cAAc,CAAC,GAAW;IACjC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAgB,UAAU,CAC9B,EAAa,EACb,OAA2B;;;QAE3B,MAAM,WAAW,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,mCAAI,MAAM,CAAC;QACnD,4EAA4E;QAC5E,MAAM,UAAU,GAAmD,EAAE,CAAC;QACtE,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,kBAAkB,EAAE,EAAE,CAAC;YACrD,IAAI,CAAC,CAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,CAAA;gBAAE,SAAS;YACjC,MAAM,GAAG,GAAI,UAAqC,CAAC,WAAW,CAAC;YAC/D,yFAAyF;YACzF,uEAAuE;YACvE,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,iBAAiB;gBAAE,SAAS;YAChD,UAAU,CAAC,IAAI,CAAC,EAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,KAAK,EAAC,CAAC,CAAC;QACtD,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,KAAK,MAAM,EAAC,GAAG,EAAE,SAAS,EAAC,IAAI,UAAU,EAAE,CAAC;YAC1C,6BAA6B,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,CAAC;QACD,8FAA8F;QAC9F,wFAAwF;QACxF,yFAAyF;QACzF,qDAAqD;QACrD,KAAK,MAAM,EAAC,GAAG,EAAE,SAAS,EAAC,IAAI,UAAU,EAAE,CAAC;YAC1C,KAAK,MAAM,SAAS,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC;gBAAE,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzF,CAAC;QACD,iGAAiG;QACjG,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAE9E,MAAM,MAAM,GAA+B,EAAE,CAAC;QAE9C,wDAAwD;QACxD,KAAK,MAAM,EAAC,GAAG,EAAE,SAAS,EAAC,IAAI,UAAU,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;QAED,gGAAgG;QAChG,uFAAuF;QACvF,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;YAC3B,MAAM,YAAY,GAAG,CAAC,MAAO,SAE3B,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAwB,CAAC;YAE7C,0FAA0F;YAC1F,8FAA8F;YAC9F,0EAA0E;YAC1E,KAAK,MAAM,EAAC,EAAE,EAAE,GAAG,EAAC,IAAI,YAAY,EAAE,CAAC;gBACrC,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBACtC,IAAI,WAAW,KAAK,iBAAiB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;oBACnF,SAAS;gBACX,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CACd,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,EAAC,EAAE,EAAE,GAAG,EAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAsB,CAAC,IAAI,CAC1E,GAAG,EAAE,CAAC,SAAS,CAChB,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,SAAS,CAAC,MAA6B,EAAE,EAAa;IACpE,iDAAiD;IACjD,MAAM,GAAG,GACP,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAuB,CAAC;IAChF,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,0CAA0C,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,SAAS,GAAG,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,KAAK,CAAC;IAC7B,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,OAAO,MAAM,KAAK,QAAQ;YACxB,CAAC,CAAC,0CAA0C,MAAM,EAAE;YACpD,CAAC,CAAC,6CAA6C,CAClD,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC;IAEzB,+FAA+F;IAC/F,uDAAuD;IACvD,MAAM,GAAG,GAAI,GAA8B,CAAC,WAAW,CAAC;IACxD,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,iBAAiB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,oDAAoD,GAAG,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,6BAA6B,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3E,OAAO,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;AAC5C,CAAC","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 {getAllShapeClasses, getShapeClass} from '../utils/ShapeClass.js';\nimport {NodeShape} from './SHACL.js';\nimport {\n getUniquePropertyShapes,\n type NodeShapeData,\n type PropertyShapeData,\n} from './nodeShapeData.js';\nimport {Shape} from './Shape.js';\nimport {DeleteBuilder} from '../queries/DeleteBuilder.js';\nimport type {IDataset} from '../interfaces/IDataset.js';\nimport {rdfList} from './List.js';\nimport {serializePathToNodeData} from './serializePathToNodeData.js';\nimport {buildSingleOwnerConstraints} from './singleOwnerConstraint.js';\n\n/**\n * The npm scope of framework shapes (NodeShape/PropertyShape/List/PathNode/Shape) that\n * describe the meta-model itself and must never be synced as user data.\n */\nconst FRAMEWORK_PACKAGE = '@_linked/core';\n\n/**\n * Build the create-pipeline data object for a single PropertyShape (flattened under shapeIri).\n *\n * @internal Exported for tests only — not part of the public API.\n */\nexport function buildPropertyShapeData(ps: PropertyShapeData, shapeIri: string): Record<string, unknown> {\n const psIri = `${shapeIri}/${ps.label}`;\n const d: Record<string, unknown> = {\n __id: psIri,\n path: serializePathToNodeData(ps.path),\n };\n // `rdfs:label` — the DSL label, i.e. the accessor name the property is reached\n // by in code (`person.memberships`). It is NOT `sh:name`, written below, which\n // is a human-readable display string.\n //\n // Without this the label is only ever implied by `psIri`, and a reader has to\n // re-derive one from the path's local part. That works right up until the two\n // differ — `@_linked/org` declares `memberships` on the path `org:hasMembership`\n // — at which point the catalog reports `hasMembership`, the query proxy indexes\n // the shape with it, and the class (which only knows `memberships`) throws.\n // Readers already prefer a stored label and fall back to the path, so writing it\n // is the whole fix; old catalogs keep working via that fallback.\n if (ps.label) d.label = ps.label;\n if (ps.nodeKind) d.nodeKind = ps.nodeKind;\n if (ps.datatype) d.datatype = ps.datatype;\n if (typeof ps.minCount === 'number') d.minCount = ps.minCount;\n if (typeof ps.maxCount === 'number') d.maxCount = ps.maxCount;\n if (ps.name) d.name = ps.name;\n if (ps.description) d.description = ps.description;\n if (typeof ps.order === 'number') d.order = ps.order;\n if (ps.group) d.group = ps.group;\n if (typeof ps.displayRank === 'number') d.displayRank = ps.displayRank;\n if (ps.displayHidden !== undefined) d.displayHidden = ps.displayHidden;\n if (ps.class) d.class = ps.class;\n if (ps.in) d.in = rdfList(ps.in, {base: `${psIri}/in`});\n if (ps.equalsConstraint) d.equalsConstraint = ps.equalsConstraint;\n if (ps.disjoint) d.disjoint = ps.disjoint;\n if (ps.lessThan) d.lessThan = ps.lessThan;\n if (ps.lessThanOrEquals) d.lessThanOrEquals = ps.lessThanOrEquals;\n if (ps.minInclusive !== undefined) d.minInclusive = ps.minInclusive;\n if (ps.maxInclusive !== undefined) d.maxInclusive = ps.maxInclusive;\n if (ps.minExclusive !== undefined) d.minExclusive = ps.minExclusive;\n if (ps.maxExclusive !== undefined) d.maxExclusive = ps.maxExclusive;\n if (typeof ps.minLength === 'number') d.minLength = ps.minLength;\n if (typeof ps.maxLength === 'number') d.maxLength = ps.maxLength;\n // sh:pattern is serialized as its regex source string.\n if (ps.pattern) d.pattern = ps.pattern.source;\n if (ps.hasValueConstraint !== undefined) d.hasValue = ps.hasValueConstraint;\n if (ps.valueShape) d.valueShape = ps.valueShape;\n if (ps.contains) d.contains = true;\n return d;\n}\n\n/** Build the create-pipeline data object for a NodeShape and its (flattened) property shapes. */\nfunction buildNodeShapeData(nodeShape: NodeShapeData, shapeIri: string): Record<string, unknown> {\n const d: Record<string, unknown> = {};\n if (nodeShape.targetClass) d.targetClass = nodeShape.targetClass;\n if (nodeShape.description) d.description = nodeShape.description;\n if (nodeShape.extends) d.extends = nodeShape.extends;\n if (nodeShape.dependent) d.dependent = true;\n if (nodeShape.closed !== undefined) d.closed = nodeShape.closed;\n if (nodeShape.ignoredProperties && nodeShape.ignoredProperties.length) {\n d.ignoredProperties = nodeShape.ignoredProperties;\n }\n // Flatten: emit own + inherited property shapes (deduped by label) under this shape.\n d.properties = getUniquePropertyShapes(nodeShape).map((ps) =>\n buildPropertyShapeData(ps, shapeIri),\n );\n // The single-owner constraints of the shape's own `contains` properties, owned through\n // `core:generatedShape` and written nested in the same create with their fixed IRIs.\n const generated = buildSingleOwnerConstraints(nodeShape, shapeIri);\n if (generated.length > 0) {\n d.generatedShapes = generated.map(({iri, data}) => ({__id: iri, ...data}));\n }\n return d;\n}\n\n/**\n * The per-shape `delete → create` thunk shared by `syncShapes()` and `syncShape()`: exactly two\n * requests. The delete cascade-cleans the shape's old property-shape / list / path subtrees; the\n * create rebuilds them.\n *\n * The single-owner constraints generated from the shape's own `contains` properties\n * (`${iri}/${label}.singleOwner`, see `singleOwnerConstraint.ts`) are owned by the shape through\n * `core:generatedShape` (a `contains` property with value shape NodeShape). The delete removes\n * them and their property shapes through the owned walk, and their blank path and qualified-shape\n * nodes through the blank-node cascade; the create writes the current ones nested, in the same request\n * as the shape. A removed or renamed accessor therefore loses its constraint on the next sync,\n * and a NodeShape the shape does not own is never touched.\n *\n * `ds` (optional) targets an explicit dataset — both requests run against it instead of the\n * global router. Omitted → today's global behavior.\n */\nfunction buildSyncThunk(nodeShape: NodeShapeData, iri: string, ds?: IDataset): () => Promise<void> {\n return async () => {\n // Build the data fresh on each invocation, so every run writes from the current\n // shape metadata.\n const data = buildNodeShapeData(nodeShape, iri);\n await DeleteBuilder.from(NodeShape, {id: iri}).exec(ds);\n await (NodeShape.create(data as never).withId(iri) as unknown as {\n exec: (target?: IDataset) => Promise<unknown>;\n }).exec(ds);\n };\n}\n\n/** The generated single-owner shape IRIs a code shape writes. */\nfunction generatedIrisOf(nodeShape: NodeShapeData, iri: string): string[] {\n return buildSingleOwnerConstraints(nodeShape, iri).map((c) => c.iri);\n}\n\n/** Throw when a registered shape IRI equals one of a shape's generated single-owner IRIs. */\nfunction assertNoGeneratedIriCollision(\n nodeShape: NodeShapeData,\n iri: string,\n isRegistered: (iri: string) => boolean,\n): void {\n for (const generated of generatedIrisOf(nodeShape, iri)) {\n if (isRegistered(generated)) {\n throw new Error(`Shape IRI ${generated} collides with a generated single-owner shape`);\n }\n }\n}\n\n/** Options controlling how {@link syncShapes} treats store shapes that are not in code. */\nexport interface SyncShapesOptions {\n /**\n * How to treat NodeShapes present in the store but not in the current process's code:\n * - `'none'` (default) — never prune; purely additive (delete→recreate the code shapes, touch\n * nothing else). The only mode that is safe when the process has loaded a *subset* of the\n * shapes in the dataset, or when the dataset has more than one writer.\n * - `'all'` — prune every store-only shape as an orphan. Only correct when this process has\n * loaded **every** shape the dataset should hold and is its **only** writer; \"not registered\n * here\" is then taken to mean \"deleted from code\".\n * - `'ownedNamespaces'` — prune a store-only shape ONLY if its IRI falls within a namespace\n * this process actually writes (the package-namespace of one of its code-registered shapes,\n * i.e. the IRI prefix up to the last `/` or `#`). Protects other writers' packages (arch-04:\n * one app-data dataset, several writers), but NOT the rest of a package the process only\n * partially loaded — the namespace is the whole package, so a deep import of one shape file\n * still claims all of it.\n *\n * `'all'` and `'ownedNamespaces'` are destructive and must be requested explicitly.\n */\n orphanScope?: 'all' | 'ownedNamespaces' | 'none';\n}\n\n/** The owning namespace of a shape IRI — everything up to and including the last `/` or `#`. */\nfunction shapeNamespace(iri: string): string {\n const cut = Math.max(iri.lastIndexOf('/'), iri.lastIndexOf('#'));\n return cut >= 0 ? iri.slice(0, cut + 1) : iri;\n}\n\n/**\n * Plan an idempotent sync of all code-registered (non-framework) NodeShapes into the store as\n * SHACL data — the forward (code → graph) materialization of arch-05 \"code is canonical\".\n *\n * Returns built-but-unexecuted thunks; the caller controls execution and batching:\n * ```ts\n * await Promise.all((await syncShapes()).map((run) => run()));\n * ```\n * Each in-code shape's thunk runs `delete → create` in order (the delete cascade-cleans the old\n * property shapes / list / path subtrees via the containment cascade, the create rebuilds them).\n *\n * **Shapes in the store but not registered in this process are left alone by default**\n * (`orphanScope: 'none'`). The registry only holds what this process happened to import, and\n * that is routinely a subset of what the dataset holds: shapes are deep-importable one file at a\n * time, and an app-data dataset also holds shapes other writers materialized (CN's\n * `bindShape` / `enableCapability`). \"Not registered here\" therefore does not mean \"deleted from\n * code\". Measured against a dataset holding the 36 `@_linked/schema` shapes: a process that\n * imported only `@_linked/schema/shapes/Thing` (11 shapes registered) deleted the other 25 under\n * both `'all'` and `'ownedNamespaces'`, and a process that imported no shapes deleted all 36.\n *\n * Pruning is opt-in via `options.orphanScope` (see {@link SyncShapesOptions}): pass `'all'` only\n * from a process that has loaded every shape the dataset should hold and is its sole writer, and\n * `'ownedNamespaces'` only when it has loaded every shape of each package it registers. Pruned\n * orphans are deleted with their owned subtree. Pruning reads the existing shape IRIs first;\n * with `'none'` no read is made.\n *\n * `ds` (optional) targets an explicit dataset instead of the global router. It is a **plan-time**\n * parameter: it feeds both the orphan-detection read (so orphans are computed against the same\n * store they'll be pruned from) and every delete/create thunk. Omitted → the global router;\n * the returned thunks stay nullary either way.\n */\nexport async function syncShapes(\n ds?: IDataset,\n options?: SyncShapesOptions,\n): Promise<Array<() => Promise<void>>> {\n const orphanScope = options?.orphanScope ?? 'none';\n // 1. Enumerate code-registered user shapes (exclude framework/meta shapes).\n const userShapes: Array<{iri: string; nodeShape: NodeShapeData}> = [];\n for (const [iri, shapeClass] of getAllShapeClasses()) {\n if (!shapeClass?.shape) continue;\n const pkg = (shapeClass as {packageName?: string}).packageName;\n // Skip framework/meta shapes: the core package, and the base `Shape` which is registered\n // directly (no applyLinkedShape) and therefore carries no packageName.\n if (!pkg || pkg === FRAMEWORK_PACKAGE) continue;\n userShapes.push({iri, nodeShape: shapeClass.shape});\n }\n const localShapeIris = new Set(userShapes.map((s) => s.iri));\n for (const {iri, nodeShape} of userShapes) {\n assertNoGeneratedIriCollision(nodeShape, iri, (id) => localShapeIris.has(id));\n }\n // The generated single-owner shapes of in-code shapes are written by the same thunks, so they\n // are never orphans. Derived in memory from the same function that builds them; a stale\n // generated shape (removed accessor or shape) is not protected and goes with its owner's\n // delete, through the `core:generatedShape` cascade.\n for (const {iri, nodeShape} of userShapes) {\n for (const generated of generatedIrisOf(nodeShape, iri)) localShapeIris.add(generated);\n }\n // Namespaces this process actually writes — the ownership scope for `'ownedNamespaces'` pruning.\n const ownedNamespaces = new Set(userShapes.map((s) => shapeNamespace(s.iri)));\n\n const thunks: Array<() => Promise<void>> = [];\n\n // 2. Per in-code shape: delete (cascade) then recreate.\n for (const {iri, nodeShape} of userShapes) {\n thunks.push(buildSyncThunk(nodeShape, iri, ds));\n }\n\n // 3. Orphan sweep (unless disabled). Identity-read existing NodeShape IRIs (ids only). Must hit\n // the same `ds` the thunks target, or orphans get computed against the wrong store.\n if (orphanScope !== 'none') {\n const existingRows = (await (NodeShape as unknown as {\n select: () => {exec: (target?: IDataset) => Promise<Array<{id: string}>>};\n }).select().exec(ds)) as Array<{id: string}>;\n\n // Orphan shapes (in store, not in code) → delete (cascade cleans their owned subtree). In\n // `'ownedNamespaces'` mode, only shapes within a namespace this process writes are eligible —\n // other writers' shapes (different package namespace) are left untouched.\n for (const {id: iri} of existingRows) {\n if (localShapeIris.has(iri)) continue;\n if (orphanScope === 'ownedNamespaces' && !ownedNamespaces.has(shapeNamespace(iri))) {\n continue;\n }\n thunks.push(() =>\n (DeleteBuilder.from(NodeShape, {id: iri}).exec(ds) as Promise<unknown>).then(\n () => undefined,\n ),\n );\n }\n }\n\n return thunks;\n}\n\n/**\n * Plan an idempotent sync of ONE code-registered NodeShape into the store as SHACL data.\n *\n * Scoped counterpart to {@link syncShapes}: materializes a single shape (delete → recreate, so the\n * delete cascade-cleans the old property-shape / list / path subtrees and the create rebuilds them)\n * and does NOT run the store-wide orphan sweep — other shapes in the store are untouched.\n *\n * @param target a shape class (e.g. `Person`) or its NodeShape IRI string. An IRI is resolved to\n * its registered class via `getShapeClass`; the shape must be code-registered.\n * @param ds optional explicit dataset to materialize into (a store or a router) — both the delete\n * and the create run against it instead of the global router. Omitted → global behavior.\n * @returns a single unexecuted thunk (consistent with {@link syncShapes}), so callers can batch\n * several together — e.g. a shape plus its referenced object-property shapes:\n * `await Promise.all([syncShape(Person), syncShape(Address)].map((run) => run()))`.\n * @throws if `target` is a framework/meta shape, is not registered, or has no `.shape`.\n */\nexport function syncShape(target: typeof Shape | string, ds?: IDataset): () => Promise<void> {\n // Normalize target → its registered shape class.\n const cls =\n typeof target === 'string' ? getShapeClass(target) : (target as typeof Shape);\n if (typeof target === 'string' && !cls) {\n throw new Error(`syncShape: no registered shape for IRI ${target}`);\n }\n const nodeShape = cls?.shape;\n if (!nodeShape) {\n throw new Error(\n typeof target === 'string'\n ? `syncShape: no registered shape for IRI ${target}`\n : `syncShape: shape class has no static .shape`,\n );\n }\n const iri = nodeShape.id;\n\n // Never materialize a framework/meta shape as user data — same invariant syncShapes() enforces\n // by skipping (the base Shape carries no packageName).\n const pkg = (cls as {packageName?: string}).packageName;\n if (!pkg || pkg === FRAMEWORK_PACKAGE) {\n throw new Error(`syncShape: refusing to sync framework/meta shape ${iri}`);\n }\n\n assertNoGeneratedIriCollision(nodeShape, iri, (id) => !!getShapeClass(id));\n return buildSyncThunk(nodeShape, iri, ds);\n}\n"]}
@@ -55,10 +55,10 @@ export class ShapeValidationError extends Error {
55
55
  }
56
56
  /**
57
57
  * Keys that carry metadata about the node rather than a property value: `id` /
58
- * `__id` name it, `__blank` asks for an anonymous node, and `shape` names the shape of a nested value when the
58
+ * `__id` name it, and `shape` names the shape of a nested value when the
59
59
  * property shape itself doesn't declare one (see `convertUpdateValue`).
60
60
  */
61
- const RESERVED_KEYS = new Set(['id', '__id', '__blank', 'shape']);
61
+ const RESERVED_KEYS = new Set(['id', '__id', 'shape']);
62
62
  /**
63
63
  * The message for an undeclared property key. Shared with the normalization path
64
64
  * in `MutationQuery`, which needs the same guard to build a field at all.