@_linked/core 2.29.0 → 2.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +20 -2
- package/lib/esm/queries/IRMutation.js +1 -1
- package/lib/esm/queries/IRMutation.js.map +1 -1
- package/lib/esm/queries/IntermediateRepresentation.d.ts +5 -0
- package/lib/esm/queries/IntermediateRepresentation.js.map +1 -1
- package/lib/esm/queries/MutationQuery.js +15 -0
- package/lib/esm/queries/MutationQuery.js.map +1 -1
- package/lib/esm/queries/MutationSerialization.d.ts +5 -4
- package/lib/esm/queries/MutationSerialization.js +9 -1
- package/lib/esm/queries/MutationSerialization.js.map +1 -1
- package/lib/esm/queries/QueryFactory.d.ts +6 -0
- package/lib/esm/queries/QueryFactory.js.map +1 -1
- package/lib/esm/queries/SelectQuery.js +3 -1
- package/lib/esm/queries/SelectQuery.js.map +1 -1
- package/lib/esm/queries/lowerMutationJSON.js +6 -1
- package/lib/esm/queries/lowerMutationJSON.js.map +1 -1
- package/lib/esm/shapes/List.d.ts +1 -0
- package/lib/esm/shapes/List.js.map +1 -1
- package/lib/esm/shapes/PathNode.d.ts +5 -2
- package/lib/esm/shapes/PathNode.js +5 -2
- package/lib/esm/shapes/PathNode.js.map +1 -1
- package/lib/esm/shapes/serializePathToNodeData.d.ts +17 -5
- package/lib/esm/shapes/serializePathToNodeData.js +41 -27
- package/lib/esm/shapes/serializePathToNodeData.js.map +1 -1
- package/lib/esm/shapes/singleOwnerConstraint.js +1 -1
- package/lib/esm/shapes/singleOwnerConstraint.js.map +1 -1
- package/lib/esm/shapes/syncShapes.js +1 -1
- package/lib/esm/shapes/syncShapes.js.map +1 -1
- package/lib/esm/shapes/validation.js +2 -2
- package/lib/esm/shapes/validation.js.map +1 -1
- package/lib/esm/sparql/SparqlAlgebra.d.ts +5 -0
- package/lib/esm/sparql/SparqlAlgebra.js.map +1 -1
- package/lib/esm/sparql/algebraToString.js +2 -0
- package/lib/esm/sparql/algebraToString.js.map +1 -1
- package/lib/esm/sparql/irToAlgebra.js +113 -17
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/package.json +2 -2
|
@@ -1421,18 +1421,65 @@ function fieldValueToTerms(value, options, datatype) {
|
|
|
1421
1421
|
}
|
|
1422
1422
|
return [];
|
|
1423
1423
|
}
|
|
1424
|
+
/**
|
|
1425
|
+
* The RDF-collection and SHACL-path predicates the cascade's blank-node block walks inside a
|
|
1426
|
+
* blank structure: the syntax core writes as blank nodes (`serializePathToNodeData`).
|
|
1427
|
+
*/
|
|
1428
|
+
const BLANK_SYNTAX_PREDICATES = [
|
|
1429
|
+
rdf.first.id,
|
|
1430
|
+
rdf.rest.id,
|
|
1431
|
+
shacl.inversePath.id,
|
|
1432
|
+
shacl.alternativePath.id,
|
|
1433
|
+
shacl.zeroOrMorePath.id,
|
|
1434
|
+
shacl.oneOrMorePath.id,
|
|
1435
|
+
shacl.zeroOrOnePath.id,
|
|
1436
|
+
];
|
|
1437
|
+
/**
|
|
1438
|
+
* A blank node is removed only by the cascade's blank-node block, which reaches it through a
|
|
1439
|
+
* `contains` edge, or, inside a blank structure, through a list / path syntax predicate
|
|
1440
|
+
* (`rdf:first` of a blank list cell). A blank node held any other way could never be deleted:
|
|
1441
|
+
* refuse to write one.
|
|
1442
|
+
*/
|
|
1443
|
+
function assertBlankIsOwned(data, propertyId, propertyTerm, parentIsBlank) {
|
|
1444
|
+
var _a;
|
|
1445
|
+
if (!data.blank || ((_a = findPropertyShapeById(propertyId)) === null || _a === void 0 ? void 0 : _a.contains))
|
|
1446
|
+
return;
|
|
1447
|
+
const syntaxEdge = parentIsBlank && propertyTerm.kind === 'iri' && BLANK_SYNTAX_PREDICATES.includes(propertyTerm.value);
|
|
1448
|
+
if (syntaxEdge)
|
|
1449
|
+
return;
|
|
1450
|
+
throw new Error(`A blank nested node (__blank) must be the value of a \`contains\` property, or of a list / ` +
|
|
1451
|
+
`path syntax predicate inside a blank node; ${propertyId} is neither, so nothing would ` +
|
|
1452
|
+
`ever delete it.`);
|
|
1453
|
+
}
|
|
1424
1454
|
/**
|
|
1425
1455
|
* Recursively generate triples for an IRNodeData (used in create and nested creates).
|
|
1426
|
-
* Returns the
|
|
1456
|
+
* Returns the term used for this node and all generated triples.
|
|
1457
|
+
*
|
|
1458
|
+
* A node marked `blank` (nested only, see `NodeDescriptionValue.__blank`) becomes a fresh
|
|
1459
|
+
* blank node with its field triples and NO `rdf:type`: it is RDF syntax, like a SHACL path
|
|
1460
|
+
* operator, which the spec defines as a blank node that is the subject of exactly one triple.
|
|
1461
|
+
* Without a `blanks` minter (the update paths) a blank node throws.
|
|
1427
1462
|
*/
|
|
1428
|
-
function generateNodeDataTriples(data, options) {
|
|
1429
|
-
const uri = data.id || generateEntityUri(data.shape, options);
|
|
1463
|
+
function generateNodeDataTriples(data, options, blanks) {
|
|
1430
1464
|
const triples = [];
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1465
|
+
let subjectTerm;
|
|
1466
|
+
if (data.blank) {
|
|
1467
|
+
if (data.id) {
|
|
1468
|
+
throw new Error(`A blank nested node (__blank) cannot carry an id (${data.id}).`);
|
|
1469
|
+
}
|
|
1470
|
+
if (!blanks) {
|
|
1471
|
+
throw new Error('A blank nested node (__blank) can only be written by a create. An update writes its ' +
|
|
1472
|
+
'INSERT once per WHERE solution, which would duplicate the blank node.');
|
|
1473
|
+
}
|
|
1474
|
+
subjectTerm = { kind: 'blank', label: `b${blanks.next++}` };
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
subjectTerm = iriTerm(data.id || generateEntityUri(data.shape, options));
|
|
1478
|
+
// Type triple — resolve the SHACL NodeShapeData id in the IR to the ontology
|
|
1479
|
+
// targetClass URI (sibling of PR #77's SELECT-side resolveShapeScanIri).
|
|
1480
|
+
// Mutations were not covered by #77; see mutation-uri-fidelity.test.ts.
|
|
1481
|
+
triples.push(tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(data.shape))));
|
|
1482
|
+
}
|
|
1436
1483
|
// Field triples
|
|
1437
1484
|
for (const field of data.fields) {
|
|
1438
1485
|
// Resolve the SHACL PropertyShapeData id to its declared `path` URI.
|
|
@@ -1445,8 +1492,9 @@ function generateNodeDataTriples(data, options) {
|
|
|
1445
1492
|
for (const item of field.value) {
|
|
1446
1493
|
if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
|
|
1447
1494
|
// Nested create
|
|
1448
|
-
|
|
1449
|
-
|
|
1495
|
+
assertBlankIsOwned(item, field.property, propertyTerm, !!data.blank);
|
|
1496
|
+
const nested = generateNodeDataTriples(item, options, blanks);
|
|
1497
|
+
triples.push(tripleOf(subjectTerm, propertyTerm, nested.term));
|
|
1450
1498
|
triples.push(...nested.triples);
|
|
1451
1499
|
}
|
|
1452
1500
|
else {
|
|
@@ -1460,8 +1508,9 @@ function generateNodeDataTriples(data, options) {
|
|
|
1460
1508
|
}
|
|
1461
1509
|
// Handle nested IRNodeData
|
|
1462
1510
|
if (typeof field.value === 'object' && 'shape' in field.value && 'fields' in field.value) {
|
|
1463
|
-
|
|
1464
|
-
|
|
1511
|
+
assertBlankIsOwned(field.value, field.property, propertyTerm, !!data.blank);
|
|
1512
|
+
const nested = generateNodeDataTriples(field.value, options, blanks);
|
|
1513
|
+
triples.push(tripleOf(subjectTerm, propertyTerm, nested.term));
|
|
1465
1514
|
triples.push(...nested.triples);
|
|
1466
1515
|
continue;
|
|
1467
1516
|
}
|
|
@@ -1471,13 +1520,16 @@ function generateNodeDataTriples(data, options) {
|
|
|
1471
1520
|
triples.push(tripleOf(subjectTerm, propertyTerm, term));
|
|
1472
1521
|
}
|
|
1473
1522
|
}
|
|
1474
|
-
return {
|
|
1523
|
+
return { term: subjectTerm, triples };
|
|
1475
1524
|
}
|
|
1476
1525
|
/**
|
|
1477
1526
|
* Converts an IRCreateMutation to a SparqlInsertDataPlan.
|
|
1478
1527
|
*/
|
|
1479
1528
|
export function createToAlgebra(query, options) {
|
|
1480
|
-
|
|
1529
|
+
if (query.data.blank) {
|
|
1530
|
+
throw new Error('The root node of a create cannot be blank (__blank): it must be addressable.');
|
|
1531
|
+
}
|
|
1532
|
+
const { triples } = generateNodeDataTriples(query.data, options, { next: 0 });
|
|
1481
1533
|
return {
|
|
1482
1534
|
type: 'insert_data',
|
|
1483
1535
|
triples,
|
|
@@ -1568,7 +1620,7 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1568
1620
|
for (const addItem of setMod.add) {
|
|
1569
1621
|
if (addItem && typeof addItem === 'object' && 'shape' in addItem && 'fields' in addItem) {
|
|
1570
1622
|
const nested = generateNodeDataTriples(addItem, options);
|
|
1571
|
-
insertPatterns.push(tripleOf(subjectTerm, propertyTerm,
|
|
1623
|
+
insertPatterns.push(tripleOf(subjectTerm, propertyTerm, nested.term));
|
|
1572
1624
|
insertPatterns.push(...nested.triples);
|
|
1573
1625
|
}
|
|
1574
1626
|
else {
|
|
@@ -1606,7 +1658,7 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1606
1658
|
for (const item of field.value) {
|
|
1607
1659
|
if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
|
|
1608
1660
|
const nested = generateNodeDataTriples(item, options);
|
|
1609
|
-
insertPatterns.push(tripleOf(subjectTerm, propertyTerm,
|
|
1661
|
+
insertPatterns.push(tripleOf(subjectTerm, propertyTerm, nested.term));
|
|
1610
1662
|
insertPatterns.push(...nested.triples);
|
|
1611
1663
|
}
|
|
1612
1664
|
else {
|
|
@@ -1624,7 +1676,7 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1624
1676
|
deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
|
|
1625
1677
|
oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
|
|
1626
1678
|
const nested = generateNodeDataTriples(field.value, options);
|
|
1627
|
-
insertPatterns.push(tripleOf(subjectTerm, propertyTerm,
|
|
1679
|
+
insertPatterns.push(tripleOf(subjectTerm, propertyTerm, nested.term));
|
|
1628
1680
|
insertPatterns.push(...nested.triples);
|
|
1629
1681
|
continue;
|
|
1630
1682
|
}
|
|
@@ -2080,6 +2132,50 @@ export function buildOwnedCascade(rootTerm, varPrefix, anchor, keep, onlyTypes)
|
|
|
2080
2132
|
],
|
|
2081
2133
|
}, rootTerm, keep));
|
|
2082
2134
|
});
|
|
2135
|
+
// Blank nodes carry no type (a SHACL path operator must be the subject of exactly one
|
|
2136
|
+
// triple), so the typed blocks above cannot see them. Every blank node owned through
|
|
2137
|
+
// `contains` edges (`?b0`) is deleted, together with the RDF-list / SHACL-path syntax below it
|
|
2138
|
+
// (`?bn`). That inner walk follows `rdf:first` too, because a sequence member that is itself a
|
|
2139
|
+
// path operator is held by `rdf:first`, which is not a `contains` edge. It is restricted to the
|
|
2140
|
+
// syntax predicates on purpose: SPARQL cannot require the nodes *between* `?b0` and `?bn` to be
|
|
2141
|
+
// blank, so following every `contains` edge there would let a list's `rdf:first` lead into an
|
|
2142
|
+
// entity it merely lists and delete that entity's blank children.
|
|
2143
|
+
const insidePathExpr = { zeroOrMore: { alt: BLANK_SYNTAX_PREDICATES.map((id) => ({ id })) } };
|
|
2144
|
+
const insidePathTerm = {
|
|
2145
|
+
kind: 'path',
|
|
2146
|
+
value: pathExprToSparql(insidePathExpr),
|
|
2147
|
+
uris: collectPathUris(insidePathExpr),
|
|
2148
|
+
};
|
|
2149
|
+
const ownedBlank = varTerm(`${varPrefix}bn0`);
|
|
2150
|
+
const blank = varTerm(`${varPrefix}bn`);
|
|
2151
|
+
const bp = varTerm(`${varPrefix}bnp`);
|
|
2152
|
+
const bo = varTerm(`${varPrefix}bno`);
|
|
2153
|
+
const isBlank = (name) => ({
|
|
2154
|
+
kind: 'function_expr',
|
|
2155
|
+
name: 'isBlank',
|
|
2156
|
+
args: [{ kind: 'variable_expr', name }],
|
|
2157
|
+
});
|
|
2158
|
+
deletePatterns.push(tripleOf(blank, bp, bo));
|
|
2159
|
+
whereOptionals.push(keepFilter({
|
|
2160
|
+
type: 'filter',
|
|
2161
|
+
expression: isBlank(`${varPrefix}bn`),
|
|
2162
|
+
inner: {
|
|
2163
|
+
type: 'join',
|
|
2164
|
+
// Filter the owned blank nodes before the inner walk runs from each of them.
|
|
2165
|
+
left: {
|
|
2166
|
+
type: 'filter',
|
|
2167
|
+
expression: isBlank(`${varPrefix}bn0`),
|
|
2168
|
+
inner: {
|
|
2169
|
+
type: 'bgp',
|
|
2170
|
+
triples: [...(anchor ? [anchor] : []), tripleOf(rootTerm, pathTerm, ownedBlank)],
|
|
2171
|
+
},
|
|
2172
|
+
},
|
|
2173
|
+
right: {
|
|
2174
|
+
type: 'bgp',
|
|
2175
|
+
triples: [tripleOf(ownedBlank, insidePathTerm, blank), tripleOf(blank, bp, bo)],
|
|
2176
|
+
},
|
|
2177
|
+
},
|
|
2178
|
+
}, rootTerm, keep));
|
|
2083
2179
|
return { deletePatterns, whereOptionals };
|
|
2084
2180
|
}
|
|
2085
2181
|
/**
|