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.
- package/dist/befly.js +443 -255
- package/dist/befly.min.js +11 -11
- package/dist/checks/checkApi.js +12 -12
- package/dist/index.js +14 -5
- package/dist/lib/asyncContext.js +4 -4
- package/dist/lib/cacheHelper.d.ts +2 -2
- package/dist/lib/dbHelper.d.ts +21 -13
- package/dist/lib/dbHelper.js +37 -9
- package/dist/lib/dbUtils.js +95 -20
- package/dist/lib/jwt.js +31 -19
- package/dist/lib/logger.js +7 -7
- package/dist/lib/sqlBuilder.js +53 -13
- package/dist/lib/validator.js +1 -1
- package/dist/loader/loadApis.js +3 -4
- package/dist/loader/loadHooks.js +1 -1
- package/dist/loader/loadPlugins.js +1 -1
- package/dist/router/api.js +8 -7
- package/dist/sync/syncApi.js +54 -31
- package/dist/sync/syncDev.js +88 -97
- package/dist/sync/syncTable.d.ts +1 -1
- package/dist/sync/syncTable.js +31 -10
- package/dist/types/common.d.ts +9 -9
- package/dist/types/database.d.ts +25 -22
- package/dist/types/sync.d.ts +2 -1
- package/dist/utils/loadMenuConfigs.js +1 -1
- package/dist/utils/loggerUtils.js +7 -3
- package/dist/utils/processAtSymbol.d.ts +1 -1
- package/dist/utils/processAtSymbol.js +2 -2
- package/dist/utils/processInfo.js +2 -2
- package/dist/utils/scanFiles.js +16 -12
- package/dist/utils/util.js +10 -3
- package/package.json +2 -2
package/dist/sync/syncTable.js
CHANGED
|
@@ -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
|
-
|
|
615
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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({
|
package/dist/types/common.d.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* SQL 值类型
|
|
7
7
|
*/
|
|
8
|
-
export type SqlValue = string | number | boolean | null | Date | Record<string,
|
|
8
|
+
export type SqlValue = string | number | boolean | null | Date | Record<string, unknown> | unknown[];
|
|
9
9
|
/**
|
|
10
10
|
* 通用键值对类型
|
|
11
11
|
*/
|
|
12
|
-
export type KeyValue<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,
|
|
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,
|
|
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:
|
|
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 =
|
|
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 =
|
|
126
|
+
export type AsyncFunction<T = unknown> = (...args: unknown[]) => Promise<T>;
|
|
127
127
|
/**
|
|
128
128
|
* 同步函数类型
|
|
129
129
|
*/
|
|
130
|
-
export type SyncFunction<T =
|
|
130
|
+
export type SyncFunction<T = unknown> = (...args: unknown[]) => T;
|
|
131
131
|
/**
|
|
132
132
|
* 通用回调函数
|
|
133
133
|
*/
|
|
134
|
-
export type Callback<T =
|
|
134
|
+
export type Callback<T = unknown> = (error: Error | null, result?: T) => void;
|
package/dist/types/database.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface DeleteOptions {
|
|
|
63
63
|
*/
|
|
64
64
|
export interface SqlInfo {
|
|
65
65
|
sql: string;
|
|
66
|
-
params:
|
|
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
|
|
80
|
-
data:
|
|
81
|
-
sql:
|
|
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
|
|
87
|
-
lists:
|
|
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
|
|
97
|
-
lists:
|
|
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<
|
|
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<
|
|
114
|
-
|
|
115
|
-
|
|
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<
|
|
118
|
+
getFieldValue<TValue = unknown>(options: Omit<QueryOptions, "fields"> & {
|
|
118
119
|
field: string;
|
|
119
|
-
}): Promise<DbResult<
|
|
120
|
-
insData(options: InsertOptions
|
|
121
|
-
|
|
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,
|
|
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?:
|
|
133
|
-
unsafe(sqlStr: string, params?:
|
|
134
|
-
trans<
|
|
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]:
|
|
140
|
+
[key: string]: unknown;
|
|
138
141
|
}
|
package/dist/types/sync.d.ts
CHANGED
|
@@ -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) =>
|
|
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
|
|
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
|
|
146
|
-
|
|
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,
|
|
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,
|
|
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}] (${
|
|
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
|
|
8
|
-
const pm2InstanceId = runtimeEnv
|
|
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 {
|
package/dist/utils/scanFiles.js
CHANGED
|
@@ -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:
|
|
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
|
|
100
|
+
base["content"] = content;
|
|
101
101
|
results.push(base);
|
|
102
102
|
continue;
|
|
103
103
|
}
|
|
104
104
|
if (type === "api") {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
base
|
|
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
|
|
113
|
-
base
|
|
114
|
-
base
|
|
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
|
|
121
|
-
base
|
|
124
|
+
base["routePrefix"] = source === "app" ? "/app" : `/addon/${addonName}`;
|
|
125
|
+
base["path"] = `/api${base["routePrefix"]}/${relativePath}`;
|
|
122
126
|
}
|
|
123
127
|
results.push(base);
|
|
124
128
|
}
|
package/dist/utils/util.js
CHANGED
|
@@ -108,7 +108,7 @@ function upperFirst(s) {
|
|
|
108
108
|
if (s.length === 0) {
|
|
109
109
|
return s;
|
|
110
110
|
}
|
|
111
|
-
return s
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.14.1",
|
|
4
|
+
"gitHead": "f836a457a4b5936d566051b629ed80eb264d6bf8",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|