1- import { timingSafeEqual } from "node:crypto" ;
1+ import { randomUUID , timingSafeEqual } from "node:crypto" ;
2+ import { readFileSync , rmSync } from "node:fs" ;
23import { createRequire } from "node:module" ;
4+ import { tmpdir } from "node:os" ;
5+ import { join } from "node:path" ;
36import { assert } from "../src/Assert.js" ;
47import type { TimingSafeEqual } from "../src/Crypto.js" ;
58import { lazyTrue , lazyVoid } from "../src/Function.js" ;
@@ -121,9 +124,46 @@ interface BunSqliteModule {
121124 readonly Database : new ( filename : string ) => BunSqliteDbLike ;
122125}
123126
127+ interface NodeSqliteStatementLike {
128+ readonly all : ( ...parameters : ReadonlyArray < SqliteValue > ) => Array < SqliteRow > ;
129+ readonly run : ( ...parameters : ReadonlyArray < SqliteValue > ) => {
130+ readonly changes ?: number ;
131+ } ;
132+ }
133+
134+ interface NodeSqliteDbLike {
135+ readonly prepare : ( sql : string ) => NodeSqliteStatementLike ;
136+ readonly exec : ( sql : string ) => void ;
137+ readonly close : ( ) => void ;
138+ }
139+
140+ interface NodeSqliteModule {
141+ readonly DatabaseSync : new ( filename : string ) => NodeSqliteDbLike ;
142+ }
143+
124144const isReaderSql = ( sql : string ) : boolean =>
125145 / ^ \s * ( s e l e c t | p r a g m a | w i t h | e x p l a i n | v a l u e s ) \b / i. test ( sql ) ;
126146
147+ const sqliteEscape = ( value : string ) : string => value . replaceAll ( "'" , "''" ) ;
148+
149+ const serializeToBytes = ( exec : ( sql : string ) => void ) : Uint8Array => {
150+ const path = join ( tmpdir ( ) , `evolu-test-export-${ randomUUID ( ) } .db` ) ;
151+
152+ try {
153+ exec ( `vacuum into '${ sqliteEscape ( path ) } '` ) ;
154+ const file = readFileSync ( path ) ;
155+ const { buffer } = file ;
156+
157+ if ( buffer instanceof ArrayBuffer ) {
158+ return new Uint8Array ( buffer , file . byteOffset , file . byteLength ) ;
159+ }
160+
161+ return new Uint8Array ( file ) ;
162+ } finally {
163+ rmSync ( path , { force : true } ) ;
164+ }
165+ } ;
166+
127167const createDb = ( filename : string ) : DbLike => {
128168 try {
129169 const BetterSQLite = require ( "better-sqlite3" ) as BetterSqliteConstructor ;
@@ -141,10 +181,9 @@ const createDb = (filename: string): DbLike => {
141181 serialize : ( ) => db . serialize ( ) ,
142182 close : ( ) => db . close ( ) ,
143183 } ;
144- } catch ( error ) {
145- const hasBunRuntime = ( globalThis as Record < string , unknown > ) . Bun != null ;
146- if ( ! hasBunRuntime ) throw error ;
184+ } catch { }
147185
186+ try {
148187 const { Database } = require ( "bun:sqlite" ) as BunSqliteModule ;
149188 const db = new Database ( filename ) ;
150189
@@ -160,7 +199,25 @@ const createDb = (filename: string): DbLike => {
160199 serialize : ( ) => db . serialize ( ) ,
161200 close : ( ) => db . close ( ) ,
162201 } ;
163- }
202+ } catch { }
203+
204+ const { DatabaseSync } = require ( "node:sqlite" ) as NodeSqliteModule ;
205+ const db = new DatabaseSync ( filename ) ;
206+
207+ return {
208+ prepare : ( sql ) => {
209+ const statement = db . prepare ( sql ) ;
210+ return {
211+ reader : isReaderSql ( sql ) ,
212+ all : ( ...parameters ) => statement . all ( ...parameters ) ,
213+ run : ( ...parameters ) => ( {
214+ changes : statement . run ( ...parameters ) . changes ?? 0 ,
215+ } ) ,
216+ } ;
217+ } ,
218+ serialize : ( ) => serializeToBytes ( ( sql ) => db . exec ( sql ) ) ,
219+ close : ( ) => db . close ( ) ,
220+ } ;
164221} ;
165222
166223// Duplicated from @evolu /nodejs because @evolu /common cannot depend on it
0 commit comments