befly 3.13.10 → 3.14.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.
@@ -611,8 +611,14 @@ async function getTableColumnsRuntime(runtime, tableName) {
611
611
  let max = null;
612
612
  const m = /^(\w+)\s*\((\d+)\)/.exec(baseType);
613
613
  if (m) {
614
- baseType = m[1];
615
- max = Number(m[2]);
614
+ const base = m[1];
615
+ const maxText = m[2];
616
+ if (typeof base === "string") {
617
+ baseType = base;
618
+ }
619
+ if (typeof maxText === "string") {
620
+ max = Number(maxText);
621
+ }
616
622
  }
617
623
  columns[row.name] = {
618
624
  type: baseType.toLowerCase(),
@@ -649,9 +655,14 @@ async function getTableIndexesRuntime(runtime, tableName) {
649
655
  const q = getSyncTableIndexesQuery({ dialect: "mysql", table: tableName, dbName: runtime.dbName });
650
656
  const result = await runtime.db.unsafe(q.sql, q.params);
651
657
  for (const row of result.data) {
652
- if (!indexes[row.INDEX_NAME])
653
- indexes[row.INDEX_NAME] = [];
654
- indexes[row.INDEX_NAME].push(row.COLUMN_NAME);
658
+ const indexName = row.INDEX_NAME;
659
+ const current = indexes[indexName];
660
+ if (Array.isArray(current)) {
661
+ current.push(row.COLUMN_NAME);
662
+ }
663
+ else {
664
+ indexes[indexName] = [row.COLUMN_NAME];
665
+ }
655
666
  }
656
667
  }
657
668
  else if (runtime.dbDialect === "postgresql") {
@@ -660,7 +671,8 @@ async function getTableIndexesRuntime(runtime, tableName) {
660
671
  for (const row of result.data) {
661
672
  const m = /\(([^)]+)\)/.exec(row.indexdef);
662
673
  if (m) {
663
- const col = m[1].replace(/"/g, "").trim();
674
+ const colPart = m[1];
675
+ const col = typeof colPart === "string" ? colPart.replace(/"/g, "").trim() : "";
664
676
  indexes[row.indexname] = [col];
665
677
  }
666
678
  }
@@ -701,7 +713,8 @@ async function ensureDbVersion(dbDialect, db) {
701
713
  throw new Error("无法获取 MySQL 版本信息");
702
714
  }
703
715
  const version = r.data[0].version;
704
- const majorVersion = parseInt(String(version).split(".")[0], 10);
716
+ const majorPart = String(version).split(".")[0] || "0";
717
+ const majorVersion = parseInt(majorPart, 10);
705
718
  if (!Number.isFinite(majorVersion) || majorVersion < DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR) {
706
719
  throw new Error(`此脚本仅支持 MySQL ${DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR}.0+,当前版本: ${version}`);
707
720
  }
@@ -714,7 +727,8 @@ async function ensureDbVersion(dbDialect, db) {
714
727
  }
715
728
  const versionText = r.data[0].version;
716
729
  const m = /PostgreSQL\s+(\d+)/i.exec(versionText);
717
- const major = m ? parseInt(m[1], 10) : NaN;
730
+ const majorText = m ? m[1] : undefined;
731
+ const major = typeof majorText === "string" ? parseInt(majorText, 10) : NaN;
718
732
  if (!Number.isFinite(major) || major < DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR) {
719
733
  throw new Error(`此脚本要求 PostgreSQL >= ${DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR},当前: ${versionText}`);
720
734
  }
@@ -726,9 +740,12 @@ async function ensureDbVersion(dbDialect, db) {
726
740
  throw new Error("无法获取 SQLite 版本信息");
727
741
  }
728
742
  const version = r.data[0].version;
729
- const [maj, min, patch] = String(version)
743
+ const parts = String(version)
730
744
  .split(".")
731
745
  .map((v) => parseInt(v, 10) || 0);
746
+ const maj = parts[0] ?? 0;
747
+ const min = parts[1] ?? 0;
748
+ const patch = parts[2] ?? 0;
732
749
  const vnum = maj * 10000 + min * 100 + patch;
733
750
  if (!Number.isFinite(vnum) || vnum < DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION_NUM) {
734
751
  throw new Error(`此脚本要求 SQLite >= ${DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION},当前: ${version}`);
@@ -765,7 +782,11 @@ function compareFieldDefinition(dbDialect, existingColumn, fieldDef) {
765
782
  }
766
783
  }
767
784
  const typeMapping = getTypeMapping(dbDialect);
768
- const expectedType = typeMapping[fieldDef.type].toLowerCase();
785
+ const mapped = typeMapping[fieldDef.type];
786
+ if (typeof mapped !== "string") {
787
+ throw new Error(`未知字段类型映射:dialect=${dbDialect} type=${String(fieldDef.type)}`);
788
+ }
789
+ const expectedType = mapped.toLowerCase();
769
790
  const currentType = existingColumn.type.toLowerCase();
770
791
  if (currentType !== expectedType) {
771
792
  changes.push({
@@ -5,11 +5,11 @@
5
5
  /**
6
6
  * SQL 值类型
7
7
  */
8
- export type SqlValue = string | number | boolean | null | Date | Record<string, any> | any[];
8
+ export type SqlValue = string | number | boolean | null | Date | Record<string, unknown> | unknown[];
9
9
  /**
10
10
  * 通用键值对类型
11
11
  */
12
- export type KeyValue<T = any> = Record<string, T>;
12
+ export type KeyValue<T = unknown> = Record<string, T>;
13
13
  /**
14
14
  * WHERE 条件操作符
15
15
  */
@@ -17,7 +17,7 @@ export type WhereOperator = "$eq" | "$ne" | "$not" | "$gt" | "$gte" | "$lt" | "$
17
17
  /**
18
18
  * WHERE 条件类型
19
19
  */
20
- export type WhereConditions = Record<string, any>;
20
+ export type WhereConditions = Record<string, unknown>;
21
21
  /**
22
22
  * 排序方向
23
23
  */
@@ -47,7 +47,7 @@ export type UpdateData = Record<string, SqlValue>;
47
47
  /**
48
48
  * 任意对象类型
49
49
  */
50
- export type AnyObject = Record<string, any>;
50
+ export type AnyObject = Record<string, unknown>;
51
51
  /**
52
52
  * 字段规则字符串(已废弃,保留用于兼容)
53
53
  * 格式: "字段名|类型|最小值|最大值|默认值|是否索引|正则约束"
@@ -68,7 +68,7 @@ export interface ParsedFieldRule {
68
68
  type: "string" | "number" | "text" | "array_string" | "array_text";
69
69
  min: number | null;
70
70
  max: number | null;
71
- default: any;
71
+ default: unknown;
72
72
  index: 0 | 1;
73
73
  regex: string | null;
74
74
  }
@@ -95,7 +95,7 @@ export interface JoinOption {
95
95
  /**
96
96
  * 工具函数返回类型
97
97
  */
98
- export interface ToolResponse<T = any> {
98
+ export interface ToolResponse<T = unknown> {
99
99
  success: boolean;
100
100
  data?: T;
101
101
  error?: string;
@@ -123,12 +123,12 @@ export interface BaseRecord {
123
123
  /**
124
124
  * 异步函数类型
125
125
  */
126
- export type AsyncFunction<T = any> = (...args: any[]) => Promise<T>;
126
+ export type AsyncFunction<T = unknown> = (...args: unknown[]) => Promise<T>;
127
127
  /**
128
128
  * 同步函数类型
129
129
  */
130
- export type SyncFunction<T = any> = (...args: any[]) => T;
130
+ export type SyncFunction<T = unknown> = (...args: unknown[]) => T;
131
131
  /**
132
132
  * 通用回调函数
133
133
  */
134
- export type Callback<T = any> = (error: Error | null, result?: T) => void;
134
+ export type Callback<T = unknown> = (error: Error | null, result?: T) => void;
@@ -63,7 +63,7 @@ export interface DeleteOptions {
63
63
  */
64
64
  export interface SqlInfo {
65
65
  sql: string;
66
- params: any[];
66
+ params: unknown[];
67
67
  duration: number;
68
68
  }
69
69
  /**
@@ -76,15 +76,15 @@ export interface ListSql {
76
76
  /**
77
77
  * 统一返回结构
78
78
  */
79
- export interface DbResult<T = any, SqlT = SqlInfo> {
80
- data: T;
81
- sql: SqlT;
82
- }
79
+ export type DbResult<TData = unknown, TSql = SqlInfo> = {
80
+ data: TData;
81
+ sql: TSql;
82
+ };
83
83
  /**
84
84
  * 分页结果
85
85
  */
86
- export interface ListResult<T = any> {
87
- lists: T[];
86
+ export interface DbPageResult<TItem = unknown> {
87
+ lists: TItem[];
88
88
  total: number;
89
89
  page: number;
90
90
  limit: number;
@@ -93,14 +93,14 @@ export interface ListResult<T = any> {
93
93
  /**
94
94
  * 不分页结果(带 total)
95
95
  */
96
- export interface AllResult<T = any> {
97
- lists: T[];
96
+ export interface DbListResult<TItem = unknown> {
97
+ lists: TItem[];
98
98
  total: number;
99
99
  }
100
100
  /**
101
101
  * 事务回调
102
102
  */
103
- export type TransactionCallback<T = any> = (db: any) => Promise<T>;
103
+ export type TransactionCallback<TResult = unknown, TDb = unknown> = (db: TDb) => Promise<TResult>;
104
104
  /**
105
105
  * DbHelper 公开接口(类型层)。
106
106
  *
@@ -110,29 +110,32 @@ export type TransactionCallback<T = any> = (db: any) => Promise<T>;
110
110
  export interface DbHelper {
111
111
  tableExists(tableName: string): Promise<DbResult<boolean>>;
112
112
  getCount(options: Omit<QueryOptions, "fields" | "page" | "limit" | "orderBy">): Promise<DbResult<number>>;
113
- getOne<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<DbResult<T | null>>;
114
- getList<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<DbResult<ListResult<T>, ListSql>>;
115
- getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, "page" | "limit">): Promise<DbResult<AllResult<T>, ListSql>>;
113
+ getOne<TItem = unknown>(options: QueryOptions): Promise<DbResult<TItem | null>>;
114
+ getDetail<TItem = unknown>(options: QueryOptions): Promise<DbResult<TItem | null>>;
115
+ getList<TItem = unknown>(options: QueryOptions): Promise<DbResult<DbPageResult<TItem>, ListSql>>;
116
+ getAll<TItem = unknown>(options: Omit<QueryOptions, "page" | "limit">): Promise<DbResult<DbListResult<TItem>, ListSql>>;
116
117
  exists(options: Omit<QueryOptions, "fields" | "orderBy" | "page" | "limit">): Promise<DbResult<boolean>>;
117
- getFieldValue<T = any>(options: Omit<QueryOptions, "fields"> & {
118
+ getFieldValue<TValue = unknown>(options: Omit<QueryOptions, "fields"> & {
118
119
  field: string;
119
- }): Promise<DbResult<T | null>>;
120
- insData(options: InsertOptions): Promise<DbResult<number>>;
121
- insBatch(table: string, dataList: Record<string, any>[]): Promise<DbResult<number[]>>;
120
+ }): Promise<DbResult<TValue | null>>;
121
+ insData<TInsert extends Record<string, SqlValue> = Record<string, SqlValue>>(options: Omit<InsertOptions, "data"> & {
122
+ data: TInsert | TInsert[];
123
+ }): Promise<DbResult<number>>;
124
+ insBatch<TInsert extends Record<string, SqlValue> = Record<string, SqlValue>>(table: string, dataList: TInsert[]): Promise<DbResult<number[]>>;
122
125
  updData(options: UpdateOptions): Promise<DbResult<number>>;
123
126
  updBatch(table: string, dataList: Array<{
124
127
  id: number;
125
- data: Record<string, any>;
128
+ data: Record<string, unknown>;
126
129
  }>): Promise<DbResult<number>>;
127
130
  delData(options: DeleteOptions): Promise<DbResult<number>>;
128
131
  delForce(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>>;
129
132
  delForceBatch(table: string, ids: number[]): Promise<DbResult<number>>;
130
133
  enableData(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>>;
131
134
  disableData(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>>;
132
- query(sql: string, params?: any[]): Promise<DbResult<any>>;
133
- unsafe(sqlStr: string, params?: any[]): Promise<DbResult<any>>;
134
- trans<T = any>(callback: TransactionCallback<T>): Promise<T>;
135
+ query<TResult = unknown>(sql: string, params?: unknown[]): Promise<DbResult<TResult>>;
136
+ unsafe<TResult = unknown>(sqlStr: string, params?: unknown[]): Promise<DbResult<TResult>>;
137
+ trans<TResult = unknown>(callback: TransactionCallback<TResult, DbHelper>): Promise<TResult>;
135
138
  increment(table: string, field: string, where: WhereConditions, value?: number): Promise<DbResult<number>>;
136
139
  decrement(table: string, field: string, where: WhereConditions, value?: number): Promise<DbResult<number>>;
137
- [key: string]: any;
140
+ [key: string]: unknown;
138
141
  }
@@ -61,7 +61,8 @@ export interface SyncApiItem {
61
61
  name: string;
62
62
  /** 是否需要登录:0=免登录,1=需登录(同步时会归一化为 0/1) */
63
63
  auth?: 0 | 1 | boolean;
64
- routePath: string;
64
+ path: string;
65
+ parentPath: string;
65
66
  addonName: string;
66
67
  [key: string]: any;
67
68
  }
@@ -73,7 +73,7 @@ export async function scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath = "
73
73
  export function getParentPath(path) {
74
74
  // "/a/b" => "/a"
75
75
  // "/a" => ""
76
- const parts = path.split("/").filter((p) => !!p);
76
+ const parts = path.split("/").filter((p) => Boolean(p));
77
77
  if (parts.length <= 1) {
78
78
  return "";
79
79
  }
@@ -51,7 +51,7 @@ function sanitizeErrorValue(err, options) {
51
51
  message: truncateString(err.message || "", options.maxStringLen)
52
52
  };
53
53
  if (typeof err.stack === "string") {
54
- out.stack = truncateString(err.stack, options.maxStringLen);
54
+ out["stack"] = truncateString(err.stack, options.maxStringLen);
55
55
  }
56
56
  return out;
57
57
  }
@@ -142,8 +142,12 @@ function sanitizeAny(value, options, state, depth, visited) {
142
142
  const entries = Object.entries(obj);
143
143
  const limit = entries.length > options.sanitizeObjectKeys ? options.sanitizeObjectKeys : entries.length;
144
144
  for (let i = 0; i < limit; i++) {
145
- const key = entries[i][0];
146
- const child = entries[i][1];
145
+ const entry = entries[i];
146
+ if (!entry) {
147
+ continue;
148
+ }
149
+ const key = entry[0];
150
+ const child = entry[1];
147
151
  if (isSensitiveKey(key, options.sensitiveKeyMatcher)) {
148
152
  out[key] = "[MASKED]";
149
153
  continue;
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * 处理字段定义:将 @ 符号引用替换为实际字段定义
3
3
  */
4
- export declare function processAtSymbol(fields: Record<string, any>, apiName: string, routePath: string): Record<string, any>;
4
+ export declare function processAtSymbol(fields: Record<string, any>, apiName: string, path: string): Record<string, any>;
@@ -2,7 +2,7 @@ import { presetFields } from "../configs/presetFields";
2
2
  /**
3
3
  * 处理字段定义:将 @ 符号引用替换为实际字段定义
4
4
  */
5
- export function processAtSymbol(fields, apiName, routePath) {
5
+ export function processAtSymbol(fields, apiName, path) {
6
6
  if (!fields || typeof fields !== "object")
7
7
  return fields;
8
8
  const processed = {};
@@ -13,7 +13,7 @@ export function processAtSymbol(fields, apiName, routePath) {
13
13
  continue;
14
14
  }
15
15
  const validKeys = Object.keys(presetFields).join(", ");
16
- throw new Error(`API [${apiName}] (${routePath}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
16
+ throw new Error(`API [${apiName}] (${path}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
17
17
  }
18
18
  processed[key] = value;
19
19
  }
@@ -4,8 +4,8 @@
4
4
  */
5
5
  export function getProcessRole(env) {
6
6
  const runtimeEnv = env || {};
7
- const bunWorkerId = runtimeEnv.BUN_WORKER_ID;
8
- const pm2InstanceId = runtimeEnv.PM2_INSTANCE_ID;
7
+ const bunWorkerId = runtimeEnv["BUN_WORKER_ID"];
8
+ const pm2InstanceId = runtimeEnv["PM2_INSTANCE_ID"];
9
9
  // Bun 集群模式
10
10
  if (bunWorkerId !== undefined) {
11
11
  return {
@@ -86,7 +86,7 @@ export async function scanFiles(dir, source, type, pattern) {
86
86
  const base = {
87
87
  source: source,
88
88
  type: type,
89
- sourceName: { core: "核心", addon: "组件", app: "项目" }[source],
89
+ sourceName: source === "core" ? "核心" : source === "addon" ? "组件" : "项目",
90
90
  filePath: normalizedFile,
91
91
  relativePath: relativePath,
92
92
  fileName: fileName,
@@ -97,28 +97,32 @@ export async function scanFiles(dir, source, type, pattern) {
97
97
  customKeys: isPlainObject(content) ? Object.keys(content) : []
98
98
  };
99
99
  if (type === "table") {
100
- base.content = content;
100
+ base["content"] = content;
101
101
  results.push(base);
102
102
  continue;
103
103
  }
104
104
  if (type === "api") {
105
- base.auth = true;
106
- base.rawBody = false;
107
- base.method = "POST";
108
- base.fields = {};
109
- base.required = [];
105
+ // 运行时 auth 必须是 boolean:
106
+ // - checkApi 会校验 auth 类型
107
+ // - permission hook 以 ctx.api.auth === false 判断公开接口
108
+ // DB 存储的 0/1 由 syncApi 负责转换写入。
109
+ base["auth"] = true;
110
+ base["rawBody"] = false;
111
+ base["method"] = "POST";
112
+ base["fields"] = {};
113
+ base["required"] = [];
110
114
  }
111
115
  if (type === "plugin" || type === "hook") {
112
- base.deps = [];
113
- base.name = "";
114
- base.handler = null;
116
+ base["deps"] = [];
117
+ base["name"] = "";
118
+ base["handler"] = null;
115
119
  }
116
120
  forOwn(content, (value, key) => {
117
121
  base[key] = value;
118
122
  });
119
123
  if (type === "api") {
120
- base.routePrefix = source === "app" ? "/app" : `/addon/${addonName}`;
121
- base.routePath = `/api${base.routePrefix}/${relativePath}`;
124
+ base["routePrefix"] = source === "app" ? "/app" : `/addon/${addonName}`;
125
+ base["path"] = `/api${base["routePrefix"]}/${relativePath}`;
122
126
  }
123
127
  results.push(base);
124
128
  }
@@ -108,7 +108,7 @@ function upperFirst(s) {
108
108
  if (s.length === 0) {
109
109
  return s;
110
110
  }
111
- return s[0].toUpperCase() + s.slice(1);
111
+ return s.charAt(0).toUpperCase() + s.slice(1);
112
112
  }
113
113
  /**
114
114
  * 把字符串转为小驼峰。
@@ -119,9 +119,13 @@ export function camelCase(input) {
119
119
  if (parts.length === 0) {
120
120
  return "";
121
121
  }
122
- const first = parts[0].toLowerCase();
122
+ const firstPart = parts[0];
123
+ if (!firstPart) {
124
+ return "";
125
+ }
126
+ const first = firstPart.toLowerCase();
123
127
  const rest = parts.slice(1).map((p) => upperFirst(p.toLowerCase()));
124
- return [first, ...rest].join("");
128
+ return [first].concat(rest).join("");
125
129
  }
126
130
  function normalizeToWords(input) {
127
131
  return String(input)
@@ -227,6 +231,9 @@ export function setByPath(target, path, value) {
227
231
  let cur = target;
228
232
  for (let i = 0; i < parts.length; i++) {
229
233
  const key = parts[i];
234
+ if (!key) {
235
+ return;
236
+ }
230
237
  const isLast = i === parts.length - 1;
231
238
  if (isLast) {
232
239
  cur[key] = value;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.13.10",
4
- "gitHead": "28629fbf7854a0268d3963fd4a8ee8820175951c",
3
+ "version": "3.14.1",
4
+ "gitHead": "f836a457a4b5936d566051b629ed80eb264d6bf8",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
7
7
  "keywords": [