@canon-protocol/sdk 8.5.0 → 8.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/graph/GraphBuilder.js +87 -76
- package/package.json +2 -2
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { CanonObjectParser } from '../parsing/CanonObjectParser.js';
|
|
2
|
-
import { ResourceTypeClassifier } from '../ctl/ResourceTypeClassifier.js';
|
|
3
1
|
/**
|
|
4
2
|
* Node types in a Canon graph.
|
|
5
3
|
*/
|
|
@@ -56,61 +54,60 @@ export class GraphBuilder {
|
|
|
56
54
|
* This is the main entry point — handles imports, resolution, and classification.
|
|
57
55
|
*/
|
|
58
56
|
static async buildFromRepository(repository) {
|
|
59
|
-
const
|
|
60
|
-
const canons = await objectParser.parseCanons(repository);
|
|
57
|
+
const documents = await repository.getAllDocumentsAsync();
|
|
61
58
|
const nodes = [];
|
|
62
59
|
const edges = [];
|
|
63
60
|
const knownClassNames = new Set();
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
// Build a map from "publisher/packageName" to the full namespace string for alias resolution
|
|
62
|
+
const packageToNamespace = new Map();
|
|
63
|
+
for (const document of documents) {
|
|
64
|
+
const ns = document.metadata.namespace_;
|
|
65
|
+
if (ns) {
|
|
66
|
+
const namespace = ns.toString();
|
|
67
|
+
const packageKey = `${ns.publisher}/${ns.package_}`;
|
|
68
|
+
packageToNamespace.set(packageKey, namespace);
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
|
-
//
|
|
74
|
-
for (const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const properties = {};
|
|
83
|
-
const entity = subject.entity ?? {};
|
|
84
|
-
// Extract properties from the raw entity for metadata
|
|
85
|
-
for (const [key, value] of Object.entries(entity)) {
|
|
86
|
-
if (key === 'type')
|
|
87
|
-
continue; // handled by nodeType
|
|
88
|
-
properties[key] = value;
|
|
71
|
+
// First pass: identify all classes across all documents
|
|
72
|
+
for (const document of documents) {
|
|
73
|
+
for (const [name, entity] of Object.entries(document.body)) {
|
|
74
|
+
const type = entity?.type;
|
|
75
|
+
if (!type)
|
|
76
|
+
continue;
|
|
77
|
+
if (isClassTypeName(type)) {
|
|
78
|
+
knownClassNames.add(name);
|
|
79
|
+
}
|
|
89
80
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
}
|
|
82
|
+
// Second pass: build graph
|
|
83
|
+
for (const document of documents) {
|
|
84
|
+
const namespace = document.metadata.namespace_
|
|
85
|
+
? document.metadata.namespace_.toString()
|
|
86
|
+
: '';
|
|
87
|
+
const aliasMap = buildAliasMap(document, packageToNamespace);
|
|
88
|
+
for (const [name, entity] of Object.entries(document.body)) {
|
|
89
|
+
if (!entity || typeof entity !== 'object')
|
|
90
|
+
continue;
|
|
91
|
+
const typeName = entity.type;
|
|
92
|
+
const nodeType = classifyByTypeName(typeName, name, knownClassNames);
|
|
93
|
+
const nodeId = namespace ? `${namespace}/${name}` : name;
|
|
94
|
+
const properties = {};
|
|
95
|
+
for (const [key, value] of Object.entries(entity)) {
|
|
96
|
+
if (key === 'type')
|
|
95
97
|
continue;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
properties[predName] = obj.subject.name;
|
|
99
|
-
}
|
|
100
|
-
else if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
|
|
101
|
-
properties[predName] = obj;
|
|
98
|
+
if (typeof value !== 'object' || value === null) {
|
|
99
|
+
properties[key] = value;
|
|
102
100
|
}
|
|
103
101
|
}
|
|
102
|
+
nodes.push({
|
|
103
|
+
id: nodeId,
|
|
104
|
+
label: entity.label ?? name,
|
|
105
|
+
type: nodeType,
|
|
106
|
+
namespace,
|
|
107
|
+
properties,
|
|
108
|
+
});
|
|
109
|
+
extractEdges(nodeId, entity, nodeType, knownClassNames, edges, namespace, aliasMap);
|
|
104
110
|
}
|
|
105
|
-
nodes.push({
|
|
106
|
-
id: nodeId,
|
|
107
|
-
label: entity.label ?? subject.name,
|
|
108
|
-
type: nodeType,
|
|
109
|
-
namespace: subject.namespace ?? '',
|
|
110
|
-
properties,
|
|
111
|
-
});
|
|
112
|
-
// Extract edges from entity properties
|
|
113
|
-
extractEdges(nodeId, entity, nodeType, knownClassNames, edges, subject.namespace ?? '');
|
|
114
111
|
}
|
|
115
112
|
return { nodes, edges };
|
|
116
113
|
}
|
|
@@ -134,6 +131,8 @@ export class GraphBuilder {
|
|
|
134
131
|
knownClassNames.add(name);
|
|
135
132
|
}
|
|
136
133
|
}
|
|
134
|
+
// Build partial alias map from document imports
|
|
135
|
+
const aliasMap = buildAliasMap(document);
|
|
137
136
|
// Second pass: build graph
|
|
138
137
|
for (const [name, entity] of Object.entries(document.body)) {
|
|
139
138
|
if (!entity || typeof entity !== 'object')
|
|
@@ -156,27 +155,27 @@ export class GraphBuilder {
|
|
|
156
155
|
namespace,
|
|
157
156
|
properties,
|
|
158
157
|
});
|
|
159
|
-
extractEdges(nodeId, entity, nodeType, knownClassNames, edges, namespace);
|
|
158
|
+
extractEdges(nodeId, entity, nodeType, knownClassNames, edges, namespace, aliasMap);
|
|
160
159
|
}
|
|
161
160
|
return { nodes, edges };
|
|
162
161
|
}
|
|
163
162
|
}
|
|
164
163
|
// --- Helpers ---
|
|
165
|
-
function
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return
|
|
164
|
+
function buildAliasMap(document, packageToNamespace) {
|
|
165
|
+
const aliasMap = new Map();
|
|
166
|
+
if (document.metadata?.imports) {
|
|
167
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
168
|
+
for (const import_ of imports) {
|
|
169
|
+
const alias = import_.alias ?? import_.packageName;
|
|
170
|
+
const packageKey = `${publisher}/${import_.packageName}`;
|
|
171
|
+
// Use resolved namespace from repository if available, otherwise approximate from import
|
|
172
|
+
const targetNamespace = packageToNamespace?.get(packageKey)
|
|
173
|
+
?? `${publisher}/${import_.packageName}@${import_.version}`;
|
|
174
|
+
aliasMap.set(alias, targetNamespace);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return aliasMap;
|
|
180
179
|
}
|
|
181
180
|
function isClassTypeName(typeName) {
|
|
182
181
|
const name = typeName.split('.').pop() ?? typeName;
|
|
@@ -198,13 +197,13 @@ function classifyByTypeName(typeName, entityName, knownClassNames) {
|
|
|
198
197
|
return NodeType.Unknown;
|
|
199
198
|
}
|
|
200
199
|
}
|
|
201
|
-
function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namespace) {
|
|
200
|
+
function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namespace, aliasMap) {
|
|
202
201
|
const type = entity.type;
|
|
203
202
|
// subClassOf → SubClassOf edge
|
|
204
203
|
if (entity.subClassOf && typeof entity.subClassOf === 'string') {
|
|
205
204
|
edges.push({
|
|
206
205
|
source: sourceId,
|
|
207
|
-
target: resolveLocalId(entity.subClassOf, namespace),
|
|
206
|
+
target: resolveLocalId(entity.subClassOf, namespace, aliasMap),
|
|
208
207
|
type: EdgeType.SubClassOf,
|
|
209
208
|
label: 'subClassOf',
|
|
210
209
|
});
|
|
@@ -213,7 +212,7 @@ function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namesp
|
|
|
213
212
|
if (entity.subPropertyOf && typeof entity.subPropertyOf === 'string') {
|
|
214
213
|
edges.push({
|
|
215
214
|
source: sourceId,
|
|
216
|
-
target: resolveLocalId(entity.subPropertyOf, namespace),
|
|
215
|
+
target: resolveLocalId(entity.subPropertyOf, namespace, aliasMap),
|
|
217
216
|
type: EdgeType.SubPropertyOf,
|
|
218
217
|
label: 'subPropertyOf',
|
|
219
218
|
});
|
|
@@ -222,7 +221,7 @@ function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namesp
|
|
|
222
221
|
if (entity.domain && typeof entity.domain === 'string') {
|
|
223
222
|
edges.push({
|
|
224
223
|
source: sourceId,
|
|
225
|
-
target: resolveLocalId(entity.domain, namespace),
|
|
224
|
+
target: resolveLocalId(entity.domain, namespace, aliasMap),
|
|
226
225
|
type: EdgeType.Domain,
|
|
227
226
|
label: 'domain',
|
|
228
227
|
});
|
|
@@ -231,7 +230,7 @@ function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namesp
|
|
|
231
230
|
if (entity.range && typeof entity.range === 'string') {
|
|
232
231
|
edges.push({
|
|
233
232
|
source: sourceId,
|
|
234
|
-
target: resolveLocalId(entity.range, namespace),
|
|
233
|
+
target: resolveLocalId(entity.range, namespace, aliasMap),
|
|
235
234
|
type: EdgeType.Range,
|
|
236
235
|
label: 'range',
|
|
237
236
|
});
|
|
@@ -241,7 +240,7 @@ function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namesp
|
|
|
241
240
|
const typeName = type.split('.').pop() ?? type;
|
|
242
241
|
edges.push({
|
|
243
242
|
source: sourceId,
|
|
244
|
-
target: resolveLocalId(typeName, namespace),
|
|
243
|
+
target: resolveLocalId(typeName, namespace, aliasMap),
|
|
245
244
|
type: EdgeType.InstanceOf,
|
|
246
245
|
label: 'type',
|
|
247
246
|
});
|
|
@@ -261,19 +260,31 @@ function extractEdges(sourceId, entity, nodeType, knownClassNames, edges, namesp
|
|
|
261
260
|
const rangeStr = typeof entity.range === 'string' ? entity.range : null;
|
|
262
261
|
if (domainStr && rangeStr) {
|
|
263
262
|
edges.push({
|
|
264
|
-
source: resolveLocalId(domainStr, namespace),
|
|
265
|
-
target: resolveLocalId(rangeStr, namespace),
|
|
263
|
+
source: resolveLocalId(domainStr, namespace, aliasMap),
|
|
264
|
+
target: resolveLocalId(rangeStr, namespace, aliasMap),
|
|
266
265
|
type: EdgeType.ObjectRelationship,
|
|
267
266
|
label: entity.label ?? sourceId.split('/').pop() ?? '',
|
|
268
267
|
});
|
|
269
268
|
}
|
|
270
269
|
}
|
|
271
270
|
}
|
|
272
|
-
function resolveLocalId(name, namespace) {
|
|
273
|
-
//
|
|
271
|
+
function resolveLocalId(name, namespace, aliasMap) {
|
|
272
|
+
// Already qualified with a slash — return as-is
|
|
274
273
|
if (name.includes('/'))
|
|
275
274
|
return name;
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
275
|
+
// Handle aliased references (e.g., "sdg.gdpr")
|
|
276
|
+
if (name.includes('.')) {
|
|
277
|
+
const dotIndex = name.indexOf('.');
|
|
278
|
+
const alias = name.substring(0, dotIndex);
|
|
279
|
+
const entityName = name.substring(dotIndex + 1);
|
|
280
|
+
if (aliasMap) {
|
|
281
|
+
const targetNamespace = aliasMap.get(alias);
|
|
282
|
+
if (targetNamespace) {
|
|
283
|
+
return `${targetNamespace}/${entityName}`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Fallback: use entity name with local namespace
|
|
287
|
+
return namespace ? `${namespace}/${entityName}` : entityName;
|
|
288
|
+
}
|
|
289
|
+
return namespace ? `${namespace}/${name}` : name;
|
|
279
290
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canon-protocol/sdk",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "Canon Protocol SDK - Document repository and parsing implementations for TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"yaml-parser"
|
|
67
67
|
],
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@canon-protocol/types": "^8.
|
|
69
|
+
"@canon-protocol/types": "^8.6.0",
|
|
70
70
|
"ignore": "^7.0.5",
|
|
71
71
|
"js-yaml": "^4.1.0"
|
|
72
72
|
},
|