Repository navigation
Expand file tree
/
Copy pathdatabase.cjs
More file actions
13 lines (13 loc) · 2.76 KB
/
Copy pathdatabase.cjs
File metadata and controls
13 lines (13 loc) · 2.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
// Preserve the app's atomic, synchronous SQLite operations while network I/O
// runs on a dedicated worker. This bridge deliberately fails closed on timeout:
// an uncertain remote write must never be retried or redirected to a local file.
const {Worker}=require('node:worker_threads'),path=require('node:path');
class TursoDatabase {
constructor({url,authToken,allowLocal=false,timeout=45000}){if(!url||(!allowLocal&&!/^(libsql|https):\/\//.test(url)))throw Error('Use a libSQL database URL from Turso.');if(!allowLocal&&!authToken)throw Error('TURSO_AUTH_TOKEN is required.');this.kind='turso';this.closed=false;this.timeout=timeout;this.control=new Int32Array(new SharedArrayBuffer(8));this.bytes=new Uint8Array(new SharedArrayBuffer(64*1024*1024));this.worker=new Worker(path.join(__dirname,'turso-worker.cjs'),{workerData:{url,authToken,control:this.control.buffer,buffer:this.bytes.buffer}});this.worker.on('error',()=>{this.closed=true});try{this.call('query','SELECT 1',[])}catch(error){this.close();throw error}}
call(operation,sql,args=[]){if(this.closed)throw Error('Database connection is unavailable. Restart the server.');Atomics.store(this.control,0,0);this.worker.postMessage({operation,sql,args});if(Atomics.wait(this.control,0,0,this.timeout)==='timed-out'){this.close(false);throw Error('Database request timed out; its outcome may be unknown. Reload before retrying.')}const reply=JSON.parse(Buffer.from(this.bytes.subarray(0,Atomics.load(this.control,1))).toString('utf8'));if(reply.error)throw Object.assign(Error(reply.error.message),{code:reply.error.code});return reply.value}
exec(sql){return this.call('exec',sql)}
prepare(sql){return {all:(...args)=>this.call('query',sql,args).rows,get:(...args)=>this.call('query',sql,args).rows[0],run:(...args)=>{const result=this.call('query',sql,args);return {changes:result.rowsAffected,lastInsertRowid:result.lastInsertRowid}}}}
close(graceful=true){if(this.closing)return this.closing;let natural=false;const finished=new Promise(resolve=>this.worker.once('exit',resolve));try{if(graceful&&!this.closed){this.call('close','',[]);natural=true}}finally{this.closed=true;this.closing=natural?finished:this.worker.terminate()}return this.closing}
}
function createDatabase({database,turso={}}){const url=turso.url||process.env.TURSO_DATABASE_URL,authToken=turso.authToken||process.env.TURSO_AUTH_TOKEN;if(url||authToken){if(!url||!authToken&&!turso.allowLocal)throw Error('Set both TURSO_DATABASE_URL and TURSO_AUTH_TOKEN.');return new TursoDatabase({...turso,url,authToken})}if(process.env.REQUIRE_TURSO==='true')throw Error('This deployment requires Turso. Set TURSO_DATABASE_URL and TURSO_AUTH_TOKEN.');const {DatabaseSync}=require('node:sqlite'),db=new DatabaseSync(database);db.kind='sqlite';return db}
module.exports={TursoDatabase,createDatabase};