mcp-xdx-schema-ctx 1.1.9

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 (60) hide show
  1. package/.env.example +43 -0
  2. package/README.md +269 -0
  3. package/dist/config.d.ts +131 -0
  4. package/dist/config.js +138 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/crypto-utils.d.ts +29 -0
  7. package/dist/crypto-utils.js +118 -0
  8. package/dist/crypto-utils.js.map +1 -0
  9. package/dist/db.d.ts +10 -0
  10. package/dist/db.js +149 -0
  11. package/dist/db.js.map +1 -0
  12. package/dist/diff-utils.d.ts +42 -0
  13. package/dist/diff-utils.js +130 -0
  14. package/dist/diff-utils.js.map +1 -0
  15. package/dist/encrypt-secrets.d.ts +12 -0
  16. package/dist/encrypt-secrets.js +190 -0
  17. package/dist/encrypt-secrets.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +30 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/migrate-utils.d.ts +91 -0
  22. package/dist/migrate-utils.js +399 -0
  23. package/dist/migrate-utils.js.map +1 -0
  24. package/dist/schema-compat.d.ts +46 -0
  25. package/dist/schema-compat.js +169 -0
  26. package/dist/schema-compat.js.map +1 -0
  27. package/dist/shared.d.ts +70 -0
  28. package/dist/shared.js +197 -0
  29. package/dist/shared.js.map +1 -0
  30. package/dist/sql-build.d.ts +121 -0
  31. package/dist/sql-build.js +254 -0
  32. package/dist/sql-build.js.map +1 -0
  33. package/dist/tool-availability.d.ts +35 -0
  34. package/dist/tool-availability.js +90 -0
  35. package/dist/tool-availability.js.map +1 -0
  36. package/dist/tools/data-tools.d.ts +2 -0
  37. package/dist/tools/data-tools.js +243 -0
  38. package/dist/tools/data-tools.js.map +1 -0
  39. package/dist/tools/dataflow-tools.d.ts +2 -0
  40. package/dist/tools/dataflow-tools.js +434 -0
  41. package/dist/tools/dataflow-tools.js.map +1 -0
  42. package/dist/tools/dbo-tools.d.ts +2 -0
  43. package/dist/tools/dbo-tools.js +228 -0
  44. package/dist/tools/dbo-tools.js.map +1 -0
  45. package/dist/tools/diff-tools.d.ts +2 -0
  46. package/dist/tools/diff-tools.js +224 -0
  47. package/dist/tools/diff-tools.js.map +1 -0
  48. package/dist/tools/form-tools.d.ts +2 -0
  49. package/dist/tools/form-tools.js +715 -0
  50. package/dist/tools/form-tools.js.map +1 -0
  51. package/dist/tools/migrate-tools.d.ts +2 -0
  52. package/dist/tools/migrate-tools.js +423 -0
  53. package/dist/tools/migrate-tools.js.map +1 -0
  54. package/dist/tools/system-tools.d.ts +2 -0
  55. package/dist/tools/system-tools.js +1102 -0
  56. package/dist/tools/system-tools.js.map +1 -0
  57. package/dist/tools/table-tools.d.ts +2 -0
  58. package/dist/tools/table-tools.js +1322 -0
  59. package/dist/tools/table-tools.js.map +1 -0
  60. package/package.json +48 -0
@@ -0,0 +1,1102 @@
1
+ import { z } from "zod";
2
+ import { table, envDb } from "../db.js";
3
+ import { T, userName, WARNING, escapeSql, escapeLike, envelope, ok, sqlPagination, topNPagination, } from "../shared.js";
4
+ import { tableNames } from "../config.js";
5
+ import { getTableColumns, getTableInfo, pickCols, hasCol, tableExists, userTableExists, resolveSelect, tryQuery, clearSchemaCache, } from "../schema-compat.js";
6
+ import { buildWhere, buildOrderBy, buildPagedQuery, buildViewLineageSql, } from "../sql-build.js";
7
+ import { computeToolAvailability, USER_DB_PROBE_TABLES, } from "../tool-availability.js";
8
+ export function registerSystemTools(server) {
9
+ // ============================================================
10
+ // get_function_tree
11
+ // ============================================================
12
+ server.registerTool("get_function_tree", {
13
+ description: WARNING +
14
+ "\n\n获取功能菜单树。返回 FunctionTB → FormSetTB 的层级结构,包含菜单类型、关联窗体、前端路由、组件路径。支持按平台(WEB/APP/PDA)过滤。",
15
+ inputSchema: {
16
+ keyword: z.string().max(200).optional().describe("按功能名称模糊搜索"),
17
+ type: z
18
+ .enum(["menu", "button", "all"])
19
+ .optional()
20
+ .default("all")
21
+ .describe("过滤类型"),
22
+ platform: z
23
+ .enum(["WEB", "APP", "PDA"])
24
+ .optional()
25
+ .describe("平台过滤: WEB/APP/PDA"),
26
+ limit: z.number().optional().default(100).describe("返回条数上限"),
27
+ page: z.number().int().min(1).default(1).describe("页码"),
28
+ pageSize: z
29
+ .number()
30
+ .int()
31
+ .min(1)
32
+ .max(200)
33
+ .default(50)
34
+ .describe("每页数量"),
35
+ },
36
+ }, async ({ keyword, type, platform, page = 1, pageSize = 50 }) => {
37
+ const warnings = [];
38
+ if (!(await tableExists(T("function")))) {
39
+ return ok(envelope({
40
+ items: [],
41
+ pagination: sqlPagination(page, pageSize, 0),
42
+ warnings: [
43
+ {
44
+ module: "get_function_tree",
45
+ message: `环境库缺少 ${T("function")}`,
46
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
47
+ fallback: "get_capabilities",
48
+ },
49
+ ],
50
+ }));
51
+ }
52
+ const funcInfo = await getTableInfo(T("function"));
53
+ const rs = await resolveSelect(T("function"), [
54
+ "FunctionID",
55
+ "FunctionName",
56
+ "Type",
57
+ "LinkForm",
58
+ "Component",
59
+ "Icon",
60
+ "Path",
61
+ "ApiPath",
62
+ "Redirect",
63
+ "IsHidden",
64
+ "ParentId",
65
+ "LevelCode",
66
+ "WebSort",
67
+ "PlatformType",
68
+ ]);
69
+ if (rs.missing.length > 0) {
70
+ warnings.push({
71
+ module: "get_function_tree",
72
+ message: `以下列在本环境库不存在,已置空: ${rs.missing.join(", ")}`,
73
+ });
74
+ }
75
+ const groups = [];
76
+ if (keyword)
77
+ groups.push({
78
+ filters: [{ col: "FunctionName", op: "like", value: keyword }],
79
+ });
80
+ if (type && type !== "all")
81
+ groups.push({ filters: [{ col: "Type", op: "eq", value: type }] });
82
+ if (platform)
83
+ groups.push({
84
+ filters: [{ col: "PlatformType", op: "eq", value: platform }],
85
+ });
86
+ const { where, skipped } = buildWhere(funcInfo.columns, groups);
87
+ if (skipped.length > 0) {
88
+ warnings.push({
89
+ module: "get_function_tree",
90
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
91
+ });
92
+ }
93
+ const order = buildOrderBy(funcInfo.columns, ["LevelCode", "WebSort"]);
94
+ const q = buildPagedQuery({
95
+ table: T("function"),
96
+ cols: rs.present.length > 0
97
+ ? rs.present.map((c) => `[${c}]`)
98
+ : ["[FunctionID]"],
99
+ where,
100
+ orderBy: order.orderBy,
101
+ page,
102
+ pageSize,
103
+ });
104
+ const cnt = await tryQuery("get_function_tree.count", q.countSql, warnings);
105
+ const total = Number(cnt[0]?.Total ?? 0);
106
+ const funcs = total > 0 ? await tryQuery("get_function_tree", q.sql, warnings) : [];
107
+ const formIds = [
108
+ ...new Set(funcs
109
+ .filter((f) => f.LinkForm)
110
+ .map((f) => `'${escapeSql(String(f.LinkForm))}'`)),
111
+ ];
112
+ let forms = [];
113
+ if (formIds.length > 0 && (await tableExists(T("formDef")))) {
114
+ const frs = await resolveSelect(T("formDef"), [
115
+ "FormID",
116
+ "FormName",
117
+ "LinkTable",
118
+ "FormType",
119
+ "ApiPath",
120
+ "ServiceType",
121
+ ]);
122
+ forms = await tryQuery("get_function_tree.forms", `SELECT ${frs.select} FROM ${T("formDef")} WHERE [FormID] IN (${formIds.join(",")})`, warnings);
123
+ }
124
+ let platformStats = [];
125
+ if (platform && hasCol(funcInfo.columns, "PlatformType")) {
126
+ platformStats = await tryQuery("get_function_tree.platformStats", `SELECT [PlatformType], COUNT(*) AS cnt FROM ${T("function")} WHERE [PlatformType]='${escapeSql(platform)}' GROUP BY [PlatformType]`, warnings);
127
+ }
128
+ return ok(envelope({
129
+ items: funcs,
130
+ pagination: sqlPagination(page, pageSize, total),
131
+ warnings,
132
+ forms,
133
+ platformStats,
134
+ }));
135
+ });
136
+ // ============================================================
137
+ // get_page_actions
138
+ // ============================================================
139
+ server.registerTool("get_page_actions", {
140
+ description: WARNING +
141
+ "\n\n根据页面Code查询该页面所有可用按钮及功能。MenuFunction 表(28518条)记录了每个页面(Code)可用哪些按钮(ButtonID)和功能(FunctionID)。",
142
+ inputSchema: {
143
+ code: z
144
+ .string()
145
+ .max(50)
146
+ .optional()
147
+ .describe("页面Code,如 ckwhfgoutlist"),
148
+ functionName: z.string().max(200).optional().describe("按功能名称搜索"),
149
+ buttonId: z.string().max(50).optional().describe("按按钮ID搜索"),
150
+ limit: z.number().optional().default(50),
151
+ page: z.number().int().min(1).default(1).describe("页码"),
152
+ pageSize: z
153
+ .number()
154
+ .int()
155
+ .min(1)
156
+ .max(200)
157
+ .default(50)
158
+ .describe("每页数量"),
159
+ },
160
+ }, async ({ code, functionName, buttonId, page = 1, pageSize = 50 }) => {
161
+ const warnings = [];
162
+ if (!(await tableExists(T("menuFunction")))) {
163
+ return ok(envelope({
164
+ items: [],
165
+ pagination: sqlPagination(page, pageSize, 0),
166
+ warnings: [
167
+ {
168
+ module: "get_page_actions",
169
+ message: `环境库缺少 ${T("menuFunction")}`,
170
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
171
+ fallback: "get_capabilities",
172
+ },
173
+ ],
174
+ }));
175
+ }
176
+ const info = await getTableInfo(T("menuFunction"));
177
+ const rs = await resolveSelect(T("menuFunction"), [
178
+ "Code",
179
+ "FunctionID",
180
+ "FunctionName",
181
+ "ButtonID",
182
+ "iLineNo",
183
+ ]);
184
+ if (rs.missing.length > 0) {
185
+ warnings.push({
186
+ module: "get_page_actions",
187
+ message: `以下列在本环境库不存在,已置空: ${rs.missing.join(", ")}`,
188
+ });
189
+ }
190
+ const groups = [];
191
+ if (code)
192
+ groups.push({ filters: [{ col: "Code", op: "like", value: code }] });
193
+ if (functionName)
194
+ groups.push({
195
+ filters: [{ col: "FunctionName", op: "like", value: functionName }],
196
+ });
197
+ if (buttonId)
198
+ groups.push({ filters: [{ col: "ButtonID", op: "eq", value: buttonId }] });
199
+ const { where, skipped } = buildWhere(info.columns, groups);
200
+ if (skipped.length > 0) {
201
+ warnings.push({
202
+ module: "get_page_actions",
203
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
204
+ });
205
+ }
206
+ const order = buildOrderBy(info.columns, ["Code", "iLineNo"]);
207
+ const q = buildPagedQuery({
208
+ table: T("menuFunction"),
209
+ cols: rs.present.length > 0
210
+ ? rs.present.map((c) => `[${c}]`)
211
+ : ["[Code]"],
212
+ where,
213
+ orderBy: order.orderBy,
214
+ page,
215
+ pageSize,
216
+ });
217
+ const cnt = await tryQuery("get_page_actions.count", q.countSql, warnings);
218
+ const total = Number(cnt[0]?.Total ?? 0);
219
+ const items = total > 0 ? await tryQuery("get_page_actions", q.sql, warnings) : [];
220
+ const buttonIds = [
221
+ ...new Set(items
222
+ .filter((i) => i.ButtonID)
223
+ .map((i) => `'${escapeSql(String(i.ButtonID))}'`)),
224
+ ];
225
+ let buttons = {
226
+ module: "buttons",
227
+ available: true,
228
+ items: [],
229
+ };
230
+ if (buttonIds.length > 0) {
231
+ if (await tableExists(T("button"))) {
232
+ const brs = await resolveSelect(T("button"), [
233
+ "ButtonID",
234
+ "ButtonText",
235
+ "ButtonStyle",
236
+ "Seq",
237
+ "isBatch",
238
+ "AuthorityFlag",
239
+ ]);
240
+ buttons = {
241
+ module: "buttons",
242
+ available: true,
243
+ items: await tryQuery("get_page_actions.buttons", `SELECT ${brs.select} FROM ${T("button")} WHERE [ButtonID] IN (${buttonIds.join(",")})`, warnings),
244
+ };
245
+ }
246
+ else {
247
+ buttons = {
248
+ module: "buttons",
249
+ available: false,
250
+ reason: `环境库缺少 ${T("button")}`,
251
+ };
252
+ warnings.push({
253
+ module: "get_page_actions.buttons",
254
+ message: `环境库缺少 ${T("button")},按钮明细不可用(页面-功能映射仍可用)`,
255
+ fallback: "get_capabilities",
256
+ });
257
+ }
258
+ }
259
+ return ok(envelope({
260
+ items,
261
+ pagination: sqlPagination(page, pageSize, total),
262
+ warnings,
263
+ buttons,
264
+ }));
265
+ });
266
+ // ============================================================
267
+ // get_choose_ui_detail
268
+ // ============================================================
269
+ server.registerTool("get_choose_ui_detail", {
270
+ description: WARNING +
271
+ "\n\n获取UI选择器(下拉框/参照)的配置详情,包括数据源SQL、显示字段、树结构、权限过滤等。ChooseUISetTB(234条)定义了系统中所有弹出选择器的行为。",
272
+ inputSchema: {
273
+ name: z.string().max(200).optional().describe("选择器名称"),
274
+ keyword: z.string().max(200).optional().describe("模糊搜索名称"),
275
+ page: z.number().int().min(1).default(1).describe("页码"),
276
+ pageSize: z
277
+ .number()
278
+ .int()
279
+ .min(1)
280
+ .max(200)
281
+ .default(50)
282
+ .describe("每页数量"),
283
+ },
284
+ }, async ({ name, keyword, page = 1, pageSize = 50 }) => {
285
+ const warnings = [];
286
+ if (!(await tableExists(T("chooseUISet")))) {
287
+ return ok(envelope({
288
+ items: [],
289
+ pagination: sqlPagination(page, pageSize, 0),
290
+ warnings: [
291
+ {
292
+ module: "get_choose_ui_detail",
293
+ message: `环境库缺少 ${T("chooseUISet")}`,
294
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
295
+ fallback: "get_capabilities",
296
+ },
297
+ ],
298
+ }));
299
+ }
300
+ const info = await getTableInfo(T("chooseUISet"));
301
+ const rs = await resolveSelect(T("chooseUISet"), [
302
+ "cName",
303
+ "cFormType",
304
+ "bMutiChs",
305
+ "cListSource",
306
+ "cListShowField",
307
+ "cListFilter",
308
+ "cListSort",
309
+ "cTreeInfo",
310
+ "cTreeFilter",
311
+ "cTreeSort",
312
+ "PrimayKeyField",
313
+ "cListUniqueKey",
314
+ "WareRightField",
315
+ "CustRightField",
316
+ "ItemKindRightField",
317
+ ]);
318
+ if (rs.missing.length > 0) {
319
+ warnings.push({
320
+ module: "get_choose_ui_detail",
321
+ message: `以下列在本环境库不存在,已置空: ${rs.missing.join(", ")}`,
322
+ });
323
+ }
324
+ const groups = [];
325
+ if (name)
326
+ groups.push({ filters: [{ col: "cName", op: "eq", value: name }] });
327
+ if (keyword)
328
+ groups.push({ filters: [{ col: "cName", op: "like", value: keyword }] });
329
+ const { where, skipped } = buildWhere(info.columns, groups);
330
+ if (skipped.length > 0) {
331
+ warnings.push({
332
+ module: "get_choose_ui_detail",
333
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
334
+ });
335
+ }
336
+ const order = buildOrderBy(info.columns, ["cName"]);
337
+ const q = buildPagedQuery({
338
+ table: T("chooseUISet"),
339
+ cols: rs.present.length > 0
340
+ ? rs.present.map((c) => `[${c}]`)
341
+ : ["[cName]"],
342
+ where,
343
+ orderBy: order.orderBy,
344
+ page,
345
+ pageSize,
346
+ });
347
+ const cnt = await tryQuery("get_choose_ui_detail.count", q.countSql, warnings);
348
+ const total = Number(cnt[0]?.Total ?? 0);
349
+ const items = total > 0 ? await tryQuery("get_choose_ui_detail", q.sql, warnings) : [];
350
+ if (rs.missing.length > 0 && items.length === 0) {
351
+ warnings.push({
352
+ module: "get_choose_ui_detail",
353
+ message: `因列缺失(${rs.missing.join(", ")}),过滤条件可能失效,结果可能不完整——无法确定是「确实没有匹配项」还是「条件未生效」`,
354
+ hint: '可用 describe_table(tableName="ChooseUISetTB") 查看实际字段',
355
+ });
356
+ }
357
+ return ok(envelope({
358
+ items,
359
+ pagination: sqlPagination(page, pageSize, total),
360
+ warnings,
361
+ }));
362
+ });
363
+ // ============================================================
364
+ // get_button_detail
365
+ // ============================================================
366
+ server.registerTool("get_button_detail", {
367
+ description: WARNING +
368
+ "\n\n查询按钮的完整定义和所有使用场景。串联: ButtonTB(按钮库10980条)→FormStateSetTB(哪些窗体用了)→MenuFunction(哪些页面用了)。",
369
+ inputSchema: {
370
+ buttonId: z
371
+ .string()
372
+ .max(50)
373
+ .optional()
374
+ .describe("按钮ID,如 add/edit/delete"),
375
+ keyword: z.string().max(200).optional().describe("按按钮文本模糊搜索"),
376
+ limit: z.number().optional().default(50),
377
+ page: z.number().int().min(1).default(1).describe("页码"),
378
+ pageSize: z
379
+ .number()
380
+ .int()
381
+ .min(1)
382
+ .max(200)
383
+ .default(50)
384
+ .describe("每页数量"),
385
+ },
386
+ }, async ({ buttonId, keyword, page = 1, pageSize = 50 }) => {
387
+ const warnings = [];
388
+ if (!(await tableExists(T("button")))) {
389
+ return ok(envelope({
390
+ items: [],
391
+ pagination: sqlPagination(page, pageSize, 0),
392
+ warnings: [
393
+ {
394
+ module: "get_button_detail",
395
+ message: `环境库缺少 ${T("button")}`,
396
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
397
+ fallback: "get_capabilities",
398
+ },
399
+ ],
400
+ }));
401
+ }
402
+ const info = await getTableInfo(T("button"));
403
+ const rs = await resolveSelect(T("button"), [
404
+ "ButtonID",
405
+ "ButtonText",
406
+ "ButtonStyle",
407
+ "ButtonFunctionType",
408
+ "Seq",
409
+ "isBatch",
410
+ "AuthorityFlag",
411
+ "FrontFunctionName",
412
+ "BackFunctionName",
413
+ ]);
414
+ if (rs.missing.length > 0) {
415
+ warnings.push({
416
+ module: "get_button_detail",
417
+ message: `以下列在本环境库不存在,已置空: ${rs.missing.join(", ")}`,
418
+ });
419
+ }
420
+ const groups = [];
421
+ if (buttonId)
422
+ groups.push({ filters: [{ col: "ButtonID", op: "eq", value: buttonId }] });
423
+ if (keyword)
424
+ groups.push({ filters: [{ col: "ButtonText", op: "like", value: keyword }] });
425
+ const { where, skipped } = buildWhere(info.columns, groups);
426
+ if (skipped.length > 0) {
427
+ warnings.push({
428
+ module: "get_button_detail",
429
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
430
+ });
431
+ }
432
+ const order = buildOrderBy(info.columns, ["ButtonID"]);
433
+ const q = buildPagedQuery({
434
+ table: T("button"),
435
+ cols: rs.present.length > 0
436
+ ? rs.present.map((c) => `[${c}]`)
437
+ : ["[ButtonID]"],
438
+ where,
439
+ orderBy: order.orderBy,
440
+ page,
441
+ pageSize,
442
+ });
443
+ const cnt = await tryQuery("get_button_detail.count", q.countSql, warnings);
444
+ const total = Number(cnt[0]?.Total ?? 0);
445
+ const buttons = total > 0
446
+ ? (await tryQuery("get_button_detail", q.sql, warnings))
447
+ : [];
448
+ const result = [];
449
+ for (const b of buttons) {
450
+ const bid = String(b.ButtonID ?? "").replace(/'/g, "''");
451
+ // FormStateSetTB 可能缺失 → 降级为不可用
452
+ let formStates = { module: "formStates", available: true, items: [] };
453
+ if (await tableExists(T("formState"))) {
454
+ const frs = await resolveSelect(T("formState"), [
455
+ "FormID",
456
+ "ButtonID",
457
+ "ButtonText",
458
+ "AddState",
459
+ "UpdState",
460
+ "AuditState",
461
+ "ViewState",
462
+ ]);
463
+ formStates = {
464
+ module: "formStates",
465
+ available: true,
466
+ items: await tryQuery("get_button_detail.formStates", `SELECT TOP 20 ${frs.select} FROM ${T("formState")} WHERE [ButtonID]='${bid}'`, warnings),
467
+ };
468
+ }
469
+ else {
470
+ formStates = {
471
+ module: "formStates",
472
+ available: false,
473
+ reason: `环境库缺少 ${T("formState")}`,
474
+ };
475
+ warnings.push({
476
+ module: "get_button_detail.formStates",
477
+ message: `环境库缺少 ${T("formState")}`,
478
+ fallback: "get_capabilities",
479
+ });
480
+ }
481
+ let menuUsage = { module: "menuUsage", available: true, items: [] };
482
+ if (await tableExists(T("menuFunction"))) {
483
+ const mrs = await resolveSelect(T("menuFunction"), [
484
+ "Code",
485
+ "FunctionName",
486
+ "ButtonID",
487
+ ]);
488
+ menuUsage = {
489
+ module: "menuUsage",
490
+ available: true,
491
+ items: await tryQuery("get_button_detail.menuUsage", `SELECT TOP 20 ${mrs.select} FROM ${T("menuFunction")} WHERE [ButtonID]='${bid}'`, warnings),
492
+ };
493
+ }
494
+ else {
495
+ menuUsage = {
496
+ module: "menuUsage",
497
+ available: false,
498
+ reason: `环境库缺少 ${T("menuFunction")}`,
499
+ };
500
+ warnings.push({
501
+ module: "get_button_detail.menuUsage",
502
+ message: `环境库缺少 ${T("menuFunction")}`,
503
+ fallback: "get_capabilities",
504
+ });
505
+ }
506
+ const formIds = [
507
+ ...new Set((Array.isArray(formStates.items)
508
+ ? formStates.items
509
+ : [])
510
+ .filter((f) => f.FormID !== undefined && f.FormID !== null)
511
+ .map((f) => `'${escapeSql(String(f.FormID))}'`)),
512
+ ];
513
+ let forms = [];
514
+ if (formIds.length > 0 && (await tableExists(T("formDef")))) {
515
+ const frs2 = await resolveSelect(T("formDef"), [
516
+ "FormID",
517
+ "FormName",
518
+ "LinkTable",
519
+ "FormType",
520
+ ]);
521
+ forms = await tryQuery("get_button_detail.forms", `SELECT ${frs2.select} FROM ${T("formDef")} WHERE [FormID] IN (${formIds.join(",")})`, warnings);
522
+ }
523
+ result.push({ button: b, usedInForms: forms, formStates, menuUsage });
524
+ }
525
+ return ok(envelope({
526
+ items: result,
527
+ pagination: sqlPagination(page, pageSize, total),
528
+ warnings,
529
+ }));
530
+ });
531
+ // ============================================================
532
+ // get_system_enums
533
+ // ============================================================
534
+ server.registerTool("get_system_enums", {
535
+ description: WARNING +
536
+ "\n\n查询系统内置字典: 链接类型(LinkTypeTB)、操作类型(OperateTypeTB)、功能类型(FunctionKindTB)、用户报表方案(UserReportFindSetTB)。支持按类型筛选或一次返回全部。",
537
+ inputSchema: {
538
+ type: z
539
+ .enum([
540
+ "linkTypes",
541
+ "operateTypes",
542
+ "functionKinds",
543
+ "reportSchemes",
544
+ "all",
545
+ ])
546
+ .optional()
547
+ .default("all")
548
+ .describe("字典类型: linkTypes/operateTypes/functionKinds/reportSchemes/all"),
549
+ userId: z
550
+ .string()
551
+ .max(50)
552
+ .optional()
553
+ .describe("按用户ID过滤报表方案(仅 reportSchemes/all 时有效)"),
554
+ reportId: z
555
+ .string()
556
+ .max(128)
557
+ .optional()
558
+ .describe("按报表ID过滤(仅 reportSchemes/all 时有效)"),
559
+ },
560
+ }, async ({ type, userId, reportId }) => {
561
+ const warnings = [];
562
+ const groupMeta = [];
563
+ const result = {};
564
+ const want = (t) => type === "all" || type === t;
565
+ /** 单个字典组:缺表 → available:false;缺列 → NULL 占位 + warning */
566
+ const enumGroup = async (module, tableRef, wanted, orderSql, alias) => {
567
+ if (!(await tableExists(tableRef))) {
568
+ warnings.push({
569
+ module,
570
+ message: `环境库缺少 ${tableRef}`,
571
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
572
+ fallback: "get_capabilities",
573
+ });
574
+ const g = {
575
+ module,
576
+ available: false,
577
+ reason: `环境库缺少 ${tableRef}`,
578
+ };
579
+ groupMeta.push({ type: module, available: false, reason: g.reason });
580
+ return g;
581
+ }
582
+ const rs = await resolveSelect(tableRef, wanted);
583
+ if (rs.missing.length > 0) {
584
+ warnings.push({
585
+ module,
586
+ message: `以下列在本环境库不存在,已置空: ${rs.missing.join(", ")}`,
587
+ });
588
+ }
589
+ const rows = (await tryQuery(module, `SELECT ${rs.select} FROM ${tableRef}${orderSql}`, warnings));
590
+ const items = alias ? rows.map(alias) : rows;
591
+ groupMeta.push({ type: module, available: true, count: items.length });
592
+ return { module, available: true, items, missing: rs.missing };
593
+ };
594
+ if (want("linkTypes")) {
595
+ result.linkTypes = await enumGroup("linkTypes", T("linkType"), ["iLinkType", "cTypeName", "LinkTypeDesc", "LinkURL", "IconName"], " ORDER BY [iLinkType]", (r) => ({
596
+ LinkTypeID: r.iLinkType,
597
+ LinkTypeName: r.cTypeName,
598
+ LinkTypeDesc: r.LinkTypeDesc,
599
+ LinkURL: r.LinkURL,
600
+ IconName: r.IconName,
601
+ }));
602
+ }
603
+ if (want("operateTypes")) {
604
+ result.operateTypes = await enumGroup("operateTypes", T("operateType"), [
605
+ "iOperateType",
606
+ "cOperateName",
607
+ "OperateTypeDesc",
608
+ "OperateCode",
609
+ "IconName",
610
+ ], " ORDER BY [iOperateType]", (r) => ({
611
+ OperateTypeID: r.iOperateType,
612
+ OperateTypeName: r.cOperateName,
613
+ OperateTypeDesc: r.OperateTypeDesc,
614
+ OperateCode: r.OperateCode,
615
+ IconName: r.IconName,
616
+ }));
617
+ }
618
+ if (want("functionKinds")) {
619
+ result.functionKinds = await enumGroup("functionKinds", T("functionKind"), ["KindID", "KindName", "KindDesc"], " ORDER BY [KindID]");
620
+ }
621
+ if (want("reportSchemes")) {
622
+ const tableRef = T("userReportFind");
623
+ if (!(await tableExists(tableRef))) {
624
+ warnings.push({
625
+ module: "reportSchemes",
626
+ message: `环境库缺少 ${tableRef}`,
627
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
628
+ fallback: "get_capabilities",
629
+ });
630
+ groupMeta.push({
631
+ type: "reportSchemes",
632
+ available: false,
633
+ reason: `环境库缺少 ${tableRef}`,
634
+ });
635
+ result.reportSchemes = {
636
+ module: "reportSchemes",
637
+ available: false,
638
+ reason: `环境库缺少 ${tableRef}`,
639
+ };
640
+ }
641
+ else {
642
+ const info = await getTableInfo(tableRef);
643
+ const rs = await resolveSelect(tableRef, [
644
+ "XDID",
645
+ "FormID",
646
+ "UserID",
647
+ "FieldName",
648
+ "FieldDesc",
649
+ "iOrder",
650
+ "bVisiable",
651
+ "MakeDate",
652
+ ]);
653
+ const groups = [];
654
+ if (reportId)
655
+ groups.push({
656
+ filters: [{ col: "FormID", op: "eq", value: reportId }],
657
+ });
658
+ if (userId)
659
+ groups.push({ filters: [{ col: "UserID", op: "eq", value: userId }] });
660
+ const { where } = buildWhere(info.columns, groups);
661
+ const order = buildOrderBy(info.columns, ["FormID", "iOrder"]);
662
+ const items = await tryQuery("reportSchemes", `SELECT ${rs.select} FROM ${tableRef}${where}${order.orderBy}`, warnings);
663
+ groupMeta.push({
664
+ type: "reportSchemes",
665
+ available: true,
666
+ count: items.length,
667
+ });
668
+ result.reportSchemes = {
669
+ module: "reportSchemes",
670
+ available: true,
671
+ items,
672
+ missing: rs.missing,
673
+ };
674
+ }
675
+ }
676
+ return ok(envelope({
677
+ items: groupMeta,
678
+ pagination: null,
679
+ warnings,
680
+ ...result,
681
+ }));
682
+ });
683
+ // ============================================================
684
+ // get_sys_dict
685
+ // ============================================================
686
+ server.registerTool("get_sys_dict", {
687
+ description: WARNING +
688
+ "\n\n查询系统字典。传 typeName 返回该类型的键值列表+类型描述;不传则返回 SysDbType 分类树+各类型值数量统计。数据来源: XDWmsUser.dbo.SysDbType + SyDbValue",
689
+ inputSchema: {
690
+ typeName: z
691
+ .string()
692
+ .max(50)
693
+ .optional()
694
+ .describe("字典类型名,如 BsSex。不传返回类型分类树"),
695
+ keyword: z
696
+ .string()
697
+ .max(200)
698
+ .optional()
699
+ .describe("按编码值/名称模糊搜索"),
700
+ limit: z
701
+ .number()
702
+ .optional()
703
+ .default(100)
704
+ .describe("已废弃:本工具已改为 SQL 端分页,请用 page/pageSize"),
705
+ page: z.number().int().min(1).default(1).describe("页码"),
706
+ pageSize: z
707
+ .number()
708
+ .int()
709
+ .min(1)
710
+ .max(200)
711
+ .default(50)
712
+ .describe("每页数量"),
713
+ },
714
+ }, async ({ typeName, keyword, page = 1, pageSize = 50 }) => {
715
+ const warnings = [];
716
+ if (!userName) {
717
+ return ok(envelope({
718
+ items: [],
719
+ pagination: null,
720
+ warnings: [
721
+ {
722
+ module: "get_sys_dict",
723
+ message: "用户库未配置(DB_TARGET_DATABASES 为空)",
724
+ },
725
+ ],
726
+ }));
727
+ }
728
+ // 用户库字典表字段因环境而异(如 IsDelete),先取实际列再拼 SQL
729
+ const syDbValueRef = table(userName, "SyDbValue");
730
+ const sysDbTypeRef = table(userName, "SysDbType");
731
+ const syDbValueCols = await getTableColumns(syDbValueRef);
732
+ const sysDbTypeCols = await getTableColumns(sysDbTypeRef);
733
+ if (typeName) {
734
+ const vCols = pickCols(syDbValueCols, [
735
+ "XDID",
736
+ "ParentType",
737
+ "cCodeValue",
738
+ "cCodeName",
739
+ "Remark",
740
+ "Sort",
741
+ "ILineNo",
742
+ "ParentXDID",
743
+ "Level",
744
+ "LevelPath",
745
+ "IsStop",
746
+ ]);
747
+ if (vCols.length === 0) {
748
+ return ok(envelope({
749
+ items: [],
750
+ pagination: sqlPagination(page, pageSize, 0),
751
+ warnings: [
752
+ { module: "get_sys_dict", message: "SyDbValue 无可用字段" },
753
+ ],
754
+ typeName,
755
+ typeInfos: [],
756
+ totalValues: 0,
757
+ }));
758
+ }
759
+ const groups = [];
760
+ if (hasCol(syDbValueCols, "ParentType")) {
761
+ groups.push({
762
+ filters: [{ col: "ParentType", op: "eq", value: typeName }],
763
+ });
764
+ }
765
+ else {
766
+ warnings.push({
767
+ module: "get_sys_dict",
768
+ message: "SyDbValue 缺少 ParentType 列,typeName 过滤无法下推,返回的是全部字典值",
769
+ hint: "可用 describe_table 查看用户库 SyDbValue 的实际字段",
770
+ });
771
+ }
772
+ if (keyword) {
773
+ const filters = ["cCodeValue", "cCodeName", "Remark"]
774
+ .filter((c) => hasCol(syDbValueCols, c))
775
+ .map((c) => ({ col: c, op: "like", value: keyword }));
776
+ if (filters.length > 0)
777
+ groups.push({ logic: "or", filters });
778
+ }
779
+ if (hasCol(syDbValueCols, "IsDelete"))
780
+ groups.push({ filters: [{ col: "IsDelete", op: "eq", value: "0" }] });
781
+ const { where, skipped } = buildWhere(syDbValueCols, groups);
782
+ const order = buildOrderBy(syDbValueCols, [
783
+ { col: "Sort", expr: "ISNULL([Sort],9999)" },
784
+ { col: "ILineNo", expr: "[ILineNo]" },
785
+ ]);
786
+ if (skipped.length > 0) {
787
+ warnings.push({
788
+ module: "get_sys_dict",
789
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
790
+ });
791
+ }
792
+ const q = buildPagedQuery({
793
+ table: syDbValueRef,
794
+ cols: vCols,
795
+ where,
796
+ orderBy: order.orderBy,
797
+ page,
798
+ pageSize,
799
+ });
800
+ const cnt = await tryQuery("get_sys_dict.values.count", q.countSql, warnings);
801
+ const total = Number(cnt[0]?.Total ?? 0);
802
+ const values = total > 0 ? await tryQuery("get_sys_dict.values", q.sql, warnings) : [];
803
+ const tCols = pickCols(sysDbTypeCols, [
804
+ "TypeName",
805
+ "TypeDesc",
806
+ "ParentType",
807
+ ]);
808
+ const typeInfos = tCols.length > 0
809
+ ? await tryQuery("get_sys_dict.typeInfos", `SELECT ${tCols.map((c) => `[${c}]`).join(", ")} FROM ${sysDbTypeRef} WHERE [TypeName]='${escapeSql(typeName)}'`, warnings)
810
+ : [];
811
+ return ok(envelope({
812
+ items: values,
813
+ pagination: sqlPagination(page, pageSize, total),
814
+ warnings,
815
+ typeName,
816
+ typeInfos,
817
+ totalValues: total,
818
+ }));
819
+ }
820
+ // 无 typeName:返回类型分类树 + 各类型值数量
821
+ const tCols = pickCols(sysDbTypeCols, [
822
+ "TypeName",
823
+ "TypeDesc",
824
+ "ParentType",
825
+ "bUser",
826
+ ]);
827
+ if (tCols.length === 0) {
828
+ return ok(envelope({
829
+ items: [],
830
+ pagination: sqlPagination(page, pageSize, 0),
831
+ warnings: [
832
+ { module: "get_sys_dict", message: "SysDbType 无可用字段" },
833
+ ],
834
+ totalTypes: 0,
835
+ }));
836
+ }
837
+ const groups = [];
838
+ if (keyword) {
839
+ const filters = ["TypeName", "TypeDesc"]
840
+ .filter((c) => hasCol(sysDbTypeCols, c))
841
+ .map((c) => ({ col: c, op: "like", value: keyword }));
842
+ if (filters.length > 0)
843
+ groups.push({ logic: "or", filters });
844
+ }
845
+ if (hasCol(sysDbTypeCols, "IsDelete"))
846
+ groups.push({ filters: [{ col: "IsDelete", op: "eq", value: "0" }] });
847
+ const { where } = buildWhere(sysDbTypeCols, groups);
848
+ const order = buildOrderBy(sysDbTypeCols, [
849
+ { col: "ParentType", expr: "ISNULL([ParentType],'')" },
850
+ { col: "TypeName", expr: "[TypeName]" },
851
+ ]);
852
+ const q = buildPagedQuery({
853
+ table: sysDbTypeRef,
854
+ cols: tCols,
855
+ where,
856
+ orderBy: order.orderBy,
857
+ page,
858
+ pageSize,
859
+ });
860
+ const cnt = await tryQuery("get_sys_dict.types.count", q.countSql, warnings);
861
+ const total = Number(cnt[0]?.Total ?? 0);
862
+ const types = total > 0 ? await tryQuery("get_sys_dict.types", q.sql, warnings) : [];
863
+ const typeNames = hasCol(sysDbTypeCols, "TypeName")
864
+ ? types
865
+ .map((t) => `'${escapeSql(String(t.TypeName))}'`)
866
+ .join(",")
867
+ : "";
868
+ let valueCounts = [];
869
+ if (typeNames && hasCol(syDbValueCols, "ParentType")) {
870
+ let countSql = `SELECT [ParentType], COUNT(*) AS valueCount FROM ${syDbValueRef} WHERE [ParentType] IN (${typeNames})`;
871
+ if (hasCol(syDbValueCols, "IsDelete"))
872
+ countSql += " AND [IsDelete]=0";
873
+ countSql += " GROUP BY [ParentType]";
874
+ valueCounts = await tryQuery("get_sys_dict.valueCounts", countSql, warnings);
875
+ }
876
+ return ok(envelope({
877
+ items: types,
878
+ pagination: sqlPagination(page, pageSize, total),
879
+ warnings,
880
+ valueCounts,
881
+ totalTypes: total,
882
+ }));
883
+ });
884
+ // ============================================================
885
+ // get_db_object_source
886
+ // ============================================================
887
+ server.registerTool("get_db_object_source", {
888
+ description: WARNING +
889
+ "\n\n查询用户库的视图/存储过程/函数完整定义(CREATE SQL 正文),支持按类型过滤、按名称精确或模糊搜索,SQL 端分页。数据来源: 用户库 sys.objects + sys.sql_modules。",
890
+ inputSchema: {
891
+ objectType: z
892
+ .enum(["view", "procedure", "function", "all"])
893
+ .optional()
894
+ .default("all")
895
+ .describe("view=视图 / procedure=存储过程 / function=函数(标量+表值) / all=全部"),
896
+ name: z
897
+ .string()
898
+ .max(128)
899
+ .optional()
900
+ .describe("对象精确名称(如 v_SaleOrder)"),
901
+ keyword: z
902
+ .string()
903
+ .max(200)
904
+ .optional()
905
+ .describe("按对象名模糊搜索(% _ [ ] 按普通字符处理)"),
906
+ includeDefinition: z
907
+ .boolean()
908
+ .optional()
909
+ .describe("是否返回 CREATE SQL 正文(默认:仅当传了 name 精确查询时为 true,列表模式为 false,避免输出被截断)"),
910
+ page: z.number().int().min(1).default(1).describe("页码"),
911
+ pageSize: z
912
+ .number()
913
+ .int()
914
+ .min(1)
915
+ .max(200)
916
+ .default(50)
917
+ .describe("每页数量"),
918
+ },
919
+ }, async ({ objectType, name, keyword, includeDefinition, page = 1, pageSize = 50, }) => {
920
+ const warnings = [];
921
+ if (!userName) {
922
+ return ok(envelope({
923
+ items: [],
924
+ pagination: null,
925
+ warnings: [
926
+ {
927
+ module: "get_db_object_source",
928
+ message: "用户库未配置(DB_TARGET_DATABASES 为空)",
929
+ },
930
+ ],
931
+ }));
932
+ }
933
+ const typeMap = {
934
+ view: ["V"],
935
+ procedure: ["P"],
936
+ function: ["FN", "IF", "TF"],
937
+ };
938
+ const types = objectType === "all"
939
+ ? ["V", "P", "FN", "IF", "TF"]
940
+ : typeMap[objectType] ?? ["V", "P", "FN", "IF", "TF"];
941
+ const typeList = types.map((t) => `'${t}'`).join(",");
942
+ const withDefinition = includeDefinition === true || (!!name && includeDefinition !== false);
943
+ let where = `WHERE o.is_ms_shipped = 0 AND o.type IN (${typeList})`;
944
+ if (name)
945
+ where += ` AND o.name = N'${escapeSql(name)}'`;
946
+ if (keyword)
947
+ where += ` AND o.name LIKE N'%${escapeLike(keyword)}%' ESCAPE '\\'`;
948
+ const base = `FROM [${userName}].sys.objects o INNER JOIN [${userName}].sys.sql_modules m ON o.object_id = m.object_id ${where}`;
949
+ const countRows = await tryQuery("get_db_object_source.count", `SELECT COUNT(*) AS Total ${base}`, warnings);
950
+ const total = Number(countRows[0]?.Total ?? 0);
951
+ const offset = (page - 1) * pageSize;
952
+ const metaCols = "o.name AS ObjectName, SCHEMA_NAME(o.schema_id) AS SchemaName, o.type AS ObjectType, o.type_desc AS ObjectTypeDesc, o.create_date AS CreateDate, o.modify_date AS ModifyDate, LEN(m.definition) AS DefinitionLength";
953
+ const sql = `SELECT ${metaCols}${withDefinition ? ", m.definition AS Definition" : ""} ${base} ORDER BY o.type, SCHEMA_NAME(o.schema_id), o.name OFFSET ${offset} ROWS FETCH NEXT ${pageSize} ROWS ONLY`;
954
+ const items = total > 0
955
+ ? await tryQuery("get_db_object_source", sql, warnings)
956
+ : [];
957
+ return ok(envelope({
958
+ items,
959
+ pagination: sqlPagination(page, pageSize, total),
960
+ warnings,
961
+ objectType,
962
+ userDb: userName,
963
+ totalObjects: total,
964
+ definitionIncluded: withDefinition,
965
+ }));
966
+ });
967
+ // ============================================================
968
+ // get_capabilities(能力自描述 — 探测本环境实际支持的元数据表)
969
+ // ============================================================
970
+ server.registerTool("get_capabilities", {
971
+ description: WARNING +
972
+ "\n\n【能力自描述】探测当前环境库实际支持的元数据表与各工具可用性。首次诊断、或遇到「缺表/缺列」时建议先调用本工具,避免盲目试错。",
973
+ inputSchema: {
974
+ refresh: z
975
+ .boolean()
976
+ .optional()
977
+ .default(false)
978
+ .describe("清空 schema 缓存后重新探测"),
979
+ },
980
+ }, async ({ refresh }) => {
981
+ const warnings = [];
982
+ if (refresh)
983
+ clearSchemaCache();
984
+ const probedTables = {};
985
+ const keys = Object.keys(tableNames);
986
+ await Promise.all(keys.map(async (k) => {
987
+ probedTables[tableNames[k]] = await tableExists(T(k));
988
+ }));
989
+ let userDbReachable = false;
990
+ if (userName) {
991
+ const rows = await tryQuery("get_capabilities.userDb", `SELECT DB_ID('${userName.replace(/'/g, "''")}') AS id`, warnings);
992
+ userDbReachable = Number(rows[0]?.id ?? 0) > 0;
993
+ }
994
+ // 用户库表探测(如 get_sys_dict 依赖的 SysDbType/SyDbValue 在用户库)
995
+ const userTables = {};
996
+ if (userName && userDbReachable) {
997
+ await Promise.all(USER_DB_PROBE_TABLES.map(async (t) => {
998
+ userTables[t] = await userTableExists(userName, t);
999
+ }));
1000
+ }
1001
+ const items = computeToolAvailability({
1002
+ tables: probedTables,
1003
+ userTables,
1004
+ userDbConfigured: !!userName,
1005
+ userDbReachable,
1006
+ });
1007
+ const presentTables = Object.entries(probedTables)
1008
+ .filter(([, v]) => v)
1009
+ .map(([k]) => k);
1010
+ return ok(envelope({
1011
+ items,
1012
+ pagination: null,
1013
+ warnings,
1014
+ env: {
1015
+ serverDatabase: envDb(),
1016
+ userDatabase: userName || null,
1017
+ userDatabaseReachable: userDbReachable,
1018
+ presentTableCount: presentTables.length,
1019
+ probedTableCount: keys.length,
1020
+ },
1021
+ presentTables,
1022
+ probedTables,
1023
+ }));
1024
+ });
1025
+ // ============================================================
1026
+ // get_view_lineage(视图/对象血缘 — 递归 CTE)
1027
+ // ============================================================
1028
+ server.registerTool("get_view_lineage", {
1029
+ description: WARNING +
1030
+ "\n\n追踪视图/对象的依赖血缘(基于用户库 sys.sql_expression_dependencies 递归展开)。返回 level/objectName/objectType/parent 的依赖链,便于理解视图由哪些表/视图构成。",
1031
+ inputSchema: {
1032
+ name: z.string().max(128).describe("视图/对象名,如 vwAboutOut"),
1033
+ maxDepth: z
1034
+ .number()
1035
+ .int()
1036
+ .min(1)
1037
+ .max(10)
1038
+ .default(5)
1039
+ .describe("递归深度(1..10)"),
1040
+ },
1041
+ }, async ({ name, maxDepth = 5 }) => {
1042
+ const warnings = [];
1043
+ if (!userName) {
1044
+ return ok(envelope({
1045
+ items: [],
1046
+ pagination: null,
1047
+ warnings: [
1048
+ {
1049
+ module: "get_view_lineage",
1050
+ message: "用户库未配置(DB_TARGET_DATABASES 为空)",
1051
+ },
1052
+ ],
1053
+ }));
1054
+ }
1055
+ const safeName = name.replace(/[^a-zA-Z0-9_]/g, "");
1056
+ if (!safeName) {
1057
+ return ok(envelope({
1058
+ items: [],
1059
+ pagination: null,
1060
+ warnings: [
1061
+ {
1062
+ module: "get_view_lineage",
1063
+ message: `对象名 "${name}" 非法(仅允许字母/数字/下划线)`,
1064
+ },
1065
+ ],
1066
+ }));
1067
+ }
1068
+ const sql = buildViewLineageSql({
1069
+ db: userName,
1070
+ name: safeName,
1071
+ maxDepth,
1072
+ });
1073
+ const rows = await tryQuery("get_view_lineage", sql, warnings);
1074
+ const items = rows.map((r) => ({
1075
+ level: Number(r.lvl),
1076
+ objectName: r.objectName,
1077
+ objectType: r.objectType,
1078
+ parent: r.parentName,
1079
+ }));
1080
+ if (items.length === 0 && warnings.length === 0) {
1081
+ warnings.push({
1082
+ module: "get_view_lineage",
1083
+ message: `未找到 "${safeName}" 的依赖,或该对象没有依赖记录`,
1084
+ hint: "可用 get_db_object_source(name=...) 确认对象是否存在",
1085
+ fallback: "get_db_object_source",
1086
+ });
1087
+ }
1088
+ const tree = {};
1089
+ for (const it of items)
1090
+ (tree[it.level] ??= []).push(it);
1091
+ return ok(envelope({
1092
+ items,
1093
+ pagination: topNPagination(500, items.length),
1094
+ warnings,
1095
+ root: safeName,
1096
+ userDb: userName,
1097
+ maxDepth,
1098
+ tree,
1099
+ }));
1100
+ });
1101
+ }
1102
+ //# sourceMappingURL=system-tools.js.map