Dexie.js изначально проектировалась как JavaScript-библиотека для работы с IndexedDB, но её архитектура полностью совместима с TypeScript и позволяет выстраивать строгую типизацию поверх динамического хранилища браузера. Типы в Dexie не являются внешним дополнением — они интегрируются в определение базы данных, таблиц, ключей и операций запросов, формируя единый слой безопасности на уровне компиляции.
Ключевая идея типизации Dexie заключается в описании структуры данных один раз и дальнейшем распространении этой структуры на все операции: добавление, чтение, обновление и удаление записей.
Dexie уже содержит встроенные TypeScript-определения, поэтому отдельный пакет типов не требуется. Достаточно использовать стандартную установку:
npm install dexie
При использовании TypeScript дополнительные зависимости не нужны, так как декларации входят в основной пакет.
Основой типизации является интерфейс, описывающий структуру сущностей таблицы. Например, модель пользователя:
export interface User {
id?: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
Важно учитывать, что поле первичного ключа часто помечается как optional, поскольку Dexie может генерировать его автоматически при автоинкременте.
Dexie позволяет описывать базу данных через наследование класса
Dexie и явное указание типов таблиц.
import Dexie, { Table } fr om 'dexie';
export class AppDB extends Dexie {
users!: Table<User, number>;
constructor() {
super('AppDB');
this.version(1).stores({
users: '++id, name, email, age'
});
}
}
Здесь происходит связывание:
User — тип записиnumber — тип первичного ключаTable<User, number> — строго типизированная
таблицаТакая конструкция обеспечивает автодополнение и контроль типов при
работе с таблицей users.
Dexie поддерживает различные типы ключей: числовые, строковые и составные.
users!: Table<User, number>;
export interface Session {
token: string;
createdAt: number;
}
sessions!: Table<Session, string>;
this.version(1).stores({
sessions: 'token'
});
export interface LogEntry {
userId: number;
timestamp: number;
message: string;
}
logs!: Table<LogEntry, [number, number]>;
this.version(1).stores({
logs: '[userId+timestamp]'
});
Составной ключ в TypeScript выражается кортежем, отражающим порядок индексов.
Dexie автоматически выводит типы для большинства CRUD-операций, если
таблица объявлена через Table<T, Key>.
const db = new AppDB();
await db.users.add({
name: 'Alex',
email: 'alex@mail.com',
age: 30,
isActive: true
});
TypeScript проверяет соответствие структуры User,
исключая лишние поля и контролируя обязательные.
const user = await db.users.get(1);
Результат имеет тип:
User | undefined
Это важный аспект строгой типизации Dexie: любое чтение может вернуть
undefined, если запись не найдена.
await db.users.update(1, {
isActive: false
});
TypeScript ограничивает обновление только полями, существующими в
User, предотвращая случайные ошибки.
await db.users.delete(1);
Операция не требует типизации полей, но строго контролирует тип ключа.
Dexie использует ленивые коллекции
(Collection<T, Key>), которые также поддерживают
типизацию.
const activeUsers = await db.users
.wh ere('isActive')
.equals(true)
.toArray();
Результат:
User[]
Коллекции сохраняют тип сущности на всех этапах цепочки запросов:
const result = db.users
.where('age')
.above(18)
.filter(u => u.isActive)
.toArray();
TypeScript гарантирует, что u имеет тип
User.
Dexie позволяет централизованно описывать все таблицы базы данных через интерфейс схемы.
interface AppSchema {
users: User;
logs: LogEntry;
sessions: Session;
}
Однако Dexie не требует такого подхода напрямую, чаще используется
классовая модель с явным указанием Table<T, Key>.
Транзакции в Dexie также полностью типизированы.
await db.transaction('rw', db.users, db.logs, async () => {
await db.users.add({
name: 'Maria',
email: 'maria@mail.com',
age: 25,
isActive: true
});
await db.logs.add({
userId: 1,
timestamp: Date.now(),
message: 'User created'
});
});
TypeScript контролирует:
Dexie поддерживает события жизненного цикла, которые также могут быть типизированы.
db.users.hook('creating', (primKey, obj) => {
obj.createdAt = Date.now();
});
Здесь obj автоматически выводится как User,
что позволяет безопасно расширять объект без нарушения структуры.
При миграциях важно сохранять соответствие типов между версиями.
this.version(1).stores({
users: '++id, name, email'
});
this.version(2).stores({
users: '++id, name, email, age'
});
TypeScript не отслеживает эволюцию схемы автоматически, поэтому корректность структуры обеспечивается разработчиком, но все операции с таблицей остаются строго типизированными согласно финальному интерфейсу.
Типизированная база часто используется через единый экземпляр:
export const db = new AppDB();
И далее:
const users = await db.users.toArray();
На уровне TypeScript это обеспечивает:
Dexie не типизирует строки индексов напрямую, но связка
Table<T, Key> гарантирует корректность ключей.
db.users.where('email')
Если поле отсутствует в интерфейсе User, TypeScript
выдаст ошибку на этапе компиляции.
Dexie активно использует Promise-based API, что позволяет TypeScript автоматически распространять типы:
const names: string[] = await db.users
.filter(u => u.isActive)
.map(u => u.name)
.toArray();
Каждый этап сохраняет строгость типов:
filter → User[]map → string[]toArray → финальный массивDexie позволяет расширять типы без изменения ядра базы.
interface User {
id?: number;
name: string;
email: string;
isActive: boolean;
lastLogin?: number;
}
Добавление новых полей автоматически отражается во всех запросах, обновлениях и транзакциях.
Типизация Dexie фактически заменяет runtime-валидацию на compile-time контроль:
В результате Dexie в TypeScript становится не просто wrapper над IndexedDB, а строго типизированной локальной базой данных с предсказуемым поведением на уровне компиляции.