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.
- package/lib/dbHelper/builders.js +2 -3
- package/lib/dbHelper/dataOps.js +76 -5
- package/lib/dbHelper/execute.js +5 -3
- package/lib/dbHelper/index.js +1 -4
- package/lib/dbHelper/util.js +101 -2
- package/package.json +2 -2
- package/lib/dbHelper/context.js +0 -131
- package/lib/dbHelper/transaction.js +0 -43
package/lib/dbHelper/builders.js
CHANGED
|
@@ -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 "./
|
|
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
|
|
package/lib/dbHelper/dataOps.js
CHANGED
|
@@ -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 "./
|
|
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({
|
|
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({
|
|
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:
|
|
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
|
};
|
package/lib/dbHelper/execute.js
CHANGED
|
@@ -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
|
|
51
|
-
|
|
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: {
|
package/lib/dbHelper/index.js
CHANGED
|
@@ -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
|
|
26
|
-
DbHelper.convertBigIntFields = convertBigIntFields;
|
|
23
|
+
Object.assign(DbHelper.prototype, builderMethods, executeMethods, dataOpsMethods);
|
|
27
24
|
|
|
28
25
|
export { DbHelper };
|
package/lib/dbHelper/util.js
CHANGED
|
@@ -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.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.18.22",
|
|
4
|
+
"gitHead": "fdf4570d70dbec2c68b317db54bdc9c8a7229c11",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
package/lib/dbHelper/context.js
DELETED
|
@@ -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
|
-
};
|