@waukeshamakerspace/db-kit 0.2.1 → 0.3.0

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 CHANGED
@@ -27,11 +27,12 @@ need no environment at all.
27
27
 
28
28
  ### Scripts
29
29
 
30
- The two admin scripts ship as binaries, so a service's `package.json` is:
30
+ The admin scripts ship as binaries, so a service's `package.json` is:
31
31
 
32
32
  ```json
33
33
  {
34
34
  "scripts": {
35
+ "db:provision": "wmi-db-provision",
35
36
  "db:ensure": "wmi-db-ensure",
36
37
  "db:migrate": "wmi-db-migrate"
37
38
  }
@@ -41,9 +42,71 @@ The two admin scripts ship as binaries, so a service's `package.json` is:
41
42
  `wmi-db-migrate` takes the migrations folder as an optional argument
42
43
  (`MIGRATIONS_DIR` also works), defaulting to `./migrations`.
43
44
 
44
- Both walk up from the cwd to the nearest `.env` and say which file they loaded.
45
- That replaces the hard-coded `resolve(cwd, '../../.env')` in the copied
46
- versions, which assumed every repo was exactly two levels deep.
45
+ `wmi-db-ensure` and `wmi-db-migrate` walk up from the cwd to the nearest `.env`
46
+ and say which file they loaded. That replaces the hard-coded
47
+ `resolve(cwd, '../../.env')` in the copied versions, which assumed every repo
48
+ was exactly two levels deep. **`wmi-db-provision` deliberately does not**, for
49
+ the reason below.
50
+
51
+ ### Provisioning
52
+
53
+ `wmi-db-provision` creates a database and its two scoped users. It replaces the
54
+ block of manual SQL that each repo has been carrying in `docs/deploy.md`, so
55
+ dev and production get set up the same way.
56
+
57
+ ```bash
58
+ wmi-db-provision journeyman --admin-url mysql://admin:pw@rds.internal:3306
59
+ ```
60
+
61
+ It creates, idempotently:
62
+
63
+ | | |
64
+ |---|---|
65
+ | `` `journeyman` `` | `CREATE DATABASE IF NOT EXISTS` |
66
+ | `journeyman_migrator` | `ALTER, CREATE, DROP, INDEX, REFERENCES, SELECT, INSERT, UPDATE, DELETE` |
67
+ | `journeyman_app` | `SELECT, INSERT, UPDATE, DELETE` |
68
+
69
+ **The migrator holds DML as well as DDL on purpose.** Drizzle maintains its
70
+ `__drizzle_migrations` bookkeeping table over the same connection, so a migrator
71
+ granted DDL alone looks correct and then fails partway through the first
72
+ migration it tries to record.
73
+
74
+ **The app user never holds DDL**, so a bug cannot alter its own schema. Narrow
75
+ it further with `--app-privileges`, which accepts only those four DML
76
+ privileges and refuses anything else:
77
+
78
+ ```bash
79
+ # Journeyman's Lambda user: its append-only events table cannot be rewritten
80
+ wmi-db-provision journeyman --admin-url ... --app-privileges SELECT,INSERT
81
+ ```
82
+
83
+ Both passwords are generated here, printed once, and never written to disk. The
84
+ output is ready to paste: `DATABASE_URL` + `DATABASE_NAME` for the service's
85
+ `.env` and `samconfig.toml`, and the migrator's URI for the
86
+ `MIGRATOR_DATABASE_URL` GitHub secret.
87
+
88
+ `--dry-run` prints the exact statements and exits without connecting, which is
89
+ also how you hand the SQL to someone else to run.
90
+
91
+ Two things worth knowing before re-running it:
92
+
93
+ - **It rotates both passwords.** `CREATE USER IF NOT EXISTS` is a no-op for an
94
+ existing account, so the password is set with a following `ALTER USER`.
95
+ Without that a re-run would print credentials that do not work. Existing
96
+ deployments need the new values.
97
+ - **It revokes before granting**, so a re-run tightens an account that was
98
+ over-granted by hand rather than only widening it. Narrowing an existing
99
+ `journeyman_app` to `SELECT,INSERT` really does take `UPDATE` and `DELETE`
100
+ away.
101
+
102
+ The admin credential is passed every time, via `--admin-url` or
103
+ `WMI_DB_ADMIN_URL` (which keeps it out of shell history). This bin never reads
104
+ the service's `.env`, because a stray `DATABASE_URL` must not be able to decide
105
+ which server gets provisioned.
106
+
107
+ The generated SQL is verified against MySQL 8.4 in a container, not just
108
+ asserted as strings: the grants, the privilege boundary (the app user is
109
+ refused `CREATE`, `ALTER`, and `DROP`), password rotation, and narrowing.
47
110
 
48
111
  ### The convention
49
112
 
@@ -56,8 +119,9 @@ app connect to the same place as different users:
56
119
  - `<service>_app` holds only the DML the service needs
57
120
 
58
121
  Journeyman takes that further: its Lambda user has `SELECT` and `INSERT` only,
59
- so its append-only events table cannot be rewritten even by a bug. That
60
- convention lives in each repo's deploy workflow; db-kit is the client half.
122
+ so its append-only events table cannot be rewritten even by a bug. That is what
123
+ `wmi-db-provision --app-privileges SELECT,INSERT` produces. The convention used
124
+ to live as hand-written SQL in each repo's deploy doc; it is now executable.
61
125
 
62
126
  ## Notable fixes over the copied scripts
63
127
 
package/dist/admin.d.ts CHANGED
@@ -12,6 +12,66 @@ export type Logger = {
12
12
  * it is interpolated into DDL where binding cannot help.
13
13
  */
14
14
  export declare function ensureDatabase(config: DatabaseConfig, logger?: Logger): Promise<void>;
15
+ /**
16
+ * The migrator's grants. DDL *and* DML, deliberately: Drizzle keeps its
17
+ * `__drizzle_migrations` bookkeeping table over the same connection, so a
18
+ * migrator holding DDL alone looks right and then fails partway through the
19
+ * first migration it records.
20
+ */
21
+ export declare const MIGRATOR_PRIVILEGES: readonly ["ALTER", "CREATE", "DROP", "INDEX", "REFERENCES", "SELECT", "INSERT", "UPDATE", "DELETE"];
22
+ /** The app's grants: row movement only, never schema. */
23
+ export declare const APP_PRIVILEGES: readonly ["SELECT", "INSERT", "UPDATE", "DELETE"];
24
+ export type ProvisionStatement = {
25
+ sql: string;
26
+ /**
27
+ * MySQL error numbers that mean "already in the desired state". Tolerated so
28
+ * a re-run converges instead of failing halfway.
29
+ */
30
+ tolerate?: number[];
31
+ };
32
+ export type ProvisionedUser = {
33
+ user: string;
34
+ password: string;
35
+ /** Server URI carrying this user's credentials. No database path. */
36
+ url: string;
37
+ };
38
+ export type ProvisionPlan = {
39
+ name: string;
40
+ statements: ProvisionStatement[];
41
+ migrator: ProvisionedUser;
42
+ app: ProvisionedUser;
43
+ };
44
+ export type ProvisionOptions = {
45
+ /**
46
+ * Admin credential, server only. Passed in deliberately and never read from
47
+ * the service's own .env, so this cannot quietly provision whichever
48
+ * instance a stray DATABASE_URL happens to point at.
49
+ */
50
+ adminUrl: string;
51
+ /** Database name. Validated before it reaches DDL. */
52
+ name: string;
53
+ /** Host part of both accounts. Defaults to `%`. */
54
+ grantHost?: string;
55
+ /** Narrow the app user, e.g. `['SELECT', 'INSERT']`. Defaults to all four DML. */
56
+ appPrivileges?: readonly string[];
57
+ /** Injectable for tests. Defaults to 24 random bytes, base64url. */
58
+ generatePassword?: () => string;
59
+ };
60
+ /** 192 bits, in an alphabet that needs no escaping in a URI or a SQL literal. */
61
+ export declare const generatePassword: () => string;
62
+ /**
63
+ * Build the provisioning plan without touching a server. Separated from
64
+ * execution so `--dry-run` prints exactly what would run, and so the statement
65
+ * order and grants are testable without a MySQL instance.
66
+ */
67
+ export declare function planProvision(options: ProvisionOptions): ProvisionPlan;
68
+ /**
69
+ * Create the database and its two scoped users, idempotently.
70
+ *
71
+ * Re-running rotates both passwords and re-applies the grants, so the plan it
72
+ * prints is always the truth about how to connect afterwards.
73
+ */
74
+ export declare function provisionDatabase(plan: ProvisionPlan, adminUrl: string, logger?: Logger): Promise<void>;
15
75
  /**
16
76
  * Apply pending Drizzle migrations.
17
77
  *
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAeA,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1E,MAAM,MAAM,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAAC;AAIzD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,gBAAgB,EAAE,MAAM,EACxB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAqC,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAErF,MAAM,MAAM,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAAC;AAIzD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAQf;AAUD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,qGAUtB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc,mDAAoD,CAAC;AAgBhF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;CACjC,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,QAAO,MAA+C,CAAC;AA6BpF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAkFtE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,gBAAgB,EAAE,MAAM,EACxB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAYf"}
package/dist/admin.js CHANGED
@@ -9,10 +9,11 @@
9
9
  // goes further: its Lambda user has SELECT+INSERT only, so the append-only
10
10
  // events table cannot be rewritten even by a bug.
11
11
  // ============================================================================
12
+ import { randomBytes } from 'node:crypto';
12
13
  import { drizzle } from 'drizzle-orm/mysql2';
13
14
  import { migrate } from 'drizzle-orm/mysql2/migrator';
14
15
  import mysql from 'mysql2/promise';
15
- import { databaseUri, serverUri } from './config.js';
16
+ import { SAFE_NAME, databaseUri, serverUri } from './config.js';
16
17
  const consoleLogger = { info: (message) => console.log(message) };
17
18
  /**
18
19
  * Create the database if it does not exist.
@@ -33,6 +34,177 @@ export async function ensureDatabase(config, logger = consoleLogger) {
33
34
  await connection.end();
34
35
  }
35
36
  }
37
+ // ============================================================================
38
+ // Provisioning: the database plus its two scoped users.
39
+ //
40
+ // This is the convention `shared/database.md` describes and that every repo
41
+ // has so far encoded by hand in a deploy doc. A `<db>_migrator` owns the
42
+ // schema, a `<db>_app` can only move rows through it.
43
+ // ============================================================================
44
+ /**
45
+ * The migrator's grants. DDL *and* DML, deliberately: Drizzle keeps its
46
+ * `__drizzle_migrations` bookkeeping table over the same connection, so a
47
+ * migrator holding DDL alone looks right and then fails partway through the
48
+ * first migration it records.
49
+ */
50
+ export const MIGRATOR_PRIVILEGES = [
51
+ 'ALTER',
52
+ 'CREATE',
53
+ 'DROP',
54
+ 'INDEX',
55
+ 'REFERENCES',
56
+ 'SELECT',
57
+ 'INSERT',
58
+ 'UPDATE',
59
+ 'DELETE',
60
+ ];
61
+ /** The app's grants: row movement only, never schema. */
62
+ export const APP_PRIVILEGES = ['SELECT', 'INSERT', 'UPDATE', 'DELETE'];
63
+ /**
64
+ * What an app user may be narrowed to. An allowlist rather than a DDL denylist,
65
+ * so a privilege nobody thought about cannot slip through: Journeyman's Lambda
66
+ * user is SELECT+INSERT so its append-only events table cannot be rewritten
67
+ * even by a bug, and that is the shape of tightening this supports.
68
+ */
69
+ const NARROWABLE_APP_PRIVILEGES = new Set(APP_PRIVILEGES);
70
+ /** base64url: safe unquoted in a URI and unescaped in a SQL string literal. */
71
+ const SAFE_SECRET = /^[A-Za-z0-9_-]+$/;
72
+ /** MySQL account name host part. `%` is any host, the RDS default. */
73
+ const SAFE_HOST = /^[A-Za-z0-9_%.:-]+$/;
74
+ /** 192 bits, in an alphabet that needs no escaping in a URI or a SQL literal. */
75
+ export const generatePassword = () => randomBytes(24).toString('base64url');
76
+ function assertServerOnly(url) {
77
+ let parsed;
78
+ try {
79
+ parsed = new URL(url);
80
+ }
81
+ catch {
82
+ throw new Error(`db-kit: admin URL is not a valid URI: ${JSON.stringify(url)}`);
83
+ }
84
+ if (parsed.pathname && parsed.pathname !== '/' && parsed.pathname !== '') {
85
+ throw new Error(`db-kit: admin URL must be the server only, with no database path (got "${parsed.pathname}").`);
86
+ }
87
+ return parsed;
88
+ }
89
+ function credentialUrl(admin, user, password) {
90
+ const url = new URL(admin.href);
91
+ url.username = user;
92
+ url.password = password;
93
+ url.pathname = '';
94
+ return url.href;
95
+ }
96
+ function grant(privileges, name, user, host) {
97
+ return `GRANT ${privileges.join(', ')} ON \`${name}\`.* TO '${user}'@'${host}'`;
98
+ }
99
+ /**
100
+ * Build the provisioning plan without touching a server. Separated from
101
+ * execution so `--dry-run` prints exactly what would run, and so the statement
102
+ * order and grants are testable without a MySQL instance.
103
+ */
104
+ export function planProvision(options) {
105
+ const name = options.name.trim();
106
+ if (!name)
107
+ throw new Error('db-kit: a database name is required');
108
+ // The name is interpolated into DDL, where binding cannot help.
109
+ if (!SAFE_NAME.test(name)) {
110
+ throw new Error(`db-kit: refusing an unsafe database name: ${JSON.stringify(name)}`);
111
+ }
112
+ const host = options.grantHost ?? '%';
113
+ if (!SAFE_HOST.test(host)) {
114
+ throw new Error(`db-kit: refusing an unsafe grant host: ${JSON.stringify(host)}`);
115
+ }
116
+ const appPrivileges = options.appPrivileges ?? APP_PRIVILEGES;
117
+ if (appPrivileges.length === 0) {
118
+ throw new Error('db-kit: the app user needs at least one privilege');
119
+ }
120
+ for (const privilege of appPrivileges) {
121
+ if (!NARROWABLE_APP_PRIVILEGES.has(privilege)) {
122
+ throw new Error(`db-kit: refusing to grant ${JSON.stringify(privilege)} to the app user. ` +
123
+ `It may hold only ${[...NARROWABLE_APP_PRIVILEGES].join(', ')}; schema changes belong to the migrator.`);
124
+ }
125
+ }
126
+ const admin = assertServerOnly(options.adminUrl);
127
+ const generate = options.generatePassword ?? generatePassword;
128
+ const migratorUser = `${name}_migrator`;
129
+ const appUser = `${name}_app`;
130
+ const migratorPassword = generate();
131
+ const appPassword = generate();
132
+ for (const secret of [migratorPassword, appPassword]) {
133
+ if (!SAFE_SECRET.test(secret)) {
134
+ throw new Error('db-kit: generated password contains characters that are unsafe unescaped');
135
+ }
136
+ }
137
+ const account = (user, password) => [
138
+ { sql: `CREATE USER IF NOT EXISTS '${user}'@'${host}' IDENTIFIED BY '${password}'` },
139
+ // The CREATE above is a no-op for an existing account, so set the password
140
+ // explicitly. Otherwise a re-run prints credentials that do not work.
141
+ { sql: `ALTER USER '${user}'@'${host}' IDENTIFIED BY '${password}'` },
142
+ // Grants are additive, so revoke first: re-running must be able to TIGHTEN
143
+ // an account that was over-granted by hand, not just widen it. 1141 is
144
+ // "no such grant defined", which is the expected state on a fresh account.
145
+ //
146
+ // Two statements, not one. `REVOKE ALL PRIVILEGES, GRANT OPTION` is a
147
+ // separate grammar that takes no ON clause and drops everything globally;
148
+ // combining it with `ON db.*` is a syntax error, which is what MySQL 8.4
149
+ // said when this was first written as one line.
150
+ {
151
+ sql: `REVOKE ALL PRIVILEGES ON \`${name}\`.* FROM '${user}'@'${host}'`,
152
+ tolerate: [1141],
153
+ },
154
+ {
155
+ sql: `REVOKE GRANT OPTION ON \`${name}\`.* FROM '${user}'@'${host}'`,
156
+ tolerate: [1141],
157
+ },
158
+ ];
159
+ return {
160
+ name,
161
+ statements: [
162
+ { sql: `CREATE DATABASE IF NOT EXISTS \`${name}\`` },
163
+ ...account(migratorUser, migratorPassword),
164
+ { sql: grant(MIGRATOR_PRIVILEGES, name, migratorUser, host) },
165
+ ...account(appUser, appPassword),
166
+ { sql: grant(appPrivileges, name, appUser, host) },
167
+ { sql: 'FLUSH PRIVILEGES' },
168
+ ],
169
+ migrator: {
170
+ user: migratorUser,
171
+ password: migratorPassword,
172
+ url: credentialUrl(admin, migratorUser, migratorPassword),
173
+ },
174
+ app: {
175
+ user: appUser,
176
+ password: appPassword,
177
+ url: credentialUrl(admin, appUser, appPassword),
178
+ },
179
+ };
180
+ }
181
+ /**
182
+ * Create the database and its two scoped users, idempotently.
183
+ *
184
+ * Re-running rotates both passwords and re-applies the grants, so the plan it
185
+ * prints is always the truth about how to connect afterwards.
186
+ */
187
+ export async function provisionDatabase(plan, adminUrl, logger = consoleLogger) {
188
+ const connection = await mysql.createConnection({ uri: adminUrl });
189
+ try {
190
+ for (const statement of plan.statements) {
191
+ try {
192
+ await connection.query(statement.sql);
193
+ }
194
+ catch (error) {
195
+ const errno = error.errno;
196
+ if (statement.tolerate && errno !== undefined && statement.tolerate.includes(errno)) {
197
+ continue;
198
+ }
199
+ throw error;
200
+ }
201
+ }
202
+ logger.info(`Provisioned ${plan.name} with ${plan.migrator.user} and ${plan.app.user}.`);
203
+ }
204
+ finally {
205
+ await connection.end();
206
+ }
207
+ }
36
208
  /**
37
209
  * Apply pending Drizzle migrations.
38
210
  *
package/dist/admin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,sEAAsE;AACtE,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,wEAAwE;AACxE,gFAAgF;AAChF,2EAA2E;AAC3E,kDAAkD;AAClD,+EAA+E;AAE/E,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,KAAK,MAAM,gBAAgB,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAuB,MAAM,aAAa,CAAC;AAI1E,MAAM,aAAa,GAAW,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAsB,EACtB,SAAiB,aAAa;IAE9B,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5E,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,KAAK,CAAC,mCAAmC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,IAAI,cAAc,CAAC,CAAC;IACrD,CAAC;YAAS,CAAC;QACT,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAsB,EACtB,gBAAwB,EACxB,SAAiB,aAAa;IAE9B,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC;QAC9C,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC;QACxB,kBAAkB,EAAE,IAAI;KACzB,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,4BAA4B,gBAAgB,OAAO,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC;QAClF,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACrC,CAAC;YAAS,CAAC;QACT,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC;IACzB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=provision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../../src/bin/provision.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ // `wmi-db-provision <name> --admin-url <uri>` — create a service's database and
3
+ // its two scoped users.
4
+ //
5
+ // Replaces the block of manual SQL each repo carries in docs/deploy.md.
6
+ // "db:provision": "wmi-db-provision"
7
+ //
8
+ // Note what this does NOT do: it never calls loadNearestEnv(). The other two
9
+ // bins read the service's .env on purpose; this one must not, or it would
10
+ // happily provision whichever instance a stray DATABASE_URL points at. The
11
+ // admin credential is supplied deliberately every time.
12
+ import { parseArgs } from 'node:util';
13
+ import { APP_PRIVILEGES, planProvision, provisionDatabase } from '../admin.js';
14
+ const USAGE = `Usage: wmi-db-provision <database> --admin-url <uri> [options]
15
+
16
+ Creates, idempotently:
17
+ \`<database>\`
18
+ <database>_migrator schema owner (DDL + DML, for Drizzle's bookkeeping)
19
+ <database>_app row access only (${APP_PRIVILEGES.join(', ')})
20
+
21
+ Options:
22
+ --admin-url <uri> Admin credential, server only, no database path.
23
+ Reads WMI_DB_ADMIN_URL if the flag is omitted, which
24
+ keeps the password out of your shell history.
25
+ --app-privileges <l> Narrow the app user, e.g. SELECT,INSERT for an
26
+ append-only service. Default: ${APP_PRIVILEGES.join(',')}
27
+ --grant-host <host> Host part of both accounts. Default: %
28
+ --dry-run Print the exact statements and exit without connecting.
29
+ --help
30
+
31
+ Passwords are generated here, printed once, and never written to disk.
32
+ Re-running rotates them and re-applies the grants.`;
33
+ function fail(message) {
34
+ console.error(message);
35
+ process.exit(1);
36
+ }
37
+ let parsed;
38
+ try {
39
+ parsed = parseArgs({
40
+ allowPositionals: true,
41
+ options: {
42
+ 'admin-url': { type: 'string' },
43
+ 'app-privileges': { type: 'string' },
44
+ 'grant-host': { type: 'string' },
45
+ 'dry-run': { type: 'boolean', default: false },
46
+ help: { type: 'boolean', default: false },
47
+ },
48
+ });
49
+ }
50
+ catch (error) {
51
+ fail(`${error instanceof Error ? error.message : error}\n\n${USAGE}`);
52
+ }
53
+ const { values, positionals } = parsed;
54
+ if (values.help) {
55
+ console.log(USAGE);
56
+ process.exit(0);
57
+ }
58
+ const name = positionals[0];
59
+ if (!name)
60
+ fail(`A database name is required.\n\n${USAGE}`);
61
+ if (positionals.length > 1) {
62
+ fail(`Unexpected argument ${JSON.stringify(positionals[1])}. One database at a time.\n\n${USAGE}`);
63
+ }
64
+ const adminUrl = values['admin-url'] ?? process.env.WMI_DB_ADMIN_URL;
65
+ if (!adminUrl) {
66
+ fail('An admin credential is required: pass --admin-url or set WMI_DB_ADMIN_URL.\n' +
67
+ 'It is never read from the service .env, so that provisioning cannot target ' +
68
+ 'the wrong instance by accident.');
69
+ }
70
+ const appPrivileges = values['app-privileges']
71
+ ?.split(',')
72
+ .map((p) => p.trim().toUpperCase())
73
+ .filter(Boolean);
74
+ try {
75
+ const plan = planProvision({
76
+ adminUrl,
77
+ name,
78
+ ...(values['grant-host'] ? { grantHost: values['grant-host'] } : {}),
79
+ ...(appPrivileges?.length ? { appPrivileges } : {}),
80
+ });
81
+ if (values['dry-run']) {
82
+ console.log(`-- Dry run for \`${plan.name}\`. Nothing was executed.`);
83
+ console.log(plan.statements.map((s) => `${s.sql};`).join('\n'));
84
+ process.exit(0);
85
+ }
86
+ await provisionDatabase(plan, adminUrl);
87
+ // Once, to stdout, and nowhere else.
88
+ console.log('');
89
+ console.log('Copy these now. They are shown once and never written to disk.');
90
+ console.log('');
91
+ console.log(` The service (.env, samconfig.toml):`);
92
+ console.log(` DATABASE_URL=${plan.app.url}`);
93
+ console.log(` DATABASE_NAME=${plan.name}`);
94
+ console.log('');
95
+ console.log(` The deploy workflow (GitHub secret MIGRATOR_DATABASE_URL):`);
96
+ console.log(` ${plan.migrator.url}`);
97
+ console.log('');
98
+ }
99
+ catch (error) {
100
+ fail(error instanceof Error ? error.message : String(error));
101
+ }
102
+ //# sourceMappingURL=provision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/bin/provision.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { DEFAULT_CONNECTION_LIMIT, SAFE_NAME, databaseConfig, databaseConfigFromEnv, databaseUri, serverUri, type DatabaseConfig, } from './config.js';
2
2
  export { createDbHandle, type Db, type DbHandle } from './client.js';
3
- export { ensureDatabase, runMigrations, type Logger } from './admin.js';
3
+ export { APP_PRIVILEGES, MIGRATOR_PRIVILEGES, ensureDatabase, generatePassword, planProvision, provisionDatabase, runMigrations, type Logger, type ProvisionOptions, type ProvisionPlan, type ProvisionStatement, type ProvisionedUser, } from './admin.js';
4
4
  export { findNearestEnvFile, loadNearestEnv } from './env.js';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { DEFAULT_CONNECTION_LIMIT, SAFE_NAME, databaseConfig, databaseConfigFromEnv, databaseUri, serverUri, } from './config.js';
2
2
  export { createDbHandle } from './client.js';
3
- export { ensureDatabase, runMigrations } from './admin.js';
3
+ export { APP_PRIVILEGES, MIGRATOR_PRIVILEGES, ensureDatabase, generatePassword, planProvision, provisionDatabase, runMigrations, } from './admin.js';
4
4
  export { findNearestEnvFile, loadNearestEnv } from './env.js';
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,GAEV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAA0B,MAAM,aAAa,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,GAEV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAA0B,MAAM,aAAa,CAAC;AAErE,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,GAMd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waukeshamakerspace/db-kit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "MySQL + Drizzle plumbing for Waukesha Makerspace services: the house DATABASE_URL/DATABASE_NAME convention, a lazy pool, and the ensure/migrate commands every WMI repo was copying by hand",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,7 +20,8 @@
20
20
  },
21
21
  "bin": {
22
22
  "wmi-db-ensure": "dist/bin/ensure.js",
23
- "wmi-db-migrate": "dist/bin/migrate.js"
23
+ "wmi-db-migrate": "dist/bin/migrate.js",
24
+ "wmi-db-provision": "dist/bin/provision.js"
24
25
  },
25
26
  "files": [
26
27
  "dist/"