@chidchanun/bcp 0.2.2 → 0.2.3
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/README.md +62 -20
- package/docs/api-manifest.json +2 -2
- package/docs/api-reference.md +21 -2
- package/docs/database-migrations.md +79 -19
- package/docs/database.md +197 -28
- package/docs/docs-web-manifest.json +4 -3
- package/docs/platform-manifest.json +14 -4
- package/docs/releases/0.2.3.md +77 -0
- package/package.json +1 -1
- package/packages/cli/src/database-migrations.ts +91 -21
- package/packages/client/src/database-mysql.ts +291 -0
- package/packages/client/src/database-postgresql.ts +355 -0
- package/packages/client/src/database-sqlite.ts +445 -0
- package/packages/client/src/database.mjs +709 -57
- package/packages/client/src/database.ts +347 -182
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
// packages/client/src/database.ts
|
|
1
|
+
// packages/client/src/database-mysql.ts
|
|
2
2
|
var MYSQL_MODULE_SPECIFIER = "mysql2/promise";
|
|
3
|
-
|
|
3
|
+
function createMysqlDatabaseAdapter(options) {
|
|
4
|
+
return new MysqlDatabaseAdapter(
|
|
5
|
+
options
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
var MysqlDatabaseAdapter = class {
|
|
9
|
+
driver = "mysql";
|
|
4
10
|
options;
|
|
5
11
|
poolPromise = null;
|
|
6
|
-
constructor(options
|
|
7
|
-
this.options =
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
async connect() {
|
|
16
|
+
if (!this.poolPromise) {
|
|
17
|
+
this.poolPromise = createMysqlPool(
|
|
18
|
+
this.options
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
await this.poolPromise;
|
|
10
22
|
}
|
|
11
23
|
async query(sql, parameters) {
|
|
12
|
-
assertSql(
|
|
24
|
+
assertSql(
|
|
25
|
+
sql
|
|
26
|
+
);
|
|
13
27
|
const pool = await this.getPool();
|
|
14
28
|
const [rows] = await pool.query(
|
|
15
29
|
sql,
|
|
@@ -18,7 +32,9 @@ var BcpDatabase = class {
|
|
|
18
32
|
return rows;
|
|
19
33
|
}
|
|
20
34
|
async execute(sql, parameters) {
|
|
21
|
-
assertSql(
|
|
35
|
+
assertSql(
|
|
36
|
+
sql
|
|
37
|
+
);
|
|
22
38
|
const pool = await this.getPool();
|
|
23
39
|
const [result] = await pool.execute(
|
|
24
40
|
sql,
|
|
@@ -27,17 +43,17 @@ var BcpDatabase = class {
|
|
|
27
43
|
return result;
|
|
28
44
|
}
|
|
29
45
|
async transaction(callback) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
);
|
|
34
|
-
}
|
|
46
|
+
assertTransactionCallback(
|
|
47
|
+
callback
|
|
48
|
+
);
|
|
35
49
|
const pool = await this.getPool();
|
|
36
50
|
const connection = await pool.getConnection();
|
|
37
51
|
await connection.beginTransaction();
|
|
38
52
|
const transactionDatabase = {
|
|
39
53
|
query: async (sql, parameters) => {
|
|
40
|
-
assertSql(
|
|
54
|
+
assertSql(
|
|
55
|
+
sql
|
|
56
|
+
);
|
|
41
57
|
const [rows] = await connection.query(
|
|
42
58
|
sql,
|
|
43
59
|
parameters
|
|
@@ -45,7 +61,9 @@ var BcpDatabase = class {
|
|
|
45
61
|
return rows;
|
|
46
62
|
},
|
|
47
63
|
execute: async (sql, parameters) => {
|
|
48
|
-
assertSql(
|
|
64
|
+
assertSql(
|
|
65
|
+
sql
|
|
66
|
+
);
|
|
49
67
|
const [result] = await connection.execute(
|
|
50
68
|
sql,
|
|
51
69
|
parameters
|
|
@@ -69,62 +87,612 @@ var BcpDatabase = class {
|
|
|
69
87
|
connection.release();
|
|
70
88
|
}
|
|
71
89
|
}
|
|
72
|
-
async
|
|
90
|
+
async disconnect() {
|
|
73
91
|
const pendingPool = this.poolPromise;
|
|
74
92
|
this.poolPromise = null;
|
|
75
93
|
if (!pendingPool) {
|
|
76
94
|
return;
|
|
77
95
|
}
|
|
78
|
-
|
|
96
|
+
let pool;
|
|
97
|
+
try {
|
|
98
|
+
pool = await pendingPool;
|
|
99
|
+
} catch {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
79
102
|
await pool.end();
|
|
80
103
|
}
|
|
81
|
-
getPool() {
|
|
104
|
+
async getPool() {
|
|
105
|
+
await this.connect();
|
|
106
|
+
return this.poolPromise;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
async function createMysqlPool(options) {
|
|
110
|
+
let mysqlModule;
|
|
111
|
+
try {
|
|
112
|
+
mysqlModule = await import(MYSQL_MODULE_SPECIFIER);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"BCP Database: MySQL requires the optional dependency mysql2. Install it with `npm install mysql2` or create the app with the MySQL preset.",
|
|
116
|
+
{
|
|
117
|
+
cause: error
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return mysqlModule.createPool({
|
|
122
|
+
host: options.host,
|
|
123
|
+
port: options.port,
|
|
124
|
+
user: options.user,
|
|
125
|
+
password: options.password,
|
|
126
|
+
database: options.database,
|
|
127
|
+
waitForConnections: options.waitForConnections,
|
|
128
|
+
connectionLimit: options.connectionLimit,
|
|
129
|
+
queueLimit: options.queueLimit,
|
|
130
|
+
charset: options.charset
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function assertSql(sql) {
|
|
134
|
+
if (typeof sql !== "string" || sql.trim() === "") {
|
|
135
|
+
throw new TypeError(
|
|
136
|
+
"BCP Database: SQL must be a non-empty string."
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function assertTransactionCallback(callback) {
|
|
141
|
+
if (typeof callback !== "function") {
|
|
142
|
+
throw new TypeError(
|
|
143
|
+
"BCP Database: transaction callback must be a function."
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// packages/client/src/database-postgresql.ts
|
|
149
|
+
var POSTGRESQL_MODULE_SPECIFIER = "pg";
|
|
150
|
+
function createPostgresqlDatabaseAdapter(options) {
|
|
151
|
+
return new PostgresqlDatabaseAdapter(
|
|
152
|
+
options
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
var PostgresqlDatabaseAdapter = class {
|
|
156
|
+
driver = "postgresql";
|
|
157
|
+
options;
|
|
158
|
+
poolPromise = null;
|
|
159
|
+
constructor(options) {
|
|
160
|
+
this.options = options;
|
|
161
|
+
}
|
|
162
|
+
async connect() {
|
|
82
163
|
if (!this.poolPromise) {
|
|
83
|
-
this.poolPromise =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
164
|
+
this.poolPromise = createPostgresqlPool(
|
|
165
|
+
this.options
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
await this.poolPromise;
|
|
169
|
+
}
|
|
170
|
+
async query(sql, parameters) {
|
|
171
|
+
assertSql2(
|
|
172
|
+
sql
|
|
173
|
+
);
|
|
174
|
+
const pool = await this.getPool();
|
|
175
|
+
const result = await pool.query(
|
|
176
|
+
sql,
|
|
177
|
+
normalizePostgresqlParameters(
|
|
178
|
+
parameters
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
return result.rows;
|
|
182
|
+
}
|
|
183
|
+
async execute(sql, parameters) {
|
|
184
|
+
assertSql2(
|
|
185
|
+
sql
|
|
186
|
+
);
|
|
187
|
+
const pool = await this.getPool();
|
|
188
|
+
const result = await pool.query(
|
|
189
|
+
sql,
|
|
190
|
+
normalizePostgresqlParameters(
|
|
191
|
+
parameters
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
async transaction(callback) {
|
|
197
|
+
assertTransactionCallback2(
|
|
198
|
+
callback
|
|
199
|
+
);
|
|
200
|
+
const pool = await this.getPool();
|
|
201
|
+
const connection = await pool.connect();
|
|
202
|
+
await connection.query(
|
|
203
|
+
"BEGIN"
|
|
204
|
+
);
|
|
205
|
+
const transactionDatabase = {
|
|
206
|
+
query: async (sql, parameters) => {
|
|
207
|
+
assertSql2(
|
|
208
|
+
sql
|
|
209
|
+
);
|
|
210
|
+
const result = await connection.query(
|
|
211
|
+
sql,
|
|
212
|
+
normalizePostgresqlParameters(
|
|
213
|
+
parameters
|
|
214
|
+
)
|
|
215
|
+
);
|
|
216
|
+
return result.rows;
|
|
217
|
+
},
|
|
218
|
+
execute: async (sql, parameters) => {
|
|
219
|
+
assertSql2(
|
|
220
|
+
sql
|
|
221
|
+
);
|
|
222
|
+
const result = await connection.query(
|
|
223
|
+
sql,
|
|
224
|
+
normalizePostgresqlParameters(
|
|
225
|
+
parameters
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
try {
|
|
232
|
+
const result = await callback(
|
|
233
|
+
transactionDatabase
|
|
234
|
+
);
|
|
235
|
+
await connection.query(
|
|
236
|
+
"COMMIT"
|
|
87
237
|
);
|
|
238
|
+
return result;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
try {
|
|
241
|
+
await connection.query(
|
|
242
|
+
"ROLLBACK"
|
|
243
|
+
);
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
} finally {
|
|
248
|
+
connection.release();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async disconnect() {
|
|
252
|
+
const pendingPool = this.poolPromise;
|
|
253
|
+
this.poolPromise = null;
|
|
254
|
+
if (!pendingPool) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
let pool;
|
|
258
|
+
try {
|
|
259
|
+
pool = await pendingPool;
|
|
260
|
+
} catch {
|
|
261
|
+
return;
|
|
88
262
|
}
|
|
263
|
+
await pool.end();
|
|
264
|
+
}
|
|
265
|
+
async getPool() {
|
|
266
|
+
await this.connect();
|
|
89
267
|
return this.poolPromise;
|
|
90
268
|
}
|
|
91
269
|
};
|
|
270
|
+
async function createPostgresqlPool(options) {
|
|
271
|
+
let postgresqlModule;
|
|
272
|
+
try {
|
|
273
|
+
postgresqlModule = await import(POSTGRESQL_MODULE_SPECIFIER);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
"BCP Database: PostgreSQL requires the optional dependency pg. Install it with `npm install pg` or create the app with the PostgreSQL preset.",
|
|
277
|
+
{
|
|
278
|
+
cause: error
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const Pool = postgresqlModule.Pool ?? postgresqlModule.default?.Pool;
|
|
283
|
+
if (typeof Pool !== "function") {
|
|
284
|
+
throw new Error(
|
|
285
|
+
"BCP Database: pg did not expose a compatible Pool constructor."
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
const poolOptions = options.url ? {
|
|
289
|
+
connectionString: options.url,
|
|
290
|
+
max: options.connectionLimit
|
|
291
|
+
} : {
|
|
292
|
+
host: options.host,
|
|
293
|
+
port: options.port,
|
|
294
|
+
user: options.user,
|
|
295
|
+
password: options.password,
|
|
296
|
+
database: options.database,
|
|
297
|
+
max: options.connectionLimit
|
|
298
|
+
};
|
|
299
|
+
return new Pool(
|
|
300
|
+
poolOptions
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
function normalizePostgresqlParameters(parameters) {
|
|
304
|
+
if (parameters === void 0) {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
if (Array.isArray(
|
|
308
|
+
parameters
|
|
309
|
+
)) {
|
|
310
|
+
return parameters;
|
|
311
|
+
}
|
|
312
|
+
throw new TypeError(
|
|
313
|
+
"BCP Database: PostgreSQL parameters must be an array. Use $1, $2, ... placeholders."
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function assertSql2(sql) {
|
|
317
|
+
if (typeof sql !== "string" || sql.trim() === "") {
|
|
318
|
+
throw new TypeError(
|
|
319
|
+
"BCP Database: SQL must be a non-empty string."
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function assertTransactionCallback2(callback) {
|
|
324
|
+
if (typeof callback !== "function") {
|
|
325
|
+
throw new TypeError(
|
|
326
|
+
"BCP Database: transaction callback must be a function."
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// packages/client/src/database-sqlite.ts
|
|
332
|
+
import fs from "node:fs";
|
|
333
|
+
import path from "node:path";
|
|
334
|
+
import {
|
|
335
|
+
fileURLToPath
|
|
336
|
+
} from "node:url";
|
|
337
|
+
var SQLITE_MODULE_SPECIFIER = "better-sqlite3";
|
|
338
|
+
function createSqliteDatabaseAdapter(options) {
|
|
339
|
+
return new SqliteDatabaseAdapter(
|
|
340
|
+
options
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
var SqliteDatabaseAdapter = class {
|
|
344
|
+
driver = "sqlite";
|
|
345
|
+
options;
|
|
346
|
+
databasePromise = null;
|
|
347
|
+
operationTail = Promise.resolve();
|
|
348
|
+
constructor(options) {
|
|
349
|
+
this.options = options;
|
|
350
|
+
}
|
|
351
|
+
async connect() {
|
|
352
|
+
await this.getDatabase();
|
|
353
|
+
}
|
|
354
|
+
async query(sql, parameters) {
|
|
355
|
+
return this.runExclusive(
|
|
356
|
+
async () => {
|
|
357
|
+
const database = await this.getDatabase();
|
|
358
|
+
return runSqliteQuery(
|
|
359
|
+
database,
|
|
360
|
+
sql,
|
|
361
|
+
parameters
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
async execute(sql, parameters) {
|
|
367
|
+
return this.runExclusive(
|
|
368
|
+
async () => {
|
|
369
|
+
const database = await this.getDatabase();
|
|
370
|
+
return runSqliteExecute(
|
|
371
|
+
database,
|
|
372
|
+
sql,
|
|
373
|
+
parameters
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
async transaction(callback) {
|
|
379
|
+
return this.runExclusive(
|
|
380
|
+
async () => {
|
|
381
|
+
const database = await this.getDatabase();
|
|
382
|
+
database.exec(
|
|
383
|
+
"BEGIN"
|
|
384
|
+
);
|
|
385
|
+
const transactionDatabase = {
|
|
386
|
+
query: async (sql, parameters) => runSqliteQuery(
|
|
387
|
+
database,
|
|
388
|
+
sql,
|
|
389
|
+
parameters
|
|
390
|
+
),
|
|
391
|
+
execute: async (sql, parameters) => runSqliteExecute(
|
|
392
|
+
database,
|
|
393
|
+
sql,
|
|
394
|
+
parameters
|
|
395
|
+
)
|
|
396
|
+
};
|
|
397
|
+
try {
|
|
398
|
+
const result = await callback(
|
|
399
|
+
transactionDatabase
|
|
400
|
+
);
|
|
401
|
+
database.exec(
|
|
402
|
+
"COMMIT"
|
|
403
|
+
);
|
|
404
|
+
return result;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
try {
|
|
407
|
+
database.exec(
|
|
408
|
+
"ROLLBACK"
|
|
409
|
+
);
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
async disconnect() {
|
|
418
|
+
await this.runExclusive(
|
|
419
|
+
async () => {
|
|
420
|
+
const pendingDatabase = this.databasePromise;
|
|
421
|
+
this.databasePromise = null;
|
|
422
|
+
if (!pendingDatabase) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
let database;
|
|
426
|
+
try {
|
|
427
|
+
database = await pendingDatabase;
|
|
428
|
+
} catch {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
database.close();
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
getDatabase() {
|
|
436
|
+
if (!this.databasePromise) {
|
|
437
|
+
this.databasePromise = createSqliteDatabase(
|
|
438
|
+
this.options
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
return this.databasePromise;
|
|
442
|
+
}
|
|
443
|
+
runExclusive(operation) {
|
|
444
|
+
const result = this.operationTail.then(
|
|
445
|
+
operation,
|
|
446
|
+
operation
|
|
447
|
+
);
|
|
448
|
+
this.operationTail = result.then(
|
|
449
|
+
() => void 0,
|
|
450
|
+
() => void 0
|
|
451
|
+
);
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
async function createSqliteDatabase(options) {
|
|
456
|
+
let sqliteModule;
|
|
457
|
+
try {
|
|
458
|
+
sqliteModule = await import(SQLITE_MODULE_SPECIFIER);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
"BCP Database: SQLite requires the optional dependency better-sqlite3. Install it with `npm install better-sqlite3` or create the app with the SQLite preset.",
|
|
462
|
+
{
|
|
463
|
+
cause: error
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const Database = sqliteModule.default;
|
|
468
|
+
if (typeof Database !== "function") {
|
|
469
|
+
throw new Error(
|
|
470
|
+
"BCP Database: better-sqlite3 did not expose a compatible database constructor."
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const filename = resolveSqliteFilename(
|
|
474
|
+
options.url ?? options.database
|
|
475
|
+
);
|
|
476
|
+
if (filename !== ":memory:") {
|
|
477
|
+
const resolvedFilename = path.resolve(
|
|
478
|
+
filename
|
|
479
|
+
);
|
|
480
|
+
fs.mkdirSync(
|
|
481
|
+
path.dirname(
|
|
482
|
+
resolvedFilename
|
|
483
|
+
),
|
|
484
|
+
{
|
|
485
|
+
recursive: true
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
return new Database(
|
|
489
|
+
resolvedFilename
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return new Database(
|
|
493
|
+
filename
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
function runSqliteQuery(database, sql, parameters) {
|
|
497
|
+
assertSql3(
|
|
498
|
+
sql
|
|
499
|
+
);
|
|
500
|
+
const statement = database.prepare(
|
|
501
|
+
sql
|
|
502
|
+
);
|
|
503
|
+
return callStatement(
|
|
504
|
+
statement.all.bind(
|
|
505
|
+
statement
|
|
506
|
+
),
|
|
507
|
+
parameters
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
function runSqliteExecute(database, sql, parameters) {
|
|
511
|
+
assertSql3(
|
|
512
|
+
sql
|
|
513
|
+
);
|
|
514
|
+
const statement = database.prepare(
|
|
515
|
+
sql
|
|
516
|
+
);
|
|
517
|
+
return callStatement(
|
|
518
|
+
statement.run.bind(
|
|
519
|
+
statement
|
|
520
|
+
),
|
|
521
|
+
parameters
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
function callStatement(operation, parameters) {
|
|
525
|
+
if (parameters === void 0) {
|
|
526
|
+
return operation();
|
|
527
|
+
}
|
|
528
|
+
if (isParameterArray(
|
|
529
|
+
parameters
|
|
530
|
+
)) {
|
|
531
|
+
return operation(
|
|
532
|
+
...parameters
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return operation(
|
|
536
|
+
parameters
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
function isParameterArray(value) {
|
|
540
|
+
return Array.isArray(
|
|
541
|
+
value
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
function resolveSqliteFilename(value) {
|
|
545
|
+
const normalized = value.trim();
|
|
546
|
+
if (normalized === "" || normalized === ":memory:") {
|
|
547
|
+
return normalized || ":memory:";
|
|
548
|
+
}
|
|
549
|
+
if (normalized.startsWith(
|
|
550
|
+
"file:"
|
|
551
|
+
)) {
|
|
552
|
+
return fileURLToPath(
|
|
553
|
+
normalized
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
if (normalized.startsWith(
|
|
557
|
+
"sqlite://"
|
|
558
|
+
)) {
|
|
559
|
+
return normalized.slice(
|
|
560
|
+
"sqlite://".length
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (normalized.startsWith(
|
|
564
|
+
"sqlite:"
|
|
565
|
+
)) {
|
|
566
|
+
return normalized.slice(
|
|
567
|
+
"sqlite:".length
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return normalized;
|
|
571
|
+
}
|
|
572
|
+
function assertSql3(sql) {
|
|
573
|
+
if (typeof sql !== "string" || sql.trim() === "") {
|
|
574
|
+
throw new TypeError(
|
|
575
|
+
"BCP Database: SQL must be a non-empty string."
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// packages/client/src/database.ts
|
|
581
|
+
var BcpDatabase = class {
|
|
582
|
+
options;
|
|
583
|
+
adapterPromise = null;
|
|
584
|
+
constructor(options = {}) {
|
|
585
|
+
this.options = {
|
|
586
|
+
...options
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async connect() {
|
|
590
|
+
await this.getAdapter();
|
|
591
|
+
}
|
|
592
|
+
async query(sql, parameters) {
|
|
593
|
+
assertSql4(
|
|
594
|
+
sql
|
|
595
|
+
);
|
|
596
|
+
const adapter = await this.getAdapter();
|
|
597
|
+
return adapter.query(
|
|
598
|
+
sql,
|
|
599
|
+
parameters
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
async execute(sql, parameters) {
|
|
603
|
+
assertSql4(
|
|
604
|
+
sql
|
|
605
|
+
);
|
|
606
|
+
const adapter = await this.getAdapter();
|
|
607
|
+
return adapter.execute(
|
|
608
|
+
sql,
|
|
609
|
+
parameters
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
async transaction(callback) {
|
|
613
|
+
assertTransactionCallback3(
|
|
614
|
+
callback
|
|
615
|
+
);
|
|
616
|
+
const adapter = await this.getAdapter();
|
|
617
|
+
return adapter.transaction(
|
|
618
|
+
callback
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
async disconnect() {
|
|
622
|
+
await this.close();
|
|
623
|
+
}
|
|
624
|
+
async close() {
|
|
625
|
+
const pendingAdapter = this.adapterPromise;
|
|
626
|
+
this.adapterPromise = null;
|
|
627
|
+
if (!pendingAdapter) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
let adapter;
|
|
631
|
+
try {
|
|
632
|
+
adapter = await pendingAdapter;
|
|
633
|
+
} catch {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
await adapter.disconnect();
|
|
637
|
+
}
|
|
638
|
+
async getAdapter() {
|
|
639
|
+
if (!this.adapterPromise) {
|
|
640
|
+
this.adapterPromise = initializeDatabaseAdapter(
|
|
641
|
+
this.options
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
try {
|
|
645
|
+
return await this.adapterPromise;
|
|
646
|
+
} catch (error) {
|
|
647
|
+
this.adapterPromise = null;
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
92
652
|
function createDatabase(options = {}) {
|
|
93
653
|
return new BcpDatabase(
|
|
94
654
|
options
|
|
95
655
|
);
|
|
96
656
|
}
|
|
97
657
|
function resolveDatabaseOptions(options = {}, environment = process.env) {
|
|
98
|
-
const
|
|
99
|
-
|
|
658
|
+
const url = nonEmpty(
|
|
659
|
+
options.url ?? environment.DATABASE_URL
|
|
660
|
+
) ?? void 0;
|
|
661
|
+
const driver = normalizeDriver(
|
|
662
|
+
String(
|
|
663
|
+
options.driver ?? environment.DB_DRIVER ?? inferDriverFromDatabaseUrl(
|
|
664
|
+
url
|
|
665
|
+
) ?? "mysql"
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
const defaults = getDriverDefaults(
|
|
669
|
+
driver
|
|
100
670
|
);
|
|
101
|
-
if (driver !== "mysql") {
|
|
102
|
-
throw new Error(
|
|
103
|
-
`BCP Database: unsupported database driver "${driver}".`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
671
|
return {
|
|
107
672
|
driver,
|
|
673
|
+
...url ? {
|
|
674
|
+
url
|
|
675
|
+
} : {},
|
|
108
676
|
host: nonEmpty(
|
|
109
677
|
options.host ?? environment.DB_HOST
|
|
110
|
-
) ??
|
|
678
|
+
) ?? defaults.host,
|
|
111
679
|
port: resolvePositiveInteger(
|
|
112
680
|
options.port ?? environment.DB_PORT,
|
|
113
|
-
|
|
681
|
+
defaults.port,
|
|
114
682
|
"DB_PORT"
|
|
115
683
|
),
|
|
116
684
|
user: nonEmpty(
|
|
117
685
|
options.user ?? environment.DB_USER
|
|
118
|
-
) ??
|
|
686
|
+
) ?? defaults.user,
|
|
119
687
|
password: String(
|
|
120
688
|
options.password ?? environment.DB_PASSWORD ?? ""
|
|
121
689
|
),
|
|
122
690
|
database: nonEmpty(
|
|
123
691
|
options.database ?? environment.DB_NAME
|
|
124
|
-
) ??
|
|
692
|
+
) ?? defaults.database,
|
|
125
693
|
connectionLimit: resolvePositiveInteger(
|
|
126
694
|
options.connectionLimit ?? environment.DB_CONNECTION_LIMIT,
|
|
127
|
-
|
|
695
|
+
defaults.connectionLimit,
|
|
128
696
|
"DB_CONNECTION_LIMIT"
|
|
129
697
|
),
|
|
130
698
|
waitForConnections: options.waitForConnections ?? resolveBoolean(
|
|
@@ -139,43 +707,113 @@ function resolveDatabaseOptions(options = {}, environment = process.env) {
|
|
|
139
707
|
),
|
|
140
708
|
charset: nonEmpty(
|
|
141
709
|
options.charset ?? environment.DB_CHARSET
|
|
142
|
-
) ??
|
|
710
|
+
) ?? defaults.charset
|
|
143
711
|
};
|
|
144
712
|
}
|
|
145
713
|
var db = createDatabase();
|
|
146
|
-
async function
|
|
147
|
-
let
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
{
|
|
154
|
-
cause: error
|
|
155
|
-
}
|
|
714
|
+
async function initializeDatabaseAdapter(options) {
|
|
715
|
+
let adapter;
|
|
716
|
+
if (options.adapter) {
|
|
717
|
+
adapter = typeof options.adapter === "function" ? await options.adapter() : options.adapter;
|
|
718
|
+
} else {
|
|
719
|
+
const resolvedOptions = resolveDatabaseOptions(
|
|
720
|
+
options
|
|
156
721
|
);
|
|
722
|
+
switch (resolvedOptions.driver) {
|
|
723
|
+
case "postgresql": {
|
|
724
|
+
adapter = createPostgresqlDatabaseAdapter(
|
|
725
|
+
resolvedOptions
|
|
726
|
+
);
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
case "sqlite": {
|
|
730
|
+
adapter = createSqliteDatabaseAdapter(
|
|
731
|
+
resolvedOptions
|
|
732
|
+
);
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
default: {
|
|
736
|
+
adapter = createMysqlDatabaseAdapter(
|
|
737
|
+
resolvedOptions
|
|
738
|
+
);
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
157
742
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
database: options.database,
|
|
164
|
-
waitForConnections: options.waitForConnections,
|
|
165
|
-
connectionLimit: options.connectionLimit,
|
|
166
|
-
queueLimit: options.queueLimit,
|
|
167
|
-
charset: options.charset
|
|
168
|
-
});
|
|
743
|
+
assertDatabaseAdapter(
|
|
744
|
+
adapter
|
|
745
|
+
);
|
|
746
|
+
await adapter.connect();
|
|
747
|
+
return adapter;
|
|
169
748
|
}
|
|
170
749
|
function normalizeDriver(value) {
|
|
171
750
|
const normalized = value.trim().toLowerCase();
|
|
172
751
|
if (normalized === "mysql") {
|
|
173
752
|
return "mysql";
|
|
174
753
|
}
|
|
754
|
+
if (normalized === "postgresql" || normalized === "postgres" || normalized === "pg") {
|
|
755
|
+
return "postgresql";
|
|
756
|
+
}
|
|
757
|
+
if (normalized === "sqlite" || normalized === "sqlite3") {
|
|
758
|
+
return "sqlite";
|
|
759
|
+
}
|
|
175
760
|
throw new Error(
|
|
176
761
|
`BCP Database: unsupported database driver "${value}".`
|
|
177
762
|
);
|
|
178
763
|
}
|
|
764
|
+
function inferDriverFromDatabaseUrl(value) {
|
|
765
|
+
if (!value) {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
if (/^postgres(?:ql)?:\/\//i.test(
|
|
769
|
+
value
|
|
770
|
+
)) {
|
|
771
|
+
return "postgresql";
|
|
772
|
+
}
|
|
773
|
+
if (/^mysql:\/\//i.test(
|
|
774
|
+
value
|
|
775
|
+
)) {
|
|
776
|
+
return "mysql";
|
|
777
|
+
}
|
|
778
|
+
if (value === ":memory:" || /^(?:sqlite|file):/i.test(
|
|
779
|
+
value
|
|
780
|
+
) || /\.(?:sqlite|sqlite3|db)$/i.test(
|
|
781
|
+
value
|
|
782
|
+
)) {
|
|
783
|
+
return "sqlite";
|
|
784
|
+
}
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
function getDriverDefaults(driver) {
|
|
788
|
+
if (driver === "postgresql") {
|
|
789
|
+
return {
|
|
790
|
+
host: "localhost",
|
|
791
|
+
port: 5432,
|
|
792
|
+
user: "postgres",
|
|
793
|
+
database: "bcp_app",
|
|
794
|
+
connectionLimit: 10,
|
|
795
|
+
charset: "utf8"
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
if (driver === "sqlite") {
|
|
799
|
+
return {
|
|
800
|
+
host: "",
|
|
801
|
+
port: 0,
|
|
802
|
+
user: "",
|
|
803
|
+
database: "./data/bcp.sqlite",
|
|
804
|
+
connectionLimit: 1,
|
|
805
|
+
charset: "utf8"
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
host: "localhost",
|
|
810
|
+
port: 3306,
|
|
811
|
+
user: "root",
|
|
812
|
+
database: "bcp_app",
|
|
813
|
+
connectionLimit: 10,
|
|
814
|
+
charset: "utf8mb4"
|
|
815
|
+
};
|
|
816
|
+
}
|
|
179
817
|
function nonEmpty(value) {
|
|
180
818
|
if (typeof value !== "string") {
|
|
181
819
|
return null;
|
|
@@ -221,13 +859,27 @@ function resolveBoolean(value, fallback, label) {
|
|
|
221
859
|
`BCP Database: ${label} must be true/false or 1/0.`
|
|
222
860
|
);
|
|
223
861
|
}
|
|
224
|
-
function
|
|
862
|
+
function assertSql4(sql) {
|
|
225
863
|
if (typeof sql !== "string" || sql.trim() === "") {
|
|
226
864
|
throw new TypeError(
|
|
227
865
|
"BCP Database: SQL must be a non-empty string."
|
|
228
866
|
);
|
|
229
867
|
}
|
|
230
868
|
}
|
|
869
|
+
function assertTransactionCallback3(callback) {
|
|
870
|
+
if (typeof callback !== "function") {
|
|
871
|
+
throw new TypeError(
|
|
872
|
+
"BCP Database: transaction callback must be a function."
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function assertDatabaseAdapter(adapter) {
|
|
877
|
+
if (!adapter || typeof adapter !== "object" || typeof adapter.driver !== "string" || adapter.driver.trim() === "" || typeof adapter.connect !== "function" || typeof adapter.query !== "function" || typeof adapter.execute !== "function" || typeof adapter.transaction !== "function" || typeof adapter.disconnect !== "function") {
|
|
878
|
+
throw new TypeError(
|
|
879
|
+
"BCP Database: adapter must implement driver, connect, query, execute, transaction and disconnect."
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
231
883
|
export {
|
|
232
884
|
BcpDatabase,
|
|
233
885
|
createDatabase,
|