wip library to store cold objects in s3, warm objects on disk, and hot objects in memory
nodejs
typescript
1# tiered-storage
2
3Cascading cache that flows hot → warm → cold. Memory, disk, S3—or bring your own.
4
5## Features
6
7- **Cascading writes** - data flows down through all tiers
8- **Bubbling reads** - check hot first, fall back to warm, then cold
9- **Pluggable backends** - memory, disk, S3, or implement your own
10- **Selective placement** - skip tiers for big files that don't need memory caching
11- **Prefix invalidation** - `invalidate('user:')` nukes all user keys
12- **Optional compression** - transparent gzip
13
14## Install
15
16```bash
17npm install tiered-storage
18```
19
20## Example
21
22```typescript
23import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage'
24
25const storage = new TieredStorage({
26 tiers: {
27 hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }),
28 warm: new DiskStorageTier({ directory: './cache' }),
29 cold: new S3StorageTier({ bucket: 'my-bucket', region: 'us-east-1' }),
30 },
31 compression: true,
32})
33
34// critical file: keep in memory for instant serving
35await storage.set('site:abc/index.html', indexHtml)
36
37// big files: skip hot, let them live in warm + cold
38await storage.set('site:abc/video.mp4', videoData, { skipTiers: ['hot'] })
39await storage.set('site:abc/hero.png', imageData, { skipTiers: ['hot'] })
40
41// on read, bubbles up from wherever it lives
42const result = await storage.getWithMetadata('site:abc/index.html')
43console.log(result.source) // 'hot' - served from memory
44
45const video = await storage.getWithMetadata('site:abc/video.mp4')
46console.log(video.source) // 'warm' - served from disk, never touches memory
47
48// nuke entire site
49await storage.invalidate('site:abc/')
50```
51
52Hot tier stays small and fast. Warm tier has everything. Cold tier is the source of truth.
53
54## How it works
55
56```
57┌─────────────────────────────────────────────┐
58│ Cold (S3) - source of truth, all data │
59│ ↑ │
60│ Warm (disk) - everything hot has + more │
61│ ↑ │
62│ Hot (memory) - just the hottest stuff │
63└─────────────────────────────────────────────┘
64```
65
66Writes cascade **down**. Reads bubble **up**.
67
68## API
69
70### `storage.get(key)`
71
72Get data. Returns `null` if missing or expired.
73
74### `storage.getWithMetadata(key)`
75
76Get data plus which tier served it.
77
78### `storage.set(key, data, options?)`
79
80Store data. Options:
81
82```typescript
83{
84 ttl: 86400000, // custom TTL
85 skipTiers: ['hot'], // skip specific tiers
86 metadata: { ... }, // custom metadata
87}
88```
89
90### `storage.delete(key)`
91
92Delete from all tiers.
93
94### `storage.invalidate(prefix)`
95
96Delete all keys matching prefix. Returns count.
97
98### `storage.touch(key, ttl?)`
99
100Renew TTL.
101
102### `storage.listKeys(prefix?)`
103
104Async iterator over keys.
105
106### `storage.getStats()`
107
108Stats across all tiers.
109
110### `storage.bootstrapHot(limit?)`
111
112Warm up hot tier from warm tier. Run on startup.
113
114### `storage.bootstrapWarm(options?)`
115
116Warm up warm tier from cold tier.
117
118## Built-in tiers
119
120### MemoryStorageTier
121
122```typescript
123new MemoryStorageTier({
124 maxSizeBytes: 100 * 1024 * 1024,
125 maxItems: 1000,
126})
127```
128
129LRU eviction. Fast. Single process only.
130
131### DiskStorageTier
132
133```typescript
134new DiskStorageTier({
135 directory: './cache',
136 maxSizeBytes: 10 * 1024 * 1024 * 1024,
137 evictionPolicy: 'lru', // or 'fifo', 'size'
138})
139```
140
141Files on disk with `.meta` sidecars.
142
143### S3StorageTier
144
145```typescript
146new S3StorageTier({
147 bucket: 'data',
148 metadataBucket: 'metadata', // recommended!
149 region: 'us-east-1',
150})
151```
152
153Works with AWS S3, Cloudflare R2, MinIO. Use a separate metadata bucket—otherwise updating access counts requires copying entire objects.
154
155## Custom tiers
156
157Implement `StorageTier`:
158
159```typescript
160interface StorageTier {
161 get(key: string): Promise<Uint8Array | null>
162 set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void>
163 delete(key: string): Promise<void>
164 exists(key: string): Promise<boolean>
165 listKeys(prefix?: string): AsyncIterableIterator<string>
166 deleteMany(keys: string[]): Promise<void>
167 getMetadata(key: string): Promise<StorageMetadata | null>
168 setMetadata(key: string, metadata: StorageMetadata): Promise<void>
169 getStats(): Promise<TierStats>
170 clear(): Promise<void>
171}
172```
173
174## Running the demo
175
176```bash
177cp .env.example .env # add S3 creds
178bun run serve
179```
180
181Visit http://localhost:3000 to see it work. Check http://localhost:3000/admin/stats for live cache stats.
182
183## License
184
185MIT