Skip to content

Deposit Examples

Practical examples showing common use cases and patterns.

💡 Complete Applications

These are complete application examples. For API reference and basic usage, see Usage Guide.

Table of Contents

Basic Setup

Define Schema

ts
import { Deposit, IndexedDBAdapter, type DepositDataSchema } from '@vielzeug/deposit';

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  role: 'admin' | 'user';
  createdAt: number;
}

interface Post {
  id: string;
  userId: string;
  title: string;
  content: string;
  published: boolean;
  createdAt: number;
}

interface Session {
  id: string;
  userId: string;
  token: string;
  expiresAt: number;
}

const schema = defineSchema<{ users: User; posts: Post; sessions: Session }>()({
  users: {
    key: 'id',
    indexes: ['email', 'role'],
  },
  posts: {
    key: 'id',
    indexes: ['userId', 'published', 'createdAt'],
  },
  sessions: {
    key: 'id',
    indexes: ['userId'],
  },
});

Create Instance

🔍 Choosing an Adapter

  • IndexedDB: Recommended for production (larger storage, better performance)
  • LocalStorage: Simple apps with small data (<5MB)
  • Use the shorthand config for quick setup
ts
// IndexedDB (recommended for production)
const adapter = new IndexedDBAdapter('my-app-db', 1, schema);
const db = new Deposit(adapter);
ts
// LocalStorage (simpler, smaller storage)
const adapter = new LocalStorageAdapter('my-app-db', 1, schema);
const db = new Deposit(adapter);
ts
// Or use shorthand config
const db = new Deposit({
  type: 'indexedDB',
  dbName: 'my-app-db',
  version: 1,
  schema,
});

Basic CRUD Operations

Insert Records

ts
// Single insert
await db.put('users', {
  id: crypto.randomUUID(),
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
  role: 'admin',
  createdAt: Date.now(),
});

// Bulk insert
const newUsers = [
  { id: crypto.randomUUID(), name: 'Bob', email: 'bob@example.com', age: 25, role: 'user', createdAt: Date.now() },
  { id: crypto.randomUUID(), name: 'Carol', email: 'carol@example.com', age: 28, role: 'user', createdAt: Date.now() },
  { id: crypto.randomUUID(), name: 'Dave', email: 'dave@example.com', age: 35, role: 'admin', createdAt: Date.now() },
];

await db.bulkPut('users', newUsers);

Read Records

ts
// Get by ID
const user = await db.get('users', 'user-id');
if (user) {
  console.log(user.name);
}

// Get with default
const guestUser = await db.get('users', 'unknown-id', {
  id: 'guest',
  name: 'Guest',
  email: '',
  age: 0,
  role: 'user',
  createdAt: Date.now(),
});

// Get all
const allUsers = await db.getAll('users');

// Count
const totalUsers = await db.count('users');

Update Records

ts
// Get, modify, put
const user = await db.get('users', 'user-id');
if (user) {
  user.age = 31;
  user.name = 'Alice Smith';
  await db.put('users', user);
}

// Or create new object
await db.put('users', {
  id: 'user-id',
  name: 'Alice Smith',
  email: 'alice@example.com',
  age: 31,
  role: 'admin',
  createdAt: Date.now(),
});

Delete Records

ts
// Single delete
await db.delete('users', 'user-id');

// Bulk delete
await db.bulkDelete('users', ['id1', 'id2', 'id3']);

// Clear table
await db.clear('users');

Query Builder Examples

Basic Filtering

ts
// Find all admins
const admins = await db.query('users').equals('role', 'admin').toArray();

// Find users in age range
const youngAdults = await db.query('users').between('age', 18, 30).toArray();

// Find by string prefix
const aliceUsers = await db.query('users').startsWith('name', 'Alice', true).toArray();

// Custom filtering
const verified = await db
  .query('users')
  .where('email', (email) => email.endsWith('@company.com'))
  .toArray();

// Complex filter
const special = await db
  .query('users')
  .filter((user) => user.age > 25 && user.role === 'admin')
  .toArray();

Sorting and Pagination

ts
// Sort by name
const sorted = await db.query('users').orderBy('name', 'asc').toArray();

// Sort descending
const newest = await db.query('users').orderBy('createdAt', 'desc').toArray();

// Limit results
const topTen = await db.query('users').orderBy('age', 'desc').limit(10).toArray();

// Offset
const skipFirst10 = await db.query('users').offset(10).limit(10).toArray();

// Pagination
const page2 = await db
  .query('users')
  .orderBy('name', 'asc')
  .page(2, 20) // Page 2, 20 per page
  .toArray();

// Reverse
const reversed = await db.query('users').orderBy('name', 'asc').reverse().toArray();

Aggregations

ts
// Count
const userCount = await db.query('users').equals('role', 'admin').count();

// First and last
const firstUser = await db.query('users').orderBy('createdAt', 'asc').first();

const lastUser = await db.query('users').orderBy('createdAt', 'asc').last();

// Numeric aggregations
const avgAge = await db.query('users').average('age');
const minAge = await db.query('users').min('age');
const maxAge = await db.query('users').max('age');
const totalAge = await db.query('users').sum('age');

Logical Operators

ts
// NOT
const nonAdmins = await db
  .query('users')
  .not((user) => user.role === 'admin')
  .toArray();

// AND
const seniorAdmins = await db
  .query('users')
  .and(
    (user) => user.role === 'admin',
    (user) => user.age >= 30,
  )
  .toArray();

// OR
const either = await db
  .query('users')
  .or(
    (user) => user.role === 'admin',
    (user) => user.age >= 50,
  )
  .toArray();

Transformations

ts
// Modify results
const uppercased = await db
  .query('users')
  .modify((user) => ({
    ...user,
    name: user.name.toUpperCase(),
  }))
  .toArray();

// Group by field (returns object)
const byRole = await db.query('users').groupBy('role').toArray();
// Result: { admin: User[], user: User[] }

// Type-safe grouping (recommended)
const byRoleTyped = await db.query('users').toGrouped('role');
// Result: Array<{ key: 'admin' | 'user', values: User[] }>

// Example: Count users by role
for (const group of byRoleTyped) {
  console.log(`${group.key}: ${group.values.length} users`);
}

// Example: Filter groups
const adminGroup = byRoleTyped.find((g) => g.key === 'admin');
if (adminGroup) {
  console.log('Admin users:', adminGroup.values);
}

// Search
const searchResults = await db.query('users').search('alice').toArray();

Type-Safe Grouping

Use toGrouped() instead of groupBy().toArray() for better type safety and clearer results structure. It returns an array that's easier to iterate and filter.

Chaining Multiple Operations

ts
const result = await db
  .query('users')
  .filter((u) => u.age >= 18)
  .equals('role', 'admin')
  .orderBy('name', 'asc')
  .limit(10)
  .toArray();

TTL (Time-To-Live) Examples

TTL Feature

Records with TTL automatically expire and are removed when accessed after the TTL period.

Session Management

ts
// Create session with 1-hour expiry
await db.put(
  'sessions',
  {
    id: crypto.randomUUID(),
    userId: 'user-id',
    token: 'abc123',
    expiresAt: Date.now() + 3600000,
  },
  3600000,
); // TTL: 1 hour

// After 1 hour, this returns undefined
const session = await db.get('sessions', 'session-id');

API Cache

ts
// Cache API response for 5 minutes
async function fetchWithCache(url: string) {
  const cacheKey = btoa(url);
  const cached = await db.get('api-cache', cacheKey);

  if (cached) {
    return cached.data;
  }

  const response = await fetch(url);
  const data = await response.json();

  await db.put(
    'api-cache',
    {
      id: cacheKey,
      url,
      data,
      cachedAt: Date.now(),
    },
    300000,
  ); // 5 minutes

  return data;
}

Temporary Files

ts
// Store temp data for 30 minutes
await db.put(
  'temp-files',
  {
    id: 'temp-123',
    content: 'temporary data',
    createdAt: Date.now(),
  },
  1800000,
); // 30 minutes

Transaction Examples

⚡ Automatic Atomicity

Transactions automatically use atomic operations for IndexedDB (all changes in a single IDBTransaction) and optimistic updates for LocalStorage. For critical data integrity, use IndexedDB.

User Registration

ts
// Create user and their first post atomically (IndexedDB) or optimistically (LocalStorage)
await db.transaction(['users', 'posts'], async (stores) => {
  const userId = crypto.randomUUID();

  // Add user
  stores.users.push({
    id: userId,
    name: 'New User',
    email: 'new@example.com',
    age: 25,
    role: 'user',
    createdAt: Date.now(),
  });

  // Add welcome post
  stores.posts.push({
    id: crypto.randomUUID(),
    userId,
    title: 'Welcome!',
    content: 'Thanks for joining!',
    published: true,
    createdAt: Date.now(),
  });

  // For IndexedDB: Both changes committed atomically
  // For LocalStorage: Changes committed optimistically
});

Data Migration

ts
// Move data from one table structure to another
await db.transaction(['users', 'profiles'], async (stores) => {
  // Create profiles from users
  for (const user of stores.users) {
    stores.profiles.push({
      id: crypto.randomUUID(),
      userId: user.id,
      bio: '',
      avatar: '',
      createdAt: Date.now(),
    });
  }
});

Patch Operations Examples

Batch Updates

ts
const patches = [
  // Add new user
  {
    type: 'put' as const,
    value: {
      id: 'u1',
      name: 'Alice',
      email: 'alice@example.com',
      age: 30,
      role: 'admin' as const,
      createdAt: Date.now(),
    },
  },
  // Add another with TTL
  {
    type: 'put' as const,
    value: {
      id: 'u2',
      name: 'Bob',
      email: 'bob@example.com',
      age: 25,
      role: 'user' as const,
      createdAt: Date.now(),
    },
    ttl: 3600000,
  },
  // Delete old user
  { type: 'delete' as const, key: 'old-user-id' },
];

await db.patch('users', patches);

Sync from Server

ts
async function syncFromServer() {
  const serverData = await fetch('/api/sync').then((r) => r.json());

  const patches = [
    { type: 'clear' as const }, // Clear existing data
    ...serverData.map((item) => ({
      type: 'put' as const,
      value: item,
    })),
  ];

  await db.patch('users', patches);
}

Real-World Patterns

Offline-First Todo App

ts
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({ type: 'indexedDB', dbName: 'todos', version: 1, schema });

// Add todo
async function addTodo(text: string) {
  await db.put('todos', {
    id: crypto.randomUUID(),
    text,
    completed: false,
    createdAt: Date.now(),
  });
}

// Toggle completion
async function toggleTodo(id: string) {
  const todo = await db.get('todos', id);
  if (todo) {
    await db.put('todos', { ...todo, completed: !todo.completed });
  }
}

// Get active todos
async function getActiveTodos() {
  return await db.query('todos').equals('completed', false).orderBy('createdAt', 'desc').toArray();
}

// Delete completed
async function clearCompleted() {
  const completed = await db.query('todos').equals('completed', true).toArray();

  await db.bulkDelete(
    'todos',
    completed.map((t) => t.id),
  );
}

User Preferences

ts
interface Preference {
  id: string;
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
  updatedAt: number;
}

const db = new Deposit({
  type: 'localStorage',
  dbName: 'preferences',
  version: 1,
  schema: {
    prefs: {
      key: 'id',
      record: {} as Preference,
    },
  },
});

// Save preferences
async function savePreferences(prefs: Omit<Preference, 'id' | 'updatedAt'>) {
  await db.put('prefs', {
    id: 'user-prefs',
    ...prefs,
    updatedAt: Date.now(),
  });
}

// Load preferences
async function loadPreferences() {
  return await db.get('prefs', 'user-prefs', {
    id: 'user-prefs',
    theme: 'light',
    language: 'en',
    notifications: true,
    updatedAt: Date.now(),
  });
}

Analytics Queue

ts
interface Event {
  id: string;
  type: string;
  data: any;
  timestamp: number;
  sent: boolean;
}

const db = new Deposit({
  type: 'indexedDB',
  dbName: 'analytics',
  version: 1,
  schema: {
    events: {
      key: 'id',
      indexes: ['sent', 'timestamp'],
      record: {} as Event,
    },
  },
});

// Track event
async function track(type: string, data: any) {
  await db.put('events', {
    id: crypto.randomUUID(),
    type,
    data,
    timestamp: Date.now(),
    sent: false,
  });
}

// Flush to server
async function flush() {
  const pending = await db.query('events').equals('sent', false).orderBy('timestamp', 'asc').toArray();

  if (pending.length === 0) return;

  try {
    await fetch('/api/analytics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(pending),
    });

    // Mark as sent
    for (const event of pending) {
      await db.put('events', { ...event, sent: true });
    }
  } catch (err) {
    console.error('Failed to flush analytics', err);
  }
}

Data Export/Import

ts
// Export all data
async function exportData() {
  const users = await db.getAll('users');
  const posts = await db.getAll('posts');

  return {
    version: 1,
    timestamp: Date.now(),
    data: { users, posts },
  };
}

// Import data
async function importData(exported: any) {
  await db.transaction(['users', 'posts'], async (stores) => {
    stores.users = exported.data.users;
    stores.posts = exported.data.posts;
    // Atomically replaces all data for IndexedDB
  });
}

Search and Autocomplete

ts
async function searchUsers(query: string, limit = 10) {
  if (!query.trim()) {
    return await db.query('users').orderBy('name', 'asc').limit(limit).toArray();
  }

  return await db.query('users').search(query).limit(limit).toArray();
}

Migration Examples

Version 1 to 2: Add Field

ts
const migration = (db, oldVersion, newVersion, tx, schema) => {
  if (oldVersion < 2) {
    const store = tx.objectStore('users');
    const request = store.getAll();

    request.onsuccess = () => {
      for (const user of request.result) {
        if (!user.role) {
          user.role = 'user';
          store.put(user);
        }
      }
    };
  }
};

Version 2 to 3: Transform Data

ts
const migration = (db, oldVersion, newVersion, tx, schema) => {
  if (oldVersion < 3) {
    const store = tx.objectStore('posts');
    const request = store.getAll();

    request.onsuccess = () => {
      for (const post of request.result) {
        // Convert old format to new
        post.content = post.body || '';
        delete post.body;
        store.put(post);
      }
    };
  }
};

Performance Tips

Performance

Always use bulk operations instead of loops for better performance.

ts
// Slow – multiple async operations
for (const user of users) {
  await db.put('users', user);
}
ts
// Fast – single bulk operation
await db.bulkPut('users', users);

Query Caching

Query results are automatically memoized for better performance.

ts
const adminQuery = db.query('users').equals('role', 'admin');
const admins = await adminQuery.toArray(); // Cached
const adminCount = await adminQuery.count(); // Uses cache