Skip to content
VersionSizeTypeScriptZero Dependencies
Deposit Logo

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:

ts
// 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:

ts
// Clean, type-safe, one-liner
await db.put('users', { id: '1', name: 'Alice' });

Comparison with Alternatives

FeatureDepositDexie.jsLocalForageNative 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~8KB0KB
TTL Support✅ Native
Transactions✅ Yes✅ Yes✅ Complex
Dependencies000N/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

sh
pnpm add @vielzeug/deposit
sh
npm install @vielzeug/deposit
sh
yarn add @vielzeug/deposit

Basic Setup

ts
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

ts
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:

ts
// 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:

ts
const schema = {
  users: {
    record: {} as User, // ✅ Correct
    // record: User     // ❌ Wrong
  },
};

Migration not running

Problem

Schema changes not applied.

Solution

Increment the version number:

ts
// 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


Tip: Deposit is part of the Vielzeug ecosystem, which includes utilities for logging, HTTP clients, permissions, and more.