patram 0.1.1 → 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.
- package/lib/build-graph-identity.js +57 -24
- package/lib/build-graph.js +383 -17
- package/lib/build-graph.types.ts +5 -2
- package/lib/check-directive-metadata.js +516 -0
- package/lib/check-directive-value.js +282 -0
- package/lib/check-graph.js +24 -5
- package/lib/cli-help-metadata.js +580 -0
- package/lib/derived-summary.js +280 -0
- package/lib/directive-diagnostics.js +38 -0
- package/lib/directive-type-rules.js +133 -0
- package/lib/discover-fields.js +427 -0
- package/lib/discover-fields.types.ts +52 -0
- package/lib/format-derived-summary-row.js +9 -0
- package/lib/format-node-header.js +21 -0
- package/lib/format-output-item-block.js +22 -0
- package/lib/format-output-metadata.js +54 -0
- package/lib/layout-stored-queries.js +96 -2
- package/lib/load-patram-config.js +754 -18
- package/lib/load-patram-config.types.ts +128 -2
- package/lib/load-project-graph.js +4 -1
- package/lib/output-view.types.ts +29 -6
- package/lib/parse-cli-arguments-helpers.js +263 -90
- package/lib/parse-cli-arguments.js +160 -8
- package/lib/parse-cli-arguments.types.ts +49 -4
- package/lib/parse-where-clause.js +670 -209
- package/lib/parse-where-clause.types.ts +72 -0
- package/lib/patram-cli.js +180 -21
- package/lib/patram-config.js +31 -31
- package/lib/patram-config.types.ts +10 -4
- package/lib/patram.js +6 -0
- package/lib/query-graph.js +444 -113
- package/lib/query-inspection.js +798 -0
- package/lib/render-check-output.js +1 -1
- package/lib/render-cli-help.js +419 -0
- package/lib/render-field-discovery.js +148 -0
- package/lib/render-json-output.js +66 -14
- package/lib/render-output-view.js +272 -22
- package/lib/render-plain-output.js +31 -86
- package/lib/render-rich-output.js +34 -87
- package/lib/resolve-patram-graph-config.js +15 -9
- package/lib/resolve-where-clause.js +18 -3
- package/lib/show-document.js +51 -7
- package/lib/tagged-fenced-block-error.js +17 -0
- package/lib/tagged-fenced-block-markdown.js +111 -0
- package/lib/tagged-fenced-block-metadata.js +97 -0
- package/lib/tagged-fenced-block-parser.js +292 -0
- package/lib/tagged-fenced-blocks.js +100 -0
- package/lib/tagged-fenced-blocks.types.ts +38 -0
- package/package.json +12 -7
package/lib/build-graph.js
CHANGED
|
@@ -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-
|
|
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
|
*
|
|
@@ -47,20 +56,29 @@ export function buildGraph(patram_config, claims) {
|
|
|
47
56
|
patram_config.mappings,
|
|
48
57
|
claims,
|
|
49
58
|
);
|
|
59
|
+
/** @type {Set<string>} */
|
|
60
|
+
const document_paths = new Set(
|
|
61
|
+
claims.map((claim) => normalizeRepoRelativePath(claim.origin.path)),
|
|
62
|
+
);
|
|
63
|
+
/** @type {Map<string, number>} */
|
|
64
|
+
const title_priorities = new Map();
|
|
50
65
|
|
|
51
66
|
createDocumentNodes(graph_nodes, claims);
|
|
52
67
|
applyNodeMappings(
|
|
53
68
|
graph_nodes,
|
|
54
|
-
patram_config
|
|
69
|
+
patram_config,
|
|
55
70
|
claims,
|
|
56
71
|
document_entity_keys,
|
|
72
|
+
title_priorities,
|
|
57
73
|
);
|
|
58
74
|
const graph_edges = createGraphEdges(
|
|
59
75
|
graph_nodes,
|
|
60
76
|
patram_config.mappings,
|
|
61
77
|
claims,
|
|
62
78
|
document_entity_keys,
|
|
79
|
+
document_paths,
|
|
63
80
|
);
|
|
81
|
+
applyFallbackTitles(graph_nodes, title_priorities);
|
|
64
82
|
|
|
65
83
|
return {
|
|
66
84
|
edges: graph_edges,
|
|
@@ -112,18 +130,23 @@ function createDocumentNodes(graph_nodes, claims) {
|
|
|
112
130
|
|
|
113
131
|
/**
|
|
114
132
|
* @param {Map<string, GraphNode>} graph_nodes
|
|
115
|
-
* @param {
|
|
133
|
+
* @param {PatramConfig} patram_config
|
|
116
134
|
* @param {PatramClaim[]} claims
|
|
117
135
|
* @param {Map<string, string>} document_entity_keys
|
|
136
|
+
* @param {Map<string, number>} title_priorities
|
|
118
137
|
*/
|
|
119
138
|
function applyNodeMappings(
|
|
120
139
|
graph_nodes,
|
|
121
|
-
|
|
140
|
+
patram_config,
|
|
122
141
|
claims,
|
|
123
142
|
document_entity_keys,
|
|
143
|
+
title_priorities,
|
|
124
144
|
) {
|
|
125
145
|
for (const claim of claims) {
|
|
126
|
-
const mapping_definition = resolveMappingDefinition(
|
|
146
|
+
const mapping_definition = resolveMappingDefinition(
|
|
147
|
+
patram_config.mappings,
|
|
148
|
+
claim,
|
|
149
|
+
);
|
|
127
150
|
|
|
128
151
|
if (!mapping_definition?.node) {
|
|
129
152
|
continue;
|
|
@@ -131,9 +154,11 @@ function applyNodeMappings(
|
|
|
131
154
|
|
|
132
155
|
applyNodeMapping(
|
|
133
156
|
graph_nodes,
|
|
157
|
+
patram_config,
|
|
134
158
|
mapping_definition.node,
|
|
135
159
|
claim,
|
|
136
160
|
document_entity_keys,
|
|
161
|
+
title_priorities,
|
|
137
162
|
);
|
|
138
163
|
}
|
|
139
164
|
}
|
|
@@ -143,9 +168,16 @@ function applyNodeMappings(
|
|
|
143
168
|
* @param {Record<string, MappingDefinition>} mappings
|
|
144
169
|
* @param {PatramClaim[]} claims
|
|
145
170
|
* @param {Map<string, string>} document_entity_keys
|
|
171
|
+
* @param {Set<string>} document_paths
|
|
146
172
|
* @returns {GraphEdge[]}
|
|
147
173
|
*/
|
|
148
|
-
function createGraphEdges(
|
|
174
|
+
function createGraphEdges(
|
|
175
|
+
graph_nodes,
|
|
176
|
+
mappings,
|
|
177
|
+
claims,
|
|
178
|
+
document_entity_keys,
|
|
179
|
+
document_paths,
|
|
180
|
+
) {
|
|
149
181
|
/** @type {GraphEdge[]} */
|
|
150
182
|
const graph_edges = [];
|
|
151
183
|
let edge_number = 0;
|
|
@@ -163,14 +195,15 @@ function createGraphEdges(graph_nodes, mappings, claims, document_entity_keys) {
|
|
|
163
195
|
normalizeRepoRelativePath(claim.origin.path),
|
|
164
196
|
);
|
|
165
197
|
const target_reference = resolveTargetReference(
|
|
166
|
-
mapping_definition.emit.
|
|
198
|
+
mapping_definition.emit.target_class,
|
|
167
199
|
mapping_definition.emit.target,
|
|
168
200
|
claim,
|
|
169
201
|
document_entity_keys,
|
|
202
|
+
document_paths,
|
|
170
203
|
);
|
|
171
204
|
const target_node = upsertNode(
|
|
172
205
|
graph_nodes,
|
|
173
|
-
mapping_definition.emit.
|
|
206
|
+
mapping_definition.emit.target_class,
|
|
174
207
|
target_reference.key,
|
|
175
208
|
);
|
|
176
209
|
|
|
@@ -191,23 +224,117 @@ function createGraphEdges(graph_nodes, mappings, claims, document_entity_keys) {
|
|
|
191
224
|
|
|
192
225
|
/**
|
|
193
226
|
* @param {Map<string, GraphNode>} graph_nodes
|
|
194
|
-
* @param {
|
|
227
|
+
* @param {PatramConfig} patram_config
|
|
228
|
+
* @param {{ field: string, key?: 'path' | 'value', class: string }} node_mapping
|
|
195
229
|
* @param {PatramClaim} claim
|
|
196
230
|
* @param {Map<string, string>} document_entity_keys
|
|
231
|
+
* @param {Map<string, number>} title_priorities
|
|
197
232
|
*/
|
|
198
233
|
function applyNodeMapping(
|
|
199
234
|
graph_nodes,
|
|
235
|
+
patram_config,
|
|
200
236
|
node_mapping,
|
|
201
237
|
claim,
|
|
202
238
|
document_entity_keys,
|
|
239
|
+
title_priorities,
|
|
203
240
|
) {
|
|
204
241
|
const source_key = normalizeRepoRelativePath(claim.origin.path);
|
|
205
242
|
const node_key = resolveNodeKey(node_mapping, claim, document_entity_keys);
|
|
206
|
-
const graph_node = upsertNode(graph_nodes, node_mapping.
|
|
243
|
+
const graph_node = upsertNode(graph_nodes, node_mapping.class, node_key);
|
|
244
|
+
const field_value = getNodeFieldValue(claim);
|
|
207
245
|
|
|
208
246
|
setNonDocumentPath(graph_node, source_key);
|
|
247
|
+
validateNodeFieldMapping(
|
|
248
|
+
patram_config,
|
|
249
|
+
node_mapping.class,
|
|
250
|
+
node_mapping.field,
|
|
251
|
+
);
|
|
209
252
|
|
|
210
|
-
|
|
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
|
+
}
|
|
330
|
+
|
|
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;
|
|
211
338
|
}
|
|
212
339
|
|
|
213
340
|
/**
|
|
@@ -233,23 +360,26 @@ function upsertNode(graph_nodes, kind_name, node_key) {
|
|
|
233
360
|
|
|
234
361
|
/**
|
|
235
362
|
* @param {string} node_id
|
|
236
|
-
* @param {string}
|
|
363
|
+
* @param {string} class_name
|
|
237
364
|
* @param {string} node_key
|
|
238
365
|
* @returns {GraphNode}
|
|
239
366
|
*/
|
|
240
|
-
function createNode(node_id,
|
|
241
|
-
if (
|
|
367
|
+
function createNode(node_id, class_name, node_key) {
|
|
368
|
+
if (class_name === 'document') {
|
|
242
369
|
return {
|
|
370
|
+
$class: class_name,
|
|
371
|
+
$id: node_id,
|
|
372
|
+
$path: node_key,
|
|
243
373
|
id: node_id,
|
|
244
|
-
kind: kind_name,
|
|
245
374
|
path: node_key,
|
|
246
375
|
};
|
|
247
376
|
}
|
|
248
377
|
|
|
249
378
|
return {
|
|
379
|
+
$class: class_name,
|
|
380
|
+
$id: node_id,
|
|
250
381
|
id: node_id,
|
|
251
382
|
key: node_key,
|
|
252
|
-
kind: kind_name,
|
|
253
383
|
};
|
|
254
384
|
}
|
|
255
385
|
|
|
@@ -278,11 +408,247 @@ function getNodeFieldValue(claim) {
|
|
|
278
408
|
throw new Error(`Claim "${claim.id}" does not carry a string value.`);
|
|
279
409
|
}
|
|
280
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
|
+
|
|
281
633
|
/**
|
|
282
634
|
* @param {[string, GraphNode]} left_entry
|
|
283
635
|
* @param {[string, GraphNode]} right_entry
|
|
284
636
|
* @returns {number}
|
|
285
637
|
*/
|
|
286
638
|
function compareNodeEntries(left_entry, right_entry) {
|
|
287
|
-
return left_entry[0].localeCompare(right_entry[0],
|
|
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);
|
|
288
654
|
}
|
package/lib/build-graph.types.ts
CHANGED
|
@@ -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
|
|
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 {
|