@@ -23,6 +23,7 @@ import type { Frame, Event, Anchor } from '../context/index.js';
2323import { logger } from '../monitoring/logger.js' ;
2424import { DatabaseError , ErrorCode , ValidationError } from '../errors/index.js' ;
2525import type { EmbeddingProvider } from './embedding-provider.js' ;
26+ import { FrameDatabase } from '../context/frame-database.js' ;
2627import * as fs from 'fs/promises' ;
2728import * as path from 'path' ;
2829
@@ -150,101 +151,9 @@ export class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
150151 ErrorCode . DB_CONNECTION_FAILED
151152 ) ;
152153
153- this . db . exec ( `
154- CREATE TABLE IF NOT EXISTS frames (
155- frame_id TEXT PRIMARY KEY,
156- run_id TEXT NOT NULL,
157- project_id TEXT NOT NULL,
158- parent_frame_id TEXT REFERENCES frames(frame_id),
159- depth INTEGER NOT NULL DEFAULT 0,
160- type TEXT NOT NULL,
161- name TEXT NOT NULL,
162- state TEXT DEFAULT 'active',
163- inputs TEXT DEFAULT '{}',
164- outputs TEXT DEFAULT '{}',
165- digest_text TEXT,
166- digest_json TEXT DEFAULT '{}',
167- created_at INTEGER DEFAULT (unixepoch()),
168- closed_at INTEGER
169- );
170-
171- CREATE TABLE IF NOT EXISTS events (
172- event_id TEXT PRIMARY KEY,
173- run_id TEXT NOT NULL,
174- frame_id TEXT NOT NULL,
175- seq INTEGER NOT NULL,
176- event_type TEXT NOT NULL,
177- payload TEXT NOT NULL,
178- ts INTEGER DEFAULT (unixepoch()),
179- FOREIGN KEY(frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE
180- );
181-
182- CREATE TABLE IF NOT EXISTS anchors (
183- anchor_id TEXT PRIMARY KEY,
184- frame_id TEXT NOT NULL,
185- project_id TEXT NOT NULL,
186- type TEXT NOT NULL,
187- text TEXT NOT NULL,
188- priority INTEGER DEFAULT 0,
189- created_at INTEGER DEFAULT (unixepoch()),
190- metadata TEXT DEFAULT '{}',
191- FOREIGN KEY(frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE
192- );
193-
194- CREATE TABLE IF NOT EXISTS schema_version (
195- version INTEGER PRIMARY KEY,
196- applied_at INTEGER DEFAULT (unixepoch())
197- );
198-
199- -- Indexes for performance
200- CREATE INDEX IF NOT EXISTS idx_frames_run ON frames(run_id);
201- CREATE INDEX IF NOT EXISTS idx_frames_project ON frames(project_id);
202- CREATE INDEX IF NOT EXISTS idx_frames_parent ON frames(parent_frame_id);
203- CREATE INDEX IF NOT EXISTS idx_frames_state ON frames(state);
204- CREATE INDEX IF NOT EXISTS idx_frames_created ON frames(created_at DESC);
205- CREATE INDEX IF NOT EXISTS idx_events_frame ON events(frame_id);
206- CREATE INDEX IF NOT EXISTS idx_events_seq ON events(frame_id, seq);
207- CREATE INDEX IF NOT EXISTS idx_anchors_frame ON anchors(frame_id);
208-
209- -- Composite index for project-scoped time queries (most common access pattern)
210- CREATE INDEX IF NOT EXISTS idx_frames_project_created ON frames(project_id, created_at DESC);
211-
212- -- Note: frame_embeddings is a vec0 virtual table with frame_id as PRIMARY KEY;
213- -- vec0 handles its own indexing so no CREATE INDEX needed.
214-
215- CREATE TABLE IF NOT EXISTS retrieval_log (
216- id INTEGER PRIMARY KEY AUTOINCREMENT,
217- query_text TEXT NOT NULL,
218- strategy TEXT NOT NULL,
219- results_count INTEGER NOT NULL,
220- top_score REAL,
221- latency_ms INTEGER NOT NULL,
222- result_frame_ids TEXT,
223- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
224- );
225-
226- CREATE INDEX IF NOT EXISTS idx_retrieval_log_created ON retrieval_log(created_at);
227-
228- CREATE TABLE IF NOT EXISTS maintenance_state (
229- key TEXT PRIMARY KEY,
230- value TEXT NOT NULL,
231- updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
232- );
233-
234- CREATE TABLE IF NOT EXISTS project_registry (
235- project_id TEXT PRIMARY KEY,
236- repo_path TEXT NOT NULL,
237- display_name TEXT,
238- db_path TEXT NOT NULL,
239- is_active INTEGER DEFAULT 0,
240- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
241- last_accessed INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
242- );
243- CREATE INDEX IF NOT EXISTS idx_project_registry_active ON project_registry(is_active);
244-
245- -- Set initial schema version if not exists
246- INSERT OR IGNORE INTO schema_version (version) VALUES (1);
247- ` ) ;
154+ // Delegate base table creation to FrameDatabase (single canonical schema source)
155+ const frameDb = new FrameDatabase ( this . db ) ;
156+ frameDb . initSchema ( ) ;
248157
249158 // Migration: add retention_policy column if not exists
250159 try {
@@ -294,6 +203,13 @@ export class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
294203 ) ;
295204 } ;
296205
206+ const hasColumn = ( table : string , column : string ) : boolean => {
207+ const cols = this . db ! . prepare (
208+ `PRAGMA table_info(${ table } )`
209+ ) . all ( ) as Array < { name : string } > ;
210+ return cols . some ( ( c ) => c . name === column ) ;
211+ } ;
212+
297213 const migrateTable = ( table : 'events' | 'anchors' ) => {
298214 const createSql =
299215 table === 'events'
@@ -310,7 +226,7 @@ export class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
310226 : `CREATE TABLE anchors_new (
311227 anchor_id TEXT PRIMARY KEY,
312228 frame_id TEXT NOT NULL,
313- project_id TEXT NOT NULL,
229+ project_id TEXT NOT NULL DEFAULT '' ,
314230 type TEXT NOT NULL,
315231 text TEXT NOT NULL,
316232 priority INTEGER DEFAULT 0,
@@ -319,6 +235,17 @@ export class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
319235 FOREIGN KEY(frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE
320236 );` ;
321237
238+ // For anchors, handle missing project_id column in old schemas
239+ let selectCols : string ;
240+ if ( table === 'anchors' ) {
241+ const hasProjectId = hasColumn ( 'anchors' , 'project_id' ) ;
242+ selectCols = hasProjectId
243+ ? 'anchor_id, frame_id, project_id, type, text, priority, created_at, metadata'
244+ : `anchor_id, frame_id, '${ this . projectId } ' as project_id, type, text, priority, created_at, metadata` ;
245+ } else {
246+ selectCols = 'event_id, run_id, frame_id, seq, event_type, payload, ts' ;
247+ }
248+
322249 const cols =
323250 table === 'events'
324251 ? 'event_id, run_id, frame_id, seq, event_type, payload, ts'
@@ -338,7 +265,7 @@ export class SQLiteAdapter extends FeatureAwareDatabaseAdapter {
338265 this . db ! . exec ( 'BEGIN;' ) ;
339266 this . db ! . exec ( createSql ) ;
340267 this . db ! . prepare (
341- `INSERT INTO ${ table === 'events' ? 'events_new' : 'anchors_new' } (${ cols } ) SELECT ${ cols } FROM ${ table } `
268+ `INSERT INTO ${ table === 'events' ? 'events_new' : 'anchors_new' } (${ cols } ) SELECT ${ selectCols } FROM ${ table } `
342269 ) . run ( ) ;
343270 this . db ! . exec ( `DROP TABLE ${ table } ;` ) ;
344271 this . db ! . exec ( `ALTER TABLE ${ table } _new RENAME TO ${ table } ;` ) ;
0 commit comments