@chidchanun/bcp 0.1.12 → 0.1.14
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/docs/database.md +127 -0
- package/package.json +6 -1
- package/packages/client/src/database.ts +492 -0
package/docs/database.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Database
|
|
2
|
+
|
|
3
|
+
BCP `0.1.14` adds the first framework-native database API through the server-only `bcp/database` entrypoint.
|
|
4
|
+
|
|
5
|
+
The initial adapter targets MySQL. Additional adapters can be added in later releases without changing the page, loader, guard or action APIs that consume the database layer.
|
|
6
|
+
|
|
7
|
+
## Create an app with MySQL
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx create-bcp-app my-app
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Choose **MySQL** during interactive setup. The generated project installs `mysql2`, writes the database environment variables and creates `lib/database.ts` as a thin wrapper around `bcp/database`.
|
|
14
|
+
|
|
15
|
+
## Environment
|
|
16
|
+
|
|
17
|
+
```env
|
|
18
|
+
DB_DRIVER=mysql
|
|
19
|
+
DB_HOST=localhost
|
|
20
|
+
DB_PORT=3306
|
|
21
|
+
DB_USER=root
|
|
22
|
+
DB_PASSWORD=
|
|
23
|
+
DB_NAME=bcp_app
|
|
24
|
+
DB_CONNECTION_LIMIT=10
|
|
25
|
+
DB_WAIT_FOR_CONNECTIONS=1
|
|
26
|
+
DB_QUEUE_LIMIT=0
|
|
27
|
+
DB_CHARSET=utf8mb4
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Only `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD` and `DB_NAME` are required for the standard generated preset. The remaining values have safe framework defaults.
|
|
31
|
+
|
|
32
|
+
## Query
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
db,
|
|
37
|
+
} from "bcp/database";
|
|
38
|
+
|
|
39
|
+
interface UserRow {
|
|
40
|
+
id: number;
|
|
41
|
+
email: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const users =
|
|
45
|
+
await db.query<UserRow[]>(
|
|
46
|
+
"SELECT id, email FROM users WHERE active = ?",
|
|
47
|
+
[
|
|
48
|
+
1,
|
|
49
|
+
]
|
|
50
|
+
);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Execute
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const result =
|
|
57
|
+
await db.execute(
|
|
58
|
+
"INSERT INTO users (email) VALUES (?)",
|
|
59
|
+
[
|
|
60
|
+
"user@example.com",
|
|
61
|
+
]
|
|
62
|
+
);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use placeholders and parameter arrays instead of concatenating untrusted values into SQL strings.
|
|
66
|
+
|
|
67
|
+
## Transaction
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
await db.transaction(
|
|
71
|
+
async (transaction) => {
|
|
72
|
+
await transaction.execute(
|
|
73
|
+
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
|
|
74
|
+
[
|
|
75
|
+
100,
|
|
76
|
+
1,
|
|
77
|
+
]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
await transaction.execute(
|
|
81
|
+
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
|
|
82
|
+
[
|
|
83
|
+
100,
|
|
84
|
+
2,
|
|
85
|
+
]
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
BCP commits the transaction when the callback resolves and rolls it back when the callback throws.
|
|
92
|
+
|
|
93
|
+
## Custom database instance
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import {
|
|
97
|
+
createDatabase,
|
|
98
|
+
} from "bcp/database";
|
|
99
|
+
|
|
100
|
+
export const reportingDb =
|
|
101
|
+
createDatabase({
|
|
102
|
+
host:
|
|
103
|
+
"reporting-db.internal",
|
|
104
|
+
database:
|
|
105
|
+
"analytics",
|
|
106
|
+
connectionLimit:
|
|
107
|
+
5,
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Explicit options override environment values for that database instance.
|
|
112
|
+
|
|
113
|
+
## Lifecycle
|
|
114
|
+
|
|
115
|
+
Connections are lazy. Importing `bcp/database` does not open a MySQL connection. The pool is created on the first `query`, `execute` or `transaction` call.
|
|
116
|
+
|
|
117
|
+
For custom shutdown handling, close the pool with:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
await db.close();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Server-only boundary
|
|
124
|
+
|
|
125
|
+
`bcp/database` is a server-only package export. Importing it into a browser bundle is blocked by the framework's browser export boundary.
|
|
126
|
+
|
|
127
|
+
The MySQL adapter loads `mysql2/promise` only when a connection is first needed. Projects that do not use MySQL do not need to install the driver.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chidchanun/bcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "BCP Framework - a React full-stack framework with file-based routing, SSR, APIs, middleware, islands, caching and standalone production builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,6 +43,11 @@
|
|
|
43
43
|
"types": "./packages/client/src/config.ts",
|
|
44
44
|
"default": "./packages/client/src/config.ts"
|
|
45
45
|
},
|
|
46
|
+
"./database": {
|
|
47
|
+
"types": "./packages/client/src/database.ts",
|
|
48
|
+
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
49
|
+
"default": "./packages/client/src/database.ts"
|
|
50
|
+
},
|
|
46
51
|
"./server": {
|
|
47
52
|
"types": "./packages/client/src/server.ts",
|
|
48
53
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
export type DatabaseDriver = "mysql";
|
|
2
|
+
|
|
3
|
+
export interface DatabaseOptions {
|
|
4
|
+
driver?: DatabaseDriver;
|
|
5
|
+
host?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
user?: string;
|
|
8
|
+
password?: string;
|
|
9
|
+
database?: string;
|
|
10
|
+
connectionLimit?: number;
|
|
11
|
+
waitForConnections?: boolean;
|
|
12
|
+
queueLimit?: number;
|
|
13
|
+
charset?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DatabaseParameters =
|
|
17
|
+
| readonly unknown[]
|
|
18
|
+
| Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
export interface TransactionDatabase {
|
|
21
|
+
query<T = unknown>(
|
|
22
|
+
sql: string,
|
|
23
|
+
parameters?: DatabaseParameters
|
|
24
|
+
): Promise<T>;
|
|
25
|
+
execute<T = unknown>(
|
|
26
|
+
sql: string,
|
|
27
|
+
parameters?: DatabaseParameters
|
|
28
|
+
): Promise<T>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MysqlExecutor {
|
|
32
|
+
query(
|
|
33
|
+
sql: string,
|
|
34
|
+
parameters?: DatabaseParameters
|
|
35
|
+
): Promise<[unknown, unknown]>;
|
|
36
|
+
execute(
|
|
37
|
+
sql: string,
|
|
38
|
+
parameters?: DatabaseParameters
|
|
39
|
+
): Promise<[unknown, unknown]>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface MysqlConnection extends MysqlExecutor {
|
|
43
|
+
beginTransaction(): Promise<void>;
|
|
44
|
+
commit(): Promise<void>;
|
|
45
|
+
rollback(): Promise<void>;
|
|
46
|
+
release(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface MysqlPool extends MysqlExecutor {
|
|
50
|
+
getConnection(): Promise<MysqlConnection>;
|
|
51
|
+
end(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface MysqlModule {
|
|
55
|
+
createPool(
|
|
56
|
+
options: Record<string, unknown>
|
|
57
|
+
): MysqlPool;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const MYSQL_MODULE_SPECIFIER =
|
|
61
|
+
"mysql2/promise";
|
|
62
|
+
|
|
63
|
+
export class BcpDatabase {
|
|
64
|
+
private readonly options:
|
|
65
|
+
DatabaseOptions;
|
|
66
|
+
|
|
67
|
+
private poolPromise:
|
|
68
|
+
Promise<MysqlPool> | null =
|
|
69
|
+
null;
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
options: DatabaseOptions = {}
|
|
73
|
+
) {
|
|
74
|
+
this.options = {
|
|
75
|
+
...options,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async query<T = unknown>(
|
|
80
|
+
sql: string,
|
|
81
|
+
parameters?: DatabaseParameters
|
|
82
|
+
): Promise<T> {
|
|
83
|
+
assertSql(sql);
|
|
84
|
+
|
|
85
|
+
const pool =
|
|
86
|
+
await this.getPool();
|
|
87
|
+
const [rows] =
|
|
88
|
+
await pool.query(
|
|
89
|
+
sql,
|
|
90
|
+
parameters
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return rows as T;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async execute<T = unknown>(
|
|
97
|
+
sql: string,
|
|
98
|
+
parameters?: DatabaseParameters
|
|
99
|
+
): Promise<T> {
|
|
100
|
+
assertSql(sql);
|
|
101
|
+
|
|
102
|
+
const pool =
|
|
103
|
+
await this.getPool();
|
|
104
|
+
const [result] =
|
|
105
|
+
await pool.execute(
|
|
106
|
+
sql,
|
|
107
|
+
parameters
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return result as T;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async transaction<T>(
|
|
114
|
+
callback: (
|
|
115
|
+
database: TransactionDatabase
|
|
116
|
+
) => Promise<T>
|
|
117
|
+
): Promise<T> {
|
|
118
|
+
if (
|
|
119
|
+
typeof callback !==
|
|
120
|
+
"function"
|
|
121
|
+
) {
|
|
122
|
+
throw new TypeError(
|
|
123
|
+
"BCP Database: transaction callback must be a function."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const pool =
|
|
128
|
+
await this.getPool();
|
|
129
|
+
const connection =
|
|
130
|
+
await pool.getConnection();
|
|
131
|
+
|
|
132
|
+
await connection.beginTransaction();
|
|
133
|
+
|
|
134
|
+
const transactionDatabase:
|
|
135
|
+
TransactionDatabase = {
|
|
136
|
+
query: async <R = unknown>(
|
|
137
|
+
sql: string,
|
|
138
|
+
parameters?: DatabaseParameters
|
|
139
|
+
): Promise<R> => {
|
|
140
|
+
assertSql(sql);
|
|
141
|
+
|
|
142
|
+
const [rows] =
|
|
143
|
+
await connection.query(
|
|
144
|
+
sql,
|
|
145
|
+
parameters
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return rows as R;
|
|
149
|
+
},
|
|
150
|
+
execute: async <R = unknown>(
|
|
151
|
+
sql: string,
|
|
152
|
+
parameters?: DatabaseParameters
|
|
153
|
+
): Promise<R> => {
|
|
154
|
+
assertSql(sql);
|
|
155
|
+
|
|
156
|
+
const [result] =
|
|
157
|
+
await connection.execute(
|
|
158
|
+
sql,
|
|
159
|
+
parameters
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return result as R;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const result =
|
|
168
|
+
await callback(
|
|
169
|
+
transactionDatabase
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
await connection.commit();
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
try {
|
|
177
|
+
await connection.rollback();
|
|
178
|
+
} catch {
|
|
179
|
+
// Preserve the original transaction error.
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw error;
|
|
183
|
+
} finally {
|
|
184
|
+
connection.release();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async close(): Promise<void> {
|
|
189
|
+
const pendingPool =
|
|
190
|
+
this.poolPromise;
|
|
191
|
+
|
|
192
|
+
this.poolPromise =
|
|
193
|
+
null;
|
|
194
|
+
|
|
195
|
+
if (!pendingPool) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const pool =
|
|
200
|
+
await pendingPool;
|
|
201
|
+
|
|
202
|
+
await pool.end();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private getPool(): Promise<MysqlPool> {
|
|
206
|
+
if (!this.poolPromise) {
|
|
207
|
+
this.poolPromise =
|
|
208
|
+
createMysqlPool(
|
|
209
|
+
resolveDatabaseOptions(
|
|
210
|
+
this.options
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return this.poolPromise;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function createDatabase(
|
|
220
|
+
options: DatabaseOptions = {}
|
|
221
|
+
): BcpDatabase {
|
|
222
|
+
return new BcpDatabase(
|
|
223
|
+
options
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function resolveDatabaseOptions(
|
|
228
|
+
options: DatabaseOptions = {},
|
|
229
|
+
environment: NodeJS.ProcessEnv =
|
|
230
|
+
process.env
|
|
231
|
+
): Required<DatabaseOptions> {
|
|
232
|
+
const driver =
|
|
233
|
+
options.driver ??
|
|
234
|
+
normalizeDriver(
|
|
235
|
+
environment.DB_DRIVER ??
|
|
236
|
+
"mysql"
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
driver !== "mysql"
|
|
241
|
+
) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`BCP Database: unsupported database driver "${driver}".`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
driver,
|
|
249
|
+
host:
|
|
250
|
+
nonEmpty(
|
|
251
|
+
options.host ??
|
|
252
|
+
environment.DB_HOST
|
|
253
|
+
) ??
|
|
254
|
+
"localhost",
|
|
255
|
+
port:
|
|
256
|
+
resolvePositiveInteger(
|
|
257
|
+
options.port ??
|
|
258
|
+
environment.DB_PORT,
|
|
259
|
+
3306,
|
|
260
|
+
"DB_PORT"
|
|
261
|
+
),
|
|
262
|
+
user:
|
|
263
|
+
nonEmpty(
|
|
264
|
+
options.user ??
|
|
265
|
+
environment.DB_USER
|
|
266
|
+
) ??
|
|
267
|
+
"root",
|
|
268
|
+
password:
|
|
269
|
+
String(
|
|
270
|
+
options.password ??
|
|
271
|
+
environment.DB_PASSWORD ??
|
|
272
|
+
""
|
|
273
|
+
),
|
|
274
|
+
database:
|
|
275
|
+
nonEmpty(
|
|
276
|
+
options.database ??
|
|
277
|
+
environment.DB_NAME
|
|
278
|
+
) ??
|
|
279
|
+
"bcp_app",
|
|
280
|
+
connectionLimit:
|
|
281
|
+
resolvePositiveInteger(
|
|
282
|
+
options.connectionLimit ??
|
|
283
|
+
environment.DB_CONNECTION_LIMIT,
|
|
284
|
+
10,
|
|
285
|
+
"DB_CONNECTION_LIMIT"
|
|
286
|
+
),
|
|
287
|
+
waitForConnections:
|
|
288
|
+
options.waitForConnections ??
|
|
289
|
+
resolveBoolean(
|
|
290
|
+
environment.DB_WAIT_FOR_CONNECTIONS,
|
|
291
|
+
true,
|
|
292
|
+
"DB_WAIT_FOR_CONNECTIONS"
|
|
293
|
+
),
|
|
294
|
+
queueLimit:
|
|
295
|
+
resolveNonNegativeInteger(
|
|
296
|
+
options.queueLimit ??
|
|
297
|
+
environment.DB_QUEUE_LIMIT,
|
|
298
|
+
0,
|
|
299
|
+
"DB_QUEUE_LIMIT"
|
|
300
|
+
),
|
|
301
|
+
charset:
|
|
302
|
+
nonEmpty(
|
|
303
|
+
options.charset ??
|
|
304
|
+
environment.DB_CHARSET
|
|
305
|
+
) ??
|
|
306
|
+
"utf8mb4",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export const db =
|
|
311
|
+
createDatabase();
|
|
312
|
+
|
|
313
|
+
async function createMysqlPool(
|
|
314
|
+
options: Required<DatabaseOptions>
|
|
315
|
+
): Promise<MysqlPool> {
|
|
316
|
+
let mysqlModule:
|
|
317
|
+
MysqlModule;
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
mysqlModule =
|
|
321
|
+
await import(
|
|
322
|
+
MYSQL_MODULE_SPECIFIER
|
|
323
|
+
) as unknown as MysqlModule;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
"BCP Database: MySQL requires the optional dependency mysql2. Install it with `npm install mysql2` or create the app with the MySQL preset.",
|
|
327
|
+
{
|
|
328
|
+
cause:
|
|
329
|
+
error,
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return mysqlModule.createPool({
|
|
335
|
+
host:
|
|
336
|
+
options.host,
|
|
337
|
+
port:
|
|
338
|
+
options.port,
|
|
339
|
+
user:
|
|
340
|
+
options.user,
|
|
341
|
+
password:
|
|
342
|
+
options.password,
|
|
343
|
+
database:
|
|
344
|
+
options.database,
|
|
345
|
+
waitForConnections:
|
|
346
|
+
options.waitForConnections,
|
|
347
|
+
connectionLimit:
|
|
348
|
+
options.connectionLimit,
|
|
349
|
+
queueLimit:
|
|
350
|
+
options.queueLimit,
|
|
351
|
+
charset:
|
|
352
|
+
options.charset,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function normalizeDriver(
|
|
357
|
+
value: string
|
|
358
|
+
): DatabaseDriver {
|
|
359
|
+
const normalized =
|
|
360
|
+
value
|
|
361
|
+
.trim()
|
|
362
|
+
.toLowerCase();
|
|
363
|
+
|
|
364
|
+
if (
|
|
365
|
+
normalized === "mysql"
|
|
366
|
+
) {
|
|
367
|
+
return "mysql";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
throw new Error(
|
|
371
|
+
`BCP Database: unsupported database driver "${value}".`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function nonEmpty(
|
|
376
|
+
value: unknown
|
|
377
|
+
): string | null {
|
|
378
|
+
if (
|
|
379
|
+
typeof value !== "string"
|
|
380
|
+
) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const normalized =
|
|
385
|
+
value.trim();
|
|
386
|
+
|
|
387
|
+
return normalized ||
|
|
388
|
+
null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function resolvePositiveInteger(
|
|
392
|
+
value: unknown,
|
|
393
|
+
fallback: number,
|
|
394
|
+
label: string
|
|
395
|
+
): number {
|
|
396
|
+
if (
|
|
397
|
+
value === undefined ||
|
|
398
|
+
value === null ||
|
|
399
|
+
value === ""
|
|
400
|
+
) {
|
|
401
|
+
return fallback;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const parsed =
|
|
405
|
+
Number(value);
|
|
406
|
+
|
|
407
|
+
if (
|
|
408
|
+
!Number.isInteger(parsed) ||
|
|
409
|
+
parsed <= 0
|
|
410
|
+
) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`BCP Database: ${label} must be a positive integer.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return parsed;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function resolveNonNegativeInteger(
|
|
420
|
+
value: unknown,
|
|
421
|
+
fallback: number,
|
|
422
|
+
label: string
|
|
423
|
+
): number {
|
|
424
|
+
if (
|
|
425
|
+
value === undefined ||
|
|
426
|
+
value === null ||
|
|
427
|
+
value === ""
|
|
428
|
+
) {
|
|
429
|
+
return fallback;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const parsed =
|
|
433
|
+
Number(value);
|
|
434
|
+
|
|
435
|
+
if (
|
|
436
|
+
!Number.isInteger(parsed) ||
|
|
437
|
+
parsed < 0
|
|
438
|
+
) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`BCP Database: ${label} must be a non-negative integer.`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return parsed;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function resolveBoolean(
|
|
448
|
+
value: unknown,
|
|
449
|
+
fallback: boolean,
|
|
450
|
+
label: string
|
|
451
|
+
): boolean {
|
|
452
|
+
if (
|
|
453
|
+
value === undefined ||
|
|
454
|
+
value === null ||
|
|
455
|
+
value === ""
|
|
456
|
+
) {
|
|
457
|
+
return fallback;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (
|
|
461
|
+
value === true ||
|
|
462
|
+
value === "1" ||
|
|
463
|
+
value === "true"
|
|
464
|
+
) {
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (
|
|
469
|
+
value === false ||
|
|
470
|
+
value === "0" ||
|
|
471
|
+
value === "false"
|
|
472
|
+
) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
throw new Error(
|
|
477
|
+
`BCP Database: ${label} must be true/false or 1/0.`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function assertSql(
|
|
482
|
+
sql: string
|
|
483
|
+
): void {
|
|
484
|
+
if (
|
|
485
|
+
typeof sql !== "string" ||
|
|
486
|
+
sql.trim() === ""
|
|
487
|
+
) {
|
|
488
|
+
throw new TypeError(
|
|
489
|
+
"BCP Database: SQL must be a non-empty string."
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|