@_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.
Files changed (38) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +20 -2
  3. package/lib/esm/queries/IRMutation.js +1 -1
  4. package/lib/esm/queries/IRMutation.js.map +1 -1
  5. package/lib/esm/queries/IntermediateRepresentation.d.ts +5 -0
  6. package/lib/esm/queries/IntermediateRepresentation.js.map +1 -1
  7. package/lib/esm/queries/MutationQuery.js +15 -0
  8. package/lib/esm/queries/MutationQuery.js.map +1 -1
  9. package/lib/esm/queries/MutationSerialization.d.ts +5 -4
  10. package/lib/esm/queries/MutationSerialization.js +9 -1
  11. package/lib/esm/queries/MutationSerialization.js.map +1 -1
  12. package/lib/esm/queries/QueryFactory.d.ts +6 -0
  13. package/lib/esm/queries/QueryFactory.js.map +1 -1
  14. package/lib/esm/queries/SelectQuery.js +3 -1
  15. package/lib/esm/queries/SelectQuery.js.map +1 -1
  16. package/lib/esm/queries/lowerMutationJSON.js +6 -1
  17. package/lib/esm/queries/lowerMutationJSON.js.map +1 -1
  18. package/lib/esm/shapes/List.d.ts +1 -0
  19. package/lib/esm/shapes/List.js.map +1 -1
  20. package/lib/esm/shapes/PathNode.d.ts +5 -2
  21. package/lib/esm/shapes/PathNode.js +5 -2
  22. package/lib/esm/shapes/PathNode.js.map +1 -1
  23. package/lib/esm/shapes/serializePathToNodeData.d.ts +17 -5
  24. package/lib/esm/shapes/serializePathToNodeData.js +41 -27
  25. package/lib/esm/shapes/serializePathToNodeData.js.map +1 -1
  26. package/lib/esm/shapes/singleOwnerConstraint.js +1 -1
  27. package/lib/esm/shapes/singleOwnerConstraint.js.map +1 -1
  28. package/lib/esm/shapes/syncShapes.js +1 -1
  29. package/lib/esm/shapes/syncShapes.js.map +1 -1
  30. package/lib/esm/shapes/validation.js +2 -2
  31. package/lib/esm/shapes/validation.js.map +1 -1
  32. package/lib/esm/sparql/SparqlAlgebra.d.ts +5 -0
  33. package/lib/esm/sparql/SparqlAlgebra.js.map +1 -1
  34. package/lib/esm/sparql/algebraToString.js +2 -0
  35. package/lib/esm/sparql/algebraToString.js.map +1 -1
  36. package/lib/esm/sparql/irToAlgebra.js +113 -17
  37. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  38. 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 URI used for this node and all generated triples.
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
- const subjectTerm = iriTerm(uri);
1432
- // Type triple — resolve the SHACL NodeShapeData id in the IR to the ontology
1433
- // targetClass URI (sibling of PR #77's SELECT-side resolveShapeScanIri).
1434
- // Mutations were not covered by #77; see mutation-uri-fidelity.test.ts.
1435
- triples.push(tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(data.shape))));
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
- const nested = generateNodeDataTriples(item, options);
1449
- triples.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
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
- const nested = generateNodeDataTriples(field.value, options);
1464
- triples.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
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 { uri, triples };
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
- const { triples } = generateNodeDataTriples(query.data, options);
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, iriTerm(nested.uri)));
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, iriTerm(nested.uri)));
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, iriTerm(nested.uri)));
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
  /**