A decentralized music tracking and discovery platform built on AT Protocol 馃幍
listenbrainz
spotify
atproto
lastfm
musicbrainz
scrobbling
1import type { CacheResult, DidCache, DidDocument } from "@atproto/identity";
2import type { Storage } from "unstorage";
3
4const HOUR = 60e3 * 60;
5const DAY = HOUR * 24;
6
7type CacheVal = {
8 doc: DidDocument;
9 updatedAt: number;
10};
11
12/**
13 * An unstorage based DidCache with staleness and max TTL
14 */
15export class StorageCache implements DidCache {
16 public staleTTL: number;
17 public maxTTL: number;
18 public cache: Storage<CacheVal>;
19 private prefix: string;
20 constructor({
21 store,
22 prefix,
23 staleTTL,
24 maxTTL,
25 }: {
26 store: Storage;
27 prefix: string;
28 staleTTL?: number;
29 maxTTL?: number;
30 }) {
31 this.cache = store as Storage<CacheVal>;
32 this.prefix = prefix;
33 this.staleTTL = staleTTL ?? HOUR;
34 this.maxTTL = maxTTL ?? DAY;
35 }
36
37 async cacheDid(did: string, doc: DidDocument): Promise<void> {
38 await this.cache.set(this.prefix + did, { doc, updatedAt: Date.now() });
39 }
40
41 async refreshCache(
42 did: string,
43 getDoc: () => Promise<DidDocument | null>,
44 ): Promise<void> {
45 const doc = await getDoc();
46 if (doc) {
47 await this.cacheDid(did, doc);
48 }
49 }
50
51 async checkCache(did: string): Promise<CacheResult | null> {
52 const val = await this.cache.get<CacheVal>(this.prefix + did);
53 if (!val) return null;
54 const now = Date.now();
55 const expired = now > val.updatedAt + this.maxTTL;
56 const stale = now > val.updatedAt + this.staleTTL;
57 return {
58 ...val,
59 did,
60 stale,
61 expired,
62 };
63 }
64
65 async clearEntry(did: string): Promise<void> {
66 await this.cache.remove(this.prefix + did);
67 }
68
69 async clear(): Promise<void> {
70 await this.cache.clear(this.prefix);
71 }
72}