Модель хранения данных в IndexedDB основана на концепции объектных хранилищ (object stores), которые в Dexie описываются декларативно через версионирование схемы. Однако во многих прикладных сценариях требуется не фиксированная структура, а динамическое формирование наборов таблиц и индексов в зависимости от конфигурации, пользовательских данных или среды выполнения.
Dexie поддерживает несколько уровней динамичности: от генерации схемы перед открытием базы до программного обращения к таблицам, созданным на лету в рамках версий.
Основной способ описания структуры базы данных — через версию:
const db = new Dexie("AppDatabase");
db.version(1).stores({
users: "++id,name,email",
orders: "++id,userId,createdAt"
});
Каждый ключ объекта stores — это имя хранилища, а
значение — строка схемы индексов.
Этот объект можно сформировать динамически до передачи в Dexie:
const entities = ["users", "orders", "logs"];
const schema = {
users: "++id,name,email",
orders: "++id,userId,createdAt",
logs: "++id,level,timestamp"
};
db.version(1).stores(schema);
Таким образом, сама библиотека не ограничивает статичность структуры
— ограничение задаётся только моментом вызова
version().stores().
Динамическое создание хранилищ часто опирается на конфигурационные данные:
function buildSchema(config) {
const stores = {};
for (const table of config.tables) {
const indexes = [];
if (table.primaryKey) {
indexes.push(table.primaryKey === "auto" ? "++id" : table.primaryKey);
}
if (table.fields) {
indexes.push(...table.fields);
}
stores[table.name] = indexes.join(",");
}
return stores;
}
const config = {
tables: [
{ name: "users", primaryKey: "auto", fields: ["name", "email"] },
{ name: "events", primaryKey: "auto", fields: ["type", "date"] }
]
};
db.version(1).stores(buildSchema(config));
Подобный подход позволяет генерировать структуру базы данных из JSON, API-ответов или пользовательских настроек.
IndexedDB требует строгого соблюдения версий. После открытия базы изменение схемы возможно только через увеличение версии:
db.version(2).stores({
users: "++id,name,email,role"
});
Это означает, что динамическое создание хранилищ всегда связано с управлением версиями.
Типичная проблема возникает при попытке изменить схему после открытия базы:
Поэтому генерация схемы должна происходить до вызова
db.open().
Динамические приложения часто включают опциональные модули. В этом случае структура базы формируется условно:
const stores = {
users: "++id,name,email"
};
if (featureFlags.enableLogs) {
stores.logs = "++id,type,message,timestamp";
}
if (featureFlags.enableAnalytics) {
stores.analytics = "++id,event,meta,createdAt";
}
db.version(1).stores(stores);
Такой подход позволяет включать или отключать части схемы без изменения основного кода базы.
При изменении структуры на основе динамических условий часто требуется формировать цепочку версий:
db.version(1).stores({
users: "++id,name"
});
db.version(2).stores({
users: "++id,name,email"
});
db.version(3).stores({
users: "++id,name,email,role",
logs: "++id,type,timestamp"
});
Версионность становится не просто механизмом миграции, а инструментом эволюции динамически собранной схемы.
Даже если хранилище не было явно описано в текущей версии, Dexie
позволяет обращаться к нему через table:
const tableName = "users";
const table = db.table(tableName);
await table.add({
name: "Alex",
email: "alex@example.com"
});
Это поведение важно в сценариях, где имена таблиц неизвестны на этапе компиляции.
Динамические хранилища часто применяются для изоляции данных разных пользователей:
function createTenantSchema(tenantId) {
return {
[`${tenantId}_users`]: "++id,name,email",
[`${tenantId}_orders`]: "++id,productId,createdAt"
};
}
db.version(1).stores(createTenantSchema("tenantA"));
Такой подход приводит к экспоненциальному росту числа таблиц, поэтому требуется осторожность при масштабировании.
Dexie поддерживает строковую декларацию схемы, которая может генерироваться программно:
function createIndexString(fields) {
return "++id," + fields.join(",");
}
const schema = {
users: createIndexString(["name", "email"]),
posts: createIndexString(["title", "createdAt"])
};
db.version(1).stores(schema);
Это упрощает интеграцию с внешними моделями данных.
При радикальном изменении схемы иногда требуется удаление старой базы:
await db.delete();
db.version(1).stores(dynamicSchema);
await db.open();
Такой подход используется в сценариях, где миграция невозможна или экономически нецелесообразна.
Наиболее частые проблемы:
1. Несоответствие версии
VersionError: Schema not updated
Возникает при попытке изменить stores без увеличения версии.
2. Попытка открыть базу до генерации схемы
db.open(); // schema еще не сформирована
3. Неконсистентные ключи Если primary key определяется динамически, важно сохранять его стабильность между версиями.
В некоторых случаях требуется добавление индексов без изменения структуры:
db.version(2).stores({
users: "++id,name,email,age"
});
Dexie автоматически сохраняет существующие данные и лишь расширяет индексацию.
Сложные приложения используют композицию модулей:
const baseSchema = {
users: "++id,name,email"
};
const auditSchema = {
logs: "++id,action,timestamp"
};
const schema = {
...baseSchema,
...auditSchema
};
db.version(1).stores(schema);
Такой подход позволяет строить базу как набор независимых слоёв.
В редких сценариях используется несколько конфигураций базы в одном приложении:
function createDb(mode) {
const db = new Dexie("AppDB");
const schema =
mode === "light"
? { users: "++id,name" }
: { users: "++id,name,email,role", logs: "++id,type" };
db.version(1).stores(schema);
return db;
}
Dexie не требует предварительного объявления таблицы для обращения:
Поэтому динамическое имя таблицы всегда должно быть синхронизировано со схемой версии.
Архитектура расширяемых приложений часто использует плагины:
const plugins = [
{
name: "users",
schema: "++id,name,email"
},
{
name: "notifications",
schema: "++id,type,read"
}
];
const stores = Object.fromEntries(
plugins.map(p => [p.name, p.schema])
);
db.version(1).stores(stores);
Такой подход позволяет добавлять функциональные блоки без изменения ядра приложения.
В некоторых архитектурах схема собирается поэтапно:
let stores = {};
stores = addUserModule(stores);
stores = addOrderModule(stores);
stores = addLoggingModule(stores);
db.version(1).stores(stores);
Каждый модуль расширяет объект, не зная о других частях системы.
Динамическое создание хранилищ требует соблюдения нескольких технических ограничений:
Эти принципы определяют предсказуемость поведения IndexedDB в среде Dexie и позволяют безопасно комбинировать статические и динамические элементы схемы.