@chidchanun/bcp 0.2.2 → 0.2.4

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.
@@ -1,14 +1,33 @@
1
1
  # Database Migrations
2
2
 
3
- BCP Framework `0.1.15` adds MySQL migration commands to the `bcp` CLI.
3
+ BCP `0.2.3` extends framework-managed SQL migrations across MySQL, PostgreSQL and SQLite.
4
4
 
5
- ## Requirements
5
+ ## Supported providers
6
6
 
7
- Database migrations currently use the MySQL adapter from `bcp/database`.
7
+ Migration bookkeeping uses the same provider selection as `bcp/database`:
8
8
 
9
- Configure the same environment variables used by your application:
9
+ ```text
10
+ mysql
11
+ postgresql
12
+ sqlite
13
+ ```
14
+
15
+ Install the optional driver used by the application:
16
+
17
+ ```bash
18
+ npm install mysql2
19
+ npm install pg
20
+ npm install better-sqlite3
21
+ ```
22
+
23
+ Only one driver is required when the project uses one database provider.
24
+
25
+ ## Provider configuration
26
+
27
+ MySQL:
10
28
 
11
29
  ```env
30
+ DB_DRIVER=mysql
12
31
  DB_HOST=localhost
13
32
  DB_PORT=3306
14
33
  DB_USER=root
@@ -16,7 +35,20 @@ DB_PASSWORD=
16
35
  DB_NAME=bcp_app
17
36
  ```
18
37
 
19
- A project using migrations must have `mysql2` installed. Applications created with the MySQL preset already include it.
38
+ PostgreSQL:
39
+
40
+ ```env
41
+ DATABASE_URL=postgresql://postgres:password@localhost:5432/bcp_app
42
+ ```
43
+
44
+ SQLite:
45
+
46
+ ```env
47
+ DB_DRIVER=sqlite
48
+ DATABASE_URL=./data/bcp.sqlite
49
+ ```
50
+
51
+ Database commands load the development environment before connecting, so migration commands and `bcp dev` use the same provider selection.
20
52
 
21
53
  ## Create a migration
22
54
 
@@ -24,14 +56,14 @@ A project using migrations must have `mysql2` installed. Applications created wi
24
56
  bcp db create create_users
25
57
  ```
26
58
 
27
- BCP creates an ordered TypeScript file in `migrations/`:
59
+ BCP creates an ordered TypeScript file:
28
60
 
29
61
  ```text
30
62
  migrations/
31
- 20260827040506_create_users.ts
63
+ 20260829090000_create_users.ts
32
64
  ```
33
65
 
34
- A generated migration exports `up()` and `down()`:
66
+ Generated migrations export `up()` and `down()`:
35
67
 
36
68
  ```ts
37
69
  import type {
@@ -43,9 +75,9 @@ export async function up(
43
75
  ): Promise<void> {
44
76
  await db.execute(`
45
77
  CREATE TABLE users (
46
- id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
78
+ id INTEGER PRIMARY KEY,
47
79
  email VARCHAR(255) NOT NULL UNIQUE
48
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
80
+ )
49
81
  `);
50
82
  }
51
83
 
@@ -58,7 +90,24 @@ export async function down(
58
90
  }
59
91
  ```
60
92
 
61
- Migration filenames use a UTC timestamp prefix so migrations have a stable execution order.
93
+ Migration filenames use a UTC timestamp prefix for stable execution order.
94
+
95
+ ## SQL dialect responsibility
96
+
97
+ BCP keeps its own `_bcp_migrations` bookkeeping provider-aware, but it does not translate application migration SQL.
98
+
99
+ This means migrations should either:
100
+
101
+ - use SQL supported by every database target used by the application, or
102
+ - intentionally target one provider and use that provider's SQL syntax.
103
+
104
+ Parameter placeholders also follow the active provider:
105
+
106
+ ```text
107
+ MySQL ?
108
+ SQLite ?
109
+ PostgreSQL $1, $2, ...
110
+ ```
62
111
 
63
112
  ## Run pending migrations
64
113
 
@@ -66,9 +115,15 @@ Migration filenames use a UTC timestamp prefix so migrations have a stable execu
66
115
  bcp db migrate
67
116
  ```
68
117
 
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.
118
+ BCP creates `_bcp_migrations` with provider-specific DDL, detects pending migration files, and executes them in filename order.
119
+
120
+ All migrations applied by one command share the same batch number. Each migration runs inside its own transaction and its bookkeeping record is written in that same transaction.
70
121
 
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()`.
122
+ Provider-specific bookkeeping includes:
123
+
124
+ - MySQL `AUTO_INCREMENT`,
125
+ - PostgreSQL `BIGSERIAL`,
126
+ - SQLite `INTEGER PRIMARY KEY AUTOINCREMENT`.
72
127
 
73
128
  ## Check status
74
129
 
@@ -84,22 +139,27 @@ The command reports applied and pending migration files together with the batch
84
139
  bcp db rollback
85
140
  ```
86
141
 
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.
142
+ Rollback reverses only the latest migration batch. Migrations run from newest to oldest and each `down()` executes in a transaction together with deletion of its migration record.
88
143
 
89
144
  BCP refuses to roll back an applied migration when its migration file is missing.
90
145
 
91
146
  ## Project root
92
147
 
93
- All database commands support the normal BCP project-root option:
148
+ All database commands support the normal project-root option:
94
149
 
95
150
  ```bash
96
151
  bcp db status --root ./apps/admin
97
152
  ```
98
153
 
99
- ## Environment files
154
+ ## Database Platform v2 behavior
100
155
 
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.
156
+ `0.2.3` keeps migration commands consistent across the built-in SQL providers:
102
157
 
103
- ## Current scope
158
+ ```text
159
+ bcp db create
160
+ bcp db migrate
161
+ bcp db status
162
+ bcp db rollback
163
+ ```
104
164
 
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.
165
+ The command names and migration file contract stay the same regardless of whether the application uses MySQL, PostgreSQL or SQLite.
package/docs/database.md CHANGED
@@ -1,18 +1,24 @@
1
1
  # Database
2
2
 
3
- BCP `0.1.14` adds the first framework-native database API through the server-only `bcp/database` entrypoint.
3
+ BCP `0.2.3` completes Database Platform v2 behind the server-only `bcp/database` entrypoint.
4
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.
5
+ The public database facade is provider-neutral and currently includes built-in adapters for:
6
6
 
7
- ## Create an app with MySQL
7
+ - MySQL,
8
+ - PostgreSQL,
9
+ - SQLite.
10
+
11
+ Applications can keep the same `db.query()`, `db.execute()`, `db.transaction()` and lifecycle API while selecting the provider through environment or explicit options.
12
+
13
+ ## MySQL
14
+
15
+ Install the optional driver:
8
16
 
9
17
  ```bash
10
- npx create-bcp-app my-app
18
+ npm install mysql2
11
19
  ```
12
20
 
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
21
+ Environment:
16
22
 
17
23
  ```env
18
24
  DB_DRIVER=mysql
@@ -22,20 +28,105 @@ DB_USER=root
22
28
  DB_PASSWORD=
23
29
  DB_NAME=bcp_app
24
30
  DB_CONNECTION_LIMIT=10
25
- DB_WAIT_FOR_CONNECTIONS=1
26
- DB_QUEUE_LIMIT=0
27
- DB_CHARSET=utf8mb4
28
31
  ```
29
32
 
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.
33
+ MySQL remains the default when no provider can be inferred.
31
34
 
32
- ## Query
35
+ Parameterized query:
33
36
 
34
37
  ```ts
35
38
  import {
36
39
  db,
37
40
  } from "bcp/database";
38
41
 
42
+ const users =
43
+ await db.query(
44
+ "SELECT id, email FROM users WHERE active = ?",
45
+ [
46
+ 1,
47
+ ]
48
+ );
49
+ ```
50
+
51
+ ## PostgreSQL
52
+
53
+ Install the optional driver:
54
+
55
+ ```bash
56
+ npm install pg
57
+ ```
58
+
59
+ BCP can infer PostgreSQL from a connection URL:
60
+
61
+ ```env
62
+ DATABASE_URL=postgresql://postgres:password@localhost:5432/bcp_app
63
+ ```
64
+
65
+ or use explicit fields:
66
+
67
+ ```env
68
+ DB_DRIVER=postgresql
69
+ DB_HOST=localhost
70
+ DB_PORT=5432
71
+ DB_USER=postgres
72
+ DB_PASSWORD=password
73
+ DB_NAME=bcp_app
74
+ ```
75
+
76
+ PostgreSQL parameters use `$1`, `$2`, and later placeholders:
77
+
78
+ ```ts
79
+ const users =
80
+ await db.query(
81
+ "SELECT id, email FROM users WHERE active = $1",
82
+ [
83
+ true,
84
+ ]
85
+ );
86
+ ```
87
+
88
+ `postgres` and `pg` are accepted as environment aliases for the PostgreSQL driver and are normalized to `postgresql`.
89
+
90
+ ## SQLite
91
+
92
+ Install the optional driver:
93
+
94
+ ```bash
95
+ npm install better-sqlite3
96
+ ```
97
+
98
+ Recommended environment:
99
+
100
+ ```env
101
+ DB_DRIVER=sqlite
102
+ DATABASE_URL=./data/bcp.sqlite
103
+ ```
104
+
105
+ BCP also recognizes `:memory:`, `sqlite:` / `file:` URLs and common `.sqlite`, `.sqlite3`, and `.db` file names.
106
+
107
+ Example:
108
+
109
+ ```ts
110
+ import {
111
+ createDatabase,
112
+ } from "bcp/database";
113
+
114
+ const database =
115
+ createDatabase({
116
+ driver:
117
+ "sqlite",
118
+ database:
119
+ "./data/app.sqlite",
120
+ });
121
+ ```
122
+
123
+ SQLite operations are serialized by the built-in adapter so async transaction callbacks cannot interleave unrelated queries on the same native connection.
124
+
125
+ ## Query and execute
126
+
127
+ `query<T>()` is intended for row-returning statements:
128
+
129
+ ```ts
39
130
  interface UserRow {
40
131
  id: number;
41
132
  email: string;
@@ -43,28 +134,27 @@ interface UserRow {
43
134
 
44
135
  const users =
45
136
  await db.query<UserRow[]>(
46
- "SELECT id, email FROM users WHERE active = ?",
47
- [
48
- 1,
49
- ]
137
+ "SELECT id, email FROM users"
50
138
  );
51
139
  ```
52
140
 
53
- ## Execute
141
+ `execute<T>()` is intended for mutations and DDL:
54
142
 
55
143
  ```ts
56
144
  const result =
57
145
  await db.execute(
58
- "INSERT INTO users (email) VALUES (?)",
146
+ "DELETE FROM sessions WHERE expired_at < ?",
59
147
  [
60
- "user@example.com",
148
+ new Date(),
61
149
  ]
62
150
  );
63
151
  ```
64
152
 
65
- Use placeholders and parameter arrays instead of concatenating untrusted values into SQL strings.
153
+ Use the placeholder syntax of the active database provider. BCP does not rewrite application SQL between dialects.
66
154
 
67
- ## Transaction
155
+ ## Transactions
156
+
157
+ The transaction callback receives a provider-scoped `TransactionDatabase`:
68
158
 
69
159
  ```ts
70
160
  await db.transaction(
@@ -88,7 +178,33 @@ await db.transaction(
88
178
  );
89
179
  ```
90
180
 
91
- BCP commits the transaction when the callback resolves and rolls it back when the callback throws.
181
+ The example above uses MySQL/SQLite placeholders. PostgreSQL migrations and application statements should use `$1`, `$2`, and so on.
182
+
183
+ BCP commits when the callback resolves and rolls back when it throws.
184
+
185
+ ## Connection lifecycle
186
+
187
+ Database instances remain lazy by default. Importing `bcp/database` does not connect to a provider.
188
+
189
+ BCP `0.2.3` adds explicit lifecycle methods:
190
+
191
+ ```ts
192
+ await db.connect();
193
+
194
+ // application work
195
+
196
+ await db.disconnect();
197
+ ```
198
+
199
+ `close()` remains available as the backward-compatible shutdown method and is equivalent to `disconnect()`:
200
+
201
+ ```ts
202
+ await db.close();
203
+ ```
204
+
205
+ After disconnecting, the next operation or `connect()` call creates a fresh adapter connection/pool.
206
+
207
+ If adapter initialization fails, BCP resets the pending lifecycle state so a later attempt can retry cleanly. Calling `close()` after a failed initialization is safe.
92
208
 
93
209
  ## Custom database instance
94
210
 
@@ -99,6 +215,8 @@ import {
99
215
 
100
216
  export const reportingDb =
101
217
  createDatabase({
218
+ driver:
219
+ "postgresql",
102
220
  host:
103
221
  "reporting-db.internal",
104
222
  database:
@@ -110,18 +228,69 @@ export const reportingDb =
110
228
 
111
229
  Explicit options override environment values for that database instance.
112
230
 
113
- ## Lifecycle
231
+ ## Database adapter contract
114
232
 
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.
233
+ Provider implementations use the shared adapter contract:
234
+
235
+ ```ts
236
+ import type {
237
+ DatabaseAdapter,
238
+ TransactionDatabase,
239
+ } from "bcp/database";
116
240
 
117
- For custom shutdown handling, close the pool with:
241
+ const adapter: DatabaseAdapter = {
242
+ driver: "custom",
243
+
244
+ async connect() {},
245
+
246
+ async query<T>(sql, parameters) {
247
+ throw new Error("Not implemented");
248
+ },
249
+
250
+ async execute<T>(sql, parameters) {
251
+ throw new Error("Not implemented");
252
+ },
253
+
254
+ async transaction<T>(
255
+ callback: (
256
+ database: TransactionDatabase
257
+ ) => Promise<T>
258
+ ) {
259
+ throw new Error("Not implemented");
260
+ },
261
+
262
+ async disconnect() {},
263
+ };
264
+ ```
265
+
266
+ Inject an adapter directly or through a lazy factory:
118
267
 
119
268
  ```ts
120
- await db.close();
269
+ export const customDb =
270
+ createDatabase({
271
+ adapter: () => adapter,
272
+ });
273
+ ```
274
+
275
+ The contract lets future providers integrate without changing application-facing database calls.
276
+
277
+ ## Migrations
278
+
279
+ Framework migration bookkeeping supports MySQL, PostgreSQL and SQLite in `0.2.3`.
280
+
281
+ ```bash
282
+ bcp db create create_users
283
+ bcp db migrate
284
+ bcp db status
285
+ bcp db rollback
121
286
  ```
122
287
 
288
+ BCP selects the internal migration-table dialect from the same active database environment. Migration files themselves remain normal provider SQL and are not automatically translated between SQL dialects.
289
+
290
+ Read more: [Database Migrations](database-migrations.md)
291
+
123
292
  ## Server-only boundary
124
293
 
125
- `bcp/database` is a server-only package export. Importing it into a browser bundle is blocked by the framework's browser export boundary.
294
+ `bcp/database` is a server-only package export. Importing it into a browser bundle is blocked by the framework browser export boundary.
126
295
 
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.
296
+ Provider drivers are optional and loaded lazily. Projects only need to install the driver they actually use.
@@ -6,7 +6,7 @@
6
6
  npm run build
7
7
  ```
8
8
 
9
- BCP writes the production artifact to `.bcp-framework/build`.
9
+ BCP writes the raw production artifact to `.bcp-framework/build`.
10
10
 
11
11
  ```text
12
12
  .bcp-framework/build/
@@ -20,18 +20,79 @@ BCP writes the production artifact to `.bcp-framework/build`.
20
20
  └─ config.json
21
21
  ```
22
22
 
23
+ Use the build directory when the deployment system already manages the application's production dependencies and artifact layout.
24
+
25
+ ## Application package — 0.2.4
26
+
27
+ For a deployment-oriented directory, run:
28
+
29
+ ```bash
30
+ bcp package
31
+ ```
32
+
33
+ or from the framework repository/example script surface:
34
+
35
+ ```bash
36
+ npm run package
37
+ ```
38
+
39
+ `bcp package` runs a fresh production build and creates:
40
+
41
+ ```text
42
+ .bcp-framework/package/
43
+ ├─ client/
44
+ ├─ server/
45
+ │ └─ server.mjs
46
+ ├─ public/ # when present
47
+ ├─ manifest.json
48
+ ├─ package.json
49
+ ├─ package-lock.json # when a safe production lock can be derived
50
+ ├─ bcp.package.json
51
+ ├─ bcp.deployment.json
52
+ ├─ bcp.env.json
53
+ ├─ Dockerfile
54
+ ├─ .dockerignore
55
+ └─ README.md
56
+ ```
57
+
58
+ The package manifest excludes development dependencies. With a safely pruned npm v3 lockfile, install runtime dependencies with:
59
+
60
+ ```bash
61
+ npm ci --omit=dev
62
+ ```
63
+
64
+ If no package lock is included, follow the install command recorded in `bcp.package.json`/`bcp.deployment.json`.
65
+
66
+ BCP does not copy `.env` files into the package. Provide environment values and secrets at deployment/runtime.
67
+
68
+ See [Application Packaging](application-packaging.md) for the full packaging contract.
69
+
23
70
  ## Start
24
71
 
72
+ For the raw build:
73
+
25
74
  ```bash
26
75
  npm start
27
76
  ```
28
77
 
29
- Or run the standalone bundle directly:
78
+ or:
30
79
 
31
80
  ```bash
32
81
  node .bcp-framework/build/server/server.mjs
33
82
  ```
34
83
 
84
+ Inside `.bcp-framework/package`, after production dependencies are installed:
85
+
86
+ ```bash
87
+ npm start
88
+ ```
89
+
90
+ which runs:
91
+
92
+ ```bash
93
+ node server/server.mjs
94
+ ```
95
+
35
96
  ## Runtime server overrides
36
97
 
37
98
  The build retains the configured server defaults. Deployment can override the public bind address with supported runtime variables:
@@ -41,22 +102,34 @@ BCP_PORT
41
102
  BCP_HOSTNAME
42
103
  ```
43
104
 
44
- The CLI also supports:
105
+ The CLI also supports the raw build runtime flow:
45
106
 
46
107
  ```bash
47
108
  bcp start --port 8080 --hostname 0.0.0.0
48
109
  ```
49
110
 
111
+ For a packaged application, provide `BCP_PORT` and `BCP_HOSTNAME` through the runtime environment.
112
+
50
113
  ## Containers
51
114
 
52
- A minimal deployment image needs the standalone build plus the runtime dependencies required by the generated server bundle, including React/React DOM when they remain external to the bundle.
115
+ `bcp package` generates a starter Dockerfile in `.bcp-framework/package`.
116
+
117
+ ```bash
118
+ cd .bcp-framework/package
119
+ docker build -t my-bcp-app .
120
+ docker run --rm -p 3000:3000 my-bcp-app
121
+ ```
122
+
123
+ Pass credentials and environment-specific settings through the container/orchestrator environment rather than baking them into the image.
53
124
 
54
- A typical application image can install production dependencies, copy `.bcp-framework/build`, expose the runtime port and execute `npm start` or `node .bcp-framework/build/server/server.mjs`.
125
+ If you deploy the raw `.bcp-framework/build` directory instead, the image must separately include the runtime dependencies required by the generated server bundle, including React/React DOM when they remain external to the bundle.
55
126
 
56
127
  ## Reverse proxies
57
128
 
58
129
  BCP can run behind a reverse proxy or tunnel. Forward the original host correctly when application middleware or absolute URL construction depends on host information.
59
130
 
131
+ Only enable trusted-proxy handling when the application process is actually isolated behind a trusted proxy/load balancer.
132
+
60
133
  ## Multiple instances
61
134
 
62
135
  The current response/data cache is process-local. If multiple containers or Node.js processes serve the application, each maintains independent cache entries and invalidation state.
@@ -68,7 +141,21 @@ Before deployment:
68
141
  ```bash
69
142
  npm run typecheck
70
143
  npm test
71
- npm run build
144
+ bcp package
145
+ ```
146
+
147
+ Then validate the deployment package:
148
+
149
+ ```bash
150
+ cd .bcp-framework/package
151
+ npm ci --omit=dev
152
+ npm start
72
153
  ```
73
154
 
74
- For framework releases themselves, use `npm run release:check` in the framework repository.
155
+ If `bcp.package.json` reports `lockfile: false`, use its recorded install command instead of `npm ci`.
156
+
157
+ For framework releases themselves, use:
158
+
159
+ ```bash
160
+ npm run rc:check
161
+ ```
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "framework": "bcp",
4
- "versionTarget": "0.2.2",
4
+ "versionTarget": "0.2.4",
5
5
  "releaseState": "unreleased",
6
6
  "sections": [
7
7
  {
8
8
  "id": "getting-started",
9
9
  "title": "Getting Started",
10
- "description": "Create, configure, deploy and update BCP applications.",
10
+ "description": "Create, configure, package, deploy and update BCP applications.",
11
11
  "pages": [
12
12
  { "route": "/docs/getting-started", "source": "getting-started.md", "title": "Getting Started" },
13
13
  { "route": "/docs/configuration", "source": "configuration.md", "title": "Configuration" },
14
14
  { "route": "/docs/environment-validation", "source": "environment-validation.md", "title": "Environment Validation" },
15
15
  { "route": "/docs/application-modules", "source": "application-modules.md", "title": "Application Modules" },
16
16
  { "route": "/docs/project-metadata", "source": "project-metadata.md", "title": "Project Metadata" },
17
+ { "route": "/docs/application-packaging", "source": "application-packaging.md", "title": "Application Packaging" },
17
18
  { "route": "/docs/deployment", "source": "deployment.md", "title": "Deployment" },
18
19
  { "route": "/docs/updating", "source": "updating.md", "title": "Updating BCP" }
19
20
  ]
@@ -45,7 +46,7 @@
45
46
  {
46
47
  "id": "database",
47
48
  "title": "Database",
48
- "description": "Database primitives, transactions and migration workflows.",
49
+ "description": "Provider-neutral MySQL, PostgreSQL and SQLite primitives, lifecycle and migrations.",
49
50
  "pages": [
50
51
  { "route": "/docs/database", "source": "database.md", "title": "Database" },
51
52
  { "route": "/docs/database-migrations", "source": "database-migrations.md", "title": "Database Migrations" }
@@ -104,7 +105,9 @@
104
105
  }
105
106
  ],
106
107
  "releases": [
107
- { "route": "/releases/0.2.2", "source": "releases/0.2.2.md", "version": "0.2.2", "state": "unreleased" },
108
+ { "route": "/releases/0.2.4", "source": "releases/0.2.4.md", "version": "0.2.4", "state": "unreleased" },
109
+ { "route": "/releases/0.2.3", "source": "releases/0.2.3.md", "version": "0.2.3" },
110
+ { "route": "/releases/0.2.2", "source": "releases/0.2.2.md", "version": "0.2.2" },
108
111
  { "route": "/releases/0.2.1", "source": "releases/0.2.1.md", "version": "0.2.1" },
109
112
  { "route": "/releases/0.2.0", "source": "releases/0.2.0.md", "version": "0.2.0" },
110
113
  { "route": "/releases/0.1.29", "source": "releases/0.1.29.md", "version": "0.1.29" },