@@ -9,15 +9,50 @@ export type D1DatabaseLike = {
99
1010type CacheRow = { value : string ; expires_at : number } ;
1111
12+ async function ensureCacheTable ( db : D1DatabaseLike ) : Promise < void > {
13+ await db
14+ . prepare (
15+ "CREATE TABLE IF NOT EXISTS kv_cache (" +
16+ "key TEXT PRIMARY KEY, " +
17+ "value TEXT NOT NULL, " +
18+ "updated_at INTEGER NOT NULL, " +
19+ "expires_at INTEGER NOT NULL" +
20+ ")"
21+ )
22+ . bind ( )
23+ . run ( ) ;
24+
25+ await db
26+ . prepare ( "CREATE INDEX IF NOT EXISTS kv_cache_expires_at ON kv_cache (expires_at)" )
27+ . bind ( )
28+ . run ( ) ;
29+ }
30+
31+ function isMissingTableError ( err : unknown ) : boolean {
32+ const msg =
33+ err instanceof Error ? err . message : typeof err === "string" ? err : "" ;
34+ return msg . includes ( "no such table: kv_cache" ) ;
35+ }
36+
1237export async function getCacheJson < T > (
1338 db : D1DatabaseLike ,
1439 key : string
1540) : Promise < { hit : true ; value : T } | { hit : false } > {
1641 const now = Math . floor ( Date . now ( ) / 1000 ) ;
17- const row = await db
18- . prepare ( "SELECT value, expires_at FROM kv_cache WHERE key = ?1" )
19- . bind ( key )
20- . first < CacheRow > ( ) ;
42+ let row : CacheRow | null = null ;
43+ try {
44+ row = await db
45+ . prepare ( "SELECT value, expires_at FROM kv_cache WHERE key = ?1" )
46+ . bind ( key )
47+ . first < CacheRow > ( ) ;
48+ } catch ( err ) {
49+ if ( ! isMissingTableError ( err ) ) throw err ;
50+ await ensureCacheTable ( db ) ;
51+ row = await db
52+ . prepare ( "SELECT value, expires_at FROM kv_cache WHERE key = ?1" )
53+ . bind ( key )
54+ . first < CacheRow > ( ) ;
55+ }
2156
2257 if ( ! row ) return { hit : false } ;
2358 if ( typeof row . expires_at !== "number" || row . expires_at <= now ) return { hit : false } ;
@@ -35,17 +70,33 @@ export async function setCacheJson(
3570 const expiresAt = now + ttlSeconds ;
3671 const json = JSON . stringify ( value ) ;
3772
38- await db
39- . prepare (
40- "INSERT INTO kv_cache(key, value, updated_at, expires_at) VALUES (?1, ?2, ?3, ?4) " +
41- "ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at, expires_at = excluded.expires_at"
42- )
43- . bind ( key , json , now , expiresAt )
44- . run ( ) ;
73+ try {
74+ await db
75+ . prepare (
76+ "INSERT INTO kv_cache(key, value, updated_at, expires_at) VALUES (?1, ?2, ?3, ?4) " +
77+ "ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at, expires_at = excluded.expires_at"
78+ )
79+ . bind ( key , json , now , expiresAt )
80+ . run ( ) ;
81+ } catch ( err ) {
82+ if ( ! isMissingTableError ( err ) ) throw err ;
83+ await ensureCacheTable ( db ) ;
84+ await db
85+ . prepare (
86+ "INSERT INTO kv_cache(key, value, updated_at, expires_at) VALUES (?1, ?2, ?3, ?4) " +
87+ "ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at, expires_at = excluded.expires_at"
88+ )
89+ . bind ( key , json , now , expiresAt )
90+ . run ( ) ;
91+ }
4592}
4693
4794export async function purgeExpired ( db : D1DatabaseLike ) : Promise < void > {
4895 const now = Math . floor ( Date . now ( ) / 1000 ) ;
49- await db . prepare ( "DELETE FROM kv_cache WHERE expires_at <= ?1" ) . bind ( now ) . run ( ) ;
96+ try {
97+ await db . prepare ( "DELETE FROM kv_cache WHERE expires_at <= ?1" ) . bind ( now ) . run ( ) ;
98+ } catch ( err ) {
99+ if ( ! isMissingTableError ( err ) ) throw err ;
100+ await ensureCacheTable ( db ) ;
101+ }
50102}
51-
0 commit comments