Подключение типов Dexie

Dexie.js изначально проектировалась как JavaScript-библиотека для работы с IndexedDB, но её архитектура полностью совместима с TypeScript и позволяет выстраивать строгую типизацию поверх динамического хранилища браузера. Типы в Dexie не являются внешним дополнением — они интегрируются в определение базы данных, таблиц, ключей и операций запросов, формируя единый слой безопасности на уровне компиляции.

Ключевая идея типизации 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.


Расширенная типизация через generics Dexie

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 не отслеживает эволюцию схемы автоматически, поэтому корректность структуры обеспечивается разработчиком, но все операции с таблицей остаются строго типизированными согласно финальному интерфейсу.


Обобщённый типизированный доступ к Dexie

Типизированная база часто используется через единый экземпляр:

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();

Каждый этап сохраняет строгость типов:

  • filterUser[]
  • mapstring[]
  • toArray → финальный массив

Интеграция пользовательских типов и расширений

Dexie позволяет расширять типы без изменения ядра базы.

interface User {
  id?: number;
  name: string;
  email: string;
  isActive: boolean;
  lastLogin?: number;
}

Добавление новых полей автоматически отражается во всех запросах, обновлениях и транзакциях.


Использование строгих типов для предотвращения ошибок данных

Типизация Dexie фактически заменяет runtime-валидацию на compile-time контроль:

  • исключает неверные поля при вставке
  • предотвращает несоответствие типов ключей
  • гарантирует согласованность структуры таблиц
  • обеспечивает корректное использование результатов запросов

В результате Dexie в TypeScript становится не просто wrapper над IndexedDB, а строго типизированной локальной базой данных с предсказуемым поведением на уровне компиляции.