npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@indexeddb-orm/idb-orm

v0.0.3

Published

TypeScript ORM wrapper for indexeddb with Zod runtime validation, build on top of dexie.js

Downloads

9,551

Readme

Dexie ORM

TypeScript ORM wrapper for indexed-db with Zod runtime validation, built on the top of dexie.js.

View Homepage - Interactive documentation and examples

Features

  • TypeScript Support - Full type safety with generics

  • Zod Validation - Runtime validation with Zod schemas

  • Config-based entities - Define entities with a simple defineEntity(...) API

  • Dexie Compatibility - All Dexie.js operations supported

  • Clean API - Simple and intuitive API design

Installation

npm install @indexeddb-orm/idb-orm dexie zod

Important Notes

Dexie Dependencies

This library includes Dexie.js internally. To ensure that liveQuery works properly, you need to configure Vite to dedupe Dexie.

// In your vite.config.ts, add:
import { defineConfig } from 'vite';

export default defineConfig({
  resolve: {
    dedupe: ['dexie'],
  },
  // ... other config
});

// This ensures liveQuery works correctly
// Without dedupe, liveQuery may not function properly

Built on Dexie.js

This library is built on top of Dexie.js - a powerful wrapper for IndexedDB. All Dexie.js methods and features are available through the db object.

// All Dexie.js methods work with our library
const db = await Database.createDatabase({...});

// Direct Dexie.js operations
await db.transaction('rw', db.users, async () => {
    await db.users.add({ name: 'John', email: '[email protected]' });
});

// Dexie.js query methods
const users = await db.users
    .where('age')
    .above(18)
    .and(user => user.name.startsWith('J'))
    .toArray();

// Dexie.js liveQuery (reactive queries)
import { liveQuery } from 'dexie';
const observableUsers = liveQuery(() => 
    db.users.where('active').equals(true).toArray()
);

Benefits: You get all the power of Dexie.js plus our ORM features like entity validation, relations, and cloud sync.

Index Requirements for Queries

Critical: When searching by any field, you MUST create an index on that field. Queries without proper indexes will fail.

const UserSchema = z.object({
    id: z.number(),
    name: z.string(),
    email: z.string(),
    age: z.number()
});

defineEntity(User, {
    schema: UserSchema,
    indexes: [
        { key: 'email' },        // Required for email queries
        { key: 'age' },          // Required for age queries
        { key: 'name' }          // Required for name queries
    ]
});

Compound Indexes for Complex Queries

For queries involving multiple fields, you need compound indexes. Single field indexes won't work for multi-field queries.

defineEntity(Post, {
    schema: PostSchema,
    compoundIndexes: [
        { key: ['category', 'status'] },
        { key: ['authorId', 'createdAt'] }
    ]
});

Usage

1. Define Entities

import { BaseEntity, defineEntity } from '@indexeddb-orm/idb-orm';
import { z } from 'zod';

// Zod schema for validation
const UserSchema = z.object({
  id: z.number().optional(),
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email format'),
  age: z.number().min(18, 'Must be at least 18 years old'),
  createdAt: z.number(),
  updatedAt: z.number(),
  isActive: z.boolean().default(true),
  tags: z.array(z.string()).default([]),
  metadata: z.record(z.string(), z.unknown()).default({}),
});

export class UserEntity extends BaseEntity<number> {
  name!: string;
  email!: string;
  age!: number;
  createdAt!: number;
  updatedAt!: number;
  isActive!: boolean;
  tags!: string[];
  metadata!: Record<string, unknown>;
}

defineEntity(UserEntity, {
  tableName: 'users',
  schema: UserSchema,
  columns: {
    name: { required: true, indexed: true },
    email: { required: true, unique: true },
    age: { indexed: true },
    createdAt: { indexed: true },
    updatedAt: { indexed: true },
    isActive: { indexed: true },
    tags: {},
    metadata: {},
  },
  // Example relations (optional)
  // relations: {
  //   posts: { type: 'one-to-many', target: PostEntity, foreignKey: 'authorId' },
  // },
});

2. Create Database

import { Database } from '@indexeddb-orm/idb-orm';
import { UserEntity } from './entities/User';
import { PostEntity } from './entities/Post';

export const db = Database.createDatabase({
  name: 'MyApp',
  version: 1,
  entities: [UserEntity, PostEntity],
  config: { 
    onSchemaChangeStrategy: 'all',
    cloudSync: {
      databaseUrl: 'https://your-sync-server.com',
      enableOfflineQueue: true,
      syncInterval: 30000
    }
  },
});

Database Configuration Options

The createDatabase() method accepts the following configuration options:

Basic Configuration

const db = Database.createDatabase({
  name: string,           // Required: Database name
  version: number,        // Required: Schema version
  entities: EntityConstructor[], // Required: Array of entity classes
  config?: {             // Optional: Additional configuration
    // Schema change handling
    onSchemaChangeStrategy?: 'selective' | 'all',
    
    // Migrations list
    migrations?: Migration[],
    
    // Cloud synchronization
    cloudSync?: CloudSyncConfig
  }
});

Schema Change Strategy Options

onSchemaChangeStrategy - Defines how to handle database schema changes:

  • 'selective' (recommended for production): Only resets tables that have schema changes, preserving data in unchanged tables
  • 'all': Resets the entire database when any schema change is detected, useful for development

Cloud Sync Configuration Options

CloudSyncConfig - Configuration for cloud synchronization:

interface CloudSyncConfig {
  databaseUrl: string;                    // Required: Base URL of the sync server
  enableOfflineSupport?: boolean;         // Optional: Queue changes when offline (default: false)
  syncInterval?: number;                  // Optional: Auto-sync interval in milliseconds (default: 30000)
  // Note: Authentication is handled by Dexie Cloud addon configuration
}

Cloud Sync Options:

  • databaseUrl (required): The base URL of your synchronization server
  • enableOfflineSupport (optional): If true, changes are queued when offline and synced when connection is restored
  • syncInterval (optional): Automatic synchronization interval in milliseconds (default: 30 seconds)

Authentication Note: Authentication for cloud sync is handled by the Dexie Cloud addon configuration, not through this interface. You need to configure authentication separately when setting up the Dexie Cloud addon.

Schema Change Strategy Examples

// Option 1: Reset only changed tables (recommended for production)
const db = Database.createDatabase({
  name: 'MyApp',
  version: 1,
  entities: [UserEntity, PostEntity],
  config: {
    onSchemaChangeStrategy: 'selective' // Only reset tables that changed
  }
});

// Option 2: Reset entire database on schema changes
const db = Database.createDatabase({
  name: 'MyApp',
  version: 1,
  entities: [UserEntity, PostEntity],
  config: {
    onSchemaChangeStrategy: 'all' // Reset all data when schema changes
  }
});

Migration Configuration

const db = Database.createDatabase({
  name: 'MyApp',
  version: 2,
  entities: [UserEntity, PostEntity],
  config: {
    migrations: [
      {
        version: 2,
        name: 'Add fullName field',
        up: async (db) => {
          await db.getRepository(UserEntity).toCollection().modify(user => {
            user.fullName = `${user.firstName} ${user.lastName}`;
          });
        },
        down: async (db) => {
          await db.getRepository(UserEntity).toCollection().modify(user => {
            delete user.fullName;
          });
        }
      }
    ]
  }
});

Cloud Synchronization Configuration

const db = Database.createDatabase({
  name: 'MyApp',
  version: 1,
  entities: [UserEntity, PostEntity],
  config: {
    cloudSync: {
      databaseUrl: 'https://your-sync-server.com', // Required: Sync server URL
      enableOfflineSupport: true,                  // Optional: Queue changes when offline
      syncInterval: 30000                          // Optional: Auto-sync interval (30s)
    }
  }
});

Complete Configuration Example

const db = Database.createDatabase({
  name: 'MyApp',
  version: 3,
  entities: [UserEntity, PostEntity, CommentEntity],
  config: {
    // Handle schema changes by resetting only affected tables
    onSchemaChangeStrategy: 'selective',
    
    // Define migrations for version upgrades
    migrations: [
      {
        version: 2,
        name: 'Add user profiles',
        up: async (db) => {
          // Migration logic for version 2
        }
      },
      {
        version: 3,
        name: 'Add comment system',
        up: async (db) => {
          // Migration logic for version 3
        }
      }
    ],
    
    // Enable cloud synchronization
    cloudSync: {
      databaseUrl: 'https://api.myapp.com/sync',
      enableOfflineSupport: true,
      syncInterval: 60000 // Sync every minute
    }
  }
});

3. Basic CRUD Operations

// Prefer explicit repositories for strict typing
const users = await db.getRepository(UserEntity).toArray(); // Type: UserEntity[]
const posts = await db.getRepository(PostEntity).toArray(); // Type: PostEntity[]

// Create new entity with validation in one call
import { newEntity } from '@indexeddb-orm/idb-orm';

const newUser = newEntity(UserEntity, {
  name: 'John Doe',
  email: '[email protected]',
  age: 25,
  createdAt: Date.now(),
  updatedAt: Date.now(),
  tags: ['developer'],
  metadata: { source: 'manual' },
});

// Save to database
await db.getRepository(UserEntity).add(newUser);

// Query with full Dexie.js API
const activeUsers = await db.getRepository(UserEntity)
  .where('isActive')
  .equals(true)
  .toArray();

4. Entity Relations

// Define entities with relations
defineEntity(UserEntity, {
  tableName: 'users',
  schema: UserSchema,
  columns: { /* ... */ },
  relations: {
    posts: { 
      type: 'one-to-many', 
      target: PostEntity, 
      foreignKey: 'authorId' 
    },
    profile: { 
      type: 'one-to-one', 
      target: ProfileEntity, 
      foreignKey: 'userId' 
    }
  }
});

defineEntity(PostEntity, {
  tableName: 'posts',
  schema: PostSchema,
  columns: { /* ... */ },
  relations: {
    author: { 
      type: 'many-to-one', 
      target: UserEntity, 
      foreignKey: 'authorId' 
    },
    tags: { 
      type: 'many-to-many', 
      target: TagEntity, 
      joinTable: 'post_tags' 
    }
  }
});

// Load relations
const userWithPosts = await db.loadRelations({
  entity: user,
  entityClass: UserEntity,
  relationNames: ['posts', 'profile']
});

// Load specific relation
const userPosts = await db.loadRelationByName({
  entity: user,
  entityClass: UserEntity,
  relationName: 'posts'
});

// Save entity with all its relations
const savedUser = await db.saveWithRelations({
  entity: user,
  entityClass: UserEntity
});

// Delete entity with cascade handling for relations
await db.deleteWithRelations({
  entity: user,
  entityClass: UserEntity
});

5. Reactive Queries with liveQuery

Use Dexie's liveQuery for reactive data that automatically updates when the database changes. Works with all major frameworks:

React Example

import { liveQuery } from 'dexie';
import { useObservable } from 'dexie-react-hooks';
import { Database } from 'indexed-db-orm';
import { User } from './entities/User';

function UserList() {
  const users = useObservable(
    liveQuery(() => {
      const userRepo = db.getRepository(User);
      return userRepo.where('active').equals(true).toArray();
    })
  );
  
  return (
    <div>
      {users?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Vue Example

import { liveQuery } from 'dexie';
import { ref, onMounted, onUnmounted } from 'vue';
import { Database } from 'indexed-db-orm';
import { User } from './entities/User';

export default {
  setup() {
    const users = ref<User[]>([]);
    let subscription;
    
    onMounted(() => {
      subscription = liveQuery(() => {
        const userRepo = db.getRepository(User);
        return userRepo.where('active').equals(true).toArray();
      }).subscribe(result => {
        users.value = result;
      });
    });
    
    onUnmounted(() => {
      subscription?.unsubscribe();
    });
    
    return { users };
  }
};

Svelte Example

import { liveQuery } from 'dexie';
import { onMount, onDestroy } from 'svelte';
import { Database } from 'indexed-db-orm';
import { User } from './entities/User';

let users: User[] = [];
let subscription;

onMount(() => {
  subscription = liveQuery(() => {
    const userRepo = db.getRepository(User);
    return userRepo.where('active').equals(true).toArray();
  }).subscribe(result => {
    users = result;
  });
});

onDestroy(() => {
  subscription?.unsubscribe();
});

Angular Example

import { liveQuery } from 'dexie';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { Database } from 'indexed-db-orm';
import { User } from './entities/User';

@Component({
  selector: 'app-user-list',
  template: '<div *ngFor="let user of users">{{user.name}}</div>'
})
export class UserListComponent implements OnInit, OnDestroy {
  users: User[] = [];
  private subscription?: Subscription;
  
  ngOnInit() {
    this.subscription = liveQuery(() => {
      const userRepo = db.getRepository(User);
      return userRepo.where('active').equals(true).toArray();
    }).subscribe(result => {
      this.users = result;
    });
  }
  
  ngOnDestroy() {
    this.subscription?.unsubscribe();
  }
}

6. Advanced Queries and Aggregation

// Aggregation operations
const result = await db.aggregate({
  entityClass: PostEntity,
  options: {
    where: { category: 'tech' },
    sort: { field: 'views', direction: 'desc' },
    limit: 10,
    count: true,
    sum: ['views'],
    avg: ['likes'],
    groupBy: ['category']
  }
});

// Complex filtering and sorting
const topPosts = await db.getRepository(PostEntity)
  .where('published')
  .equals(true)
  .and(post => post.views > 100)
  .orderBy('views')
  .reverse()
  .limit(5)
  .toArray();

6. Entity Validation

// Manual validation
const user = new UserEntity();
user.name = 'A'; // Too short
user.email = 'invalid-email'; // Invalid format

const result = user.validate();
if (!result.isValid) {
  console.log(result.errors);
  // ['name: Name must be at least 2 characters', 'email: Invalid email format']
}

// Validation with error throwing
try {
  user.validateOrThrow();
} catch (error) {
  console.log('Validation failed:', error.message);
}

// Initialize with validation
const validUser = new UserEntity().init({
  name: 'John Doe',
  email: '[email protected]',
  age: 25
});

7. Database Management

// Check for schema changes
const hasChanges = await db.checkSchemaChanges();
if (hasChanges) {
  await db.performSelectiveReset(); // Reset only changed tables
  // or
  await db.resetDatabase(); // Reset entire database
}

// Clear all data
await db.clearAllData();

// Run migrations
await db.runMigrations([
  {
    version: 2,
    up: async (db) => {
      await db.getRepository(UserEntity).toCollection().modify(user => {
        user.fullName = `${user.firstName} ${user.lastName}`;
      });
    }
  }
]);

8. Cloud Synchronization

Installation

npm install dexie-cloud-addon

Configuration

// Basic cloud sync configuration
const db = Database.createDatabase({
  name: 'MyApp',
  version: 1,
  entities: [UserEntity, PostEntity],
  config: {
    cloudSync: {
      databaseUrl: 'https://your-database-url.dexie.cloud',
      enableOfflineSupport: true,
      syncInterval: 30000 // 30 seconds
    }
  }
});

// Enable cloud sync manually
await db.enableCloudSync({
  databaseUrl: 'https://your-sync-server.com',
  enableOfflineSupport: true,
  syncInterval: 30000
});

Cloud Sync API

// Check sync status
const status = db.getSyncStatus();
console.log('Sync enabled:', status.enabled);
console.log('Last sync:', status.lastSync);
console.log('Online:', status.isOnline);

// Manual sync
await db.sync();

// Sync specific tables
await db.syncTables(['users', 'posts']);

// Check if cloud sync is enabled
const isEnabled = db.isCloudSyncEnabled();

// Get cloud sync configuration
const config = db.getCloudSyncConfig();

// Disable cloud sync
db.disableCloudSync();

Automatic Synchronization

// Configure automatic sync with interval
const db = Database.createDatabase({
  name: 'MyApp',
  version: 1,
  entities: [UserEntity, PostEntity],
  config: {
    cloudSync: {
      databaseUrl: 'https://your-database-url.dexie.cloud',
      syncInterval: 60000 // Sync every minute
    }
  }
});

React Integration Example

import { useState, useEffect } from 'react';
import { useLiveQuery } from 'dexie-react-hooks';

function MyComponent() {
  const [syncStatus, setSyncStatus] = useState({ enabled: false });
  
  // Data with automatic synchronization
  const users = useLiveQuery(() => db.getRepository(UserEntity).toArray());
  
  useEffect(() => {
    const status = db.getSyncStatus();
    setSyncStatus(status);
  }, []);
  
  const handleSync = async () => {
    try {
      await db.sync();
      alert('Synchronization completed!');
    } catch (error) {
      alert(`Sync error: ${error.message}`);
    }
  };
  
  return (
    <div>
      <h2>Status: {syncStatus.enabled ? 'Enabled' : 'Disabled'}</h2>
      <button onClick={handleSync}>Sync Now</button>
      
      {users?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Troubleshooting

// Check if cloud sync is configured
const config = db.getCloudSyncConfig();

if (!config) {
  await db.enableCloudSync({
    databaseUrl: 'https://your-database-url.dexie.cloud'
  });
}

// Check sync status for debugging
const status = db.getSyncStatus();
console.log('Online:', status.isOnline);
console.log('Last sync:', status.lastSync);

// Try manual sync if automatic sync fails
await db.sync();

9. Compound Indexes

Compound indexes allow you to create indexes on multiple columns for better query performance.

Defining Compound Indexes

import { BaseEntity, defineEntity } from '@indexeddb-orm/idb-orm';
import { z } from 'zod';

// Zod schema for validation
const UserSchema = z.object({
  id: z.number().optional(),
  name: z.string(),
  email: z.string().email(),
  age: z.number(),
  isActive: z.boolean(),
});

export class UserEntity extends BaseEntity {
  name!: string;
  email!: string;
  age!: number;
  isActive!: boolean;
}

// Define entity with compound indexes
defineEntity(UserEntity, {
  tableName: 'users',
  schema: UserSchema,
  columns: {
    name: { indexed: true },
    email: { indexed: true, unique: true },
    age: { indexed: true },
    isActive: { indexed: true },
  },
  compoundIndexes: [
    {
      columns: ['name', 'email'],
      unique: false,
      name: 'name_email_index'
    },
    {
      columns: ['age', 'isActive'],
      unique: false,
      name: 'age_active_index'
    },
    {
      columns: ['email'],
      unique: true,
      name: 'unique_email_index'
    }
  ]
});

Querying with Compound Indexes

// Query using compound index (name + email)
const users = await db.getRepository(UserEntity)
  .where(['name', 'email'])
  .equals(['John Doe', '[email protected]'])
  .toArray();

// Query using compound index (age + isActive)
const activeAdults = await db.getRepository(UserEntity)
  .where(['age', 'isActive'])
  .above([18, 1]) // age > 18, isActive = true
  .toArray();

// Query using unique compound index
const user = await db.getRepository(UserEntity)
  .where('email')
  .equals('[email protected]')
  .first();

Different Query Operators

// equals - exact match
const users = await db.getRepository(UserEntity)
  .where(['name', 'email'])
  .equals(['John', '[email protected]'])
  .toArray();

// above - greater than
const adults = await db.getRepository(UserEntity)
  .where(['age', 'isActive'])
  .above([18, 1])
  .toArray();

// below - less than
const youngUsers = await db.getRepository(UserEntity)
  .where(['age'])
  .below([25])
  .toArray();

// between - between values
const middleAged = await db.getRepository(UserEntity)
  .where(['age'])
  .between([25, 50])
  .toArray();

Real-world Examples

// 1. User search by name and email
defineEntity(UserEntity, {
  tableName: 'users',
  schema: UserSchema,
  columns: {
    name: { indexed: true },
    email: { indexed: true },
  },
  compoundIndexes: [
    { columns: ['name', 'email'], name: 'name_email_index' }
  ]
});
// Usage: Find user by exact name and email
const user = await db.getRepository(UserEntity)
  .where(['name', 'email'])
  .equals(['John Doe', '[email protected]'])
  .first();

// 2. Active adults filter
defineEntity(UserEntity, {
  tableName: 'users',
  schema: UserSchema,
  columns: {
    age: { indexed: true },
    isActive: { indexed: true },
  },
  compoundIndexes: [
    { columns: ['age', 'isActive'], name: 'age_active_index' }
  ]
});
// Usage: Find active adults
const activeAdults = await db.getRepository(UserEntity)
  .where(['age', 'isActive'])
  .above([18, 1])
  .toArray();

// 3. Posts by date and category
defineEntity(PostEntity, {
  tableName: 'posts',
  schema: PostSchema,
  columns: {
    createdAt: { indexed: true },
    category: { indexed: true },
  },
  compoundIndexes: [
    { columns: ['createdAt', 'category'], name: 'date_category_index' }
  ]
});
// Usage: Find recent posts in specific category
const recentTechPosts = await db.getRepository(PostEntity)
  .where(['createdAt', 'category'])
  .above([Date.now() - 7 * 24 * 60 * 60 * 1000, 'tech'])
  .toArray();

Compound Index Benefits

  • Query Performance: Faster searches on multiple columns
  • Uniqueness: Guarantee unique combinations of values
  • Flexibility: Create indexes on any column combinations
  • Optimization: Better performance for complex queries

Vite note: ensure a single Dexie instance for live queries

If useLiveQuery doesn't re-render with this library's Database, make sure Vite bundles a single Dexie copy by deduping Dexie:

// vite.config.ts
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [react()],
  resolve: { dedupe: ['dexie'] },
});

This ensures the app and the library share the same Dexie singleton so reactivity works correctly.

Indexing Note

When querying with where('field') or sorting with orderBy('field'), ensure the field is indexed in your entity definition:

defineEntity(PostEntity, {
  tableName: 'posts',
  columns: {
    createdAt: { indexed: true },
    published: { indexed: true },
    likes: { indexed: true },
  },
});

If you change indexes, bump your database version or run a migration so Dexie can apply the schema update.

Example Applications

The library includes comprehensive demo applications showcasing all features across different frameworks:

Available Demo Apps

React Demo App

  • Framework: React 19 + TypeScript
  • UI Library: Material-UI (MUI)
  • Features: Live queries, TypeScript demo, Cloud sync, Aggregations
  • Run: cd react-demo-app && npm install && npm run dev
  • URL: http://localhost:5173

Vue Demo App

  • Framework: Vue 3 + TypeScript
  • UI Library: Vuetify 3
  • Features: Reactive components, Cloud sync, Entity management
  • Run: cd vue-demo-app && npm install && npm run dev
  • URL: http://localhost:5173

Svelte Demo App

  • Framework: SvelteKit + TypeScript
  • UI Library: Material-UI (MUI)
  • Features: Server-side rendering, Live queries, TypeScript integration
  • Run: cd svelte-demo-app && npm install && npm run dev
  • URL: http://localhost:5173

Angular Demo App

  • Framework: Angular 17 + TypeScript
  • UI Library: Angular Material
  • Features: Services, Dependency injection, Reactive forms
  • Run: cd angular-demo-app && npm install && npm start
  • URL: http://localhost:4200

Demo App Features

All demo applications showcase:

Core Features

  • Entity Definition - Using defineEntity() API
  • Zod Validation - Runtime validation with error handling
  • Relations - One-to-one, one-to-many, many-to-many relationships
  • Aggregations - Count, sum, average, min, max operations
  • Live Queries - Reactive data with useLiveQuery()
  • TypeScript - Full type safety and IntelliSense

Advanced Features

  • Cloud Sync - Dexie Cloud synchronization
  • Migrations - Database schema migrations
  • Compound Indexes - Multi-column indexing
  • Entity Management - CRUD operations with relations
  • Error Handling - Validation errors and user feedback

Demo App Structure

Each demo app follows similar structure:

demo-app/
├── src/
│   ├── entities/         # Entity definitions
│   ├── database/         # Database configuration
│   └── migrations/       # Database migrations
├── package.json
└── README.md

Each demo is fully functional and can serve as a starting point for your own applications!

API Reference

Core Classes

Database

Main database class extending Dexie with ORM capabilities.

Static Methods:

  • Database.createDatabase(params) - Create database with entity registration

Instance Methods:

  • getRepository<T>(entityClass) - Get typed repository for entity
  • aggregate<T>(params) - Perform aggregation operations
  • clearAllData() - Clear all data from database
  • resetDatabase() - Reset database when schema changes
  • checkSchemaChanges() - Check if schema has changed
  • performSelectiveReset() - Reset only changed tables
  • runMigrations(migrations) - Run database migrations
  • getTypedTable<T>(entityClass) - Get typed table for entity
  • getTableForEntity<T>(entityClass) - Get table with proper typing
  • getEntities() - Get all registered entities
  • getEntity(tableName) - Get entity by table name
  • loadRelations<T>(params) - Load relations for entity
  • loadRelationByName<T, K>(params) - Load specific relation
  • saveWithRelations<T>(params) - Save entity with relations
  • deleteWithRelations<T>(params) - Delete entity with cascade
  • sync() - Manual cloud sync
  • getSyncStatus() - Get cloud sync status
  • enableCloudSync(config) - Enable cloud synchronization
  • disableCloudSync() - Disable cloud sync
  • isCloudSyncEnabled() - Check if cloud sync is enabled
  • getCloudSyncConfig() - Get cloud sync configuration
  • syncTables(tableNames) - Sync specific tables

BaseEntity<TKey>

Base class for all entities with validation capabilities.

Methods:

  • validate(): ValidationResult - Validate entity against schema
  • validateOrThrow(): void - Validate and throw error if invalid
  • init(data): this - Initialize entity with data and validate

EntitySchema<T>

Schema management for entities.

Methods:

  • getTableName(): string - Get table name for entity
  • getSchema(): ZodSchema | undefined - Get Zod schema
  • getColumns(): Record<string, ColumnOptions> - Get column metadata
  • validate(data): ValidationResult - Validate data against schema
  • create(data?): T - Create new entity instance

Entity Definition

defineEntity(EntityClass, options)

Define entity with metadata and configuration.

Parameters:

  • EntityClass - Entity constructor class
  • options - Entity configuration object

Options:

interface EntityOptions {
  tableName?: string;           // Custom table name
  schema?: ZodSchema;           // Zod validation schema
  timestamps?: boolean;         // Enable timestamps
  columns?: Record<string, ColumnOptions>;
  relations?: Record<string, RelationOptions>;
  compoundIndexes?: CompoundIndexOptions[];
}

newEntity(EntityClass, data)

Create new entity instance with validation.

Parameters:

  • EntityClass - Entity constructor
  • data - Partial entity data

Returns: Fully initialized and validated entity instance

Decorators

@Entity(options?)

Class decorator for entity definition.

@Column(options?)

Property decorator for column configuration.

@Relation(options)

Property decorator for relation definition.

Relation Types:

  • @OneToOne(target, options?) - One-to-one relation
  • @OneToMany(target, foreignKey) - One-to-many relation
  • @ManyToMany(target, joinTable) - Many-to-many relation

Type Definitions

EntityConstructor<T>

interface EntityConstructor<T extends BaseEntity = BaseEntity> {
  new (): T;
  schema?: ZodSchema;
  tableName?: string;
}

ColumnOptions

interface ColumnOptions {
  kind?: 'string' | 'number' | 'boolean' | 'array' | 'object';
  unique?: boolean;
  indexed?: boolean;
  required?: boolean;
  default?: unknown;
  primaryKey?: boolean;
  autoIncrement?: boolean;
}

RelationOptions

interface RelationOptions {
  type: 'one-to-one' | 'one-to-many' | 'many-to-many';
  target: EntityConstructor | string;
  foreignKey?: string;
  joinTable?: string;
  cascade?: boolean;
  eager?: boolean;
}

ValidationResult

interface ValidationResult {
  isValid: boolean;
  errors: string[];
}

DatabaseConfig

interface DatabaseConfig {
  name: string;
  version: number;
  entities: EntityConstructor[];
  onSchemaChangeStrategy?: 'selective' | 'all';
  migrations?: Migration[];
  cloudSync?: CloudSyncConfig;
}

CloudSyncConfig

interface CloudSyncConfig {
  databaseUrl: string;
  enableOfflineSupport?: boolean;
  syncInterval?: number;
}

Migration

interface Migration {
  version: number;
  name: string;
  up: (db: Dexie) => Promise<void>;
  down?: (db: Dexie) => Promise<void>;
}

AggregationOptions<T>

interface AggregationOptions<T extends BaseEntity> {
  where?: Partial<T>;
  count?: boolean;
  sum?: (keyof T)[];
  avg?: (keyof T)[];
  min?: (keyof T)[];
  max?: (keyof T)[];
  groupBy?: keyof T;
  include?: string[];
  limit?: number;
  sort?: {
    field: keyof T;
    direction: 'asc' | 'desc';
  };
}

AggregationResult

interface AggregationResult {
  count?: number;
  sum?: Record<string, number>;
  avg?: Record<string, number>;
  min?: Record<string, number>;
  max?: Record<string, number>;
  groups?: Array<{
    key: unknown;
    count: number;
    sum?: Record<string, number>;
    avg?: Record<string, number>;
    min?: Record<string, number>;
    max?: Record<string, number>;
  }>;
  data?: unknown[];
}

Error Classes

ValidationError

Error thrown when entity validation fails.

Properties:

  • message: string - Error message
  • errors: string[] - Array of validation errors

Utility Functions

newEntity(EntityClass, data)

Factory function for creating validated entities.

defineEntity(EntityClass, options)

Function for defining entity metadata.

Schema Change Strategies

'selective'

Only resets tables that have schema changes, preserving data in unchanged tables.

'all'

Resets the entire database when any schema change is detected.

Cloud Sync Status

interface SyncStatus {
  enabled: boolean;
  lastSync?: Date;
  isOnline?: boolean;
}

License

MIT