befly 3.14.3 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/befly.config.js +1 -1
  2. package/dist/befly.js +618 -209
  3. package/dist/befly.min.js +13 -13
  4. package/dist/checks/checkApi.d.ts +1 -1
  5. package/dist/checks/checkApi.js +64 -24
  6. package/dist/checks/checkHook.js +20 -14
  7. package/dist/checks/checkPlugin.js +20 -14
  8. package/dist/checks/checkTable.js +6 -3
  9. package/dist/hooks/permission.js +1 -1
  10. package/dist/lib/cipher.js +3 -4
  11. package/dist/lib/connect.js +2 -1
  12. package/dist/lib/dbDialect.d.ts +4 -4
  13. package/dist/lib/dbDialect.js +3 -12
  14. package/dist/lib/dbHelper.d.ts +8 -3
  15. package/dist/lib/dbHelper.js +85 -35
  16. package/dist/lib/dbUtils.js +2 -2
  17. package/dist/lib/logger.d.ts +6 -11
  18. package/dist/lib/logger.js +15 -10
  19. package/dist/lib/sqlBuilder.js +10 -2
  20. package/dist/lib/validator.d.ts +3 -3
  21. package/dist/lib/validator.js +59 -7
  22. package/dist/loader/loadApis.js +38 -6
  23. package/dist/loader/loadHooks.js +12 -5
  24. package/dist/loader/loadPlugins.js +13 -6
  25. package/dist/plugins/tool.d.ts +11 -9
  26. package/dist/plugins/tool.js +1 -1
  27. package/dist/sync/syncApi.d.ts +2 -2
  28. package/dist/sync/syncApi.js +23 -11
  29. package/dist/sync/syncDev.js +2 -2
  30. package/dist/sync/syncMenu.js +3 -2
  31. package/dist/sync/syncTable.d.ts +22 -26
  32. package/dist/sync/syncTable.js +114 -25
  33. package/dist/types/api.d.ts +11 -10
  34. package/dist/types/befly.d.ts +12 -12
  35. package/dist/types/common.d.ts +23 -6
  36. package/dist/types/context.d.ts +7 -3
  37. package/dist/types/database.d.ts +15 -15
  38. package/dist/types/jwt.d.ts +3 -2
  39. package/dist/types/logger.d.ts +24 -7
  40. package/dist/types/sync.d.ts +7 -7
  41. package/dist/types/validate.d.ts +12 -4
  42. package/dist/utils/convertBigIntFields.d.ts +2 -1
  43. package/dist/utils/convertBigIntFields.js +3 -12
  44. package/dist/utils/isDirentDirectory.d.ts +2 -1
  45. package/dist/utils/loadMenuConfigs.d.ts +1 -1
  46. package/dist/utils/loadMenuConfigs.js +11 -3
  47. package/dist/utils/mergeAndConcat.d.ts +1 -1
  48. package/dist/utils/mergeAndConcat.js +22 -17
  49. package/dist/utils/normalizeFieldDefinition.d.ts +2 -1
  50. package/dist/utils/scanCoreBuiltins.js +9 -5
  51. package/dist/utils/scanFiles.d.ts +2 -2
  52. package/dist/utils/scanFiles.js +1 -1
  53. package/dist/utils/sortModules.js +8 -1
  54. package/dist/utils/sqlParams.d.ts +10 -0
  55. package/dist/utils/sqlParams.js +78 -0
  56. package/dist/utils/sqlResult.d.ts +5 -0
  57. package/dist/utils/sqlResult.js +7 -0
  58. package/dist/utils/util.d.ts +7 -6
  59. package/dist/utils/util.js +8 -5
  60. package/package.json +2 -2
@@ -7,13 +7,14 @@ import { sortModules } from "../utils/sortModules";
7
7
  export async function loadPlugins(plugins, context) {
8
8
  const pluginsMap = [];
9
9
  const enabledPlugins = plugins.filter((item) => {
10
- const moduleName = item?.moduleName;
10
+ const moduleName = item.moduleName;
11
11
  if (typeof moduleName !== "string" || moduleName.trim() === "") {
12
12
  return false;
13
13
  }
14
14
  // enable=false 表示禁用(替代 disablePlugins 列表)。
15
15
  // enable 仅允许 boolean;缺失 enable 的默认值应在 checkPlugin 阶段被补全为 true。
16
- if (item?.enable === false) {
16
+ const enable = Object.hasOwn(item, "enable") ? item.enable : undefined;
17
+ if (enable === false) {
17
18
  return false;
18
19
  }
19
20
  return true;
@@ -24,15 +25,21 @@ export async function loadPlugins(plugins, context) {
24
25
  }
25
26
  for (const item of sortedPlugins) {
26
27
  const pluginName = item.moduleName;
27
- const plugin = item;
28
+ const depsRaw = Object.hasOwn(item, "deps") ? item.deps : undefined;
29
+ const deps = Array.isArray(depsRaw) ? depsRaw.filter((x) => typeof x === "string") : [];
30
+ const handlerRaw = Object.hasOwn(item, "handler") ? item.handler : undefined;
31
+ if (typeof handlerRaw !== "function") {
32
+ throw new Error(`插件 '${pluginName}' handler 必须是函数`);
33
+ }
34
+ const handler = handlerRaw;
28
35
  try {
29
- const pluginInstance = typeof plugin.handler === "function" ? await plugin.handler(context) : {};
36
+ const pluginInstance = await handler(context);
30
37
  context[pluginName] = pluginInstance;
31
38
  pluginsMap.push({
32
39
  name: pluginName,
33
40
  enable: true,
34
- deps: Array.isArray(plugin.deps) ? plugin.deps : [],
35
- handler: plugin.handler
41
+ deps: deps,
42
+ handler: handler
36
43
  });
37
44
  }
38
45
  catch (error) {
@@ -2,8 +2,10 @@
2
2
  * 工具插件
3
3
  * 提供常用的工具函数
4
4
  */
5
+ import type { JsonValue } from "../types/common";
5
6
  import type { RequestContext } from "../types/context";
6
7
  import type { Plugin } from "../types/plugin";
8
+ type ToolExtra = Record<string, JsonValue | undefined>;
7
9
  /**
8
10
  * 成功响应
9
11
  * @param msg - 消息
@@ -11,11 +13,11 @@ import type { Plugin } from "../types/plugin";
11
13
  * @param other - 其他字段(可选)
12
14
  * @returns 成功响应对象
13
15
  */
14
- export declare function Yes(msg: string, data?: any, other?: Record<string, any>): {
15
- code: number;
16
+ export declare function Yes<TData extends JsonValue = JsonValue, TOther extends ToolExtra = ToolExtra>(msg: string, data?: TData, other?: TOther): {
17
+ code: 0;
16
18
  msg: string;
17
- data: any;
18
- };
19
+ data: TData;
20
+ } & TOther;
19
21
  /**
20
22
  * 失败响应
21
23
  * @param msg - 消息
@@ -23,11 +25,11 @@ export declare function Yes(msg: string, data?: any, other?: Record<string, any>
23
25
  * @param other - 其他字段(可选)
24
26
  * @returns 失败响应对象
25
27
  */
26
- export declare function No(msg: string, data?: any, other?: Record<string, any>): {
27
- code: number;
28
+ export declare function No<TData extends JsonValue = JsonValue, TOther extends ToolExtra = ToolExtra>(msg: string, data?: TData, other?: TOther): {
29
+ code: 1;
28
30
  msg: string;
29
- data: any;
30
- };
31
+ data: TData;
32
+ } & TOther;
31
33
  /**
32
34
  * 响应选项
33
35
  */
@@ -68,6 +70,6 @@ interface ResponseOptions {
68
70
  * headers: { 'X-Custom': 'value' }
69
71
  * });
70
72
  */
71
- export declare function Raw(ctx: RequestContext, data: Record<string, any> | string, options?: ResponseOptions): Response;
73
+ export declare function Raw(ctx: RequestContext, data: JsonValue | string, options?: ResponseOptions): Response;
72
74
  declare const toolPlugin: Plugin;
73
75
  export default toolPlugin;
@@ -75,7 +75,7 @@ export function Raw(ctx, data, options = {}) {
75
75
  }
76
76
  }
77
77
  else {
78
- // 对象类型,JSON 序列化
78
+ // JSON 序列化(对象/数组/原始值均可)
79
79
  body = JSON.stringify(data);
80
80
  finalContentType = finalContentType || "application/json";
81
81
  }
@@ -1,3 +1,3 @@
1
1
  import type { BeflyContext } from "../types/befly";
2
- import type { SyncApiItem } from "../types/sync";
3
- export declare function syncApi(ctx: Pick<BeflyContext, "db" | "cache">, apis: SyncApiItem[]): Promise<void>;
2
+ import type { ScanFileResult } from "../utils/scanFiles";
3
+ export declare function syncApi(ctx: Pick<BeflyContext, "db" | "cache">, apis: ScanFileResult[]): Promise<void>;
@@ -45,31 +45,43 @@ export async function syncApi(ctx, apis) {
45
45
  if (api.type !== "api") {
46
46
  continue;
47
47
  }
48
+ const record = api;
49
+ const path = record["path"];
50
+ const name = record["name"];
51
+ const addonNameRaw = record["addonName"];
52
+ if (typeof path !== "string" || path.trim() === "") {
53
+ continue;
54
+ }
55
+ if (typeof name !== "string" || name.trim() === "") {
56
+ continue;
57
+ }
58
+ const addonName = typeof addonNameRaw === "string" ? addonNameRaw : "";
48
59
  // auth:运行时 API 定义使用 boolean;DB 字段使用 0/1。
49
60
  // 统一在 syncApi 写库前做归一化,避免类型不一致导致每次启动都触发更新。
50
- const auth = api.auth === false || api.auth === 0 ? 0 : 1;
51
- const parentPath = getApiParentPath(api.path);
52
- apiRouteKeys.add(api.path);
53
- const item = allDbApiMap[api.path];
61
+ const authRaw = record["auth"];
62
+ const auth = authRaw === false || authRaw === 0 ? 0 : 1;
63
+ const parentPath = getApiParentPath(path);
64
+ apiRouteKeys.add(path);
65
+ const item = allDbApiMap[path];
54
66
  if (item) {
55
- const shouldUpdate = api.name !== item.name || api.path !== item.path || api.addonName !== item.addonName || parentPath !== item.parentPath || auth !== item.auth;
67
+ const shouldUpdate = name !== item.name || path !== item.path || addonName !== item.addonName || parentPath !== item.parentPath || auth !== item.auth;
56
68
  if (shouldUpdate) {
57
69
  updData.push({
58
70
  id: item.id,
59
- name: api.name,
60
- path: api.path,
71
+ name: name,
72
+ path: path,
61
73
  parentPath: parentPath,
62
- addonName: api.addonName,
74
+ addonName: addonName,
63
75
  auth: auth
64
76
  });
65
77
  }
66
78
  }
67
79
  else {
68
80
  insData.push({
69
- name: api.name,
70
- path: api.path,
81
+ name: name,
82
+ path: path,
71
83
  parentPath: parentPath,
72
- addonName: api.addonName,
84
+ addonName: addonName,
73
85
  auth: auth
74
86
  });
75
87
  }
@@ -46,8 +46,8 @@ export async function syncDev(ctx, config = {}) {
46
46
  code: "dev",
47
47
  name: "开发者角色",
48
48
  description: "拥有所有菜单和接口权限的开发者角色",
49
- menus: allMenus.data.lists.map((item) => item.path).filter((v) => v),
50
- apis: allApis.data.lists.map((item) => item.path).filter((v) => v),
49
+ menus: allMenus.data.lists.map((item) => item.path).filter((v) => typeof v === "string" && v.length > 0),
50
+ apis: allApis.data.lists.map((item) => item.path).filter((v) => typeof v === "string" && v.length > 0),
51
51
  sort: 0
52
52
  };
53
53
  if (typeof devRole.data.id === "number") {
@@ -36,12 +36,13 @@ function createDisableMenuMatcher(ctx) {
36
36
  candidates.push(`/${trimmed}`);
37
37
  }
38
38
  for (const glob of globs) {
39
- const match = glob.match;
39
+ const match = typeof glob === "object" && glob !== null && "match" in glob ? glob.match : undefined;
40
40
  if (typeof match !== "function") {
41
41
  throw new Error("syncMenu: 当前 Bun 版本不支持 Bun.Glob.match,无法按 disableMenus 做 glob 匹配");
42
42
  }
43
+ const matchFn = match;
43
44
  for (const candidate of candidates) {
44
- if (match.call(glob, candidate)) {
45
+ if (matchFn.call(glob, candidate)) {
45
46
  return true;
46
47
  }
47
48
  }
@@ -6,7 +6,6 @@
6
6
  * - 现在按项目要求,将所有实现合并到本文件(目录 packages/core/sync/syncTable/ 已删除)
7
7
  */
8
8
  import type { DbDialectName } from "../lib/dbDialect";
9
- import type { BeflyContext } from "../types/befly";
10
9
  import type { DbResult, SqlInfo } from "../types/database";
11
10
  import type { ColumnInfo, FieldChange, IndexInfo } from "../types/sync";
12
11
  import type { FieldDefinition } from "../types/validate";
@@ -14,6 +13,18 @@ import type { ScanFileResult } from "../utils/scanFiles";
14
13
  type SqlExecutor = {
15
14
  unsafe<T = unknown>(sqlStr: string, params?: unknown[]): Promise<DbResult<T, SqlInfo>>;
16
15
  };
16
+ type SyncTableContext = {
17
+ db: SqlExecutor;
18
+ redis: {
19
+ delBatch(keys: string[]): Promise<unknown>;
20
+ };
21
+ config: {
22
+ db?: {
23
+ type?: string;
24
+ database?: string;
25
+ };
26
+ };
27
+ };
17
28
  type DbDialect = DbDialectName;
18
29
  /**
19
30
  * 文件导航(推荐阅读顺序)
@@ -23,7 +34,7 @@ type DbDialect = DbDialectName;
23
34
  * 4) Runtime I/O(只读元信息:表/列/索引/版本)
24
35
  * 5) plan/apply(写变更:建表/改表/SQLite 重建)
25
36
  */
26
- type SyncTableFn = ((ctx: BeflyContext, items: ScanFileResult[]) => Promise<void>) & {
37
+ type SyncTableFn = ((ctx: SyncTableContext, items: ScanFileResult[]) => Promise<void>) & {
27
38
  TestKit: typeof SYNC_TABLE_TEST_KIT;
28
39
  };
29
40
  /**
@@ -68,7 +79,7 @@ declare const SYNC_TABLE_TEST_KIT: {
68
79
  tableExistsRuntime: typeof tableExistsRuntime;
69
80
  getTableColumnsRuntime: typeof getTableColumnsRuntime;
70
81
  getTableIndexesRuntime: typeof getTableIndexesRuntime;
71
- createRuntime: (dbDialect: DbDialectName, db: SqlExecutor, dbName?: string) => SyncRuntime;
82
+ createRuntime: (dbDialect: DbDialectName, db: SqlExecutor | null, dbName?: string) => SyncRuntimeForIO;
72
83
  };
73
84
  /**
74
85
  * 获取字段类型映射(根据当前数据库类型)
@@ -82,10 +93,7 @@ declare function quoteIdentifier(dbDialect: DbDialect, identifier: string): stri
82
93
  * 转义 SQL 注释中的双引号
83
94
  */
84
95
  declare function escapeComment(str: string): string;
85
- /**
86
- * 为字段定义应用默认值
87
- */
88
- declare function applyFieldDefaults(fieldDef: any): void;
96
+ declare function applyFieldDefaults(fieldDef: unknown): void;
89
97
  /**
90
98
  * 判断是否为字符串或数组类型(需要长度参数)
91
99
  */
@@ -121,31 +129,19 @@ declare function generateDDLClause(dbDialect: DbDialect, fieldKey: string, field
121
129
  /**
122
130
  * 判断是否为兼容的类型变更(宽化型变更,无数据丢失风险)
123
131
  */
124
- declare function isCompatibleTypeChange(currentType: string, newType: string): boolean;
125
- type SyncRuntime = {
126
- /**
127
- * 当前数据库方言(mysql/postgresql/sqlite),决定 SQL 片段与元信息查询方式。
128
- * 约束:必须与 ctx.config.db.type 一致(经归一化)。
129
- */
132
+ declare function isCompatibleTypeChange(currentType: string | null | undefined, newType: string | null | undefined): boolean;
133
+ type SyncRuntimeForIO = {
130
134
  dbDialect: DbDialect;
131
- /**
132
- * SQL 执行器:必须复用 ctx.db。
133
- * 约束:syncTable 内部禁止新建 DB 连接/事务;runtime 仅保存引用,不拥有生命周期。
134
- */
135
- db: SqlExecutor;
136
- /**
137
- * 数据库名:主要用于 MySQL information_schema 查询。
138
- * 约束:PG/SQLite 可以传空字符串;不要在非 MySQL 方言依赖该值。
139
- */
135
+ db: SqlExecutor | null;
140
136
  dbName: string;
141
137
  };
142
- declare function tableExistsRuntime(runtime: SyncRuntime, tableName: string): Promise<boolean>;
143
- declare function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string): Promise<{
138
+ declare function tableExistsRuntime(runtime: SyncRuntimeForIO, tableName: string): Promise<boolean>;
139
+ declare function getTableColumnsRuntime(runtime: SyncRuntimeForIO, tableName: string): Promise<{
144
140
  [key: string]: ColumnInfo;
145
141
  }>;
146
- declare function getTableIndexesRuntime(runtime: SyncRuntime, tableName: string): Promise<IndexInfo>;
142
+ declare function getTableIndexesRuntime(runtime: SyncRuntimeForIO, tableName: string): Promise<IndexInfo>;
147
143
  /**
148
144
  * 比较字段定义变化
149
145
  */
150
- declare function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo, fieldDef: FieldDefinition): FieldChange[];
146
+ declare function compareFieldDefinition(dbDialect: DbDialect, existingColumn: Pick<ColumnInfo, "type" | "max" | "nullable" | "defaultValue" | "comment">, fieldDef: FieldDefinition): FieldChange[];
151
147
  export {};
@@ -89,11 +89,12 @@ export const syncTable = (async (ctx, items) => {
89
89
  applyFieldDefaults(fieldDef);
90
90
  }
91
91
  const existsTable = await tableExistsRuntime(runtime, tableName);
92
+ const tableFields = tableDefinition;
92
93
  if (existsTable) {
93
- await modifyTableRuntime(runtime, tableName, tableDefinition);
94
+ await modifyTableRuntime(runtime, tableName, tableFields);
94
95
  }
95
96
  else {
96
- await createTable(runtime, tableName, tableDefinition);
97
+ await createTable(runtime, tableName, tableFields);
97
98
  }
98
99
  // 记录处理过的表名(用于清理缓存)
99
100
  processedTables.push(tableName);
@@ -264,24 +265,105 @@ function quoteIdentifier(dbDialect, identifier) {
264
265
  function escapeComment(str) {
265
266
  return String(str).replace(/"/g, '\\"');
266
267
  }
268
+ function normalizeColumnDefaultValue(value) {
269
+ if (value === null)
270
+ return null;
271
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
272
+ return value;
273
+ if (Array.isArray(value)) {
274
+ const items = [];
275
+ for (const v of value) {
276
+ items.push(normalizeColumnDefaultValue(v));
277
+ }
278
+ return items;
279
+ }
280
+ return String(value);
281
+ }
267
282
  // 注意:这里刻意不封装“logFieldChange/formatFieldList”之类的一次性工具函数,
268
283
  // 以减少抽象层级(按项目要求:能直写就直写)。
269
284
  /**
270
285
  * 为字段定义应用默认值
271
286
  */
287
+ function isJsonValue(value) {
288
+ if (value === null)
289
+ return true;
290
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
291
+ return true;
292
+ if (Array.isArray(value)) {
293
+ return value.every((v) => isJsonValue(v));
294
+ }
295
+ if (typeof value === "object") {
296
+ for (const v of Object.values(value)) {
297
+ if (v === undefined)
298
+ continue;
299
+ if (!isJsonValue(v))
300
+ return false;
301
+ }
302
+ return true;
303
+ }
304
+ return false;
305
+ }
272
306
  function applyFieldDefaults(fieldDef) {
273
307
  if (!fieldDef || typeof fieldDef !== "object")
274
308
  return;
275
- const normalized = normalizeFieldDefinition(fieldDef);
276
- fieldDef.detail = normalized.detail;
277
- fieldDef.min = normalized.min;
278
- fieldDef.max = normalized.max;
279
- fieldDef.default = normalized.default;
280
- fieldDef.index = normalized.index;
281
- fieldDef.unique = normalized.unique;
282
- fieldDef.nullable = normalized.nullable;
283
- fieldDef.unsigned = normalized.unsigned;
284
- fieldDef.regexp = normalized.regexp;
309
+ const record = fieldDef;
310
+ const name = record["name"];
311
+ const type = record["type"];
312
+ if (typeof name !== "string" || typeof type !== "string")
313
+ return;
314
+ const minRaw = record["min"];
315
+ const maxRaw = record["max"];
316
+ const defaultRaw = record["default"];
317
+ const detailRaw = record["detail"];
318
+ const indexRaw = record["index"];
319
+ const uniqueRaw = record["unique"];
320
+ const nullableRaw = record["nullable"];
321
+ const unsignedRaw = record["unsigned"];
322
+ const regexpRaw = record["regexp"];
323
+ const input = {
324
+ name: name,
325
+ type: type
326
+ };
327
+ if (typeof detailRaw === "string") {
328
+ input.detail = detailRaw;
329
+ }
330
+ if (typeof minRaw === "number" || minRaw === null) {
331
+ input.min = minRaw;
332
+ }
333
+ if (typeof maxRaw === "number" || maxRaw === null) {
334
+ input.max = maxRaw;
335
+ }
336
+ if (defaultRaw === null) {
337
+ input.default = null;
338
+ }
339
+ else if (isJsonValue(defaultRaw)) {
340
+ input.default = defaultRaw;
341
+ }
342
+ if (typeof indexRaw === "boolean") {
343
+ input.index = indexRaw;
344
+ }
345
+ if (typeof uniqueRaw === "boolean") {
346
+ input.unique = uniqueRaw;
347
+ }
348
+ if (typeof nullableRaw === "boolean") {
349
+ input.nullable = nullableRaw;
350
+ }
351
+ if (typeof unsignedRaw === "boolean") {
352
+ input.unsigned = unsignedRaw;
353
+ }
354
+ if (typeof regexpRaw === "string" || regexpRaw === null) {
355
+ input.regexp = regexpRaw;
356
+ }
357
+ const normalized = normalizeFieldDefinition(input);
358
+ record["detail"] = normalized.detail;
359
+ record["min"] = normalized.min;
360
+ record["max"] = normalized.max;
361
+ record["default"] = normalized.default;
362
+ record["index"] = normalized.index;
363
+ record["unique"] = normalized.unique;
364
+ record["nullable"] = normalized.nullable;
365
+ record["unsigned"] = normalized.unsigned;
366
+ record["regexp"] = normalized.regexp;
285
367
  }
286
368
  /**
287
369
  * 判断是否为字符串或数组类型(需要长度参数)
@@ -538,7 +620,8 @@ function isCompatibleTypeChange(currentType, newType) {
538
620
  // 读:表是否存在
539
621
  // ---------------------------------------------------------------------------
540
622
  async function tableExistsRuntime(runtime, tableName) {
541
- if (!runtime.db)
623
+ const db = runtime.db;
624
+ if (!db)
542
625
  throw new Error("SQL 执行器未初始化");
543
626
  try {
544
627
  // 统一交由方言层构造 SQL;syncTable 仅决定“要查哪个 schema/db”。
@@ -553,7 +636,7 @@ async function tableExistsRuntime(runtime, tableName) {
553
636
  schema = "public";
554
637
  }
555
638
  const q = getDialectByName(runtime.dbDialect).tableExistsQuery(tableName, schema);
556
- const res = await runtime.db.unsafe(q.sql, q.params);
639
+ const res = await db.unsafe(q.sql, q.params);
557
640
  return (res.data?.[0]?.count || 0) > 0;
558
641
  }
559
642
  catch (error) {
@@ -569,6 +652,9 @@ async function tableExistsRuntime(runtime, tableName) {
569
652
  // ---------------------------------------------------------------------------
570
653
  async function getTableColumnsRuntime(runtime, tableName) {
571
654
  const columns = {};
655
+ const db = runtime.db;
656
+ if (!db)
657
+ throw new Error("SQL 执行器未初始化");
572
658
  try {
573
659
  // 方言差异说明:
574
660
  // - MySQL:information_schema.columns 最完整,包含 COLUMN_TYPE 与 COLUMN_COMMENT。
@@ -576,9 +662,9 @@ async function getTableColumnsRuntime(runtime, tableName) {
576
662
  // - SQLite:PRAGMA table_info 仅提供 type/notnull/default 等有限信息,无列注释。
577
663
  if (runtime.dbDialect === "mysql") {
578
664
  const q = getSyncTableColumnsInfoQuery({ dialect: "mysql", table: tableName, dbName: runtime.dbName });
579
- const result = await runtime.db.unsafe(q.columns.sql, q.columns.params);
665
+ const result = await db.unsafe(q.columns.sql, q.columns.params);
580
666
  for (const row of result.data) {
581
- const defaultValue = row.COLUMN_DEFAULT;
667
+ const defaultValue = normalizeColumnDefaultValue(row.COLUMN_DEFAULT);
582
668
  columns[row.COLUMN_NAME] = {
583
669
  type: row.DATA_TYPE,
584
670
  columnType: row.COLUMN_TYPE,
@@ -592,8 +678,8 @@ async function getTableColumnsRuntime(runtime, tableName) {
592
678
  }
593
679
  else if (runtime.dbDialect === "postgresql") {
594
680
  const q = getSyncTableColumnsInfoQuery({ dialect: "postgresql", table: tableName, dbName: runtime.dbName });
595
- const result = await runtime.db.unsafe(q.columns.sql, q.columns.params);
596
- const comments = q.comments ? (await runtime.db.unsafe(q.comments.sql, q.comments.params)).data : [];
681
+ const result = await db.unsafe(q.columns.sql, q.columns.params);
682
+ const comments = q.comments ? (await db.unsafe(q.comments.sql, q.comments.params)).data : [];
597
683
  const commentMap = {};
598
684
  for (const r of comments)
599
685
  commentMap[r.column_name] = r.column_comment;
@@ -604,14 +690,14 @@ async function getTableColumnsRuntime(runtime, tableName) {
604
690
  length: row.character_maximum_length,
605
691
  max: row.character_maximum_length,
606
692
  nullable: String(row.is_nullable).toUpperCase() === "YES",
607
- defaultValue: row.column_default,
693
+ defaultValue: normalizeColumnDefaultValue(row.column_default),
608
694
  comment: commentMap[row.column_name] ?? null
609
695
  };
610
696
  }
611
697
  }
612
698
  else if (runtime.dbDialect === "sqlite") {
613
699
  const q = getSyncTableColumnsInfoQuery({ dialect: "sqlite", table: tableName, dbName: runtime.dbName });
614
- const result = await runtime.db.unsafe(q.columns.sql, q.columns.params);
700
+ const result = await db.unsafe(q.columns.sql, q.columns.params);
615
701
  for (const row of result.data) {
616
702
  let baseType = String(row.type || "").toUpperCase();
617
703
  let max = null;
@@ -632,7 +718,7 @@ async function getTableColumnsRuntime(runtime, tableName) {
632
718
  length: max,
633
719
  max: max,
634
720
  nullable: row.notnull === 0,
635
- defaultValue: row.dflt_value,
721
+ defaultValue: normalizeColumnDefaultValue(row.dflt_value),
636
722
  comment: null
637
723
  };
638
724
  }
@@ -652,6 +738,9 @@ async function getTableColumnsRuntime(runtime, tableName) {
652
738
  // ---------------------------------------------------------------------------
653
739
  async function getTableIndexesRuntime(runtime, tableName) {
654
740
  const indexes = {};
741
+ const db = runtime.db;
742
+ if (!db)
743
+ throw new Error("SQL 执行器未初始化");
655
744
  try {
656
745
  // 方言差异说明:
657
746
  // - MySQL:information_schema.statistics 直接给出 index -> column 映射。
@@ -659,7 +748,7 @@ async function getTableIndexesRuntime(runtime, tableName) {
659
748
  // - SQLite:PRAGMA index_list + index_info;同样仅收集单列索引,避免多列索引误判。
660
749
  if (runtime.dbDialect === "mysql") {
661
750
  const q = getSyncTableIndexesQuery({ dialect: "mysql", table: tableName, dbName: runtime.dbName });
662
- const result = await runtime.db.unsafe(q.sql, q.params);
751
+ const result = await db.unsafe(q.sql, q.params);
663
752
  for (const row of result.data) {
664
753
  const indexName = row.INDEX_NAME;
665
754
  const current = indexes[indexName];
@@ -673,7 +762,7 @@ async function getTableIndexesRuntime(runtime, tableName) {
673
762
  }
674
763
  else if (runtime.dbDialect === "postgresql") {
675
764
  const q = getSyncTableIndexesQuery({ dialect: "postgresql", table: tableName, dbName: runtime.dbName });
676
- const result = await runtime.db.unsafe(q.sql, q.params);
765
+ const result = await db.unsafe(q.sql, q.params);
677
766
  for (const row of result.data) {
678
767
  const m = /\(([^)]+)\)/.exec(row.indexdef);
679
768
  if (m) {
@@ -685,10 +774,10 @@ async function getTableIndexesRuntime(runtime, tableName) {
685
774
  }
686
775
  else if (runtime.dbDialect === "sqlite") {
687
776
  const quotedTable = quoteIdentifier("sqlite", tableName);
688
- const list = await runtime.db.unsafe(`PRAGMA index_list(${quotedTable})`);
777
+ const list = await db.unsafe(`PRAGMA index_list(${quotedTable})`);
689
778
  for (const idx of list.data) {
690
779
  const quotedIndex = quoteIdentifier("sqlite", idx.name);
691
- const info = await runtime.db.unsafe(`PRAGMA index_info(${quotedIndex})`);
780
+ const info = await db.unsafe(`PRAGMA index_info(${quotedIndex})`);
692
781
  const cols = info.data.map((r) => r.name);
693
782
  if (cols.length === 1)
694
783
  indexes[idx.name] = cols;
@@ -2,8 +2,9 @@
2
2
  * Befly API 类型定义
3
3
  */
4
4
  import type { BeflyContext } from "./befly";
5
- import type { KeyValue } from "./common";
5
+ import type { JsonValue, KeyValue } from "./common";
6
6
  import type { RequestContext } from "./context";
7
+ import type { JwtPayload } from "./jwt";
7
8
  import type { TableDefinition } from "./validate";
8
9
  /**
9
10
  * HTTP 方法类型
@@ -18,7 +19,7 @@ export interface UserInfo {
18
19
  username?: string;
19
20
  email?: string;
20
21
  role?: string;
21
- [key: string]: any;
22
+ [key: string]: JsonValue | undefined;
22
23
  }
23
24
  /**
24
25
  * 认证类型
@@ -32,7 +33,7 @@ export type AuthType = boolean | "admin" | "user" | string[];
32
33
  /**
33
34
  * API 处理器函数类型
34
35
  */
35
- export type ApiHandler<TBody = any, R = any> = (befly: BeflyContext, ctx: RequestContext<TBody>) => Promise<Response | R> | Response | R;
36
+ export type ApiHandler<TBody = Record<string, JsonValue>, R = JsonValue> = (befly: BeflyContext, ctx: RequestContext<TBody>) => Promise<Response | R> | Response | R;
36
37
  /**
37
38
  * 字段规则定义
38
39
  * 键为字段名,值为字段规则字符串
@@ -41,7 +42,7 @@ export type FieldRules = Record<string, string>;
41
42
  /**
42
43
  * API 配置选项
43
44
  */
44
- export interface ApiOptions<TBody = any, R = any> {
45
+ export interface ApiOptions<TBody = Record<string, JsonValue>, R = JsonValue> {
45
46
  /** HTTP 方法 */
46
47
  method: HttpMethod;
47
48
  /** 是否需要认证(true/false/角色数组) */
@@ -56,7 +57,7 @@ export interface ApiOptions<TBody = any, R = any> {
56
57
  /**
57
58
  * API 路由配置
58
59
  */
59
- export interface ApiRoute<TBody = any, R = any> {
60
+ export interface ApiRoute<TBody = Record<string, JsonValue>, R = JsonValue> {
60
61
  /** 接口名称(必填) */
61
62
  name: string;
62
63
  /** 处理器函数(必填) */
@@ -97,7 +98,7 @@ export interface ApiBuilder {
97
98
  /** 设置必填字段 */
98
99
  required(fields: string[]): this;
99
100
  /** 设置处理器 */
100
- handler<TBody = any, R = any>(handler: ApiHandler<TBody, R>): this;
101
+ handler<TBody = Record<string, JsonValue>, R = JsonValue>(handler: ApiHandler<TBody, R>): this;
101
102
  /** 构建路由 */
102
103
  build(): ApiRoute;
103
104
  }
@@ -106,11 +107,11 @@ export interface ApiBuilder {
106
107
  */
107
108
  export interface ApiResponse {
108
109
  /** 成功响应 */
109
- success<T = any>(message: string, data?: T): Response;
110
+ success<T = JsonValue>(message: string, data?: T): Response;
110
111
  /** 失败响应 */
111
- error(message: string, error?: any): Response;
112
+ error(message: string, error?: Error | JsonValue): Response;
112
113
  /** JSON 响应 */
113
- json<T = any>(data: T, status?: number): Response;
114
+ json<T = JsonValue>(data: T, status?: number): Response;
114
115
  /** 文本响应 */
115
116
  text(text: string, status?: number): Response;
116
117
  /** HTML 响应 */
@@ -147,7 +148,7 @@ export interface TokenCheckData {
147
148
  /** 令牌是否有效 */
148
149
  valid: boolean;
149
150
  /** JWT 载荷(有效时返回) */
150
- payload?: any;
151
+ payload?: JwtPayload;
151
152
  /** 过期时间(秒) */
152
153
  expiresIn?: number;
153
154
  }