befly 3.18.21 → 3.18.23

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) {
@@ -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
 
@@ -520,5 +519,59 @@ export const dataOpsMethods = {
520
519
 
521
520
  async decrement(table, field, where, value = 1) {
522
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
+ isTransaction: true,
555
+ idMode: this.idMode,
556
+ beflyMode: this.beflyMode
557
+ });
558
+ const result = await callback(trans);
559
+ if (result?.code !== undefined && result.code !== 0) {
560
+ const abortError = new Error("TRANSACTION_ABORT", {
561
+ cause: null,
562
+ code: "runtime"
563
+ });
564
+ abortError[abortMark] = true;
565
+ abortError.payload = result;
566
+ throw abortError;
567
+ }
568
+ return result;
569
+ });
570
+ } catch (error) {
571
+ if (error && error[abortMark] === true) {
572
+ return error.payload;
573
+ }
574
+ throw error;
575
+ }
523
576
  }
524
577
  };
@@ -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;
@@ -17,12 +15,11 @@ function DbHelper(options) {
17
15
  this.dbName = options.dbName;
18
16
 
19
17
  this.sql = options.sql || null;
20
- this.isTransaction = Boolean(options.sql);
18
+ this.isTransaction = options.isTransaction === true;
21
19
  this.idMode = options.idMode === "autoId" ? "autoId" : "timeId";
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.21",
4
- "gitHead": "ccc26691d98b520da6d2b524ed654da15fee427f",
3
+ "version": "3.18.23",
4
+ "gitHead": "d18f2e30b2e70845d26b4b7b5190b87eb8709337",
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
- };