1- import fs from 'fs'
1+ import fs from 'node:fs'
2+ import path from 'node:path'
3+ import { DatabaseSync } from 'node:sqlite'
24import {
35 type Cache ,
46 cachified as baseCachified ,
@@ -9,7 +11,6 @@ import {
911 totalTtl ,
1012} from '@epic-web/cachified'
1113import { remember } from '@epic-web/remember'
12- import Database , { type default as BetterSqlite3 } from 'better-sqlite3'
1314import { getInstanceInfo , getInstanceInfoSync } from 'litefs-js'
1415import { LRUCache } from 'lru-cache'
1516import { updatePrimaryCacheValue } from '#app/routes/resources+/cache.sqlite.ts'
@@ -21,8 +22,10 @@ const CACHE_DATABASE_PATH = getRequiredServerEnvVar('CACHE_DATABASE_PATH')
2122
2223const cacheDb = remember ( 'cacheDb' , createDatabase )
2324
24- function createDatabase ( tryAgain = true ) : BetterSqlite3 . Database {
25- const db = new Database ( CACHE_DATABASE_PATH )
25+ function createDatabase ( tryAgain = true ) : DatabaseSync {
26+ const parentDir = path . dirname ( CACHE_DATABASE_PATH )
27+ fs . mkdirSync ( parentDir , { recursive : true } )
28+ const db = new DatabaseSync ( CACHE_DATABASE_PATH )
2629 const { currentIsPrimary } = getInstanceInfoSync ( )
2730 if ( ! currentIsPrimary ) return db
2831
@@ -69,32 +72,57 @@ export const lruCache = {
6972 } ,
7073} satisfies Cache
7174
75+ const isBuffer = ( obj : unknown ) : obj is Buffer =>
76+ Buffer . isBuffer ( obj ) || obj instanceof Uint8Array
77+
78+ function bufferReplacer ( _key : string , value : unknown ) {
79+ if ( isBuffer ( value ) ) {
80+ return {
81+ __isBuffer : true ,
82+ data : value . toString ( 'base64' ) ,
83+ }
84+ }
85+ return value
86+ }
87+
88+ function bufferReviver ( _key : string , value : unknown ) {
89+ if (
90+ value &&
91+ typeof value === 'object' &&
92+ '__isBuffer' in value &&
93+ ( value as any ) . data
94+ ) {
95+ return Buffer . from ( ( value as any ) . data , 'base64' )
96+ }
97+ return value
98+ }
99+
72100const preparedGet = cacheDb . prepare (
73101 'SELECT value, metadata FROM cache WHERE key = ?' ,
74102)
75103const preparedSet = cacheDb . prepare (
76- 'INSERT OR REPLACE INTO cache (key, value, metadata) VALUES (@key, @value, @metadata )' ,
104+ 'INSERT OR REPLACE INTO cache (key, value, metadata) VALUES (?, ?, ? )' ,
77105)
78106const preparedDelete = cacheDb . prepare ( 'DELETE FROM cache WHERE key = ?' )
79107
80108export const cache : CachifiedCache = {
81109 name : 'SQLite cache' ,
82110 get ( key ) {
83- const result = preparedGet . get ( key ) as any // TODO: fix this with zod or something
111+ const result = preparedGet . get ( key ) as any
84112 if ( ! result ) return null
85113 return {
86114 metadata : JSON . parse ( result . metadata ) ,
87- value : JSON . parse ( result . value ) ,
115+ value : JSON . parse ( result . value , bufferReviver ) ,
88116 }
89117 } ,
90118 async set ( key , entry ) {
91119 const { currentIsPrimary, primaryInstance } = await getInstanceInfo ( )
92120 if ( currentIsPrimary ) {
93- preparedSet . run ( {
121+ preparedSet . run (
94122 key ,
95- value : JSON . stringify ( entry . value ) ,
96- metadata : JSON . stringify ( entry . metadata ) ,
97- } )
123+ JSON . stringify ( entry . value , bufferReplacer ) ,
124+ JSON . stringify ( entry . metadata ) ,
125+ )
98126 } else {
99127 // fire-and-forget cache update
100128 void updatePrimaryCacheValue ! ( {
0 commit comments