fix: wire database and repositories into server startup for tRPC context
The server never created a database or instantiated repositories, so all tRPC procedures requiring initiativeRepository (and other repos) threw "Initiative repository not available" on every request. - Create ensureSchema() for shared database table initialization - Extend TrpcAdapterOptions to accept all repository/manager dependencies - Add ServerContextDeps to CoordinationServer for dependency injection - Wire database, schema, and repositories in CLI startServer() - Refactor test-helpers to use shared ensureSchema()
This commit is contained in:
@@ -13,6 +13,15 @@ import { ProcessManager, ProcessRegistry } from '../process/index.js';
|
||||
import { LogManager } from '../logging/index.js';
|
||||
import { createEventBus } from '../events/index.js';
|
||||
import { createDefaultTrpcClient } from './trpc-client.js';
|
||||
import {
|
||||
createDatabase,
|
||||
ensureSchema,
|
||||
DrizzleInitiativeRepository,
|
||||
DrizzlePhaseRepository,
|
||||
DrizzlePlanRepository,
|
||||
DrizzleTaskRepository,
|
||||
DrizzleMessageRepository,
|
||||
} from '../db/index.js';
|
||||
|
||||
/** Environment variable for custom port */
|
||||
const CW_PORT_ENV = 'CW_PORT';
|
||||
@@ -32,12 +41,30 @@ async function startServer(port?: number): Promise<void> {
|
||||
const processManager = new ProcessManager(registry, eventBus);
|
||||
const logManager = new LogManager();
|
||||
|
||||
// Create database and ensure schema
|
||||
const db = createDatabase();
|
||||
ensureSchema(db);
|
||||
|
||||
// Create repositories
|
||||
const initiativeRepository = new DrizzleInitiativeRepository(db);
|
||||
const phaseRepository = new DrizzlePhaseRepository(db);
|
||||
const planRepository = new DrizzlePlanRepository(db);
|
||||
const taskRepository = new DrizzleTaskRepository(db);
|
||||
const messageRepository = new DrizzleMessageRepository(db);
|
||||
|
||||
// Create and start server
|
||||
const server = new CoordinationServer(
|
||||
{ port: serverPort },
|
||||
processManager,
|
||||
logManager,
|
||||
eventBus
|
||||
eventBus,
|
||||
{
|
||||
initiativeRepository,
|
||||
phaseRepository,
|
||||
planRepository,
|
||||
taskRepository,
|
||||
messageRepository,
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
123
src/db/ensure-schema.ts
Normal file
123
src/db/ensure-schema.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Database Schema Initialization
|
||||
*
|
||||
* Ensures all required tables exist in the database.
|
||||
* Uses CREATE TABLE IF NOT EXISTS so it's safe to call multiple times.
|
||||
*/
|
||||
|
||||
import type { DrizzleDatabase } from './index.js';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
/**
|
||||
* Individual CREATE TABLE statements for each table.
|
||||
* Each must be a single statement for drizzle-orm compatibility.
|
||||
* These mirror the schema defined in schema.ts.
|
||||
*/
|
||||
const TABLE_STATEMENTS = [
|
||||
// Initiatives table
|
||||
`CREATE TABLE IF NOT EXISTS initiatives (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Phases table
|
||||
`CREATE TABLE IF NOT EXISTS phases (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
initiative_id TEXT NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
number INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Phase dependencies table
|
||||
`CREATE TABLE IF NOT EXISTS phase_dependencies (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE,
|
||||
depends_on_phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE,
|
||||
created_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Plans table
|
||||
`CREATE TABLE IF NOT EXISTS plans (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE,
|
||||
number INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Tasks table
|
||||
`CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
type TEXT NOT NULL DEFAULT 'auto',
|
||||
priority TEXT NOT NULL DEFAULT 'medium',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
"order" INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Task dependencies table
|
||||
`CREATE TABLE IF NOT EXISTS task_dependencies (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
depends_on_task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
created_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Agents table
|
||||
`CREATE TABLE IF NOT EXISTS agents (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
||||
session_id TEXT,
|
||||
worktree_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'idle',
|
||||
mode TEXT NOT NULL DEFAULT 'execute' CHECK(mode IN ('execute', 'discuss', 'breakdown', 'decompose')),
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)`,
|
||||
|
||||
// Messages table
|
||||
`CREATE TABLE IF NOT EXISTS messages (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
sender_type TEXT NOT NULL,
|
||||
sender_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
||||
recipient_type TEXT NOT NULL,
|
||||
recipient_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
||||
type TEXT NOT NULL DEFAULT 'info',
|
||||
content TEXT NOT NULL,
|
||||
requires_response INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
parent_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Ensure all database tables exist.
|
||||
*
|
||||
* Uses CREATE TABLE IF NOT EXISTS, so safe to call on every startup.
|
||||
* Must be called before any repository operations on a fresh database.
|
||||
*
|
||||
* @param db - Drizzle database instance
|
||||
*/
|
||||
export function ensureSchema(db: DrizzleDatabase): void {
|
||||
for (const statement of TABLE_STATEMENTS) {
|
||||
db.run(sql.raw(statement));
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ export function createDatabase(path?: string): DrizzleDatabase {
|
||||
// Re-export config utilities
|
||||
export { getDbPath, ensureDbDirectory } from './config.js';
|
||||
|
||||
// Re-export schema initialization
|
||||
export { ensureSchema } from './ensure-schema.js';
|
||||
|
||||
// Re-export schema and types
|
||||
export * from './schema.js';
|
||||
|
||||
|
||||
@@ -8,107 +8,9 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import type { DrizzleDatabase } from '../../index.js';
|
||||
import { ensureSchema } from '../../ensure-schema.js';
|
||||
import * as schema from '../../schema.js';
|
||||
|
||||
/**
|
||||
* SQL statements to create the database schema.
|
||||
* These mirror the schema defined in schema.ts.
|
||||
*/
|
||||
const CREATE_TABLES_SQL = `
|
||||
-- Initiatives table
|
||||
CREATE TABLE IF NOT EXISTS initiatives (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Phases table
|
||||
CREATE TABLE IF NOT EXISTS phases (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
initiative_id TEXT NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
number INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Phase dependencies table
|
||||
CREATE TABLE IF NOT EXISTS phase_dependencies (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE,
|
||||
depends_on_phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Plans table
|
||||
CREATE TABLE IF NOT EXISTS plans (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE,
|
||||
number INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Tasks table
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
type TEXT NOT NULL DEFAULT 'auto',
|
||||
priority TEXT NOT NULL DEFAULT 'medium',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
"order" INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Task dependencies table
|
||||
CREATE TABLE IF NOT EXISTS task_dependencies (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
depends_on_task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Agents table
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
||||
session_id TEXT,
|
||||
worktree_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'idle',
|
||||
mode TEXT NOT NULL DEFAULT 'execute' CHECK(mode IN ('execute', 'discuss', 'breakdown', 'decompose')),
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Messages table
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
sender_type TEXT NOT NULL,
|
||||
sender_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
||||
recipient_type TEXT NOT NULL,
|
||||
recipient_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
||||
type TEXT NOT NULL DEFAULT 'info',
|
||||
content TEXT NOT NULL,
|
||||
requires_response INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
parent_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
/**
|
||||
* Create an in-memory test database with schema applied.
|
||||
* Returns a fresh Drizzle instance for each call.
|
||||
@@ -119,8 +21,10 @@ export function createTestDatabase(): DrizzleDatabase {
|
||||
// Enable foreign keys
|
||||
sqlite.pragma('foreign_keys = ON');
|
||||
|
||||
// Create all tables
|
||||
sqlite.exec(CREATE_TABLES_SQL);
|
||||
const db = drizzle(sqlite, { schema });
|
||||
|
||||
return drizzle(sqlite, { schema });
|
||||
// Create all tables
|
||||
ensureSchema(db);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,13 @@ import type { ServerConfig, ServerState, HealthResponse, StatusResponse } from '
|
||||
import type { ProcessManager } from '../process/index.js';
|
||||
import type { LogManager } from '../logging/index.js';
|
||||
import type { EventBus, ServerStartedEvent, ServerStoppedEvent } from '../events/index.js';
|
||||
import { createTrpcHandler } from './trpc-adapter.js';
|
||||
import { createTrpcHandler, type TrpcAdapterOptions } from './trpc-adapter.js';
|
||||
|
||||
/**
|
||||
* Optional dependencies for tRPC context.
|
||||
* Passed through to the tRPC adapter for procedure access.
|
||||
*/
|
||||
export type ServerContextDeps = Omit<TrpcAdapterOptions, 'eventBus' | 'serverStartedAt' | 'processCount'>;
|
||||
|
||||
/** Default port for the coordination server */
|
||||
const DEFAULT_PORT = 3847;
|
||||
@@ -38,6 +44,7 @@ export class CoordinationServer {
|
||||
private readonly processManager: ProcessManager;
|
||||
private readonly logManager: LogManager;
|
||||
private readonly eventBus: EventBus | undefined;
|
||||
private readonly contextDeps: ServerContextDeps;
|
||||
private server: Server | null = null;
|
||||
private state: ServerState | null = null;
|
||||
|
||||
@@ -45,7 +52,8 @@ export class CoordinationServer {
|
||||
config: Partial<ServerConfig>,
|
||||
processManager: ProcessManager,
|
||||
logManager: LogManager,
|
||||
eventBus?: EventBus
|
||||
eventBus?: EventBus,
|
||||
contextDeps?: ServerContextDeps
|
||||
) {
|
||||
this.config = {
|
||||
port: config.port ?? DEFAULT_PORT,
|
||||
@@ -55,6 +63,7 @@ export class CoordinationServer {
|
||||
this.processManager = processManager;
|
||||
this.logManager = logManager;
|
||||
this.eventBus = eventBus;
|
||||
this.contextDeps = contextDeps ?? {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,6 +231,7 @@ export class CoordinationServer {
|
||||
eventBus: this.eventBus,
|
||||
serverStartedAt: this.state.startedAt,
|
||||
processCount: this.state.processCount,
|
||||
...this.contextDeps,
|
||||
});
|
||||
|
||||
trpcHandler(req, res).catch((error: Error) => {
|
||||
|
||||
@@ -10,6 +10,13 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
|
||||
import { appRouter, createContext } from '../trpc/index.js';
|
||||
import type { EventBus } from '../events/index.js';
|
||||
import type { AgentManager } from '../agent/types.js';
|
||||
import type { TaskRepository } from '../db/repositories/task-repository.js';
|
||||
import type { MessageRepository } from '../db/repositories/message-repository.js';
|
||||
import type { InitiativeRepository } from '../db/repositories/initiative-repository.js';
|
||||
import type { PhaseRepository } from '../db/repositories/phase-repository.js';
|
||||
import type { PlanRepository } from '../db/repositories/plan-repository.js';
|
||||
import type { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js';
|
||||
import type { CoordinationManager } from '../coordination/types.js';
|
||||
|
||||
/**
|
||||
* Options for creating the tRPC request handler.
|
||||
@@ -23,6 +30,22 @@ export interface TrpcAdapterOptions {
|
||||
processCount: number;
|
||||
/** Agent manager for agent lifecycle operations (optional until full wiring) */
|
||||
agentManager?: AgentManager;
|
||||
/** Task repository for task CRUD operations */
|
||||
taskRepository?: TaskRepository;
|
||||
/** Message repository for agent-user communication */
|
||||
messageRepository?: MessageRepository;
|
||||
/** Initiative repository for initiative CRUD operations */
|
||||
initiativeRepository?: InitiativeRepository;
|
||||
/** Phase repository for phase CRUD operations */
|
||||
phaseRepository?: PhaseRepository;
|
||||
/** Plan repository for plan CRUD operations */
|
||||
planRepository?: PlanRepository;
|
||||
/** Dispatch manager for task queue operations */
|
||||
dispatchManager?: DispatchManager;
|
||||
/** Coordination manager for merge queue operations */
|
||||
coordinationManager?: CoordinationManager;
|
||||
/** Phase dispatch manager for phase queue operations */
|
||||
phaseDispatchManager?: PhaseDispatchManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +108,14 @@ export function createTrpcHandler(options: TrpcAdapterOptions) {
|
||||
serverStartedAt: options.serverStartedAt,
|
||||
processCount: options.processCount,
|
||||
agentManager: options.agentManager,
|
||||
taskRepository: options.taskRepository,
|
||||
messageRepository: options.messageRepository,
|
||||
initiativeRepository: options.initiativeRepository,
|
||||
phaseRepository: options.phaseRepository,
|
||||
planRepository: options.planRepository,
|
||||
dispatchManager: options.dispatchManager,
|
||||
coordinationManager: options.coordinationManager,
|
||||
phaseDispatchManager: options.phaseDispatchManager,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user