patram 0.9.0 → 0.11.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/cli/arguments.types.d.ts +64 -0
- package/lib/cli/commands/check.js +27 -15
- package/lib/cli/commands/queries.js +189 -1
- package/lib/cli/commands/query.js +6 -3
- package/lib/cli/help-metadata.js +45 -110
- package/lib/cli/parse-arguments-helpers.js +295 -39
- package/lib/cli/render-help.js +87 -0
- package/lib/config/load-patram-config.d.ts +11 -0
- package/lib/config/load-patram-config.js +9 -88
- package/lib/config/manage-stored-queries-helpers.d.ts +69 -0
- package/lib/config/manage-stored-queries-helpers.js +262 -0
- package/lib/config/manage-stored-queries-jsonc.d.ts +31 -0
- package/lib/config/manage-stored-queries-jsonc.js +95 -0
- package/lib/config/manage-stored-queries.d.ts +77 -0
- package/lib/config/manage-stored-queries.js +294 -0
- package/lib/config/schema.d.ts +2 -0
- package/lib/config/schema.js +4 -0
- package/lib/config/validate-patram-config-value.d.ts +13 -0
- package/lib/config/validate-patram-config-value.js +119 -0
- package/lib/find-close-match.d.ts +8 -0
- package/lib/find-close-match.js +98 -0
- package/lib/graph/query/resolve.d.ts +9 -5
- package/lib/graph/query/resolve.js +41 -4
- package/lib/output/layout-stored-queries.js +18 -2
- package/lib/output/list-queries.js +2 -1
- package/lib/output/renderers/json.js +9 -5
- package/lib/output/renderers/plain.js +15 -26
- package/lib/output/renderers/rich.js +22 -26
- package/lib/output/resolve-check-target.js +120 -11
- package/lib/output/view-model/index.js +5 -18
- package/lib/patram.d.ts +8 -0
- package/lib/scan/discover-fields.js +136 -10
- package/package.json +2 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { CliParseError } from '../cli/arguments.types.ts';
|
|
3
|
+
* @import {
|
|
4
|
+
* PatramDiagnostic,
|
|
5
|
+
* PatramRepoConfig,
|
|
6
|
+
* } from './load-patram-config.types.ts';
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { loadPatramConfig } from './load-patram-config.js';
|
|
12
|
+
import {
|
|
13
|
+
createStoredQueryDefinition,
|
|
14
|
+
createUpdatedStoredQueryDefinition,
|
|
15
|
+
ensureRawQueries,
|
|
16
|
+
loadRawConfig,
|
|
17
|
+
persistStoredQueryMutation,
|
|
18
|
+
rawQueryValueToRecord,
|
|
19
|
+
} from './manage-stored-queries-helpers.js';
|
|
20
|
+
import { CONFIG_FILE_NAME } from './schema.js';
|
|
21
|
+
import { createUnknownStoredQueryError } from '../graph/query/resolve.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {{
|
|
25
|
+
* action: 'add',
|
|
26
|
+
* description?: string,
|
|
27
|
+
* name: string,
|
|
28
|
+
* where: string,
|
|
29
|
+
* } | {
|
|
30
|
+
* action: 'remove',
|
|
31
|
+
* name: string,
|
|
32
|
+
* } | {
|
|
33
|
+
* action: 'update',
|
|
34
|
+
* description?: string,
|
|
35
|
+
* name: string,
|
|
36
|
+
* next_name?: string,
|
|
37
|
+
* where?: string,
|
|
38
|
+
* }} StoredQueryMutation
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {{
|
|
43
|
+
* action: 'added',
|
|
44
|
+
* name: string,
|
|
45
|
+
* } | {
|
|
46
|
+
* action: 'removed',
|
|
47
|
+
* name: string,
|
|
48
|
+
* } | {
|
|
49
|
+
* action: 'updated',
|
|
50
|
+
* name: string,
|
|
51
|
+
* previous_name?: string,
|
|
52
|
+
* }} StoredQueryMutationResult
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} project_directory
|
|
57
|
+
* @param {StoredQueryMutation} stored_query_mutation
|
|
58
|
+
* @returns {Promise<
|
|
59
|
+
* | { success: true, value: StoredQueryMutationResult }
|
|
60
|
+
* | { diagnostics: PatramDiagnostic[], success: false }
|
|
61
|
+
* | { error: CliParseError, success: false }
|
|
62
|
+
* >}
|
|
63
|
+
*/
|
|
64
|
+
export async function manageStoredQueries(
|
|
65
|
+
project_directory,
|
|
66
|
+
stored_query_mutation,
|
|
67
|
+
) {
|
|
68
|
+
const load_result = await loadPatramConfig(project_directory);
|
|
69
|
+
|
|
70
|
+
if (load_result.diagnostics.length > 0) {
|
|
71
|
+
return {
|
|
72
|
+
diagnostics: load_result.diagnostics,
|
|
73
|
+
success: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const repo_config = load_result.config;
|
|
78
|
+
|
|
79
|
+
if (!repo_config) {
|
|
80
|
+
throw new Error('Expected a valid Patram repo config.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const config_file_path = resolve(project_directory, CONFIG_FILE_NAME);
|
|
84
|
+
const raw_config_result = await loadRawConfig(config_file_path);
|
|
85
|
+
|
|
86
|
+
if (!raw_config_result.success) {
|
|
87
|
+
return {
|
|
88
|
+
diagnostics: [raw_config_result.diagnostic],
|
|
89
|
+
success: false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (stored_query_mutation.action === 'add') {
|
|
94
|
+
return applyAddStoredQuery(
|
|
95
|
+
config_file_path,
|
|
96
|
+
raw_config_result.value,
|
|
97
|
+
repo_config,
|
|
98
|
+
stored_query_mutation,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (stored_query_mutation.action === 'remove') {
|
|
103
|
+
return applyRemoveStoredQuery(
|
|
104
|
+
config_file_path,
|
|
105
|
+
raw_config_result.value,
|
|
106
|
+
repo_config,
|
|
107
|
+
stored_query_mutation,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return applyUpdateStoredQuery(
|
|
112
|
+
config_file_path,
|
|
113
|
+
raw_config_result.value,
|
|
114
|
+
repo_config,
|
|
115
|
+
stored_query_mutation,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} config_file_path
|
|
121
|
+
* @param {Record<string, unknown>} raw_config
|
|
122
|
+
* @param {PatramRepoConfig} repo_config
|
|
123
|
+
* @param {{ description?: string, name: string, where: string }} stored_query_mutation
|
|
124
|
+
* @returns {Promise<
|
|
125
|
+
* | { success: true, value: StoredQueryMutationResult }
|
|
126
|
+
* | { diagnostics: PatramDiagnostic[], success: false }
|
|
127
|
+
* | { error: CliParseError, success: false }
|
|
128
|
+
* >}
|
|
129
|
+
*/
|
|
130
|
+
async function applyAddStoredQuery(
|
|
131
|
+
config_file_path,
|
|
132
|
+
raw_config,
|
|
133
|
+
repo_config,
|
|
134
|
+
stored_query_mutation,
|
|
135
|
+
) {
|
|
136
|
+
if (repo_config.queries[stored_query_mutation.name]) {
|
|
137
|
+
return {
|
|
138
|
+
error: {
|
|
139
|
+
code: 'message',
|
|
140
|
+
message: `Stored query already exists: ${stored_query_mutation.name}.`,
|
|
141
|
+
},
|
|
142
|
+
success: false,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const raw_queries = ensureRawQueries(raw_config);
|
|
147
|
+
raw_queries[stored_query_mutation.name] = createStoredQueryDefinition(
|
|
148
|
+
stored_query_mutation.where,
|
|
149
|
+
stored_query_mutation.description,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return persistStoredQueryMutation(config_file_path, raw_config, {
|
|
153
|
+
action: 'added',
|
|
154
|
+
name: stored_query_mutation.name,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {string} config_file_path
|
|
160
|
+
* @param {Record<string, unknown>} raw_config
|
|
161
|
+
* @param {PatramRepoConfig} repo_config
|
|
162
|
+
* @param {{ name: string }} stored_query_mutation
|
|
163
|
+
* @returns {Promise<
|
|
164
|
+
* | { success: true, value: StoredQueryMutationResult }
|
|
165
|
+
* | { diagnostics: PatramDiagnostic[], success: false }
|
|
166
|
+
* | { error: CliParseError, success: false }
|
|
167
|
+
* >}
|
|
168
|
+
*/
|
|
169
|
+
async function applyRemoveStoredQuery(
|
|
170
|
+
config_file_path,
|
|
171
|
+
raw_config,
|
|
172
|
+
repo_config,
|
|
173
|
+
stored_query_mutation,
|
|
174
|
+
) {
|
|
175
|
+
if (!repo_config.queries[stored_query_mutation.name]) {
|
|
176
|
+
return {
|
|
177
|
+
error: createQueryMutationUnknownStoredQueryError(
|
|
178
|
+
stored_query_mutation.name,
|
|
179
|
+
Object.keys(repo_config.queries),
|
|
180
|
+
'remove',
|
|
181
|
+
),
|
|
182
|
+
success: false,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const raw_queries = ensureRawQueries(raw_config);
|
|
187
|
+
delete raw_queries[stored_query_mutation.name];
|
|
188
|
+
|
|
189
|
+
return persistStoredQueryMutation(config_file_path, raw_config, {
|
|
190
|
+
action: 'removed',
|
|
191
|
+
name: stored_query_mutation.name,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {string} config_file_path
|
|
197
|
+
* @param {Record<string, unknown>} raw_config
|
|
198
|
+
* @param {PatramRepoConfig} repo_config
|
|
199
|
+
* @param {{ description?: string, name: string, next_name?: string, where?: string }} stored_query_mutation
|
|
200
|
+
* @returns {Promise<
|
|
201
|
+
* | { success: true, value: StoredQueryMutationResult }
|
|
202
|
+
* | { diagnostics: PatramDiagnostic[], success: false }
|
|
203
|
+
* | { error: CliParseError, success: false }
|
|
204
|
+
* >}
|
|
205
|
+
*/
|
|
206
|
+
async function applyUpdateStoredQuery(
|
|
207
|
+
config_file_path,
|
|
208
|
+
raw_config,
|
|
209
|
+
repo_config,
|
|
210
|
+
stored_query_mutation,
|
|
211
|
+
) {
|
|
212
|
+
const existing_query = repo_config.queries[stored_query_mutation.name];
|
|
213
|
+
|
|
214
|
+
if (!existing_query) {
|
|
215
|
+
return {
|
|
216
|
+
error: createQueryMutationUnknownStoredQueryError(
|
|
217
|
+
stored_query_mutation.name,
|
|
218
|
+
Object.keys(repo_config.queries),
|
|
219
|
+
'update',
|
|
220
|
+
),
|
|
221
|
+
success: false,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const next_name =
|
|
226
|
+
stored_query_mutation.next_name ?? stored_query_mutation.name;
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
next_name !== stored_query_mutation.name &&
|
|
230
|
+
repo_config.queries[next_name]
|
|
231
|
+
) {
|
|
232
|
+
return {
|
|
233
|
+
error: {
|
|
234
|
+
code: 'message',
|
|
235
|
+
message: `Stored query already exists: ${next_name}.`,
|
|
236
|
+
},
|
|
237
|
+
success: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const raw_queries = ensureRawQueries(raw_config);
|
|
242
|
+
const raw_query_value = rawQueryValueToRecord(
|
|
243
|
+
raw_queries[stored_query_mutation.name],
|
|
244
|
+
);
|
|
245
|
+
const next_query = createUpdatedStoredQueryDefinition(
|
|
246
|
+
raw_query_value,
|
|
247
|
+
existing_query,
|
|
248
|
+
stored_query_mutation,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (next_name !== stored_query_mutation.name) {
|
|
252
|
+
delete raw_queries[stored_query_mutation.name];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
raw_queries[next_name] = next_query;
|
|
256
|
+
|
|
257
|
+
return persistStoredQueryMutation(config_file_path, raw_config, {
|
|
258
|
+
action: 'updated',
|
|
259
|
+
name: next_name,
|
|
260
|
+
previous_name:
|
|
261
|
+
next_name === stored_query_mutation.name
|
|
262
|
+
? undefined
|
|
263
|
+
: stored_query_mutation.name,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {string} stored_query_name
|
|
269
|
+
* @param {string[]} stored_query_names
|
|
270
|
+
* @param {'remove' | 'update'} subcommand_name
|
|
271
|
+
* @returns {CliParseError}
|
|
272
|
+
*/
|
|
273
|
+
function createQueryMutationUnknownStoredQueryError(
|
|
274
|
+
stored_query_name,
|
|
275
|
+
stored_query_names,
|
|
276
|
+
subcommand_name,
|
|
277
|
+
) {
|
|
278
|
+
const parse_error = createUnknownStoredQueryError(
|
|
279
|
+
stored_query_name,
|
|
280
|
+
stored_query_names,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
parse_error.code !== 'unknown_stored_query' ||
|
|
285
|
+
parse_error.suggestion === undefined
|
|
286
|
+
) {
|
|
287
|
+
return parse_error;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
...parse_error,
|
|
292
|
+
next_path: `patram queries ${subcommand_name} ${parse_error.suggestion}`,
|
|
293
|
+
};
|
|
294
|
+
}
|
package/lib/config/schema.d.ts
CHANGED
|
@@ -141,6 +141,7 @@ export const patram_repo_config_schema: z.ZodObject<{
|
|
|
141
141
|
prefixes: z.ZodArray<z.ZodString>;
|
|
142
142
|
}, z.core.$strict>>>;
|
|
143
143
|
queries: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
144
|
+
description: z.ZodOptional<z.ZodString>;
|
|
144
145
|
where: z.ZodString;
|
|
145
146
|
}, z.core.$strict>>>;
|
|
146
147
|
relations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -167,6 +168,7 @@ import { z } from 'zod';
|
|
|
167
168
|
* @typedef {z.output<typeof stored_query_schema>} StoredQueryConfig
|
|
168
169
|
*/
|
|
169
170
|
declare const stored_query_schema: z.ZodObject<{
|
|
171
|
+
description: z.ZodOptional<z.ZodString>;
|
|
170
172
|
where: z.ZodString;
|
|
171
173
|
}, z.core.$strict>;
|
|
172
174
|
/**
|
package/lib/config/schema.js
CHANGED
|
@@ -27,6 +27,10 @@ const MIXED_STYLE_VALUES = new Set(['ignore', 'error']);
|
|
|
27
27
|
*/
|
|
28
28
|
const stored_query_schema = z
|
|
29
29
|
.object({
|
|
30
|
+
description: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, 'Stored query "description" must not be empty.')
|
|
33
|
+
.optional(),
|
|
30
34
|
where: z.string().min(1, 'Stored query "where" must not be empty.'),
|
|
31
35
|
})
|
|
32
36
|
.strict();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {unknown} config_value
|
|
3
|
+
* @returns {{ config: PatramRepoConfig, success: true } | { diagnostics: PatramDiagnostic[], success: false }}
|
|
4
|
+
*/
|
|
5
|
+
export function validatePatramConfigValue(config_value: unknown): {
|
|
6
|
+
config: PatramRepoConfig;
|
|
7
|
+
success: true;
|
|
8
|
+
} | {
|
|
9
|
+
diagnostics: PatramDiagnostic[];
|
|
10
|
+
success: false;
|
|
11
|
+
};
|
|
12
|
+
import type { PatramRepoConfig } from './load-patram-config.types.d.ts';
|
|
13
|
+
import type { PatramDiagnostic } from './load-patram-config.types.d.ts';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import {
|
|
3
|
+
* PatramDiagnostic,
|
|
4
|
+
* PatramRepoConfig,
|
|
5
|
+
* } from './load-patram-config.types.ts';
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { patram_repo_config_schema } from './schema.js';
|
|
9
|
+
import { normalizeRepoConfig } from './defaults.js';
|
|
10
|
+
import {
|
|
11
|
+
validateDerivedSummaries,
|
|
12
|
+
validateFieldSchemaConfig,
|
|
13
|
+
validateGraphSchema,
|
|
14
|
+
validateLegacyConfigShape,
|
|
15
|
+
validateStoredQueries,
|
|
16
|
+
} from './validation.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {unknown} config_value
|
|
20
|
+
* @returns {{ config: PatramRepoConfig, success: true } | { diagnostics: PatramDiagnostic[], success: false }}
|
|
21
|
+
*/
|
|
22
|
+
export function validatePatramConfigValue(config_value) {
|
|
23
|
+
const legacy_config_diagnostics = validateLegacyConfigShape(config_value);
|
|
24
|
+
|
|
25
|
+
if (legacy_config_diagnostics.length > 0) {
|
|
26
|
+
return {
|
|
27
|
+
diagnostics: legacy_config_diagnostics,
|
|
28
|
+
success: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const config_result = patram_repo_config_schema.safeParse(config_value);
|
|
33
|
+
|
|
34
|
+
if (!config_result.success) {
|
|
35
|
+
return {
|
|
36
|
+
diagnostics: config_result.error.issues.map(createValidationDiagnostic),
|
|
37
|
+
success: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const graph_schema_diagnostics = validateGraphSchema(config_result.data);
|
|
42
|
+
|
|
43
|
+
if (graph_schema_diagnostics.length > 0) {
|
|
44
|
+
return {
|
|
45
|
+
diagnostics: graph_schema_diagnostics,
|
|
46
|
+
success: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const normalized_config = normalizeRepoConfig(config_result.data);
|
|
51
|
+
const field_schema_diagnostics = validateFieldSchemaConfig(normalized_config);
|
|
52
|
+
|
|
53
|
+
if (field_schema_diagnostics.length > 0) {
|
|
54
|
+
return {
|
|
55
|
+
diagnostics: field_schema_diagnostics,
|
|
56
|
+
success: false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stored_query_diagnostics = validateStoredQueries(normalized_config);
|
|
61
|
+
|
|
62
|
+
if (stored_query_diagnostics.length > 0) {
|
|
63
|
+
return {
|
|
64
|
+
diagnostics: stored_query_diagnostics,
|
|
65
|
+
success: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const derived_summary_diagnostics =
|
|
70
|
+
validateDerivedSummaries(normalized_config);
|
|
71
|
+
|
|
72
|
+
if (derived_summary_diagnostics.length > 0) {
|
|
73
|
+
return {
|
|
74
|
+
diagnostics: derived_summary_diagnostics,
|
|
75
|
+
success: false,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
config: normalized_config,
|
|
81
|
+
success: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {import('zod').core.$ZodIssue} issue
|
|
87
|
+
* @returns {PatramDiagnostic}
|
|
88
|
+
*/
|
|
89
|
+
function createValidationDiagnostic(issue) {
|
|
90
|
+
const issue_path = formatIssuePath(issue.path);
|
|
91
|
+
|
|
92
|
+
if (issue_path) {
|
|
93
|
+
return {
|
|
94
|
+
code: 'config.invalid',
|
|
95
|
+
column: 1,
|
|
96
|
+
level: 'error',
|
|
97
|
+
line: 1,
|
|
98
|
+
message: `Invalid config at "${issue_path}": ${issue.message}`,
|
|
99
|
+
path: '.patram.json',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
code: 'config.invalid',
|
|
105
|
+
column: 1,
|
|
106
|
+
level: 'error',
|
|
107
|
+
line: 1,
|
|
108
|
+
message: `Invalid config: ${issue.message}`,
|
|
109
|
+
path: '.patram.json',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {(string | number | symbol | undefined)[]} issue_path
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
function formatIssuePath(issue_path) {
|
|
118
|
+
return issue_path.map(String).join('.');
|
|
119
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the closest candidate above the shared suggestion threshold.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} input_text
|
|
5
|
+
* @param {readonly string[]} candidates
|
|
6
|
+
* @returns {string | undefined}
|
|
7
|
+
*/
|
|
8
|
+
export function findCloseMatch(input_text: string, candidates: readonly string[]): string | undefined;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the closest candidate above the shared suggestion threshold.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} input_text
|
|
5
|
+
* @param {readonly string[]} candidates
|
|
6
|
+
* @returns {string | undefined}
|
|
7
|
+
*/
|
|
8
|
+
export function findCloseMatch(input_text, candidates) {
|
|
9
|
+
let best_candidate;
|
|
10
|
+
let best_score = 0;
|
|
11
|
+
|
|
12
|
+
for (const candidate of candidates) {
|
|
13
|
+
const score = scoreCandidate(input_text, candidate);
|
|
14
|
+
|
|
15
|
+
if (score > best_score) {
|
|
16
|
+
best_candidate = candidate;
|
|
17
|
+
best_score = score;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (best_score < 0.6) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return best_candidate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} input_text
|
|
30
|
+
* @param {string} candidate
|
|
31
|
+
* @returns {number}
|
|
32
|
+
*/
|
|
33
|
+
function scoreCandidate(input_text, candidate) {
|
|
34
|
+
const max_length = Math.max(input_text.length, candidate.length);
|
|
35
|
+
|
|
36
|
+
if (max_length === 0) {
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
1 - calculateDamerauLevenshteinDistance(input_text, candidate) / max_length
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} left_text
|
|
47
|
+
* @param {string} right_text
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
function calculateDamerauLevenshteinDistance(left_text, right_text) {
|
|
51
|
+
/** @type {number[][]} */
|
|
52
|
+
const matrix = Array.from({ length: left_text.length + 1 }, () =>
|
|
53
|
+
Array.from({ length: right_text.length + 1 }, () => 0),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
for (let left_index = 0; left_index <= left_text.length; left_index += 1) {
|
|
57
|
+
matrix[left_index][0] = left_index;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (
|
|
61
|
+
let right_index = 0;
|
|
62
|
+
right_index <= right_text.length;
|
|
63
|
+
right_index += 1
|
|
64
|
+
) {
|
|
65
|
+
matrix[0][right_index] = right_index;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (let left_index = 1; left_index <= left_text.length; left_index += 1) {
|
|
69
|
+
for (
|
|
70
|
+
let right_index = 1;
|
|
71
|
+
right_index <= right_text.length;
|
|
72
|
+
right_index += 1
|
|
73
|
+
) {
|
|
74
|
+
const substitution_cost =
|
|
75
|
+
left_text[left_index - 1] === right_text[right_index - 1] ? 0 : 1;
|
|
76
|
+
|
|
77
|
+
matrix[left_index][right_index] = Math.min(
|
|
78
|
+
matrix[left_index - 1][right_index] + 1,
|
|
79
|
+
matrix[left_index][right_index - 1] + 1,
|
|
80
|
+
matrix[left_index - 1][right_index - 1] + substitution_cost,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
left_index > 1 &&
|
|
85
|
+
right_index > 1 &&
|
|
86
|
+
left_text[left_index - 1] === right_text[right_index - 2] &&
|
|
87
|
+
left_text[left_index - 2] === right_text[right_index - 1]
|
|
88
|
+
) {
|
|
89
|
+
matrix[left_index][right_index] = Math.min(
|
|
90
|
+
matrix[left_index][right_index],
|
|
91
|
+
matrix[left_index - 2][right_index - 2] + substitution_cost,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return matrix[left_text.length][right_text.length];
|
|
98
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { PatramRepoConfig } from '../../config/load-patram-config.types.d.ts';
|
|
3
|
-
*/
|
|
4
1
|
/**
|
|
5
2
|
* @typedef {{ kind: 'ad_hoc' } | { kind: 'stored_query', name: string }} QuerySource
|
|
6
3
|
*/
|
|
@@ -9,7 +6,7 @@
|
|
|
9
6
|
*
|
|
10
7
|
* @param {PatramRepoConfig} repo_config
|
|
11
8
|
* @param {string[]} command_arguments
|
|
12
|
-
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | {
|
|
9
|
+
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | { error: CliParseError, success: false }}
|
|
13
10
|
*/
|
|
14
11
|
export function resolveWhereClause(repo_config: PatramRepoConfig, command_arguments: string[]): {
|
|
15
12
|
success: true;
|
|
@@ -18,9 +15,15 @@ export function resolveWhereClause(repo_config: PatramRepoConfig, command_argume
|
|
|
18
15
|
where_clause: string;
|
|
19
16
|
};
|
|
20
17
|
} | {
|
|
18
|
+
error: CliParseError;
|
|
21
19
|
success: false;
|
|
22
|
-
message: string;
|
|
23
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} stored_query_name
|
|
23
|
+
* @param {string[]} stored_query_names
|
|
24
|
+
* @returns {CliParseError}
|
|
25
|
+
*/
|
|
26
|
+
export function createUnknownStoredQueryError(stored_query_name: string, stored_query_names: string[]): CliParseError;
|
|
24
27
|
export type QuerySource = {
|
|
25
28
|
kind: "ad_hoc";
|
|
26
29
|
} | {
|
|
@@ -28,3 +31,4 @@ export type QuerySource = {
|
|
|
28
31
|
name: string;
|
|
29
32
|
};
|
|
30
33
|
import type { PatramRepoConfig } from '../../config/load-patram-config.types.d.ts';
|
|
34
|
+
import type { CliParseError } from '../../cli/arguments.types.d.ts';
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @import { CliParseError } from '../../cli/arguments.types.ts';
|
|
2
3
|
* @import { PatramRepoConfig } from '../../config/load-patram-config.types.ts';
|
|
3
4
|
*/
|
|
4
5
|
|
|
6
|
+
import { findCloseMatch } from '../../find-close-match.js';
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* @typedef {{ kind: 'ad_hoc' } | { kind: 'stored_query', name: string }} QuerySource
|
|
7
10
|
*/
|
|
@@ -11,7 +14,7 @@
|
|
|
11
14
|
*
|
|
12
15
|
* @param {PatramRepoConfig} repo_config
|
|
13
16
|
* @param {string[]} command_arguments
|
|
14
|
-
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | {
|
|
17
|
+
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | { error: CliParseError, success: false }}
|
|
15
18
|
*/
|
|
16
19
|
export function resolveWhereClause(repo_config, command_arguments) {
|
|
17
20
|
if (command_arguments[0] === '--where') {
|
|
@@ -19,7 +22,10 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
19
22
|
|
|
20
23
|
if (where_clause.length === 0) {
|
|
21
24
|
return {
|
|
22
|
-
|
|
25
|
+
error: {
|
|
26
|
+
code: 'message',
|
|
27
|
+
message: 'Query requires a where clause.',
|
|
28
|
+
},
|
|
23
29
|
success: false,
|
|
24
30
|
};
|
|
25
31
|
}
|
|
@@ -39,7 +45,10 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
39
45
|
|
|
40
46
|
if (!stored_query_name) {
|
|
41
47
|
return {
|
|
42
|
-
|
|
48
|
+
error: {
|
|
49
|
+
code: 'message',
|
|
50
|
+
message: 'Query requires "--where" or a stored query name.',
|
|
51
|
+
},
|
|
43
52
|
success: false,
|
|
44
53
|
};
|
|
45
54
|
}
|
|
@@ -48,7 +57,10 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
48
57
|
|
|
49
58
|
if (!stored_query) {
|
|
50
59
|
return {
|
|
51
|
-
|
|
60
|
+
error: createUnknownStoredQueryError(
|
|
61
|
+
stored_query_name,
|
|
62
|
+
Object.keys(repo_config.queries),
|
|
63
|
+
),
|
|
52
64
|
success: false,
|
|
53
65
|
};
|
|
54
66
|
}
|
|
@@ -64,3 +76,28 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
64
76
|
},
|
|
65
77
|
};
|
|
66
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} stored_query_name
|
|
82
|
+
* @param {string[]} stored_query_names
|
|
83
|
+
* @returns {CliParseError}
|
|
84
|
+
*/
|
|
85
|
+
export function createUnknownStoredQueryError(
|
|
86
|
+
stored_query_name,
|
|
87
|
+
stored_query_names,
|
|
88
|
+
) {
|
|
89
|
+
const suggestion = findCloseMatch(stored_query_name, stored_query_names);
|
|
90
|
+
|
|
91
|
+
if (!suggestion) {
|
|
92
|
+
return {
|
|
93
|
+
code: 'unknown_stored_query',
|
|
94
|
+
name: stored_query_name,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
code: 'unknown_stored_query',
|
|
100
|
+
name: stored_query_name,
|
|
101
|
+
suggestion,
|
|
102
|
+
};
|
|
103
|
+
}
|