feat: Implement database migration system and update agent/session handling
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { type Client } from '@libsql/client'
|
||||
import { loggerService } from '@logger'
|
||||
import { getResourcePath } from '@main/utils'
|
||||
import { type LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
@@ -29,15 +30,18 @@ export class MigrationService {
|
||||
constructor(db: LibSQLDatabase<typeof schema>, client: Client) {
|
||||
this.db = db
|
||||
this.client = client
|
||||
this.migrationDir = path.join(__dirname, 'drizzle')
|
||||
this.migrationDir = path.join(getResourcePath(), 'database', 'drizzle')
|
||||
}
|
||||
|
||||
async runMigrations(): Promise<void> {
|
||||
try {
|
||||
logger.info('Starting migration check...')
|
||||
|
||||
// Ensure migrations table exists
|
||||
await this.ensureMigrationsTable()
|
||||
const hasMigrationsTable = await this.migrationsTableExists()
|
||||
|
||||
if (!hasMigrationsTable) {
|
||||
logger.info('Migrations table not found; assuming fresh database state')
|
||||
}
|
||||
|
||||
// Read migration journal
|
||||
const journal = await this.readMigrationJournal()
|
||||
@@ -47,7 +51,9 @@ export class MigrationService {
|
||||
}
|
||||
|
||||
// Get applied migrations
|
||||
const appliedMigrations = await this.getAppliedMigrations()
|
||||
const appliedMigrations = hasMigrationsTable
|
||||
? await this.getAppliedMigrations()
|
||||
: []
|
||||
const appliedVersions = new Set(appliedMigrations.map((m) => Number(m.version)))
|
||||
|
||||
const latestAppliedVersion = appliedMigrations.reduce(
|
||||
@@ -82,27 +88,14 @@ export class MigrationService {
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureMigrationsTable(): Promise<void> {
|
||||
private async migrationsTableExists(): Promise<boolean> {
|
||||
try {
|
||||
const tableExists = await this.client.execute(`SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'`)
|
||||
|
||||
if (tableExists.rows.length === 0) {
|
||||
logger.info('Migrations table missing, creating...')
|
||||
|
||||
await this.client.execute(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
tag TEXT NOT NULL,
|
||||
executed_at INTEGER NOT NULL
|
||||
)
|
||||
`)
|
||||
|
||||
logger.info('Migrations table created successfully')
|
||||
} else {
|
||||
logger.debug('Migrations table already exists')
|
||||
}
|
||||
const table = await this.client.execute(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'`
|
||||
)
|
||||
return table.rows.length > 0
|
||||
} catch (error) {
|
||||
logger.error('Failed to ensure migrations table exists:', { error })
|
||||
logger.error('Failed to check migrations table status:', { error })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -147,7 +140,7 @@ export class MigrationService {
|
||||
|
||||
// Read and execute SQL
|
||||
const sqlContent = fs.readFileSync(sqlFilePath, 'utf-8')
|
||||
await this.client.execute(sqlContent)
|
||||
await this.client.executeMultiple(sqlContent)
|
||||
|
||||
// Record migration as applied (store journal idx as version for tracking)
|
||||
const newMigration: NewMigration = {
|
||||
@@ -156,6 +149,10 @@ export class MigrationService {
|
||||
executedAt: Date.now()
|
||||
}
|
||||
|
||||
if (!(await this.migrationsTableExists())) {
|
||||
throw new Error('Migrations table missing after executing migration; cannot record progress')
|
||||
}
|
||||
|
||||
await this.db.insert(migrations).values(newMigration)
|
||||
|
||||
const executionTime = Date.now() - startTime
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
CREATE TABLE `agents` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`description` text,
|
||||
`accessible_paths` text,
|
||||
`instructions` text,
|
||||
`model` text NOT NULL,
|
||||
`plan_model` text,
|
||||
`small_model` text,
|
||||
`mcps` text,
|
||||
`allowed_tools` text,
|
||||
`configuration` text,
|
||||
`created_at` text NOT NULL,
|
||||
`updated_at` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `sessions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`agent_id` text NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`description` text,
|
||||
`accessible_paths` text,
|
||||
`instructions` text,
|
||||
`model` text NOT NULL,
|
||||
`plan_model` text,
|
||||
`small_model` text,
|
||||
`mcps` text,
|
||||
`allowed_tools` text,
|
||||
`configuration` text,
|
||||
`created_at` text NOT NULL,
|
||||
`updated_at` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `session_messages` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`session_id` text NOT NULL,
|
||||
`role` text NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`metadata` text,
|
||||
`created_at` text NOT NULL,
|
||||
`updated_at` text NOT NULL
|
||||
);
|
||||
@@ -1,293 +0,0 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "7ef99575-0fcf-471c-9da7-77e5cf8de6a2",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"agents": {
|
||||
"name": "agents",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"accessible_paths": {
|
||||
"name": "accessible_paths",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"instructions": {
|
||||
"name": "instructions",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"plan_model": {
|
||||
"name": "plan_model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"small_model": {
|
||||
"name": "small_model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"mcps": {
|
||||
"name": "mcps",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"allowed_tools": {
|
||||
"name": "allowed_tools",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"configuration": {
|
||||
"name": "configuration",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"sessions": {
|
||||
"name": "sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"agent_id": {
|
||||
"name": "agent_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"accessible_paths": {
|
||||
"name": "accessible_paths",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"instructions": {
|
||||
"name": "instructions",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"plan_model": {
|
||||
"name": "plan_model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"small_model": {
|
||||
"name": "small_model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"mcps": {
|
||||
"name": "mcps",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"allowed_tools": {
|
||||
"name": "allowed_tools",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"configuration": {
|
||||
"name": "configuration",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"session_messages": {
|
||||
"name": "session_messages",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"session_id": {
|
||||
"name": "session_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1758035192486,
|
||||
"tag": "0000_dry_luke_cage",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,12 +5,11 @@
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { isDev } from '@main/constant'
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
import { app } from 'electron'
|
||||
|
||||
function getDbPath() {
|
||||
if (isDev) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return path.join(os.homedir(), '.cherrystudio', 'data', 'agents.db')
|
||||
}
|
||||
return path.join(app.getPath('userData'), 'agents.db')
|
||||
@@ -23,7 +22,7 @@ export const dbPath = resolvedDbPath
|
||||
export default defineConfig({
|
||||
dialect: 'sqlite',
|
||||
schema: './src/main/services/agents/database/schema/index.ts',
|
||||
out: './src/main/services/agents/database/drizzle',
|
||||
out: './resources/database/drizzle',
|
||||
dbCredentials: {
|
||||
url: `file:${resolvedDbPath}`
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user