fix(api): load Drizzle migrations at runtime (#2307)

This commit is contained in:
RainbowBird
2026-08-17 11:19:53 +00:00
committed by GitHub
parent 07b26b530f
commit bd790098d2
19 changed files with 11 additions and 236 deletions
-105
View File
@@ -384,9 +384,6 @@ catalogs:
'@proj-airi/drizzle-duckdb-wasm':
specifier: ^0.5.0
version: 0.5.0
'@proj-airi/drizzle-orm-browser-migrator':
specifier: ^0.1.6
version: 0.1.6
'@proj-airi/iconify-meteocons':
specifier: ^0.1.5
version: 0.1.5
@@ -396,9 +393,6 @@ catalogs:
'@proj-airi/unocss-preset-chromatic':
specifier: ^1.1.1
version: 1.1.1
'@proj-airi/unplugin-drizzle-orm-migrations':
specifier: ^0.1.6
version: 0.1.6
'@proj-airi/unplugin-fetch':
specifier: ^0.2.3
version: 0.2.3
@@ -5428,12 +5422,6 @@ importers:
'@proj-airi/auth-shared':
specifier: workspace:*
version: link:../../packages/auth-shared
'@proj-airi/drizzle-migration':
specifier: workspace:*
version: link:../../packages/drizzle-migration
'@proj-airi/drizzle-orm-browser-migrator':
specifier: 'catalog:'
version: 0.1.6(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.4)(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.20.0)(better-sqlite3@12.5.0)(kysely@0.29.4)(pg@8.20.0)(postgres@3.4.9))
'@proj-airi/server-sdk-shared':
specifier: workspace:*
version: link:../../../packages/server-sdk-shared
@@ -5638,12 +5626,6 @@ importers:
specifier: 'catalog:'
version: 0.45.2(@electric-sql/pglite@0.4.4)(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.20.0)(better-sqlite3@12.5.0)(kysely@0.29.4)(pg@8.20.0)(postgres@3.4.9)
server/packages/drizzle-migration:
devDependencies:
'@proj-airi/unplugin-drizzle-orm-migrations':
specifier: 'catalog:'
version: 0.1.6(magicast@0.5.2)(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
services/computer-use-mcp:
dependencies:
'@modelcontextprotocol/sdk':
@@ -10137,11 +10119,6 @@ packages:
web-worker:
optional: true
'@proj-airi/drizzle-orm-browser-migrator@0.1.6':
resolution: {integrity: sha512-7A7Ay5vSLBer2P+Ydbrl9AktE7geIlh396zB/3a9odwI/1GT89Pljzvjz5SVxG0e17+wMOPLRKnTnCzukL1B9Q==}
peerDependencies:
drizzle-orm: '>=0.44.0'
'@proj-airi/duckdb-wasm@0.5.0':
resolution: {integrity: sha512-kI29wDhmgWXQ7dpbBNA2bSK/S4RXBO54hRqOQR8SQPnQB8F2010leUmvdf7kjtFgvIacfAMc0L1BxaOMmnA7ig==}
peerDependencies:
@@ -10160,14 +10137,6 @@ packages:
'@proj-airi/unocss-preset-chromatic@1.1.1':
resolution: {integrity: sha512-nZThnWRpxBOQwuJjLq60QKmiuAbhjuqF9CjwnxqXs1d0IeywP/0aVlmnfSoUkTqkmrHH7hqoEOy2i4W2tLMmZg==}
'@proj-airi/unplugin-drizzle-orm-migrations@0.1.6':
resolution: {integrity: sha512-YBlxafvxFLZJxlUm1QDSLdY7Hy1K71OAkg29bzeuzau/VZc1csUp75b9Fdb/RhtZtP2G3WQh8riMho4/gDoWMQ==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
peerDependenciesMeta:
vite:
optional: true
'@proj-airi/unplugin-fetch@0.2.3':
resolution: {integrity: sha512-90I6CfbC7xMSm9/0gl+DJvHmg2Hw9E/7L5rtfXpqMmrRQqd/jhfD1Sb5LjywqTZ3rlFfnb39d051yFGeYigi9A==}
peerDependencies:
@@ -19440,46 +19409,6 @@ packages:
vue: ^3.2.4
vue-router: ^4.0.11
vite@6.4.2:
resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vite@7.3.2:
resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -24839,11 +24768,6 @@ snapshots:
transitivePeerDependencies:
- '@75lb/nature'
'@proj-airi/drizzle-orm-browser-migrator@0.1.6(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.4)(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.20.0)(better-sqlite3@12.5.0)(kysely@0.29.4)(pg@8.20.0)(postgres@3.4.9))':
dependencies:
'@guiiai/logg': 1.2.11
drizzle-orm: 0.45.2(@electric-sql/pglite@0.4.4)(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.20.0)(better-sqlite3@12.5.0)(kysely@0.29.4)(pg@8.20.0)(postgres@3.4.9)
'@proj-airi/duckdb-wasm@0.5.0(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.4)(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.20.0)(better-sqlite3@12.5.0)(kysely@0.29.4)(pg@8.20.0)(postgres@3.4.9))(web-worker@1.5.0)':
dependencies:
'@date-fns/tz': 1.4.1
@@ -24869,16 +24793,6 @@ snapshots:
'@proj-airi/unocss-preset-chromatic@1.1.1': {}
'@proj-airi/unplugin-drizzle-orm-migrations@0.1.6(magicast@0.5.2)(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
c12: 3.3.3(magicast@0.5.2)
uncrypto: 0.1.3
unplugin: 2.3.11
optionalDependencies:
vite: 6.4.2(@types/node@25.6.0)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
transitivePeerDependencies:
- magicast
'@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.7.0)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
ofetch: 1.5.1
@@ -35914,25 +35828,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.10
rollup: 4.60.1
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 25.6.0
fsevents: 2.3.3
jiti: 2.7.0
less: 4.6.4
lightningcss: 1.32.0
terser: 5.46.1
tsx: 4.21.0
yaml: 2.8.3
optional: true
vite@7.3.2(@types/node@24.12.2)(jiti@2.7.0)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
esbuild: 0.27.2
-2
View File
@@ -163,11 +163,9 @@ catalog:
'@pnpm/find-workspace-dir': ^1000.1.5
'@proj-airi/chromatic': ^1.1.1
'@proj-airi/drizzle-duckdb-wasm': ^0.5.0
'@proj-airi/drizzle-orm-browser-migrator': ^0.1.6
'@proj-airi/iconify-meteocons': ^0.1.5
'@proj-airi/lobe-icons': ^1.0.19
'@proj-airi/unocss-preset-chromatic': ^1.1.1
'@proj-airi/unplugin-drizzle-orm-migrations': ^0.1.6
'@proj-airi/unplugin-fetch': ^0.2.3
'@proj-airi/unplugin-live2d-sdk': ^0.1.7
'@radix-ui/colors': ^3.0.0
-1
View File
@@ -9,7 +9,6 @@ while production deployment configuration remains in `proj-airi/airi-railway`.
- `apps/api`: resource API, business domains, database migrations, and API runtime.
- `apps/auth`: standalone Better Auth and OIDC service.
- `packages/auth-shared`: Auth-owned database schema and principal contracts.
- `packages/drizzle-migration`: bundled migration history consumed by the API migration owner.
- `dev/caddy`: local-only public edge routing for the shared Auth/API origin.
- `docker-compose.yaml`: complete local API + Auth + PostgreSQL + Redis + Caddy stack.
+1 -1
View File
@@ -40,7 +40,7 @@ Local observability is maintained in `proj-airi/airi-railway`; run its `otel/doc
**Layering**:
- **Routes** (`src/routes/`): thin — param validation (Valibot), auth guards, error mapping. No business logic here.
- **Services** (`src/services/`): core business logic and DB transactions.
- **Schemas** (`src/schemas/`): Drizzle table definitions. Migrations in `@proj-airi/drizzle-migration`.
- **Schemas** (`src/schemas/`): Drizzle table definitions. Drizzle loads migrations from `drizzle/` at startup.
**Middleware chain** (`/api/*`): CORS → hono/logger → optional otel → sessionMiddleware → bodyLimit(1MB) → per-route guards. WebSocket `/ws/chat` registered before bodyLimit.
-11
View File
@@ -10,22 +10,11 @@ COPY pnpm-lock.yaml pnpm-workspace.yaml package.json tsconfig.json ./
COPY patches/ ./patches/
COPY server/apps/api server/apps/api
COPY server/packages/auth-shared server/packages/auth-shared
COPY server/packages/drizzle-migration server/packages/drizzle-migration
COPY packages/server-sdk-shared packages/server-sdk-shared
RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \
pnpm install --frozen-lockfile --ignore-scripts
# NOTICE:
# The Rolldown adapter of unplugin-drizzle-orm-migrations intermittently left
# the virtual import in the output on Railway. The Rollup adapter is compatible
# with tsdown and inlines the migration data. Import the output to verify the
# runtime contract. Do not check a generated chunk name because adapters use
# different output layouts.
RUN rm -rf server/packages/drizzle-migration/dist \
&& pnpm -F @proj-airi/drizzle-migration run build \
&& node --input-type=module -e "import('./server/packages/drizzle-migration/dist/index.mjs').then(({ migrations }) => { if (!Array.isArray(migrations) || migrations.length === 0) throw new Error('drizzle-migration did not export migrations') })"
RUN pnpm -F @proj-airi/server-sdk-shared run build
RUN pnpm -F @proj-airi/api-server run build
+1 -1
View File
@@ -8,7 +8,7 @@ auth/OIDC routes.
- Hono business APIs and WebSocket endpoints.
- Characters, chats, providers, Flux, Stripe, model routing, and billing.
- PostgreSQL migration ownership for the currently shared database.
- PostgreSQL migration ownership for the currently shared database. Drizzle reads the checked-in `drizzle/` journal and SQL files at startup.
- Redis cache, configuration KV, and cross-instance Pub/Sub.
- Local verification of Auth-issued OIDC JWTs through public JWKS.
-2
View File
@@ -44,8 +44,6 @@
"@opentelemetry/sdk-trace-node": "catalog:",
"@opentelemetry/semantic-conventions": "catalog:",
"@proj-airi/auth-shared": "workspace:*",
"@proj-airi/drizzle-migration": "workspace:*",
"@proj-airi/drizzle-orm-browser-migrator": "catalog:",
"@proj-airi/server-sdk-shared": "workspace:*",
"drizzle-orm": "catalog:",
"drizzle-valibot": "catalog:",
@@ -10,21 +10,10 @@ COPY pnpm-lock.yaml pnpm-workspace.yaml package.json tsconfig.json ./
COPY patches/ ./patches/
COPY server/apps/api server/apps/api
COPY server/packages/auth-shared server/packages/auth-shared
COPY server/packages/drizzle-migration server/packages/drizzle-migration
COPY packages/server-sdk-shared packages/server-sdk-shared
RUN pnpm install --frozen-lockfile --ignore-scripts
# NOTICE:
# The Rolldown adapter of unplugin-drizzle-orm-migrations intermittently left
# the virtual import in the output on Railway. The Rollup adapter is compatible
# with tsdown and inlines the migration data. Import the output to verify the
# runtime contract. Do not check a generated chunk name because adapters use
# different output layouts.
RUN rm -rf server/packages/drizzle-migration/dist \
&& pnpm -F @proj-airi/drizzle-migration run build \
&& node --input-type=module -e "import('./server/packages/drizzle-migration/dist/index.mjs').then(({ migrations }) => { if (!Array.isArray(migrations) || migrations.length === 0) throw new Error('drizzle-migration did not export migrations') })"
RUN pnpm -F @proj-airi/server-sdk-shared run build
RUN pnpm -F @proj-airi/api-server run build
-1
View File
@@ -4,7 +4,6 @@ dockerfilePath = "/server/apps/api/production/railway/Dockerfile"
watchPatterns = [
"server/apps/api/**",
"server/packages/auth-shared/**",
"server/packages/drizzle-migration/**",
"packages/server-sdk-shared/**",
"package.json",
"pnpm-lock.yaml",
+6 -4
View File
@@ -1,15 +1,17 @@
import type { Env } from './env'
import { fileURLToPath } from 'node:url'
import pg from 'pg'
import { useLogger } from '@guiiai/logg'
import { migrations } from '@proj-airi/drizzle-migration'
import { migrate } from '@proj-airi/drizzle-orm-browser-migrator/pg'
import { drizzle } from 'drizzle-orm/node-postgres'
import { migrate } from 'drizzle-orm/node-postgres/migrator'
import * as fullSchema from '../schemas'
const logger = useLogger('db')
const migrationsFolder = fileURLToPath(new URL('../../drizzle', import.meta.url))
export type Database = ReturnType<typeof createDrizzle>['db']
@@ -36,6 +38,6 @@ export function createDrizzle(env: DrizzleEnv) {
return { db, pool }
}
export function migrateDatabase(db: Database) {
return migrate(db, migrations)
export async function migrateDatabase(db: Database) {
await migrate(db, { migrationsFolder })
}
+1 -1
View File
@@ -63,4 +63,4 @@ service contract.
- Importing modules from `server/apps/api`.
- Running the shared database migration history during normal process startup.
Auth tables and principal contracts live in `@proj-airi/auth-shared`. The existing `@proj-airi/drizzle-migration` build remains the migration owner while both applications share one PostgreSQL database.
Auth tables and principal contracts live in `@proj-airi/auth-shared`. Drizzle reads shared migration files during API startup. The API remains the migration owner.
+2 -2
View File
@@ -168,8 +168,8 @@ export async function createAuthServer() {
try {
await candidate.db.execute('SELECT 1')
logger.log(`Connected to database on attempt ${attempt}`)
// The drizzle-migration build owns the shared database history.
// Auth startup only checks connectivity and never races migrations.
// The API loads the shared migration history during its startup.
// Auth only checks connectivity and never races migrations.
return candidate
}
catch (error) {
@@ -1,13 +0,0 @@
# `@proj-airi/drizzle-migration`
Build-time package that compiles the SQL migrations from `server/apps/api/drizzle` into an importable module for the API runtime.
## Usage
The API imports the generated `migrations` value and applies it through the Drizzle browser migrator. Build the package from the repository root:
```sh
pnpm -F @proj-airi/drizzle-migration build
```
Use this package for API-owned Drizzle migration bundling. Runtime schemas remain in `server/apps/api/src/schemas`; shared browser or SDK contracts do not belong here.
@@ -1,31 +0,0 @@
{
"name": "@proj-airi/drizzle-migration",
"type": "module",
"version": "0.11.3",
"private": true,
"description": "Drizzle migration for AIRI",
"author": {
"name": "Moeru AI Project AIRI Team",
"email": "airi@moeru.ai",
"url": "https://github.com/moeru-ai"
},
"exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
},
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
"README.md",
"dist",
"package.json"
],
"scripts": {
"build": "tsdown"
},
"devDependencies": {
"@proj-airi/unplugin-drizzle-orm-migrations": "catalog:"
}
}
@@ -1 +0,0 @@
export { default as migrations } from 'virtual:drizzle-migrations.sql'
@@ -1,21 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": [
"ESNext"
],
"module": "ESNext",
"moduleResolution": "bundler",
"types": [
"@proj-airi/unplugin-drizzle-orm-migrations/types"
],
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
},
"include": [
"src/**/*.ts"
]
}
@@ -1,18 +0,0 @@
import DrizzleORMMigrations from '@proj-airi/unplugin-drizzle-orm-migrations/rollup'
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: [
'src/index.ts',
],
dts: true,
sourcemap: true,
unused: true,
fixedExtension: true,
plugins: [
DrizzleORMMigrations({
root: '../../apps/api',
}),
],
})
@@ -1,7 +0,0 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
},
})
-3
View File
@@ -5,9 +5,6 @@
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"@proj-airi/drizzle-migration#build": {
"cache": false
},
"@proj-airi/electron-vueuse#build": {
"dependsOn": ["@proj-airi/electron-eventa#build"],
"outputs": ["dist/**"]