d365fo-mcp 1.10.0 → 1.10.1
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/dist/metadata/labelParser.d.ts +2 -0
- package/dist/metadata/labelParser.js +25 -8
- package/dist/metadata/symbolIndex.d.ts +95 -3
- package/dist/metadata/symbolIndex.js +339 -54
- package/dist/scripts/build-database.js +380 -70
- package/dist/scripts/build-fts.js +381 -68
- package/dist/server/toolSchemas/labels.js +2 -1
- package/dist/tools/analysis/validateCode.js +13 -5
- package/dist/tools/labels.d.ts +6 -3
- package/dist/tools/labels.js +6 -13
- package/dist/tools/prepare/prepareCreate.js +7 -1
- package/dist/tools/readers/getLabelInfo.js +24 -6
- package/dist/tools/write/createLabel.js +11 -3
- package/dist/tools/write/resolveReferences.js +7 -1
- package/dist/utils/concurrency.d.ts +20 -0
- package/dist/utils/concurrency.js +41 -0
- package/dist/utils/labelReference.d.ts +33 -0
- package/dist/utils/labelReference.js +53 -0
- package/package.json +1 -1
|
@@ -55,8 +55,10 @@ export declare function discoverLabelFiles(modelDir: string): Promise<Array<{
|
|
|
55
55
|
*/
|
|
56
56
|
export declare function indexAllLabels(symbolIndex: XppSymbolIndex, packagesPath: string, modelFilter?: (modelName: string) => boolean, opts?: {
|
|
57
57
|
ftsStrategy?: 'rebuild' | 'incremental';
|
|
58
|
+
skipFtsRebuild?: boolean;
|
|
58
59
|
}): Promise<{
|
|
59
60
|
totalLabels: number;
|
|
60
61
|
modelsIndexed: number;
|
|
62
|
+
ftsRebuildPending: boolean;
|
|
61
63
|
}>;
|
|
62
64
|
//# sourceMappingURL=labelParser.d.ts.map
|
|
@@ -15,6 +15,7 @@ import * as fs from 'fs/promises';
|
|
|
15
15
|
import * as fsSync from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { settingByEnv } from '../config/settings.js';
|
|
18
|
+
import { defaultFileConcurrency, mapWithConcurrency } from '../utils/concurrency.js';
|
|
18
19
|
/**
|
|
19
20
|
* Fallback when LABEL_LANGUAGES is unset, taken from the registry rather than
|
|
20
21
|
* spelled out here. This used to be the literal 'en-US,cs,sk,de' while
|
|
@@ -158,15 +159,23 @@ async function indexModelLabels(symbolIndex, modelDir, model, opts) {
|
|
|
158
159
|
if (labelFiles.length === 0)
|
|
159
160
|
return 0;
|
|
160
161
|
const allEntries = [];
|
|
161
|
-
|
|
162
|
+
// Read the model's label files with a bounded number in flight rather than one at
|
|
163
|
+
// a time. Barely matters on a default en-US build (813 files / ~38 MB across the
|
|
164
|
+
// whole PackagesLocalDirectory), but LABEL_LANGUAGES=all multiplies that by the ~74
|
|
165
|
+
// shipped locales — ~60 K files and a few GB — and a serial await per file makes
|
|
166
|
+
// that phase cost the sum of every read's latency. mapWithConcurrency preserves
|
|
167
|
+
// input order, so the rows still go in deterministically.
|
|
168
|
+
const perFile = await mapWithConcurrency(labelFiles, defaultFileConcurrency(), async ({ labelFileId, language, filePath }) => {
|
|
162
169
|
let content;
|
|
163
170
|
try {
|
|
164
171
|
content = await fs.readFile(filePath, 'utf-8');
|
|
165
172
|
}
|
|
166
173
|
catch {
|
|
167
|
-
|
|
174
|
+
return [];
|
|
168
175
|
}
|
|
169
|
-
|
|
176
|
+
return parseLabelFile(content, labelFileId, model, language, filePath);
|
|
177
|
+
});
|
|
178
|
+
for (const labels of perFile) {
|
|
170
179
|
for (const lbl of labels) {
|
|
171
180
|
allEntries.push({
|
|
172
181
|
labelId: lbl.labelId,
|
|
@@ -203,7 +212,7 @@ export async function indexAllLabels(symbolIndex, packagesPath, modelFilter, opt
|
|
|
203
212
|
}
|
|
204
213
|
catch {
|
|
205
214
|
console.error(`[LabelParser] Cannot read packages path: ${packagesPath}`);
|
|
206
|
-
return { totalLabels, modelsIndexed };
|
|
215
|
+
return { totalLabels, modelsIndexed, ftsRebuildPending: false };
|
|
207
216
|
}
|
|
208
217
|
let skippedByFilter = 0;
|
|
209
218
|
let skippedMissingDir = 0;
|
|
@@ -263,9 +272,13 @@ export async function indexAllLabels(symbolIndex, packagesPath, modelFilter, opt
|
|
|
263
272
|
}
|
|
264
273
|
}
|
|
265
274
|
}
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
275
|
+
// The rebuild is O(every label in the database) no matter how narrow this scan was,
|
|
276
|
+
// so a caller looping over several package roots must not pay it once per root —
|
|
277
|
+
// `skipFtsRebuild` lets it hoist the single rebuild out of its own loop and run it
|
|
278
|
+
// when `ftsRebuildPending` comes back true. Skipped under 'incremental' regardless:
|
|
279
|
+
// the triggers already kept labels_fts in sync per row.
|
|
280
|
+
const ftsRebuildPending = totalLabels > 0 && !incrementalFts;
|
|
281
|
+
if (ftsRebuildPending && !opts?.skipFtsRebuild) {
|
|
269
282
|
symbolIndex.rebuildLabelsFts();
|
|
270
283
|
}
|
|
271
284
|
if (modelsIndexed === 0) {
|
|
@@ -275,6 +288,10 @@ export async function indexAllLabels(symbolIndex, packagesPath, modelFilter, opt
|
|
|
275
288
|
console.log(` - Models with no labels: ${skippedNoLabels}`);
|
|
276
289
|
console.log(` - Total models found: ${models.length}`);
|
|
277
290
|
}
|
|
278
|
-
return {
|
|
291
|
+
return {
|
|
292
|
+
totalLabels,
|
|
293
|
+
modelsIndexed,
|
|
294
|
+
ftsRebuildPending: ftsRebuildPending && !!opts?.skipFtsRebuild,
|
|
295
|
+
};
|
|
279
296
|
}
|
|
280
297
|
//# sourceMappingURL=labelParser.js.map
|
|
@@ -69,6 +69,42 @@ export declare class XppSymbolIndex {
|
|
|
69
69
|
*/
|
|
70
70
|
private rowToSymbol;
|
|
71
71
|
private initializeDatabase;
|
|
72
|
+
/**
|
|
73
|
+
* The `labels` indexes that exist purely to accelerate reads, keyed by name so
|
|
74
|
+
* they can be dropped for a bulk load and rebuilt afterwards.
|
|
75
|
+
*
|
|
76
|
+
* `idx_labels_unique` is NOT in here — it enforces the dedupe that
|
|
77
|
+
* INSERT OR REPLACE relies on and has to stay live through the load.
|
|
78
|
+
*
|
|
79
|
+
* The two file_path entries are also created on demand by ensureFilePathIndexes()
|
|
80
|
+
* (which adds the large-DB worker dispatch that startup needs); this list is the
|
|
81
|
+
* single definition of their SQL so the two paths cannot drift apart.
|
|
82
|
+
*/
|
|
83
|
+
private static readonly LABEL_SECONDARY_INDEXES;
|
|
84
|
+
/** The subset created at schema-init time; the file_path_id index is left to ensureFilePathIndexes(). */
|
|
85
|
+
private static readonly LABEL_SECONDARY_INDEX_SQL;
|
|
86
|
+
/**
|
|
87
|
+
* Drop the read-only `labels` indexes ahead of a bulk load, and report which ones
|
|
88
|
+
* were actually there so the caller can put back exactly that set.
|
|
89
|
+
*
|
|
90
|
+
* Every row of a bulk load otherwise maintains eight B-trees, two of them keyed on
|
|
91
|
+
* a ~130-character absolute path. Measured on a 400 K-row / 150-model reproduction
|
|
92
|
+
* of this exact write path: 17.2 s with the indexes live versus 10.6 s dropping
|
|
93
|
+
* these seven and rebuilding them at the end (the insert itself, 14.9 s → 4.4 s).
|
|
94
|
+
* This is the same trade the symbols side already makes in ensureFilePathIndexes().
|
|
95
|
+
*
|
|
96
|
+
* Build-time only. Do NOT call this on a server that is answering queries — label
|
|
97
|
+
* search degrades to a full scan until createLabelSecondaryIndexes() finishes.
|
|
98
|
+
*/
|
|
99
|
+
dropLabelSecondaryIndexes(): string[];
|
|
100
|
+
/**
|
|
101
|
+
* Rebuild the indexes dropped by dropLabelSecondaryIndexes().
|
|
102
|
+
*
|
|
103
|
+
* Pass the array that call returned to restore exactly the set that was there;
|
|
104
|
+
* omit it to create all of them. Returns the elapsed milliseconds so build scripts
|
|
105
|
+
* can report the cost they moved out of the insert loop.
|
|
106
|
+
*/
|
|
107
|
+
createLabelSecondaryIndexes(only?: string[]): number;
|
|
72
108
|
/**
|
|
73
109
|
* Index `symbols.file_path` and `labels.file_path`.
|
|
74
110
|
*
|
|
@@ -138,6 +174,27 @@ export declare class XppSymbolIndex {
|
|
|
138
174
|
* on every database that predates this, so the one-time rebuild is self-triggering
|
|
139
175
|
* and costs nothing on an already-migrated file.
|
|
140
176
|
*/
|
|
177
|
+
/**
|
|
178
|
+
* Move an existing database from the inline `labels.file_path` column to the
|
|
179
|
+
* `label_files` lookup table.
|
|
180
|
+
*
|
|
181
|
+
* Runs once, gated on `PRAGMA user_version` like the FTS coverage migration below.
|
|
182
|
+
* A database built before this carries the path spelled out on every row; the
|
|
183
|
+
* column cannot simply be dropped, because the rows have to be rewritten to point
|
|
184
|
+
* at the extracted paths instead.
|
|
185
|
+
*
|
|
186
|
+
* This is a full table rewrite, so it is minutes on a multi-GB labels database
|
|
187
|
+
* rather than the ~23 s the FTS migration costs — announced up front for the same
|
|
188
|
+
* reason: a silent stall of that length reads as a hung server. It is worth paying
|
|
189
|
+
* once. The rewritten table is less than half the size, and every full-table
|
|
190
|
+
* operation on it afterwards (FTS rebuild, ANALYZE, VACUUM, any cold-cache scan)
|
|
191
|
+
* moves proportionally less disk.
|
|
192
|
+
*
|
|
193
|
+
* Row ids are carried over deliberately: `labels_fts` is an external-content index
|
|
194
|
+
* keyed on `labels.id`, so preserving them keeps it valid. It is rebuilt at the end
|
|
195
|
+
* anyway, because the content table it points at is a different table object by then.
|
|
196
|
+
*/
|
|
197
|
+
private migrateLabelPathsToLabelFiles;
|
|
141
198
|
private migrateLabelsFtsLanguageCoverage;
|
|
142
199
|
/**
|
|
143
200
|
* Add a symbol to the index with enhanced metadata
|
|
@@ -600,6 +657,19 @@ export declare class XppSymbolIndex {
|
|
|
600
657
|
comment?: string;
|
|
601
658
|
filePath: string;
|
|
602
659
|
}): void;
|
|
660
|
+
/**
|
|
661
|
+
* Row id of `filePath` in `label_files`, inserting it if it is new.
|
|
662
|
+
*
|
|
663
|
+
* Memoised for the process: a bulk load calls this once per label but there is
|
|
664
|
+
* one distinct path per .label.txt (813 across a default en-US build of 374 K
|
|
665
|
+
* rows), so without the cache it would be ~374 K index probes to learn 813 answers.
|
|
666
|
+
* The cache is only ever added to — rows in label_files are never deleted, since a
|
|
667
|
+
* path that had labels once may have them again after the next scan and the table
|
|
668
|
+
* is trivially small either way.
|
|
669
|
+
*/
|
|
670
|
+
private labelFilePathId;
|
|
671
|
+
/** filePath -> label_files.id, populated lazily by labelFilePathId(). */
|
|
672
|
+
private readonly labelFilePathIds;
|
|
603
673
|
/**
|
|
604
674
|
* Bulk-insert labels (drops FTS triggers for speed).
|
|
605
675
|
* Pass `{ skipFtsRebuild: true }` when indexing many models sequentially;
|
|
@@ -672,7 +742,22 @@ export declare class XppSymbolIndex {
|
|
|
672
742
|
*/
|
|
673
743
|
private searchLabelsLike;
|
|
674
744
|
/**
|
|
675
|
-
* Get a single label by
|
|
745
|
+
* Get a single label by ID (returns all languages).
|
|
746
|
+
*
|
|
747
|
+
* The ID may be spelled any way the rest of the server emits it: a reference
|
|
748
|
+
* (`@ContosoExt:EquipmentName`, `@GLS4170035`, even the doubled
|
|
749
|
+
* `@SYS:@SYS67433`) or the bare key. #888: matching the caller's string
|
|
750
|
+
* against the stored one verbatim made the natural spelling fail for the 27
|
|
751
|
+
* legacy label files, whose keys are stored WITH the sigil — 61% of the
|
|
752
|
+
* indexed rows — and `search` output, which is always a reference, was never
|
|
753
|
+
* valid input here. Both branches of the IN-list still use `idx_labels_id`
|
|
754
|
+
* (and `idx_labels_unique` once the file/model filters are added), so the
|
|
755
|
+
* widening costs nothing; see labelIdSpellings for why the sigil is not
|
|
756
|
+
* normalised away at storage time instead.
|
|
757
|
+
*
|
|
758
|
+
* Rows come back with the id EXACTLY as stored, which is the spelling
|
|
759
|
+
* callers must keep using for anything that reads the .label.txt (see
|
|
760
|
+
* labelMissingOnDisk) or writes a reference.
|
|
676
761
|
*/
|
|
677
762
|
getLabelById(labelId: string, labelFileId?: string, model?: string): Array<{
|
|
678
763
|
labelId: string;
|
|
@@ -720,8 +805,15 @@ export declare class XppSymbolIndex {
|
|
|
720
805
|
getLabelCount(): number;
|
|
721
806
|
/**
|
|
722
807
|
* Rename a label ID in the index (used by rename_label tool).
|
|
723
|
-
* Updates all rows for the given labelId + labelFileId + model combination
|
|
724
|
-
*
|
|
808
|
+
* Updates all rows for the given labelId + labelFileId + model combination.
|
|
809
|
+
*
|
|
810
|
+
* No FTS rebuild: the `labels_au` trigger deletes the old term and inserts the new
|
|
811
|
+
* one for each updated row, which is the whole of the work a rebuild would redo.
|
|
812
|
+
* This used to call rebuildLabelsFts() afterwards — re-tokenising every label in
|
|
813
|
+
* the database (~105 s on the production DB) to reflect a handful of renamed rows,
|
|
814
|
+
* and because node:sqlite is synchronous the server answered nothing for its whole
|
|
815
|
+
* duration. create_label and update_symbol_index were moved off that pattern
|
|
816
|
+
* earlier; this was the last caller still on it.
|
|
725
817
|
*/
|
|
726
818
|
renameLabelInIndex(oldLabelId: string, newLabelId: string, labelFileId: string, model: string): void;
|
|
727
819
|
}
|