@_linked/core 2.30.0 → 2.31.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 (37) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +54 -7
  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 +0 -5
  6. package/lib/esm/queries/IntermediateRepresentation.js.map +1 -1
  7. package/lib/esm/queries/MutationQuery.js +9 -19
  8. package/lib/esm/queries/MutationQuery.js.map +1 -1
  9. package/lib/esm/queries/MutationSerialization.d.ts +6 -5
  10. package/lib/esm/queries/MutationSerialization.js +30 -13
  11. package/lib/esm/queries/MutationSerialization.js.map +1 -1
  12. package/lib/esm/queries/QueryFactory.d.ts +0 -6
  13. package/lib/esm/queries/QueryFactory.js.map +1 -1
  14. package/lib/esm/queries/lowerMutationJSON.js +1 -6
  15. package/lib/esm/queries/lowerMutationJSON.js.map +1 -1
  16. package/lib/esm/shapes/List.d.ts +0 -1
  17. package/lib/esm/shapes/List.js.map +1 -1
  18. package/lib/esm/shapes/PathList.d.ts +27 -0
  19. package/lib/esm/shapes/PathList.js +63 -0
  20. package/lib/esm/shapes/PathList.js.map +1 -0
  21. package/lib/esm/shapes/PathNode.d.ts +10 -9
  22. package/lib/esm/shapes/PathNode.js +21 -13
  23. package/lib/esm/shapes/PathNode.js.map +1 -1
  24. package/lib/esm/shapes/index.d.ts +1 -0
  25. package/lib/esm/shapes/index.js +1 -0
  26. package/lib/esm/shapes/index.js.map +1 -1
  27. package/lib/esm/shapes/serializePathToNodeData.d.ts +12 -11
  28. package/lib/esm/shapes/serializePathToNodeData.js +12 -11
  29. package/lib/esm/shapes/serializePathToNodeData.js.map +1 -1
  30. package/lib/esm/shapes/validation.js +16 -12
  31. package/lib/esm/shapes/validation.js.map +1 -1
  32. package/lib/esm/sparql/irToAlgebra.d.ts +7 -2
  33. package/lib/esm/sparql/irToAlgebra.js +415 -181
  34. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  35. package/lib/esm/utils/Package.js +6 -3
  36. package/lib/esm/utils/Package.js.map +1 -1
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.31.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#336](https://github.com/linked-fw/core/pull/336) [`742d0a2`](https://github.com/linked-fw/core/commit/742d0a24496a84cdb6c14cb8eb6b9e705998df33) Thanks [@flyon](https://github.com/flyon)! - A nested create is now written as a blank node when the **property** that holds it allows blank nodes through its `nodeKind`. This replaces the `__blank: true` node-data key added in 2.30.0, which is removed.
8
+
9
+ **Minting follows the property's `nodeKind`:**
10
+
11
+ | `nodeKind` | nested value without an id | with an id |
12
+ |---|---|---|
13
+ | unset, `shacl.IRI` (the `objectProperty` default), `shacl.IRIOrLiteral` | IRI (as before) | IRI |
14
+ | `shacl.BlankNodeOrIRI` | blank node | IRI |
15
+ | `shacl.BlankNode`, `shacl.BlankNodeOrLiteral` | blank node | error |
16
+ | `shacl.Literal` | error | error |
17
+
18
+ ```ts
19
+ @objectProperty({path: ex.geo, shape: GeoPoint, maxCount: 1, nodeKind: shacl.BlankNodeOrIRI})
20
+ declare geo: GeoPoint;
21
+
22
+ // <place1> ex:geo _:b0 . _:b0 ex:lat "52.1" .
23
+ await Place.create({geo: {lat: '52.1'}}).withId(place1);
24
+ ```
25
+
26
+ A blank node is written with its field triples only, without `rdf:type`. Nothing about it is sent in DSL-JSON: the side that runs the mutation decides from its own shapes. **Upgrade the side that lowers mutations (the server) first:** a 2.30.x server lowering DSL-JSON from this version writes complex `sh:path` values as IRI nodes again, since the `__blank` key it read is no longer sent.
27
+
28
+ **Blank values are deleted with what holds them.** Deleting the subject (by id, `.all()`, `.where()`), replacing or unsetting the property, deleting or replacing an owned (`contains`) child, and the containment cascade delete the blank value and the blank nodes below it, following only blank-capable properties. Only blank nodes are deleted, never an IRI the structure refers to, and `contains` is no longer needed for it. This replaces the `contains`-based blank-node cleanup of 2.30.0 and the bulk deletes' blank-node cleanup.
29
+
30
+ **Refused with an error** (not supported yet): an `update` / `upsert` / `updateWhere` whose new value would be a blank node, and an id of the form `_:…` used to link, `update().for()` or delete by id.
31
+
32
+ **Shape sync is unchanged.** `PropertyShape.path` and the path operator slots are declared `shacl.BlankNodeOrIRI`, and path lists use a new `PathList` shape (`@_linked/core/shapes/PathList`), so sync writes the same blank `sh:path` structure as 2.30.0, byte for byte (pinned by a test against the 2.30.0 output). `PathNode.alternativePath` now declares `PathList` as its value shape; `List`, `rdfList()` and `sh:in` lists are unchanged.
33
+
34
+ **Removed:** `__blank` on `NodeDescriptionValue`, `IRNodeData.blank`, and the `__blank` DSL-JSON key. Only shape sync used it. A `__blank` key in mutation data is now rejected as an undeclared property.
35
+
36
+ See README "Blank-node values (`nodeKind`)" and `docs/architecture/owned-values.md`.
37
+
38
+ ## 2.30.1
39
+
40
+ ### Patch Changes
41
+
42
+ - [#334](https://github.com/linked-fw/core/pull/334) [`d3e5d5f`](https://github.com/linked-fw/core/commit/d3e5d5f485aa6d7048d0ffdb28639e802719908d) Thanks [@flyon](https://github.com/flyon)! - A mutation sent as DSL-JSON now keeps the concrete shape of its nested values, so it validates and writes the same as when it runs in-process.
43
+
44
+ **What was wrong.** A nested value whose shape differs from the property's declared value shape is sent with `__shape`. `fromJSON()` and `lowerMutationJSON()` read that shape to decode the value's fields, then dropped it, and the mutation was checked against the declared shape instead:
45
+
46
+ - a subclass value under a base-typed property was rejected because of its subclass-only fields (`Invalid property key: department. The shape Person does not have …`);
47
+ - any nested value under a property that declares no value shape, such as `PropertyShape.path`, was rejected with `the property declares no shape for its values`. A shape sync that reaches its dataset over the mutation wire could not write an inverse, alternative or `*`/`+`/`?` path.
48
+
49
+ **What works now.** The decoded value carries its shape as the reserved `shape` key, and both decode paths validate and lower it against that shape:
50
+
51
+ ```ts
52
+ Person.create({friends: [{shape: Employee, name: 'Eve', department: 'Research'}]});
53
+ ```
54
+
55
+ That form now also works in-process: the `shape` key of a nested value may name a subclass of the property's declared shape, where before it had to be the declared shape itself. A `shape` that is not the declared shape or a subclass of it is still rejected. A `__shape` naming a shape the receiver has not registered now fails in `fromJSON()` too, as it already did in `lowerMutationJSON()`, instead of quietly validating against the declared shape.
56
+
3
57
  ## 2.30.0
4
58
 
5
59
  ### Minor Changes
package/README.md CHANGED
@@ -357,7 +357,8 @@ constraint at `Thing`, not one per subclass. It is marked `core:generated true`
357
357
 
358
358
  The inverse path is stored as an untyped blank node, as is every complex `sh:path` shape sync
359
359
  writes (inverse, alternative, sequence, `*`/`+`/`?`): an IRI in `sh:path` is a predicate path to a
360
- SHACL validator. Versions before 2.30 wrote IRI-named nodes (`…/owners/inv`); the next sync
360
+ SHACL validator. Nothing marks them: `PropertyShape.path` and the path operator slots are
361
+ declared `sh:BlankNodeOrIRI`, see [Blank-node values](#blank-node-values-nodekind). Versions before 2.30 wrote IRI-named nodes (`…/owners/inv`); the next sync
361
362
  replaces them, except an operator node inside a sequence (`…/seq/0/inv`), which is left
362
363
  unreferenced. To remove those:
363
364
 
@@ -367,13 +368,10 @@ DELETE { ?n ?p ?o } WHERE {
367
368
  FILTER(isIRI(?n)) FILTER NOT EXISTS { ?x ?y ?n }
368
369
  }
369
370
  ```
370
- Read a stored path's structure in the same query that finds it (`?ps sh:path ?p .
371
+
372
+ Read a stored path's structure in the same query that finds it (`?ps sh:path ?p .
371
373
  ?p sh:inversePath ?x`): a blank node's label is not stable across queries.
372
374
 
373
- Nested node-data in a `create` may carry `__blank: true` to be written that way: a fresh blank
374
- node with only its field triples and no `rdf:type`. It is meant for RDF syntax such as SHACL
375
- paths, has no id (combining it with `__id`/`id` throws), cannot be the created root, and is
376
- refused in `update`/`upsert`, whose INSERT runs once per WHERE solution.
377
375
  The declaring shape owns its generated shapes through `core:generatedShape`, a `contains` edge:
378
376
  `<…/Thing> core:generatedShape <…/Thing/image.singleOwner>`. Syncing a shape is still two
379
377
  requests: deleting the shape deletes the generated shapes with it (their own triples, property
@@ -409,6 +407,55 @@ export class ListCell extends Shape { /* ... */ }
409
407
  > **shape** whose instances never stand alone. They are separate mechanisms — `contains` decides
410
408
  > *what to follow*, `dependent` decides *what may be deleted when reached*.
411
409
 
410
+ ### Blank-node values (`nodeKind`)
411
+
412
+ Every node core writes is IRI-named unless the **property** that holds it says otherwise. A
413
+ nested create is written as a blank node when its property's `nodeKind` allows blank nodes:
414
+
415
+ | `nodeKind` of the property | nested value without an id | with an id |
416
+ |---|---|---|
417
+ | unset, `shacl.IRI` (the `objectProperty` default), `shacl.IRIOrLiteral` | IRI (minted) | IRI |
418
+ | `shacl.BlankNodeOrIRI` | blank node | IRI |
419
+ | `shacl.BlankNode` | blank node | error |
420
+ | `shacl.BlankNodeOrLiteral` | blank node | error |
421
+ | `shacl.Literal` | error | error |
422
+
423
+ ```typescript
424
+ @linkedShape
425
+ export class Place extends Shape {
426
+ static targetClass = ex.Place;
427
+
428
+ @objectProperty({path: ex.geo, shape: GeoPoint, maxCount: 1, nodeKind: shacl.BlankNodeOrIRI})
429
+ declare geo: GeoPoint;
430
+ }
431
+
432
+ // <place1> ex:geo _:b0 . _:b0 ex:lat "52.1" .
433
+ await Place.create({geo: {lat: '52.1'}}).withId(place1);
434
+ ```
435
+
436
+ A blank node is written with its field triples only, without `rdf:type`: its shape is the one the
437
+ property declares. Nothing about it travels in DSL-JSON; the side that runs the mutation decides
438
+ from its own copy of the shape. Core uses this for SHACL paths: `PropertyShape.path`, the
439
+ `PathNode` slots and `PathList.first` / `.rest` are `sh:BlankNodeOrIRI`, so a predicate path (an
440
+ `{id}`) stays an IRI and every operator node and list cell becomes a blank node.
441
+
442
+ A blank value goes with what holds it. Deleting the subject (by id, `.all()`, `.where()`),
443
+ replacing or unsetting the property, deleting or replacing an owned (`contains`) child, and the
444
+ containment cascade (a dependent node it deletes) delete the blank value and the blank nodes below it, following only blank-capable properties.
445
+ Only blank nodes are deleted: an IRI the structure refers to (`schema:image` in
446
+ `[ sh:inversePath schema:image ]`, an entity a list lists) is never touched, nor is a blank node
447
+ reached through one. `contains` is not needed for this.
448
+
449
+ Not supported yet, and refused with an error rather than written wrongly:
450
+
451
+ - an `update` / `upsert` / `updateWhere` whose new value would be a blank node (its INSERT runs
452
+ once per WHERE solution, so the node would be written several times). Create it with its
453
+ parent, or give it an id under `sh:BlankNodeOrIRI`;
454
+ - addressing a blank node by id: linking `{id: '_:b0'}`, `update().for('_:b0')`, deleting
455
+ `{id: '_:b0'}`. Change it through the property that holds it.
456
+
457
+ A select returns a blank value as its store label (Fuseki: `b0`), valid only within that result.
458
+
412
459
  ## Queries: Create, Select, Update, Delete
413
460
 
414
461
  Queries are expressed with the same Shape classes and compile to a query object that a store executes.
@@ -988,7 +1035,7 @@ const deleted = await Person.delete({id: 'https://my.app/node1'});
988
1035
  // Delete multiple nodes
989
1036
  const deleted = await Person.delete([{id: 'https://my.app/node1'}, {id: 'https://my.app/node2'}]);
990
1037
 
991
- // Delete all instances of a shape (with blank node cleanup)
1038
+ // Delete all instances of a shape (with their blank-node values)
992
1039
  await Person.deleteAll();
993
1040
 
994
1041
  // Conditional delete
@@ -84,7 +84,7 @@ const toFieldUpdate = (field, collector) => {
84
84
  };
85
85
  };
86
86
  const toNodeData = (description, collector) => {
87
- return Object.assign(Object.assign({ shape: description.shape.id, fields: description.fields.map((f) => toFieldUpdate(f, collector)) }, (description.__id ? { id: description.__id } : {})), (description.__blank ? { blank: true } : {}));
87
+ return Object.assign({ shape: description.shape.id, fields: description.fields.map((f) => toFieldUpdate(f, collector)) }, (description.__id ? { id: description.__id } : {}));
88
88
  };
89
89
  /** Builds an IRCreateMutation from a create factory's internal description. */
90
90
  export const buildCanonicalCreateMutationIR = (query) => {
@@ -1 +1 @@
1
- {"version":3,"file":"IRMutation.js","sourceRoot":"","sources":["../../../src/queries/IRMutation.ts"],"names":[],"mappings":"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","sourcesContent":["import type {NodeShapeData} from '../shapes/SHACL.js';\nimport {\n type NodeDescriptionValue,\n NodeReferenceValue,\n type PropUpdateValue,\n type SetModificationValue,\n type SinglePropertyUpdateValue,\n isSetModificationValue,\n} from './QueryFactory.js';\nimport {isExpressionNode, resolveExpressionRefs} from '../expressions/ExpressionNode.js';\nimport type {\n IRCreateMutation,\n IRDeleteMutation,\n IRDeleteAllMutation,\n IRDeleteWhereMutation,\n IRUpdateWhereMutation,\n IRFieldValue,\n IRNodeData,\n IRFieldUpdate,\n IRSetModificationValue,\n IRUpdateMutation,\n IRUpsertMutation,\n IRExpression,\n IRGraphPattern,\n IRTraversalPattern,\n} from './IntermediateRepresentation.js';\nimport {createTraversalResolver} from './IRLower.js';\n\ntype CreateMutationInput = {\n shape: NodeShapeData;\n description: NodeDescriptionValue;\n};\n\ntype UpdateMutationInput = {\n id: string;\n shape: NodeShapeData;\n updates: NodeDescriptionValue;\n};\n\ntype UpsertMutationInput = {\n id: string;\n shape: NodeShapeData;\n updates: NodeDescriptionValue;\n};\n\n/**\n * Throws if any field of an upsert carries an expression. Named per offending property so\n * the caller is not left decoding an unbound-variable mystery further down the pipeline.\n */\nconst assertNoExpressionFields = (\n updates: NodeDescriptionValue,\n shapeId: string,\n): void => {\n for (const field of updates?.fields ?? []) {\n // Mirrors how `toSingleFieldValue` recognises an expression, so the guard cannot\n // drift from the lowering it is protecting.\n if (isExpressionNode(field.val)) {\n throw new Error(\n `upsert does not support expression-valued fields (property \"${field.prop.id}\" of ${shapeId}). ` +\n `An expression reads the node's current value, which does not exist when upsert creates it, ` +\n `and the property would be silently skipped. Pass a concrete value, or use ` +\n `update().for({id}) if the node is known to exist.`,\n );\n }\n }\n};\n\ntype DeleteMutationInput = {\n shape: NodeShapeData;\n ids: NodeReferenceValue[];\n};\n\nconst toNodeReference = (value: NodeReferenceValue): NodeReferenceValue => ({\n id: value.id,\n});\n\nconst toSetModification = (value: SetModificationValue): IRSetModificationValue => {\n return {\n add: value.$add\n ? value.$add.map((item) => toFieldValue(item as unknown as PropUpdateValue))\n : undefined,\n remove: value.$remove ? value.$remove.map((item) => toNodeReference(item)) : undefined,\n };\n};\n\n/** Alias used as the mutation subject for expression ref resolution. */\nconst MUTATION_SUBJECT_ALIAS = '__mutation_subject__';\n\nexport type TraversalCollector = {\n resolve: (fromAlias: string, propertyShapeId: string) => string;\n patterns: IRTraversalPattern[];\n};\n\n/**\n * Create a traversal collector for mutation expressions. Uses `__trav_N__` alias\n * prefixes to avoid collision with query aliases (a0, a1...) and the mutation\n * subject placeholder. Delegates to the shared createTraversalResolver factory.\n */\nexport function createTraversalCollector(): TraversalCollector {\n let counter = 0;\n return createTraversalResolver(\n () => `__trav_${counter++}__`,\n (from, to, property): IRTraversalPattern => ({from, property, to}),\n );\n}\n\nconst toSingleFieldValue = (\n value: SinglePropertyUpdateValue,\n collector?: TraversalCollector,\n): IRFieldValue => {\n if (value === undefined) {\n return undefined;\n }\n\n // ExpressionNode → resolve refs and extract IRExpression\n if (isExpressionNode(value)) {\n return resolveExpressionRefs(\n value.ir,\n value._refs,\n MUTATION_SUBJECT_ALIAS,\n // In mutation context, all property segments are on the subject entity.\n // For single-segment refs (e.g. p.age), the property_expr already has the property as its .property.\n // For multi-segment refs (e.g. p.bestFriend.name), this creates intermediate traversals.\n collector\n ? collector.resolve\n : (_fromAlias, _propertyShapeId) => {\n // No collector provided — multi-segment traversals cannot be resolved.\n return MUTATION_SUBJECT_ALIAS;\n },\n );\n }\n\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n value instanceof Date\n ) {\n return value;\n }\n\n if ('id' in (value as NodeReferenceValue)) {\n return toNodeReference(value as NodeReferenceValue);\n }\n\n return toNodeData(value as NodeDescriptionValue);\n};\n\nconst toFieldValue = (\n value: PropUpdateValue,\n collector?: TraversalCollector,\n): IRFieldValue => {\n if (Array.isArray(value)) {\n return value.map((item) => toSingleFieldValue(item, collector));\n }\n\n if (isSetModificationValue(value)) {\n return toSetModification(value);\n }\n\n return toSingleFieldValue(value, collector);\n};\n\nconst toFieldUpdate = (\n field: NodeDescriptionValue['fields'][number],\n collector?: TraversalCollector,\n): IRFieldUpdate => {\n return {\n property: field.prop.id,\n value: toFieldValue(field.val, collector),\n };\n};\n\nconst toNodeData = (\n description: NodeDescriptionValue,\n collector?: TraversalCollector,\n): IRNodeData => {\n return {\n shape: description.shape.id,\n fields: description.fields.map((f) => toFieldUpdate(f, collector)),\n ...(description.__id ? {id: description.__id} : {}),\n ...(description.__blank ? {blank: true as const} : {}),\n };\n};\n\n/** Builds an IRCreateMutation from a create factory's internal description. */\nexport const buildCanonicalCreateMutationIR = (\n query: CreateMutationInput,\n): IRCreateMutation => {\n return {\n kind: 'create',\n shape: query.shape.id,\n data: toNodeData(query.description),\n };\n};\n\n/** Builds an IRUpdateMutation from an update factory's internal description. */\nexport const buildCanonicalUpdateMutationIR = (\n query: UpdateMutationInput,\n): IRUpdateMutation => {\n const collector = createTraversalCollector();\n const data = toNodeData(query.updates, collector);\n return {\n kind: 'update',\n shape: query.shape.id,\n id: query.id,\n data,\n ...(collector.patterns.length > 0\n ? {traversalPatterns: collector.patterns}\n : {}),\n };\n};\n\n/**\n * Builds an IRUpsertMutation — an update that also asserts the node's type, so it is\n * correct whether or not the node exists.\n *\n * Rejects expression-valued fields. An expression such as `age: p.age.plus(1)` lowers to\n * a BIND over the node's current value; when the node does not exist that ref is unbound,\n * and SPARQL drops any INSERT triple containing an unbound variable. The write would\n * report success and store nothing for that property — precisely the silent-wrong-write\n * this primitive exists to remove, so it is refused rather than documented.\n */\nexport const buildCanonicalUpsertMutationIR = (\n query: UpsertMutationInput,\n): IRUpsertMutation => {\n assertNoExpressionFields(query.updates, query.shape.id);\n const collector = createTraversalCollector();\n const data = toNodeData(query.updates, collector);\n return {\n kind: 'upsert',\n shape: query.shape.id,\n id: query.id,\n data,\n ...(collector.patterns.length > 0\n ? {traversalPatterns: collector.patterns}\n : {}),\n };\n};\n\n/** Builds an IRDeleteMutation from a delete factory's internal description. */\nexport const buildCanonicalDeleteMutationIR = (\n query: DeleteMutationInput,\n): IRDeleteMutation => {\n return {\n kind: 'delete',\n shape: query.shape.id,\n ids: query.ids.map((id) => ({id: id.id})),\n };\n};\n\ntype DeleteAllMutationInput = {\n shape: NodeShapeData;\n};\n\n/** Builds an IRDeleteAllMutation — delete all instances of a shape type. */\nexport const buildCanonicalDeleteAllMutationIR = (\n query: DeleteAllMutationInput,\n): IRDeleteAllMutation => {\n return {\n kind: 'delete_all',\n shape: query.shape.id,\n };\n};\n\ntype DeleteWhereMutationInput = {\n shape: NodeShapeData;\n where: IRExpression;\n wherePatterns: IRGraphPattern[];\n};\n\n/** Builds an IRDeleteWhereMutation — delete instances matching a condition. */\nexport const buildCanonicalDeleteWhereMutationIR = (\n query: DeleteWhereMutationInput,\n): IRDeleteWhereMutation => {\n return {\n kind: 'delete_where',\n shape: query.shape.id,\n where: query.where,\n wherePatterns: query.wherePatterns,\n };\n};\n\ntype UpdateWhereMutationInput = {\n shape: NodeShapeData;\n updates: NodeDescriptionValue;\n where?: IRExpression;\n wherePatterns?: IRGraphPattern[];\n};\n\n/** Builds an IRUpdateWhereMutation — update instances matching a condition or all. */\nexport const buildCanonicalUpdateWhereMutationIR = (\n query: UpdateWhereMutationInput,\n): IRUpdateWhereMutation => {\n const collector = createTraversalCollector();\n const data = toNodeData(query.updates, collector);\n return {\n kind: 'update_where',\n shape: query.shape.id,\n data,\n where: query.where,\n wherePatterns: query.wherePatterns,\n ...(collector.patterns.length > 0\n ? {traversalPatterns: collector.patterns}\n : {}),\n };\n};\n"]}
1
+ {"version":3,"file":"IRMutation.js","sourceRoot":"","sources":["../../../src/queries/IRMutation.ts"],"names":[],"mappings":"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","sourcesContent":["import type {NodeShapeData} from '../shapes/SHACL.js';\nimport {\n type NodeDescriptionValue,\n NodeReferenceValue,\n type PropUpdateValue,\n type SetModificationValue,\n type SinglePropertyUpdateValue,\n isSetModificationValue,\n} from './QueryFactory.js';\nimport {isExpressionNode, resolveExpressionRefs} from '../expressions/ExpressionNode.js';\nimport type {\n IRCreateMutation,\n IRDeleteMutation,\n IRDeleteAllMutation,\n IRDeleteWhereMutation,\n IRUpdateWhereMutation,\n IRFieldValue,\n IRNodeData,\n IRFieldUpdate,\n IRSetModificationValue,\n IRUpdateMutation,\n IRUpsertMutation,\n IRExpression,\n IRGraphPattern,\n IRTraversalPattern,\n} from './IntermediateRepresentation.js';\nimport {createTraversalResolver} from './IRLower.js';\n\ntype CreateMutationInput = {\n shape: NodeShapeData;\n description: NodeDescriptionValue;\n};\n\ntype UpdateMutationInput = {\n id: string;\n shape: NodeShapeData;\n updates: NodeDescriptionValue;\n};\n\ntype UpsertMutationInput = {\n id: string;\n shape: NodeShapeData;\n updates: NodeDescriptionValue;\n};\n\n/**\n * Throws if any field of an upsert carries an expression. Named per offending property so\n * the caller is not left decoding an unbound-variable mystery further down the pipeline.\n */\nconst assertNoExpressionFields = (\n updates: NodeDescriptionValue,\n shapeId: string,\n): void => {\n for (const field of updates?.fields ?? []) {\n // Mirrors how `toSingleFieldValue` recognises an expression, so the guard cannot\n // drift from the lowering it is protecting.\n if (isExpressionNode(field.val)) {\n throw new Error(\n `upsert does not support expression-valued fields (property \"${field.prop.id}\" of ${shapeId}). ` +\n `An expression reads the node's current value, which does not exist when upsert creates it, ` +\n `and the property would be silently skipped. Pass a concrete value, or use ` +\n `update().for({id}) if the node is known to exist.`,\n );\n }\n }\n};\n\ntype DeleteMutationInput = {\n shape: NodeShapeData;\n ids: NodeReferenceValue[];\n};\n\nconst toNodeReference = (value: NodeReferenceValue): NodeReferenceValue => ({\n id: value.id,\n});\n\nconst toSetModification = (value: SetModificationValue): IRSetModificationValue => {\n return {\n add: value.$add\n ? value.$add.map((item) => toFieldValue(item as unknown as PropUpdateValue))\n : undefined,\n remove: value.$remove ? value.$remove.map((item) => toNodeReference(item)) : undefined,\n };\n};\n\n/** Alias used as the mutation subject for expression ref resolution. */\nconst MUTATION_SUBJECT_ALIAS = '__mutation_subject__';\n\nexport type TraversalCollector = {\n resolve: (fromAlias: string, propertyShapeId: string) => string;\n patterns: IRTraversalPattern[];\n};\n\n/**\n * Create a traversal collector for mutation expressions. Uses `__trav_N__` alias\n * prefixes to avoid collision with query aliases (a0, a1...) and the mutation\n * subject placeholder. Delegates to the shared createTraversalResolver factory.\n */\nexport function createTraversalCollector(): TraversalCollector {\n let counter = 0;\n return createTraversalResolver(\n () => `__trav_${counter++}__`,\n (from, to, property): IRTraversalPattern => ({from, property, to}),\n );\n}\n\nconst toSingleFieldValue = (\n value: SinglePropertyUpdateValue,\n collector?: TraversalCollector,\n): IRFieldValue => {\n if (value === undefined) {\n return undefined;\n }\n\n // ExpressionNode → resolve refs and extract IRExpression\n if (isExpressionNode(value)) {\n return resolveExpressionRefs(\n value.ir,\n value._refs,\n MUTATION_SUBJECT_ALIAS,\n // In mutation context, all property segments are on the subject entity.\n // For single-segment refs (e.g. p.age), the property_expr already has the property as its .property.\n // For multi-segment refs (e.g. p.bestFriend.name), this creates intermediate traversals.\n collector\n ? collector.resolve\n : (_fromAlias, _propertyShapeId) => {\n // No collector provided — multi-segment traversals cannot be resolved.\n return MUTATION_SUBJECT_ALIAS;\n },\n );\n }\n\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n value instanceof Date\n ) {\n return value;\n }\n\n if ('id' in (value as NodeReferenceValue)) {\n return toNodeReference(value as NodeReferenceValue);\n }\n\n return toNodeData(value as NodeDescriptionValue);\n};\n\nconst toFieldValue = (\n value: PropUpdateValue,\n collector?: TraversalCollector,\n): IRFieldValue => {\n if (Array.isArray(value)) {\n return value.map((item) => toSingleFieldValue(item, collector));\n }\n\n if (isSetModificationValue(value)) {\n return toSetModification(value);\n }\n\n return toSingleFieldValue(value, collector);\n};\n\nconst toFieldUpdate = (\n field: NodeDescriptionValue['fields'][number],\n collector?: TraversalCollector,\n): IRFieldUpdate => {\n return {\n property: field.prop.id,\n value: toFieldValue(field.val, collector),\n };\n};\n\nconst toNodeData = (\n description: NodeDescriptionValue,\n collector?: TraversalCollector,\n): IRNodeData => {\n return {\n shape: description.shape.id,\n fields: description.fields.map((f) => toFieldUpdate(f, collector)),\n ...(description.__id ? {id: description.__id} : {}),\n };\n};\n\n/** Builds an IRCreateMutation from a create factory's internal description. */\nexport const buildCanonicalCreateMutationIR = (\n query: CreateMutationInput,\n): IRCreateMutation => {\n return {\n kind: 'create',\n shape: query.shape.id,\n data: toNodeData(query.description),\n };\n};\n\n/** Builds an IRUpdateMutation from an update factory's internal description. */\nexport const buildCanonicalUpdateMutationIR = (\n query: UpdateMutationInput,\n): IRUpdateMutation => {\n const collector = createTraversalCollector();\n const data = toNodeData(query.updates, collector);\n return {\n kind: 'update',\n shape: query.shape.id,\n id: query.id,\n data,\n ...(collector.patterns.length > 0\n ? {traversalPatterns: collector.patterns}\n : {}),\n };\n};\n\n/**\n * Builds an IRUpsertMutation — an update that also asserts the node's type, so it is\n * correct whether or not the node exists.\n *\n * Rejects expression-valued fields. An expression such as `age: p.age.plus(1)` lowers to\n * a BIND over the node's current value; when the node does not exist that ref is unbound,\n * and SPARQL drops any INSERT triple containing an unbound variable. The write would\n * report success and store nothing for that property — precisely the silent-wrong-write\n * this primitive exists to remove, so it is refused rather than documented.\n */\nexport const buildCanonicalUpsertMutationIR = (\n query: UpsertMutationInput,\n): IRUpsertMutation => {\n assertNoExpressionFields(query.updates, query.shape.id);\n const collector = createTraversalCollector();\n const data = toNodeData(query.updates, collector);\n return {\n kind: 'upsert',\n shape: query.shape.id,\n id: query.id,\n data,\n ...(collector.patterns.length > 0\n ? {traversalPatterns: collector.patterns}\n : {}),\n };\n};\n\n/** Builds an IRDeleteMutation from a delete factory's internal description. */\nexport const buildCanonicalDeleteMutationIR = (\n query: DeleteMutationInput,\n): IRDeleteMutation => {\n return {\n kind: 'delete',\n shape: query.shape.id,\n ids: query.ids.map((id) => ({id: id.id})),\n };\n};\n\ntype DeleteAllMutationInput = {\n shape: NodeShapeData;\n};\n\n/** Builds an IRDeleteAllMutation — delete all instances of a shape type. */\nexport const buildCanonicalDeleteAllMutationIR = (\n query: DeleteAllMutationInput,\n): IRDeleteAllMutation => {\n return {\n kind: 'delete_all',\n shape: query.shape.id,\n };\n};\n\ntype DeleteWhereMutationInput = {\n shape: NodeShapeData;\n where: IRExpression;\n wherePatterns: IRGraphPattern[];\n};\n\n/** Builds an IRDeleteWhereMutation — delete instances matching a condition. */\nexport const buildCanonicalDeleteWhereMutationIR = (\n query: DeleteWhereMutationInput,\n): IRDeleteWhereMutation => {\n return {\n kind: 'delete_where',\n shape: query.shape.id,\n where: query.where,\n wherePatterns: query.wherePatterns,\n };\n};\n\ntype UpdateWhereMutationInput = {\n shape: NodeShapeData;\n updates: NodeDescriptionValue;\n where?: IRExpression;\n wherePatterns?: IRGraphPattern[];\n};\n\n/** Builds an IRUpdateWhereMutation — update instances matching a condition or all. */\nexport const buildCanonicalUpdateWhereMutationIR = (\n query: UpdateWhereMutationInput,\n): IRUpdateWhereMutation => {\n const collector = createTraversalCollector();\n const data = toNodeData(query.updates, collector);\n return {\n kind: 'update_where',\n shape: query.shape.id,\n data,\n where: query.where,\n wherePatterns: query.wherePatterns,\n ...(collector.patterns.length > 0\n ? {traversalPatterns: collector.patterns}\n : {}),\n };\n};\n"]}
@@ -280,11 +280,6 @@ export type IRNodeData = {
280
280
  shape: string;
281
281
  fields: IRFieldUpdate[];
282
282
  id?: string;
283
- /**
284
- * A nested node written as a fresh blank node with only its field triples: no IRI, no
285
- * `rdf:type` (see `NodeDescriptionValue.__blank`). Never set together with `id`.
286
- */
287
- blank?: true;
288
283
  };
289
284
  export type IRFieldUpdate = {
290
285
  property: string;
@@ -1 +1 @@
1
- {"version":3,"file":"IntermediateRepresentation.js","sourceRoot":"","sources":["../../../src/queries/IntermediateRepresentation.ts"],"names":[],"mappings":"","sourcesContent":["import {NodeReferenceValue} from './QueryFactory.js';\nimport type {PathExpr} from '../paths/PropertyPathExpr.js';\n\nexport type IRDirection = 'ASC' | 'DESC';\nexport type IRAlias = string;\n\nexport type IRValue = string | number | boolean | null;\n\nexport type IRQuery =\n | IRSelectQuery\n | IRAskQuery\n | IRCountQuery\n | IRCreateMutation\n | IRUpdateMutation\n | IRUpsertMutation\n | IRDeleteMutation\n | IRDeleteAllMutation\n | IRDeleteWhereMutation\n | IRUpdateWhereMutation;\n\nexport type IRSelectQuery = {\n kind: 'select';\n root: IRShapeScanPattern;\n patterns: IRGraphPattern[];\n projection: IRProjectionItem[];\n where?: IRExpression;\n orderBy?: IROrderByItem[];\n limit?: number;\n offset?: number;\n subjectId?: string;\n subjectIds?: string[];\n singleResult?: boolean;\n resultMap?: IRResultMapEntry[];\n};\n\n/**\n * A query whose answer is a boolean.\n *\n * Carries a pattern and nothing else. There is no `projection`, `orderBy`,\n * `limit` or `offset`: each of those shapes or windows a *solution sequence*, and\n * an ask has none — so rather than being ignored during conversion, or guarded\n * against, they are unrepresentable.\n *\n * `root` is **optional**. Absent means no `rdf:type` constraint at all — \"does a\n * node with this IRI exist\", under any shape or none — which lowers to\n * `ASK { <iri> ?p ?o }`. A rootless ask carries no `patterns` or `where`, since\n * both reference properties and a property is only resolvable through a shape.\n */\nexport type IRAskQuery = {\n kind: 'ask';\n root?: IRShapeScanPattern;\n patterns: IRGraphPattern[];\n where?: IRExpression;\n subjectId?: string;\n subjectIds?: string[];\n};\n\n/**\n * A query whose answer is a single number: how many instances of a shape match.\n *\n * Like {@link IRAskQuery}, it carries a **pattern and nothing else**. There is no\n * `projection`, `orderBy`, `limit` or `offset`: each of those shapes or windows a\n * *solution sequence*, and a count has none — the number it answers is a property\n * of the whole match set. So rather than being ignored during conversion, or\n * guarded against, they are unrepresentable.\n *\n * That is the entire reason this is a distinct IR kind and not a flag on\n * {@link IRSelectQuery}: a count of a windowed query is meaningless, and a type\n * that cannot hold the window cannot half-drop it.\n *\n * `root` is **required**, unlike an ask's. A shapeless count would count every\n * node in the store, which is never what a caller means.\n */\nexport type IRCountQuery = {\n kind: 'count';\n root: IRShapeScanPattern;\n patterns: IRGraphPattern[];\n where?: IRExpression;\n subjectId?: string;\n subjectIds?: string[];\n /**\n * The variable the `COUNT` is bound to (`(COUNT(DISTINCT ?a0) AS ?count)`).\n * Carried in the IR rather than hardcoded in two places so lowering and result\n * mapping cannot disagree about it.\n */\n alias: IRAlias;\n};\n\nexport type IRProjectionItem = {\n alias: IRAlias;\n expression: IRExpression;\n};\n\nexport type IROrderByItem = {\n expression: IRExpression;\n direction: IRDirection;\n};\n\nexport type IRResultMapEntry = {\n key: string;\n alias: IRAlias;\n};\n\nexport type IRGraphPattern =\n | IRShapeScanPattern\n | IRTraversePattern\n | IRJoinPattern\n | IROptionalPattern\n | IRUnionPattern\n | IRExistsPattern\n | IRMinusPattern;\n\nexport type IRShapeScanPattern = {\n kind: 'shape_scan';\n shape: string;\n alias: IRAlias;\n};\n\nexport type IRTraversePattern = {\n kind: 'traverse';\n from: IRAlias;\n to: IRAlias;\n property: string;\n pathExpr?: PathExpr;\n filter?: IRExpression;\n maxCount?: number;\n /**\n * Inner LIMIT/OFFSET/ORDER BY for a nested select on this traversal's\n * related collection. When set, the root→child traverse is wrapped in a\n * SPARQL sub-SELECT to bound the collection per parent. Only valid when the\n * outer query targets a single root subject (enforced in irToAlgebra).\n */\n innerLimit?: number;\n innerOffset?: number;\n innerOrderBy?: IRInnerOrderBy[];\n};\n\nexport type IRInnerOrderBy = {\n property: string;\n direction: IRDirection;\n};\n\nexport type IRJoinPattern = {\n kind: 'join';\n patterns: IRGraphPattern[];\n};\n\nexport type IROptionalPattern = {\n kind: 'optional';\n pattern: IRGraphPattern;\n};\n\nexport type IRUnionPattern = {\n kind: 'union';\n branches: IRGraphPattern[];\n};\n\nexport type IRExistsPattern = {\n kind: 'exists';\n pattern: IRGraphPattern;\n};\n\nexport type IRMinusPattern = {\n kind: 'minus';\n pattern: IRGraphPattern;\n filter?: IRExpression;\n};\n\nexport type IRExpression =\n | IRLiteralExpression\n | IRReferenceExpression\n | IRAliasExpression\n | IRPropertyExpression\n | IRContextPropertyExpression\n | IRBinaryExpression\n | IRLogicalExpression\n | IRNotExpression\n | IRFunctionExpression\n | IRAggregateExpression\n | IRExistsExpression\n | IRInExpression;\n\n/**\n * Membership test — `value IN (…)` / `NOT IN (…)`.\n * `source` is forward-shaped: today only the explicit-`list` arm exists (Rung 1);\n * a `{query}` arm (Rung 2 — membership against a subquery, lowering to EXISTS)\n * can be added without changing the node's identity or the `oneOf`/`notOneOf` DSL.\n */\nexport type IRInExpression = {\n kind: 'in_expr';\n negated: boolean;\n value: IRExpression;\n source: {list: IRExpression[]};\n};\n\nexport type IRLiteralExpression = {\n kind: 'literal_expr';\n /**\n * `Date` is admitted alongside `IRValue`: a temporal comparison has to reach the\n * SPARQL layer still knowing it is temporal, or the literal is emitted untyped\n * and never equals the `^^xsd:dateTime` term the mutation side writes.\n */\n value: IRValue | Date;\n};\n\nexport type IRReferenceExpression = {\n kind: 'reference_expr';\n /** The referenced node IRI. Optional only while a `contextName` is unresolved. */\n value?: string;\n /**\n * A query-context reference (`{@ctx}` on the wire). When set, `value` is filled\n * by `lower()` from the live context map — resolved or thrown there, never baked\n * at build time. Absent for a plain node reference.\n */\n contextName?: string;\n};\n\nexport type IRAliasExpression = {\n kind: 'alias_expr';\n alias: IRAlias;\n};\n\nexport type IRPropertyExpression = {\n kind: 'property_expr';\n sourceAlias: IRAlias;\n property: string;\n pathExpr?: import('../paths/PropertyPathExpr.js').PathExpr;\n maxCount?: number;\n};\n\nexport type IRContextPropertyExpression = {\n kind: 'context_property_expr';\n /** The context entity IRI. Optional only while a `contextName` is unresolved. */\n contextIri?: string;\n /** A query-context reference resolved into `contextIri` by `lower()`. */\n contextName?: string;\n property: string;\n};\n\nexport type IRBinaryOperator =\n | '='\n | '!='\n | '>'\n | '>='\n | '<'\n | '<='\n | '+'\n | '-'\n | '*'\n | '/';\n\nexport type IRBinaryExpression = {\n kind: 'binary_expr';\n operator: IRBinaryOperator;\n left: IRExpression;\n right: IRExpression;\n};\n\nexport type IRLogicalOperator = 'and' | 'or';\n\nexport type IRLogicalExpression = {\n kind: 'logical_expr';\n operator: IRLogicalOperator;\n expressions: IRExpression[];\n};\n\nexport type IRNotExpression = {\n kind: 'not_expr';\n expression: IRExpression;\n};\n\nexport type IRFunctionExpression = {\n kind: 'function_expr';\n name: string;\n args: IRExpression[];\n};\n\nexport type IRAggregateExpression = {\n kind: 'aggregate_expr';\n name: 'count' | 'sum' | 'avg' | 'min' | 'max';\n args: IRExpression[];\n};\n\nexport type IRExistsExpression = {\n kind: 'exists_expr';\n pattern: IRGraphPattern;\n filter?: IRExpression;\n};\n\nexport type IRCreateMutation = {\n kind: 'create';\n shape: string;\n data: IRNodeData;\n};\n\nexport type IRTraversalPattern = {\n from: string; // source alias\n property: string; // property IRI\n to: string; // target alias\n};\n\nexport type IRUpdateMutation = {\n kind: 'update';\n shape: string;\n id: string;\n data: IRNodeData;\n traversalPatterns?: IRTraversalPattern[];\n};\n\n/**\n * Create-or-replace against a known id, in one request.\n *\n * Structurally identical to {@link IRUpdateMutation} — it lowers through the same\n * DELETE/INSERT/WHERE body — and differs in exactly one emitted triple: upsert also\n * asserts `?id rdf:type <targetClass>`, which the update path never writes.\n *\n * That single triple is the whole difference between the two, because `update`'s WHERE\n * is a bare `OPTIONAL` and so already matches when the node is absent. It is a distinct\n * IR kind rather than a flag on the update mutation so that a consumer which does not\n * know about upsert fails loudly, instead of quietly writing an untyped node and\n * reporting success.\n */\nexport type IRUpsertMutation = {\n kind: 'upsert';\n shape: string;\n id: string;\n data: IRNodeData;\n traversalPatterns?: IRTraversalPattern[];\n};\n\nexport type IRDeleteMutation = {\n kind: 'delete';\n shape: string;\n ids: NodeReferenceValue[];\n};\n\nexport type IRDeleteAllMutation = {\n kind: 'delete_all';\n shape: string;\n};\n\nexport type IRDeleteWhereMutation = {\n kind: 'delete_where';\n shape: string;\n where: IRExpression;\n wherePatterns: IRGraphPattern[];\n};\n\nexport type IRUpdateWhereMutation = {\n kind: 'update_where';\n shape: string;\n data: IRNodeData;\n where?: IRExpression;\n wherePatterns?: IRGraphPattern[];\n traversalPatterns?: IRTraversalPattern[];\n};\n\nexport type IRNodeData = {\n shape: string;\n fields: IRFieldUpdate[];\n id?: string;\n /**\n * A nested node written as a fresh blank node with only its field triples: no IRI, no\n * `rdf:type` (see `NodeDescriptionValue.__blank`). Never set together with `id`.\n */\n blank?: true;\n};\n\nexport type IRFieldUpdate = {\n property: string;\n value: IRFieldValue;\n};\n\nexport type IRSetModificationValue = {\n add?: IRFieldValue[];\n remove?: NodeReferenceValue[];\n};\n\nexport type IRFieldValue =\n | IRValue\n | Date\n | NodeReferenceValue\n | IRNodeData\n | IRSetModificationValue\n | IRFieldValue[]\n | IRExpression\n | undefined;\n\n// ---------------------------------------------------------------------------\n// Store result types\n// ---------------------------------------------------------------------------\n// These types describe what an IDataset implementation should return.\n// The calling layer (LinkedStorage via queryDispatch) threads the precise\n// DSL-level TypeScript result type back to the caller, so the store\n// only needs to produce data that satisfies these structural contracts.\n// ---------------------------------------------------------------------------\n\n/**\n * A single result row — an object with a node id and dynamic fields.\n * Used in select and create results.\n */\nexport type ResultRow = {id: string; [key: string]: ResultFieldValue};\n\n/**\n * Possible field values in a select or create result row.\n */\nexport type ResultFieldValue =\n | string\n | number\n | boolean\n | Date\n | null\n | undefined\n | ResultRow\n | ResultRow[]\n | string[]\n | number[]\n | boolean[]\n | Date[];\n\n/**\n * What `selectQuery` should return.\n *\n * - Array of rows for multi-result queries (the default).\n * - A single row when `query.singleResult` is true (`.one()` or subject-targeted query).\n * - `null` when `singleResult` is true and the target node doesn't exist.\n */\nexport type SelectResult = ResultRow[] | ResultRow | null;\n\n/**\n * What `createQuery` should return.\n *\n * A single row with the generated `id` and the created field values.\n * Nested creates appear as nested `ResultRow` objects (with their own ids).\n * Array fields (e.g. friends) appear as `ResultRow[]`.\n */\nexport type CreateResult = ResultRow;\n\n/**\n * When a set property is fully overwritten, the result contains `updatedTo`\n * with the new complete set of values.\n */\nexport type SetOverwriteResult = {updatedTo: ResultRow[]};\n\n/**\n * When a set property is incrementally modified with `{add, remove}`,\n * the result contains the added and removed entries.\n */\nexport type SetModificationResult = {added: ResultRow[]; removed: ResultRow[]};\n\n/**\n * Possible field values in an update result row.\n * Extends `ResultFieldValue` with set modification shapes.\n */\nexport type UpdateFieldValue =\n | ResultFieldValue\n | SetOverwriteResult\n | SetModificationResult;\n\n/**\n * What `updateQuery` should return.\n *\n * A single row with the target node's `id` and only the changed fields.\n * Fields not included in the update are omitted (not returned).\n *\n * Field value shapes depend on the type of update:\n * - Literal fields: the new value (string, number, boolean, Date).\n * - Single object fields: a `ResultRow` with the referenced/created node.\n * - Set overwrite (passing an array): `{updatedTo: ResultRow[]}`.\n * - Set add/remove (passing `{add, remove}`): `{added: ResultRow[], removed: ResultRow[]}`.\n * - Unset single field (passing `undefined` or `null`): field is `undefined`.\n * - Unset multi-value field (passing `undefined`): empty array `[]`.\n */\nexport type UpdateResult = {id: string; [key: string]: UpdateFieldValue};\n"]}
1
+ {"version":3,"file":"IntermediateRepresentation.js","sourceRoot":"","sources":["../../../src/queries/IntermediateRepresentation.ts"],"names":[],"mappings":"","sourcesContent":["import {NodeReferenceValue} from './QueryFactory.js';\nimport type {PathExpr} from '../paths/PropertyPathExpr.js';\n\nexport type IRDirection = 'ASC' | 'DESC';\nexport type IRAlias = string;\n\nexport type IRValue = string | number | boolean | null;\n\nexport type IRQuery =\n | IRSelectQuery\n | IRAskQuery\n | IRCountQuery\n | IRCreateMutation\n | IRUpdateMutation\n | IRUpsertMutation\n | IRDeleteMutation\n | IRDeleteAllMutation\n | IRDeleteWhereMutation\n | IRUpdateWhereMutation;\n\nexport type IRSelectQuery = {\n kind: 'select';\n root: IRShapeScanPattern;\n patterns: IRGraphPattern[];\n projection: IRProjectionItem[];\n where?: IRExpression;\n orderBy?: IROrderByItem[];\n limit?: number;\n offset?: number;\n subjectId?: string;\n subjectIds?: string[];\n singleResult?: boolean;\n resultMap?: IRResultMapEntry[];\n};\n\n/**\n * A query whose answer is a boolean.\n *\n * Carries a pattern and nothing else. There is no `projection`, `orderBy`,\n * `limit` or `offset`: each of those shapes or windows a *solution sequence*, and\n * an ask has none — so rather than being ignored during conversion, or guarded\n * against, they are unrepresentable.\n *\n * `root` is **optional**. Absent means no `rdf:type` constraint at all — \"does a\n * node with this IRI exist\", under any shape or none — which lowers to\n * `ASK { <iri> ?p ?o }`. A rootless ask carries no `patterns` or `where`, since\n * both reference properties and a property is only resolvable through a shape.\n */\nexport type IRAskQuery = {\n kind: 'ask';\n root?: IRShapeScanPattern;\n patterns: IRGraphPattern[];\n where?: IRExpression;\n subjectId?: string;\n subjectIds?: string[];\n};\n\n/**\n * A query whose answer is a single number: how many instances of a shape match.\n *\n * Like {@link IRAskQuery}, it carries a **pattern and nothing else**. There is no\n * `projection`, `orderBy`, `limit` or `offset`: each of those shapes or windows a\n * *solution sequence*, and a count has none — the number it answers is a property\n * of the whole match set. So rather than being ignored during conversion, or\n * guarded against, they are unrepresentable.\n *\n * That is the entire reason this is a distinct IR kind and not a flag on\n * {@link IRSelectQuery}: a count of a windowed query is meaningless, and a type\n * that cannot hold the window cannot half-drop it.\n *\n * `root` is **required**, unlike an ask's. A shapeless count would count every\n * node in the store, which is never what a caller means.\n */\nexport type IRCountQuery = {\n kind: 'count';\n root: IRShapeScanPattern;\n patterns: IRGraphPattern[];\n where?: IRExpression;\n subjectId?: string;\n subjectIds?: string[];\n /**\n * The variable the `COUNT` is bound to (`(COUNT(DISTINCT ?a0) AS ?count)`).\n * Carried in the IR rather than hardcoded in two places so lowering and result\n * mapping cannot disagree about it.\n */\n alias: IRAlias;\n};\n\nexport type IRProjectionItem = {\n alias: IRAlias;\n expression: IRExpression;\n};\n\nexport type IROrderByItem = {\n expression: IRExpression;\n direction: IRDirection;\n};\n\nexport type IRResultMapEntry = {\n key: string;\n alias: IRAlias;\n};\n\nexport type IRGraphPattern =\n | IRShapeScanPattern\n | IRTraversePattern\n | IRJoinPattern\n | IROptionalPattern\n | IRUnionPattern\n | IRExistsPattern\n | IRMinusPattern;\n\nexport type IRShapeScanPattern = {\n kind: 'shape_scan';\n shape: string;\n alias: IRAlias;\n};\n\nexport type IRTraversePattern = {\n kind: 'traverse';\n from: IRAlias;\n to: IRAlias;\n property: string;\n pathExpr?: PathExpr;\n filter?: IRExpression;\n maxCount?: number;\n /**\n * Inner LIMIT/OFFSET/ORDER BY for a nested select on this traversal's\n * related collection. When set, the root→child traverse is wrapped in a\n * SPARQL sub-SELECT to bound the collection per parent. Only valid when the\n * outer query targets a single root subject (enforced in irToAlgebra).\n */\n innerLimit?: number;\n innerOffset?: number;\n innerOrderBy?: IRInnerOrderBy[];\n};\n\nexport type IRInnerOrderBy = {\n property: string;\n direction: IRDirection;\n};\n\nexport type IRJoinPattern = {\n kind: 'join';\n patterns: IRGraphPattern[];\n};\n\nexport type IROptionalPattern = {\n kind: 'optional';\n pattern: IRGraphPattern;\n};\n\nexport type IRUnionPattern = {\n kind: 'union';\n branches: IRGraphPattern[];\n};\n\nexport type IRExistsPattern = {\n kind: 'exists';\n pattern: IRGraphPattern;\n};\n\nexport type IRMinusPattern = {\n kind: 'minus';\n pattern: IRGraphPattern;\n filter?: IRExpression;\n};\n\nexport type IRExpression =\n | IRLiteralExpression\n | IRReferenceExpression\n | IRAliasExpression\n | IRPropertyExpression\n | IRContextPropertyExpression\n | IRBinaryExpression\n | IRLogicalExpression\n | IRNotExpression\n | IRFunctionExpression\n | IRAggregateExpression\n | IRExistsExpression\n | IRInExpression;\n\n/**\n * Membership test — `value IN (…)` / `NOT IN (…)`.\n * `source` is forward-shaped: today only the explicit-`list` arm exists (Rung 1);\n * a `{query}` arm (Rung 2 — membership against a subquery, lowering to EXISTS)\n * can be added without changing the node's identity or the `oneOf`/`notOneOf` DSL.\n */\nexport type IRInExpression = {\n kind: 'in_expr';\n negated: boolean;\n value: IRExpression;\n source: {list: IRExpression[]};\n};\n\nexport type IRLiteralExpression = {\n kind: 'literal_expr';\n /**\n * `Date` is admitted alongside `IRValue`: a temporal comparison has to reach the\n * SPARQL layer still knowing it is temporal, or the literal is emitted untyped\n * and never equals the `^^xsd:dateTime` term the mutation side writes.\n */\n value: IRValue | Date;\n};\n\nexport type IRReferenceExpression = {\n kind: 'reference_expr';\n /** The referenced node IRI. Optional only while a `contextName` is unresolved. */\n value?: string;\n /**\n * A query-context reference (`{@ctx}` on the wire). When set, `value` is filled\n * by `lower()` from the live context map — resolved or thrown there, never baked\n * at build time. Absent for a plain node reference.\n */\n contextName?: string;\n};\n\nexport type IRAliasExpression = {\n kind: 'alias_expr';\n alias: IRAlias;\n};\n\nexport type IRPropertyExpression = {\n kind: 'property_expr';\n sourceAlias: IRAlias;\n property: string;\n pathExpr?: import('../paths/PropertyPathExpr.js').PathExpr;\n maxCount?: number;\n};\n\nexport type IRContextPropertyExpression = {\n kind: 'context_property_expr';\n /** The context entity IRI. Optional only while a `contextName` is unresolved. */\n contextIri?: string;\n /** A query-context reference resolved into `contextIri` by `lower()`. */\n contextName?: string;\n property: string;\n};\n\nexport type IRBinaryOperator =\n | '='\n | '!='\n | '>'\n | '>='\n | '<'\n | '<='\n | '+'\n | '-'\n | '*'\n | '/';\n\nexport type IRBinaryExpression = {\n kind: 'binary_expr';\n operator: IRBinaryOperator;\n left: IRExpression;\n right: IRExpression;\n};\n\nexport type IRLogicalOperator = 'and' | 'or';\n\nexport type IRLogicalExpression = {\n kind: 'logical_expr';\n operator: IRLogicalOperator;\n expressions: IRExpression[];\n};\n\nexport type IRNotExpression = {\n kind: 'not_expr';\n expression: IRExpression;\n};\n\nexport type IRFunctionExpression = {\n kind: 'function_expr';\n name: string;\n args: IRExpression[];\n};\n\nexport type IRAggregateExpression = {\n kind: 'aggregate_expr';\n name: 'count' | 'sum' | 'avg' | 'min' | 'max';\n args: IRExpression[];\n};\n\nexport type IRExistsExpression = {\n kind: 'exists_expr';\n pattern: IRGraphPattern;\n filter?: IRExpression;\n};\n\nexport type IRCreateMutation = {\n kind: 'create';\n shape: string;\n data: IRNodeData;\n};\n\nexport type IRTraversalPattern = {\n from: string; // source alias\n property: string; // property IRI\n to: string; // target alias\n};\n\nexport type IRUpdateMutation = {\n kind: 'update';\n shape: string;\n id: string;\n data: IRNodeData;\n traversalPatterns?: IRTraversalPattern[];\n};\n\n/**\n * Create-or-replace against a known id, in one request.\n *\n * Structurally identical to {@link IRUpdateMutation} — it lowers through the same\n * DELETE/INSERT/WHERE body — and differs in exactly one emitted triple: upsert also\n * asserts `?id rdf:type <targetClass>`, which the update path never writes.\n *\n * That single triple is the whole difference between the two, because `update`'s WHERE\n * is a bare `OPTIONAL` and so already matches when the node is absent. It is a distinct\n * IR kind rather than a flag on the update mutation so that a consumer which does not\n * know about upsert fails loudly, instead of quietly writing an untyped node and\n * reporting success.\n */\nexport type IRUpsertMutation = {\n kind: 'upsert';\n shape: string;\n id: string;\n data: IRNodeData;\n traversalPatterns?: IRTraversalPattern[];\n};\n\nexport type IRDeleteMutation = {\n kind: 'delete';\n shape: string;\n ids: NodeReferenceValue[];\n};\n\nexport type IRDeleteAllMutation = {\n kind: 'delete_all';\n shape: string;\n};\n\nexport type IRDeleteWhereMutation = {\n kind: 'delete_where';\n shape: string;\n where: IRExpression;\n wherePatterns: IRGraphPattern[];\n};\n\nexport type IRUpdateWhereMutation = {\n kind: 'update_where';\n shape: string;\n data: IRNodeData;\n where?: IRExpression;\n wherePatterns?: IRGraphPattern[];\n traversalPatterns?: IRTraversalPattern[];\n};\n\nexport type IRNodeData = {\n shape: string;\n fields: IRFieldUpdate[];\n id?: string;\n};\n\nexport type IRFieldUpdate = {\n property: string;\n value: IRFieldValue;\n};\n\nexport type IRSetModificationValue = {\n add?: IRFieldValue[];\n remove?: NodeReferenceValue[];\n};\n\nexport type IRFieldValue =\n | IRValue\n | Date\n | NodeReferenceValue\n | IRNodeData\n | IRSetModificationValue\n | IRFieldValue[]\n | IRExpression\n | undefined;\n\n// ---------------------------------------------------------------------------\n// Store result types\n// ---------------------------------------------------------------------------\n// These types describe what an IDataset implementation should return.\n// The calling layer (LinkedStorage via queryDispatch) threads the precise\n// DSL-level TypeScript result type back to the caller, so the store\n// only needs to produce data that satisfies these structural contracts.\n// ---------------------------------------------------------------------------\n\n/**\n * A single result row — an object with a node id and dynamic fields.\n * Used in select and create results.\n */\nexport type ResultRow = {id: string; [key: string]: ResultFieldValue};\n\n/**\n * Possible field values in a select or create result row.\n */\nexport type ResultFieldValue =\n | string\n | number\n | boolean\n | Date\n | null\n | undefined\n | ResultRow\n | ResultRow[]\n | string[]\n | number[]\n | boolean[]\n | Date[];\n\n/**\n * What `selectQuery` should return.\n *\n * - Array of rows for multi-result queries (the default).\n * - A single row when `query.singleResult` is true (`.one()` or subject-targeted query).\n * - `null` when `singleResult` is true and the target node doesn't exist.\n */\nexport type SelectResult = ResultRow[] | ResultRow | null;\n\n/**\n * What `createQuery` should return.\n *\n * A single row with the generated `id` and the created field values.\n * Nested creates appear as nested `ResultRow` objects (with their own ids).\n * Array fields (e.g. friends) appear as `ResultRow[]`.\n */\nexport type CreateResult = ResultRow;\n\n/**\n * When a set property is fully overwritten, the result contains `updatedTo`\n * with the new complete set of values.\n */\nexport type SetOverwriteResult = {updatedTo: ResultRow[]};\n\n/**\n * When a set property is incrementally modified with `{add, remove}`,\n * the result contains the added and removed entries.\n */\nexport type SetModificationResult = {added: ResultRow[]; removed: ResultRow[]};\n\n/**\n * Possible field values in an update result row.\n * Extends `ResultFieldValue` with set modification shapes.\n */\nexport type UpdateFieldValue =\n | ResultFieldValue\n | SetOverwriteResult\n | SetModificationResult;\n\n/**\n * What `updateQuery` should return.\n *\n * A single row with the target node's `id` and only the changed fields.\n * Fields not included in the update are omitted (not returned).\n *\n * Field value shapes depend on the type of update:\n * - Literal fields: the new value (string, number, boolean, Date).\n * - Single object fields: a `ResultRow` with the referenced/created node.\n * - Set overwrite (passing an array): `{updatedTo: ResultRow[]}`.\n * - Set add/remove (passing `{add, remove}`): `{added: ResultRow[], removed: ResultRow[]}`.\n * - Unset single field (passing `undefined` or `null`): field is `undefined`.\n * - Unset multi-value field (passing `undefined`): empty array `[]`.\n */\nexport type UpdateResult = {id: string; [key: string]: UpdateFieldValue};\n"]}
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { QueryFactory, } from './QueryFactory.js';
13
13
  import { getPropertyShapes } from '../shapes/nodeShapeData.js';
14
- import { resolveShapeConstructor } from '../utils/ShapeClass.js';
14
+ import { isSubShapeOf, resolveShapeConstructor } from '../utils/ShapeClass.js';
15
15
  import { isExpressionNode } from '../expressions/ExpressionNode.js';
16
16
  import { createProxiedPathBuilder } from './ProxiedPathBuilder.js';
17
17
  import { asContextRef } from './QueryContext.js';
@@ -128,18 +128,6 @@ export class MutationQueryFactory extends QueryFactory {
128
128
  const props = getPropertyShapes(shape, true);
129
129
  const fields = [];
130
130
  let id;
131
- // `__blank` asks for an anonymous node (a blank node without a type). It has no id by
132
- // definition, so it cannot be combined with one.
133
- const blank = obj.__blank === true;
134
- if ('__blank' in obj) {
135
- if (obj.__blank !== true) {
136
- throw new Error(`__blank must be true when given, got ${JSON.stringify(obj.__blank)}`);
137
- }
138
- delete obj.__blank;
139
- if ('__id' in obj || 'id' in obj) {
140
- throw new Error('A node cannot be both __blank and carry an id (__id or id).');
141
- }
142
- }
143
131
  if ('__id' in obj) {
144
132
  //if the object has a __id key, then we should use that in the result
145
133
  id = obj.__id.toString();
@@ -171,9 +159,6 @@ export class MutationQueryFactory extends QueryFactory {
171
159
  if (id) {
172
160
  res.__id = id;
173
161
  }
174
- if (blank) {
175
- res.__blank = true;
176
- }
177
162
  return res;
178
163
  }
179
164
  createNodePropertyValue(value, propShape) {
@@ -250,9 +235,14 @@ export class MutationQueryFactory extends QueryFactory {
250
235
  }
251
236
  //never keep a shape key in the value object
252
237
  if (value.shape) {
253
- //double check that IF a shape value is provided, that it matches the shape from the @objectProperty decorator
254
- if (value.shape.shape.id !== valueShape.id) {
255
- throw new Error(`The property 'shape' is reserved in LINCD and should not be used here in this way. The ${propShape.label} property already defines the shape of the value as ${propShape.label}. If you want to use a different shape, use the 'shape' key in the @objectProperty decorator.`);
238
+ // A value may name its own concrete shape when that is the declared value
239
+ // shape or a subclass of it (a Company under an Organization-typed property).
240
+ const ownShape = value.shape.shape;
241
+ if (ownShape && isSubShapeOf(ownShape, valueShape)) {
242
+ valueShape = ownShape;
243
+ }
244
+ else if (!ownShape || ownShape.id !== valueShape.id) {
245
+ throw new Error(`The 'shape' key of a value of ${propShape.label} must be its declared shape ${valueShape.label || valueShape.id} or a subclass of it, got ${(ownShape === null || ownShape === void 0 ? void 0 : ownShape.label) || (ownShape === null || ownShape === void 0 ? void 0 : ownShape.id) || 'an invalid shape'}.`);
256
246
  }
257
247
  // Drop it from a copy, never from the caller's object: the same data
258
248
  // is lowered again after the mutation ran (LiveQueryStore derives the
@@ -1 +1 @@
1
- {"version":3,"file":"MutationQuery.js","sourceRoot":"","sources":["../../../src/queries/MutationQuery.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type LiteralUpdateValue,\n type NodeDescriptionValue,\n NodeReferenceValue,\n type PropUpdateValue,\n QueryFactory,\n type SetModification,\n type SetModificationValue,\n type SinglePropertyUpdateValue,\n type UpdateNodePropertyValue,\n type UpdatePartial,\n} from './QueryFactory.js';\nimport {getPropertyShapes} from '../shapes/nodeShapeData.js';\nimport type {NodeShapeData, PropertyShapeData} from '../shapes/SHACL.js';\nimport {Shape, type ShapeConstructor} from '../shapes/Shape.js';\nimport {resolveShapeConstructor} from '../utils/ShapeClass.js';\nimport {isExpressionNode, ExpressionNode} from '../expressions/ExpressionNode.js';\nimport {createProxiedPathBuilder} from './ProxiedPathBuilder.js';\nimport {asContextRef} from './QueryContext.js';\nimport {\n assertValid,\n undeclaredPropertyMessage,\n type ValidationMode,\n} from '../shapes/validation.js';\n\nexport type NodeId = {id: string} | string;\n\n/** Options for {@link MutationQueryFactory.describe}. */\nexport interface DescribeOptions {\n /** Create data may carry a top-level `id`; update data may not. */\n allowTopLevelId?: boolean;\n /**\n * Validate the data against the shape before normalizing, throwing a\n * `ShapeValidationError` with every violation. Omit to skip validation\n * (normalization-only, e.g. when the caller has already validated).\n */\n validate?: ValidationMode;\n}\n\nexport class MutationQueryFactory extends QueryFactory {\n /**\n * Normalize raw create/update data into a {@link NodeDescriptionValue} — the\n * IR-free step shared by `toJSON()` and `lower()`. Kept on this base (which\n * imports no IR) so the builders can normalize for serialization without\n * pulling the canonical-IR pipeline.\n *\n * This is also the single validation gate: pass `validate` and the resolved\n * data is checked against the shape first, so `toJSON()`, `lower()` and\n * `exec()` reject exactly the same input. See `shapes/validation`.\n */\n describe(\n shape: NodeShapeData,\n data: unknown,\n options: DescribeOptions = {},\n ): NodeDescriptionValue {\n return this.convertUpdateObject(data as any, shape, options);\n }\n\n /** Normalize delete ids (strings / `{id}`) to `NodeReferenceValue[]` (IR-free). */\n normalizeNodeRefs(ids: NodeId[] | NodeId): NodeReferenceValue[] {\n return this.convertNodeReferences(ids);\n }\n\n protected convertUpdateObject(\n obj,\n shape: NodeShapeData,\n {allowTopLevelId = false, validate}: DescribeOptions = {},\n ): NodeDescriptionValue {\n let resolved;\n if (typeof obj === 'object' && !(obj instanceof Date) && obj !== null) {\n if (!allowTopLevelId && 'id' in obj) {\n throw new Error(\n 'You cannot use id in the top level of an update object',\n );\n }\n resolved = obj;\n } else if (typeof obj === 'function') {\n // A shape authored in a project exists only as metadata, so it has no authored\n // class. `getOrCreateShapeAdapter` derives a constructor from that metadata --\n // which is the documented return leg of the shape round trip (see\n // registerRuntimeShape). Resolving with `getShapeClass` alone made every runtime shape\n // fail here with \"Shape class not found\".\n const shapeClass = shape.id ? resolveShapeConstructor(shape.id) : undefined;\n if (!shapeClass) {\n throw new Error(`Shape class not found for ${shape.id || 'unknown'}`);\n }\n const proxy = createProxiedPathBuilder(shapeClass);\n const result = obj(proxy);\n if (typeof result !== 'object' || result === null) {\n throw new Error('Update function must return an object');\n }\n // Validate what the callback produced, not the callback itself.\n resolved = result;\n } else {\n throw new Error('Invalid update object');\n }\n if (validate) {\n assertValid(shape, resolved, {mode: validate});\n }\n return this.convertNodeDescription(resolved, shape);\n }\n\n protected isSetModification(obj, shape) {\n let hasAdd = obj.add;\n let hasRemove = obj.remove;\n let numKeysExpected = (hasAdd ? 1 : 0) + (hasRemove ? 1 : 0);\n let numKeys = Object.getOwnPropertyNames(obj).length;\n // A set modification is ONLY add/remove keys. Mixing them with other keys\n // (e.g. {add:[…], name:'x'}) must NOT be silently treated as a set mod —\n // that dropped the sibling fields. Require an exact key-count match for\n // both add and remove (mirrors isSetModificationValue).\n return (hasAdd || hasRemove) && numKeysExpected === numKeys;\n }\n\n protected convertSetModification(\n obj: SetModification<any>,\n shape: PropertyShapeData,\n ): SetModificationValue {\n if (!obj.add && !obj.remove) {\n throw new Error('Set modification should have either add or remove key');\n }\n const res: SetModificationValue = {};\n if (obj.add) {\n res.$add = this.convertSetAddValue(obj.add, shape);\n }\n if (obj.remove) {\n res.$remove = this.convertSetRemoveValue(obj.remove, shape);\n }\n return res;\n }\n\n protected convertSetRemoveValue(\n obj: UpdatePartial | UpdatePartial[],\n shape: PropertyShapeData,\n ): NodeReferenceValue[] {\n //the user can either pass an array of node references or a single node reference\n //either way we should return an array of node reference values\n if (Array.isArray(obj)) {\n return obj.map((o) => this.convertSingleRemoveValue(o, shape));\n } else {\n return [this.convertSingleRemoveValue(obj, shape)];\n }\n }\n\n protected convertSetAddValue(\n obj: UpdatePartial | UpdatePartial[],\n shape: PropertyShapeData,\n ): UpdatePartial[] {\n if (Array.isArray(obj)) {\n return obj.map((o) => this.convertUpdateValue(o, shape) as UpdatePartial);\n } else {\n return [this.convertUpdateValue(obj, shape) as UpdatePartial];\n }\n }\n\n protected convertSingleRemoveValue(\n value,\n shape: PropertyShapeData,\n ): NodeReferenceValue {\n const ctx = asContextRef(value);\n if (ctx) {\n return ctx as unknown as NodeReferenceValue;\n }\n if (this.isNodeReference(value)) {\n return this.convertNodeReference(value);\n } else {\n throw new Error(\n `Invalid value for ${shape.label}.$remove. Expected an object with an id as key: {id:string}`,\n );\n }\n }\n\n protected convertNodeDescription(\n inputObj: Record<string, unknown>,\n shape: NodeShapeData,\n ): NodeDescriptionValue {\n // Shallow-copy so we never mutate the caller's object\n const obj = {...inputObj};\n const props = getPropertyShapes(shape, true);\n const fields: UpdateNodePropertyValue[] = [];\n let id;\n // `__blank` asks for an anonymous node (a blank node without a type). It has no id by\n // definition, so it cannot be combined with one.\n const blank = obj.__blank === true;\n if ('__blank' in obj) {\n if (obj.__blank !== true) {\n throw new Error(`__blank must be true when given, got ${JSON.stringify(obj.__blank)}`);\n }\n delete obj.__blank;\n if ('__id' in obj || 'id' in obj) {\n throw new Error('A node cannot be both __blank and carry an id (__id or id).');\n }\n }\n if ('__id' in obj) {\n //if the object has a __id key, then we should use that in the result\n id = obj.__id.toString();\n //but we should not include it in the fields\n delete obj.__id;\n } else if ('id' in obj) {\n //if the object has an id key alongside other data properties,\n //treat it as a nested create with a predefined ID\n id = String(obj.id);\n delete obj.id;\n }\n for (var key in obj) {\n let propShape = props.find((p) => p.label === key);\n if (!propShape) {\n // Also reported as an `sh:ClosedConstraintComponent` violation when the\n // caller validates; kept here because normalization cannot build a field\n // without a property shape (`describe()` may run without validation).\n throw Error(undeclaredPropertyMessage(key, shape));\n } else {\n fields.push(this.createNodePropertyValue(obj[key], propShape));\n }\n }\n const res: NodeDescriptionValue = {\n fields,\n shape,\n };\n if (id) {\n res.__id = id;\n }\n if (blank) {\n res.__blank = true;\n }\n\n return res;\n }\n\n protected createNodePropertyValue(\n value,\n propShape: PropertyShapeData,\n ): UpdateNodePropertyValue {\n return {\n prop: propShape,\n val: this.convertUpdateValue(value, propShape),\n } as UpdateNodePropertyValue;\n }\n\n protected convertUpdateValue(\n value,\n propShape?: PropertyShapeData,\n allowArrays: boolean = true,\n ): PropUpdateValue {\n // ExpressionNode → pass through as-is (will be converted to IRExpression by IRMutation)\n if (isExpressionNode(value)) {\n return value as unknown as PropUpdateValue;\n }\n\n // Query-context reference → preserve the live ref (do NOT collapse to {id},\n // which would read an unresolved `.id` as undefined). Handles both an unset\n // PendingQueryContext and a resolved context shape; both serialize as a {@ctx}\n // marker and are resolved — or throw — at lowering time.\n {\n const ctx = asContextRef(value);\n if (ctx) return ctx as unknown as PropUpdateValue;\n }\n\n //single value which will\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n value instanceof Date\n ) {\n return value as LiteralUpdateValue;\n }\n\n //if multiple items are given as value of this prop\n if (Array.isArray(value)) {\n if (!allowArrays) {\n throw new Error('Nested arrays are not allowed as values of keys');\n }\n //then convert each value, but disallow nested arrays moving forward\n return value.map((o) => {\n return this.convertUpdateValue(o, propShape, false);\n }) as SinglePropertyUpdateValue[];\n } else if (typeof value === 'undefined') {\n return value;\n } else if (value === null) {\n //unsetting a value with null is also possible. But we pass it as undefined in the query object\n return undefined;\n } else if (typeof value === 'object') {\n if (this.isNodeReference(value)) {\n return this.convertNodeReference(value);\n } else {\n let valueShape: NodeShapeData = null;\n if (propShape.valueShape) {\n const shapeClass = resolveShapeConstructor(propShape.valueShape);\n valueShape = shapeClass?.shape || null;\n if (!valueShape) {\n throw new Error(\n `Shape class not found for ${propShape.valueShape.id}`,\n );\n }\n }\n //pass the value shape of the property as the node shape of this value\n if (!propShape.valueShape) {\n //It's possible to define the shape of the value in the value itself for properties who do not define the shape in their objectProperty\n if (value.shape) {\n if (!value.shape.shape) {\n throw new Error(\n `The value of property \"shape\" is invalid and should be a class that extends Shape.`,\n );\n }\n valueShape = (value.shape as typeof Shape).shape;\n } else {\n //TODO: not sure if this should be an error. Does every @linkedObject need to define the shape of the values?\n // If not, then how do we continue? because currently we use the value shape to look up further property shapes\n throw new Error(\n `Cannot update properties with plain objects if the shape of the values is not known. Make sure get/set ${propShape.parentNodeShape.label}.${propShape.label} defines the 'shape' key in its @objectProperty decorator.`,\n );\n }\n }\n //never keep a shape key in the value object\n if (value.shape) {\n //double check that IF a shape value is provided, that it matches the shape from the @objectProperty decorator\n if ((value.shape as typeof Shape).shape.id !== valueShape.id) {\n throw new Error(\n `The property 'shape' is reserved in LINCD and should not be used here in this way. The ${propShape.label} property already defines the shape of the value as ${propShape.label}. If you want to use a different shape, use the 'shape' key in the @objectProperty decorator.`,\n );\n }\n // Drop it from a copy, never from the caller's object: the same data\n // is lowered again after the mutation ran (LiveQueryStore derives the\n // mutation's effects from it), and without its `shape` key the value\n // can no longer be validated.\n const {shape: _shape, ...rest} = value;\n value = rest;\n }\n\n if (this.isSetModification(value, propShape)) {\n return this.convertSetModification(value, propShape);\n } else {\n return this.convertNodeDescription(value, valueShape);\n }\n }\n }\n throw new Error(`Unsupported update value type: ${typeof value}`);\n }\n\n protected isNodeReference(obj): obj is NodeReferenceValue {\n //check if obj is an object with only an id property\n //if additional data properties are present, it's a nested create with predefined ID\n return typeof obj === 'object' && obj !== null && 'id' in obj && Object.keys(obj).length === 1;\n }\n\n protected convertNodeReferences(\n input: NodeId[] | NodeId,\n ): NodeReferenceValue[] {\n if (Array.isArray(input)) {\n return input.map((o) => {\n return this.convertNodeReferenceOrString(o);\n });\n } else {\n return [this.convertNodeReferenceOrString(input)];\n }\n }\n\n protected convertNodeReferenceOrString(\n o: {id: string} | string,\n ): NodeReferenceValue {\n if (typeof o === 'string') {\n return {id: o};\n } else if (this.isNodeReference(o)) {\n return this.convertNodeReference(o);\n } else {\n throw new Error(`Invalid node reference: ${JSON.stringify(o)}`);\n }\n }\n\n protected convertNodeReference(obj: {id: string}): NodeReferenceValue {\n return {id: obj.id};\n }\n}\n"]}
1
+ {"version":3,"file":"MutationQuery.js","sourceRoot":"","sources":["../../../src/queries/MutationQuery.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type LiteralUpdateValue,\n type NodeDescriptionValue,\n NodeReferenceValue,\n type PropUpdateValue,\n QueryFactory,\n type SetModification,\n type SetModificationValue,\n type SinglePropertyUpdateValue,\n type UpdateNodePropertyValue,\n type UpdatePartial,\n} from './QueryFactory.js';\nimport {getPropertyShapes} from '../shapes/nodeShapeData.js';\nimport type {NodeShapeData, PropertyShapeData} from '../shapes/SHACL.js';\nimport {Shape, type ShapeConstructor} from '../shapes/Shape.js';\nimport {isSubShapeOf, resolveShapeConstructor} from '../utils/ShapeClass.js';\nimport {isExpressionNode, ExpressionNode} from '../expressions/ExpressionNode.js';\nimport {createProxiedPathBuilder} from './ProxiedPathBuilder.js';\nimport {asContextRef} from './QueryContext.js';\nimport {\n assertValid,\n undeclaredPropertyMessage,\n type ValidationMode,\n} from '../shapes/validation.js';\n\nexport type NodeId = {id: string} | string;\n\n/** Options for {@link MutationQueryFactory.describe}. */\nexport interface DescribeOptions {\n /** Create data may carry a top-level `id`; update data may not. */\n allowTopLevelId?: boolean;\n /**\n * Validate the data against the shape before normalizing, throwing a\n * `ShapeValidationError` with every violation. Omit to skip validation\n * (normalization-only, e.g. when the caller has already validated).\n */\n validate?: ValidationMode;\n}\n\nexport class MutationQueryFactory extends QueryFactory {\n /**\n * Normalize raw create/update data into a {@link NodeDescriptionValue} — the\n * IR-free step shared by `toJSON()` and `lower()`. Kept on this base (which\n * imports no IR) so the builders can normalize for serialization without\n * pulling the canonical-IR pipeline.\n *\n * This is also the single validation gate: pass `validate` and the resolved\n * data is checked against the shape first, so `toJSON()`, `lower()` and\n * `exec()` reject exactly the same input. See `shapes/validation`.\n */\n describe(\n shape: NodeShapeData,\n data: unknown,\n options: DescribeOptions = {},\n ): NodeDescriptionValue {\n return this.convertUpdateObject(data as any, shape, options);\n }\n\n /** Normalize delete ids (strings / `{id}`) to `NodeReferenceValue[]` (IR-free). */\n normalizeNodeRefs(ids: NodeId[] | NodeId): NodeReferenceValue[] {\n return this.convertNodeReferences(ids);\n }\n\n protected convertUpdateObject(\n obj,\n shape: NodeShapeData,\n {allowTopLevelId = false, validate}: DescribeOptions = {},\n ): NodeDescriptionValue {\n let resolved;\n if (typeof obj === 'object' && !(obj instanceof Date) && obj !== null) {\n if (!allowTopLevelId && 'id' in obj) {\n throw new Error(\n 'You cannot use id in the top level of an update object',\n );\n }\n resolved = obj;\n } else if (typeof obj === 'function') {\n // A shape authored in a project exists only as metadata, so it has no authored\n // class. `getOrCreateShapeAdapter` derives a constructor from that metadata --\n // which is the documented return leg of the shape round trip (see\n // registerRuntimeShape). Resolving with `getShapeClass` alone made every runtime shape\n // fail here with \"Shape class not found\".\n const shapeClass = shape.id ? resolveShapeConstructor(shape.id) : undefined;\n if (!shapeClass) {\n throw new Error(`Shape class not found for ${shape.id || 'unknown'}`);\n }\n const proxy = createProxiedPathBuilder(shapeClass);\n const result = obj(proxy);\n if (typeof result !== 'object' || result === null) {\n throw new Error('Update function must return an object');\n }\n // Validate what the callback produced, not the callback itself.\n resolved = result;\n } else {\n throw new Error('Invalid update object');\n }\n if (validate) {\n assertValid(shape, resolved, {mode: validate});\n }\n return this.convertNodeDescription(resolved, shape);\n }\n\n protected isSetModification(obj, shape) {\n let hasAdd = obj.add;\n let hasRemove = obj.remove;\n let numKeysExpected = (hasAdd ? 1 : 0) + (hasRemove ? 1 : 0);\n let numKeys = Object.getOwnPropertyNames(obj).length;\n // A set modification is ONLY add/remove keys. Mixing them with other keys\n // (e.g. {add:[…], name:'x'}) must NOT be silently treated as a set mod —\n // that dropped the sibling fields. Require an exact key-count match for\n // both add and remove (mirrors isSetModificationValue).\n return (hasAdd || hasRemove) && numKeysExpected === numKeys;\n }\n\n protected convertSetModification(\n obj: SetModification<any>,\n shape: PropertyShapeData,\n ): SetModificationValue {\n if (!obj.add && !obj.remove) {\n throw new Error('Set modification should have either add or remove key');\n }\n const res: SetModificationValue = {};\n if (obj.add) {\n res.$add = this.convertSetAddValue(obj.add, shape);\n }\n if (obj.remove) {\n res.$remove = this.convertSetRemoveValue(obj.remove, shape);\n }\n return res;\n }\n\n protected convertSetRemoveValue(\n obj: UpdatePartial | UpdatePartial[],\n shape: PropertyShapeData,\n ): NodeReferenceValue[] {\n //the user can either pass an array of node references or a single node reference\n //either way we should return an array of node reference values\n if (Array.isArray(obj)) {\n return obj.map((o) => this.convertSingleRemoveValue(o, shape));\n } else {\n return [this.convertSingleRemoveValue(obj, shape)];\n }\n }\n\n protected convertSetAddValue(\n obj: UpdatePartial | UpdatePartial[],\n shape: PropertyShapeData,\n ): UpdatePartial[] {\n if (Array.isArray(obj)) {\n return obj.map((o) => this.convertUpdateValue(o, shape) as UpdatePartial);\n } else {\n return [this.convertUpdateValue(obj, shape) as UpdatePartial];\n }\n }\n\n protected convertSingleRemoveValue(\n value,\n shape: PropertyShapeData,\n ): NodeReferenceValue {\n const ctx = asContextRef(value);\n if (ctx) {\n return ctx as unknown as NodeReferenceValue;\n }\n if (this.isNodeReference(value)) {\n return this.convertNodeReference(value);\n } else {\n throw new Error(\n `Invalid value for ${shape.label}.$remove. Expected an object with an id as key: {id:string}`,\n );\n }\n }\n\n protected convertNodeDescription(\n inputObj: Record<string, unknown>,\n shape: NodeShapeData,\n ): NodeDescriptionValue {\n // Shallow-copy so we never mutate the caller's object\n const obj = {...inputObj};\n const props = getPropertyShapes(shape, true);\n const fields: UpdateNodePropertyValue[] = [];\n let id;\n if ('__id' in obj) {\n //if the object has a __id key, then we should use that in the result\n id = obj.__id.toString();\n //but we should not include it in the fields\n delete obj.__id;\n } else if ('id' in obj) {\n //if the object has an id key alongside other data properties,\n //treat it as a nested create with a predefined ID\n id = String(obj.id);\n delete obj.id;\n }\n for (var key in obj) {\n let propShape = props.find((p) => p.label === key);\n if (!propShape) {\n // Also reported as an `sh:ClosedConstraintComponent` violation when the\n // caller validates; kept here because normalization cannot build a field\n // without a property shape (`describe()` may run without validation).\n throw Error(undeclaredPropertyMessage(key, shape));\n } else {\n fields.push(this.createNodePropertyValue(obj[key], propShape));\n }\n }\n const res: NodeDescriptionValue = {\n fields,\n shape,\n };\n if (id) {\n res.__id = id;\n }\n\n return res;\n }\n\n protected createNodePropertyValue(\n value,\n propShape: PropertyShapeData,\n ): UpdateNodePropertyValue {\n return {\n prop: propShape,\n val: this.convertUpdateValue(value, propShape),\n } as UpdateNodePropertyValue;\n }\n\n protected convertUpdateValue(\n value,\n propShape?: PropertyShapeData,\n allowArrays: boolean = true,\n ): PropUpdateValue {\n // ExpressionNode → pass through as-is (will be converted to IRExpression by IRMutation)\n if (isExpressionNode(value)) {\n return value as unknown as PropUpdateValue;\n }\n\n // Query-context reference → preserve the live ref (do NOT collapse to {id},\n // which would read an unresolved `.id` as undefined). Handles both an unset\n // PendingQueryContext and a resolved context shape; both serialize as a {@ctx}\n // marker and are resolved — or throw — at lowering time.\n {\n const ctx = asContextRef(value);\n if (ctx) return ctx as unknown as PropUpdateValue;\n }\n\n //single value which will\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n value instanceof Date\n ) {\n return value as LiteralUpdateValue;\n }\n\n //if multiple items are given as value of this prop\n if (Array.isArray(value)) {\n if (!allowArrays) {\n throw new Error('Nested arrays are not allowed as values of keys');\n }\n //then convert each value, but disallow nested arrays moving forward\n return value.map((o) => {\n return this.convertUpdateValue(o, propShape, false);\n }) as SinglePropertyUpdateValue[];\n } else if (typeof value === 'undefined') {\n return value;\n } else if (value === null) {\n //unsetting a value with null is also possible. But we pass it as undefined in the query object\n return undefined;\n } else if (typeof value === 'object') {\n if (this.isNodeReference(value)) {\n return this.convertNodeReference(value);\n } else {\n let valueShape: NodeShapeData = null;\n if (propShape.valueShape) {\n const shapeClass = resolveShapeConstructor(propShape.valueShape);\n valueShape = shapeClass?.shape || null;\n if (!valueShape) {\n throw new Error(\n `Shape class not found for ${propShape.valueShape.id}`,\n );\n }\n }\n //pass the value shape of the property as the node shape of this value\n if (!propShape.valueShape) {\n //It's possible to define the shape of the value in the value itself for properties who do not define the shape in their objectProperty\n if (value.shape) {\n if (!value.shape.shape) {\n throw new Error(\n `The value of property \"shape\" is invalid and should be a class that extends Shape.`,\n );\n }\n valueShape = (value.shape as typeof Shape).shape;\n } else {\n //TODO: not sure if this should be an error. Does every @linkedObject need to define the shape of the values?\n // If not, then how do we continue? because currently we use the value shape to look up further property shapes\n throw new Error(\n `Cannot update properties with plain objects if the shape of the values is not known. Make sure get/set ${propShape.parentNodeShape.label}.${propShape.label} defines the 'shape' key in its @objectProperty decorator.`,\n );\n }\n }\n //never keep a shape key in the value object\n if (value.shape) {\n // A value may name its own concrete shape when that is the declared value\n // shape or a subclass of it (a Company under an Organization-typed property).\n const ownShape = (value.shape as typeof Shape).shape;\n if (ownShape && isSubShapeOf(ownShape, valueShape)) {\n valueShape = ownShape;\n } else if (!ownShape || ownShape.id !== valueShape.id) {\n throw new Error(\n `The 'shape' key of a value of ${propShape.label} must be its declared shape ${valueShape.label || valueShape.id} or a subclass of it, got ${ownShape?.label || ownShape?.id || 'an invalid shape'}.`,\n );\n }\n // Drop it from a copy, never from the caller's object: the same data\n // is lowered again after the mutation ran (LiveQueryStore derives the\n // mutation's effects from it), and without its `shape` key the value\n // can no longer be validated.\n const {shape: _shape, ...rest} = value;\n value = rest;\n }\n\n if (this.isSetModification(value, propShape)) {\n return this.convertSetModification(value, propShape);\n } else {\n return this.convertNodeDescription(value, valueShape);\n }\n }\n }\n throw new Error(`Unsupported update value type: ${typeof value}`);\n }\n\n protected isNodeReference(obj): obj is NodeReferenceValue {\n //check if obj is an object with only an id property\n //if additional data properties are present, it's a nested create with predefined ID\n return typeof obj === 'object' && obj !== null && 'id' in obj && Object.keys(obj).length === 1;\n }\n\n protected convertNodeReferences(\n input: NodeId[] | NodeId,\n ): NodeReferenceValue[] {\n if (Array.isArray(input)) {\n return input.map((o) => {\n return this.convertNodeReferenceOrString(o);\n });\n } else {\n return [this.convertNodeReferenceOrString(input)];\n }\n }\n\n protected convertNodeReferenceOrString(\n o: {id: string} | string,\n ): NodeReferenceValue {\n if (typeof o === 'string') {\n return {id: o};\n } else if (this.isNodeReference(o)) {\n return this.convertNodeReference(o);\n } else {\n throw new Error(`Invalid node reference: ${JSON.stringify(o)}`);\n }\n }\n\n protected convertNodeReference(obj: {id: string}): NodeReferenceValue {\n return {id: obj.id};\n }\n}\n"]}
@@ -41,11 +41,12 @@ export type MutationValueJSON = string | number | boolean | null | {
41
41
  '@unset': true;
42
42
  } | MutationNodeDataJSON | DslJsonValue;
43
43
  /**
44
- * A node description in **path-keyed** form: `label → value`, plus three reserved
45
- * keys — `__id` (a fixed/predefined id), `__blank` (`true`: a nested node written as
46
- * a blank node) and `__shape` (the concrete shape IRI, emitted only when it differs
47
- * from the shape inferred from context, e.g. a subclass instance under a
48
- * superclass-typed relation).
44
+ * A node description in **path-keyed** form: `label → value`, plus two reserved
45
+ * keys — `__id` (a fixed/predefined id) and `__shape` (the concrete shape IRI,
46
+ * emitted only when it differs from the shape inferred from context, e.g. a
47
+ * subclass instance under a superclass-typed relation). Whether a nested node is
48
+ * written as a blank node is not on the wire: the lowering decides it from the
49
+ * property's `sh:nodeKind`.
49
50
  */
50
51
  export type MutationNodeDataJSON = {
51
52
  [key: string]: MutationValueJSON | undefined;
@@ -24,13 +24,11 @@ import { isSetModificationValue, } from './QueryFactory.js';
24
24
  import { getPropertyShape } from '../shapes/nodeShapeData.js';
25
25
  import { ExpressionNode, isExpressionNode } from '../expressions/ExpressionNode.js';
26
26
  import { PendingQueryContext } from './QueryContext.js';
27
- import { getShapeClass } from '../utils/ShapeClass.js';
27
+ import { getShapeClass, resolveShapeConstructor } from '../utils/ShapeClass.js';
28
28
  import { encodeValueExpr, decodeValueExpr, } from './DslJsonExpression.js';
29
29
  /** Reserved node-data keys (not property labels). */
30
30
  const NODE_ID_KEY = '__id';
31
31
  const NODE_SHAPE_KEY = '__shape';
32
- /** Marks a nested node written as a blank node (`NodeDescriptionValue.__blank`). */
33
- const NODE_BLANK_KEY = '__blank';
34
32
  // =============================================================================
35
33
  // Value codec
36
34
  // =============================================================================
@@ -97,8 +95,6 @@ export function encodeNodeData(desc, expectedShape) {
97
95
  const json = {};
98
96
  if (desc.__id)
99
97
  json[NODE_ID_KEY] = desc.__id;
100
- if (desc.__blank)
101
- json[NODE_BLANK_KEY] = true;
102
98
  if (!expectedShape || desc.shape.id !== expectedShape.id) {
103
99
  json[NODE_SHAPE_KEY] = desc.shape.id;
104
100
  }
@@ -171,7 +167,33 @@ function decodeValueToRawInner(json, currentShape, prop) {
171
167
  return new ExpressionNode(ir, refs);
172
168
  }
173
169
  // Otherwise: a bare path-keyed nested-node create.
174
- return decodeNodeDataToRaw(o, (_a = valueShapeOf(prop)) !== null && _a !== void 0 ? _a : currentShape);
170
+ return decodeNestedNodeDataToRaw(o, (_a = valueShapeOf(prop)) !== null && _a !== void 0 ? _a : currentShape);
171
+ }
172
+ /**
173
+ * Decode a nested node and, when it names its concrete shape (`__shape`), carry that
174
+ * shape as the reserved `shape` key `.set()` accepts on a nested value. Without it the
175
+ * builders validate and lower the value against the property's declared value shape —
176
+ * or none, for a polymorphic slot like `PropertyShape.path` — and reject the fields
177
+ * only the concrete shape has.
178
+ */
179
+ /**
180
+ * The class for a node's `__shape`. Either registry — a project-authored shape exists only
181
+ * as metadata. Throws like `lowerMutationJSON` does, so both decode paths reject an
182
+ * unregistered shape instead of one of them silently validating against another.
183
+ */
184
+ function requireNodeShapeClass(shapeId) {
185
+ const shapeClass = resolveShapeConstructor(shapeId);
186
+ if (!shapeClass) {
187
+ throw new Error(`Shape '${shapeId}' is not registered. The receiving side must have the shape registered to decode mutation DSL-JSON.`);
188
+ }
189
+ return shapeClass;
190
+ }
191
+ function decodeNestedNodeDataToRaw(json, shape) {
192
+ const obj = decodeNodeDataToRaw(json, shape);
193
+ const shapeId = json[NODE_SHAPE_KEY];
194
+ if (typeof shapeId === 'string')
195
+ obj.shape = requireNodeShapeClass(shapeId);
196
+ return obj;
175
197
  }
176
198
  function requireShapeForExpr(shape) {
177
199
  if (!shape) {
@@ -185,19 +207,14 @@ function requireShapeForExpr(shape) {
185
207
  * {@link encodeNodeData}.
186
208
  */
187
209
  export function decodeNodeDataToRaw(json, shape) {
188
- var _a, _b;
189
210
  const nodeShape = typeof json[NODE_SHAPE_KEY] === 'string'
190
- ? (_b = (_a = getShapeClass(json[NODE_SHAPE_KEY])) === null || _a === void 0 ? void 0 : _a.shape) !== null && _b !== void 0 ? _b : shape
211
+ ? requireNodeShapeClass(json[NODE_SHAPE_KEY]).shape
191
212
  : shape;
192
213
  const obj = {};
193
214
  if (typeof json[NODE_ID_KEY] === 'string')
194
215
  obj.id = json[NODE_ID_KEY];
195
- if (json[NODE_BLANK_KEY] === true)
196
- obj.__blank = true;
197
216
  for (const [key, val] of Object.entries(json)) {
198
- if (key === NODE_ID_KEY || key === NODE_SHAPE_KEY || key === NODE_BLANK_KEY)
199
- continue;
200
- if (val === undefined)
217
+ if (key === NODE_ID_KEY || key === NODE_SHAPE_KEY || val === undefined)
201
218
  continue;
202
219
  // Skip prototype-polluting keys from untrusted DSL-JSON input.
203
220
  if (key === '__proto__' || key === 'constructor' || key === 'prototype')