befly 3.18.20 → 3.18.22

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.
@@ -2,8 +2,7 @@ import { fieldClear } from "../../utils/fieldClear.js";
2
2
  import { isFiniteNumber, isNonEmptyString, isNullable, isPlainObject, isString } from "../../utils/is.js";
3
3
  import { arrayKeysToCamel, keysToCamel, keysToSnake, snakeCase } from "../../utils/util.js";
4
4
  import { SqlBuilder } from "../sqlBuilder/index.js";
5
- import { convertBigIntFields, quoteIdentMySql } from "./context.js";
6
- import { getJoinMainQualifier, normalizeTableRef } from "./util.js";
5
+ import { convertBigIntFields, getJoinMainQualifier, normalizeTableRef, quoteIdentMySql } from "./util.js";
7
6
  import { assertNoExprField, validateAndClassifyFields, validateExcludeFieldsResult, validateQueryOptions, validateTimeIdValue } from "./validate.js";
8
7
 
9
8
  export function clearDeep(value, options) {
@@ -497,8 +496,8 @@ export function buildInsertRow(options) {
497
496
  if (options.beflyMode !== 0) {
498
497
  result["created_at"] = options.now;
499
498
  result["updated_at"] = options.now;
499
+ result["state"] = 1;
500
500
  }
501
- result["state"] = 1;
502
501
  return result;
503
502
  }
504
503
 
@@ -2,8 +2,7 @@ import { isNumber } from "../../utils/is.js";
2
2
  import { snakeCase } from "../../utils/util.js";
3
3
  import { Logger } from "../logger.js";
4
4
  import { SqlBuilder } from "../sqlBuilder/index.js";
5
- import { quoteIdentMySql } from "./context.js";
6
- import { toNumberFromSql } from "./util.js";
5
+ import { quoteIdentMySql, toNumberFromSql } from "./util.js";
7
6
  import { addDefaultStateFilter, buildInsertRow, buildPartialUpdateData, buildUpdateRow, clearDeep, whereKeysToSnake } from "./builders.js";
8
7
  import { assertBatchInsertRowsConsistent, assertNoUndefinedInRecord, validateGeneratedBatchId, validateIncrementOptions, validateInsertBatchSize, validateNoJoinReadOptions, validatePageLimitRange, validateSafeFieldName, validateTableBatchDataOptions, validateTableDataOptions, validateTableName, validateTableWhereOptions } from "./validate.js";
9
8
 
@@ -194,7 +193,12 @@ export const dataOpsMethods = {
194
193
 
195
194
  let processed;
196
195
  if (this.idMode === "autoId") {
197
- processed = buildInsertRow({ idMode: "autoId", data: data, now: now, beflyMode: this.beflyMode });
196
+ processed = buildInsertRow({
197
+ idMode: "autoId",
198
+ data: data,
199
+ now: now,
200
+ beflyMode: this.beflyMode
201
+ });
198
202
  } else {
199
203
  let id;
200
204
  try {
@@ -208,7 +212,13 @@ export const dataOpsMethods = {
208
212
  table: table
209
213
  });
210
214
  }
211
- processed = buildInsertRow({ idMode: "timeId", data: data, id: id, now: now, beflyMode: this.beflyMode });
215
+ processed = buildInsertRow({
216
+ idMode: "timeId",
217
+ data: data,
218
+ id: id,
219
+ now: now,
220
+ beflyMode: this.beflyMode
221
+ });
212
222
  }
213
223
 
214
224
  assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
@@ -421,9 +431,17 @@ export const dataOpsMethods = {
421
431
  validateTableWhereOptions(options, "delData", true);
422
432
  const { table, where } = options;
423
433
 
434
+ // beflyMode=0:关闭默认 state 软删语义,delData 直接走物理删除
435
+ if (this.beflyMode === 0) {
436
+ return await this.delForce({
437
+ table: table,
438
+ where: where
439
+ });
440
+ }
441
+
424
442
  return await this.updData({
425
443
  table: table,
426
- data: this.beflyMode === 1 ? { state: 0, deleted_at: Date.now() } : { state: 0 },
444
+ data: { state: 0, deleted_at: Date.now() },
427
445
  where: where
428
446
  });
429
447
  },
@@ -501,5 +519,58 @@ export const dataOpsMethods = {
501
519
 
502
520
  async decrement(table, field, where, value = 1) {
503
521
  return await this.increment(table, field, where, -value);
522
+ },
523
+
524
+ async trans(callback) {
525
+ const abortMark = "__beflyTransAbort";
526
+
527
+ if (this.isTransaction) {
528
+ const result = await callback(this);
529
+ if (result?.code !== undefined && result.code !== 0) {
530
+ const abortError = new Error("TRANSACTION_ABORT", {
531
+ cause: null,
532
+ code: "runtime"
533
+ });
534
+ abortError[abortMark] = true;
535
+ abortError.payload = result;
536
+ throw abortError;
537
+ }
538
+ return result;
539
+ }
540
+
541
+ if (typeof this.sql?.begin !== "function") {
542
+ throw new Error("不支持事务 begin() 方法", {
543
+ cause: null,
544
+ code: "runtime"
545
+ });
546
+ }
547
+
548
+ try {
549
+ return await this.sql.begin(async (tx) => {
550
+ const trans = new this.constructor({
551
+ redis: this.redis,
552
+ dbName: this.dbName,
553
+ sql: tx,
554
+ idMode: this.idMode,
555
+ beflyMode: this.beflyMode
556
+ });
557
+ const result = await callback(trans);
558
+ if (result?.code !== undefined && result.code !== 0) {
559
+ const abortError = new Error("TRANSACTION_ABORT", {
560
+ cause: null,
561
+ code: "runtime"
562
+ });
563
+ abortError[abortMark] = true;
564
+ abortError.payload = result;
565
+ throw abortError;
566
+ }
567
+ return result;
568
+ });
569
+ } catch (error) {
570
+ if (error && error[abortMark] === true) {
571
+ return error.payload;
572
+ }
573
+ throw error;
574
+ }
504
575
  }
505
576
  };
@@ -1,6 +1,5 @@
1
1
  import { toSqlParams } from "../sqlBuilder/util.js";
2
2
  import { snakeCase } from "../../utils/util.js";
3
- import { DbSqlError } from "./context.js";
4
3
  import { validateExecuteSql } from "./validate.js";
5
4
 
6
5
  export const executeMethods = {
@@ -47,8 +46,11 @@ export const executeMethods = {
47
46
  const duration = Date.now() - startTime;
48
47
  const msg = error instanceof Error ? error.message : String(error);
49
48
 
50
- throw new DbSqlError(`SQL执行失败: ${msg}`, {
51
- originalError: error,
49
+ throw new Error(`SQL执行失败: ${msg}`, {
50
+ cause: error,
51
+ code: "runtime",
52
+ subsystem: "sql",
53
+ operation: "execute",
52
54
  params: safeParams,
53
55
  duration: duration,
54
56
  sqlInfo: {
@@ -2,8 +2,6 @@ import { isNonEmptyString } from "../../utils/is.js";
2
2
  import { builderMethods } from "./builders.js";
3
3
  import { dataOpsMethods } from "./dataOps.js";
4
4
  import { executeMethods } from "./execute.js";
5
- import { transactionMethods } from "./transaction.js";
6
- import { convertBigIntFields } from "./context.js";
7
5
 
8
6
  function DbHelper(options) {
9
7
  this.redis = options.redis;
@@ -22,7 +20,6 @@ function DbHelper(options) {
22
20
  this.beflyMode = options.beflyMode === 0 ? 0 : 1;
23
21
  }
24
22
 
25
- Object.assign(DbHelper.prototype, builderMethods, executeMethods, dataOpsMethods, transactionMethods);
26
- DbHelper.convertBigIntFields = convertBigIntFields;
23
+ Object.assign(DbHelper.prototype, builderMethods, executeMethods, dataOpsMethods);
27
24
 
28
25
  export { DbHelper };
@@ -1,7 +1,26 @@
1
- import { isNullable } from "../../utils/is.js";
2
- import { snakeCase } from "../../utils/util.js";
1
+ import { isNonEmptyString, isNullable, isString } from "../../utils/is.js";
2
+ import { canConvertToNumber, snakeCase } from "../../utils/util.js";
3
3
  import { parseTableRef } from "./validate.js";
4
4
 
5
+ export function quoteIdentMySql(identifier) {
6
+ if (!isString(identifier)) {
7
+ throw new Error(`quoteIdentifier 需要字符串类型标识符 (identifier: ${String(identifier)})`, {
8
+ cause: null,
9
+ code: "validation"
10
+ });
11
+ }
12
+
13
+ const trimmed = identifier.trim();
14
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
15
+ throw new Error(`无效的 SQL 标识符: ${trimmed}`, {
16
+ cause: null,
17
+ code: "validation"
18
+ });
19
+ }
20
+
21
+ return `\`${trimmed}\``;
22
+ }
23
+
5
24
  export function normalizeTableRef(tableRef) {
6
25
  const parsed = parseTableRef(tableRef);
7
26
  const schemaPart = parsed.schema ? snakeCase(parsed.schema) : null;
@@ -56,3 +75,83 @@ export function toNumberFromSql(value) {
56
75
 
57
76
  return 0;
58
77
  }
78
+
79
+ export function convertBigIntFields(arr, fields) {
80
+ if (isNullable(arr)) {
81
+ return arr;
82
+ }
83
+
84
+ const defaultFields = ["id", "pid", "sort"];
85
+
86
+ const buildFields = (userFields) => {
87
+ if (!userFields || userFields.length === 0) {
88
+ return defaultFields;
89
+ }
90
+
91
+ const merged = ["id", "pid", "sort"];
92
+ for (const f of userFields) {
93
+ if (!isString(f)) {
94
+ continue;
95
+ }
96
+ if (!isNonEmptyString(f)) {
97
+ continue;
98
+ }
99
+ const trimmed = f.trim();
100
+ if (!merged.includes(trimmed)) {
101
+ merged.push(trimmed);
102
+ }
103
+ }
104
+ return merged;
105
+ };
106
+
107
+ const effectiveFields = buildFields(fields);
108
+ const fieldSet = new Set(effectiveFields);
109
+
110
+ const convertRecord = (source) => {
111
+ const converted = {};
112
+
113
+ for (const [key, value] of Object.entries(source)) {
114
+ let nextValue = value;
115
+
116
+ if (!isNullable(value)) {
117
+ const shouldConvert = fieldSet.has(key) || key.endsWith("Id") || key.endsWith("_id") || key.endsWith("At") || key.endsWith("_at");
118
+
119
+ if (shouldConvert) {
120
+ let bigintValue = null;
121
+ if (typeof value === "bigint") {
122
+ bigintValue = value;
123
+ } else if (isString(value)) {
124
+ if (/^-?\d+$/.test(value)) {
125
+ try {
126
+ bigintValue = BigInt(value);
127
+ } catch {
128
+ bigintValue = null;
129
+ }
130
+ }
131
+ }
132
+
133
+ if (bigintValue !== null) {
134
+ const convertedNumber = canConvertToNumber(bigintValue);
135
+ if (convertedNumber !== null) {
136
+ nextValue = convertedNumber;
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ converted[key] = nextValue;
143
+ }
144
+
145
+ return converted;
146
+ };
147
+
148
+ if (Array.isArray(arr)) {
149
+ return arr.map((item) => convertRecord(item));
150
+ }
151
+
152
+ if (typeof arr === "object") {
153
+ return convertRecord(arr);
154
+ }
155
+
156
+ return arr;
157
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.18.20",
4
- "gitHead": "d8dd84c77c854770d6e42fc5849d5c2fa95452df",
3
+ "version": "3.18.22",
4
+ "gitHead": "fdf4570d70dbec2c68b317db54bdc9c8a7229c11",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
7
7
  "keywords": [
@@ -1,131 +0,0 @@
1
- import { isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "../../utils/is.js";
2
- import { canConvertToNumber } from "../../utils/util.js";
3
-
4
- export function quoteIdentMySql(identifier) {
5
- if (!isString(identifier)) {
6
- throw new Error(`quoteIdentifier 需要字符串类型标识符 (identifier: ${String(identifier)})`, {
7
- cause: null,
8
- code: "validation"
9
- });
10
- }
11
-
12
- const trimmed = identifier.trim();
13
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
14
- throw new Error(`无效的 SQL 标识符: ${trimmed}`, {
15
- cause: null,
16
- code: "validation"
17
- });
18
- }
19
-
20
- return `\`${trimmed}\``;
21
- }
22
-
23
- export function hasBegin(sql) {
24
- return typeof sql.begin === "function";
25
- }
26
-
27
- export class DbSqlError extends Error {
28
- constructor(message, options) {
29
- super(message);
30
- this.originalError = options.originalError;
31
- this.params = options.params;
32
- this.duration = options.duration;
33
- this.sqlInfo = options.sqlInfo;
34
- }
35
- }
36
-
37
- export class TransAbortError extends Error {
38
- constructor(payload) {
39
- super("TRANSACTION_ABORT");
40
- this.payload = payload;
41
- }
42
- }
43
-
44
- export function isBeflyResponse(value) {
45
- if (!isPlainObject(value)) {
46
- return false;
47
- }
48
-
49
- const record = value;
50
- return isNumber(record["code"]) && isString(record["msg"]);
51
- }
52
-
53
- export function convertBigIntFields(arr, fields) {
54
- if (isNullable(arr)) {
55
- return arr;
56
- }
57
-
58
- const defaultFields = ["id", "pid", "sort"];
59
-
60
- const buildFields = (userFields) => {
61
- if (!userFields || userFields.length === 0) {
62
- return defaultFields;
63
- }
64
-
65
- const merged = ["id", "pid", "sort"];
66
- for (const f of userFields) {
67
- if (!isString(f)) {
68
- continue;
69
- }
70
- if (!isNonEmptyString(f)) {
71
- continue;
72
- }
73
- const trimmed = f.trim();
74
- if (!merged.includes(trimmed)) {
75
- merged.push(trimmed);
76
- }
77
- }
78
- return merged;
79
- };
80
-
81
- const effectiveFields = buildFields(fields);
82
- const fieldSet = new Set(effectiveFields);
83
-
84
- const convertRecord = (source) => {
85
- const converted = {};
86
-
87
- for (const [key, value] of Object.entries(source)) {
88
- let nextValue = value;
89
-
90
- if (!isNullable(value)) {
91
- const shouldConvert = fieldSet.has(key) || key.endsWith("Id") || key.endsWith("_id") || key.endsWith("At") || key.endsWith("_at");
92
-
93
- if (shouldConvert) {
94
- let bigintValue = null;
95
- if (typeof value === "bigint") {
96
- bigintValue = value;
97
- } else if (isString(value)) {
98
- if (/^-?\d+$/.test(value)) {
99
- try {
100
- bigintValue = BigInt(value);
101
- } catch {
102
- bigintValue = null;
103
- }
104
- }
105
- }
106
-
107
- if (bigintValue !== null) {
108
- const convertedNumber = canConvertToNumber(bigintValue);
109
- if (convertedNumber !== null) {
110
- nextValue = convertedNumber;
111
- }
112
- }
113
- }
114
- }
115
-
116
- converted[key] = nextValue;
117
- }
118
-
119
- return converted;
120
- };
121
-
122
- if (Array.isArray(arr)) {
123
- return arr.map((item) => convertRecord(item));
124
- }
125
-
126
- if (typeof arr === "object") {
127
- return convertRecord(arr);
128
- }
129
-
130
- return arr;
131
- }
@@ -1,43 +0,0 @@
1
- import { hasBegin, isBeflyResponse, TransAbortError } from "./context.js";
2
-
3
- export const transactionMethods = {
4
- async trans(callback) {
5
- if (this.isTransaction) {
6
- const innerResult = await callback(this);
7
- if (isBeflyResponse(innerResult) && innerResult.code !== 0) {
8
- throw new TransAbortError(innerResult);
9
- }
10
- return innerResult;
11
- }
12
-
13
- const sql = this.sql;
14
- if (!sql) {
15
- throw new Error("数据库连接未初始化", {
16
- cause: null,
17
- code: "runtime"
18
- });
19
- }
20
- if (!hasBegin(sql)) {
21
- throw new Error("当前 SQL 客户端不支持事务 begin() 方法", {
22
- cause: null,
23
- code: "runtime"
24
- });
25
- }
26
-
27
- try {
28
- return await sql.begin(async (tx) => {
29
- const trans = new this.constructor({ redis: this.redis, dbName: this.dbName, sql: tx, idMode: this.idMode, beflyMode: this.beflyMode });
30
- const result = await callback(trans);
31
- if (isBeflyResponse(result) && result.code !== 0) {
32
- throw new TransAbortError(result);
33
- }
34
- return result;
35
- });
36
- } catch (error) {
37
- if (error instanceof TransAbortError) {
38
- return error.payload;
39
- }
40
- throw error;
41
- }
42
- }
43
- };