@@ -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 } " on primary instance (${ 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 } " on primary instance (${ 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