befly 3.14.0 → 3.14.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/dist/befly.js CHANGED
@@ -105,16 +105,20 @@ function upperFirst(s) {
105
105
  if (s.length === 0) {
106
106
  return s;
107
107
  }
108
- return s[0].toUpperCase() + s.slice(1);
108
+ return s.charAt(0).toUpperCase() + s.slice(1);
109
109
  }
110
110
  function camelCase(input) {
111
111
  const parts = toWordParts(input);
112
112
  if (parts.length === 0) {
113
113
  return "";
114
114
  }
115
- const first = parts[0].toLowerCase();
115
+ const firstPart = parts[0];
116
+ if (!firstPart) {
117
+ return "";
118
+ }
119
+ const first = firstPart.toLowerCase();
116
120
  const rest = parts.slice(1).map((p) => upperFirst(p.toLowerCase()));
117
- return [first, ...rest].join("");
121
+ return [first].concat(rest).join("");
118
122
  }
119
123
  function normalizeToWords(input) {
120
124
  return String(input).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]+/g, " ").trim();
@@ -235,7 +239,7 @@ function sanitizeErrorValue(err, options) {
235
239
  message: truncateString(err.message || "", options.maxStringLen)
236
240
  };
237
241
  if (typeof err.stack === "string") {
238
- out.stack = truncateString(err.stack, options.maxStringLen);
242
+ out["stack"] = truncateString(err.stack, options.maxStringLen);
239
243
  }
240
244
  return out;
241
245
  }
@@ -323,8 +327,12 @@ function sanitizeAny(value, options, state, depth, visited) {
323
327
  const entries = Object.entries(obj);
324
328
  const limit = entries.length > options.sanitizeObjectKeys ? options.sanitizeObjectKeys : entries.length;
325
329
  for (let i = 0;i < limit; i++) {
326
- const key = entries[i][0];
327
- const child = entries[i][1];
330
+ const entry = entries[i];
331
+ if (!entry) {
332
+ continue;
333
+ }
334
+ const key = entry[0];
335
+ const child = entry[1];
328
336
  if (isSensitiveKey(key, options.sensitiveKeyMatcher)) {
329
337
  out[key] = "[MASKED]";
330
338
  continue;
@@ -365,10 +373,10 @@ function setCtxUser(userId, roleCode, nickname, roleType) {
365
373
  const store = storage.getStore();
366
374
  if (!store)
367
375
  return;
368
- store.userId = userId;
369
- store.roleCode = roleCode;
370
- store.nickname = nickname;
371
- store.roleType = roleType;
376
+ store.userId = userId === undefined ? null : userId;
377
+ store.roleCode = roleCode === undefined ? null : roleCode;
378
+ store.nickname = nickname === undefined ? null : nickname;
379
+ store.roleType = roleType === undefined ? null : roleType;
372
380
  }
373
381
  var storage;
374
382
  var init_asyncContext = __esm(() => {
@@ -798,8 +806,8 @@ function buildLogLine(level, record) {
798
806
  return `${safeJsonStringify(out)}
799
807
  `;
800
808
  }
801
- if (base.msg === undefined) {
802
- base.msg = "";
809
+ if (base["msg"] === undefined) {
810
+ base["msg"] = "";
803
811
  }
804
812
  return `${safeJsonStringify(base)}
805
813
  `;
@@ -809,7 +817,7 @@ function safeJsonStringify(obj) {
809
817
  return JSON.stringify(obj);
810
818
  } catch {
811
819
  try {
812
- return JSON.stringify({ level: obj.level, time: obj.time, pid: obj.pid, hostname: obj.hostname, msg: "[Unserializable log record]" });
820
+ return JSON.stringify({ level: obj["level"], time: obj["time"], pid: obj["pid"], hostname: obj["hostname"], msg: "[Unserializable log record]" });
813
821
  } catch {
814
822
  return '{"msg":"[Unserializable log record]"}';
815
823
  }
@@ -855,10 +863,10 @@ function metaToObject() {
855
863
  now: meta.now,
856
864
  durationSinceNowMs
857
865
  };
858
- obj.userId = meta.userId;
859
- obj.roleCode = meta.roleCode;
860
- obj.nickname = meta.nickname;
861
- obj.roleType = meta.roleType;
866
+ obj["userId"] = meta.userId;
867
+ obj["roleCode"] = meta.roleCode;
868
+ obj["nickname"] = meta.nickname;
869
+ obj["roleType"] = meta.roleType;
862
870
  return obj;
863
871
  }
864
872
  function mergeMetaIntoObject(input, meta) {
@@ -1309,7 +1317,7 @@ async function scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath = "") {
1309
1317
  return menus;
1310
1318
  }
1311
1319
  function getParentPath(path) {
1312
- const parts = path.split("/").filter((p) => !!p);
1320
+ const parts = path.split("/").filter((p) => Boolean(p));
1313
1321
  if (parts.length <= 1) {
1314
1322
  return "";
1315
1323
  }
@@ -8092,10 +8100,10 @@ async function checkTable(tables) {
8092
8100
  hasError = true;
8093
8101
  }
8094
8102
  } else if (fieldType === "string" || fieldType === "array_string" || fieldType === "array_number_string") {
8095
- if (fieldMax !== undefined && (fieldMax === null || typeof fieldMax !== "number")) {
8096
- Logger.warn(`${tablePrefix}${fileName} \u6587\u4EF6 ${colKey} \u4E3A ${fieldType} \u7C7B\u578B\uFF0C` + `\u6700\u5927\u957F\u5EA6\u5FC5\u987B\u4E3A\u6570\u5B57\uFF0C\u5F53\u524D\u4E3A "${fieldMax}"`);
8103
+ if (fieldMax === undefined || fieldMax === null || typeof fieldMax !== "number") {
8104
+ Logger.warn(`${tablePrefix}${fileName} \u6587\u4EF6 ${colKey} \u4E3A ${fieldType} \u7C7B\u578B\uFF0C` + `\u5FC5\u987B\u8BBE\u7F6E max \u4E14\u7C7B\u578B\u4E3A\u6570\u5B57\uFF1B\u5176\u4E2D array_*_string \u7684 max \u8868\u793A\u5355\u4E2A\u5143\u7D20\u957F\u5EA6\uFF0C\u5F53\u524D\u4E3A "${fieldMax}"`);
8097
8105
  hasError = true;
8098
- } else if (fieldMax !== undefined && fieldMax > MAX_VARCHAR_LENGTH) {
8106
+ } else if (fieldMax > MAX_VARCHAR_LENGTH) {
8099
8107
  Logger.warn(`${tablePrefix}${fileName} \u6587\u4EF6 ${colKey} \u6700\u5927\u957F\u5EA6 ${fieldMax} \u8D8A\u754C\uFF0C` + `${fieldType} \u7C7B\u578B\u957F\u5EA6\u5FC5\u987B\u5728 1..${MAX_VARCHAR_LENGTH} \u8303\u56F4\u5185`);
8100
8108
  hasError = true;
8101
8109
  }
@@ -8309,46 +8317,14 @@ init_logger();
8309
8317
 
8310
8318
  // loader/loadApis.ts
8311
8319
  init_logger();
8312
-
8313
- // configs/presetFields.ts
8314
- var presetFields = {
8315
- "@id": { name: "ID", type: "number", min: 1, max: null },
8316
- "@page": { name: "\u9875\u7801", type: "number", min: 1, max: 9999, default: 1 },
8317
- "@limit": { name: "\u6BCF\u9875\u6570\u91CF", type: "number", min: 1, max: 100, default: 30 },
8318
- "@keyword": { name: "\u5173\u952E\u8BCD", type: "string", min: 0, max: 50 },
8319
- "@state": { name: "\u72B6\u6001", type: "number", regex: "^[0-2]$" }
8320
- };
8321
-
8322
- // utils/processAtSymbol.ts
8323
- function processAtSymbol(fields, apiName, path) {
8324
- if (!fields || typeof fields !== "object")
8325
- return fields;
8326
- const processed = {};
8327
- for (const [key, value] of Object.entries(fields)) {
8328
- if (typeof value === "string" && value.startsWith("@")) {
8329
- if (presetFields[value]) {
8330
- processed[key] = presetFields[value];
8331
- continue;
8332
- }
8333
- const validKeys = Object.keys(presetFields).join(", ");
8334
- throw new Error(`API [${apiName}] (${path}) \u5B57\u6BB5 [${key}] \u5F15\u7528\u4E86\u672A\u5B9A\u4E49\u7684\u9884\u8BBE\u5B57\u6BB5 "${value}"\u3002\u53EF\u7528\u7684\u9884\u8BBE\u5B57\u6BB5\u6709: ${validKeys}`);
8335
- }
8336
- processed[key] = value;
8337
- }
8338
- return processed;
8339
- }
8340
-
8341
- // loader/loadApis.ts
8342
8320
  async function loadApis(apis) {
8343
8321
  const apisMap = new Map;
8344
8322
  for (const api of apis) {
8345
- if (api.type !== "api") {
8323
+ if (Object.hasOwn(api, "type") && api.type !== "api") {
8346
8324
  continue;
8347
8325
  }
8348
8326
  try {
8349
- const apiRoute = api;
8350
- apiRoute.fields = processAtSymbol(apiRoute.fields || {}, apiRoute.name, apiRoute.path);
8351
- apisMap.set(apiRoute.path, apiRoute);
8327
+ apisMap.set(api.path, api);
8352
8328
  } catch (error) {
8353
8329
  Logger.error({ err: error, api: api.relativePath, file: api.filePath, msg: "\u63A5\u53E3\u52A0\u8F7D\u5931\u8D25" });
8354
8330
  throw error;
@@ -8468,7 +8444,7 @@ async function loadHooks(hooks) {
8468
8444
  hooksMap.push({
8469
8445
  name: hookName,
8470
8446
  enable: true,
8471
- deps: hook.deps,
8447
+ deps: Array.isArray(hook.deps) ? hook.deps : [],
8472
8448
  handler: hook.handler
8473
8449
  });
8474
8450
  }
@@ -8502,7 +8478,7 @@ async function loadPlugins(plugins, context) {
8502
8478
  pluginsMap.push({
8503
8479
  name: pluginName,
8504
8480
  enable: true,
8505
- deps: plugin.deps,
8481
+ deps: Array.isArray(plugin.deps) ? plugin.deps : [],
8506
8482
  handler: plugin.handler
8507
8483
  });
8508
8484
  } catch (error) {
@@ -8641,11 +8617,12 @@ function apiHandler(apis, hooks, context) {
8641
8617
  requestId,
8642
8618
  corsHeaders: {
8643
8619
  "X-Request-ID": requestId
8644
- },
8645
- api: apis.get(apiPath),
8646
- response: undefined,
8647
- result: undefined
8620
+ }
8648
8621
  };
8622
+ const api = apis.get(apiPath);
8623
+ if (api) {
8624
+ ctx.api = api;
8625
+ }
8649
8626
  return withCtx({
8650
8627
  requestId,
8651
8628
  method: req.method,
@@ -8666,9 +8643,9 @@ function apiHandler(apis, hooks, context) {
8666
8643
  apiName: ctx.api.name
8667
8644
  };
8668
8645
  if (ctx.body && Object.keys(ctx.body).length > 0) {
8669
- logData.body = ctx.body;
8646
+ logData["body"] = ctx.body;
8670
8647
  }
8671
- logData.msg = "request";
8648
+ logData["msg"] = "request";
8672
8649
  Logger.info(logData);
8673
8650
  }
8674
8651
  if (!ctx.api) {
@@ -9061,7 +9038,7 @@ async function syncDev(ctx, config2 = {}) {
9061
9038
  apis: allApis.data.lists.map((item) => item.path).filter((v) => v),
9062
9039
  sort: 0
9063
9040
  };
9064
- if (devRole.data) {
9041
+ if (typeof devRole.data.id === "number") {
9065
9042
  await ctx.db.updData({
9066
9043
  table: "addon_admin_role",
9067
9044
  where: { code: "dev" },
@@ -9091,7 +9068,7 @@ async function syncDev(ctx, config2 = {}) {
9091
9068
  table: "addon_admin_admin",
9092
9069
  where: { username: "dev" }
9093
9070
  });
9094
- if (devAdmin.data) {
9071
+ if (typeof devAdmin.data.id === "number") {
9095
9072
  await ctx.db.updData({
9096
9073
  table: "addon_admin_admin",
9097
9074
  where: { username: "dev" },
@@ -9643,6 +9620,25 @@ class SqliteDialect {
9643
9620
 
9644
9621
  // sync/syncTable.ts
9645
9622
  init_logger();
9623
+
9624
+ // utils/normalizeFieldDefinition.ts
9625
+ function normalizeFieldDefinition(fieldDef) {
9626
+ return {
9627
+ name: fieldDef.name,
9628
+ type: fieldDef.type,
9629
+ detail: fieldDef.detail ?? "",
9630
+ min: fieldDef.min ?? null,
9631
+ max: fieldDef.max ?? null,
9632
+ default: fieldDef.default ?? null,
9633
+ index: fieldDef.index ?? false,
9634
+ unique: fieldDef.unique ?? false,
9635
+ nullable: fieldDef.nullable ?? false,
9636
+ unsigned: fieldDef.unsigned ?? false,
9637
+ regexp: fieldDef.regexp ?? null
9638
+ };
9639
+ }
9640
+
9641
+ // sync/syncTable.ts
9646
9642
  init_util();
9647
9643
  var syncTable = async (ctx, items) => {
9648
9644
  try {
@@ -9837,15 +9833,18 @@ function escapeComment(str) {
9837
9833
  return String(str).replace(/"/g, "\\\"");
9838
9834
  }
9839
9835
  function applyFieldDefaults(fieldDef) {
9840
- fieldDef.detail = fieldDef.detail ?? "";
9841
- fieldDef.min = fieldDef.min ?? 0;
9842
- fieldDef.max = fieldDef.max ?? (fieldDef.type === "number" ? Number.MAX_SAFE_INTEGER : 100);
9843
- fieldDef.default = fieldDef.default ?? null;
9844
- fieldDef.index = fieldDef.index ?? false;
9845
- fieldDef.unique = fieldDef.unique ?? false;
9846
- fieldDef.nullable = fieldDef.nullable ?? false;
9847
- fieldDef.unsigned = fieldDef.unsigned ?? true;
9848
- fieldDef.regexp = fieldDef.regexp ?? null;
9836
+ if (!fieldDef || typeof fieldDef !== "object")
9837
+ return;
9838
+ const normalized = normalizeFieldDefinition(fieldDef);
9839
+ fieldDef.detail = normalized.detail;
9840
+ fieldDef.min = normalized.min;
9841
+ fieldDef.max = normalized.max;
9842
+ fieldDef.default = normalized.default;
9843
+ fieldDef.index = normalized.index;
9844
+ fieldDef.unique = normalized.unique;
9845
+ fieldDef.nullable = normalized.nullable;
9846
+ fieldDef.unsigned = normalized.unsigned;
9847
+ fieldDef.regexp = normalized.regexp;
9849
9848
  }
9850
9849
  function isStringOrArrayType(fieldType) {
9851
9850
  return fieldType === "string" || fieldType === "array_string" || fieldType === "array_number_string";
@@ -9944,15 +9943,16 @@ function buildSystemColumnDefs(dbDialect) {
9944
9943
  function buildBusinessColumnDefs(dbDialect, fields) {
9945
9944
  const colDefs = [];
9946
9945
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
9946
+ const normalized = normalizeFieldDefinition(fieldDef);
9947
9947
  const dbFieldName = snakeCase(fieldKey);
9948
9948
  const colQuoted = quoteIdentifier(dbDialect, dbFieldName);
9949
- const sqlType = getSqlType(dbDialect, fieldDef.type, fieldDef.max, fieldDef.unsigned);
9950
- const actualDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
9951
- const defaultSql = generateDefaultSql(actualDefault, fieldDef.type);
9952
- const uniqueSql = fieldDef.unique ? " UNIQUE" : "";
9953
- const nullableSql = fieldDef.nullable ? " NULL" : " NOT NULL";
9949
+ const sqlType = getSqlType(dbDialect, normalized.type, normalized.max, normalized.unsigned);
9950
+ const actualDefault = resolveDefaultValue(normalized.default, normalized.type);
9951
+ const defaultSql = generateDefaultSql(actualDefault, normalized.type);
9952
+ const uniqueSql = normalized.unique ? " UNIQUE" : "";
9953
+ const nullableSql = normalized.nullable ? " NULL" : " NOT NULL";
9954
9954
  if (dbDialect === "mysql") {
9955
- colDefs.push(`${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(fieldDef.name)}"`);
9955
+ colDefs.push(`${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(normalized.name)}"`);
9956
9956
  } else {
9957
9957
  colDefs.push(`${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`);
9958
9958
  }
@@ -9962,13 +9962,14 @@ function buildBusinessColumnDefs(dbDialect, fields) {
9962
9962
  function generateDDLClause(dbDialect, fieldKey, fieldDef, isAdd = false) {
9963
9963
  const dbFieldName = snakeCase(fieldKey);
9964
9964
  const colQuoted = quoteIdentifier(dbDialect, dbFieldName);
9965
- const sqlType = getSqlType(dbDialect, fieldDef.type, fieldDef.max, fieldDef.unsigned);
9966
- const actualDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
9967
- const defaultSql = generateDefaultSql(actualDefault, fieldDef.type);
9968
- const uniqueSql = fieldDef.unique ? " UNIQUE" : "";
9969
- const nullableSql = fieldDef.nullable ? " NULL" : " NOT NULL";
9965
+ const normalized = normalizeFieldDefinition(fieldDef);
9966
+ const sqlType = getSqlType(dbDialect, normalized.type, normalized.max, normalized.unsigned);
9967
+ const actualDefault = resolveDefaultValue(normalized.default, normalized.type);
9968
+ const defaultSql = generateDefaultSql(actualDefault, normalized.type);
9969
+ const uniqueSql = normalized.unique ? " UNIQUE" : "";
9970
+ const nullableSql = normalized.nullable ? " NULL" : " NOT NULL";
9970
9971
  if (dbDialect === "mysql") {
9971
- return `${isAdd ? "ADD COLUMN" : "MODIFY COLUMN"} ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(fieldDef.name)}"`;
9972
+ return `${isAdd ? "ADD COLUMN" : "MODIFY COLUMN"} ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(normalized.name)}"`;
9972
9973
  }
9973
9974
  if (dbDialect === "postgresql") {
9974
9975
  if (isAdd)
@@ -10085,8 +10086,14 @@ async function getTableColumnsRuntime(runtime, tableName) {
10085
10086
  let max = null;
10086
10087
  const m = /^(\w+)\s*\((\d+)\)/.exec(baseType);
10087
10088
  if (m) {
10088
- baseType = m[1];
10089
- max = Number(m[2]);
10089
+ const base = m[1];
10090
+ const maxText = m[2];
10091
+ if (typeof base === "string") {
10092
+ baseType = base;
10093
+ }
10094
+ if (typeof maxText === "string") {
10095
+ max = Number(maxText);
10096
+ }
10090
10097
  }
10091
10098
  columns[row.name] = {
10092
10099
  type: baseType.toLowerCase(),
@@ -10115,9 +10122,13 @@ async function getTableIndexesRuntime(runtime, tableName) {
10115
10122
  const q = getSyncTableIndexesQuery({ dialect: "mysql", table: tableName, dbName: runtime.dbName });
10116
10123
  const result = await runtime.db.unsafe(q.sql, q.params);
10117
10124
  for (const row of result.data) {
10118
- if (!indexes[row.INDEX_NAME])
10119
- indexes[row.INDEX_NAME] = [];
10120
- indexes[row.INDEX_NAME].push(row.COLUMN_NAME);
10125
+ const indexName = row.INDEX_NAME;
10126
+ const current = indexes[indexName];
10127
+ if (Array.isArray(current)) {
10128
+ current.push(row.COLUMN_NAME);
10129
+ } else {
10130
+ indexes[indexName] = [row.COLUMN_NAME];
10131
+ }
10121
10132
  }
10122
10133
  } else if (runtime.dbDialect === "postgresql") {
10123
10134
  const q = getSyncTableIndexesQuery({ dialect: "postgresql", table: tableName, dbName: runtime.dbName });
@@ -10125,7 +10136,8 @@ async function getTableIndexesRuntime(runtime, tableName) {
10125
10136
  for (const row of result.data) {
10126
10137
  const m = /\(([^)]+)\)/.exec(row.indexdef);
10127
10138
  if (m) {
10128
- const col = m[1].replace(/"/g, "").trim();
10139
+ const colPart = m[1];
10140
+ const col = typeof colPart === "string" ? colPart.replace(/"/g, "").trim() : "";
10129
10141
  indexes[row.indexname] = [col];
10130
10142
  }
10131
10143
  }
@@ -10158,7 +10170,8 @@ async function ensureDbVersion(dbDialect, db) {
10158
10170
  throw new Error("\u65E0\u6CD5\u83B7\u53D6 MySQL \u7248\u672C\u4FE1\u606F");
10159
10171
  }
10160
10172
  const version = r.data[0].version;
10161
- const majorVersion = parseInt(String(version).split(".")[0], 10);
10173
+ const majorPart = String(version).split(".")[0] || "0";
10174
+ const majorVersion = parseInt(majorPart, 10);
10162
10175
  if (!Number.isFinite(majorVersion) || majorVersion < DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR) {
10163
10176
  throw new Error(`\u6B64\u811A\u672C\u4EC5\u652F\u6301 MySQL ${DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR}.0+\uFF0C\u5F53\u524D\u7248\u672C: ${version}`);
10164
10177
  }
@@ -10171,7 +10184,8 @@ async function ensureDbVersion(dbDialect, db) {
10171
10184
  }
10172
10185
  const versionText = r.data[0].version;
10173
10186
  const m = /PostgreSQL\s+(\d+)/i.exec(versionText);
10174
- const major = m ? parseInt(m[1], 10) : NaN;
10187
+ const majorText = m ? m[1] : undefined;
10188
+ const major = typeof majorText === "string" ? parseInt(majorText, 10) : NaN;
10175
10189
  if (!Number.isFinite(major) || major < DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR) {
10176
10190
  throw new Error(`\u6B64\u811A\u672C\u8981\u6C42 PostgreSQL >= ${DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR}\uFF0C\u5F53\u524D: ${versionText}`);
10177
10191
  }
@@ -10183,7 +10197,10 @@ async function ensureDbVersion(dbDialect, db) {
10183
10197
  throw new Error("\u65E0\u6CD5\u83B7\u53D6 SQLite \u7248\u672C\u4FE1\u606F");
10184
10198
  }
10185
10199
  const version = r.data[0].version;
10186
- const [maj, min, patch] = String(version).split(".").map((v) => parseInt(v, 10) || 0);
10200
+ const parts = String(version).split(".").map((v) => parseInt(v, 10) || 0);
10201
+ const maj = parts[0] ?? 0;
10202
+ const min = parts[1] ?? 0;
10203
+ const patch = parts[2] ?? 0;
10187
10204
  const vnum = maj * 1e4 + min * 100 + patch;
10188
10205
  if (!Number.isFinite(vnum) || vnum < DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION_NUM) {
10189
10206
  throw new Error(`\u6B64\u811A\u672C\u8981\u6C42 SQLite >= ${DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION}\uFF0C\u5F53\u524D: ${version}`);
@@ -10193,27 +10210,33 @@ async function ensureDbVersion(dbDialect, db) {
10193
10210
  }
10194
10211
  function compareFieldDefinition(dbDialect, existingColumn, fieldDef) {
10195
10212
  const changes = [];
10196
- if (dbDialect !== "sqlite" && isStringOrArrayType(fieldDef.type)) {
10197
- if (existingColumn.max !== fieldDef.max) {
10213
+ const normalized = normalizeFieldDefinition(fieldDef);
10214
+ if (dbDialect !== "sqlite" && isStringOrArrayType(normalized.type)) {
10215
+ const expectedMax = normalized.max;
10216
+ if (expectedMax !== null && existingColumn.max !== expectedMax) {
10198
10217
  changes.push({
10199
10218
  type: "length",
10200
10219
  current: existingColumn.max,
10201
- expected: fieldDef.max
10220
+ expected: expectedMax
10202
10221
  });
10203
10222
  }
10204
10223
  }
10205
10224
  if (dbDialect !== "sqlite") {
10206
10225
  const currentComment = existingColumn.comment || "";
10207
- if (currentComment !== fieldDef.name) {
10226
+ if (currentComment !== normalized.name) {
10208
10227
  changes.push({
10209
10228
  type: "comment",
10210
10229
  current: currentComment,
10211
- expected: fieldDef.name
10230
+ expected: normalized.name
10212
10231
  });
10213
10232
  }
10214
10233
  }
10215
10234
  const typeMapping = getTypeMapping(dbDialect);
10216
- const expectedType = typeMapping[fieldDef.type].toLowerCase();
10235
+ const mapped = typeMapping[normalized.type];
10236
+ if (typeof mapped !== "string") {
10237
+ throw new Error(`\u672A\u77E5\u5B57\u6BB5\u7C7B\u578B\u6620\u5C04\uFF1Adialect=${dbDialect} type=${String(normalized.type)}`);
10238
+ }
10239
+ const expectedType = mapped.toLowerCase();
10217
10240
  const currentType = existingColumn.type.toLowerCase();
10218
10241
  if (currentType !== expectedType) {
10219
10242
  changes.push({
@@ -10222,7 +10245,7 @@ function compareFieldDefinition(dbDialect, existingColumn, fieldDef) {
10222
10245
  expected: expectedType
10223
10246
  });
10224
10247
  }
10225
- const expectedNullable = fieldDef.nullable;
10248
+ const expectedNullable = normalized.nullable;
10226
10249
  if (existingColumn.nullable !== expectedNullable) {
10227
10250
  changes.push({
10228
10251
  type: "nullable",
@@ -10230,7 +10253,7 @@ function compareFieldDefinition(dbDialect, existingColumn, fieldDef) {
10230
10253
  expected: expectedNullable
10231
10254
  });
10232
10255
  }
10233
- const expectedDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
10256
+ const expectedDefault = resolveDefaultValue(normalized.default, normalized.type);
10234
10257
  if (String(existingColumn.defaultValue) !== String(expectedDefault)) {
10235
10258
  changes.push({
10236
10259
  type: "default",
@@ -10390,7 +10413,7 @@ async function modifyTableRuntime(runtime, tableName, fields) {
10390
10413
  const changeLabel = CHANGE_TYPE_LABELS[c.type] || "\u672A\u77E5";
10391
10414
  Logger.debug(` ~ \u4FEE\u6539 ${dbFieldName} ${changeLabel}: ${c.current} -> ${c.expected}`);
10392
10415
  }
10393
- if (isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && fieldDef.max !== null) {
10416
+ if (isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && typeof fieldDef.max === "number") {
10394
10417
  if (existingColumns[dbFieldName].max > fieldDef.max) {
10395
10418
  Logger.warn(`[\u8DF3\u8FC7\u5371\u9669\u53D8\u66F4] ${tableName}.${dbFieldName} \u957F\u5EA6\u6536\u7F29 ${existingColumns[dbFieldName].max} -> ${fieldDef.max} \u5DF2\u88AB\u8DF3\u8FC7\uFF08\u9700\u624B\u52A8\u5904\u7406\uFF09`);
10396
10419
  }
@@ -10412,7 +10435,7 @@ async function modifyTableRuntime(runtime, tableName, fields) {
10412
10435
  Logger.debug(`[\u517C\u5BB9\u7C7B\u578B\u53D8\u66F4] ${tableName}.${dbFieldName} ${currentType} -> ${expectedType}`);
10413
10436
  }
10414
10437
  if (defaultChanged) {
10415
- const actualDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
10438
+ const actualDefault = resolveDefaultValue(fieldDef.default ?? null, fieldDef.type);
10416
10439
  let v = null;
10417
10440
  if (actualDefault !== "null") {
10418
10441
  const defaultSql = generateDefaultSql(actualDefault, fieldDef.type);
@@ -10432,7 +10455,7 @@ async function modifyTableRuntime(runtime, tableName, fields) {
10432
10455
  }
10433
10456
  if (!onlyDefaultChanged) {
10434
10457
  let skipModify = false;
10435
- if (hasLengthChange && isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && fieldDef.max !== null) {
10458
+ if (hasLengthChange && isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && typeof fieldDef.max === "number") {
10436
10459
  const isShrink = existingColumns[dbFieldName].max > fieldDef.max;
10437
10460
  if (isShrink)
10438
10461
  skipModify = true;
@@ -10523,8 +10546,8 @@ var calcPerfTime = (startTime, endTime = Bun.nanoseconds()) => {
10523
10546
  // utils/processInfo.ts
10524
10547
  function getProcessRole(env) {
10525
10548
  const runtimeEnv = env || {};
10526
- const bunWorkerId = runtimeEnv.BUN_WORKER_ID;
10527
- const pm2InstanceId = runtimeEnv.PM2_INSTANCE_ID;
10549
+ const bunWorkerId = runtimeEnv["BUN_WORKER_ID"];
10550
+ const pm2InstanceId = runtimeEnv["PM2_INSTANCE_ID"];
10528
10551
  if (bunWorkerId !== undefined) {
10529
10552
  return {
10530
10553
  role: bunWorkerId === "" ? "primary" : "worker",
@@ -12290,7 +12313,9 @@ class Validator {
12290
12313
  return this.buildResult(fieldErrors);
12291
12314
  }
12292
12315
  static single(value, fieldDef) {
12293
- const { type, default: defaultValue } = fieldDef;
12316
+ const normalized = normalizeFieldDefinition(fieldDef);
12317
+ const type = normalized.type;
12318
+ const defaultValue = normalized.default;
12294
12319
  if (value === undefined || value === null || value === "") {
12295
12320
  return { value: this.defaultFor(type, defaultValue), error: null };
12296
12321
  }
@@ -12311,7 +12336,7 @@ class Validator {
12311
12336
  return {
12312
12337
  code: failed ? 1 : 0,
12313
12338
  failed,
12314
- firstError: failed ? errors[0] : null,
12339
+ firstError: failed ? errors[0] ?? null : null,
12315
12340
  errors,
12316
12341
  errorFields,
12317
12342
  fieldErrors
@@ -12359,7 +12384,11 @@ class Validator {
12359
12384
  }
12360
12385
  }
12361
12386
  static checkRule(value, fieldDef) {
12362
- const { type, min, max, regexp } = fieldDef;
12387
+ const normalized = normalizeFieldDefinition(fieldDef);
12388
+ const type = normalized.type;
12389
+ const min = normalized.min;
12390
+ const max = normalized.max;
12391
+ const regexp = normalized.regexp;
12363
12392
  const regex = this.resolveRegex(regexp);
12364
12393
  switch (type.toLowerCase()) {
12365
12394
  case "number":
@@ -12461,6 +12490,7 @@ var validatorHook = {
12461
12490
  const rawBody = isPlainObject(ctx.body) ? ctx.body : {};
12462
12491
  const nextBody = {};
12463
12492
  for (const [field, fieldDef] of Object.entries(ctx.api.fields)) {
12493
+ const normalized = normalizeFieldDefinition(fieldDef);
12464
12494
  let value = rawBody[field];
12465
12495
  if (value === undefined) {
12466
12496
  const snakeField = snakeCase(field);
@@ -12468,8 +12498,8 @@ var validatorHook = {
12468
12498
  value = rawBody[snakeField];
12469
12499
  }
12470
12500
  }
12471
- if (value === undefined && fieldDef?.default !== undefined && fieldDef?.default !== null) {
12472
- value = fieldDef.default;
12501
+ if (value === undefined && normalized.default !== null) {
12502
+ value = normalized.default;
12473
12503
  }
12474
12504
  if (value !== undefined) {
12475
12505
  nextBody[field] = value;
@@ -12825,18 +12855,44 @@ class DbUtils {
12825
12855
  throw new Error("tableRef \u4E0D\u80FD\u4E3A\u7A7A");
12826
12856
  }
12827
12857
  const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
12858
+ if (parts.length === 0) {
12859
+ throw new Error("tableRef \u4E0D\u80FD\u4E3A\u7A7A");
12860
+ }
12828
12861
  if (parts.length > 2) {
12829
12862
  throw new Error(`\u4E0D\u652F\u6301\u7684\u8868\u5F15\u7528\u683C\u5F0F\uFF08\u5305\u542B\u8FC7\u591A\u7247\u6BB5\uFF09\u3002\u8BF7\u4F7F\u7528\u6700\u7B80\u5F62\u5F0F\uFF1Atable \u6216 table alias \u6216 schema.table \u6216 schema.table alias (tableRef: ${trimmed})`);
12830
12863
  }
12831
12864
  const namePart = parts[0];
12832
- const aliasPart = parts.length === 2 ? parts[1] : null;
12865
+ if (typeof namePart !== "string" || namePart.trim() === "") {
12866
+ throw new Error(`tableRef \u89E3\u6790\u5931\u8D25\uFF1A\u7F3A\u5C11\u8868\u540D (tableRef: ${trimmed})`);
12867
+ }
12868
+ let aliasPart = null;
12869
+ if (parts.length === 2) {
12870
+ const alias = parts[1];
12871
+ if (typeof alias !== "string" || alias.trim() === "") {
12872
+ throw new Error(`tableRef \u89E3\u6790\u5931\u8D25\uFF1A\u7F3A\u5C11 alias (tableRef: ${trimmed})`);
12873
+ }
12874
+ aliasPart = alias;
12875
+ }
12833
12876
  const nameSegments = namePart.split(".");
12834
12877
  if (nameSegments.length > 2) {
12835
12878
  throw new Error(`\u4E0D\u652F\u6301\u7684\u8868\u5F15\u7528\u683C\u5F0F\uFF08schema \u5C42\u7EA7\u8FC7\u6DF1\uFF09 (tableRef: ${trimmed})`);
12836
12879
  }
12837
- const schema = nameSegments.length === 2 ? nameSegments[0] : null;
12838
- const table = nameSegments.length === 2 ? nameSegments[1] : nameSegments[0];
12839
- return { schema, table, alias: aliasPart };
12880
+ if (nameSegments.length === 2) {
12881
+ const schema = nameSegments[0];
12882
+ const table2 = nameSegments[1];
12883
+ if (typeof schema !== "string" || schema.trim() === "") {
12884
+ throw new Error(`tableRef \u89E3\u6790\u5931\u8D25\uFF1Aschema \u4E3A\u7A7A (tableRef: ${trimmed})`);
12885
+ }
12886
+ if (typeof table2 !== "string" || table2.trim() === "") {
12887
+ throw new Error(`tableRef \u89E3\u6790\u5931\u8D25\uFF1Atable \u4E3A\u7A7A (tableRef: ${trimmed})`);
12888
+ }
12889
+ return { schema, table: table2, alias: aliasPart };
12890
+ }
12891
+ const table = nameSegments[0];
12892
+ if (typeof table !== "string" || table.trim() === "") {
12893
+ throw new Error(`tableRef \u89E3\u6790\u5931\u8D25\uFF1Atable \u4E3A\u7A7A (tableRef: ${trimmed})`);
12894
+ }
12895
+ return { schema: null, table, alias: aliasPart };
12840
12896
  }
12841
12897
  static normalizeTableRef(tableRef) {
12842
12898
  const parsed = DbUtils.parseTableRef(tableRef);
@@ -12914,7 +12970,15 @@ class DbUtils {
12914
12970
  if (typeof item !== "string" || !item.includes("#")) {
12915
12971
  return item;
12916
12972
  }
12917
- const [field, direction] = item.split("#");
12973
+ const parts = item.split("#");
12974
+ if (parts.length !== 2) {
12975
+ return item;
12976
+ }
12977
+ const field = parts[0];
12978
+ const direction = parts[1];
12979
+ if (typeof field !== "string" || typeof direction !== "string") {
12980
+ return item;
12981
+ }
12918
12982
  return `${snakeCase(field.trim())}#${direction.trim()}`;
12919
12983
  });
12920
12984
  }
@@ -12924,14 +12988,26 @@ class DbUtils {
12924
12988
  }
12925
12989
  if (field.toUpperCase().includes(" AS ")) {
12926
12990
  const parts = field.split(/\s+AS\s+/i);
12927
- const fieldPart = parts[0].trim();
12928
- const aliasPart = parts[1].trim();
12929
- return `${DbUtils.processJoinField(fieldPart)} AS ${aliasPart}`;
12991
+ const fieldPart = parts[0];
12992
+ const aliasPart = parts[1];
12993
+ if (typeof fieldPart !== "string" || typeof aliasPart !== "string") {
12994
+ return field;
12995
+ }
12996
+ return `${DbUtils.processJoinField(fieldPart.trim())} AS ${aliasPart.trim()}`;
12930
12997
  }
12931
12998
  if (field.includes(".")) {
12932
12999
  const parts = field.split(".");
12933
- const tableName = parts[0];
12934
- const fieldName = parts[1];
13000
+ if (parts.length < 2) {
13001
+ return snakeCase(field);
13002
+ }
13003
+ if (parts.some((p) => p.trim() === "")) {
13004
+ return field;
13005
+ }
13006
+ const fieldName = parts[parts.length - 1];
13007
+ const tableName = parts.slice(0, parts.length - 1).join(".");
13008
+ if (typeof fieldName !== "string" || typeof tableName !== "string") {
13009
+ return snakeCase(field);
13010
+ }
12935
13011
  return `${tableName.trim()}.${snakeCase(fieldName)}`;
12936
13012
  }
12937
13013
  return snakeCase(field);
@@ -12946,16 +13022,34 @@ class DbUtils {
12946
13022
  const operator = key.substring(lastDollarIndex);
12947
13023
  if (fieldPart.includes(".")) {
12948
13024
  const parts = fieldPart.split(".");
12949
- const tableName = parts[0];
12950
- const fieldName = parts[1];
13025
+ if (parts.length < 2) {
13026
+ return `${snakeCase(fieldPart)}${operator}`;
13027
+ }
13028
+ if (parts.some((p) => p.trim() === "")) {
13029
+ return `${snakeCase(fieldPart)}${operator}`;
13030
+ }
13031
+ const fieldName = parts[parts.length - 1];
13032
+ const tableName = parts.slice(0, parts.length - 1).join(".");
13033
+ if (typeof fieldName !== "string" || typeof tableName !== "string") {
13034
+ return `${snakeCase(fieldPart)}${operator}`;
13035
+ }
12951
13036
  return `${tableName.trim()}.${snakeCase(fieldName)}${operator}`;
12952
13037
  }
12953
13038
  return `${snakeCase(fieldPart)}${operator}`;
12954
13039
  }
12955
13040
  if (key.includes(".")) {
12956
13041
  const parts = key.split(".");
12957
- const tableName = parts[0];
12958
- const fieldName = parts[1];
13042
+ if (parts.length < 2) {
13043
+ return snakeCase(key);
13044
+ }
13045
+ if (parts.some((p) => p.trim() === "")) {
13046
+ return snakeCase(key);
13047
+ }
13048
+ const fieldName = parts[parts.length - 1];
13049
+ const tableName = parts.slice(0, parts.length - 1).join(".");
13050
+ if (typeof fieldName !== "string" || typeof tableName !== "string") {
13051
+ return snakeCase(key);
13052
+ }
12959
13053
  return `${tableName.trim()}.${snakeCase(fieldName)}`;
12960
13054
  }
12961
13055
  return snakeCase(key);
@@ -12988,7 +13082,15 @@ class DbUtils {
12988
13082
  if (typeof item !== "string" || !item.includes("#")) {
12989
13083
  return item;
12990
13084
  }
12991
- const [field, direction] = item.split("#");
13085
+ const parts = item.split("#");
13086
+ if (parts.length !== 2) {
13087
+ return item;
13088
+ }
13089
+ const field = parts[0];
13090
+ const direction = parts[1];
13091
+ if (typeof field !== "string" || typeof direction !== "string") {
13092
+ return item;
13093
+ }
12992
13094
  return `${DbUtils.processJoinField(field.trim())}#${direction.trim()}`;
12993
13095
  });
12994
13096
  }
@@ -13109,10 +13211,10 @@ class DbUtils {
13109
13211
  for (const [key, value] of Object.entries(userData)) {
13110
13212
  result[key] = value;
13111
13213
  }
13112
- result.id = options.id;
13113
- result.created_at = options.now;
13114
- result.updated_at = options.now;
13115
- result.state = 1;
13214
+ result["id"] = options.id;
13215
+ result["created_at"] = options.now;
13216
+ result["updated_at"] = options.now;
13217
+ result["state"] = 1;
13116
13218
  return result;
13117
13219
  }
13118
13220
  static buildUpdateRow(options) {
@@ -13122,7 +13224,7 @@ class DbUtils {
13122
13224
  for (const [key, value] of Object.entries(userData)) {
13123
13225
  result[key] = value;
13124
13226
  }
13125
- result.updated_at = options.now;
13227
+ result["updated_at"] = options.now;
13126
13228
  return result;
13127
13229
  }
13128
13230
  static buildPartialUpdateData(options) {
@@ -13246,6 +13348,7 @@ var SqlBuilderError = {
13246
13348
  QUOTE_IDENT_NEED_STRING: (identifier) => `quoteIdent \u9700\u8981\u5B57\u7B26\u4E32\u7C7B\u578B\u6807\u8BC6\u7B26 (identifier: ${String(identifier)})`,
13247
13349
  IDENT_EMPTY: "SQL \u6807\u8BC6\u7B26\u4E0D\u80FD\u4E3A\u7A7A",
13248
13350
  FIELD_EXPR_NOT_ALLOWED: (field) => `\u5B57\u6BB5\u5305\u542B\u51FD\u6570/\u8868\u8FBE\u5F0F\uFF0C\u8BF7\u4F7F\u7528 selectRaw/whereRaw (field: ${field})`,
13351
+ FIELD_INVALID: (field) => `\u5B57\u6BB5\u683C\u5F0F\u975E\u6CD5\uFF0C\u8BF7\u4F7F\u7528\u7B80\u5355\u5B57\u6BB5\u540D\u6216\u5B89\u5168\u5F15\u7528\uFF0C\u590D\u6742\u8868\u8FBE\u5F0F\u8BF7\u4F7F\u7528 selectRaw/whereRaw (field: ${field})`,
13249
13352
  FROM_EMPTY: "FROM \u8868\u540D\u4E0D\u80FD\u4E3A\u7A7A",
13250
13353
  FROM_NEED_NON_EMPTY: (table) => `FROM \u8868\u540D\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32 (table: ${String(table)})`,
13251
13354
  FROM_REQUIRED: "FROM \u8868\u540D\u662F\u5FC5\u9700\u7684",
@@ -13349,10 +13452,18 @@ class SqlBuilder {
13349
13452
  }
13350
13453
  if (field.toUpperCase().includes(" AS ")) {
13351
13454
  const parts = field.split(/\s+AS\s+/i);
13352
- const fieldPart = parts[0].trim();
13353
- const aliasPart = parts[1].trim();
13354
- SqlCheck.assertSafeAlias(aliasPart);
13355
- return `${this._escapeField(fieldPart)} AS ${aliasPart}`;
13455
+ if (parts.length !== 2) {
13456
+ throw new Error(SqlBuilderError.FIELD_INVALID(field));
13457
+ }
13458
+ const fieldPart = parts[0];
13459
+ const aliasPart = parts[1];
13460
+ if (typeof fieldPart !== "string" || typeof aliasPart !== "string") {
13461
+ throw new Error(SqlBuilderError.FIELD_INVALID(field));
13462
+ }
13463
+ const cleanFieldPart = fieldPart.trim();
13464
+ const cleanAliasPart = aliasPart.trim();
13465
+ SqlCheck.assertSafeAlias(cleanAliasPart);
13466
+ return `${this._escapeField(cleanFieldPart)} AS ${cleanAliasPart}`;
13356
13467
  }
13357
13468
  if (field.includes(".")) {
13358
13469
  const parts = field.split(".");
@@ -13386,16 +13497,26 @@ class SqlBuilder {
13386
13497
  if (parts.length > 2) {
13387
13498
  throw new Error(SqlBuilderError.TABLE_REF_TOO_MANY_PARTS(table));
13388
13499
  }
13389
- const namePart = parts[0].trim();
13390
- const aliasPart = parts.length === 2 ? parts[1].trim() : null;
13500
+ const namePartRaw = parts[0];
13501
+ if (typeof namePartRaw !== "string" || namePartRaw.trim() === "") {
13502
+ throw new Error(SqlBuilderError.FROM_EMPTY);
13503
+ }
13504
+ const namePart = namePartRaw.trim();
13505
+ const aliasPartRaw = parts.length === 2 ? parts[1] : null;
13506
+ const aliasPart = typeof aliasPartRaw === "string" ? aliasPartRaw.trim() : null;
13391
13507
  const nameSegments = namePart.split(".");
13392
13508
  if (nameSegments.length > 2) {
13393
13509
  throw new Error(SqlBuilderError.TABLE_REF_SCHEMA_TOO_DEEP(table));
13394
13510
  }
13395
13511
  let escapedName = "";
13396
13512
  if (nameSegments.length === 2) {
13397
- const schema = nameSegments[0].trim();
13398
- const tableName = nameSegments[1].trim();
13513
+ const schemaRaw = nameSegments[0];
13514
+ const tableNameRaw = nameSegments[1];
13515
+ if (typeof schemaRaw !== "string" || typeof tableNameRaw !== "string") {
13516
+ throw new Error(SqlBuilderError.TABLE_REF_SCHEMA_TOO_DEEP(table));
13517
+ }
13518
+ const schema = schemaRaw.trim();
13519
+ const tableName = tableNameRaw.trim();
13399
13520
  const escapedSchema = this._isQuotedIdent(schema) ? schema : (() => {
13400
13521
  if (this._startsWithQuote(schema) && !this._isQuotedIdent(schema)) {
13401
13522
  throw new Error(SqlBuilderError.SCHEMA_QUOTE_NOT_PAIRED(schema));
@@ -13412,7 +13533,11 @@ class SqlBuilder {
13412
13533
  })();
13413
13534
  escapedName = `${escapedSchema}.${escapedTableName}`;
13414
13535
  } else {
13415
- const tableName = nameSegments[0].trim();
13536
+ const tableNameRaw = nameSegments[0];
13537
+ if (typeof tableNameRaw !== "string") {
13538
+ throw new Error(SqlBuilderError.FROM_EMPTY);
13539
+ }
13540
+ const tableName = tableNameRaw.trim();
13416
13541
  if (this._isQuotedIdent(tableName)) {
13417
13542
  escapedName = tableName;
13418
13543
  } else {
@@ -13670,7 +13795,15 @@ class SqlBuilder {
13670
13795
  if (typeof item !== "string" || !item.includes("#")) {
13671
13796
  throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
13672
13797
  }
13673
- const [fieldName, direction] = item.split("#");
13798
+ const parts = item.split("#");
13799
+ if (parts.length !== 2) {
13800
+ throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
13801
+ }
13802
+ const fieldName = parts[0];
13803
+ const direction = parts[1];
13804
+ if (typeof fieldName !== "string" || typeof direction !== "string") {
13805
+ throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
13806
+ }
13674
13807
  const cleanField = fieldName.trim();
13675
13808
  const cleanDir = direction.trim().toUpperCase();
13676
13809
  if (!cleanField) {
@@ -13767,7 +13900,9 @@ class SqlBuilder {
13767
13900
  for (let i = 0;i < data.length; i++) {
13768
13901
  const row = data[i];
13769
13902
  for (const field of fields) {
13770
- params.push(row[field]);
13903
+ const value = row[field];
13904
+ this._validateParam(value);
13905
+ params.push(value);
13771
13906
  }
13772
13907
  }
13773
13908
  return { sql, params };
@@ -13782,7 +13917,12 @@ class SqlBuilder {
13782
13917
  const escapedFields = fields.map((field) => this._escapeField(field));
13783
13918
  const placeholders = fields.map(() => "?").join(", ");
13784
13919
  const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`;
13785
- const params = fields.map((field) => data[field]);
13920
+ const params = [];
13921
+ for (const field of fields) {
13922
+ const value = data[field];
13923
+ this._validateParam(value);
13924
+ params.push(value);
13925
+ }
13786
13926
  return { sql, params };
13787
13927
  }
13788
13928
  }
@@ -13884,7 +14024,9 @@ class SqlBuilder {
13884
14024
  }
13885
14025
  whenList.push("WHEN ? THEN ?");
13886
14026
  args.push(row.id);
13887
- args.push(row.data[field]);
14027
+ const value = row.data[field];
14028
+ SqlCheck.assertNoUndefinedParam(value, "SQL \u53C2\u6570\u503C");
14029
+ args.push(value);
13888
14030
  }
13889
14031
  if (whenList.length === 0) {
13890
14032
  continue;
@@ -13916,7 +14058,7 @@ class DbHelper {
13916
14058
  constructor(options) {
13917
14059
  this.redis = options.redis;
13918
14060
  this.sql = options.sql || null;
13919
- this.isTransaction = !!options.sql;
14061
+ this.isTransaction = Boolean(options.sql);
13920
14062
  this.dialect = options.dialect ? options.dialect : new MySqlDialect;
13921
14063
  }
13922
14064
  createSqlBuilder() {
@@ -14046,7 +14188,8 @@ class DbHelper {
14046
14188
  }
14047
14189
  async getCount(options) {
14048
14190
  const { table, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
14049
- const builder = this.createSqlBuilder().selectRaw("COUNT(*) as count").from(table).where(DbUtils.addDefaultStateFilter(where, tableQualifier, !!joins));
14191
+ const hasJoins = Array.isArray(joins) && joins.length > 0;
14192
+ const builder = this.createSqlBuilder().selectRaw("COUNT(*) as count").from(table).where(DbUtils.addDefaultStateFilter(where, tableQualifier, hasJoins));
14050
14193
  this.applyJoins(builder, joins);
14051
14194
  const { sql, params } = builder.toSelectSql();
14052
14195
  const execRes = await this.executeWithConn(sql, params);
@@ -14058,7 +14201,8 @@ class DbHelper {
14058
14201
  }
14059
14202
  async getOne(options) {
14060
14203
  const { table, fields, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
14061
- const builder = this.createSqlBuilder().select(fields).from(table).where(DbUtils.addDefaultStateFilter(where, tableQualifier, !!joins));
14204
+ const hasJoins = Array.isArray(joins) && joins.length > 0;
14205
+ const builder = this.createSqlBuilder().select(fields).from(table).where(DbUtils.addDefaultStateFilter(where, tableQualifier, hasJoins));
14062
14206
  this.applyJoins(builder, joins);
14063
14207
  const { sql, params } = builder.toSelectSql();
14064
14208
  const execRes = await this.executeWithConn(sql, params);
@@ -14066,7 +14210,7 @@ class DbHelper {
14066
14210
  const row = result?.[0] || null;
14067
14211
  if (!row) {
14068
14212
  return {
14069
- data: null,
14213
+ data: {},
14070
14214
  sql: execRes.sql
14071
14215
  };
14072
14216
  }
@@ -14074,16 +14218,20 @@ class DbHelper {
14074
14218
  const deserialized = DbUtils.deserializeArrayFields(camelRow);
14075
14219
  if (!deserialized) {
14076
14220
  return {
14077
- data: null,
14221
+ data: {},
14078
14222
  sql: execRes.sql
14079
14223
  };
14080
14224
  }
14081
- const data = convertBigIntFields([deserialized])[0];
14225
+ const convertedList = convertBigIntFields([deserialized]);
14226
+ const data = convertedList[0] ?? deserialized;
14082
14227
  return {
14083
14228
  data,
14084
14229
  sql: execRes.sql
14085
14230
  };
14086
14231
  }
14232
+ async getDetail(options) {
14233
+ return await this.getOne(options);
14234
+ }
14087
14235
  async getList(options) {
14088
14236
  const prepared = await this.prepareQueryOptions(options);
14089
14237
  if (prepared.page < 1 || prepared.page > 1e4) {
@@ -14092,7 +14240,7 @@ class DbHelper {
14092
14240
  if (prepared.limit < 1 || prepared.limit > 1000) {
14093
14241
  throw new Error(`\u6BCF\u9875\u6570\u91CF\u5FC5\u987B\u5728 1 \u5230 1000 \u4E4B\u95F4 (table: ${options.table}, page: ${prepared.page}, limit: ${prepared.limit})`);
14094
14242
  }
14095
- const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, !!prepared.joins);
14243
+ const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, Array.isArray(prepared.joins) && prepared.joins.length > 0);
14096
14244
  const countBuilder = this.createSqlBuilder().selectRaw("COUNT(*) as total").from(prepared.table).where(whereFiltered);
14097
14245
  this.applyJoins(countBuilder, prepared.joins);
14098
14246
  const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
@@ -14140,8 +14288,21 @@ class DbHelper {
14140
14288
  async getAll(options) {
14141
14289
  const MAX_LIMIT = 1e4;
14142
14290
  const WARNING_LIMIT = 1000;
14143
- const prepared = await this.prepareQueryOptions({ ...options, page: 1, limit: 10 });
14144
- const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, !!prepared.joins);
14291
+ const prepareOptions = {
14292
+ table: options.table,
14293
+ page: 1,
14294
+ limit: 10
14295
+ };
14296
+ if (options.fields !== undefined)
14297
+ prepareOptions.fields = options.fields;
14298
+ if (options.where !== undefined)
14299
+ prepareOptions.where = options.where;
14300
+ if (options.joins !== undefined)
14301
+ prepareOptions.joins = options.joins;
14302
+ if (options.orderBy !== undefined)
14303
+ prepareOptions.orderBy = options.orderBy;
14304
+ const prepared = await this.prepareQueryOptions(prepareOptions);
14305
+ const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, Array.isArray(prepared.joins) && prepared.joins.length > 0);
14145
14306
  const countBuilder = this.createSqlBuilder().selectRaw("COUNT(*) as total").from(prepared.table).where(whereFiltered);
14146
14307
  this.applyJoins(countBuilder, prepared.joins);
14147
14308
  const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
@@ -14201,7 +14362,7 @@ class DbHelper {
14201
14362
  const builder = this.createSqlBuilder();
14202
14363
  const { sql, params } = builder.toInsertSql(snakeTable, processed);
14203
14364
  const execRes = await this.executeWithConn(sql, params);
14204
- const insertedId = processed.id || execRes.data?.lastInsertRowid || 0;
14365
+ const insertedId = processed["id"] || execRes.data?.lastInsertRowid || 0;
14205
14366
  return {
14206
14367
  data: insertedId,
14207
14368
  sql: execRes.sql
@@ -14226,7 +14387,11 @@ class DbHelper {
14226
14387
  }
14227
14388
  const now = Date.now();
14228
14389
  const processedList = dataList.map((data, index) => {
14229
- return DbUtils.buildInsertRow({ data, id: ids[index], now });
14390
+ const id = ids[index];
14391
+ if (typeof id !== "number") {
14392
+ throw new Error(`\u6279\u91CF\u63D2\u5165\u751F\u6210 ID \u5931\u8D25\uFF1Aids[${index}] \u4E0D\u662F number (table: ${snakeTable})`);
14393
+ }
14394
+ return DbUtils.buildInsertRow({ data, id, now });
14230
14395
  });
14231
14396
  const insertFields = SqlCheck.assertBatchInsertRowsConsistent(processedList, { table: snakeTable });
14232
14397
  const builder = this.createSqlBuilder();
@@ -14398,36 +14563,41 @@ class DbHelper {
14398
14563
  };
14399
14564
  }
14400
14565
  async getFieldValue(options) {
14401
- const { field, ...queryOptions } = options;
14566
+ const field = options.field;
14402
14567
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
14403
14568
  throw new Error(`\u65E0\u6548\u7684\u5B57\u6BB5\u540D: ${field}\uFF0C\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF`);
14404
14569
  }
14405
- const oneRes = await this.getOne({
14406
- ...queryOptions,
14407
- fields: [field]
14408
- });
14570
+ const oneOptions = {
14571
+ table: options.table
14572
+ };
14573
+ if (options.where !== undefined)
14574
+ oneOptions.where = options.where;
14575
+ if (options.joins !== undefined)
14576
+ oneOptions.joins = options.joins;
14577
+ if (options.orderBy !== undefined)
14578
+ oneOptions.orderBy = options.orderBy;
14579
+ if (options.page !== undefined)
14580
+ oneOptions.page = options.page;
14581
+ if (options.limit !== undefined)
14582
+ oneOptions.limit = options.limit;
14583
+ oneOptions.fields = [field];
14584
+ const oneRes = await this.getOne(oneOptions);
14409
14585
  const result = oneRes.data;
14410
- if (!result) {
14411
- return {
14412
- data: null,
14413
- sql: oneRes.sql
14414
- };
14415
- }
14416
- if (field in result) {
14586
+ if (Object.hasOwn(result, field)) {
14417
14587
  return {
14418
14588
  data: result[field],
14419
14589
  sql: oneRes.sql
14420
14590
  };
14421
14591
  }
14422
14592
  const camelField = field.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
14423
- if (camelField !== field && camelField in result) {
14593
+ if (camelField !== field && Object.hasOwn(result, camelField)) {
14424
14594
  return {
14425
14595
  data: result[camelField],
14426
14596
  sql: oneRes.sql
14427
14597
  };
14428
14598
  }
14429
14599
  const snakeField = field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
14430
- if (snakeField !== field && snakeField in result) {
14600
+ if (snakeField !== field && Object.hasOwn(result, snakeField)) {
14431
14601
  return {
14432
14602
  data: result[snakeField],
14433
14603
  sql: oneRes.sql
@@ -14509,38 +14679,50 @@ class Jwt {
14509
14679
  };
14510
14680
  }
14511
14681
  sign(payload, options = {}) {
14512
- const key = options.secret || this.config.secret || "befly-secret";
14682
+ const key = options.secret || this.config.secret;
14513
14683
  const algorithm = options.algorithm || this.config.algorithm || "HS256";
14514
- const signer = import_fast_jwt.createSigner({
14684
+ const signerOptions = {
14515
14685
  key,
14516
14686
  algorithm,
14517
- expiresIn: options.expiresIn || this.config.expiresIn,
14518
- iss: options.issuer,
14519
- aud: options.audience,
14520
- sub: options.subject,
14521
- jti: options.jwtId,
14522
- notBefore: typeof options.notBefore === "number" ? options.notBefore : undefined
14523
- });
14687
+ expiresIn: options.expiresIn ?? this.config.expiresIn
14688
+ };
14689
+ if (options.issuer !== undefined)
14690
+ signerOptions.iss = options.issuer;
14691
+ if (options.audience !== undefined)
14692
+ signerOptions.aud = options.audience;
14693
+ if (options.subject !== undefined)
14694
+ signerOptions.sub = options.subject;
14695
+ if (options.jwtId !== undefined)
14696
+ signerOptions.jti = options.jwtId;
14697
+ if (typeof options.notBefore === "number")
14698
+ signerOptions.notBefore = options.notBefore;
14699
+ const signer = import_fast_jwt.createSigner(signerOptions);
14524
14700
  return signer(payload);
14525
14701
  }
14526
14702
  verify(token, options = {}) {
14527
14703
  if (!token || typeof token !== "string") {
14528
14704
  throw new Error("Token\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
14529
14705
  }
14530
- const key = options.secret || this.config.secret || "befly-secret";
14706
+ const key = options.secret || this.config.secret;
14531
14707
  const algorithm = this.config.algorithm || "HS256";
14532
14708
  const algorithms = options.algorithms ? options.algorithms : [algorithm];
14533
- const verifier = import_fast_jwt.createVerifier({
14709
+ const verifierOptions = {
14534
14710
  key,
14535
14711
  algorithms,
14536
- allowedIss: options.issuer,
14537
- allowedAud: options.audience,
14538
- allowedSub: options.subject,
14539
- ignoreExpiration: options.ignoreExpiration,
14540
- ignoreNotBefore: options.ignoreNotBefore,
14541
14712
  cache: true,
14542
14713
  cacheTTL: 600000
14543
- });
14714
+ };
14715
+ if (options.issuer !== undefined)
14716
+ verifierOptions.allowedIss = options.issuer;
14717
+ if (options.audience !== undefined)
14718
+ verifierOptions.allowedAud = options.audience;
14719
+ if (options.subject !== undefined)
14720
+ verifierOptions.allowedSub = options.subject;
14721
+ if (typeof options.ignoreExpiration === "boolean")
14722
+ verifierOptions.ignoreExpiration = options.ignoreExpiration;
14723
+ if (typeof options.ignoreNotBefore === "boolean")
14724
+ verifierOptions.ignoreNotBefore = options.ignoreNotBefore;
14725
+ const verifier = import_fast_jwt.createVerifier(verifierOptions);
14544
14726
  return verifier(token);
14545
14727
  }
14546
14728
  decode(token, complete = false) {
@@ -15151,7 +15333,7 @@ async function scanFiles(dir, source, type, pattern) {
15151
15333
  const base = {
15152
15334
  source,
15153
15335
  type,
15154
- sourceName: { core: "\u6838\u5FC3", addon: "\u7EC4\u4EF6", app: "\u9879\u76EE" }[source],
15336
+ sourceName: source === "core" ? "\u6838\u5FC3" : source === "addon" ? "\u7EC4\u4EF6" : "\u9879\u76EE",
15155
15337
  filePath: normalizedFile,
15156
15338
  relativePath,
15157
15339
  fileName,
@@ -15162,28 +15344,28 @@ async function scanFiles(dir, source, type, pattern) {
15162
15344
  customKeys: isPlainObject(content) ? Object.keys(content) : []
15163
15345
  };
15164
15346
  if (type === "table") {
15165
- base.content = content;
15347
+ base["content"] = content;
15166
15348
  results.push(base);
15167
15349
  continue;
15168
15350
  }
15169
15351
  if (type === "api") {
15170
- base.auth = true;
15171
- base.rawBody = false;
15172
- base.method = "POST";
15173
- base.fields = {};
15174
- base.required = [];
15352
+ base["auth"] = true;
15353
+ base["rawBody"] = false;
15354
+ base["method"] = "POST";
15355
+ base["fields"] = {};
15356
+ base["required"] = [];
15175
15357
  }
15176
15358
  if (type === "plugin" || type === "hook") {
15177
- base.deps = [];
15178
- base.name = "";
15179
- base.handler = null;
15359
+ base["deps"] = [];
15360
+ base["name"] = "";
15361
+ base["handler"] = null;
15180
15362
  }
15181
15363
  forOwn(content, (value, key) => {
15182
15364
  base[key] = value;
15183
15365
  });
15184
15366
  if (type === "api") {
15185
- base.routePrefix = source === "app" ? "/app" : `/addon/${addonName}`;
15186
- base.path = `/api${base.routePrefix}/${relativePath}`;
15367
+ base["routePrefix"] = source === "app" ? "/app" : `/addon/${addonName}`;
15368
+ base["path"] = `/api${base["routePrefix"]}/${relativePath}`;
15187
15369
  }
15188
15370
  results.push(base);
15189
15371
  }
@@ -15239,11 +15421,11 @@ class Befly {
15239
15421
  async start(env = {}) {
15240
15422
  try {
15241
15423
  const serverStartTime = Bun.nanoseconds();
15242
- this.config = await loadBeflyConfig(env.NODE_ENV || "development");
15424
+ this.config = await loadBeflyConfig(env["NODE_ENV"] || "development");
15243
15425
  this.context.config = this.config;
15244
15426
  this.context.env = env;
15245
15427
  const { apis, tables, plugins, hooks, addons } = await scanSources();
15246
- this.context.addons = addons;
15428
+ this.context["addons"] = addons;
15247
15429
  await checkApi(apis);
15248
15430
  await checkTable(tables);
15249
15431
  await checkPlugin(plugins);
@@ -15266,15 +15448,23 @@ class Befly {
15266
15448
  await syncTable(this.context, tables);
15267
15449
  await syncApi(this.context, apis);
15268
15450
  await syncMenu(this.context, checkedMenus);
15269
- await syncDev(this.context, { devEmail: this.config.devEmail, devPassword: this.config.devPassword });
15451
+ const devEmail = this.config.devEmail;
15452
+ const devPassword = this.config.devPassword;
15453
+ if (typeof devEmail === "string" && devEmail.length > 0 && typeof devPassword === "string" && devPassword.length > 0) {
15454
+ await syncDev(this.context, { devEmail, devPassword });
15455
+ } else {
15456
+ Logger.debug("\u8DF3\u8FC7 syncDev\uFF1A\u672A\u914D\u7F6E devEmail/devPassword");
15457
+ }
15270
15458
  await syncCache(this.context);
15271
15459
  this.hooks = await loadHooks(hooks);
15272
15460
  this.apis = await loadApis(apis);
15273
15461
  const apiFetch = apiHandler(this.apis, this.hooks, this.context);
15274
15462
  const staticFetch = staticHandler(this.config.cors);
15463
+ const port = typeof this.config.appPort === "number" ? this.config.appPort : 3000;
15464
+ const hostname = typeof this.config.appHost === "string" && this.config.appHost.length > 0 ? this.config.appHost : "0.0.0.0";
15275
15465
  const server = Bun.serve({
15276
- port: this.config.appPort,
15277
- hostname: this.config.appHost,
15466
+ port,
15467
+ hostname,
15278
15468
  development: this.config.nodeEnv === "development",
15279
15469
  idleTimeout: 30,
15280
15470
  fetch: async (req, bunServer) => {