@ottocode/database 0.1.173

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.
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,92 @@
1
+ CREATE TABLE `artifacts` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `message_part_id` text,
4
+ `kind` text NOT NULL,
5
+ `path` text,
6
+ `mime` text,
7
+ `size` integer,
8
+ `sha256` text,
9
+ FOREIGN KEY (`message_part_id`) REFERENCES `message_parts`(`id`) ON UPDATE no action ON DELETE cascade
10
+ );
11
+ --> statement-breakpoint
12
+ CREATE UNIQUE INDEX `artifacts_message_part_id_unique` ON `artifacts` (`message_part_id`);--> statement-breakpoint
13
+ CREATE TABLE `message_parts` (
14
+ `id` text PRIMARY KEY NOT NULL,
15
+ `message_id` text NOT NULL,
16
+ `index` integer NOT NULL,
17
+ `step_index` integer,
18
+ `type` text NOT NULL,
19
+ `content` text NOT NULL,
20
+ `agent` text NOT NULL,
21
+ `provider` text NOT NULL,
22
+ `model` text NOT NULL,
23
+ `started_at` integer,
24
+ `completed_at` integer,
25
+ `compacted_at` integer,
26
+ `tool_name` text,
27
+ `tool_call_id` text,
28
+ `tool_duration_ms` integer,
29
+ FOREIGN KEY (`message_id`) REFERENCES `messages`(`id`) ON UPDATE no action ON DELETE cascade
30
+ );
31
+ --> statement-breakpoint
32
+ CREATE TABLE `messages` (
33
+ `id` text PRIMARY KEY NOT NULL,
34
+ `session_id` text NOT NULL,
35
+ `role` text NOT NULL,
36
+ `status` text NOT NULL,
37
+ `agent` text NOT NULL,
38
+ `provider` text NOT NULL,
39
+ `model` text NOT NULL,
40
+ `created_at` integer NOT NULL,
41
+ `completed_at` integer,
42
+ `latency_ms` integer,
43
+ `prompt_tokens` integer,
44
+ `completion_tokens` integer,
45
+ `total_tokens` integer,
46
+ `cached_input_tokens` integer,
47
+ `cache_creation_input_tokens` integer,
48
+ `reasoning_tokens` integer,
49
+ `error` text,
50
+ `error_type` text,
51
+ `error_details` text,
52
+ `is_aborted` integer,
53
+ FOREIGN KEY (`session_id`) REFERENCES `sessions`(`id`) ON UPDATE no action ON DELETE cascade
54
+ );
55
+ --> statement-breakpoint
56
+ CREATE TABLE `sessions` (
57
+ `id` text PRIMARY KEY NOT NULL,
58
+ `title` text,
59
+ `agent` text NOT NULL,
60
+ `provider` text NOT NULL,
61
+ `model` text NOT NULL,
62
+ `project_path` text NOT NULL,
63
+ `created_at` integer NOT NULL,
64
+ `last_active_at` integer,
65
+ `total_input_tokens` integer,
66
+ `total_output_tokens` integer,
67
+ `total_cached_tokens` integer,
68
+ `total_cache_creation_tokens` integer,
69
+ `total_reasoning_tokens` integer,
70
+ `total_tool_time_ms` integer,
71
+ `tool_counts_json` text,
72
+ `current_context_tokens` integer,
73
+ `context_summary` text,
74
+ `last_compacted_at` integer,
75
+ `parent_session_id` text,
76
+ `branch_point_message_id` text,
77
+ `session_type` text DEFAULT 'main'
78
+ );
79
+ --> statement-breakpoint
80
+ CREATE TABLE `shares` (
81
+ `session_id` text PRIMARY KEY NOT NULL,
82
+ `share_id` text NOT NULL,
83
+ `secret` text NOT NULL,
84
+ `url` text NOT NULL,
85
+ `title` text,
86
+ `description` text,
87
+ `created_at` integer NOT NULL,
88
+ `last_synced_at` integer NOT NULL,
89
+ `last_synced_message_id` text NOT NULL
90
+ );
91
+ --> statement-breakpoint
92
+ CREATE UNIQUE INDEX `shares_share_id_unique` ON `shares` (`share_id`);
@@ -0,0 +1,615 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "d82dacf8-ad38-4e05-acae-902a4a831ba7",
5
+ "prevId": "00000000-0000-0000-0000-000000000000",
6
+ "tables": {
7
+ "artifacts": {
8
+ "name": "artifacts",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": false
16
+ },
17
+ "message_part_id": {
18
+ "name": "message_part_id",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": false,
22
+ "autoincrement": false
23
+ },
24
+ "kind": {
25
+ "name": "kind",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "path": {
32
+ "name": "path",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "mime": {
39
+ "name": "mime",
40
+ "type": "text",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ },
45
+ "size": {
46
+ "name": "size",
47
+ "type": "integer",
48
+ "primaryKey": false,
49
+ "notNull": false,
50
+ "autoincrement": false
51
+ },
52
+ "sha256": {
53
+ "name": "sha256",
54
+ "type": "text",
55
+ "primaryKey": false,
56
+ "notNull": false,
57
+ "autoincrement": false
58
+ }
59
+ },
60
+ "indexes": {
61
+ "artifacts_message_part_id_unique": {
62
+ "name": "artifacts_message_part_id_unique",
63
+ "columns": ["message_part_id"],
64
+ "isUnique": true
65
+ }
66
+ },
67
+ "foreignKeys": {
68
+ "artifacts_message_part_id_message_parts_id_fk": {
69
+ "name": "artifacts_message_part_id_message_parts_id_fk",
70
+ "tableFrom": "artifacts",
71
+ "tableTo": "message_parts",
72
+ "columnsFrom": ["message_part_id"],
73
+ "columnsTo": ["id"],
74
+ "onDelete": "cascade",
75
+ "onUpdate": "no action"
76
+ }
77
+ },
78
+ "compositePrimaryKeys": {},
79
+ "uniqueConstraints": {},
80
+ "checkConstraints": {}
81
+ },
82
+ "message_parts": {
83
+ "name": "message_parts",
84
+ "columns": {
85
+ "id": {
86
+ "name": "id",
87
+ "type": "text",
88
+ "primaryKey": true,
89
+ "notNull": true,
90
+ "autoincrement": false
91
+ },
92
+ "message_id": {
93
+ "name": "message_id",
94
+ "type": "text",
95
+ "primaryKey": false,
96
+ "notNull": true,
97
+ "autoincrement": false
98
+ },
99
+ "index": {
100
+ "name": "index",
101
+ "type": "integer",
102
+ "primaryKey": false,
103
+ "notNull": true,
104
+ "autoincrement": false
105
+ },
106
+ "step_index": {
107
+ "name": "step_index",
108
+ "type": "integer",
109
+ "primaryKey": false,
110
+ "notNull": false,
111
+ "autoincrement": false
112
+ },
113
+ "type": {
114
+ "name": "type",
115
+ "type": "text",
116
+ "primaryKey": false,
117
+ "notNull": true,
118
+ "autoincrement": false
119
+ },
120
+ "content": {
121
+ "name": "content",
122
+ "type": "text",
123
+ "primaryKey": false,
124
+ "notNull": true,
125
+ "autoincrement": false
126
+ },
127
+ "agent": {
128
+ "name": "agent",
129
+ "type": "text",
130
+ "primaryKey": false,
131
+ "notNull": true,
132
+ "autoincrement": false
133
+ },
134
+ "provider": {
135
+ "name": "provider",
136
+ "type": "text",
137
+ "primaryKey": false,
138
+ "notNull": true,
139
+ "autoincrement": false
140
+ },
141
+ "model": {
142
+ "name": "model",
143
+ "type": "text",
144
+ "primaryKey": false,
145
+ "notNull": true,
146
+ "autoincrement": false
147
+ },
148
+ "started_at": {
149
+ "name": "started_at",
150
+ "type": "integer",
151
+ "primaryKey": false,
152
+ "notNull": false,
153
+ "autoincrement": false
154
+ },
155
+ "completed_at": {
156
+ "name": "completed_at",
157
+ "type": "integer",
158
+ "primaryKey": false,
159
+ "notNull": false,
160
+ "autoincrement": false
161
+ },
162
+ "compacted_at": {
163
+ "name": "compacted_at",
164
+ "type": "integer",
165
+ "primaryKey": false,
166
+ "notNull": false,
167
+ "autoincrement": false
168
+ },
169
+ "tool_name": {
170
+ "name": "tool_name",
171
+ "type": "text",
172
+ "primaryKey": false,
173
+ "notNull": false,
174
+ "autoincrement": false
175
+ },
176
+ "tool_call_id": {
177
+ "name": "tool_call_id",
178
+ "type": "text",
179
+ "primaryKey": false,
180
+ "notNull": false,
181
+ "autoincrement": false
182
+ },
183
+ "tool_duration_ms": {
184
+ "name": "tool_duration_ms",
185
+ "type": "integer",
186
+ "primaryKey": false,
187
+ "notNull": false,
188
+ "autoincrement": false
189
+ }
190
+ },
191
+ "indexes": {},
192
+ "foreignKeys": {
193
+ "message_parts_message_id_messages_id_fk": {
194
+ "name": "message_parts_message_id_messages_id_fk",
195
+ "tableFrom": "message_parts",
196
+ "tableTo": "messages",
197
+ "columnsFrom": ["message_id"],
198
+ "columnsTo": ["id"],
199
+ "onDelete": "cascade",
200
+ "onUpdate": "no action"
201
+ }
202
+ },
203
+ "compositePrimaryKeys": {},
204
+ "uniqueConstraints": {},
205
+ "checkConstraints": {}
206
+ },
207
+ "messages": {
208
+ "name": "messages",
209
+ "columns": {
210
+ "id": {
211
+ "name": "id",
212
+ "type": "text",
213
+ "primaryKey": true,
214
+ "notNull": true,
215
+ "autoincrement": false
216
+ },
217
+ "session_id": {
218
+ "name": "session_id",
219
+ "type": "text",
220
+ "primaryKey": false,
221
+ "notNull": true,
222
+ "autoincrement": false
223
+ },
224
+ "role": {
225
+ "name": "role",
226
+ "type": "text",
227
+ "primaryKey": false,
228
+ "notNull": true,
229
+ "autoincrement": false
230
+ },
231
+ "status": {
232
+ "name": "status",
233
+ "type": "text",
234
+ "primaryKey": false,
235
+ "notNull": true,
236
+ "autoincrement": false
237
+ },
238
+ "agent": {
239
+ "name": "agent",
240
+ "type": "text",
241
+ "primaryKey": false,
242
+ "notNull": true,
243
+ "autoincrement": false
244
+ },
245
+ "provider": {
246
+ "name": "provider",
247
+ "type": "text",
248
+ "primaryKey": false,
249
+ "notNull": true,
250
+ "autoincrement": false
251
+ },
252
+ "model": {
253
+ "name": "model",
254
+ "type": "text",
255
+ "primaryKey": false,
256
+ "notNull": true,
257
+ "autoincrement": false
258
+ },
259
+ "created_at": {
260
+ "name": "created_at",
261
+ "type": "integer",
262
+ "primaryKey": false,
263
+ "notNull": true,
264
+ "autoincrement": false
265
+ },
266
+ "completed_at": {
267
+ "name": "completed_at",
268
+ "type": "integer",
269
+ "primaryKey": false,
270
+ "notNull": false,
271
+ "autoincrement": false
272
+ },
273
+ "latency_ms": {
274
+ "name": "latency_ms",
275
+ "type": "integer",
276
+ "primaryKey": false,
277
+ "notNull": false,
278
+ "autoincrement": false
279
+ },
280
+ "prompt_tokens": {
281
+ "name": "prompt_tokens",
282
+ "type": "integer",
283
+ "primaryKey": false,
284
+ "notNull": false,
285
+ "autoincrement": false
286
+ },
287
+ "completion_tokens": {
288
+ "name": "completion_tokens",
289
+ "type": "integer",
290
+ "primaryKey": false,
291
+ "notNull": false,
292
+ "autoincrement": false
293
+ },
294
+ "total_tokens": {
295
+ "name": "total_tokens",
296
+ "type": "integer",
297
+ "primaryKey": false,
298
+ "notNull": false,
299
+ "autoincrement": false
300
+ },
301
+ "cached_input_tokens": {
302
+ "name": "cached_input_tokens",
303
+ "type": "integer",
304
+ "primaryKey": false,
305
+ "notNull": false,
306
+ "autoincrement": false
307
+ },
308
+ "cache_creation_input_tokens": {
309
+ "name": "cache_creation_input_tokens",
310
+ "type": "integer",
311
+ "primaryKey": false,
312
+ "notNull": false,
313
+ "autoincrement": false
314
+ },
315
+ "reasoning_tokens": {
316
+ "name": "reasoning_tokens",
317
+ "type": "integer",
318
+ "primaryKey": false,
319
+ "notNull": false,
320
+ "autoincrement": false
321
+ },
322
+ "error": {
323
+ "name": "error",
324
+ "type": "text",
325
+ "primaryKey": false,
326
+ "notNull": false,
327
+ "autoincrement": false
328
+ },
329
+ "error_type": {
330
+ "name": "error_type",
331
+ "type": "text",
332
+ "primaryKey": false,
333
+ "notNull": false,
334
+ "autoincrement": false
335
+ },
336
+ "error_details": {
337
+ "name": "error_details",
338
+ "type": "text",
339
+ "primaryKey": false,
340
+ "notNull": false,
341
+ "autoincrement": false
342
+ },
343
+ "is_aborted": {
344
+ "name": "is_aborted",
345
+ "type": "integer",
346
+ "primaryKey": false,
347
+ "notNull": false,
348
+ "autoincrement": false
349
+ }
350
+ },
351
+ "indexes": {},
352
+ "foreignKeys": {
353
+ "messages_session_id_sessions_id_fk": {
354
+ "name": "messages_session_id_sessions_id_fk",
355
+ "tableFrom": "messages",
356
+ "tableTo": "sessions",
357
+ "columnsFrom": ["session_id"],
358
+ "columnsTo": ["id"],
359
+ "onDelete": "cascade",
360
+ "onUpdate": "no action"
361
+ }
362
+ },
363
+ "compositePrimaryKeys": {},
364
+ "uniqueConstraints": {},
365
+ "checkConstraints": {}
366
+ },
367
+ "sessions": {
368
+ "name": "sessions",
369
+ "columns": {
370
+ "id": {
371
+ "name": "id",
372
+ "type": "text",
373
+ "primaryKey": true,
374
+ "notNull": true,
375
+ "autoincrement": false
376
+ },
377
+ "title": {
378
+ "name": "title",
379
+ "type": "text",
380
+ "primaryKey": false,
381
+ "notNull": false,
382
+ "autoincrement": false
383
+ },
384
+ "agent": {
385
+ "name": "agent",
386
+ "type": "text",
387
+ "primaryKey": false,
388
+ "notNull": true,
389
+ "autoincrement": false
390
+ },
391
+ "provider": {
392
+ "name": "provider",
393
+ "type": "text",
394
+ "primaryKey": false,
395
+ "notNull": true,
396
+ "autoincrement": false
397
+ },
398
+ "model": {
399
+ "name": "model",
400
+ "type": "text",
401
+ "primaryKey": false,
402
+ "notNull": true,
403
+ "autoincrement": false
404
+ },
405
+ "project_path": {
406
+ "name": "project_path",
407
+ "type": "text",
408
+ "primaryKey": false,
409
+ "notNull": true,
410
+ "autoincrement": false
411
+ },
412
+ "created_at": {
413
+ "name": "created_at",
414
+ "type": "integer",
415
+ "primaryKey": false,
416
+ "notNull": true,
417
+ "autoincrement": false
418
+ },
419
+ "last_active_at": {
420
+ "name": "last_active_at",
421
+ "type": "integer",
422
+ "primaryKey": false,
423
+ "notNull": false,
424
+ "autoincrement": false
425
+ },
426
+ "total_input_tokens": {
427
+ "name": "total_input_tokens",
428
+ "type": "integer",
429
+ "primaryKey": false,
430
+ "notNull": false,
431
+ "autoincrement": false
432
+ },
433
+ "total_output_tokens": {
434
+ "name": "total_output_tokens",
435
+ "type": "integer",
436
+ "primaryKey": false,
437
+ "notNull": false,
438
+ "autoincrement": false
439
+ },
440
+ "total_cached_tokens": {
441
+ "name": "total_cached_tokens",
442
+ "type": "integer",
443
+ "primaryKey": false,
444
+ "notNull": false,
445
+ "autoincrement": false
446
+ },
447
+ "total_cache_creation_tokens": {
448
+ "name": "total_cache_creation_tokens",
449
+ "type": "integer",
450
+ "primaryKey": false,
451
+ "notNull": false,
452
+ "autoincrement": false
453
+ },
454
+ "total_reasoning_tokens": {
455
+ "name": "total_reasoning_tokens",
456
+ "type": "integer",
457
+ "primaryKey": false,
458
+ "notNull": false,
459
+ "autoincrement": false
460
+ },
461
+ "total_tool_time_ms": {
462
+ "name": "total_tool_time_ms",
463
+ "type": "integer",
464
+ "primaryKey": false,
465
+ "notNull": false,
466
+ "autoincrement": false
467
+ },
468
+ "tool_counts_json": {
469
+ "name": "tool_counts_json",
470
+ "type": "text",
471
+ "primaryKey": false,
472
+ "notNull": false,
473
+ "autoincrement": false
474
+ },
475
+ "current_context_tokens": {
476
+ "name": "current_context_tokens",
477
+ "type": "integer",
478
+ "primaryKey": false,
479
+ "notNull": false,
480
+ "autoincrement": false
481
+ },
482
+ "context_summary": {
483
+ "name": "context_summary",
484
+ "type": "text",
485
+ "primaryKey": false,
486
+ "notNull": false,
487
+ "autoincrement": false
488
+ },
489
+ "last_compacted_at": {
490
+ "name": "last_compacted_at",
491
+ "type": "integer",
492
+ "primaryKey": false,
493
+ "notNull": false,
494
+ "autoincrement": false
495
+ },
496
+ "parent_session_id": {
497
+ "name": "parent_session_id",
498
+ "type": "text",
499
+ "primaryKey": false,
500
+ "notNull": false,
501
+ "autoincrement": false
502
+ },
503
+ "branch_point_message_id": {
504
+ "name": "branch_point_message_id",
505
+ "type": "text",
506
+ "primaryKey": false,
507
+ "notNull": false,
508
+ "autoincrement": false
509
+ },
510
+ "session_type": {
511
+ "name": "session_type",
512
+ "type": "text",
513
+ "primaryKey": false,
514
+ "notNull": false,
515
+ "autoincrement": false,
516
+ "default": "'main'"
517
+ }
518
+ },
519
+ "indexes": {},
520
+ "foreignKeys": {},
521
+ "compositePrimaryKeys": {},
522
+ "uniqueConstraints": {},
523
+ "checkConstraints": {}
524
+ },
525
+ "shares": {
526
+ "name": "shares",
527
+ "columns": {
528
+ "session_id": {
529
+ "name": "session_id",
530
+ "type": "text",
531
+ "primaryKey": true,
532
+ "notNull": true,
533
+ "autoincrement": false
534
+ },
535
+ "share_id": {
536
+ "name": "share_id",
537
+ "type": "text",
538
+ "primaryKey": false,
539
+ "notNull": true,
540
+ "autoincrement": false
541
+ },
542
+ "secret": {
543
+ "name": "secret",
544
+ "type": "text",
545
+ "primaryKey": false,
546
+ "notNull": true,
547
+ "autoincrement": false
548
+ },
549
+ "url": {
550
+ "name": "url",
551
+ "type": "text",
552
+ "primaryKey": false,
553
+ "notNull": true,
554
+ "autoincrement": false
555
+ },
556
+ "title": {
557
+ "name": "title",
558
+ "type": "text",
559
+ "primaryKey": false,
560
+ "notNull": false,
561
+ "autoincrement": false
562
+ },
563
+ "description": {
564
+ "name": "description",
565
+ "type": "text",
566
+ "primaryKey": false,
567
+ "notNull": false,
568
+ "autoincrement": false
569
+ },
570
+ "created_at": {
571
+ "name": "created_at",
572
+ "type": "integer",
573
+ "primaryKey": false,
574
+ "notNull": true,
575
+ "autoincrement": false
576
+ },
577
+ "last_synced_at": {
578
+ "name": "last_synced_at",
579
+ "type": "integer",
580
+ "primaryKey": false,
581
+ "notNull": true,
582
+ "autoincrement": false
583
+ },
584
+ "last_synced_message_id": {
585
+ "name": "last_synced_message_id",
586
+ "type": "text",
587
+ "primaryKey": false,
588
+ "notNull": true,
589
+ "autoincrement": false
590
+ }
591
+ },
592
+ "indexes": {
593
+ "shares_share_id_unique": {
594
+ "name": "shares_share_id_unique",
595
+ "columns": ["share_id"],
596
+ "isUnique": true
597
+ }
598
+ },
599
+ "foreignKeys": {},
600
+ "compositePrimaryKeys": {},
601
+ "uniqueConstraints": {},
602
+ "checkConstraints": {}
603
+ }
604
+ },
605
+ "views": {},
606
+ "enums": {},
607
+ "_meta": {
608
+ "schemas": {},
609
+ "tables": {},
610
+ "columns": {}
611
+ },
612
+ "internal": {
613
+ "indexes": {}
614
+ }
615
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1770227330085,
9
+ "tag": "0000_material_swarm",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ export default defineConfig({
4
+ dialect: 'sqlite',
5
+ schema: './src/schema.ts',
6
+ out: './drizzle',
7
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@ottocode/database",
3
+ "version": "0.1.173",
4
+ "description": "Database and persistence layer for ottocode",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./src/index.ts",
11
+ "types": "./src/index.ts"
12
+ },
13
+ "./schema": {
14
+ "import": "./src/schema.ts",
15
+ "types": "./src/schema.ts"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "dev": "bun run src/index.ts",
20
+ "test": "bun test",
21
+ "db:generate": "drizzle-kit generate --config=drizzle.config.ts",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@ottocode/sdk": "0.1.173",
26
+ "drizzle-orm": "^0.44.5"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "latest",
30
+ "drizzle-kit": "^0.31.4",
31
+ "typescript": "^5"
32
+ }
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { drizzle, type BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
3
+ import { loadConfig, logger } from '@ottocode/sdk';
4
+ import * as schema from './schema/index.ts';
5
+ import { bundledMigrations } from './runtime/migrations-bundled.ts';
6
+
7
+ const dbCache: Map<string, BunSQLiteDatabase<typeof schema>> = new Map();
8
+ const migratedPaths = new Set<string>();
9
+
10
+ export async function getDb(projectRootInput?: string) {
11
+ const cfg = await loadConfig(projectRootInput);
12
+ const dbPath = cfg.paths.dbPath;
13
+ // Data dir is ensured by loadConfig() already.
14
+
15
+ const key = dbPath;
16
+ const cached = dbCache.get(key);
17
+ if (cached) return cached;
18
+
19
+ const sqlite = new Database(dbPath, { create: true });
20
+
21
+ sqlite.exec('PRAGMA journal_mode = WAL');
22
+ sqlite.exec('PRAGMA busy_timeout = 5000');
23
+ sqlite.exec('PRAGMA synchronous = NORMAL');
24
+
25
+ const db = drizzle(sqlite, { schema });
26
+
27
+ // Run migrations once per db path (apply any not yet applied)
28
+ if (!migratedPaths.has(dbPath)) {
29
+ try {
30
+ // Ensure migrations tracking table exists
31
+ sqlite.exec(
32
+ 'CREATE TABLE IF NOT EXISTS otto_migrations (name TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)',
33
+ );
34
+
35
+ // Read applied migrations
36
+ const appliedRows = sqlite
37
+ .query('SELECT name FROM otto_migrations')
38
+ .all() as Array<{ name: string }>;
39
+ const applied = new Set(appliedRows.map((r) => r.name));
40
+
41
+ for (const m of bundledMigrations) {
42
+ if (applied.has(m.name)) continue;
43
+ try {
44
+ sqlite.exec('BEGIN TRANSACTION');
45
+ sqlite.exec(m.content);
46
+ sqlite.exec('COMMIT');
47
+ sqlite
48
+ .query(
49
+ 'INSERT INTO otto_migrations (name, applied_at) VALUES (?, ?)',
50
+ )
51
+ .run(m.name, Date.now());
52
+ } catch (err) {
53
+ // If migration fails due to already-applied schema (e.g., table exists / duplicate column), mark as applied and continue.
54
+ sqlite.exec('ROLLBACK');
55
+ const msg = String((err as Error)?.message ?? err);
56
+ const benign =
57
+ msg.includes('already exists') || msg.includes('duplicate column');
58
+ if (benign) {
59
+ sqlite
60
+ .query(
61
+ 'INSERT OR IGNORE INTO otto_migrations (name, applied_at) VALUES (?, ?)',
62
+ )
63
+ .run(m.name, Date.now());
64
+ continue;
65
+ }
66
+ throw err;
67
+ }
68
+ }
69
+ migratedPaths.add(dbPath);
70
+ } catch (error) {
71
+ logger.error('Local database migration failed', error);
72
+ throw error;
73
+ }
74
+ }
75
+ dbCache.set(key, db);
76
+ return db;
77
+ }
78
+
79
+ export type DB = Awaited<ReturnType<typeof getDb>>;
80
+ export * as dbSchema from './schema/index.ts';
@@ -0,0 +1,7 @@
1
+ import mig0000 from '../../drizzle/0000_material_swarm.sql' with {
2
+ type: 'text',
3
+ };
4
+
5
+ export const bundledMigrations: Array<{ name: string; content: string }> = [
6
+ { name: '0000_material_swarm.sql', content: mig0000 },
7
+ ];
@@ -0,0 +1,14 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+ import { messageParts } from './message-parts.ts';
3
+
4
+ export const artifacts = sqliteTable('artifacts', {
5
+ id: text('id').primaryKey(),
6
+ messagePartId: text('message_part_id')
7
+ .unique()
8
+ .references(() => messageParts.id, { onDelete: 'cascade' }),
9
+ kind: text('kind').notNull(), // 'file' | 'file_diff' | ...
10
+ path: text('path'),
11
+ mime: text('mime'),
12
+ size: integer('size'),
13
+ sha256: text('sha256'),
14
+ });
@@ -0,0 +1,45 @@
1
+ import { relations } from 'drizzle-orm';
2
+ export { sessions } from './sessions.ts';
3
+ export { messages } from './messages.ts';
4
+ export { messageParts } from './message-parts.ts';
5
+ export { artifacts } from './artifacts.ts';
6
+ export { shares } from './shares.ts';
7
+
8
+ import { sessions } from './sessions.ts';
9
+ import { messages } from './messages.ts';
10
+ import { messageParts } from './message-parts.ts';
11
+ import { artifacts } from './artifacts.ts';
12
+ import { shares } from './shares.ts';
13
+
14
+ export const sessionsRelations = relations(sessions, ({ many }) => ({
15
+ messages: many(messages),
16
+ }));
17
+
18
+ export const messagesRelations = relations(messages, ({ one, many }) => ({
19
+ session: one(sessions, {
20
+ fields: [messages.sessionId],
21
+ references: [sessions.id],
22
+ }),
23
+ parts: many(messageParts),
24
+ }));
25
+
26
+ export const messagePartsRelations = relations(messageParts, ({ one }) => ({
27
+ message: one(messages, {
28
+ fields: [messageParts.messageId],
29
+ references: [messages.id],
30
+ }),
31
+ }));
32
+
33
+ export const artifactsRelations = relations(artifacts, ({ one }) => ({
34
+ part: one(messageParts, {
35
+ fields: [artifacts.messagePartId],
36
+ references: [messageParts.id],
37
+ }),
38
+ }));
39
+
40
+ export const sharesRelations = relations(shares, ({ one }) => ({
41
+ session: one(sessions, {
42
+ fields: [shares.sessionId],
43
+ references: [sessions.id],
44
+ }),
45
+ }));
@@ -0,0 +1,24 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+ import { messages } from './messages.ts';
3
+
4
+ export const messageParts = sqliteTable('message_parts', {
5
+ id: text('id').primaryKey(),
6
+ messageId: text('message_id')
7
+ .notNull()
8
+ .references(() => messages.id, { onDelete: 'cascade' }),
9
+ index: integer('index').notNull(),
10
+ stepIndex: integer('step_index'),
11
+ type: text('type').notNull(), // 'text' | 'tool_call' | 'tool_result' | 'image' | 'error'
12
+ content: text('content').notNull(), // JSON string
13
+ agent: text('agent').notNull(),
14
+ provider: text('provider').notNull(),
15
+ model: text('model').notNull(),
16
+ // Timestamps
17
+ startedAt: integer('started_at', { mode: 'number' }),
18
+ completedAt: integer('completed_at', { mode: 'number' }),
19
+ compactedAt: integer('compacted_at', { mode: 'number' }),
20
+ // Tool metadata (for tool_call/tool_result)
21
+ toolName: text('tool_name'),
22
+ toolCallId: text('tool_call_id'),
23
+ toolDurationMs: integer('tool_duration_ms'),
24
+ });
@@ -0,0 +1,29 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+ import { sessions } from './sessions.ts';
3
+
4
+ export const messages = sqliteTable('messages', {
5
+ id: text('id').primaryKey(),
6
+ sessionId: text('session_id')
7
+ .notNull()
8
+ .references(() => sessions.id, { onDelete: 'cascade' }),
9
+ role: text('role').notNull(), // 'system' | 'user' | 'assistant' | 'tool'
10
+ status: text('status').notNull(), // 'pending' | 'complete' | 'error'
11
+ agent: text('agent').notNull(),
12
+ provider: text('provider').notNull(),
13
+ model: text('model').notNull(),
14
+ createdAt: integer('created_at', { mode: 'number' }).notNull(),
15
+ // Metadata
16
+ completedAt: integer('completed_at', { mode: 'number' }),
17
+ latencyMs: integer('latency_ms'),
18
+ inputTokens: integer('prompt_tokens'),
19
+ outputTokens: integer('completion_tokens'),
20
+ totalTokens: integer('total_tokens'),
21
+ cachedInputTokens: integer('cached_input_tokens'),
22
+ cacheCreationInputTokens: integer('cache_creation_input_tokens'),
23
+ reasoningTokens: integer('reasoning_tokens'),
24
+ // Error fields
25
+ error: text('error'),
26
+ errorType: text('error_type'), // 'api_error', 'abort', 'validation_error', etc.
27
+ errorDetails: text('error_details'), // JSON string with full error object
28
+ isAborted: integer('is_aborted', { mode: 'boolean' }), // flag for user aborts
29
+ });
@@ -0,0 +1,28 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+
3
+ export const sessions = sqliteTable('sessions', {
4
+ id: text('id').primaryKey(),
5
+ title: text('title'),
6
+ agent: text('agent').notNull(),
7
+ provider: text('provider').notNull(),
8
+ model: text('model').notNull(),
9
+ projectPath: text('project_path').notNull(),
10
+ createdAt: integer('created_at', { mode: 'number' }).notNull(),
11
+ // Metadata
12
+ lastActiveAt: integer('last_active_at', { mode: 'number' }),
13
+ totalInputTokens: integer('total_input_tokens'),
14
+ totalOutputTokens: integer('total_output_tokens'),
15
+ totalCachedTokens: integer('total_cached_tokens'),
16
+ totalCacheCreationTokens: integer('total_cache_creation_tokens'),
17
+ totalReasoningTokens: integer('total_reasoning_tokens'),
18
+ totalToolTimeMs: integer('total_tool_time_ms'),
19
+ toolCountsJson: text('tool_counts_json'), // JSON object of name->count
20
+ currentContextTokens: integer('current_context_tokens'),
21
+ // Compaction
22
+ contextSummary: text('context_summary'), // LLM-generated summary of conversation context
23
+ lastCompactedAt: integer('last_compacted_at', { mode: 'number' }),
24
+ // Branching
25
+ parentSessionId: text('parent_session_id'),
26
+ branchPointMessageId: text('branch_point_message_id'),
27
+ sessionType: text('session_type').default('main'),
28
+ });
@@ -0,0 +1,13 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+
3
+ export const shares = sqliteTable('shares', {
4
+ sessionId: text('session_id').primaryKey(),
5
+ shareId: text('share_id').notNull().unique(),
6
+ secret: text('secret').notNull(),
7
+ url: text('url').notNull(),
8
+ title: text('title'),
9
+ description: text('description'),
10
+ createdAt: integer('created_at', { mode: 'number' }).notNull(),
11
+ lastSyncedAt: integer('last_synced_at', { mode: 'number' }).notNull(),
12
+ lastSyncedMessageId: text('last_synced_message_id').notNull(),
13
+ });
package/src/schema.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './schema/index.ts';
@@ -0,0 +1,8 @@
1
+ import type { InferSelectModel } from 'drizzle-orm';
2
+ import type { sessions } from '../schema/sessions.ts';
3
+ import type { messages } from '../schema/messages.ts';
4
+ import type { messageParts } from '../schema/message-parts.ts';
5
+
6
+ export type Session = InferSelectModel<typeof sessions>;
7
+ export type Message = InferSelectModel<typeof messages>;
8
+ export type MessagePart = InferSelectModel<typeof messageParts>;
@@ -0,0 +1,5 @@
1
+ // Type declarations for .sql file imports (Bun-specific)
2
+ declare module '*.sql' {
3
+ const content: string;
4
+ export default content;
5
+ }
package/sst-env.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* deno-fmt-ignore-file */
5
+
6
+ /// <reference path="../../sst-env.d.ts" />
7
+
8
+ import 'sst';
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist"
5
+ },
6
+ "include": ["src/**/*"]
7
+ }