Skip to content

Commit a46c5dd

Browse files
committed
Ensure D1 cache table exists
1 parent 9d4fd6c commit a46c5dd

1 file changed

Lines changed: 64 additions & 13 deletions

File tree

functions/_lib/cache.ts

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,50 @@ export type D1DatabaseLike = {
99

1010
type 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+
1237
export 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

4794
export 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

Comments
 (0)