@@ -11,21 +11,17 @@ import {
11
11
type CreateReporter ,
12
12
} from '@epic-web/cachified'
13
13
import { remember } from '@epic-web/remember'
14
- import Database from 'better-sqlite3'
15
14
import { LRUCache } from 'lru-cache'
15
+ import { DatabaseSync } from 'node:sqlite'
16
16
import { z } from 'zod'
17
- import { updatePrimaryCacheValue } from '#app/routes/admin+/cache_.sqlite.server.ts'
18
- import { getInstanceInfo , getInstanceInfoSync } from './litefs.server.ts'
19
17
import { cachifiedTimingReporter , type Timings } from './timing.server.ts'
20
18
21
19
const CACHE_DATABASE_PATH = process . env . CACHE_DATABASE_PATH
22
20
23
21
const cacheDb = remember ( 'cacheDb' , createDatabase )
24
22
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 )
29
25
30
26
try {
31
27
// 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 {
46
42
}
47
43
throw error
48
44
}
45
+
49
46
return db
50
47
}
51
48
@@ -68,6 +65,31 @@ export const lruCache = {
68
65
delete : ( key ) => lru . delete ( key ) ,
69
66
} satisfies Cache
70
67
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
+
71
93
const cacheEntrySchema = z . object ( {
72
94
metadata : z . object ( {
73
95
createdTime : z . number ( ) ,
@@ -81,75 +103,46 @@ const cacheQueryResultSchema = z.object({
81
103
value : z . string ( ) ,
82
104
} )
83
105
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
+
84
118
export const cache : CachifiedCache = {
85
119
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 )
90
122
const parseResult = cacheQueryResultSchema . safeParse ( result )
91
123
if ( ! parseResult . success ) return null
92
124
93
125
const parsedEntry = cacheEntrySchema . safeParse ( {
94
126
metadata : JSON . parse ( parseResult . data . metadata ) ,
95
- value : JSON . parse ( parseResult . data . value ) ,
127
+ value : JSON . parse ( parseResult . data . value , bufferReviver ) ,
96
128
} )
97
129
if ( ! parsedEntry . success ) return null
98
130
const { metadata, value } = parsedEntry . data
99
131
if ( ! value ) return null
100
132
return { metadata, value }
101
133
} ,
102
134
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 ) )
128
137
} ,
129
138
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 )
146
140
} ,
147
141
}
148
142
149
143
export async function getAllCacheKeys ( limit : number ) {
150
144
return {
151
- sqlite : cacheDb
152
- . prepare ( 'SELECT key FROM cache LIMIT ?' )
145
+ sqlite : getAllKeysStatement
153
146
. all ( limit )
154
147
. map ( ( row ) => ( row as { key : string } ) . key ) ,
155
148
lru : [ ...lru . keys ( ) ] ,
@@ -158,8 +151,7 @@ export async function getAllCacheKeys(limit: number) {
158
151
159
152
export async function searchCacheKeys ( search : string , limit : number ) {
160
153
return {
161
- sqlite : cacheDb
162
- . prepare ( 'SELECT key FROM cache WHERE key LIKE ? LIMIT ?' )
154
+ sqlite : searchKeysStatement
163
155
. all ( `%${ search } %` , limit )
164
156
. map ( ( row ) => ( row as { key : string } ) . key ) ,
165
157
lru : [ ...lru . keys ( ) ] . filter ( ( key ) => key . includes ( search ) ) ,
0 commit comments