1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { sqlite } from "https://esm.town/v/std/sqlite";
// Retrieve the table name from the environment variable
// Fail if it's not present.
const tableName = Deno.env.get("CACHE_TABLE_NAME");
if (tableName === undefined) {
console.error(
"Env variable 'CACHE_TABLE_NAME' is not configured. Stopping execution.",
);
throw new Error("Env variable 'CACHE_TABLE_NAME' is not configured");
}
/**
* Defualt TTL (time to live) - this describes how long a default cache entry
* is guaranteed to persist. The default value is 24 hours, can be changed by
* setting the `CACHE_DEFAULT_TTL` environment variable with a new default value
* in seconds.
*/
const defaultTTL = parseInt(
Deno.env.get("CACHE_DEFAULT_TTL") || `${24 * 60 * 60}`,
);
/**
* Setup the cache table if it does not exist.
* Make sure to set the `CACHE_TABLE_NAME` environment variable first.
*/
export async function setup() {
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS ${tableName} (
key TEXT PRIMARY KEY,
content TEXT NOT NULL,
expires_at DATETIME
)
`);
console.log("Cache setup complete.");
}
/**
* Check if the provided key exists (has value) in the cache.
* If the key is expired, it's considered non-existent.
* @param key The key to check if exists
* @returns The value indicating whether the key is present in cache.
*/
export async function exists(key): Promise<Boolean> {
const result = await sqlite.execute({
sql: `SELECT 1 FROM ${tableName} WHERE key = :key AND (expires_at IS NULL OR expires_at > datetime('now'))`,
args: { key },
});
return result.rows.length > 0;
}
/**
* Get a value from the cache by key. You can provide a type of the return value or it will default to `unknown`.
* @param key The key to get the value for
* @returns The value for the key, or `null` if the key does not exist or has expired
*/
export async function get<T = unknown>(key): Promise<T | null> {
const result = await sqlite.execute({
sql: `SELECT content FROM ${tableName} WHERE key = :key AND (expires_at IS NULL OR expires_at > datetime('now'))`,
args: { key },
});
return result.rows.length > 0
? JSON.parse(result.rows[0][0] as string) as T
: null;
}
/**
* Set a value in the cache
* @param key The key to set the value for
* @param value The value to set
* @param ttl The time to live in seconds. If not set, the default TTL is used.
* @returns The number of keys set (1 if the key was inserted/updated, 0 if the ttl was 0)
*/
export async function set(key, value, ttl: number = defaultTTL): Promise<number> {
if (ttl <= 0) return 0;
const expires_at = ttl ? `datetime('now', '+${ttl} seconds')` : null;
const result = await sqlite.execute({
sql:
`INSERT INTO ${tableName} (key, content, expires_at) VALUES (:key, :content, ${expires_at}) ON CONFLICT(key) DO UPDATE SET content = :content, expires_at = ${expires_at}`,
args: { key, content: JSON.stringify(value) },
});
return result.rowsAffected;
}
/**
* Set a value in the cache
* @param key The key to set the value for
* @param value The value to set
* @param expiresAt The expiration time as a UTC date string. If not set, the default expiration is used.
* @returns The number of keys set (1 if the key was inserted/updated, 0 if the expiresAt was in the past)
*/
export async function setUntil(key: string, value: unknown, expiresAt: string): Promise<number> {
const currentDateTime = new Date().toISOString();
if (expiresAt <= currentDateTime) return 0;
const result = await sqlite.execute({
sql:
`INSERT INTO ${tableName} (key, content, expires_at) VALUES (:key, :content, :expiresAt) ON CONFLICT(key) DO UPDATE SET content = :content, expires_at = :expiresAt`,
args: { key, content: JSON.stringify(value), expiresAt },
});