@chidchanun/bcp 0.1.13 → 0.1.15
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-migrations.md +105 -0
- package/docs/database.md +127 -0
- package/package.json +6 -1
- package/packages/cli/src/args.ts +71 -1
- package/packages/cli/src/database-migrations.ts +708 -0
- package/packages/cli/src/index.ts +112 -1
- package/packages/client/src/database.ts +492 -0
|
@@ -66,6 +66,11 @@ switch (cliOptions.command) {
|
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
case "db": {
|
|
70
|
+
await runDatabaseCommand();
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
case "help": {
|
|
70
75
|
printHelp();
|
|
71
76
|
break;
|
|
@@ -381,6 +386,86 @@ async function runUpdate() {
|
|
|
381
386
|
});
|
|
382
387
|
}
|
|
383
388
|
|
|
389
|
+
async function runDatabaseCommand() {
|
|
390
|
+
const action =
|
|
391
|
+
cliOptions.dbAction;
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
action === undefined ||
|
|
395
|
+
action === "help"
|
|
396
|
+
) {
|
|
397
|
+
printDatabaseHelp();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const rootDirectory =
|
|
402
|
+
resolveProjectRoot(
|
|
403
|
+
cliOptions.rootDirectory
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
process.env.NODE_ENV =
|
|
407
|
+
process.env.NODE_ENV ??
|
|
408
|
+
"development";
|
|
409
|
+
|
|
410
|
+
const {
|
|
411
|
+
loadEnvironment,
|
|
412
|
+
} =
|
|
413
|
+
await import(
|
|
414
|
+
"../../env/src/index.js"
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const environment =
|
|
418
|
+
loadEnvironment(
|
|
419
|
+
rootDirectory,
|
|
420
|
+
"development"
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
if (
|
|
424
|
+
environment.files.length > 0
|
|
425
|
+
) {
|
|
426
|
+
console.log(
|
|
427
|
+
`[BCP Env] development: ${environment.files.join(", ")}`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const {
|
|
432
|
+
runDatabaseMigrationCommand,
|
|
433
|
+
} =
|
|
434
|
+
await import(
|
|
435
|
+
"./database-migrations.js"
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
action === "create"
|
|
440
|
+
) {
|
|
441
|
+
await runDatabaseMigrationCommand({
|
|
442
|
+
rootDirectory,
|
|
443
|
+
action,
|
|
444
|
+
name:
|
|
445
|
+
cliOptions.dbMigrationName,
|
|
446
|
+
});
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const {
|
|
451
|
+
db,
|
|
452
|
+
} =
|
|
453
|
+
await import(
|
|
454
|
+
"../../client/src/database.js"
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
await runDatabaseMigrationCommand({
|
|
459
|
+
rootDirectory,
|
|
460
|
+
action,
|
|
461
|
+
database:
|
|
462
|
+
db,
|
|
463
|
+
});
|
|
464
|
+
} finally {
|
|
465
|
+
await db.close();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
384
469
|
function installShutdownHandlers(
|
|
385
470
|
server: {
|
|
386
471
|
stop(): Promise<void>;
|
|
@@ -521,6 +606,28 @@ function formatBytes(
|
|
|
521
606
|
).toFixed(2)} MiB`;
|
|
522
607
|
}
|
|
523
608
|
|
|
609
|
+
function printDatabaseHelp() {
|
|
610
|
+
console.log(`
|
|
611
|
+
BCP Database Migrations
|
|
612
|
+
|
|
613
|
+
Usage:
|
|
614
|
+
bcp db <command> [arguments]
|
|
615
|
+
|
|
616
|
+
Commands:
|
|
617
|
+
create <name> Create a timestamped migration in migrations/
|
|
618
|
+
migrate Run all pending migrations
|
|
619
|
+
status Show applied and pending migrations
|
|
620
|
+
rollback Roll back the latest migration batch
|
|
621
|
+
help Show database command help
|
|
622
|
+
|
|
623
|
+
Examples:
|
|
624
|
+
bcp db create create_users
|
|
625
|
+
bcp db migrate
|
|
626
|
+
bcp db status
|
|
627
|
+
bcp db rollback
|
|
628
|
+
`);
|
|
629
|
+
}
|
|
630
|
+
|
|
524
631
|
function printHelp() {
|
|
525
632
|
console.log(`
|
|
526
633
|
BCP Framework v${FRAMEWORK_VERSION}
|
|
@@ -534,6 +641,7 @@ Commands:
|
|
|
534
641
|
start Start the standalone production build
|
|
535
642
|
routes Print discovered page and API routes
|
|
536
643
|
update [target] Update BCP Framework (default target: latest)
|
|
644
|
+
db <command> Manage database migrations
|
|
537
645
|
help Show this help message
|
|
538
646
|
version Show framework version
|
|
539
647
|
|
|
@@ -558,6 +666,9 @@ Examples:
|
|
|
558
666
|
bcp routes
|
|
559
667
|
bcp update
|
|
560
668
|
bcp update --check
|
|
561
|
-
bcp
|
|
669
|
+
bcp db create create_users
|
|
670
|
+
bcp db migrate
|
|
671
|
+
bcp db status
|
|
672
|
+
bcp db rollback
|
|
562
673
|
`);
|
|
563
674
|
}
|
|
@@ -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
|
+
}
|