@api-client/core 0.18.16 → 0.18.18
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/build/src/{modeling → decorators}/observed.d.ts +3 -3
- package/build/src/decorators/observed.d.ts.map +1 -0
- package/build/src/{modeling → decorators}/observed.js +4 -4
- package/build/src/decorators/observed.js.map +1 -0
- package/build/src/modeling/ApiModel.js +1 -1
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/DataDomain.d.ts +7 -2
- package/build/src/modeling/DataDomain.d.ts.map +1 -1
- package/build/src/modeling/DataDomain.js +15 -2
- package/build/src/modeling/DataDomain.js.map +1 -1
- package/build/src/modeling/DomainAssociation.d.ts +7 -0
- package/build/src/modeling/DomainAssociation.d.ts.map +1 -1
- package/build/src/modeling/DomainAssociation.js +44 -1
- package/build/src/modeling/DomainAssociation.js.map +1 -1
- package/build/src/modeling/DomainEntity.d.ts +6 -0
- package/build/src/modeling/DomainEntity.d.ts.map +1 -1
- package/build/src/modeling/DomainEntity.js +21 -1
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/DomainModel.js +1 -1
- package/build/src/modeling/DomainModel.js.map +1 -1
- package/build/src/modeling/DomainNamespace.js +1 -1
- package/build/src/modeling/DomainNamespace.js.map +1 -1
- package/build/src/modeling/DomainProperty.d.ts +5 -0
- package/build/src/modeling/DomainProperty.d.ts.map +1 -1
- package/build/src/modeling/DomainProperty.js +38 -1
- package/build/src/modeling/DomainProperty.js.map +1 -1
- package/build/src/modeling/DomainSerialization.d.ts +6 -3
- package/build/src/modeling/DomainSerialization.d.ts.map +1 -1
- package/build/src/modeling/DomainSerialization.js +374 -52
- package/build/src/modeling/DomainSerialization.js.map +1 -1
- package/build/src/modeling/types.d.ts +69 -2
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/src/models/Thing.js +1 -1
- package/build/src/models/Thing.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +10 -10
- package/package.json +2 -1
- package/src/{modeling → decorators}/observed.ts +5 -5
- package/src/modeling/ApiModel.ts +1 -1
- package/src/modeling/DataDomain.ts +24 -3
- package/src/modeling/DomainAssociation.ts +51 -1
- package/src/modeling/DomainEntity.ts +24 -1
- package/src/modeling/DomainModel.ts +1 -1
- package/src/modeling/DomainNamespace.ts +1 -1
- package/src/modeling/DomainProperty.ts +43 -1
- package/src/modeling/DomainSerialization.ts +440 -56
- package/src/modeling/types.ts +73 -2
- package/src/models/Thing.ts +1 -1
- package/tests/unit/decorators/observed.spec.ts +527 -0
- package/tests/unit/modeling/data_domain_serialization.spec.ts +508 -0
- package/tests/unit/modeling/domain_asociation.spec.ts +376 -0
- package/tests/unit/modeling/domain_entity.spec.ts +147 -0
- package/tests/unit/modeling/domain_property.spec.ts +273 -0
- package/build/src/modeling/observed.d.ts.map +0 -1
- package/build/src/modeling/observed.js.map +0 -1
|
@@ -55,7 +55,125 @@ function writeEdges(g, domainKey) {
|
|
|
55
55
|
}
|
|
56
56
|
return result;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Validates the graph consistency before serialization.
|
|
60
|
+
* Ensures that all required edges exist for properties and associations.
|
|
61
|
+
* @param g The graph to validate
|
|
62
|
+
* @param domainKey The domain key to validate
|
|
63
|
+
* @throws Error if the graph is inconsistent
|
|
64
|
+
*/
|
|
65
|
+
function validateGraphConsistency(g, domainKey) {
|
|
66
|
+
const validationErrors = [];
|
|
67
|
+
// Get all nodes that belong to this domain
|
|
68
|
+
const domainNodes = new Set();
|
|
69
|
+
for (const nodeId of g.nodes()) {
|
|
70
|
+
const node = g.node(nodeId);
|
|
71
|
+
if (node && node.domain.key === domainKey) {
|
|
72
|
+
domainNodes.add(nodeId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Validate that properties and associations have parent edges
|
|
76
|
+
for (const nodeId of domainNodes) {
|
|
77
|
+
const node = g.node(nodeId);
|
|
78
|
+
if (!node)
|
|
79
|
+
continue;
|
|
80
|
+
if (node.kind === DomainPropertyKind || node.kind === DomainAssociationKind) {
|
|
81
|
+
// These nodes must have exactly one incoming edge from their parent entity
|
|
82
|
+
const incomingEdges = [...g.inEdges(nodeId)];
|
|
83
|
+
const parentEdges = incomingEdges.filter((edge) => {
|
|
84
|
+
const sourceNode = g.node(edge.v);
|
|
85
|
+
return sourceNode && sourceNode.domain.key === domainKey;
|
|
86
|
+
});
|
|
87
|
+
if (parentEdges.length === 0) {
|
|
88
|
+
const nodeType = node.kind === DomainPropertyKind ? 'Property' : 'Association';
|
|
89
|
+
validationErrors.push(`${nodeType} "${node.info.name}" (${nodeId}) has no parent entity edge. ` +
|
|
90
|
+
`This will cause deserialization to fail.`);
|
|
91
|
+
}
|
|
92
|
+
else if (parentEdges.length > 1) {
|
|
93
|
+
const nodeType = node.kind === DomainPropertyKind ? 'Property' : 'Association';
|
|
94
|
+
const parentIds = parentEdges.map((e) => e.v).join(', ');
|
|
95
|
+
validationErrors.push(`${nodeType} "${node.info.name}" (${nodeId}) has multiple parent edges: ${parentIds}. ` +
|
|
96
|
+
`This may cause deserialization issues.`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Validate that the parent is actually an entity
|
|
100
|
+
const parentEdge = parentEdges[0];
|
|
101
|
+
const parentNode = g.node(parentEdge.v);
|
|
102
|
+
if (!parentNode || parentNode.kind !== DomainEntityKind) {
|
|
103
|
+
const nodeType = node.kind === DomainPropertyKind ? 'Property' : 'Association';
|
|
104
|
+
validationErrors.push(`${nodeType} "${node.info.name}" (${nodeId}) has a parent that is not an entity. ` +
|
|
105
|
+
`Parent: ${parentNode?.kind || 'unknown'} (${parentEdge.v})`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Validate that entities have model parents
|
|
110
|
+
if (node.kind === DomainEntityKind) {
|
|
111
|
+
const parents = [...g.parents(nodeId)];
|
|
112
|
+
const hasModelParent = parents.some((parentId) => {
|
|
113
|
+
const parent = g.node(parentId);
|
|
114
|
+
return parent && parent.kind === DomainModelKind;
|
|
115
|
+
});
|
|
116
|
+
if (!hasModelParent) {
|
|
117
|
+
validationErrors.push(`Entity "${node.info.name}" (${nodeId}) has no model parent. ` + `Entities must belong to a model.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Validate that models have either namespace parents or are at root level
|
|
121
|
+
if (node.kind === DomainModelKind) {
|
|
122
|
+
const parents = [...g.parents(nodeId)];
|
|
123
|
+
const hasNamespaceParent = parents.some((parentId) => {
|
|
124
|
+
const parent = g.node(parentId);
|
|
125
|
+
return parent && parent.kind === DomainNamespaceKind;
|
|
126
|
+
});
|
|
127
|
+
// Models can either be at root level (no parents) or have a namespace parent
|
|
128
|
+
// They should not have other types of parents
|
|
129
|
+
if (parents.length > 0 && !hasNamespaceParent) {
|
|
130
|
+
const invalidParents = parents
|
|
131
|
+
.map((parentId) => {
|
|
132
|
+
const parent = g.node(parentId);
|
|
133
|
+
return `${parent?.kind || 'unknown'} (${parentId})`;
|
|
134
|
+
})
|
|
135
|
+
.join(', ');
|
|
136
|
+
validationErrors.push(`Model "${node.info.name}" (${nodeId}) has invalid parent types: ${invalidParents}. ` +
|
|
137
|
+
`Models can only belong to namespaces or be at root level.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Validate association targets exist
|
|
142
|
+
for (const nodeId of domainNodes) {
|
|
143
|
+
const node = g.node(nodeId);
|
|
144
|
+
if (node && node.kind === DomainAssociationKind) {
|
|
145
|
+
const association = node;
|
|
146
|
+
const outgoingEdges = [...g.outEdges(nodeId)];
|
|
147
|
+
// Check that association has target edges
|
|
148
|
+
const targetEdges = outgoingEdges.filter((edge) => {
|
|
149
|
+
const edgeData = g.edge(edge);
|
|
150
|
+
return edgeData && edgeData.type === 'association';
|
|
151
|
+
});
|
|
152
|
+
if (targetEdges.length === 0) {
|
|
153
|
+
// This is general warning message, do not an error.
|
|
154
|
+
// We can serialize and deserialize associations without targets.
|
|
155
|
+
}
|
|
156
|
+
// Validate that all target entities exist
|
|
157
|
+
for (const targetEdge of targetEdges) {
|
|
158
|
+
const targetNode = g.node(targetEdge.w);
|
|
159
|
+
if (!targetNode) {
|
|
160
|
+
validationErrors.push(`Association "${association.info.name}" (${nodeId}) references non-existent target: ${targetEdge.w}`);
|
|
161
|
+
}
|
|
162
|
+
else if (targetNode.kind !== DomainEntityKind) {
|
|
163
|
+
validationErrors.push(`Association "${association.info.name}" (${nodeId}) references non-entity target: ` +
|
|
164
|
+
`${targetNode.kind} (${targetEdge.w})`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (validationErrors.length > 0) {
|
|
170
|
+
throw new Error(`Graph consistency validation failed for domain "${domainKey}":\n` +
|
|
171
|
+
validationErrors.map((error) => ` - ${error}`).join('\n'));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
58
174
|
export function serialize(g, domainKey) {
|
|
175
|
+
// Validate graph consistency before serialization
|
|
176
|
+
validateGraphConsistency(g, domainKey);
|
|
59
177
|
const json = {
|
|
60
178
|
options: {
|
|
61
179
|
directed: g.isDirected(),
|
|
@@ -173,43 +291,111 @@ function findEdgeParent(child, edges) {
|
|
|
173
291
|
* @param root The DataDomain instance to use as the root for the graph
|
|
174
292
|
* @param entry The node entry to restore
|
|
175
293
|
* @param edges The list of serialized graph edges
|
|
176
|
-
* @
|
|
177
|
-
* @
|
|
294
|
+
* @param mode The deserialization mode
|
|
295
|
+
* @param issues Array to collect issues in lenient mode
|
|
296
|
+
* @returns The restored node instance or undefined if failed in lenient mode
|
|
297
|
+
* @throws Error if the entry is malformed or the kind is unknown in strict mode
|
|
178
298
|
*/
|
|
179
|
-
function prepareNode(root, entry, edges) {
|
|
299
|
+
function prepareNode(root, entry, edges, mode, issues) {
|
|
180
300
|
if (!entry) {
|
|
181
|
-
|
|
301
|
+
const issue = {
|
|
302
|
+
type: 'malformed_entry',
|
|
303
|
+
severity: 'error',
|
|
304
|
+
message: 'Unable to restore data domain graph. Malformed node entry',
|
|
305
|
+
};
|
|
306
|
+
issues.push(issue);
|
|
307
|
+
if (mode === 'strict') {
|
|
308
|
+
throw new Error(issue.message);
|
|
309
|
+
}
|
|
310
|
+
return undefined;
|
|
182
311
|
}
|
|
183
312
|
const domainElement = entry;
|
|
184
313
|
if (!domainElement.kind) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
314
|
+
const issue = {
|
|
315
|
+
type: 'malformed_entry',
|
|
316
|
+
severity: 'error',
|
|
317
|
+
message: 'Unable to restore data domain graph. Node entry missing kind property',
|
|
318
|
+
affectedKey: domainElement.key,
|
|
319
|
+
};
|
|
320
|
+
issues.push(issue);
|
|
321
|
+
if (mode === 'strict') {
|
|
322
|
+
throw new Error(issue.message);
|
|
323
|
+
}
|
|
324
|
+
return undefined;
|
|
195
325
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (!parent) {
|
|
200
|
-
throw new Error(`Unable to restore data domain graph. Malformed node entry`);
|
|
326
|
+
try {
|
|
327
|
+
if (domainElement.kind === DomainNamespaceKind) {
|
|
328
|
+
return new DomainNamespace(root, domainElement);
|
|
201
329
|
}
|
|
202
|
-
|
|
330
|
+
if (domainElement.kind === DomainModelKind) {
|
|
331
|
+
return new DomainModel(root, domainElement);
|
|
332
|
+
}
|
|
333
|
+
if (domainElement.kind === DomainEntityKind) {
|
|
334
|
+
return new DomainEntity(root, domainElement);
|
|
335
|
+
}
|
|
336
|
+
if (domainElement.kind === DomainPropertyKind) {
|
|
337
|
+
const typed = domainElement;
|
|
338
|
+
const parent = findEdgeParent(typed.key, edges);
|
|
339
|
+
if (!parent) {
|
|
340
|
+
const issue = {
|
|
341
|
+
type: 'missing_edge',
|
|
342
|
+
severity: 'error',
|
|
343
|
+
message: `Property "${typed.info?.name || typed.key}" has no parent entity edge`,
|
|
344
|
+
affectedKey: typed.key,
|
|
345
|
+
resolution: mode === 'lenient' ? 'Property will be skipped' : undefined,
|
|
346
|
+
};
|
|
347
|
+
issues.push(issue);
|
|
348
|
+
if (mode === 'strict') {
|
|
349
|
+
throw new Error(`Unable to restore data domain graph. Malformed node entry`);
|
|
350
|
+
}
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
return new DomainProperty(root, parent, typed);
|
|
354
|
+
}
|
|
355
|
+
if (domainElement.kind === DomainAssociationKind) {
|
|
356
|
+
const typed = domainElement;
|
|
357
|
+
const parent = findEdgeParent(typed.key, edges);
|
|
358
|
+
if (!parent) {
|
|
359
|
+
const issue = {
|
|
360
|
+
type: 'missing_edge',
|
|
361
|
+
severity: 'error',
|
|
362
|
+
message: `Association "${typed.info?.name || typed.key}" has no parent entity edge`,
|
|
363
|
+
affectedKey: typed.key,
|
|
364
|
+
resolution: mode === 'lenient' ? 'Association will be skipped' : undefined,
|
|
365
|
+
};
|
|
366
|
+
issues.push(issue);
|
|
367
|
+
if (mode === 'strict') {
|
|
368
|
+
throw new Error(`Unable to restore data domain graph. Malformed node entry`);
|
|
369
|
+
}
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
return new DomainAssociation(root, parent, typed);
|
|
373
|
+
}
|
|
374
|
+
const issue = {
|
|
375
|
+
type: 'unknown_kind',
|
|
376
|
+
severity: 'error',
|
|
377
|
+
message: `Unknown node kind: ${domainElement.kind}`,
|
|
378
|
+
affectedKey: domainElement.key,
|
|
379
|
+
};
|
|
380
|
+
issues.push(issue);
|
|
381
|
+
if (mode === 'strict') {
|
|
382
|
+
throw new Error(`Unable to restore data domain graph. Unknown node kind ${domainElement.kind}`);
|
|
383
|
+
}
|
|
384
|
+
return undefined;
|
|
203
385
|
}
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
386
|
+
catch (error) {
|
|
387
|
+
const issue = {
|
|
388
|
+
type: 'malformed_entry',
|
|
389
|
+
severity: 'error',
|
|
390
|
+
message: `Failed to create node: ${error instanceof Error ? error.message : String(error)}`,
|
|
391
|
+
affectedKey: domainElement.key,
|
|
392
|
+
};
|
|
393
|
+
issues.push(issue);
|
|
394
|
+
if (mode === 'strict') {
|
|
395
|
+
throw error;
|
|
209
396
|
}
|
|
210
|
-
return
|
|
397
|
+
return undefined;
|
|
211
398
|
}
|
|
212
|
-
throw new Error(`Unable to restore data domain graph. Unknown node kind ${domainElement.kind}`);
|
|
213
399
|
}
|
|
214
400
|
/**
|
|
215
401
|
* To deserialize a graph:
|
|
@@ -220,75 +406,211 @@ function prepareNode(root, entry, edges) {
|
|
|
220
406
|
*
|
|
221
407
|
* @param root The DataDomain instance to use as the root for the graph
|
|
222
408
|
* @param json The previously serialized graph
|
|
223
|
-
* @
|
|
409
|
+
* @param dependencies An array of foreign data domains to register with this domain
|
|
410
|
+
* @param mode The deserialization mode - 'strict' throws errors, 'lenient' collects issues
|
|
411
|
+
* @returns DeserializationResult with graph, issues, and success status
|
|
412
|
+
* @throws Error in strict mode when any issue is encountered
|
|
224
413
|
*/
|
|
225
|
-
export function deserialize(root,
|
|
414
|
+
export function deserialize(root, options = {}) {
|
|
415
|
+
const { json, dependencies, mode = 'strict' } = options;
|
|
226
416
|
const g = new Graph({
|
|
227
417
|
compound: true,
|
|
228
418
|
multigraph: true,
|
|
229
419
|
directed: true,
|
|
230
420
|
});
|
|
421
|
+
const issues = [];
|
|
231
422
|
let foreignNodes = new Set();
|
|
232
423
|
let foreignEdges = new Set();
|
|
424
|
+
// Process dependencies
|
|
233
425
|
if (dependencies) {
|
|
234
426
|
for (const dependency of dependencies) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
427
|
+
try {
|
|
428
|
+
const result = mergeGraph(g, dependency.graph, dependency.key);
|
|
429
|
+
if (result.edges.size) {
|
|
430
|
+
foreignEdges = new Set([...foreignEdges, ...result.edges]);
|
|
431
|
+
}
|
|
432
|
+
if (result.nodes.size) {
|
|
433
|
+
foreignNodes = new Set([...foreignNodes, ...result.nodes]);
|
|
434
|
+
}
|
|
435
|
+
root.dependencies.set(dependency.key, dependency);
|
|
238
436
|
}
|
|
239
|
-
|
|
240
|
-
|
|
437
|
+
catch (error) {
|
|
438
|
+
const message = `Failed to merge dependency "${dependency.key}": ${error instanceof Error ? error.message : String(error)}`;
|
|
439
|
+
if (mode === 'strict') {
|
|
440
|
+
throw new Error(message);
|
|
441
|
+
}
|
|
442
|
+
const issue = {
|
|
443
|
+
type: 'missing_dependency',
|
|
444
|
+
severity: 'error',
|
|
445
|
+
message,
|
|
446
|
+
affectedKey: dependency.key,
|
|
447
|
+
resolution: 'Dependency will be skipped',
|
|
448
|
+
};
|
|
449
|
+
issues.push(issue);
|
|
241
450
|
}
|
|
242
|
-
root.dependencies.set(dependency.key, dependency);
|
|
243
451
|
}
|
|
244
452
|
}
|
|
245
453
|
if (!json) {
|
|
246
|
-
return
|
|
454
|
+
return {
|
|
455
|
+
graph: g,
|
|
456
|
+
issues,
|
|
457
|
+
success: true,
|
|
458
|
+
};
|
|
247
459
|
}
|
|
460
|
+
// Process nodes
|
|
248
461
|
if (Array.isArray(json.nodes)) {
|
|
249
462
|
// 1st pass - set up nodes
|
|
250
463
|
const parentInfo = new Map();
|
|
251
464
|
for (const entry of json.nodes) {
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
465
|
+
const node = prepareNode(root, entry.value, json.edges, mode, issues);
|
|
466
|
+
if (node) {
|
|
467
|
+
g.setNode(entry.v, node);
|
|
468
|
+
if (entry.parents) {
|
|
469
|
+
parentInfo.set(entry.v, entry.parents);
|
|
470
|
+
}
|
|
255
471
|
}
|
|
472
|
+
// Note: prepareNode throws in strict mode, so we won't reach here if there's an error
|
|
256
473
|
}
|
|
257
474
|
// 2nd pass - set up parents
|
|
258
475
|
for (const [key, parents] of parentInfo) {
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
476
|
+
// Verify the node still exists (might have been skipped in lenient mode)
|
|
477
|
+
if (!g.hasNode(key)) {
|
|
478
|
+
const issue = {
|
|
479
|
+
type: 'missing_node',
|
|
480
|
+
severity: 'warning',
|
|
481
|
+
message: `Cannot set parents for missing node: ${key}`,
|
|
482
|
+
affectedKey: key,
|
|
483
|
+
resolution: 'Parent relationship will be skipped',
|
|
484
|
+
};
|
|
485
|
+
issues.push(issue);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
266
488
|
for (const parent of parents) {
|
|
267
|
-
|
|
489
|
+
// Verify parent exists
|
|
490
|
+
if (!g.hasNode(parent)) {
|
|
491
|
+
const issue = {
|
|
492
|
+
type: 'missing_node',
|
|
493
|
+
severity: 'warning',
|
|
494
|
+
message: `Parent node "${parent}" not found for child "${key}"`,
|
|
495
|
+
affectedKey: key,
|
|
496
|
+
context: { parentKey: parent },
|
|
497
|
+
resolution: 'Parent relationship will be skipped',
|
|
498
|
+
};
|
|
499
|
+
issues.push(issue);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
g.setParent(key, parent);
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
const message = `Failed to set parent "${parent}" for child "${key}": ${error instanceof Error ? error.message : String(error)}`;
|
|
507
|
+
const issue = {
|
|
508
|
+
type: 'invalid_parent',
|
|
509
|
+
severity: 'warning',
|
|
510
|
+
message,
|
|
511
|
+
affectedKey: key,
|
|
512
|
+
context: { parentKey: parent },
|
|
513
|
+
resolution: 'Parent relationship will be skipped',
|
|
514
|
+
};
|
|
515
|
+
issues.push(issue);
|
|
516
|
+
}
|
|
268
517
|
}
|
|
269
518
|
}
|
|
270
519
|
}
|
|
520
|
+
// Process edges
|
|
271
521
|
if (Array.isArray(json.edges)) {
|
|
272
522
|
for (const entry of json.edges) {
|
|
273
523
|
if (!entry.value) {
|
|
274
|
-
|
|
524
|
+
const message = 'Edge entry missing value';
|
|
525
|
+
if (mode === 'strict') {
|
|
526
|
+
throw new Error(message);
|
|
527
|
+
}
|
|
528
|
+
const issue = {
|
|
529
|
+
type: 'malformed_entry',
|
|
530
|
+
severity: 'error',
|
|
531
|
+
message,
|
|
532
|
+
context: { edge: entry },
|
|
533
|
+
};
|
|
534
|
+
issues.push(issue);
|
|
535
|
+
continue;
|
|
275
536
|
}
|
|
537
|
+
// Handle foreign edges
|
|
276
538
|
if (entry.value.foreign) {
|
|
277
539
|
// The `v` has to be local to the graph. The `w` must be foreign.
|
|
278
540
|
if (!foreignNodes.has(entry.w)) {
|
|
279
|
-
//
|
|
541
|
+
// Note, we don't consider this an error, just a warning.
|
|
542
|
+
// Because of that, we don't throw an error here.
|
|
543
|
+
const issue = {
|
|
544
|
+
type: 'missing_node',
|
|
545
|
+
severity: 'warning',
|
|
546
|
+
message: `Missing foreign node: ${entry.w}`,
|
|
547
|
+
affectedKey: entry.w,
|
|
548
|
+
resolution: 'Edge will be skipped',
|
|
549
|
+
};
|
|
550
|
+
issues.push(issue);
|
|
280
551
|
continue;
|
|
281
552
|
}
|
|
282
553
|
}
|
|
554
|
+
// Check if nodes exist for the edge
|
|
283
555
|
if (foreignNodes.has(entry.v) || foreignNodes.has(entry.w)) {
|
|
284
556
|
if (!g.hasNode(entry.v) || !g.hasNode(entry.w)) {
|
|
285
|
-
|
|
557
|
+
const issue = {
|
|
558
|
+
type: 'missing_node',
|
|
559
|
+
severity: 'warning',
|
|
560
|
+
message: `Missing foreign node for edge: ${entry.v} -> ${entry.w}`,
|
|
561
|
+
context: { sourceNode: entry.v, targetNode: entry.w },
|
|
562
|
+
resolution: 'Edge will be skipped',
|
|
563
|
+
};
|
|
564
|
+
issues.push(issue);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
// Both nodes should be local - verify they exist
|
|
570
|
+
if (!g.hasNode(entry.v)) {
|
|
571
|
+
const issue = {
|
|
572
|
+
type: 'missing_node',
|
|
573
|
+
severity: 'warning',
|
|
574
|
+
message: `Source node not found for edge: ${entry.v}`,
|
|
575
|
+
affectedKey: entry.v,
|
|
576
|
+
resolution: 'Edge will be skipped',
|
|
577
|
+
};
|
|
578
|
+
issues.push(issue);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (!g.hasNode(entry.w)) {
|
|
582
|
+
const issue = {
|
|
583
|
+
type: 'missing_node',
|
|
584
|
+
severity: 'warning',
|
|
585
|
+
message: `Target node not found for edge: ${entry.w}`,
|
|
586
|
+
affectedKey: entry.w,
|
|
587
|
+
resolution: 'Edge will be skipped',
|
|
588
|
+
};
|
|
589
|
+
issues.push(issue);
|
|
286
590
|
continue;
|
|
287
591
|
}
|
|
288
592
|
}
|
|
289
|
-
|
|
593
|
+
try {
|
|
594
|
+
g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
const message = `Failed to create edge ${entry.v} -> ${entry.w}: ${error instanceof Error ? error.message : String(error)}`;
|
|
598
|
+
const issue = {
|
|
599
|
+
type: 'malformed_entry',
|
|
600
|
+
severity: 'warning',
|
|
601
|
+
message,
|
|
602
|
+
context: { sourceNode: entry.v, targetNode: entry.w },
|
|
603
|
+
resolution: 'Edge will be skipped',
|
|
604
|
+
};
|
|
605
|
+
issues.push(issue);
|
|
606
|
+
}
|
|
290
607
|
}
|
|
291
608
|
}
|
|
292
|
-
|
|
609
|
+
const hasErrors = issues.some((issue) => issue.severity === 'error');
|
|
610
|
+
return {
|
|
611
|
+
graph: g,
|
|
612
|
+
issues,
|
|
613
|
+
success: !hasErrors,
|
|
614
|
+
};
|
|
293
615
|
}
|
|
294
616
|
//# sourceMappingURL=DomainSerialization.js.map
|