@dynlabs/react-native-immutable-file-cache
v1.1.0
Published
Cross-platform immutable file cache with pluggable storage adapters for React Native and Web
Downloads
790
Maintainers
Readme
Features
- 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
Installation
npm install @dynlabs/react-native-immutable-file-cacheReact Native
npm install react-native-fs
cd ios && pod installWeb
No additional dependencies required — uses Cache Storage and IndexedDB.
Quick Start
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) {
<Image source={{ uri: entry.uri }} />
}API
Configuration
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
});Put Operations
All put operations are immutable — returns { status: "exists" } if the key already exists.
// 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);Get Operations
// 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",
}));List & Stats
// 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? }Cache Management
// 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 TTLLifecycle
// Flush pending writes (when using debounce)
await cache.flush();
// Destroy and release resources
await cache.destroy();Adapters
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) |
Default Adapter (Auto-selected)
// 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";Custom Adapter
import { createImmutableFileCache, createRnfsAdapter } from "@dynlabs/react-native-immutable-file-cache";
const cache = await createImmutableFileCache({
adapter: createRnfsAdapter({ baseDir: "/custom/path", namespace: "my-app" }),
});Supported Sources
| Source | RNFS | Web | Memory |
|--------|------|-----|--------|
| url | ✓ | ✓ | ✓ |
| file | ✓ | — | ✓ |
| blob | — | ✓ | ✓ |
| bytes | ✓ | ✓ | ✓ |
Implementing Custom Adapters
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) { /* ... */ },
};Error Handling
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}`);
}
}Observability
const cache = await createImmutableFileCache({
onEvent: (event) => {
switch (event.type) {
case "cache_hit":
console.log(`HIT: ${event.key} (${event.ageMs}ms old)`);
break;
case "cache_miss":
console.log(`MISS: ${event.key} (${event.reason})`);
break;
case "cache_write":
console.log(`WRITE: ${event.key} (${event.sizeBytes} bytes)`);
break;
case "cache_prune":
console.log(`PRUNE: removed ${event.removedCount} entries`);
break;
}
},
});Testing
Use the memory adapter for unit tests:
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 upContributing
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 # BuildSee CONTRIBUTING.md for details.
