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.
Files changed (38) hide show
  1. package/lib/build-graph-identity.js +86 -99
  2. package/lib/build-graph.js +536 -31
  3. package/lib/build-graph.types.ts +6 -2
  4. package/lib/check-directive-metadata.js +534 -0
  5. package/lib/check-directive-value.js +291 -0
  6. package/lib/check-graph.js +23 -5
  7. package/lib/cli-help-metadata.js +56 -16
  8. package/lib/command-output.js +16 -1
  9. package/lib/derived-summary.js +10 -8
  10. package/lib/directive-diagnostics.js +38 -0
  11. package/lib/directive-type-rules.js +133 -0
  12. package/lib/discover-fields.js +435 -0
  13. package/lib/discover-fields.types.ts +52 -0
  14. package/lib/document-node-identity.js +317 -0
  15. package/lib/format-node-header.js +9 -7
  16. package/lib/format-output-metadata.js +15 -23
  17. package/lib/layout-stored-queries.js +124 -85
  18. package/lib/load-patram-config.js +433 -96
  19. package/lib/load-patram-config.types.ts +98 -3
  20. package/lib/load-project-graph.js +4 -1
  21. package/lib/output-view.types.ts +14 -6
  22. package/lib/parse-cli-arguments.types.ts +1 -1
  23. package/lib/parse-where-clause.js +344 -107
  24. package/lib/parse-where-clause.types.ts +25 -8
  25. package/lib/patram-cli.js +68 -4
  26. package/lib/patram-config.js +31 -31
  27. package/lib/patram-config.types.ts +10 -4
  28. package/lib/query-graph.js +269 -40
  29. package/lib/query-inspection.js +440 -60
  30. package/lib/render-field-discovery.js +184 -0
  31. package/lib/render-json-output.js +21 -22
  32. package/lib/render-output-view.js +301 -34
  33. package/lib/render-plain-output.js +1 -1
  34. package/lib/render-rich-output.js +1 -1
  35. package/lib/render-rich-source.js +245 -14
  36. package/lib/resolve-patram-graph-config.js +15 -9
  37. package/lib/show-document.js +66 -9
  38. package/package.json +5 -5
@@ -1,15 +1,17 @@
1
1
  import type { PatramDiagnostic } from './load-patram-config.types.ts';
2
2
 
3
- export type ParsedFieldName = 'id' | 'kind' | 'path' | 'status' | 'title';
3
+ export type ParsedFieldName = string;
4
4
 
5
5
  export interface ParsedFieldTerm {
6
+ column: number;
6
7
  field_name: ParsedFieldName;
7
8
  kind: 'field';
8
- operator: '=' | '^=' | '~';
9
+ operator: '!=' | '<' | '<=' | '=' | '>' | '>=' | '^=' | '~';
9
10
  value: string;
10
11
  }
11
12
 
12
13
  export interface ParsedFieldSetTerm {
14
+ column: number;
13
15
  field_name: ParsedFieldName;
14
16
  kind: 'field_set';
15
17
  operator: 'in' | 'not in';
@@ -40,13 +42,28 @@ export type ParsedAggregateName = 'any' | 'count' | 'none';
40
42
 
41
43
  export interface ParsedAggregateTerm {
42
44
  aggregate_name: ParsedAggregateName;
43
- clauses: ParsedClause[];
44
45
  comparison?: ParsedAggregateComparison;
46
+ expression: ParsedExpression;
45
47
  kind: 'aggregate';
46
48
  traversal: ParsedTraversalTerm;
47
49
  value?: number;
48
50
  }
49
51
 
52
+ export interface ParsedTermExpression {
53
+ kind: 'term';
54
+ term: ParsedTerm;
55
+ }
56
+
57
+ export interface ParsedNotExpression {
58
+ expression: ParsedExpression;
59
+ kind: 'not';
60
+ }
61
+
62
+ export interface ParsedBooleanExpression {
63
+ expressions: ParsedExpression[];
64
+ kind: 'and' | 'or';
65
+ }
66
+
50
67
  export type ParsedTerm =
51
68
  | ParsedAggregateTerm
52
69
  | ParsedFieldSetTerm
@@ -54,14 +71,14 @@ export type ParsedTerm =
54
71
  | ParsedRelationTargetTerm
55
72
  | ParsedRelationTerm;
56
73
 
57
- export interface ParsedClause {
58
- is_negated: boolean;
59
- term: ParsedTerm;
60
- }
74
+ export type ParsedExpression =
75
+ | ParsedBooleanExpression
76
+ | ParsedNotExpression
77
+ | ParsedTermExpression;
61
78
 
62
79
  export type ParseWhereClauseResult =
63
80
  | {
64
- clauses: ParsedClause[];
81
+ expression: ParsedExpression;
65
82
  success: true;
66
83
  }
67
84
  | {
package/lib/patram-cli.js CHANGED
@@ -9,7 +9,9 @@ import { checkGraph } from './check-graph.js';
9
9
  import {
10
10
  shouldPageCommandOutput,
11
11
  writeCommandOutput,
12
+ writeRenderedCommandOutput,
12
13
  } from './command-output.js';
14
+ import { discoverFields } from './discover-fields.js';
13
15
  import { listRepoFiles } from './list-source-files.js';
14
16
  import { listQueries } from './list-queries.js';
15
17
  import { loadPatramConfig } from './load-patram-config.js';
@@ -36,6 +38,7 @@ import {
36
38
  createOutputView,
37
39
  createShowOutputView,
38
40
  } from './render-output-view.js';
41
+ import { renderFieldDiscovery } from './render-field-discovery.js';
39
42
  import { resolveWhereClause } from './resolve-where-clause.js';
40
43
  import { resolveOutputMode } from './resolve-output-mode.js';
41
44
  import { loadShowOutput } from './show-document.js';
@@ -43,8 +46,8 @@ import { loadShowOutput } from './show-document.js';
43
46
  /**
44
47
  * Patram command execution flow.
45
48
  *
46
- * Loads repo state and routes `check`, `query`, `queries`, and `show` through
47
- * the shared output pipeline.
49
+ * Loads repo state and routes `check`, `fields`, `query`, `queries`, and
50
+ * `show` through the shared output pipeline.
48
51
  *
49
52
  * Kind: cli
50
53
  * Status: active
@@ -94,6 +97,10 @@ export async function main(cli_arguments, io_context) {
94
97
  return runQueryCommand(parsed_command, io_context);
95
98
  }
96
99
 
100
+ if (parsed_command.command_name === 'fields') {
101
+ return runFieldsCommand(parsed_command, io_context);
102
+ }
103
+
97
104
  if (parsed_command.command_name === 'queries') {
98
105
  return runQueriesCommand(parsed_command, io_context);
99
106
  }
@@ -140,7 +147,12 @@ async function runCheckCommand(parsed_command, io_context) {
140
147
  return 1;
141
148
  }
142
149
 
143
- const diagnostics = checkGraph(project_graph_result.graph, repo_file_paths);
150
+ const diagnostics = checkGraph(
151
+ project_graph_result.graph,
152
+ repo_file_paths,
153
+ project_graph_result.config,
154
+ project_graph_result.claims,
155
+ );
144
156
  const selected_diagnostics = selectCheckTargetDiagnostics(
145
157
  diagnostics,
146
158
  resolved_target,
@@ -205,6 +217,7 @@ async function runQueryCommand(parsed_command, io_context) {
205
217
  const query_result = queryGraph(
206
218
  project_graph_result.graph,
207
219
  where_clause.value.where_clause,
220
+ project_graph_result.config,
208
221
  createQueryPaginationOptions(parsed_command, use_pager),
209
222
  );
210
223
 
@@ -227,12 +240,42 @@ async function runQueryCommand(parsed_command, io_context) {
227
240
  createOutputView('query', query_result.nodes, {
228
241
  derived_summary_evaluator,
229
242
  ...createQueryOutputOptions(parsed_command, query_result, use_pager),
243
+ repo_config: project_graph_result.config,
230
244
  }),
231
245
  );
232
246
 
233
247
  return 0;
234
248
  }
235
249
 
250
+ /**
251
+ * @param {ParsedCliCommandRequest} parsed_command
252
+ * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean } }} io_context
253
+ * @returns {Promise<number>}
254
+ */
255
+ async function runFieldsCommand(parsed_command, io_context) {
256
+ const output_mode = resolveOutputMode(parsed_command, {
257
+ is_tty: io_context.stdout.isTTY === true,
258
+ no_color: process.env.NO_COLOR !== undefined,
259
+ term: process.env.TERM,
260
+ });
261
+ const load_result = await loadPatramConfig(process.cwd());
262
+ const defined_field_names =
263
+ load_result.diagnostics.length === 0
264
+ ? collectDefinedDiscoveryNames(load_result.config)
265
+ : new Set();
266
+ const discovery_result = await discoverFields(process.cwd(), {
267
+ defined_field_names,
268
+ });
269
+
270
+ await writeRenderedCommandOutput(
271
+ io_context,
272
+ parsed_command,
273
+ renderFieldDiscovery(discovery_result, output_mode),
274
+ );
275
+
276
+ return 0;
277
+ }
278
+
236
279
  /**
237
280
  * @param {ParsedCliCommandRequest} parsed_command
238
281
  * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean } }} io_context
@@ -364,7 +407,9 @@ async function runShowCommand(parsed_command, io_context) {
364
407
  parsed_command,
365
408
  createShowOutputView(show_output.value, {
366
409
  derived_summary_evaluator,
410
+ document_node_ids: project_graph_result.graph.document_node_ids,
367
411
  graph_nodes: project_graph_result.graph.nodes,
412
+ repo_config: project_graph_result.config,
368
413
  }),
369
414
  );
370
415
 
@@ -396,7 +441,7 @@ function createQueryOutputOptions(parsed_command, query_result, use_pager) {
396
441
  const offset = parsed_command.query_offset ?? 0;
397
442
 
398
443
  if (query_result.total_count === 0) {
399
- hints.push('Try: patram query --where "kind=task"');
444
+ hints.push('Try: patram query --where "$class=task"');
400
445
  }
401
446
 
402
447
  if (
@@ -462,3 +507,22 @@ function createQueryExecutionOptions(parsed_command, use_pager) {
462
507
  function formatDiagnostic(diagnostic) {
463
508
  return `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} ${diagnostic.level} ${diagnostic.code} ${diagnostic.message}\n`;
464
509
  }
510
+
511
+ /**
512
+ * @param {import('./load-patram-config.types.ts').PatramRepoConfig | null} repo_config
513
+ * @returns {Set<string>}
514
+ */
515
+ function collectDefinedDiscoveryNames(repo_config) {
516
+ /** @type {Set<string>} */
517
+ const defined_field_names = new Set();
518
+
519
+ for (const field_name of Object.keys(repo_config?.fields ?? {})) {
520
+ defined_field_names.add(field_name);
521
+ }
522
+
523
+ for (const relation_name of Object.keys(repo_config?.relations ?? {})) {
524
+ defined_field_names.add(relation_name);
525
+ }
526
+
527
+ return defined_field_names;
528
+ }
@@ -5,13 +5,13 @@
5
5
 
6
6
  import { z } from 'zod';
7
7
 
8
- const KIND_NAME_SCHEMA = z.string().min(1);
8
+ const CLASS_NAME_SCHEMA = z.string().min(1);
9
9
  const RELATION_NAME_SCHEMA = z.string().min(1);
10
10
  const CLAIM_TYPE_SCHEMA = z.string().min(1);
11
11
  const KEY_SOURCE_SCHEMA = z.enum(['path', 'value']);
12
12
  const TARGET_SCHEMA = z.enum(['path', 'value']);
13
13
 
14
- const kind_definition_schema = z
14
+ const class_definition_schema = z
15
15
  .object({
16
16
  builtin: z.boolean().optional(),
17
17
  label: z.string().min(1).optional(),
@@ -21,16 +21,16 @@ const kind_definition_schema = z
21
21
  const relation_definition_schema = z
22
22
  .object({
23
23
  builtin: z.boolean().optional(),
24
- from: z.array(KIND_NAME_SCHEMA).min(1),
25
- to: z.array(KIND_NAME_SCHEMA).min(1),
24
+ from: z.array(CLASS_NAME_SCHEMA).min(1),
25
+ to: z.array(CLASS_NAME_SCHEMA).min(1),
26
26
  })
27
27
  .strict();
28
28
 
29
29
  const mapping_node_schema = z
30
30
  .object({
31
+ class: CLASS_NAME_SCHEMA,
31
32
  field: z.string().min(1),
32
33
  key: KEY_SOURCE_SCHEMA.optional(),
33
- kind: KIND_NAME_SCHEMA,
34
34
  })
35
35
  .strict();
36
36
 
@@ -38,7 +38,7 @@ const mapping_emit_schema = z
38
38
  .object({
39
39
  relation: RELATION_NAME_SCHEMA,
40
40
  target: TARGET_SCHEMA,
41
- target_kind: KIND_NAME_SCHEMA,
41
+ target_class: CLASS_NAME_SCHEMA,
42
42
  })
43
43
  .strict();
44
44
 
@@ -53,7 +53,7 @@ const mapping_definition_schema = z
53
53
  export const patramConfigSchema = z
54
54
  .object({
55
55
  $schema: z.url().optional(),
56
- kinds: z.record(KIND_NAME_SCHEMA, kind_definition_schema),
56
+ classes: z.record(CLASS_NAME_SCHEMA, class_definition_schema),
57
57
  mappings: z.record(CLAIM_TYPE_SCHEMA, mapping_definition_schema),
58
58
  relations: z.record(RELATION_NAME_SCHEMA, relation_definition_schema),
59
59
  })
@@ -90,8 +90,8 @@ function validateMappingDefinition(mapping_definition, refinement_context) {
90
90
  * @param {RefinementCtx} refinement_context
91
91
  */
92
92
  function validatePatramConfigReferences(config_json, refinement_context) {
93
- validateRelationKinds(config_json, refinement_context);
94
- validateMappingKinds(config_json, refinement_context);
93
+ validateRelationClasses(config_json, refinement_context);
94
+ validateMappingClasses(config_json, refinement_context);
95
95
  validateMappingRelations(config_json, refinement_context);
96
96
  }
97
97
 
@@ -99,19 +99,19 @@ function validatePatramConfigReferences(config_json, refinement_context) {
99
99
  * @param {PatramConfig} config_json
100
100
  * @param {RefinementCtx} refinement_context
101
101
  */
102
- function validateRelationKinds(config_json, refinement_context) {
102
+ function validateRelationClasses(config_json, refinement_context) {
103
103
  for (const [relation_name, relation_definition] of Object.entries(
104
104
  config_json.relations,
105
105
  )) {
106
- validateReferencedKinds(
106
+ validateReferencedClasses(
107
107
  relation_definition.from,
108
- config_json.kinds,
108
+ config_json.classes,
109
109
  ['relations', relation_name, 'from'],
110
110
  refinement_context,
111
111
  );
112
- validateReferencedKinds(
112
+ validateReferencedClasses(
113
113
  relation_definition.to,
114
- config_json.kinds,
114
+ config_json.classes,
115
115
  ['relations', relation_name, 'to'],
116
116
  refinement_context,
117
117
  );
@@ -122,24 +122,24 @@ function validateRelationKinds(config_json, refinement_context) {
122
122
  * @param {PatramConfig} config_json
123
123
  * @param {RefinementCtx} refinement_context
124
124
  */
125
- function validateMappingKinds(config_json, refinement_context) {
125
+ function validateMappingClasses(config_json, refinement_context) {
126
126
  for (const [mapping_name, mapping_definition] of Object.entries(
127
127
  config_json.mappings,
128
128
  )) {
129
129
  if (mapping_definition.emit) {
130
- validateReferencedKinds(
131
- [mapping_definition.emit.target_kind],
132
- config_json.kinds,
133
- ['mappings', mapping_name, 'emit', 'target_kind'],
130
+ validateReferencedClasses(
131
+ [mapping_definition.emit.target_class],
132
+ config_json.classes,
133
+ ['mappings', mapping_name, 'emit', 'target_class'],
134
134
  refinement_context,
135
135
  );
136
136
  }
137
137
 
138
138
  if (mapping_definition.node) {
139
- validateReferencedKinds(
140
- [mapping_definition.node.kind],
141
- config_json.kinds,
142
- ['mappings', mapping_name, 'node', 'kind'],
139
+ validateReferencedClasses(
140
+ [mapping_definition.node.class],
141
+ config_json.classes,
142
+ ['mappings', mapping_name, 'node', 'class'],
143
143
  refinement_context,
144
144
  );
145
145
  }
@@ -171,25 +171,25 @@ function validateMappingRelations(config_json, refinement_context) {
171
171
  }
172
172
 
173
173
  /**
174
- * @param {string[]} referenced_kinds
175
- * @param {Record<string, unknown>} known_kinds
174
+ * @param {string[]} referenced_classes
175
+ * @param {Record<string, unknown>} known_classes
176
176
  * @param {(string | number)[]} issue_path
177
177
  * @param {RefinementCtx} refinement_context
178
178
  */
179
- function validateReferencedKinds(
180
- referenced_kinds,
181
- known_kinds,
179
+ function validateReferencedClasses(
180
+ referenced_classes,
181
+ known_classes,
182
182
  issue_path,
183
183
  refinement_context,
184
184
  ) {
185
- for (const referenced_kind of referenced_kinds) {
186
- if (known_kinds[referenced_kind]) {
185
+ for (const referenced_class of referenced_classes) {
186
+ if (known_classes[referenced_class]) {
187
187
  continue;
188
188
  }
189
189
 
190
190
  refinement_context.addIssue({
191
191
  code: 'custom',
192
- message: `Unknown kind "${referenced_kind}".`,
192
+ message: `Unknown class "${referenced_class}".`,
193
193
  path: issue_path,
194
194
  });
195
195
  }
@@ -1,4 +1,4 @@
1
- export interface KindDefinition {
1
+ export interface ClassDefinition {
2
2
  builtin?: boolean;
3
3
  label?: string;
4
4
  }
@@ -10,15 +10,15 @@ export interface RelationDefinition {
10
10
  }
11
11
 
12
12
  export interface MappingNodeDefinition {
13
+ class: string;
13
14
  field: string;
14
15
  key?: 'path' | 'value';
15
- kind: string;
16
16
  }
17
17
 
18
18
  export interface MappingEmitDefinition {
19
19
  relation: string;
20
20
  target: 'path' | 'value';
21
- target_kind: string;
21
+ target_class: string;
22
22
  }
23
23
 
24
24
  export interface MappingDefinition {
@@ -28,7 +28,13 @@ export interface MappingDefinition {
28
28
 
29
29
  export interface PatramConfig {
30
30
  $schema?: string;
31
- kinds: Record<string, KindDefinition>;
31
+ classes: Record<string, ClassDefinition>;
32
+ class_schemas?: Record<string, ClassSchemaConfig>;
33
+ fields?: Record<string, MetadataFieldConfig>;
32
34
  mappings: Record<string, MappingDefinition>;
33
35
  relations: Record<string, RelationDefinition>;
34
36
  }
37
+ import type {
38
+ ClassSchemaConfig,
39
+ MetadataFieldConfig,
40
+ } from './load-patram-config.types.ts';