patram 0.2.0 → 0.3.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.
@@ -27,14 +27,14 @@ export function collectDocumentEntityKeys(mappings, claims) {
27
27
  const source_path = normalizeRepoRelativePath(claim.origin.path);
28
28
  const entity_map_key = getDocumentEntityMapKey(
29
29
  source_path,
30
- mapping_definition.node.kind,
30
+ mapping_definition.node.class,
31
31
  );
32
32
  const entity_key = getStringClaimValue(claim);
33
33
  const existing_entity_key = document_entity_keys.get(entity_map_key);
34
34
 
35
35
  if (existing_entity_key && existing_entity_key !== entity_key) {
36
36
  throw new Error(
37
- `Document "${source_path}" defines multiple ${mapping_definition.node.kind} ids.`,
37
+ `Document "${source_path}" defines multiple ${mapping_definition.node.class} ids.`,
38
38
  );
39
39
  }
40
40
 
@@ -47,7 +47,7 @@ export function collectDocumentEntityKeys(mappings, claims) {
47
47
  /**
48
48
  * Resolve the node key for one mapped claim.
49
49
  *
50
- * @param {{ field: string, key?: 'path' | 'value', kind: string }} node_mapping
50
+ * @param {{ field: string, key?: 'path' | 'value', class: string }} node_mapping
51
51
  * @param {PatramClaim} claim
52
52
  * @param {Map<string, string>} document_entity_keys
53
53
  * @returns {string}
@@ -55,7 +55,7 @@ export function collectDocumentEntityKeys(mappings, claims) {
55
55
  export function resolveNodeKey(node_mapping, claim, document_entity_keys) {
56
56
  const source_key = normalizeRepoRelativePath(claim.origin.path);
57
57
 
58
- if (node_mapping.kind === 'document') {
58
+ if (node_mapping.class === 'document') {
59
59
  return source_key;
60
60
  }
61
61
 
@@ -65,7 +65,7 @@ export function resolveNodeKey(node_mapping, claim, document_entity_keys) {
65
65
 
66
66
  return (
67
67
  document_entity_keys.get(
68
- getDocumentEntityMapKey(source_key, node_mapping.kind),
68
+ getDocumentEntityMapKey(source_key, node_mapping.class),
69
69
  ) ?? source_key
70
70
  );
71
71
  }
@@ -73,7 +73,7 @@ export function resolveNodeKey(node_mapping, claim, document_entity_keys) {
73
73
  /**
74
74
  * Resolve one edge target key and canonical path.
75
75
  *
76
- * @param {string} target_kind
76
+ * @param {string} target_class
77
77
  * @param {'path' | 'value'} target_type
78
78
  * @param {PatramClaim} claim
79
79
  * @param {Map<string, string>} document_entity_keys
@@ -81,18 +81,18 @@ export function resolveNodeKey(node_mapping, claim, document_entity_keys) {
81
81
  * @returns {{ key: string, path?: string }}
82
82
  */
83
83
  export function resolveTargetReference(
84
- target_kind,
84
+ target_class,
85
85
  target_type,
86
86
  claim,
87
87
  document_entity_keys,
88
88
  document_paths,
89
89
  ) {
90
90
  if (target_type === 'value') {
91
- return resolveValueTargetReference(target_kind, claim);
91
+ return resolveValueTargetReference(target_class, claim);
92
92
  }
93
93
 
94
94
  return resolvePathTargetReference(
95
- target_kind,
95
+ target_class,
96
96
  claim,
97
97
  document_entity_keys,
98
98
  document_paths,
@@ -106,16 +106,17 @@ export function resolveTargetReference(
106
106
  * @param {string | undefined} source_path
107
107
  */
108
108
  export function setNonDocumentPath(graph_node, source_path) {
109
- if (!source_path || graph_node.kind === 'document') {
109
+ if (!source_path || graph_node.$class === 'document') {
110
110
  return;
111
111
  }
112
112
 
113
- if (!graph_node.path) {
113
+ if (!graph_node.$path) {
114
+ graph_node.$path = source_path;
114
115
  graph_node.path = source_path;
115
116
  return;
116
117
  }
117
118
 
118
- if (graph_node.path !== source_path) {
119
+ if (graph_node.$path !== source_path) {
119
120
  throw new Error(
120
121
  `Node "${graph_node.id}" maps to multiple canonical paths.`,
121
122
  );
@@ -180,14 +181,14 @@ function resolveValueTargetReference(target_kind, claim) {
180
181
  }
181
182
 
182
183
  /**
183
- * @param {string} target_kind
184
+ * @param {string} target_class
184
185
  * @param {PatramClaim} claim
185
186
  * @param {Map<string, string>} document_entity_keys
186
187
  * @param {Set<string>} document_paths
187
188
  * @returns {{ key: string, path?: string }}
188
189
  */
189
190
  function resolvePathTargetReference(
190
- target_kind,
191
+ target_class,
191
192
  claim,
192
193
  document_entity_keys,
193
194
  document_paths,
@@ -199,7 +200,7 @@ function resolvePathTargetReference(
199
200
  document_paths,
200
201
  );
201
202
 
202
- if (target_kind === 'document') {
203
+ if (target_class === 'document') {
203
204
  return {
204
205
  key: target_path,
205
206
  path: target_path,
@@ -207,7 +208,7 @@ function resolvePathTargetReference(
207
208
  }
208
209
 
209
210
  const semantic_target_key = document_entity_keys.get(
210
- getDocumentEntityMapKey(target_path, target_kind),
211
+ getDocumentEntityMapKey(target_path, target_class),
211
212
  );
212
213
 
213
214
  return {
@@ -262,9 +263,9 @@ function getStringClaimValue(claim) {
262
263
 
263
264
  /**
264
265
  * @param {string} document_path
265
- * @param {string} kind_name
266
+ * @param {string} class_name
266
267
  * @returns {string}
267
268
  */
268
- function getDocumentEntityMapKey(document_path, kind_name) {
269
- return `${kind_name}:${document_path}`;
269
+ function getDocumentEntityMapKey(document_path, class_name) {
270
+ return `${class_name}:${document_path}`;
270
271
  }
@@ -1,9 +1,13 @@
1
+ /* eslint-disable max-lines */
1
2
  /**
2
3
  * @import { BuildGraphResult, GraphEdge, GraphNode } from './build-graph.types.ts';
3
4
  * @import { PatramClaim } from './parse-claims.types.ts';
5
+ * @import { MetadataFieldConfig } from './load-patram-config.types.ts';
4
6
  * @import { MappingDefinition, PatramConfig } from './patram-config.types.ts';
5
7
  */
6
8
 
9
+ import { posix } from 'node:path';
10
+
7
11
  import {
8
12
  collectDocumentEntityKeys,
9
13
  normalizeRepoRelativePath,
@@ -25,7 +29,7 @@ import {
25
29
  * Uses Term: ../docs/reference/terms/graph.md
26
30
  * Uses Term: ../docs/reference/terms/mapping.md
27
31
  * Uses Term: ../docs/reference/terms/relation.md
28
- * Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
32
+ * Tracked in: ../docs/plans/v0/source-anchor-dogfood.md
29
33
  * Decided by: ../docs/decisions/graph-materialization.md
30
34
  * Implements: ../docs/tasks/v0/materialize-graph.md
31
35
  * @patram
@@ -33,6 +37,11 @@ import {
33
37
  * @see {@link ../docs/decisions/graph-materialization.md}
34
38
  */
35
39
 
40
+ const STRUCTURAL_FIELD_NAMES = new Set(['$class', '$id', '$path']);
41
+ const DETERMINISTIC_LOCALE = 'en';
42
+ const DERIVED_TITLE_PRIORITY = 1;
43
+ const EXPLICIT_TITLE_PRIORITY = 2;
44
+
36
45
  /**
37
46
  * Build a Patram graph from semantic config and parsed claims.
38
47
  *
@@ -51,13 +60,16 @@ export function buildGraph(patram_config, claims) {
51
60
  const document_paths = new Set(
52
61
  claims.map((claim) => normalizeRepoRelativePath(claim.origin.path)),
53
62
  );
63
+ /** @type {Map<string, number>} */
64
+ const title_priorities = new Map();
54
65
 
55
66
  createDocumentNodes(graph_nodes, claims);
56
67
  applyNodeMappings(
57
68
  graph_nodes,
58
- patram_config.mappings,
69
+ patram_config,
59
70
  claims,
60
71
  document_entity_keys,
72
+ title_priorities,
61
73
  );
62
74
  const graph_edges = createGraphEdges(
63
75
  graph_nodes,
@@ -66,6 +78,7 @@ export function buildGraph(patram_config, claims) {
66
78
  document_entity_keys,
67
79
  document_paths,
68
80
  );
81
+ applyFallbackTitles(graph_nodes, title_priorities);
69
82
 
70
83
  return {
71
84
  edges: graph_edges,
@@ -117,18 +130,23 @@ function createDocumentNodes(graph_nodes, claims) {
117
130
 
118
131
  /**
119
132
  * @param {Map<string, GraphNode>} graph_nodes
120
- * @param {Record<string, MappingDefinition>} mappings
133
+ * @param {PatramConfig} patram_config
121
134
  * @param {PatramClaim[]} claims
122
135
  * @param {Map<string, string>} document_entity_keys
136
+ * @param {Map<string, number>} title_priorities
123
137
  */
124
138
  function applyNodeMappings(
125
139
  graph_nodes,
126
- mappings,
140
+ patram_config,
127
141
  claims,
128
142
  document_entity_keys,
143
+ title_priorities,
129
144
  ) {
130
145
  for (const claim of claims) {
131
- const mapping_definition = resolveMappingDefinition(mappings, claim);
146
+ const mapping_definition = resolveMappingDefinition(
147
+ patram_config.mappings,
148
+ claim,
149
+ );
132
150
 
133
151
  if (!mapping_definition?.node) {
134
152
  continue;
@@ -136,9 +154,11 @@ function applyNodeMappings(
136
154
 
137
155
  applyNodeMapping(
138
156
  graph_nodes,
157
+ patram_config,
139
158
  mapping_definition.node,
140
159
  claim,
141
160
  document_entity_keys,
161
+ title_priorities,
142
162
  );
143
163
  }
144
164
  }
@@ -175,7 +195,7 @@ function createGraphEdges(
175
195
  normalizeRepoRelativePath(claim.origin.path),
176
196
  );
177
197
  const target_reference = resolveTargetReference(
178
- mapping_definition.emit.target_kind,
198
+ mapping_definition.emit.target_class,
179
199
  mapping_definition.emit.target,
180
200
  claim,
181
201
  document_entity_keys,
@@ -183,7 +203,7 @@ function createGraphEdges(
183
203
  );
184
204
  const target_node = upsertNode(
185
205
  graph_nodes,
186
- mapping_definition.emit.target_kind,
206
+ mapping_definition.emit.target_class,
187
207
  target_reference.key,
188
208
  );
189
209
 
@@ -204,23 +224,117 @@ function createGraphEdges(
204
224
 
205
225
  /**
206
226
  * @param {Map<string, GraphNode>} graph_nodes
207
- * @param {{ field: string, key?: 'path' | 'value', kind: string }} node_mapping
227
+ * @param {PatramConfig} patram_config
228
+ * @param {{ field: string, key?: 'path' | 'value', class: string }} node_mapping
208
229
  * @param {PatramClaim} claim
209
230
  * @param {Map<string, string>} document_entity_keys
231
+ * @param {Map<string, number>} title_priorities
210
232
  */
211
233
  function applyNodeMapping(
212
234
  graph_nodes,
235
+ patram_config,
213
236
  node_mapping,
214
237
  claim,
215
238
  document_entity_keys,
239
+ title_priorities,
216
240
  ) {
217
241
  const source_key = normalizeRepoRelativePath(claim.origin.path);
218
242
  const node_key = resolveNodeKey(node_mapping, claim, document_entity_keys);
219
- const graph_node = upsertNode(graph_nodes, node_mapping.kind, node_key);
243
+ const graph_node = upsertNode(graph_nodes, node_mapping.class, node_key);
244
+ const field_value = getNodeFieldValue(claim);
220
245
 
221
246
  setNonDocumentPath(graph_node, source_key);
247
+ validateNodeFieldMapping(
248
+ patram_config,
249
+ node_mapping.class,
250
+ node_mapping.field,
251
+ );
252
+
253
+ if (node_mapping.field === 'title') {
254
+ setNodeTitle(
255
+ graph_node,
256
+ title_priorities,
257
+ field_value,
258
+ claim.type === 'document.title'
259
+ ? DERIVED_TITLE_PRIORITY
260
+ : EXPLICIT_TITLE_PRIORITY,
261
+ );
262
+ return;
263
+ }
264
+
265
+ setNodeFieldValue(
266
+ graph_node,
267
+ node_mapping.field,
268
+ field_value,
269
+ getFieldDefinition(patram_config, node_mapping.field),
270
+ );
271
+ }
272
+
273
+ /**
274
+ * Validate one mapped node field against the configured field model.
275
+ *
276
+ * @param {PatramConfig} patram_config
277
+ * @param {string} node_class
278
+ * @param {string} field_name
279
+ */
280
+ function validateNodeFieldMapping(patram_config, node_class, field_name) {
281
+ const validation_error = getNodeFieldValidationError(
282
+ patram_config,
283
+ node_class,
284
+ field_name,
285
+ );
286
+
287
+ if (validation_error) {
288
+ throw new Error(validation_error);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * @param {PatramConfig} patram_config
294
+ * @param {string} node_class
295
+ * @param {string} field_name
296
+ * @returns {string | null}
297
+ */
298
+ function getNodeFieldValidationError(patram_config, node_class, field_name) {
299
+ if (isStructuralFieldName(field_name) || field_name === 'title') {
300
+ return null;
301
+ }
302
+
303
+ const field_definition = getFieldDefinition(patram_config, field_name);
304
+
305
+ if (!field_definition) {
306
+ return `Node class "${node_class}" maps to unknown field "${field_name}".`;
307
+ }
308
+
309
+ const class_schema = getClassSchema(patram_config, node_class);
310
+ const class_field_rule = class_schema?.fields?.[field_name];
311
+
312
+ if (isForbiddenClassField(class_field_rule)) {
313
+ return `Field "${field_name}" is forbidden for class "${node_class}".`;
314
+ }
315
+
316
+ if (isUndeclaredClassField(class_schema, class_field_rule)) {
317
+ return `Field "${field_name}" is not declared for class "${node_class}".`;
318
+ }
319
+
320
+ return null;
321
+ }
322
+
323
+ /**
324
+ * @param {{ presence: 'required' | 'optional' | 'forbidden' } | undefined} class_field_rule
325
+ * @returns {boolean}
326
+ */
327
+ function isForbiddenClassField(class_field_rule) {
328
+ return class_field_rule?.presence === 'forbidden';
329
+ }
222
330
 
223
- graph_node[node_mapping.field] = getNodeFieldValue(claim);
331
+ /**
332
+ * @param {{ fields?: Record<string, { presence: 'required' | 'optional' | 'forbidden' }>, unknown_fields?: 'ignore' | 'error' } | undefined} class_schema
333
+ * @param {{ presence: 'required' | 'optional' | 'forbidden' } | undefined} class_field_rule
334
+ * @returns {boolean}
335
+ */
336
+ function isUndeclaredClassField(class_schema, class_field_rule) {
337
+ return class_schema?.unknown_fields === 'error' && !class_field_rule;
224
338
  }
225
339
 
226
340
  /**
@@ -246,23 +360,26 @@ function upsertNode(graph_nodes, kind_name, node_key) {
246
360
 
247
361
  /**
248
362
  * @param {string} node_id
249
- * @param {string} kind_name
363
+ * @param {string} class_name
250
364
  * @param {string} node_key
251
365
  * @returns {GraphNode}
252
366
  */
253
- function createNode(node_id, kind_name, node_key) {
254
- if (kind_name === 'document') {
367
+ function createNode(node_id, class_name, node_key) {
368
+ if (class_name === 'document') {
255
369
  return {
370
+ $class: class_name,
371
+ $id: node_id,
372
+ $path: node_key,
256
373
  id: node_id,
257
- kind: kind_name,
258
374
  path: node_key,
259
375
  };
260
376
  }
261
377
 
262
378
  return {
379
+ $class: class_name,
380
+ $id: node_id,
263
381
  id: node_id,
264
382
  key: node_key,
265
- kind: kind_name,
266
383
  };
267
384
  }
268
385
 
@@ -291,11 +408,247 @@ function getNodeFieldValue(claim) {
291
408
  throw new Error(`Claim "${claim.id}" does not carry a string value.`);
292
409
  }
293
410
 
411
+ /**
412
+ * @param {Map<string, GraphNode>} graph_nodes
413
+ * @param {Map<string, number>} title_priorities
414
+ */
415
+ function applyFallbackTitles(graph_nodes, title_priorities) {
416
+ for (const graph_node of graph_nodes.values()) {
417
+ if (graph_node.title !== undefined) {
418
+ continue;
419
+ }
420
+
421
+ const fallback_title = getFallbackTitle(graph_node);
422
+
423
+ graph_node.title = fallback_title;
424
+ title_priorities.set(graph_node.id, 0);
425
+ }
426
+ }
427
+
428
+ /**
429
+ * @param {GraphNode} graph_node
430
+ * @returns {string}
431
+ */
432
+ function getFallbackTitle(graph_node) {
433
+ if (graph_node.$path) {
434
+ return posix.basename(graph_node.$path);
435
+ }
436
+
437
+ return getNodeIdKey(graph_node.$id ?? graph_node.id);
438
+ }
439
+
440
+ /**
441
+ * @param {GraphNode} graph_node
442
+ * @param {Map<string, number>} title_priorities
443
+ * @param {string} title_value
444
+ * @param {number} source_priority
445
+ */
446
+ function setNodeTitle(
447
+ graph_node,
448
+ title_priorities,
449
+ title_value,
450
+ source_priority,
451
+ ) {
452
+ const current_priority = title_priorities.get(graph_node.id);
453
+
454
+ if (current_priority === undefined) {
455
+ graph_node.title = title_value;
456
+ title_priorities.set(graph_node.id, source_priority);
457
+ return;
458
+ }
459
+
460
+ if (source_priority > current_priority) {
461
+ graph_node.title = title_value;
462
+ title_priorities.set(graph_node.id, source_priority);
463
+ return;
464
+ }
465
+
466
+ if (
467
+ source_priority === current_priority &&
468
+ graph_node.title !== title_value
469
+ ) {
470
+ throw new Error(
471
+ `Node "${graph_node.id}" has conflicting title values "${graph_node.title}" and "${title_value}".`,
472
+ );
473
+ }
474
+ }
475
+
476
+ /**
477
+ * @param {GraphNode} graph_node
478
+ * @param {string} field_name
479
+ * @param {string} field_value
480
+ * @param {MetadataFieldConfig | undefined} field_definition
481
+ */
482
+ function setNodeFieldValue(
483
+ graph_node,
484
+ field_name,
485
+ field_value,
486
+ field_definition,
487
+ ) {
488
+ if (
489
+ field_name === '$id' ||
490
+ field_name === '$class' ||
491
+ field_name === '$path'
492
+ ) {
493
+ setStructuralFieldValue(graph_node, field_name, field_value);
494
+ return;
495
+ }
496
+
497
+ if (!field_definition || field_definition.multiple !== true) {
498
+ setSingleValueField(graph_node, field_name, field_value);
499
+ return;
500
+ }
501
+
502
+ setMultiValueField(graph_node, field_name, field_value);
503
+ }
504
+
505
+ /**
506
+ * @param {GraphNode} graph_node
507
+ * @param {'$id' | '$class' | '$path'} field_name
508
+ * @param {string} field_value
509
+ */
510
+ function setStructuralFieldValue(graph_node, field_name, field_value) {
511
+ const current_value = graph_node[field_name];
512
+
513
+ if (current_value === undefined) {
514
+ assignStructuralFieldValue(graph_node, field_name, field_value);
515
+ return;
516
+ }
517
+
518
+ if (
519
+ field_name === '$class' &&
520
+ graph_node.id.startsWith('doc:') &&
521
+ current_value === 'document'
522
+ ) {
523
+ assignStructuralFieldValue(graph_node, field_name, field_value);
524
+ return;
525
+ }
526
+
527
+ if (current_value !== field_value) {
528
+ throw new Error(
529
+ `Node "${graph_node.id}" has conflicting structural values for "${field_name}".`,
530
+ );
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Keep legacy mirrors in sync while the rest of the codebase still reads them.
536
+ *
537
+ * @param {GraphNode} graph_node
538
+ * @param {'$id' | '$class' | '$path'} field_name
539
+ * @param {string} field_value
540
+ */
541
+ function assignStructuralFieldValue(graph_node, field_name, field_value) {
542
+ graph_node[field_name] = field_value;
543
+
544
+ if (field_name === '$path') {
545
+ graph_node.path = field_value;
546
+ }
547
+ }
548
+
549
+ /**
550
+ * @param {GraphNode} graph_node
551
+ * @param {string} field_name
552
+ * @param {string} field_value
553
+ */
554
+ function setSingleValueField(graph_node, field_name, field_value) {
555
+ const current_value = graph_node[field_name];
556
+
557
+ if (current_value === undefined) {
558
+ graph_node[field_name] = field_value;
559
+ return;
560
+ }
561
+
562
+ if (current_value !== field_value) {
563
+ throw new Error(
564
+ `Node "${graph_node.id}" has conflicting values for field "${field_name}": "${current_value}" and "${field_value}".`,
565
+ );
566
+ }
567
+ }
568
+
569
+ /**
570
+ * @param {GraphNode} graph_node
571
+ * @param {string} field_name
572
+ * @param {string} field_value
573
+ */
574
+ function setMultiValueField(graph_node, field_name, field_value) {
575
+ const current_value = graph_node[field_name];
576
+
577
+ if (current_value === undefined) {
578
+ graph_node[field_name] = [field_value];
579
+ return;
580
+ }
581
+
582
+ if (Array.isArray(current_value)) {
583
+ if (current_value.includes(field_value)) {
584
+ return;
585
+ }
586
+
587
+ graph_node[field_name] = [...current_value, field_value].sort(
588
+ compareFieldValues,
589
+ );
590
+ return;
591
+ }
592
+
593
+ graph_node[field_name] = [current_value, field_value].sort(
594
+ compareFieldValues,
595
+ );
596
+ }
597
+
598
+ /**
599
+ * @param {string} left_value
600
+ * @param {string} right_value
601
+ * @returns {number}
602
+ */
603
+ function compareFieldValues(left_value, right_value) {
604
+ return left_value.localeCompare(right_value, DETERMINISTIC_LOCALE);
605
+ }
606
+
607
+ /**
608
+ * @param {PatramConfig} patram_config
609
+ * @param {string} field_name
610
+ * @returns {MetadataFieldConfig | undefined}
611
+ */
612
+ function getFieldDefinition(patram_config, field_name) {
613
+ return patram_config.fields?.[field_name];
614
+ }
615
+
616
+ /**
617
+ * @param {PatramConfig} patram_config
618
+ * @param {string} class_name
619
+ * @returns {{ fields?: Record<string, { presence: 'required' | 'optional' | 'forbidden' }>, unknown_fields?: 'ignore' | 'error' } | undefined}
620
+ */
621
+ function getClassSchema(patram_config, class_name) {
622
+ return patram_config.class_schemas?.[class_name];
623
+ }
624
+
625
+ /**
626
+ * @param {string} field_name
627
+ * @returns {field_name is '$class' | '$id' | '$path'}
628
+ */
629
+ function isStructuralFieldName(field_name) {
630
+ return STRUCTURAL_FIELD_NAMES.has(field_name);
631
+ }
632
+
294
633
  /**
295
634
  * @param {[string, GraphNode]} left_entry
296
635
  * @param {[string, GraphNode]} right_entry
297
636
  * @returns {number}
298
637
  */
299
638
  function compareNodeEntries(left_entry, right_entry) {
300
- return left_entry[0].localeCompare(right_entry[0], 'en');
639
+ return left_entry[0].localeCompare(right_entry[0], DETERMINISTIC_LOCALE);
640
+ }
641
+
642
+ /**
643
+ * @param {string} node_id
644
+ * @returns {string}
645
+ */
646
+ function getNodeIdKey(node_id) {
647
+ const separator_index = node_id.indexOf(':');
648
+
649
+ if (separator_index < 0) {
650
+ return node_id;
651
+ }
652
+
653
+ return node_id.slice(separator_index + 1);
301
654
  }
@@ -1,12 +1,15 @@
1
1
  import type { ClaimOrigin } from './parse-claims.types.ts';
2
2
 
3
3
  export interface GraphNode {
4
+ $class?: string;
5
+ $id?: string;
6
+ $path?: string;
4
7
  id: string;
5
- kind: string;
8
+ kind?: string;
6
9
  key?: string;
7
10
  path?: string;
8
11
  title?: string;
9
- [field: string]: string | undefined;
12
+ [field: string]: string | string[] | undefined;
10
13
  }
11
14
 
12
15
  export interface GraphEdge {