synapse-storage 3.0.4 → 3.0.7
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.
- package/README.md +30 -64
- package/dist/api.cjs +890 -1
- package/dist/api.js +6 -1
- package/dist/core.cjs +2420 -1
- package/dist/core.js +2 -1
- package/dist/index.cjs +4179 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -1
- package/dist/react.cjs +267 -1
- package/dist/react.js +2 -1
- package/dist/reactive.cjs +642 -1
- package/dist/reactive.js +2 -1
- package/dist/utils.cjs +600 -1
- package/dist/utils.js +4 -1
- package/package.json +12 -10
- package/dist/api.d.cts +0 -365
- package/dist/chunk-22J2S57D.cjs +0 -1
- package/dist/chunk-4USKKL5R.js +0 -1
- package/dist/chunk-5X65PSGD.js +0 -1
- package/dist/chunk-635Q6YJZ.cjs +0 -1
- package/dist/chunk-6RNZVHSR.js +0 -1
- package/dist/chunk-FW5NGLPP.cjs +0 -1
- package/dist/chunk-IPUPRMZK.js +0 -1
- package/dist/chunk-NMDHQXMS.cjs +0 -1
- package/dist/chunk-S7X7IDBT.js +0 -1
- package/dist/chunk-UFBCZ25Y.cjs +0 -1
- package/dist/chunk-VSIVOWZF.cjs +0 -1
- package/dist/chunk-WC5TDS6C.cjs +0 -1
- package/dist/chunk-WQNH3LVB.js +0 -1
- package/dist/chunk-ZE2EJX2Y.js +0 -1
- package/dist/core.d.cts +0 -397
- package/dist/dispatcher.module-CdpmkplA.d.cts +0 -363
- package/dist/index.d.cts +0 -10
- package/dist/react.d.cts +0 -74
- package/dist/reactive.d.cts +0 -35
- package/dist/selector.interface-CA5y-kD_.d.cts +0 -63
- package/dist/storage.interface-Dl8SLUd1.d.cts +0 -128
- package/dist/utils.d.cts +0 -92
package/dist/index.cjs
CHANGED
|
@@ -1 +1,4179 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
ApiClient: () => ApiClient,
|
|
24
|
+
Dispatcher: () => Dispatcher,
|
|
25
|
+
EffectsModule: () => EffectsModule,
|
|
26
|
+
IndexedDBStorage: () => IndexedDBStorage,
|
|
27
|
+
LocalStorage: () => LocalStorage,
|
|
28
|
+
MemoryStorage: () => MemoryStorage,
|
|
29
|
+
ResponseFormat: () => ResponseFormat,
|
|
30
|
+
SelectorModule: () => SelectorModule,
|
|
31
|
+
StorageEvents: () => StorageEvents,
|
|
32
|
+
StoragePluginModule: () => StoragePluginModule,
|
|
33
|
+
apiLogger: () => apiLogger,
|
|
34
|
+
broadcastMiddleware: () => broadcastMiddleware,
|
|
35
|
+
combineEffects: () => combineEffects,
|
|
36
|
+
createDispatcher: () => createDispatcher,
|
|
37
|
+
createEffect: () => createEffect,
|
|
38
|
+
createEffectBase: () => createEffectBase,
|
|
39
|
+
createSynapse: () => createSynapse,
|
|
40
|
+
createSynapseCtx: () => createSynapseCtx,
|
|
41
|
+
createUniqueId: () => createUniqueId,
|
|
42
|
+
headersToObject: () => headersToObject,
|
|
43
|
+
loggerDispatcherMiddleware: () => loggerDispatcherMiddleware,
|
|
44
|
+
ofType: () => ofType,
|
|
45
|
+
ofTypes: () => ofTypes,
|
|
46
|
+
ofTypesWaitAll: () => ofTypesWaitAll,
|
|
47
|
+
selectorMap: () => selectorMap,
|
|
48
|
+
selectorObject: () => selectorObject,
|
|
49
|
+
useSelector: () => useSelector,
|
|
50
|
+
useStorageSubscribe: () => useStorageSubscribe,
|
|
51
|
+
validateMap: () => validateMap
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(src_exports);
|
|
54
|
+
|
|
55
|
+
// src/api/utils/api-helpers.ts
|
|
56
|
+
var apiLogger = {
|
|
57
|
+
debug: (message, ...args) => {
|
|
58
|
+
if (process.env.NODE_ENV !== "production") {
|
|
59
|
+
console.debug(`[API] ${message}`, ...args);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
log: (message, ...args) => {
|
|
63
|
+
if (process.env.NODE_ENV !== "production") {
|
|
64
|
+
console.log(`[API] ${message}`, ...args);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
info: (message, ...args) => {
|
|
68
|
+
console.info(`[API] ${message}`, ...args);
|
|
69
|
+
},
|
|
70
|
+
warn: (message, ...args) => {
|
|
71
|
+
console.warn(`[API] ${message}`, ...args);
|
|
72
|
+
},
|
|
73
|
+
error: (message, ...args) => {
|
|
74
|
+
console.error(`[API] ${message}`, ...args);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
function createUniqueId(name) {
|
|
78
|
+
return `${name ? `${name}|` : ""}${Math.random().toString(36).substring(2, 9) + Date.now().toString(36)}`;
|
|
79
|
+
}
|
|
80
|
+
function headersToObject(headers) {
|
|
81
|
+
const result = {};
|
|
82
|
+
headers.forEach((value, key) => {
|
|
83
|
+
result[key.toLowerCase()] = value;
|
|
84
|
+
});
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/api/utils/create-header-context.ts
|
|
89
|
+
function createHeaderContext(context = {}, optionContext = {}) {
|
|
90
|
+
return {
|
|
91
|
+
...context,
|
|
92
|
+
...optionContext,
|
|
93
|
+
getFromStorage: context.getFromStorage || ((key) => {
|
|
94
|
+
try {
|
|
95
|
+
const item = localStorage.getItem(key);
|
|
96
|
+
return item ? JSON.parse(item) : void 0;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.warn(`[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u0447\u0442\u0435\u043D\u0438\u044F \u0438\u0437 localStorage: ${error}`);
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
101
|
+
}),
|
|
102
|
+
getCookie: context.getCookie || ((name) => {
|
|
103
|
+
try {
|
|
104
|
+
const matches = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1")}=([^;]*)`));
|
|
105
|
+
return matches ? decodeURIComponent(matches[1]) : void 0;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.warn(`[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u0447\u0442\u0435\u043D\u0438\u044F cookie: ${error}`);
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/api/utils/endpoint-headers.ts
|
|
115
|
+
async function prepareRequestHeaders(prepareHeadersFn, context) {
|
|
116
|
+
let headers = new Headers();
|
|
117
|
+
const headerContext = context || createHeaderContext({}, {});
|
|
118
|
+
if (prepareHeadersFn) {
|
|
119
|
+
try {
|
|
120
|
+
headers = await Promise.resolve(prepareHeadersFn(headers, headerContext));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn("[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0435 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u043E\u0432", error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return headers;
|
|
126
|
+
}
|
|
127
|
+
function createPrepareHeaders(globalPrepareHeaders, endpointPrepareHeaders) {
|
|
128
|
+
return async (headers, context) => {
|
|
129
|
+
let processedHeaders = new Headers(headers);
|
|
130
|
+
if (globalPrepareHeaders) {
|
|
131
|
+
try {
|
|
132
|
+
processedHeaders = await Promise.resolve(globalPrepareHeaders(processedHeaders, context));
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn("[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0435 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0445 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u043E\u0432", error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (endpointPrepareHeaders) {
|
|
138
|
+
try {
|
|
139
|
+
processedHeaders = await Promise.resolve(endpointPrepareHeaders(processedHeaders, context));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn("[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0435 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u043E\u0432 \u044D\u043D\u0434\u043F\u043E\u0438\u043D\u0442\u0430", error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return processedHeaders;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/api/types/api.interface.ts
|
|
149
|
+
var ResponseFormat = /* @__PURE__ */ ((ResponseFormat2) => {
|
|
150
|
+
ResponseFormat2["Json"] = "json";
|
|
151
|
+
ResponseFormat2["Blob"] = "blob";
|
|
152
|
+
ResponseFormat2["ArrayBuffer"] = "arrayBuffer";
|
|
153
|
+
ResponseFormat2["Text"] = "text";
|
|
154
|
+
ResponseFormat2["FormData"] = "formData";
|
|
155
|
+
ResponseFormat2["Raw"] = "raw";
|
|
156
|
+
return ResponseFormat2;
|
|
157
|
+
})(ResponseFormat || {});
|
|
158
|
+
|
|
159
|
+
// src/api/utils/file-utils.ts
|
|
160
|
+
function getResponseFormatForMimeType(contentType) {
|
|
161
|
+
const type = contentType.toLowerCase().split(";")[0].trim();
|
|
162
|
+
if (type.includes("application/json")) {
|
|
163
|
+
return "json" /* Json */;
|
|
164
|
+
}
|
|
165
|
+
if (type.includes("text/")) {
|
|
166
|
+
return "text" /* Text */;
|
|
167
|
+
}
|
|
168
|
+
if (type.includes("multipart/form-data")) {
|
|
169
|
+
return "formData" /* FormData */;
|
|
170
|
+
}
|
|
171
|
+
if (type.includes("application/octet-stream") || type.includes("application/pdf") || type.includes("image/") || type.includes("audio/") || type.includes("video/")) {
|
|
172
|
+
return "blob" /* Blob */;
|
|
173
|
+
}
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function isFileResponse(headers) {
|
|
177
|
+
const contentType = headers.get("content-type") || "";
|
|
178
|
+
const contentDisposition = headers.get("content-disposition") || "";
|
|
179
|
+
const isFileContentType = contentType.includes("application/octet-stream") || contentType.includes("application/pdf") || contentType.includes("image/") || contentType.includes("audio/") || contentType.includes("video/");
|
|
180
|
+
const isAttachment = contentDisposition.includes("attachment") || contentDisposition.includes("filename=");
|
|
181
|
+
return isFileContentType || isAttachment;
|
|
182
|
+
}
|
|
183
|
+
function extractFilenameFromHeaders(headers) {
|
|
184
|
+
const contentDisposition = headers.get("content-disposition");
|
|
185
|
+
if (!contentDisposition) return void 0;
|
|
186
|
+
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
|
187
|
+
if (filenameMatch && filenameMatch[1]) {
|
|
188
|
+
return filenameMatch[1].replace(/['"]/g, "").trim();
|
|
189
|
+
}
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
function getFileMetadataFromHeaders(headers) {
|
|
193
|
+
const contentType = headers.get("content-type") || "";
|
|
194
|
+
const contentDisposition = headers.get("content-disposition") || "";
|
|
195
|
+
const contentLength = headers.get("content-length");
|
|
196
|
+
if (!isFileResponse(headers)) {
|
|
197
|
+
return void 0;
|
|
198
|
+
}
|
|
199
|
+
const filename = extractFilenameFromHeaders(headers);
|
|
200
|
+
return {
|
|
201
|
+
filename,
|
|
202
|
+
contentType,
|
|
203
|
+
contentDisposition,
|
|
204
|
+
size: contentLength ? parseInt(contentLength, 10) : void 0
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/api/utils/fetch-base-query.ts
|
|
209
|
+
async function getResponseData(response, format) {
|
|
210
|
+
let responseFormat = format;
|
|
211
|
+
const contentType = response.headers.get("content-type") || "";
|
|
212
|
+
if (!responseFormat && contentType) {
|
|
213
|
+
if (isFileResponse(response.headers)) {
|
|
214
|
+
responseFormat = "blob" /* Blob */;
|
|
215
|
+
} else {
|
|
216
|
+
responseFormat = getResponseFormatForMimeType(contentType);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!responseFormat) {
|
|
220
|
+
responseFormat = "json" /* Json */;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
let fileMetadata;
|
|
224
|
+
if (responseFormat === "blob" /* Blob */ || responseFormat === "arrayBuffer" /* ArrayBuffer */) {
|
|
225
|
+
fileMetadata = getFileMetadataFromHeaders(response.headers);
|
|
226
|
+
}
|
|
227
|
+
switch (responseFormat) {
|
|
228
|
+
case "json" /* Json */: {
|
|
229
|
+
try {
|
|
230
|
+
const data = await response.json();
|
|
231
|
+
return response.ok ? { data, fileMetadata } : { error: data, fileMetadata };
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const text = await response.text();
|
|
234
|
+
return response.ok ? { data: text, fileMetadata } : { error: text, fileMetadata };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
case "text" /* Text */: {
|
|
238
|
+
const text = await response.text();
|
|
239
|
+
return response.ok ? { data: text, fileMetadata } : { error: text, fileMetadata };
|
|
240
|
+
}
|
|
241
|
+
case "blob" /* Blob */: {
|
|
242
|
+
const blob2 = await response.blob();
|
|
243
|
+
return response.ok ? { data: blob2, fileMetadata } : { error: blob2, fileMetadata };
|
|
244
|
+
}
|
|
245
|
+
case "arrayBuffer" /* ArrayBuffer */: {
|
|
246
|
+
const buffer = await response.arrayBuffer();
|
|
247
|
+
return response.ok ? { data: buffer, fileMetadata } : { error: buffer, fileMetadata };
|
|
248
|
+
}
|
|
249
|
+
case "formData" /* FormData */: {
|
|
250
|
+
const formData = await response.formData();
|
|
251
|
+
return response.ok ? { data: formData, fileMetadata } : { error: formData, fileMetadata };
|
|
252
|
+
}
|
|
253
|
+
case "raw" /* Raw */: {
|
|
254
|
+
return response.ok ? { data: response, fileMetadata } : { error: response, fileMetadata };
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
const blob = await response.blob();
|
|
258
|
+
return response.ok ? { data: blob, fileMetadata } : { error: blob, fileMetadata };
|
|
259
|
+
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error(`[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u0437\u0432\u043B\u0435\u0447\u0435\u043D\u0438\u044F \u0434\u0430\u043D\u043D\u044B\u0445 \u0438\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 (\u0444\u043E\u0440\u043C\u0430\u0442: ${responseFormat})`, err);
|
|
262
|
+
return response.ok ? { data: void 0 } : { error: err };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function fetchBaseQuery(options) {
|
|
266
|
+
const { baseUrl, timeout = 3e4, fetchFn = fetch, credentials = "same-origin" } = options;
|
|
267
|
+
return async (args, queryOptions = {}, headers) => {
|
|
268
|
+
const { path, method, body, query, responseFormat: reqResponseFormat } = args;
|
|
269
|
+
const { signal, timeout: requestTimeout = timeout, responseFormat: optResponseFormat } = queryOptions;
|
|
270
|
+
const responseFormat = optResponseFormat || reqResponseFormat;
|
|
271
|
+
const url = new URL(path.startsWith("http") ? path : `${baseUrl}${path}`);
|
|
272
|
+
if (query) {
|
|
273
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
274
|
+
if (value !== void 0 && value !== null) {
|
|
275
|
+
if (Array.isArray(value)) {
|
|
276
|
+
value.forEach((item) => url.searchParams.append(key, String(item)));
|
|
277
|
+
} else {
|
|
278
|
+
url.searchParams.append(key, String(value));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
let serializedBody;
|
|
284
|
+
if (body !== void 0) {
|
|
285
|
+
if (body instanceof FormData || body instanceof Blob) {
|
|
286
|
+
serializedBody = body;
|
|
287
|
+
} else if (typeof body === "object" && body !== null) {
|
|
288
|
+
try {
|
|
289
|
+
serializedBody = JSON.stringify(body);
|
|
290
|
+
if (!headers.has("Content-Type")) {
|
|
291
|
+
headers.set("Content-Type", "application/json");
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error("[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0435\u043B\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u0430", error);
|
|
295
|
+
serializedBody = String(body);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
serializedBody = String(body);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
let timeoutId;
|
|
302
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
303
|
+
if (requestTimeout) {
|
|
304
|
+
timeoutId = window.setTimeout(() => {
|
|
305
|
+
reject(new Error(`\u041F\u0440\u0435\u0432\u044B\u0448\u0435\u043D\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430 (${requestTimeout}\u043C\u0441)`));
|
|
306
|
+
}, requestTimeout);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
try {
|
|
310
|
+
const fetchPromise = fetchFn(url.toString(), {
|
|
311
|
+
method,
|
|
312
|
+
headers,
|
|
313
|
+
body: serializedBody,
|
|
314
|
+
signal,
|
|
315
|
+
credentials
|
|
316
|
+
});
|
|
317
|
+
const response = await Promise.race([fetchPromise, timeoutPromise]);
|
|
318
|
+
const { data, error, fileMetadata } = await getResponseData(response, responseFormat);
|
|
319
|
+
const result = {
|
|
320
|
+
data,
|
|
321
|
+
error,
|
|
322
|
+
ok: response.ok,
|
|
323
|
+
status: response.status,
|
|
324
|
+
statusText: response.statusText,
|
|
325
|
+
headers: response.headers,
|
|
326
|
+
fileDownloadResult: fileMetadata
|
|
327
|
+
};
|
|
328
|
+
return result;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
const error = err;
|
|
331
|
+
console.error("[API] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430", error);
|
|
332
|
+
return {
|
|
333
|
+
error,
|
|
334
|
+
ok: false,
|
|
335
|
+
status: 0,
|
|
336
|
+
statusText: error.message,
|
|
337
|
+
headers: new Headers()
|
|
338
|
+
};
|
|
339
|
+
} finally {
|
|
340
|
+
if (timeoutId) {
|
|
341
|
+
window.clearTimeout(timeoutId);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/api/utils/get-cacheable-headers.ts
|
|
348
|
+
function getCacheableHeaders(headers, cacheableHeaders = []) {
|
|
349
|
+
const result = {};
|
|
350
|
+
if (!headers || cacheableHeaders.length === 0) {
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
cacheableHeaders.forEach((key) => {
|
|
354
|
+
if (headers.has(key)) {
|
|
355
|
+
result[key] = headers.get(key) || "";
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/api/components/endpoint.ts
|
|
362
|
+
var EndpointClass = class {
|
|
363
|
+
constructor(name, queryStorage, configCurrentEndpoint, cacheableHeaderKeys, globalCacheConfig, baseQueryConfig) {
|
|
364
|
+
this.name = name;
|
|
365
|
+
this.queryStorage = queryStorage;
|
|
366
|
+
this.configCurrentEndpoint = configCurrentEndpoint;
|
|
367
|
+
this.cacheableHeaderKeys = cacheableHeaderKeys;
|
|
368
|
+
this.globalCacheConfig = globalCacheConfig;
|
|
369
|
+
this.baseQueryConfig = baseQueryConfig;
|
|
370
|
+
this.prepareHeaders = createPrepareHeaders(baseQueryConfig.prepareHeaders, configCurrentEndpoint.prepareHeaders);
|
|
371
|
+
this.queryFunction = fetchBaseQuery({
|
|
372
|
+
baseUrl: baseQueryConfig.baseUrl,
|
|
373
|
+
fetchFn: baseQueryConfig.fetchFn,
|
|
374
|
+
timeout: baseQueryConfig.timeout,
|
|
375
|
+
credentials: baseQueryConfig.credentials
|
|
376
|
+
});
|
|
377
|
+
this.cacheableHeaders = [...cacheableHeaderKeys || [], ...configCurrentEndpoint.includeCacheableHeaderKeys || []].filter(
|
|
378
|
+
(key) => !configCurrentEndpoint.excludeCacheableHeaderKeys?.includes(key)
|
|
379
|
+
);
|
|
380
|
+
this.meta.name = name;
|
|
381
|
+
this.meta.tags = configCurrentEndpoint.tags ?? this.meta.tags;
|
|
382
|
+
this.meta.invalidatesTags = configCurrentEndpoint.invalidatesTags ?? this.meta.invalidatesTags;
|
|
383
|
+
this.meta.cache = this.queryStorage.createCacheConfig(this.configCurrentEndpoint) ?? this.meta.cache;
|
|
384
|
+
}
|
|
385
|
+
endpointSubscribers = /* @__PURE__ */ new Set();
|
|
386
|
+
/** Сколько раз был вызван метод request */
|
|
387
|
+
fetchCounts = 0;
|
|
388
|
+
meta = {
|
|
389
|
+
cache: false,
|
|
390
|
+
invalidatesTags: [],
|
|
391
|
+
name: "",
|
|
392
|
+
tags: []
|
|
393
|
+
};
|
|
394
|
+
queryFunction;
|
|
395
|
+
/** Массив заголовков, которые нужно включить в ключ кэширования */
|
|
396
|
+
cacheableHeaders;
|
|
397
|
+
prepareHeaders;
|
|
398
|
+
request(params, options) {
|
|
399
|
+
this.fetchCounts++;
|
|
400
|
+
const requestId = createUniqueId(this.name);
|
|
401
|
+
const controller = new AbortController();
|
|
402
|
+
const requestSubscribers = /* @__PURE__ */ new Set();
|
|
403
|
+
const currentState = {
|
|
404
|
+
status: "idle",
|
|
405
|
+
requestParams: params,
|
|
406
|
+
headers: {},
|
|
407
|
+
error: void 0,
|
|
408
|
+
data: void 0,
|
|
409
|
+
fromCache: false
|
|
410
|
+
};
|
|
411
|
+
const headerContext = createHeaderContext({ requestParams: params }, options?.context || {});
|
|
412
|
+
const notifyRequestSubscribers = (newState) => {
|
|
413
|
+
Object.assign(currentState, newState);
|
|
414
|
+
requestSubscribers.forEach((cb) => {
|
|
415
|
+
cb({ ...currentState });
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
const waitPromise = new Promise(async (resolve, reject) => {
|
|
419
|
+
try {
|
|
420
|
+
const headers = await prepareRequestHeaders(this.prepareHeaders, headerContext);
|
|
421
|
+
const headersForCache = getCacheableHeaders(headers, options?.cacheableHeaderKeys ? options.cacheableHeaderKeys : this.cacheableHeaders);
|
|
422
|
+
const shouldCache = this.queryStorage.shouldCache(this.configCurrentEndpoint, options);
|
|
423
|
+
const [cacheKey, cacheParams] = this.queryStorage.createCacheKey(this.name, { ...params, ...headersForCache });
|
|
424
|
+
let cachedResult;
|
|
425
|
+
if (shouldCache) {
|
|
426
|
+
cachedResult = await this.queryStorage.getCachedResult(cacheKey);
|
|
427
|
+
}
|
|
428
|
+
if (cachedResult) {
|
|
429
|
+
notifyRequestSubscribers({
|
|
430
|
+
fromCache: true,
|
|
431
|
+
status: "success",
|
|
432
|
+
data: cachedResult.data,
|
|
433
|
+
error: void 0,
|
|
434
|
+
headers: cachedResult.headers,
|
|
435
|
+
requestParams: params
|
|
436
|
+
});
|
|
437
|
+
resolve({
|
|
438
|
+
...cachedResult,
|
|
439
|
+
fromCache: true
|
|
440
|
+
});
|
|
441
|
+
} else {
|
|
442
|
+
notifyRequestSubscribers({
|
|
443
|
+
fromCache: false,
|
|
444
|
+
status: "loading"
|
|
445
|
+
});
|
|
446
|
+
const requestDefinition = this.configCurrentEndpoint.request(params, options?.context);
|
|
447
|
+
const mergedOptions = { ...options, signal: controller.signal };
|
|
448
|
+
const response = await this.queryFunction(requestDefinition, mergedOptions, headers);
|
|
449
|
+
if (response.ok) {
|
|
450
|
+
const { headers: headers2, ...restResponse } = response;
|
|
451
|
+
if (shouldCache) {
|
|
452
|
+
const currentCacheConfig = this.queryStorage.createCacheConfig(this.configCurrentEndpoint);
|
|
453
|
+
await this.queryStorage.setCachedResult(
|
|
454
|
+
cacheKey,
|
|
455
|
+
{ ...restResponse, headers: headersToObject(headers2) },
|
|
456
|
+
currentCacheConfig,
|
|
457
|
+
cacheParams ?? {},
|
|
458
|
+
this.configCurrentEndpoint.tags ?? [],
|
|
459
|
+
this.configCurrentEndpoint.invalidatesTags ?? []
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
notifyRequestSubscribers({
|
|
463
|
+
fromCache: false,
|
|
464
|
+
status: "success",
|
|
465
|
+
data: response.data,
|
|
466
|
+
error: void 0,
|
|
467
|
+
headers: response.headers,
|
|
468
|
+
requestParams: params
|
|
469
|
+
});
|
|
470
|
+
this.endpointSubscribers.forEach((cb) => {
|
|
471
|
+
const endpointState = {
|
|
472
|
+
status: "success",
|
|
473
|
+
fetchCounts: this.fetchCounts,
|
|
474
|
+
meta: this.meta,
|
|
475
|
+
cacheableHeaders: this.cacheableHeaders,
|
|
476
|
+
error: void 0
|
|
477
|
+
};
|
|
478
|
+
cb(endpointState);
|
|
479
|
+
});
|
|
480
|
+
resolve({
|
|
481
|
+
...response,
|
|
482
|
+
fromCache: false
|
|
483
|
+
});
|
|
484
|
+
} else {
|
|
485
|
+
notifyRequestSubscribers({
|
|
486
|
+
fromCache: false,
|
|
487
|
+
status: "error",
|
|
488
|
+
data: void 0,
|
|
489
|
+
error: response.error,
|
|
490
|
+
headers: response.headers,
|
|
491
|
+
requestParams: params
|
|
492
|
+
});
|
|
493
|
+
this.endpointSubscribers.forEach((cb) => {
|
|
494
|
+
const endpointState = {
|
|
495
|
+
status: "error",
|
|
496
|
+
fetchCounts: this.fetchCounts,
|
|
497
|
+
meta: this.meta,
|
|
498
|
+
cacheableHeaders: this.cacheableHeaders,
|
|
499
|
+
error: response.error
|
|
500
|
+
};
|
|
501
|
+
cb(endpointState);
|
|
502
|
+
});
|
|
503
|
+
reject(response.error);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
notifyRequestSubscribers({
|
|
508
|
+
fromCache: false,
|
|
509
|
+
status: "error",
|
|
510
|
+
data: void 0,
|
|
511
|
+
error,
|
|
512
|
+
headers: void 0,
|
|
513
|
+
requestParams: params
|
|
514
|
+
});
|
|
515
|
+
reject(error);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
return {
|
|
519
|
+
id: requestId,
|
|
520
|
+
subscribe(listener, options2 = {}) {
|
|
521
|
+
const { autoUnsubscribe = true } = options2;
|
|
522
|
+
requestSubscribers.add(listener);
|
|
523
|
+
listener(currentState);
|
|
524
|
+
const unsubscribe = () => requestSubscribers.delete(listener);
|
|
525
|
+
if (autoUnsubscribe) {
|
|
526
|
+
waitPromise.finally(() => {
|
|
527
|
+
unsubscribe();
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return unsubscribe;
|
|
531
|
+
},
|
|
532
|
+
wait: () => waitPromise,
|
|
533
|
+
waitWithCallbacks(handlers = {}) {
|
|
534
|
+
const { idle, loading, success, error } = handlers;
|
|
535
|
+
this.subscribe(
|
|
536
|
+
(state) => {
|
|
537
|
+
switch (state.status) {
|
|
538
|
+
case "idle":
|
|
539
|
+
idle?.(state);
|
|
540
|
+
break;
|
|
541
|
+
case "loading":
|
|
542
|
+
loading?.(state);
|
|
543
|
+
break;
|
|
544
|
+
case "success":
|
|
545
|
+
success?.(state.data, state);
|
|
546
|
+
break;
|
|
547
|
+
case "error":
|
|
548
|
+
error?.(state.error, state);
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
{ autoUnsubscribe: true }
|
|
553
|
+
);
|
|
554
|
+
return waitPromise;
|
|
555
|
+
},
|
|
556
|
+
abort: () => {
|
|
557
|
+
if (controller && !controller.signal.aborted) {
|
|
558
|
+
controller.abort();
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
then: (onfulfilled, onrejected) => waitPromise.then(onfulfilled, onrejected),
|
|
562
|
+
catch: (onrejected) => waitPromise.catch(onrejected),
|
|
563
|
+
finally: (onfinally) => waitPromise.finally(onfinally)
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
subscribe(cb) {
|
|
567
|
+
this.endpointSubscribers.add(cb);
|
|
568
|
+
const currentState = {
|
|
569
|
+
status: "idle",
|
|
570
|
+
fetchCounts: this.fetchCounts,
|
|
571
|
+
meta: this.meta,
|
|
572
|
+
cacheableHeaders: this.cacheableHeaders,
|
|
573
|
+
error: void 0
|
|
574
|
+
};
|
|
575
|
+
cb(currentState);
|
|
576
|
+
return () => this.endpointSubscribers.delete(cb);
|
|
577
|
+
}
|
|
578
|
+
reset() {
|
|
579
|
+
this.fetchCounts = 0;
|
|
580
|
+
return Promise.resolve();
|
|
581
|
+
}
|
|
582
|
+
destroy() {
|
|
583
|
+
this.endpointSubscribers.clear();
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// src/core/storage/utils/storage-key.ts
|
|
588
|
+
var StorageKey = class {
|
|
589
|
+
constructor(value, isRawKey = false) {
|
|
590
|
+
this.value = value;
|
|
591
|
+
this.isRawKey = isRawKey;
|
|
592
|
+
}
|
|
593
|
+
toString() {
|
|
594
|
+
return this.value;
|
|
595
|
+
}
|
|
596
|
+
toJSON() {
|
|
597
|
+
return this.value;
|
|
598
|
+
}
|
|
599
|
+
valueOf() {
|
|
600
|
+
return this.value;
|
|
601
|
+
}
|
|
602
|
+
isUnparseable() {
|
|
603
|
+
return this.isRawKey;
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/core/storage/utils/cache.util.ts
|
|
608
|
+
var CacheUtils = class {
|
|
609
|
+
static createMetadata(ttl = 0, tags = []) {
|
|
610
|
+
const now = Date.now();
|
|
611
|
+
const expiresAt = ttl > 0 ? now + ttl : Infinity;
|
|
612
|
+
return {
|
|
613
|
+
createdAt: now,
|
|
614
|
+
updatedAt: now,
|
|
615
|
+
expiresAt,
|
|
616
|
+
tags,
|
|
617
|
+
createdAtDateTime: this.formatDateTime(now),
|
|
618
|
+
updatedAtDateTime: this.formatDateTime(now),
|
|
619
|
+
expiresAtDateTime: expiresAt === Infinity ? "never" : this.formatDateTime(expiresAt)
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
static formatDateTime(timestamp) {
|
|
623
|
+
return new Date(timestamp).toISOString();
|
|
624
|
+
}
|
|
625
|
+
static isExpired(metadata) {
|
|
626
|
+
return Date.now() > metadata.expiresAt;
|
|
627
|
+
}
|
|
628
|
+
static updateMetadata(metadata) {
|
|
629
|
+
return {
|
|
630
|
+
...metadata,
|
|
631
|
+
updatedAt: Date.now()
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
static createKey(...parts) {
|
|
635
|
+
return new StorageKey(parts.join("_"));
|
|
636
|
+
}
|
|
637
|
+
static createApiKey(endpoint, params) {
|
|
638
|
+
if (!params) return [new StorageKey(endpoint, true), params];
|
|
639
|
+
const sortedParams = Object.entries(params).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join("&");
|
|
640
|
+
return [new StorageKey(`${endpoint}_${sortedParams}`, true), params];
|
|
641
|
+
}
|
|
642
|
+
// Функция для проверки, есть ли у записи определенные теги
|
|
643
|
+
static hasAnyTag(metadata, tags = []) {
|
|
644
|
+
if (!metadata.tags || !tags.length) return false;
|
|
645
|
+
return tags.some((tag) => metadata.tags?.includes(tag));
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/api/components/query-storage.ts
|
|
650
|
+
var QueryStorage = class {
|
|
651
|
+
constructor(storageExternal, globalCacheConfig) {
|
|
652
|
+
this.storageExternal = storageExternal;
|
|
653
|
+
this.globalCacheConfig = globalCacheConfig;
|
|
654
|
+
}
|
|
655
|
+
/** Экземпляр хранилища */
|
|
656
|
+
storage = null;
|
|
657
|
+
cleanupInterval = null;
|
|
658
|
+
/** Настройки кэша по умолчанию */
|
|
659
|
+
defaultCacheOptions = {
|
|
660
|
+
ttl: 5 * 60 * 1e3,
|
|
661
|
+
// 5 минут по умолчанию
|
|
662
|
+
cleanup: {
|
|
663
|
+
enabled: true,
|
|
664
|
+
interval: 10 * 60 * 1e3
|
|
665
|
+
// 10 минут
|
|
666
|
+
},
|
|
667
|
+
invalidateOnError: true
|
|
668
|
+
};
|
|
669
|
+
async initialize() {
|
|
670
|
+
await this.createStorage();
|
|
671
|
+
this.startCleanupInterval();
|
|
672
|
+
return this;
|
|
673
|
+
}
|
|
674
|
+
async createStorage() {
|
|
675
|
+
try {
|
|
676
|
+
const s = this.storageExternal;
|
|
677
|
+
await s.initialize();
|
|
678
|
+
this.storage = s;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430", error);
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
startCleanupInterval() {
|
|
685
|
+
if (this.cleanupInterval) {
|
|
686
|
+
clearInterval(this.cleanupInterval);
|
|
687
|
+
this.cleanupInterval = null;
|
|
688
|
+
}
|
|
689
|
+
const cleanupConfig = typeof this.globalCacheConfig === "object" ? this.globalCacheConfig.cleanup : this.defaultCacheOptions.cleanup;
|
|
690
|
+
if (cleanupConfig?.enabled && cleanupConfig.interval) {
|
|
691
|
+
this.cleanupInterval = setInterval(() => {
|
|
692
|
+
this.cleanup().catch((err) => console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043E\u0447\u0438\u0441\u0442\u043A\u0435 \u043A\u044D\u0448\u0430:", err));
|
|
693
|
+
}, cleanupConfig.interval);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Получает экземпляр хранилища
|
|
698
|
+
*/
|
|
699
|
+
getStorage() {
|
|
700
|
+
return this.storage;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Создает ключ кэша для запроса с учетом заголовков
|
|
704
|
+
* @param endpoint Имя эндпоинта
|
|
705
|
+
* @param params Параметры запроса (все что посчитаем нужным)
|
|
706
|
+
*/
|
|
707
|
+
createCacheKey(endpoint, params) {
|
|
708
|
+
return CacheUtils.createApiKey(endpoint, params);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Получает результат запроса из кэша
|
|
712
|
+
*/
|
|
713
|
+
async getCachedResult(cacheKey) {
|
|
714
|
+
if (!this.storage) throw new Error("\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E");
|
|
715
|
+
const cachedEntry = await this.storage.get(cacheKey);
|
|
716
|
+
if (!cachedEntry) return void 0;
|
|
717
|
+
if (CacheUtils.isExpired(cachedEntry.metadata)) {
|
|
718
|
+
await this.storage.delete(cacheKey);
|
|
719
|
+
return void 0;
|
|
720
|
+
}
|
|
721
|
+
const updatedEntry = {
|
|
722
|
+
...cachedEntry,
|
|
723
|
+
metadata: CacheUtils.updateMetadata(cachedEntry.metadata)
|
|
724
|
+
};
|
|
725
|
+
await this.storage.set(cacheKey, updatedEntry);
|
|
726
|
+
return cachedEntry.data;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Сохраняет результат запроса в кэш
|
|
730
|
+
* @param cacheKey Ключ кэша
|
|
731
|
+
* @param data Данные для кэширования
|
|
732
|
+
* @param cacheOptions Метаданные
|
|
733
|
+
* @param cacheParams Параметры которые влияли на созадние ключа
|
|
734
|
+
* @param tags Тэги эндпоинта
|
|
735
|
+
* @param invalidatesTags Тэги которые нужно инвалидировать
|
|
736
|
+
*/
|
|
737
|
+
async setCachedResult(cacheKey, data, cacheOptions, cacheParams, tags, invalidatesTags) {
|
|
738
|
+
if (!this.storage) throw new Error("\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E");
|
|
739
|
+
if (invalidatesTags?.length) {
|
|
740
|
+
await this.invalidateCacheByTags(invalidatesTags);
|
|
741
|
+
}
|
|
742
|
+
const cacheMetadata = CacheUtils.createMetadata(cacheOptions.ttl, tags);
|
|
743
|
+
const cacheEntry = {
|
|
744
|
+
data,
|
|
745
|
+
metadata: cacheMetadata,
|
|
746
|
+
params: cacheParams
|
|
747
|
+
};
|
|
748
|
+
await this.storage.set(cacheKey, cacheEntry);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Проверяет, должен ли запрос быть кэширован
|
|
752
|
+
* @param endpointConfig Конфигурация эндпоинта
|
|
753
|
+
* @param options Опции запроса
|
|
754
|
+
* @returns true если запрос должен кэшироваться
|
|
755
|
+
*/
|
|
756
|
+
shouldCache(endpointConfig, options) {
|
|
757
|
+
if (this.globalCacheConfig === false) return false;
|
|
758
|
+
if (endpointConfig?.cache === false) return false;
|
|
759
|
+
if (typeof endpointConfig?.cache === "object" && endpointConfig?.cache.ttl === 0) return false;
|
|
760
|
+
if (options?.disableCache === true) return false;
|
|
761
|
+
if (this.globalCacheConfig === void 0 && endpointConfig?.cache === void 0) return false;
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Создает итоговую конфигурацию кэширования для конкретного эндпоинта
|
|
766
|
+
* Объединяет глабальный конфиг с текущим
|
|
767
|
+
* @param endpointConfig Конфигурация эндпоинта
|
|
768
|
+
*/
|
|
769
|
+
createCacheConfig(endpointConfig) {
|
|
770
|
+
let resultConfig = this.defaultCacheOptions;
|
|
771
|
+
if (typeof this.globalCacheConfig === "object") {
|
|
772
|
+
resultConfig = this.globalCacheConfig;
|
|
773
|
+
}
|
|
774
|
+
if (typeof endpointConfig?.cache === "object") {
|
|
775
|
+
const endpointCache = endpointConfig.cache;
|
|
776
|
+
resultConfig = {
|
|
777
|
+
// @ts-ignore
|
|
778
|
+
...resultConfig,
|
|
779
|
+
...endpointCache
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
return resultConfig;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Инвалидирует кэш по тегам
|
|
786
|
+
* @param tags Теги для инвалидации
|
|
787
|
+
*/
|
|
788
|
+
async invalidateCacheByTags(tags) {
|
|
789
|
+
if (!this.storage) throw new Error("\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E");
|
|
790
|
+
const keys = await this.storage.keys();
|
|
791
|
+
for (const key of keys) {
|
|
792
|
+
const cachedEntry = await this.storage.get(key);
|
|
793
|
+
if (cachedEntry && CacheUtils.hasAnyTag(cachedEntry.metadata, tags)) {
|
|
794
|
+
await this.storage.delete(key);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Инвалидирует кэш по ключу
|
|
800
|
+
* @param cacheKey Ключ кэша
|
|
801
|
+
*/
|
|
802
|
+
async invalidateCache(cacheKey) {
|
|
803
|
+
if (!this.storage) throw new Error("\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E");
|
|
804
|
+
await this.storage.delete(cacheKey);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Выполняет очистку всех просроченных записей кэша
|
|
808
|
+
*/
|
|
809
|
+
async cleanup() {
|
|
810
|
+
if (!this.storage) {
|
|
811
|
+
throw new Error("\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E");
|
|
812
|
+
}
|
|
813
|
+
const keys = await this.storage.keys();
|
|
814
|
+
for (const key of keys) {
|
|
815
|
+
const value = await this.storage.get(key);
|
|
816
|
+
if (value && CacheUtils.isExpired(value.metadata)) {
|
|
817
|
+
await this.storage.delete(key);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Уничтожает хранилище и освобождает ресурсы
|
|
823
|
+
*/
|
|
824
|
+
async destroy() {
|
|
825
|
+
if (this.cleanupInterval) {
|
|
826
|
+
window.clearInterval(this.cleanupInterval);
|
|
827
|
+
this.cleanupInterval = null;
|
|
828
|
+
}
|
|
829
|
+
if (this.storage) {
|
|
830
|
+
await this.storage.destroy();
|
|
831
|
+
this.storage = null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// src/api/api.module.ts
|
|
837
|
+
var ApiClient = class {
|
|
838
|
+
/** Хранилище запросов */
|
|
839
|
+
// @ts-ignore
|
|
840
|
+
queryStorage;
|
|
841
|
+
cacheableHeaderKeys;
|
|
842
|
+
globalCacheConfig;
|
|
843
|
+
baseQueryConfig;
|
|
844
|
+
storageExternal;
|
|
845
|
+
createEndpoints;
|
|
846
|
+
/** Реестр эндпоинтов */
|
|
847
|
+
endpoints = {};
|
|
848
|
+
constructor(options) {
|
|
849
|
+
this.cacheableHeaderKeys = options.cacheableHeaderKeys;
|
|
850
|
+
this.globalCacheConfig = options.cache;
|
|
851
|
+
this.baseQueryConfig = options.baseQuery;
|
|
852
|
+
this.storageExternal = options.storage;
|
|
853
|
+
this.createEndpoints = options.endpoints;
|
|
854
|
+
}
|
|
855
|
+
async init() {
|
|
856
|
+
this.queryStorage = await new QueryStorage(this.storageExternal, this.globalCacheConfig).initialize();
|
|
857
|
+
await this.initializeEndpoints();
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
860
|
+
async initializeEndpoints() {
|
|
861
|
+
const create = (config) => config;
|
|
862
|
+
const endpointsConfig = await this.createEndpoints(create) || {};
|
|
863
|
+
for (const [endpointKey, endpointConfig] of Object.entries(endpointsConfig)) {
|
|
864
|
+
const key = endpointKey;
|
|
865
|
+
this.endpoints[key] = new EndpointClass(endpointKey, this.queryStorage, endpointConfig, this.cacheableHeaderKeys, this.globalCacheConfig, this.baseQueryConfig);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Получает все эндпоинты с улучшенной типизацией
|
|
870
|
+
* @returns Типизированный объект эндпоинтов
|
|
871
|
+
*/
|
|
872
|
+
getEndpoints() {
|
|
873
|
+
return this.endpoints;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Выполняет запрос к API с типизацией и обработкой ошибок
|
|
877
|
+
* @param endpointName Имя эндпоинта (с подсказками TypeScript)
|
|
878
|
+
* @param params Параметры запроса (с типизацией)
|
|
879
|
+
* @param options Опции запроса
|
|
880
|
+
* @returns Promise с типизированным результатом запроса
|
|
881
|
+
*/
|
|
882
|
+
async request(endpointName, params, options) {
|
|
883
|
+
const endpoints = this.getEndpoints();
|
|
884
|
+
const endpoint = endpoints[endpointName];
|
|
885
|
+
if (!endpoint) {
|
|
886
|
+
throw new Error(`\u042D\u043D\u0434\u043F\u043E\u0438\u043D\u0442 ${String(endpointName)} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D`);
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
const stateRequest = endpoint.request(params, options);
|
|
890
|
+
return await stateRequest.wait();
|
|
891
|
+
} catch (error) {
|
|
892
|
+
apiLogger.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u043A ${String(endpointName)}`, { error, params });
|
|
893
|
+
throw error;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async destroy() {
|
|
897
|
+
await Promise.all(
|
|
898
|
+
Object.values(this.endpoints).map(async (endpoint) => {
|
|
899
|
+
endpoint.destroy();
|
|
900
|
+
return Promise.resolve();
|
|
901
|
+
})
|
|
902
|
+
);
|
|
903
|
+
this.endpoints = {};
|
|
904
|
+
await this.queryStorage.destroy();
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/core/selector/selector.module.ts
|
|
909
|
+
var DEBUG = false;
|
|
910
|
+
var GLOBAL_SELECTOR_CACHE = /* @__PURE__ */ new Map();
|
|
911
|
+
function getStringHash(str) {
|
|
912
|
+
let hash = 0;
|
|
913
|
+
if (str.length === 0) return hash.toString(36);
|
|
914
|
+
for (let i = 0; i < str.length; i++) {
|
|
915
|
+
const char = str.charCodeAt(i);
|
|
916
|
+
hash = (hash << 5) - hash + char;
|
|
917
|
+
hash = hash & hash;
|
|
918
|
+
}
|
|
919
|
+
return Math.abs(hash).toString(36).substring(0, 6);
|
|
920
|
+
}
|
|
921
|
+
function defaultEquals(a, b) {
|
|
922
|
+
if (a === b) return true;
|
|
923
|
+
if (a == null || b == null) return false;
|
|
924
|
+
if (typeof a !== "object" && typeof a !== "function" && typeof b !== "object" && typeof b !== "function") {
|
|
925
|
+
return a === b;
|
|
926
|
+
}
|
|
927
|
+
if (typeof a !== typeof b) return false;
|
|
928
|
+
if (a instanceof Date && b instanceof Date) {
|
|
929
|
+
return a.getTime() === b.getTime();
|
|
930
|
+
}
|
|
931
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
932
|
+
if (a.length !== b.length) return false;
|
|
933
|
+
for (let i = 0; i < a.length; i++) {
|
|
934
|
+
if (!defaultEquals(a[i], b[i])) return false;
|
|
935
|
+
}
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
939
|
+
const keysA = Object.keys(a);
|
|
940
|
+
const keysB = Object.keys(b);
|
|
941
|
+
if (keysA.length !== keysB.length) return false;
|
|
942
|
+
return keysA.every((key) => {
|
|
943
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
944
|
+
return defaultEquals(a[key], b[key]);
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
function memoizeSelector(selectorFn, equals = defaultEquals) {
|
|
950
|
+
let lastState;
|
|
951
|
+
let lastResult;
|
|
952
|
+
let hasResult = false;
|
|
953
|
+
return function memoized(state) {
|
|
954
|
+
if (!hasResult || lastState !== state) {
|
|
955
|
+
const newResult = selectorFn(state);
|
|
956
|
+
if (!hasResult || !equals(newResult, lastResult)) {
|
|
957
|
+
lastResult = newResult;
|
|
958
|
+
}
|
|
959
|
+
lastState = state;
|
|
960
|
+
hasResult = true;
|
|
961
|
+
}
|
|
962
|
+
return lastResult;
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
var SelectorSubscription = class {
|
|
966
|
+
constructor(name, getState, equals = defaultEquals, logger) {
|
|
967
|
+
this.name = name;
|
|
968
|
+
this.equals = equals;
|
|
969
|
+
this.logger = logger;
|
|
970
|
+
this.id = name;
|
|
971
|
+
this.memoizedGetState = this.createMemoizedGetState(getState);
|
|
972
|
+
if (DEBUG) {
|
|
973
|
+
console.log(`[${this.id}] \u0421\u043E\u0437\u0434\u0430\u043D new SelectorSubscription`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
id;
|
|
977
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
978
|
+
lastValue;
|
|
979
|
+
memoizedGetState;
|
|
980
|
+
// Создает мемоизированную версию getState с кешированием результата
|
|
981
|
+
createMemoizedGetState(getState) {
|
|
982
|
+
let lastPromise = null;
|
|
983
|
+
let isExecuting = false;
|
|
984
|
+
return async () => {
|
|
985
|
+
if (isExecuting && lastPromise) {
|
|
986
|
+
return lastPromise;
|
|
987
|
+
}
|
|
988
|
+
isExecuting = true;
|
|
989
|
+
try {
|
|
990
|
+
lastPromise = getState();
|
|
991
|
+
return await lastPromise;
|
|
992
|
+
} finally {
|
|
993
|
+
isExecuting = false;
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
async notify() {
|
|
998
|
+
try {
|
|
999
|
+
const newValue = await this.memoizedGetState();
|
|
1000
|
+
if (this.lastValue === void 0 || !this.equals(newValue, this.lastValue)) {
|
|
1001
|
+
if (DEBUG) {
|
|
1002
|
+
console.log(`[${this.id}] \u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C, notify()`, {
|
|
1003
|
+
old: this.lastValue,
|
|
1004
|
+
new: newValue
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
this.lastValue = newValue;
|
|
1008
|
+
const promises = Array.from(this.subscribers).map(async (subscriber) => {
|
|
1009
|
+
try {
|
|
1010
|
+
await subscriber.notify(newValue);
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
this.logger?.error(`[${this.id}] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0438 \u043F\u043E\u0434\u043F\u0438\u0441\u0447\u0438\u043A\u0430`, { error });
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
await Promise.all(promises);
|
|
1016
|
+
} else if (DEBUG) {
|
|
1017
|
+
console.log(`[${this.id}] \u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C in notify(), \u043F\u0440\u043E\u043F\u0443\u0441\u043A \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F`);
|
|
1018
|
+
}
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
this.logger?.error(`[${this.id}] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432 notify()`, { error });
|
|
1021
|
+
throw error;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
subscribe(subscriber) {
|
|
1025
|
+
if (DEBUG) {
|
|
1026
|
+
console.log(`[${this.id}] \u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E \u043D\u043E\u0432\u044B\u0439 \u043F\u043E\u0434\u043F\u0438\u0441\u0447\u0438\u043A, \u0432\u0441\u0435\u0433\u043E: ${this.subscribers.size + 1}`);
|
|
1027
|
+
}
|
|
1028
|
+
this.subscribers.add(subscriber);
|
|
1029
|
+
if (this.lastValue !== void 0) {
|
|
1030
|
+
Promise.resolve().then(() => {
|
|
1031
|
+
try {
|
|
1032
|
+
subscriber.notify(this.lastValue);
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
this.logger?.error(`[${this.id}] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u0435\u0440\u0432\u043E\u043D\u0430\u0447\u0430\u043B\u044C\u043D\u043E\u043C \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0438`, { error });
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
} else {
|
|
1038
|
+
this.notify().catch((error) => {
|
|
1039
|
+
this.logger?.error(`[${this.id}] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u0435\u0440\u0432\u043E\u043D\u0430\u0447\u0430\u043B\u044C\u043D\u043E\u043C \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0438`, { error });
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
return () => {
|
|
1043
|
+
if (DEBUG) {
|
|
1044
|
+
console.log(`[${this.id}] \u041F\u043E\u0434\u043F\u0438\u0441\u0447\u0438\u043A \u0443\u0434\u0430\u043B\u0435\u043D, \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C: ${this.subscribers.size - 1}`);
|
|
1045
|
+
}
|
|
1046
|
+
this.subscribers.delete(subscriber);
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
cleanup() {
|
|
1050
|
+
if (DEBUG) {
|
|
1051
|
+
console.log(`[${this.id}] \u041E\u0447\u0438\u0441\u0442\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438, \u0431\u044B\u043B\u043E ${this.subscribers.size} \u043F\u043E\u0434\u043F\u0438\u0441\u0447\u0438\u043A\u043E\u0432`);
|
|
1052
|
+
}
|
|
1053
|
+
this.subscribers.clear();
|
|
1054
|
+
this.lastValue = void 0;
|
|
1055
|
+
}
|
|
1056
|
+
getId() {
|
|
1057
|
+
return this.id;
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
var SelectorModule = class {
|
|
1061
|
+
constructor(source, logger) {
|
|
1062
|
+
this.source = source;
|
|
1063
|
+
this.logger = logger;
|
|
1064
|
+
this.storageName = source.name;
|
|
1065
|
+
if (DEBUG) {
|
|
1066
|
+
console.log(`\u0421\u043E\u0437\u0434\u0430\u043D SelectorModule \u0434\u043B\u044F \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430: ${this.storageName}`);
|
|
1067
|
+
}
|
|
1068
|
+
this.source.getState().then((state) => {
|
|
1069
|
+
this.cachedState = state;
|
|
1070
|
+
if (DEBUG) {
|
|
1071
|
+
console.log(`\u041A\u044D\u0448\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u0435 \u043D\u0430\u0447\u0430\u043B\u044C\u043D\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0434\u043B\u044F ${this.storageName}`);
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
storageName;
|
|
1076
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
1077
|
+
cachedState;
|
|
1078
|
+
localSelectorCache = /* @__PURE__ */ new Map();
|
|
1079
|
+
// Флаг для батчинга обновлений
|
|
1080
|
+
batchUpdateInProgress = false;
|
|
1081
|
+
pendingUpdates = /* @__PURE__ */ new Set();
|
|
1082
|
+
/**
|
|
1083
|
+
* Генерирует имя для селектора на основе его типа и функции
|
|
1084
|
+
*/
|
|
1085
|
+
generateName(isSimpleSelector, selectorOrDeps, resultFnOrOptions) {
|
|
1086
|
+
const type = isSimpleSelector ? "simple" : "combined";
|
|
1087
|
+
let hash = "";
|
|
1088
|
+
if (isSimpleSelector) {
|
|
1089
|
+
const selectorStr = selectorOrDeps.toString();
|
|
1090
|
+
hash = getStringHash(selectorStr);
|
|
1091
|
+
} else {
|
|
1092
|
+
const depsIds = selectorOrDeps.map((s) => s.getId()).join("_");
|
|
1093
|
+
const resultFnStr = resultFnOrOptions.toString();
|
|
1094
|
+
hash = getStringHash(depsIds + resultFnStr);
|
|
1095
|
+
}
|
|
1096
|
+
return `${this.storageName}_${type}_${hash}`;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Обрабатывает отложенные обновления, чтобы избежать каскадных уведомлений
|
|
1100
|
+
*/
|
|
1101
|
+
processPendingUpdates() {
|
|
1102
|
+
if (this.pendingUpdates.size === 0 || this.batchUpdateInProgress) return;
|
|
1103
|
+
this.batchUpdateInProgress = true;
|
|
1104
|
+
setTimeout(async () => {
|
|
1105
|
+
try {
|
|
1106
|
+
const subscriptionsToUpdate = Array.from(this.pendingUpdates);
|
|
1107
|
+
this.pendingUpdates.clear();
|
|
1108
|
+
this.cachedState = await this.source.getState();
|
|
1109
|
+
const updatePromises = subscriptionsToUpdate.map(async (id) => {
|
|
1110
|
+
const subscription = this.subscriptions.get(id);
|
|
1111
|
+
if (subscription) {
|
|
1112
|
+
try {
|
|
1113
|
+
return await subscription.notify();
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F \u043F\u043E\u0434\u043F\u0438\u0441\u0447\u0438\u043A\u0430 ${id}`, { error });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return Promise.resolve();
|
|
1119
|
+
});
|
|
1120
|
+
await Promise.all(updatePromises);
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
this.logger?.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u043E\u0436\u0438\u0434\u0430\u044E\u0449\u0438\u0445 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0439", { error });
|
|
1123
|
+
} finally {
|
|
1124
|
+
this.batchUpdateInProgress = false;
|
|
1125
|
+
if (this.pendingUpdates.size > 0) {
|
|
1126
|
+
this.processPendingUpdates();
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}, 0);
|
|
1130
|
+
}
|
|
1131
|
+
createSelector(selectorOrDeps, resultFnOrOptions, optionsArg) {
|
|
1132
|
+
const isSimpleSelector = !Array.isArray(selectorOrDeps);
|
|
1133
|
+
const options = isSimpleSelector ? resultFnOrOptions || {} : optionsArg || {};
|
|
1134
|
+
const selectorId = options.name || this.generateName(isSimpleSelector, selectorOrDeps, isSimpleSelector ? void 0 : resultFnOrOptions);
|
|
1135
|
+
if (this.localSelectorCache.has(selectorId)) {
|
|
1136
|
+
if (DEBUG) {
|
|
1137
|
+
console.log(`[${this.storageName}] Reusing cached selector: ${selectorId}`);
|
|
1138
|
+
}
|
|
1139
|
+
return this.localSelectorCache.get(selectorId).api;
|
|
1140
|
+
}
|
|
1141
|
+
if (GLOBAL_SELECTOR_CACHE.has(selectorId)) {
|
|
1142
|
+
const cached = GLOBAL_SELECTOR_CACHE.get(selectorId);
|
|
1143
|
+
cached.refCount++;
|
|
1144
|
+
if (DEBUG) {
|
|
1145
|
+
console.log(`[${this.storageName}] \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u044D\u0448\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440\u0430: ${selectorId}, refCount: ${cached.refCount}`);
|
|
1146
|
+
}
|
|
1147
|
+
return cached.api;
|
|
1148
|
+
}
|
|
1149
|
+
let result;
|
|
1150
|
+
let dependencies;
|
|
1151
|
+
let unsubscribeFunctions = [];
|
|
1152
|
+
if (isSimpleSelector) {
|
|
1153
|
+
const memoized = memoizeSelector(selectorOrDeps, options.equals || defaultEquals);
|
|
1154
|
+
const created = this.createSimpleSelector(memoized, { ...options, name: selectorId, equals: options.equals || defaultEquals });
|
|
1155
|
+
result = created.api;
|
|
1156
|
+
unsubscribeFunctions = created.unsubscribeFunctions;
|
|
1157
|
+
} else {
|
|
1158
|
+
dependencies = selectorOrDeps;
|
|
1159
|
+
const created = this.createCombinedSelector(dependencies, resultFnOrOptions, {
|
|
1160
|
+
...options,
|
|
1161
|
+
name: selectorId,
|
|
1162
|
+
equals: options.equals || defaultEquals
|
|
1163
|
+
});
|
|
1164
|
+
result = created.api;
|
|
1165
|
+
unsubscribeFunctions = created.unsubscribeFunctions;
|
|
1166
|
+
}
|
|
1167
|
+
this.localSelectorCache.set(selectorId, {
|
|
1168
|
+
api: result,
|
|
1169
|
+
dependencies,
|
|
1170
|
+
unsubscribeFunctions
|
|
1171
|
+
});
|
|
1172
|
+
GLOBAL_SELECTOR_CACHE.set(selectorId, {
|
|
1173
|
+
api: result,
|
|
1174
|
+
refCount: 1,
|
|
1175
|
+
unsubscribeFunctions
|
|
1176
|
+
});
|
|
1177
|
+
if (DEBUG) {
|
|
1178
|
+
console.log(`[${this.storageName}] \u0421\u043E\u0437\u0434\u0430\u043D \u043D\u043E\u0432\u044B\u0439 \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440: ${selectorId}`);
|
|
1179
|
+
}
|
|
1180
|
+
return result;
|
|
1181
|
+
}
|
|
1182
|
+
createSimpleSelector(selector, options) {
|
|
1183
|
+
if (DEBUG) {
|
|
1184
|
+
console.log(`[${this.storageName}] \u0421\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u043E\u0441\u0442\u043E\u0439 \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440: ${options.name}`);
|
|
1185
|
+
}
|
|
1186
|
+
const getState = async () => {
|
|
1187
|
+
if (this.cachedState) {
|
|
1188
|
+
return selector(this.cachedState);
|
|
1189
|
+
}
|
|
1190
|
+
const state = await this.source.getState();
|
|
1191
|
+
this.cachedState = state;
|
|
1192
|
+
return selector(state);
|
|
1193
|
+
};
|
|
1194
|
+
const subscription = new SelectorSubscription(options.name, getState, options.equals || defaultEquals, this.logger);
|
|
1195
|
+
const id = subscription.getId();
|
|
1196
|
+
this.subscriptions.set(id, subscription);
|
|
1197
|
+
const unsubscribeFromStorage = this.source.subscribeToAll(async (event) => {
|
|
1198
|
+
if (event?.type === "storage:update") {
|
|
1199
|
+
if (DEBUG) {
|
|
1200
|
+
console.log(`[${id}] \u041F\u043E\u043B\u0443\u0447\u0435\u043D\u043E \u0441\u043E\u0431\u044B\u0442\u0438\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430`);
|
|
1201
|
+
}
|
|
1202
|
+
this.pendingUpdates.add(id);
|
|
1203
|
+
this.processPendingUpdates();
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
const unsubscribeFunctions = [unsubscribeFromStorage];
|
|
1207
|
+
return {
|
|
1208
|
+
api: {
|
|
1209
|
+
select: () => getState(),
|
|
1210
|
+
subscribe: (subscriber) => {
|
|
1211
|
+
return subscription.subscribe(subscriber);
|
|
1212
|
+
},
|
|
1213
|
+
getId: () => id
|
|
1214
|
+
},
|
|
1215
|
+
unsubscribeFunctions
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
createCombinedSelector(selectors, resultFn, options) {
|
|
1219
|
+
const memoizedResultFn = memoizeSelector((args) => resultFn(...args), options.equals || defaultEquals);
|
|
1220
|
+
const getState = async () => {
|
|
1221
|
+
const values = await Promise.all(selectors.map((s) => s.select()));
|
|
1222
|
+
return memoizedResultFn(values);
|
|
1223
|
+
};
|
|
1224
|
+
const subscription = new SelectorSubscription(options.name, getState, options.equals || defaultEquals, this.logger);
|
|
1225
|
+
const id = subscription.getId();
|
|
1226
|
+
this.subscriptions.set(id, subscription);
|
|
1227
|
+
let debounceTimer = null;
|
|
1228
|
+
const triggerUpdate = () => {
|
|
1229
|
+
if (debounceTimer !== null) {
|
|
1230
|
+
clearTimeout(debounceTimer);
|
|
1231
|
+
}
|
|
1232
|
+
debounceTimer = setTimeout(() => {
|
|
1233
|
+
debounceTimer = null;
|
|
1234
|
+
subscription.notify().catch((error) => this.logger?.error(`[${id}] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043E\u0431\u044A\u0435\u0434\u0438\u043D\u0435\u043D\u043D\u043E\u043C \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0438:`, { error }));
|
|
1235
|
+
}, 10);
|
|
1236
|
+
};
|
|
1237
|
+
const unsubscribeFunctions = selectors.map(
|
|
1238
|
+
(selector) => selector.subscribe({
|
|
1239
|
+
notify: () => {
|
|
1240
|
+
triggerUpdate();
|
|
1241
|
+
}
|
|
1242
|
+
})
|
|
1243
|
+
);
|
|
1244
|
+
return {
|
|
1245
|
+
api: {
|
|
1246
|
+
select: () => getState(),
|
|
1247
|
+
subscribe: (subscriber) => {
|
|
1248
|
+
return subscription.subscribe(subscriber);
|
|
1249
|
+
},
|
|
1250
|
+
getId: () => id
|
|
1251
|
+
},
|
|
1252
|
+
unsubscribeFunctions
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
destroy() {
|
|
1256
|
+
if (DEBUG) {
|
|
1257
|
+
console.log(`[${this.storageName}] \u041D\u0430\u0447\u0430\u043B\u043E\u0441\u044C \u0443\u043D\u0438\u0447\u0442\u043E\u0436\u0435\u043D\u0438\u0435 SelectorModule`);
|
|
1258
|
+
}
|
|
1259
|
+
this.subscriptions.forEach((sub) => sub.cleanup());
|
|
1260
|
+
this.subscriptions.clear();
|
|
1261
|
+
this.cachedState = void 0;
|
|
1262
|
+
this.pendingUpdates.clear();
|
|
1263
|
+
this.localSelectorCache.forEach((cached) => {
|
|
1264
|
+
cached.unsubscribeFunctions.forEach((unsub) => unsub());
|
|
1265
|
+
});
|
|
1266
|
+
const keysToCheck = /* @__PURE__ */ new Set();
|
|
1267
|
+
this.localSelectorCache.forEach((_, key) => {
|
|
1268
|
+
keysToCheck.add(key);
|
|
1269
|
+
});
|
|
1270
|
+
this.localSelectorCache.clear();
|
|
1271
|
+
keysToCheck.forEach((key) => {
|
|
1272
|
+
const globalCached = GLOBAL_SELECTOR_CACHE.get(key);
|
|
1273
|
+
if (globalCached) {
|
|
1274
|
+
globalCached.refCount--;
|
|
1275
|
+
if (globalCached.refCount <= 0) {
|
|
1276
|
+
globalCached.unsubscribeFunctions.forEach((unsub) => unsub());
|
|
1277
|
+
GLOBAL_SELECTOR_CACHE.delete(key);
|
|
1278
|
+
if (DEBUG) {
|
|
1279
|
+
console.log(`[${this.storageName}] \u0423\u0434\u0430\u043B\u0435\u043D \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440 \u0438\u0437 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u044D\u0448\u0430: ${key}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
if (DEBUG) {
|
|
1285
|
+
console.log(`[${this.storageName}] \u0423\u043D\u0438\u0447\u0442\u043E\u0436\u0435\u043D`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
// src/core/storage/modules/plugin/plugin.service.ts
|
|
1291
|
+
var StoragePluginModule = class {
|
|
1292
|
+
constructor(parentExecutor, logger, storageName = "default") {
|
|
1293
|
+
this.parentExecutor = parentExecutor;
|
|
1294
|
+
this.logger = logger;
|
|
1295
|
+
this.storageName = storageName;
|
|
1296
|
+
}
|
|
1297
|
+
plugins = /* @__PURE__ */ new Map();
|
|
1298
|
+
createContext(metadata) {
|
|
1299
|
+
return {
|
|
1300
|
+
storageName: this.storageName,
|
|
1301
|
+
timestamp: Date.now(),
|
|
1302
|
+
metadata
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
async add(plugin) {
|
|
1306
|
+
if (this.plugins.has(plugin.name)) {
|
|
1307
|
+
this.logger?.warn(`\u041F\u043B\u0430\u0433\u0438\u043D ${plugin.name} \u0443\u0436\u0435 \u0431\u044B\u043B \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D`);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
try {
|
|
1311
|
+
await plugin.initialize?.();
|
|
1312
|
+
this.plugins.set(plugin.name, plugin);
|
|
1313
|
+
this.logger?.info("\u041F\u043B\u0430\u0433\u0438\u043D \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D", { name: plugin.name });
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
throw error;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
async remove(name) {
|
|
1319
|
+
const plugin = this.plugins.get(name);
|
|
1320
|
+
if (plugin) {
|
|
1321
|
+
await plugin.destroy?.();
|
|
1322
|
+
this.plugins.delete(name);
|
|
1323
|
+
this.logger?.info("\u041F\u043B\u0430\u0433\u0438\u043D \u0443\u0434\u0430\u043B\u0435\u043D", { name });
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
get(name) {
|
|
1327
|
+
return this.plugins.get(name);
|
|
1328
|
+
}
|
|
1329
|
+
getAll() {
|
|
1330
|
+
return Array.from(this.plugins.values());
|
|
1331
|
+
}
|
|
1332
|
+
async initialize() {
|
|
1333
|
+
for (const plugin of this.plugins.values()) {
|
|
1334
|
+
await plugin.initialize?.();
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
async destroy() {
|
|
1338
|
+
await Promise.all(Array.from(this.plugins.values()).map((plugin) => plugin.destroy?.() ?? Promise.resolve()));
|
|
1339
|
+
this.plugins.clear();
|
|
1340
|
+
}
|
|
1341
|
+
async executeBeforeSet(value, metadata) {
|
|
1342
|
+
let result = value;
|
|
1343
|
+
const context = this.createContext(metadata);
|
|
1344
|
+
if (this.parentExecutor) {
|
|
1345
|
+
result = await this.parentExecutor.executeBeforeSet(result, context);
|
|
1346
|
+
}
|
|
1347
|
+
for (const plugin of this.plugins.values()) {
|
|
1348
|
+
if (plugin.onBeforeSet) {
|
|
1349
|
+
try {
|
|
1350
|
+
result = await plugin.onBeforeSet(result, context);
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onBeforeSet`, { error });
|
|
1353
|
+
throw error;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return result;
|
|
1358
|
+
}
|
|
1359
|
+
async executeAfterSet(key, value, metadata) {
|
|
1360
|
+
let result = value;
|
|
1361
|
+
const context = this.createContext(metadata);
|
|
1362
|
+
if (this.parentExecutor) {
|
|
1363
|
+
result = await this.parentExecutor.executeAfterSet(key, result, context);
|
|
1364
|
+
}
|
|
1365
|
+
for (const plugin of this.plugins.values()) {
|
|
1366
|
+
if (plugin.onAfterSet) {
|
|
1367
|
+
try {
|
|
1368
|
+
result = await plugin.onAfterSet(key, result, context);
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onAfterSet`, { key, error });
|
|
1371
|
+
throw error;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return result;
|
|
1376
|
+
}
|
|
1377
|
+
async executeBeforeGet(key, metadata) {
|
|
1378
|
+
let processedKey = key;
|
|
1379
|
+
const context = this.createContext(metadata);
|
|
1380
|
+
if (this.parentExecutor) {
|
|
1381
|
+
processedKey = await this.parentExecutor.executeBeforeGet(processedKey, context);
|
|
1382
|
+
}
|
|
1383
|
+
for (const plugin of this.plugins.values()) {
|
|
1384
|
+
if (plugin.onBeforeGet) {
|
|
1385
|
+
try {
|
|
1386
|
+
processedKey = await plugin.onBeforeGet(processedKey, context);
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onBeforeGet`, { key, error });
|
|
1389
|
+
throw error;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return processedKey;
|
|
1394
|
+
}
|
|
1395
|
+
async executeAfterGet(key, value, metadata) {
|
|
1396
|
+
let result = value;
|
|
1397
|
+
const context = this.createContext(metadata);
|
|
1398
|
+
if (this.parentExecutor) {
|
|
1399
|
+
result = await this.parentExecutor.executeAfterGet(key, result, context);
|
|
1400
|
+
}
|
|
1401
|
+
console.log("executeAfterGet", key, value, metadata);
|
|
1402
|
+
for (const plugin of this.plugins.values()) {
|
|
1403
|
+
if (plugin.onAfterGet) {
|
|
1404
|
+
try {
|
|
1405
|
+
result = await plugin.onAfterGet(key, result, context);
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onAfterGet`, { key, error });
|
|
1408
|
+
throw error;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return result;
|
|
1413
|
+
}
|
|
1414
|
+
async executeBeforeDelete(key, metadata) {
|
|
1415
|
+
let canDelete = true;
|
|
1416
|
+
const context = this.createContext(metadata);
|
|
1417
|
+
if (this.parentExecutor) {
|
|
1418
|
+
canDelete = await this.parentExecutor.executeBeforeDelete(key, context);
|
|
1419
|
+
}
|
|
1420
|
+
for (const plugin of this.plugins.values()) {
|
|
1421
|
+
if (plugin.onBeforeDelete) {
|
|
1422
|
+
try {
|
|
1423
|
+
canDelete = await plugin.onBeforeDelete(key, context) && canDelete;
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onBeforeDelete`, { key, error });
|
|
1426
|
+
throw error;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return canDelete;
|
|
1431
|
+
}
|
|
1432
|
+
async executeAfterDelete(key, metadata) {
|
|
1433
|
+
const context = this.createContext(metadata);
|
|
1434
|
+
if (this.parentExecutor) {
|
|
1435
|
+
await this.parentExecutor.executeAfterDelete(key, context);
|
|
1436
|
+
}
|
|
1437
|
+
for (const plugin of this.plugins.values()) {
|
|
1438
|
+
if (plugin.onAfterDelete) {
|
|
1439
|
+
try {
|
|
1440
|
+
await plugin.onAfterDelete(key, context);
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onAfterDelete`, { key, error });
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
async executeOnClear(metadata) {
|
|
1449
|
+
const context = this.createContext(metadata);
|
|
1450
|
+
if (this.parentExecutor) {
|
|
1451
|
+
await this.parentExecutor.executeOnClear(context);
|
|
1452
|
+
}
|
|
1453
|
+
for (const plugin of this.plugins.values()) {
|
|
1454
|
+
if (plugin.onClear) {
|
|
1455
|
+
try {
|
|
1456
|
+
await plugin.onClear(context);
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043B\u0430\u0433\u0438\u043D\u0435 ${plugin.name} onClear`, { error });
|
|
1459
|
+
throw error;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
// src/core/storage/storage.interface.ts
|
|
1467
|
+
var StorageEvents = /* @__PURE__ */ ((StorageEvents2) => {
|
|
1468
|
+
StorageEvents2["STORAGE_UPDATE"] = "storage:update";
|
|
1469
|
+
StorageEvents2["STORAGE_DELETE"] = "storage:delete";
|
|
1470
|
+
StorageEvents2["STORAGE_PATCH"] = "storage:patch";
|
|
1471
|
+
StorageEvents2["STORAGE_SELECT"] = "storage:select";
|
|
1472
|
+
StorageEvents2["STORAGE_CLEAR"] = "storage:clear";
|
|
1473
|
+
StorageEvents2["STORAGE_DESTROY"] = "storage:destroy";
|
|
1474
|
+
return StorageEvents2;
|
|
1475
|
+
})(StorageEvents || {});
|
|
1476
|
+
|
|
1477
|
+
// src/core/storage/utils/broadcast.util.ts
|
|
1478
|
+
var SyncBroadcastChannel = class {
|
|
1479
|
+
channel;
|
|
1480
|
+
tabId;
|
|
1481
|
+
messageHandlers;
|
|
1482
|
+
syncHandler;
|
|
1483
|
+
debug;
|
|
1484
|
+
syncTimeoutMs = 1e3;
|
|
1485
|
+
pendingSyncRequests;
|
|
1486
|
+
constructor(channelName, options = {}) {
|
|
1487
|
+
this.channel = new BroadcastChannel(channelName);
|
|
1488
|
+
this.tabId = crypto.randomUUID();
|
|
1489
|
+
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
1490
|
+
this.debug = options.debug ?? false;
|
|
1491
|
+
this.pendingSyncRequests = /* @__PURE__ */ new Map();
|
|
1492
|
+
this.channel.onmessage = this.handleMessage.bind(this);
|
|
1493
|
+
this.channel.onmessageerror = this.handleError.bind(this);
|
|
1494
|
+
}
|
|
1495
|
+
log(...args) {
|
|
1496
|
+
if (this.debug) {
|
|
1497
|
+
console.log(`[SyncBroadcastChannel][${this.tabId}]`, ...args);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
error(...args) {
|
|
1501
|
+
console.error(`[SyncBroadcastChannel][${this.tabId}]`, ...args);
|
|
1502
|
+
}
|
|
1503
|
+
async handleMessage(event) {
|
|
1504
|
+
const message = event.data;
|
|
1505
|
+
if (message.senderId === this.tabId) {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
if (message.type === "SYNC_REQUEST") {
|
|
1509
|
+
if (this.syncHandler) {
|
|
1510
|
+
try {
|
|
1511
|
+
const state = await this.syncHandler();
|
|
1512
|
+
this.postMessage("SYNC_RESPONSE", state, message.senderId);
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
this.error("Error handling sync request:", error);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
if (message.type === "SYNC_RESPONSE") {
|
|
1520
|
+
const request = this.pendingSyncRequests.get(this.tabId);
|
|
1521
|
+
if (request) {
|
|
1522
|
+
clearTimeout(request.timeout);
|
|
1523
|
+
this.pendingSyncRequests.delete(this.tabId);
|
|
1524
|
+
request.resolve(message.payload);
|
|
1525
|
+
}
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
for (const handler of this.messageHandlers) {
|
|
1529
|
+
try {
|
|
1530
|
+
await handler(message);
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
this.error("Error in message handler:", error);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
handleError(event) {
|
|
1537
|
+
this.error("Channel error:", event);
|
|
1538
|
+
}
|
|
1539
|
+
postMessage(type, payload, targetId) {
|
|
1540
|
+
const message = {
|
|
1541
|
+
type,
|
|
1542
|
+
payload,
|
|
1543
|
+
senderId: this.tabId,
|
|
1544
|
+
timestamp: Date.now()
|
|
1545
|
+
};
|
|
1546
|
+
this.channel.postMessage(message);
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Подписка на сообщения канала
|
|
1550
|
+
*/
|
|
1551
|
+
subscribe(handler) {
|
|
1552
|
+
this.messageHandlers.add(handler);
|
|
1553
|
+
return () => this.messageHandlers.delete(handler);
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Установка обработчика запросов на синхронизацию
|
|
1557
|
+
*/
|
|
1558
|
+
setSyncHandler(handler) {
|
|
1559
|
+
this.syncHandler = handler;
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Отправка сообщения всем подписчикам
|
|
1563
|
+
*/
|
|
1564
|
+
broadcast(type, payload) {
|
|
1565
|
+
this.postMessage(type, payload);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Запрос синхронизации данных с других вкладок
|
|
1569
|
+
*/
|
|
1570
|
+
async requestSync() {
|
|
1571
|
+
return new Promise((resolve, reject) => {
|
|
1572
|
+
const timeout = setTimeout(() => {
|
|
1573
|
+
this.pendingSyncRequests.delete(this.tabId);
|
|
1574
|
+
resolve(null);
|
|
1575
|
+
}, this.syncTimeoutMs);
|
|
1576
|
+
this.pendingSyncRequests.set(this.tabId, { resolve, reject, timeout });
|
|
1577
|
+
this.postMessage("SYNC_REQUEST", { type: "sync" });
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Закрытие канала
|
|
1582
|
+
*/
|
|
1583
|
+
close() {
|
|
1584
|
+
for (const [, request] of this.pendingSyncRequests) {
|
|
1585
|
+
clearTimeout(request.timeout);
|
|
1586
|
+
request.reject(new Error("Channel closed"));
|
|
1587
|
+
}
|
|
1588
|
+
this.pendingSyncRequests.clear();
|
|
1589
|
+
this.messageHandlers.clear();
|
|
1590
|
+
this.syncHandler = void 0;
|
|
1591
|
+
this.channel.close();
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
// src/core/storage/middlewares/broadcast.middleware.ts
|
|
1596
|
+
async function handleMemoryStorageMessage(api, type, payload) {
|
|
1597
|
+
switch (type) {
|
|
1598
|
+
case "set":
|
|
1599
|
+
if (payload?.key !== void 0 && payload?.value !== void 0) {
|
|
1600
|
+
await api.storage.doSet(payload.key, payload.value);
|
|
1601
|
+
api.storage.notifySubscribers(payload.key, payload.value);
|
|
1602
|
+
}
|
|
1603
|
+
break;
|
|
1604
|
+
case "update":
|
|
1605
|
+
if (Array.isArray(payload?.value)) {
|
|
1606
|
+
await api.storage.doUpdate(payload.value);
|
|
1607
|
+
payload.value.forEach(({ key, value }) => {
|
|
1608
|
+
api.storage.notifySubscribers(key, value);
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
break;
|
|
1612
|
+
case "delete":
|
|
1613
|
+
if (payload?.key !== void 0) {
|
|
1614
|
+
await api.storage.doDelete(payload.key);
|
|
1615
|
+
api.storage.notifySubscribers(payload.key, void 0);
|
|
1616
|
+
}
|
|
1617
|
+
break;
|
|
1618
|
+
case "clear":
|
|
1619
|
+
await api.storage.doClear();
|
|
1620
|
+
api.storage.notifySubscribers("*", {
|
|
1621
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
1622
|
+
value: {},
|
|
1623
|
+
source: "broadcast"
|
|
1624
|
+
});
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
api.storage.notifySubscribers("*", {
|
|
1628
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
1629
|
+
key: payload?.key,
|
|
1630
|
+
value: payload?.value,
|
|
1631
|
+
source: "broadcast"
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
async function handlePersistentStorageMessage(api, type, payload) {
|
|
1635
|
+
switch (type) {
|
|
1636
|
+
case "set":
|
|
1637
|
+
if (payload?.key !== void 0) {
|
|
1638
|
+
const currentValue = await api.storage.doGet(payload.key);
|
|
1639
|
+
api.storage.notifySubscribers(payload.key, currentValue);
|
|
1640
|
+
}
|
|
1641
|
+
break;
|
|
1642
|
+
case "update":
|
|
1643
|
+
if (Array.isArray(payload?.value)) {
|
|
1644
|
+
for (const { key } of payload.value) {
|
|
1645
|
+
const currentValue = await api.storage.doGet(key);
|
|
1646
|
+
api.storage.notifySubscribers(key, currentValue);
|
|
1647
|
+
}
|
|
1648
|
+
api.storage.notifySubscribers("*", {
|
|
1649
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
1650
|
+
//@ts-ignore
|
|
1651
|
+
key: payload.value.map(({ key }) => key),
|
|
1652
|
+
value: payload.value,
|
|
1653
|
+
source: "broadcast"
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
break;
|
|
1657
|
+
case "delete":
|
|
1658
|
+
if (payload?.key !== void 0) {
|
|
1659
|
+
api.storage.notifySubscribers(payload.key, void 0);
|
|
1660
|
+
}
|
|
1661
|
+
break;
|
|
1662
|
+
case "clear":
|
|
1663
|
+
api.storage.notifySubscribers("*", {
|
|
1664
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
1665
|
+
value: {},
|
|
1666
|
+
source: "broadcast"
|
|
1667
|
+
});
|
|
1668
|
+
break;
|
|
1669
|
+
}
|
|
1670
|
+
if (type !== "update") {
|
|
1671
|
+
api.storage.notifySubscribers("*", {
|
|
1672
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
1673
|
+
key: payload?.key,
|
|
1674
|
+
value: type === "delete" ? void 0 : payload?.value,
|
|
1675
|
+
source: "broadcast"
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
var broadcastMiddleware = (props) => {
|
|
1680
|
+
const { storageName, storageType } = props;
|
|
1681
|
+
const channelName = `${storageType}-${storageName}`;
|
|
1682
|
+
const channel = new SyncBroadcastChannel(channelName, { debug: true });
|
|
1683
|
+
return {
|
|
1684
|
+
name: "broadcast",
|
|
1685
|
+
setup: (api) => {
|
|
1686
|
+
if (storageType === "memory") {
|
|
1687
|
+
channel.setSyncHandler(async () => {
|
|
1688
|
+
const state = await api.getState();
|
|
1689
|
+
const updates = Object.entries(state).map(([key, value]) => ({
|
|
1690
|
+
key,
|
|
1691
|
+
value
|
|
1692
|
+
}));
|
|
1693
|
+
const action = {
|
|
1694
|
+
type: "update",
|
|
1695
|
+
key: "*",
|
|
1696
|
+
value: updates,
|
|
1697
|
+
metadata: {
|
|
1698
|
+
batchUpdate: true,
|
|
1699
|
+
timestamp: Date.now()
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
return action;
|
|
1703
|
+
});
|
|
1704
|
+
channel.requestSync().then(async (action) => {
|
|
1705
|
+
if (action?.type === "update" && Array.isArray(action.value)) {
|
|
1706
|
+
try {
|
|
1707
|
+
const validUpdates = action.value.every((update) => update && typeof update === "object" && "key" in update && "value" in update);
|
|
1708
|
+
if (!validUpdates) {
|
|
1709
|
+
console.error("[Sync Response] Invalid updates structure:", action.value);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
await api.storage.doUpdate(action.value);
|
|
1713
|
+
action.value.forEach(({ key, value }) => {
|
|
1714
|
+
api.storage.notifySubscribers(key, value);
|
|
1715
|
+
});
|
|
1716
|
+
api.storage.notifySubscribers("*", {
|
|
1717
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
1718
|
+
value: action.value,
|
|
1719
|
+
source: "broadcast"
|
|
1720
|
+
});
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
console.error("[Sync Response] Error applying updates:", error);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
return channel.subscribe(async (message) => {
|
|
1728
|
+
const { type, payload } = message;
|
|
1729
|
+
if (storageType === "memory") {
|
|
1730
|
+
await handleMemoryStorageMessage(api, type, payload);
|
|
1731
|
+
} else {
|
|
1732
|
+
await handlePersistentStorageMessage(api, type, payload);
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
},
|
|
1736
|
+
reducer: (api) => (next) => async (action) => {
|
|
1737
|
+
const result = await next(action);
|
|
1738
|
+
if (["set", "delete", "clear", "update"].includes(action.type)) {
|
|
1739
|
+
channel.broadcast(action.type, action);
|
|
1740
|
+
}
|
|
1741
|
+
return result;
|
|
1742
|
+
},
|
|
1743
|
+
cleanup: () => {
|
|
1744
|
+
channel.close();
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
// src/core/storage/middlewares/storage-batching.middleware.ts
|
|
1750
|
+
var batchingMiddleware = (options = {}) => {
|
|
1751
|
+
const batchSize = options.batchSize ?? 10;
|
|
1752
|
+
const batchDelay = options.batchDelay ?? 10;
|
|
1753
|
+
const queues = /* @__PURE__ */ new Map();
|
|
1754
|
+
const timeouts = /* @__PURE__ */ new Map();
|
|
1755
|
+
const shouldBatch = (action) => {
|
|
1756
|
+
return action.type === "set" || action.type === "update";
|
|
1757
|
+
};
|
|
1758
|
+
const getSegmentKey = (action) => {
|
|
1759
|
+
return `${action.type}_${action.key?.toString() || "default"}`;
|
|
1760
|
+
};
|
|
1761
|
+
const mergeActions = (actions) => {
|
|
1762
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1763
|
+
for (const action of actions) {
|
|
1764
|
+
const key = `${action.type}_${action.key?.toString() || "default"}`;
|
|
1765
|
+
merged.set(key, action);
|
|
1766
|
+
}
|
|
1767
|
+
return Array.from(merged.values());
|
|
1768
|
+
};
|
|
1769
|
+
const clearTimeout2 = (segment) => {
|
|
1770
|
+
const timeout = timeouts.get(segment);
|
|
1771
|
+
if (timeout) {
|
|
1772
|
+
globalThis.clearTimeout(timeout);
|
|
1773
|
+
timeouts.delete(segment);
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
const setTimeout2 = (segment, callback) => {
|
|
1777
|
+
const timeout = globalThis.setTimeout(callback, batchDelay);
|
|
1778
|
+
timeouts.set(segment, timeout);
|
|
1779
|
+
};
|
|
1780
|
+
const processBatch = async (segment, api, next) => {
|
|
1781
|
+
const queue = queues.get(segment);
|
|
1782
|
+
if (!queue || queue.length === 0) return;
|
|
1783
|
+
queues.delete(segment);
|
|
1784
|
+
clearTimeout2(segment);
|
|
1785
|
+
try {
|
|
1786
|
+
const actions = queue.map((item) => item.action);
|
|
1787
|
+
const mergedActions = mergeActions(actions);
|
|
1788
|
+
console.log(`\u{1F504} Batching ${segment}: ${queue.length} actions -> ${mergedActions.length} merged`, {
|
|
1789
|
+
original: actions.map((a) => ({ type: a.type, key: a.key?.toString(), value: a.value })),
|
|
1790
|
+
merged: mergedActions.map((a) => ({ type: a.type, key: a.key?.toString(), value: a.value }))
|
|
1791
|
+
});
|
|
1792
|
+
for (const mergedAction of mergedActions) {
|
|
1793
|
+
try {
|
|
1794
|
+
const result = await next(mergedAction);
|
|
1795
|
+
const matchingItems = queue.filter((item) => item.action.type === mergedAction.type && item.action.key?.toString() === mergedAction.key?.toString());
|
|
1796
|
+
matchingItems.forEach((item) => item.resolve(result));
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
const matchingItems = queue.filter((item) => item.action.type === mergedAction.type && item.action.key?.toString() === mergedAction.key?.toString());
|
|
1799
|
+
matchingItems.forEach((item) => item.reject(error));
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
queue.forEach((item) => item.reject(error));
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
const addToQueue = async (action, api, next) => {
|
|
1807
|
+
return new Promise((resolve, reject) => {
|
|
1808
|
+
const segment = getSegmentKey(action);
|
|
1809
|
+
let queue = queues.get(segment);
|
|
1810
|
+
if (!queue) {
|
|
1811
|
+
queue = [];
|
|
1812
|
+
queues.set(segment, queue);
|
|
1813
|
+
}
|
|
1814
|
+
queue.push({
|
|
1815
|
+
action,
|
|
1816
|
+
resolve,
|
|
1817
|
+
reject,
|
|
1818
|
+
timestamp: Date.now()
|
|
1819
|
+
});
|
|
1820
|
+
clearTimeout2(segment);
|
|
1821
|
+
if (queue.length >= batchSize) {
|
|
1822
|
+
setImmediate(() => processBatch(segment, api, next));
|
|
1823
|
+
} else {
|
|
1824
|
+
setTimeout2(segment, () => processBatch(segment, api, next));
|
|
1825
|
+
}
|
|
1826
|
+
});
|
|
1827
|
+
};
|
|
1828
|
+
return {
|
|
1829
|
+
name: "batching",
|
|
1830
|
+
setup: () => {
|
|
1831
|
+
},
|
|
1832
|
+
cleanup: async () => {
|
|
1833
|
+
timeouts.forEach((timeout) => globalThis.clearTimeout(timeout));
|
|
1834
|
+
timeouts.clear();
|
|
1835
|
+
queues.clear();
|
|
1836
|
+
},
|
|
1837
|
+
reducer: (api) => (next) => async (action) => {
|
|
1838
|
+
if (!shouldBatch(action)) {
|
|
1839
|
+
return next(action);
|
|
1840
|
+
}
|
|
1841
|
+
return addToQueue(action, api, next);
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
// src/core/storage/middlewares/storage-shallow-compare.middleware.ts
|
|
1847
|
+
var shallowCompareMiddleware = (options = {}) => {
|
|
1848
|
+
const {
|
|
1849
|
+
comparator = (prev, next) => {
|
|
1850
|
+
if (prev === next) return true;
|
|
1851
|
+
if (typeof prev !== "object" || typeof next !== "object" || prev === null || next === null) {
|
|
1852
|
+
return prev === next;
|
|
1853
|
+
}
|
|
1854
|
+
const keysA = Object.keys(prev);
|
|
1855
|
+
const keysB = Object.keys(next);
|
|
1856
|
+
if (keysA.length !== keysB.length) return false;
|
|
1857
|
+
return keysA.every((key) => Object.prototype.hasOwnProperty.call(next, key) && prev[key] === next[key]);
|
|
1858
|
+
},
|
|
1859
|
+
segments = []
|
|
1860
|
+
} = options;
|
|
1861
|
+
const valueCache = /* @__PURE__ */ new Map();
|
|
1862
|
+
return {
|
|
1863
|
+
name: "shallow-compare",
|
|
1864
|
+
setup: (api) => {
|
|
1865
|
+
},
|
|
1866
|
+
reducer: (api) => (next) => async (action) => {
|
|
1867
|
+
if (action.type !== "set" || segments.length && !segments.includes(action.metadata?.segment ?? "default")) {
|
|
1868
|
+
return next(action);
|
|
1869
|
+
}
|
|
1870
|
+
const cacheKey = action.key;
|
|
1871
|
+
const prevValue = valueCache.get(cacheKey);
|
|
1872
|
+
const nextValue = action.value;
|
|
1873
|
+
if (prevValue !== void 0 && comparator(prevValue, nextValue)) {
|
|
1874
|
+
console.log("ShallowCompare: \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0438\u0434\u0435\u043D\u0442\u0438\u0447\u043D\u044B, \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u043C \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044E", { key: cacheKey, value: nextValue });
|
|
1875
|
+
return {
|
|
1876
|
+
...prevValue,
|
|
1877
|
+
__metadata: {
|
|
1878
|
+
valueNotChanged: true,
|
|
1879
|
+
// Этот флаг будет проверяться в BaseStorage перед notifySubscribers
|
|
1880
|
+
originalValue: prevValue
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
const result = await next(action);
|
|
1885
|
+
valueCache.set(cacheKey, nextValue);
|
|
1886
|
+
return result;
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
// src/core/storage/utils/middleware-module.ts
|
|
1892
|
+
var MiddlewareModule = class {
|
|
1893
|
+
middlewares = [];
|
|
1894
|
+
api;
|
|
1895
|
+
initialized = false;
|
|
1896
|
+
dispatchFn;
|
|
1897
|
+
constructor(storage) {
|
|
1898
|
+
this.api = {
|
|
1899
|
+
dispatch: async (action) => this.dispatch(action),
|
|
1900
|
+
getState: () => storage.getState(),
|
|
1901
|
+
storage: {
|
|
1902
|
+
doGet: storage.doGet.bind(storage),
|
|
1903
|
+
doSet: storage.doSet.bind(storage),
|
|
1904
|
+
doUpdate: storage.doUpdate.bind(storage),
|
|
1905
|
+
doDelete: storage.doDelete.bind(storage),
|
|
1906
|
+
doClear: storage.doClear.bind(storage),
|
|
1907
|
+
doKeys: storage.doKeys.bind(storage),
|
|
1908
|
+
notifySubscribers: storage.notifySubscribers.bind(storage)
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
async baseOperation(action) {
|
|
1913
|
+
const { processed: _, ...metadata } = action.metadata || {};
|
|
1914
|
+
const cleanAction = { ...action, metadata };
|
|
1915
|
+
switch (cleanAction.type) {
|
|
1916
|
+
case "get": {
|
|
1917
|
+
return this.api.storage.doGet(cleanAction.key);
|
|
1918
|
+
}
|
|
1919
|
+
case "set": {
|
|
1920
|
+
await this.api.storage.doSet(cleanAction.key, cleanAction.value);
|
|
1921
|
+
return this.api.storage.doGet(cleanAction.key);
|
|
1922
|
+
}
|
|
1923
|
+
case "update": {
|
|
1924
|
+
if (Array.isArray(cleanAction.value)) {
|
|
1925
|
+
await this.api.storage.doUpdate(cleanAction.value);
|
|
1926
|
+
return this.api.storage.doGet("");
|
|
1927
|
+
}
|
|
1928
|
+
return cleanAction.value;
|
|
1929
|
+
}
|
|
1930
|
+
case "delete": {
|
|
1931
|
+
return this.api.storage.doDelete(cleanAction.key);
|
|
1932
|
+
}
|
|
1933
|
+
case "clear": {
|
|
1934
|
+
return this.api.storage.doClear();
|
|
1935
|
+
}
|
|
1936
|
+
case "init": {
|
|
1937
|
+
const currentState = await this.api.storage.doGet("");
|
|
1938
|
+
if (Object.keys(currentState || {}).length > 0) {
|
|
1939
|
+
return currentState;
|
|
1940
|
+
}
|
|
1941
|
+
if (cleanAction.value) {
|
|
1942
|
+
await this.api.storage.doSet("", cleanAction.value);
|
|
1943
|
+
return this.api.storage.doGet("");
|
|
1944
|
+
}
|
|
1945
|
+
return currentState;
|
|
1946
|
+
}
|
|
1947
|
+
case "keys": {
|
|
1948
|
+
return this.api.storage.doKeys();
|
|
1949
|
+
}
|
|
1950
|
+
default: {
|
|
1951
|
+
throw new Error(`Unknown action type: ${cleanAction.type}`);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
initializeMiddlewares() {
|
|
1956
|
+
if (this.initialized) return;
|
|
1957
|
+
let chain = this.baseOperation.bind(this);
|
|
1958
|
+
for (const middleware of [...this.middlewares].reverse()) {
|
|
1959
|
+
const nextChain = chain;
|
|
1960
|
+
chain = async (action) => {
|
|
1961
|
+
if (action.metadata?.processed) {
|
|
1962
|
+
return nextChain(action);
|
|
1963
|
+
}
|
|
1964
|
+
const actionWithMeta = {
|
|
1965
|
+
...action,
|
|
1966
|
+
metadata: {
|
|
1967
|
+
...action.metadata,
|
|
1968
|
+
processed: true,
|
|
1969
|
+
timestamp: action.metadata?.timestamp || Date.now()
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
return middleware.reducer(this.api)(nextChain)(actionWithMeta);
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
this.dispatchFn = chain;
|
|
1976
|
+
this.initialized = true;
|
|
1977
|
+
}
|
|
1978
|
+
use(middleware) {
|
|
1979
|
+
if (middleware.setup) {
|
|
1980
|
+
middleware.setup(this.api);
|
|
1981
|
+
}
|
|
1982
|
+
this.middlewares.push(middleware);
|
|
1983
|
+
this.initialized = false;
|
|
1984
|
+
}
|
|
1985
|
+
async dispatch(action) {
|
|
1986
|
+
if (!this.initialized) {
|
|
1987
|
+
this.initializeMiddlewares();
|
|
1988
|
+
}
|
|
1989
|
+
try {
|
|
1990
|
+
return this.dispatchFn(action);
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
console.error("Error in middleware chain:", error);
|
|
1993
|
+
throw error;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
|
|
1998
|
+
// src/core/storage/adapters/path.utils.ts
|
|
1999
|
+
function parsePath(path) {
|
|
2000
|
+
if (path instanceof StorageKey && path.isUnparseable()) {
|
|
2001
|
+
return [path.toString()];
|
|
2002
|
+
}
|
|
2003
|
+
const pathStr = path.toString();
|
|
2004
|
+
return pathStr.replace(/\[/g, ".").replace(/\]/g, "").split(".").filter(Boolean);
|
|
2005
|
+
}
|
|
2006
|
+
function getValueByPath(obj, path) {
|
|
2007
|
+
const parts = parsePath(path);
|
|
2008
|
+
return parts.reduce((curr, key) => curr === void 0 ? void 0 : curr[key], obj);
|
|
2009
|
+
}
|
|
2010
|
+
function setValueByPath(obj, path, value) {
|
|
2011
|
+
if (path === "") return value;
|
|
2012
|
+
const parts = parsePath(path);
|
|
2013
|
+
if (path instanceof StorageKey && path.isUnparseable()) {
|
|
2014
|
+
obj[path.toString()] = value;
|
|
2015
|
+
return obj;
|
|
2016
|
+
}
|
|
2017
|
+
const lastKey = parts.pop();
|
|
2018
|
+
const target = parts.reduce((curr, key) => {
|
|
2019
|
+
const nextKey = parts[parts.indexOf(key) + 1];
|
|
2020
|
+
const shouldBeArray = !Number.isNaN(Number(nextKey));
|
|
2021
|
+
if (!(key in curr)) {
|
|
2022
|
+
curr[key] = shouldBeArray ? [] : {};
|
|
2023
|
+
}
|
|
2024
|
+
return curr[key];
|
|
2025
|
+
}, obj);
|
|
2026
|
+
target[lastKey] = value;
|
|
2027
|
+
return obj;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// src/core/storage/adapters/base-storage.service.ts
|
|
2031
|
+
var BaseStorage = class _BaseStorage {
|
|
2032
|
+
constructor(config, pluginExecutor, eventEmitter, logger) {
|
|
2033
|
+
this.config = config;
|
|
2034
|
+
this.pluginExecutor = pluginExecutor;
|
|
2035
|
+
this.eventEmitter = eventEmitter;
|
|
2036
|
+
this.logger = logger;
|
|
2037
|
+
this.name = config.name;
|
|
2038
|
+
this.middlewareModule = new MiddlewareModule({
|
|
2039
|
+
getState: this.getState.bind(this),
|
|
2040
|
+
// Предоставляем базовые операции хранилища
|
|
2041
|
+
doGet: this.doGet.bind(this),
|
|
2042
|
+
doSet: this.doSet.bind(this),
|
|
2043
|
+
doUpdate: this.doUpdate.bind(this),
|
|
2044
|
+
doDelete: this.doDelete.bind(this),
|
|
2045
|
+
doClear: this.doClear.bind(this),
|
|
2046
|
+
doKeys: this.doKeys.bind(this),
|
|
2047
|
+
// Предоставляем методы для работы с подписчиками
|
|
2048
|
+
notifySubscribers: this.notifySubscribers.bind(this),
|
|
2049
|
+
// Предоставляем плагины и эмиттер
|
|
2050
|
+
pluginExecutor: this.pluginExecutor,
|
|
2051
|
+
eventEmitter: this.eventEmitter,
|
|
2052
|
+
logger: this.logger
|
|
2053
|
+
});
|
|
2054
|
+
this.initializeMiddlewares();
|
|
2055
|
+
}
|
|
2056
|
+
// Константа для глобальной подписки
|
|
2057
|
+
static GLOBAL_SUBSCRIPTION_KEY = "*";
|
|
2058
|
+
name;
|
|
2059
|
+
selectorPathCache = /* @__PURE__ */ new WeakMap();
|
|
2060
|
+
middlewareModule;
|
|
2061
|
+
initializedMiddlewares = null;
|
|
2062
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
2063
|
+
initializeMiddlewares() {
|
|
2064
|
+
if (this.config.middlewares && !this.initializedMiddlewares) {
|
|
2065
|
+
this.initializedMiddlewares = this.config.middlewares(() => this.getDefaultMiddleware());
|
|
2066
|
+
this.initializedMiddlewares.forEach((middleware) => this.middlewareModule.use(middleware));
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
getDefaultMiddleware() {
|
|
2070
|
+
return {
|
|
2071
|
+
batching: (options = {}) => batchingMiddleware(options),
|
|
2072
|
+
shallowCompare: (options = {}) => shallowCompareMiddleware(options)
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
async initializeWithMiddlewares() {
|
|
2076
|
+
try {
|
|
2077
|
+
const state = await this.getState();
|
|
2078
|
+
const hasExistingState = Object.keys(state).length > 0;
|
|
2079
|
+
if (!hasExistingState && this.config.initialState) {
|
|
2080
|
+
await this.middlewareModule.dispatch({
|
|
2081
|
+
type: "init",
|
|
2082
|
+
value: this.config.initialState
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
this.logger?.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430", { error });
|
|
2087
|
+
throw error;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
async get(key) {
|
|
2091
|
+
try {
|
|
2092
|
+
const metadata = { operation: "get", timestamp: Date.now(), key };
|
|
2093
|
+
const middlewareResult = await this.middlewareModule.dispatch({
|
|
2094
|
+
type: "get",
|
|
2095
|
+
key,
|
|
2096
|
+
metadata
|
|
2097
|
+
});
|
|
2098
|
+
const finalResult = await this.pluginExecutor?.executeAfterGet(key, middlewareResult, metadata) ?? middlewareResult;
|
|
2099
|
+
await this.emitEvent({
|
|
2100
|
+
type: "storage:select" /* STORAGE_SELECT */,
|
|
2101
|
+
payload: { key, value: finalResult }
|
|
2102
|
+
});
|
|
2103
|
+
return finalResult;
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
this.logger?.error("Error getting value", { key, error });
|
|
2106
|
+
throw error;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
async set(key, value) {
|
|
2110
|
+
try {
|
|
2111
|
+
const metadata = { operation: "set", timestamp: Date.now(), key };
|
|
2112
|
+
const processedValue = await this.pluginExecutor?.executeBeforeSet(value, metadata) ?? value;
|
|
2113
|
+
const middlewareResult = await this.middlewareModule.dispatch({
|
|
2114
|
+
type: "set",
|
|
2115
|
+
key,
|
|
2116
|
+
value: processedValue,
|
|
2117
|
+
metadata
|
|
2118
|
+
});
|
|
2119
|
+
const valueNotChanged = middlewareResult?.__metadata?.valueNotChanged === true;
|
|
2120
|
+
let finalResult;
|
|
2121
|
+
if (valueNotChanged && middlewareResult?.__metadata?.originalValue !== void 0) {
|
|
2122
|
+
finalResult = middlewareResult.__metadata.originalValue;
|
|
2123
|
+
} else {
|
|
2124
|
+
finalResult = await this.pluginExecutor?.executeAfterSet(key, middlewareResult, metadata) ?? middlewareResult;
|
|
2125
|
+
}
|
|
2126
|
+
if (!valueNotChanged) {
|
|
2127
|
+
const keyStr = key.toString();
|
|
2128
|
+
const changedPaths = [keyStr];
|
|
2129
|
+
this.notifySubscribers(key, finalResult);
|
|
2130
|
+
this.notifySubscribers(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY, {
|
|
2131
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
2132
|
+
key,
|
|
2133
|
+
value: finalResult,
|
|
2134
|
+
changedPaths
|
|
2135
|
+
});
|
|
2136
|
+
await this.emitEvent({
|
|
2137
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
2138
|
+
payload: {
|
|
2139
|
+
key,
|
|
2140
|
+
value: finalResult,
|
|
2141
|
+
changedPaths
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
} catch (error) {
|
|
2146
|
+
this.logger?.error("Error setting value", { key, error });
|
|
2147
|
+
throw error;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
async update(updater) {
|
|
2151
|
+
try {
|
|
2152
|
+
const metadata = { operation: "update", timestamp: Date.now() };
|
|
2153
|
+
const currentState = await this.getState();
|
|
2154
|
+
const newState = structuredClone(currentState);
|
|
2155
|
+
updater(newState);
|
|
2156
|
+
const changedPaths = this.findChangedPaths(currentState, newState);
|
|
2157
|
+
if (changedPaths.size === 0) {
|
|
2158
|
+
if (this.logger?.debug) {
|
|
2159
|
+
this.logger.debug("No changes detected in update");
|
|
2160
|
+
}
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
if (this.logger?.debug) {
|
|
2164
|
+
this.logger.debug("Changed paths:", { paths: Array.from(changedPaths) });
|
|
2165
|
+
}
|
|
2166
|
+
const changedTopLevelKeys = /* @__PURE__ */ new Set();
|
|
2167
|
+
for (const path of changedPaths) {
|
|
2168
|
+
const topLevelKey = path.split(".")[0];
|
|
2169
|
+
changedTopLevelKeys.add(topLevelKey);
|
|
2170
|
+
}
|
|
2171
|
+
const updates = await Promise.all(
|
|
2172
|
+
Array.from(changedTopLevelKeys).map(async (key) => {
|
|
2173
|
+
const keyMetadata = { ...metadata, key };
|
|
2174
|
+
const processedValue = await this.pluginExecutor?.executeBeforeSet(newState[key], keyMetadata) ?? newState[key];
|
|
2175
|
+
return { key, value: processedValue };
|
|
2176
|
+
})
|
|
2177
|
+
);
|
|
2178
|
+
const result = await this.middlewareModule.dispatch({
|
|
2179
|
+
type: "update",
|
|
2180
|
+
value: updates,
|
|
2181
|
+
metadata: {
|
|
2182
|
+
...metadata,
|
|
2183
|
+
batchUpdate: true,
|
|
2184
|
+
changedPaths: Array.from(changedPaths)
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
let updatedValues = {};
|
|
2188
|
+
if (Array.isArray(result)) {
|
|
2189
|
+
result.forEach((update) => {
|
|
2190
|
+
if (update && typeof update === "object" && "key" in update && "value" in update) {
|
|
2191
|
+
updatedValues[update.key] = update.value;
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
} else if (result && typeof result === "object") {
|
|
2195
|
+
updatedValues = { ...result };
|
|
2196
|
+
}
|
|
2197
|
+
const actuallyChangedKeys = Object.keys(updatedValues).filter((key) => !this.isEqual(currentState[key], updatedValues[key]));
|
|
2198
|
+
if (actuallyChangedKeys.length === 0) {
|
|
2199
|
+
if (this.logger?.debug) {
|
|
2200
|
+
this.logger.debug("No actual changes after middleware processing");
|
|
2201
|
+
}
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
const finalUpdates = {};
|
|
2205
|
+
actuallyChangedKeys.forEach((key) => {
|
|
2206
|
+
finalUpdates[key] = updatedValues[key];
|
|
2207
|
+
});
|
|
2208
|
+
if (this.logger?.debug) {
|
|
2209
|
+
this.logger.debug("Notifying subscribers about changes:", { keys: actuallyChangedKeys });
|
|
2210
|
+
}
|
|
2211
|
+
this.notifySubscribers(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY, {
|
|
2212
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
2213
|
+
key: actuallyChangedKeys,
|
|
2214
|
+
value: finalUpdates,
|
|
2215
|
+
changedPaths: Array.from(changedPaths)
|
|
2216
|
+
// Добавляем информацию о всех изменившихся путях
|
|
2217
|
+
});
|
|
2218
|
+
for (const path of changedPaths) {
|
|
2219
|
+
try {
|
|
2220
|
+
const topLevelKey = path.split(".")[0];
|
|
2221
|
+
if (topLevelKey in finalUpdates) {
|
|
2222
|
+
let value;
|
|
2223
|
+
if (path === topLevelKey) {
|
|
2224
|
+
value = finalUpdates[topLevelKey];
|
|
2225
|
+
} else {
|
|
2226
|
+
const restPath = path.substring(topLevelKey.length + 1);
|
|
2227
|
+
value = getValueByPath(finalUpdates[topLevelKey], restPath);
|
|
2228
|
+
}
|
|
2229
|
+
if (value !== void 0) {
|
|
2230
|
+
this.notifySubscribers(path, value);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
} catch (error) {
|
|
2234
|
+
this.logger?.error("Error notifying path subscribers", { path, error });
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
await this.emitEvent({
|
|
2238
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
2239
|
+
payload: {
|
|
2240
|
+
state: finalUpdates,
|
|
2241
|
+
key: actuallyChangedKeys,
|
|
2242
|
+
changedPaths: Array.from(changedPaths)
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
this.logger?.error("Error updating state", { error });
|
|
2247
|
+
throw error;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
async delete(key) {
|
|
2251
|
+
try {
|
|
2252
|
+
const metadata = { operation: "delete", timestamp: Date.now(), key };
|
|
2253
|
+
if (await this.pluginExecutor?.executeBeforeDelete(key, metadata)) {
|
|
2254
|
+
const middlewareResult = await this.middlewareModule.dispatch({
|
|
2255
|
+
type: "delete",
|
|
2256
|
+
key,
|
|
2257
|
+
metadata
|
|
2258
|
+
});
|
|
2259
|
+
await this.pluginExecutor?.executeAfterDelete(key, metadata);
|
|
2260
|
+
const keyStr = key.toString();
|
|
2261
|
+
const changedPaths = [keyStr];
|
|
2262
|
+
this.notifySubscribers(key, void 0);
|
|
2263
|
+
this.notifySubscribers(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY, {
|
|
2264
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
2265
|
+
key,
|
|
2266
|
+
value: void 0,
|
|
2267
|
+
result: middlewareResult,
|
|
2268
|
+
changedPaths
|
|
2269
|
+
});
|
|
2270
|
+
await this.emitEvent({
|
|
2271
|
+
type: "storage:update" /* STORAGE_UPDATE */,
|
|
2272
|
+
payload: {
|
|
2273
|
+
key,
|
|
2274
|
+
value: void 0,
|
|
2275
|
+
result: middlewareResult,
|
|
2276
|
+
changedPaths
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
this.logger?.error("Error deleting value", { key, error });
|
|
2282
|
+
throw error;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
async clear() {
|
|
2286
|
+
try {
|
|
2287
|
+
this.pluginExecutor?.executeOnClear();
|
|
2288
|
+
await this.middlewareModule.dispatch({
|
|
2289
|
+
type: "clear"
|
|
2290
|
+
});
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
this.logger?.error("Error clearing storage", { error });
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
async keys() {
|
|
2297
|
+
try {
|
|
2298
|
+
return await this.middlewareModule.dispatch({
|
|
2299
|
+
type: "keys"
|
|
2300
|
+
});
|
|
2301
|
+
} catch (error) {
|
|
2302
|
+
this.logger?.error("Error getting keys", { error });
|
|
2303
|
+
throw error;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
async has(key) {
|
|
2307
|
+
try {
|
|
2308
|
+
return await this.doHas(key);
|
|
2309
|
+
} catch (error) {
|
|
2310
|
+
this.logger?.error("Error checking value existence", { key, error });
|
|
2311
|
+
throw error;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
async getState() {
|
|
2315
|
+
try {
|
|
2316
|
+
const value = await this.doGet("");
|
|
2317
|
+
return value || {};
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
this.logger?.error("Error getting state", { error });
|
|
2320
|
+
throw error;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// Вспомогательный метод для подписки на все изменения
|
|
2324
|
+
subscribeToAll(callback) {
|
|
2325
|
+
if (!this.subscribers.has(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY)) {
|
|
2326
|
+
this.subscribers.set(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY, /* @__PURE__ */ new Set());
|
|
2327
|
+
}
|
|
2328
|
+
this.subscribers.get(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY).add(callback);
|
|
2329
|
+
return () => {
|
|
2330
|
+
const subscribers = this.subscribers.get(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY);
|
|
2331
|
+
if (subscribers) {
|
|
2332
|
+
subscribers.delete(callback);
|
|
2333
|
+
if (subscribers.size === 0) {
|
|
2334
|
+
this.subscribers.delete(_BaseStorage.GLOBAL_SUBSCRIPTION_KEY);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
subscribe(keyOrSelector, callback) {
|
|
2340
|
+
if (typeof keyOrSelector === "string") {
|
|
2341
|
+
return this.subscribeByKey(keyOrSelector, callback);
|
|
2342
|
+
}
|
|
2343
|
+
return this.subscribeBySelector(keyOrSelector, callback);
|
|
2344
|
+
}
|
|
2345
|
+
async destroy() {
|
|
2346
|
+
try {
|
|
2347
|
+
await this.clear();
|
|
2348
|
+
await this.doDestroy();
|
|
2349
|
+
if (this.initializedMiddlewares) {
|
|
2350
|
+
await Promise.all(
|
|
2351
|
+
this.initializedMiddlewares.map(async (middleware) => {
|
|
2352
|
+
if ("cleanup" in middleware) {
|
|
2353
|
+
await middleware.cleanup?.();
|
|
2354
|
+
}
|
|
2355
|
+
})
|
|
2356
|
+
);
|
|
2357
|
+
this.initializedMiddlewares = null;
|
|
2358
|
+
}
|
|
2359
|
+
await this.emitEvent({
|
|
2360
|
+
type: "storage:destroy" /* STORAGE_DESTROY */
|
|
2361
|
+
});
|
|
2362
|
+
} catch (error) {
|
|
2363
|
+
this.logger?.error("Error destroying storage", { error });
|
|
2364
|
+
throw error;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
// Вспомогательные методы
|
|
2368
|
+
subscribeByKey(key, callback) {
|
|
2369
|
+
if (!this.subscribers.has(key)) {
|
|
2370
|
+
this.subscribers.set(key, /* @__PURE__ */ new Set());
|
|
2371
|
+
}
|
|
2372
|
+
let initialValueSent = false;
|
|
2373
|
+
this.subscribers.get(key).add(callback);
|
|
2374
|
+
this.get(key).then((value) => {
|
|
2375
|
+
try {
|
|
2376
|
+
if (!initialValueSent) {
|
|
2377
|
+
initialValueSent = true;
|
|
2378
|
+
callback(value);
|
|
2379
|
+
}
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
this.logger?.error("Error in initial callback", { key, error });
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
return () => {
|
|
2385
|
+
const subscribers = this.subscribers.get(key);
|
|
2386
|
+
if (subscribers) {
|
|
2387
|
+
subscribers.delete(callback);
|
|
2388
|
+
if (subscribers.size === 0) {
|
|
2389
|
+
this.subscribers.delete(key);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
createDummyState() {
|
|
2395
|
+
const handler = {
|
|
2396
|
+
get: (target, prop) => {
|
|
2397
|
+
target[prop] = target[prop] || new Proxy({}, handler);
|
|
2398
|
+
return target[prop];
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
return new Proxy({}, handler);
|
|
2402
|
+
}
|
|
2403
|
+
isEqual(a, b) {
|
|
2404
|
+
if (a === b) return true;
|
|
2405
|
+
if (a == null || b == null) return a === b;
|
|
2406
|
+
const typeA = typeof a;
|
|
2407
|
+
const typeB = typeof b;
|
|
2408
|
+
if (typeA !== typeB) return false;
|
|
2409
|
+
if (typeA !== "object") return a === b;
|
|
2410
|
+
if (a instanceof Date && b instanceof Date) {
|
|
2411
|
+
return a.getTime() === b.getTime();
|
|
2412
|
+
}
|
|
2413
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
2414
|
+
if (a.length !== b.length) return false;
|
|
2415
|
+
for (let i = 0; i < a.length; i++) {
|
|
2416
|
+
if (!this.isEqual(a[i], b[i])) return false;
|
|
2417
|
+
}
|
|
2418
|
+
return true;
|
|
2419
|
+
}
|
|
2420
|
+
const keysA = Object.keys(a);
|
|
2421
|
+
const keysB = Object.keys(b);
|
|
2422
|
+
if (keysA.length !== keysB.length) return false;
|
|
2423
|
+
return keysA.every((key) => Object.prototype.hasOwnProperty.call(b, key) && this.isEqual(a[key], b[key]));
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Возвращает полный путь, а не только корневой ключ
|
|
2427
|
+
*/
|
|
2428
|
+
extractPath(selector, dummyState) {
|
|
2429
|
+
if (this.selectorPathCache.has(selector)) {
|
|
2430
|
+
return this.selectorPathCache.get(selector);
|
|
2431
|
+
}
|
|
2432
|
+
const accessedPaths = [];
|
|
2433
|
+
const createProxyHandler = (path = "") => ({
|
|
2434
|
+
get: (target, prop) => {
|
|
2435
|
+
if (typeof prop === "symbol") {
|
|
2436
|
+
return Reflect.get(target, prop);
|
|
2437
|
+
}
|
|
2438
|
+
const currentPath = path ? `${path}.${prop}` : prop;
|
|
2439
|
+
accessedPaths.push(currentPath);
|
|
2440
|
+
return new Proxy({}, createProxyHandler(currentPath));
|
|
2441
|
+
},
|
|
2442
|
+
// Обработка опциональной цепочки (?.)
|
|
2443
|
+
has: (target, prop) => {
|
|
2444
|
+
return true;
|
|
2445
|
+
},
|
|
2446
|
+
// Поддержка для Array.prototype.map и других операций над массивами
|
|
2447
|
+
ownKeys: () => [],
|
|
2448
|
+
getOwnPropertyDescriptor: () => ({
|
|
2449
|
+
configurable: true,
|
|
2450
|
+
enumerable: true
|
|
2451
|
+
}),
|
|
2452
|
+
apply: (target, thisArg, args) => {
|
|
2453
|
+
return new Proxy(() => {
|
|
2454
|
+
}, createProxyHandler(path));
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
try {
|
|
2458
|
+
selector(new Proxy(dummyState, createProxyHandler()));
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
}
|
|
2461
|
+
if (accessedPaths.length === 0) return "";
|
|
2462
|
+
accessedPaths.sort((a, b) => b.length - a.length);
|
|
2463
|
+
this.selectorPathCache.set(selector, accessedPaths[0]);
|
|
2464
|
+
return accessedPaths[0];
|
|
2465
|
+
}
|
|
2466
|
+
notifySubscribers(key, value) {
|
|
2467
|
+
const keyStr = key.toString();
|
|
2468
|
+
const exactSubscribers = this.subscribers.get(keyStr);
|
|
2469
|
+
if (exactSubscribers?.size) {
|
|
2470
|
+
const subscribersCopy = new Set(exactSubscribers);
|
|
2471
|
+
subscribersCopy.forEach((callback) => {
|
|
2472
|
+
try {
|
|
2473
|
+
callback(value);
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
this.logger?.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043E\u0434\u043F\u0438\u0441\u0447\u0438\u043A\u0435 \u043D\u0430 \u043A\u043E\u043B\u0431\u044D\u043A", { key: keyStr, error });
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Метод для определения изменившихся путей между двумя объектами
|
|
2482
|
+
*/
|
|
2483
|
+
findChangedPaths(oldObj, newObj, prefix = "", changedPaths = /* @__PURE__ */ new Set(), visited = /* @__PURE__ */ new WeakMap()) {
|
|
2484
|
+
if (oldObj === newObj) return changedPaths;
|
|
2485
|
+
if (typeof oldObj !== "object" || typeof newObj !== "object" || oldObj === null || newObj === null) {
|
|
2486
|
+
if (oldObj !== newObj) {
|
|
2487
|
+
changedPaths.add(prefix || "");
|
|
2488
|
+
}
|
|
2489
|
+
return changedPaths;
|
|
2490
|
+
}
|
|
2491
|
+
if (visited.has(oldObj)) return changedPaths;
|
|
2492
|
+
visited.set(oldObj, true);
|
|
2493
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldObj || {}), ...Object.keys(newObj || {})]);
|
|
2494
|
+
for (const key of allKeys) {
|
|
2495
|
+
const oldValue = oldObj[key];
|
|
2496
|
+
const newValue = newObj[key];
|
|
2497
|
+
if (oldValue === newValue) continue;
|
|
2498
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2499
|
+
if (oldValue && newValue && typeof oldValue === "object" && typeof newValue === "object" && !Array.isArray(oldValue) && !Array.isArray(newValue)) {
|
|
2500
|
+
this.findChangedPaths(oldValue, newValue, path, changedPaths, visited);
|
|
2501
|
+
} else if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
|
2502
|
+
if (!this.isEqual(oldValue, newValue)) {
|
|
2503
|
+
changedPaths.add(path);
|
|
2504
|
+
}
|
|
2505
|
+
} else if (!this.isEqual(oldValue, newValue)) {
|
|
2506
|
+
changedPaths.add(path);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
return changedPaths;
|
|
2510
|
+
}
|
|
2511
|
+
subscribeBySelector(pathSelector, callback) {
|
|
2512
|
+
const dummyState = this.createDummyState();
|
|
2513
|
+
const fullPath = this.extractPath(pathSelector, dummyState);
|
|
2514
|
+
if (this.logger?.debug) {
|
|
2515
|
+
this.logger.debug("Subscribing to path:", { path: fullPath });
|
|
2516
|
+
}
|
|
2517
|
+
const wrappedCallback = async (value) => {
|
|
2518
|
+
try {
|
|
2519
|
+
if (value === void 0 || value === null) {
|
|
2520
|
+
const currentState2 = await this.getState();
|
|
2521
|
+
const selectedValue2 = pathSelector(currentState2);
|
|
2522
|
+
callback(selectedValue2);
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (typeof value !== "object" || value === null) {
|
|
2526
|
+
callback(value);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
const currentState = await this.getState();
|
|
2530
|
+
const selectedValue = pathSelector(currentState);
|
|
2531
|
+
callback(selectedValue);
|
|
2532
|
+
} catch (error) {
|
|
2533
|
+
this.logger?.error("Error in selector callback", { path: fullPath, error });
|
|
2534
|
+
callback(value);
|
|
2535
|
+
}
|
|
2536
|
+
};
|
|
2537
|
+
if (!fullPath) {
|
|
2538
|
+
return this.subscribeToAll(() => {
|
|
2539
|
+
this.getState().then((state) => {
|
|
2540
|
+
callback(pathSelector(state));
|
|
2541
|
+
});
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
return this.subscribeByKey(fullPath, wrappedCallback);
|
|
2545
|
+
}
|
|
2546
|
+
async emitEvent(event) {
|
|
2547
|
+
try {
|
|
2548
|
+
await this.eventEmitter?.emit({
|
|
2549
|
+
...event,
|
|
2550
|
+
metadata: {
|
|
2551
|
+
...event.metadata || {},
|
|
2552
|
+
timestamp: Date.now(),
|
|
2553
|
+
storageName: this.name
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
} catch (error) {
|
|
2557
|
+
this.logger?.error("Error emitting event", { event, error });
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
|
|
2562
|
+
// src/core/storage/adapters/indexed-DB.service.ts
|
|
2563
|
+
var IndexedDBManager = class _IndexedDBManager {
|
|
2564
|
+
constructor(dbName, dbVersion, logger) {
|
|
2565
|
+
this.dbName = dbName;
|
|
2566
|
+
this.logger = logger;
|
|
2567
|
+
this.dbVersion = dbVersion;
|
|
2568
|
+
}
|
|
2569
|
+
static instances = /* @__PURE__ */ new Map();
|
|
2570
|
+
db = null;
|
|
2571
|
+
initPromise = null;
|
|
2572
|
+
storeNames = /* @__PURE__ */ new Set();
|
|
2573
|
+
dbVersion;
|
|
2574
|
+
static getInstance(dbName, dbVersion = 1, logger) {
|
|
2575
|
+
if (!_IndexedDBManager.instances.has(dbName)) {
|
|
2576
|
+
_IndexedDBManager.instances.set(dbName, new _IndexedDBManager(dbName, dbVersion, logger));
|
|
2577
|
+
}
|
|
2578
|
+
const instance = _IndexedDBManager.instances.get(dbName);
|
|
2579
|
+
if (dbVersion > instance.dbVersion) {
|
|
2580
|
+
instance.dbVersion = dbVersion;
|
|
2581
|
+
}
|
|
2582
|
+
return instance;
|
|
2583
|
+
}
|
|
2584
|
+
async initialize() {
|
|
2585
|
+
if (this.db) {
|
|
2586
|
+
return this.db;
|
|
2587
|
+
}
|
|
2588
|
+
if (!this.initPromise) {
|
|
2589
|
+
this.initPromise = this.openDatabase();
|
|
2590
|
+
}
|
|
2591
|
+
return this.initPromise;
|
|
2592
|
+
}
|
|
2593
|
+
async ensureStoreExists(storeName) {
|
|
2594
|
+
await this.initialize();
|
|
2595
|
+
if (this.db.objectStoreNames.contains(storeName)) {
|
|
2596
|
+
this.storeNames.add(storeName);
|
|
2597
|
+
return this.db;
|
|
2598
|
+
}
|
|
2599
|
+
this.logger?.debug(`Store "${storeName}" not found, upgrading database`, {
|
|
2600
|
+
dbName: this.dbName,
|
|
2601
|
+
currentStores: Array.from(this.db.objectStoreNames)
|
|
2602
|
+
});
|
|
2603
|
+
this.db.close();
|
|
2604
|
+
this.db = null;
|
|
2605
|
+
this.dbVersion++;
|
|
2606
|
+
this.initPromise = this.openDatabase([storeName]);
|
|
2607
|
+
const newDb = await this.initPromise;
|
|
2608
|
+
this.storeNames.add(storeName);
|
|
2609
|
+
return newDb;
|
|
2610
|
+
}
|
|
2611
|
+
async openDatabase(newStores = []) {
|
|
2612
|
+
return new Promise((resolve, reject) => {
|
|
2613
|
+
this.logger?.debug(`Opening database "${this.dbName}" with version ${this.dbVersion}`);
|
|
2614
|
+
const request = indexedDB.open(this.dbName, this.dbVersion);
|
|
2615
|
+
request.onerror = () => {
|
|
2616
|
+
this.logger?.error(`Failed to open database "${this.dbName}"`, { error: request.error });
|
|
2617
|
+
reject(request.error);
|
|
2618
|
+
};
|
|
2619
|
+
request.onsuccess = () => {
|
|
2620
|
+
this.db = request.result;
|
|
2621
|
+
for (let i = 0; i < this.db.objectStoreNames.length; i++) {
|
|
2622
|
+
this.storeNames.add(this.db.objectStoreNames[i]);
|
|
2623
|
+
}
|
|
2624
|
+
this.logger?.debug(`Database "${this.dbName}" opened successfully`, {
|
|
2625
|
+
version: this.db.version,
|
|
2626
|
+
stores: Array.from(this.db.objectStoreNames)
|
|
2627
|
+
});
|
|
2628
|
+
resolve(this.db);
|
|
2629
|
+
};
|
|
2630
|
+
request.onupgradeneeded = (event) => {
|
|
2631
|
+
const db = event.target.result;
|
|
2632
|
+
this.logger?.debug(`Upgrading database "${this.dbName}" to version ${this.dbVersion}`);
|
|
2633
|
+
for (const storeName of newStores) {
|
|
2634
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
2635
|
+
this.logger?.debug(`Creating store "${storeName}"`);
|
|
2636
|
+
db.createObjectStore(storeName);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
closeDatabase() {
|
|
2643
|
+
if (this.db) {
|
|
2644
|
+
this.db.close();
|
|
2645
|
+
this.db = null;
|
|
2646
|
+
this.initPromise = null;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
async deleteDatabase() {
|
|
2650
|
+
this.closeDatabase();
|
|
2651
|
+
return new Promise((resolve, reject) => {
|
|
2652
|
+
const request = indexedDB.deleteDatabase(this.dbName);
|
|
2653
|
+
request.onsuccess = () => {
|
|
2654
|
+
this.logger?.debug(`Database "${this.dbName}" deleted successfully`);
|
|
2655
|
+
_IndexedDBManager.instances.delete(this.dbName);
|
|
2656
|
+
this.storeNames.clear();
|
|
2657
|
+
resolve();
|
|
2658
|
+
};
|
|
2659
|
+
request.onerror = () => {
|
|
2660
|
+
this.logger?.error(`Failed to delete database "${this.dbName}"`, { error: request.error });
|
|
2661
|
+
reject(request.error);
|
|
2662
|
+
};
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
async ensureStoresExist(storeNames) {
|
|
2666
|
+
await this.initialize();
|
|
2667
|
+
const missingStores = storeNames.filter((name) => !this.db.objectStoreNames.contains(name));
|
|
2668
|
+
if (missingStores.length === 0) {
|
|
2669
|
+
return this.db;
|
|
2670
|
+
}
|
|
2671
|
+
this.logger?.debug(`\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0445 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449: ${missingStores.join(", ")}`, {
|
|
2672
|
+
dbName: this.dbName,
|
|
2673
|
+
currentStores: Array.from(this.db.objectStoreNames)
|
|
2674
|
+
});
|
|
2675
|
+
this.db.close();
|
|
2676
|
+
this.db = null;
|
|
2677
|
+
this.dbVersion++;
|
|
2678
|
+
this.initPromise = this.openDatabase(missingStores);
|
|
2679
|
+
return this.initPromise;
|
|
2680
|
+
}
|
|
2681
|
+
// Метод для получения текущей версии
|
|
2682
|
+
getCurrentVersion() {
|
|
2683
|
+
return this.dbVersion;
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
var DBVersionManager = class {
|
|
2687
|
+
constructor(dbName, initialVersion = 1, logger) {
|
|
2688
|
+
this.dbName = dbName;
|
|
2689
|
+
this.logger = logger;
|
|
2690
|
+
this.version = initialVersion;
|
|
2691
|
+
}
|
|
2692
|
+
db = null;
|
|
2693
|
+
version;
|
|
2694
|
+
/**
|
|
2695
|
+
* Получает текущую версию базы данных
|
|
2696
|
+
*/
|
|
2697
|
+
getCurrentVersion() {
|
|
2698
|
+
return this.version;
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Открывает базу данных с заданной версией
|
|
2702
|
+
*/
|
|
2703
|
+
openDatabase(version, newStores = []) {
|
|
2704
|
+
return new Promise((resolve, reject) => {
|
|
2705
|
+
this.logger?.debug(`\u041E\u0442\u043A\u0440\u044B\u0442\u0438\u0435 \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445 "${this.dbName}" \u0441 \u0432\u0435\u0440\u0441\u0438\u0435\u0439 ${version}`);
|
|
2706
|
+
const request = indexedDB.open(this.dbName, version);
|
|
2707
|
+
request.onerror = () => {
|
|
2708
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u0438\u0438 \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445 "${this.dbName}"`, { error: request.error });
|
|
2709
|
+
reject(request.error);
|
|
2710
|
+
};
|
|
2711
|
+
request.onsuccess = () => {
|
|
2712
|
+
this.db = request.result;
|
|
2713
|
+
this.version = this.db.version;
|
|
2714
|
+
this.logger?.debug(`\u0411\u0430\u0437\u0430 \u0434\u0430\u043D\u043D\u044B\u0445 "${this.dbName}" \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043E\u0442\u043A\u0440\u044B\u0442\u0430`, {
|
|
2715
|
+
version: this.db.version,
|
|
2716
|
+
stores: Array.from(this.db.objectStoreNames)
|
|
2717
|
+
});
|
|
2718
|
+
resolve(this.db);
|
|
2719
|
+
};
|
|
2720
|
+
request.onupgradeneeded = (event) => {
|
|
2721
|
+
const db = event.target.result;
|
|
2722
|
+
this.logger?.debug(`\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445 "${this.dbName}" \u0434\u043E \u0432\u0435\u0440\u0441\u0438\u0438 ${version}`);
|
|
2723
|
+
for (const storeName of newStores) {
|
|
2724
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
2725
|
+
this.logger?.debug(`\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430 "${storeName}"`);
|
|
2726
|
+
db.createObjectStore(storeName);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Убеждается, что все указанные хранилища существуют в базе данных
|
|
2734
|
+
*/
|
|
2735
|
+
async ensureStoresExist(storeNames) {
|
|
2736
|
+
if (!this.db) {
|
|
2737
|
+
this.db = await this.openDatabase(this.version);
|
|
2738
|
+
}
|
|
2739
|
+
const missingStores = storeNames.filter((name) => !this.db.objectStoreNames.contains(name));
|
|
2740
|
+
if (missingStores.length === 0) {
|
|
2741
|
+
return this.db;
|
|
2742
|
+
}
|
|
2743
|
+
this.logger?.debug(`\u041D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430: ${missingStores.join(", ")}`, {
|
|
2744
|
+
dbName: this.dbName,
|
|
2745
|
+
currentVersion: this.version
|
|
2746
|
+
});
|
|
2747
|
+
this.db.close();
|
|
2748
|
+
this.db = null;
|
|
2749
|
+
this.version++;
|
|
2750
|
+
this.db = await this.openDatabase(this.version, missingStores);
|
|
2751
|
+
return this.db;
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Закрывает соединение с базой данных
|
|
2755
|
+
*/
|
|
2756
|
+
close() {
|
|
2757
|
+
if (this.db) {
|
|
2758
|
+
this.db.close();
|
|
2759
|
+
this.db = null;
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
2763
|
+
var IndexedDBStorage = class _IndexedDBStorage extends BaseStorage {
|
|
2764
|
+
DB_NAME;
|
|
2765
|
+
STORE_NAME;
|
|
2766
|
+
DB_VERSION;
|
|
2767
|
+
dbManager;
|
|
2768
|
+
constructor(config, pluginExecutor, eventEmitter, logger) {
|
|
2769
|
+
super(config, pluginExecutor, eventEmitter, logger);
|
|
2770
|
+
const options = config.options;
|
|
2771
|
+
this.DB_NAME = options.dbName || "app_storage";
|
|
2772
|
+
this.STORE_NAME = config.name;
|
|
2773
|
+
this.DB_VERSION = options.dbVersion || 1;
|
|
2774
|
+
this.dbManager = IndexedDBManager.getInstance(this.DB_NAME, this.DB_VERSION, logger);
|
|
2775
|
+
}
|
|
2776
|
+
async initialize() {
|
|
2777
|
+
try {
|
|
2778
|
+
this.logger?.debug(`Initializing IndexedDB storage "${this.STORE_NAME}"`);
|
|
2779
|
+
await this.dbManager.ensureStoreExists(this.STORE_NAME);
|
|
2780
|
+
try {
|
|
2781
|
+
const db = await this.dbManager.initialize();
|
|
2782
|
+
if (!db.objectStoreNames.contains(this.STORE_NAME)) {
|
|
2783
|
+
throw new Error(`Store "${this.STORE_NAME}" not found after initialization`);
|
|
2784
|
+
}
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
this.logger?.error(`Error verifying store "${this.STORE_NAME}" exists:`, { error });
|
|
2787
|
+
throw error;
|
|
2788
|
+
}
|
|
2789
|
+
this.initializeMiddlewares();
|
|
2790
|
+
try {
|
|
2791
|
+
await this.initializeWithMiddlewares();
|
|
2792
|
+
this.logger?.debug(`IndexedDB storage "${this.STORE_NAME}" initialized successfully`);
|
|
2793
|
+
} catch (error) {
|
|
2794
|
+
this.logger?.error(`Failed to initialize middleware for store "${this.STORE_NAME}"`, { error });
|
|
2795
|
+
}
|
|
2796
|
+
return this;
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
this.logger?.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 IndexedDB "${this.name}"`, { error });
|
|
2799
|
+
throw error;
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
static async getCurrentDBVersion(dbName) {
|
|
2803
|
+
return new Promise((resolve) => {
|
|
2804
|
+
try {
|
|
2805
|
+
const request = indexedDB.open(dbName);
|
|
2806
|
+
request.onsuccess = () => {
|
|
2807
|
+
const version = request.result.version;
|
|
2808
|
+
request.result.close();
|
|
2809
|
+
resolve(version);
|
|
2810
|
+
};
|
|
2811
|
+
request.onerror = () => {
|
|
2812
|
+
console.warn(`\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u0438 \u0432\u0435\u0440\u0441\u0438\u0438 \u0411\u0414 ${dbName}`, request.error);
|
|
2813
|
+
resolve(0);
|
|
2814
|
+
};
|
|
2815
|
+
} catch (error) {
|
|
2816
|
+
console.warn(`\u0418\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u0438 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u0438 \u0432\u0435\u0440\u0441\u0438\u0438 \u0411\u0414 ${dbName}`, error);
|
|
2817
|
+
resolve(0);
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
static async createStorages(dbName, configs, logger) {
|
|
2822
|
+
const currentVersion = await this.getCurrentDBVersion(dbName);
|
|
2823
|
+
const initialVersion = currentVersion || 1;
|
|
2824
|
+
const dbManager = new DBVersionManager(dbName, initialVersion, logger);
|
|
2825
|
+
const storeNames = Object.values(configs).map((config) => config.name);
|
|
2826
|
+
await dbManager.ensureStoresExist(storeNames);
|
|
2827
|
+
const result = {};
|
|
2828
|
+
for (const [key, config] of Object.entries(configs)) {
|
|
2829
|
+
const storage = new _IndexedDBStorage(
|
|
2830
|
+
{
|
|
2831
|
+
...config,
|
|
2832
|
+
options: {
|
|
2833
|
+
dbName,
|
|
2834
|
+
dbVersion: dbManager.getCurrentVersion()
|
|
2835
|
+
}
|
|
2836
|
+
},
|
|
2837
|
+
config.pluginExecutor,
|
|
2838
|
+
config.eventEmitter,
|
|
2839
|
+
logger
|
|
2840
|
+
);
|
|
2841
|
+
result[key] = await storage.initialize();
|
|
2842
|
+
}
|
|
2843
|
+
return result;
|
|
2844
|
+
}
|
|
2845
|
+
async getTransaction(mode = "readonly") {
|
|
2846
|
+
try {
|
|
2847
|
+
const db = await this.dbManager.ensureStoreExists(this.STORE_NAME);
|
|
2848
|
+
if (!db.objectStoreNames.contains(this.STORE_NAME)) {
|
|
2849
|
+
this.logger?.warn(`Object store "${this.STORE_NAME}" not found, attempting to repair`);
|
|
2850
|
+
db.close();
|
|
2851
|
+
this.dbManager.closeDatabase();
|
|
2852
|
+
const newDb = await this.dbManager.ensureStoreExists(this.STORE_NAME);
|
|
2853
|
+
if (!newDb.objectStoreNames.contains(this.STORE_NAME)) {
|
|
2854
|
+
throw new Error(`Object store "${this.STORE_NAME}" still doesn't exist after repair attempt`);
|
|
2855
|
+
}
|
|
2856
|
+
return newDb.transaction(this.STORE_NAME, mode);
|
|
2857
|
+
}
|
|
2858
|
+
return db.transaction(this.STORE_NAME, mode);
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
this.logger?.error(`Failed to create transaction for store "${this.STORE_NAME}"`, { error });
|
|
2861
|
+
throw error;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
async getObjectStore(mode = "readonly") {
|
|
2865
|
+
const transaction = await this.getTransaction(mode);
|
|
2866
|
+
return transaction.objectStore(this.STORE_NAME);
|
|
2867
|
+
}
|
|
2868
|
+
async doGet(key) {
|
|
2869
|
+
const store = await this.getObjectStore();
|
|
2870
|
+
if (key === "") {
|
|
2871
|
+
return new Promise((resolve, reject) => {
|
|
2872
|
+
const request = store.getAll();
|
|
2873
|
+
request.onerror = () => reject(request.error);
|
|
2874
|
+
request.onsuccess = () => {
|
|
2875
|
+
const allValues = request.result;
|
|
2876
|
+
const allKeys = store.getAllKeys();
|
|
2877
|
+
allKeys.onsuccess = () => {
|
|
2878
|
+
const state = allKeys.result.reduce(
|
|
2879
|
+
(acc, k, index) => {
|
|
2880
|
+
if (k !== "root") {
|
|
2881
|
+
acc[k] = allValues[index];
|
|
2882
|
+
}
|
|
2883
|
+
return acc;
|
|
2884
|
+
},
|
|
2885
|
+
{}
|
|
2886
|
+
);
|
|
2887
|
+
resolve(state);
|
|
2888
|
+
};
|
|
2889
|
+
allKeys.onerror = () => reject(allKeys.error);
|
|
2890
|
+
};
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
2894
|
+
return new Promise((resolve, reject) => {
|
|
2895
|
+
const request = store.get(key.valueOf());
|
|
2896
|
+
request.onerror = () => reject(request.error);
|
|
2897
|
+
request.onsuccess = () => resolve(request.result);
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
const parts = parsePath(key);
|
|
2901
|
+
if (parts.length > 1) {
|
|
2902
|
+
const rootKey = parts[0];
|
|
2903
|
+
return new Promise((resolve, reject) => {
|
|
2904
|
+
const request = store.get(rootKey);
|
|
2905
|
+
request.onerror = () => reject(request.error);
|
|
2906
|
+
request.onsuccess = () => {
|
|
2907
|
+
const rootValue = request.result;
|
|
2908
|
+
if (!rootValue) {
|
|
2909
|
+
resolve(void 0);
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
const value = getValueByPath(rootValue, parts.slice(1).join("."));
|
|
2913
|
+
resolve(value);
|
|
2914
|
+
};
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
return new Promise((resolve, reject) => {
|
|
2918
|
+
const request = store.get(parts[0]);
|
|
2919
|
+
request.onerror = () => reject(request.error);
|
|
2920
|
+
request.onsuccess = () => resolve(request.result);
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
async doSet(key, value) {
|
|
2924
|
+
if (key === "") {
|
|
2925
|
+
const store2 = await this.getObjectStore("readwrite");
|
|
2926
|
+
return new Promise((resolve, reject) => {
|
|
2927
|
+
const tx = store2.transaction;
|
|
2928
|
+
tx.oncomplete = () => {
|
|
2929
|
+
resolve();
|
|
2930
|
+
};
|
|
2931
|
+
tx.onerror = () => {
|
|
2932
|
+
reject(tx.error);
|
|
2933
|
+
};
|
|
2934
|
+
const clearRequest = store2.clear();
|
|
2935
|
+
clearRequest.onsuccess = () => {
|
|
2936
|
+
const entries = Object.entries(value);
|
|
2937
|
+
for (const [entryKey, entryValue] of entries) {
|
|
2938
|
+
store2.put(entryValue, entryKey);
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
clearRequest.onerror = () => {
|
|
2942
|
+
reject(clearRequest.error);
|
|
2943
|
+
};
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
const store = await this.getObjectStore("readwrite");
|
|
2947
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
2948
|
+
await this.putValueInStore(store, key.valueOf(), value);
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
const parts = parsePath(key);
|
|
2952
|
+
if (parts.length > 1) {
|
|
2953
|
+
const rootKey = parts[0];
|
|
2954
|
+
return new Promise((resolve, reject) => {
|
|
2955
|
+
const request = store.get(rootKey);
|
|
2956
|
+
request.onerror = () => reject(request.error);
|
|
2957
|
+
request.onsuccess = () => {
|
|
2958
|
+
const rootValue = request.result || {};
|
|
2959
|
+
const updatedValue = setValueByPath(rootValue, parts.slice(1).join("."), value);
|
|
2960
|
+
const putRequest = store.put(updatedValue, rootKey);
|
|
2961
|
+
putRequest.onerror = () => reject(putRequest.error);
|
|
2962
|
+
putRequest.onsuccess = () => resolve();
|
|
2963
|
+
};
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
await this.putValueInStore(store, parts[0], value);
|
|
2967
|
+
}
|
|
2968
|
+
async putValueInStore(store, key, value) {
|
|
2969
|
+
return new Promise((resolve, reject) => {
|
|
2970
|
+
const request = store.put(value, key.valueOf());
|
|
2971
|
+
request.onerror = () => reject(request.error);
|
|
2972
|
+
request.onsuccess = () => resolve();
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
async doUpdate(updates) {
|
|
2976
|
+
const updatesByRoot = /* @__PURE__ */ new Map();
|
|
2977
|
+
const rawUpdates = [];
|
|
2978
|
+
for (const { key, value } of updates) {
|
|
2979
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
2980
|
+
rawUpdates.push({ key: key.valueOf(), value });
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
const parts = parsePath(key);
|
|
2984
|
+
const rootKey = parts[0];
|
|
2985
|
+
const path = parts.slice(1);
|
|
2986
|
+
if (!updatesByRoot.has(rootKey)) {
|
|
2987
|
+
updatesByRoot.set(rootKey, []);
|
|
2988
|
+
}
|
|
2989
|
+
updatesByRoot.get(rootKey).push({ path, value });
|
|
2990
|
+
}
|
|
2991
|
+
try {
|
|
2992
|
+
for (const { key, value } of rawUpdates) {
|
|
2993
|
+
const store = await this.getObjectStore("readwrite");
|
|
2994
|
+
await this.putValueInStore(store, key, value);
|
|
2995
|
+
}
|
|
2996
|
+
for (const [rootKey, pathUpdates] of updatesByRoot) {
|
|
2997
|
+
const rootValue = await this.doGet(rootKey) || {};
|
|
2998
|
+
let updatedValue = { ...rootValue };
|
|
2999
|
+
for (const { path, value } of pathUpdates) {
|
|
3000
|
+
if (path.length === 0) {
|
|
3001
|
+
updatedValue = value;
|
|
3002
|
+
} else {
|
|
3003
|
+
updatedValue = setValueByPath(updatedValue, path.join("."), value);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
const store = await this.getObjectStore("readwrite");
|
|
3007
|
+
await this.putValueInStore(store, rootKey, updatedValue);
|
|
3008
|
+
}
|
|
3009
|
+
} catch (error) {
|
|
3010
|
+
this.logger?.error("Error during update:", { error });
|
|
3011
|
+
throw error;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
async doDelete(key) {
|
|
3015
|
+
const store = await this.getObjectStore("readwrite");
|
|
3016
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3017
|
+
return new Promise((resolve, reject) => {
|
|
3018
|
+
const request = store.delete(key.valueOf());
|
|
3019
|
+
request.onerror = () => reject(request.error);
|
|
3020
|
+
request.onsuccess = () => resolve(true);
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
const parts = parsePath(key);
|
|
3024
|
+
if (parts.length === 1) {
|
|
3025
|
+
return new Promise((resolve, reject) => {
|
|
3026
|
+
const request = store.delete(parts[0]);
|
|
3027
|
+
request.onerror = () => reject(request.error);
|
|
3028
|
+
request.onsuccess = () => resolve(true);
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
const rootKey = parts[0];
|
|
3032
|
+
return new Promise((resolve, reject) => {
|
|
3033
|
+
const getRequest = store.get(rootKey);
|
|
3034
|
+
getRequest.onerror = () => reject(getRequest.error);
|
|
3035
|
+
getRequest.onsuccess = () => {
|
|
3036
|
+
const rootValue = getRequest.result;
|
|
3037
|
+
if (!rootValue) {
|
|
3038
|
+
resolve(false);
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
const parent = getValueByPath(rootValue, parts.slice(0, -1).join("."));
|
|
3042
|
+
const lastKey = parts[parts.length - 1];
|
|
3043
|
+
if (!parent || !(lastKey in parent)) {
|
|
3044
|
+
resolve(false);
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
if (Array.isArray(parent)) {
|
|
3048
|
+
const index = parseInt(lastKey, 10);
|
|
3049
|
+
if (!isNaN(index)) {
|
|
3050
|
+
parent.splice(index, 1);
|
|
3051
|
+
} else {
|
|
3052
|
+
delete parent[lastKey];
|
|
3053
|
+
}
|
|
3054
|
+
} else {
|
|
3055
|
+
delete parent[lastKey];
|
|
3056
|
+
}
|
|
3057
|
+
const putRequest = store.put(rootValue, rootKey);
|
|
3058
|
+
putRequest.onerror = () => reject(putRequest.error);
|
|
3059
|
+
putRequest.onsuccess = () => resolve(true);
|
|
3060
|
+
};
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
async doClear() {
|
|
3064
|
+
const store = await this.getObjectStore("readwrite");
|
|
3065
|
+
return new Promise((resolve, reject) => {
|
|
3066
|
+
const request = store.clear();
|
|
3067
|
+
request.onsuccess = () => resolve();
|
|
3068
|
+
request.onerror = () => reject(request.error);
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
async doKeys() {
|
|
3072
|
+
const store = await this.getObjectStore();
|
|
3073
|
+
const request = store.getAllKeys();
|
|
3074
|
+
return new Promise((resolve, reject) => {
|
|
3075
|
+
request.onsuccess = () => {
|
|
3076
|
+
resolve(request.result);
|
|
3077
|
+
};
|
|
3078
|
+
request.onerror = () => reject(request.error);
|
|
3079
|
+
});
|
|
3080
|
+
}
|
|
3081
|
+
async doHas(key) {
|
|
3082
|
+
const value = await this.doGet(key);
|
|
3083
|
+
return value !== void 0;
|
|
3084
|
+
}
|
|
3085
|
+
async doDestroy() {
|
|
3086
|
+
try {
|
|
3087
|
+
await this.doClear();
|
|
3088
|
+
} catch (error) {
|
|
3089
|
+
this.logger?.error(`Error destroying store "${this.STORE_NAME}"`, { error });
|
|
3090
|
+
throw error;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
|
|
3095
|
+
// src/core/storage/adapters/local-storage.service.ts
|
|
3096
|
+
var LocalStorage = class extends BaseStorage {
|
|
3097
|
+
constructor(config, pluginExecutor, eventEmitter, logger) {
|
|
3098
|
+
super(config, pluginExecutor, eventEmitter, logger);
|
|
3099
|
+
}
|
|
3100
|
+
async initialize() {
|
|
3101
|
+
try {
|
|
3102
|
+
await this.initializeWithMiddlewares();
|
|
3103
|
+
return this;
|
|
3104
|
+
} catch (error) {
|
|
3105
|
+
this.logger?.error("Error initializing LocalStorage", { error });
|
|
3106
|
+
throw error;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
async doGet(key) {
|
|
3110
|
+
const storageData = localStorage.getItem(this.name);
|
|
3111
|
+
if (!storageData) return void 0;
|
|
3112
|
+
const state = JSON.parse(storageData);
|
|
3113
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3114
|
+
return state[key.valueOf()];
|
|
3115
|
+
}
|
|
3116
|
+
return getValueByPath(state, key);
|
|
3117
|
+
}
|
|
3118
|
+
async doSet(key, value) {
|
|
3119
|
+
const storageData = localStorage.getItem(this.name);
|
|
3120
|
+
const state = storageData ? JSON.parse(storageData) : {};
|
|
3121
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3122
|
+
state[key.valueOf()] = value;
|
|
3123
|
+
localStorage.setItem(this.name, JSON.stringify(state));
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
const newState = setValueByPath({ ...state }, key, value);
|
|
3127
|
+
localStorage.setItem(this.name, JSON.stringify(newState));
|
|
3128
|
+
}
|
|
3129
|
+
async doDelete(key) {
|
|
3130
|
+
const storageData = localStorage.getItem(this.name);
|
|
3131
|
+
if (!storageData) return false;
|
|
3132
|
+
const state = JSON.parse(storageData);
|
|
3133
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3134
|
+
const rawKey = key.valueOf();
|
|
3135
|
+
if (!(rawKey in state)) return false;
|
|
3136
|
+
delete state[rawKey];
|
|
3137
|
+
localStorage.setItem(this.name, JSON.stringify(state));
|
|
3138
|
+
return true;
|
|
3139
|
+
}
|
|
3140
|
+
const pathParts = parsePath(key);
|
|
3141
|
+
const parentPath = pathParts.slice(0, -1).join(".");
|
|
3142
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
3143
|
+
const parent = parentPath ? getValueByPath(state, parentPath) : state;
|
|
3144
|
+
if (!parent || !(lastKey in parent)) return false;
|
|
3145
|
+
delete parent[lastKey];
|
|
3146
|
+
localStorage.setItem(this.name, JSON.stringify(state));
|
|
3147
|
+
return true;
|
|
3148
|
+
}
|
|
3149
|
+
async doUpdate(updates) {
|
|
3150
|
+
const storageData = localStorage.getItem(this.name);
|
|
3151
|
+
const state = storageData ? JSON.parse(storageData) : {};
|
|
3152
|
+
for (const { key, value } of updates) {
|
|
3153
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3154
|
+
state[key.valueOf()] = value;
|
|
3155
|
+
} else {
|
|
3156
|
+
setValueByPath(state, key, value);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
localStorage.setItem(this.name, JSON.stringify(state));
|
|
3160
|
+
}
|
|
3161
|
+
async doClear() {
|
|
3162
|
+
localStorage.removeItem(this.name);
|
|
3163
|
+
}
|
|
3164
|
+
async doKeys() {
|
|
3165
|
+
const storageData = localStorage.getItem(this.name);
|
|
3166
|
+
if (!storageData) return [];
|
|
3167
|
+
const state = JSON.parse(storageData);
|
|
3168
|
+
return this.getAllKeys(state);
|
|
3169
|
+
}
|
|
3170
|
+
async doHas(key) {
|
|
3171
|
+
const value = await this.doGet(key);
|
|
3172
|
+
return value !== void 0;
|
|
3173
|
+
}
|
|
3174
|
+
getAllKeys(obj) {
|
|
3175
|
+
return Object.keys(obj);
|
|
3176
|
+
}
|
|
3177
|
+
async doDestroy() {
|
|
3178
|
+
await this.doClear();
|
|
3179
|
+
}
|
|
3180
|
+
};
|
|
3181
|
+
|
|
3182
|
+
// src/core/storage/adapters/memory-storage.service.ts
|
|
3183
|
+
var MemoryStorage = class extends BaseStorage {
|
|
3184
|
+
storage = /* @__PURE__ */ new Map();
|
|
3185
|
+
constructor(config, pluginExecutor, eventEmitter, logger) {
|
|
3186
|
+
super(config, pluginExecutor, eventEmitter, logger);
|
|
3187
|
+
}
|
|
3188
|
+
async initialize() {
|
|
3189
|
+
try {
|
|
3190
|
+
this.initializeMiddlewares();
|
|
3191
|
+
await this.initializeWithMiddlewares();
|
|
3192
|
+
return this;
|
|
3193
|
+
} catch (error) {
|
|
3194
|
+
this.logger?.error("Error initializing MemoryStorage", { error });
|
|
3195
|
+
throw error;
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
async doGet(key) {
|
|
3199
|
+
const state = this.storage.get(this.name);
|
|
3200
|
+
if (!state) return void 0;
|
|
3201
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3202
|
+
return state[key.valueOf()];
|
|
3203
|
+
}
|
|
3204
|
+
return getValueByPath(state, key);
|
|
3205
|
+
}
|
|
3206
|
+
async doSet(key, value) {
|
|
3207
|
+
const state = this.storage.get(this.name) || {};
|
|
3208
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3209
|
+
state[key.valueOf()] = value;
|
|
3210
|
+
this.storage.set(this.name, state);
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
const newState = setValueByPath({ ...state }, key, value);
|
|
3214
|
+
this.storage.set(this.name, newState);
|
|
3215
|
+
}
|
|
3216
|
+
async doDelete(key) {
|
|
3217
|
+
const state = this.storage.get(this.name);
|
|
3218
|
+
if (!state) return false;
|
|
3219
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3220
|
+
const rawKey = key.valueOf();
|
|
3221
|
+
if (!(rawKey in state)) return false;
|
|
3222
|
+
delete state[rawKey];
|
|
3223
|
+
this.storage.set(this.name, state);
|
|
3224
|
+
return true;
|
|
3225
|
+
}
|
|
3226
|
+
const pathParts = parsePath(key);
|
|
3227
|
+
const parentPath = pathParts.slice(0, -1).join(".");
|
|
3228
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
3229
|
+
const parent = parentPath ? getValueByPath(state, parentPath) : state;
|
|
3230
|
+
if (!parent || !(lastKey in parent)) return false;
|
|
3231
|
+
delete parent[lastKey];
|
|
3232
|
+
this.storage.set(this.name, state);
|
|
3233
|
+
return true;
|
|
3234
|
+
}
|
|
3235
|
+
async doUpdate(updates) {
|
|
3236
|
+
const currentState = this.storage.get(this.name) || {};
|
|
3237
|
+
const newState = { ...currentState };
|
|
3238
|
+
for (const { key, value } of updates) {
|
|
3239
|
+
if (key instanceof StorageKey && key.isUnparseable()) {
|
|
3240
|
+
newState[key.valueOf()] = value;
|
|
3241
|
+
} else {
|
|
3242
|
+
setValueByPath(newState, key, value);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
this.storage.set(this.name, newState);
|
|
3246
|
+
}
|
|
3247
|
+
async doClear() {
|
|
3248
|
+
this.storage.delete(this.name);
|
|
3249
|
+
}
|
|
3250
|
+
async doKeys() {
|
|
3251
|
+
const state = this.storage.get(this.name);
|
|
3252
|
+
if (!state) return [];
|
|
3253
|
+
return this.getAllKeys(state);
|
|
3254
|
+
}
|
|
3255
|
+
async doHas(key) {
|
|
3256
|
+
const value = await this.doGet(key);
|
|
3257
|
+
return value !== void 0;
|
|
3258
|
+
}
|
|
3259
|
+
async doDestroy() {
|
|
3260
|
+
this.storage.delete(this.name);
|
|
3261
|
+
}
|
|
3262
|
+
getAllKeys(obj) {
|
|
3263
|
+
return Object.keys(obj);
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
|
|
3267
|
+
// src/react/hooks/useSelector.ts
|
|
3268
|
+
var import_react = require("react");
|
|
3269
|
+
var SELECTOR_REGISTRY = /* @__PURE__ */ new Map();
|
|
3270
|
+
function useSelector(selector, options) {
|
|
3271
|
+
const [state, setState] = (0, import_react.useState)(options?.initialValue);
|
|
3272
|
+
const [isLoading, setIsLoading] = (0, import_react.useState)(!!options?.withLoading);
|
|
3273
|
+
const prevValueRef = (0, import_react.useRef)(options?.initialValue);
|
|
3274
|
+
const equalsRef = (0, import_react.useRef)(options?.equals || ((a, b) => a === b));
|
|
3275
|
+
const selectorId = selector.getId();
|
|
3276
|
+
const updateComponentState = (newValue) => {
|
|
3277
|
+
if (prevValueRef.current === void 0 || !equalsRef.current(newValue, prevValueRef.current)) {
|
|
3278
|
+
prevValueRef.current = newValue;
|
|
3279
|
+
setState(newValue);
|
|
3280
|
+
}
|
|
3281
|
+
};
|
|
3282
|
+
(0, import_react.useEffect)(() => {
|
|
3283
|
+
if (!SELECTOR_REGISTRY.has(selectorId)) {
|
|
3284
|
+
SELECTOR_REGISTRY.set(selectorId, {
|
|
3285
|
+
lastValue: void 0,
|
|
3286
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
3287
|
+
unsubscribe: null
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
const registry = SELECTOR_REGISTRY.get(selectorId);
|
|
3291
|
+
registry.listeners.add(updateComponentState);
|
|
3292
|
+
if (registry.lastValue !== void 0) {
|
|
3293
|
+
updateComponentState(registry.lastValue);
|
|
3294
|
+
if (options?.withLoading) {
|
|
3295
|
+
setIsLoading(false);
|
|
3296
|
+
}
|
|
3297
|
+
} else {
|
|
3298
|
+
if (options?.withLoading) setIsLoading(true);
|
|
3299
|
+
selector.select().then((initialValue) => {
|
|
3300
|
+
registry.lastValue = initialValue;
|
|
3301
|
+
registry.listeners.forEach((listener) => listener(initialValue));
|
|
3302
|
+
if (options?.withLoading) setIsLoading(false);
|
|
3303
|
+
}).catch((error) => {
|
|
3304
|
+
console.error(`useSelector: \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u0438 \u043D\u0430\u0447\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0434\u043B\u044F ${selectorId}`, error);
|
|
3305
|
+
if (options?.withLoading) setIsLoading(false);
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
if (!registry.unsubscribe) {
|
|
3309
|
+
registry.unsubscribe = selector.subscribe({
|
|
3310
|
+
notify: (newValue) => {
|
|
3311
|
+
registry.lastValue = newValue;
|
|
3312
|
+
registry.listeners.forEach((listener) => {
|
|
3313
|
+
try {
|
|
3314
|
+
listener(newValue);
|
|
3315
|
+
} catch (error) {
|
|
3316
|
+
console.error(`useSelector: \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0438 \u0441\u043B\u0443\u0448\u0430\u0442\u0435\u043B\u044F \u0434\u043B\u044F ${selectorId}`, error);
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
return () => {
|
|
3323
|
+
const registry2 = SELECTOR_REGISTRY.get(selectorId);
|
|
3324
|
+
if (registry2) {
|
|
3325
|
+
registry2.listeners.delete(updateComponentState);
|
|
3326
|
+
if (registry2.listeners.size === 0) {
|
|
3327
|
+
if (registry2.unsubscribe) {
|
|
3328
|
+
registry2.unsubscribe();
|
|
3329
|
+
}
|
|
3330
|
+
SELECTOR_REGISTRY.delete(selectorId);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
}, [selector, selectorId]);
|
|
3335
|
+
if (options?.withLoading) {
|
|
3336
|
+
return { data: state, isLoading };
|
|
3337
|
+
}
|
|
3338
|
+
return state;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
// src/react/hooks/useStorageSubscribe.ts
|
|
3342
|
+
var import_react2 = require("react");
|
|
3343
|
+
var useStorageSubscribe = (storage, selector) => {
|
|
3344
|
+
const [value, setValue] = (0, import_react2.useState)(void 0);
|
|
3345
|
+
(0, import_react2.useEffect)(() => {
|
|
3346
|
+
let isMounted = true;
|
|
3347
|
+
const initializeValue = async () => {
|
|
3348
|
+
try {
|
|
3349
|
+
const state = await storage.getState();
|
|
3350
|
+
const selectedValue = selector(state);
|
|
3351
|
+
if (isMounted) {
|
|
3352
|
+
setValue(selectedValue);
|
|
3353
|
+
}
|
|
3354
|
+
} catch (error) {
|
|
3355
|
+
console.error("Failed to initialize storage value:", error);
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3358
|
+
initializeValue();
|
|
3359
|
+
let unsubscribe;
|
|
3360
|
+
try {
|
|
3361
|
+
unsubscribe = storage.subscribe(selector, (newValue) => {
|
|
3362
|
+
if (isMounted) {
|
|
3363
|
+
setValue(newValue);
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
} catch (error) {
|
|
3367
|
+
console.error("Failed to subscribe to storage:", error);
|
|
3368
|
+
unsubscribe = () => {
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
return () => {
|
|
3372
|
+
isMounted = false;
|
|
3373
|
+
unsubscribe();
|
|
3374
|
+
};
|
|
3375
|
+
}, [storage, selector]);
|
|
3376
|
+
return value;
|
|
3377
|
+
};
|
|
3378
|
+
|
|
3379
|
+
// src/react/utils/createSynapseCtx.tsx
|
|
3380
|
+
var import_react3 = require("react");
|
|
3381
|
+
|
|
3382
|
+
// src/_utils/chunk.util.ts
|
|
3383
|
+
function chunk(array, size = 1) {
|
|
3384
|
+
if (size <= 0) throw new Error("Size must be greater than 0");
|
|
3385
|
+
if (!array || !array.length) return [];
|
|
3386
|
+
const result = [];
|
|
3387
|
+
const length = array.length;
|
|
3388
|
+
let index = 0;
|
|
3389
|
+
while (index < length) {
|
|
3390
|
+
result.push(array.slice(index, index + size));
|
|
3391
|
+
index += size;
|
|
3392
|
+
}
|
|
3393
|
+
return result;
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
// src/_utils/deepMerge.util.ts
|
|
3397
|
+
var deepMerge = (target, source) => {
|
|
3398
|
+
for (const key in source) {
|
|
3399
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
3400
|
+
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) {
|
|
3401
|
+
if (!target[key] || typeof target[key] !== "object") {
|
|
3402
|
+
target[key] = {};
|
|
3403
|
+
}
|
|
3404
|
+
deepMerge(target[key], source[key]);
|
|
3405
|
+
} else {
|
|
3406
|
+
target[key] = source[key];
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
};
|
|
3411
|
+
|
|
3412
|
+
// src/react/utils/createSynapseCtx.tsx
|
|
3413
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
3414
|
+
var ERROR_HOOK_MESSAGE = "useSynapseActions \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u0432\u043D\u0443\u0442\u0440\u0438 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u0430 contextSynapse";
|
|
3415
|
+
var ERROR_CONTEXT_INIT = "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430:";
|
|
3416
|
+
function createSynapseCtx(synapseStorePromise, options) {
|
|
3417
|
+
const { loadingComponent = null, mergeFn = deepMerge } = options || {};
|
|
3418
|
+
let synapseStore = null;
|
|
3419
|
+
let storeReady = false;
|
|
3420
|
+
const initPromise = (async () => {
|
|
3421
|
+
try {
|
|
3422
|
+
synapseStore = await (synapseStorePromise instanceof Promise ? synapseStorePromise : Promise.resolve(synapseStorePromise));
|
|
3423
|
+
storeReady = true;
|
|
3424
|
+
} catch (error) {
|
|
3425
|
+
console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430 Synapse:", error);
|
|
3426
|
+
}
|
|
3427
|
+
})();
|
|
3428
|
+
const SynapseContext = (0, import_react3.createContext)(null);
|
|
3429
|
+
const useSynapseStorage = () => {
|
|
3430
|
+
const context = (0, import_react3.useContext)(SynapseContext);
|
|
3431
|
+
if (!context) throw new Error(ERROR_HOOK_MESSAGE);
|
|
3432
|
+
return context.storage;
|
|
3433
|
+
};
|
|
3434
|
+
const useSynapseSelectors = () => {
|
|
3435
|
+
const context = (0, import_react3.useContext)(SynapseContext);
|
|
3436
|
+
if (!context) throw new Error(ERROR_HOOK_MESSAGE);
|
|
3437
|
+
return context.selectors;
|
|
3438
|
+
};
|
|
3439
|
+
const useSynapseActions = () => {
|
|
3440
|
+
const context = (0, import_react3.useContext)(SynapseContext);
|
|
3441
|
+
if (!context) throw new Error(ERROR_HOOK_MESSAGE);
|
|
3442
|
+
if ("actions" in context) {
|
|
3443
|
+
return context.actions;
|
|
3444
|
+
}
|
|
3445
|
+
throw new Error("useSynapseActions: actions \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043F\u0430 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044C, \u0447\u0442\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F createDispatcherFn \u043F\u0440\u0438 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0438 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430.");
|
|
3446
|
+
};
|
|
3447
|
+
const useSynapseState$ = () => {
|
|
3448
|
+
const context = (0, import_react3.useContext)(SynapseContext);
|
|
3449
|
+
if (!context) throw new Error(ERROR_HOOK_MESSAGE);
|
|
3450
|
+
if ("state$" in context) {
|
|
3451
|
+
return context.state$;
|
|
3452
|
+
}
|
|
3453
|
+
throw new Error("useSynapseState$: state$ \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043F\u0430 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044C, \u0447\u0442\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u044B \u0444\u0443\u043D\u043A\u0446\u0438\u0438 createDispatcherFn \u0438 createEffectConfig \u043F\u0440\u0438 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0438 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430.");
|
|
3454
|
+
};
|
|
3455
|
+
function contextSynapse(Component) {
|
|
3456
|
+
function WrappedComponent({ contextProps, ...props }) {
|
|
3457
|
+
const [initialized, setInitialized] = (0, import_react3.useState)(false);
|
|
3458
|
+
const [storeInitialized, setStoreInitialized] = (0, import_react3.useState)(storeReady);
|
|
3459
|
+
const debugIdRef = (0, import_react3.useRef)(`synapse-${synapseStore?.storage.name || "initializing"}`);
|
|
3460
|
+
(0, import_react3.useEffect)(() => {
|
|
3461
|
+
if (!storeReady) {
|
|
3462
|
+
initPromise.then(() => {
|
|
3463
|
+
setStoreInitialized(true);
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3466
|
+
}, []);
|
|
3467
|
+
(0, import_react3.useEffect)(() => {
|
|
3468
|
+
if (!storeInitialized) return;
|
|
3469
|
+
let mounted = true;
|
|
3470
|
+
const initializeContext = async () => {
|
|
3471
|
+
try {
|
|
3472
|
+
if (synapseStore && contextProps && Object.keys(contextProps).length > 0) {
|
|
3473
|
+
await synapseStore.storage.update((state) => {
|
|
3474
|
+
mergeFn(state, contextProps);
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
if (mounted) {
|
|
3478
|
+
setInitialized(true);
|
|
3479
|
+
}
|
|
3480
|
+
} catch (error) {
|
|
3481
|
+
console.error(`[${debugIdRef.current}] ${ERROR_CONTEXT_INIT}`, error);
|
|
3482
|
+
}
|
|
3483
|
+
};
|
|
3484
|
+
initializeContext();
|
|
3485
|
+
return () => {
|
|
3486
|
+
mounted = false;
|
|
3487
|
+
};
|
|
3488
|
+
}, [contextProps, storeInitialized]);
|
|
3489
|
+
if (!storeInitialized) {
|
|
3490
|
+
return loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430..." });
|
|
3491
|
+
}
|
|
3492
|
+
if (!initialized) {
|
|
3493
|
+
return loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "\u0418\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044F \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430..." });
|
|
3494
|
+
}
|
|
3495
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SynapseContext.Provider, { value: synapseStore, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { ...props }) });
|
|
3496
|
+
}
|
|
3497
|
+
const componentName = Component.displayName || Component.name || "Component";
|
|
3498
|
+
WrappedComponent.displayName = `SynapseContext(${componentName})`;
|
|
3499
|
+
return WrappedComponent;
|
|
3500
|
+
}
|
|
3501
|
+
const cleanupSynapse = async () => {
|
|
3502
|
+
await initPromise;
|
|
3503
|
+
return synapseStore?.destroy() || Promise.resolve();
|
|
3504
|
+
};
|
|
3505
|
+
return {
|
|
3506
|
+
contextSynapse,
|
|
3507
|
+
useSynapseStorage,
|
|
3508
|
+
useSynapseSelectors,
|
|
3509
|
+
useSynapseActions,
|
|
3510
|
+
useSynapseState$,
|
|
3511
|
+
cleanupSynapse
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
// src/reactive/dispatcher/dispatcher.module.ts
|
|
3516
|
+
var import_rxjs = require("rxjs");
|
|
3517
|
+
var Dispatcher = class {
|
|
3518
|
+
/**
|
|
3519
|
+
* Создает новый экземпляр Dispatcher
|
|
3520
|
+
*/
|
|
3521
|
+
constructor(options) {
|
|
3522
|
+
this.options = options;
|
|
3523
|
+
this.storage = options.storage;
|
|
3524
|
+
this.middlewareAPI = {
|
|
3525
|
+
getState: () => this.storage.getState(),
|
|
3526
|
+
dispatch: async (action) => {
|
|
3527
|
+
this.actions$.next(action);
|
|
3528
|
+
return action.payload;
|
|
3529
|
+
},
|
|
3530
|
+
storage: this.storage,
|
|
3531
|
+
actions$: this.actions,
|
|
3532
|
+
actions: this.dispatch,
|
|
3533
|
+
watchers: this.watchers,
|
|
3534
|
+
findActionByType: (type) => this.findActionByType(type),
|
|
3535
|
+
findWatcherByType: (type) => this.findWatcherByType(type)
|
|
3536
|
+
};
|
|
3537
|
+
if (options.middlewares && options.middlewares.length > 0) {
|
|
3538
|
+
this.use(...options.middlewares);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
// Поток действий
|
|
3542
|
+
actions$ = new import_rxjs.Subject();
|
|
3543
|
+
// Публичный Observable для действий
|
|
3544
|
+
actions = this.actions$.asObservable();
|
|
3545
|
+
// Методы диспетчеризации действий с типизацией
|
|
3546
|
+
dispatch = {};
|
|
3547
|
+
// Watcher'ы для реактивной подписки на изменения
|
|
3548
|
+
watchers = {};
|
|
3549
|
+
// Ссылка на хранилище
|
|
3550
|
+
storage;
|
|
3551
|
+
// Только один массив для хранения инициализированных middleware
|
|
3552
|
+
middlewareFunctions = [];
|
|
3553
|
+
// API для инициализации middleware
|
|
3554
|
+
middlewareAPI;
|
|
3555
|
+
/**
|
|
3556
|
+
* Добавляет middleware в цепочку обработки
|
|
3557
|
+
*/
|
|
3558
|
+
use(...middlewares) {
|
|
3559
|
+
for (let i = 0; i < middlewares.length; i++) {
|
|
3560
|
+
try {
|
|
3561
|
+
const initializedMiddleware = middlewares[i](this.middlewareAPI);
|
|
3562
|
+
this.middlewareFunctions.push(initializedMiddleware);
|
|
3563
|
+
} catch (error) {
|
|
3564
|
+
console.error(`Error initializing middleware [${i}]:`, error);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
return this;
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Получает все действия с улучшенной типизацией
|
|
3571
|
+
*/
|
|
3572
|
+
getActions() {
|
|
3573
|
+
return this.dispatch;
|
|
3574
|
+
}
|
|
3575
|
+
/**
|
|
3576
|
+
* Получает типизированные действия диспетчера
|
|
3577
|
+
*/
|
|
3578
|
+
getTypedDispatch() {
|
|
3579
|
+
return this.dispatch;
|
|
3580
|
+
}
|
|
3581
|
+
/**
|
|
3582
|
+
* Получает типизированные watcher'ы
|
|
3583
|
+
*/
|
|
3584
|
+
getTypedWatchers() {
|
|
3585
|
+
return this.watchers;
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Находит действие по типу
|
|
3589
|
+
*/
|
|
3590
|
+
findActionByType(actionType) {
|
|
3591
|
+
return Object.values(this.dispatch).find((action) => {
|
|
3592
|
+
return action.actionType.split(`[${this.storage.name}]`)[1] === actionType;
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
/**
|
|
3596
|
+
* Находит наблюдатель по типу
|
|
3597
|
+
*/
|
|
3598
|
+
findWatcherByType(actionType) {
|
|
3599
|
+
return Object.values(this.watchers).find((watcher) => watcher.actionType === actionType);
|
|
3600
|
+
}
|
|
3601
|
+
/**
|
|
3602
|
+
* Создает действие
|
|
3603
|
+
*/
|
|
3604
|
+
createAction(actionConfig, executionOptions) {
|
|
3605
|
+
const actionType = `[${this.storage.name}]${actionConfig.type}`;
|
|
3606
|
+
let lastArgs = null;
|
|
3607
|
+
let lastResult = null;
|
|
3608
|
+
const dispatchFn = async (params) => {
|
|
3609
|
+
const args = [params];
|
|
3610
|
+
if (executionOptions?.memoize && lastArgs && lastResult) {
|
|
3611
|
+
if (executionOptions.memoize(args, lastArgs, lastResult)) {
|
|
3612
|
+
return lastResult;
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
const actionObject = {
|
|
3616
|
+
type: actionType,
|
|
3617
|
+
meta: actionConfig.meta
|
|
3618
|
+
};
|
|
3619
|
+
let result;
|
|
3620
|
+
if (this.middlewareFunctions.length > 0) {
|
|
3621
|
+
let chain = async (action) => {
|
|
3622
|
+
if (executionOptions?.worker) {
|
|
3623
|
+
return this.executeInWorker(executionOptions.worker, actionType, args, actionConfig.action);
|
|
3624
|
+
} else {
|
|
3625
|
+
return Promise.resolve(actionConfig.action(params));
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
for (let i = this.middlewareFunctions.length - 1; i >= 0; i--) {
|
|
3629
|
+
const currentMiddleware = this.middlewareFunctions[i];
|
|
3630
|
+
const nextChain = chain;
|
|
3631
|
+
chain = async (action) => {
|
|
3632
|
+
const next = async (nextAction) => nextChain(nextAction);
|
|
3633
|
+
return currentMiddleware(next)(action);
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
result = await chain(actionObject);
|
|
3637
|
+
} else {
|
|
3638
|
+
if (executionOptions?.worker) {
|
|
3639
|
+
result = await this.executeInWorker(executionOptions.worker, actionType, args, actionConfig.action);
|
|
3640
|
+
} else {
|
|
3641
|
+
result = await actionConfig.action(params);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
actionObject.payload = result;
|
|
3645
|
+
lastArgs = [...args];
|
|
3646
|
+
lastResult = result;
|
|
3647
|
+
this.actions$.next(actionObject);
|
|
3648
|
+
return result;
|
|
3649
|
+
};
|
|
3650
|
+
dispatchFn._type = "dispatch";
|
|
3651
|
+
Object.defineProperty(dispatchFn, "actionType", {
|
|
3652
|
+
value: actionType,
|
|
3653
|
+
writable: false,
|
|
3654
|
+
enumerable: true
|
|
3655
|
+
});
|
|
3656
|
+
if (actionConfig.meta) {
|
|
3657
|
+
Object.defineProperty(dispatchFn, "meta", {
|
|
3658
|
+
value: actionConfig.meta,
|
|
3659
|
+
writable: false,
|
|
3660
|
+
enumerable: true
|
|
3661
|
+
});
|
|
3662
|
+
}
|
|
3663
|
+
return dispatchFn;
|
|
3664
|
+
}
|
|
3665
|
+
/**
|
|
3666
|
+
* Создает watcher для отслеживания изменений в хранилище
|
|
3667
|
+
*/
|
|
3668
|
+
createWatcher(config) {
|
|
3669
|
+
const actionType = `[${this.storage.name}]${config.type}`;
|
|
3670
|
+
const subject = new import_rxjs.Subject();
|
|
3671
|
+
let prevValue;
|
|
3672
|
+
const unsubscribe = this.storage.subscribe(config.selector, (value) => {
|
|
3673
|
+
if (!config.shouldTrigger || config.shouldTrigger(prevValue, value)) {
|
|
3674
|
+
const action = {
|
|
3675
|
+
type: actionType,
|
|
3676
|
+
payload: value,
|
|
3677
|
+
meta: config.meta
|
|
3678
|
+
};
|
|
3679
|
+
this.actions$.next(action);
|
|
3680
|
+
subject.next(action);
|
|
3681
|
+
prevValue = value;
|
|
3682
|
+
}
|
|
3683
|
+
});
|
|
3684
|
+
const watcherFn = () => subject.asObservable();
|
|
3685
|
+
watcherFn._type = "watchers";
|
|
3686
|
+
Object.defineProperty(watcherFn, "actionType", {
|
|
3687
|
+
value: actionType,
|
|
3688
|
+
writable: false,
|
|
3689
|
+
enumerable: true
|
|
3690
|
+
});
|
|
3691
|
+
if (config.meta) {
|
|
3692
|
+
Object.defineProperty(watcherFn, "meta", {
|
|
3693
|
+
value: config.meta,
|
|
3694
|
+
writable: false,
|
|
3695
|
+
enumerable: true
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
Object.defineProperty(watcherFn, "unsubscribe", {
|
|
3699
|
+
value: unsubscribe,
|
|
3700
|
+
writable: false,
|
|
3701
|
+
enumerable: true
|
|
3702
|
+
});
|
|
3703
|
+
return watcherFn;
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Выполняет действие в worker
|
|
3707
|
+
*/
|
|
3708
|
+
async executeInWorker(worker, actionType, args, fallbackAction) {
|
|
3709
|
+
return new Promise((resolve, reject) => {
|
|
3710
|
+
const requestId = `${actionType}_${Date.now()}_${Math.random()}`;
|
|
3711
|
+
const handleMessage = (event) => {
|
|
3712
|
+
if (event.data.requestId === requestId) {
|
|
3713
|
+
worker.removeEventListener("message", handleMessage);
|
|
3714
|
+
if (event.data.error) {
|
|
3715
|
+
reject(new Error(event.data.error));
|
|
3716
|
+
} else {
|
|
3717
|
+
resolve(event.data.result);
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
worker.addEventListener("message", handleMessage);
|
|
3722
|
+
worker.postMessage({
|
|
3723
|
+
type: actionType,
|
|
3724
|
+
args,
|
|
3725
|
+
requestId
|
|
3726
|
+
});
|
|
3727
|
+
setTimeout(() => {
|
|
3728
|
+
worker.removeEventListener("message", handleMessage);
|
|
3729
|
+
reject(new Error(`Worker execution timeout for action: ${actionType}`));
|
|
3730
|
+
}, 3e4);
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3734
|
+
function createDispatcher(options, actionsSetup) {
|
|
3735
|
+
const dispatcher = new Dispatcher(options);
|
|
3736
|
+
const actions = actionsSetup(options.storage, {
|
|
3737
|
+
createAction: (actionConfig, executionOptions) => dispatcher.createAction(actionConfig, executionOptions),
|
|
3738
|
+
createWatcher: (config) => dispatcher.createWatcher(config)
|
|
3739
|
+
});
|
|
3740
|
+
for (const [key, fn] of Object.entries(actions)) {
|
|
3741
|
+
if (typeof fn === "function") {
|
|
3742
|
+
const type = fn._type;
|
|
3743
|
+
dispatcher[type][key] = fn;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
return dispatcher;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
// src/reactive/dispatcher/middlewares/logger.middleware.ts
|
|
3750
|
+
function getStateDiff(prevState, nextState) {
|
|
3751
|
+
const diff = {};
|
|
3752
|
+
const allKeys = [.../* @__PURE__ */ new Set([...Object.keys(prevState), ...Object.keys(nextState)])];
|
|
3753
|
+
allKeys.forEach((key) => {
|
|
3754
|
+
if (key in prevState && key in nextState) {
|
|
3755
|
+
if (typeof prevState[key] === "object" && prevState[key] !== null && typeof nextState[key] === "object" && nextState[key] !== null && !Array.isArray(prevState[key]) && !Array.isArray(nextState[key])) {
|
|
3756
|
+
const nestedDiff = getStateDiff(prevState[key], nextState[key]);
|
|
3757
|
+
if (Object.keys(nestedDiff).length > 0) {
|
|
3758
|
+
diff[key] = nestedDiff;
|
|
3759
|
+
}
|
|
3760
|
+
} else if (JSON.stringify(prevState[key]) !== JSON.stringify(nextState[key])) {
|
|
3761
|
+
diff[key] = { PREV: prevState[key], NEXT: nextState[key] };
|
|
3762
|
+
}
|
|
3763
|
+
} else if (key in prevState) {
|
|
3764
|
+
diff[key] = { PREV: prevState[key], NEXT: void 0 };
|
|
3765
|
+
} else {
|
|
3766
|
+
diff[key] = { PREV: void 0, NEXT: nextState[key] };
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
return diff;
|
|
3770
|
+
}
|
|
3771
|
+
var loggerDispatcherMiddleware = (options = {}) => {
|
|
3772
|
+
const defaultTranslations = {
|
|
3773
|
+
action: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435",
|
|
3774
|
+
prevState: "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
|
|
3775
|
+
nextState: "\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
|
|
3776
|
+
duration: "\u0414\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C",
|
|
3777
|
+
error: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438",
|
|
3778
|
+
diff: "\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F",
|
|
3779
|
+
changesCount: "\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439",
|
|
3780
|
+
showFullState: "\u041F\u043E\u043B\u043D\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435"
|
|
3781
|
+
};
|
|
3782
|
+
const defaultOptions = {
|
|
3783
|
+
collapsed: false,
|
|
3784
|
+
duration: true,
|
|
3785
|
+
diff: false,
|
|
3786
|
+
showFullState: true,
|
|
3787
|
+
// Показывать полное состояние по умолчанию
|
|
3788
|
+
translations: defaultTranslations,
|
|
3789
|
+
colors: {
|
|
3790
|
+
title: "#3498db",
|
|
3791
|
+
prevState: "#9E9E9E",
|
|
3792
|
+
fullState: "#008a15",
|
|
3793
|
+
action: "#03A9F4",
|
|
3794
|
+
nextState: "#4CAF50",
|
|
3795
|
+
error: "#F20404",
|
|
3796
|
+
diff: "#9C27B0"
|
|
3797
|
+
}
|
|
3798
|
+
};
|
|
3799
|
+
const mergedOptions = {
|
|
3800
|
+
...defaultOptions,
|
|
3801
|
+
...options,
|
|
3802
|
+
// Объединяем переводы отдельно, чтобы позволить частичное переопределение
|
|
3803
|
+
translations: {
|
|
3804
|
+
...defaultTranslations,
|
|
3805
|
+
...options.translations || {}
|
|
3806
|
+
},
|
|
3807
|
+
// Объединяем цвета отдельно, чтобы позволить частичное переопределение
|
|
3808
|
+
colors: {
|
|
3809
|
+
...defaultOptions.colors,
|
|
3810
|
+
...options.colors || {}
|
|
3811
|
+
}
|
|
3812
|
+
};
|
|
3813
|
+
const { collapsed, duration, colors, translations } = mergedOptions;
|
|
3814
|
+
return (api) => (next) => async (action) => {
|
|
3815
|
+
const started = Date.now();
|
|
3816
|
+
const prevState = await api.getState();
|
|
3817
|
+
try {
|
|
3818
|
+
const result = await next(action);
|
|
3819
|
+
const nextState = await api.getState();
|
|
3820
|
+
const ended = Date.now();
|
|
3821
|
+
const time = ended - started;
|
|
3822
|
+
const title = `${action.type}`;
|
|
3823
|
+
const groupMethod = collapsed ? console.groupCollapsed : console.group;
|
|
3824
|
+
groupMethod(`%c ${title}`, `color: ${colors.title}; font-weight: bold`);
|
|
3825
|
+
console.log(`%c ${translations.action}:`, `color: ${colors.action}; font-weight: bold`, action);
|
|
3826
|
+
if (mergedOptions.diff) {
|
|
3827
|
+
const stateDiff = getStateDiff(prevState, nextState);
|
|
3828
|
+
const changesCount = Object.keys(stateDiff).length;
|
|
3829
|
+
console.log(`%c ${translations.diff} (${translations.changesCount}: ${changesCount}):`, `color: ${colors.diff}; font-weight: bold`, stateDiff);
|
|
3830
|
+
}
|
|
3831
|
+
if (mergedOptions.showFullState) {
|
|
3832
|
+
console.groupCollapsed(`%c ${translations.showFullState}`, `color: ${colors.fullState}; font-weight: bold`);
|
|
3833
|
+
console.log(`%c ${translations.prevState}:`, `color: ${colors.prevState}; font-weight: bold`, prevState);
|
|
3834
|
+
console.log(`%c ${translations.nextState}:`, `color: ${colors.nextState}; font-weight: bold`, nextState);
|
|
3835
|
+
console.groupEnd();
|
|
3836
|
+
}
|
|
3837
|
+
if (duration) {
|
|
3838
|
+
console.log(`%c ${translations.duration}: %c ${time}ms`, "font-weight: bold", "color: #4CAF50");
|
|
3839
|
+
}
|
|
3840
|
+
console.groupEnd();
|
|
3841
|
+
return result;
|
|
3842
|
+
} catch (error) {
|
|
3843
|
+
console.error(`%c ${translations.error}:`, `color: ${colors.error}; font-weight: bold`, action.type, error);
|
|
3844
|
+
throw error;
|
|
3845
|
+
}
|
|
3846
|
+
};
|
|
3847
|
+
};
|
|
3848
|
+
|
|
3849
|
+
// src/reactive/effects/effects.module.ts
|
|
3850
|
+
var import_rxjs4 = require("rxjs");
|
|
3851
|
+
var import_operators3 = require("rxjs/operators");
|
|
3852
|
+
|
|
3853
|
+
// src/reactive/effects/utils/chunkRequestConsistent.ts
|
|
3854
|
+
var import_rxjs2 = require("rxjs");
|
|
3855
|
+
var import_operators = require("rxjs/operators");
|
|
3856
|
+
var chunkRequestConsistent = (fn, arr, size, delayMs = 0) => {
|
|
3857
|
+
const chunks = chunk(arr, size).map((chunkItem) => (0, import_rxjs2.of)(chunkItem).pipe((0, import_operators.delay)(delayMs), (0, import_operators.mergeMap)(fn)));
|
|
3858
|
+
return (0, import_rxjs2.of)(...chunks).pipe((0, import_operators.concatAll)(), (0, import_operators.toArray)());
|
|
3859
|
+
};
|
|
3860
|
+
|
|
3861
|
+
// src/reactive/effects/utils/chunkRequestParallel.ts
|
|
3862
|
+
var import_rxjs3 = require("rxjs");
|
|
3863
|
+
var import_operators2 = require("rxjs/operators");
|
|
3864
|
+
var chunkRequestParallel = (fn, arr, size, delayMs = 0) => (0, import_rxjs3.forkJoin)(chunk(arr, size).map((chunkItem, index) => (0, import_rxjs3.timer)(index * delayMs).pipe((0, import_operators2.mergeMap)(() => fn(chunkItem)))));
|
|
3865
|
+
|
|
3866
|
+
// src/reactive/effects/effects.module.ts
|
|
3867
|
+
function ofType(actionFn) {
|
|
3868
|
+
const { actionType } = actionFn;
|
|
3869
|
+
if (!actionType) {
|
|
3870
|
+
console.warn("ofType: Action function does not have actionType property", actionFn);
|
|
3871
|
+
return (0, import_operators3.filter)(() => false);
|
|
3872
|
+
}
|
|
3873
|
+
return (source$) => {
|
|
3874
|
+
return source$.pipe((0, import_operators3.filter)((action) => action !== void 0 && action.type === actionType));
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
function ofTypes(actionFns) {
|
|
3878
|
+
const actionTypes = actionFns.map((fn) => fn.actionType).filter(Boolean);
|
|
3879
|
+
if (actionTypes.length === 0) {
|
|
3880
|
+
console.warn("ofTypes: No valid action types found in array", actionFns);
|
|
3881
|
+
return (0, import_operators3.filter)(() => false);
|
|
3882
|
+
}
|
|
3883
|
+
return (source$) => {
|
|
3884
|
+
return source$.pipe((0, import_operators3.filter)((action) => action !== void 0 && actionTypes.includes(action.type)));
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
function ofTypesWaitAll(actionFns) {
|
|
3888
|
+
return (source$) => {
|
|
3889
|
+
const actionTypes = actionFns.map((fn) => fn.actionType).filter(Boolean);
|
|
3890
|
+
if (actionTypes.length === 0) {
|
|
3891
|
+
console.warn("ofTypesWaitAll: No valid action types found in array", actionFns);
|
|
3892
|
+
return (0, import_rxjs4.of)([]);
|
|
3893
|
+
}
|
|
3894
|
+
const actionStreams = actionTypes.map(
|
|
3895
|
+
(type, index) => source$.pipe(
|
|
3896
|
+
(0, import_operators3.filter)((action) => action.type === type),
|
|
3897
|
+
(0, import_operators3.take)(1),
|
|
3898
|
+
(0, import_operators3.map)(
|
|
3899
|
+
(action) => (
|
|
3900
|
+
// Сохраняем ассоциацию с индексом, чтобы соответствовать
|
|
3901
|
+
// порядку в исходном массиве actionFns
|
|
3902
|
+
{ index, action }
|
|
3903
|
+
)
|
|
3904
|
+
)
|
|
3905
|
+
)
|
|
3906
|
+
);
|
|
3907
|
+
return (0, import_rxjs4.combineLatest)(actionStreams).pipe(
|
|
3908
|
+
(0, import_operators3.map)((results) => {
|
|
3909
|
+
results.sort((a, b) => a.index - b.index);
|
|
3910
|
+
return results.map((r) => r.action);
|
|
3911
|
+
})
|
|
3912
|
+
);
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
function selectorMap(state$, ...selectors) {
|
|
3916
|
+
return state$.pipe(
|
|
3917
|
+
(0, import_operators3.map)((state) => {
|
|
3918
|
+
return selectors.map((selector) => selector(state));
|
|
3919
|
+
})
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
function selectorObject(state$, selectors) {
|
|
3923
|
+
return state$.pipe(
|
|
3924
|
+
(0, import_operators3.map)((state) => {
|
|
3925
|
+
const result = {};
|
|
3926
|
+
for (const [key, selector] of Object.entries(selectors)) {
|
|
3927
|
+
result[key] = selector(state);
|
|
3928
|
+
}
|
|
3929
|
+
return result;
|
|
3930
|
+
})
|
|
3931
|
+
);
|
|
3932
|
+
}
|
|
3933
|
+
function validateMap({
|
|
3934
|
+
validator,
|
|
3935
|
+
apiCall
|
|
3936
|
+
}) {
|
|
3937
|
+
return (0, import_rxjs4.pipe)(
|
|
3938
|
+
(0, import_operators3.switchMap)((pipeData) => {
|
|
3939
|
+
const callApi = () => apiCall(pipeData, {
|
|
3940
|
+
chunkRequest: chunkRequestParallel,
|
|
3941
|
+
chunkRequestConsistent
|
|
3942
|
+
});
|
|
3943
|
+
if (!validator) return callApi();
|
|
3944
|
+
const validateConfig = validator(pipeData);
|
|
3945
|
+
const { conditions, skipAction } = validateConfig;
|
|
3946
|
+
const conditionMet = conditions.every(Boolean);
|
|
3947
|
+
if (!conditionMet) {
|
|
3948
|
+
if (Array.isArray(skipAction)) {
|
|
3949
|
+
return (0, import_rxjs4.of)(...skipAction?.filter(Boolean).map((action) => typeof action === "function" ? action() : action));
|
|
3950
|
+
}
|
|
3951
|
+
return (0, import_rxjs4.of)(typeof skipAction === "function" ? skipAction() : skipAction);
|
|
3952
|
+
}
|
|
3953
|
+
return callApi();
|
|
3954
|
+
})
|
|
3955
|
+
);
|
|
3956
|
+
}
|
|
3957
|
+
var EffectsModule = class {
|
|
3958
|
+
/**
|
|
3959
|
+
* Создает модуль эффектов с доступом к состоянию, внешним состояниям и конфигурации
|
|
3960
|
+
* @param storage Хранилище состояния
|
|
3961
|
+
* @param externalStates Внешние состояния
|
|
3962
|
+
* @param dispatchers Объект с диспетчерами
|
|
3963
|
+
* @param services Объект с сервисами
|
|
3964
|
+
* @param config Глобальная конфигурация для всех эффектов
|
|
3965
|
+
*/
|
|
3966
|
+
constructor(storage, externalStates = {}, dispatchers, services = {}, config = {}) {
|
|
3967
|
+
this.storage = storage;
|
|
3968
|
+
this.externalStates = externalStates;
|
|
3969
|
+
this.dispatchers = dispatchers;
|
|
3970
|
+
this.services = services;
|
|
3971
|
+
this.config = config;
|
|
3972
|
+
this.subscribeToDispatchers();
|
|
3973
|
+
this.state$ = new import_rxjs4.Observable((observer) => {
|
|
3974
|
+
this.storage.getState().then((state) => observer.next(state));
|
|
3975
|
+
const unsubscribe = this.storage.subscribeToAll(() => {
|
|
3976
|
+
this.storage.getState().then((state) => observer.next(state));
|
|
3977
|
+
});
|
|
3978
|
+
return () => unsubscribe();
|
|
3979
|
+
}).pipe((0, import_operators3.share)());
|
|
3980
|
+
}
|
|
3981
|
+
effects = [];
|
|
3982
|
+
subscriptions = [];
|
|
3983
|
+
running = false;
|
|
3984
|
+
action$ = new import_rxjs4.Subject();
|
|
3985
|
+
/**
|
|
3986
|
+
* Поток состояния
|
|
3987
|
+
*/
|
|
3988
|
+
state$;
|
|
3989
|
+
/**
|
|
3990
|
+
* Подписывается на действия от всех диспетчеров
|
|
3991
|
+
*/
|
|
3992
|
+
subscribeToDispatchers() {
|
|
3993
|
+
for (const [_, dispatcher] of Object.entries(this.dispatchers)) {
|
|
3994
|
+
const subscription = dispatcher.actions.subscribe((action) => {
|
|
3995
|
+
this.action$.next(action);
|
|
3996
|
+
});
|
|
3997
|
+
this.subscriptions.push(subscription);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
add(effect) {
|
|
4001
|
+
this.effects.push(effect);
|
|
4002
|
+
if (this.running) {
|
|
4003
|
+
this.subscribeToEffect(effect);
|
|
4004
|
+
}
|
|
4005
|
+
return this;
|
|
4006
|
+
}
|
|
4007
|
+
/**
|
|
4008
|
+
* Добавляет несколько эффектов
|
|
4009
|
+
* @param effects Эффекты для добавления
|
|
4010
|
+
* @returns Текущий модуль
|
|
4011
|
+
*/
|
|
4012
|
+
addEffects(effects) {
|
|
4013
|
+
effects.forEach((effect) => this.add(effect));
|
|
4014
|
+
return this;
|
|
4015
|
+
}
|
|
4016
|
+
/**
|
|
4017
|
+
* Запускает все эффекты
|
|
4018
|
+
* @returns Текущий модуль
|
|
4019
|
+
*/
|
|
4020
|
+
start() {
|
|
4021
|
+
if (this.running) {
|
|
4022
|
+
return this;
|
|
4023
|
+
}
|
|
4024
|
+
this.effects.forEach((effect) => this.subscribeToEffect(effect));
|
|
4025
|
+
this.running = true;
|
|
4026
|
+
return this;
|
|
4027
|
+
}
|
|
4028
|
+
/**
|
|
4029
|
+
* Останавливает все эффекты
|
|
4030
|
+
* @returns Текущий модуль
|
|
4031
|
+
*/
|
|
4032
|
+
stop() {
|
|
4033
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
4034
|
+
this.subscriptions = [];
|
|
4035
|
+
this.running = false;
|
|
4036
|
+
return this;
|
|
4037
|
+
}
|
|
4038
|
+
/**
|
|
4039
|
+
* Подписывается на конкретный эффект
|
|
4040
|
+
* @param effect Эффект для подписки
|
|
4041
|
+
*/
|
|
4042
|
+
subscribeToEffect(effect) {
|
|
4043
|
+
try {
|
|
4044
|
+
const output$ = effect(this.action$.asObservable(), this.state$, this.externalStates, this.dispatchers, this.services, this.config).pipe(
|
|
4045
|
+
(0, import_operators3.catchError)((err) => {
|
|
4046
|
+
console.error("Error in effect:", err);
|
|
4047
|
+
return (0, import_rxjs4.of)(null);
|
|
4048
|
+
})
|
|
4049
|
+
);
|
|
4050
|
+
const subscription = output$.subscribe((result) => {
|
|
4051
|
+
if (result === null || result === void 0) {
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
4054
|
+
if (typeof result === "function") {
|
|
4055
|
+
try {
|
|
4056
|
+
result();
|
|
4057
|
+
} catch (callError) {
|
|
4058
|
+
console.error("Error calling effect result function:", callError);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
});
|
|
4062
|
+
this.subscriptions.push(subscription);
|
|
4063
|
+
} catch (setupError) {
|
|
4064
|
+
console.error("Error setting up effect:", setupError);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
4068
|
+
function createEffectBase(effect) {
|
|
4069
|
+
return effect;
|
|
4070
|
+
}
|
|
4071
|
+
function createEffect(effect) {
|
|
4072
|
+
return effect;
|
|
4073
|
+
}
|
|
4074
|
+
function combineEffects(...effects) {
|
|
4075
|
+
return (action$, state$, externalStates, dispatchers, services, config) => {
|
|
4076
|
+
const outputs = effects.map((effect) => {
|
|
4077
|
+
try {
|
|
4078
|
+
return effect(action$, state$, externalStates, dispatchers, services, config);
|
|
4079
|
+
} catch (error) {
|
|
4080
|
+
console.error("Error in one of combined effects:", error);
|
|
4081
|
+
return (0, import_rxjs4.of)(null);
|
|
4082
|
+
}
|
|
4083
|
+
});
|
|
4084
|
+
return (0, import_rxjs4.merge)(...outputs);
|
|
4085
|
+
};
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
// src/utils/createSynapse.ts
|
|
4089
|
+
async function createSynapse(config) {
|
|
4090
|
+
const storageInstance = config.createStorageFn ? await config.createStorageFn() : config.storage;
|
|
4091
|
+
const cleanupCallbacks = [];
|
|
4092
|
+
const result = {
|
|
4093
|
+
storage: storageInstance,
|
|
4094
|
+
selectors: {},
|
|
4095
|
+
destroy: async () => {
|
|
4096
|
+
for (const callback of cleanupCallbacks) {
|
|
4097
|
+
await callback();
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
};
|
|
4101
|
+
cleanupCallbacks.push(() => storageInstance.destroy());
|
|
4102
|
+
let dispatcher;
|
|
4103
|
+
let selectorModule;
|
|
4104
|
+
let effectsModule;
|
|
4105
|
+
if (config.createSelectorsFn) {
|
|
4106
|
+
try {
|
|
4107
|
+
selectorModule = new SelectorModule(storageInstance);
|
|
4108
|
+
const externalSelectors = config.externalSelectors || {};
|
|
4109
|
+
result.selectors = config.createSelectorsFn(selectorModule, externalSelectors);
|
|
4110
|
+
if (typeof selectorModule.destroy === "function") {
|
|
4111
|
+
cleanupCallbacks.push(() => selectorModule.destroy());
|
|
4112
|
+
}
|
|
4113
|
+
} catch (error) {
|
|
4114
|
+
console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F selectors:", error);
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
if (config.createDispatcherFn) {
|
|
4118
|
+
dispatcher = config.createDispatcherFn(storageInstance);
|
|
4119
|
+
result.dispatcher = dispatcher;
|
|
4120
|
+
if (dispatcher && "dispatch" in dispatcher) {
|
|
4121
|
+
result.actions = dispatcher.dispatch;
|
|
4122
|
+
if (typeof dispatcher.destroy === "function") {
|
|
4123
|
+
cleanupCallbacks.push(() => dispatcher.destroy());
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
if (config.createEffectConfig && dispatcher) {
|
|
4128
|
+
try {
|
|
4129
|
+
const { dispatchers, api, config: effectConfig, externalStates } = config.createEffectConfig(dispatcher);
|
|
4130
|
+
const effectExternalStates = externalStates || {};
|
|
4131
|
+
effectsModule = new EffectsModule(storageInstance, effectExternalStates, dispatchers, api, effectConfig);
|
|
4132
|
+
if (Array.isArray(config.effects)) {
|
|
4133
|
+
config.effects.forEach((effect) => {
|
|
4134
|
+
if (effectsModule) effectsModule.add(effect);
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
effectsModule.start();
|
|
4138
|
+
result.state$ = effectsModule.state$;
|
|
4139
|
+
cleanupCallbacks.push(() => {
|
|
4140
|
+
if (effectsModule) effectsModule.stop();
|
|
4141
|
+
});
|
|
4142
|
+
} catch (error) {
|
|
4143
|
+
console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043C\u043E\u0434\u0443\u043B\u044F \u044D\u0444\u0444\u0435\u043A\u0442\u043E\u0432:", error);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
return result;
|
|
4147
|
+
}
|
|
4148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4149
|
+
0 && (module.exports = {
|
|
4150
|
+
ApiClient,
|
|
4151
|
+
Dispatcher,
|
|
4152
|
+
EffectsModule,
|
|
4153
|
+
IndexedDBStorage,
|
|
4154
|
+
LocalStorage,
|
|
4155
|
+
MemoryStorage,
|
|
4156
|
+
ResponseFormat,
|
|
4157
|
+
SelectorModule,
|
|
4158
|
+
StorageEvents,
|
|
4159
|
+
StoragePluginModule,
|
|
4160
|
+
apiLogger,
|
|
4161
|
+
broadcastMiddleware,
|
|
4162
|
+
combineEffects,
|
|
4163
|
+
createDispatcher,
|
|
4164
|
+
createEffect,
|
|
4165
|
+
createEffectBase,
|
|
4166
|
+
createSynapse,
|
|
4167
|
+
createSynapseCtx,
|
|
4168
|
+
createUniqueId,
|
|
4169
|
+
headersToObject,
|
|
4170
|
+
loggerDispatcherMiddleware,
|
|
4171
|
+
ofType,
|
|
4172
|
+
ofTypes,
|
|
4173
|
+
ofTypesWaitAll,
|
|
4174
|
+
selectorMap,
|
|
4175
|
+
selectorObject,
|
|
4176
|
+
useSelector,
|
|
4177
|
+
useStorageSubscribe,
|
|
4178
|
+
validateMap
|
|
4179
|
+
});
|