befly 3.21.1 → 3.21.2

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/README.md CHANGED
@@ -9,3 +9,10 @@
9
9
  "bundle:min": "bun build ./index.js --outfile ./befly.min.js --target bun --format esm --packages bundle --minify",
10
10
  "build": "rimraf befly.js befly.min.js && bun run bundle && bun run bundle:min"
11
11
  ```
12
+
13
+ 我们把要求和实现对齐一下,要符合如下规范:
14
+
15
+ 1. 单表查询,不传fields或fields为空数组,表示查询所有字段。
16
+ 2. 单表查询,fields指定查询字段,表示明确只查询这些字段。
17
+ 3. 单表查询,fields用!感叹号开头,表示排除不查询这些字段。
18
+ 4. 单表查询,不存在明确查询字段又排除某些字段的混合情况。
package/apis/dict/all.js CHANGED
@@ -12,7 +12,7 @@ export default {
12
12
  const result = await befly.mysql.getAll({
13
13
  table: ["beflyDict", "beflyDictType"],
14
14
  leftJoin: ["beflyDict.typeCode beflyDictType.code"],
15
- fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name AS typeName"],
15
+ fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name typeName"],
16
16
  orderBy: ["beflyDict.sort#ASC", "beflyDict.id#ASC"]
17
17
  });
18
18
 
@@ -11,7 +11,7 @@ export default {
11
11
  const dict = await befly.mysql.getOne({
12
12
  table: ["beflyDict", "beflyDictType"],
13
13
  leftJoin: ["beflyDict.typeCode beflyDictType.code"],
14
- fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name AS typeName"],
14
+ fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name typeName"],
15
15
  where: { "beflyDict.id": ctx.body.id }
16
16
  });
17
17
 
package/apis/dict/list.js CHANGED
@@ -16,7 +16,7 @@ export default {
16
16
  const result = await befly.mysql.getList({
17
17
  table: ["beflyDict", "beflyDictType"],
18
18
  leftJoin: ["beflyDict.typeCode beflyDictType.code"],
19
- fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name AS typeName"],
19
+ fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name typeName"],
20
20
  where: {
21
21
  "beflyDict.typeCode": ctx.body.typeCode
22
22
  },
package/checks/config.js CHANGED
@@ -29,9 +29,7 @@ const configSchema = z
29
29
  excludeFields: z.array(noTrimString),
30
30
  dir: noTrimString,
31
31
  console: boolIntSchema,
32
- maxSize: z.int().min(1),
33
- maxStringLen: z.int().min(1),
34
- maxArrayItems: z.int().min(1)
32
+ maxSize: z.int().min(1)
35
33
  })
36
34
  .strict(),
37
35
 
package/checks/table.js CHANGED
@@ -76,20 +76,7 @@ const fieldDefSchema = z
76
76
  });
77
77
 
78
78
  const tableContentSchema = z.record(z.string().regex(lowerCamelRegex), fieldDefSchema);
79
-
80
- const tableItemSchema = z
81
- .object({
82
- source: noTrimString.min(1),
83
- type: noTrimString.min(1),
84
- filePath: noTrimString.min(1),
85
- relativePath: noTrimString.min(1),
86
- apiPath: noTrimString.min(1),
87
- fileName: z.string().min(1).regex(lowerCamelRegex),
88
- fieldsDef: tableContentSchema
89
- })
90
- .strict();
91
-
92
- const tableListSchema = z.array(tableItemSchema);
79
+ const tableRegistrySchema = z.record(z.string().regex(lowerCamelRegex), tableContentSchema);
93
80
 
94
81
  /**
95
82
  * 检查表定义文件
@@ -99,7 +86,7 @@ export async function checkTable(tables) {
99
86
  // 收集所有表文件
100
87
  let hasError = false;
101
88
 
102
- const schemaResult = tableListSchema.safeParse(tables);
89
+ const schemaResult = tableRegistrySchema.safeParse(tables);
103
90
  if (!schemaResult.success) {
104
91
  const errors = formatZodIssues(schemaResult.error.issues, { items: tables, itemLabel: "table" });
105
92
  Logger.warn("表结构校验失败", { errors: errors }, false);
@@ -14,9 +14,7 @@
14
14
  "excludeFields": ["password", "token", "secret"],
15
15
  "dir": "./logs",
16
16
  "console": 1,
17
- "maxSize": 20,
18
- "maxStringLen": 100,
19
- "maxArrayItems": 100
17
+ "maxSize": 20
20
18
  },
21
19
 
22
20
  "mysql": {
package/index.js CHANGED
@@ -26,13 +26,14 @@ import { syncApi } from "./sync/api.js";
26
26
  import { syncCache } from "./sync/cache.js";
27
27
  import { syncDev } from "./sync/dev.js";
28
28
  import { syncMenu } from "./sync/menu.js";
29
- import { syncDbApply, syncDbCheck } from "./scripts/syncDb/index.js";
30
29
  // 工具
31
30
  import { calcPerfTime } from "./utils/calcPerfTime.js";
32
31
  import { scanSources } from "./utils/scanSources.js";
33
32
  import { isPrimaryProcess } from "./utils/is.js";
34
33
  import { deepMerge } from "./utils/deepMerge.js";
35
34
 
35
+ export { syncDbApply as syncDb } from "./scripts/syncDb/index.js";
36
+
36
37
  function prefixMenuPaths(menus, prefix) {
37
38
  const output = [];
38
39
  for (const menu of menus) {
@@ -94,14 +95,6 @@ async function ensureSyncPrerequisites(ctx) {
94
95
  }
95
96
  }
96
97
 
97
- export async function dbCheck(mysqlConfig) {
98
- return syncDbCheck(mysqlConfig);
99
- }
100
-
101
- export async function dbApply(mysqlConfig) {
102
- return syncDbApply(mysqlConfig);
103
- }
104
-
105
98
  export async function createBefly(env = {}, config = {}, menus = []) {
106
99
  const mergedConfig = deepMerge(beflyConfig, config);
107
100
  const mergedMenus = deepMerge(prefixMenuPaths(beflyMenus, "core"), menus);
@@ -127,6 +120,7 @@ export async function createBefly(env = {}, config = {}, menus = []) {
127
120
  return new Befly({
128
121
  env: env,
129
122
  config: mergedConfig,
123
+ tables: tables,
130
124
  menus: mergedMenus,
131
125
  apis: apis,
132
126
  hooks: hooks,
@@ -143,7 +137,8 @@ export class Befly {
143
137
  constructor(init = {}) {
144
138
  this.context = {
145
139
  env: init.env || {},
146
- config: init.config || {}
140
+ config: init.config || {},
141
+ tables: init.tables || {}
147
142
  };
148
143
  this.menus = Array.isArray(init.menus) ? init.menus : [];
149
144
  this.hooks = Array.isArray(init.hooks) ? init.hooks : [];
package/lib/dbHelper.js CHANGED
@@ -614,7 +614,7 @@ function validateDataReadOptions(options, label) {
614
614
  validateQueryOptions(options, label);
615
615
 
616
616
  const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
617
- const classifiedFields = hasLeftJoin ? validateExplicitLeftJoinFields(options.fields) : validateExplicitReadFields(options.fields);
617
+ const classifiedFields = hasLeftJoin ? validateExplicitLeftJoinFields(options.fields, options.table[0]) : validateExplicitReadFields(options.fields);
618
618
 
619
619
  return {
620
620
  hasLeftJoin: hasLeftJoin,
@@ -661,6 +661,30 @@ class DbHelper {
661
661
  this.sql = options.sql || null;
662
662
  this.isTransaction = options.isTransaction === true;
663
663
  this.beflyMode = options.beflyMode === "manual" ? "manual" : "auto";
664
+ this.tables = isPlainObject(options.tables) ? options.tables : {};
665
+ }
666
+
667
+ getTableDef(table) {
668
+ const tableName = parseTableRef(table).table;
669
+
670
+ if (!/^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/.test(tableName)) {
671
+ throw new Error(`表名必须使用小驼峰写法 (table: ${tableName})`, {
672
+ cause: null,
673
+ code: "validation"
674
+ });
675
+ }
676
+
677
+ const tableInfo = this.tables[tableName];
678
+
679
+ if (!isPlainObject(tableInfo)) {
680
+ const sourceLabel = tableName.startsWith("befly") ? "packages/core/tables" : "app tables";
681
+ throw new Error(`表 ${tableName} 缺少本地 tables 定义,请检查 ${sourceLabel}`, {
682
+ cause: null,
683
+ code: "runtime"
684
+ });
685
+ }
686
+
687
+ return tableInfo;
664
688
  }
665
689
 
666
690
  async execute(sql, params) {
@@ -815,12 +839,45 @@ class DbHelper {
815
839
  return normalizeBigIntValues(deserializedList);
816
840
  }
817
841
 
818
- _joinFieldsToSnake(classifiedFields) {
819
- if (classifiedFields.type === "include") {
820
- return classifiedFields.fields.map((field) => processJoinField(field));
842
+ async _joinFieldsToSnake(mainTableRef, classifiedFields) {
843
+ if (classifiedFields.type !== "join") {
844
+ return [];
845
+ }
846
+
847
+ const processedFields = [];
848
+ const seenFields = new Set();
849
+ const pushField = (field) => {
850
+ if (seenFields.has(field)) {
851
+ return;
852
+ }
853
+ seenFields.add(field);
854
+ processedFields.push(field);
855
+ };
856
+
857
+ if (classifiedFields.mainAllIncluded) {
858
+ const mainColumns = await this.getTableColumns(mainTableRef);
859
+ const excludeFieldSet = new Set(classifiedFields.mainExcludeFields.map((field) => snakeCase(field)));
860
+
861
+ for (const column of mainColumns) {
862
+ if (excludeFieldSet.has(column)) {
863
+ continue;
864
+ }
865
+ pushField(`${classifiedFields.mainQualifier}.${column}`);
866
+ }
867
+ }
868
+
869
+ for (const field of classifiedFields.includeFields) {
870
+ pushField(processJoinField(field));
821
871
  }
822
872
 
823
- return [];
873
+ if (processedFields.length === 0) {
874
+ throw new Error(`排除字段后没有剩余字段可查询。表: ${mainTableRef}, 排除: ${classifiedFields.mainExcludeFields.join(", ")}`, {
875
+ cause: null,
876
+ code: "validation"
877
+ });
878
+ }
879
+
880
+ return processedFields;
824
881
  }
825
882
 
826
883
  _cleanAndSnakeAndSerializeWriteData(data, excludeValues = [null, undefined]) {
@@ -874,9 +931,9 @@ class DbHelper {
874
931
  return this._prepareWriteUserData(options.data, options.allowState);
875
932
  }
876
933
 
877
- normalizeLeftJoinOptions(options, cleanWhere, classifiedFields) {
878
- const processedFields = this._joinFieldsToSnake(classifiedFields);
934
+ async normalizeLeftJoinOptions(options, cleanWhere, classifiedFields) {
879
935
  const mainTableRef = options.table[0];
936
+ const processedFields = await this._joinFieldsToSnake(mainTableRef, classifiedFields);
880
937
  const joinTableRefs = options.table.slice(1);
881
938
  const normalizedTableRef = normalizeTableRef(mainTableRef);
882
939
  const mainQualifier = getJoinMainQualifier(mainTableRef);
@@ -916,7 +973,7 @@ class DbHelper {
916
973
  async normalizeSingleTableOptions(options, cleanWhere, classifiedFields) {
917
974
  const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
918
975
  const snakeTable = snakeCase(tableRef);
919
- const processedFields = await fieldsToSnake(snakeTable, classifiedFields, this.getTableColumns.bind(this));
976
+ const processedFields = await fieldsToSnake(tableRef, classifiedFields, this.getTableColumns.bind(this));
920
977
 
921
978
  return {
922
979
  table: snakeTable,
@@ -967,29 +1024,17 @@ class DbHelper {
967
1024
  }
968
1025
 
969
1026
  async getTableColumns(table) {
970
- const parsed = parseTableRef(table);
971
- const schemaPart = parsed.schema ? `${quoteIdentMySql(snakeCase(parsed.schema))}.` : "";
972
- const quotedTable = quoteIdentMySql(snakeCase(parsed.table));
973
- const executeRes = await this.execute(`SHOW COLUMNS FROM ${schemaPart}${quotedTable}`, []);
974
- const result = executeRes.data;
1027
+ const tableInfo = this.getTableDef(table);
1028
+ const fieldNames = Object.keys(tableInfo);
975
1029
 
976
- if (!result || result.length === 0) {
977
- throw new Error(`表 ${table} 不存在或没有字段`, {
1030
+ if (fieldNames.length === 0) {
1031
+ throw new Error(`表 ${table} 的本地 tables 定义为空或没有字段`, {
978
1032
  cause: null,
979
1033
  code: "runtime"
980
1034
  });
981
1035
  }
982
1036
 
983
- const columnNames = [];
984
- for (const row of result) {
985
- const record = row;
986
- const name = record.Field;
987
- if (isNonEmptyString(name)) {
988
- columnNames.push(name);
989
- }
990
- }
991
-
992
- return columnNames;
1037
+ return fieldNames.map((fieldName) => snakeCase(fieldName));
993
1038
  }
994
1039
 
995
1040
  applyLeftJoins(builder, leftJoins) {
@@ -1014,7 +1059,7 @@ class DbHelper {
1014
1059
 
1015
1060
  async buildPreparedReadOptions(options, cleanWhere, validation) {
1016
1061
  if (validation.hasLeftJoin) {
1017
- return this.normalizeLeftJoinOptions(options, cleanWhere, validation.classifiedFields);
1062
+ return await this.normalizeLeftJoinOptions(options, cleanWhere, validation.classifiedFields);
1018
1063
  }
1019
1064
 
1020
1065
  return await this.normalizeSingleTableOptions(options, cleanWhere, validation.classifiedFields);
@@ -1600,7 +1645,8 @@ class DbHelper {
1600
1645
  dbName: this.dbName,
1601
1646
  sql: tx,
1602
1647
  isTransaction: true,
1603
- beflyMode: this.beflyMode
1648
+ beflyMode: this.beflyMode,
1649
+ tables: this.tables
1604
1650
  });
1605
1651
  const result = await callback(trans);
1606
1652
  if (result?.code !== undefined && result.code !== 0) {
package/lib/dbUtil.js CHANGED
@@ -299,33 +299,113 @@ export function validateAndClassifyFields(fields) {
299
299
  }
300
300
 
301
301
  export function validateExplicitReadFields(fields) {
302
- const classified = validateAndClassifyFields(fields);
303
- if (classified.type === "all") {
304
- throw new Error("查询必须显式传 fields,不支持空 fields 或查询全部字段", {
305
- cause: null,
306
- code: "validation"
307
- });
302
+ return validateAndClassifyFields(fields);
303
+ }
304
+
305
+ function getTableQualifier(tableRef) {
306
+ const parsed = parseTableRef(tableRef);
307
+ if (parsed.alias) {
308
+ return snakeCase(parsed.alias);
308
309
  }
309
310
 
310
- return classified;
311
+ return snakeCase(parsed.table);
311
312
  }
312
313
 
313
- export function validateExplicitLeftJoinFields(fields) {
314
- const classified = validateAndClassifyFields(fields);
315
- if (classified.type === "all") {
314
+ export function validateExplicitLeftJoinFields(fields, mainTableRef) {
315
+ if (!fields || fields.length === 0) {
316
316
  throw new Error("leftJoin 查询必须显式传 fields,不支持空 fields 或查询全部字段", {
317
317
  cause: null,
318
318
  code: "validation"
319
319
  });
320
320
  }
321
- if (classified.type === "exclude") {
322
- throw new Error("leftJoin 查询不支持排除字段,必须显式指定主表/从表字段", {
321
+
322
+ const mainQualifier = getTableQualifier(mainTableRef);
323
+ const mainWildcard = `${mainQualifier}.*`;
324
+ const includeFields = [];
325
+ const mainExcludeFields = [];
326
+ let mainAllIncluded = false;
327
+
328
+ for (const rawField of fields) {
329
+ if (!isNonEmptyString(rawField)) {
330
+ throw new Error("fields 不能包含空字符串或无效值", {
331
+ cause: null,
332
+ code: "validation"
333
+ });
334
+ }
335
+
336
+ const trimmed = rawField.trim();
337
+ if (trimmed === "*") {
338
+ throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段", {
339
+ cause: null,
340
+ code: "validation"
341
+ });
342
+ }
343
+
344
+ if (trimmed.startsWith("!")) {
345
+ const fieldPart = trimmed.substring(1).trim();
346
+ if (!isNonEmptyString(fieldPart)) {
347
+ throw new Error("fields 不能包含空字符串或无效值", {
348
+ cause: null,
349
+ code: "validation"
350
+ });
351
+ }
352
+ if (/\s/.test(fieldPart)) {
353
+ throw new Error("leftJoin 排除字段不支持别名", {
354
+ cause: null,
355
+ code: "validation"
356
+ });
357
+ }
358
+
359
+ assertNoExprField(fieldPart);
360
+ const normalizedField = normalizeQualifierField(fieldPart);
361
+ if (normalizedField === mainWildcard) {
362
+ throw new Error("leftJoin 主表排除字段不能使用 *", {
363
+ cause: null,
364
+ code: "validation"
365
+ });
366
+ }
367
+ if (!normalizedField.startsWith(`${mainQualifier}.`)) {
368
+ throw new Error("leftJoin 排除字段仅支持主表字段,请使用主表限定符", {
369
+ cause: null,
370
+ code: "validation"
371
+ });
372
+ }
373
+
374
+ mainExcludeFields.push(normalizedField.substring(mainQualifier.length + 1));
375
+ continue;
376
+ }
377
+
378
+ assertNoExprField(trimmed);
379
+ const aliasParts = parseFieldAliasParts(trimmed);
380
+ const normalizedField = normalizeQualifierField(aliasParts ? aliasParts.fieldPart.trim() : trimmed);
381
+ if (normalizedField === mainWildcard) {
382
+ if (aliasParts) {
383
+ throw new Error("leftJoin 主表全字段不支持别名", {
384
+ cause: null,
385
+ code: "validation"
386
+ });
387
+ }
388
+ mainAllIncluded = true;
389
+ continue;
390
+ }
391
+
392
+ includeFields.push(trimmed);
393
+ }
394
+
395
+ if (mainExcludeFields.length > 0 && !mainAllIncluded) {
396
+ throw new Error("leftJoin 使用主表排除字段时,必须同时传主表限定符.*", {
323
397
  cause: null,
324
398
  code: "validation"
325
399
  });
326
400
  }
327
401
 
328
- return classified;
402
+ return {
403
+ type: "join",
404
+ includeFields: includeFields,
405
+ mainAllIncluded: mainAllIncluded,
406
+ mainExcludeFields: mainExcludeFields,
407
+ mainQualifier: mainQualifier
408
+ };
329
409
  }
330
410
 
331
411
  function isQuotedIdentPaired(value) {
@@ -390,6 +470,41 @@ export function startsWithQuote(value) {
390
470
  return startsWithIdentifierQuote(value);
391
471
  }
392
472
 
473
+ function parseFieldAliasParts(field) {
474
+ if (!isString(field)) {
475
+ return null;
476
+ }
477
+
478
+ const trimmed = field.trim();
479
+ if (!trimmed) {
480
+ return null;
481
+ }
482
+
483
+ if (/\s+AS\s+/i.test(trimmed)) {
484
+ throw new Error(`字段别名不支持 AS,请使用单个空格分隔字段与别名 (field: ${trimmed})`, {
485
+ cause: null,
486
+ code: "validation"
487
+ });
488
+ }
489
+
490
+ if (!/\s/.test(trimmed)) {
491
+ return null;
492
+ }
493
+
494
+ if (!/^[^\s]+ [^\s]+$/.test(trimmed)) {
495
+ throw new Error(`字段别名必须使用单个空格分隔字段与别名 (field: ${trimmed})`, {
496
+ cause: null,
497
+ code: "validation"
498
+ });
499
+ }
500
+
501
+ const parts = trimmed.split(" ");
502
+ return {
503
+ fieldPart: parts[0],
504
+ aliasPart: parts[1]
505
+ };
506
+ }
507
+
393
508
  export function escapeField(field, quoteIdent) {
394
509
  if (!isString(field)) {
395
510
  return field;
@@ -412,19 +527,10 @@ export function escapeField(field, quoteIdent) {
412
527
  });
413
528
  }
414
529
 
415
- if (trimmed.toUpperCase().includes(" AS ")) {
416
- const parts = trimmed.split(/\s+AS\s+/i);
417
- if (parts.length !== 2) {
418
- throw new Error(`字段格式非法,请使用简单字段名或安全引用,复杂表达式请使用 selectRaw/whereRaw (field: ${trimmed})`, {
419
- cause: null,
420
- code: "validation"
421
- });
422
- }
423
-
424
- const fieldPart = parts[0];
425
- const aliasPart = parts[1];
426
- const cleanFieldPart = fieldPart.trim();
427
- const cleanAliasPart = aliasPart.trim();
530
+ const aliasParts = parseFieldAliasParts(trimmed);
531
+ if (aliasParts) {
532
+ const cleanFieldPart = aliasParts.fieldPart.trim();
533
+ const cleanAliasPart = aliasParts.aliasPart.trim();
428
534
  if (!isQuotedIdent(cleanAliasPart)) {
429
535
  if (!SAFE_IDENTIFIER_RE.test(cleanAliasPart)) {
430
536
  throw new Error(`无效的字段别名: ${cleanAliasPart}`, {
@@ -433,7 +539,7 @@ export function escapeField(field, quoteIdent) {
433
539
  });
434
540
  }
435
541
  }
436
- return `${escapeField(cleanFieldPart, quoteIdent)} AS ${cleanAliasPart}`;
542
+ return `${escapeField(cleanFieldPart, quoteIdent)} ${cleanAliasPart}`;
437
543
  }
438
544
 
439
545
  if (trimmed.includes(".")) {
@@ -805,14 +911,9 @@ export function processJoinField(field) {
805
911
  return field;
806
912
  }
807
913
 
808
- if (field.toUpperCase().includes(" AS ")) {
809
- const parts = field.split(/\s+AS\s+/i);
810
- const fieldPart = parts[0];
811
- const aliasPart = parts[1];
812
- if (!isString(fieldPart) || !isString(aliasPart)) {
813
- return field;
814
- }
815
- return `${processJoinField(fieldPart.trim())} AS ${aliasPart.trim()}`;
914
+ const aliasParts = parseFieldAliasParts(field);
915
+ if (aliasParts) {
916
+ return `${processJoinField(aliasParts.fieldPart.trim())} ${aliasParts.aliasPart.trim()}`;
816
917
  }
817
918
 
818
919
  return normalizeQualifierField(field);
package/lib/logger.js CHANGED
@@ -19,28 +19,12 @@ const RUNTIME_LOG_DIR = nodePathResolve(INITIAL_CWD, "logs");
19
19
  const BUILTIN_SENSITIVE_KEYS = ["*password*", "pass", "pwd", "*token*", "access_token", "refresh_token", "accessToken", "refreshToken", "authorization", "cookie", "set-cookie", "*secret*", "apiKey", "api_key", "privateKey", "private_key"];
20
20
 
21
21
  let sanitizeOptions = {
22
- maxStringLen: 200,
23
- maxArrayItems: 500,
24
22
  sanitizeDepth: 5,
25
23
  sanitizeNodes: 5000,
26
24
  sanitizeObjectKeys: 500,
27
25
  sensitiveKeyMatcher: buildSensitiveKeyMatcher({ builtinPatterns: BUILTIN_SENSITIVE_KEYS, userPatterns: [] })
28
26
  };
29
27
 
30
- function buildSanitizeOptionsForWriteOptions(writeOptions) {
31
- if (!writeOptions || writeOptions.truncate !== false) return sanitizeOptions;
32
-
33
- // 仅关闭“截断”,仍保留敏感字段掩码与结构化清洗(避免泄露敏感信息)。
34
- return {
35
- maxStringLen: 200000,
36
- maxArrayItems: 5000,
37
- sanitizeDepth: sanitizeOptions.sanitizeDepth,
38
- sanitizeNodes: sanitizeOptions.sanitizeNodes,
39
- sanitizeObjectKeys: sanitizeOptions.sanitizeObjectKeys,
40
- sensitiveKeyMatcher: sanitizeOptions.sensitiveKeyMatcher
41
- };
42
- }
43
-
44
28
  let mockInstance = null;
45
29
 
46
30
  let appFileSink = null;
@@ -381,10 +365,8 @@ export function configure(cfg) {
381
365
  appFileSink = null;
382
366
  errorFileSink = null;
383
367
 
384
- // 运行时清洗/截断上限(可配置)
368
+ // 运行时清洗上限:只保留深度/节点/对象键数量保护。
385
369
  sanitizeOptions = {
386
- maxStringLen: normalizePositiveInt(config.maxStringLen, 200, 20, 200000),
387
- maxArrayItems: normalizePositiveInt(config.maxArrayItems, 500, 10, 5000),
388
370
  sanitizeDepth: normalizePositiveInt(config.sanitizeDepth, 5, 1, 10),
389
371
  sanitizeNodes: normalizePositiveInt(config.sanitizeNodes, 5000, 50, 20000),
390
372
  sanitizeObjectKeys: normalizePositiveInt(config.sanitizeObjectKeys, 500, 10, 5000),
@@ -476,16 +458,15 @@ function ensureSinksReady(kind) {
476
458
  return { fileSink: errorFileSink };
477
459
  }
478
460
 
479
- function writeJsonl(kind, level, record, options) {
461
+ function writeJsonl(kind, level, record) {
480
462
  const sinks = ensureSinksReady(kind);
481
463
  const time = Date.now();
482
- const effectiveSanitizeOptions = buildSanitizeOptionsForWriteOptions(options);
483
- const sanitizedRecord = sanitizeLogObject(record, effectiveSanitizeOptions);
464
+ const sanitizedRecord = sanitizeLogObject(record, sanitizeOptions);
484
465
  const fileLine = buildJsonLine(level, time, sanitizedRecord);
485
466
  sinks.fileSink.enqueue(fileLine);
486
467
  }
487
468
 
488
- // 对象清洗/脱敏/截断逻辑已下沉到 utils/loggerUtils.ts(减少 logger.ts 复杂度)。
469
+ // 对象清洗/脱敏逻辑已下沉到 utils/loggerUtils.js(减少 logger.js 复杂度)。
489
470
 
490
471
  // 日志仅接受 1 个入参(任意类型)。
491
472
  // - plain object({})直接作为 record
@@ -519,16 +500,15 @@ function toRecord(input) {
519
500
  }
520
501
  }
521
502
 
522
- function logWrite(level, input, options) {
503
+ function logWrite(level, input) {
523
504
  // debug!=1/true 则不记录 debug 日志
524
505
  if (level === "debug" && config.debug !== 1 && config.debug !== true) return;
525
506
 
526
507
  const record0 = toRecord(input);
527
508
 
528
- // 测试场景:mock logger 走同步写入,并在入口进行清洗/脱敏/截断控制
509
+ // 测试场景:mock logger 走同步写入,并在入口进行清洗/脱敏控制
529
510
  if (mockInstance) {
530
- const effective = buildSanitizeOptionsForWriteOptions(options);
531
- const sanitized = sanitizeLogObject(record0, effective);
511
+ const sanitized = sanitizeLogObject(record0, sanitizeOptions);
532
512
  if (level === "info") {
533
513
  mockInstance.info(sanitized);
534
514
  } else if (level === "warn") {
@@ -543,9 +523,9 @@ function logWrite(level, input, options) {
543
523
 
544
524
  if (level === "error") {
545
525
  // error 仅写入 error 文件
546
- writeJsonl("error", "error", record0, options);
526
+ writeJsonl("error", "error", record0);
547
527
  } else {
548
- writeJsonl("app", level, record0, options);
528
+ writeJsonl("app", level, record0);
549
529
  }
550
530
  }
551
531
 
@@ -553,41 +533,37 @@ function logWrite(level, input, options) {
553
533
  * 日志实例(延迟初始化)
554
534
  */
555
535
  export const Logger = {
556
- info(msg, data, truncate) {
557
- const options = truncate === false ? { truncate: false } : undefined;
536
+ info(msg, data) {
558
537
  if (isPlainObject(msg)) {
559
- logWrite("info", msg, options);
538
+ logWrite("info", msg);
560
539
  return;
561
540
  }
562
541
 
563
- logWrite("info", { msg: msg, data: data }, options);
542
+ logWrite("info", { msg: msg, data: data });
564
543
  },
565
- warn(msg, data, truncate) {
566
- const options = truncate === false ? { truncate: false } : undefined;
544
+ warn(msg, data) {
567
545
  if (isPlainObject(msg)) {
568
- logWrite("warn", msg, options);
546
+ logWrite("warn", msg);
569
547
  return;
570
548
  }
571
549
 
572
- logWrite("warn", { msg: msg, data: data }, options);
550
+ logWrite("warn", { msg: msg, data: data });
573
551
  },
574
- error(msg, err, data, truncate) {
575
- const options = truncate === false ? { truncate: false } : undefined;
552
+ error(msg, err, data) {
576
553
  if (isPlainObject(msg)) {
577
- logWrite("error", msg, options);
554
+ logWrite("error", msg);
578
555
  return;
579
556
  }
580
557
 
581
- logWrite("error", { msg: msg, err: err, data: data }, options);
558
+ logWrite("error", { msg: msg, err: err, data: data });
582
559
  },
583
- debug(msg, data, truncate) {
584
- const options = truncate === false ? { truncate: false } : undefined;
560
+ debug(msg, data) {
585
561
  if (isPlainObject(msg)) {
586
- logWrite("debug", msg, options);
562
+ logWrite("debug", msg);
587
563
  return;
588
564
  }
589
565
 
590
- logWrite("debug", { msg: msg, data: data }, options);
566
+ logWrite("debug", { msg: msg, data: data });
591
567
  },
592
568
  async flush() {
593
569
  await flush();