smart-delta-cache
v1.0.0
Published
Client-side smart delta updates using IndexedDB
Maintainers
Readme
smart-delta
A tiny client-side utility to detect added, updated, and removed items in API responses by comparing them with previously cached data.
It uses IndexedDB for persistence and works great for:
- Lists (blogs, users, products)
- Polling APIs
- Incremental UI updates
How it works (simple idea)
You fetch fresh data from an API
smart-deltacompares it with previously cached dataIt tells you:
- what is new
- what is updated
- what is removed
Cache is updated automatically for next time
No state management. No reducers. Just diff + callbacks.
Installation
npm install smart-deltaBasic Usage
import { useSmartDelta } from "smart-delta";
const tracker = useSmartDelta({
key: "blogs", // unique cache key
cacheTime: 60000 // optional (ms)
});
fetch("/api/blogs")
.then(r => r.json())
.then(data => {
tracker.apply(data, {
onUpdate: item => {
console.log("Added or updated", item);
},
onRemove: id => {
console.log("Removed", id);
}
});
});Callbacks
| Callback | When it runs |
| ---------------- | ------------------------ |
| onUpdate(item) | New item OR changed item |
| onRemove(id) | Item removed from list |
First API call treats all items as updates.
How items are identified
Each item must have some unique identifier. smart-delta checks in this order:
item.id
item._id
item.uuid
item.slugIf none exist, it creates a stable hash from the object.
Cache behavior
- Data is stored in IndexedDB (
smart-delta-db) - Each key is namespaced automatically
- Cache expires if
cacheTimeis provided
useSmartDelta({
key: "users",
cacheTime: 5 * 60 * 1000 // 5 minutes
});If cache expires → treated as first load.
What is NOT included
- No React state
- No rendering logic
- No background polling
This keeps the library small and flexible.
React Example
import { useEffect, useState } from "react";
import { useSmartDelta } from "smart-delta";
export default function BlogList() {
const [blogs, setBlogs] = useState([]);
const tracker = useSmartDelta({
key: "blogs",
cacheTime: 60000 // 1 minute
});
useEffect(() => {
fetch("/api/blogs")
.then(r => r.json())
.then(data => {
tracker.apply(data, {
onUpdate: item => {
setBlogs(prev => {
const map = new Map(prev.map(i => [i.id, i]));
map.set(item.id, item);
return Array.from(map.values());
});
},
onRemove: id => {
setBlogs(prev => prev.filter(i => i.id !== id));
}
});
});
}, []);
return (
<ul>
{blogs.map(blog => (
<li key={blog.id}>{blog.title}</li>
))}
</ul>
);
}What happens here?
- First load → all blogs are added
- Next fetch → only changed blogs update the UI
- Removed blogs disappear automatically
Next.js Example (Client Component)
"use client";
import { useEffect, useState } from "react";
import { useSmartDelta } from "smart-delta";
export default function UsersPage() {
const [users, setUsers] = useState([]);
const tracker = useSmartDelta({
key: "users",
cacheTime: 300000 // 5 minutes
});
useEffect(() => {
const load = async () => {
const res = await fetch("/api/users");
const data = await res.json();
tracker.apply(data, {
onUpdate: user => {
setUsers(prev => {
const exists = prev.find(u => u.id === user.id);
if (exists) {
return prev.map(u => (u.id === user.id ? user : u));
}
return [...prev, user];
});
},
onRemove: id => {
setUsers(prev => prev.filter(u => u.id !== id));
}
});
};
load();
}, []);
return (
<div>
<h1>Users</h1>
{users.map(u => (
<div key={u.id}>{u.name}</div>
))}
</div>
);
}⚠️
useSmartDeltaruns only on the client because it uses IndexedDB.
Ideal use cases
- Polling APIs every few seconds
- Updating lists without re-rendering everything
- Syncing UI with server changes
Internal structure (for contributors)
src/
├─ db.js # IndexedDB read/write
├─ tracker.js # diff + callbacks
├─ utils.js # ID + diff helpers
└─ index.js # public APILicense
MIT
