При работе с Dexie.js ключевой архитектурный момент — исключение
множественного создания экземпляров базы данных в рамках одного
приложения. Dexie использует поверх IndexedDB собственный слой
управления схемой, транзакциями и версиями, и каждый новый экземпляр
класса Dexie фактически создаёт независимый контекст работы
с одной и той же физической базой браузера.
Если приложение многократно создаёт new Dexie('dbName'),
возникают следующие эффекты:
Singleton-подход решает задачу централизованного управления одним экземпляром базы данных в пределах процесса исполнения.
Наиболее простой и распространённый вариант — использование механизма модулей ES:
// db.js
import Dexie from 'dexie';
const db = new Dexie('appDatabase');
db.version(1).stores({
users: '++id, name, email',
posts: '++id, userId, title, createdAt'
});
db.open();
export default db;
Особенность модульной системы JavaScript заключается в том, что импортируемый модуль кэшируется. Это означает:
db создаётся ровно один раз на уровне модуля;Такой подход уже является singleton по своей природе.
В некоторых сценариях требуется отложенное создание базы — например, когда имя базы зависит от конфигурации пользователя или окружения.
import Dexie from 'dexie';
let dbInstance = null;
export function getDB() {
if (dbInstance) return dbInstance;
dbInstance = new Dexie('appDatabase');
dbInstance.version(1).stores({
cache: '++id, key, value, updatedAt'
});
return dbInstance;
}
Особенности подхода:
Однако важно учитывать, что open() в Dexie — асинхронная
операция, и lazy-инициализация должна учитывать состояние открытия.
Более строгая модель учитывает состояние открытия:
import Dexie from 'dexie';
let dbPromise = null;
export function getDB() {
if (!dbPromise) {
const db = new Dexie('appDatabase');
db.version(1).stores({
logs: '++id, level, message, createdAt'
});
dbPromise = db.open().then(() => db);
}
return dbPromise;
}
Здесь важно:
Promise<Dexie>;open();В средах разработки (Vite, Webpack, Next.js) hot module replacement может приводить к повторной загрузке модуля, где создаётся Dexie-инстанс.
Типичная проблема:
Dexie('appDatabase');Решение — привязка к глобальному объекту:
import Dexie from 'dexie';
const globalScope = typeof globalThis !== 'undefined' ? globalThis : window;
const db = globalScope.__dbInstance || new Dexie('appDatabase');
if (!globalScope.__dbInstance) {
db.version(1).stores({
messages: '++id, text, sentAt'
});
db.open();
globalScope.__dbInstance = db;
}
export default db;
Такой подход:
В SSR-окружениях (например, Next.js) важно различать сервер и клиент.
IndexedDB существует только в браузере, поэтому прямое создание Dexie на сервере недопустимо.
Корректная структура:
let db;
export function getDB() {
if (typeof window === 'undefined') {
return null;
}
if (!db) {
const Dexie = require('dexie');
db = new Dexie('appDatabase');
db.version(1).stores({
sessions: '++id, token, expiresAt'
});
db.open();
}
return db;
}
Особенности:
IndexedDB is not defined.Dexie поддерживает миграции через version(). Singleton
должен учитывать, что схема может эволюционировать.
import Dexie from 'dexie';
let db;
export function getDB() {
if (db) return db;
db = new Dexie('appDatabase');
db.version(1).stores({
notes: '++id, title'
});
db.version(2).stores({
notes: '++id, title, updatedAt'
}).upgrade(tx => {
return tx.table('notes').toCollection().modify(note => {
note.updatedAt = Date.now();
});
});
db.open();
return db;
}
Важно:
open();В связке с UI-фреймворками singleton становится фундаментом реактивности.
Пример с React и liveQuery:
import { liveQuery } from 'dexie';
import { getDB } from './db';
const db = await getDB();
export const usersQuery = liveQuery(() => db.users.toArray());
Ключевой момент:
db не singleton, создаются дублирующиеся
подписки;Singleton обеспечивает:
В тестовой среде singleton часто мешает изоляции. Решение — сброс модуля или явный reset:
export function resetDB() {
if (db) {
db.close();
}
db = null;
}
Или вариант с фабрикой:
export function createDB(name) {
const db = new Dexie(name);
db.version(1).stores({
items: '++id, value'
});
return db;
}
В тестах:
При проектировании архитектуры важно соблюдать несколько устойчивых правил:
open() вызывается один раз на жизненный цикл
приложения;globalThis.Эти принципы формируют устойчивую модель работы с IndexedDB через Dexie в приложениях любой сложности.