patram 0.2.0 → 0.4.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 +86 -99
- package/lib/build-graph.js +536 -31
- package/lib/build-graph.types.ts +6 -2
- package/lib/check-directive-metadata.js +534 -0
- package/lib/check-directive-value.js +291 -0
- package/lib/check-graph.js +23 -5
- package/lib/cli-help-metadata.js +56 -16
- package/lib/command-output.js +16 -1
- package/lib/derived-summary.js +10 -8
- package/lib/directive-diagnostics.js +38 -0
- package/lib/directive-type-rules.js +133 -0
- package/lib/discover-fields.js +435 -0
- package/lib/discover-fields.types.ts +52 -0
- package/lib/document-node-identity.js +317 -0
- package/lib/format-node-header.js +9 -7
- package/lib/format-output-metadata.js +15 -23
- package/lib/layout-stored-queries.js +124 -85
- package/lib/load-patram-config.js +433 -96
- package/lib/load-patram-config.types.ts +98 -3
- package/lib/load-project-graph.js +4 -1
- package/lib/output-view.types.ts +14 -6
- package/lib/parse-cli-arguments.types.ts +1 -1
- package/lib/parse-where-clause.js +344 -107
- package/lib/parse-where-clause.types.ts +25 -8
- package/lib/patram-cli.js +68 -4
- package/lib/patram-config.js +31 -31
- package/lib/patram-config.types.ts +10 -4
- package/lib/query-graph.js +269 -40
- package/lib/query-inspection.js +440 -60
- package/lib/render-field-discovery.js +184 -0
- package/lib/render-json-output.js +21 -22
- package/lib/render-output-view.js +301 -34
- package/lib/render-plain-output.js +1 -1
- package/lib/render-rich-output.js +1 -1
- package/lib/render-rich-source.js +245 -14
- package/lib/resolve-patram-graph-config.js +15 -9
- package/lib/show-document.js +66 -9
- package/package.json +5 -5
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
/**
|
|
3
|
-
* @import {
|
|
3
|
+
* @import {
|
|
4
|
+
* ClassSchemaConfig,
|
|
5
|
+
* LoadPatramConfigResult,
|
|
6
|
+
* MetadataFieldConfig,
|
|
7
|
+
* PatramDiagnostic,
|
|
8
|
+
* PatramRepoConfig,
|
|
9
|
+
* } from './load-patram-config.types.ts';
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import { readFile } from 'node:fs/promises';
|
|
@@ -11,6 +17,7 @@ import { z } from 'zod';
|
|
|
11
17
|
|
|
12
18
|
import { parsePatramConfig } from './patram-config.js';
|
|
13
19
|
import { parseWhereClause } from './parse-where-clause.js';
|
|
20
|
+
import { getQuerySemanticDiagnostics } from './query-inspection.js';
|
|
14
21
|
import { resolvePatramGraphConfig } from './resolve-patram-graph-config.js';
|
|
15
22
|
import { DEFAULT_INCLUDE_PATTERNS } from './source-file-defaults.js';
|
|
16
23
|
|
|
@@ -31,6 +38,7 @@ import { DEFAULT_INCLUDE_PATTERNS } from './source-file-defaults.js';
|
|
|
31
38
|
*/
|
|
32
39
|
|
|
33
40
|
const CONFIG_FILE_NAME = '.patram.json';
|
|
41
|
+
const RESERVED_STRUCTURAL_FIELD_NAMES = new Set(['$class', '$id', '$path']);
|
|
34
42
|
|
|
35
43
|
const stored_query_schema = z
|
|
36
44
|
.object({
|
|
@@ -80,31 +88,126 @@ const derived_summary_field_schema = z
|
|
|
80
88
|
|
|
81
89
|
const derived_summary_schema = z
|
|
82
90
|
.object({
|
|
91
|
+
classes: z
|
|
92
|
+
.array(z.string().min(1))
|
|
93
|
+
.min(1, 'Derived summary "classes" must contain at least one class.'),
|
|
83
94
|
fields: z
|
|
84
95
|
.array(derived_summary_field_schema)
|
|
85
96
|
.min(1, 'Derived summary "fields" must contain at least one field.'),
|
|
86
|
-
kinds: z
|
|
87
|
-
.array(z.string().min(1))
|
|
88
|
-
.min(1, 'Derived summary "kinds" must contain at least one kind.'),
|
|
89
97
|
})
|
|
90
98
|
.strict()
|
|
91
99
|
.superRefine(validateDerivedSummaryDefinition);
|
|
92
100
|
|
|
101
|
+
const field_display_schema = z
|
|
102
|
+
.object({
|
|
103
|
+
hidden: z.boolean().optional(),
|
|
104
|
+
order: z.number().optional(),
|
|
105
|
+
})
|
|
106
|
+
.strict();
|
|
107
|
+
|
|
108
|
+
const field_query_schema = z
|
|
109
|
+
.object({
|
|
110
|
+
contains: z.boolean().optional(),
|
|
111
|
+
prefix: z.boolean().optional(),
|
|
112
|
+
})
|
|
113
|
+
.strict();
|
|
114
|
+
|
|
115
|
+
const field_base_shape = {
|
|
116
|
+
display: field_display_schema.optional(),
|
|
117
|
+
multiple: z.boolean().optional(),
|
|
118
|
+
path_class: z.string().min(1).optional(),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const metadata_field_schema = z.discriminatedUnion('type', [
|
|
122
|
+
z
|
|
123
|
+
.object({
|
|
124
|
+
...field_base_shape,
|
|
125
|
+
query: field_query_schema.optional(),
|
|
126
|
+
type: z.literal('string'),
|
|
127
|
+
})
|
|
128
|
+
.strict(),
|
|
129
|
+
z
|
|
130
|
+
.object({
|
|
131
|
+
...field_base_shape,
|
|
132
|
+
type: z.literal('integer'),
|
|
133
|
+
})
|
|
134
|
+
.strict(),
|
|
135
|
+
z
|
|
136
|
+
.object({
|
|
137
|
+
...field_base_shape,
|
|
138
|
+
type: z.literal('enum'),
|
|
139
|
+
values: z
|
|
140
|
+
.array(z.string().min(1, 'Field enum values must not be empty.'))
|
|
141
|
+
.min(1, 'Field enum values must contain at least one value.'),
|
|
142
|
+
})
|
|
143
|
+
.strict(),
|
|
144
|
+
z
|
|
145
|
+
.object({
|
|
146
|
+
...field_base_shape,
|
|
147
|
+
type: z.literal('path'),
|
|
148
|
+
})
|
|
149
|
+
.strict(),
|
|
150
|
+
z
|
|
151
|
+
.object({
|
|
152
|
+
...field_base_shape,
|
|
153
|
+
type: z.literal('glob'),
|
|
154
|
+
})
|
|
155
|
+
.strict(),
|
|
156
|
+
z
|
|
157
|
+
.object({
|
|
158
|
+
...field_base_shape,
|
|
159
|
+
type: z.literal('date'),
|
|
160
|
+
})
|
|
161
|
+
.strict(),
|
|
162
|
+
z
|
|
163
|
+
.object({
|
|
164
|
+
...field_base_shape,
|
|
165
|
+
type: z.literal('date_time'),
|
|
166
|
+
})
|
|
167
|
+
.strict(),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const class_field_rule_schema = z
|
|
171
|
+
.object({
|
|
172
|
+
presence: z.enum(['required', 'optional', 'forbidden']),
|
|
173
|
+
})
|
|
174
|
+
.strict();
|
|
175
|
+
|
|
176
|
+
const class_schema_schema = z
|
|
177
|
+
.object({
|
|
178
|
+
document_path_class: z.string().min(1).optional(),
|
|
179
|
+
fields: z.record(z.string().min(1), class_field_rule_schema).default({}),
|
|
180
|
+
unknown_fields: z.enum(['ignore', 'error']).optional(),
|
|
181
|
+
})
|
|
182
|
+
.strict();
|
|
183
|
+
|
|
184
|
+
const path_class_schema = z
|
|
185
|
+
.object({
|
|
186
|
+
prefixes: z
|
|
187
|
+
.array(z.string().min(1, 'Path class prefixes must not be empty.'))
|
|
188
|
+
.min(1, 'Path classes must contain at least one prefix.'),
|
|
189
|
+
})
|
|
190
|
+
.strict();
|
|
191
|
+
|
|
93
192
|
const patram_repo_config_schema = z
|
|
94
193
|
.object({
|
|
194
|
+
class_schemas: z.record(z.string().min(1), class_schema_schema).optional(),
|
|
195
|
+
classes: z.unknown().optional(),
|
|
95
196
|
derived_summaries: z
|
|
96
197
|
.record(z.string().min(1), derived_summary_schema)
|
|
97
198
|
.optional(),
|
|
199
|
+
fields: z.record(z.string().min(1), metadata_field_schema).optional(),
|
|
98
200
|
include: z
|
|
99
201
|
.array(z.string().min(1, 'Include globs must not be empty.'))
|
|
100
202
|
.min(1, 'Include must contain at least one glob.')
|
|
101
203
|
.default(DEFAULT_INCLUDE_PATTERNS),
|
|
102
|
-
kinds: z.unknown().optional(),
|
|
103
204
|
mappings: z.unknown().optional(),
|
|
205
|
+
path_classes: z.record(z.string().min(1), path_class_schema).optional(),
|
|
104
206
|
queries: z.record(z.string().min(1), stored_query_schema).default({}),
|
|
105
207
|
relations: z.unknown().optional(),
|
|
106
208
|
})
|
|
107
|
-
.strict()
|
|
209
|
+
.strict()
|
|
210
|
+
.superRefine(validateFieldDefinitionKeys);
|
|
108
211
|
|
|
109
212
|
/**
|
|
110
213
|
* Load and validate the repo Patram config.
|
|
@@ -142,6 +245,18 @@ export async function loadPatramConfig(project_directory = process.cwd()) {
|
|
|
142
245
|
}
|
|
143
246
|
|
|
144
247
|
const normalized_config = normalizeRepoConfig(config_result.data);
|
|
248
|
+
const field_schema_diagnostics = validateFieldSchemaConfig(normalized_config);
|
|
249
|
+
|
|
250
|
+
if (field_schema_diagnostics.length > 0) {
|
|
251
|
+
return createLoadResult(null, field_schema_diagnostics);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const stored_query_diagnostics = validateStoredQueries(normalized_config);
|
|
255
|
+
|
|
256
|
+
if (stored_query_diagnostics.length > 0) {
|
|
257
|
+
return createLoadResult(null, stored_query_diagnostics);
|
|
258
|
+
}
|
|
259
|
+
|
|
145
260
|
const derived_summary_diagnostics =
|
|
146
261
|
validateDerivedSummaries(normalized_config);
|
|
147
262
|
|
|
@@ -249,6 +364,24 @@ function createValidationDiagnostic(issue) {
|
|
|
249
364
|
};
|
|
250
365
|
}
|
|
251
366
|
|
|
367
|
+
/**
|
|
368
|
+
* @param {{ fields?: Record<string, MetadataFieldConfig> }} repo_config
|
|
369
|
+
* @param {import('zod').RefinementCtx} refinement_context
|
|
370
|
+
*/
|
|
371
|
+
function validateFieldDefinitionKeys(repo_config, refinement_context) {
|
|
372
|
+
for (const field_name of Object.keys(repo_config.fields ?? {})) {
|
|
373
|
+
if (!field_name.startsWith('$')) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
refinement_context.addIssue({
|
|
378
|
+
code: 'custom',
|
|
379
|
+
message: 'Metadata field names must not start with "$".',
|
|
380
|
+
path: ['fields', field_name],
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
252
385
|
/**
|
|
253
386
|
* @param {{ count?: unknown, default?: unknown, select?: unknown }} field_definition
|
|
254
387
|
* @param {import('zod').RefinementCtx} refinement_context
|
|
@@ -332,12 +465,12 @@ function validateDerivedSummaryDefinition(
|
|
|
332
465
|
}
|
|
333
466
|
|
|
334
467
|
/**
|
|
335
|
-
* @param {{ include: string[], queries: Record<string, { where: string }>,
|
|
468
|
+
* @param {{ include: string[], queries: Record<string, { where: string }>, classes?: unknown, mappings?: unknown, relations?: unknown }} repo_config
|
|
336
469
|
* @returns {PatramDiagnostic[]}
|
|
337
470
|
*/
|
|
338
471
|
function validateGraphSchema(repo_config) {
|
|
339
472
|
if (
|
|
340
|
-
repo_config.
|
|
473
|
+
repo_config.classes === undefined &&
|
|
341
474
|
repo_config.mappings === undefined &&
|
|
342
475
|
repo_config.relations === undefined
|
|
343
476
|
) {
|
|
@@ -346,7 +479,7 @@ function validateGraphSchema(repo_config) {
|
|
|
346
479
|
|
|
347
480
|
try {
|
|
348
481
|
parsePatramConfig({
|
|
349
|
-
|
|
482
|
+
classes: repo_config.classes ?? {},
|
|
350
483
|
mappings: repo_config.mappings ?? {},
|
|
351
484
|
relations: repo_config.relations ?? {},
|
|
352
485
|
});
|
|
@@ -361,6 +494,51 @@ function validateGraphSchema(repo_config) {
|
|
|
361
494
|
return [];
|
|
362
495
|
}
|
|
363
496
|
|
|
497
|
+
/**
|
|
498
|
+
* @param {PatramRepoConfig} repo_config
|
|
499
|
+
* @returns {PatramDiagnostic[]}
|
|
500
|
+
*/
|
|
501
|
+
function validateFieldSchemaConfig(repo_config) {
|
|
502
|
+
const path_classes = repo_config.path_classes ?? {};
|
|
503
|
+
const classes = repo_config.classes ?? {};
|
|
504
|
+
const fields = repo_config.fields ?? {};
|
|
505
|
+
/** @type {PatramDiagnostic[]} */
|
|
506
|
+
const diagnostics = [];
|
|
507
|
+
|
|
508
|
+
collectFieldConfigDiagnostics(diagnostics, path_classes, fields);
|
|
509
|
+
collectClassSchemaConfigDiagnostics(
|
|
510
|
+
diagnostics,
|
|
511
|
+
path_classes,
|
|
512
|
+
classes,
|
|
513
|
+
fields,
|
|
514
|
+
repo_config.class_schemas,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return diagnostics;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @param {PatramRepoConfig} repo_config
|
|
522
|
+
* @returns {PatramDiagnostic[]}
|
|
523
|
+
*/
|
|
524
|
+
function validateStoredQueries(repo_config) {
|
|
525
|
+
/** @type {PatramDiagnostic[]} */
|
|
526
|
+
const diagnostics = [];
|
|
527
|
+
|
|
528
|
+
for (const [query_name, stored_query] of Object.entries(
|
|
529
|
+
repo_config.queries,
|
|
530
|
+
)) {
|
|
531
|
+
collectWhereClauseDiagnostics(
|
|
532
|
+
diagnostics,
|
|
533
|
+
repo_config,
|
|
534
|
+
stored_query.where,
|
|
535
|
+
`queries.${query_name}.where`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return diagnostics;
|
|
540
|
+
}
|
|
541
|
+
|
|
364
542
|
/**
|
|
365
543
|
* @param {PatramRepoConfig} repo_config
|
|
366
544
|
* @returns {PatramDiagnostic[]}
|
|
@@ -374,20 +552,21 @@ function validateDerivedSummaries(repo_config) {
|
|
|
374
552
|
const known_relation_names = new Set(Object.keys(graph_config.relations));
|
|
375
553
|
/** @type {PatramDiagnostic[]} */
|
|
376
554
|
const diagnostics = [];
|
|
377
|
-
const
|
|
555
|
+
const class_coverage = new Map();
|
|
378
556
|
|
|
379
557
|
for (const [summary_name, summary_definition] of Object.entries(
|
|
380
558
|
repo_config.derived_summaries,
|
|
381
559
|
)) {
|
|
382
|
-
|
|
560
|
+
collectDuplicateClassDiagnostics(
|
|
383
561
|
diagnostics,
|
|
384
|
-
|
|
385
|
-
summary_definition.
|
|
562
|
+
class_coverage,
|
|
563
|
+
summary_definition.classes,
|
|
386
564
|
summary_name,
|
|
387
565
|
);
|
|
388
566
|
collectDerivedSummaryFieldDiagnostics(
|
|
389
567
|
diagnostics,
|
|
390
568
|
known_relation_names,
|
|
569
|
+
repo_config,
|
|
391
570
|
summary_name,
|
|
392
571
|
summary_definition.fields,
|
|
393
572
|
);
|
|
@@ -407,7 +586,7 @@ function createDefaultRepoConfig() {
|
|
|
407
586
|
}
|
|
408
587
|
|
|
409
588
|
/**
|
|
410
|
-
* @param {{ derived_summaries?: unknown, include: string[], queries: Record<string, { where: string }>,
|
|
589
|
+
* @param {{ class_schemas?: unknown, classes?: unknown, derived_summaries?: unknown, fields?: unknown, include: string[], mappings?: unknown, path_classes?: unknown, queries: Record<string, { where: string }>, relations?: unknown }} repo_config
|
|
411
590
|
* @returns {PatramRepoConfig}
|
|
412
591
|
*/
|
|
413
592
|
function normalizeRepoConfig(repo_config) {
|
|
@@ -417,61 +596,69 @@ function normalizeRepoConfig(repo_config) {
|
|
|
417
596
|
queries: { ...repo_config.queries },
|
|
418
597
|
};
|
|
419
598
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
normalized_config
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
599
|
+
assignOptionalRepoConfigField(
|
|
600
|
+
normalized_config,
|
|
601
|
+
'class_schemas',
|
|
602
|
+
repo_config.class_schemas,
|
|
603
|
+
);
|
|
604
|
+
assignOptionalRepoConfigField(
|
|
605
|
+
normalized_config,
|
|
606
|
+
'classes',
|
|
607
|
+
repo_config.classes,
|
|
608
|
+
);
|
|
609
|
+
assignOptionalRepoConfigField(
|
|
610
|
+
normalized_config,
|
|
611
|
+
'derived_summaries',
|
|
612
|
+
repo_config.derived_summaries,
|
|
613
|
+
);
|
|
614
|
+
assignOptionalRepoConfigField(
|
|
615
|
+
normalized_config,
|
|
616
|
+
'fields',
|
|
617
|
+
repo_config.fields,
|
|
618
|
+
);
|
|
619
|
+
assignOptionalRepoConfigField(
|
|
620
|
+
normalized_config,
|
|
621
|
+
'mappings',
|
|
622
|
+
repo_config.mappings,
|
|
623
|
+
);
|
|
624
|
+
assignOptionalRepoConfigField(
|
|
625
|
+
normalized_config,
|
|
626
|
+
'path_classes',
|
|
627
|
+
repo_config.path_classes,
|
|
628
|
+
);
|
|
629
|
+
assignOptionalRepoConfigField(
|
|
630
|
+
normalized_config,
|
|
631
|
+
'relations',
|
|
632
|
+
repo_config.relations,
|
|
633
|
+
);
|
|
447
634
|
|
|
448
635
|
return normalized_config;
|
|
449
636
|
}
|
|
450
637
|
|
|
451
638
|
/**
|
|
452
639
|
* @param {PatramDiagnostic[]} diagnostics
|
|
453
|
-
* @param {Map<string, string>}
|
|
454
|
-
* @param {string[]}
|
|
640
|
+
* @param {Map<string, string>} class_coverage
|
|
641
|
+
* @param {string[]} class_names
|
|
455
642
|
* @param {string} summary_name
|
|
456
643
|
*/
|
|
457
|
-
function
|
|
644
|
+
function collectDuplicateClassDiagnostics(
|
|
458
645
|
diagnostics,
|
|
459
|
-
|
|
460
|
-
|
|
646
|
+
class_coverage,
|
|
647
|
+
class_names,
|
|
461
648
|
summary_name,
|
|
462
649
|
) {
|
|
463
|
-
for (const
|
|
464
|
-
const existing_summary_name =
|
|
650
|
+
for (const class_name of class_names) {
|
|
651
|
+
const existing_summary_name = class_coverage.get(class_name);
|
|
465
652
|
|
|
466
653
|
if (!existing_summary_name) {
|
|
467
|
-
|
|
654
|
+
class_coverage.set(class_name, summary_name);
|
|
468
655
|
continue;
|
|
469
656
|
}
|
|
470
657
|
|
|
471
658
|
diagnostics.push(
|
|
472
659
|
createConfigDiagnostic(
|
|
473
|
-
`derived_summaries.${summary_name}.
|
|
474
|
-
`
|
|
660
|
+
`derived_summaries.${summary_name}.classes`,
|
|
661
|
+
`Class "${class_name}" is already covered by derived summary "${existing_summary_name}".`,
|
|
475
662
|
),
|
|
476
663
|
);
|
|
477
664
|
}
|
|
@@ -480,12 +667,14 @@ function collectDuplicateKindDiagnostics(
|
|
|
480
667
|
/**
|
|
481
668
|
* @param {PatramDiagnostic[]} diagnostics
|
|
482
669
|
* @param {Set<string>} known_relation_names
|
|
670
|
+
* @param {PatramRepoConfig} repo_config
|
|
483
671
|
* @param {string} summary_name
|
|
484
672
|
* @param {import('./load-patram-config.types.ts').DerivedSummaryFieldConfig[]} field_definitions
|
|
485
673
|
*/
|
|
486
674
|
function collectDerivedSummaryFieldDiagnostics(
|
|
487
675
|
diagnostics,
|
|
488
676
|
known_relation_names,
|
|
677
|
+
repo_config,
|
|
489
678
|
summary_name,
|
|
490
679
|
field_definitions,
|
|
491
680
|
) {
|
|
@@ -499,8 +688,8 @@ function collectDerivedSummaryFieldDiagnostics(
|
|
|
499
688
|
);
|
|
500
689
|
collectWhereClauseDiagnostics(
|
|
501
690
|
diagnostics,
|
|
691
|
+
repo_config,
|
|
502
692
|
field_definition.count.where,
|
|
503
|
-
known_relation_names,
|
|
504
693
|
`derived_summaries.${summary_name}.fields.${field_index}.count.where`,
|
|
505
694
|
);
|
|
506
695
|
continue;
|
|
@@ -509,8 +698,8 @@ function collectDerivedSummaryFieldDiagnostics(
|
|
|
509
698
|
for (const [case_index, select_case] of field_definition.select.entries()) {
|
|
510
699
|
collectWhereClauseDiagnostics(
|
|
511
700
|
diagnostics,
|
|
701
|
+
repo_config,
|
|
512
702
|
select_case.when,
|
|
513
|
-
known_relation_names,
|
|
514
703
|
`derived_summaries.${summary_name}.fields.${field_index}.select.${case_index}.when`,
|
|
515
704
|
);
|
|
516
705
|
}
|
|
@@ -559,84 +748,232 @@ function collectTraversalDiagnostic(
|
|
|
559
748
|
|
|
560
749
|
/**
|
|
561
750
|
* @param {PatramDiagnostic[]} diagnostics
|
|
562
|
-
* @param {string}
|
|
563
|
-
* @param {
|
|
564
|
-
* @param {string} diagnostic_path
|
|
751
|
+
* @param {Record<string, { prefixes: string[] }>} path_classes
|
|
752
|
+
* @param {Record<string, MetadataFieldConfig>} fields
|
|
565
753
|
*/
|
|
566
|
-
function
|
|
754
|
+
function collectFieldConfigDiagnostics(diagnostics, path_classes, fields) {
|
|
755
|
+
for (const [field_name, field_definition] of Object.entries(fields)) {
|
|
756
|
+
if (collectReservedFieldDiagnostic(diagnostics, field_name)) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
collectDisplayOrderDiagnostic(diagnostics, field_name, field_definition);
|
|
761
|
+
collectFieldPathClassDiagnostic(
|
|
762
|
+
diagnostics,
|
|
763
|
+
path_classes,
|
|
764
|
+
field_name,
|
|
765
|
+
field_definition,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
772
|
+
* @param {string} field_name
|
|
773
|
+
* @returns {boolean}
|
|
774
|
+
*/
|
|
775
|
+
function collectReservedFieldDiagnostic(diagnostics, field_name) {
|
|
776
|
+
if (
|
|
777
|
+
!field_name.startsWith('$') ||
|
|
778
|
+
!RESERVED_STRUCTURAL_FIELD_NAMES.has(field_name)
|
|
779
|
+
) {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
diagnostics.push(
|
|
784
|
+
createConfigDiagnostic(
|
|
785
|
+
`fields.${field_name}`,
|
|
786
|
+
'Metadata field names must not start with "$".',
|
|
787
|
+
),
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
795
|
+
* @param {string} field_name
|
|
796
|
+
* @param {MetadataFieldConfig} field_definition
|
|
797
|
+
*/
|
|
798
|
+
function collectDisplayOrderDiagnostic(
|
|
567
799
|
diagnostics,
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
diagnostic_path,
|
|
800
|
+
field_name,
|
|
801
|
+
field_definition,
|
|
571
802
|
) {
|
|
572
|
-
|
|
803
|
+
if (
|
|
804
|
+
field_definition.display?.order === undefined ||
|
|
805
|
+
(Number.isInteger(field_definition.display.order) &&
|
|
806
|
+
field_definition.display.order >= 0)
|
|
807
|
+
) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
573
810
|
|
|
574
|
-
|
|
811
|
+
diagnostics.push(
|
|
812
|
+
createConfigDiagnostic(
|
|
813
|
+
`fields.${field_name}.display.order`,
|
|
814
|
+
'Display order must be a non-negative integer.',
|
|
815
|
+
),
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
821
|
+
* @param {Record<string, { prefixes: string[] }>} path_classes
|
|
822
|
+
* @param {string} field_name
|
|
823
|
+
* @param {MetadataFieldConfig} field_definition
|
|
824
|
+
*/
|
|
825
|
+
function collectFieldPathClassDiagnostic(
|
|
826
|
+
diagnostics,
|
|
827
|
+
path_classes,
|
|
828
|
+
field_name,
|
|
829
|
+
field_definition,
|
|
830
|
+
) {
|
|
831
|
+
if (
|
|
832
|
+
!('path_class' in field_definition) ||
|
|
833
|
+
field_definition.path_class === undefined
|
|
834
|
+
) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (field_definition.type !== 'path') {
|
|
575
839
|
diagnostics.push(
|
|
576
|
-
createConfigDiagnostic(
|
|
840
|
+
createConfigDiagnostic(
|
|
841
|
+
`fields.${field_name}.path_class`,
|
|
842
|
+
'Path classes are only valid for path fields.',
|
|
843
|
+
),
|
|
577
844
|
);
|
|
578
845
|
|
|
579
846
|
return;
|
|
580
847
|
}
|
|
581
848
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
diagnostics,
|
|
585
|
-
clause.term,
|
|
586
|
-
known_relation_names,
|
|
587
|
-
diagnostic_path,
|
|
588
|
-
);
|
|
849
|
+
if (path_classes[field_definition.path_class]) {
|
|
850
|
+
return;
|
|
589
851
|
}
|
|
852
|
+
|
|
853
|
+
diagnostics.push(
|
|
854
|
+
createConfigDiagnostic(
|
|
855
|
+
`fields.${field_name}.path_class`,
|
|
856
|
+
`Unknown path class "${field_definition.path_class}".`,
|
|
857
|
+
),
|
|
858
|
+
);
|
|
590
859
|
}
|
|
591
860
|
|
|
592
861
|
/**
|
|
593
862
|
* @param {PatramDiagnostic[]} diagnostics
|
|
594
|
-
* @param {
|
|
595
|
-
* @param {
|
|
596
|
-
* @param {string}
|
|
863
|
+
* @param {Record<string, { prefixes: string[] }>} path_classes
|
|
864
|
+
* @param {Record<string, unknown>} classes
|
|
865
|
+
* @param {Record<string, MetadataFieldConfig>} fields
|
|
866
|
+
* @param {PatramRepoConfig['class_schemas']} class_schemas
|
|
597
867
|
*/
|
|
598
|
-
function
|
|
868
|
+
function collectClassSchemaConfigDiagnostics(
|
|
599
869
|
diagnostics,
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
870
|
+
path_classes,
|
|
871
|
+
classes,
|
|
872
|
+
fields,
|
|
873
|
+
class_schemas,
|
|
603
874
|
) {
|
|
604
|
-
if (
|
|
605
|
-
|
|
875
|
+
if (!class_schemas) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
for (const class_name of Object.keys(class_schemas)) {
|
|
880
|
+
if (classes[class_name]) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
diagnostics.push(
|
|
885
|
+
createConfigDiagnostic(
|
|
886
|
+
`class_schemas.${class_name}`,
|
|
887
|
+
`Unknown class "${class_name}".`,
|
|
888
|
+
),
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
for (const [class_name, schema_definition] of Object.entries(class_schemas)) {
|
|
893
|
+
for (const field_name of Object.keys(schema_definition.fields)) {
|
|
894
|
+
if (fields[field_name]) {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
|
|
606
898
|
diagnostics.push(
|
|
607
899
|
createConfigDiagnostic(
|
|
608
|
-
|
|
609
|
-
`Unknown
|
|
900
|
+
`class_schemas.${class_name}.fields.${field_name}`,
|
|
901
|
+
`Unknown field "${field_name}".`,
|
|
610
902
|
),
|
|
611
903
|
);
|
|
612
904
|
}
|
|
905
|
+
}
|
|
613
906
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
);
|
|
907
|
+
for (const [class_name, schema_definition] of Object.entries(class_schemas)) {
|
|
908
|
+
if (
|
|
909
|
+
schema_definition.document_path_class === undefined ||
|
|
910
|
+
path_classes[schema_definition.document_path_class]
|
|
911
|
+
) {
|
|
912
|
+
continue;
|
|
621
913
|
}
|
|
622
914
|
|
|
623
|
-
|
|
915
|
+
diagnostics.push(
|
|
916
|
+
createConfigDiagnostic(
|
|
917
|
+
`class_schemas.${class_name}.document_path_class`,
|
|
918
|
+
`Unknown path class "${schema_definition.document_path_class}".`,
|
|
919
|
+
),
|
|
920
|
+
);
|
|
624
921
|
}
|
|
922
|
+
}
|
|
625
923
|
|
|
626
|
-
|
|
924
|
+
/**
|
|
925
|
+
* @template {Exclude<keyof PatramRepoConfig, 'include' | 'queries'>} TKey
|
|
926
|
+
* @param {PatramRepoConfig} normalized_config
|
|
927
|
+
* @param {TKey} field_name
|
|
928
|
+
* @param {unknown} field_value
|
|
929
|
+
*/
|
|
930
|
+
function assignOptionalRepoConfigField(
|
|
931
|
+
normalized_config,
|
|
932
|
+
field_name,
|
|
933
|
+
field_value,
|
|
934
|
+
) {
|
|
935
|
+
if (field_value === undefined || field_value === null) {
|
|
627
936
|
return;
|
|
628
937
|
}
|
|
629
938
|
|
|
630
|
-
|
|
939
|
+
normalized_config[field_name] = /** @type {PatramRepoConfig[TKey]} */ (
|
|
940
|
+
field_value
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
946
|
+
* @param {PatramRepoConfig} repo_config
|
|
947
|
+
* @param {string} where_clause
|
|
948
|
+
* @param {string} diagnostic_path
|
|
949
|
+
*/
|
|
950
|
+
function collectWhereClauseDiagnostics(
|
|
951
|
+
diagnostics,
|
|
952
|
+
repo_config,
|
|
953
|
+
where_clause,
|
|
954
|
+
diagnostic_path,
|
|
955
|
+
) {
|
|
956
|
+
const parse_result = parseWhereClause(where_clause);
|
|
957
|
+
|
|
958
|
+
if (!parse_result.success) {
|
|
959
|
+
diagnostics.push(
|
|
960
|
+
createConfigDiagnostic(diagnostic_path, parse_result.diagnostic.message),
|
|
961
|
+
);
|
|
962
|
+
|
|
631
963
|
return;
|
|
632
964
|
}
|
|
633
965
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
),
|
|
966
|
+
const semantic_diagnostics = getQuerySemanticDiagnostics(
|
|
967
|
+
repo_config,
|
|
968
|
+
{ kind: 'ad_hoc' },
|
|
969
|
+
parse_result.expression,
|
|
639
970
|
);
|
|
971
|
+
|
|
972
|
+
for (const semantic_diagnostic of semantic_diagnostics) {
|
|
973
|
+
diagnostics.push(
|
|
974
|
+
createConfigDiagnostic(diagnostic_path, semantic_diagnostic.message),
|
|
975
|
+
);
|
|
976
|
+
}
|
|
640
977
|
}
|
|
641
978
|
|
|
642
979
|
/**
|