synapse-storage 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,12 +13,27 @@ export declare class IndexedDBManager {
13
13
  private initPromise;
14
14
  private storeNames;
15
15
  private dbVersion;
16
+ private opQueue;
16
17
  private constructor();
17
18
  static getInstance(dbName: string, dbVersion?: number, logger?: ILogger): IndexedDBManager;
18
19
  initialize(): Promise<IDBDatabase>;
19
20
  private autoDetectAndOpen;
20
21
  private detectCurrentVersion;
21
22
  ensureStoreExists(storeName: string): Promise<IDBDatabase>;
23
+ /**
24
+ * Ставит операцию изменения схемы в последовательную очередь, чтобы создания
25
+ * сторов на одной БД не перекрывались между await-точками. Очередь не должна
26
+ * "застревать" из-за упавшей операции — ошибку прокидываем вызывающему, но
27
+ * следующий элемент очереди стартует независимо от результата предыдущего.
28
+ */
29
+ private enqueue;
30
+ /**
31
+ * Идемпотентно гарантирует наличие переданных сторов. Никогда не обращается к
32
+ * this.db после await — работает с локальной ссылкой, возвращённой initialize()
33
+ * / openDatabase(), поэтому параллельное обнуление this.db (другим клиентом
34
+ * или onversionchange) не приводит к чтению свойств у null.
35
+ */
36
+ private ensureStoresInternal;
22
37
  private openDatabase;
23
38
  closeDatabase(): void;
24
39
  deleteDatabase(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"indexed-DB.service.d.ts","sourceRoot":"","sources":["../../../../src/core/storage/adapters/indexed-DB.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AAEzE,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAC7H,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAG/D,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAGD,qBAAa,gBAAgB;IAQzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAT1B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAsC;IAC9D,OAAO,CAAC,EAAE,CAA2B;IACrC,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,SAAS,CAAQ;IAEzB,OAAO;IAQP,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,MAAU,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB;IAevF,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC;YAY1B,iBAAiB;IAU/B,OAAO,CAAC,oBAAoB;IAgBtB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YAwBlD,YAAY;IAsD1B,aAAa,IAAI,IAAI;IAQf,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB/B,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IA8BnE,iBAAiB,IAAI,MAAM;CAG5B;AAED,qBAAa,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAE,SAAQ,gBAAgB,CAAC,CAAC,CAAC;IACtF,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAc;IACjE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAc;IAExC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,SAAS,CAAkB;gBAEvB,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,oBAAoB,EAAE,YAAY,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,OAAO;IAUpI,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzC,MAAM,EAAE,sBAAsB,EAC9B,cAAc,CAAC,EAAE,oBAAoB,EACrC,YAAY,CAAC,EAAE,aAAa,EAC5B,MAAM,CAAC,EAAE,OAAO,GACf,gBAAgB,CAAC,CAAC,CAAC;cASN,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;WA2BhC,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvD,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;SACN,CAAC,IAAI,MAAM,CAAC,GAAG;YACd,IAAI,EAAE,MAAM,CAAA;YACZ,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YACnB,WAAW,CAAC,EAAE,yBAAyB,CAAA;YACvC,cAAc,CAAC,EAAE,oBAAoB,CAAA;YACrC,YAAY,CAAC,EAAE,aAAa,CAAA;SAC7B;KACF,EACD,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAAE,CAAC;IAiCtD;;;;;;;OAOG;IACG,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyB5G;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;YAIa,cAAc;YA8Bd,cAAc;cAKZ,KAAK,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC;cAiExC,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YA2DvD,eAAe;cAQb,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;cAyEjF,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;cA8D/C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;cASxB,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;cAW3B,KAAK,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D;;;OAGG;cACa,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;cAK/B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAI3C"}
1
+ {"version":3,"file":"indexed-DB.service.d.ts","sourceRoot":"","sources":["../../../../src/core/storage/adapters/indexed-DB.service.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,12 @@ class IndexedDBManager {
20
20
  initPromise = null;
21
21
  storeNames = new Set();
22
22
  dbVersion;
23
+ // Последовательная очередь операций изменения схемы (создание сторов).
24
+ // На одну БД (singleton по dbName) обычно инициализируется сразу несколько
25
+ // ApiClient-ов (comments, posts, reactions, ...). Без сериализации их
26
+ // ensureStoreExists перекрывались: один обнулял this.db между close() и
27
+ // переоткрытием, второй в этот момент читал this.db.objectStoreNames → null.
28
+ opQueue = Promise.resolve();
23
29
  constructor(dbName, dbVersion, logger){
24
30
  this.dbName = dbName;
25
31
  this.logger = logger;
@@ -70,24 +76,43 @@ class IndexedDBManager {
70
76
  });
71
77
  }
72
78
  async ensureStoreExists(storeName) {
73
- await this.initialize();
74
- if (this.db.objectStoreNames.contains(storeName)) {
75
- this.storeNames.add(storeName);
76
- return this.db;
79
+ return this.enqueue(()=>this.ensureStoresInternal([
80
+ storeName
81
+ ]));
82
+ }
83
+ /**
84
+ * Ставит операцию изменения схемы в последовательную очередь, чтобы создания
85
+ * сторов на одной БД не перекрывались между await-точками. Очередь не должна
86
+ * "застревать" из-за упавшей операции — ошибку прокидываем вызывающему, но
87
+ * следующий элемент очереди стартует независимо от результата предыдущего.
88
+ */ enqueue(operation) {
89
+ const result = this.opQueue.then(operation, operation);
90
+ this.opQueue = result.then(()=>undefined, ()=>undefined);
91
+ return result;
92
+ }
93
+ /**
94
+ * Идемпотентно гарантирует наличие переданных сторов. Никогда не обращается к
95
+ * this.db после await — работает с локальной ссылкой, возвращённой initialize()
96
+ * / openDatabase(), поэтому параллельное обнуление this.db (другим клиентом
97
+ * или onversionchange) не приводит к чтению свойств у null.
98
+ */ async ensureStoresInternal(storeNames) {
99
+ let db = await this.initialize();
100
+ const missingStores = storeNames.filter((name)=>!db.objectStoreNames.contains(name));
101
+ if (missingStores.length === 0) {
102
+ for (const name of storeNames)this.storeNames.add(name);
103
+ return db;
77
104
  }
78
- this.logger?.debug(`Store "${storeName}" not found, upgrading database`, {
105
+ this.logger?.debug(`Создание недостающих хранилищ: ${missingStores.join(', ')}`, {
79
106
  dbName: this.dbName,
80
- currentStores: Array.from(this.db.objectStoreNames)
107
+ currentStores: Array.from(db.objectStoreNames)
81
108
  });
82
- this.db.close();
109
+ db.close();
83
110
  this.db = null;
84
111
  this.dbVersion++;
85
- this.initPromise = this.openDatabase([
86
- storeName
87
- ]);
88
- const newDb = await this.initPromise;
89
- this.storeNames.add(storeName);
90
- return newDb;
112
+ this.initPromise = this.openDatabase(missingStores);
113
+ db = await this.initPromise;
114
+ for (const name of storeNames)this.storeNames.add(name);
115
+ return db;
91
116
  }
92
117
  async openDatabase(newStores = []) {
93
118
  return new Promise((resolve, reject)=>{
@@ -160,26 +185,7 @@ class IndexedDBManager {
160
185
  });
161
186
  }
162
187
  async ensureStoresExist(storeNames) {
163
- // Сначала инициализируем базу
164
- await this.initialize();
165
- // Проверяем, какие хранилища уже существуют
166
- const missingStores = storeNames.filter((name)=>!this.db.objectStoreNames.contains(name));
167
- // Если все хранилища уже существуют, просто возвращаем базу
168
- if (missingStores.length === 0) {
169
- return this.db;
170
- }
171
- // Иначе нам нужно обновить базу для создания новых хранилищ
172
- this.logger?.debug(`Создание недостающих хранилищ: ${missingStores.join(', ')}`, {
173
- dbName: this.dbName,
174
- currentStores: Array.from(this.db.objectStoreNames)
175
- });
176
- // Закрываем текущее соединение
177
- this.db.close();
178
- this.db = null;
179
- // Увеличиваем версию один раз для всех новых хранилищ
180
- this.dbVersion++;
181
- this.initPromise = this.openDatabase(missingStores);
182
- return this.initPromise;
188
+ return this.enqueue(()=>this.ensureStoresInternal(storeNames));
183
189
  }
184
190
  // Метод для получения текущей версии
185
191
  getCurrentVersion() {
@@ -1 +1 @@
1
- {"version":3,"file":"core/storage/adapters/indexed-DB.service.js","sources":["../../../../src/core/storage/adapters/indexed-DB.service.ts"],"sourcesContent":["import { IAsyncPluginExecutor } from '../modules/plugin/plugin.interface'\nimport { SingletonMixin } from '../modules/singleton/mixin.util'\nimport { ConfigureAsyncMiddlewares, IEventEmitter, ILogger, IndexedDBStorageConfig, StorageType } from '../storage.interface'\nimport { StorageKey, StorageKeyType } from '../utils/storage-key'\nimport { AsyncBaseStorage } from './async-base-storage.service'\nimport { getValueByPath, parsePath, setValueByPath } from './path.utils'\n\nexport interface IndexedDBConfig {\n dbName?: string\n}\n\n// Управляет соединением с базой данных\nexport class IndexedDBManager {\n private static instances = new Map<string, IndexedDBManager>()\n private db: IDBDatabase | null = null\n private initPromise: Promise<IDBDatabase> | null = null\n private storeNames: Set<string> = new Set()\n private dbVersion: number\n\n private constructor(\n private readonly dbName: string,\n dbVersion: number,\n private readonly logger?: ILogger,\n ) {\n this.dbVersion = dbVersion\n }\n\n static getInstance(dbName: string, dbVersion: number = 1, logger?: ILogger): IndexedDBManager {\n if (!IndexedDBManager.instances.has(dbName)) {\n IndexedDBManager.instances.set(dbName, new IndexedDBManager(dbName, dbVersion, logger))\n }\n\n const instance = IndexedDBManager.instances.get(dbName)!\n\n // Update version if higher version is requested\n if (dbVersion > instance.dbVersion) {\n instance.dbVersion = dbVersion\n }\n\n return instance\n }\n\n async initialize(): Promise<IDBDatabase> {\n if (this.db) {\n return this.db\n }\n\n if (!this.initPromise) {\n this.initPromise = this.autoDetectAndOpen()\n }\n\n return this.initPromise\n }\n\n private async autoDetectAndOpen(): Promise<IDBDatabase> {\n // Auto-detect the current DB version to avoid VersionError\n const currentVersion = await this.detectCurrentVersion()\n if (currentVersion > this.dbVersion) {\n this.logger?.debug(`Auto-detected higher DB version: ${currentVersion} (requested: ${this.dbVersion})`)\n this.dbVersion = currentVersion\n }\n return this.openDatabase()\n }\n\n private detectCurrentVersion(): Promise<number> {\n return new Promise<number>((resolve) => {\n try {\n const request = indexedDB.open(this.dbName)\n request.onsuccess = () => {\n const version = request.result.version\n request.result.close()\n resolve(version)\n }\n request.onerror = () => resolve(0)\n } catch {\n resolve(0)\n }\n })\n }\n\n async ensureStoreExists(storeName: string): Promise<IDBDatabase> {\n await this.initialize()\n\n if (this.db!.objectStoreNames.contains(storeName)) {\n this.storeNames.add(storeName)\n return this.db!\n }\n\n this.logger?.debug(`Store \"${storeName}\" not found, upgrading database`, {\n dbName: this.dbName,\n currentStores: Array.from(this.db!.objectStoreNames),\n })\n\n this.db!.close()\n this.db = null\n\n this.dbVersion++\n this.initPromise = this.openDatabase([storeName])\n\n const newDb = await this.initPromise\n this.storeNames.add(storeName)\n return newDb\n }\n\n private async openDatabase(newStores: string[] = []): Promise<IDBDatabase> {\n return new Promise<IDBDatabase>((resolve, reject) => {\n this.logger?.debug(`Opening database \"${this.dbName}\" with version ${this.dbVersion}`)\n\n const request = indexedDB.open(this.dbName, this.dbVersion)\n\n request.onerror = () => {\n this.logger?.error(`Failed to open database \"${this.dbName}\"`, { error: request.error })\n reject(request.error)\n }\n\n request.onblocked = () => {\n this.logger?.warn(`Database \"${this.dbName}\" upgrade blocked by another connection. Close other tabs or connections.`)\n reject(new Error(`Database \"${this.dbName}\" upgrade blocked — close other tabs using this database`))\n }\n\n request.onsuccess = () => {\n this.db = request.result\n\n // Auto-close when another connection requests a version upgrade\n this.db.onversionchange = () => {\n this.db?.close()\n this.db = null\n this.initPromise = null\n }\n\n // Add existing stores to our set\n for (let i = 0; i < this.db.objectStoreNames.length; i++) {\n this.storeNames.add(this.db.objectStoreNames[i])\n }\n\n this.logger?.debug(`Database \"${this.dbName}\" opened successfully`, {\n version: this.db.version,\n stores: Array.from(this.db.objectStoreNames),\n })\n\n resolve(this.db)\n }\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n this.logger?.debug(`Upgrading database \"${this.dbName}\" to version ${this.dbVersion}`)\n\n // Create new stores that don't exist yet\n for (const storeName of newStores) {\n if (!db.objectStoreNames.contains(storeName)) {\n this.logger?.debug(`Creating store \"${storeName}\"`)\n db.createObjectStore(storeName)\n }\n }\n }\n })\n }\n\n closeDatabase(): void {\n if (this.db) {\n this.db.close()\n this.db = null\n this.initPromise = null\n }\n }\n\n async deleteDatabase(): Promise<void> {\n this.closeDatabase()\n\n return new Promise<void>((resolve, reject) => {\n const request = indexedDB.deleteDatabase(this.dbName)\n request.onsuccess = () => {\n this.logger?.debug(`Database \"${this.dbName}\" deleted successfully`)\n IndexedDBManager.instances.delete(this.dbName)\n this.storeNames.clear()\n resolve()\n }\n request.onerror = () => {\n this.logger?.error(`Failed to delete database \"${this.dbName}\"`, { error: request.error })\n reject(request.error)\n }\n })\n }\n async ensureStoresExist(storeNames: string[]): Promise<IDBDatabase> {\n // Сначала инициализируем базу\n await this.initialize()\n\n // Проверяем, какие хранилища уже существуют\n const missingStores = storeNames.filter((name) => !this.db!.objectStoreNames.contains(name))\n\n // Если все хранилища уже существуют, просто возвращаем базу\n if (missingStores.length === 0) {\n return this.db!\n }\n\n // Иначе нам нужно обновить базу для создания новых хранилищ\n this.logger?.debug(`Создание недостающих хранилищ: ${missingStores.join(', ')}`, {\n dbName: this.dbName,\n currentStores: Array.from(this.db!.objectStoreNames),\n })\n\n // Закрываем текущее соединение\n this.db!.close()\n this.db = null\n\n // Увеличиваем версию один раз для всех новых хранилищ\n this.dbVersion++\n this.initPromise = this.openDatabase(missingStores)\n\n return this.initPromise\n }\n\n // Метод для получения текущей версии\n getCurrentVersion(): number {\n return this.dbVersion\n }\n}\n\nexport class IndexedDBStorage<T extends Record<string, any>> extends AsyncBaseStorage<T> {\n protected static readonly STORAGE_TYPE: StorageType = 'indexedDB'\n readonly type: StorageType = 'indexedDB'\n\n private readonly DB_NAME: string\n private readonly STORE_NAME: string\n private dbManager: IndexedDBManager\n\n constructor(config: IndexedDBStorageConfig<T>, pluginExecutor?: IAsyncPluginExecutor, eventEmitter?: IEventEmitter, logger?: ILogger) {\n super(config, pluginExecutor, eventEmitter, logger)\n\n this.DB_NAME = config.options?.dbName || 'app_storage'\n this.STORE_NAME = config.name\n\n // Get database manager instance (version is auto-detected internally)\n this.dbManager = IndexedDBManager.getInstance(this.DB_NAME, 1, logger)\n }\n\n static create<T extends Record<string, any>>(\n config: IndexedDBStorageConfig,\n pluginExecutor?: IAsyncPluginExecutor,\n eventEmitter?: IEventEmitter,\n logger?: ILogger,\n ): IndexedDBStorage<T> {\n return SingletonMixin.handleSingletonCreation(\n config,\n this.STORAGE_TYPE,\n (finalConfig) => new IndexedDBStorage<T>(finalConfig as IndexedDBStorageConfig<T>, pluginExecutor, eventEmitter, logger),\n logger,\n )\n }\n\n protected async doInitialize(): Promise<this> {\n try {\n this.logger?.debug(`Initializing IndexedDB storage \"${this.STORE_NAME}\"`)\n\n // Создаем store в базе данных\n await this.dbManager.ensureStoreExists(this.STORE_NAME)\n\n // Проверяем, что хранилище доступно\n const db = await this.dbManager.initialize()\n if (!db.objectStoreNames.contains(this.STORE_NAME)) {\n throw new Error(`Store \"${this.STORE_NAME}\" not found after initialization`)\n }\n\n // Инициализируем middleware\n this.initializeMiddlewares()\n\n // Инициализируем с middleware\n await this.initializeWithMiddlewares()\n\n this.logger?.debug(`IndexedDB storage \"${this.STORE_NAME}\" initialized successfully`)\n return this\n } catch (error) {\n this.logger?.error(`Ошибка инициализации IndexedDB \"${this.name}\"`, { error })\n throw error\n }\n }\n\n static async createStorages<S extends Record<string, any>>(\n dbName: string,\n configs: {\n [K in keyof S]: {\n name: string\n initialState?: S[K]\n middlewares?: ConfigureAsyncMiddlewares\n pluginExecutor?: IAsyncPluginExecutor\n eventEmitter?: IEventEmitter\n }\n },\n logger?: ILogger,\n ): Promise<{ [K in keyof S]: IndexedDBStorage<S[K]> }> {\n // Используем единый IndexedDBManager (версия определяется автоматически)\n const dbManager = IndexedDBManager.getInstance(dbName, 1, logger)\n\n // Получаем имена всех хранилищ, которые нужно создать\n const storeNames = Object.values(configs).map((config) => config.name)\n\n // Предварительно создаем все хранилища в рамках одной операции\n await dbManager.ensureStoresExist(storeNames)\n\n // Создаем и инициализируем все хранилища\n const result: Record<string, IndexedDBStorage<any>> = {}\n\n for (const [key, config] of Object.entries(configs)) {\n const storage = new IndexedDBStorage(\n {\n ...config,\n options: { dbName },\n },\n config.pluginExecutor,\n config.eventEmitter,\n logger,\n )\n\n // Инициализируем хранилище\n result[key] = await storage.initialize()\n }\n\n return result as { [K in keyof S]: IndexedDBStorage<S[K]> }\n }\n\n // ─── IndexedDB-specific API ────────────────────────────────────────────────\n\n /**\n * Выполняет операцию в рамках IDB-транзакции.\n * Обёртка для низкоуровневого IDB transaction API.\n *\n * @param mode - Режим транзакции ('readonly' | 'readwrite')\n * @param fn - Callback, получающий IDBObjectStore. Возвращает результат операции.\n * @returns Promise с результатом callback-а\n */\n async transaction<R>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => IDBRequest<R> | R): Promise<R> {\n const store = await this.getObjectStore(mode)\n\n return new Promise<R>((resolve, reject) => {\n const tx = store.transaction\n\n tx.onerror = () => reject(tx.error)\n tx.onabort = () => reject(tx.error || new Error('Transaction aborted'))\n\n try {\n const result = fn(store)\n\n if (result instanceof IDBRequest) {\n result.onsuccess = () => resolve(result.result)\n result.onerror = () => reject(result.error)\n } else {\n // Sync result — resolve when transaction completes\n tx.oncomplete = () => resolve(result)\n }\n } catch (error) {\n reject(error)\n }\n })\n }\n\n /**\n * Текущая версия базы данных.\n */\n get dbVersion(): number {\n return this.dbManager.getCurrentVersion()\n }\n\n /**\n * Имя базы данных.\n */\n get dbName(): string {\n return this.DB_NAME\n }\n\n /**\n * Имя object store в IndexedDB.\n */\n get storeName(): string {\n return this.STORE_NAME\n }\n\n // ─── Private helpers ───────────────────────────────────────────────────────\n\n private async getTransaction(mode: IDBTransactionMode = 'readonly'): Promise<IDBTransaction> {\n try {\n // Ensure database is open and our store exists\n const db = await this.dbManager.ensureStoreExists(this.STORE_NAME)\n\n // Проверяем существование хранилища перед созданием транзакции\n if (!db.objectStoreNames.contains(this.STORE_NAME)) {\n // Попытка исправить проблему - закрываем и снова открываем\n this.logger?.warn(`Object store \"${this.STORE_NAME}\" not found, attempting to repair`)\n\n db.close()\n this.dbManager.closeDatabase()\n\n // Пробуем заново создать хранилище с инкрементом версии\n const newDb = await this.dbManager.ensureStoreExists(this.STORE_NAME)\n\n if (!newDb.objectStoreNames.contains(this.STORE_NAME)) {\n throw new Error(`Object store \"${this.STORE_NAME}\" still doesn't exist after repair attempt`)\n }\n\n return newDb.transaction(this.STORE_NAME, mode)\n }\n\n return db.transaction(this.STORE_NAME, mode)\n } catch (error) {\n this.logger?.error(`Failed to create transaction for store \"${this.STORE_NAME}\"`, { error })\n throw error\n }\n }\n\n private async getObjectStore(mode: IDBTransactionMode = 'readonly'): Promise<IDBObjectStore> {\n const transaction = await this.getTransaction(mode)\n return transaction.objectStore(this.STORE_NAME)\n }\n\n protected async doGet(key: StorageKeyType): Promise<any> {\n const store = await this.getObjectStore()\n\n // Для пустого ключа возвращаем все состояние\n if (key === '') {\n return new Promise((resolve, reject) => {\n const request = store.getAll()\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n const allValues = request.result\n const allKeys = store.getAllKeys()\n\n allKeys.onsuccess = () => {\n const state = allKeys.result.reduce(\n (acc, k, index) => {\n if (k !== 'root') {\n acc[k as string] = allValues[index]\n }\n return acc\n },\n {} as Record<string, any>,\n )\n resolve(state)\n }\n allKeys.onerror = () => reject(allKeys.error)\n }\n })\n }\n\n // Проверяем, является ли ключ \"сырым\"\n if (key instanceof StorageKey && key.isUnparseable()) {\n return new Promise((resolve, reject) => {\n const request = store.get(key.valueOf())\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result)\n })\n }\n\n // Для вложенного пути\n const parts = parsePath(key)\n if (parts.length > 1) {\n const rootKey = parts[0]\n return new Promise((resolve, reject) => {\n const request = store.get(rootKey)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n const rootValue = request.result\n if (!rootValue) {\n resolve(undefined)\n return\n }\n const value = getValueByPath(rootValue, parts.slice(1).join('.'))\n resolve(value)\n }\n })\n }\n\n // Для простого ключа\n return new Promise((resolve, reject) => {\n const request = store.get(parts[0])\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result)\n })\n }\n\n protected async doSet(key: StorageKeyType, value: any): Promise<void> {\n // Для пустого ключа устанавливаем все состояние\n if (key === '') {\n const store = await this.getObjectStore('readwrite')\n return new Promise((resolve, reject) => {\n const tx = store.transaction\n\n tx.oncomplete = () => {\n resolve()\n }\n\n tx.onerror = () => {\n reject(tx.error)\n }\n\n const clearRequest = store.clear()\n\n clearRequest.onsuccess = () => {\n const entries = Object.entries(value)\n for (const [entryKey, entryValue] of entries) {\n store.put(entryValue, entryKey)\n }\n }\n\n clearRequest.onerror = () => {\n reject(clearRequest.error)\n }\n })\n }\n\n const store = await this.getObjectStore('readwrite')\n\n // Для \"сырого\" ключа\n if (key instanceof StorageKey && key.isUnparseable()) {\n await this.putValueInStore(store, key.valueOf(), value)\n return\n }\n\n // Для вложенного пути\n const parts = parsePath(key)\n if (parts.length > 1) {\n const rootKey = parts[0]\n return new Promise((resolve, reject) => {\n const request = store.get(rootKey)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n const rootValue = request.result || {}\n const updatedValue = setValueByPath(rootValue, parts.slice(1).join('.'), value)\n const putRequest = store.put(updatedValue, rootKey)\n putRequest.onerror = () => reject(putRequest.error)\n putRequest.onsuccess = () => resolve()\n }\n })\n }\n\n // Для простого ключа\n await this.putValueInStore(store, parts[0], value)\n }\n\n private async putValueInStore(store: IDBObjectStore, key: StorageKeyType, value: any): Promise<void> {\n return new Promise((resolve, reject) => {\n const request = store.put(value, key.valueOf())\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve()\n })\n }\n\n protected async doUpdate(updates: Array<{ key: string | StorageKey; value: any }>): Promise<void> {\n // Группируем обновления\n const updatesByRoot = new Map<string, Array<{ path: string[]; value: any }>>()\n const rawUpdates: Array<{ key: string; value: any }> = []\n\n // Разделяем обновления на \"сырые\" и обычные\n for (const { key, value } of updates) {\n if (key instanceof StorageKey && key.isUnparseable()) {\n rawUpdates.push({ key: key.valueOf(), value })\n continue\n }\n\n const parts = parsePath(key)\n const rootKey = parts[0]\n const path = parts.slice(1)\n\n if (!updatesByRoot.has(rootKey)) {\n updatesByRoot.set(rootKey, [])\n }\n updatesByRoot.get(rootKey)!.push({ path, value })\n }\n\n // Одна транзакция на весь doUpdate — атомарность\n const transaction = await this.getTransaction('readwrite')\n const store = transaction.objectStore(this.STORE_NAME)\n\n return new Promise<void>((resolve, reject) => {\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => {\n this.logger?.error('Error during update:', { error: transaction.error })\n reject(transaction.error)\n }\n transaction.onabort = () => {\n this.logger?.error('Update transaction aborted:', { error: transaction.error })\n reject(transaction.error || new Error('Transaction aborted'))\n }\n\n // Обрабатываем \"сырые\" обновления\n for (const { key, value } of rawUpdates) {\n store.put(value, key)\n }\n\n // Обрабатываем сгруппированные обновления\n // Для каждого rootKey: читаем текущее значение, применяем все path-обновления, записываем обратно\n const rootKeys = Array.from(updatesByRoot.keys())\n\n if (rootKeys.length === 0) {\n // Нет сгруппированных обновлений — транзакция завершится сама\n return\n }\n\n for (const rootKey of rootKeys) {\n const getRequest = store.get(rootKey)\n\n getRequest.onsuccess = () => {\n const rootValue = getRequest.result || {}\n let updatedValue = { ...rootValue }\n const pathUpdates = updatesByRoot.get(rootKey)!\n\n for (const { path, value } of pathUpdates) {\n if (path.length === 0) {\n updatedValue = value\n } else {\n updatedValue = setValueByPath(updatedValue, path.join('.'), value)\n }\n }\n\n store.put(updatedValue, rootKey)\n }\n }\n })\n }\n\n protected async doDelete(key: StorageKeyType): Promise<boolean> {\n const store = await this.getObjectStore('readwrite')\n\n // Для \"сырого\" ключа\n if (key instanceof StorageKey && key.isUnparseable()) {\n return new Promise((resolve, reject) => {\n const request = store.delete(key.valueOf())\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(true)\n })\n }\n\n const parts = parsePath(key)\n\n // Для простого ключа\n if (parts.length === 1) {\n return new Promise((resolve, reject) => {\n const request = store.delete(parts[0])\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(true)\n })\n }\n\n // Для вложенного пути\n const rootKey = parts[0]\n return new Promise((resolve, reject) => {\n const getRequest = store.get(rootKey)\n getRequest.onerror = () => reject(getRequest.error)\n getRequest.onsuccess = () => {\n const rootValue = getRequest.result\n if (!rootValue) {\n resolve(false)\n return\n }\n\n const parent = getValueByPath(rootValue, parts.slice(0, -1).join('.'))\n const lastKey = parts[parts.length - 1]\n\n if (!parent || !(lastKey in parent)) {\n resolve(false)\n return\n }\n\n if (Array.isArray(parent)) {\n const index = parseInt(lastKey, 10)\n if (!isNaN(index)) {\n parent.splice(index, 1)\n } else {\n // @ts-ignore\n delete parent[lastKey]\n }\n } else {\n delete parent[lastKey]\n }\n\n const putRequest = store.put(rootValue, rootKey)\n putRequest.onerror = () => reject(putRequest.error)\n putRequest.onsuccess = () => resolve(true)\n }\n })\n }\n\n protected async doClear(): Promise<void> {\n const store = await this.getObjectStore('readwrite')\n return new Promise((resolve, reject) => {\n const request = store.clear()\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n }\n\n protected async doKeys(): Promise<string[]> {\n const store = await this.getObjectStore()\n const request = store.getAllKeys()\n return new Promise((resolve, reject) => {\n request.onsuccess = () => {\n resolve(request.result as string[])\n }\n request.onerror = () => reject(request.error)\n })\n }\n\n protected async doHas(key: StorageKeyType): Promise<boolean> {\n const value = await this.doGet(key)\n return value !== undefined\n }\n\n /**\n * Override performCleanup: persistent storage should NOT clear data on destroy.\n * Only clean up middleware and runtime resources, not persisted data.\n */\n protected async performCleanup(): Promise<void> {\n await this.pluginExecutor?.executeOnClear()\n await this.doDestroy()\n }\n\n protected async doDestroy(): Promise<void> {\n // Persistent storage: do NOT clear data on destroy.\n // Only release runtime resources. Data persists across component lifecycles.\n }\n}\n"],"names":["SingletonMixin","StorageKey","AsyncBaseStorage","getValueByPath","parsePath","setValueByPath","IndexedDBManager","Map","Set","dbName","dbVersion","logger","instance","currentVersion","Promise","resolve","request","indexedDB","version","storeName","Array","newDb","newStores","reject","Error","i","event","db","storeNames","missingStores","name","IndexedDBStorage","config","pluginExecutor","eventEmitter","finalConfig","error","configs","dbManager","Object","result","key","storage","mode","fn","store","tx","IDBRequest","transaction","allValues","allKeys","state","acc","k","index","parts","rootKey","rootValue","undefined","value","clearRequest","entries","entryKey","entryValue","updatedValue","putRequest","updates","updatesByRoot","rawUpdates","path","rootKeys","getRequest","pathUpdates","parent","lastKey","parseInt","isNaN"],"mappings":"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"}
1
+ {"version":3,"file":"core/storage/adapters/indexed-DB.service.js","sources":["../../../../src/core/storage/adapters/indexed-DB.service.ts"],"sourcesContent":["import { IAsyncPluginExecutor } from '../modules/plugin/plugin.interface'\nimport { SingletonMixin } from '../modules/singleton/mixin.util'\nimport { ConfigureAsyncMiddlewares, IEventEmitter, ILogger, IndexedDBStorageConfig, StorageType } from '../storage.interface'\nimport { StorageKey, StorageKeyType } from '../utils/storage-key'\nimport { AsyncBaseStorage } from './async-base-storage.service'\nimport { getValueByPath, parsePath, setValueByPath } from './path.utils'\n\nexport interface IndexedDBConfig {\n dbName?: string\n}\n\n// Управляет соединением с базой данных\nexport class IndexedDBManager {\n private static instances = new Map<string, IndexedDBManager>()\n private db: IDBDatabase | null = null\n private initPromise: Promise<IDBDatabase> | null = null\n private storeNames: Set<string> = new Set()\n private dbVersion: number\n // Последовательная очередь операций изменения схемы (создание сторов).\n // На одну БД (singleton по dbName) обычно инициализируется сразу несколько\n // ApiClient-ов (comments, posts, reactions, ...). Без сериализации их\n // ensureStoreExists перекрывались: один обнулял this.db между close() и\n // переоткрытием, второй в этот момент читал this.db.objectStoreNames → null.\n private opQueue: Promise<unknown> = Promise.resolve()\n\n private constructor(\n private readonly dbName: string,\n dbVersion: number,\n private readonly logger?: ILogger,\n ) {\n this.dbVersion = dbVersion\n }\n\n static getInstance(dbName: string, dbVersion: number = 1, logger?: ILogger): IndexedDBManager {\n if (!IndexedDBManager.instances.has(dbName)) {\n IndexedDBManager.instances.set(dbName, new IndexedDBManager(dbName, dbVersion, logger))\n }\n\n const instance = IndexedDBManager.instances.get(dbName)!\n\n // Update version if higher version is requested\n if (dbVersion > instance.dbVersion) {\n instance.dbVersion = dbVersion\n }\n\n return instance\n }\n\n async initialize(): Promise<IDBDatabase> {\n if (this.db) {\n return this.db\n }\n\n if (!this.initPromise) {\n this.initPromise = this.autoDetectAndOpen()\n }\n\n return this.initPromise\n }\n\n private async autoDetectAndOpen(): Promise<IDBDatabase> {\n // Auto-detect the current DB version to avoid VersionError\n const currentVersion = await this.detectCurrentVersion()\n if (currentVersion > this.dbVersion) {\n this.logger?.debug(`Auto-detected higher DB version: ${currentVersion} (requested: ${this.dbVersion})`)\n this.dbVersion = currentVersion\n }\n return this.openDatabase()\n }\n\n private detectCurrentVersion(): Promise<number> {\n return new Promise<number>((resolve) => {\n try {\n const request = indexedDB.open(this.dbName)\n request.onsuccess = () => {\n const version = request.result.version\n request.result.close()\n resolve(version)\n }\n request.onerror = () => resolve(0)\n } catch {\n resolve(0)\n }\n })\n }\n\n async ensureStoreExists(storeName: string): Promise<IDBDatabase> {\n return this.enqueue(() => this.ensureStoresInternal([storeName]))\n }\n\n /**\n * Ставит операцию изменения схемы в последовательную очередь, чтобы создания\n * сторов на одной БД не перекрывались между await-точками. Очередь не должна\n * \"застревать\" из-за упавшей операции — ошибку прокидываем вызывающему, но\n * следующий элемент очереди стартует независимо от результата предыдущего.\n */\n private enqueue<R>(operation: () => Promise<R>): Promise<R> {\n const result = this.opQueue.then(operation, operation)\n this.opQueue = result.then(\n () => undefined,\n () => undefined,\n )\n return result\n }\n\n /**\n * Идемпотентно гарантирует наличие переданных сторов. Никогда не обращается к\n * this.db после await — работает с локальной ссылкой, возвращённой initialize()\n * / openDatabase(), поэтому параллельное обнуление this.db (другим клиентом\n * или onversionchange) не приводит к чтению свойств у null.\n */\n private async ensureStoresInternal(storeNames: string[]): Promise<IDBDatabase> {\n let db = await this.initialize()\n\n const missingStores = storeNames.filter((name) => !db.objectStoreNames.contains(name))\n if (missingStores.length === 0) {\n for (const name of storeNames) this.storeNames.add(name)\n return db\n }\n\n this.logger?.debug(`Создание недостающих хранилищ: ${missingStores.join(', ')}`, {\n dbName: this.dbName,\n currentStores: Array.from(db.objectStoreNames),\n })\n\n db.close()\n this.db = null\n\n this.dbVersion++\n this.initPromise = this.openDatabase(missingStores)\n db = await this.initPromise\n\n for (const name of storeNames) this.storeNames.add(name)\n return db\n }\n\n private async openDatabase(newStores: string[] = []): Promise<IDBDatabase> {\n return new Promise<IDBDatabase>((resolve, reject) => {\n this.logger?.debug(`Opening database \"${this.dbName}\" with version ${this.dbVersion}`)\n\n const request = indexedDB.open(this.dbName, this.dbVersion)\n\n request.onerror = () => {\n this.logger?.error(`Failed to open database \"${this.dbName}\"`, { error: request.error })\n reject(request.error)\n }\n\n request.onblocked = () => {\n this.logger?.warn(`Database \"${this.dbName}\" upgrade blocked by another connection. Close other tabs or connections.`)\n reject(new Error(`Database \"${this.dbName}\" upgrade blocked — close other tabs using this database`))\n }\n\n request.onsuccess = () => {\n this.db = request.result\n\n // Auto-close when another connection requests a version upgrade\n this.db.onversionchange = () => {\n this.db?.close()\n this.db = null\n this.initPromise = null\n }\n\n // Add existing stores to our set\n for (let i = 0; i < this.db.objectStoreNames.length; i++) {\n this.storeNames.add(this.db.objectStoreNames[i])\n }\n\n this.logger?.debug(`Database \"${this.dbName}\" opened successfully`, {\n version: this.db.version,\n stores: Array.from(this.db.objectStoreNames),\n })\n\n resolve(this.db)\n }\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n this.logger?.debug(`Upgrading database \"${this.dbName}\" to version ${this.dbVersion}`)\n\n // Create new stores that don't exist yet\n for (const storeName of newStores) {\n if (!db.objectStoreNames.contains(storeName)) {\n this.logger?.debug(`Creating store \"${storeName}\"`)\n db.createObjectStore(storeName)\n }\n }\n }\n })\n }\n\n closeDatabase(): void {\n if (this.db) {\n this.db.close()\n this.db = null\n this.initPromise = null\n }\n }\n\n async deleteDatabase(): Promise<void> {\n this.closeDatabase()\n\n return new Promise<void>((resolve, reject) => {\n const request = indexedDB.deleteDatabase(this.dbName)\n request.onsuccess = () => {\n this.logger?.debug(`Database \"${this.dbName}\" deleted successfully`)\n IndexedDBManager.instances.delete(this.dbName)\n this.storeNames.clear()\n resolve()\n }\n request.onerror = () => {\n this.logger?.error(`Failed to delete database \"${this.dbName}\"`, { error: request.error })\n reject(request.error)\n }\n })\n }\n async ensureStoresExist(storeNames: string[]): Promise<IDBDatabase> {\n return this.enqueue(() => this.ensureStoresInternal(storeNames))\n }\n\n // Метод для получения текущей версии\n getCurrentVersion(): number {\n return this.dbVersion\n }\n}\n\nexport class IndexedDBStorage<T extends Record<string, any>> extends AsyncBaseStorage<T> {\n protected static readonly STORAGE_TYPE: StorageType = 'indexedDB'\n readonly type: StorageType = 'indexedDB'\n\n private readonly DB_NAME: string\n private readonly STORE_NAME: string\n private dbManager: IndexedDBManager\n\n constructor(config: IndexedDBStorageConfig<T>, pluginExecutor?: IAsyncPluginExecutor, eventEmitter?: IEventEmitter, logger?: ILogger) {\n super(config, pluginExecutor, eventEmitter, logger)\n\n this.DB_NAME = config.options?.dbName || 'app_storage'\n this.STORE_NAME = config.name\n\n // Get database manager instance (version is auto-detected internally)\n this.dbManager = IndexedDBManager.getInstance(this.DB_NAME, 1, logger)\n }\n\n static create<T extends Record<string, any>>(\n config: IndexedDBStorageConfig,\n pluginExecutor?: IAsyncPluginExecutor,\n eventEmitter?: IEventEmitter,\n logger?: ILogger,\n ): IndexedDBStorage<T> {\n return SingletonMixin.handleSingletonCreation(\n config,\n this.STORAGE_TYPE,\n (finalConfig) => new IndexedDBStorage<T>(finalConfig as IndexedDBStorageConfig<T>, pluginExecutor, eventEmitter, logger),\n logger,\n )\n }\n\n protected async doInitialize(): Promise<this> {\n try {\n this.logger?.debug(`Initializing IndexedDB storage \"${this.STORE_NAME}\"`)\n\n // Создаем store в базе данных\n await this.dbManager.ensureStoreExists(this.STORE_NAME)\n\n // Проверяем, что хранилище доступно\n const db = await this.dbManager.initialize()\n if (!db.objectStoreNames.contains(this.STORE_NAME)) {\n throw new Error(`Store \"${this.STORE_NAME}\" not found after initialization`)\n }\n\n // Инициализируем middleware\n this.initializeMiddlewares()\n\n // Инициализируем с middleware\n await this.initializeWithMiddlewares()\n\n this.logger?.debug(`IndexedDB storage \"${this.STORE_NAME}\" initialized successfully`)\n return this\n } catch (error) {\n this.logger?.error(`Ошибка инициализации IndexedDB \"${this.name}\"`, { error })\n throw error\n }\n }\n\n static async createStorages<S extends Record<string, any>>(\n dbName: string,\n configs: {\n [K in keyof S]: {\n name: string\n initialState?: S[K]\n middlewares?: ConfigureAsyncMiddlewares\n pluginExecutor?: IAsyncPluginExecutor\n eventEmitter?: IEventEmitter\n }\n },\n logger?: ILogger,\n ): Promise<{ [K in keyof S]: IndexedDBStorage<S[K]> }> {\n // Используем единый IndexedDBManager (версия определяется автоматически)\n const dbManager = IndexedDBManager.getInstance(dbName, 1, logger)\n\n // Получаем имена всех хранилищ, которые нужно создать\n const storeNames = Object.values(configs).map((config) => config.name)\n\n // Предварительно создаем все хранилища в рамках одной операции\n await dbManager.ensureStoresExist(storeNames)\n\n // Создаем и инициализируем все хранилища\n const result: Record<string, IndexedDBStorage<any>> = {}\n\n for (const [key, config] of Object.entries(configs)) {\n const storage = new IndexedDBStorage(\n {\n ...config,\n options: { dbName },\n },\n config.pluginExecutor,\n config.eventEmitter,\n logger,\n )\n\n // Инициализируем хранилище\n result[key] = await storage.initialize()\n }\n\n return result as { [K in keyof S]: IndexedDBStorage<S[K]> }\n }\n\n // ─── IndexedDB-specific API ────────────────────────────────────────────────\n\n /**\n * Выполняет операцию в рамках IDB-транзакции.\n * Обёртка для низкоуровневого IDB transaction API.\n *\n * @param mode - Режим транзакции ('readonly' | 'readwrite')\n * @param fn - Callback, получающий IDBObjectStore. Возвращает результат операции.\n * @returns Promise с результатом callback-а\n */\n async transaction<R>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => IDBRequest<R> | R): Promise<R> {\n const store = await this.getObjectStore(mode)\n\n return new Promise<R>((resolve, reject) => {\n const tx = store.transaction\n\n tx.onerror = () => reject(tx.error)\n tx.onabort = () => reject(tx.error || new Error('Transaction aborted'))\n\n try {\n const result = fn(store)\n\n if (result instanceof IDBRequest) {\n result.onsuccess = () => resolve(result.result)\n result.onerror = () => reject(result.error)\n } else {\n // Sync result — resolve when transaction completes\n tx.oncomplete = () => resolve(result)\n }\n } catch (error) {\n reject(error)\n }\n })\n }\n\n /**\n * Текущая версия базы данных.\n */\n get dbVersion(): number {\n return this.dbManager.getCurrentVersion()\n }\n\n /**\n * Имя базы данных.\n */\n get dbName(): string {\n return this.DB_NAME\n }\n\n /**\n * Имя object store в IndexedDB.\n */\n get storeName(): string {\n return this.STORE_NAME\n }\n\n // ─── Private helpers ───────────────────────────────────────────────────────\n\n private async getTransaction(mode: IDBTransactionMode = 'readonly'): Promise<IDBTransaction> {\n try {\n // Ensure database is open and our store exists\n const db = await this.dbManager.ensureStoreExists(this.STORE_NAME)\n\n // Проверяем существование хранилища перед созданием транзакции\n if (!db.objectStoreNames.contains(this.STORE_NAME)) {\n // Попытка исправить проблему - закрываем и снова открываем\n this.logger?.warn(`Object store \"${this.STORE_NAME}\" not found, attempting to repair`)\n\n db.close()\n this.dbManager.closeDatabase()\n\n // Пробуем заново создать хранилище с инкрементом версии\n const newDb = await this.dbManager.ensureStoreExists(this.STORE_NAME)\n\n if (!newDb.objectStoreNames.contains(this.STORE_NAME)) {\n throw new Error(`Object store \"${this.STORE_NAME}\" still doesn't exist after repair attempt`)\n }\n\n return newDb.transaction(this.STORE_NAME, mode)\n }\n\n return db.transaction(this.STORE_NAME, mode)\n } catch (error) {\n this.logger?.error(`Failed to create transaction for store \"${this.STORE_NAME}\"`, { error })\n throw error\n }\n }\n\n private async getObjectStore(mode: IDBTransactionMode = 'readonly'): Promise<IDBObjectStore> {\n const transaction = await this.getTransaction(mode)\n return transaction.objectStore(this.STORE_NAME)\n }\n\n protected async doGet(key: StorageKeyType): Promise<any> {\n const store = await this.getObjectStore()\n\n // Для пустого ключа возвращаем все состояние\n if (key === '') {\n return new Promise((resolve, reject) => {\n const request = store.getAll()\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n const allValues = request.result\n const allKeys = store.getAllKeys()\n\n allKeys.onsuccess = () => {\n const state = allKeys.result.reduce(\n (acc, k, index) => {\n if (k !== 'root') {\n acc[k as string] = allValues[index]\n }\n return acc\n },\n {} as Record<string, any>,\n )\n resolve(state)\n }\n allKeys.onerror = () => reject(allKeys.error)\n }\n })\n }\n\n // Проверяем, является ли ключ \"сырым\"\n if (key instanceof StorageKey && key.isUnparseable()) {\n return new Promise((resolve, reject) => {\n const request = store.get(key.valueOf())\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result)\n })\n }\n\n // Для вложенного пути\n const parts = parsePath(key)\n if (parts.length > 1) {\n const rootKey = parts[0]\n return new Promise((resolve, reject) => {\n const request = store.get(rootKey)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n const rootValue = request.result\n if (!rootValue) {\n resolve(undefined)\n return\n }\n const value = getValueByPath(rootValue, parts.slice(1).join('.'))\n resolve(value)\n }\n })\n }\n\n // Для простого ключа\n return new Promise((resolve, reject) => {\n const request = store.get(parts[0])\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result)\n })\n }\n\n protected async doSet(key: StorageKeyType, value: any): Promise<void> {\n // Для пустого ключа устанавливаем все состояние\n if (key === '') {\n const store = await this.getObjectStore('readwrite')\n return new Promise((resolve, reject) => {\n const tx = store.transaction\n\n tx.oncomplete = () => {\n resolve()\n }\n\n tx.onerror = () => {\n reject(tx.error)\n }\n\n const clearRequest = store.clear()\n\n clearRequest.onsuccess = () => {\n const entries = Object.entries(value)\n for (const [entryKey, entryValue] of entries) {\n store.put(entryValue, entryKey)\n }\n }\n\n clearRequest.onerror = () => {\n reject(clearRequest.error)\n }\n })\n }\n\n const store = await this.getObjectStore('readwrite')\n\n // Для \"сырого\" ключа\n if (key instanceof StorageKey && key.isUnparseable()) {\n await this.putValueInStore(store, key.valueOf(), value)\n return\n }\n\n // Для вложенного пути\n const parts = parsePath(key)\n if (parts.length > 1) {\n const rootKey = parts[0]\n return new Promise((resolve, reject) => {\n const request = store.get(rootKey)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n const rootValue = request.result || {}\n const updatedValue = setValueByPath(rootValue, parts.slice(1).join('.'), value)\n const putRequest = store.put(updatedValue, rootKey)\n putRequest.onerror = () => reject(putRequest.error)\n putRequest.onsuccess = () => resolve()\n }\n })\n }\n\n // Для простого ключа\n await this.putValueInStore(store, parts[0], value)\n }\n\n private async putValueInStore(store: IDBObjectStore, key: StorageKeyType, value: any): Promise<void> {\n return new Promise((resolve, reject) => {\n const request = store.put(value, key.valueOf())\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve()\n })\n }\n\n protected async doUpdate(updates: Array<{ key: string | StorageKey; value: any }>): Promise<void> {\n // Группируем обновления\n const updatesByRoot = new Map<string, Array<{ path: string[]; value: any }>>()\n const rawUpdates: Array<{ key: string; value: any }> = []\n\n // Разделяем обновления на \"сырые\" и обычные\n for (const { key, value } of updates) {\n if (key instanceof StorageKey && key.isUnparseable()) {\n rawUpdates.push({ key: key.valueOf(), value })\n continue\n }\n\n const parts = parsePath(key)\n const rootKey = parts[0]\n const path = parts.slice(1)\n\n if (!updatesByRoot.has(rootKey)) {\n updatesByRoot.set(rootKey, [])\n }\n updatesByRoot.get(rootKey)!.push({ path, value })\n }\n\n // Одна транзакция на весь doUpdate — атомарность\n const transaction = await this.getTransaction('readwrite')\n const store = transaction.objectStore(this.STORE_NAME)\n\n return new Promise<void>((resolve, reject) => {\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => {\n this.logger?.error('Error during update:', { error: transaction.error })\n reject(transaction.error)\n }\n transaction.onabort = () => {\n this.logger?.error('Update transaction aborted:', { error: transaction.error })\n reject(transaction.error || new Error('Transaction aborted'))\n }\n\n // Обрабатываем \"сырые\" обновления\n for (const { key, value } of rawUpdates) {\n store.put(value, key)\n }\n\n // Обрабатываем сгруппированные обновления\n // Для каждого rootKey: читаем текущее значение, применяем все path-обновления, записываем обратно\n const rootKeys = Array.from(updatesByRoot.keys())\n\n if (rootKeys.length === 0) {\n // Нет сгруппированных обновлений — транзакция завершится сама\n return\n }\n\n for (const rootKey of rootKeys) {\n const getRequest = store.get(rootKey)\n\n getRequest.onsuccess = () => {\n const rootValue = getRequest.result || {}\n let updatedValue = { ...rootValue }\n const pathUpdates = updatesByRoot.get(rootKey)!\n\n for (const { path, value } of pathUpdates) {\n if (path.length === 0) {\n updatedValue = value\n } else {\n updatedValue = setValueByPath(updatedValue, path.join('.'), value)\n }\n }\n\n store.put(updatedValue, rootKey)\n }\n }\n })\n }\n\n protected async doDelete(key: StorageKeyType): Promise<boolean> {\n const store = await this.getObjectStore('readwrite')\n\n // Для \"сырого\" ключа\n if (key instanceof StorageKey && key.isUnparseable()) {\n return new Promise((resolve, reject) => {\n const request = store.delete(key.valueOf())\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(true)\n })\n }\n\n const parts = parsePath(key)\n\n // Для простого ключа\n if (parts.length === 1) {\n return new Promise((resolve, reject) => {\n const request = store.delete(parts[0])\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(true)\n })\n }\n\n // Для вложенного пути\n const rootKey = parts[0]\n return new Promise((resolve, reject) => {\n const getRequest = store.get(rootKey)\n getRequest.onerror = () => reject(getRequest.error)\n getRequest.onsuccess = () => {\n const rootValue = getRequest.result\n if (!rootValue) {\n resolve(false)\n return\n }\n\n const parent = getValueByPath(rootValue, parts.slice(0, -1).join('.'))\n const lastKey = parts[parts.length - 1]\n\n if (!parent || !(lastKey in parent)) {\n resolve(false)\n return\n }\n\n if (Array.isArray(parent)) {\n const index = parseInt(lastKey, 10)\n if (!isNaN(index)) {\n parent.splice(index, 1)\n } else {\n // @ts-ignore\n delete parent[lastKey]\n }\n } else {\n delete parent[lastKey]\n }\n\n const putRequest = store.put(rootValue, rootKey)\n putRequest.onerror = () => reject(putRequest.error)\n putRequest.onsuccess = () => resolve(true)\n }\n })\n }\n\n protected async doClear(): Promise<void> {\n const store = await this.getObjectStore('readwrite')\n return new Promise((resolve, reject) => {\n const request = store.clear()\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n }\n\n protected async doKeys(): Promise<string[]> {\n const store = await this.getObjectStore()\n const request = store.getAllKeys()\n return new Promise((resolve, reject) => {\n request.onsuccess = () => {\n resolve(request.result as string[])\n }\n request.onerror = () => reject(request.error)\n })\n }\n\n protected async doHas(key: StorageKeyType): Promise<boolean> {\n const value = await this.doGet(key)\n return value !== undefined\n }\n\n /**\n * Override performCleanup: persistent storage should NOT clear data on destroy.\n * Only clean up middleware and runtime resources, not persisted data.\n */\n protected async performCleanup(): Promise<void> {\n await this.pluginExecutor?.executeOnClear()\n await this.doDestroy()\n }\n\n protected async doDestroy(): Promise<void> {\n // Persistent storage: do NOT clear data on destroy.\n // Only release runtime resources. Data persists across component lifecycles.\n }\n}\n"],"names":["SingletonMixin","StorageKey","AsyncBaseStorage","getValueByPath","parsePath","setValueByPath","IndexedDBManager","Map","Set","Promise","dbName","dbVersion","logger","instance","currentVersion","resolve","request","indexedDB","version","storeName","operation","result","undefined","storeNames","db","missingStores","name","Array","newStores","reject","Error","i","event","IndexedDBStorage","config","pluginExecutor","eventEmitter","finalConfig","error","configs","dbManager","Object","key","storage","mode","fn","store","tx","IDBRequest","newDb","transaction","allValues","allKeys","state","acc","k","index","parts","rootKey","rootValue","value","clearRequest","entries","entryKey","entryValue","updatedValue","putRequest","updates","updatesByRoot","rawUpdates","path","rootKeys","getRequest","pathUpdates","parent","lastKey","parseInt","isNaN"],"mappings":"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"}
@@ -17,5 +17,20 @@ export declare function useSelector<T>(selector: SelectorAPI<T>, options: UseSel
17
17
  data: T;
18
18
  isLoading: boolean;
19
19
  };
20
+ export declare function useSelector<T>(selector: SelectorAPI<T>, options: UseSelectorOptions<T> & {
21
+ withLoading?: false;
22
+ }): T;
23
+ /**
24
+ * Изоляция ре-рендеров для keyed-map стора (паттерн «equals»). Подписка идёт на
25
+ * ВЕСЬ map (`selector`), но `equals` сравнивает только срез по `key` → компонент
26
+ * ре-рендерится лишь когда меняется его `map[key]` по ссылке. Работает, т.к.
27
+ * иммутабельные мутации по одному ключу не трогают ссылку чужих срезов.
28
+ * Гранулярность живёт в месте вызова, а селекторы остаются плоскими (без фабрик
29
+ * с `Map<key, selector>`).
30
+ *
31
+ * `fallback` ОБЯЗАН быть стабильной ссылкой (module-level константа), иначе
32
+ * `?? fallback` будет давать новый объект каждый тик и рвать нижестоящие `useMemo`.
33
+ */
34
+ export declare function useKeyedSliceSelector<V>(selector: SelectorAPI<Record<string, V>>, key: string, fallback: V): V;
20
35
  export {};
21
36
  //# sourceMappingURL=useSelector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSelector.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useSelector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,UAAU,kBAAkB,CAAC,CAAC;IAC5B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;IAChC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAC3D,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAA"}
1
+ {"version":3,"file":"useSelector.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useSelector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,UAAU,kBAAkB,CAAC,CAAC;IAC5B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;IAChC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAC3D,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAA;AACjJ,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG;IAAE,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,CAAC,CAAA;AAwDrH;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAK9G"}
@@ -50,7 +50,23 @@ function useSelector(selector, options) {
50
50
  }
51
51
  return value;
52
52
  }
53
+ /**
54
+ * Изоляция ре-рендеров для keyed-map стора (паттерн «equals»). Подписка идёт на
55
+ * ВЕСЬ map (`selector`), но `equals` сравнивает только срез по `key` → компонент
56
+ * ре-рендерится лишь когда меняется его `map[key]` по ссылке. Работает, т.к.
57
+ * иммутабельные мутации по одному ключу не трогают ссылку чужих срезов.
58
+ * Гранулярность живёт в месте вызова, а селекторы остаются плоскими (без фабрик
59
+ * с `Map<key, selector>`).
60
+ *
61
+ * `fallback` ОБЯЗАН быть стабильной ссылкой (module-level константа), иначе
62
+ * `?? fallback` будет давать новый объект каждый тик и рвать нижестоящие `useMemo`.
63
+ */ function useKeyedSliceSelector(selector, key, fallback) {
64
+ const map = useSelector(selector, {
65
+ equals: (a, b)=>a[key] === b[key]
66
+ });
67
+ return map[key] ?? fallback;
68
+ }
53
69
 
54
- export { useSelector };
70
+ export { useKeyedSliceSelector, useSelector };
55
71
 
56
72
  //# sourceMappingURL=useSelector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"react/hooks/useSelector.js","sources":["../../../src/react/hooks/useSelector.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from 'react'\n\nimport type { SelectorAPI } from '../../core'\n\ninterface UseSelectorOptions<T> {\n /** Функция сравнения для предотвращения лишних ререндеров */\n equals?: (a: T, b: T) => boolean\n /** Включать ли статус загрузки в возвращаемый результат */\n withLoading?: boolean\n}\n\n/**\n * Хук для использования селекторов в компонентах React.\n * Использует useSyncExternalStore для корректной работы в Concurrent Mode.\n * Подписывается напрямую через selector.subscribe() — без глобального реестра.\n */\nexport function useSelector<T>(selector: SelectorAPI<T>): T\nexport function useSelector<T>(selector: SelectorAPI<T>, options: UseSelectorOptions<T> & { withLoading: true }): { data: T; isLoading: boolean }\nexport function useSelector<T>(selector: SelectorAPI<T>, options?: UseSelectorOptions<T>): { data: T; isLoading: boolean } | T {\n const equalsRef = useRef(options?.equals)\n\n // Кеш для мемоизации результата getSnapshot (предотвращает лишние ререндеры)\n const cachedRef = useRef<T | undefined>(undefined)\n\n const subscribe = useCallback(\n (onStoreChange: VoidFunction) => {\n return selector.subscribe({\n notify: () => {\n onStoreChange()\n },\n })\n },\n [selector],\n )\n\n const getSnapshot = useCallback((): T => {\n const value = selector.selectSync()\n\n // Если есть пользовательская функция сравнения — мемоизируем\n const equals = equalsRef.current\n if (equals && cachedRef.current !== undefined && equals(cachedRef.current, value)) {\n return cachedRef.current\n }\n\n cachedRef.current = value\n return value\n }, [selector])\n\n const value = useSyncExternalStore(subscribe, getSnapshot)\n\n // Подписка на статус готовности storage (используется только при withLoading)\n const subscribeToStatus = useCallback(\n (onStoreChange: VoidFunction) => {\n return selector.onSourceStatusChange(() => {\n onStoreChange()\n })\n },\n [selector],\n )\n\n const getStatusSnapshot = useCallback((): boolean => {\n return !selector.isSourceReady()\n }, [selector])\n\n const isLoading = useSyncExternalStore(subscribeToStatus, getStatusSnapshot)\n\n if (options?.withLoading) {\n return { data: value, isLoading }\n }\n\n return value\n}\n"],"names":["useCallback","useRef","useSyncExternalStore","useSelector","selector","options","equalsRef","cachedRef","undefined","subscribe","onStoreChange","getSnapshot","value","equals","subscribeToStatus","getStatusSnapshot","isLoading"],"mappings":";;;AAAiE;AAkB1D,SAASG,WAAWA,CAAIC,QAAwB,EAAEC,OAA+B;IACtF,MAAMC,YAAYL,MAAMA,CAACI,SAAS;IAElC,6EAA6E;IAC7E,MAAME,YAAYN,MAAMA,CAAgBO;IAExC,MAAMC,YAAYT,WAAWA,CAC3B,CAACU;QACC,OAAON,SAAS,SAAS,CAAC;YACxB,QAAQ;gBACNM;YACF;QACF;IACF,GACA;QAACN;KAAS;IAGZ,MAAMO,cAAcX,WAAWA,CAAC;QAC9B,MAAMY,QAAQR,SAAS,UAAU;QAEjC,6DAA6D;QAC7D,MAAMS,SAASP,UAAU,OAAO;QAChC,IAAIO,UAAUN,UAAU,OAAO,KAAKC,aAAaK,OAAON,UAAU,OAAO,EAAEK,QAAQ;YACjF,OAAOL,UAAU,OAAO;QAC1B;QAEAA,UAAU,OAAO,GAAGK;QACpB,OAAOA;IACT,GAAG;QAACR;KAAS;IAEb,MAAMQ,QAAQV,oBAAoBA,CAACO,WAAWE;IAE9C,8EAA8E;IAC9E,MAAMG,oBAAoBd,WAAWA,CACnC,CAACU;QACC,OAAON,SAAS,oBAAoB,CAAC;YACnCM;QACF;IACF,GACA;QAACN;KAAS;IAGZ,MAAMW,oBAAoBf,WAAWA,CAAC;QACpC,OAAO,CAACI,SAAS,aAAa;IAChC,GAAG;QAACA;KAAS;IAEb,MAAMY,YAAYd,oBAAoBA,CAACY,mBAAmBC;IAE1D,IAAIV,SAAS,aAAa;QACxB,OAAO;YAAE,MAAMO;YAAOI;QAAU;IAClC;IAEA,OAAOJ;AACT"}
1
+ {"version":3,"file":"react/hooks/useSelector.js","sources":["../../../src/react/hooks/useSelector.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from 'react'\n\nimport type { SelectorAPI } from '../../core'\n\ninterface UseSelectorOptions<T> {\n /** Функция сравнения для предотвращения лишних ререндеров */\n equals?: (a: T, b: T) => boolean\n /** Включать ли статус загрузки в возвращаемый результат */\n withLoading?: boolean\n}\n\n/**\n * Хук для использования селекторов в компонентах React.\n * Использует useSyncExternalStore для корректной работы в Concurrent Mode.\n * Подписывается напрямую через selector.subscribe() — без глобального реестра.\n */\nexport function useSelector<T>(selector: SelectorAPI<T>): T\nexport function useSelector<T>(selector: SelectorAPI<T>, options: UseSelectorOptions<T> & { withLoading: true }): { data: T; isLoading: boolean }\nexport function useSelector<T>(selector: SelectorAPI<T>, options: UseSelectorOptions<T> & { withLoading?: false }): T\nexport function useSelector<T>(selector: SelectorAPI<T>, options?: UseSelectorOptions<T>): { data: T; isLoading: boolean } | T {\n const equalsRef = useRef(options?.equals)\n\n // Кеш для мемоизации результата getSnapshot (предотвращает лишние ререндеры)\n const cachedRef = useRef<T | undefined>(undefined)\n\n const subscribe = useCallback(\n (onStoreChange: VoidFunction) => {\n return selector.subscribe({\n notify: () => {\n onStoreChange()\n },\n })\n },\n [selector],\n )\n\n const getSnapshot = useCallback((): T => {\n const value = selector.selectSync()\n\n // Если есть пользовательская функция сравнения — мемоизируем\n const equals = equalsRef.current\n if (equals && cachedRef.current !== undefined && equals(cachedRef.current, value)) {\n return cachedRef.current\n }\n\n cachedRef.current = value\n return value\n }, [selector])\n\n const value = useSyncExternalStore(subscribe, getSnapshot)\n\n // Подписка на статус готовности storage (используется только при withLoading)\n const subscribeToStatus = useCallback(\n (onStoreChange: VoidFunction) => {\n return selector.onSourceStatusChange(() => {\n onStoreChange()\n })\n },\n [selector],\n )\n\n const getStatusSnapshot = useCallback((): boolean => {\n return !selector.isSourceReady()\n }, [selector])\n\n const isLoading = useSyncExternalStore(subscribeToStatus, getStatusSnapshot)\n\n if (options?.withLoading) {\n return { data: value, isLoading }\n }\n\n return value\n}\n\n/**\n * Изоляция ре-рендеров для keyed-map стора (паттерн «equals»). Подписка идёт на\n * ВЕСЬ map (`selector`), но `equals` сравнивает только срез по `key` → компонент\n * ре-рендерится лишь когда меняется его `map[key]` по ссылке. Работает, т.к.\n * иммутабельные мутации по одному ключу не трогают ссылку чужих срезов.\n * Гранулярность живёт в месте вызова, а селекторы остаются плоскими (без фабрик\n * с `Map<key, selector>`).\n *\n * `fallback` ОБЯЗАН быть стабильной ссылкой (module-level константа), иначе\n * `?? fallback` будет давать новый объект каждый тик и рвать нижестоящие `useMemo`.\n */\nexport function useKeyedSliceSelector<V>(selector: SelectorAPI<Record<string, V>>, key: string, fallback: V): V {\n const map = useSelector(selector, {\n equals: (a, b) => a[key] === b[key],\n })\n return map[key] ?? fallback\n}\n"],"names":["useCallback","useRef","useSyncExternalStore","useSelector","selector","options","equalsRef","cachedRef","undefined","subscribe","onStoreChange","getSnapshot","value","equals","subscribeToStatus","getStatusSnapshot","isLoading","useKeyedSliceSelector","key","fallback","map","a","b"],"mappings":";;;AAAiE;AAmB1D,SAASG,WAAWA,CAAIC,QAAwB,EAAEC,OAA+B;IACtF,MAAMC,YAAYL,MAAMA,CAACI,SAAS;IAElC,6EAA6E;IAC7E,MAAME,YAAYN,MAAMA,CAAgBO;IAExC,MAAMC,YAAYT,WAAWA,CAC3B,CAACU;QACC,OAAON,SAAS,SAAS,CAAC;YACxB,QAAQ;gBACNM;YACF;QACF;IACF,GACA;QAACN;KAAS;IAGZ,MAAMO,cAAcX,WAAWA,CAAC;QAC9B,MAAMY,QAAQR,SAAS,UAAU;QAEjC,6DAA6D;QAC7D,MAAMS,SAASP,UAAU,OAAO;QAChC,IAAIO,UAAUN,UAAU,OAAO,KAAKC,aAAaK,OAAON,UAAU,OAAO,EAAEK,QAAQ;YACjF,OAAOL,UAAU,OAAO;QAC1B;QAEAA,UAAU,OAAO,GAAGK;QACpB,OAAOA;IACT,GAAG;QAACR;KAAS;IAEb,MAAMQ,QAAQV,oBAAoBA,CAACO,WAAWE;IAE9C,8EAA8E;IAC9E,MAAMG,oBAAoBd,WAAWA,CACnC,CAACU;QACC,OAAON,SAAS,oBAAoB,CAAC;YACnCM;QACF;IACF,GACA;QAACN;KAAS;IAGZ,MAAMW,oBAAoBf,WAAWA,CAAC;QACpC,OAAO,CAACI,SAAS,aAAa;IAChC,GAAG;QAACA;KAAS;IAEb,MAAMY,YAAYd,oBAAoBA,CAACY,mBAAmBC;IAE1D,IAAIV,SAAS,aAAa;QACxB,OAAO;YAAE,MAAMO;YAAOI;QAAU;IAClC;IAEA,OAAOJ;AACT;AAEA;;;;;;;;;;CAUC,GACM,SAASK,qBAAqBA,CAAIb,QAAwC,EAAEc,GAAW,EAAEC,QAAW;IACzG,MAAMC,MAAMjB,WAAWA,CAACC,UAAU;QAChC,QAAQ,CAACiB,GAAGC,IAAMD,CAAC,CAACH,IAAI,KAAKI,CAAC,CAACJ,IAAI;IACrC;IACA,OAAOE,GAAG,CAACF,IAAI,IAAIC;AACrB"}
@@ -1,3 +1,4 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1
2
  import { useEffect, useState } from "react";
2
3
  import { createSynapseAwaiter } from "../../utils/index.js";
3
4
 
@@ -5,11 +6,20 @@ import { createSynapseAwaiter } from "../../utils/index.js";
5
6
 
6
7
 
7
8
 
9
+
10
+
8
11
  /**
9
12
  * React-обертка для фреймворк-независимой утилиты ожидания Synapse
10
13
  * Добавляет React-специфичные методы поверх createSynapseAwaiter
11
14
  */ function awaitSynapse(synapseStorePromise, options) {
12
- const { loadingComponent = /*#__PURE__*/ React.createElement("div", null, "Инициализация..."), errorComponent = (error)=>/*#__PURE__*/ React.createElement("div", null, "Ошибка инициализации: ", error.message) } = options || {};
15
+ const { loadingComponent = /*#__PURE__*/ jsx("div", {
16
+ children: "Инициализация..."
17
+ }), errorComponent = (error)=>/*#__PURE__*/ jsxs("div", {
18
+ children: [
19
+ "Ошибка инициализации: ",
20
+ error.message
21
+ ]
22
+ }) } = options || {};
13
23
  const awaiter = createSynapseAwaiter(synapseStorePromise);
14
24
  /**
15
25
  * Хук для получения текущего состояния готовности
@@ -55,11 +65,17 @@ import { createSynapseAwaiter } from "../../utils/index.js";
55
65
  function WrappedComponent(props) {
56
66
  const { isReady, isError, error } = useSynapseReady();
57
67
  // Показываем ошибку
58
- if (isError && error) return /*#__PURE__*/ React.createElement(React.Fragment, null, errorComponent(error));
68
+ if (isError && error) return /*#__PURE__*/ jsx(Fragment, {
69
+ children: errorComponent(error)
70
+ });
59
71
  // Показываем загрузку
60
- if (!isReady) return /*#__PURE__*/ React.createElement(React.Fragment, null, loadingComponent);
72
+ if (!isReady) return /*#__PURE__*/ jsx(Fragment, {
73
+ children: loadingComponent
74
+ });
61
75
  // Рендерим компонент когда все готово
62
- return /*#__PURE__*/ React.createElement(Component, props);
76
+ return /*#__PURE__*/ jsx(Component, {
77
+ ...props
78
+ });
63
79
  }
64
80
  // Устанавливаем отображаемое имя для отладки
65
81
  const componentName = Component.displayName || Component.name || 'Component';
@@ -1 +1 @@
1
- {"version":3,"file":"react/utils/awaitSynapse.js","sources":["../../../src/react/utils/awaitSynapse.tsx"],"sourcesContent":["import { ComponentType, PropsWithChildren, ReactNode, useEffect, useState } from 'react'\n\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, createSynapseAwaiter } from '../../utils'\n\ninterface ReactAwaitSynapseOptions {\n loadingComponent?: ReactNode\n errorComponent?: (error: Error) => ReactNode\n}\n\n/**\n * React-обертка для фреймворк-независимой утилиты ожидания Synapse\n * Добавляет React-специфичные методы поверх createSynapseAwaiter\n */\nexport function awaitSynapse<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: ReactAwaitSynapseOptions,\n) {\n const { loadingComponent = <div>Инициализация...</div>, errorComponent = (error: Error) => <div>Ошибка инициализации: {error.message}</div> } = options || {}\n\n const awaiter = createSynapseAwaiter(synapseStorePromise)\n\n /**\n * Хук для получения текущего состояния готовности\n */\n function useSynapseReady() {\n const [status, setStatus] = useState<'pending' | 'ready' | 'error'>(() => awaiter.getStatus())\n const [store, setStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | undefined>(() => awaiter.getStoreIfReady())\n const [error, setError] = useState<Error | null>(() => awaiter.getError())\n\n useEffect(() => {\n // Проверяем текущее состояние при монтировании\n const currentStatus = awaiter.getStatus()\n const currentStore = awaiter.getStoreIfReady()\n const currentError = awaiter.getError()\n\n setStatus(currentStatus)\n setStore(currentStore)\n setError(currentError)\n\n // Подписываемся на изменения\n const unsubscribeReady = awaiter.onReady((readyStore) => {\n setStatus('ready')\n setStore(readyStore)\n setError(null)\n })\n\n const unsubscribeError = awaiter.onError((err) => {\n setStatus('error')\n setStore(undefined)\n setError(err)\n })\n\n return () => {\n unsubscribeReady()\n unsubscribeError()\n }\n }, [])\n\n return {\n isReady: status === 'ready',\n isError: status === 'error',\n isPending: status === 'pending',\n store,\n error,\n }\n }\n\n /**\n * Обертка, которая ждет готовности Synapse\n */\n function withSynapseReady<ComponentProps>(Component: ComponentType<ComponentProps>) {\n function WrappedComponent(props: ComponentProps) {\n const { isReady, isError, error } = useSynapseReady()\n\n // Показываем ошибку\n if (isError && error) return <>{errorComponent(error)}</>\n\n // Показываем загрузку\n if (!isReady) return <>{loadingComponent}</>\n\n // Рендерим компонент когда все готово\n return <Component {...(props as PropsWithChildren<ComponentProps>)} />\n }\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `AwaitSynapse(${componentName})`\n\n return WrappedComponent\n }\n\n return {\n // React методы\n withSynapseReady,\n useSynapseReady,\n\n // Проксируем все методы из awaiter (обёртки сохраняют контекст при деструктуризации)\n waitForReady: () => awaiter.waitForReady(),\n isReady: () => awaiter.isReady(),\n getStoreIfReady: () => awaiter.getStoreIfReady(),\n onReady: (cb: Parameters<typeof awaiter.onReady>[0]) => awaiter.onReady(cb),\n onError: (cb: Parameters<typeof awaiter.onError>[0]) => awaiter.onError(cb),\n getStatus: () => awaiter.getStatus(),\n getError: () => awaiter.getError(),\n destroy: () => awaiter.destroy(),\n }\n}\n"],"names":["useEffect","useState","createSynapseAwaiter","awaitSynapse","synapseStorePromise","options","loadingComponent","errorComponent","error","awaiter","useSynapseReady","status","setStatus","store","setStore","setError","currentStatus","currentStore","currentError","unsubscribeReady","readyStore","unsubscribeError","err","undefined","withSynapseReady","Component","WrappedComponent","props","isReady","isError","componentName","cb"],"mappings":";;;;;AAAwF;AAGrB;AAOnE;;;CAGC,GACM,SAASG,YAAYA,CAC1BC,mBAA+I,EAC/IC,OAAkC;IAElC,MAAM,EAAEC,iCAAmB,oBAAC,aAAI,mBAAsB,EAAEC,iBAAiB,CAACC,sBAAiB,oBAAC,aAAI,0BAAuBA,MAAM,OAAO,CAAO,EAAE,GAAGH,WAAW,CAAC;IAE5J,MAAMI,UAAUP,oBAAoBA,CAACE;IAErC;;GAEC,GACD,SAASM;QACP,MAAM,CAACC,QAAQC,UAAU,GAAGX,QAAQA,CAAgC,IAAMQ,QAAQ,SAAS;QAC3F,MAAM,CAACI,OAAOC,SAAS,GAAGb,QAAQA,CAAsE,IAAMQ,QAAQ,eAAe;QACrI,MAAM,CAACD,OAAOO,SAAS,GAAGd,QAAQA,CAAe,IAAMQ,QAAQ,QAAQ;QAEvET,SAASA,CAAC;YACR,+CAA+C;YAC/C,MAAMgB,gBAAgBP,QAAQ,SAAS;YACvC,MAAMQ,eAAeR,QAAQ,eAAe;YAC5C,MAAMS,eAAeT,QAAQ,QAAQ;YAErCG,UAAUI;YACVF,SAASG;YACTF,SAASG;YAET,6BAA6B;YAC7B,MAAMC,mBAAmBV,QAAQ,OAAO,CAAC,CAACW;gBACxCR,UAAU;gBACVE,SAASM;gBACTL,SAAS;YACX;YAEA,MAAMM,mBAAmBZ,QAAQ,OAAO,CAAC,CAACa;gBACxCV,UAAU;gBACVE,SAASS;gBACTR,SAASO;YACX;YAEA,OAAO;gBACLH;gBACAE;YACF;QACF,GAAG,EAAE;QAEL,OAAO;YACL,SAASV,WAAW;YACpB,SAASA,WAAW;YACpB,WAAWA,WAAW;YACtBE;YACAL;QACF;IACF;IAEA;;GAEC,GACD,SAASgB,iBAAiCC,SAAwC;QAChF,SAASC,iBAAiBC,KAAqB;YAC7C,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAErB,KAAK,EAAE,GAAGE;YAEpC,oBAAoB;YACpB,IAAImB,WAAWrB,OAAO,qBAAO,0CAAGD,eAAeC;YAE/C,sBAAsB;YACtB,IAAI,CAACoB,SAAS,qBAAO,0CAAGtB;YAExB,sCAAsC;YACtC,qBAAO,oBAACmB,WAAeE;QACzB;QAEA,6CAA6C;QAC7C,MAAMG,gBAAgBL,UAAU,WAAW,IAAIA,UAAU,IAAI,IAAI;QACjEC,iBAAiB,WAAW,GAAG,CAAC,aAAa,EAAEI,cAAc,CAAC,CAAC;QAE/D,OAAOJ;IACT;IAEA,OAAO;QACL,eAAe;QACfF;QACAd;QAEA,qFAAqF;QACrF,cAAc,IAAMD,QAAQ,YAAY;QACxC,SAAS,IAAMA,QAAQ,OAAO;QAC9B,iBAAiB,IAAMA,QAAQ,eAAe;QAC9C,SAAS,CAACsB,KAA8CtB,QAAQ,OAAO,CAACsB;QACxE,SAAS,CAACA,KAA8CtB,QAAQ,OAAO,CAACsB;QACxE,WAAW,IAAMtB,QAAQ,SAAS;QAClC,UAAU,IAAMA,QAAQ,QAAQ;QAChC,SAAS,IAAMA,QAAQ,OAAO;IAChC;AACF"}
1
+ {"version":3,"file":"react/utils/awaitSynapse.js","sources":["../../../src/react/utils/awaitSynapse.tsx"],"sourcesContent":["import { ComponentType, PropsWithChildren, ReactNode, useEffect, useState } from 'react'\n\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, createSynapseAwaiter } from '../../utils'\n\ninterface ReactAwaitSynapseOptions {\n loadingComponent?: ReactNode\n errorComponent?: (error: Error) => ReactNode\n}\n\n/**\n * React-обертка для фреймворк-независимой утилиты ожидания Synapse\n * Добавляет React-специфичные методы поверх createSynapseAwaiter\n */\nexport function awaitSynapse<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: ReactAwaitSynapseOptions,\n) {\n const { loadingComponent = <div>Инициализация...</div>, errorComponent = (error: Error) => <div>Ошибка инициализации: {error.message}</div> } = options || {}\n\n const awaiter = createSynapseAwaiter(synapseStorePromise)\n\n /**\n * Хук для получения текущего состояния готовности\n */\n function useSynapseReady() {\n const [status, setStatus] = useState<'pending' | 'ready' | 'error'>(() => awaiter.getStatus())\n const [store, setStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | undefined>(() => awaiter.getStoreIfReady())\n const [error, setError] = useState<Error | null>(() => awaiter.getError())\n\n useEffect(() => {\n // Проверяем текущее состояние при монтировании\n const currentStatus = awaiter.getStatus()\n const currentStore = awaiter.getStoreIfReady()\n const currentError = awaiter.getError()\n\n setStatus(currentStatus)\n setStore(currentStore)\n setError(currentError)\n\n // Подписываемся на изменения\n const unsubscribeReady = awaiter.onReady((readyStore) => {\n setStatus('ready')\n setStore(readyStore)\n setError(null)\n })\n\n const unsubscribeError = awaiter.onError((err) => {\n setStatus('error')\n setStore(undefined)\n setError(err)\n })\n\n return () => {\n unsubscribeReady()\n unsubscribeError()\n }\n }, [])\n\n return {\n isReady: status === 'ready',\n isError: status === 'error',\n isPending: status === 'pending',\n store,\n error,\n }\n }\n\n /**\n * Обертка, которая ждет готовности Synapse\n */\n function withSynapseReady<ComponentProps>(Component: ComponentType<ComponentProps>) {\n function WrappedComponent(props: ComponentProps) {\n const { isReady, isError, error } = useSynapseReady()\n\n // Показываем ошибку\n if (isError && error) return <>{errorComponent(error)}</>\n\n // Показываем загрузку\n if (!isReady) return <>{loadingComponent}</>\n\n // Рендерим компонент когда все готово\n return <Component {...(props as PropsWithChildren<ComponentProps>)} />\n }\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `AwaitSynapse(${componentName})`\n\n return WrappedComponent\n }\n\n return {\n // React методы\n withSynapseReady,\n useSynapseReady,\n\n // Проксируем все методы из awaiter (обёртки сохраняют контекст при деструктуризации)\n waitForReady: () => awaiter.waitForReady(),\n isReady: () => awaiter.isReady(),\n getStoreIfReady: () => awaiter.getStoreIfReady(),\n onReady: (cb: Parameters<typeof awaiter.onReady>[0]) => awaiter.onReady(cb),\n onError: (cb: Parameters<typeof awaiter.onError>[0]) => awaiter.onError(cb),\n getStatus: () => awaiter.getStatus(),\n getError: () => awaiter.getError(),\n destroy: () => awaiter.destroy(),\n }\n}\n"],"names":["useEffect","useState","createSynapseAwaiter","awaitSynapse","synapseStorePromise","options","loadingComponent","errorComponent","error","awaiter","useSynapseReady","status","setStatus","store","setStore","setError","currentStatus","currentStore","currentError","unsubscribeReady","readyStore","unsubscribeError","err","undefined","withSynapseReady","Component","WrappedComponent","props","isReady","isError","componentName","cb"],"mappings":";;;;;;;;AAAwF;AAGrB;AAOnE;;;CAGC,GACM,SAASG,YAAYA,CAC1BC,mBAA+I,EAC/IC,OAAkC;IAElC,MAAM,EAAEC,iCAAmB,IAAC;kBAAI;MAAsB,EAAEC,iBAAiB,CAACC,sBAAiB,KAAC;;gBAAI;gBAAuBA,MAAM,OAAO;;UAAO,EAAE,GAAGH,WAAW,CAAC;IAE5J,MAAMI,UAAUP,oBAAoBA,CAACE;IAErC;;GAEC,GACD,SAASM;QACP,MAAM,CAACC,QAAQC,UAAU,GAAGX,QAAQA,CAAgC,IAAMQ,QAAQ,SAAS;QAC3F,MAAM,CAACI,OAAOC,SAAS,GAAGb,QAAQA,CAAsE,IAAMQ,QAAQ,eAAe;QACrI,MAAM,CAACD,OAAOO,SAAS,GAAGd,QAAQA,CAAe,IAAMQ,QAAQ,QAAQ;QAEvET,SAASA,CAAC;YACR,+CAA+C;YAC/C,MAAMgB,gBAAgBP,QAAQ,SAAS;YACvC,MAAMQ,eAAeR,QAAQ,eAAe;YAC5C,MAAMS,eAAeT,QAAQ,QAAQ;YAErCG,UAAUI;YACVF,SAASG;YACTF,SAASG;YAET,6BAA6B;YAC7B,MAAMC,mBAAmBV,QAAQ,OAAO,CAAC,CAACW;gBACxCR,UAAU;gBACVE,SAASM;gBACTL,SAAS;YACX;YAEA,MAAMM,mBAAmBZ,QAAQ,OAAO,CAAC,CAACa;gBACxCV,UAAU;gBACVE,SAASS;gBACTR,SAASO;YACX;YAEA,OAAO;gBACLH;gBACAE;YACF;QACF,GAAG,EAAE;QAEL,OAAO;YACL,SAASV,WAAW;YACpB,SAASA,WAAW;YACpB,WAAWA,WAAW;YACtBE;YACAL;QACF;IACF;IAEA;;GAEC,GACD,SAASgB,iBAAiCC,SAAwC;QAChF,SAASC,iBAAiBC,KAAqB;YAC7C,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAErB,KAAK,EAAE,GAAGE;YAEpC,oBAAoB;YACpB,IAAImB,WAAWrB,OAAO,qBAAO;0BAAGD,eAAeC;;YAE/C,sBAAsB;YACtB,IAAI,CAACoB,SAAS,qBAAO;0BAAGtB;;YAExB,sCAAsC;YACtC,qBAAO,IAACmB;gBAAW,GAAIE,KAAK;;QAC9B;QAEA,6CAA6C;QAC7C,MAAMG,gBAAgBL,UAAU,WAAW,IAAIA,UAAU,IAAI,IAAI;QACjEC,iBAAiB,WAAW,GAAG,CAAC,aAAa,EAAEI,cAAc,CAAC,CAAC;QAE/D,OAAOJ;IACT;IAEA,OAAO;QACL,eAAe;QACfF;QACAd;QAEA,qFAAqF;QACrF,cAAc,IAAMD,QAAQ,YAAY;QACxC,SAAS,IAAMA,QAAQ,OAAO;QAC9B,iBAAiB,IAAMA,QAAQ,eAAe;QAC9C,SAAS,CAACsB,KAA8CtB,QAAQ,OAAO,CAACsB;QACxE,SAAS,CAACA,KAA8CtB,QAAQ,OAAO,CAACsB;QACxE,WAAW,IAAMtB,QAAQ,SAAS;QAClC,UAAU,IAAMA,QAAQ,QAAQ;QAChC,SAAS,IAAMA,QAAQ,OAAO;IAChC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"createSynapseCtx.d.ts","sourceRoot":"","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAiF,MAAM,OAAO,CAAA;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAGjC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAmB,iBAAiB,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAKrH,UAAU,iBAAiB;IACzB,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACnC;AAED;;GAEG;AAGH,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAC1H,mBAAmB,EAAE,OAAO,CAAC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,GAAG,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,EAC/J,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,gBAAgB,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,CAAA;IAC1C,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AAGD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAC1H,mBAAmB,EAAE,OAAO,CAAC,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,EACrK,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AAGD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAChH,mBAAmB,EAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAC/H,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA"}
1
+ {"version":3,"file":"createSynapseCtx.d.ts","sourceRoot":"","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAiF,MAAM,OAAO,CAAA;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAGjC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAyC,iBAAiB,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAK3I,UAAU,iBAAiB;IACzB,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACnC;AAED;;GAEG;AAGH,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAC1H,mBAAmB,EAAE,OAAO,CAAC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,GAAG,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,EAC/J,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,gBAAgB,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,CAAA;IAC1C,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AAGD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAC1H,mBAAmB,EAAE,OAAO,CAAC,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,EACrK,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AAGD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAChH,mBAAmB,EAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAC/H,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA"}
@@ -1,5 +1,11 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
1
2
  import { createContext, forwardRef, useContext, useEffect, useState } from "react";
2
- import { handleCleanupError, handleOperationError } from "../../_utils/error-handling.util.js";
3
+ import { handleCleanupError } from "../../_utils/error-handling.util.js";
4
+ import { createSynapseAwaiter } from "../../utils/index.js";
5
+
6
+
7
+
8
+
3
9
 
4
10
 
5
11
 
@@ -9,22 +15,15 @@ const ERROR_HOOK_MESSAGE = 'Хук необходимо использовать
9
15
  const ERROR_CONTEXT_INIT = 'Ошибка при инициализации контекста:';
10
16
  // Основная реализация
11
17
  function createSynapseCtx(synapseStorePromise, options) {
12
- const { loadingComponent = /*#__PURE__*/ React.createElement("div", null, "Инициализация контекста...") } = options || {};
13
- // Lazy-инициализация: Promise создаётся при первом обращении и сбрасывается при cleanup
14
- let storeInitPromise = null;
15
- const getStoreInitPromise = ()=>{
16
- if (!storeInitPromise) {
17
- storeInitPromise = (async ()=>{
18
- try {
19
- const store = await (synapseStorePromise instanceof Promise ? synapseStorePromise : Promise.resolve(synapseStorePromise));
20
- await store.storage.waitForReady();
21
- return store;
22
- } catch (error) {
23
- handleOperationError('createSynapseCtx: Synapse storage initialization error', error);
24
- }
25
- })();
26
- }
27
- return storeInitPromise;
18
+ const { loadingComponent = /*#__PURE__*/ jsx("div", {
19
+ children: "Инициализация контекста..."
20
+ }) } = options || {};
21
+ // Lazy-инициализация: awaiter создаётся при первом обращении и сбрасывается при cleanup.
22
+ // Сам awaiter (createSynapseAwaiter) инкапсулирует ожидание готовности, статус и подписки.
23
+ let awaiter = null;
24
+ const getAwaiter = ()=>{
25
+ if (!awaiter) awaiter = createSynapseAwaiter(synapseStorePromise);
26
+ return awaiter;
28
27
  };
29
28
  const SynapseContext = /*#__PURE__*/ createContext(null);
30
29
  const useSynapseStorage = ()=>{
@@ -59,39 +58,41 @@ function createSynapseCtx(synapseStorePromise, options) {
59
58
  * Декоратор для обертывания компонентов в контекст Synapse
60
59
  */ function contextSynapse(Component) {
61
60
  const WrappedComponent = /*#__PURE__*/ forwardRef(function WrappedComponent(props, ref) {
62
- const [synapseStore, setSynapseStore] = useState(null);
63
- const [isReady, setIsReady] = useState(false);
64
- const [error, setError] = useState(null);
61
+ const [synapseStore, setSynapseStore] = useState(()=>getAwaiter().getStoreIfReady());
62
+ const [error, setError] = useState(()=>getAwaiter().getError());
65
63
  useEffect(()=>{
66
- let mounted = true;
67
- const initializeContext = async ()=>{
68
- try {
69
- const store = await getStoreInitPromise();
70
- if (mounted) {
71
- setSynapseStore(store);
72
- setIsReady(true);
73
- }
74
- } catch (err) {
75
- if (mounted) {
76
- setError(err instanceof Error ? err : new Error(String(err)));
77
- }
78
- }
79
- };
80
- initializeContext();
64
+ // awaiter мог поменять состояние между рендером и эффектом — синхронизируемся
65
+ const instance = getAwaiter();
66
+ setSynapseStore(instance.getStoreIfReady());
67
+ setError(instance.getError());
68
+ const unsubscribeReady = instance.onReady((store)=>{
69
+ setSynapseStore(store);
70
+ setError(null);
71
+ });
72
+ const unsubscribeError = instance.onError((err)=>{
73
+ setSynapseStore(undefined);
74
+ setError(err);
75
+ });
81
76
  return ()=>{
82
- mounted = false;
77
+ unsubscribeReady();
78
+ unsubscribeError();
83
79
  };
84
80
  }, []);
85
81
  // Показываем ошибку если что-то пошло не так
86
- if (error) return /*#__PURE__*/ React.createElement("div", null, `${ERROR_CONTEXT_INIT}: ${error.message}`);
87
- // Показываем загрузку пока все не готово
88
- if (!isReady || !synapseStore) return /*#__PURE__*/ React.createElement(React.Fragment, null, loadingComponent);
89
- return /*#__PURE__*/ React.createElement(SynapseContext.Provider, {
90
- value: synapseStore
91
- }, /*#__PURE__*/ React.createElement(Component, {
92
- ...props,
93
- ref: ref
94
- }));
82
+ if (error) return /*#__PURE__*/ jsx("div", {
83
+ children: `${ERROR_CONTEXT_INIT} ${error.message}`
84
+ });
85
+ // Показываем загрузку пока store не готов
86
+ if (!synapseStore) return /*#__PURE__*/ jsx(Fragment, {
87
+ children: loadingComponent
88
+ });
89
+ return /*#__PURE__*/ jsx(SynapseContext.Provider, {
90
+ value: synapseStore,
91
+ children: /*#__PURE__*/ jsx(Component, {
92
+ ...props,
93
+ ref: ref
94
+ })
95
+ });
95
96
  });
96
97
  // Устанавливаем отображаемое имя для отладки
97
98
  const componentName = Component.displayName || Component.name || 'Component';
@@ -113,14 +114,15 @@ function createSynapseCtx(synapseStorePromise, options) {
113
114
  return WrappedComponent;
114
115
  }
115
116
  const cleanupSynapse = async ()=>{
117
+ if (!awaiter) return;
118
+ const instance = awaiter;
119
+ awaiter = null; // сбрасываем сразу, чтобы следующий маунт создал новый awaiter
116
120
  try {
117
- if (storeInitPromise) {
118
- const store = await storeInitPromise;
119
- storeInitPromise = null;
120
- return store?.destroy() || Promise.resolve();
121
- }
121
+ const store = instance.getStoreIfReady() ?? await instance.waitForReady();
122
+ instance.destroy();
123
+ await store?.destroy();
122
124
  } catch (error) {
123
- storeInitPromise = null;
125
+ instance.destroy();
124
126
  handleCleanupError('createSynapseCtx: error during Synapse cleanup', error);
125
127
  }
126
128
  };
@@ -1 +1 @@
1
- {"version":3,"file":"react/utils/createSynapseCtx.js","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"sourcesContent":["import { ComponentType, createContext, forwardRef, PropsWithChildren, useContext, useEffect, useState } from 'react'\nimport { Observable } from 'rxjs'\n\nimport { handleCleanupError, handleOperationError } from '../../_utils/error-handling.util'\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, SynapseStoreBasic, SynapseStoreWithDispatcher, SynapseStoreWithEffects } from '../../utils'\n\nconst ERROR_HOOK_MESSAGE = 'Хук необходимо использовать внутри компонента contextSynapse'\nconst ERROR_CONTEXT_INIT = 'Ошибка при инициализации контекста:'\n\ninterface SimplifiedOptions {\n loadingComponent?: React.ReactNode\n}\n\n/**\n * Перегрузки для createSynapseCtx в зависимости от типа хранилища\n */\n\n// Для хранилища с effects\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n useSynapseState$: () => Observable<TStore>\n cleanupSynapse: () => Promise<void>\n}\n\n// Для хранилища с dispatcher (без effects)\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n cleanupSynapse: () => Promise<void>\n}\n\n// Для базового хранилища\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors>(\n synapseStorePromise: Promise<SynapseStoreBasic<TStore, TStorage, TSelectors>> | SynapseStoreBasic<TStore, TStorage, TSelectors>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n cleanupSynapse: () => Promise<void>\n}\n\n// Основная реализация\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n) {\n const { loadingComponent = <div>Инициализация контекста...</div> } = options || {}\n\n // Lazy-инициализация: Promise создаётся при первом обращении и сбрасывается при cleanup\n let storeInitPromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | null = null\n\n const getStoreInitPromise = () => {\n if (!storeInitPromise) {\n storeInitPromise = (async () => {\n try {\n const store = await (synapseStorePromise instanceof Promise ? synapseStorePromise : Promise.resolve(synapseStorePromise))\n await store.storage.waitForReady()\n return store\n } catch (error) {\n handleOperationError('createSynapseCtx: Synapse storage initialization error', error)\n }\n })()\n }\n return storeInitPromise\n }\n\n const SynapseContext = createContext<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | null>(null)\n\n const useSynapseStorage = (): TStorage => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseStorage: ${ERROR_HOOK_MESSAGE}`)\n return context.storage\n }\n\n const useSynapseSelectors = (): TSelectors => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseSelectors: ${ERROR_HOOK_MESSAGE}`)\n return context.selectors\n }\n\n // Условный хук для actions (только если есть dispatcher)\n const useSynapseActions = (): TActions => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseActions: ${ERROR_HOOK_MESSAGE}`)\n\n if ('actions' in context) {\n return (context as SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).actions\n }\n\n throw new Error('useSynapseActions: actions недоступны для этого типа хранилища. Убедитесь, что передана функция createDispatcherFn при создании хранилища.')\n }\n\n // Условный хук для state$ (только если есть effects)\n const useSynapseState$ = (): Observable<TStore> => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseState$: ${ERROR_HOOK_MESSAGE}`)\n\n if ('state$' in context) {\n return (context as SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).state$\n }\n\n throw new Error('useSynapseState$: state$ недоступен для этого типа хранилища. Убедитесь, что переданы функции createDispatcherFn и createEffectConfig при создании хранилища.')\n }\n\n /**\n * Декоратор для обертывания компонентов в контекст Synapse\n */\n function contextSynapse<SelfComponentProps>(Component: ComponentType<SelfComponentProps>) {\n const WrappedComponent = forwardRef<unknown, SelfComponentProps>(function WrappedComponent(props, ref) {\n const [synapseStore, setSynapseStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | null>(null)\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let mounted = true\n\n const initializeContext = async () => {\n try {\n const store = await getStoreInitPromise()\n\n if (mounted) {\n setSynapseStore(store)\n setIsReady(true)\n }\n } catch (err) {\n if (mounted) {\n setError(err instanceof Error ? err : new Error(String(err)))\n }\n }\n }\n\n initializeContext()\n\n return () => {\n mounted = false\n }\n }, [])\n\n // Показываем ошибку если что-то пошло не так\n if (error) return <div>{`${ERROR_CONTEXT_INIT}: ${error.message}`}</div>\n\n // Показываем загрузку пока все не готово\n if (!isReady || !synapseStore) return <>{loadingComponent}</>\n\n return (\n <SynapseContext.Provider value={synapseStore}>\n <Component {...(props as PropsWithChildren<SelfComponentProps>)} ref={ref} />\n </SynapseContext.Provider>\n )\n })\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `SynapseContext(${componentName})`\n\n // Копируем статические свойства оригинального компонента\n const excludedKeys = new Set(['$$typeof', 'render', 'defaultProps', 'displayName', 'propTypes'])\n Object.keys(Component).forEach((key) => {\n if (!excludedKeys.has(key)) {\n ;(WrappedComponent as any)[key] = (Component as any)[key]\n }\n })\n\n return WrappedComponent as ComponentType<SelfComponentProps>\n }\n\n const cleanupSynapse = async (): Promise<void> => {\n try {\n if (storeInitPromise) {\n const store = await storeInitPromise\n storeInitPromise = null\n return store?.destroy() || Promise.resolve()\n }\n } catch (error) {\n storeInitPromise = null\n handleCleanupError('createSynapseCtx: error during Synapse cleanup', error)\n }\n }\n\n return {\n contextSynapse,\n useSynapseStorage,\n useSynapseSelectors,\n useSynapseActions,\n useSynapseState$,\n cleanupSynapse,\n }\n}\n"],"names":["createContext","forwardRef","useContext","useEffect","useState","handleCleanupError","handleOperationError","ERROR_HOOK_MESSAGE","ERROR_CONTEXT_INIT","createSynapseCtx","synapseStorePromise","options","loadingComponent","storeInitPromise","getStoreInitPromise","store","Promise","error","SynapseContext","useSynapseStorage","context","Error","useSynapseSelectors","useSynapseActions","useSynapseState$","contextSynapse","Component","WrappedComponent","props","ref","synapseStore","setSynapseStore","isReady","setIsReady","setError","mounted","initializeContext","err","String","componentName","excludedKeys","Set","Object","key","cleanupSynapse"],"mappings":"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"}
1
+ {"version":3,"file":"react/utils/createSynapseCtx.js","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"sourcesContent":["import { ComponentType, createContext, forwardRef, PropsWithChildren, useContext, useEffect, useState } from 'react'\nimport { Observable } from 'rxjs'\n\nimport { handleCleanupError } from '../../_utils/error-handling.util'\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, createSynapseAwaiter, SynapseStoreBasic, SynapseStoreWithDispatcher, SynapseStoreWithEffects } from '../../utils'\n\nconst ERROR_HOOK_MESSAGE = 'Хук необходимо использовать внутри компонента contextSynapse'\nconst ERROR_CONTEXT_INIT = 'Ошибка при инициализации контекста:'\n\ninterface SimplifiedOptions {\n loadingComponent?: React.ReactNode\n}\n\n/**\n * Перегрузки для createSynapseCtx в зависимости от типа хранилища\n */\n\n// Для хранилища с effects\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n useSynapseState$: () => Observable<TStore>\n cleanupSynapse: () => Promise<void>\n}\n\n// Для хранилища с dispatcher (без effects)\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n cleanupSynapse: () => Promise<void>\n}\n\n// Для базового хранилища\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors>(\n synapseStorePromise: Promise<SynapseStoreBasic<TStore, TStorage, TSelectors>> | SynapseStoreBasic<TStore, TStorage, TSelectors>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n cleanupSynapse: () => Promise<void>\n}\n\n// Основная реализация\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n) {\n const { loadingComponent = <div>Инициализация контекста...</div> } = options || {}\n\n // Lazy-инициализация: awaiter создаётся при первом обращении и сбрасывается при cleanup.\n // Сам awaiter (createSynapseAwaiter) инкапсулирует ожидание готовности, статус и подписки.\n let awaiter: ReturnType<typeof createSynapseAwaiter<TStore, TStorage, TSelectors, TActions>> | null = null\n\n const getAwaiter = () => {\n if (!awaiter) awaiter = createSynapseAwaiter(synapseStorePromise)\n return awaiter\n }\n\n const SynapseContext = createContext<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | null>(null)\n\n const useSynapseStorage = (): TStorage => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseStorage: ${ERROR_HOOK_MESSAGE}`)\n return context.storage\n }\n\n const useSynapseSelectors = (): TSelectors => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseSelectors: ${ERROR_HOOK_MESSAGE}`)\n return context.selectors\n }\n\n // Условный хук для actions (только если есть dispatcher)\n const useSynapseActions = (): TActions => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseActions: ${ERROR_HOOK_MESSAGE}`)\n\n if ('actions' in context) {\n return (context as SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).actions\n }\n\n throw new Error('useSynapseActions: actions недоступны для этого типа хранилища. Убедитесь, что передана функция createDispatcherFn при создании хранилища.')\n }\n\n // Условный хук для state$ (только если есть effects)\n const useSynapseState$ = (): Observable<TStore> => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseState$: ${ERROR_HOOK_MESSAGE}`)\n\n if ('state$' in context) {\n return (context as SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).state$\n }\n\n throw new Error('useSynapseState$: state$ недоступен для этого типа хранилища. Убедитесь, что переданы функции createDispatcherFn и createEffectConfig при создании хранилища.')\n }\n\n /**\n * Декоратор для обертывания компонентов в контекст Synapse\n */\n function contextSynapse<SelfComponentProps>(Component: ComponentType<SelfComponentProps>) {\n const WrappedComponent = forwardRef<unknown, SelfComponentProps>(function WrappedComponent(props, ref) {\n const [synapseStore, setSynapseStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | undefined>(() => getAwaiter().getStoreIfReady())\n const [error, setError] = useState<Error | null>(() => getAwaiter().getError())\n\n useEffect(() => {\n // awaiter мог поменять состояние между рендером и эффектом — синхронизируемся\n const instance = getAwaiter()\n setSynapseStore(instance.getStoreIfReady())\n setError(instance.getError())\n\n const unsubscribeReady = instance.onReady((store) => {\n setSynapseStore(store)\n setError(null)\n })\n const unsubscribeError = instance.onError((err) => {\n setSynapseStore(undefined)\n setError(err)\n })\n\n return () => {\n unsubscribeReady()\n unsubscribeError()\n }\n }, [])\n\n // Показываем ошибку если что-то пошло не так\n if (error) return <div>{`${ERROR_CONTEXT_INIT} ${error.message}`}</div>\n\n // Показываем загрузку пока store не готов\n if (!synapseStore) return <>{loadingComponent}</>\n\n return (\n <SynapseContext.Provider value={synapseStore}>\n <Component {...(props as PropsWithChildren<SelfComponentProps>)} ref={ref} />\n </SynapseContext.Provider>\n )\n })\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `SynapseContext(${componentName})`\n\n // Копируем статические свойства оригинального компонента\n const excludedKeys = new Set(['$$typeof', 'render', 'defaultProps', 'displayName', 'propTypes'])\n Object.keys(Component).forEach((key) => {\n if (!excludedKeys.has(key)) {\n ;(WrappedComponent as any)[key] = (Component as any)[key]\n }\n })\n\n return WrappedComponent as ComponentType<SelfComponentProps>\n }\n\n const cleanupSynapse = async (): Promise<void> => {\n if (!awaiter) return\n\n const instance = awaiter\n awaiter = null // сбрасываем сразу, чтобы следующий маунт создал новый awaiter\n\n try {\n const store = instance.getStoreIfReady() ?? (await instance.waitForReady())\n instance.destroy()\n await store?.destroy()\n } catch (error) {\n instance.destroy()\n handleCleanupError('createSynapseCtx: error during Synapse cleanup', error)\n }\n }\n\n return {\n contextSynapse,\n useSynapseStorage,\n useSynapseSelectors,\n useSynapseActions,\n useSynapseState$,\n cleanupSynapse,\n }\n}\n"],"names":["createContext","forwardRef","useContext","useEffect","useState","handleCleanupError","createSynapseAwaiter","ERROR_HOOK_MESSAGE","ERROR_CONTEXT_INIT","createSynapseCtx","synapseStorePromise","options","loadingComponent","awaiter","getAwaiter","SynapseContext","useSynapseStorage","context","Error","useSynapseSelectors","useSynapseActions","useSynapseState$","contextSynapse","Component","WrappedComponent","props","ref","synapseStore","setSynapseStore","error","setError","instance","unsubscribeReady","store","unsubscribeError","err","undefined","componentName","excludedKeys","Set","Object","key","cleanupSynapse"],"mappings":"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"}
@@ -30,10 +30,29 @@ export interface WatcherRecipe<TState extends Record<string, any>, R> {
30
30
  };
31
31
  }
32
32
  /**
33
- * Состояние API-запроса для createApiActions
33
+ * Статусы жизненного цикла API-запроса.
34
+ *
35
+ * Сделано const-объектом (а не TS `enum`) намеренно: значения остаются обычными
36
+ * строковыми литералами, поэтому `ApiStatus.Loading` и строка `'loading'`
37
+ * взаимозаменяемы и тип обратно совместим с прежним строковым union'ом. С `enum`
38
+ * это не так — его член не присваивается к литералу и наоборот.
39
+ *
40
+ * `ApiStatus` — одновременно значение (для `ApiStatus.Loading` в коде) и тип
41
+ * (union всех статусов).
42
+ */
43
+ export declare const ApiStatus: {
44
+ readonly Idle: "idle";
45
+ readonly Loading: "loading";
46
+ readonly Success: "success";
47
+ readonly Error: "error";
48
+ readonly Reset: "reset";
49
+ };
50
+ export type ApiStatus = (typeof ApiStatus)[keyof typeof ApiStatus];
51
+ /**
52
+ * Состояние API-запроса для createApiActions / createKeyedApiActions
34
53
  */
35
54
  export interface ApiRequestState {
36
- status: 'idle' | 'loading' | 'success' | 'error' | 'reset';
55
+ status: ApiStatus;
37
56
  error: string | null;
38
57
  }
39
58
  /**
@@ -86,6 +105,11 @@ export declare function defineWatcher<TState extends Record<string, any>>(): <R>
86
105
  *
87
106
  * @param accessor - Функция-accessor, указывающая на поле ApiRequestState в стейте
88
107
  *
108
+ * @typeParam TInitPayload - Тип payload'а `init`-экшена. По умолчанию `void`
109
+ * (init без параметров). Если задать — `init` принимает payload и возвращает
110
+ * его, что удобно для intent-паттерна: эффект слушает `init` и читает payload
111
+ * намерения (target, фильтры и т.п.), а статус при этом сбрасывается в `idle`.
112
+ *
89
113
  * @example
90
114
  * ```ts
91
115
  * const listRequest = createApiActions<MyState>(
@@ -100,13 +124,58 @@ export declare function defineWatcher<TState extends Record<string, any>>(): <R>
100
124
  * loadListFailure: listRequest.failure,
101
125
  * loadListReset: listRequest.reset,
102
126
  * })
127
+ *
128
+ * // init с payload (intent): эффект получит { entityId } из возврата экшена.
129
+ * const usersReq = createApiActions<MyState, { entityId: string }>(
130
+ * (draft) => draft.api.usersRequest
131
+ * )
103
132
  * ```
104
133
  */
105
- export declare function createApiActions<TState extends Record<string, any>>(accessor: (draft: TState) => ApiRequestState): {
106
- init: ActionRecipe<TState, void, void>;
134
+ export declare function createApiActions<TState extends Record<string, any>, TInitPayload = void>(accessor: (draft: TState) => ApiRequestState): {
135
+ init: ActionRecipe<TState, TInitPayload, TInitPayload>;
107
136
  loading: ActionRecipe<TState, void, void>;
108
137
  success: ActionRecipe<TState, void, void>;
109
138
  failure: ActionRecipe<TState, string, void>;
110
139
  reset: ActionRecipe<TState, void, void>;
111
140
  };
141
+ /**
142
+ * Keyed-вариант createApiActions: статус хранится ПО КЛЮЧУ в `Record<string,
143
+ * ApiRequestState>`, а не один на весь запрос. Нужен, когда один и тот же запрос
144
+ * летит параллельно для нескольких независимых ключей и у каждого свой
145
+ * loading/error: комменты по таргетам, детали сущностей по id, per-row действия
146
+ * в таблице/ленте, пагинация по секциям — всё, что лежит как `Record<key, data>`.
147
+ *
148
+ * Все статус-экшены принимают `key` (и возвращают его — удобно эффектам), кроме
149
+ * `failure`, который принимает `{ key, error }`. Записи мутируются иммутабельно
150
+ * по одному ключу — соседние ключи (их срезы) по ссылке не затрагиваются, что и
151
+ * нужно для гранулярной изоляции ре-рендеров (см. useKeyedSliceSelector).
152
+ *
153
+ * @param accessor - Функция-accessor, указывающая на поле `Record<string, ApiRequestState>`
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * const commentsReq = createKeyedApiActions<MyState>((d) => d.api.commentsRequest)
158
+ *
159
+ * createDispatcher({ storage }, {
160
+ * commentsInit: commentsReq.init, // (key) => key
161
+ * commentsLoading: commentsReq.loading, // (key) => key
162
+ * commentsSuccess: commentsReq.success, // (key) => key
163
+ * commentsFailure: commentsReq.failure, // ({ key, error })
164
+ * commentsReset: commentsReq.reset, // (key) => key
165
+ * })
166
+ * ```
167
+ */
168
+ export declare function createKeyedApiActions<TState extends Record<string, any>>(accessor: (draft: TState) => Record<string, ApiRequestState>): {
169
+ init: ActionRecipe<TState, string, string>;
170
+ loading: ActionRecipe<TState, string, string>;
171
+ success: ActionRecipe<TState, string, string>;
172
+ reset: ActionRecipe<TState, string, string>;
173
+ failure: ActionRecipe<TState, {
174
+ key: string;
175
+ error: string;
176
+ }, {
177
+ key: string;
178
+ error: string;
179
+ }>;
180
+ };
112
181
  //# sourceMappingURL=standalone.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../../src/reactive/dispatcher/standalone.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../../src/reactive/dispatcher/standalone.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Статусы жизненного цикла API-запроса.
3
+ *
4
+ * Сделано const-объектом (а не TS `enum`) намеренно: значения остаются обычными
5
+ * строковыми литералами, поэтому `ApiStatus.Loading` и строка `'loading'`
6
+ * взаимозаменяемы и тип обратно совместим с прежним строковым union'ом. С `enum`
7
+ * это не так — его член не присваивается к литералу и наоборот.
8
+ *
9
+ * `ApiStatus` — одновременно значение (для `ApiStatus.Loading` в коде) и тип
10
+ * (union всех статусов).
11
+ */ const ApiStatus = {
12
+ Idle: 'idle',
13
+ Loading: 'loading',
14
+ Success: 'success',
15
+ Error: 'error',
16
+ Reset: 'reset'
17
+ };
1
18
  // ────────────────────────────────────────────────────────────────────────────
2
19
  // defineAction
3
20
  // ────────────────────────────────────────────────────────────────────────────
@@ -82,6 +99,11 @@
82
99
  *
83
100
  * @param accessor - Функция-accessor, указывающая на поле ApiRequestState в стейте
84
101
  *
102
+ * @typeParam TInitPayload - Тип payload'а `init`-экшена. По умолчанию `void`
103
+ * (init без параметров). Если задать — `init` принимает payload и возвращает
104
+ * его, что удобно для intent-паттерна: эффект слушает `init` и читает payload
105
+ * намерения (target, фильтры и т.п.), а статус при этом сбрасывается в `idle`.
106
+ *
85
107
  * @example
86
108
  * ```ts
87
109
  * const listRequest = createApiActions<MyState>(
@@ -96,6 +118,11 @@
96
118
  * loadListFailure: listRequest.failure,
97
119
  * loadListReset: listRequest.reset,
98
120
  * })
121
+ *
122
+ * // init с payload (intent): эффект получит { entityId } из возврата экшена.
123
+ * const usersReq = createApiActions<MyState, { entityId: string }>(
124
+ * (draft) => draft.api.usersRequest
125
+ * )
99
126
  * ```
100
127
  */ function createApiActions(accessor) {
101
128
  const path = resolvePath(accessor);
@@ -105,38 +132,106 @@
105
132
  };
106
133
  return {
107
134
  init: action({
108
- action: (storage)=>update(storage, {
109
- status: 'idle',
135
+ // Сбрасываем статус в idle и пробрасываем payload намерения дальше (эффектам).
136
+ action: (storage, payload)=>{
137
+ update(storage, {
138
+ status: ApiStatus.Idle,
110
139
  error: null
111
- })
140
+ });
141
+ return payload;
142
+ }
112
143
  }),
113
144
  loading: action({
114
145
  action: (storage)=>update(storage, {
115
- status: 'loading',
146
+ status: ApiStatus.Loading,
116
147
  error: null
117
148
  })
118
149
  }),
119
150
  success: action({
120
151
  action: (storage)=>update(storage, {
121
- status: 'success',
152
+ status: ApiStatus.Success,
122
153
  error: null
123
154
  })
124
155
  }),
125
156
  failure: action({
126
157
  action: (storage, error)=>update(storage, {
127
- status: 'error',
158
+ status: ApiStatus.Error,
128
159
  error
129
160
  })
130
161
  }),
131
162
  reset: action({
132
163
  action: (storage)=>update(storage, {
133
- status: 'reset',
164
+ status: ApiStatus.Reset,
134
165
  error: null
135
166
  })
136
167
  })
137
168
  };
138
169
  }
170
+ // ────────────────────────────────────────────────────────────────────────────
171
+ // createKeyedApiActions
172
+ // ────────────────────────────────────────────────────────────────────────────
173
+ /**
174
+ * Keyed-вариант createApiActions: статус хранится ПО КЛЮЧУ в `Record<string,
175
+ * ApiRequestState>`, а не один на весь запрос. Нужен, когда один и тот же запрос
176
+ * летит параллельно для нескольких независимых ключей и у каждого свой
177
+ * loading/error: комменты по таргетам, детали сущностей по id, per-row действия
178
+ * в таблице/ленте, пагинация по секциям — всё, что лежит как `Record<key, data>`.
179
+ *
180
+ * Все статус-экшены принимают `key` (и возвращают его — удобно эффектам), кроме
181
+ * `failure`, который принимает `{ key, error }`. Записи мутируются иммутабельно
182
+ * по одному ключу — соседние ключи (их срезы) по ссылке не затрагиваются, что и
183
+ * нужно для гранулярной изоляции ре-рендеров (см. useKeyedSliceSelector).
184
+ *
185
+ * @param accessor - Функция-accessor, указывающая на поле `Record<string, ApiRequestState>`
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * const commentsReq = createKeyedApiActions<MyState>((d) => d.api.commentsRequest)
190
+ *
191
+ * createDispatcher({ storage }, {
192
+ * commentsInit: commentsReq.init, // (key) => key
193
+ * commentsLoading: commentsReq.loading, // (key) => key
194
+ * commentsSuccess: commentsReq.success, // (key) => key
195
+ * commentsFailure: commentsReq.failure, // ({ key, error })
196
+ * commentsReset: commentsReq.reset, // (key) => key
197
+ * })
198
+ * ```
199
+ */ function createKeyedApiActions(accessor) {
200
+ const path = resolvePath(accessor);
201
+ const action = defineAction();
202
+ const write = (storage, key, request)=>{
203
+ // [...path, key] → setByPath доходит до самого Record и кладёт значение по ключу
204
+ storage.update((s)=>setByPath(s, [
205
+ ...path,
206
+ key
207
+ ], request));
208
+ };
209
+ const writer = (status)=>action({
210
+ action: (storage, key)=>{
211
+ write(storage, key, {
212
+ status,
213
+ error: null
214
+ });
215
+ return key;
216
+ }
217
+ });
218
+ return {
219
+ init: writer(ApiStatus.Idle),
220
+ loading: writer(ApiStatus.Loading),
221
+ success: writer(ApiStatus.Success),
222
+ reset: writer(ApiStatus.Reset),
223
+ failure: action({
224
+ action: (storage, payload)=>{
225
+ write(storage, payload.key, {
226
+ status: ApiStatus.Error,
227
+ error: payload.error
228
+ });
229
+ return payload;
230
+ }
231
+ })
232
+ };
233
+ }
139
234
 
140
- export { createApiActions, defineAction, defineWatcher };
235
+ export { ApiStatus, createApiActions, createKeyedApiActions, defineAction, defineWatcher };
141
236
 
142
237
  //# sourceMappingURL=standalone.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactive/dispatcher/standalone.js","sources":["../../../src/reactive/dispatcher/standalone.ts"],"sourcesContent":["import type { IStorage } from '../../core'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Параметры исполнения действия (мемоизация)\n */\nexport interface ActionExecutionOptions<TParams, TResult> {\n memoize?: (currentArgs: TParams, previousArgs: TParams, previousResult: TResult) => boolean\n}\n\n/**\n * Рецепт действия — standalone-определение, не привязанное к хранилищу.\n * Привязывается к storage при регистрации в createDispatcher.\n */\nexport interface ActionRecipe<TState extends Record<string, any>, TParams, TResult> {\n readonly _type: 'action-recipe'\n readonly _config: {\n action: (storage: IStorage<TState>, params: TParams) => Promise<TResult> | TResult\n meta?: Record<string, any>\n }\n readonly _executionOptions?: ActionExecutionOptions<TParams, TResult>\n}\n\n/**\n * Рецепт watcher'а — standalone-определение, не привязанное к хранилищу.\n */\nexport interface WatcherRecipe<TState extends Record<string, any>, R> {\n readonly _type: 'watcher-recipe'\n readonly _config: {\n selector: (state: TState) => R\n meta?: Record<string, any>\n shouldTrigger?: (prev: R | undefined, current: R) => boolean\n notifyAfterSubscribe?: boolean\n }\n}\n\n/**\n * Состояние API-запроса для createApiActions\n */\nexport interface ApiRequestState {\n status: 'idle' | 'loading' | 'success' | 'error' | 'reset'\n error: string | null\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// defineAction\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Создаёт standalone-определение действия.\n * Фиксирует TState через первый вызов, TParams/TResult инферятся из action.\n *\n * @example\n * ```ts\n * const action = defineAction<MyState>()\n *\n * export const increment = action({\n * action: (storage, amount: number) => {\n * storage.update((s) => { s.count += amount })\n * return amount\n * },\n * })\n *\n * // Void action (без параметров и возврата):\n * export const reset = action({\n * action: (storage) => {\n * storage.update((s) => { s.count = 0 })\n * },\n * })\n * ```\n */\nexport function defineAction<TState extends Record<string, any>>() {\n return <TParams = void, TResult = void>(\n config: {\n action: (storage: IStorage<TState>, params: TParams) => Promise<TResult> | TResult\n meta?: Record<string, any>\n },\n executionOptions?: ActionExecutionOptions<TParams, TResult>,\n ): ActionRecipe<TState, TParams, TResult> => ({\n _type: 'action-recipe' as const,\n _config: config,\n _executionOptions: executionOptions,\n })\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// defineWatcher\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Создаёт standalone-определение watcher'а.\n *\n * @example\n * ```ts\n * export const watchCount = defineWatcher<MyState>()({\n * selector: (s) => s.items.length,\n * notifyAfterSubscribe: true,\n * })\n * ```\n */\nexport function defineWatcher<TState extends Record<string, any>>() {\n return <R>(config: {\n selector: (state: TState) => R\n meta?: Record<string, any>\n shouldTrigger?: (prev: R | undefined, current: R) => boolean\n notifyAfterSubscribe?: boolean\n }): WatcherRecipe<TState, R> => ({\n _type: 'watcher-recipe' as const,\n _config: config,\n })\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// createApiActions\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Вычисляет путь к свойству через Proxy-перехват обращений.\n */\nfunction resolvePath<T>(accessor: (draft: T) => any): string[] {\n const path: string[] = []\n const handler: ProxyHandler<any> = {\n get(_, prop) {\n if (typeof prop === 'string') {\n path.push(prop)\n }\n return new Proxy({}, handler)\n },\n }\n accessor(new Proxy({}, handler) as T)\n return path\n}\n\n/**\n * Записывает значение по пути в объекте.\n */\nfunction setByPath(obj: any, path: string[], value: any): void {\n let current = obj\n for (let i = 0; i < path.length - 1; i++) {\n current = current[path[i]]\n }\n current[path[path.length - 1]] = value\n}\n\n/**\n * Создаёт набор шаблонных lifecycle-действий для API-запроса.\n * Accessor указывает на поле ApiRequestState в стейте — путь вычисляется автоматически.\n *\n * @param accessor - Функция-accessor, указывающая на поле ApiRequestState в стейте\n *\n * @example\n * ```ts\n * const listRequest = createApiActions<MyState>(\n * (draft) => draft.api.listRequest\n * )\n *\n * // В dispatcher:\n * createDispatcher({ storage }, {\n * loadListInit: listRequest.init,\n * loadListLoading: listRequest.loading,\n * loadListSuccess: listRequest.success,\n * loadListFailure: listRequest.failure,\n * loadListReset: listRequest.reset,\n * })\n * ```\n */\nexport function createApiActions<TState extends Record<string, any>>(accessor: (draft: TState) => ApiRequestState) {\n const path = resolvePath(accessor)\n const action = defineAction<TState>()\n\n const update = (storage: IStorage<TState>, request: ApiRequestState) => {\n storage.update((s) => setByPath(s, path, request))\n }\n\n return {\n init: action({\n action: (storage) => update(storage, { status: 'idle', error: null }),\n }),\n\n loading: action({\n action: (storage) => update(storage, { status: 'loading', error: null }),\n }),\n\n success: action({\n action: (storage) => update(storage, { status: 'success', error: null }),\n }),\n\n failure: action({\n action: (storage, error: string) => update(storage, { status: 'error', error }),\n }),\n\n reset: action({\n action: (storage) => update(storage, { status: 'reset', error: null }),\n }),\n }\n}\n"],"names":["defineAction","config","executionOptions","defineWatcher","resolvePath","accessor","path","handler","_","prop","Proxy","setByPath","obj","value","current","i","createApiActions","action","update","storage","request","s","error"],"mappings":"AA+CA,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACM,SAASA,YAAYA;IAC1B,OAAO,CACLC,QAIAC,mBAC4C;YAC5C,OAAO;YACP,SAASD;YACT,mBAAmBC;QACrB;AACF;AAEA,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;;;;;;;;;CAUC,GACM,SAASC,aAAaA;IAC3B,OAAO,CAAIF,SAKsB;YAC/B,OAAO;YACP,SAASA;QACX;AACF;AAEA,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;CAEC,GACD,SAASG,WAAWA,CAAIC,QAA2B;IACjD,MAAMC,OAAiB,EAAE;IACzB,MAAMC,UAA6B;QACjC,KAAIC,CAAC,EAAEC,IAAI;YACT,IAAI,OAAOA,SAAS,UAAU;gBAC5BH,KAAK,IAAI,CAACG;YACZ;YACA,OAAO,IAAIC,MAAM,CAAC,GAAGH;QACvB;IACF;IACAF,SAAS,IAAIK,MAAM,CAAC,GAAGH;IACvB,OAAOD;AACT;AAEA;;CAEC,GACD,SAASK,SAASA,CAACC,GAAQ,EAAEN,IAAc,EAAEO,KAAU;IACrD,IAAIC,UAAUF;IACd,IAAK,IAAIG,IAAI,GAAGA,IAAIT,KAAK,MAAM,GAAG,GAAGS,IAAK;QACxCD,UAAUA,OAAO,CAACR,IAAI,CAACS,EAAE,CAAC;IAC5B;IACAD,OAAO,CAACR,IAAI,CAACA,KAAK,MAAM,GAAG,EAAE,CAAC,GAAGO;AACnC;AAEA;;;;;;;;;;;;;;;;;;;;;CAqBC,GACM,SAASG,gBAAgBA,CAAqCX,QAA4C;IAC/G,MAAMC,OAAOF,WAAWA,CAACC;IACzB,MAAMY,SAASjB,YAAYA;IAE3B,MAAMkB,SAAS,CAACC,SAA2BC;QACzCD,QAAQ,MAAM,CAAC,CAACE,IAAMV,SAASA,CAACU,GAAGf,MAAMc;IAC3C;IAEA,OAAO;QACL,MAAMH,OAAO;YACX,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQ;oBAAQ,OAAO;gBAAK;QACrE;QAEA,SAASF,OAAO;YACd,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQ;oBAAW,OAAO;gBAAK;QACxE;QAEA,SAASF,OAAO;YACd,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQ;oBAAW,OAAO;gBAAK;QACxE;QAEA,SAASF,OAAO;YACd,QAAQ,CAACE,SAASG,QAAkBJ,OAAOC,SAAS;oBAAE,QAAQ;oBAASG;gBAAM;QAC/E;QAEA,OAAOL,OAAO;YACZ,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQ;oBAAS,OAAO;gBAAK;QACtE;IACF;AACF"}
1
+ {"version":3,"file":"reactive/dispatcher/standalone.js","sources":["../../../src/reactive/dispatcher/standalone.ts"],"sourcesContent":["import type { IStorage } from '../../core'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Параметры исполнения действия (мемоизация)\n */\nexport interface ActionExecutionOptions<TParams, TResult> {\n memoize?: (currentArgs: TParams, previousArgs: TParams, previousResult: TResult) => boolean\n}\n\n/**\n * Рецепт действия — standalone-определение, не привязанное к хранилищу.\n * Привязывается к storage при регистрации в createDispatcher.\n */\nexport interface ActionRecipe<TState extends Record<string, any>, TParams, TResult> {\n readonly _type: 'action-recipe'\n readonly _config: {\n action: (storage: IStorage<TState>, params: TParams) => Promise<TResult> | TResult\n meta?: Record<string, any>\n }\n readonly _executionOptions?: ActionExecutionOptions<TParams, TResult>\n}\n\n/**\n * Рецепт watcher'а — standalone-определение, не привязанное к хранилищу.\n */\nexport interface WatcherRecipe<TState extends Record<string, any>, R> {\n readonly _type: 'watcher-recipe'\n readonly _config: {\n selector: (state: TState) => R\n meta?: Record<string, any>\n shouldTrigger?: (prev: R | undefined, current: R) => boolean\n notifyAfterSubscribe?: boolean\n }\n}\n\n/**\n * Статусы жизненного цикла API-запроса.\n *\n * Сделано const-объектом (а не TS `enum`) намеренно: значения остаются обычными\n * строковыми литералами, поэтому `ApiStatus.Loading` и строка `'loading'`\n * взаимозаменяемы и тип обратно совместим с прежним строковым union'ом. С `enum`\n * это не так — его член не присваивается к литералу и наоборот.\n *\n * `ApiStatus` — одновременно значение (для `ApiStatus.Loading` в коде) и тип\n * (union всех статусов).\n */\nexport const ApiStatus = {\n Idle: 'idle',\n Loading: 'loading',\n Success: 'success',\n Error: 'error',\n Reset: 'reset',\n} as const\n\nexport type ApiStatus = (typeof ApiStatus)[keyof typeof ApiStatus]\n\n/**\n * Состояние API-запроса для createApiActions / createKeyedApiActions\n */\nexport interface ApiRequestState {\n status: ApiStatus\n error: string | null\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// defineAction\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Создаёт standalone-определение действия.\n * Фиксирует TState через первый вызов, TParams/TResult инферятся из action.\n *\n * @example\n * ```ts\n * const action = defineAction<MyState>()\n *\n * export const increment = action({\n * action: (storage, amount: number) => {\n * storage.update((s) => { s.count += amount })\n * return amount\n * },\n * })\n *\n * // Void action (без параметров и возврата):\n * export const reset = action({\n * action: (storage) => {\n * storage.update((s) => { s.count = 0 })\n * },\n * })\n * ```\n */\nexport function defineAction<TState extends Record<string, any>>() {\n return <TParams = void, TResult = void>(\n config: {\n action: (storage: IStorage<TState>, params: TParams) => Promise<TResult> | TResult\n meta?: Record<string, any>\n },\n executionOptions?: ActionExecutionOptions<TParams, TResult>,\n ): ActionRecipe<TState, TParams, TResult> => ({\n _type: 'action-recipe' as const,\n _config: config,\n _executionOptions: executionOptions,\n })\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// defineWatcher\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Создаёт standalone-определение watcher'а.\n *\n * @example\n * ```ts\n * export const watchCount = defineWatcher<MyState>()({\n * selector: (s) => s.items.length,\n * notifyAfterSubscribe: true,\n * })\n * ```\n */\nexport function defineWatcher<TState extends Record<string, any>>() {\n return <R>(config: {\n selector: (state: TState) => R\n meta?: Record<string, any>\n shouldTrigger?: (prev: R | undefined, current: R) => boolean\n notifyAfterSubscribe?: boolean\n }): WatcherRecipe<TState, R> => ({\n _type: 'watcher-recipe' as const,\n _config: config,\n })\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// createApiActions\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Вычисляет путь к свойству через Proxy-перехват обращений.\n */\nfunction resolvePath<T>(accessor: (draft: T) => any): string[] {\n const path: string[] = []\n const handler: ProxyHandler<any> = {\n get(_, prop) {\n if (typeof prop === 'string') {\n path.push(prop)\n }\n return new Proxy({}, handler)\n },\n }\n accessor(new Proxy({}, handler) as T)\n return path\n}\n\n/**\n * Записывает значение по пути в объекте.\n */\nfunction setByPath(obj: any, path: string[], value: any): void {\n let current = obj\n for (let i = 0; i < path.length - 1; i++) {\n current = current[path[i]]\n }\n current[path[path.length - 1]] = value\n}\n\n/**\n * Создаёт набор шаблонных lifecycle-действий для API-запроса.\n * Accessor указывает на поле ApiRequestState в стейте — путь вычисляется автоматически.\n *\n * @param accessor - Функция-accessor, указывающая на поле ApiRequestState в стейте\n *\n * @typeParam TInitPayload - Тип payload'а `init`-экшена. По умолчанию `void`\n * (init без параметров). Если задать — `init` принимает payload и возвращает\n * его, что удобно для intent-паттерна: эффект слушает `init` и читает payload\n * намерения (target, фильтры и т.п.), а статус при этом сбрасывается в `idle`.\n *\n * @example\n * ```ts\n * const listRequest = createApiActions<MyState>(\n * (draft) => draft.api.listRequest\n * )\n *\n * // В dispatcher:\n * createDispatcher({ storage }, {\n * loadListInit: listRequest.init,\n * loadListLoading: listRequest.loading,\n * loadListSuccess: listRequest.success,\n * loadListFailure: listRequest.failure,\n * loadListReset: listRequest.reset,\n * })\n *\n * // init с payload (intent): эффект получит { entityId } из возврата экшена.\n * const usersReq = createApiActions<MyState, { entityId: string }>(\n * (draft) => draft.api.usersRequest\n * )\n * ```\n */\nexport function createApiActions<TState extends Record<string, any>, TInitPayload = void>(accessor: (draft: TState) => ApiRequestState) {\n const path = resolvePath(accessor)\n const action = defineAction<TState>()\n\n const update = (storage: IStorage<TState>, request: ApiRequestState) => {\n storage.update((s) => setByPath(s, path, request))\n }\n\n return {\n init: action<TInitPayload, TInitPayload>({\n // Сбрасываем статус в idle и пробрасываем payload намерения дальше (эффектам).\n action: (storage, payload: TInitPayload) => {\n update(storage, { status: ApiStatus.Idle, error: null })\n return payload\n },\n }),\n\n loading: action({\n action: (storage) => update(storage, { status: ApiStatus.Loading, error: null }),\n }),\n\n success: action({\n action: (storage) => update(storage, { status: ApiStatus.Success, error: null }),\n }),\n\n failure: action({\n action: (storage, error: string) => update(storage, { status: ApiStatus.Error, error }),\n }),\n\n reset: action({\n action: (storage) => update(storage, { status: ApiStatus.Reset, error: null }),\n }),\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// createKeyedApiActions\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Keyed-вариант createApiActions: статус хранится ПО КЛЮЧУ в `Record<string,\n * ApiRequestState>`, а не один на весь запрос. Нужен, когда один и тот же запрос\n * летит параллельно для нескольких независимых ключей и у каждого свой\n * loading/error: комменты по таргетам, детали сущностей по id, per-row действия\n * в таблице/ленте, пагинация по секциям — всё, что лежит как `Record<key, data>`.\n *\n * Все статус-экшены принимают `key` (и возвращают его — удобно эффектам), кроме\n * `failure`, который принимает `{ key, error }`. Записи мутируются иммутабельно\n * по одному ключу — соседние ключи (их срезы) по ссылке не затрагиваются, что и\n * нужно для гранулярной изоляции ре-рендеров (см. useKeyedSliceSelector).\n *\n * @param accessor - Функция-accessor, указывающая на поле `Record<string, ApiRequestState>`\n *\n * @example\n * ```ts\n * const commentsReq = createKeyedApiActions<MyState>((d) => d.api.commentsRequest)\n *\n * createDispatcher({ storage }, {\n * commentsInit: commentsReq.init, // (key) => key\n * commentsLoading: commentsReq.loading, // (key) => key\n * commentsSuccess: commentsReq.success, // (key) => key\n * commentsFailure: commentsReq.failure, // ({ key, error })\n * commentsReset: commentsReq.reset, // (key) => key\n * })\n * ```\n */\nexport function createKeyedApiActions<TState extends Record<string, any>>(accessor: (draft: TState) => Record<string, ApiRequestState>) {\n const path = resolvePath(accessor)\n const action = defineAction<TState>()\n\n const write = (storage: IStorage<TState>, key: string, request: ApiRequestState) => {\n // [...path, key] → setByPath доходит до самого Record и кладёт значение по ключу\n storage.update((s) => setByPath(s, [...path, key], request))\n }\n\n const writer = (status: ApiStatus) =>\n action<string, string>({\n action: (storage, key: string) => {\n write(storage, key, { status, error: null })\n return key\n },\n })\n\n return {\n init: writer(ApiStatus.Idle),\n loading: writer(ApiStatus.Loading),\n success: writer(ApiStatus.Success),\n reset: writer(ApiStatus.Reset),\n\n failure: action<{ key: string; error: string }, { key: string; error: string }>({\n action: (storage, payload: { key: string; error: string }) => {\n write(storage, payload.key, { status: ApiStatus.Error, error: payload.error })\n return payload\n },\n }),\n }\n}\n"],"names":["ApiStatus","defineAction","config","executionOptions","defineWatcher","resolvePath","accessor","path","handler","_","prop","Proxy","setByPath","obj","value","current","i","createApiActions","action","update","storage","request","s","payload","error","createKeyedApiActions","write","key","writer","status"],"mappings":"AAuCA;;;;;;;;;;CAUC,GACM,MAAMA,SAASA,GAAG;IACvB,MAAM;IACN,SAAS;IACT,SAAS;IACT,OAAO;IACP,OAAO;AACT,EAAU;AAYV,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACM,SAASC,YAAYA;IAC1B,OAAO,CACLC,QAIAC,mBAC4C;YAC5C,OAAO;YACP,SAASD;YACT,mBAAmBC;QACrB;AACF;AAEA,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;;;;;;;;;CAUC,GACM,SAASC,aAAaA;IAC3B,OAAO,CAAIF,SAKsB;YAC/B,OAAO;YACP,SAASA;QACX;AACF;AAEA,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;CAEC,GACD,SAASG,WAAWA,CAAIC,QAA2B;IACjD,MAAMC,OAAiB,EAAE;IACzB,MAAMC,UAA6B;QACjC,KAAIC,CAAC,EAAEC,IAAI;YACT,IAAI,OAAOA,SAAS,UAAU;gBAC5BH,KAAK,IAAI,CAACG;YACZ;YACA,OAAO,IAAIC,MAAM,CAAC,GAAGH;QACvB;IACF;IACAF,SAAS,IAAIK,MAAM,CAAC,GAAGH;IACvB,OAAOD;AACT;AAEA;;CAEC,GACD,SAASK,SAASA,CAACC,GAAQ,EAAEN,IAAc,EAAEO,KAAU;IACrD,IAAIC,UAAUF;IACd,IAAK,IAAIG,IAAI,GAAGA,IAAIT,KAAK,MAAM,GAAG,GAAGS,IAAK;QACxCD,UAAUA,OAAO,CAACR,IAAI,CAACS,EAAE,CAAC;IAC5B;IACAD,OAAO,CAACR,IAAI,CAACA,KAAK,MAAM,GAAG,EAAE,CAAC,GAAGO;AACnC;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BC,GACM,SAASG,gBAAgBA,CAA0DX,QAA4C;IACpI,MAAMC,OAAOF,WAAWA,CAACC;IACzB,MAAMY,SAASjB,YAAYA;IAE3B,MAAMkB,SAAS,CAACC,SAA2BC;QACzCD,QAAQ,MAAM,CAAC,CAACE,IAAMV,SAASA,CAACU,GAAGf,MAAMc;IAC3C;IAEA,OAAO;QACL,MAAMH,OAAmC;YACvC,+EAA+E;YAC/E,QAAQ,CAACE,SAASG;gBAChBJ,OAAOC,SAAS;oBAAE,QAAQpB,SAASA,CAAC,IAAI;oBAAE,OAAO;gBAAK;gBACtD,OAAOuB;YACT;QACF;QAEA,SAASL,OAAO;YACd,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQpB,SAASA,CAAC,OAAO;oBAAE,OAAO;gBAAK;QAChF;QAEA,SAASkB,OAAO;YACd,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQpB,SAASA,CAAC,OAAO;oBAAE,OAAO;gBAAK;QAChF;QAEA,SAASkB,OAAO;YACd,QAAQ,CAACE,SAASI,QAAkBL,OAAOC,SAAS;oBAAE,QAAQpB,SAASA,CAAC,KAAK;oBAAEwB;gBAAM;QACvF;QAEA,OAAON,OAAO;YACZ,QAAQ,CAACE,UAAYD,OAAOC,SAAS;oBAAE,QAAQpB,SAASA,CAAC,KAAK;oBAAE,OAAO;gBAAK;QAC9E;IACF;AACF;AAEA,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACM,SAASyB,qBAAqBA,CAAqCnB,QAA4D;IACpI,MAAMC,OAAOF,WAAWA,CAACC;IACzB,MAAMY,SAASjB,YAAYA;IAE3B,MAAMyB,QAAQ,CAACN,SAA2BO,KAAaN;QACrD,iFAAiF;QACjFD,QAAQ,MAAM,CAAC,CAACE,IAAMV,SAASA,CAACU,GAAG;mBAAIf;gBAAMoB;aAAI,EAAEN;IACrD;IAEA,MAAMO,SAAS,CAACC,SACdX,OAAuB;YACrB,QAAQ,CAACE,SAASO;gBAChBD,MAAMN,SAASO,KAAK;oBAAEE;oBAAQ,OAAO;gBAAK;gBAC1C,OAAOF;YACT;QACF;IAEF,OAAO;QACL,MAAMC,OAAO5B,SAASA,CAAC,IAAI;QAC3B,SAAS4B,OAAO5B,SAASA,CAAC,OAAO;QACjC,SAAS4B,OAAO5B,SAASA,CAAC,OAAO;QACjC,OAAO4B,OAAO5B,SAASA,CAAC,KAAK;QAE7B,SAASkB,OAAuE;YAC9E,QAAQ,CAACE,SAASG;gBAChBG,MAAMN,SAASG,QAAQ,GAAG,EAAE;oBAAE,QAAQvB,SAASA,CAAC,KAAK;oBAAE,OAAOuB,QAAQ,KAAK;gBAAC;gBAC5E,OAAOA;YACT;QACF;IACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synapse-storage",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Набор инструментов для управления состоянием и апи-запросами",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",