@pravosleva/reactive-engine 2.6.3-alpha.0 → 2.6.3

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.
Files changed (31) hide show
  1. package/dist/core/core.d.ts +1 -1
  2. package/dist/core/core.d.ts.map +1 -1
  3. package/dist/core/core.logger.d.ts.map +1 -1
  4. package/dist/core/core.method.computed.d.ts.map +1 -1
  5. package/dist/core/core.method.resource.d.ts +2 -1
  6. package/dist/core/core.method.resource.d.ts.map +1 -1
  7. package/dist/core/types.d.ts +5 -0
  8. package/dist/core/types.d.ts.map +1 -1
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.logger.cjs +1 -1
  11. package/dist/core.logger.cjs.map +1 -1
  12. package/dist/core.logger.mjs +3 -3
  13. package/dist/core.logger.mjs.map +1 -1
  14. package/dist/core.method.computed.cjs +1 -1
  15. package/dist/core.method.computed.cjs.map +1 -1
  16. package/dist/core.method.computed.mjs +1 -1
  17. package/dist/core.method.computed.mjs.map +1 -1
  18. package/dist/core.method.resource.cjs +1 -1
  19. package/dist/core.method.resource.cjs.map +1 -1
  20. package/dist/core.method.resource.mjs +74 -47
  21. package/dist/core.method.resource.mjs.map +1 -1
  22. package/dist/core.mjs.map +1 -1
  23. package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.cjs +1 -1
  24. package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.cjs.map +1 -1
  25. package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.d.ts +3 -12
  26. package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.d.ts.map +1 -1
  27. package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.mjs +17 -17
  28. package/dist/decorators/withStaleWhileRevalidate/withStaleWhileRevalidate.mjs.map +1 -1
  29. package/dist/reactive-engine.umd.js +2 -2
  30. package/dist/reactive-engine.umd.js.map +1 -1
  31. 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
- }, `[CORE_INTERNAL_SIGNAL=1]:resource:state:${o}`), f = (e) => {
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: d.value
18
+ from: m.value
16
19
  }
17
- }), d.value = e;
18
- }, p = async (r, i, a = !1) => {
20
+ }), m.value = e;
21
+ }, g = async (r, i, a = !1) => {
19
22
  if (i.aborted) return;
20
- if (s.validateBeforeFetch && s.validateBeforeFetch(r), u) {
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(() => d.value.data);
25
- f({
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 p = a && c ? null : t.untrack(() => d.value.data);
35
- f({
36
- data: p,
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 <= 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, s);
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
- f({
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
- f({
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
- if (i.aborted || n.name === "AbortError" && i.aborted) return;
71
- let r = n.name === "TimeoutError" || n.name === "AbortError" || n.message?.includes("timeout"), s = e({
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, c = s && e({
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", l = s || n.name === "ValidationError";
80
- if (a === 0 || l) {
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 ? Error("Request timed out after 1000ms", { cause: n }) : s ? Error(c, { cause: n }) : n;
83
- f({
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: e instanceof Error ? e.message : typeof e == "string" ? e : `typeof finalError is ${typeof e}`,
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
- let n = Math.random() * 200;
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
- }), console.warn(`[Resource Retry] "${o}" сбой по ${r ? "таймауту" : "ошибке сети"}. Попытка ${a + 1}/${a + 1}...`);
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
- }, m = null, h = t.effect(() => {
136
+ }, _ = null, v = t.effect(() => {
110
137
  let e = r ? r.value : void 0, n = new AbortController();
111
- return m = n, t.untrack(() => {
112
- p(e, n.signal, !0);
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 d.value.data;
144
+ return m.value.data;
118
145
  },
119
146
  get loading() {
120
- return d.value.loading;
147
+ return m.value.loading;
121
148
  },
122
149
  get error() {
123
- return d.value.error;
150
+ return m.value.error;
124
151
  },
125
152
  get isRetrying() {
126
- return d.value.isRetrying;
153
+ return m.value.isRetrying;
127
154
  },
128
155
  get value() {
129
- return d.value;
156
+ return m.value;
130
157
  },
131
158
  refetch: () => {
132
- m?.abort(), m = new AbortController(), p(r ? r.value : void 0, m.signal, !1);
159
+ _?.abort(), _ = new AbortController(), g(r ? r.value : void 0, _.signal, !1);
133
160
  },
134
- subscribe: (e) => d.subscribe(e),
161
+ subscribe: (e) => m.subscribe(e),
135
162
  destroy: () => {
136
- m?.abort(), m = null, typeof h == "function" && h(), d.destroy && d.destroy();
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":";;AAYA,SAAgB,EACd,GACA,GACA,GACA,GACa;CACb,IAAM,IAAkB,KAAiB,OAAO,KAAkB,UAC5D,IAAqB,IACrB,EAAwC,QAAQ,qBACjD,KAA4B,oBAE3B,IAA0C,IAC3C,IACD,CAAC,GAEC,IAA0B,EAAQ,2BAA2B,IAC7D,IAAmB,EAAQ,kBAC3B,IAAsB,EAAQ,qBAQ9B,IAAQ,EAAO,OACnB;EAAE,MAAM;EAAM,SAAS;EAAM,OAAO;EAAM,YAAY;CAAM,GAC5D,2CAA2C,GAC7C,GAEM,KAAuB,MAAgC;EAE3D,AADA,EAAO,IAAI;GAAE,MAAM;GAAY,OAAO;GAAoB,SAAS;IAAE,IAAI;IAAW,MAAM,EAAM;GAAM;EAAE,CAAC,GACzG,EAAM,QAAQ;CAChB,GAEM,IAAO,OAAO,GAAW,GAA2B,IAAiB,OAAU;EACnF,IAAI,EAAa,SAAS;EAG1B,IAFgB,EAAQ,uBAAsB,EAAQ,oBAAoB,CAAM,GAE5E,GAAqB;GACvB,IAAM,IAAsB,EAAoB,CAAM;GACtD,IAAI,MAAwB,MAAS,OAAO,KAAwB,UAAU;IAC5E,IAAI,EAAa,SAAS;IAC1B,IAAM,IAAW,OAAO,KAAwB,WAC5C,IACA,4CAEE,IAAc,IAAiB,OAAO,EAAO,cAAc,EAAM,MAAM,IAAI;IACjF,EAAoB;KAAE,MAAM;KAAa,SAAS;KAAO,OAAO;KAAU,YAAY;IAAM,CAAC;IAC7F;GACF;EACF;EAEA,IAAM,IADc,KAAkB,IACJ,OAAO,EAAO,cAAc,EAAM,MAAM,IAAI;EAC9E,EAAoB;GAAE,MAAM;GAAa,SAAS;GAAM,OAAO;GAAM,YAAY;EAAM,CAAC;EACxF,KAAK,IAAI,IAAU,GAAG,KAAW,GAAY,KAAW;GACtD,IAAI,EAAa,SAAS;GAC1B,IAAI,IAAiB;GACrB,IAAI;IACF,IAAM,IAAO,MAAM,EAAQ,GAAQ,CAAc;IACjD,IAAI,EAAa,SAAS;IAC1B,IAAI,EAAe,SACjB,MAAM,IAAI,aAAa,4BAA4B,cAAc;IAEnE,IAAI,GAAkB;KACpB,IAAM,IAAmB,EAAiB,CAAI;KAC9C,IAAI,MAAqB,MAAS,OAAO,KAAqB,UAAU;MACtE,IAAI,EAAa,SAAS;MAE1B,EAAoB;OAAE,MAAM;OAAM,SAAS;OAAO,OADjC,OAAO,KAAqB,WAAW,IAAmB;OACR,YAAY;MAAM,CAAC;MACtF;KACF;IACF;IACA,IAAI,EAAa,SAAS;IAC1B,EAAoB;KAAE;KAAM,SAAS;KAAO,OAAO;KAAM,YAAY;IAAM,CAAC;IAC5E;GACF,SAAS,GAAY;IACnB,IAAI,EAAa,WAAa,EAAY,SAAS,gBAAgB,EAAa,SAAU;IAC1F,IAAM,IAAa,EAAY,SAAS,kBAAmB,EAAY,SAAS,gBAAiB,EAAY,SAAS,SAAS,SAAS,GAClI,IAA0B,EAAmB;KACjD,QAAQ,CAAE,EAAY,OAAO;KAAG,aAAa;KAA0C,WAAW;IACpG,CAAC,CAAC,GAAG,OAAO,OAAO,IAEf,IAAuC,KACtC,EAAmB;KACpB,QAAQ,CAAE,EAAY,OAAO;KAAG,aAAa;KAAW,WAAW;IACrE,CAAC,CAAC,GAAG,MACH,2BACE,IAA6B,KAA4B,EAAY,SAAS;IACpF,IAAI,MAAY,KAAc,GAA4B;KACxD,IAAI,EAAa,SAAS;KAC1B,IAAM,IAAa,IACX,MAAM,kCAA0C,EAAE,OAAO,EAAE,CAAC,IAChE,IACM,MAAM,GAA8B,EAAE,OAAO,EAAE,CAAC,IACpD;KAEN,EAAoB;MAClB,MAAM;MAAM,SAAS;MACrB,OAAO,aAAsB,QAAQ,EAAW,UAAU,OAAO,KAAe,WAAW,IAAa,wBAAwB,OAAO;MACvI,YAAY;KACd,CAAC;KACD;IACF,OAAO;KACL,IAAI,EAAa,SAAS;KAC1B,IAAI,IAAgC,MAAqB,KAAG;KAC5D,IAAe,KAAK,IAAI,GAAc,GAAa;KACnD,IAAM,IAAS,KAAK,OAAO,IAAI;KAI/B,AAHA,KAA8B,GAC9B,EAAoB;MAAE,MAAM,EAAM,MAAM;MAAM,SAAS;MAAM,OAAO;MAAM,YAAY;KAAK,CAAC,GAE5F,QAAQ,KAAK,qBAAqB,EAAmB,YADnC,IAAY,aAAa,cACgC,YAAY,IAAU,EAAE,GAAqC,IAAU,EAAE,IAAI;KAExJ,IAAI;MAEF,IADA,MAAM,EAAO,MAAM,GAAc,CAAY,GACzC,EAAa,SAAS;KAC5B,QAAqB;MACnB;KACF;IACF;GACF;EACF;CACF,GAEI,IAAiD,MAE/C,IAAwB,EAAO,aAAa;EAChD,IAAM,IAAS,IAAS,EAAO,QAAQ,KAAA,GACjC,IAAmB,IAAI,gBAAgB;EAO7C,OANA,IAAyB,GAEzB,EAAO,cAAc;GACnB,EAAK,GAAa,EAAiB,QAAQ,EAAI;EACjD,CAAC,SAEY,EAAiB,MAAM;CACtC,GAAG,iCAAiC;CAEpC,OAAO;EACL,IAAI,OAAO;GAAE,OAAO,EAAM,MAAM;EAAK;EACrC,IAAI,UAAU;GAAE,OAAO,EAAM,MAAM;EAAQ;EAC3C,IAAI,QAAQ;GAAE,OAAO,EAAM,MAAM;EAAM;EACvC,IAAI,aAAa;GAAE,OAAO,EAAM,MAAM;EAAW;EACjD,IAAI,QAAQ;GAAE,OAAO,EAAM;EAAM;EACjC,eAAe;GAGb,AAFA,GAAwB,MAAM,GAC9B,IAAyB,IAAI,gBAAgB,GAC7C,EAAK,IAAS,EAAO,QAAQ,KAAA,GAAgB,EAAuB,QAAQ,EAAK;EACnF;EACA,YAAY,MAAwC,EAAM,UAAU,CAAE;EACtE,eAAe;GAMb,AALA,GAAwB,MAAM,GAC9B,IAAyB,MACrB,OAAO,KAA0B,cACnC,EAAsB,GAEnB,EAAc,WACjB,EAAe,QAAQ;EAE3B;CACF;AACF"}
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":";;;;;;;;AAkBA,IAAM,IAA4C;CAChD,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,OAAO;CACP,UAAU;CACV,UAAU;AACZ,GACI,IAAgD;AACpD,SAAS,EAAkB,GAAwB;CACjD,IAAI,MAAU,MAAM,OAAO;CAC3B,IAAI,MAAU,KAAA,GAAW,OAAO;CAChC,IAAI,OAAO,KAAU,UACnB,IAAI;EACF,OAAO,KAAK,UAAU,CAAK;CAC7B,QAAY;EACV,OAAO;CACT;CAEF,OAAO,OAAO,CAAK;AACrB;AACA,SAAgB,EAAS,GAAoB,GAAe,GAAqE;CAK/H,AAJK,MACH,IAAqB;EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,GAAG,CAAC;EAAG,iBAAiB,CAAC;EAAG,YAAY;CAAM,GAC9G,qBAAqB,EAAU,CAAC,IAElC,EAAmB,gBAAgB,KAAK;EAAE;EAAM;EAAO,MAAM,GAAS;EAAM,IAAI,GAAS;EAAI,SAAS,GAAS;EAAS,WAAW,KAAK,IAAI;CAAE,CAAC;AACjJ;AACA,SAAgB,IAAkB;CAChC,IAAI,CAAC,KAAsB,EAAmB,YAAY;CAC1D,EAAmB,aAAa;CAChC,IAAM,IAAQ,EAAmB;CACjC,IAAI,EAAM,WAAW,GAAG;EACtB,IAAqB;EACrB;CACF;CACA,QAAQ,eAAe,iCAAiC,EAAmB,GAAG,KAAK,EAAM,OAAO,eAAe,8FAA8F,mCAAmC;CAChP,KAAK,IAAM,KAAU,GAAO;EAC1B,IAAM,IAAa,EAAY,EAAO,OAChC,IAAgB,EAAO,SAAS,KAAA,KAAa,EAAO,OAAO,KAAA,GAC7D,IAAa,KAAK,EAAO,KAAK,YAAY,EAAE,KAAK,EAAO;EAQ5D,AANI,MACI,EAAO,OACX,KAAc,IAAI,EAAkB,EAAO,IAAI,EAAE,KAAK,EAAkB,EAAO,EAAE,MAC9E,KAAc,KAAK,EAAkB,EAAO,EAAE,EAAE,KAGnD,EAAO,YAAY,KAAA,IAKrB,QAAQ,IAAI,GAAY,GAAY,iBAAiB,KAJrD,QAAQ,eAAe,GAAY,GAAY,iBAAiB,GAChE,QAAQ,IAAI,YAAY,EAAO,OAAO,GACtC,QAAQ,SAAS;CAIrB;CAEA,AADA,QAAQ,SAAS,GACjB,IAAqB;AACvB;AACA,IAAa,IAAb,MAAgC;CAC9B,kBAA4B;CAC5B;CACA,QAAe;CACf,iBAA6B;CAC7B,aAAoB;CACpB,eAAsB;CACtB,iBAAqD,CAAC;CACtD,kBAAyB;CACzB,gCAAuB,IAAI,QAAuB;CAClD,6BAAqB,IAAI,IAAsB;CAC/C,0BAAkB,IAAI,IAAsB;CAC5C,6BAAqB,IAAI,QAAqB;CAC9C,+BAAuB,IAAI,QAAgB;CAC3C,kCAA0B,IAAI,QAA2C;CACzE,sCAA8B,IAAI,IAAa;CAC/C,iCAAwB,IAAI,IAAS;CACrC,cAA0B;CAE1B,kCAAyB,IAAI,IAAgB;CAG7C,aAAoB,GAA2B;EAC7C,KAAK,gBAAgB,IAAI,CAAO;CAClC;CAEA,YAAY,GAA4C;EACtD,KAAK,SAAS,GAAS,UAAU,EAAE,WAAW,GAAM;CACtD;CAEA,eAAsB,GAAmB;EACvC,IAAM,IAAQ,EAAO,SAAS;EAK9B,AAJK,KAAK,eAAe,OACvB,KAAK,eAAe,qBAAS,IAAI,IAAI,IAEvC,KAAK,eAAe,EAAM,CAAE,IAAI,CAAM,GAClC,IAAQ,KAAK,oBACf,KAAK,kBAAkB;CAE3B;CAEA,SAAgB,GAAoB,GAAe,GAAqE;EACtH,EAAS,GAAM,GAAO,CAAO;CAC/B;CAEA,MAAgB,GAAgB;EAC9B,KAAK;EACL,IAAI;GACF,OAAO,EAAG;EACZ,UAAU;GAER,IADA,KAAK,cACD,KAAK,eAAe,GAAG;IACzB,IAAI,KAAK,eAAe,OAAO,GAAG;KAChC,KAAK,IAAM,KAAO,KAAK,gBACrB,AAAI,EAAI,eAAa,EAAI,YAAY;KAEvC,KAAK,eAAe,MAAM;IAC5B;IAEA,KAAK,aAAa;GACpB;EACF;CACF;CAEA,eAA4B;EAC1B,IAAI,KAAK,aAAa,GAAG;EAEzB,IAAM,IAAe,KAAK;EAE1B,IAAI;GACF,KAAK,IAAI,IAAQ,GAAG,KAAS,KAAK,iBAAiB,KAAS;IAC1D,IAAM,IAAS,KAAK,eAAe;IAEnC,IAAI,CAAC,KAAU,EAAO,SAAS,GAAG;IAElC,IAAM,IAAU,MAAM,KAAK,CAAM;IACjC,EAAO,MAAM;IAEb,KAAK,IAAM,KAAU,GACf,OAAO,cAMN,EAAO,SAEZ;SAAI,EAAO,gBAAgB,EAAO,aAAa,OAAO,GAAG;MACvD,IAAI,IAAgB;MAEpB,KAAK,IAAM,KAAO,EAAO,cAAc;OACrC,IAAM,IAAU,EAAI,SAAS;OAM7B,AAJI,EAAQ,SAAS,cAAc,EAAQ,QAAQ,KAC7C,EAAQ,UAAQ,EAAQ,OAAO,GAGjC,EAAO,eAAe,EAAO,YAAY,IAAI,CAAG,MAAM,EAAQ,YAChE,IAAgB;MAEpB;MAEA,IAAI,CAAC,KAAiB,EAAO,aAAa,OAAO,GAAG;OAClD,EAAO,UAAU;OACjB;MACF;KACF;KAEA,IAAI;MACF,EAAO,IAAI;KACb,SAAS,GAAK;MAIZ,KAAK,IAAI,IAAI,GAAO,KAAK,KAAK,iBAAiB,KAAK;OAClD,IAAM,IAAkB,KAAK,eAAe;OAC5C,IAAI,GAAiB;QACnB,KAAK,IAAM,KAAa,GACtB,EAAU,UAAU;QAEtB,EAAgB,MAAM;OACxB;MACF;MAEA,MADA,KAAK,kBAAkB,GACjB;KACR;IAnBA;IAuBF,IAAI,IAAQ,GAAG;KACb,IAAI,IAAoB;KACxB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAO,KACzB,IAAI,KAAK,eAAe,MAAM,KAAK,eAAe,EAAE,CAAE,OAAO,GAAG;MAC9D,IAAoB;MACpB;KACF;KAEF,AAAI,MAAmB,IAAQ;IACjC;GACF;GACA,KAAK,kBAAkB;EACzB,UAAU;GACR,KAAK,iBAAiB;EACxB;CACF;CAQA,IAAW,GAAa;EAClB,CAAC,KAAK,UAAU,CAAC,KAAK,OAAO,aACjC,EAAuB,WAAW,KAAK,QAAQ,GAAU,CAAM;CACjE;CAEA,OAAiB,GAAiB,GAAsD;EACtF,OAAO,EAAgB,MAAM,GAAc,CAAa;CAC1D;CAEA,SAAmB,GAAa,GAAkC;EAEhE,OAAO,EAAkB,MAAM,GAAI,CAAU;CAC/C;CAEA,OAAc,GAA4B,GAAiC;EACzE,OAAO,EAAa,MAAM,GAAI,CAAU;CAC1C;CAEA,QAAkB,GAAgB;EAChC,OAAO,EAAkB,MAAM,CAAE;CACnC;CAEA,SAAkC,GAAW,GAAkB;EAC7D,OAAO,EAAkB,MAAM,GAAQ,CAAI;CAC7C;CAEA,QAAkB,GAA0B,GAAsC;EAEhF,AADA,KAAK,WAAW,IAAI,GAAO,CAAc,GACzC,KAAK,QAAQ,OAAO,CAAK;CAC3B;CAEA,OAAiB,GAA6B;EAC5C,IAAI,KAAiC,MAAM,MAAU,MAAM,2BAA2B;EACtF,IAAI,KAAK,QAAQ,IAAI,CAAK,GAAG,OAAO,KAAK,QAAQ,IAAI,CAAK;EAC1D,IAAI,KAAK,oBAAoB,IAAI,CAAK,GAAG,MAAU,MAAM,uCAAuC;EAChG,KAAK,oBAAoB,IAAI,CAAK;EAClC,IAAI;GACF,IAAI,KAAK,WAAW,IAAI,CAAK,GAAG;IAC9B,IAAM,IAAQ,KAAK,WAAW,IAAI,CAAK;IACvC,IAAI,OAAO,KAAU,YAAY;KAC/B,IAAK,EAAc,iBAAiB;MAClC,IAAM,IAAW,EAAmB,IAAI;MAKxC,OAJI,MAAY,KAAA,IAIT,KAHL,KAAK,QAAQ,IAAI,GAAO,CAAO,GACxB;KAGX;KACA,IAAM,IAAM,EAAM,SAAS;KAE3B,IAAI,EADY,EAAI,WAAW,QAAQ,KAAK,EAAI,WAAW,QAAQ,KAAM,EAAM,aAAa,EAAM,UAAU,gBAAgB,KAAS,CAAC,EAAI,WAAW,UAAU,KAAK,CAAC,EAAI,SAAS,IAAI,IACxK;MACZ,IAAM,IAAY,EAAqB,IAAI;MAE3C,OADA,KAAK,QAAQ,IAAI,GAAO,CAAQ,GACzB;KACT;KACA,OAAO;IACT;IACA,OAAO;GACT;GACA,IAAI,OAAO,KAAU,YAAY;IAC/B,IAAM,IAAW,IAAK,EAAyB,IAAI;IAEnD,OADA,KAAK,QAAQ,IAAI,GAAO,CAAQ,GACzB;GACT;GACA,MAAU,MAAM,4BAA4B;EAC9C,UAAU;GACR,KAAK,oBAAoB,OAAO,CAAK;EACvC;CACF;CAEA,MAAa,GAAY,GAAoC;EAC3D,OAAO,IAAI,SAAS,GAAS,MAAW;GACtC,IAAI,EAAO,SACT,OAAO,EAAO,IAAI,aAAa,WAAW,YAAY,CAAC;GAGzD,IAAM,IAAY,iBAAiB;IAEjC,AADA,EAAO,oBAAoB,SAAS,CAAO,GAC3C,EAAQ;GACV,GAAG,CAAE,GAEC,UAAgB;IAEpB,AADA,aAAa,CAAS,GACtB,EAAO,IAAI,aAAa,WAAW,YAAY,CAAC;GAClD;GAEA,EAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC;EAC1D,CAAC;CACH;CAMA,SACE,GACA,GACA,GACa;EAEb,OAAO,EAAqB,MAAM,GAAS,GAAQ,CAAa;CAClE;AACF"}
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;t.initialData!==void 0&&n.set(`__initial__`,{data:t.initialData,savedTime:Date.now()});let a=e=>{try{return JSON.stringify(e)}catch{return String(e)}};return async(t,o)=>{let s=a(t),c=n.get(s)||n.get(`__initial__`),l=()=>c?i===void 0?!0:Date.now()-c.savedTime<i:!1;if(o.aborted){if(l()&&c)return c.data;throw o.reason||new DOMException(`The operation was aborted.`,`AbortError`)}try{let r=await e(t,o);return n.set(s,{data:r,savedTime:Date.now()}),n.delete(`__initial__`),r}catch(e){let t=e instanceof DOMException&&e.name===`AbortError`;if(l()&&c)return r&&console.warn(`[ReactiveEngine:Stale] Запрос для ключа ${s} ${t?`отменен`:`упал`}. Отдаем сохраненное состояние.`,e),c.data;throw e}}};exports.withStaleWhileRevalidate=e;
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 initialData?: T;\n isLogsEnabled?: boolean;\n /**\n * Время жизни кэша в миллисекундах (Time-To-Live).\n * Если текущий запрос упал ИЛИ был отменен, декоратор вернет `stale`-данные\n * для конкретного source только если они были получены в пределах этого интервала.\n */\n ttl?: number;\n}\n\n/**\n * Декоратор для сохранения предыдущего успешного состояния данных (Failover Cache) с поддержкой TTL.\n */\nexport const withStaleWhileRevalidate = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: StaleOptions<T> = {}\n) => {\n // Структурная мапа кэша: сериализованный JSON-строка ключа (source) -> { data: T, savedTime: number }\n const cacheMap = new Map<string, { data: T; savedTime: number }>()\n\n const isLogsEnabled = options.isLogsEnabled ?? false\n const ttl = options.ttl\n\n // Если переданы начальные данные initialData, заносим их под дефолтным пустым ключом\n if (options.initialData !== undefined) {\n cacheMap.set('__initial__', { data: options.initialData, savedTime: Date.now() })\n }\n\n // Внутренний хелпер для безопасной сериализации ключей (защита от круговых ссылок)\n const getCacheKey = (source: S): string => {\n try {\n return JSON.stringify(source)\n } catch (e) {\n return String(source)\n }\n }\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n const cacheKey = getCacheKey(source)\n\n // Ищем кэш конкретно под текущий сериализованный слепок параметров!\n let cachedRecord = cacheMap.get(cacheKey) || cacheMap.get('__initial__')\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 // Выполняем реальный сетевой запрос\n const freshData = await fetcher(source, signal)\n\n // Запоминаем успешный результат строго под его индивидуальным ключом параметров!\n cacheMap.set(cacheKey, { data: freshData, savedTime: Date.now() })\n\n // Удаляем временный initialData ключ после первого успешного наполнения реального кэша\n cacheMap.delete('__initial__')\n\n return freshData\n } catch (error) {\n const isAbort = error instanceof DOMException && error.name === 'AbortError'\n\n // Возвращаем старые данные ТОЛЬКО если они подходят под этот конкретный source по параметрам!\n if (isCacheValid() && cachedRecord) {\n if (isLogsEnabled) {\n console.warn(\n `[ReactiveEngine:Stale] Запрос для ключа ${cacheKey} ${isAbort ? 'отменен' : 'упал'}. Отдаем сохраненное состояние.`,\n error\n )\n }\n return cachedRecord.data\n }\n\n throw error\n }\n }\n}\n"],"mappings":"AAcA,IAAa,GACX,EACA,EAA2B,CAAC,IACzB,CAEH,IAAM,EAAW,IAAI,IAEf,EAAgB,EAAQ,eAAiB,GACzC,EAAM,EAAQ,IAGhB,EAAQ,cAAgB,IAAA,IAC1B,EAAS,IAAI,cAAe,CAAE,KAAM,EAAQ,YAAa,UAAW,KAAK,IAAI,CAAE,CAAC,EAIlF,IAAM,EAAe,GAAsB,CACzC,GAAI,CACF,OAAO,KAAK,UAAU,CAAM,CAC9B,MAAY,CACV,OAAO,OAAO,CAAM,CACtB,CACF,EAEA,OAAO,MAAO,EAAW,IAAoC,CAC3D,IAAM,EAAW,EAAY,CAAM,EAG/B,EAAe,EAAS,IAAI,CAAQ,GAAK,EAAS,IAAI,aAAa,EAEjE,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,CAEF,IAAM,EAAY,MAAM,EAAQ,EAAQ,CAAM,EAQ9C,OALA,EAAS,IAAI,EAAU,CAAE,KAAM,EAAW,UAAW,KAAK,IAAI,CAAE,CAAC,EAGjE,EAAS,OAAO,aAAa,EAEtB,CACT,OAAS,EAAO,CACd,IAAM,EAAU,aAAiB,cAAgB,EAAM,OAAS,aAGhE,GAAI,EAAa,GAAK,EAOpB,OANI,GACF,QAAQ,KACN,2CAA2C,EAAS,GAAG,EAAU,UAAY,OAAO,iCACpF,CACF,EAEK,EAAa,KAGtB,MAAM,CACR,CACF,CACF"}
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,UAAU,YAAY,CAAC,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,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,MAsBf,QAAQ,CAAC,EAAE,QAAQ,WAAW,KAAG,OAAO,CAAC,CAAC,CA6CzD,CAAA"}
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, o) => {
16
- let s = a(t), c = n.get(s) || n.get("__initial__"), l = () => c ? i === void 0 ? !0 : Date.now() - c.savedTime < i : !1;
17
- if (o.aborted) {
18
- if (l() && c) return c.data;
19
- throw o.reason || new DOMException("The operation was aborted.", "AbortError");
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 r = await e(t, o);
23
- return n.set(s, {
24
- data: r,
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
- }), n.delete("__initial__"), r;
27
+ }), i;
27
28
  } catch (e) {
28
- let t = e instanceof DOMException && e.name === "AbortError";
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 initialData?: T;\n isLogsEnabled?: boolean;\n /**\n * Время жизни кэша в миллисекундах (Time-To-Live).\n * Если текущий запрос упал ИЛИ был отменен, декоратор вернет `stale`-данные\n * для конкретного source только если они были получены в пределах этого интервала.\n */\n ttl?: number;\n}\n\n/**\n * Декоратор для сохранения предыдущего успешного состояния данных (Failover Cache) с поддержкой TTL.\n */\nexport const withStaleWhileRevalidate = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: StaleOptions<T> = {}\n) => {\n // Структурная мапа кэша: сериализованный JSON-строка ключа (source) -> { data: T, savedTime: number }\n const cacheMap = new Map<string, { data: T; savedTime: number }>()\n\n const isLogsEnabled = options.isLogsEnabled ?? false\n const ttl = options.ttl\n\n // Если переданы начальные данные initialData, заносим их под дефолтным пустым ключом\n if (options.initialData !== undefined) {\n cacheMap.set('__initial__', { data: options.initialData, savedTime: Date.now() })\n }\n\n // Внутренний хелпер для безопасной сериализации ключей (защита от круговых ссылок)\n const getCacheKey = (source: S): string => {\n try {\n return JSON.stringify(source)\n } catch (e) {\n return String(source)\n }\n }\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n const cacheKey = getCacheKey(source)\n\n // Ищем кэш конкретно под текущий сериализованный слепок параметров!\n let cachedRecord = cacheMap.get(cacheKey) || cacheMap.get('__initial__')\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 // Выполняем реальный сетевой запрос\n const freshData = await fetcher(source, signal)\n\n // Запоминаем успешный результат строго под его индивидуальным ключом параметров!\n cacheMap.set(cacheKey, { data: freshData, savedTime: Date.now() })\n\n // Удаляем временный initialData ключ после первого успешного наполнения реального кэша\n cacheMap.delete('__initial__')\n\n return freshData\n } catch (error) {\n const isAbort = error instanceof DOMException && error.name === 'AbortError'\n\n // Возвращаем старые данные ТОЛЬКО если они подходят под этот конкретный source по параметрам!\n if (isCacheValid() && cachedRecord) {\n if (isLogsEnabled) {\n console.warn(\n `[ReactiveEngine:Stale] Запрос для ключа ${cacheKey} ${isAbort ? 'отменен' : 'упал'}. Отдаем сохраненное состояние.`,\n error\n )\n }\n return cachedRecord.data\n }\n\n throw error\n }\n }\n}\n"],"mappings":";AAcA,IAAa,KACX,GACA,IAA2B,CAAC,MACzB;CAEH,IAAM,oBAAW,IAAI,IAA4C,GAE3D,IAAgB,EAAQ,iBAAiB,IACzC,IAAM,EAAQ;CAGpB,AAAI,EAAQ,gBAAgB,KAAA,KAC1B,EAAS,IAAI,eAAe;EAAE,MAAM,EAAQ;EAAa,WAAW,KAAK,IAAI;CAAE,CAAC;CAIlF,IAAM,KAAe,MAAsB;EACzC,IAAI;GACF,OAAO,KAAK,UAAU,CAAM;EAC9B,QAAY;GACV,OAAO,OAAO,CAAM;EACtB;CACF;CAEA,OAAO,OAAO,GAAW,MAAoC;EAC3D,IAAM,IAAW,EAAY,CAAM,GAG/B,IAAe,EAAS,IAAI,CAAQ,KAAK,EAAS,IAAI,aAAa,GAEjE,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;GAEF,IAAM,IAAY,MAAM,EAAQ,GAAQ,CAAM;GAQ9C,OALA,EAAS,IAAI,GAAU;IAAE,MAAM;IAAW,WAAW,KAAK,IAAI;GAAE,CAAC,GAGjE,EAAS,OAAO,aAAa,GAEtB;EACT,SAAS,GAAO;GACd,IAAM,IAAU,aAAiB,gBAAgB,EAAM,SAAS;GAGhE,IAAI,EAAa,KAAK,GAOpB,OANI,KACF,QAAQ,KACN,2CAA2C,EAAS,GAAG,IAAU,YAAY,OAAO,kCACpF,CACF,GAEK,EAAa;GAGtB,MAAM;EACR;CACF;AACF"}
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"}