Динамическое создание хранилищ

Модель хранения данных в 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"
});

Это означает, что динамическое создание хранилищ всегда связано с управлением версиями.

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

  • база уже инициализирована
  • новая версия не зарегистрирована до открытия
  • изменение структуры блокируется движком IndexedDB

Поэтому генерация схемы должна происходить до вызова 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"
});

Это поведение важно в сценариях, где имена таблиц неизвестны на этапе компиляции.


Создание мультиарендных (multi-tenant) структур

Динамические хранилища часто применяются для изоляции данных разных пользователей:

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;
}

Особенности работы с db.table() в динамических схемах

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

Каждый модуль расширяет объект, не зная о других частях системы.


Принципы устойчивого динамического проектирования

Динамическое создание хранилищ требует соблюдения нескольких технических ограничений:

  • стабильность primary key между версиями
  • неизменность структуры в рамках одной версии
  • формирование схемы до открытия базы
  • явное управление версиями при изменении структуры
  • синхронизация имён таблиц между слоями приложения

Эти принципы определяют предсказуемость поведения IndexedDB в среде Dexie и позволяют безопасно комбинировать статические и динамические элементы схемы.