@chidchanun/bcp 0.2.1 → 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 +255 -211
- package/docs/README.md +118 -79
- package/docs/api-manifest.json +5 -4
- package/docs/api-reference.md +33 -4
- package/docs/configuration.md +96 -1
- package/docs/database-migrations.md +79 -19
- package/docs/database.md +197 -28
- package/docs/docs-web-manifest.json +6 -3
- package/docs/environment-validation.md +224 -0
- package/docs/platform-manifest.json +20 -5
- package/docs/releases/0.2.2.md +118 -0
- package/docs/releases/0.2.3.md +77 -0
- package/package.json +1 -1
- package/packages/cli/src/args.ts +50 -9
- package/packages/cli/src/bootstrap.ts +22 -7
- package/packages/cli/src/configuration.ts +235 -0
- package/packages/cli/src/database-migrations.ts +91 -21
- package/packages/cli/src/index.ts +101 -1
- package/packages/client/src/config.ts +26 -0
- 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
- package/packages/config/src/diagnostics.ts +226 -0
- package/packages/config/src/environment-loader.ts +101 -0
- package/packages/config/src/environment-schema.ts +677 -0
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
pathToFileURL,
|
|
5
5
|
} from "node:url";
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
resolveDatabaseOptions,
|
|
9
|
+
type DatabaseDriver,
|
|
10
|
+
type TransactionDatabase,
|
|
9
11
|
} from "../../client/src/database.js";
|
|
10
12
|
|
|
11
13
|
export type DatabaseMigrationAction =
|
|
@@ -35,6 +37,7 @@ export interface DatabaseMigrationCommandOptions {
|
|
|
35
37
|
action: DatabaseMigrationAction;
|
|
36
38
|
name?: string;
|
|
37
39
|
database?: MigrationDatabase;
|
|
40
|
+
driver?: DatabaseDriver;
|
|
38
41
|
now?: Date;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -65,6 +68,12 @@ interface MigrationModule {
|
|
|
65
68
|
) => Promise<void> | void;
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
interface MigrationDialect {
|
|
72
|
+
createTableSql: string;
|
|
73
|
+
insertMigrationSql: string;
|
|
74
|
+
deleteMigrationSql: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
const MIGRATION_DIRECTORY =
|
|
69
78
|
"migrations";
|
|
70
79
|
const MIGRATION_TABLE =
|
|
@@ -105,8 +114,15 @@ export async function runDatabaseMigrationCommand(
|
|
|
105
114
|
);
|
|
106
115
|
}
|
|
107
116
|
|
|
117
|
+
const dialect =
|
|
118
|
+
getMigrationDialect(
|
|
119
|
+
options.driver ??
|
|
120
|
+
resolveDatabaseOptions().driver
|
|
121
|
+
);
|
|
122
|
+
|
|
108
123
|
await ensureMigrationTable(
|
|
109
|
-
database
|
|
124
|
+
database,
|
|
125
|
+
dialect
|
|
110
126
|
);
|
|
111
127
|
|
|
112
128
|
if (
|
|
@@ -129,14 +145,16 @@ export async function runDatabaseMigrationCommand(
|
|
|
129
145
|
) {
|
|
130
146
|
await migratePending(
|
|
131
147
|
rootDirectory,
|
|
132
|
-
database
|
|
148
|
+
database,
|
|
149
|
+
dialect
|
|
133
150
|
);
|
|
134
151
|
return;
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
await rollbackLatestBatch(
|
|
138
155
|
rootDirectory,
|
|
139
|
-
database
|
|
156
|
+
database,
|
|
157
|
+
dialect
|
|
140
158
|
);
|
|
141
159
|
}
|
|
142
160
|
|
|
@@ -197,13 +215,13 @@ export function createMigrationFile(
|
|
|
197
215
|
`export async function up(\n` +
|
|
198
216
|
` db: TransactionDatabase\n` +
|
|
199
217
|
`): Promise<void> {\n` +
|
|
200
|
-
` // Write
|
|
218
|
+
` // Write provider-compatible forward SQL here.\n` +
|
|
201
219
|
` void db;\n` +
|
|
202
220
|
`}\n\n` +
|
|
203
221
|
`export async function down(\n` +
|
|
204
222
|
` db: TransactionDatabase\n` +
|
|
205
223
|
`): Promise<void> {\n` +
|
|
206
|
-
` // Write
|
|
224
|
+
` // Write provider-compatible rollback SQL here.\n` +
|
|
207
225
|
` void db;\n` +
|
|
208
226
|
`}\n`;
|
|
209
227
|
|
|
@@ -363,7 +381,8 @@ export async function getMigrationStatus(
|
|
|
363
381
|
|
|
364
382
|
async function migratePending(
|
|
365
383
|
rootDirectory: string,
|
|
366
|
-
database: MigrationDatabase
|
|
384
|
+
database: MigrationDatabase,
|
|
385
|
+
dialect: MigrationDialect
|
|
367
386
|
): Promise<void> {
|
|
368
387
|
const files =
|
|
369
388
|
listMigrationFiles(
|
|
@@ -421,11 +440,11 @@ async function migratePending(
|
|
|
421
440
|
|
|
422
441
|
await database.transaction(
|
|
423
442
|
async (transaction) => {
|
|
424
|
-
await module.up
|
|
443
|
+
await module.up(
|
|
425
444
|
transaction
|
|
426
445
|
);
|
|
427
446
|
await transaction.execute(
|
|
428
|
-
|
|
447
|
+
dialect.insertMigrationSql,
|
|
429
448
|
[
|
|
430
449
|
migration.name,
|
|
431
450
|
batch,
|
|
@@ -442,7 +461,8 @@ async function migratePending(
|
|
|
442
461
|
|
|
443
462
|
async function rollbackLatestBatch(
|
|
444
463
|
rootDirectory: string,
|
|
445
|
-
database: MigrationDatabase
|
|
464
|
+
database: MigrationDatabase,
|
|
465
|
+
dialect: MigrationDialect
|
|
446
466
|
): Promise<void> {
|
|
447
467
|
const applied =
|
|
448
468
|
await readAppliedMigrations(
|
|
@@ -520,11 +540,11 @@ async function rollbackLatestBatch(
|
|
|
520
540
|
|
|
521
541
|
await database.transaction(
|
|
522
542
|
async (transaction) => {
|
|
523
|
-
await module.down
|
|
543
|
+
await module.down(
|
|
524
544
|
transaction
|
|
525
545
|
);
|
|
526
546
|
await transaction.execute(
|
|
527
|
-
|
|
547
|
+
dialect.deleteMigrationSql,
|
|
528
548
|
[
|
|
529
549
|
record.name,
|
|
530
550
|
]
|
|
@@ -539,15 +559,11 @@ async function rollbackLatestBatch(
|
|
|
539
559
|
}
|
|
540
560
|
|
|
541
561
|
async function ensureMigrationTable(
|
|
542
|
-
database: MigrationDatabase
|
|
562
|
+
database: MigrationDatabase,
|
|
563
|
+
dialect: MigrationDialect
|
|
543
564
|
): Promise<void> {
|
|
544
565
|
await database.execute(
|
|
545
|
-
|
|
546
|
-
`id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, ` +
|
|
547
|
-
`name VARCHAR(255) NOT NULL UNIQUE, ` +
|
|
548
|
-
`batch INT UNSIGNED NOT NULL, ` +
|
|
549
|
-
`applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP` +
|
|
550
|
-
`) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`
|
|
566
|
+
dialect.createTableSql
|
|
551
567
|
);
|
|
552
568
|
}
|
|
553
569
|
|
|
@@ -561,6 +577,60 @@ async function readAppliedMigrations(
|
|
|
561
577
|
);
|
|
562
578
|
}
|
|
563
579
|
|
|
580
|
+
function getMigrationDialect(
|
|
581
|
+
driver: DatabaseDriver
|
|
582
|
+
): MigrationDialect {
|
|
583
|
+
if (
|
|
584
|
+
driver === "postgresql"
|
|
585
|
+
) {
|
|
586
|
+
return {
|
|
587
|
+
createTableSql:
|
|
588
|
+
`CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (` +
|
|
589
|
+
`id BIGSERIAL PRIMARY KEY, ` +
|
|
590
|
+
`name VARCHAR(255) NOT NULL UNIQUE, ` +
|
|
591
|
+
`batch INTEGER NOT NULL, ` +
|
|
592
|
+
`applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP` +
|
|
593
|
+
`)`,
|
|
594
|
+
insertMigrationSql:
|
|
595
|
+
`INSERT INTO ${MIGRATION_TABLE} (name, batch) VALUES ($1, $2)`,
|
|
596
|
+
deleteMigrationSql:
|
|
597
|
+
`DELETE FROM ${MIGRATION_TABLE} WHERE name = $1`,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (
|
|
602
|
+
driver === "sqlite"
|
|
603
|
+
) {
|
|
604
|
+
return {
|
|
605
|
+
createTableSql:
|
|
606
|
+
`CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (` +
|
|
607
|
+
`id INTEGER PRIMARY KEY AUTOINCREMENT, ` +
|
|
608
|
+
`name TEXT NOT NULL UNIQUE, ` +
|
|
609
|
+
`batch INTEGER NOT NULL, ` +
|
|
610
|
+
`applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP` +
|
|
611
|
+
`)`,
|
|
612
|
+
insertMigrationSql:
|
|
613
|
+
`INSERT INTO ${MIGRATION_TABLE} (name, batch) VALUES (?, ?)`,
|
|
614
|
+
deleteMigrationSql:
|
|
615
|
+
`DELETE FROM ${MIGRATION_TABLE} WHERE name = ?`,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
createTableSql:
|
|
621
|
+
`CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (` +
|
|
622
|
+
`id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, ` +
|
|
623
|
+
`name VARCHAR(255) NOT NULL UNIQUE, ` +
|
|
624
|
+
`batch INT UNSIGNED NOT NULL, ` +
|
|
625
|
+
`applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP` +
|
|
626
|
+
`) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`,
|
|
627
|
+
insertMigrationSql:
|
|
628
|
+
`INSERT INTO ${MIGRATION_TABLE} (name, batch) VALUES (?, ?)`,
|
|
629
|
+
deleteMigrationSql:
|
|
630
|
+
`DELETE FROM ${MIGRATION_TABLE} WHERE name = ?`,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
564
634
|
async function loadMigrationModule(
|
|
565
635
|
migration: LoadedMigration
|
|
566
636
|
): Promise<Required<MigrationModule>> {
|
|
@@ -595,7 +665,7 @@ async function loadMigrationModule(
|
|
|
595
665
|
module.up,
|
|
596
666
|
down:
|
|
597
667
|
module.down,
|
|
598
|
-
}
|
|
668
|
+
} as Required<MigrationModule>;
|
|
599
669
|
}
|
|
600
670
|
|
|
601
671
|
function normalizeMigrationName(
|
|
@@ -76,6 +76,11 @@ switch (cliOptions.command) {
|
|
|
76
76
|
break;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
case "config": {
|
|
80
|
+
await runConfigCommand();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
case "doctor": {
|
|
80
85
|
await runDeveloperCommand(
|
|
81
86
|
"doctor"
|
|
@@ -112,6 +117,24 @@ async function runDev() {
|
|
|
112
117
|
cliOptions.rootDirectory
|
|
113
118
|
);
|
|
114
119
|
|
|
120
|
+
const {
|
|
121
|
+
runStartupConfigurationDiagnostics,
|
|
122
|
+
} =
|
|
123
|
+
await import(
|
|
124
|
+
"./configuration.js"
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
await runStartupConfigurationDiagnostics(
|
|
128
|
+
rootDirectory,
|
|
129
|
+
"development",
|
|
130
|
+
{
|
|
131
|
+
port:
|
|
132
|
+
cliOptions.port,
|
|
133
|
+
hostname:
|
|
134
|
+
cliOptions.hostname,
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
115
138
|
installApplicationModuleAlias(
|
|
116
139
|
rootDirectory
|
|
117
140
|
);
|
|
@@ -185,6 +208,24 @@ async function runBuild() {
|
|
|
185
208
|
cliOptions.rootDirectory
|
|
186
209
|
);
|
|
187
210
|
|
|
211
|
+
const {
|
|
212
|
+
runStartupConfigurationDiagnostics,
|
|
213
|
+
} =
|
|
214
|
+
await import(
|
|
215
|
+
"./configuration.js"
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
await runStartupConfigurationDiagnostics(
|
|
219
|
+
rootDirectory,
|
|
220
|
+
"production",
|
|
221
|
+
{
|
|
222
|
+
port:
|
|
223
|
+
cliOptions.port,
|
|
224
|
+
hostname:
|
|
225
|
+
cliOptions.hostname,
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
188
229
|
const appDirectory =
|
|
189
230
|
path.join(
|
|
190
231
|
rootDirectory,
|
|
@@ -518,6 +559,36 @@ async function runGenerateCommand(): Promise<void> {
|
|
|
518
559
|
});
|
|
519
560
|
}
|
|
520
561
|
|
|
562
|
+
async function runConfigCommand(): Promise<void> {
|
|
563
|
+
const action =
|
|
564
|
+
cliOptions.configAction;
|
|
565
|
+
|
|
566
|
+
if (
|
|
567
|
+
action === undefined ||
|
|
568
|
+
action === "help"
|
|
569
|
+
) {
|
|
570
|
+
printConfigHelp();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const rootDirectory =
|
|
575
|
+
resolveProjectRoot(
|
|
576
|
+
cliOptions.rootDirectory
|
|
577
|
+
);
|
|
578
|
+
const {
|
|
579
|
+
runConfigurationCheck,
|
|
580
|
+
} =
|
|
581
|
+
await import(
|
|
582
|
+
"./configuration.js"
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
await runConfigurationCheck({
|
|
586
|
+
rootDirectory,
|
|
587
|
+
json:
|
|
588
|
+
cliOptions.json,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
521
592
|
async function runDeveloperCommand(
|
|
522
593
|
command:
|
|
523
594
|
"doctor" |
|
|
@@ -759,6 +830,32 @@ Examples:
|
|
|
759
830
|
`);
|
|
760
831
|
}
|
|
761
832
|
|
|
833
|
+
function printConfigHelp() {
|
|
834
|
+
console.log(`
|
|
835
|
+
BCP Configuration & Environment
|
|
836
|
+
|
|
837
|
+
Usage:
|
|
838
|
+
bcp config <command> [options]
|
|
839
|
+
|
|
840
|
+
Commands:
|
|
841
|
+
check Validate bcp.config.*, bcp.environment.* and loaded environment values
|
|
842
|
+
help Show configuration command help
|
|
843
|
+
|
|
844
|
+
Options:
|
|
845
|
+
--root <path> Project root directory
|
|
846
|
+
--json Emit the configuration diagnostics report as JSON
|
|
847
|
+
|
|
848
|
+
Environment mode:
|
|
849
|
+
NODE_ENV=production bcp config check
|
|
850
|
+
NODE_ENV=test bcp config check
|
|
851
|
+
bcp config check (development by default)
|
|
852
|
+
|
|
853
|
+
Examples:
|
|
854
|
+
bcp config check
|
|
855
|
+
bcp config check --json
|
|
856
|
+
`);
|
|
857
|
+
}
|
|
858
|
+
|
|
762
859
|
function printHelp() {
|
|
763
860
|
console.log(`
|
|
764
861
|
BCP Framework v${FRAMEWORK_VERSION}
|
|
@@ -774,6 +871,7 @@ Commands:
|
|
|
774
871
|
update [target] Update BCP Framework (default target: latest)
|
|
775
872
|
db <command> Manage database migrations
|
|
776
873
|
generate <kind> Generate pages, API routes, middleware or migrations
|
|
874
|
+
config <command> Validate project configuration and environment schema
|
|
777
875
|
doctor Run project/runtime health diagnostics
|
|
778
876
|
inspect Print resolved env, config, dependencies and routes
|
|
779
877
|
help Show this help message
|
|
@@ -786,7 +884,7 @@ Options:
|
|
|
786
884
|
--host <host> Alias for --hostname
|
|
787
885
|
--check Check for a BCP update without changing files
|
|
788
886
|
--dry-run Preview a BCP update without changing files
|
|
789
|
-
--json JSON output for doctor/inspect
|
|
887
|
+
--json JSON output for doctor/inspect/config
|
|
790
888
|
--force Replace existing generated scaffold files
|
|
791
889
|
-h, --help Show help
|
|
792
890
|
-v, --version Show version
|
|
@@ -803,6 +901,8 @@ Examples:
|
|
|
803
901
|
bcp generate page dashboard/users
|
|
804
902
|
bcp generate api users
|
|
805
903
|
bcp generate middleware
|
|
904
|
+
bcp config check
|
|
905
|
+
bcp config check --json
|
|
806
906
|
bcp doctor
|
|
807
907
|
bcp doctor --json
|
|
808
908
|
bcp inspect
|
|
@@ -16,3 +16,29 @@ export {
|
|
|
16
16
|
type ResolveBcpConfigOverrides,
|
|
17
17
|
type ResolvedBcpConfig,
|
|
18
18
|
} from "../../config/src/index.js";
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
applyEnvironmentDefaults,
|
|
22
|
+
defineEnvironment,
|
|
23
|
+
validateEnvironment,
|
|
24
|
+
type BcpEnvironmentSchema,
|
|
25
|
+
type BcpEnvironmentValidationIssue,
|
|
26
|
+
type BcpEnvironmentValidationResult,
|
|
27
|
+
type BcpEnvironmentValueType,
|
|
28
|
+
type BcpEnvironmentVariableRule,
|
|
29
|
+
} from "../../config/src/environment-schema.js";
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
getEnvironmentSchemaFileNames,
|
|
33
|
+
loadBcpEnvironmentSchema,
|
|
34
|
+
type LoadedBcpEnvironmentSchema,
|
|
35
|
+
} from "../../config/src/environment-loader.js";
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
assertConfigurationDiagnostics,
|
|
39
|
+
diagnoseBcpConfiguration,
|
|
40
|
+
type BcpConfigurationDiagnostic,
|
|
41
|
+
type BcpConfigurationDiagnosticSeverity,
|
|
42
|
+
type BcpConfigurationDiagnosticsReport,
|
|
43
|
+
type DiagnoseBcpConfigurationOptions,
|
|
44
|
+
} from "../../config/src/diagnostics.js";
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatabaseAdapter,
|
|
3
|
+
DatabaseParameters,
|
|
4
|
+
ResolvedDatabaseOptions,
|
|
5
|
+
TransactionDatabase,
|
|
6
|
+
} from "./database.js";
|
|
7
|
+
|
|
8
|
+
interface MysqlExecutor {
|
|
9
|
+
query(
|
|
10
|
+
sql: string,
|
|
11
|
+
parameters?: DatabaseParameters
|
|
12
|
+
): Promise<[unknown, unknown]>;
|
|
13
|
+
execute(
|
|
14
|
+
sql: string,
|
|
15
|
+
parameters?: DatabaseParameters
|
|
16
|
+
): Promise<[unknown, unknown]>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface MysqlConnection extends MysqlExecutor {
|
|
20
|
+
beginTransaction(): Promise<void>;
|
|
21
|
+
commit(): Promise<void>;
|
|
22
|
+
rollback(): Promise<void>;
|
|
23
|
+
release(): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface MysqlPool extends MysqlExecutor {
|
|
27
|
+
getConnection(): Promise<MysqlConnection>;
|
|
28
|
+
end(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MysqlModule {
|
|
32
|
+
createPool(
|
|
33
|
+
options: Record<string, unknown>
|
|
34
|
+
): MysqlPool;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const MYSQL_MODULE_SPECIFIER =
|
|
38
|
+
"mysql2/promise";
|
|
39
|
+
|
|
40
|
+
export function createMysqlDatabaseAdapter(
|
|
41
|
+
options: ResolvedDatabaseOptions
|
|
42
|
+
): DatabaseAdapter {
|
|
43
|
+
return new MysqlDatabaseAdapter(
|
|
44
|
+
options
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class MysqlDatabaseAdapter
|
|
49
|
+
implements DatabaseAdapter {
|
|
50
|
+
readonly driver = "mysql";
|
|
51
|
+
|
|
52
|
+
private readonly options:
|
|
53
|
+
ResolvedDatabaseOptions;
|
|
54
|
+
|
|
55
|
+
private poolPromise:
|
|
56
|
+
Promise<MysqlPool> | null =
|
|
57
|
+
null;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
options: ResolvedDatabaseOptions
|
|
61
|
+
) {
|
|
62
|
+
this.options =
|
|
63
|
+
options;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async connect(): Promise<void> {
|
|
67
|
+
if (!this.poolPromise) {
|
|
68
|
+
this.poolPromise =
|
|
69
|
+
createMysqlPool(
|
|
70
|
+
this.options
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await this.poolPromise;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async query<T = unknown>(
|
|
78
|
+
sql: string,
|
|
79
|
+
parameters?: DatabaseParameters
|
|
80
|
+
): Promise<T> {
|
|
81
|
+
assertSql(
|
|
82
|
+
sql
|
|
83
|
+
);
|
|
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(
|
|
101
|
+
sql
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const pool =
|
|
105
|
+
await this.getPool();
|
|
106
|
+
const [result] =
|
|
107
|
+
await pool.execute(
|
|
108
|
+
sql,
|
|
109
|
+
parameters
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return result as T;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async transaction<T>(
|
|
116
|
+
callback: (
|
|
117
|
+
database: TransactionDatabase
|
|
118
|
+
) => Promise<T>
|
|
119
|
+
): Promise<T> {
|
|
120
|
+
assertTransactionCallback(
|
|
121
|
+
callback
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const pool =
|
|
125
|
+
await this.getPool();
|
|
126
|
+
const connection =
|
|
127
|
+
await pool.getConnection();
|
|
128
|
+
|
|
129
|
+
await connection.beginTransaction();
|
|
130
|
+
|
|
131
|
+
const transactionDatabase:
|
|
132
|
+
TransactionDatabase = {
|
|
133
|
+
query: async <R = unknown>(
|
|
134
|
+
sql: string,
|
|
135
|
+
parameters?: DatabaseParameters
|
|
136
|
+
): Promise<R> => {
|
|
137
|
+
assertSql(
|
|
138
|
+
sql
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const [rows] =
|
|
142
|
+
await connection.query(
|
|
143
|
+
sql,
|
|
144
|
+
parameters
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return rows as R;
|
|
148
|
+
},
|
|
149
|
+
execute: async <R = unknown>(
|
|
150
|
+
sql: string,
|
|
151
|
+
parameters?: DatabaseParameters
|
|
152
|
+
): Promise<R> => {
|
|
153
|
+
assertSql(
|
|
154
|
+
sql
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const [result] =
|
|
158
|
+
await connection.execute(
|
|
159
|
+
sql,
|
|
160
|
+
parameters
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return result as R;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const result =
|
|
169
|
+
await callback(
|
|
170
|
+
transactionDatabase
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
await connection.commit();
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
try {
|
|
178
|
+
await connection.rollback();
|
|
179
|
+
} catch {
|
|
180
|
+
// Preserve the original transaction error.
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
throw error;
|
|
184
|
+
} finally {
|
|
185
|
+
connection.release();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async disconnect(): Promise<void> {
|
|
190
|
+
const pendingPool =
|
|
191
|
+
this.poolPromise;
|
|
192
|
+
|
|
193
|
+
this.poolPromise =
|
|
194
|
+
null;
|
|
195
|
+
|
|
196
|
+
if (!pendingPool) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let pool:
|
|
201
|
+
MysqlPool;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
pool =
|
|
205
|
+
await pendingPool;
|
|
206
|
+
} catch {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await pool.end();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async getPool():
|
|
214
|
+
Promise<MysqlPool> {
|
|
215
|
+
await this.connect();
|
|
216
|
+
|
|
217
|
+
return this.poolPromise as
|
|
218
|
+
Promise<MysqlPool>;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function createMysqlPool(
|
|
223
|
+
options: ResolvedDatabaseOptions
|
|
224
|
+
): Promise<MysqlPool> {
|
|
225
|
+
let mysqlModule:
|
|
226
|
+
MysqlModule;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
mysqlModule =
|
|
230
|
+
await import(
|
|
231
|
+
MYSQL_MODULE_SPECIFIER
|
|
232
|
+
) as unknown as MysqlModule;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"BCP Database: MySQL requires the optional dependency mysql2. Install it with `npm install mysql2` or create the app with the MySQL preset.",
|
|
236
|
+
{
|
|
237
|
+
cause:
|
|
238
|
+
error,
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return mysqlModule.createPool({
|
|
244
|
+
host:
|
|
245
|
+
options.host,
|
|
246
|
+
port:
|
|
247
|
+
options.port,
|
|
248
|
+
user:
|
|
249
|
+
options.user,
|
|
250
|
+
password:
|
|
251
|
+
options.password,
|
|
252
|
+
database:
|
|
253
|
+
options.database,
|
|
254
|
+
waitForConnections:
|
|
255
|
+
options.waitForConnections,
|
|
256
|
+
connectionLimit:
|
|
257
|
+
options.connectionLimit,
|
|
258
|
+
queueLimit:
|
|
259
|
+
options.queueLimit,
|
|
260
|
+
charset:
|
|
261
|
+
options.charset,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function assertSql(
|
|
266
|
+
sql: string
|
|
267
|
+
): void {
|
|
268
|
+
if (
|
|
269
|
+
typeof sql !== "string" ||
|
|
270
|
+
sql.trim() === ""
|
|
271
|
+
) {
|
|
272
|
+
throw new TypeError(
|
|
273
|
+
"BCP Database: SQL must be a non-empty string."
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function assertTransactionCallback(
|
|
279
|
+
callback: unknown
|
|
280
|
+
): asserts callback is (
|
|
281
|
+
database: TransactionDatabase
|
|
282
|
+
) => Promise<unknown> {
|
|
283
|
+
if (
|
|
284
|
+
typeof callback !==
|
|
285
|
+
"function"
|
|
286
|
+
) {
|
|
287
|
+
throw new TypeError(
|
|
288
|
+
"BCP Database: transaction callback must be a function."
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|