@jskit-ai/database-runtime-mysql 0.1.157 → 0.1.159

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/package.json CHANGED
@@ -1,73 +1,35 @@
1
1
  {
2
2
  "name": "@jskit-ai/database-runtime-mysql",
3
- "version": "0.1.157",
3
+ "version": "0.1.159",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "exports": {
9
9
  "./client": "./src/client/index.js",
10
- "./server/providers/DatabaseRuntimeMysqlServiceProvider": "./src/server/providers/DatabaseRuntimeMysqlServiceProvider.js",
10
+ "./server/providers/MysqlDatabaseDriverProvider": "./src/server/providers/MysqlDatabaseDriverProvider.js",
11
11
  "./shared": "./src/shared/index.js",
12
12
  "./shared/dialect": "./src/shared/dialect.js"
13
13
  },
14
14
  "dependencies": {
15
- "@jskit-ai/database-runtime": "0.1.159",
16
- "@jskit-ai/kernel": "0.1.159"
15
+ "@jskit-ai/database-runtime": "0.1.161",
16
+ "@jskit-ai/kernel": "0.1.161",
17
+ "mysql2": "^3.11.2"
17
18
  },
18
19
  "jskit": {
19
20
  "kind": "runtime",
20
- "options": {
21
- "db-host": {
22
- "required": false,
23
- "values": [],
24
- "defaultValue": "localhost",
25
- "promptLabel": "Database host",
26
- "promptHint": "MySQL host (for example 127.0.0.1)"
27
- },
28
- "db-port": {
29
- "required": false,
30
- "values": [],
31
- "defaultValue": "3306",
32
- "promptLabel": "Database port",
33
- "promptHint": "MySQL port (usually 3306)"
34
- },
35
- "db-name": {
36
- "required": true,
37
- "values": [],
38
- "promptLabel": "Database name",
39
- "promptHint": "Schema/database name to connect to"
40
- },
41
- "db-user": {
42
- "required": true,
43
- "values": [],
44
- "promptLabel": "Database user",
45
- "promptHint": "Database username"
46
- },
47
- "db-password": {
48
- "required": true,
49
- "values": [],
50
- "inputType": "password",
51
- "promptLabel": "Database password",
52
- "promptHint": "Database password"
53
- }
54
- },
55
21
  "capabilities": {
56
22
  "provides": [
57
- "runtime.database.driver",
58
- "runtime.database.driver.mysql"
23
+ "runtime.database.driver"
59
24
  ],
60
- "requires": [
61
- "runtime.database"
62
- ]
25
+ "requires": []
63
26
  },
64
27
  "runtime": {
65
28
  "server": {
66
- "providerEntrypoint": "src/server/providers/DatabaseRuntimeMysqlServiceProvider.js",
67
29
  "providers": [
68
30
  {
69
- "entrypoint": "src/server/providers/DatabaseRuntimeMysqlServiceProvider.js",
70
- "export": "DatabaseRuntimeMysqlServiceProvider"
31
+ "entrypoint": "src/server/providers/MysqlDatabaseDriverProvider.js",
32
+ "export": "MysqlDatabaseDriverProvider"
71
33
  }
72
34
  ]
73
35
  },
@@ -80,7 +42,7 @@
80
42
  "surfaces": [
81
43
  {
82
44
  "subpath": "./server",
83
- "summary": "Exports MySQL database runtime provider."
45
+ "summary": "Provides the exclusive runtime.database.driver capability for MySQL."
84
46
  },
85
47
  {
86
48
  "subpath": "./shared",
@@ -90,116 +52,8 @@
90
52
  "subpath": "./client",
91
53
  "summary": "Exports no runtime API today (reserved client entrypoint)."
92
54
  }
93
- ],
94
- "containerTokens": {
95
- "server": [
96
- "runtime.database.driver.mysql"
97
- ],
98
- "client": []
99
- }
55
+ ]
100
56
  }
101
- },
102
- "ci": {
103
- "environment": {
104
- "DB_CLIENT": "mysql2",
105
- "DB_HOST": "127.0.0.1",
106
- "DB_PORT": "33060",
107
- "DB_NAME": "jskit_ci",
108
- "DB_USER": "jskit_ci",
109
- "DB_PASSWORD": "jskit_ci_only_password"
110
- },
111
- "services": [
112
- {
113
- "id": "mariadb",
114
- "image": "mariadb:11.4",
115
- "environment": {
116
- "MARIADB_DATABASE": "jskit_ci",
117
- "MARIADB_USER": "jskit_ci",
118
- "MARIADB_PASSWORD": "jskit_ci_only_password",
119
- "MARIADB_ROOT_PASSWORD": "jskit_ci_only_root_password"
120
- },
121
- "ports": [
122
- "33060:3306"
123
- ],
124
- "healthCheck": {
125
- "command": "healthcheck.sh --connect --innodb_initialized",
126
- "interval": "10s",
127
- "timeout": "5s",
128
- "retries": 10
129
- }
130
- }
131
- ],
132
- "steps": []
133
- },
134
- "mutations": {
135
- "dependencies": {
136
- "runtime": {
137
- "@jskit-ai/database-runtime": "0.1.159",
138
- "mysql2": "^3.11.2"
139
- },
140
- "dev": {}
141
- },
142
- "packageJson": {
143
- "scripts": {}
144
- },
145
- "procfile": {},
146
- "files": [],
147
- "text": [
148
- {
149
- "file": ".env",
150
- "op": "upsert-env",
151
- "key": "DB_CLIENT",
152
- "value": "mysql2",
153
- "reason": "Configure database client driver for runtime wiring.",
154
- "category": "runtime-config",
155
- "id": "database-client-mysql"
156
- },
157
- {
158
- "file": ".env",
159
- "op": "upsert-env",
160
- "key": "DB_HOST",
161
- "value": "${option:db-host}",
162
- "reason": "Configure database host.",
163
- "category": "runtime-config",
164
- "id": "database-host"
165
- },
166
- {
167
- "file": ".env",
168
- "op": "upsert-env",
169
- "key": "DB_PORT",
170
- "value": "${option:db-port}",
171
- "reason": "Configure database port.",
172
- "category": "runtime-config",
173
- "id": "database-port"
174
- },
175
- {
176
- "file": ".env",
177
- "op": "upsert-env",
178
- "key": "DB_NAME",
179
- "value": "${option:db-name}",
180
- "reason": "Configure database name.",
181
- "category": "runtime-config",
182
- "id": "database-name"
183
- },
184
- {
185
- "file": ".env",
186
- "op": "upsert-env",
187
- "key": "DB_USER",
188
- "value": "${option:db-user}",
189
- "reason": "Configure database user.",
190
- "category": "runtime-config",
191
- "id": "database-user"
192
- },
193
- {
194
- "file": ".env",
195
- "op": "upsert-env",
196
- "key": "DB_PASSWORD",
197
- "value": "${option:db-password}",
198
- "reason": "Configure database password.",
199
- "category": "runtime-config",
200
- "id": "database-password"
201
- }
202
- ]
203
57
  }
204
58
  }
205
59
  }
@@ -0,0 +1,80 @@
1
+ ---
2
+ id: database/mysql-application
3
+ title: MySQL application database
4
+ summary: Configure a JSKIT application for MySQL with a fixed driver, ordinary environment values, and rebuildable Knex migrations.
5
+ keywords: database, knex, mariadb, migrations, mysql, mysql2
6
+ requires: @jskit-ai/database-runtime-mysql
7
+ ---
8
+
9
+ # MySQL application database
10
+
11
+ ## Use when
12
+
13
+ Use this pattern when an application deliberately chooses MySQL or MariaDB.
14
+ Install `@jskit-ai/database-runtime-mysql`; its normal npm dependencies provide
15
+ the database runtime, Knex, and `mysql2` without a package-setup wizard.
16
+
17
+ ## Do not use when
18
+
19
+ Do not use this pattern for PostgreSQL, a database-neutral library, or a service
20
+ that does not own its database connection. Do not install both JSKIT drivers
21
+ unless the application genuinely selects between them at runtime.
22
+
23
+ ## Product decisions
24
+
25
+ Decide the database name, credentials, host, deployment secret source, backup
26
+ policy, and application migrations. Those are application and environment
27
+ decisions, not framework questions.
28
+
29
+ ## Invariants
30
+
31
+ - The application depends directly on the selected JSKIT driver.
32
+ - Credentials stay outside Git.
33
+ - `knexfile.js` fixes `mysql2`; a single-driver application does not require
34
+ `DB_CLIENT`.
35
+ - Schema changes are ordinary immutable migrations.
36
+ - The current package graph, environment, and migrations are authoritative.
37
+
38
+ ## Framework APIs
39
+
40
+ Use `createKnexMigrationConfigFromApp()` from
41
+ `@jskit-ai/database-runtime/server/knexMigrationConfig`. The running JSKIT
42
+ application automatically selects the sole installed database driver, while
43
+ Knex discovers package-owned migrations from the installed dependency graph.
44
+
45
+ ## Example files
46
+
47
+ `example/package.json` contains the runtime dependency and database scripts.
48
+ `example/knexfile.js` loads an optional local `.env` and fixes the MySQL dialect.
49
+ `example/scripts/prepare-database.js` is the portable migrate-then-seed
50
+ entrypoint for managed sessions and deployments. `example/.env.example` names
51
+ the five connection values without a secret.
52
+
53
+ ## Variation points
54
+
55
+ Change scripts, migration location, connection values, and secret injection to
56
+ fit the application. Supply `DATABASE_URL` instead of individual values when
57
+ the environment owns a URL. Add `DB_CLIENT=mysql2` only when a shared external
58
+ tool explicitly needs a dialect selector. When the product needs initial data,
59
+ import one idempotent app-owned seed function in `prepare-database.js` and pass
60
+ it as `seed` to `prepareDatabaseFromApp()`.
61
+
62
+ ## Verification
63
+
64
+ - Install dependencies once with `npm install`.
65
+ - Run `npm run db:prepare` against a fresh disposable database.
66
+ - Run `npm run db:migrate:status` and the application verification command.
67
+ - Exercise one transaction and one negative connection case.
68
+ - When a seed exists, run `db:prepare` twice and require the second run to be safe.
69
+
70
+ `example/.github/workflows/verify.yml` is a normal app-owned CI workflow with
71
+ an explicit MariaDB service. Adapt it as source rather than generating it from
72
+ package metadata.
73
+
74
+ ## Avoid
75
+
76
+ - framework questionnaires for credentials
77
+ - generated `.env` secrets
78
+ - depending on a live schema as the source contract
79
+ - a second driver installed “just in case”
80
+ - migration receipts, provenance, replay logs, or generator ownership
@@ -0,0 +1,5 @@
1
+ DB_HOST=127.0.0.1
2
+ DB_PORT=3306
3
+ DB_NAME=application
4
+ DB_USER=application
5
+ DB_PASSWORD=replace-me
@@ -0,0 +1,39 @@
1
+ name: Verify
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ verify:
9
+ runs-on: ubuntu-latest
10
+ services:
11
+ mariadb:
12
+ image: mariadb:11.4
13
+ env:
14
+ MARIADB_DATABASE: app_test
15
+ MARIADB_USER: app_test
16
+ MARIADB_PASSWORD: app_test_password
17
+ MARIADB_ROOT_PASSWORD: root_test_password
18
+ ports:
19
+ - 3306:3306
20
+ options: >-
21
+ --health-cmd="healthcheck.sh --connect --innodb_initialized"
22
+ --health-interval=10s
23
+ --health-timeout=5s
24
+ --health-retries=10
25
+ env:
26
+ DB_HOST: 127.0.0.1
27
+ DB_PORT: 3306
28
+ DB_NAME: app_test
29
+ DB_USER: app_test
30
+ DB_PASSWORD: app_test_password
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-node@v4
34
+ with:
35
+ node-version-file: .nvmrc
36
+ cache: npm
37
+ - run: npm ci
38
+ - run: npm run db:migrate
39
+ - run: npm run verify
@@ -0,0 +1,11 @@
1
+ import { createKnexMigrationConfigFromApp } from "@jskit-ai/database-runtime/server/knexMigrationConfig";
2
+
3
+ try {
4
+ process.loadEnvFile?.(".env");
5
+ } catch (error) {
6
+ if (error?.code !== "ENOENT") {
7
+ throw error;
8
+ }
9
+ }
10
+
11
+ export default await createKnexMigrationConfigFromApp({ client: "mysql2" });
@@ -0,0 +1,13 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "scripts": {
5
+ "db:prepare": "node ./scripts/prepare-database.js",
6
+ "db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
7
+ "db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
8
+ "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
9
+ },
10
+ "dependencies": {
11
+ "@jskit-ai/database-runtime-mysql": "0.1.159"
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ import { prepareDatabaseFromApp } from "@jskit-ai/database-runtime/server/databaseSetup";
2
+
3
+ await prepareDatabaseFromApp({ client: "mysql2" });
@@ -0,0 +1,16 @@
1
+ import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
2
+ import * as mysqlDriver from "../../shared/index.js";
3
+
4
+ const MysqlDatabaseDriverProvider = defineProvider({
5
+ id: "runtime.database.driver.mysql",
6
+ provides: {
7
+ driver: "runtime.database.driver"
8
+ },
9
+ setup() {
10
+ return {
11
+ driver: Object.freeze({ ...mysqlDriver })
12
+ };
13
+ }
14
+ });
15
+
16
+ export { MysqlDatabaseDriverProvider };
@@ -0,0 +1,18 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import test from "node:test";
4
+
5
+ const patternUrl = new URL("../patterns/mysql-application/", import.meta.url);
6
+
7
+ test("MySQL application pattern is driver-fixed and questionnaire-free", async () => {
8
+ const [document, manifestSource, knexfile] = await Promise.all([
9
+ readFile(new URL("PATTERN.md", patternUrl), "utf8"),
10
+ readFile(new URL("example/package.json", patternUrl), "utf8"),
11
+ readFile(new URL("example/knexfile.js", patternUrl), "utf8")
12
+ ]);
13
+ const manifest = JSON.parse(manifestSource);
14
+
15
+ assert.equal(manifest.dependencies["@jskit-ai/database-runtime-mysql"], "0.1.158");
16
+ assert.match(knexfile, /client: "mysql2"/u);
17
+ assert.doesNotMatch(`${document}\n${knexfile}`, /promptLabel|promptHint|\$\{option:/u);
18
+ });
@@ -4,15 +4,15 @@ import test from "node:test";
4
4
 
5
5
  import * as clientApi from "../src/client/index.js";
6
6
  import * as sharedApi from "../src/shared/index.js";
7
- import { DatabaseRuntimeMysqlServiceProvider } from "../src/server/providers/DatabaseRuntimeMysqlServiceProvider.js";
7
+ import { MysqlDatabaseDriverProvider } from "../src/server/providers/MysqlDatabaseDriverProvider.js";
8
8
 
9
9
  test("package exports include explicit shared and provider entrypoints", async () => {
10
10
  const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
11
11
  const exportsMap = packageJson && typeof packageJson === "object" ? packageJson.exports : {};
12
12
  assert.equal(exportsMap["./server"], undefined);
13
13
  assert.equal(
14
- exportsMap["./server/providers/DatabaseRuntimeMysqlServiceProvider"],
15
- "./src/server/providers/DatabaseRuntimeMysqlServiceProvider.js"
14
+ exportsMap["./server/providers/MysqlDatabaseDriverProvider"],
15
+ "./src/server/providers/MysqlDatabaseDriverProvider.js"
16
16
  );
17
17
  assert.equal(exportsMap["./shared"], "./src/shared/index.js");
18
18
  });
@@ -21,13 +21,14 @@ test("client entrypoint exports no mysql runtime api", () => {
21
21
  assert.deepEqual(Object.keys(clientApi), []);
22
22
  });
23
23
 
24
- test("server provider module exports mysql service provider only", () => {
25
- assert.equal(typeof DatabaseRuntimeMysqlServiceProvider, "function");
24
+ test("server provider module exports the declarative mysql driver provider", () => {
25
+ assert.equal(MysqlDatabaseDriverProvider.id, "runtime.database.driver.mysql");
26
+ assert.deepEqual(MysqlDatabaseDriverProvider.provides, { driver: "runtime.database.driver" });
26
27
  });
27
28
 
28
29
  test("shared entrypoint exports mysql dialect helpers", () => {
29
30
  assert.equal(sharedApi.DIALECT_ID, "mysql2");
30
31
  assert.equal(sharedApi.getDialectId(), "mysql2");
31
32
  assert.equal(typeof sharedApi.introspectCrudTableSnapshot, "function");
32
- assert.equal(typeof sharedApi.DatabaseRuntimeMysqlServiceProvider, "undefined");
33
+ assert.equal(typeof sharedApi.MysqlDatabaseDriverProvider, "undefined");
33
34
  });
@@ -1,24 +1,23 @@
1
1
  import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
2
4
  import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
3
6
  import packageJson from "../package.json" with { type: "json" };
4
7
 
5
- const packageMetadata = packageJson.jskit;
6
- import { DIALECT_ID } from "../src/shared/dialect.js";
8
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
9
 
8
- test("database-runtime-mysql contributes canonical synthetic MariaDB CI requirements", () => {
9
- assert.equal(packageMetadata.ci.environment.DB_CLIENT, DIALECT_ID);
10
- assert.equal(packageMetadata.ci.environment.DB_HOST, "127.0.0.1");
11
- assert.equal(packageMetadata.ci.environment.DB_PORT, "33060");
12
- assert.match(packageMetadata.ci.environment.DB_PASSWORD, /ci_only/u);
10
+ test("database-runtime-mysql owns its driver while CI remains app-owned source", async () => {
11
+ const workflow = await readFile(
12
+ path.join(PACKAGE_ROOT, "patterns/mysql-application/example/.github/workflows/verify.yml"),
13
+ "utf8"
14
+ );
13
15
 
14
- const service = packageMetadata.ci.services.find((entry) => entry.id === "mariadb");
15
- assert.equal(service.image, "mariadb:11.4");
16
- assert.equal(service.environment.MARIADB_DATABASE, packageMetadata.ci.environment.DB_NAME);
17
- assert.equal(service.environment.MARIADB_USER, packageMetadata.ci.environment.DB_USER);
18
- assert.equal(service.environment.MARIADB_PASSWORD, packageMetadata.ci.environment.DB_PASSWORD);
19
- assert.deepEqual(service.ports, ["33060:3306"]);
20
- assert.match(service.healthCheck.command, /healthcheck\.sh/u);
21
-
22
- const clientMutation = packageMetadata.mutations.text.find((entry) => entry.key === "DB_CLIENT");
23
- assert.equal(clientMutation.value, DIALECT_ID);
16
+ assert.equal(packageJson.dependencies.mysql2, "^3.11.2");
17
+ assert.equal(Object.hasOwn(packageJson.jskit, "options"), false);
18
+ assert.equal(Object.hasOwn(packageJson.jskit, "mutations"), false);
19
+ assert.equal(Object.hasOwn(packageJson.jskit, "ci"), false);
20
+ assert.match(workflow, /image: mariadb:11\.4/u);
21
+ assert.match(workflow, /npm run db:migrate/u);
22
+ assert.match(workflow, /npm run verify/u);
24
23
  });
@@ -1,33 +1,23 @@
1
- import test from "node:test";
2
1
  import assert from "node:assert/strict";
3
- import { DatabaseRuntimeMysqlServiceProvider } from "../src/server/providers/DatabaseRuntimeMysqlServiceProvider.js";
2
+ import test from "node:test";
4
3
 
5
- function createSingletonApp() {
6
- const singletons = new Map();
4
+ import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
5
+ import { MysqlDatabaseDriverProvider } from "../src/server/providers/MysqlDatabaseDriverProvider.js";
7
6
 
8
- return {
9
- has(token) {
10
- return singletons.has(token);
11
- },
12
- singleton(token, factory) {
13
- singletons.set(token, factory(this));
14
- },
15
- make(token) {
16
- if (!singletons.has(token)) {
17
- throw new Error(`Token ${String(token)} is not registered.`);
18
- }
19
- return singletons.get(token);
7
+ test("MysqlDatabaseDriverProvider provides the exclusive database driver capability", async () => {
8
+ let driver;
9
+ const consumer = defineProvider({
10
+ id: "test.mysql.consumer",
11
+ requires: { databaseDriver: "runtime.database.driver" },
12
+ setup({ databaseDriver }) {
13
+ driver = databaseDriver;
14
+ return {};
20
15
  }
21
- };
22
- }
23
-
24
- test("DatabaseRuntimeMysqlServiceProvider registers mysql driver api", () => {
25
- const app = createSingletonApp();
26
- const provider = new DatabaseRuntimeMysqlServiceProvider();
27
- provider.register(app);
16
+ });
17
+ const runtime = createCapabilityRuntime({ providers: [MysqlDatabaseDriverProvider, consumer] });
28
18
 
29
- assert.equal(app.has("runtime.database.driver.mysql"), true);
30
- const api = app.make("runtime.database.driver.mysql");
31
- assert.equal(api.DIALECT_ID, "mysql2");
32
- assert.equal(api.getDialectId(), "mysql2");
19
+ await runtime.start();
20
+ assert.equal(driver.DIALECT_ID, "mysql2");
21
+ assert.equal(driver.getDialectId(), "mysql2");
22
+ await runtime.shutdown();
33
23
  });
@@ -1,21 +0,0 @@
1
- import * as mysqlDialect from "../../shared/index.js";
2
-
3
- const MYSQL_DATABASE_DRIVER_API = Object.freeze({
4
- ...mysqlDialect
5
- });
6
-
7
- class DatabaseRuntimeMysqlServiceProvider {
8
- static id = "runtime.database.driver.mysql";
9
-
10
- register(app) {
11
- if (!app || typeof app.singleton !== "function") {
12
- throw new Error("DatabaseRuntimeMysqlServiceProvider requires application singleton().");
13
- }
14
-
15
- app.singleton("runtime.database.driver.mysql", () => MYSQL_DATABASE_DRIVER_API);
16
- }
17
-
18
- boot() {}
19
- }
20
-
21
- export { DatabaseRuntimeMysqlServiceProvider };