@fleettools/squawk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/AGENTS.md +0 -28
- package/dist/src/db/checkpoint-storage.d.ts +0 -19
- package/dist/src/db/checkpoint-storage.d.ts.map +0 -1
- package/dist/src/db/checkpoint-storage.js +0 -355
- package/dist/src/db/checkpoint-storage.js.map +0 -1
- package/dist/src/db/index.d.ts +0 -30
- package/dist/src/db/index.d.ts.map +0 -1
- package/dist/src/db/index.js +0 -329
- package/dist/src/db/index.js.map +0 -1
- package/dist/src/db/sqlite.d.ts +0 -31
- package/dist/src/db/sqlite.d.ts.map +0 -1
- package/dist/src/db/sqlite.js +0 -558
- package/dist/src/db/sqlite.js.map +0 -1
- package/dist/src/db/types.d.ts +0 -611
- package/dist/src/db/types.d.ts.map +0 -1
- package/dist/src/db/types.js +0 -4
- package/dist/src/db/types.js.map +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -285
- package/dist/src/index.js.map +0 -1
- package/dist/src/recovery/checkpointing.d.ts +0 -244
- package/dist/src/recovery/checkpointing.d.ts.map +0 -1
- package/dist/src/recovery/checkpointing.js +0 -511
- package/dist/src/recovery/checkpointing.js.map +0 -1
- package/dist/src/recovery/detection.d.ts +0 -137
- package/dist/src/recovery/detection.d.ts.map +0 -1
- package/dist/src/recovery/detection.js +0 -240
- package/dist/src/recovery/detection.js.map +0 -1
- package/dist/src/recovery/detector.d.ts +0 -34
- package/dist/src/recovery/detector.d.ts.map +0 -1
- package/dist/src/recovery/detector.js +0 -42
- package/dist/src/recovery/detector.js.map +0 -1
- package/dist/src/recovery/index.d.ts +0 -3
- package/dist/src/recovery/index.d.ts.map +0 -1
- package/dist/src/recovery/index.js +0 -3
- package/dist/src/recovery/index.js.map +0 -1
- package/dist/src/recovery/restorer.d.ts +0 -51
- package/dist/src/recovery/restorer.d.ts.map +0 -1
- package/dist/src/recovery/restorer.js +0 -266
- package/dist/src/recovery/restorer.js.map +0 -1
- package/dist/src/schemas.d.ts +0 -142
- package/dist/src/schemas.d.ts.map +0 -1
- package/dist/src/schemas.js +0 -110
- package/dist/src/schemas.js.map +0 -1
- package/src/db/checkpoint-storage.ts +0 -443
- package/src/db/index.d.ts +0 -30
- package/src/db/index.d.ts.map +0 -1
- package/src/db/index.js.map +0 -1
- package/src/db/index.ts +0 -417
- package/src/db/schema.sql +0 -112
- package/src/db/sqlite.d.ts +0 -31
- package/src/db/sqlite.d.ts.map +0 -1
- package/src/db/sqlite.js +0 -667
- package/src/db/sqlite.js.map +0 -1
- package/src/db/sqlite.ts +0 -677
- package/src/db/types.d.ts +0 -612
- package/src/db/types.d.ts.map +0 -1
- package/src/db/types.js +0 -4
- package/src/db/types.js.map +0 -1
- package/src/db/types.ts +0 -771
- package/src/index.ts +0 -332
- package/src/recovery/detector.ts +0 -82
- package/src/recovery/index.ts +0 -3
- package/src/recovery/restorer.ts +0 -377
package/src/db/sqlite.ts
DELETED
|
@@ -1,677 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import Database from 'bun:sqlite';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import type {
|
|
7
|
-
DatabaseAdapter,
|
|
8
|
-
DatabaseStats,
|
|
9
|
-
VersionedInterface,
|
|
10
|
-
MissionOps,
|
|
11
|
-
SortieOps,
|
|
12
|
-
LockOps,
|
|
13
|
-
EventOps,
|
|
14
|
-
CheckpointOps,
|
|
15
|
-
SpecialistOps,
|
|
16
|
-
MessageOps,
|
|
17
|
-
CursorOps,
|
|
18
|
-
Event,
|
|
19
|
-
AppendEventInput,
|
|
20
|
-
EventFilter,
|
|
21
|
-
StreamType,
|
|
22
|
-
Lock,
|
|
23
|
-
Mailbox,
|
|
24
|
-
Cursor
|
|
25
|
-
} from './types.js';
|
|
26
|
-
|
|
27
|
-
export class SQLiteAdapter implements DatabaseAdapter {
|
|
28
|
-
version = '1.0.0' as const;
|
|
29
|
-
|
|
30
|
-
private db: Database | null = null;
|
|
31
|
-
private dbPath: string;
|
|
32
|
-
private schemaPath: string;
|
|
33
|
-
|
|
34
|
-
public missions: MissionOps = {} as MissionOps;
|
|
35
|
-
public sorties: SortieOps = {} as SortieOps;
|
|
36
|
-
public locks: LockOps = {} as LockOps;
|
|
37
|
-
public events: EventOps = {} as EventOps;
|
|
38
|
-
public checkpoints: CheckpointOps = {} as CheckpointOps;
|
|
39
|
-
public specialists: SpecialistOps = {} as SpecialistOps;
|
|
40
|
-
public messages: MessageOps = {} as MessageOps;
|
|
41
|
-
public cursors: CursorOps = {} as CursorOps;
|
|
42
|
-
|
|
43
|
-
constructor(dbPath: string = ':memory:', schemaPath?: string) {
|
|
44
|
-
this.dbPath = dbPath;
|
|
45
|
-
|
|
46
|
-
if (schemaPath) {
|
|
47
|
-
this.schemaPath = schemaPath;
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.schemaPath = this.resolveSchemaPath();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private resolveSchemaPath(): string {
|
|
55
|
-
const possiblePaths: string[] = [];
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const __filename = new URL('', import.meta.url).pathname;
|
|
59
|
-
const __dirname = path.dirname(__filename);
|
|
60
|
-
possiblePaths.push(path.join(__dirname, 'schema.sql'));
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
possiblePaths.push(path.join(process.cwd(), 'squawk', 'src', 'db', 'schema.sql'));
|
|
65
|
-
|
|
66
|
-
const projectRoot = process.cwd();
|
|
67
|
-
const modulePaths = [
|
|
68
|
-
path.join(projectRoot, 'src', 'db', 'schema.sql'),
|
|
69
|
-
path.join(projectRoot, 'db', 'schema.sql'),
|
|
70
|
-
path.join(projectRoot, 'lib', 'db', 'schema.sql'),
|
|
71
|
-
];
|
|
72
|
-
possiblePaths.push(...modulePaths);
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const stack = new Error().stack;
|
|
76
|
-
if (stack) {
|
|
77
|
-
const match = stack.match(/at.*\((.*):.*\)/);
|
|
78
|
-
if (match && match[1]) {
|
|
79
|
-
const callerDir = path.dirname(match[1]);
|
|
80
|
-
possiblePaths.push(path.join(callerDir, 'schema.sql'));
|
|
81
|
-
possiblePaths.push(path.join(callerDir, '..', 'src', 'db', 'schema.sql'));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const candidatePath of possiblePaths) {
|
|
88
|
-
if (fs.existsSync(candidatePath)) {
|
|
89
|
-
return candidatePath;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return possiblePaths[0] || path.join(process.cwd(), 'squawk', 'src', 'db', 'schema.sql');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async initialize(): Promise<void> {
|
|
97
|
-
try {
|
|
98
|
-
this.db = new Database(this.dbPath);
|
|
99
|
-
|
|
100
|
-
if (this.dbPath !== ':memory:') {
|
|
101
|
-
this.db!.exec('PRAGMA journal_mode = WAL');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
this.db!.exec('PRAGMA foreign_keys = ON');
|
|
105
|
-
|
|
106
|
-
if (fs.existsSync(this.schemaPath)) {
|
|
107
|
-
const schema = fs.readFileSync(this.schemaPath, 'utf-8');
|
|
108
|
-
this.db!.exec(schema);
|
|
109
|
-
} else {
|
|
110
|
-
throw new Error(`Schema file not found: ${this.schemaPath}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
this.initializeOperations();
|
|
114
|
-
|
|
115
|
-
console.log(`SQLite database initialized: ${this.dbPath}`);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error('Failed to initialize database:', error);
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private initializeOperations(): void {
|
|
123
|
-
if (!this.db) {
|
|
124
|
-
throw new Error('Database not initialized');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// TypeScript workaround for property access
|
|
128
|
-
const adapter = this as any;
|
|
129
|
-
|
|
130
|
-
adapter.mailboxes = {
|
|
131
|
-
version: '1.0.0',
|
|
132
|
-
|
|
133
|
-
create: async (input: any): Promise<any> => {
|
|
134
|
-
const now = new Date().toISOString();
|
|
135
|
-
const createdAt = input.created_at || now;
|
|
136
|
-
const updatedAt = input.updated_at || now;
|
|
137
|
-
this.db!.prepare(`
|
|
138
|
-
INSERT INTO mailboxes (id, created_at, updated_at)
|
|
139
|
-
VALUES (?, ?, ?)
|
|
140
|
-
`).run(input.id, createdAt, updatedAt);
|
|
141
|
-
return { id: input.id, created_at: createdAt, updated_at: updatedAt };
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
getById: async (id: string): Promise<any | null> => {
|
|
145
|
-
const row = this.db!.prepare(`
|
|
146
|
-
SELECT * FROM mailboxes WHERE id = ?
|
|
147
|
-
`).get(id) as any;
|
|
148
|
-
return row || null;
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
getAll: async (): Promise<any[]> => {
|
|
152
|
-
const rows = this.db!.prepare(`
|
|
153
|
-
SELECT * FROM mailboxes ORDER BY created_at DESC
|
|
154
|
-
`).all() as any[];
|
|
155
|
-
return rows;
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
update: async (id: string, data: any): Promise<any | null> => {
|
|
159
|
-
const now = new Date().toISOString();
|
|
160
|
-
const result = this.db!.prepare(`
|
|
161
|
-
UPDATE mailboxes
|
|
162
|
-
SET updated_at = COALESCE(?, updated_at)
|
|
163
|
-
WHERE id = ?
|
|
164
|
-
`).run(now, id);
|
|
165
|
-
if (result.changes === 0) return null;
|
|
166
|
-
return await (this as any).mailboxes.getById(id);
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
delete: async (id: string): Promise<boolean> => {
|
|
170
|
-
const result = this.db!.prepare(`
|
|
171
|
-
DELETE FROM mailboxes WHERE id = ?
|
|
172
|
-
`).run(id);
|
|
173
|
-
return result.changes > 0;
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
this.cursors = {
|
|
178
|
-
version: '1.0.0',
|
|
179
|
-
|
|
180
|
-
create: async (input: any): Promise<any> => {
|
|
181
|
-
const now = new Date().toISOString();
|
|
182
|
-
this.db!.prepare(`
|
|
183
|
-
INSERT INTO cursors (id, stream_id, position, consumer_id, updated_at)
|
|
184
|
-
VALUES (?, ?, ?, ?, ?)
|
|
185
|
-
`).run(input.id, input.stream_id, input.position, input.consumer_id, now);
|
|
186
|
-
return { id: input.id, stream_id: input.stream_id, position: input.position, consumer_id: input.consumer_id, updated_at: now };
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
getById: async (id: string): Promise<any | null> => {
|
|
190
|
-
const row = this.db!.prepare(`
|
|
191
|
-
SELECT * FROM cursors WHERE id = ?
|
|
192
|
-
`).get(id) as any;
|
|
193
|
-
return row || null;
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
getByStream: async (streamId: string): Promise<any | null> => {
|
|
197
|
-
const row = this.db!.prepare(`
|
|
198
|
-
SELECT * FROM cursors WHERE stream_id = ?
|
|
199
|
-
`).get(streamId) as any;
|
|
200
|
-
return row || null;
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
advance: async (streamId: string, position: number): Promise<any> => {
|
|
204
|
-
const now = new Date().toISOString();
|
|
205
|
-
this.db!.prepare(`
|
|
206
|
-
INSERT INTO cursors (id, stream_id, position, updated_at)
|
|
207
|
-
VALUES (?, ?, ?, ?)
|
|
208
|
-
ON CONFLICT(stream_id) DO UPDATE SET
|
|
209
|
-
position = excluded.position,
|
|
210
|
-
updated_at = excluded.updated_at
|
|
211
|
-
`).run(`${streamId}_cursor`, streamId, position, now);
|
|
212
|
-
return await adapter.cursors.getByStream(streamId);
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
update: async (id: string, data: any): Promise<any> => {
|
|
216
|
-
const now = new Date().toISOString();
|
|
217
|
-
this.db!.prepare(`
|
|
218
|
-
UPDATE cursors
|
|
219
|
-
SET position = COALESCE(?, position),
|
|
220
|
-
updated_at = COALESCE(?, updated_at)
|
|
221
|
-
WHERE id = ?
|
|
222
|
-
`).run(data.position, now, id);
|
|
223
|
-
return await adapter.cursors.getById(id);
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
getAll: async (filter?: any): Promise<any[]> => {
|
|
227
|
-
let query = 'SELECT * FROM cursors';
|
|
228
|
-
const params: any[] = [];
|
|
229
|
-
|
|
230
|
-
if (filter?.stream_type) {
|
|
231
|
-
query += ' WHERE stream_id LIKE ?';
|
|
232
|
-
params.push(`${filter.stream_type}_%`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
query += ' ORDER BY updated_at DESC';
|
|
236
|
-
|
|
237
|
-
const rows = this.db!.prepare(query).all(...params) as any[];
|
|
238
|
-
return rows;
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
this.locks = {
|
|
243
|
-
version: '1.0.0',
|
|
244
|
-
|
|
245
|
-
acquire: async (input: any): Promise<any> => {
|
|
246
|
-
const id = `lock_${Math.random().toString(36).substring(2, 10)}`;
|
|
247
|
-
const now = new Date().toISOString();
|
|
248
|
-
const expiresAt = new Date(Date.now() + input.timeout_ms).toISOString();
|
|
249
|
-
|
|
250
|
-
const existing = this.db!.prepare(`
|
|
251
|
-
SELECT * FROM locks
|
|
252
|
-
WHERE file = ? AND released_at IS NULL AND expires_at > datetime('now')
|
|
253
|
-
`).get(input.file) as any;
|
|
254
|
-
|
|
255
|
-
if (existing) {
|
|
256
|
-
return {
|
|
257
|
-
conflict: true,
|
|
258
|
-
existing_lock: existing
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
this.db!.prepare(`
|
|
263
|
-
INSERT INTO locks (id, file, reserved_by, reserved_at, purpose, timeout_ms, checksum, expires_at, metadata)
|
|
264
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
265
|
-
`).run(id, input.file, input.specialist_id, now, input.purpose || 'edit', input.timeout_ms, input.checksum, expiresAt, JSON.stringify(input.metadata || {}));
|
|
266
|
-
|
|
267
|
-
const lock = await adapter.locks.getById(id);
|
|
268
|
-
return {
|
|
269
|
-
conflict: false,
|
|
270
|
-
lock: {
|
|
271
|
-
...lock,
|
|
272
|
-
status: 'active',
|
|
273
|
-
expires_at: expiresAt,
|
|
274
|
-
normalized_path: input.file
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
},
|
|
278
|
-
|
|
279
|
-
release: async (id: string): Promise<boolean> => {
|
|
280
|
-
const result = this.db!.prepare(`
|
|
281
|
-
UPDATE locks
|
|
282
|
-
SET released_at = datetime('now'),
|
|
283
|
-
status = 'released'
|
|
284
|
-
WHERE id = ?
|
|
285
|
-
`).run(id);
|
|
286
|
-
return result.changes > 0;
|
|
287
|
-
},
|
|
288
|
-
|
|
289
|
-
getById: async (id: string): Promise<any | null> => {
|
|
290
|
-
const row = this.db!.prepare(`
|
|
291
|
-
SELECT * FROM locks WHERE id = ?
|
|
292
|
-
`).get(id) as any;
|
|
293
|
-
return row || null;
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
getByFile: async (file: string): Promise<any | null> => {
|
|
297
|
-
const row = this.db!.prepare(`
|
|
298
|
-
SELECT * FROM locks
|
|
299
|
-
WHERE file = ? AND released_at IS NULL AND expires_at > datetime('now')
|
|
300
|
-
`).get(file) as any;
|
|
301
|
-
return row || null;
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
getActive: async (): Promise<any[]> => {
|
|
305
|
-
const rows = this.db!.prepare(`
|
|
306
|
-
SELECT * FROM locks
|
|
307
|
-
WHERE released_at IS NULL AND expires_at > datetime('now')
|
|
308
|
-
`).all() as any[];
|
|
309
|
-
return rows.map((row: any) => ({
|
|
310
|
-
...row,
|
|
311
|
-
status: 'active',
|
|
312
|
-
normalized_path: row.file
|
|
313
|
-
}));
|
|
314
|
-
},
|
|
315
|
-
|
|
316
|
-
getAll: async (): Promise<any[]> => {
|
|
317
|
-
const rows = this.db!.prepare(`
|
|
318
|
-
SELECT * FROM locks ORDER BY reserved_at DESC
|
|
319
|
-
`).all() as any[];
|
|
320
|
-
return rows;
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
forceRelease: async (id: string): Promise<boolean> => {
|
|
324
|
-
const result = this.db!.prepare(`
|
|
325
|
-
UPDATE locks
|
|
326
|
-
SET released_at = datetime('now'),
|
|
327
|
-
status = 'force_released'
|
|
328
|
-
WHERE id = ?
|
|
329
|
-
`).run(id);
|
|
330
|
-
return result.changes > 0;
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
releaseExpired: async (): Promise<number> => {
|
|
334
|
-
const result = this.db!.prepare(`
|
|
335
|
-
UPDATE locks
|
|
336
|
-
SET released_at = datetime('now'),
|
|
337
|
-
status = 'expired'
|
|
338
|
-
WHERE released_at IS NULL
|
|
339
|
-
AND timeout_ms IS NOT NULL
|
|
340
|
-
AND expires_at IS NOT NULL
|
|
341
|
-
AND expires_at < datetime('now')
|
|
342
|
-
`).run();
|
|
343
|
-
return result.changes;
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
this.events = {
|
|
348
|
-
version: '1.0.0',
|
|
349
|
-
|
|
350
|
-
append: async (input: AppendEventInput): Promise<Event> => {
|
|
351
|
-
const eventId = `evt_${Math.random().toString(36).substring(2, 10)}`;
|
|
352
|
-
|
|
353
|
-
const lastSeq = this.db!.prepare(`
|
|
354
|
-
SELECT MAX(sequence_number) as last_seq
|
|
355
|
-
FROM events
|
|
356
|
-
WHERE stream_type = ? AND stream_id = ?
|
|
357
|
-
`).get(input.stream_type, input.stream_id) as { last_seq: number | null } | undefined;
|
|
358
|
-
|
|
359
|
-
const sequenceNumber = (lastSeq?.last_seq || 0) + 1;
|
|
360
|
-
|
|
361
|
-
const now = new Date().toISOString();
|
|
362
|
-
|
|
363
|
-
let mailboxId = input.stream_id;
|
|
364
|
-
const existingMailbox = this.db!.prepare(`
|
|
365
|
-
SELECT id FROM mailboxes WHERE id = ?
|
|
366
|
-
`).get(input.stream_id);
|
|
367
|
-
|
|
368
|
-
if (!existingMailbox) {
|
|
369
|
-
mailboxId = `mbx_${input.stream_type}_${input.stream_id}`;
|
|
370
|
-
this.db!.prepare(`
|
|
371
|
-
INSERT OR IGNORE INTO mailboxes (id, created_at, updated_at)
|
|
372
|
-
VALUES (?, ?, ?)
|
|
373
|
-
`).run(mailboxId, now, now);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
this.db!.prepare(`
|
|
377
|
-
INSERT INTO events (
|
|
378
|
-
id, mailbox_id, type, stream_type, stream_id, sequence_number,
|
|
379
|
-
data, occurred_at, causation_id, correlation_id, metadata
|
|
380
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
381
|
-
` ).run(
|
|
382
|
-
eventId,
|
|
383
|
-
mailboxId,
|
|
384
|
-
input.event_type,
|
|
385
|
-
input.stream_type,
|
|
386
|
-
input.stream_id,
|
|
387
|
-
sequenceNumber,
|
|
388
|
-
JSON.stringify(input.data),
|
|
389
|
-
input.occurred_at || now,
|
|
390
|
-
input.causation_id || null,
|
|
391
|
-
input.correlation_id || null,
|
|
392
|
-
JSON.stringify(input.metadata || {})
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
return {
|
|
396
|
-
sequence_number: sequenceNumber,
|
|
397
|
-
event_id: eventId,
|
|
398
|
-
event_type: input.event_type,
|
|
399
|
-
stream_type: input.stream_type,
|
|
400
|
-
stream_id: input.stream_id,
|
|
401
|
-
data: input.data,
|
|
402
|
-
causation_id: input.causation_id,
|
|
403
|
-
correlation_id: input.correlation_id,
|
|
404
|
-
metadata: input.metadata,
|
|
405
|
-
occurred_at: input.occurred_at || now,
|
|
406
|
-
recorded_at: now,
|
|
407
|
-
schema_version: input.schema_version || 1
|
|
408
|
-
};
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
queryByStream: async (streamType: string, streamId: string, afterSequence?: number): Promise<Event[]> => {
|
|
412
|
-
const query = `
|
|
413
|
-
SELECT * FROM events
|
|
414
|
-
WHERE stream_type = ? AND stream_id = ?
|
|
415
|
-
${afterSequence ? 'AND sequence_number > ?' : ''}
|
|
416
|
-
ORDER BY sequence_number ASC
|
|
417
|
-
`;
|
|
418
|
-
|
|
419
|
-
const params: any[] = [streamType, streamId];
|
|
420
|
-
if (afterSequence !== undefined) params.push(afterSequence);
|
|
421
|
-
|
|
422
|
-
const rows = this.db!.prepare(query).all(...params) as any[];
|
|
423
|
-
|
|
424
|
-
return rows.map(row => ({
|
|
425
|
-
sequence_number: row.sequence_number,
|
|
426
|
-
event_id: row.id,
|
|
427
|
-
event_type: row.type,
|
|
428
|
-
stream_type: row.stream_type,
|
|
429
|
-
stream_id: row.stream_id,
|
|
430
|
-
data: JSON.parse(row.data),
|
|
431
|
-
causation_id: row.causation_id,
|
|
432
|
-
correlation_id: row.correlation_id,
|
|
433
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
434
|
-
occurred_at: row.occurred_at,
|
|
435
|
-
recorded_at: row.occurred_at,
|
|
436
|
-
schema_version: 1
|
|
437
|
-
}));
|
|
438
|
-
},
|
|
439
|
-
|
|
440
|
-
queryByType: async (eventType: string): Promise<Event[]> => {
|
|
441
|
-
const rows = this.db!.prepare(`
|
|
442
|
-
SELECT * FROM events WHERE type = ? ORDER BY occurred_at ASC
|
|
443
|
-
`).all(eventType) as any[];
|
|
444
|
-
|
|
445
|
-
return rows.map(row => ({
|
|
446
|
-
sequence_number: row.sequence_number,
|
|
447
|
-
event_id: row.id,
|
|
448
|
-
event_type: row.type,
|
|
449
|
-
stream_type: row.stream_type,
|
|
450
|
-
stream_id: row.stream_id,
|
|
451
|
-
data: JSON.parse(row.data),
|
|
452
|
-
causation_id: row.causation_id,
|
|
453
|
-
correlation_id: row.correlation_id,
|
|
454
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
455
|
-
occurred_at: row.occurred_at,
|
|
456
|
-
recorded_at: row.occurred_at,
|
|
457
|
-
schema_version: 1
|
|
458
|
-
}));
|
|
459
|
-
},
|
|
460
|
-
|
|
461
|
-
getEvents: async (filter: EventFilter): Promise<Event[]> => {
|
|
462
|
-
let query = 'SELECT * FROM events WHERE 1=1';
|
|
463
|
-
const params: any[] = [];
|
|
464
|
-
|
|
465
|
-
if (filter.event_type) {
|
|
466
|
-
if (Array.isArray(filter.event_type)) {
|
|
467
|
-
query += ` AND type IN (${filter.event_type.map(() => '?').join(',')})`;
|
|
468
|
-
params.push(...filter.event_type);
|
|
469
|
-
} else {
|
|
470
|
-
query += ' AND type = ?';
|
|
471
|
-
params.push(filter.event_type);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (filter.stream_type) {
|
|
476
|
-
if (Array.isArray(filter.stream_type)) {
|
|
477
|
-
query += ` AND stream_type IN (${filter.stream_type.map(() => '?').join(',')})`;
|
|
478
|
-
params.push(...filter.stream_type);
|
|
479
|
-
} else {
|
|
480
|
-
query += ' AND stream_type = ?';
|
|
481
|
-
params.push(filter.stream_type);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (filter.stream_id) {
|
|
486
|
-
query += ' AND stream_id = ?';
|
|
487
|
-
params.push(filter.stream_id);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (filter.after_sequence !== undefined) {
|
|
491
|
-
query += ' AND sequence_number > ?';
|
|
492
|
-
params.push(filter.after_sequence);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
query += ' ORDER BY occurred_at ASC';
|
|
496
|
-
|
|
497
|
-
const rows = this.db!.prepare(query).all(...params) as any[];
|
|
498
|
-
|
|
499
|
-
return rows.map(row => ({
|
|
500
|
-
sequence_number: row.sequence_number,
|
|
501
|
-
event_id: row.id,
|
|
502
|
-
event_type: row.type,
|
|
503
|
-
stream_type: row.stream_type,
|
|
504
|
-
stream_id: row.stream_id,
|
|
505
|
-
data: JSON.parse(row.data),
|
|
506
|
-
causation_id: row.causation_id,
|
|
507
|
-
correlation_id: row.correlation_id,
|
|
508
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
509
|
-
occurred_at: row.occurred_at,
|
|
510
|
-
recorded_at: row.occurred_at,
|
|
511
|
-
schema_version: 1
|
|
512
|
-
}));
|
|
513
|
-
},
|
|
514
|
-
|
|
515
|
-
getLatestByStream: async (streamType: string, streamId: string): Promise<any | null> => {
|
|
516
|
-
const row = this.db!.prepare(`
|
|
517
|
-
SELECT * FROM events
|
|
518
|
-
WHERE stream_type = ? AND stream_id = ?
|
|
519
|
-
ORDER BY sequence_number DESC
|
|
520
|
-
LIMIT 1
|
|
521
|
-
`).get(streamType, streamId) as any;
|
|
522
|
-
return row ? {
|
|
523
|
-
sequence_number: row.sequence_number,
|
|
524
|
-
event_id: row.id,
|
|
525
|
-
event_type: row.type,
|
|
526
|
-
stream_type: row.stream_type,
|
|
527
|
-
stream_id: row.stream_id,
|
|
528
|
-
data: JSON.parse(row.data),
|
|
529
|
-
causation_id: row.causation_id,
|
|
530
|
-
correlation_id: row.correlation_id,
|
|
531
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
532
|
-
occurred_at: row.occurred_at,
|
|
533
|
-
recorded_at: row.occurred_at,
|
|
534
|
-
schema_version: 1
|
|
535
|
-
} : null;
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
this.missions = {} as MissionOps;
|
|
540
|
-
this.sorties = {} as SortieOps;
|
|
541
|
-
this.checkpoints = {} as CheckpointOps;
|
|
542
|
-
this.specialists = {} as SpecialistOps;
|
|
543
|
-
this.messages = {} as MessageOps;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async close(): Promise<void> {
|
|
547
|
-
try {
|
|
548
|
-
if (this.db) {
|
|
549
|
-
this.db.close();
|
|
550
|
-
this.db = null;
|
|
551
|
-
console.log(`SQLite database closed: ${this.dbPath}`);
|
|
552
|
-
}
|
|
553
|
-
} catch (error) {
|
|
554
|
-
console.error('Error closing database:', error);
|
|
555
|
-
throw error;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
async isHealthy(): Promise<boolean> {
|
|
560
|
-
try {
|
|
561
|
-
if (!this.db) {
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
this.db.prepare('SELECT 1').get();
|
|
566
|
-
return true;
|
|
567
|
-
} catch {
|
|
568
|
-
return false;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async getStats(): Promise<DatabaseStats> {
|
|
573
|
-
if (!this.db) {
|
|
574
|
-
throw new Error('Database not initialized');
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const eventCount = this.db.prepare('SELECT COUNT(*) as count FROM events').get() as { count: number };
|
|
578
|
-
|
|
579
|
-
const missionCount = 0; // TODO: Implement when mission table exists
|
|
580
|
-
const activeMissionCount = 0; // TODO: Implement
|
|
581
|
-
|
|
582
|
-
const activeLockCount = this.db.prepare(
|
|
583
|
-
"SELECT COUNT(*) as count FROM locks WHERE released_at IS NULL AND expires_at > datetime('now')"
|
|
584
|
-
).get() as { count: number };
|
|
585
|
-
|
|
586
|
-
const checkpointCount = 0; // TODO: Implement
|
|
587
|
-
|
|
588
|
-
let dbSize = 0;
|
|
589
|
-
let walSize = 0;
|
|
590
|
-
|
|
591
|
-
if (this.dbPath !== ':memory:' && fs.existsSync(this.dbPath)) {
|
|
592
|
-
dbSize = fs.statSync(this.dbPath).size;
|
|
593
|
-
|
|
594
|
-
const walPath = `${this.dbPath}-wal`;
|
|
595
|
-
if (fs.existsSync(walPath)) {
|
|
596
|
-
walSize = fs.statSync(walPath).size;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return {
|
|
601
|
-
total_events: eventCount.count,
|
|
602
|
-
total_missions: missionCount,
|
|
603
|
-
active_missions: activeMissionCount,
|
|
604
|
-
active_locks: activeLockCount.count,
|
|
605
|
-
total_checkpoints: checkpointCount,
|
|
606
|
-
database_size_bytes: dbSize,
|
|
607
|
-
wal_size_bytes: walSize
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
async maintenance(): Promise<void> {
|
|
612
|
-
if (!this.db) {
|
|
613
|
-
throw new Error('Database not initialized');
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
this.db.exec('VACUUM');
|
|
618
|
-
console.log('Database maintenance completed');
|
|
619
|
-
} catch (error) {
|
|
620
|
-
console.error('Error running database maintenance:', error);
|
|
621
|
-
throw error;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
async beginTransaction(): Promise<void> {
|
|
626
|
-
if (!this.db) {
|
|
627
|
-
throw new Error('Database not initialized');
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
this.db.exec('BEGIN TRANSACTION');
|
|
632
|
-
} catch (error) {
|
|
633
|
-
console.error('Error beginning transaction:', error);
|
|
634
|
-
throw error;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
async commitTransaction(): Promise<void> {
|
|
639
|
-
if (!this.db) {
|
|
640
|
-
throw new Error('Database not initialized');
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
try {
|
|
644
|
-
this.db.exec('COMMIT');
|
|
645
|
-
} catch (error) {
|
|
646
|
-
console.error('Error committing transaction:', error);
|
|
647
|
-
throw error;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async rollbackTransaction(): Promise<void> {
|
|
652
|
-
if (!this.db) {
|
|
653
|
-
throw new Error('Database not initialized');
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
try {
|
|
657
|
-
this.db.exec('ROLLBACK');
|
|
658
|
-
} catch (error) {
|
|
659
|
-
console.error('Error rolling back transaction:', error);
|
|
660
|
-
throw error;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
getDatabase(): Database | null {
|
|
665
|
-
return this.db;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
export async function createSQLiteAdapter(dbPath: string = ':memory:'): Promise<SQLiteAdapter> {
|
|
670
|
-
const adapter = new SQLiteAdapter(dbPath);
|
|
671
|
-
await adapter.initialize();
|
|
672
|
-
return adapter;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
export async function createInMemoryAdapter(): Promise<SQLiteAdapter> {
|
|
676
|
-
return createSQLiteAdapter(':memory:');
|
|
677
|
-
}
|