@sigloch/graph-api-core 5.0.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/edge-ops.js +21 -7
- package/dist/format-e-codec.d.ts +6 -0
- package/dist/format-e-codec.js +15 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/se-descriptor.d.ts +24 -0
- package/dist/se-descriptor.js +61 -0
- package/package.json +3 -3
package/dist/browser.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export { findRoot } from './find-root.js';
|
|
15
15
|
export type { RootQueryGraph } from './find-root.js';
|
|
16
|
-
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph } from './se-descriptor.js';
|
|
16
|
+
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
17
17
|
export { FormatECodec } from './format-e-codec.js';
|
|
18
18
|
export { isValidTrace, tracePatternsOf } from './types.js';
|
|
19
19
|
export type { GraphNode, GraphEdge, Graph } from './types.js';
|
package/dist/browser.js
CHANGED
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
* @author andreas@siglochconsulting
|
|
13
13
|
*/
|
|
14
14
|
export { findRoot } from './find-root.js';
|
|
15
|
-
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph } from './se-descriptor.js';
|
|
15
|
+
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
16
16
|
export { FormatECodec } from './format-e-codec.js';
|
|
17
17
|
export { isValidTrace, tracePatternsOf } from './types.js';
|
package/dist/edge-ops.js
CHANGED
|
@@ -31,13 +31,20 @@ export function mergeNodes(graph, sourceUid, targetUid) {
|
|
|
31
31
|
if (!graph.nodes.some(n => n.uid === sourceUid)) {
|
|
32
32
|
throw new Error(`merge-nodes: source node not found: ${sourceUid}`);
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
// CR-SM-253: two passes, not one. The single-pass form checked the REWIRED edge
|
|
35
|
+
// against what the loop had collected so far, but appended an untouched edge
|
|
36
|
+
// unchecked — so the result depended on the order of `graph.edges`: source's edge
|
|
37
|
+
// first gave `B-verify->R` twice, target's edge first gave it once. Kuzu keys on
|
|
38
|
+
// (source, type, target) and silently keeps one, so the in-memory graph then claimed
|
|
39
|
+
// an edge the store did not have. Collecting every survivor first and deduplicating
|
|
40
|
+
// against the complete set makes the result order-independent.
|
|
35
41
|
const removedEdges = [];
|
|
36
|
-
const
|
|
42
|
+
const kept = [];
|
|
43
|
+
const rewiredEdges = [];
|
|
37
44
|
for (const e of graph.edges) {
|
|
38
45
|
const touchesSource = e.sourceId === sourceUid || e.targetId === sourceUid;
|
|
39
46
|
if (!touchesSource) {
|
|
40
|
-
|
|
47
|
+
kept.push(e);
|
|
41
48
|
continue;
|
|
42
49
|
}
|
|
43
50
|
removedEdges.push(e);
|
|
@@ -49,11 +56,18 @@ export function mergeNodes(graph, sourceUid, targetUid) {
|
|
|
49
56
|
if (rewired.sourceId === rewired.targetId) {
|
|
50
57
|
continue; // self-edge created by rewiring — discarded
|
|
51
58
|
}
|
|
59
|
+
rewiredEdges.push(rewired);
|
|
60
|
+
}
|
|
61
|
+
// `addedEdges` is the delta the store persists. An edge the target already carried
|
|
62
|
+
// is not an addition — persisting it would write the duplicate into the store path
|
|
63
|
+
// that the in-memory dedup just prevented.
|
|
64
|
+
const edges = kept.slice();
|
|
65
|
+
const addedEdges = [];
|
|
66
|
+
for (const rewired of rewiredEdges) {
|
|
67
|
+
if (edges.some(x => sameEdge(x, rewired)))
|
|
68
|
+
continue;
|
|
69
|
+
edges.push(rewired);
|
|
52
70
|
addedEdges.push(rewired);
|
|
53
|
-
// Two incident edges (or one incident + one pre-existing) can rewire onto
|
|
54
|
-
// the same (source,target,type) — keep the result graph free of duplicates.
|
|
55
|
-
if (!edges.some(x => sameEdge(x, rewired)))
|
|
56
|
-
edges.push(rewired);
|
|
57
71
|
}
|
|
58
72
|
const nodes = graph.nodes.filter(n => n.uid !== sourceUid);
|
|
59
73
|
return { graph: { nodes, edges }, removedNode: sourceUid, removedEdges, addedEdges };
|
package/dist/format-e-codec.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ export declare class FormatECodec {
|
|
|
23
23
|
private parseNodeLine;
|
|
24
24
|
private parseEdgeLine;
|
|
25
25
|
private parseMerge;
|
|
26
|
+
/**
|
|
27
|
+
* CR-SM-251: derselbe Hydrations-Ort wie beim `@key value`-Pfad. Vorher war dies der einzige
|
|
28
|
+
* Pfad OHNE Hydration — hart auf `Record<string, string>` typisiert, womit `[concept:true]`
|
|
29
|
+
* als String "true" ankam und jede `=== true`-Ausnahme wirkungslos blieb (CR-GC-334 hatte
|
|
30
|
+
* genau diese Doppelung schon einmal fuer Objekte aufgeloest, nur eine Zeile weiter oben).
|
|
31
|
+
*/
|
|
26
32
|
private parseInlineAttrs;
|
|
27
33
|
/**
|
|
28
34
|
* CR-SM-215: fan-out serialization — edges sharing `(sourceId, edgeType)` collapse
|
package/dist/format-e-codec.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* family (`TYPE-slug`, `Name.TypeAbbr.Counter`, `cand_<hex>`); typing by spelling made
|
|
8
8
|
* every foreign convention fail silently instead of loudly.
|
|
9
9
|
*/
|
|
10
|
-
import { hydrateAttrValue } from '@sigloch/contracts/se';
|
|
10
|
+
import { hydrateAttrValue, attributeTypeOf } from '@sigloch/contracts/se';
|
|
11
11
|
import { isValidTrace, tracePatternsOf } from './types.js';
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Regex patterns
|
|
@@ -78,7 +78,7 @@ export class FormatECodec {
|
|
|
78
78
|
// them back as objects. Kept as a string, `realRef`/`testRef` fail their schema and
|
|
79
79
|
// the element reads as UNBOUND — R-19/R-20 fired on every node authored through this
|
|
80
80
|
// path. Same rule as the contracts parser, imported, not re-implemented.
|
|
81
|
-
lastOp.attributes[attrMatch[1]] = hydrateAttrValue(attrMatch[2].trim());
|
|
81
|
+
lastOp.attributes[attrMatch[1]] = hydrateAttrValue(attrMatch[2].trim(), attributeTypeOf(lastOp.elementType, attrMatch[1]));
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
84
|
errors.push(`@attribute line without preceding node: "${line}"`);
|
|
@@ -197,7 +197,7 @@ export class FormatECodec {
|
|
|
197
197
|
const attrMatch = INLINE_ATTRS_RE.exec(rest);
|
|
198
198
|
if (attrMatch) {
|
|
199
199
|
mainPart = rest.slice(0, attrMatch.index).trim();
|
|
200
|
-
inlineAttrs = this.parseInlineAttrs(attrMatch[1]);
|
|
200
|
+
inlineAttrs = this.parseInlineAttrs(attrMatch[1], nodeType);
|
|
201
201
|
}
|
|
202
202
|
// Split uid|description
|
|
203
203
|
const pipeIdx = mainPart.indexOf('|');
|
|
@@ -237,6 +237,9 @@ export class FormatECodec {
|
|
|
237
237
|
const action = OP_PREFIX[opChar] ?? 'add';
|
|
238
238
|
// Extract inline attributes from the line
|
|
239
239
|
let mainPart = rest;
|
|
240
|
+
// CR-SM-251: Kanten-Attribute (cardinality/constraint/notes) sind Strings und bleiben es —
|
|
241
|
+
// `attributeTypeOf` kennt nur Element-Attribute, ohne Typ faellt die Hydrierung auf String
|
|
242
|
+
// zurueck. Der Typ ist trotzdem `unknown`, damit hier kein zweiter Hydrations-Pfad entsteht.
|
|
240
243
|
let inlineAttrs;
|
|
241
244
|
const attrMatch = INLINE_ATTRS_RE.exec(rest);
|
|
242
245
|
if (attrMatch) {
|
|
@@ -308,12 +311,19 @@ export class FormatECodec {
|
|
|
308
311
|
sourceIds: parts,
|
|
309
312
|
});
|
|
310
313
|
}
|
|
311
|
-
|
|
314
|
+
/**
|
|
315
|
+
* CR-SM-251: derselbe Hydrations-Ort wie beim `@key value`-Pfad. Vorher war dies der einzige
|
|
316
|
+
* Pfad OHNE Hydration — hart auf `Record<string, string>` typisiert, womit `[concept:true]`
|
|
317
|
+
* als String "true" ankam und jede `=== true`-Ausnahme wirkungslos blieb (CR-GC-334 hatte
|
|
318
|
+
* genau diese Doppelung schon einmal fuer Objekte aufgeloest, nur eine Zeile weiter oben).
|
|
319
|
+
*/
|
|
320
|
+
parseInlineAttrs(raw, nodeType) {
|
|
312
321
|
const attrs = {};
|
|
313
322
|
for (const pair of raw.split(',')) {
|
|
314
323
|
const colonIdx = pair.indexOf(':');
|
|
315
324
|
if (colonIdx > 0) {
|
|
316
|
-
|
|
325
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
326
|
+
attrs[key] = hydrateAttrValue(pair.slice(colonIdx + 1).trim(), attributeTypeOf(nodeType, key));
|
|
317
327
|
}
|
|
318
328
|
}
|
|
319
329
|
return attrs;
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { MemoryAdapter } from './memory-adapter.js';
|
|
|
22
22
|
export type { TransportAdapter, TransportConfig } from './transport-adapter.js';
|
|
23
23
|
export { createGraphApi } from './factory.js';
|
|
24
24
|
export type { GraphApiConfig } from './factory.js';
|
|
25
|
-
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph } from './se-descriptor.js';
|
|
25
|
+
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
26
26
|
export { findRoot } from './find-root.js';
|
|
27
27
|
export type { RootQueryGraph } from './find-root.js';
|
|
28
28
|
export { applyEdgeOps, updateEdge, mergeNodes } from './edge-ops.js';
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export { MemoryAdapter } from './memory-adapter.js';
|
|
|
19
19
|
// Factory
|
|
20
20
|
export { createGraphApi } from './factory.js';
|
|
21
21
|
// SE OntologyDescriptor (derived from @sigloch/contracts/se) [CR-195a]
|
|
22
|
-
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph } from './se-descriptor.js';
|
|
22
|
+
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
23
23
|
// Root-Suche — strukturelle Wurzel (SYS ohne eingehende compose), statt UID-Hardcode
|
|
24
24
|
export { findRoot } from './find-root.js';
|
|
25
25
|
// Edge ops — update-edge (flip/retype) + merge-nodes, shared by GraphService.mutate()
|
package/dist/se-descriptor.d.ts
CHANGED
|
@@ -17,6 +17,30 @@ import type { Graph, OntologyDescriptor } from './types.js';
|
|
|
17
17
|
* attributes (asil/method/kinds/status) are lifted out of `attributes`.
|
|
18
18
|
*/
|
|
19
19
|
export declare function projectToOntologyGraph(graph: Graph): OntologyGraph;
|
|
20
|
+
/**
|
|
21
|
+
* The INVERSE of `projectToOntologyGraph`: an OntologyGraph (elements/traces) back
|
|
22
|
+
* to the ontology-agnostic Graph (nodes/edges) — every attribute restored under
|
|
23
|
+
* `attributes`, where the rules read them.
|
|
24
|
+
*
|
|
25
|
+
* Why this must be published (CR-SM-254): only the forward direction was exported,
|
|
26
|
+
* so every consumer that READS a committed `*.graph.json` hand-rolled its own mirror
|
|
27
|
+
* — and the mirrors drifted. Two were found wrong the same day, both in the same
|
|
28
|
+
* half: they lifted `status/asil/method/kinds` and forgot `realRef`/`testRefs`, so
|
|
29
|
+
* `R-19`/`R-20` silently read "unbound" on a fully bound graph and a dashboard
|
|
30
|
+
* reported `TRR 0/214` against a store that said `214/214`. A hand-rolled inverse
|
|
31
|
+
* cannot be kept honest by review; it has to have one home.
|
|
32
|
+
*
|
|
33
|
+
* Accepts BOTH shapes in the wild, which is the whole point:
|
|
34
|
+
* - what THIS module emits — attributes nested under `attributes`, plus the
|
|
35
|
+
* lifted convenience fields (status/asil/method/kinds) alongside;
|
|
36
|
+
* - a FLATTENED snapshot — `{id, type, name, description, ...attributes}` with no
|
|
37
|
+
* `attributes` key at all (graphcode's committed SSOT since its CR-GC-219, which
|
|
38
|
+
* drops the nested duplicate so the file a human diffs carries each value once).
|
|
39
|
+
*
|
|
40
|
+
* Nested wins nothing: a key already present at top level is never clobbered, so
|
|
41
|
+
* re-import of an already-flat element is idempotent.
|
|
42
|
+
*/
|
|
43
|
+
export declare function fromOntologyGraph(json: OntologyGraph): Graph;
|
|
20
44
|
/**
|
|
21
45
|
* Canonical SE OntologyDescriptor (ontology + V3 rules + MT metrics), version-pinned
|
|
22
46
|
* to contracts/se ONTOLOGY_VERSION. Plug into GraphService / FormatECodec.
|
package/dist/se-descriptor.js
CHANGED
|
@@ -42,6 +42,67 @@ export function projectToOntologyGraph(graph) {
|
|
|
42
42
|
}));
|
|
43
43
|
return { elements, traces };
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The INVERSE of `projectToOntologyGraph`: an OntologyGraph (elements/traces) back
|
|
47
|
+
* to the ontology-agnostic Graph (nodes/edges) — every attribute restored under
|
|
48
|
+
* `attributes`, where the rules read them.
|
|
49
|
+
*
|
|
50
|
+
* Why this must be published (CR-SM-254): only the forward direction was exported,
|
|
51
|
+
* so every consumer that READS a committed `*.graph.json` hand-rolled its own mirror
|
|
52
|
+
* — and the mirrors drifted. Two were found wrong the same day, both in the same
|
|
53
|
+
* half: they lifted `status/asil/method/kinds` and forgot `realRef`/`testRefs`, so
|
|
54
|
+
* `R-19`/`R-20` silently read "unbound" on a fully bound graph and a dashboard
|
|
55
|
+
* reported `TRR 0/214` against a store that said `214/214`. A hand-rolled inverse
|
|
56
|
+
* cannot be kept honest by review; it has to have one home.
|
|
57
|
+
*
|
|
58
|
+
* Accepts BOTH shapes in the wild, which is the whole point:
|
|
59
|
+
* - what THIS module emits — attributes nested under `attributes`, plus the
|
|
60
|
+
* lifted convenience fields (status/asil/method/kinds) alongside;
|
|
61
|
+
* - a FLATTENED snapshot — `{id, type, name, description, ...attributes}` with no
|
|
62
|
+
* `attributes` key at all (graphcode's committed SSOT since its CR-GC-219, which
|
|
63
|
+
* drops the nested duplicate so the file a human diffs carries each value once).
|
|
64
|
+
*
|
|
65
|
+
* Nested wins nothing: a key already present at top level is never clobbered, so
|
|
66
|
+
* re-import of an already-flat element is idempotent.
|
|
67
|
+
*/
|
|
68
|
+
export function fromOntologyGraph(json) {
|
|
69
|
+
const nodes = (json.elements ?? []).map((e) => {
|
|
70
|
+
const { id, type, name, description, created_at, updated_at, ...rest } = e;
|
|
71
|
+
return {
|
|
72
|
+
uid: id,
|
|
73
|
+
type,
|
|
74
|
+
name: name ?? id,
|
|
75
|
+
description: description ?? '',
|
|
76
|
+
createdAt: created_at,
|
|
77
|
+
updatedAt: updated_at,
|
|
78
|
+
attributes: liftAttributes(rest),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
const edges = (json.traces ?? []).map((t) => {
|
|
82
|
+
const { source, target, type, ...rest } = t;
|
|
83
|
+
return { sourceId: source, targetId: target, edgeType: type, attributes: liftAttributes(rest) };
|
|
84
|
+
});
|
|
85
|
+
return { nodes, edges };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Merge a nested `attributes` object UP one level and drop the nesting key, so both
|
|
89
|
+
* the nested and the flattened shape yield the same `attributes`. Idempotent: an
|
|
90
|
+
* entry with no nested object is returned unchanged.
|
|
91
|
+
*/
|
|
92
|
+
function liftAttributes(rest) {
|
|
93
|
+
const nested = rest.attributes;
|
|
94
|
+
if (nested === null || typeof nested !== 'object' || Array.isArray(nested)) {
|
|
95
|
+
const { attributes: _absent, ...flat } = rest;
|
|
96
|
+
return flat;
|
|
97
|
+
}
|
|
98
|
+
const { attributes: _drop, ...top } = rest;
|
|
99
|
+
const out = { ...top };
|
|
100
|
+
for (const [k, v] of Object.entries(nested)) {
|
|
101
|
+
if (!(k in out))
|
|
102
|
+
out[k] = v;
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
45
106
|
/**
|
|
46
107
|
* The contracts/se rule catalog, adapted to graph-api-core's Rule shape.
|
|
47
108
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigloch/graph-api-core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@sigloch/contracts": ">=5 <
|
|
46
|
+
"@sigloch/contracts": ">=5 <7",
|
|
47
47
|
"kuzu-wasm": "^0.11.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@sigloch/contracts": "^
|
|
55
|
+
"@sigloch/contracts": "^6.0.0",
|
|
56
56
|
"kuzu-wasm": "^0.11.3"
|
|
57
57
|
}
|
|
58
58
|
}
|