Skip to content

Commit bcaa396

Browse files
committed
feat(db): add drizzle-kit generate workflow with DSQL compatibility check
- Add drizzle-kit as devDependency with drizzle.config.ts - Add check-dsql-compat.ts validator (runs after generate) - Add 'generate' pnpm script: drizzle-kit generate && check - Initialize drizzle-kit snapshot from existing 0001_initial.sql - Document schema change workflow in AGENTS.md
1 parent c894c39 commit bcaa396

7 files changed

Lines changed: 444 additions & 1 deletion

File tree

AGENTS.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,27 @@ DSQL constraints:
6060

6161
Migrations are SQL files in `packages/db/migrations/`. The migration runner is invoked automatically during `cdk deploy` via CDK Trigger. For manual invocation, use the `MigrationCommand` from CDK outputs.
6262

63-
Schema changes: edit `packages/db/src/schema.ts` → generate migration SQL → place in `packages/db/migrations/` → commit. Each SQL statement must be separated by a blank line (the runner splits on `\n\n`).
63+
Schema changes:
64+
65+
```bash
66+
# 1. Edit schema
67+
# packages/db/src/schema.ts
68+
69+
# 2. Generate migration SQL + DSQL compatibility check
70+
pnpm --filter @repo/db run generate
71+
72+
# 3. Review and fix the generated SQL in packages/db/migrations/
73+
# - Add IF NOT EXISTS for idempotency
74+
# - Replace CREATE INDEX with CREATE INDEX ASYNC
75+
# - Replace --> statement-breakpoint with blank lines (runner splits on \n\n)
76+
77+
# 4. Apply to dev cluster
78+
pnpm --filter @repo/db run migrate
79+
80+
# 5. Commit schema + migration + snapshot together
81+
```
82+
83+
Do not use `drizzle-kit push` or `drizzle-kit migrate` — DSQL requires 1 DDL per transaction. The custom runner (`pnpm run migrate`) handles this.
6484

6585
### Lambda environment
6686

packages/db/drizzle.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from 'drizzle-kit';
2+
3+
export default defineConfig({
4+
dialect: 'postgresql',
5+
schema: './src/schema.ts',
6+
out: './migrations',
7+
// No dbCredentials — generate is schema-diff only, no DB connection needed.
8+
// Do not use drizzle-kit push/migrate — DSQL requires 1 DDL per transaction.
9+
// Use `pnpm run migrate` (custom runner) to apply migrations.
10+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"id": "beb16dd9-127f-4aa6-b85f-1def0450252c",
3+
"prevId": "00000000-0000-0000-0000-000000000000",
4+
"version": "7",
5+
"dialect": "postgresql",
6+
"tables": {
7+
"public.TodoItem": {
8+
"name": "TodoItem",
9+
"schema": "",
10+
"columns": {
11+
"id": {
12+
"name": "id",
13+
"type": "uuid",
14+
"primaryKey": true,
15+
"notNull": true,
16+
"default": "gen_random_uuid()"
17+
},
18+
"title": {
19+
"name": "title",
20+
"type": "text",
21+
"primaryKey": false,
22+
"notNull": true
23+
},
24+
"description": {
25+
"name": "description",
26+
"type": "text",
27+
"primaryKey": false,
28+
"notNull": true
29+
},
30+
"userId": {
31+
"name": "userId",
32+
"type": "text",
33+
"primaryKey": false,
34+
"notNull": true
35+
},
36+
"status": {
37+
"name": "status",
38+
"type": "text",
39+
"primaryKey": false,
40+
"notNull": true,
41+
"default": "'PENDING'"
42+
},
43+
"createdAt": {
44+
"name": "createdAt",
45+
"type": "timestamp with time zone",
46+
"primaryKey": false,
47+
"notNull": true,
48+
"default": "now()"
49+
},
50+
"updatedAt": {
51+
"name": "updatedAt",
52+
"type": "timestamp with time zone",
53+
"primaryKey": false,
54+
"notNull": true,
55+
"default": "now()"
56+
}
57+
},
58+
"indexes": {},
59+
"foreignKeys": {},
60+
"compositePrimaryKeys": {},
61+
"uniqueConstraints": {},
62+
"policies": {},
63+
"checkConstraints": {},
64+
"isRLSEnabled": false
65+
},
66+
"public.User": {
67+
"name": "User",
68+
"schema": "",
69+
"columns": {
70+
"id": {
71+
"name": "id",
72+
"type": "text",
73+
"primaryKey": true,
74+
"notNull": true
75+
}
76+
},
77+
"indexes": {},
78+
"foreignKeys": {},
79+
"compositePrimaryKeys": {},
80+
"uniqueConstraints": {},
81+
"policies": {},
82+
"checkConstraints": {},
83+
"isRLSEnabled": false
84+
}
85+
},
86+
"enums": {},
87+
"schemas": {},
88+
"sequences": {},
89+
"roles": {},
90+
"policies": {},
91+
"views": {},
92+
"_meta": {
93+
"columns": {},
94+
"schemas": {},
95+
"tables": {}
96+
}
97+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "7",
3+
"dialect": "postgresql",
4+
"entries": [
5+
{
6+
"idx": 1,
7+
"version": "7",
8+
"when": 1774012279412,
9+
"tag": "0001_initial",
10+
"breakpoints": true
11+
}
12+
]
13+
}

packages/db/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"./migrate": "./src/migrate.ts"
1010
},
1111
"scripts": {
12+
"generate": "drizzle-kit generate && tsx src/check-dsql-compat.ts",
1213
"migrate": "tsx --env-file=.env src/cli.ts",
1314
"format": "oxfmt --write .",
1415
"format:ci": "oxfmt --check .",
@@ -24,6 +25,7 @@
2425
},
2526
"devDependencies": {
2627
"@types/pg": "^8",
28+
"drizzle-kit": "^0.31.10",
2729
"tsx": "^4",
2830
"typescript": "^5"
2931
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Validate migration SQL files for DSQL compatibility.
2+
// Run after drizzle-kit generate to catch incompatible DDL before commit.
3+
import fs from 'node:fs';
4+
import path from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
const migrationsDir = path.join(__dirname, '..', 'migrations');
9+
10+
const rules: { pattern: RegExp; message: string }[] = [
11+
{ pattern: /CREATE\s+INDEX\s+(?!.*ASYNC)/i, message: 'CREATE INDEX must use ASYNC keyword' },
12+
{ pattern: /REFERENCES\s+/i, message: 'FOREIGN KEY / REFERENCES not supported' },
13+
{ pattern: /FOREIGN\s+KEY/i, message: 'FOREIGN KEY not supported' },
14+
{ pattern: /ALTER\s+.*\s+TYPE\s+/i, message: 'ALTER COLUMN TYPE not supported' },
15+
{ pattern: /DROP\s+COLUMN/i, message: 'DROP COLUMN not supported' },
16+
{ pattern: /\bSERIAL\b/i, message: 'SERIAL types not supported (use UUID)' },
17+
];
18+
19+
const files = fs.readdirSync(migrationsDir).filter((f) => f.endsWith('.sql'));
20+
let errors = 0;
21+
22+
for (const file of files) {
23+
const content = fs.readFileSync(path.join(migrationsDir, file), 'utf8');
24+
for (const { pattern, message } of rules) {
25+
if (pattern.test(content)) {
26+
console.error(`ERROR: ${file}${message}`);
27+
errors++;
28+
}
29+
}
30+
}
31+
32+
if (errors > 0) {
33+
console.error(`\n${errors} DSQL compatibility error(s). Fix the SQL before committing.`);
34+
process.exit(1);
35+
}
36+
console.log('All migration files DSQL-compatible.');

0 commit comments

Comments
 (0)