@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.
@@ -0,0 +1,105 @@
1
+ # Database Migrations
2
+
3
+ BCP Framework `0.1.15` adds MySQL migration commands to the `bcp` CLI.
4
+
5
+ ## Requirements
6
+
7
+ Database migrations currently use the MySQL adapter from `bcp/database`.
8
+
9
+ Configure the same environment variables used by your application:
10
+
11
+ ```env
12
+ DB_HOST=localhost
13
+ DB_PORT=3306
14
+ DB_USER=root
15
+ DB_PASSWORD=
16
+ DB_NAME=bcp_app
17
+ ```
18
+
19
+ A project using migrations must have `mysql2` installed. Applications created with the MySQL preset already include it.
20
+
21
+ ## Create a migration
22
+
23
+ ```bash
24
+ bcp db create create_users
25
+ ```
26
+
27
+ BCP creates an ordered TypeScript file in `migrations/`:
28
+
29
+ ```text
30
+ migrations/
31
+ 20260827040506_create_users.ts
32
+ ```
33
+
34
+ A generated migration exports `up()` and `down()`:
35
+
36
+ ```ts
37
+ import type {
38
+ TransactionDatabase,
39
+ } from "bcp/database";
40
+
41
+ export async function up(
42
+ db: TransactionDatabase
43
+ ): Promise<void> {
44
+ await db.execute(`
45
+ CREATE TABLE users (
46
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
47
+ email VARCHAR(255) NOT NULL UNIQUE
48
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
49
+ `);
50
+ }
51
+
52
+ export async function down(
53
+ db: TransactionDatabase
54
+ ): Promise<void> {
55
+ await db.execute(
56
+ "DROP TABLE users"
57
+ );
58
+ }
59
+ ```
60
+
61
+ Migration filenames use a UTC timestamp prefix so migrations have a stable execution order.
62
+
63
+ ## Run pending migrations
64
+
65
+ ```bash
66
+ bcp db migrate
67
+ ```
68
+
69
+ BCP creates the internal `_bcp_migrations` table when needed, detects files that have not been applied, and runs all pending migrations in filename order.
70
+
71
+ All migrations applied by one `bcp db migrate` command share the same batch number. Each individual migration runs inside its own database transaction. The migration record is inserted in the same transaction as `up()`.
72
+
73
+ ## Check status
74
+
75
+ ```bash
76
+ bcp db status
77
+ ```
78
+
79
+ The command reports applied and pending migration files together with the batch number for applied migrations.
80
+
81
+ ## Roll back
82
+
83
+ ```bash
84
+ bcp db rollback
85
+ ```
86
+
87
+ Rollback reverses only the latest migration batch. Migrations in that batch run from newest to oldest, and each `down()` runs in a transaction together with removal of its migration record.
88
+
89
+ BCP refuses to roll back an applied migration when its migration file is missing.
90
+
91
+ ## Project root
92
+
93
+ All database commands support the normal BCP project-root option:
94
+
95
+ ```bash
96
+ bcp db status --root ./apps/admin
97
+ ```
98
+
99
+ ## Environment files
100
+
101
+ Database commands load the BCP development environment files before connecting, so the same local database settings used by `bcp dev` can be reused by migration commands.
102
+
103
+ ## Current scope
104
+
105
+ `0.1.15` migration execution supports MySQL. PostgreSQL, SQLite and MongoDB presets remain available to `create-bcp-app`, but framework-managed migrations for those adapters are planned for later releases.
@@ -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.13",
3
+ "version": "0.1.15",
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",
@@ -7,9 +7,17 @@ export type CliCommand =
7
7
  | "start"
8
8
  | "routes"
9
9
  | "update"
10
+ | "db"
10
11
  | "help"
11
12
  | "version";
12
13
 
14
+ export type DatabaseCliAction =
15
+ | "migrate"
16
+ | "status"
17
+ | "rollback"
18
+ | "create"
19
+ | "help";
20
+
13
21
  export interface CliOptions {
14
22
  command: CliCommand;
15
23
 
@@ -24,6 +32,10 @@ export interface CliOptions {
24
32
  updateCheck?: boolean;
25
33
 
26
34
  updateDryRun?: boolean;
35
+
36
+ dbAction?: DatabaseCliAction;
37
+
38
+ dbMigrationName?: string;
27
39
  }
28
40
 
29
41
  export function parseCliArgs(
@@ -49,6 +61,12 @@ export function parseCliArgs(
49
61
  let updateDryRun =
50
62
  false;
51
63
 
64
+ let dbAction:
65
+ DatabaseCliAction | undefined;
66
+
67
+ let dbMigrationName:
68
+ string | undefined;
69
+
52
70
  let commandSet = false;
53
71
 
54
72
  for (
@@ -62,7 +80,14 @@ export function parseCliArgs(
62
80
  argument === "-h" ||
63
81
  argument === "--help"
64
82
  ) {
65
- command = "help";
83
+ if (
84
+ commandSet &&
85
+ command === "db"
86
+ ) {
87
+ dbAction = "help";
88
+ } else {
89
+ command = "help";
90
+ }
66
91
  continue;
67
92
  }
68
93
 
@@ -221,6 +246,44 @@ export function parseCliArgs(
221
246
  continue;
222
247
  }
223
248
 
249
+ if (
250
+ commandSet &&
251
+ command === "db"
252
+ ) {
253
+ if (
254
+ dbAction === undefined
255
+ ) {
256
+ if (
257
+ argument === "migrate" ||
258
+ argument === "status" ||
259
+ argument === "rollback" ||
260
+ argument === "create" ||
261
+ argument === "help"
262
+ ) {
263
+ dbAction =
264
+ argument;
265
+ continue;
266
+ }
267
+
268
+ throw new Error(
269
+ `Unknown database command: ${argument}`
270
+ );
271
+ }
272
+
273
+ if (
274
+ dbAction === "create" &&
275
+ dbMigrationName === undefined
276
+ ) {
277
+ dbMigrationName =
278
+ argument;
279
+ continue;
280
+ }
281
+
282
+ throw new Error(
283
+ `Unexpected argument: ${argument}`
284
+ );
285
+ }
286
+
224
287
  if (commandSet) {
225
288
  throw new Error(
226
289
  `Unexpected argument: ${argument}`
@@ -233,6 +296,7 @@ export function parseCliArgs(
233
296
  argument === "start" ||
234
297
  argument === "routes" ||
235
298
  argument === "update" ||
299
+ argument === "db" ||
236
300
  argument === "help" ||
237
301
  argument === "version"
238
302
  ) {
@@ -266,6 +330,12 @@ export function parseCliArgs(
266
330
  updateTarget,
267
331
  updateCheck,
268
332
  updateDryRun,
333
+ ...(command === "db"
334
+ ? {
335
+ dbAction,
336
+ dbMigrationName,
337
+ }
338
+ : {}),
269
339
  };
270
340
  }
271
341