11import { SessionMessageTable , SessionTable } from "@/session/session.sql"
2- import { and , asc , Database , desc , eq , gt , gte , isNull , like , lt , or , type SQL } from "@/storage/db"
2+ import { and , asc , Database as LegacyDatabase , desc , eq , gt , gte , isNull , like , lt , or , type SQL } from "@/storage/db"
3+ import { SqliteClient } from "@effect/sql-sqlite-bun"
34import { SessionMessage } from "@opencode-ai/core/session-message"
4- import { Effect , Layer , Schema } from "effect"
5+ import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
6+ import { Context , Effect , Layer , Schema } from "effect"
57import { SessionStorage } from "./session"
68
79const decodeMessage = Schema . decodeUnknownSync ( SessionMessage . Message )
810const decodeSessionRow = Schema . decodeUnknownSync ( SessionStorage . SessionRow )
11+ const makeDatabase = EffectDrizzleSqlite . makeWithDefaults ( )
12+ type DatabaseShape = Effect . Success < typeof makeDatabase >
13+
14+ export class Database extends Context . Service < Database , DatabaseShape > ( ) ( "@opencode/v2/session/StorageSql/Database" ) { }
15+
16+ export const databaseLayer = Layer . unwrap (
17+ Effect . sync ( ( ) => {
18+ const filename = LegacyDatabase . getPath ( )
19+ return Layer . effect (
20+ Database ,
21+ Effect . gen ( function * ( ) {
22+ const db = yield * makeDatabase
23+ yield * db . run ( "PRAGMA journal_mode = WAL" )
24+ yield * db . run ( "PRAGMA synchronous = NORMAL" )
25+ yield * db . run ( "PRAGMA busy_timeout = 5000" )
26+ yield * db . run ( "PRAGMA cache_size = -64000" )
27+ yield * db . run ( "PRAGMA foreign_keys = ON" )
28+ yield * db . run ( "PRAGMA wal_checkpoint(PASSIVE)" )
29+ yield * EffectDrizzleSqlite . migrateFromJournal ( db , LegacyDatabase . migrationJournal ( ) )
30+ return db
31+ } ) ,
32+ ) . pipe ( Layer . provide ( SqliteClient . layer ( { filename, disableWAL : filename === ":memory:" } ) ) )
33+ } ) ,
34+ )
935
1036export const layer = Layer . effect (
1137 SessionStorage . Service ,
1238 Effect . gen ( function * ( ) {
13- const get : SessionStorage . Interface [ "get" ] = Effect . fn ( "SessionStorageSql.get" ) ( ( sessionID ) =>
14- attempt ( ( ) =>
15- Database . use ( ( db ) => db . select ( ) . from ( SessionTable ) . where ( eq ( SessionTable . id , sessionID ) ) . get ( ) ) ,
16- ) . pipe ( Effect . map ( ( row ) => ( row ? fromSessionRow ( row ) : undefined ) ) ) ,
17- )
39+ const db = yield * Database
1840
19- const list : SessionStorage . Interface [ "list" ] = Effect . fn ( "SessionStorageSql.list" ) ( ( input ) =>
20- attempt ( ( ) => {
21- const direction = input . cursor ?. direction ?? "next"
22- const order = SessionStorage . pageOrder ( input . order ?? "desc" , direction )
23- const sortColumn = SessionTable . time_updated
24- const conditions : SQL [ ] = [ ]
25- if ( input . directory ) conditions . push ( eq ( SessionTable . directory , input . directory ) )
26- if ( input . path )
27- conditions . push ( or ( eq ( SessionTable . path , input . path ) , like ( SessionTable . path , `${ input . path } /%` ) ) ! )
28- if ( input . workspaceID ) conditions . push ( eq ( SessionTable . workspace_id , input . workspaceID ) )
29- if ( input . roots ) conditions . push ( isNull ( SessionTable . parent_id ) )
30- if ( input . start ) conditions . push ( gte ( sortColumn , input . start ) )
31- if ( input . search ) conditions . push ( like ( SessionTable . title , `%${ input . search } %` ) )
32- if ( input . cursor ) conditions . push ( sessionCursorBoundary ( input . cursor , order ) )
33-
34- return Database . use ( ( db ) => {
35- const query = db
36- . select ( )
37- . from ( SessionTable )
38- . where ( conditions . length > 0 ? and ( ...conditions ) : undefined )
39- . orderBy (
40- order === "asc" ? asc ( sortColumn ) : desc ( sortColumn ) ,
41- order === "asc" ? asc ( SessionTable . id ) : desc ( SessionTable . id ) ,
42- )
43- const rows = input . limit === undefined ? query . all ( ) : query . limit ( input . limit ) . all ( )
44- return direction === "previous" ? rows . toReversed ( ) : rows
45- } )
46- } ) . pipe ( Effect . map ( ( rows ) => rows . map ( fromSessionRow ) ) ) ,
47- )
41+ const get : SessionStorage . Interface [ "get" ] = Effect . fn ( "SessionStorageSql.get" ) ( function * ( sessionID ) {
42+ const row = yield * attempt ( db . select ( ) . from ( SessionTable ) . where ( eq ( SessionTable . id , sessionID ) ) . get ( ) )
43+ return row ? fromSessionRow ( row ) : undefined
44+ } )
4845
49- const messages : SessionStorage . Interface [ "messages" ] = Effect . fn ( "SessionStorageSql.messages" ) ( ( input ) =>
50- attempt ( ( ) => {
51- const direction = input . cursor ?. direction ?? "next"
52- const order = SessionStorage . pageOrder ( input . order ?? "desc" , direction )
53- const boundary = input . cursor ? messageCursorBoundary ( input . cursor , order ) : undefined
54- const where = boundary
55- ? and ( eq ( SessionMessageTable . session_id , input . sessionID ) , boundary )
56- : eq ( SessionMessageTable . session_id , input . sessionID )
57-
58- return Database . use ( ( db ) => {
59- const query = db
60- . select ( )
61- . from ( SessionMessageTable )
62- . where ( where )
63- . orderBy (
64- order === "asc" ? asc ( SessionMessageTable . time_created ) : desc ( SessionMessageTable . time_created ) ,
65- order === "asc" ? asc ( SessionMessageTable . id ) : desc ( SessionMessageTable . id ) ,
66- )
67- const rows = input . limit === undefined ? query . all ( ) : query . limit ( input . limit ) . all ( )
68- return direction === "previous" ? rows . toReversed ( ) : rows
69- } )
70- } ) . pipe ( Effect . map ( ( rows ) => rows . map ( ( row ) => decodeMessage ( { ...row . data , id : row . id , type : row . type } ) ) ) ) ,
71- )
46+ const list : SessionStorage . Interface [ "list" ] = Effect . fn ( "SessionStorageSql.list" ) ( function * ( input ) {
47+ const direction = input . cursor ?. direction ?? "next"
48+ const order = SessionStorage . pageOrder ( input . order ?? "desc" , direction )
49+ const sortColumn = SessionTable . time_updated
50+ const conditions : SQL [ ] = [ ]
51+ if ( input . directory ) conditions . push ( eq ( SessionTable . directory , input . directory ) )
52+ if ( input . path )
53+ conditions . push ( or ( eq ( SessionTable . path , input . path ) , like ( SessionTable . path , `${ input . path } /%` ) ) ! )
54+ if ( input . workspaceID ) conditions . push ( eq ( SessionTable . workspace_id , input . workspaceID ) )
55+ if ( input . roots ) conditions . push ( isNull ( SessionTable . parent_id ) )
56+ if ( input . start ) conditions . push ( gte ( sortColumn , input . start ) )
57+ if ( input . search ) conditions . push ( like ( SessionTable . title , `%${ input . search } %` ) )
58+ if ( input . cursor ) conditions . push ( sessionCursorBoundary ( input . cursor , order ) )
59+
60+ const query = db
61+ . select ( )
62+ . from ( SessionTable )
63+ . where ( conditions . length > 0 ? and ( ...conditions ) : undefined )
64+ . orderBy (
65+ order === "asc" ? asc ( sortColumn ) : desc ( sortColumn ) ,
66+ order === "asc" ? asc ( SessionTable . id ) : desc ( SessionTable . id ) ,
67+ )
68+ const rows = yield * attempt ( input . limit === undefined ? query : query . limit ( input . limit ) )
69+ return ( direction === "previous" ? rows . toReversed ( ) : rows ) . map ( fromSessionRow )
70+ } )
71+
72+ const messages : SessionStorage . Interface [ "messages" ] = Effect . fn ( "SessionStorageSql.messages" ) ( function * ( input ) {
73+ const direction = input . cursor ?. direction ?? "next"
74+ const order = SessionStorage . pageOrder ( input . order ?? "desc" , direction )
75+ const boundary = input . cursor ? messageCursorBoundary ( input . cursor , order ) : undefined
76+ const where = boundary
77+ ? and ( eq ( SessionMessageTable . session_id , input . sessionID ) , boundary )
78+ : eq ( SessionMessageTable . session_id , input . sessionID )
79+
80+ const query = db
81+ . select ( )
82+ . from ( SessionMessageTable )
83+ . where ( where )
84+ . orderBy (
85+ order === "asc" ? asc ( SessionMessageTable . time_created ) : desc ( SessionMessageTable . time_created ) ,
86+ order === "asc" ? asc ( SessionMessageTable . id ) : desc ( SessionMessageTable . id ) ,
87+ )
88+ const rows = yield * attempt ( input . limit === undefined ? query : query . limit ( input . limit ) )
89+ return ( direction === "previous" ? rows . toReversed ( ) : rows ) . map ( ( row ) =>
90+ decodeMessage ( { ...row . data , id : row . id , type : row . type } ) ,
91+ )
92+ } )
7293
7394 const context : SessionStorage . Interface [ "context" ] = Effect . fn ( "SessionStorageSql.context" ) ( ( sessionID ) =>
74- attempt ( ( ) =>
75- Database . use ( ( db ) => {
76- const compaction = db
95+ Effect . gen ( function * ( ) {
96+ const compaction = yield * attempt (
97+ db
7798 . select ( )
7899 . from ( SessionMessageTable )
79100 . where ( and ( eq ( SessionMessageTable . session_id , sessionID ) , eq ( SessionMessageTable . type , "compaction" ) ) )
80101 . orderBy ( desc ( SessionMessageTable . time_created ) , desc ( SessionMessageTable . id ) )
81102 . limit ( 1 )
82- . get ( )
103+ . get ( ) ,
104+ )
83105
84- return db
106+ const rows = yield * attempt (
107+ db
85108 . select ( )
86109 . from ( SessionMessageTable )
87110 . where (
@@ -98,23 +121,25 @@ export const layer = Layer.effect(
98121 : undefined ,
99122 ) ,
100123 )
101- . orderBy ( asc ( SessionMessageTable . time_created ) , asc ( SessionMessageTable . id ) )
102- . all ( )
103- } ) ,
104- ) . pipe ( Effect . map ( ( rows ) => rows . map ( ( row ) => decodeMessage ( { ...row . data , id : row . id , type : row . type } ) ) ) ) ,
124+ . orderBy ( asc ( SessionMessageTable . time_created ) , asc ( SessionMessageTable . id ) ) ,
125+ )
126+
127+ return rows . map ( ( row ) => decodeMessage ( { ...row . data , id : row . id , type : row . type } ) )
128+ } ) ,
105129 )
106130
107131 return SessionStorage . Service . of ( { get, list, messages, context } )
108132 } ) ,
109133)
110134
111- export const defaultLayer = layer
135+ export const defaultLayer = layer . pipe ( Layer . provide ( databaseLayer . pipe ( Layer . orDie ) ) )
112136
113- function attempt < A > ( body : ( ) => A ) {
114- return Effect . try ( {
115- try : body ,
116- catch : ( cause ) => new SessionStorage . StorageError ( { message : "Session storage SQL operation failed" , cause } ) ,
117- } )
137+ function attempt < A , E , R > ( effect : Effect . Effect < A , E , R > ) {
138+ return effect . pipe (
139+ Effect . mapError (
140+ ( cause ) => new SessionStorage . StorageError ( { message : "Session storage SQL operation failed" , cause } ) ,
141+ ) ,
142+ )
118143}
119144
120145function sessionCursorBoundary ( cursor : SessionStorage . SessionCursor , order : SessionStorage . SortOrder ) {
0 commit comments