@pravosleva/reactive-engine 2.6.3-alpha.0 → 2.6.4-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/core.d.ts +1 -1
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.logger.d.ts.map +1 -1
- package/dist/core/core.method.computed.d.ts.map +1 -1
- package/dist/core/core.method.resource.d.ts +2 -1
- package/dist/core/core.method.resource.d.ts.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.logger.cjs +1 -1
- package/dist/core.logger.cjs.map +1 -1
- package/dist/core.logger.mjs +3 -3
- package/dist/core.logger.mjs.map +1 -1
- package/dist/core.method.computed.cjs +1 -1
- package/dist/core.method.computed.cjs.map +1 -1
- package/dist/core.method.computed.mjs +4 -3
- package/dist/core.method.computed.mjs.map +1 -1
- package/dist/core.method.resource.cjs +1 -1
- package/dist/core.method.resource.cjs.map +1 -1
- package/dist/core.method.resource.mjs +74 -47
- package/dist/core.method.resource.mjs.map +1 -1
- package/dist/core.mjs.map +1 -1
- package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.cjs +1 -1
- package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.cjs.map +1 -1
- package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.d.ts +3 -12
- package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.d.ts.map +1 -1
- package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.mjs +17 -17
- package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.mjs.map +1 -1
- package/dist/reactive-engine.umd.js +2 -2
- package/dist/reactive-engine.umd.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,103 +1,130 @@
|
|
|
1
1
|
import { getExtractedValues as e } from "./utils/getExtractedValues.mjs";
|
|
2
2
|
//#region src/core/core.method.resource.ts
|
|
3
3
|
function t(t, n, r, i) {
|
|
4
|
-
let a = i && typeof i == "object", o = a ? i.name || "unnamed_resource" : i || "unnamed_resource", s = a ? i : {}, c = s.resetDataOnSourceChange ?? !0, l = s.responseValidate, u = s.validateBeforeFetch, d = t.signal({
|
|
4
|
+
let a = i && typeof i == "object", o = a ? i.name || "unnamed_resource" : i || "unnamed_resource", s = a ? i : {}, c = s.resetDataOnSourceChange ?? !0, l = s.responseValidate, u = s.validateBeforeFetch, d = s.retryCount ?? 0, f = s.timeout ?? 5e3, p = Date.now(), m = t.signal({
|
|
5
5
|
data: null,
|
|
6
6
|
loading: !0,
|
|
7
7
|
error: null,
|
|
8
|
-
isRetrying: !1
|
|
9
|
-
|
|
8
|
+
isRetrying: !1,
|
|
9
|
+
attemptIndex: 0,
|
|
10
|
+
retryCount: d,
|
|
11
|
+
totalElapsedMs: void 0
|
|
12
|
+
}, `[CORE_INTERNAL_SIGNAL=1]:resource:state:${o}`), h = (e) => {
|
|
10
13
|
t.log({
|
|
11
14
|
type: "resource",
|
|
12
15
|
label: o,
|
|
13
16
|
details: {
|
|
14
17
|
to: e,
|
|
15
|
-
from:
|
|
18
|
+
from: m.value
|
|
16
19
|
}
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
20
|
+
}), m.value = e;
|
|
21
|
+
}, g = async (r, i, a = !1) => {
|
|
19
22
|
if (i.aborted) return;
|
|
20
|
-
if (
|
|
23
|
+
if (p = Date.now(), u) {
|
|
21
24
|
let e = u(r);
|
|
22
25
|
if (e === !1 || typeof e == "string") {
|
|
23
26
|
if (i.aborted) return;
|
|
24
|
-
let n = typeof e == "string" ? e : "Pre-fetch validation failed for resource", r = a ? null : t.untrack(() =>
|
|
25
|
-
|
|
27
|
+
let n = typeof e == "string" ? e : "Pre-fetch validation failed for resource", r = a ? null : t.untrack(() => m.value.data);
|
|
28
|
+
h({
|
|
26
29
|
data: r,
|
|
27
30
|
loading: !1,
|
|
28
31
|
error: n,
|
|
29
|
-
isRetrying: !1
|
|
32
|
+
isRetrying: !1,
|
|
33
|
+
attemptIndex: 0,
|
|
34
|
+
retryCount: d,
|
|
35
|
+
totalElapsedMs: void 0
|
|
30
36
|
});
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
data:
|
|
40
|
+
let s = a && c ? null : t.untrack(() => m.value.data);
|
|
41
|
+
h({
|
|
42
|
+
data: s,
|
|
37
43
|
loading: !0,
|
|
38
44
|
error: null,
|
|
39
|
-
isRetrying: !1
|
|
45
|
+
isRetrying: !1,
|
|
46
|
+
attemptIndex: 0,
|
|
47
|
+
retryCount: d,
|
|
48
|
+
totalElapsedMs: void 0
|
|
40
49
|
});
|
|
41
|
-
for (let a = 0; a <=
|
|
50
|
+
for (let a = 0; a <= d; a++) {
|
|
42
51
|
if (i.aborted) return;
|
|
43
|
-
let s = i;
|
|
52
|
+
let s = AbortSignal.timeout(f), c = AbortSignal.any([i, s]);
|
|
44
53
|
try {
|
|
45
|
-
let e = await n(r,
|
|
54
|
+
let e = await n(r, c);
|
|
46
55
|
if (i.aborted) return;
|
|
47
56
|
if (s.aborted) throw new DOMException("The operation timed out.", "TimeoutError");
|
|
48
57
|
if (l) {
|
|
49
58
|
let t = l(e);
|
|
50
59
|
if (t === !1 || typeof t == "string") {
|
|
51
60
|
if (i.aborted) return;
|
|
52
|
-
|
|
61
|
+
h({
|
|
53
62
|
data: null,
|
|
54
63
|
loading: !1,
|
|
55
64
|
error: typeof t == "string" ? t : "Validation failed",
|
|
56
|
-
isRetrying: !1
|
|
65
|
+
isRetrying: !1,
|
|
66
|
+
attemptIndex: 0,
|
|
67
|
+
retryCount: d,
|
|
68
|
+
totalElapsedMs: void 0
|
|
57
69
|
});
|
|
58
70
|
return;
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
if (i.aborted) return;
|
|
62
|
-
|
|
74
|
+
h({
|
|
63
75
|
data: e,
|
|
64
76
|
loading: !1,
|
|
65
77
|
error: null,
|
|
66
|
-
isRetrying: !1
|
|
78
|
+
isRetrying: !1,
|
|
79
|
+
attemptIndex: 0,
|
|
80
|
+
retryCount: d,
|
|
81
|
+
totalElapsedMs: void 0
|
|
67
82
|
});
|
|
68
83
|
return;
|
|
69
84
|
} catch (n) {
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
let r = s.aborted || n.name === "TimeoutError" || n.message?.includes("timeout");
|
|
86
|
+
if (i.aborted && !r) return;
|
|
87
|
+
let c = e({
|
|
72
88
|
tested: [n.message],
|
|
73
89
|
expectedKey: "THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY",
|
|
74
90
|
valueType: "number"
|
|
75
|
-
})?.[0] === "1" || !1,
|
|
91
|
+
})?.[0] === "1" || !1, l = c && e({
|
|
76
92
|
tested: [n.message],
|
|
77
93
|
expectedKey: "MESSAGE",
|
|
78
94
|
valueType: "string"
|
|
79
|
-
})?.[0] || "Custom validation error",
|
|
80
|
-
if (a ===
|
|
95
|
+
})?.[0] || "Custom validation error", u = c || n.name === "ValidationError";
|
|
96
|
+
if (a === d || u) {
|
|
81
97
|
if (i.aborted) return;
|
|
82
|
-
let e = r ?
|
|
83
|
-
|
|
98
|
+
let e = Date.now() - p, o = `${r ? `Request timed out after ${f}ms (attempt ${a + 1}/${d + 1})` : c ? l : n instanceof Error ? n.message : String(n)}.${t.logger.isCoreOptimizationDebugEnabled ? ` Всего безуспешных попыток с момента изменения исходного сигнала: ${d + 1}.` : ""}`;
|
|
99
|
+
h({
|
|
84
100
|
data: null,
|
|
85
101
|
loading: !1,
|
|
86
|
-
error:
|
|
87
|
-
isRetrying: !1
|
|
102
|
+
error: o,
|
|
103
|
+
isRetrying: !1,
|
|
104
|
+
attemptIndex: a + 1,
|
|
105
|
+
retryCount: d,
|
|
106
|
+
totalElapsedMs: e
|
|
88
107
|
});
|
|
89
108
|
return;
|
|
90
109
|
} else {
|
|
91
110
|
if (i.aborted) return;
|
|
92
111
|
let e = 1e3 * 2 ** a;
|
|
93
|
-
e = Math.min(e, 3e5)
|
|
94
|
-
|
|
95
|
-
e += n, f({
|
|
96
|
-
data: d.value.data,
|
|
112
|
+
e = Math.min(e, 3e5) + Math.random() * 200, h({
|
|
113
|
+
data: m.value.data,
|
|
97
114
|
loading: !0,
|
|
98
115
|
error: null,
|
|
99
|
-
isRetrying: !0
|
|
100
|
-
|
|
116
|
+
isRetrying: !0,
|
|
117
|
+
attemptIndex: a + 1,
|
|
118
|
+
retryCount: d
|
|
119
|
+
}), t.log({
|
|
120
|
+
type: "resource",
|
|
121
|
+
label: `${o}:retry_scheduled`,
|
|
122
|
+
details: { payload: {
|
|
123
|
+
attempt: a + 1,
|
|
124
|
+
total: d,
|
|
125
|
+
nextDelayMs: Math.round(e)
|
|
126
|
+
} }
|
|
127
|
+
}), console.warn(`[Resource Retry] "${o}" сбой по ${r ? "таймауту" : "ошибке сети"}. Запрос ${a + 1} из ${d + 1} не удался. Ожидание повтора: ${Math.round(e)}ms`);
|
|
101
128
|
try {
|
|
102
129
|
if (await t.delay(e, i), i.aborted) return;
|
|
103
130
|
} catch {
|
|
@@ -106,34 +133,34 @@ function t(t, n, r, i) {
|
|
|
106
133
|
}
|
|
107
134
|
}
|
|
108
135
|
}
|
|
109
|
-
},
|
|
136
|
+
}, _ = null, v = t.effect(() => {
|
|
110
137
|
let e = r ? r.value : void 0, n = new AbortController();
|
|
111
|
-
return
|
|
112
|
-
|
|
138
|
+
return _ = n, t.untrack(() => {
|
|
139
|
+
g(e, n.signal, !0);
|
|
113
140
|
}), () => n.abort();
|
|
114
141
|
}, "[CORE_INTERNAL_SIGNAL=1]:effect");
|
|
115
142
|
return {
|
|
116
143
|
get data() {
|
|
117
|
-
return
|
|
144
|
+
return m.value.data;
|
|
118
145
|
},
|
|
119
146
|
get loading() {
|
|
120
|
-
return
|
|
147
|
+
return m.value.loading;
|
|
121
148
|
},
|
|
122
149
|
get error() {
|
|
123
|
-
return
|
|
150
|
+
return m.value.error;
|
|
124
151
|
},
|
|
125
152
|
get isRetrying() {
|
|
126
|
-
return
|
|
153
|
+
return m.value.isRetrying;
|
|
127
154
|
},
|
|
128
155
|
get value() {
|
|
129
|
-
return
|
|
156
|
+
return m.value;
|
|
130
157
|
},
|
|
131
158
|
refetch: () => {
|
|
132
|
-
|
|
159
|
+
_?.abort(), _ = new AbortController(), g(r ? r.value : void 0, _.signal, !1);
|
|
133
160
|
},
|
|
134
|
-
subscribe: (e) =>
|
|
161
|
+
subscribe: (e) => m.subscribe(e),
|
|
135
162
|
destroy: () => {
|
|
136
|
-
|
|
163
|
+
_?.abort(), _ = null, typeof v == "function" && v(), m.destroy && m.destroy();
|
|
137
164
|
}
|
|
138
165
|
};
|
|
139
166
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.method.resource.mjs","names":[],"sources":["../src/core/core.method.resource.ts"],"sourcesContent":["import { getExtractedValues } from '../utils/getExtractedValues'\nimport { Resource, ResourceOptions, ResourceState, Signal } from './types'\n\n// Контракт ядра движка для безопасных вызовов методов с дженериками\ninterface IReactiveEngine {\n signal<V>(initialValue: V, optionsOrName?: any): Signal<V>;\n effect(fn: () => any, name?: string): () => void;\n untrack<V>(fn: () => V): V;\n log(params: any): void;\n delay(ms: number, signal: AbortSignal): Promise<void>;\n}\n\nexport function createResource<T, S = void>(\n engine: IReactiveEngine,\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>\n): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object'\n const resourceSignalName = isOptionsObject\n ? ((optionsOrName as ResourceOptions<T, S>).name || 'unnamed_resource')\n : (optionsOrName as string) || 'unnamed_resource'\n\n const options: Partial<ResourceOptions<T, S>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>)\n : {}\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true\n const responseValidate = options.responseValidate\n const validateBeforeFetch = options.validateBeforeFetch\n const retryCount = 0\n const baseDelay = 1000\n const useExponential = true\n const maxRetryDelay = 1000 * 60 * 5\n const timeoutMs = 1000\n\n // Вызов скомпилируется успешно, так как метод signal теперь строго описан в интерфейсе\n const state = engine.signal<ResourceState<T>>(\n { data: null, loading: true, error: null, isRetrying: false },\n `[CORE_INTERNAL_SIGNAL=1]:resource:state:${resourceSignalName}`\n )\n\n const updateResourceState = (nextState: ResourceState<T>) => {\n engine.log({ type: 'resource', label: resourceSignalName, details: { to: nextState, from: state.value } })\n state.value = nextState\n }\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return\n const isValid = options.validateBeforeFetch ? options.validateBeforeFetch(sValue) : true\n\n if (validateBeforeFetch) {\n const preValidationResult = validateBeforeFetch(sValue)\n if (preValidationResult === false || typeof preValidationResult === 'string') {\n if (effectSignal.aborted) return\n const errorMsg = typeof preValidationResult === 'string'\n ? preValidationResult\n : 'Pre-fetch validation failed for resource'\n\n const currentData = isSourceChange ? null : engine.untrack(() => state.value.data)\n updateResourceState({ data: currentData, loading: false, error: errorMsg, isRetrying: false })\n return\n }\n }\n const shouldClear = isSourceChange && resetDataOnSourceChange\n const currentData = shouldClear ? null : engine.untrack(() => state.value.data)\n updateResourceState({ data: currentData, loading: true, error: null, isRetrying: false })\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return\n let combinedSignal = effectSignal\n try {\n const data = await fetcher(sValue, combinedSignal)\n if (effectSignal.aborted) return\n if (combinedSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError')\n }\n if (responseValidate) {\n const validationResult = responseValidate(data)\n if (validationResult === false || typeof validationResult === 'string') {\n if (effectSignal.aborted) return\n const errorMsg = typeof validationResult === 'string' ? validationResult : 'Validation failed'\n updateResourceState({ data: null, loading: false, error: errorMsg, isRetrying: false })\n return\n }\n }\n if (effectSignal.aborted) return\n updateResourceState({ data, loading: false, error: null, isRetrying: false })\n return\n } catch (e: unknown) {\n if (effectSignal.aborted || ((e as Error).name === 'AbortError' && effectSignal.aborted)) return\n const isTimeout = (e as Error).name === 'TimeoutError' || (e as Error).name === 'AbortError' || (e as Error).message?.includes('timeout')\n const isCustomValidationError = getExtractedValues({\n tested: [(e as Error).message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n const __defaultCustomValidationErrorMessage = 'Custom validation error'\n let customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [(e as Error).message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage\n const isFetchBodyValidationError = isCustomValidationError || (e as Error).name === 'ValidationError'\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return\n const finalError = isTimeout\n ? new Error(`Request timed out after ${timeoutMs}ms`, { cause: e })\n : isCustomValidationError\n ? new Error(customValidationErrorMessage, { cause: e })\n : e\n\n updateResourceState({\n data: null, loading: false,\n error: finalError instanceof Error ? finalError.message : typeof finalError === 'string' ? finalError : `typeof finalError is ${typeof finalError}`,\n isRetrying: false,\n })\n return\n } else {\n if (effectSignal.aborted) return\n let currentDelay = useExponential ? baseDelay * Math.pow(2, attempt) : baseDelay\n currentDelay = Math.min(currentDelay, maxRetryDelay)\n const jitter = Math.random() * 200\n currentDelay = currentDelay + jitter\n updateResourceState({ data: state.value.data, loading: true, error: null, isRetrying: true })\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети'\n console.warn(`[Resource Retry] \"${resourceSignalName}\" сбой по ${logReason}. Попытка ${attempt + 1}/${retryCount > 0 ? retryCount + 1 : attempt + 1}...`)\n\n try {\n await engine.delay(currentDelay, effectSignal)\n if (effectSignal.aborted) return\n } catch (delayError) {\n return\n }\n }\n }\n }\n }\n\n let activeEffectController: AbortController | null = null\n\n const disposeInternalEffect = engine.effect(() => {\n const sValue = source ? source.value : undefined as S\n const effectController = new AbortController()\n activeEffectController = effectController\n\n engine.untrack(() => {\n load(sValue as S, effectController.signal, true)\n })\n\n return () => effectController.abort()\n }, '[CORE_INTERNAL_SIGNAL=1]:effect')\n\n return {\n get data() { return state.value.data },\n get loading() { return state.value.loading },\n get error() { return state.value.error },\n get isRetrying() { return state.value.isRetrying },\n get value() { return state.value },\n refetch: () => {\n activeEffectController?.abort()\n activeEffectController = new AbortController()\n load(source ? source.value : undefined as S, activeEffectController.signal, false)\n },\n subscribe: (cb: (val: ResourceState<T>) => void) => state.subscribe(cb),\n destroy: () => {\n activeEffectController?.abort()\n activeEffectController = null\n if (typeof disposeInternalEffect === 'function') {\n disposeInternalEffect()\n }\n if ((state as any).destroy) {\n (state as any).destroy()\n }\n }\n } as any\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core.method.resource.mjs","names":[],"sources":["../src/core/core.method.resource.ts"],"sourcesContent":["import { getExtractedValues } from '../utils/getExtractedValues'\nimport { EngineLoggerOptions, Resource, ResourceOptions, ResourceState, Signal } from './types'\n\n// Контракт ядра движка для безопасных вызовов методов с дженериками\ninterface IReactiveEngine {\n signal<V>(initialValue: V, optionsOrName?: any): Signal<V>;\n effect(fn: () => any, name?: string): () => void;\n untrack<V>(fn: () => V): V;\n log(params: any): void;\n delay(ms: number, signal: AbortSignal): Promise<void>;\n logger: EngineLoggerOptions\n}\n\nexport function createResource<T, S = void>(\n engine: IReactiveEngine,\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>\n): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object'\n const resourceSignalName = isOptionsObject\n ? ((optionsOrName as ResourceOptions<T, S>).name || 'unnamed_resource')\n : (optionsOrName as string) || 'unnamed_resource'\n\n const options: Partial<ResourceOptions<T, S>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>)\n : {}\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true\n const responseValidate = options.responseValidate\n const validateBeforeFetch = options.validateBeforeFetch\n const baseDelay = 1000\n const useExponential = true\n const maxRetryDelay = 1000 * 60 * 5\n\n const retryCount = options.retryCount ?? 0\n const timeoutMs = options.timeout ?? 5000\n // Запоминаем точное время старта ВСЕЙ цепочки (включая ретраи и паузы)\n let startTime = Date.now()\n\n const state = engine.signal<ResourceState<T>>(\n {\n data: null,\n loading: true,\n error: null,\n isRetrying: false,\n attemptIndex: 0,\n retryCount,\n totalElapsedMs: undefined // Сбрасываем старый таймер\n },\n `[CORE_INTERNAL_SIGNAL=1]:resource:state:${resourceSignalName}`\n )\n\n const updateResourceState = (nextState: ResourceState<T>) => {\n engine.log({\n type: 'resource',\n label: resourceSignalName,\n details: { to: nextState, from: state.value },\n })\n state.value = nextState\n }\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return\n\n // Фиксируем время старта бизнес-процесса в самом начале метода load\n startTime = Date.now()\n\n if (validateBeforeFetch) {\n const preValidationResult = validateBeforeFetch(sValue)\n if (preValidationResult === false || typeof preValidationResult === 'string') {\n if (effectSignal.aborted) return\n const errorMsg = typeof preValidationResult === 'string'\n ? preValidationResult\n : 'Pre-fetch validation failed for resource'\n\n const currentData = isSourceChange ? null : engine.untrack(() => state.value.data)\n // Явно зачищаем следы старого таймера при ошибке валидации аргументов\n updateResourceState({\n data: currentData,\n loading: false,\n error: errorMsg,\n isRetrying: false,\n attemptIndex: 0,\n retryCount,\n totalElapsedMs: undefined\n })\n return\n }\n }\n\n const shouldClear = isSourceChange && resetDataOnSourceChange\n const currentData = shouldClear ? null : engine.untrack(() => state.value.data)\n\n // Принудительно сбрасываем таймер в undefined перед входом в цикл ретраев\n updateResourceState({\n data: currentData,\n loading: true,\n error: null,\n isRetrying: false,\n attemptIndex: 0,\n retryCount,\n totalElapsedMs: undefined\n })\n\n // 🔄 Контур циклического выполнения попыток (от 0 до retryCount)\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return\n\n // Создаем персональный сигнал таймаута на эту конкретную попытку\n const timeoutSignal = AbortSignal.timeout(timeoutMs)\n\n // Объединяем управляющий сигнал графа и наш таймаут\n const combinedSignal = AbortSignal.any([effectSignal, timeoutSignal])\n\n try {\n const data = await fetcher(sValue, combinedSignal)\n\n if (effectSignal.aborted) return\n\n // Если фетчер вернул управление, но таймаут успел отработать раньше\n if (timeoutSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError')\n }\n\n if (responseValidate) {\n const validationResult = responseValidate(data)\n if (validationResult === false || typeof validationResult === 'string') {\n if (effectSignal.aborted) return\n const errorMsg = typeof validationResult === 'string' ? validationResult : 'Validation failed'\n updateResourceState({\n data: null,\n loading: false,\n error: errorMsg,\n isRetrying: false,\n attemptIndex: 0,\n retryCount,\n totalElapsedMs: undefined\n })\n return\n }\n }\n\n if (effectSignal.aborted) return\n // Успех: сохраняем данные, полностью очищаем ошибки и сквозной таймер\n updateResourceState({\n data,\n loading: false,\n error: null,\n isRetrying: false,\n attemptIndex: 0,\n retryCount,\n totalElapsedMs: undefined\n })\n return\n\n } catch (e: unknown) {\n // 1. Выявляем, произошел ли сбой по таймауту\n const isTimeout = timeoutSignal.aborted ||\n (e as Error).name === 'TimeoutError' ||\n (e as Error).message?.includes('timeout')\n\n // Если это обычная отмена дебаунсом из внешнего контура, мгновенно прекращаем работу\n if (effectSignal.aborted && !isTimeout) return\n\n // 2. Безопасно извлекаем валидационные маркеры до их использования в условиях\n const isCustomValidationError = getExtractedValues({\n tested: [(e as Error).message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n\n const __defaultCustomValidationErrorMessage = 'Custom validation error'\n const customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [(e as Error).message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage\n\n const isFetchBodyValidationError = isCustomValidationError || (e as Error).name === 'ValidationError'\n\n // Если попытки исчерпаны ИЛИ это валидационная ошибка тела, которая не подлежит повтору\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return\n\n // Считаем суммарное время от первого клика/сдвига карты до текущего момента\n const totalElapsedMs = Date.now() - startTime\n\n // Переменная customValidationErrorMessage теперь гарантированно определена выше!\n const baseErrorMsg = isTimeout\n ? `Request timed out after ${timeoutMs}ms (attempt ${attempt + 1}/${retryCount + 1})`\n : isCustomValidationError\n ? customValidationErrorMessage\n : e instanceof Error ? e.message : String(e)\n\n // Формируем детальное сообщение для вывода в UI\n const finalErrorMessage = `${baseErrorMsg}.${engine.logger.isCoreOptimizationDebugEnabled ? ` Всего безуспешных попыток с момента изменения исходного сигнала: ${retryCount + 1}.` : ''}`\n\n updateResourceState({\n data: null,\n loading: false,\n error: finalErrorMessage,\n isRetrying: false,\n attemptIndex: attempt + 1, // Выводим финальное число сделанных попыток\n retryCount,\n totalElapsedMs,\n })\n return\n } else {\n // Инициализируем планирование следующей попытки (Retry Backoff Phase)\n if (effectSignal.aborted) return\n\n let currentDelay = useExponential ? baseDelay * Math.pow(2, attempt) : baseDelay\n currentDelay = Math.min(currentDelay, maxRetryDelay) + Math.random() * 200\n\n updateResourceState({\n data: state.value.data,\n loading: true,\n error: null,\n isRetrying: true,\n attemptIndex: attempt + 1, // Индекс следующей накатываемой попытки для UI (1, 2, 3...)\n retryCount\n })\n\n engine.log({\n type: 'resource',\n label: `${resourceSignalName}:retry_scheduled`,\n details: { payload: { attempt: attempt + 1, total: retryCount, nextDelayMs: Math.round(currentDelay) } }\n })\n\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети'\n // Исправлен счетчик: пишем честный индекс текущего упавшего запроса из общего пула\n console.warn(`[Resource Retry] \"${resourceSignalName}\" сбой по ${logReason}. Запрос ${attempt + 1} из ${retryCount + 1} не удался. Ожидание повтора: ${Math.round(currentDelay)}ms`)\n\n try {\n // Спим перед следующим циклом, уважая отмену внешнего графа\n await engine.delay(currentDelay, effectSignal)\n if (effectSignal.aborted) return\n } catch (delayError) {\n return\n }\n }\n }\n }\n }\n\n let activeEffectController: AbortController | null = null\n\n const disposeInternalEffect = engine.effect(() => {\n const sValue = source ? source.value : undefined as S\n const effectController = new AbortController()\n activeEffectController = effectController\n\n engine.untrack(() => {\n load(sValue as S, effectController.signal, true)\n })\n\n return () => effectController.abort()\n }, '[CORE_INTERNAL_SIGNAL=1]:effect')\n\n return {\n get data() { return state.value.data },\n get loading() { return state.value.loading },\n get error() { return state.value.error },\n get isRetrying() { return state.value.isRetrying },\n get value() { return state.value },\n refetch: () => {\n activeEffectController?.abort()\n activeEffectController = new AbortController()\n load(source ? source.value : undefined as S, activeEffectController.signal, false)\n },\n subscribe: (cb: (val: ResourceState<T>) => void) => state.subscribe(cb),\n destroy: () => {\n activeEffectController?.abort()\n activeEffectController = null\n if (typeof disposeInternalEffect === 'function') {\n disposeInternalEffect()\n }\n if ((state as any).destroy) {\n (state as any).destroy()\n }\n }\n } as any\n}\n"],"mappings":"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"}
|
package/dist/core.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.mjs","names":[],"sources":["../src/core/core.ts"],"sourcesContent":["import { getExtractedValues } from '../utils/getExtractedValues'\nimport { Signal, Computed, IEffect, EngineLoggerOptions, LogEventType, CleanupFn, TransactionContext, LogRecord, SignalOptions, ResourceOptions, ResourceState, Resource } from './types'\nimport { createComputed } from './core.method.computed'\nimport { createSignal } from './core.method.signal'\n// import { executeBatch } from './core.method.batch'\nimport { executeUntrack } from './core.method.untrack'\nimport { createReactive } from './core.method.reactive'\nimport { createEffect } from './core.method.effect'\nimport { ReactiveDebounceLogger } from './core.logger'\nimport { createResource } from './core.method.resource'\n\n// Тип для конструктора класса\ntype Constructor<T> = new (...args: any[]) => T\n// Тип для фабричной функции зависимостей\ntype Factory<T> = (engine: any) => T\n// Универсальный тип токена DI\ntype InjectionToken<T> = Constructor<T> | Factory<T> | symbol | string\n\nconst badgeColors: Record<LogEventType, string> = {\n signal: 'background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n computed: 'background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n effect: 'background: #e01e5a; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n batch: 'background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n resource: 'background: #d97706; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n reactive: 'background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;'\n}\nlet currentTransaction: TransactionContext | null = null\nfunction safeValueToString(value: unknown): string {\n if (value === null) return 'null'\n if (value === undefined) return 'undefined'\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value)\n } catch (e) {\n return '[Cyclic Object or Layer]'\n }\n }\n return String(value)\n}\nexport function queueLog(type: LogEventType, label: string, details?: { from?: unknown; to?: unknown; payload?: unknown }): void {\n if (!currentTransaction) {\n currentTransaction = { id: Math.random().toString(36).substring(2, 9), pendingLogQueue: [], isFlushing: false }\n queueMicrotask(() => flushLogs())\n }\n currentTransaction.pendingLogQueue.push({ type, label, from: details?.from, to: details?.to, payload: details?.payload, timestamp: Date.now() })\n}\nexport function flushLogs(): void {\n if (!currentTransaction || currentTransaction.isFlushing) return\n currentTransaction.isFlushing = true\n const queue = currentTransaction.pendingLogQueue\n if (queue.length === 0) {\n currentTransaction = null\n return\n }\n console.groupCollapsed(`%cREACTIVE TRANSACTION%c [id: ${currentTransaction.id}] (${queue.length} operations)`, 'background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;', 'color: #888; font-weight: normal;')\n for (const record of queue) {\n const colorStyle = badgeColors[record.type]\n const hasTransition = record.from !== undefined || record.to !== undefined\n let logMessage = `%c${record.type.toUpperCase()}%c ${record.label}`\n\n if (hasTransition) {\n if (!!record.from)\n logMessage += ` ${safeValueToString(record.from)} → ${safeValueToString(record.to)}`\n else logMessage += ` (${safeValueToString(record.to)})`\n }\n\n if (record.payload !== undefined) {\n console.groupCollapsed(logMessage, colorStyle, 'color: inherit;')\n console.log('Payload:', record.payload)\n console.groupEnd()\n } else {\n console.log(logMessage, colorStyle, 'color: inherit;')\n }\n }\n console.groupEnd()\n currentTransaction = null\n}\nexport class ReactiveEngineCore {\n protected frameworkPrefix = 'core'\n private logger: EngineLoggerOptions\n public epoch = 0\n public activeConsumer: any = null\n public batchDepth = 0\n public subscriberId = 0\n public pendingEffects: Array<Set<any> | undefined> = []\n public maxPendingDepth = 0\n public computedCache = new WeakMap<Function, any>()\n private diRegistry = new Map<unknown, unknown>()\n private diCache = new Map<unknown, unknown>()\n private proxyCache = new WeakMap<object, any>()\n private proxyTargets = new WeakSet<object>()\n private reactiveSignals = new WeakMap<object, Map<string | symbol, any>>()\n private cycleDetectionStack = new Set<unknown>()\n public batchedSignals = new Set<any>()\n public activeOwner: any = null\n // Системная очередь для отложенного батчинга деструкторов\n public pendingCleanups = new Set<() => void>()\n\n // Метод для регистрации отложенной очистки\n public queueCleanup(cleanup: () => void): void {\n this.pendingCleanups.add(cleanup)\n }\n\n constructor(options?: { logger?: EngineLoggerOptions }) {\n this.logger = options?.logger || { isEnabled: false }\n }\n\n public scheduleEffect(effect: any): void {\n const depth = effect.depth || 0\n if (!this.pendingEffects[depth]) {\n this.pendingEffects[depth] = new Set()\n }\n this.pendingEffects[depth]!.add(effect)\n if (depth > this.maxPendingDepth) {\n this.maxPendingDepth = depth\n }\n }\n\n public queueLog(type: LogEventType, label: string, details?: { from?: unknown; to?: unknown; payload?: unknown }): void {\n queueLog(type, label, details)\n }\n\n public batch<T>(fn: () => T): T {\n this.batchDepth++\n try {\n return fn()\n } finally {\n this.batchDepth--\n if (this.batchDepth === 0) {\n if (this.batchedSignals.size > 0) {\n for (const sig of this.batchedSignals) {\n if (sig._flushBatch) sig._flushBatch()\n }\n this.batchedSignals.clear()\n }\n\n this.flushEffects()\n }\n }\n }\n\n public flushEffects(): void {\n if (this.batchDepth > 0) return\n\n const prevConsumer = this.activeConsumer\n\n try {\n for (let depth = 0; depth <= this.maxPendingDepth; depth++) {\n const bucket = this.pendingEffects[depth]\n\n if (!bucket || bucket.size === 0) continue\n\n const effects = Array.from(bucket)\n bucket.clear()\n\n for (const effect of effects) {\n if (effect.isDisposed) continue\n\n // ⚡ ЗOЛОТOЙ СТАНДАРТ ОПТИМИЗАЦИИ (Early Dirty Check Bypass):\n // Переносим проверку грязи в самое начало цикла. Если эффект не был промаркирован\n // как DIRTY волной push-уведомлений, мы мгновенно пропускаем его, полностью\n // избавляя V8 от холостого обхода родителей и тяжелых хэш-поисков depVersions.get(dep).\n if (!effect.isDirty) continue\n\n if (effect.dependencies && effect.dependencies.size > 0) {\n let parentChanged = false\n\n for (const dep of effect.dependencies) {\n const rawNode = dep._node || dep\n\n if (rawNode.type === 'computed' && rawNode.state > 0) {\n if (rawNode.update) rawNode.update()\n }\n\n if (effect.depVersions && effect.depVersions.get(dep) !== rawNode.version) {\n parentChanged = true\n }\n }\n\n if (!parentChanged && effect.dependencies.size > 0) {\n effect.isDirty = false\n continue\n }\n }\n\n try {\n effect.run()\n } catch (err) {\n // ⚡ ФИКС ТЕСТА #177: Если один из эффектов выбросил исключение,\n // принудительно очищаем и гасим флаги грязи (isDirty) у всех оставшихся\n // в очереди эффектов текущего и последующих уровней глубины.\n for (let d = depth; d <= this.maxPendingDepth; d++) {\n const remainingBucket = this.pendingEffects[d]\n if (remainingBucket) {\n for (const remEffect of remainingBucket) {\n remEffect.isDirty = false\n }\n remainingBucket.clear()\n }\n }\n this.maxPendingDepth = 0\n throw err // Прокидываем ошибку дальше по спецификации\n }\n\n }\n\n if (depth > 0) {\n let hasEarlierEffects = false\n for (let i = 0; i < depth; i++) {\n if (this.pendingEffects[i] && this.pendingEffects[i]!.size > 0) {\n hasEarlierEffects = true\n break\n }\n }\n if (hasEarlierEffects) depth = -1\n }\n }\n this.maxPendingDepth = 0\n } finally {\n this.activeConsumer = prevConsumer\n }\n }\n\n // public batch<T>(fn: () => T): T {\n // return executeBatch<T>(this, fn)\n // }\n\n // ⚛️ ФИКС ДЛЯ РЕЛИЗА 2.5.0: Меняем private на public,\n // чтобы изолированные функции-методы имели легальный доступ к регистратору логов\n public log(params: any) {\n if (!this.logger || !this.logger.isEnabled) return\n ReactiveDebounceLogger.processLog(this.logger, queueLog, params)\n }\n\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n return createSignal<T>(this, initialValue, optionsOrName)\n }\n\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n // Просто делегируем вызов строго изолированной чистой функции\n return createComputed<T>(this, fn, signalName)\n }\n\n public effect(fn: () => void | CleanupFn, effectName?: string): () => void {\n return createEffect(this, fn, effectName)\n }\n\n public untrack<T>(fn: () => T): T {\n return executeUntrack<T>(this, fn)\n }\n\n public reactive<T extends object>(target: T, name?: string): T {\n return createReactive<T>(this, target, name)\n }\n\n public provide<T>(token: InjectionToken<T>, valueOrFactory: T | Factory<T>): void {\n this.diRegistry.set(token, valueOrFactory)\n this.diCache.delete(token)\n }\n\n public inject<T>(token: InjectionToken<T>): T {\n if (token === undefined || token === null) throw new Error('[DI Error] Token is empty')\n if (this.diCache.has(token)) return this.diCache.get(token) as T\n if (this.cycleDetectionStack.has(token)) throw new Error('[DI Error] Cyclic dependency detected')\n this.cycleDetectionStack.add(token)\n try {\n if (this.diRegistry.has(token)) {\n const entry = this.diRegistry.get(token)\n if (typeof entry === 'function') {\n if ((entry as any)._isMockFunction) {\n const mockRes = (entry as Function)(this)\n if (mockRes !== undefined) {\n this.diCache.set(token, mockRes)\n return mockRes as T\n }\n return entry as unknown as T\n }\n const str = entry.toString()\n const isClass = str.startsWith('class ') || str.startsWith('class{') || (entry.prototype && entry.prototype.constructor === entry && !str.startsWith('function') && !str.includes('=>'))\n if (!isClass) {\n const resolved = (entry as Factory<T>)(this)\n this.diCache.set(token, resolved)\n return resolved\n }\n return entry as unknown as T\n }\n return entry as T\n }\n if (typeof token === 'function') {\n const resolved = new (token as Constructor<T>)(this)\n this.diCache.set(token, resolved)\n return resolved\n }\n throw new Error('[DI Error] Token not found')\n } finally {\n this.cycleDetectionStack.delete(token)\n }\n }\n\n public delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n return reject(new DOMException('Aborted', 'AbortError'))\n }\n\n const timeoutId = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n const onAbort = () => {\n clearTimeout(timeoutId)\n reject(new DOMException('Aborted', 'AbortError'))\n }\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n }\n\n /**\n * Создает умную реактивную декларативную обертку над асинхронными операциями\n * с автоматическим подавлением Race Conditions и встроенным методом .destroy().\n */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>,\n ): Resource<T> {\n // Передаем инстанс движка (this) как IReactiveEngine для соблюдения строгой типизации\n return createResource<T, S>(this, fetcher, source, optionsOrName)\n }\n}\n\nexport { ReactiveEngineCore as ReactiveEngine, ReactiveEngineCore as ReactiveEngineAutomatic }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core.mjs","names":[],"sources":["../src/core/core.ts"],"sourcesContent":["// import { getExtractedValues } from '../utils/getExtractedValues'\nimport { Signal, Computed, IEffect, EngineLoggerOptions, LogEventType, CleanupFn, TransactionContext, LogRecord, SignalOptions, ResourceOptions, ResourceState, Resource } from './types'\nimport { createComputed } from './core.method.computed'\nimport { createSignal } from './core.method.signal'\n// import { executeBatch } from './core.method.batch'\nimport { executeUntrack } from './core.method.untrack'\nimport { createReactive } from './core.method.reactive'\nimport { createEffect } from './core.method.effect'\nimport { ReactiveDebounceLogger } from './core.logger'\nimport { createResource } from './core.method.resource'\n\n// Тип для конструктора класса\ntype Constructor<T> = new (...args: any[]) => T\n// Тип для фабричной функции зависимостей\ntype Factory<T> = (engine: any) => T\n// Универсальный тип токена DI\ntype InjectionToken<T> = Constructor<T> | Factory<T> | symbol | string\n\nconst badgeColors: Record<LogEventType, string> = {\n signal: 'background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n computed: 'background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n effect: 'background: #e01e5a; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n batch: 'background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n resource: 'background: #d97706; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n reactive: 'background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;'\n}\nlet currentTransaction: TransactionContext | null = null\nfunction safeValueToString(value: unknown): string {\n if (value === null) return 'null'\n if (value === undefined) return 'undefined'\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value)\n } catch (e) {\n return '[Cyclic Object or Layer]'\n }\n }\n return String(value)\n}\nexport function queueLog(type: LogEventType, label: string, details?: { from?: unknown; to?: unknown; payload?: unknown }): void {\n if (!currentTransaction) {\n currentTransaction = { id: Math.random().toString(36).substring(2, 9), pendingLogQueue: [], isFlushing: false }\n queueMicrotask(() => flushLogs())\n }\n currentTransaction.pendingLogQueue.push({ type, label, from: details?.from, to: details?.to, payload: details?.payload, timestamp: Date.now() })\n}\nexport function flushLogs(): void {\n if (!currentTransaction || currentTransaction.isFlushing) return\n currentTransaction.isFlushing = true\n const queue = currentTransaction.pendingLogQueue\n if (queue.length === 0) {\n currentTransaction = null\n return\n }\n console.groupCollapsed(`%cREACTIVE TRANSACTION%c [id: ${currentTransaction.id}] (${queue.length} operations)`, 'background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;', 'color: #888; font-weight: normal;')\n for (const record of queue) {\n const colorStyle = badgeColors[record.type]\n const hasTransition = record.from !== undefined || record.to !== undefined\n let logMessage = `%c${record.type.toUpperCase()}%c ${record.label}`\n\n if (hasTransition) {\n if (!!record.from)\n logMessage += ` ${safeValueToString(record.from)} → ${safeValueToString(record.to)}`\n else logMessage += ` (${safeValueToString(record.to)})`\n }\n\n if (record.payload !== undefined) {\n console.groupCollapsed(logMessage, colorStyle, 'color: inherit;')\n console.log('Payload:', record.payload)\n console.groupEnd()\n } else {\n console.log(logMessage, colorStyle, 'color: inherit;')\n }\n }\n console.groupEnd()\n currentTransaction = null\n}\nexport class ReactiveEngineCore {\n protected frameworkPrefix = 'core'\n public logger: EngineLoggerOptions\n public epoch = 0\n public activeConsumer: any = null\n public batchDepth = 0\n public subscriberId = 0\n public pendingEffects: Array<Set<any> | undefined> = []\n public maxPendingDepth = 0\n public computedCache = new WeakMap<Function, any>()\n private diRegistry = new Map<unknown, unknown>()\n private diCache = new Map<unknown, unknown>()\n private proxyCache = new WeakMap<object, any>()\n private proxyTargets = new WeakSet<object>()\n private reactiveSignals = new WeakMap<object, Map<string | symbol, any>>()\n private cycleDetectionStack = new Set<unknown>()\n public batchedSignals = new Set<any>()\n public activeOwner: any = null\n // Системная очередь для отложенного батчинга деструкторов\n public pendingCleanups = new Set<() => void>()\n\n // Метод для регистрации отложенной очистки\n public queueCleanup(cleanup: () => void): void {\n this.pendingCleanups.add(cleanup)\n }\n\n constructor(options?: { logger?: EngineLoggerOptions }) {\n this.logger = options?.logger || { isEnabled: false }\n }\n\n public scheduleEffect(effect: any): void {\n const depth = effect.depth || 0\n if (!this.pendingEffects[depth]) {\n this.pendingEffects[depth] = new Set()\n }\n this.pendingEffects[depth]!.add(effect)\n if (depth > this.maxPendingDepth) {\n this.maxPendingDepth = depth\n }\n }\n\n public queueLog(type: LogEventType, label: string, details?: { from?: unknown; to?: unknown; payload?: unknown }): void {\n queueLog(type, label, details)\n }\n\n public batch<T>(fn: () => T): T {\n this.batchDepth++\n try {\n return fn()\n } finally {\n this.batchDepth--\n if (this.batchDepth === 0) {\n if (this.batchedSignals.size > 0) {\n for (const sig of this.batchedSignals) {\n if (sig._flushBatch) sig._flushBatch()\n }\n this.batchedSignals.clear()\n }\n\n this.flushEffects()\n }\n }\n }\n\n public flushEffects(): void {\n if (this.batchDepth > 0) return\n\n const prevConsumer = this.activeConsumer\n\n try {\n for (let depth = 0; depth <= this.maxPendingDepth; depth++) {\n const bucket = this.pendingEffects[depth]\n\n if (!bucket || bucket.size === 0) continue\n\n const effects = Array.from(bucket)\n bucket.clear()\n\n for (const effect of effects) {\n if (effect.isDisposed) continue\n\n // ⚡ ЗOЛОТOЙ СТАНДАРТ ОПТИМИЗАЦИИ (Early Dirty Check Bypass):\n // Переносим проверку грязи в самое начало цикла. Если эффект не был промаркирован\n // как DIRTY волной push-уведомлений, мы мгновенно пропускаем его, полностью\n // избавляя V8 от холостого обхода родителей и тяжелых хэш-поисков depVersions.get(dep).\n if (!effect.isDirty) continue\n\n if (effect.dependencies && effect.dependencies.size > 0) {\n let parentChanged = false\n\n for (const dep of effect.dependencies) {\n const rawNode = dep._node || dep\n\n if (rawNode.type === 'computed' && rawNode.state > 0) {\n if (rawNode.update) rawNode.update()\n }\n\n if (effect.depVersions && effect.depVersions.get(dep) !== rawNode.version) {\n parentChanged = true\n }\n }\n\n if (!parentChanged && effect.dependencies.size > 0) {\n effect.isDirty = false\n continue\n }\n }\n\n try {\n effect.run()\n } catch (err) {\n // ⚡ ФИКС ТЕСТА #177: Если один из эффектов выбросил исключение,\n // принудительно очищаем и гасим флаги грязи (isDirty) у всех оставшихся\n // в очереди эффектов текущего и последующих уровней глубины.\n for (let d = depth; d <= this.maxPendingDepth; d++) {\n const remainingBucket = this.pendingEffects[d]\n if (remainingBucket) {\n for (const remEffect of remainingBucket) {\n remEffect.isDirty = false\n }\n remainingBucket.clear()\n }\n }\n this.maxPendingDepth = 0\n throw err // Прокидываем ошибку дальше по спецификации\n }\n\n }\n\n if (depth > 0) {\n let hasEarlierEffects = false\n for (let i = 0; i < depth; i++) {\n if (this.pendingEffects[i] && this.pendingEffects[i]!.size > 0) {\n hasEarlierEffects = true\n break\n }\n }\n if (hasEarlierEffects) depth = -1\n }\n }\n this.maxPendingDepth = 0\n } finally {\n this.activeConsumer = prevConsumer\n }\n }\n\n // public batch<T>(fn: () => T): T {\n // return executeBatch<T>(this, fn)\n // }\n\n // ⚛️ ФИКС ДЛЯ РЕЛИЗА 2.5.0: Меняем private на public,\n // чтобы изолированные функции-методы имели легальный доступ к регистратору логов\n public log(params: any) {\n if (!this.logger || !this.logger.isEnabled) return\n ReactiveDebounceLogger.processLog(this.logger, queueLog, params)\n }\n\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n return createSignal<T>(this, initialValue, optionsOrName)\n }\n\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n // Просто делегируем вызов строго изолированной чистой функции\n return createComputed<T>(this, fn, signalName)\n }\n\n public effect(fn: () => void | CleanupFn, effectName?: string): () => void {\n return createEffect(this, fn, effectName)\n }\n\n public untrack<T>(fn: () => T): T {\n return executeUntrack<T>(this, fn)\n }\n\n public reactive<T extends object>(target: T, name?: string): T {\n return createReactive<T>(this, target, name)\n }\n\n public provide<T>(token: InjectionToken<T>, valueOrFactory: T | Factory<T>): void {\n this.diRegistry.set(token, valueOrFactory)\n this.diCache.delete(token)\n }\n\n public inject<T>(token: InjectionToken<T>): T {\n if (token === undefined || token === null) throw new Error('[DI Error] Token is empty')\n if (this.diCache.has(token)) return this.diCache.get(token) as T\n if (this.cycleDetectionStack.has(token)) throw new Error('[DI Error] Cyclic dependency detected')\n this.cycleDetectionStack.add(token)\n try {\n if (this.diRegistry.has(token)) {\n const entry = this.diRegistry.get(token)\n if (typeof entry === 'function') {\n if ((entry as any)._isMockFunction) {\n const mockRes = (entry as Function)(this)\n if (mockRes !== undefined) {\n this.diCache.set(token, mockRes)\n return mockRes as T\n }\n return entry as unknown as T\n }\n const str = entry.toString()\n const isClass = str.startsWith('class ') || str.startsWith('class{') || (entry.prototype && entry.prototype.constructor === entry && !str.startsWith('function') && !str.includes('=>'))\n if (!isClass) {\n const resolved = (entry as Factory<T>)(this)\n this.diCache.set(token, resolved)\n return resolved\n }\n return entry as unknown as T\n }\n return entry as T\n }\n if (typeof token === 'function') {\n const resolved = new (token as Constructor<T>)(this)\n this.diCache.set(token, resolved)\n return resolved\n }\n throw new Error('[DI Error] Token not found')\n } finally {\n this.cycleDetectionStack.delete(token)\n }\n }\n\n public delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n return reject(new DOMException('Aborted', 'AbortError'))\n }\n\n const timeoutId = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n const onAbort = () => {\n clearTimeout(timeoutId)\n reject(new DOMException('Aborted', 'AbortError'))\n }\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n }\n\n /**\n * Создает умную реактивную декларативную обертку над асинхронными операциями\n * с автоматическим подавлением Race Conditions и встроенным методом .destroy().\n */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>,\n ): Resource<T> {\n // Передаем инстанс движка (this) как IReactiveEngine для соблюдения строгой типизации\n return createResource<T, S>(this, fetcher, source, optionsOrName)\n }\n}\n\nexport { ReactiveEngineCore as ReactiveEngine, ReactiveEngineCore as ReactiveEngineAutomatic }\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=(e,t={})=>{let n=new Map,r=t.isLogsEnabled??!1,i=t.ttl
|
|
1
|
+
var e=(e,t={})=>{let n=new Map,r=t.isLogsEnabled??!1,i=t.ttl,a=t.maxSize,o=a!==void 0&&a>0,s=e=>{try{return JSON.stringify(e)}catch{return String(e)}};return async(t,c)=>{let l=s(t),u=n.get(l);u&&o&&(n.delete(l),n.set(l,u));let d=()=>u?i===void 0?!0:Date.now()-u.savedTime<i:!1;if(c.aborted){if(d()&&u)return u.data;throw c.reason||new DOMException(`The operation was aborted.`,`AbortError`)}try{let i=await e(t,c);if(o&&n.size>=a&&!n.has(l)){let e=n.keys().next().value;e!==void 0&&(n.delete(e),r&&console.warn(`[ReactiveEngine:Stale] Кэш переполнен. Удален старый ключ: ${e}`))}return n.set(l,{data:i,savedTime:Date.now()}),i}catch(e){if(d()&&u)return r&&console.warn(`[ReactiveEngine:Stale] Запрос ${l} упал. Отдаем кэш.`,e),u.data;throw e}}};exports.withStaleWhileRevalidate=e;
|
|
2
2
|
//# sourceMappingURL=withStaleWhileRevalidate.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withStaleWhileRevalidate.cjs","names":[],"sources":["../../../src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts"],"sourcesContent":["interface StaleOptions<T> {\n
|
|
1
|
+
{"version":3,"file":"withStaleWhileRevalidate.cjs","names":[],"sources":["../../../src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts"],"sourcesContent":["export interface StaleOptions<T> {\n ttl?: number;\n isLogsEnabled?: boolean;\n maxSize?: number;\n}\n\nexport const withStaleWhileRevalidate = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: StaleOptions<T> = {}\n) => {\n const cacheMap = new Map<string, { data: T; savedTime: number }>()\n const isLogsEnabled = options.isLogsEnabled ?? false\n const ttl = options.ttl\n const maxSize = options.maxSize\n\n // Кэшируем флаг, чтобы не проверять \"undefined\" в горячем цикле\n const hasMaxLimit = maxSize !== undefined && maxSize > 0\n\n const getCacheKey = (source: S): string => {\n try { return JSON.stringify(source) } catch (e) { return String(source) }\n }\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n const cacheKey = getCacheKey(source)\n let cachedRecord = cacheMap.get(cacheKey)\n\n // ⚡ ОПТИМИЗАЦИЯ #1 (Fast Path): Перестраиваем Map только если включено ограничение памяти\n if (cachedRecord && hasMaxLimit) {\n cacheMap.delete(cacheKey)\n cacheMap.set(cacheKey, cachedRecord)\n }\n\n const isCacheValid = (): boolean => {\n if (!cachedRecord) return false\n if (ttl === undefined) return true\n return Date.now() - cachedRecord.savedTime < ttl\n }\n\n if (signal.aborted) {\n if (isCacheValid() && cachedRecord) return cachedRecord.data\n throw signal.reason || new DOMException('The operation was aborted.', 'AbortError')\n }\n\n try {\n const freshData = await fetcher(source, signal)\n\n // ⚡ ОПТИМИЗАЦИЯ #2: Итератор `.keys()` вызывается СТРОГО при реальном переполнении\n if (hasMaxLimit && cacheMap.size >= maxSize && !cacheMap.has(cacheKey)) {\n const oldestKey = cacheMap.keys().next().value\n if (oldestKey !== undefined) {\n cacheMap.delete(oldestKey)\n if (isLogsEnabled) {\n console.warn(`[ReactiveEngine:Stale] Кэш переполнен. Удален старый ключ: ${oldestKey}`)\n }\n }\n }\n\n cacheMap.set(cacheKey, {\n data: freshData,\n savedTime: Date.now()\n })\n\n return freshData\n } catch (error) {\n if (isCacheValid() && cachedRecord) {\n if (isLogsEnabled) {\n console.warn(`[ReactiveEngine:Stale] Запрос ${cacheKey} упал. Отдаем кэш.`, error)\n }\n return cachedRecord.data\n }\n\n throw error\n }\n }\n}\n"],"mappings":"AAMA,IAAa,GACX,EACA,EAA2B,CAAC,IACzB,CACH,IAAM,EAAW,IAAI,IACf,EAAgB,EAAQ,eAAiB,GACzC,EAAM,EAAQ,IACd,EAAU,EAAQ,QAGlB,EAAc,IAAY,IAAA,IAAa,EAAU,EAEjD,EAAe,GAAsB,CACzC,GAAI,CAAE,OAAO,KAAK,UAAU,CAAM,CAAE,MAAY,CAAE,OAAO,OAAO,CAAM,CAAE,CAC1E,EAEA,OAAO,MAAO,EAAW,IAAoC,CAC3D,IAAM,EAAW,EAAY,CAAM,EAC/B,EAAe,EAAS,IAAI,CAAQ,EAGpC,GAAgB,IAClB,EAAS,OAAO,CAAQ,EACxB,EAAS,IAAI,EAAU,CAAY,GAGrC,IAAM,MACC,EACD,IAAQ,IAAA,GAAkB,GACvB,KAAK,IAAI,EAAI,EAAa,UAAY,EAFnB,GAK5B,GAAI,EAAO,QAAS,CAClB,GAAI,EAAa,GAAK,EAAc,OAAO,EAAa,KACxD,MAAM,EAAO,QAAU,IAAI,aAAa,6BAA8B,YAAY,CACpF,CAEA,GAAI,CACF,IAAM,EAAY,MAAM,EAAQ,EAAQ,CAAM,EAG9C,GAAI,GAAe,EAAS,MAAQ,GAAW,CAAC,EAAS,IAAI,CAAQ,EAAG,CACtE,IAAM,EAAY,EAAS,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,MACrC,IAAc,IAAA,KAChB,EAAS,OAAO,CAAS,EACrB,GACF,QAAQ,KAAK,8DAA8D,GAAW,EAG5F,CAOA,OALA,EAAS,IAAI,EAAU,CACrB,KAAM,EACN,UAAW,KAAK,IAAI,CACtB,CAAC,EAEM,CACT,OAAS,EAAO,CACd,GAAI,EAAa,GAAK,EAIpB,OAHI,GACF,QAAQ,KAAK,iCAAiC,EAAS,oBAAqB,CAAK,EAE5E,EAAa,KAGtB,MAAM,CACR,CACF,CACF"}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
interface StaleOptions<T> {
|
|
2
|
-
initialData?: T;
|
|
3
|
-
isLogsEnabled?: boolean;
|
|
4
|
-
/**
|
|
5
|
-
* Время жизни кэша в миллисекундах (Time-To-Live).
|
|
6
|
-
* Если текущий запрос упал ИЛИ был отменен, декоратор вернет `stale`-данные
|
|
7
|
-
* для конкретного source только если они были получены в пределах этого интервала.
|
|
8
|
-
*/
|
|
1
|
+
export interface StaleOptions<T> {
|
|
9
2
|
ttl?: number;
|
|
3
|
+
isLogsEnabled?: boolean;
|
|
4
|
+
maxSize?: number;
|
|
10
5
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Декоратор для сохранения предыдущего успешного состояния данных (Failover Cache) с поддержкой TTL.
|
|
13
|
-
*/
|
|
14
6
|
export declare const withStaleWhileRevalidate: <S, T>(fetcher: (source: S, signal: AbortSignal) => Promise<T>, options?: StaleOptions<T>) => (source: S, signal: AbortSignal) => Promise<T>;
|
|
15
|
-
export {};
|
|
16
7
|
//# sourceMappingURL=withStaleWhileRevalidate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withStaleWhileRevalidate.d.ts","sourceRoot":"","sources":["../../../src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"withStaleWhileRevalidate.d.ts","sourceRoot":"","sources":["../../../src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,CAAC,EAC3C,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACvD,UAAS,YAAY,CAAC,CAAC,CAAM,MAcf,QAAQ,CAAC,EAAE,QAAQ,WAAW,KAAG,OAAO,CAAC,CAAC,CAoDzD,CAAA"}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
//#region src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts
|
|
2
2
|
var e = (e, t = {}) => {
|
|
3
|
-
let n = /* @__PURE__ */ new Map(), r = t.isLogsEnabled ?? !1, i = t.ttl
|
|
4
|
-
t.initialData !== void 0 && n.set("__initial__", {
|
|
5
|
-
data: t.initialData,
|
|
6
|
-
savedTime: Date.now()
|
|
7
|
-
});
|
|
8
|
-
let a = (e) => {
|
|
3
|
+
let n = /* @__PURE__ */ new Map(), r = t.isLogsEnabled ?? !1, i = t.ttl, a = t.maxSize, o = a !== void 0 && a > 0, s = (e) => {
|
|
9
4
|
try {
|
|
10
5
|
return JSON.stringify(e);
|
|
11
6
|
} catch {
|
|
12
7
|
return String(e);
|
|
13
8
|
}
|
|
14
9
|
};
|
|
15
|
-
return async (t,
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
return async (t, c) => {
|
|
11
|
+
let l = s(t), u = n.get(l);
|
|
12
|
+
u && o && (n.delete(l), n.set(l, u));
|
|
13
|
+
let d = () => u ? i === void 0 ? !0 : Date.now() - u.savedTime < i : !1;
|
|
14
|
+
if (c.aborted) {
|
|
15
|
+
if (d() && u) return u.data;
|
|
16
|
+
throw c.reason || new DOMException("The operation was aborted.", "AbortError");
|
|
20
17
|
}
|
|
21
18
|
try {
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
let i = await e(t, c);
|
|
20
|
+
if (o && n.size >= a && !n.has(l)) {
|
|
21
|
+
let e = n.keys().next().value;
|
|
22
|
+
e !== void 0 && (n.delete(e), r && console.warn(`[ReactiveEngine:Stale] Кэш переполнен. Удален старый ключ: ${e}`));
|
|
23
|
+
}
|
|
24
|
+
return n.set(l, {
|
|
25
|
+
data: i,
|
|
25
26
|
savedTime: Date.now()
|
|
26
|
-
}),
|
|
27
|
+
}), i;
|
|
27
28
|
} catch (e) {
|
|
28
|
-
|
|
29
|
-
if (l() && c) return r && console.warn(`[ReactiveEngine:Stale] Запрос для ключа ${s} ${t ? "отменен" : "упал"}. Отдаем сохраненное состояние.`, e), c.data;
|
|
29
|
+
if (d() && u) return r && console.warn(`[ReactiveEngine:Stale] Запрос ${l} упал. Отдаем кэш.`, e), u.data;
|
|
30
30
|
throw e;
|
|
31
31
|
}
|
|
32
32
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withStaleWhileRevalidate.mjs","names":[],"sources":["../../../src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts"],"sourcesContent":["interface StaleOptions<T> {\n
|
|
1
|
+
{"version":3,"file":"withStaleWhileRevalidate.mjs","names":[],"sources":["../../../src/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.ts"],"sourcesContent":["export interface StaleOptions<T> {\n ttl?: number;\n isLogsEnabled?: boolean;\n maxSize?: number;\n}\n\nexport const withStaleWhileRevalidate = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: StaleOptions<T> = {}\n) => {\n const cacheMap = new Map<string, { data: T; savedTime: number }>()\n const isLogsEnabled = options.isLogsEnabled ?? false\n const ttl = options.ttl\n const maxSize = options.maxSize\n\n // Кэшируем флаг, чтобы не проверять \"undefined\" в горячем цикле\n const hasMaxLimit = maxSize !== undefined && maxSize > 0\n\n const getCacheKey = (source: S): string => {\n try { return JSON.stringify(source) } catch (e) { return String(source) }\n }\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n const cacheKey = getCacheKey(source)\n let cachedRecord = cacheMap.get(cacheKey)\n\n // ⚡ ОПТИМИЗАЦИЯ #1 (Fast Path): Перестраиваем Map только если включено ограничение памяти\n if (cachedRecord && hasMaxLimit) {\n cacheMap.delete(cacheKey)\n cacheMap.set(cacheKey, cachedRecord)\n }\n\n const isCacheValid = (): boolean => {\n if (!cachedRecord) return false\n if (ttl === undefined) return true\n return Date.now() - cachedRecord.savedTime < ttl\n }\n\n if (signal.aborted) {\n if (isCacheValid() && cachedRecord) return cachedRecord.data\n throw signal.reason || new DOMException('The operation was aborted.', 'AbortError')\n }\n\n try {\n const freshData = await fetcher(source, signal)\n\n // ⚡ ОПТИМИЗАЦИЯ #2: Итератор `.keys()` вызывается СТРОГО при реальном переполнении\n if (hasMaxLimit && cacheMap.size >= maxSize && !cacheMap.has(cacheKey)) {\n const oldestKey = cacheMap.keys().next().value\n if (oldestKey !== undefined) {\n cacheMap.delete(oldestKey)\n if (isLogsEnabled) {\n console.warn(`[ReactiveEngine:Stale] Кэш переполнен. Удален старый ключ: ${oldestKey}`)\n }\n }\n }\n\n cacheMap.set(cacheKey, {\n data: freshData,\n savedTime: Date.now()\n })\n\n return freshData\n } catch (error) {\n if (isCacheValid() && cachedRecord) {\n if (isLogsEnabled) {\n console.warn(`[ReactiveEngine:Stale] Запрос ${cacheKey} упал. Отдаем кэш.`, error)\n }\n return cachedRecord.data\n }\n\n throw error\n }\n }\n}\n"],"mappings":";AAMA,IAAa,KACX,GACA,IAA2B,CAAC,MACzB;CACH,IAAM,oBAAW,IAAI,IAA4C,GAC3D,IAAgB,EAAQ,iBAAiB,IACzC,IAAM,EAAQ,KACd,IAAU,EAAQ,SAGlB,IAAc,MAAY,KAAA,KAAa,IAAU,GAEjD,KAAe,MAAsB;EACzC,IAAI;GAAE,OAAO,KAAK,UAAU,CAAM;EAAE,QAAY;GAAE,OAAO,OAAO,CAAM;EAAE;CAC1E;CAEA,OAAO,OAAO,GAAW,MAAoC;EAC3D,IAAM,IAAW,EAAY,CAAM,GAC/B,IAAe,EAAS,IAAI,CAAQ;EAGxC,AAAI,KAAgB,MAClB,EAAS,OAAO,CAAQ,GACxB,EAAS,IAAI,GAAU,CAAY;EAGrC,IAAM,UACC,IACD,MAAQ,KAAA,IAAkB,KACvB,KAAK,IAAI,IAAI,EAAa,YAAY,IAFnB;EAK5B,IAAI,EAAO,SAAS;GAClB,IAAI,EAAa,KAAK,GAAc,OAAO,EAAa;GACxD,MAAM,EAAO,UAAU,IAAI,aAAa,8BAA8B,YAAY;EACpF;EAEA,IAAI;GACF,IAAM,IAAY,MAAM,EAAQ,GAAQ,CAAM;GAG9C,IAAI,KAAe,EAAS,QAAQ,KAAW,CAAC,EAAS,IAAI,CAAQ,GAAG;IACtE,IAAM,IAAY,EAAS,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IACzC,AAAI,MAAc,KAAA,MAChB,EAAS,OAAO,CAAS,GACrB,KACF,QAAQ,KAAK,8DAA8D,GAAW;GAG5F;GAOA,OALA,EAAS,IAAI,GAAU;IACrB,MAAM;IACN,WAAW,KAAK,IAAI;GACtB,CAAC,GAEM;EACT,SAAS,GAAO;GACd,IAAI,EAAa,KAAK,GAIpB,OAHI,KACF,QAAQ,KAAK,iCAAiC,EAAS,qBAAqB,CAAK,GAE5E,EAAa;GAGtB,MAAM;EACR;CACF;AACF"}
|