Cross-platform immutable file cache with pluggable storage adapters for React Native and Web
npm install @dynlabs/react-native-immutable-file-cache
Cross-platform immutable file cache for React Native and Web
Installation •
Quick Start •
API •
Adapters •
Contributing
---
- Immutable entries — Once cached, entries cannot be overwritten
- Cross-platform — iOS, Android, and Web support
- Pluggable adapters — Swap storage backends without code changes
- TTL & LRU pruning — Automatic cache management
- Atomic writes — Crash-safe file operations
- TypeScript first — Full type safety
``bash`
npm install @dynlabs/react-native-immutable-file-cache
#### React Native
`bash`
npm install react-native-fs
cd ios && pod install
#### Web
No additional dependencies required — uses Cache Storage and IndexedDB.
`typescript
import { createImmutableFileCache } from "@dynlabs/react-native-immutable-file-cache";
const cache = await createImmutableFileCache({
namespace: "images",
defaultTtlMs: 7 24 60 60 1000, // 7 days
maxSizeBytes: 100 1024 1024, // 100 MB
});
// Cache from URL
const result = await cache.putFromUrl("avatar-123", "https://example.com/avatar.jpg");
if (result.status === "created") {
console.log("Cached:", result.entry.sizeBytes, "bytes");
}
// Retrieve
const entry = await cache.get("avatar-123");
if (entry) {
}
`
`typescript`
createImmutableFileCache({
namespace: "default", // Cache isolation namespace
defaultTtlMs: undefined, // Default TTL (ms), undefined = no expiry
maxSizeBytes: undefined, // Max size, triggers LRU when exceeded
autoPruneExpired: true, // Auto-remove expired entries
adapter: undefined, // Custom storage adapter
hashFn: undefined, // Custom hash function (default: SHA-256)
onEvent: undefined, // Event handler for observability
});
All put operations are immutable — returns { status: "exists" } if the key already exists.
`typescript
// From URL
await cache.putFromUrl(key, url, { ttlMs, ext, metadata, headers, onProgress });
// From file (Native only)
await cache.putFromFile(key, filePath, options);
// From Blob (Web only)
await cache.putFromBlob(key, blob, options);
// From bytes
await cache.putFromBytes(key, new Uint8Array([...]), options);
`
`typescript
// Get with URI (updates lastAccessedAt)
const result = await cache.get(key);
// => { entry, uri } | null
// Check existence
const exists = await cache.has(key);
// Peek without updating access time
const entry = await cache.peek(key);
// Get URI only
const uri = await cache.getUri(key);
// Read-through cache
const result = await cache.getOrPut(key, async (k) => ({
bytes: new Uint8Array([...]),
ttlMs: 86400000,
ext: ".json",
}));
`
`typescript
// List entries
const entries = await cache.list({
sortBy: "createdAt", // "createdAt" | "lastAccessedAt" | "sizeBytes" | "key"
order: "desc", // "asc" | "desc"
limit: 10,
offset: 0,
filter: (e) => e.sizeBytes > 1000,
});
// Get all keys
const keys = await cache.keys();
// Count entries
const count = await cache.count();
// Statistics
const stats = await cache.stats();
// => { entryCount, totalSizeBytes, oldestEntry?, newestEntry? }
`
`typescript
// Remove single entry
await cache.remove(key);
// Remove expired entries
await cache.removeExpired();
// => { removedCount, freedBytes, removedKeys }
// LRU prune to size limit
await cache.pruneLru(50 1024 1024);
// Clear all
await cache.clear();
// Validate & repair index
await cache.validateAndRepair();
// TTL management
await cache.touch(key); // Refresh access time
await cache.setTtl(key, 86400000); // Set new TTL
await cache.extendTtl(key, 3600000); // Extend TTL
`
`typescript
// Flush pending writes (when using debounce)
await cache.flush();
// Destroy and release resources
await cache.destroy();
`
The library uses a pluggable adapter architecture:
| Adapter | Platform | Storage |
|---------|----------|---------|
| RNFS | iOS/Android | react-native-fs |
| Web | Browser | Cache Storage + IndexedDB |
| Memory | All | In-memory (testing) |
`typescript
// React Native — uses RNFS adapter
import { createImmutableFileCache } from "@dynlabs/react-native-immutable-file-cache";
// Web — uses Web adapter
import { createImmutableFileCache } from "@dynlabs/react-native-immutable-file-cache/web";
`
`typescript
import { createImmutableFileCache, createRnfsAdapter } from "@dynlabs/react-native-immutable-file-cache";
const cache = await createImmutableFileCache({
adapter: createRnfsAdapter({ baseDir: "/custom/path", namespace: "my-app" }),
});
`
| Source | RNFS | Web | Memory |
|--------|------|-----|--------|
| url | ✓ | ✓ | ✓ |file
| | ✓ | — | ✓ |blob
| | — | ✓ | ✓ |bytes
| | ✓ | ✓ | ✓ |
`typescript
import type { IStorageAdapter } from "@dynlabs/react-native-immutable-file-cache";
const myAdapter: IStorageAdapter = {
kind: "my-storage",
rootId: "my-root",
supportedSources: new Set(["url", "bytes"]),
async ensureDir(path) { / ... / },
async exists(path) { / ... / },
async remove(path) { / ... / },
async removeDir(path) { / ... / },
async listDir(path) { / ... / },
async readText(path, encoding) { / ... / },
async writeTextAtomic(path, content, encoding) { / ... / },
async stat(path) { / ... / },
async writeBinaryAtomic(path, source, options) { / ... / },
async getPublicUri(path) { / ... / },
};
`
`typescript
import {
CacheError,
UnsupportedSourceError,
AdapterIOError,
CorruptIndexError,
ImmutableConflictError,
EntryNotFoundError,
} from "@dynlabs/react-native-immutable-file-cache";
try {
await cache.putFromBlob("key", blob);
} catch (error) {
if (error instanceof UnsupportedSourceError) {
console.log(${error.sourceType} not supported on ${error.adapterKind});`
}
}
`typescriptHIT: ${event.key} (${event.ageMs}ms old)
const cache = await createImmutableFileCache({
onEvent: (event) => {
switch (event.type) {
case "cache_hit":
console.log();MISS: ${event.key} (${event.reason})
break;
case "cache_miss":
console.log();WRITE: ${event.key} (${event.sizeBytes} bytes)
break;
case "cache_write":
console.log();PRUNE: removed ${event.removedCount} entries
break;
case "cache_prune":
console.log();`
break;
}
},
});
Use the memory adapter for unit tests:
`typescript
import { CacheEngine, createMemoryAdapter } from "@dynlabs/react-native-immutable-file-cache";
const adapter = createMemoryAdapter("test");
const cache = new CacheEngine({ namespace: "test" }, adapter);
await cache.init();
// Run tests...
adapter._reset(); // Clean up
`
`bash
git clone https://github.com/dienp/react-native-immutable-file-cache.git
cd react-native-immutable-file-cache
npm install
npm run typecheck # Type check
npm run lint # Lint
npm test # Run tests
npm run build # Build
``
See CONTRIBUTING.md for details.