Deposit
Deposit is a powerful, type-safe browser storage utility for modern web apps. It provides a unified, developer-friendly API for IndexedDB and LocalStorage, featuring advanced querying, migrations, and transactions.
What Problem Does Deposit Solve?
Browser storage APIs are powerful but notoriously complex. IndexedDB requires verbose boilerplate, lacks type safety, and has inconsistent error handling. LocalStorage is simple but limited to strings and lacks advanced features.
Without Deposit:
// IndexedDB - verbose and error-prone
const request = indexedDB.open('myDB', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore('users', { keyPath: 'id' });
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
store.add({ id: '1', name: 'Alice' });
};With Deposit:
// Clean, type-safe, one-liner
await db.put('users', { id: '1', name: 'Alice' });Comparison with Alternatives
| Feature | Deposit | Dexie.js | LocalForage | Native IndexedDB |
|---|---|---|---|---|
| TypeScript Support | ✅ First-class | ✅ Good | ⚠️ Limited | ❌ |
| Query Builder | ✅ Advanced | ✅ Good | ❌ | ❌ |
| Migrations | ✅ Built-in | ✅ Advanced | ❌ | ⚠️ Manual |
| LocalStorage Support | ✅ Unified API | ❌ | ✅ | ❌ |
| Bundle Size (gzip) | 3.9 KB | ~20KB | ~8KB | 0KB |
| TTL Support | ✅ Native | ❌ | ❌ | ❌ |
| Transactions | ✅ Yes | ✅ Yes | ❌ | ✅ Complex |
| Dependencies | 0 | 0 | 0 | N/A |
When to Use Deposit
✅ Use Deposit when you:
- Need type-safe client-side storage with autocompletion
- Want to abstract IndexedDB complexity without losing power
- Require advanced querying (filters, sorting, grouping)
- Need schema migrations for evolving data structures
- Want unified API across LocalStorage and IndexedDB
- Build offline-first or PWA applications
❌ Consider alternatives when you:
- Only need simple key-value storage (use LocalStorage directly)
- Already invested heavily in Dexie.js ecosystem
- Need extremely minimal bundle size (use native APIs)
- Building Node.js-only applications (use SQLite or other DB)
🚀 Key Features
- Unified API: Switch between LocalStorage and IndexedDB without changing your code
- Type-safe: Define your schemas once and enjoy full autocompletion and type checking
- Advanced Querying: A rich QueryBuilder supporting filters, sorting, grouping, and pagination
- Migrations: Robust support for schema versioning and data migrations in IndexedDB
- Transactions: Ensure data integrity with atomic operations across multiple tables
- TTL (Time-To-Live): Native support for record expiration
- Isomorphic: Works in all modern browsers with minimal footprint
- Zero Dependencies: No external dependencies, fully self-contained
🏁 Quick Start
Installation
pnpm add @vielzeug/depositnpm install @vielzeug/deposityarn add @vielzeug/depositBasic Setup
import { Deposit, IndexedDBAdapter } from '@vielzeug/deposit';
// 1. Define your schema with types
const schema = {
users: {
key: 'id',
indexes: ['email'],
record: {} as { id: string; name: string; email: string },
},
posts: {
key: 'id',
indexes: ['userId', 'createdAt'],
record: {} as { id: string; userId: string; title: string; createdAt: number },
},
};
// 2. Initialize the depot
const adapter = new IndexedDBAdapter('my-app-db', 1, schema);
const db = new Deposit(adapter);
// 3. Start storing data
await db.put('users', { id: 'u1', name: 'Alice', email: 'alice@example.com' });
const user = await db.get('users', 'u1');Real-World Example: Todo App
import { Deposit, IndexedDBAdapter } from '@vielzeug/deposit';
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: number;
}
const schema = {
todos: {
key: 'id',
indexes: ['completed', 'createdAt'],
record: {} as Todo,
},
};
const db = new Deposit(new IndexedDBAdapter('todos-db', 1, schema));
// Add todo
await db.put('todos', {
id: crypto.randomUUID(),
text: 'Learn Deposit',
completed: false,
createdAt: Date.now(),
});
// Query active todos
const activeTodos = await db.query('todos').equals('completed', false).orderBy('createdAt', 'desc').toArray();
// Update todo (put with same id)
const todo = await db.get('todos', 'todo-id');
if (todo) {
await db.put('todos', { ...todo, completed: true });
}
// Delete completed todos
const completed = await db.query('todos').equals('completed', true).toArray();
await db.bulkDelete(
'todos',
completed.map((t) => t.id),
);📚 Documentation
- Usage Guide: Detailed setup, adapters, and basic operations
- API Reference: Comprehensive documentation of all methods and types
- Examples: Practical patterns for querying, transactions, and migrations
❓ FAQ
Is Deposit production-ready?
Yes! Deposit is battle-tested and used in production applications. It has comprehensive test coverage and follows semantic versioning.
Does Deposit work with React/Vue/Angular?
Absolutely! Deposit is framework-agnostic and works great with any JavaScript framework.
Can I use Deposit in Node.js?
Deposit is designed for browser environments (IndexedDB, LocalStorage). For Node.js, consider using SQLite or other server-side databases.
How do I handle schema changes?
Deposit supports migrations through version management. See the Usage Guide for details.
What's the performance impact?
Deposit adds minimal overhead (~1-2ms) over native IndexedDB. The QueryBuilder is optimized for common operations.
Can I use multiple databases?
Yes! Create multiple Deposit instances with different adapters and database names.
🐛 Troubleshooting
QuotaExceededError
Problem
Storage quota exceeded.
Solution
Check available storage and clean up old data:
// Check available storage
if ('storage' in navigator && 'estimate' in navigator.storage) {
const { usage, quota } = await navigator.storage.estimate();
console.log(`Using ${usage} of ${quota} bytes`);
}
// Clean up old data
const oldTodos = await db
.query('todos')
.filter((todo) => todo.createdAt < Date.now() - 30 * 24 * 60 * 60 * 1000)
.toArray();
await db.bulkDelete(
'todos',
oldTodos.map((t) => t.id),
);TypeScript errors with schema
Problem
Type inference not working.
Solution
Ensure you're using type assertions in schema:
const schema = {
users: {
record: {} as User, // ✅ Correct
// record: User // ❌ Wrong
},
};Migration not running
Problem
Schema changes not applied.
Solution
Increment the version number:
// Old
const adapter = new IndexedDBAdapter('my-db', 1, schema);
// New
const adapter = new IndexedDBAdapter('my-db', 2, schema);🤝 Contributing
Found a bug or want to contribute? Check our GitHub repository.
📄 License
MIT © Helmuth Duarte
🔗 Useful Links
Tip: Deposit is part of the Vielzeug ecosystem, which includes utilities for logging, HTTP clients, permissions, and more.