В браузерных приложениях, использующих localForage, организация экземпляров напрямую влияет на изоляцию данных, читаемость архитектуры и предсказуемость работы хранилища. Библиотека предоставляет модель, в которой каждый экземпляр может иметь собственные настройки драйвера, собственного пространства ключей и независимый набор данных. Это позволяет рассматривать хранилище не как глобальную свалку значений, а как набор логически разделённых модулей.
localForage по умолчанию использует единый глобальный экземпляр. Он
создаётся автоматически и наследует настройки, заданные через
config. Однако при росте приложения такой подход быстро
становится ограничением: ключи начинают пересекаться, данные разных
подсистем смешиваются, а управление жизненным циклом хранения
усложняется.
Для решения этой проблемы используется метод
createInstance, который позволяет создавать независимые
экземпляры:
import localForage from "localforage";
const userStore = localForage.createInstance({
name: "app",
storeName: "users"
});
const settingsStore = localForage.createInstance({
name: "app",
storeName: "settings"
});
Каждый экземпляр получает собственное пространство ключей и не пересекается с другими.
Поле name задаёт логическую область хранения. На уровне
IndexedDB оно соответствует имени базы данных. В контексте localForage
это первый уровень изоляции.
Использование разных name имеет смысл в следующих
случаях:
Пример:
const adminStore = localForage.createInstance({
name: "adminPanel",
storeName: "cache"
});
const clientStore = localForage.createInstance({
name: "clientApp",
storeName: "cache"
});
Хотя storeName совпадает, данные физически разделены на
уровне базы.
Если name задаёт уровень базы, то storeName
определяет внутреннюю коллекцию. Это наиболее важный инструмент
архитектурного разделения внутри localForage.
Типичные подходы к именованию storeName:
usersauthcartpreferencescachesessionВажно избегать слишком абстрактных названий вроде data
или store, так как они не отражают предметную область и
усложняют поддержку.
В крупных приложениях распространён подход, при котором для каждой предметной области создаётся отдельный экземпляр:
import localForage from "localforage";
export const authStorage = localForage.createInstance({
name: "shopApp",
storeName: "auth"
});
export const cartStorage = localForage.createInstance({
name: "shopApp",
storeName: "cart"
});
export const productStorage = localForage.createInstance({
name: "shopApp",
storeName: "products"
});
Такой подход позволяет:
При динамическом создании модулей часто используется фабрика экземпляров. Это особенно актуально, когда требуется изоляция по userId или tenantId:
import localForage from "localforage";
function createUserStore(userId) {
return localForage.createInstance({
name: `app_user_${userId}`,
storeName: "data"
});
}
В контексте localForage это позволяет масштабировать клиентское хранилище до многопользовательских сценариев без конфликтов ключей.
Даже при использовании createInstance критически важно
соблюдать единый стиль именования ключей внутри storeName.
Распространённые стратегии:
await userStore.setItem("user:profile", data);
await userStore.setItem("user:settings", settings);
await cartStore.setItem("items", items);
await cartStore.setItem("totals", totals);
await storage.setItem("auth/token", token);
await storage.setItem("auth/refreshToken", refreshToken);
localForage не интерпретирует ключи как структуру, но соглашения позволяют имитировать иерархию на уровне логики приложения.
В некоторых архитектурах экземпляры создаются на лету, например при переключении workspace:
import localForage from "localforage";
const stores = new Map();
function getWorkspaceStore(workspaceId) {
if (!stores.has(workspaceId)) {
stores.set(
workspaceId,
localForage.createInstance({
name: "workspaceApp",
storeName: `ws_${workspaceId}`
})
);
}
return stores.get(workspaceId);
}
Такой подход уменьшает вероятность конфликтов данных между контекстами и обеспечивает ленивую инициализацию.
На практике встречаются типичные архитектурные ошибки при использовании localForage:
Использование единственного экземпляра приводит к:
Динамическое или непоследовательное именование вроде:
storeName: "data1"
storeName: "tempStore"
storeName: "cache_new_final"
создаёт хаос в структуре хранения.
Хранение авторизации, UI-состояния и бизнес-данных в одном экземпляре нарушает принципы изоляции и усложняет миграции.
В архитектурах среднего и крупного масштаба используется строгая схема:
name → идентификатор приложения или окруженияstoreName → доменная областьПример структуры:
name: "crmApp"
storeName: "contacts"
keys:
- contact:list
- contact:selected
- contact:filters
Такая организация в localForage позволяет легко переносить данные между версиями приложения и упрощает миграции схемы хранения.
Отдельный подход — использование версии приложения в имени базы:
const storageV1 = localForage.createInstance({
name: "app_v1",
storeName: "data"
});
const storageV2 = localForage.createInstance({
name: "app_v2",
storeName: "data"
});
Это упрощает миграцию данных, позволяя работать с несколькими схемами параллельно.
В некоторых приложениях экземпляры разделяются по средам:
const devStore = localForage.createInstance({
name: "app_dev",
storeName: "cache"
});
const prodStore = localForage.createInstance({
name: "app_prod",
storeName: "cache"
});
localForage в этом случае используется как единый API-слой, а изоляция достигается через namespace.
Структурно корректная архитектура экземпляров строится по принципу трёх уровней:
name)storeName)Такая модель обеспечивает предсказуемость, минимизирует конфликты и делает клиентское хранилище управляемым даже в сложных распределённых интерфейсах.