@terrafirmagreg-team/data 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrafirmagreg-team/data",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "description": "Storage drivers and shared helpers",
6
6
  "license": "LGPL-3.0-only",
@@ -15,13 +15,13 @@
15
15
  "src"
16
16
  ],
17
17
  "dependencies": {
18
- "fumadb": "0.6.0",
18
+ "fumadb": "0.7.1",
19
19
  "kysely": "^0.29.8",
20
20
  "kysely-d1": "^0.4.0",
21
21
  "server-only": "^0.0.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/node": "^26.6.5"
24
+ "@types/node": "^26.6.6"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
@@ -0,0 +1,62 @@
1
+ import 'server-only';
2
+ import { createHash } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { dataDb, dbSql } from './db';
5
+ import { loadJsonFile, saveJsonFile } from './json-file';
6
+
7
+ type Entry = { value: unknown; expires: number };
8
+
9
+ let tableReady: Promise<unknown> | undefined;
10
+
11
+ function fileFor(key: string): string {
12
+ const name = key.replace(/[^a-z0-9._-]+/gi, '_').slice(0, 60);
13
+ const hash = createHash('sha1').update(key).digest('hex').slice(0, 8);
14
+ return join(process.cwd(), 'data', 'cache', `${name}-${hash}.json`);
15
+ }
16
+
17
+ async function d1Read(key: string): Promise<Entry | undefined> {
18
+ try {
19
+ tableReady ??= dbSql`
20
+ CREATE TABLE IF NOT EXISTS tfg_cache (key TEXT PRIMARY KEY, value TEXT NOT NULL, expires INTEGER NOT NULL)
21
+ `;
22
+ await tableReady;
23
+ const rows = (await dbSql`
24
+ SELECT value, expires FROM tfg_cache WHERE key = ${key}
25
+ `) as { rows?: { value: string; expires: number }[] } | undefined;
26
+ const row = rows?.rows?.[0];
27
+ return row
28
+ ? { value: JSON.parse(row.value), expires: row.expires }
29
+ : undefined;
30
+ } catch {
31
+ return undefined;
32
+ }
33
+ }
34
+
35
+ async function d1Write(key: string, entry: Entry): Promise<void> {
36
+ try {
37
+ await tableReady;
38
+ await dbSql`
39
+ INSERT OR REPLACE INTO tfg_cache (key, value, expires)
40
+ VALUES (${key}, ${JSON.stringify(entry.value)}, ${entry.expires})
41
+ `;
42
+ } catch {}
43
+ }
44
+
45
+ export async function persistentCache<T>(
46
+ key: string,
47
+ ttlMs: number,
48
+ loader: () => Promise<T | undefined>,
49
+ ): Promise<T | undefined> {
50
+ const db = await dataDb().catch(() => undefined);
51
+ const file = fileFor(key);
52
+ const entry = db
53
+ ? await d1Read(key)
54
+ : loadJsonFile<Entry | undefined>(file, () => undefined);
55
+ if (entry && entry.expires > Date.now()) return entry.value as T;
56
+ const value = await loader();
57
+ if (value === undefined) return entry?.value as T | undefined;
58
+ const next: Entry = { value, expires: Date.now() + ttlMs };
59
+ if (db) await d1Write(key, next);
60
+ else saveJsonFile(file, next);
61
+ return value;
62
+ }