patram 0.11.0 → 0.12.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/bin/patram.js +4 -4
- package/lib/cli/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +10 -20
- package/lib/cli/commands/query.js +1 -8
- package/lib/cli/commands/refs.js +3 -10
- package/lib/cli/commands/show.js +1 -8
- package/lib/cli/help-metadata.js +71 -106
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +165 -59
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +2 -2
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +8 -33
- package/lib/config/load-patram-config.js +9 -33
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +4 -4
- package/lib/config/manage-stored-queries-helpers.js +91 -33
- package/lib/config/manage-stored-queries.d.ts +4 -4
- package/lib/config/manage-stored-queries.js +11 -5
- package/lib/config/patram-config.d.ts +34 -34
- package/lib/config/patram-config.js +3 -3
- package/lib/config/patram-config.types.d.ts +5 -11
- package/lib/config/resolve-patram-graph-config.d.ts +5 -1
- package/lib/config/resolve-patram-graph-config.js +3 -119
- package/lib/config/schema.d.ts +158 -269
- package/lib/config/schema.js +72 -210
- package/lib/config/validate-patram-config-value.js +6 -31
- package/lib/config/validation.d.ts +2 -12
- package/lib/config/validation.js +125 -483
- package/lib/find-close-match.d.ts +4 -1
- package/lib/graph/build-graph-identity.d.ts +1 -32
- package/lib/graph/build-graph-identity.js +5 -269
- package/lib/graph/build-graph.d.ts +13 -4
- package/lib/graph/build-graph.js +347 -488
- package/lib/graph/build-graph.types.d.ts +8 -9
- package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
- package/lib/graph/check-directive-metadata-helpers.js +126 -0
- package/lib/graph/check-directive-metadata.d.ts +8 -9
- package/lib/graph/check-directive-metadata.js +70 -561
- package/lib/graph/check-directive-path-target.d.ts +6 -13
- package/lib/graph/check-directive-path-target.js +26 -57
- package/lib/graph/check-directive-value.d.ts +1 -5
- package/lib/graph/check-directive-value.js +40 -180
- package/lib/graph/check-graph.d.ts +5 -5
- package/lib/graph/check-graph.js +8 -6
- package/lib/graph/document-node-identity.d.ts +23 -7
- package/lib/graph/document-node-identity.js +417 -160
- package/lib/graph/graph-node.d.ts +42 -0
- package/lib/graph/graph-node.js +83 -0
- package/lib/graph/inspect-reverse-references.js +16 -11
- package/lib/graph/load-project-graph.d.ts +7 -7
- package/lib/graph/load-project-graph.js +7 -7
- package/lib/graph/parse-where-clause.types.d.ts +3 -2
- package/lib/graph/query/cypher-reader.d.ts +59 -0
- package/lib/graph/query/cypher-reader.js +151 -0
- package/lib/graph/query/cypher-support.d.ts +79 -0
- package/lib/graph/query/cypher-support.js +213 -0
- package/lib/graph/query/cypher-tokenize.d.ts +13 -0
- package/lib/graph/query/cypher-tokenize.js +225 -0
- package/lib/graph/query/cypher.types.d.ts +43 -0
- package/lib/graph/query/execute.d.ts +7 -7
- package/lib/graph/query/execute.js +71 -33
- package/lib/graph/query/inspect.js +58 -24
- package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
- package/lib/graph/query/parse-cypher-patterns.js +382 -0
- package/lib/graph/query/parse-cypher.d.ts +7 -0
- package/lib/graph/query/parse-cypher.js +580 -0
- package/lib/graph/query/parse-query.d.ts +13 -0
- package/lib/graph/query/parse-query.js +97 -0
- package/lib/graph/query/resolve.js +77 -23
- package/lib/output/command-output.js +12 -5
- package/lib/output/compact-layout.js +221 -0
- package/lib/output/format-output-item-block.js +31 -1
- package/lib/output/format-output-metadata.js +16 -29
- package/lib/output/format-stored-query-block.js +95 -0
- package/lib/output/layout-incoming-references.js +101 -19
- package/lib/output/layout-stored-queries.js +23 -330
- package/lib/output/list-queries.js +1 -1
- package/lib/output/render-field-discovery.js +11 -2
- package/lib/output/render-output-view.js +9 -5
- package/lib/output/renderers/json.js +5 -26
- package/lib/output/renderers/plain.js +155 -35
- package/lib/output/renderers/rich.js +250 -36
- package/lib/output/resolved-link-layout.js +43 -0
- package/lib/output/rich-source/render.js +193 -35
- package/lib/output/show-document.js +25 -18
- package/lib/output/view-model/index.js +124 -103
- package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
- package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
- package/lib/parse/markdown/parse-markdown-claims.js +99 -62
- package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
- package/lib/parse/markdown/parse-markdown-directives.js +104 -18
- package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
- package/lib/parse/markdown/parse-markdown-prose.js +243 -0
- package/lib/parse/parse-claims.d.ts +2 -6
- package/lib/parse/parse-claims.js +11 -53
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
- package/lib/parse/yaml/parse-yaml-claims.js +4 -4
- package/lib/patram.d.ts +3 -5
- package/lib/patram.js +1 -1
- package/lib/scan/discover-fields.js +194 -55
- package/lib/scan/list-source-files.d.ts +4 -4
- package/lib/scan/list-source-files.js +4 -4
- package/package.json +1 -1
- package/lib/directive-validation-test-helpers.js +0 -87
- package/lib/graph/query/parse.d.ts +0 -75
- package/lib/graph/query/parse.js +0 -1064
- package/lib/output/derived-summary.js +0 -280
- package/lib/output/format-derived-summary-row.js +0 -9
package/lib/graph/build-graph.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @import { BuildGraphResult, GraphEdge, GraphNode } from './build-graph.types.ts';
|
|
4
4
|
* @import { PatramClaim } from '../parse/parse-claims.types.ts';
|
|
5
|
-
* @import { MetadataFieldConfig } from '../config/load-patram-config.types.ts';
|
|
6
|
-
* @import { MappingDefinition, PatramConfig } from '../config/patram-config.types.ts';
|
|
5
|
+
* @import { MetadataFieldConfig, PatramRepoConfig } from '../config/load-patram-config.types.ts';
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
import { posix } from 'node:path';
|
|
@@ -12,74 +11,60 @@ import {
|
|
|
12
11
|
collectDocumentEntityKeys,
|
|
13
12
|
collectDocumentNodeReferences,
|
|
14
13
|
normalizeRepoRelativePath,
|
|
15
|
-
resolveNodeKey,
|
|
16
14
|
resolveTargetReference,
|
|
17
15
|
setCanonicalPath,
|
|
18
16
|
} from './build-graph-identity.js';
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
* Claim-to-graph materialization.
|
|
22
|
-
*
|
|
23
|
-
* Maps parsed claims into document nodes and relations using the resolved
|
|
24
|
-
* Patram graph config.
|
|
25
|
-
*
|
|
26
|
-
* Kind: graph
|
|
27
|
-
* Status: active
|
|
28
|
-
* Uses Term: ../../docs/reference/terms/claim.md
|
|
29
|
-
* Uses Term: ../../docs/reference/terms/document.md
|
|
30
|
-
* Uses Term: ../../docs/reference/terms/graph.md
|
|
31
|
-
* Uses Term: ../../docs/reference/terms/mapping.md
|
|
32
|
-
* Uses Term: ../../docs/reference/terms/relation.md
|
|
33
|
-
* Tracked in: ../../docs/plans/v0/source-anchor-dogfooding.md
|
|
34
|
-
* Decided by: ../../docs/decisions/graph-materialization.md
|
|
35
|
-
* Implements: ../../docs/tasks/v0/materialize-graph.md
|
|
36
|
-
* @patram
|
|
37
|
-
* @see {@link ./load-project-graph.js}
|
|
38
|
-
* @see {@link ../../docs/decisions/graph-materialization.md}
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
const STRUCTURAL_FIELD_NAMES = new Set(['$class', '$id', '$path']);
|
|
42
|
-
const DETERMINISTIC_LOCALE = 'en';
|
|
18
|
+
const DERIVED_DESCRIPTION_PRIORITY = 1;
|
|
43
19
|
const DERIVED_TITLE_PRIORITY = 1;
|
|
20
|
+
const EXPLICIT_DESCRIPTION_PRIORITY = 2;
|
|
44
21
|
const EXPLICIT_TITLE_PRIORITY = 2;
|
|
45
22
|
|
|
46
23
|
/**
|
|
47
|
-
* Build a Patram graph from
|
|
24
|
+
* Build a Patram graph from repo config and parsed claims.
|
|
48
25
|
*
|
|
49
|
-
*
|
|
26
|
+
* kind: graph
|
|
27
|
+
* status: active
|
|
28
|
+
* uses_term: ../../docs/reference/terms/graph.md
|
|
29
|
+
* tracked_in: ../../docs/plans/v2/types-and-fields-config.md
|
|
30
|
+
* decided_by: ../../docs/decisions/types-and-fields-config.md
|
|
31
|
+
* @patram
|
|
32
|
+
* @see {@link ./build-graph-identity.js}
|
|
33
|
+
* @see {@link ../../docs/decisions/types-and-fields-config.md}
|
|
34
|
+
*
|
|
35
|
+
* @param {PatramRepoConfig} repo_config
|
|
50
36
|
* @param {PatramClaim[]} claims
|
|
51
37
|
* @returns {BuildGraphResult}
|
|
52
38
|
*/
|
|
53
|
-
export function buildGraph(
|
|
39
|
+
export function buildGraph(repo_config, claims) {
|
|
54
40
|
/** @type {Map<string, GraphNode>} */
|
|
55
41
|
const graph_nodes = new Map();
|
|
56
42
|
const document_node_references = collectDocumentNodeReferences(
|
|
57
|
-
|
|
58
|
-
claims,
|
|
59
|
-
);
|
|
60
|
-
const document_entity_keys = collectDocumentEntityKeys(
|
|
61
|
-
patram_config.mappings,
|
|
43
|
+
repo_config,
|
|
62
44
|
claims,
|
|
63
45
|
);
|
|
46
|
+
const document_entity_keys = collectDocumentEntityKeys(repo_config, claims);
|
|
64
47
|
/** @type {Set<string>} */
|
|
65
48
|
const document_paths = new Set(
|
|
66
49
|
claims.map((claim) => normalizeRepoRelativePath(claim.origin.path)),
|
|
67
50
|
);
|
|
68
51
|
/** @type {Map<string, number>} */
|
|
52
|
+
const description_priorities = new Map();
|
|
53
|
+
/** @type {Map<string, number>} */
|
|
69
54
|
const title_priorities = new Map();
|
|
70
55
|
|
|
71
56
|
createDocumentNodes(graph_nodes, document_node_references);
|
|
72
|
-
|
|
57
|
+
applyMetadataClaims(
|
|
73
58
|
graph_nodes,
|
|
74
|
-
|
|
59
|
+
repo_config,
|
|
75
60
|
claims,
|
|
76
|
-
document_entity_keys,
|
|
77
61
|
document_node_references,
|
|
78
62
|
title_priorities,
|
|
63
|
+
description_priorities,
|
|
79
64
|
);
|
|
80
65
|
const graph_edges = createGraphEdges(
|
|
81
66
|
graph_nodes,
|
|
82
|
-
|
|
67
|
+
repo_config,
|
|
83
68
|
claims,
|
|
84
69
|
document_entity_keys,
|
|
85
70
|
document_node_references,
|
|
@@ -97,39 +82,13 @@ export function buildGraph(patram_config, claims) {
|
|
|
97
82
|
attachDocumentNodeAliases(graph_result.nodes, document_node_references);
|
|
98
83
|
Object.defineProperty(
|
|
99
84
|
graph_result,
|
|
100
|
-
'
|
|
101
|
-
|
|
85
|
+
'document_path_ids',
|
|
86
|
+
createDocumentPathIdsProperty(document_node_references),
|
|
102
87
|
);
|
|
103
88
|
|
|
104
89
|
return graph_result;
|
|
105
90
|
}
|
|
106
91
|
|
|
107
|
-
/**
|
|
108
|
-
* @param {Record<string, MappingDefinition>} mappings
|
|
109
|
-
* @param {PatramClaim} claim
|
|
110
|
-
* @returns {MappingDefinition | null}
|
|
111
|
-
*/
|
|
112
|
-
function resolveMappingDefinition(mappings, claim) {
|
|
113
|
-
if (claim.type === 'directive') {
|
|
114
|
-
return resolveDirectiveMapping(mappings, claim);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return mappings[claim.type] ?? null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @param {Record<string, MappingDefinition>} mappings
|
|
122
|
-
* @param {PatramClaim} claim
|
|
123
|
-
* @returns {MappingDefinition | null}
|
|
124
|
-
*/
|
|
125
|
-
function resolveDirectiveMapping(mappings, claim) {
|
|
126
|
-
if (!claim.parser || !claim.name) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return mappings[`${claim.parser}.directive.${claim.name}`] ?? null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
92
|
/**
|
|
134
93
|
* @param {Map<string, GraphNode>} graph_nodes
|
|
135
94
|
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
@@ -148,45 +107,67 @@ function createDocumentNodes(graph_nodes, document_node_references) {
|
|
|
148
107
|
|
|
149
108
|
/**
|
|
150
109
|
* @param {Map<string, GraphNode>} graph_nodes
|
|
151
|
-
* @param {
|
|
110
|
+
* @param {PatramRepoConfig} repo_config
|
|
152
111
|
* @param {PatramClaim[]} claims
|
|
153
|
-
* @param {Map<string, string>} document_entity_keys
|
|
154
112
|
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
155
113
|
* @param {Map<string, number>} title_priorities
|
|
114
|
+
* @param {Map<string, number>} description_priorities
|
|
156
115
|
*/
|
|
157
|
-
function
|
|
116
|
+
function applyMetadataClaims(
|
|
158
117
|
graph_nodes,
|
|
159
|
-
|
|
118
|
+
repo_config,
|
|
160
119
|
claims,
|
|
161
|
-
document_entity_keys,
|
|
162
120
|
document_node_references,
|
|
163
121
|
title_priorities,
|
|
122
|
+
description_priorities,
|
|
164
123
|
) {
|
|
165
124
|
for (const claim of claims) {
|
|
166
|
-
const
|
|
167
|
-
|
|
125
|
+
const source_graph_node = getSourceGraphNode(
|
|
126
|
+
graph_nodes,
|
|
127
|
+
document_node_references,
|
|
168
128
|
claim,
|
|
169
129
|
);
|
|
170
130
|
|
|
171
|
-
if (
|
|
131
|
+
if (
|
|
132
|
+
applyDerivedMetadataClaim(
|
|
133
|
+
source_graph_node,
|
|
134
|
+
claim,
|
|
135
|
+
title_priorities,
|
|
136
|
+
description_priorities,
|
|
137
|
+
)
|
|
138
|
+
) {
|
|
172
139
|
continue;
|
|
173
140
|
}
|
|
174
141
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
142
|
+
if (claim.type !== 'directive' || !claim.name) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const field_context = resolveScalarFieldContext(
|
|
147
|
+
repo_config,
|
|
148
|
+
source_graph_node,
|
|
149
|
+
claim.name,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (!field_context) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const field_value = getScalarClaimValue(claim);
|
|
157
|
+
assignExplicitMetadataValue(
|
|
158
|
+
source_graph_node,
|
|
159
|
+
claim.name,
|
|
160
|
+
field_value,
|
|
161
|
+
field_context.is_many,
|
|
182
162
|
title_priorities,
|
|
163
|
+
description_priorities,
|
|
183
164
|
);
|
|
184
165
|
}
|
|
185
166
|
}
|
|
186
167
|
|
|
187
168
|
/**
|
|
188
169
|
* @param {Map<string, GraphNode>} graph_nodes
|
|
189
|
-
* @param {
|
|
170
|
+
* @param {PatramRepoConfig} repo_config
|
|
190
171
|
* @param {PatramClaim[]} claims
|
|
191
172
|
* @param {Map<string, string>} document_entity_keys
|
|
192
173
|
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
@@ -195,7 +176,7 @@ function applyNodeMappings(
|
|
|
195
176
|
*/
|
|
196
177
|
function createGraphEdges(
|
|
197
178
|
graph_nodes,
|
|
198
|
-
|
|
179
|
+
repo_config,
|
|
199
180
|
claims,
|
|
200
181
|
document_entity_keys,
|
|
201
182
|
document_node_references,
|
|
@@ -206,20 +187,20 @@ function createGraphEdges(
|
|
|
206
187
|
let edge_number = 0;
|
|
207
188
|
|
|
208
189
|
for (const claim of claims) {
|
|
209
|
-
const
|
|
190
|
+
const edge_definition = resolveEdgeDefinition(repo_config, claim);
|
|
210
191
|
|
|
211
|
-
if (!
|
|
192
|
+
if (!edge_definition) {
|
|
212
193
|
continue;
|
|
213
194
|
}
|
|
214
195
|
|
|
215
|
-
const source_document_node =
|
|
196
|
+
const source_document_node = getSourceGraphNode(
|
|
216
197
|
graph_nodes,
|
|
217
198
|
document_node_references,
|
|
218
199
|
claim,
|
|
219
200
|
);
|
|
220
201
|
const target_reference = resolveTargetReference(
|
|
221
|
-
|
|
222
|
-
|
|
202
|
+
edge_definition.target_class,
|
|
203
|
+
'path',
|
|
223
204
|
claim,
|
|
224
205
|
document_entity_keys,
|
|
225
206
|
document_node_references,
|
|
@@ -235,11 +216,11 @@ function createGraphEdges(
|
|
|
235
216
|
|
|
236
217
|
edge_number += 1;
|
|
237
218
|
graph_edges.push({
|
|
238
|
-
from: source_document_node.id,
|
|
219
|
+
from: source_document_node.identity.id,
|
|
239
220
|
id: `edge:${edge_number}`,
|
|
240
221
|
origin: claim.origin,
|
|
241
|
-
relation:
|
|
242
|
-
to: target_node.id,
|
|
222
|
+
relation: edge_definition.relation_name,
|
|
223
|
+
to: target_node.identity.id,
|
|
243
224
|
});
|
|
244
225
|
}
|
|
245
226
|
|
|
@@ -247,220 +228,31 @@ function createGraphEdges(
|
|
|
247
228
|
}
|
|
248
229
|
|
|
249
230
|
/**
|
|
250
|
-
* @param {
|
|
251
|
-
* @param {PatramConfig} patram_config
|
|
252
|
-
* @param {{ field: string, key?: 'path' | 'value', class: string }} node_mapping
|
|
231
|
+
* @param {PatramRepoConfig} repo_config
|
|
253
232
|
* @param {PatramClaim} claim
|
|
254
|
-
* @
|
|
255
|
-
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
256
|
-
* @param {Map<string, number>} title_priorities
|
|
257
|
-
*/
|
|
258
|
-
function applyNodeMapping(
|
|
259
|
-
graph_nodes,
|
|
260
|
-
patram_config,
|
|
261
|
-
node_mapping,
|
|
262
|
-
claim,
|
|
263
|
-
document_entity_keys,
|
|
264
|
-
document_node_references,
|
|
265
|
-
title_priorities,
|
|
266
|
-
) {
|
|
267
|
-
const source_key = normalizeRepoRelativePath(claim.origin.path);
|
|
268
|
-
const graph_node = resolveGraphNode(
|
|
269
|
-
graph_nodes,
|
|
270
|
-
node_mapping,
|
|
271
|
-
claim,
|
|
272
|
-
document_entity_keys,
|
|
273
|
-
document_node_references,
|
|
274
|
-
);
|
|
275
|
-
const field_value = resolveNodeFieldValue(
|
|
276
|
-
graph_node,
|
|
277
|
-
node_mapping,
|
|
278
|
-
claim,
|
|
279
|
-
source_key,
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
setCanonicalPath(graph_node, source_key);
|
|
283
|
-
validateNodeFieldMapping(
|
|
284
|
-
patram_config,
|
|
285
|
-
node_mapping.class,
|
|
286
|
-
node_mapping.field,
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
if (node_mapping.field === 'title') {
|
|
290
|
-
setNodeTitle(
|
|
291
|
-
graph_node,
|
|
292
|
-
title_priorities,
|
|
293
|
-
field_value,
|
|
294
|
-
claim.type === 'document.title'
|
|
295
|
-
? DERIVED_TITLE_PRIORITY
|
|
296
|
-
: EXPLICIT_TITLE_PRIORITY,
|
|
297
|
-
);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
setNodeFieldValue(
|
|
302
|
-
graph_node,
|
|
303
|
-
node_mapping.field,
|
|
304
|
-
field_value,
|
|
305
|
-
getFieldDefinition(patram_config, node_mapping.field),
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Validate one mapped node field against the configured field model.
|
|
311
|
-
*
|
|
312
|
-
* @param {PatramConfig} patram_config
|
|
313
|
-
* @param {string} node_class
|
|
314
|
-
* @param {string} field_name
|
|
315
|
-
*/
|
|
316
|
-
function validateNodeFieldMapping(patram_config, node_class, field_name) {
|
|
317
|
-
const validation_error = getNodeFieldValidationError(
|
|
318
|
-
patram_config,
|
|
319
|
-
node_class,
|
|
320
|
-
field_name,
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
if (validation_error) {
|
|
324
|
-
throw new Error(validation_error);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* @param {PatramConfig} patram_config
|
|
330
|
-
* @param {string} node_class
|
|
331
|
-
* @param {string} field_name
|
|
332
|
-
* @returns {string | null}
|
|
233
|
+
* @returns {{ relation_name: string, target_class: string } | null}
|
|
333
234
|
*/
|
|
334
|
-
function
|
|
335
|
-
if (
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const field_definition = getFieldDefinition(patram_config, field_name);
|
|
340
|
-
|
|
341
|
-
if (!field_definition) {
|
|
342
|
-
return `Node class "${node_class}" maps to unknown field "${field_name}".`;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const class_schema = getClassSchema(patram_config, node_class);
|
|
346
|
-
const class_field_rule = class_schema?.fields?.[field_name];
|
|
347
|
-
|
|
348
|
-
if (isForbiddenClassField(class_field_rule)) {
|
|
349
|
-
return `Field "${field_name}" is forbidden for class "${node_class}".`;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (isUndeclaredClassField(class_schema, class_field_rule)) {
|
|
353
|
-
return `Field "${field_name}" is not declared for class "${node_class}".`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* @param {{ presence: 'required' | 'optional' | 'forbidden' } | undefined} class_field_rule
|
|
361
|
-
* @returns {boolean}
|
|
362
|
-
*/
|
|
363
|
-
function isForbiddenClassField(class_field_rule) {
|
|
364
|
-
return class_field_rule?.presence === 'forbidden';
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* @param {{ fields?: Record<string, { presence: 'required' | 'optional' | 'forbidden' }>, unknown_fields?: 'ignore' | 'error' } | undefined} class_schema
|
|
369
|
-
* @param {{ presence: 'required' | 'optional' | 'forbidden' } | undefined} class_field_rule
|
|
370
|
-
* @returns {boolean}
|
|
371
|
-
*/
|
|
372
|
-
function isUndeclaredClassField(class_schema, class_field_rule) {
|
|
373
|
-
return class_schema?.unknown_fields === 'error' && !class_field_rule;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* @param {Map<string, GraphNode>} graph_nodes
|
|
378
|
-
* @param {string} kind_name
|
|
379
|
-
* @param {string} node_key
|
|
380
|
-
* @returns {GraphNode}
|
|
381
|
-
*/
|
|
382
|
-
function upsertNode(graph_nodes, kind_name, node_key) {
|
|
383
|
-
const node_id = getNodeId(kind_name, node_key);
|
|
384
|
-
const existing_node = graph_nodes.get(node_id);
|
|
385
|
-
|
|
386
|
-
if (existing_node) {
|
|
387
|
-
return existing_node;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const graph_node = createNode(node_id, kind_name, node_key);
|
|
391
|
-
|
|
392
|
-
graph_nodes.set(node_id, graph_node);
|
|
393
|
-
|
|
394
|
-
return graph_node;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* @param {string} node_id
|
|
399
|
-
* @param {string} class_name
|
|
400
|
-
* @param {string} node_key
|
|
401
|
-
* @returns {GraphNode}
|
|
402
|
-
*/
|
|
403
|
-
function createNode(node_id, class_name, node_key) {
|
|
404
|
-
if (class_name === 'document') {
|
|
235
|
+
function resolveEdgeDefinition(repo_config, claim) {
|
|
236
|
+
if (claim.type === 'markdown.link' || claim.type === 'jsdoc.link') {
|
|
405
237
|
return {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
$path: node_key,
|
|
409
|
-
id: node_id,
|
|
410
|
-
path: node_key,
|
|
238
|
+
relation_name: 'links_to',
|
|
239
|
+
target_class: 'document',
|
|
411
240
|
};
|
|
412
241
|
}
|
|
413
242
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
$id: node_id,
|
|
417
|
-
id: node_id,
|
|
418
|
-
key: node_key,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* @param {string} kind_name
|
|
424
|
-
* @param {string} node_key
|
|
425
|
-
* @returns {string}
|
|
426
|
-
*/
|
|
427
|
-
function getNodeId(kind_name, node_key) {
|
|
428
|
-
if (kind_name === 'document') {
|
|
429
|
-
return `doc:${node_key}`;
|
|
243
|
+
if (claim.type !== 'directive' || !claim.name) {
|
|
244
|
+
return null;
|
|
430
245
|
}
|
|
431
246
|
|
|
432
|
-
|
|
433
|
-
}
|
|
247
|
+
const field_definition = repo_config.fields?.[claim.name];
|
|
434
248
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
* @returns {string}
|
|
438
|
-
*/
|
|
439
|
-
function getNodeFieldValue(claim) {
|
|
440
|
-
if (typeof claim.value === 'string') {
|
|
441
|
-
return claim.value;
|
|
249
|
+
if (field_definition?.type !== 'ref') {
|
|
250
|
+
return null;
|
|
442
251
|
}
|
|
443
252
|
|
|
444
|
-
throw new Error(`Claim "${claim.id}" does not carry a string value.`);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
449
|
-
* @returns {{ configurable: false, enumerable: false, value: Record<string, string>, writable: false }}
|
|
450
|
-
*/
|
|
451
|
-
function createDocumentNodeIdsProperty(document_node_references) {
|
|
452
253
|
return {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
value: Object.fromEntries(
|
|
456
|
-
[...document_node_references.entries()].map(
|
|
457
|
-
([document_path, document_node_reference]) => [
|
|
458
|
-
document_path,
|
|
459
|
-
document_node_reference.id,
|
|
460
|
-
],
|
|
461
|
-
),
|
|
462
|
-
),
|
|
463
|
-
writable: false,
|
|
254
|
+
relation_name: claim.name,
|
|
255
|
+
target_class: field_definition.to,
|
|
464
256
|
};
|
|
465
257
|
}
|
|
466
258
|
|
|
@@ -470,12 +262,12 @@ function createDocumentNodeIdsProperty(document_node_references) {
|
|
|
470
262
|
* @param {PatramClaim} claim
|
|
471
263
|
* @returns {GraphNode}
|
|
472
264
|
*/
|
|
473
|
-
function
|
|
474
|
-
const
|
|
475
|
-
const document_node_reference = document_node_references.get(
|
|
265
|
+
function getSourceGraphNode(graph_nodes, document_node_references, claim) {
|
|
266
|
+
const source_path = normalizeRepoRelativePath(claim.origin.path);
|
|
267
|
+
const document_node_reference = document_node_references.get(source_path);
|
|
476
268
|
|
|
477
269
|
if (!document_node_reference) {
|
|
478
|
-
|
|
270
|
+
return upsertNode(graph_nodes, 'document', source_path);
|
|
479
271
|
}
|
|
480
272
|
|
|
481
273
|
const graph_node = upsertNode(
|
|
@@ -485,305 +277,354 @@ function getDocumentGraphNode(graph_nodes, document_node_references, claim) {
|
|
|
485
277
|
);
|
|
486
278
|
|
|
487
279
|
setCanonicalPath(graph_node, document_node_reference.path);
|
|
488
|
-
|
|
489
280
|
return graph_node;
|
|
490
281
|
}
|
|
491
282
|
|
|
492
283
|
/**
|
|
493
|
-
* @param {
|
|
494
|
-
* @param {
|
|
495
|
-
* @
|
|
496
|
-
* @param {Map<string, string>} document_entity_keys
|
|
497
|
-
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
498
|
-
* @returns {GraphNode}
|
|
284
|
+
* @param {MetadataFieldConfig} field_definition
|
|
285
|
+
* @param {GraphNode} graph_node
|
|
286
|
+
* @returns {boolean}
|
|
499
287
|
*/
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
claim,
|
|
504
|
-
document_entity_keys,
|
|
505
|
-
document_node_references,
|
|
506
|
-
) {
|
|
507
|
-
if (node_mapping.class === 'document') {
|
|
508
|
-
return getDocumentGraphNode(graph_nodes, document_node_references, claim);
|
|
288
|
+
function fieldAppliesToNode(field_definition, graph_node) {
|
|
289
|
+
if (!field_definition.on || field_definition.on.length === 0) {
|
|
290
|
+
return true;
|
|
509
291
|
}
|
|
510
292
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return upsertNode(graph_nodes, node_mapping.class, node_key);
|
|
293
|
+
return field_definition.on.includes(graph_node.identity.class_name);
|
|
514
294
|
}
|
|
515
295
|
|
|
516
296
|
/**
|
|
517
|
-
* @param {GraphNode} graph_node
|
|
518
|
-
* @param {{ field: string, key?: 'path' | 'value', class: string }} node_mapping
|
|
519
297
|
* @param {PatramClaim} claim
|
|
520
|
-
* @param {string} source_path
|
|
521
298
|
* @returns {string}
|
|
522
299
|
*/
|
|
523
|
-
function
|
|
524
|
-
if (
|
|
525
|
-
return
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (node_mapping.field === '$class') {
|
|
529
|
-
return graph_node.$class ?? graph_node.kind ?? node_mapping.class;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (node_mapping.field === '$path') {
|
|
533
|
-
return source_path;
|
|
300
|
+
function getScalarClaimValue(claim) {
|
|
301
|
+
if (typeof claim.value === 'string') {
|
|
302
|
+
return claim.value;
|
|
534
303
|
}
|
|
535
304
|
|
|
536
|
-
|
|
305
|
+
throw new Error(`Claim "${claim.id}" does not carry a string value.`);
|
|
537
306
|
}
|
|
538
307
|
|
|
539
308
|
/**
|
|
540
|
-
* @param {
|
|
541
|
-
* @param {
|
|
309
|
+
* @param {GraphNode} source_graph_node
|
|
310
|
+
* @param {PatramClaim} claim
|
|
311
|
+
* @param {Map<string, number>} title_priorities
|
|
312
|
+
* @param {Map<string, number>} description_priorities
|
|
313
|
+
* @returns {boolean}
|
|
542
314
|
*/
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
315
|
+
function applyDerivedMetadataClaim(
|
|
316
|
+
source_graph_node,
|
|
317
|
+
claim,
|
|
318
|
+
title_priorities,
|
|
319
|
+
description_priorities,
|
|
320
|
+
) {
|
|
321
|
+
if (claim.type === 'document.title' && typeof claim.value === 'string') {
|
|
322
|
+
assignMetadataValue(
|
|
323
|
+
source_graph_node,
|
|
324
|
+
'title',
|
|
325
|
+
claim.value,
|
|
326
|
+
false,
|
|
327
|
+
title_priorities,
|
|
328
|
+
DERIVED_TITLE_PRIORITY,
|
|
329
|
+
);
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
553
332
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
333
|
+
if (
|
|
334
|
+
claim.type === 'document.description' &&
|
|
335
|
+
typeof claim.value === 'string'
|
|
336
|
+
) {
|
|
337
|
+
assignMetadataValue(
|
|
338
|
+
source_graph_node,
|
|
339
|
+
'description',
|
|
340
|
+
claim.value,
|
|
341
|
+
false,
|
|
342
|
+
description_priorities,
|
|
343
|
+
DERIVED_DESCRIPTION_PRIORITY,
|
|
344
|
+
);
|
|
345
|
+
return true;
|
|
560
346
|
}
|
|
347
|
+
|
|
348
|
+
return false;
|
|
561
349
|
}
|
|
562
350
|
|
|
563
351
|
/**
|
|
564
|
-
* @param {
|
|
565
|
-
* @param {
|
|
352
|
+
* @param {PatramRepoConfig} repo_config
|
|
353
|
+
* @param {GraphNode} source_graph_node
|
|
354
|
+
* @param {string} field_name
|
|
355
|
+
* @returns {{ is_many: boolean } | null}
|
|
566
356
|
*/
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
if (graph_node.title !== undefined) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const fallback_title = getFallbackTitle(graph_node);
|
|
357
|
+
function resolveScalarFieldContext(repo_config, source_graph_node, field_name) {
|
|
358
|
+
const field_definition = repo_config.fields?.[field_name];
|
|
574
359
|
|
|
575
|
-
|
|
576
|
-
|
|
360
|
+
if (
|
|
361
|
+
field_name !== 'title' &&
|
|
362
|
+
field_name !== 'description' &&
|
|
363
|
+
(!field_definition || field_definition.type === 'ref')
|
|
364
|
+
) {
|
|
365
|
+
return null;
|
|
577
366
|
}
|
|
578
|
-
}
|
|
579
367
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (graph_node.$path) {
|
|
586
|
-
return posix.basename(graph_node.$path);
|
|
368
|
+
if (
|
|
369
|
+
field_definition &&
|
|
370
|
+
!fieldAppliesToNode(field_definition, source_graph_node)
|
|
371
|
+
) {
|
|
372
|
+
return null;
|
|
587
373
|
}
|
|
588
374
|
|
|
589
|
-
return
|
|
375
|
+
return {
|
|
376
|
+
is_many: field_definition?.many === true,
|
|
377
|
+
};
|
|
590
378
|
}
|
|
591
379
|
|
|
592
380
|
/**
|
|
593
381
|
* @param {GraphNode} graph_node
|
|
382
|
+
* @param {string} field_name
|
|
383
|
+
* @param {string} field_value
|
|
384
|
+
* @param {boolean} is_many
|
|
594
385
|
* @param {Map<string, number>} title_priorities
|
|
595
|
-
* @param {string}
|
|
596
|
-
* @param {number} source_priority
|
|
386
|
+
* @param {Map<string, number>} description_priorities
|
|
597
387
|
*/
|
|
598
|
-
function
|
|
388
|
+
function assignExplicitMetadataValue(
|
|
599
389
|
graph_node,
|
|
390
|
+
field_name,
|
|
391
|
+
field_value,
|
|
392
|
+
is_many,
|
|
600
393
|
title_priorities,
|
|
601
|
-
|
|
602
|
-
source_priority,
|
|
394
|
+
description_priorities,
|
|
603
395
|
) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
396
|
+
if (field_name === 'title') {
|
|
397
|
+
assignMetadataValue(
|
|
398
|
+
graph_node,
|
|
399
|
+
'title',
|
|
400
|
+
field_value,
|
|
401
|
+
is_many,
|
|
402
|
+
title_priorities,
|
|
403
|
+
EXPLICIT_TITLE_PRIORITY,
|
|
404
|
+
);
|
|
609
405
|
return;
|
|
610
406
|
}
|
|
611
407
|
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
|
|
408
|
+
if (field_name === 'description') {
|
|
409
|
+
assignMetadataValue(
|
|
410
|
+
graph_node,
|
|
411
|
+
'description',
|
|
412
|
+
field_value,
|
|
413
|
+
is_many,
|
|
414
|
+
description_priorities,
|
|
415
|
+
EXPLICIT_DESCRIPTION_PRIORITY,
|
|
416
|
+
);
|
|
615
417
|
return;
|
|
616
418
|
}
|
|
617
419
|
|
|
618
|
-
|
|
619
|
-
source_priority === current_priority &&
|
|
620
|
-
graph_node.title !== title_value
|
|
621
|
-
) {
|
|
622
|
-
throw new Error(
|
|
623
|
-
`Node "${graph_node.id}" has conflicting title values "${graph_node.title}" and "${title_value}".`,
|
|
624
|
-
);
|
|
625
|
-
}
|
|
420
|
+
assignMetadataValue(graph_node, field_name, field_value, is_many);
|
|
626
421
|
}
|
|
627
422
|
|
|
628
423
|
/**
|
|
629
424
|
* @param {GraphNode} graph_node
|
|
630
425
|
* @param {string} field_name
|
|
631
426
|
* @param {string} field_value
|
|
632
|
-
* @param {
|
|
427
|
+
* @param {boolean} multiple
|
|
428
|
+
* @param {Map<string, number>} [field_priorities]
|
|
429
|
+
* @param {number} [field_priority]
|
|
633
430
|
*/
|
|
634
|
-
function
|
|
431
|
+
function assignMetadataValue(
|
|
635
432
|
graph_node,
|
|
636
433
|
field_name,
|
|
637
434
|
field_value,
|
|
638
|
-
|
|
435
|
+
multiple,
|
|
436
|
+
field_priorities,
|
|
437
|
+
field_priority,
|
|
639
438
|
) {
|
|
640
439
|
if (
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
440
|
+
shouldSkipLowerPriorityValue(
|
|
441
|
+
graph_node,
|
|
442
|
+
field_name,
|
|
443
|
+
field_priorities,
|
|
444
|
+
field_priority,
|
|
445
|
+
)
|
|
644
446
|
) {
|
|
645
|
-
setStructuralFieldValue(graph_node, field_name, field_value);
|
|
646
447
|
return;
|
|
647
448
|
}
|
|
648
449
|
|
|
649
|
-
|
|
650
|
-
|
|
450
|
+
const existing_value = graph_node.metadata[field_name];
|
|
451
|
+
|
|
452
|
+
if (multiple) {
|
|
453
|
+
graph_node.metadata[field_name] = appendMetadataValue(
|
|
454
|
+
existing_value,
|
|
455
|
+
field_value,
|
|
456
|
+
);
|
|
651
457
|
return;
|
|
652
458
|
}
|
|
653
459
|
|
|
654
|
-
|
|
460
|
+
assignSingleMetadataValue(
|
|
461
|
+
graph_node,
|
|
462
|
+
field_name,
|
|
463
|
+
field_value,
|
|
464
|
+
existing_value,
|
|
465
|
+
);
|
|
655
466
|
}
|
|
656
467
|
|
|
657
468
|
/**
|
|
658
469
|
* @param {GraphNode} graph_node
|
|
659
|
-
* @param {
|
|
660
|
-
* @param {string}
|
|
470
|
+
* @param {string} field_name
|
|
471
|
+
* @param {Map<string, number> | undefined} field_priorities
|
|
472
|
+
* @param {number | undefined} field_priority
|
|
473
|
+
* @returns {boolean}
|
|
661
474
|
*/
|
|
662
|
-
function
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
475
|
+
function shouldSkipLowerPriorityValue(
|
|
476
|
+
graph_node,
|
|
477
|
+
field_name,
|
|
478
|
+
field_priorities,
|
|
479
|
+
field_priority,
|
|
480
|
+
) {
|
|
481
|
+
if (!field_priorities || field_priority === undefined) {
|
|
482
|
+
return false;
|
|
668
483
|
}
|
|
669
484
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
assignStructuralFieldValue(graph_node, field_name, field_value);
|
|
676
|
-
return;
|
|
485
|
+
const priority_key = `${graph_node.identity.id}:${field_name}`;
|
|
486
|
+
const previous_priority = field_priorities.get(priority_key) ?? 0;
|
|
487
|
+
|
|
488
|
+
if (field_priority < previous_priority) {
|
|
489
|
+
return true;
|
|
677
490
|
}
|
|
678
491
|
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
`Node "${graph_node.id}" has conflicting structural values for "${field_name}".`,
|
|
682
|
-
);
|
|
492
|
+
if (field_priority <= previous_priority) {
|
|
493
|
+
return false;
|
|
683
494
|
}
|
|
495
|
+
|
|
496
|
+
graph_node.metadata[field_name] = undefined;
|
|
497
|
+
field_priorities.set(priority_key, field_priority);
|
|
498
|
+
return false;
|
|
684
499
|
}
|
|
685
500
|
|
|
686
501
|
/**
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
* @param {GraphNode} graph_node
|
|
690
|
-
* @param {'$id' | '$class' | '$path'} field_name
|
|
502
|
+
* @param {GraphNode['metadata'][string]} existing_value
|
|
691
503
|
* @param {string} field_value
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
504
|
+
* @returns {string[]}
|
|
505
|
+
*/
|
|
506
|
+
function appendMetadataValue(existing_value, field_value) {
|
|
507
|
+
const next_values = new Set(
|
|
508
|
+
Array.isArray(existing_value)
|
|
509
|
+
? existing_value
|
|
510
|
+
: existing_value
|
|
511
|
+
? [existing_value]
|
|
512
|
+
: [],
|
|
513
|
+
);
|
|
695
514
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
515
|
+
next_values.add(field_value);
|
|
516
|
+
return [...next_values].sort();
|
|
699
517
|
}
|
|
700
518
|
|
|
701
519
|
/**
|
|
702
520
|
* @param {GraphNode} graph_node
|
|
703
521
|
* @param {string} field_name
|
|
704
522
|
* @param {string} field_value
|
|
523
|
+
* @param {GraphNode['metadata'][string]} existing_value
|
|
705
524
|
*/
|
|
706
|
-
function
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
525
|
+
function assignSingleMetadataValue(
|
|
526
|
+
graph_node,
|
|
527
|
+
field_name,
|
|
528
|
+
field_value,
|
|
529
|
+
existing_value,
|
|
530
|
+
) {
|
|
531
|
+
if (existing_value === undefined) {
|
|
532
|
+
graph_node.metadata[field_name] = field_value;
|
|
711
533
|
return;
|
|
712
534
|
}
|
|
713
535
|
|
|
714
|
-
if (
|
|
715
|
-
const current_value_text = Array.isArray(current_value)
|
|
716
|
-
? current_value.join(', ')
|
|
717
|
-
: current_value;
|
|
718
|
-
|
|
536
|
+
if (Array.isArray(existing_value)) {
|
|
719
537
|
throw new Error(
|
|
720
|
-
`Node "${graph_node.id}" has conflicting values for field "${field_name}"
|
|
538
|
+
`Node "${graph_node.identity.id}" has conflicting values for field "${field_name}".`,
|
|
721
539
|
);
|
|
722
540
|
}
|
|
541
|
+
|
|
542
|
+
if (existing_value === field_value) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
throw new Error(
|
|
547
|
+
`Node "${graph_node.identity.id}" has conflicting values for field "${field_name}": "${existing_value}" and "${field_value}".`,
|
|
548
|
+
);
|
|
723
549
|
}
|
|
724
550
|
|
|
725
551
|
/**
|
|
726
|
-
* @param {GraphNode}
|
|
727
|
-
* @param {string}
|
|
728
|
-
* @param {string} field_value
|
|
552
|
+
* @param {Map<string, GraphNode>} graph_nodes
|
|
553
|
+
* @param {Map<string, number>} title_priorities
|
|
729
554
|
*/
|
|
730
|
-
function
|
|
731
|
-
const
|
|
555
|
+
function applyFallbackTitles(graph_nodes, title_priorities) {
|
|
556
|
+
for (const graph_node of graph_nodes.values()) {
|
|
557
|
+
if (graph_node.metadata.title !== undefined) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
732
560
|
|
|
733
|
-
|
|
734
|
-
graph_node[field_name] = [field_value];
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
561
|
+
const fallback_title = resolveFallbackTitle(graph_node);
|
|
737
562
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
return;
|
|
563
|
+
if (!fallback_title) {
|
|
564
|
+
continue;
|
|
741
565
|
}
|
|
742
566
|
|
|
743
|
-
|
|
744
|
-
|
|
567
|
+
assignMetadataValue(
|
|
568
|
+
graph_node,
|
|
569
|
+
'title',
|
|
570
|
+
fallback_title,
|
|
571
|
+
false,
|
|
572
|
+
title_priorities,
|
|
573
|
+
DERIVED_TITLE_PRIORITY,
|
|
745
574
|
);
|
|
746
|
-
return;
|
|
747
575
|
}
|
|
748
|
-
|
|
749
|
-
graph_node[field_name] = [current_value, field_value].sort(
|
|
750
|
-
compareFieldValues,
|
|
751
|
-
);
|
|
752
576
|
}
|
|
753
577
|
|
|
754
578
|
/**
|
|
755
|
-
* @param {
|
|
756
|
-
* @
|
|
757
|
-
* @returns {number}
|
|
579
|
+
* @param {GraphNode} graph_node
|
|
580
|
+
* @returns {string | null}
|
|
758
581
|
*/
|
|
759
|
-
function
|
|
760
|
-
|
|
761
|
-
|
|
582
|
+
function resolveFallbackTitle(graph_node) {
|
|
583
|
+
if (graph_node.identity.path) {
|
|
584
|
+
return posix.basename(graph_node.identity.path);
|
|
585
|
+
}
|
|
762
586
|
|
|
763
|
-
|
|
764
|
-
* @param {PatramConfig} patram_config
|
|
765
|
-
* @param {string} field_name
|
|
766
|
-
* @returns {MetadataFieldConfig | undefined}
|
|
767
|
-
*/
|
|
768
|
-
function getFieldDefinition(patram_config, field_name) {
|
|
769
|
-
return patram_config.fields?.[field_name];
|
|
587
|
+
return graph_node.key ?? null;
|
|
770
588
|
}
|
|
771
589
|
|
|
772
590
|
/**
|
|
773
|
-
* @param {
|
|
591
|
+
* @param {Map<string, GraphNode>} graph_nodes
|
|
774
592
|
* @param {string} class_name
|
|
775
|
-
* @
|
|
593
|
+
* @param {string} node_key
|
|
594
|
+
* @returns {GraphNode}
|
|
776
595
|
*/
|
|
777
|
-
function
|
|
778
|
-
|
|
596
|
+
function upsertNode(graph_nodes, class_name, node_key) {
|
|
597
|
+
const node_id = getNodeId(class_name, node_key);
|
|
598
|
+
const existing_node = graph_nodes.get(node_id);
|
|
599
|
+
|
|
600
|
+
if (existing_node) {
|
|
601
|
+
return existing_node;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const graph_node = {
|
|
605
|
+
identity: {
|
|
606
|
+
class_name,
|
|
607
|
+
id: node_id,
|
|
608
|
+
},
|
|
609
|
+
key: node_key,
|
|
610
|
+
metadata: {},
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
graph_nodes.set(node_id, graph_node);
|
|
614
|
+
return graph_node;
|
|
779
615
|
}
|
|
780
616
|
|
|
781
617
|
/**
|
|
782
|
-
* @param {string}
|
|
783
|
-
* @
|
|
618
|
+
* @param {string} class_name
|
|
619
|
+
* @param {string} node_key
|
|
620
|
+
* @returns {string}
|
|
784
621
|
*/
|
|
785
|
-
function
|
|
786
|
-
|
|
622
|
+
function getNodeId(class_name, node_key) {
|
|
623
|
+
if (class_name === 'document') {
|
|
624
|
+
return `doc:${node_key}`;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return `${class_name}:${node_key}`;
|
|
787
628
|
}
|
|
788
629
|
|
|
789
630
|
/**
|
|
@@ -792,19 +633,37 @@ function isStructuralFieldName(field_name) {
|
|
|
792
633
|
* @returns {number}
|
|
793
634
|
*/
|
|
794
635
|
function compareNodeEntries(left_entry, right_entry) {
|
|
795
|
-
return left_entry[0].localeCompare(right_entry[0]
|
|
636
|
+
return left_entry[0].localeCompare(right_entry[0]);
|
|
796
637
|
}
|
|
797
638
|
|
|
798
639
|
/**
|
|
799
|
-
* @param {string}
|
|
800
|
-
* @
|
|
640
|
+
* @param {Record<string, GraphNode>} graph_nodes
|
|
641
|
+
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
801
642
|
*/
|
|
802
|
-
function
|
|
803
|
-
const
|
|
643
|
+
function attachDocumentNodeAliases(graph_nodes, document_node_references) {
|
|
644
|
+
for (const document_node_reference of document_node_references.values()) {
|
|
645
|
+
const document_id = `doc:${document_node_reference.path}`;
|
|
804
646
|
|
|
805
|
-
|
|
806
|
-
|
|
647
|
+
if (graph_nodes[document_id]) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
graph_nodes[document_id] = graph_nodes[document_node_reference.id];
|
|
807
652
|
}
|
|
653
|
+
}
|
|
808
654
|
|
|
809
|
-
|
|
655
|
+
/**
|
|
656
|
+
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
657
|
+
* @returns {PropertyDescriptor}
|
|
658
|
+
*/
|
|
659
|
+
function createDocumentPathIdsProperty(document_node_references) {
|
|
660
|
+
return {
|
|
661
|
+
enumerable: false,
|
|
662
|
+
value: Object.fromEntries(
|
|
663
|
+
[...document_node_references.entries()].map(
|
|
664
|
+
([document_path, node_reference]) => [document_path, node_reference.id],
|
|
665
|
+
),
|
|
666
|
+
),
|
|
667
|
+
writable: false,
|
|
668
|
+
};
|
|
810
669
|
}
|