@@ -11,21 +11,17 @@ import {
1111	type  CreateReporter , 
1212}  from  '@epic-web/cachified' 
1313import  {  remember  }  from  '@epic-web/remember' 
14- import  Database  from  'better-sqlite3' 
1514import  {  LRUCache  }  from  'lru-cache' 
15+ import  {  DatabaseSync  }  from  'node:sqlite' 
1616import  {  z  }  from  'zod' 
17- import  {  updatePrimaryCacheValue  }  from  '#app/routes/admin+/cache_.sqlite.server.ts' 
18- import  {  getInstanceInfo ,  getInstanceInfoSync  }  from  './litefs.server.ts' 
1917import  {  cachifiedTimingReporter ,  type  Timings  }  from  './timing.server.ts' 
2018
2119const  CACHE_DATABASE_PATH  =  process . env . CACHE_DATABASE_PATH 
2220
2321const  cacheDb  =  remember ( 'cacheDb' ,  createDatabase ) 
2422
25- function  createDatabase ( tryAgain  =  true ) : Database . Database  { 
26- 	const  db  =  new  Database ( CACHE_DATABASE_PATH ) 
27- 	const  {  currentIsPrimary }  =  getInstanceInfoSync ( ) 
28- 	if  ( ! currentIsPrimary )  return  db 
23+ function  createDatabase ( tryAgain  =  true ) : DatabaseSync  { 
24+ 	const  db  =  new  DatabaseSync ( CACHE_DATABASE_PATH ) 
2925
3026	try  { 
3127		// create cache table with metadata JSON column and value JSON column if it does not exist already 
@@ -46,6 +42,7 @@ function createDatabase(tryAgain = true): Database.Database {
4642		} 
4743		throw  error 
4844	} 
45+ 
4946	return  db 
5047} 
5148
@@ -68,6 +65,31 @@ export const lruCache = {
6865	delete : ( key )  =>  lru . delete ( key ) , 
6966}  satisfies  Cache 
7067
68+ const  isBuffer  =  ( obj : unknown ) : obj  is Buffer  => 
69+ 	Buffer . isBuffer ( obj )  ||  obj  instanceof  Uint8Array 
70+ 
71+ function  bufferReplacer ( _key : string ,  value : unknown )  { 
72+ 	if  ( isBuffer ( value ) )  { 
73+ 		return  { 
74+ 			__isBuffer : true , 
75+ 			data : value . toString ( 'base64' ) , 
76+ 		} 
77+ 	} 
78+ 	return  value 
79+ } 
80+ 
81+ function  bufferReviver ( _key : string ,  value : unknown )  { 
82+ 	if  ( 
83+ 		value  && 
84+ 		typeof  value  ===  'object'  && 
85+ 		'__isBuffer'  in  value  && 
86+ 		( value  as  any ) . data 
87+ 	)  { 
88+ 		return  Buffer . from ( ( value  as  any ) . data ,  'base64' ) 
89+ 	} 
90+ 	return  value 
91+ } 
92+ 
7193const  cacheEntrySchema  =  z . object ( { 
7294	metadata : z . object ( { 
7395		createdTime : z . number ( ) , 
@@ -81,75 +103,46 @@ const cacheQueryResultSchema = z.object({
81103	value : z . string ( ) , 
82104} ) 
83105
106+ const  getStatement  =  cacheDb . prepare ( 
107+ 	'SELECT value, metadata FROM cache WHERE key = ?' , 
108+ ) 
109+ const  setStatement  =  cacheDb . prepare ( 
110+ 	'INSERT OR REPLACE INTO cache (key, value, metadata) VALUES (?, ?, ?)' , 
111+ ) 
112+ const  deleteStatement  =  cacheDb . prepare ( 'DELETE FROM cache WHERE key = ?' ) 
113+ const  getAllKeysStatement  =  cacheDb . prepare ( 'SELECT key FROM cache LIMIT ?' ) 
114+ const  searchKeysStatement  =  cacheDb . prepare ( 
115+ 	'SELECT key FROM cache WHERE key LIKE ? LIMIT ?' , 
116+ ) 
117+ 
84118export  const  cache : CachifiedCache  =  { 
85119	name : 'SQLite cache' , 
86- 	get ( key )  { 
87- 		const  result  =  cacheDb 
88- 			. prepare ( 'SELECT value, metadata FROM cache WHERE key = ?' ) 
89- 			. get ( key ) 
120+ 	async  get ( key )  { 
121+ 		const  result  =  getStatement . get ( key ) 
90122		const  parseResult  =  cacheQueryResultSchema . safeParse ( result ) 
91123		if  ( ! parseResult . success )  return  null 
92124
93125		const  parsedEntry  =  cacheEntrySchema . safeParse ( { 
94126			metadata : JSON . parse ( parseResult . data . metadata ) , 
95- 			value : JSON . parse ( parseResult . data . value ) , 
127+ 			value : JSON . parse ( parseResult . data . value ,   bufferReviver ) , 
96128		} ) 
97129		if  ( ! parsedEntry . success )  return  null 
98130		const  {  metadata,  value }  =  parsedEntry . data 
99131		if  ( ! value )  return  null 
100132		return  {  metadata,  value } 
101133	} , 
102134	async  set ( key ,  entry )  { 
103- 		const  {  currentIsPrimary,  primaryInstance }  =  await  getInstanceInfo ( ) 
104- 		if  ( currentIsPrimary )  { 
105- 			cacheDb 
106- 				. prepare ( 
107- 					'INSERT OR REPLACE INTO cache (key, value, metadata) VALUES (@key, @value, @metadata)' , 
108- 				) 
109- 				. run ( { 
110- 					key, 
111- 					value : JSON . stringify ( entry . value ) , 
112- 					metadata : JSON . stringify ( entry . metadata ) , 
113- 				} ) 
114- 		}  else  { 
115- 			// fire-and-forget cache update 
116- 			void  updatePrimaryCacheValue ( { 
117- 				key, 
118- 				cacheValue : entry , 
119- 			} ) . then ( ( response )  =>  { 
120- 				if  ( ! response . ok )  { 
121- 					console . error ( 
122- 						`Error updating cache value for key "${ key } ${ primaryInstance } ${ response . status } ${ response . statusText }  , 
123- 						{  entry } , 
124- 					) 
125- 				} 
126- 			} ) 
127- 		} 
135+ 		const  value  =  JSON . stringify ( entry . value ,  bufferReplacer ) 
136+ 		setStatement . run ( key ,  value ,  JSON . stringify ( entry . metadata ) ) 
128137	} , 
129138	async  delete ( key )  { 
130- 		const  {  currentIsPrimary,  primaryInstance }  =  await  getInstanceInfo ( ) 
131- 		if  ( currentIsPrimary )  { 
132- 			cacheDb . prepare ( 'DELETE FROM cache WHERE key = ?' ) . run ( key ) 
133- 		}  else  { 
134- 			// fire-and-forget cache update 
135- 			void  updatePrimaryCacheValue ( { 
136- 				key, 
137- 				cacheValue : undefined , 
138- 			} ) . then ( ( response )  =>  { 
139- 				if  ( ! response . ok )  { 
140- 					console . error ( 
141- 						`Error deleting cache value for key "${ key } ${ primaryInstance } ${ response . status } ${ response . statusText }  , 
142- 					) 
143- 				} 
144- 			} ) 
145- 		} 
139+ 		deleteStatement . run ( key ) 
146140	} , 
147141} 
148142
149143export  async  function  getAllCacheKeys ( limit : number )  { 
150144	return  { 
151- 		sqlite : cacheDb 
152- 			. prepare ( 'SELECT key FROM cache LIMIT ?' ) 
145+ 		sqlite : getAllKeysStatement 
153146			. all ( limit ) 
154147			. map ( ( row )  =>  ( row  as  {  key : string  } ) . key ) , 
155148		lru : [ ...lru . keys ( ) ] , 
@@ -158,8 +151,7 @@ export async function getAllCacheKeys(limit: number) {
158151
159152export  async  function  searchCacheKeys ( search : string ,  limit : number )  { 
160153	return  { 
161- 		sqlite : cacheDb 
162- 			. prepare ( 'SELECT key FROM cache WHERE key LIKE ? LIMIT ?' ) 
154+ 		sqlite : searchKeysStatement 
163155			. all ( `%${ search }  ,  limit ) 
164156			. map ( ( row )  =>  ( row  as  {  key : string  } ) . key ) , 
165157		lru : [ ...lru . keys ( ) ] . filter ( ( key )  =>  key . includes ( search ) ) , 
0 commit comments