@pravosleva/reactive-engine 1.3.2-beta → 1.4.1-beta

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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/angular/ReactiveEngine4Angular.cjs +1 -1
  3. package/dist/angular/ReactiveEngine4Angular.cjs.map +1 -1
  4. package/dist/angular/ReactiveEngine4Angular.d.ts +1 -0
  5. package/dist/angular/ReactiveEngine4Angular.d.ts.map +1 -1
  6. package/dist/angular/ReactiveEngine4Angular.mjs +4 -2
  7. package/dist/angular/ReactiveEngine4Angular.mjs.map +1 -1
  8. package/dist/angular/rxjs-interop.d.ts +21 -0
  9. package/dist/angular/rxjs-interop.d.ts.map +1 -0
  10. package/dist/core/core.d.ts +110 -0
  11. package/dist/core/core.d.ts.map +1 -1
  12. package/dist/core.cjs +1 -1
  13. package/dist/core.cjs.map +1 -1
  14. package/dist/core.mjs +178 -33
  15. package/dist/core.mjs.map +1 -1
  16. package/dist/decorators/index.d.ts +1 -0
  17. package/dist/decorators/index.d.ts.map +1 -1
  18. package/dist/decorators/withThrottleComputed/_withThrottleComputed.d.ts +22 -0
  19. package/dist/decorators/withThrottleComputed/_withThrottleComputed.d.ts.map +1 -0
  20. package/dist/decorators/withThrottleComputed/index.d.ts +2 -0
  21. package/dist/decorators/withThrottleComputed/index.d.ts.map +1 -0
  22. package/dist/decorators/withThrottleComputed/withThrottleComputed.d.ts +11 -0
  23. package/dist/decorators/withThrottleComputed/withThrottleComputed.d.ts.map +1 -0
  24. package/dist/decorators/withThrottleComputed/withThrottleComputed.test.d.ts +2 -0
  25. package/dist/decorators/withThrottleComputed/withThrottleComputed.test.d.ts.map +1 -0
  26. package/dist/react/ReactiveEngine4React.cjs +1 -1
  27. package/dist/react/ReactiveEngine4React.cjs.map +1 -1
  28. package/dist/react/ReactiveEngine4React.d.ts +1 -0
  29. package/dist/react/ReactiveEngine4React.d.ts.map +1 -1
  30. package/dist/react/ReactiveEngine4React.mjs +1 -0
  31. package/dist/react/ReactiveEngine4React.mjs.map +1 -1
  32. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.cjs +1 -1
  33. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.cjs.map +1 -1
  34. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.d.ts +5 -2
  35. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.d.ts.map +1 -1
  36. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.mjs +10 -6
  37. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.mjs.map +1 -1
  38. package/dist/utils/getExtractedValues.cjs +1 -1
  39. package/dist/utils/getExtractedValues.cjs.map +1 -1
  40. package/dist/utils/getExtractedValues.d.ts.map +1 -1
  41. package/dist/utils/getExtractedValues.mjs +4 -1
  42. package/dist/utils/getExtractedValues.mjs.map +1 -1
  43. package/dist/vue/ReactiveEngine4Vue.cjs.map +1 -1
  44. package/dist/vue/ReactiveEngine4Vue.d.ts +1 -0
  45. package/dist/vue/ReactiveEngine4Vue.d.ts.map +1 -1
  46. package/dist/vue/ReactiveEngine4Vue.mjs.map +1 -1
  47. package/package.json +1 -1
package/dist/core.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getExtractedValues as e } from "./utils/getExtractedValues.mjs";
2
2
  //#region src/core/core.ts
3
3
  var t = class {
4
+ frameworkPrefix = "core";
4
5
  activeEffect = null;
5
6
  isBatching = !1;
6
7
  pendingEffects = /* @__PURE__ */ new Set();
@@ -8,6 +9,87 @@ var t = class {
8
9
  factories = /* @__PURE__ */ new Map();
9
10
  proxyCache = /* @__PURE__ */ new WeakMap();
10
11
  allEffects = /* @__PURE__ */ new Set();
12
+ loggerOptions;
13
+ pendingLogQueue = [];
14
+ lastTransactionDuration = null;
15
+ signalMutationCounts = /* @__PURE__ */ new Map();
16
+ lastComputedDurations = /* @__PURE__ */ new Map();
17
+ effectExecutionCounts = /* @__PURE__ */ new Map();
18
+ batchTickCounts = 0;
19
+ constructor(e) {
20
+ this.loggerOptions = e?.logger;
21
+ }
22
+ log(t, n, r) {
23
+ if (!this.loggerOptions?.isEnabled || this.loggerOptions.filter && !(this.loggerOptions.filter instanceof RegExp ? this.loggerOptions.filter : new RegExp(this.loggerOptions.filter.replace(/^\/|\/$/g, ""))).test(n)) return;
24
+ let i = {
25
+ signal: "background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;",
26
+ computed: "background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;",
27
+ effect: "background: #e01e5a; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;",
28
+ batch: "background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;",
29
+ resource: "background: #d97706; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
30
+ }, a = "", o = "color: #aaa; font-weight: normal;";
31
+ switch (t) {
32
+ case "signal": {
33
+ let s = e({
34
+ tested: [n],
35
+ expectedKey: "CORE_INTERNAL_SIGNAL",
36
+ valueType: "number"
37
+ })?.[0] === "1", c = n, l = (this.signalMutationCounts.get(c) || 0) + 1;
38
+ this.signalMutationCounts.set(c, l);
39
+ let u = r, d = u.subscribers && u.subscribers.length > 0 ? u.subscribers.every((t) => t ? e({
40
+ tested: [t],
41
+ expectedKey: "IS_OPTIMIZED",
42
+ valueType: "number"
43
+ })?.[0] === "1" : !1) : !1, f = l > 50, p = s ? "COMPUTED_INTERNAL" : "SIGNAL";
44
+ a = ` | 🔄 Изменений ${s ? "кэша" : "сигнала"}: ${l}${f ? " ⚠️ (high noise — обнаружен дребезг/спам значений!)" : ""}`, f && (o = "color: orange; font-weight: bold;", d || (r.__performance_advice__ = {
45
+ issue: `Сигнал [${c}] обновляется слишком часто (${l} раз за такт). Это приводит к избыточным ререндерам UI.`,
46
+ solution: "Оберните чтение этого сигнала в декоратор сжатия потока данных 'withThrottleComputed'",
47
+ example: "public throttled = withThrottleComputed(this.engine, () => this.yourSignalName.value, { limit: 300 });"
48
+ })), console.groupCollapsed(`%c${p}%c [${c}]%c${a}`, s ? "background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;" : i[t], "color: #aaa; font-weight: bold;", o), console.log("Данные (Payload):", r), this.loggerOptions.traceTime && typeof performance < "u" && console.log("Тайминг рантайма:", `${performance.now().toFixed(2)} ms`), console.groupEnd();
49
+ return;
50
+ }
51
+ case "computed": {
52
+ let e = r;
53
+ if (e && "to" in e) {
54
+ let t = e.to;
55
+ a = ` | 🧮 Значение: ${typeof t == "object" && t ? "{...}" : String(t)}`;
56
+ } else if (e && "duration" in e) {
57
+ let t = parseFloat(e.duration);
58
+ if (!isNaN(t)) {
59
+ let r = this.lastComputedDurations.get(n);
60
+ if (r !== void 0 && r > 0) {
61
+ let n = (t - r) / r * 100;
62
+ n > 5 ? (a = ` | 🔴 Замедление: +${n.toFixed(1)}% (${e.duration})`, o = "color: #e01e5a; font-weight: bold;") : n < -5 ? (a = ` | 🟢 Ускорение: ${n.toFixed(1)}% (${e.duration})`, o = "color: #42b883; font-weight: bold;") : (a = ` | 🟢 Стабильно (${e.duration})`, o = "color: #42b883; font-weight: normal;");
63
+ } else a = ` | ⚪ Первый расчет (${e.duration})`;
64
+ this.lastComputedDurations.set(n, t);
65
+ }
66
+ }
67
+ break;
68
+ }
69
+ case "effect": {
70
+ let e = (this.effectExecutionCounts.get(n) || 0) + 1;
71
+ this.effectExecutionCounts.set(n, e);
72
+ let t = e > 30;
73
+ a = ` | 🚀 Вызовов эффекта: ${e}${t ? " ⚠️ (heavy re-renders)" : ""}`, t && (o = "color: #ff4a4a; font-weight: bold;");
74
+ break;
75
+ }
76
+ case "batch":
77
+ this.batchTickCounts += 1, a = ` | 📦 Номер транзакции: #${this.batchTickCounts}`;
78
+ break;
79
+ case "resource": {
80
+ let e = r;
81
+ if (e.loading) a = " | ⏳ ЗАГРУЗКА (fetching...)", o = "color: #d97706; font-weight: bold;";
82
+ else if (e.error) a = ` | 🔴 ОШИБКА: ${e.error.message || "Unknown Error"}`, o = "color: #ff4a4a; font-weight: bold;";
83
+ else {
84
+ let t = e.data;
85
+ a = ` | 🟢 УСПЕХ (data: ${typeof t == "object" && t ? "{...}" : String(t)})`, o = "color: #42b883; font-weight: bold;";
86
+ }
87
+ break;
88
+ }
89
+ default: break;
90
+ }
91
+ console.groupCollapsed(`%c${t.toUpperCase()}%c [${n}]%c${a}`, i[t], "color: #aaa; font-weight: bold;", o), console.log("Данные (Payload):", r), this.loggerOptions.traceTime && typeof performance < "u" && console.log("Тайминг рантайма:", `${performance.now().toFixed(2)} ms`), console.groupEnd();
92
+ }
11
93
  onSignalChange;
12
94
  provide(e, t) {
13
95
  typeof t == "function" && !t.prototype ? this.factories.set(e, t) : this.services.set(e, t);
@@ -31,11 +113,56 @@ var t = class {
31
113
  throw Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message || "No e?.message"}`);
32
114
  }
33
115
  }
116
+ queueLog(e, t, n) {
117
+ this.loggerOptions?.isEnabled && this.pendingLogQueue.push({
118
+ type: e,
119
+ name: t,
120
+ detail: n
121
+ });
122
+ }
123
+ flushLogs() {
124
+ if (!this.loggerOptions?.isEnabled || this.pendingLogQueue.length === 0) return;
125
+ let e = typeof performance < "u" ? performance.now() : 0, t = this.pendingLogQueue;
126
+ if (this.loggerOptions.filter) try {
127
+ let e;
128
+ if (this.loggerOptions.filter instanceof RegExp) e = this.loggerOptions.filter;
129
+ else {
130
+ let t = this.loggerOptions.filter.replace(/^\/|\/$/g, "");
131
+ e = new RegExp(t);
132
+ }
133
+ t = t.filter((t) => e.test(t.name));
134
+ } catch (e) {
135
+ console.warn("[Logger Error]: Некорректный паттерн фильтрации логов", e);
136
+ }
137
+ if (t.length === 0) {
138
+ this.pendingLogQueue = [];
139
+ return;
140
+ }
141
+ let n = "", r = "", i = "color: #aaa; font-weight: normal;";
142
+ if (this.loggerOptions.traceTime && e) {
143
+ let t = performance.now() - e;
144
+ if (n = ` | Duration: ${t.toFixed(3)} ms`, this.lastTransactionDuration !== null && this.lastTransactionDuration > 0) {
145
+ let e = (t - this.lastTransactionDuration) / this.lastTransactionDuration * 100;
146
+ e > .1 ? (r = ` 🔴 +${e.toFixed(1)}%`, i = "color: #e01e5a; font-weight: bold;") : e < -.1 ? (r = ` 🟢 ${e.toFixed(1)}%`, i = "color: #42b883; font-weight: bold;") : (r = " 🟢 stable", i = "color: #42b883; font-weight: bold;");
147
+ } else r = " ⚪ first";
148
+ this.lastTransactionDuration = t;
149
+ }
150
+ console.groupCollapsed(`%cREACTIVE TRANSACTION%c Microtask Tick (Size: ${t.length}${n})%c${r}`, "background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;", "color: #aaa; font-weight: normal;", i), t.forEach((e) => {
151
+ this.log(e.type, e.name, e.detail);
152
+ }), this.loggerOptions.traceTime && typeof performance < "u" && console.log(`%c[Execution Timestamp]: ${performance.now().toFixed(2)} ms`, "color: #7952b3; font-weight: bold;"), console.groupEnd(), this.pendingLogQueue = [];
153
+ }
34
154
  signal(e, t) {
35
155
  let n = this, r = e, i = /* @__PURE__ */ new Set(), a = typeof t == "string" ? { name: t } : t || {}, o = a.name || "unnamed_signal";
36
156
  return {
37
157
  get value() {
38
- return n.activeEffect && i.add(n.activeEffect), r;
158
+ if (n.activeEffect) {
159
+ i.add(n.activeEffect);
160
+ let e = n.activeEffect;
161
+ e.cleanups.add(() => {
162
+ i.delete(e);
163
+ });
164
+ }
165
+ return r;
39
166
  },
40
167
  set value(e) {
41
168
  if (r === e) return;
@@ -51,15 +178,22 @@ var t = class {
51
178
  }
52
179
  }
53
180
  let t = r;
54
- r = e, n.onSignalChange?.(o, e, t), i.forEach((e) => n.pendingEffects.add(e)), n.isBatching || (n.isBatching = !0, queueMicrotask(() => {
55
- let e = Array.from(n.pendingEffects);
56
- n.pendingEffects.clear(), n.isBatching = !1, e.forEach((e) => e.run());
181
+ r = e, n.onSignalChange?.(o, e, t);
182
+ let s = Array.from(i).map((e) => e.label || "unnamed_effect");
183
+ n.loggerOptions?.isEnabled && (/* @__PURE__ */ Error()).stack, n.queueLog?.("signal", o, {
184
+ from: t,
185
+ to: e,
186
+ subscribersCount: i.size,
187
+ subscribers: s
188
+ }), i.forEach((e) => n.pendingEffects.add(e)), n.isBatching || (n.isBatching = !0, queueMicrotask(() => {
189
+ for (let e of n.pendingEffects) n.pendingEffects.delete(e), e.run();
190
+ n.isBatching = !1, n.flushLogs?.();
57
191
  }));
58
192
  },
59
193
  subscribe(e) {
60
194
  return n.effect(() => {
61
195
  e(this.value);
62
- });
196
+ }, `${n.frameworkPrefix}:use:${o}`);
63
197
  }
64
198
  };
65
199
  }
@@ -89,21 +223,25 @@ var t = class {
89
223
  let t = this.computedCache.get(e)?.deref();
90
224
  if (t) return t;
91
225
  }
92
- let n = this, r = this.signal(void 0, t || "unnamed_computed"), i = this.effect(() => {
93
- let t = e();
94
- r.value = t;
95
- }), a = Array.from(this.allEffects)[this.allEffects.size - 1], o = () => {
96
- i(), a && n.allEffects.delete(a), n.computedCache.delete(e);
97
- }, s = {
226
+ let n = this, r = t || "unnamed_computed", i = this.signal(void 0, `[CORE_INTERNAL_SIGNAL=1]:${r}`), a = !0, o, s = this.effect(() => {
227
+ o = e(), a = !1;
228
+ let t = typeof performance < "u" && performance.now() ? "0.010ms" : "N/A";
229
+ typeof n.queueLog == "function" && n.queueLog("computed", r, {
230
+ value: o,
231
+ duration: t
232
+ }), i.value = o;
233
+ }, `[IS_OPTIMIZED=1]:${r}`), c = Array.from(this.allEffects)[this.allEffects.size - 1], l = () => {
234
+ s(), c && n.allEffects.delete(c), n.computedCache.delete(e);
235
+ }, u = {
98
236
  get value() {
99
- return r.value;
237
+ return a && (o = e(), a = !1, i.value = o), i.value;
100
238
  },
101
- subscribe: (e) => r.subscribe(e),
239
+ subscribe: (e) => i.subscribe(e),
102
240
  destroy() {
103
- o();
241
+ l();
104
242
  }
105
243
  };
106
- return this.cleanupRegistry.register(s, o), this.computedCache.set(e, new WeakRef(s)), s;
244
+ return this.cleanupRegistry.register(u, l), this.computedCache.set(e, new WeakRef(u)), u;
107
245
  }
108
246
  reactive(e, t = "reactive") {
109
247
  if (this.proxyCache.has(e)) return this.proxyCache.get(e);
@@ -140,29 +278,36 @@ var t = class {
140
278
  loading: !0,
141
279
  error: null,
142
280
  isRetrying: !1
143
- }, a), g = async (n, r, i = !1) => {
281
+ }, `[CORE_INTERNAL_SIGNAL=1]:resource:state:${a}`), g = (e) => {
282
+ h.value = e, typeof this.queueLog == "function" && this.queueLog("resource", a, {
283
+ loading: e.loading,
284
+ data: e.data,
285
+ error: e.error,
286
+ isRetrying: e.isRetrying
287
+ });
288
+ }, _ = async (n, r, i = !1) => {
144
289
  if (r.aborted) return;
145
290
  if (l) {
146
291
  let e = l(n);
147
292
  if (e === !1 || typeof e == "string") {
148
293
  if (r.aborted) return;
149
294
  let t = typeof e == "string" ? e : "Pre-fetch validation failed for resource", n = i ? null : this.untrack(() => h.value.data);
150
- h.value = {
295
+ g({
151
296
  data: n,
152
297
  loading: !1,
153
298
  error: Error(t),
154
299
  isRetrying: !1
155
- };
300
+ });
156
301
  return;
157
302
  }
158
303
  }
159
304
  let o = i && s ? null : this.untrack(() => h.value.data);
160
- h.value = {
305
+ g({
161
306
  data: o,
162
307
  loading: !0,
163
308
  error: null,
164
309
  isRetrying: !1
165
- };
310
+ });
166
311
  for (let i = 0; i <= u; i++) {
167
312
  if (r.aborted) return;
168
313
  let o = r;
@@ -178,22 +323,22 @@ var t = class {
178
323
  let t = c(e);
179
324
  if (t === !1 || typeof t == "string") {
180
325
  if (r.aborted) return;
181
- h.value = {
326
+ g({
182
327
  data: null,
183
328
  loading: !1,
184
329
  error: Error(typeof t == "string" ? t : "Validation failed"),
185
330
  isRetrying: !1
186
- };
331
+ });
187
332
  return;
188
333
  }
189
334
  }
190
335
  if (r.aborted) return;
191
- h.value = {
336
+ g({
192
337
  data: e,
193
338
  loading: !1,
194
339
  error: null,
195
340
  isRetrying: !1
196
- };
341
+ });
197
342
  return;
198
343
  } catch (t) {
199
344
  if (r.aborted || t.name === "AbortError" && r.aborted) return;
@@ -209,24 +354,24 @@ var t = class {
209
354
  if (i === u || c) {
210
355
  if (r.aborted) return;
211
356
  let e = n ? Error(`Request timed out after ${m}ms`, { cause: t }) : o ? Error(s, { cause: t }) : t;
212
- h.value = {
357
+ g({
213
358
  data: null,
214
359
  loading: !1,
215
360
  error: e,
216
361
  isRetrying: !1
217
- };
362
+ });
218
363
  return;
219
364
  } else {
220
365
  if (r.aborted) return;
221
366
  let e = f ? d * 2 ** i : d;
222
367
  e = Math.min(e, p);
223
368
  let t = Math.random() * 200;
224
- e += t, h.value = {
369
+ e += t, g({
225
370
  data: h.value.data,
226
371
  loading: !0,
227
372
  error: null,
228
373
  isRetrying: !0
229
- }, console.warn(`[Resource Retry] "${a}" сбой по ${n ? "таймауту" : "ошибке сети"}. Попытка ${i + 1}/${u + 1}...`);
374
+ }), console.warn(`[Resource Retry] "${a}" сбой по ${n ? "таймауту" : "ошибке сети"}. Попытка ${i + 1}/${u + 1}...`);
230
375
  try {
231
376
  if (await this.delay(e, r), r.aborted) return;
232
377
  } catch {
@@ -235,13 +380,13 @@ var t = class {
235
380
  }
236
381
  }
237
382
  }
238
- }, _ = null;
383
+ }, v = null;
239
384
  return this.effect(() => {
240
385
  let e = n ? n.value : void 0, t = new AbortController();
241
- return _ = t, this.untrack(() => {
242
- g(e, t.signal, !0);
386
+ return v = t, this.untrack(() => {
387
+ _(e, t.signal, !0);
243
388
  }), () => t.abort();
244
- }), {
389
+ }, "[dev]"), {
245
390
  get data() {
246
391
  return h.value.data;
247
392
  },
@@ -258,7 +403,7 @@ var t = class {
258
403
  return h.value;
259
404
  },
260
405
  refetch: () => {
261
- _?.abort(), _ = new AbortController(), g(n ? n.value : void 0, _.signal, !1);
406
+ v?.abort(), v = new AbortController(), _(n ? n.value : void 0, v.signal, !1);
262
407
  },
263
408
  subscribe: (e) => h.subscribe(e)
264
409
  };
package/dist/core.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.mjs","names":[],"sources":["../src/core/core.ts"],"sourcesContent":["import { ResourceOptions, ResourceState, SignalOptions } from './types';\nimport { getExtractedValues } from '../utils'\n\n/**\n * Интерфейс для сигнала.\n * @template T\n * @interface Signal<T>\n */\nexport interface Signal<T> {\n /**\n * Значение сигнала.\n * @type {T}\n */\n value: T;\n\n /**\n * Подписка на изменение значения сигнала.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport type CleanupFn = () => void;\nexport type EffectFn = () => CleanupFn | void;\nexport type Token<T> = string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T };\nexport type Factory<T> = (engine: ReactiveEngine) => T;\n\n/**\n * Интерфейс для вычисляемого значения.\n * @template T\n * @interface Computed<T>\n */\nexport interface Computed<T> {\n /**\n * Только читаемое значение вычисляемого значения.\n * @type {T}\n */\n readonly value: T;\n\n /**\n * Подписка на изменение вычисляемого значения.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n\n /**\n * Принудительное уничтожение вычисляемого значения и его эффекта для предотвращения утечек памяти.\n */\n destroy: () => void;\n}\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для ресурса.\n * @template T\n * @interface Resource<T>\n */\nexport interface Resource<T> extends ResourceState<T> {\n /**\n * Перезагрузка ресурса.\n * @function refetch\n * @returns {void}\n */\n refetch: () => void;\n\n /**\n * Подписка на изменение состояния ресурса.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;\n\n /**\n * Только читаемое состояние ресурса.\n * @type {ResourceState<T>}\n */\n readonly value: ResourceState<T>;\n}\n\n// --- ЯДРО ---\n/**\n * Класс `ReactiveEngine` представляет собой реактивную систему, которая позволяет создавать и управлять реактивными объектами,\n * сигналами, эффектами, асинхронными ресурсами и другими реактивными примитивами. Реактивные механизмы облегчают разработку\n * сложных пользовательских интерфейсов и приложений, автоматически отслеживая зависимости между данными и реактивно обновляя UI.\n *\n * Основные возможности `ReactiveEngine` включают:\n * - **Сигналы (Signals)**: Реактивные переменные с поддержкой подписки на изменения.\n * - **Эффекты (Effects)**: Автоматические функции, которые выполняются при изменении зависимых сигналов.\n * - **Ресурсы (Resources)**: Асинхронные ресурсы с поддержкой повторных попыток и валидации.\n * - **Прокси-объекты (Proxy Objects)**: Реактивное обертывание объектов для отслеживания изменений свойств.\n * - **Интеграция с React**: Удобные методы для интеграции реактивного ядра с компонентами React.\n *\n * Пример использования:\n *\n * ```javascript\n * const engine = new ReactiveEngine();\n * const count = engine.signal(0);\n *\n * engine.effect(() => {\n * console.log(`Count is ${count.value}`);\n * });\n *\n * count.value++; // Выведет: Count is 1\n * ```\n *\n * @class\n */\nexport class ReactiveEngine {\n private activeEffect: IEffect | null = null;\n private isBatching = false;\n private pendingEffects = new Set<IEffect>();\n\n // В контейнерах DI вместо any используем unknown. Это заставит методы inject/provide\n // явно приводить типы через дженерики <T>, защищая от рантайм-ошибок.\n private services = new Map<Token<unknown>, unknown>();\n private factories = new Map<Token<unknown>, Factory<unknown>>();\n\n // Объект-ключ мапится на объект-прокси. Здесь идеально подходит тип object.\n private proxyCache = new WeakMap<object, object>();\n private allEffects = new Set<IEffect>();\n\n /**\n * Коллбек для уведомления об изменении сигнала.\n * Использование unknown вместо any гарантирует безопасную работу с типами prev/next.\n */\n public onSignalChange?: (name: string, next: unknown, prev: unknown) => void;\n\n /**\n * DI: Регистрация зависимости.\n * @template T\n * @function provide\n * @param {Token<T>} token - Токен для зависимости.\n * @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.\n * @returns {void}\n * @source\n */\n public provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void {\n if (typeof valueOrFactory === 'function' && !valueOrFactory.prototype) {\n this.factories.set(token, valueOrFactory as Factory<T>);\n } else {\n this.services.set(token, valueOrFactory);\n }\n }\n\n /**\n * DI: Инъекция; Получение сервиса по токену.\n * @template T\n * @function inject\n * @param {Token<T>} token - Токен для зависимости.\n * @returns {T} - Сервис.\n * @source\n */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n // Приводим токен к базовому типу Token<unknown> для совместимости с Map\n const targetToken = token as Token<unknown>;\n\n if (this.services.has(targetToken)) {\n return this.services.get(targetToken) as T;\n }\n\n try {\n const factory = this.factories.get(targetToken);\n if (factory) {\n const instance = factory(this) as T;\n this.services.set(targetToken, instance);\n return instance;\n }\n if (typeof token === 'function' && token.prototype) {\n const instance = new (token as { new(eng: ReactiveEngine): T })(this);\n this.services.set(targetToken, instance);\n return instance;\n }\n throw new Error(`Service not found: ${String(token)}`);\n } catch (e) {\n throw new Error(`[DI Error]: Не удалось создать сервис ${String(token)}. Ошибка: ${(e as Error)?.message || 'No e?.message'}`);\n }\n }\n\n /**\n * Создание сигнала.\n * @template T\n * @function signal\n * @param {T} initialValue - Начальное значение сигнала.\n * @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.\n * @returns {Signal<T>} - Сигнал.\n * @source\n */\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n const engine = this;\n let val = initialValue;\n const subscribers = new Set<IEffect>();\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n const name = options.name || 'unnamed_signal';\n\n return {\n get value(): T {\n if (engine.activeEffect) subscribers.add(engine.activeEffect);\n return val;\n },\n set value(newValue: T) {\n if (val === newValue) return;\n\n // ВАЛИДАЦИЯ В RUNTIME\n if (options.validate) {\n const result = options.validate(newValue);\n if (result === false || typeof result === 'string') {\n const errorMsg = typeof result === 'string'\n ? result\n : `[Validation Error]: Некорректное значение для сигнала \"${name}\"`;\n\n console.error(`%c${errorMsg}`, \"color: orange; font-weight: bold;\", {\n received: newValue,\n current: val\n });\n return; // Прерываем обновление, если данные не валидны\n }\n }\n\n const old = val;\n val = newValue;\n engine.onSignalChange?.(name, newValue, old);\n\n // 1. Всегда добавляем подписчиков в очередь отложенных эффектов\n subscribers.forEach(e => engine.pendingEffects.add(e));\n\n // 2. Если очередь еще не запущена, планируем её автоматическое выполнение в микрозадачу\n if (!engine.isBatching) {\n engine.isBatching = true;\n\n queueMicrotask(() => {\n const effects = Array.from(engine.pendingEffects);\n engine.pendingEffects.clear();\n engine.isBatching = false;\n\n // Запускаем все накопившиеся эффекты и ререндеры за один раз\n effects.forEach(e => e.run());\n });\n }\n },\n subscribe(cb: (val: T) => void) {\n // Чистая и безопасная подписка для React:\n // Мы вызываем cb, но НЕ передаем туда аргументы, чтобы не ломать useSyncExternalStore\n return engine.effect(() => {\n cb(this.value);\n });\n }\n };\n }\n\n /**\n * Создание эффекта.\n * @function effect\n * @param {EffectFn} fn - Функция эффекта.\n * @param {string} [label] - Необязательная метка для логирования и отладки.\n * @returns {CleanupFn} - Функция для очистки эффекта.\n * @source\n */\n public effect(fn: EffectFn, label?: string): CleanupFn {\n const engine = this;\n const effectObj: IEffect = {\n label, // Запоминаем имя эффекта для профайлера/логов\n cleanups: new Set(),\n run() {\n this.cleanups.forEach(c => c());\n this.cleanups.clear();\n const prev = engine.activeEffect;\n engine.activeEffect = this;\n engine.safeRun(this, () => {\n const cleanup = fn();\n if (typeof cleanup === 'function') this.cleanups.add(cleanup);\n });\n engine.activeEffect = prev;\n }\n };\n this.allEffects.add(effectObj);\n effectObj.run();\n\n return () => {\n effectObj.cleanups.forEach(c => c());\n engine.pendingEffects.delete(effectObj);\n engine.allEffects.delete(effectObj); // Всегда чистим за собой\n };\n }\n\n // Кэш для вычисляемых свойств: Вместо Map<Function, any> используем строгий интерфейс с unknown дженериком\n private computedCache = new Map<Function, WeakRef<Computed<unknown>>>();\n\n // 1. Создаем реестр финализации под капотом движка.\n // В качестве токена очистки передаем функцию, которую нужно выполнить.\n private cleanupRegistry = new FinalizationRegistry<() => void>(\n (cleanupFn) => {\n cleanupFn(); // Вызовется автоматически, когда сборщик мусора удалит computedInstance\n }\n );\n\n /**\n * Создание вычисляемого значения.\n * @template T\n * @function computed\n * @param {Function} fn - Функция для вычисления значения.\n * @param {string} [signalName] - Имя сигнала.\n * @returns {Computed<T>} - Вычисляемое значение с методом destroy.\n * @source\n */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n // 1. Проверяем кэш. Если WeakRef существует и объект внутри него еще не удален GC:\n if (this.computedCache.has(fn)) {\n const cachedRef = this.computedCache.get(fn);\n const cachedInstance = cachedRef?.deref();\n if (cachedInstance) {\n return cachedInstance as Computed<T>;\n }\n }\n\n const engine = this;\n const sig = this.signal<T>(undefined as unknown as T, signalName || 'unnamed_computed');\n\n const unsubscribeEffect = this.effect(() => {\n const newValue = fn();\n sig.value = newValue;\n });\n\n const effectObj = Array.from(this.allEffects)[this.allEffects.size - 1];\n\n const performCleanup = () => {\n unsubscribeEffect();\n if (effectObj) {\n engine.allEffects.delete(effectObj);\n }\n engine.computedCache.delete(fn);\n };\n\n // Создаем инстанс computed\n const computedInstance: Computed<T> = {\n get value() { return sig.value; },\n subscribe: (cb: (val: T) => void) => sig.subscribe(cb),\n destroy() {\n performCleanup();\n }\n };\n\n this.cleanupRegistry.register(computedInstance, performCleanup);\n\n // 2. Сохраняем в кэш СЛАБУЮ ССЫЛКУ (WeakRef) на наш объект.\n // Теперь этот кэш больше НЕ будет удерживать объект в памяти насильно!\n this.computedCache.set(fn, new WeakRef(computedInstance as Computed<unknown>));\n\n return computedInstance;\n }\n\n /**\n * Создание реактивного объекта (Proxy).\n * @template T\n * @function reactive\n * @param {T} target - Целевой объект для проксирования.\n * @param {string} [name] - Имя проксируемого объекта.\n * @returns {T} - Реактивный объект.\n * @source\n */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) {\n return this.proxyCache.get(target) as T;\n }\n const engine = this;\n const propsSubscribers = new Map<string | symbol, Set<IEffect>>();\n\n const proxy = new Proxy(target, {\n get(obj, prop, receiver) {\n if (engine.activeEffect) {\n if (!propsSubscribers.has(prop)) propsSubscribers.set(prop, new Set());\n propsSubscribers.get(prop)!.add(engine.activeEffect);\n }\n const value = Reflect.get(obj, prop, receiver);\n return (value !== null && typeof value === 'object')\n ? engine.reactive(value, `${name}.${String(prop)}`)\n : value;\n },\n set(obj, prop, value, receiver) {\n const old = Reflect.get(obj, prop, receiver);\n // ПРОВЕРКА НА ИДЕНТИЧНОСТЬ\n if (old === value) return true;\n\n if (old !== value) {\n Reflect.set(obj, prop, value, receiver);\n engine.onSignalChange?.(`${name}.${String(prop)}`, value, old);\n propsSubscribers.get(prop)?.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n );\n }\n return true;\n }\n });\n this.proxyCache.set(target, proxy);\n return proxy;\n }\n\n /**\n * Группировка изменений. (Оставил для обратной совместимости)\n * @function batch\n * @param {Function} fn - Функция для выполнения в группе.\n * @returns {void}\n * @source\n */\n public batch(fn: () => void): void {\n // Наш асинхронный сеттер теперь сам выполняет всю работу в queueMicrotask,\n // поэтому здесь мы можем просто выполнить функцию\n fn();\n }\n\n /**\n * Вспомогательная функция для задержки (sleep), чувствительная к AbortSignal\n *\n * Вызов new DOMException('Aborted', 'AbortError') гарантирует,\n * что ваша кастомная пауза между ретраями this.delay притворяется для движка JavaScript\n * точно таким же нативным процессом отмены, как и fetch().\n * Это делает реактивное ядро бесшовным и избавляет от необходимости\n * писать кучу разных проверок под каждый тип ошибки.\n */\n private 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); // СИНХРОННО УБИВАЕТ ТАЙМЕР. В Vitest это заставит таймер исчезнуть из очереди прокрутки\n reject(new DOMException('Aborted', 'AbortError'));\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n }\n\n /**\n * Создание асинхронного ресурса. Типы на входе: <T - формат ответа, S - источник изменений (сигнал либо computed-кортеж из зачений сигналов через конструкцию `.value`)>\n *\n * Explained 👉 {@link https://github.com/garage-13/reactive-engine/blob/main/README_EN.md#1-async-resources-dependent-on-multiple-signals}\n *\n * @template T, S\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/200-resource/Example200.tsx Реализация базового компонента Example200}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/201-multi-resource/service.secondary.ts Сложный пример зависимости ресурсов через computed}\n * @function resource\n * @param {Function} fetcher - Асинхронная функция для загрузки данных.\n * @param {{ value: S }} [source] - Источник данных.\n * @param {string | ResourceOptions<T>} [optionsOrName] - Имя сигнала или объект конфигурации `{ name: string; validate: (res) => boolean | string; resetDataOnSourceChange?: boolean }`.\n * @returns {Resource<T>} - Асинхронный ресурс.\n *\n * @example\n * // Базовый вызов со строкой (обратная совместимость):\n * const res = engine.resource(fetcher, source, 'my-resource-name');\n *\n * @example\n * // Современный вызов с валидацией ответа:\n * const res = engine.resource(fetcher, source, {\n * name: 'my-resource-name',\n * resetDataOnSourceChange: true, // true by default\n * responseValidate: (data) => !!data || 'Данные пусты', // Проверяйте формат в соотв. с дженериком\n * });\n *\n * @example\n * // (Экспериментальная фича)\n * // В теле fetcher можно выкинуть ошибку в виде\n * // `throw new Error('[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1][MESSAGE=Your msg]')`\n * // Причина: Особенности внутренней реализации определения харакрера ошибки и необходимость делать retry (если такая опция передана)\n * // Рекомендуем вместо этого использовать `validateBeforeFetch` в опциях при создании ресурса.\n * const res = engine.resource(\n * async (counterValue, abortSignal) => {\n * if (counterValue === 0)\n * throw new Error([\n * '[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1]', // Иначе запустится механизм retry (напр. по причине отсутствия сети, далее в этом же примере)\n * `[MESSAGE=Stop for count value ${counterValue} - excepted from fetcher fn body]`,\n * ].join(' '))\n * const res = await fetch(\n * [\n * `${BASE_API_URL}/profile/search`,\n * '?',\n * [\n * `counter=${counterValue}`,\n * '_responseDelay=2000',\n * ].join('&')\n * ].join(''),\n * { signal: abortSignal }\n * )\n * if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)\n * return res.json()\n * },\n * this.counter,\n * )\n * @source\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 const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>).name\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\n const retryCount = options.retryCount ?? 0;\n const baseDelay = options.retryDelay ?? 1000;\n const useExponential = options.isExponentialBackoffEnabled ?? false;\n const maxRetryDelay = options.maxRetryDelay ?? 30000;\n const timeoutMs = options.timeout;\n\n const state = this.signal<ResourceState<T>>(\n { data: null, loading: true, error: null, isRetrying: false },\n signalName\n );\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return;\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 // При ручном refetch (не смена source) сохраняем старые данные, а не зануляем\n const currentData = isSourceChange ? null : this.untrack(() => state.value.data);\n state.value = { data: currentData, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n const shouldClear = isSourceChange && resetDataOnSourceChange;\n const currentData = shouldClear ? null : this.untrack(() => state.value.data);\n\n state.value = { data: currentData, loading: true, error: null, isRetrying: false };\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return;\n let combinedSignal = effectSignal;\n\n try {\n if (timeoutMs && timeoutMs > 0) {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);\n }\n\n const data = await fetcher(sValue, combinedSignal);\n\n if (effectSignal.aborted) return;\n\n if (combinedSignal.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 state.value = { data: null, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n if (effectSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n } catch (e: any) {\n // ЖЕСТКИЙ ВЫХОД: Если внешняя область (эффект или новый refetch) сделала abort,\n // мы НЕМЕДЛЕННО прекращаем выполнение и ничего не пишем в стейт.\n if (effectSignal.aborted || (e.name === 'AbortError' && effectSignal.aborted)) return;\n\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n const isCustomValidationError = getExtractedValues({\n tested: [e.message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n\n const __defaultCustomValidationErrorMessage = 'Custom validation error';\n let customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [e.message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage;\n\n const isFetchBodyValidationError = isCustomValidationError || e.name === 'ValidationError';\n\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return;\n\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 state.value = { data: null, loading: false, error: finalError, isRetrying: false };\n return;\n } else {\n if (effectSignal.aborted) return;\n\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\n // Безопасно обновляем статус ретрая, только если сигнал живой\n state.value = { data: state.value.data, loading: true, error: null, isRetrying: true };\n\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети';\n console.warn(`[Resource Retry] \"${signalName}\" сбой по ${logReason}. Попытка ${attempt + 1}/${retryCount + 1}...`);\n\n try {\n await this.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 this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n const effectController = new AbortController();\n activeEffectController = effectController;\n\n this.untrack(() => {\n load(sValue as S, effectController.signal, true);\n });\n\n return () => effectController.abort();\n });\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 };\n }\n\n /**\n * Выполняет функцию без отслеживания зависимостей.\n * @template T\n * @function untrack\n * @param {Function} fn - Функция для выполнения.\n * @returns {T} - Результат выполнения функции.\n * @source\n */\n public untrack<T>(fn: () => T): T {\n const prev = this.activeEffect;\n this.activeEffect = null; // Временно \"забываем\" про активный эффект\n try {\n return fn();\n } finally {\n this.activeEffect = prev; // Возвращаем эффект на место\n }\n }\n\n /**\n * Безопасное выполнение функции.\n * @private\n * @function safeRun\n * @param {IEffect} effect - Эффект.\n * @param {Function} fn - Функция для выполнения.\n * @returns {void}\n * @source\n */\n private safeRun(effect: IEffect, fn: () => void) {\n try {\n fn();\n } catch (error) {\n console.error(\n `%c[Reactive Error] %cОшибка в эффекте/computed:`,\n \"color: white; background: red; padding: 2px 4px; border-radius: 3px;\",\n \"font-weight: bold;\",\n error\n );\n // Здесь можно отправить ошибку в Sentry или другой сервис мониторинга\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"core.mjs","names":[],"sources":["../src/core/core.ts"],"sourcesContent":["import { ResourceOptions, ResourceState, SignalOptions } from './types';\nimport { getExtractedValues } from '../utils'\n\n/**\n * Интерфейс для сигнала.\n * @template T\n * @interface Signal<T>\n */\nexport interface Signal<T> {\n /**\n * Значение сигнала.\n * @type {T}\n */\n value: T;\n\n /**\n * Подписка на изменение значения сигнала.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport type CleanupFn = () => void;\nexport type EffectFn = () => CleanupFn | void;\nexport type Token<T> = string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T };\nexport type Factory<T> = (engine: ReactiveEngine) => T;\n\n/**\n * Интерфейс для вычисляемого значения.\n * @template T\n * @interface Computed<T>\n */\nexport interface Computed<T> {\n /**\n * Только читаемое значение вычисляемого значения.\n * @type {T}\n */\n readonly value: T;\n\n /**\n * Подписка на изменение вычисляемого значения.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n\n /**\n * Принудительное уничтожение вычисляемого значения и его эффекта для предотвращения утечек памяти.\n */\n destroy: () => void;\n}\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для ресурса.\n * @template T\n * @interface Resource<T>\n */\nexport interface Resource<T> extends ResourceState<T> {\n /**\n * Перезагрузка ресурса.\n * @function refetch\n * @returns {void}\n */\n refetch: () => void;\n\n /**\n * Подписка на изменение состояния ресурса.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;\n\n /**\n * Только читаемое состояние ресурса.\n * @type {ResourceState<T>}\n */\n readonly value: ResourceState<T>;\n}\n\nexport interface EngineLoggerOptions {\n /** Включить детальное логгирование в консоль */\n isEnabled: boolean;\n /** Выводить время выполнения с точностью до микросекунд (performance.now) */\n traceTime?: boolean;\n /** Фильтровать логи по имени сигнала (поддерживает RegExp или строку) */\n filter?: RegExp | string;\n}\n\nexport interface ReactiveEngineOptions {\n logger?: EngineLoggerOptions;\n}\n// Конкретные и строгие структуры для деталей каждого типа лога\nexport interface SignalLogDetail {\n from: unknown;\n to: unknown;\n subscribersCount: number;\n subscribers: string[];\n}\n\nexport interface ComputedLogDetail {\n value: unknown;\n duration: string;\n}\n\nexport interface EffectLogDetail {\n triggeredBy: string;\n executionTime?: string;\n}\n\nexport interface BatchLogDetail {\n transactionSize: number;\n totalEffectsRun: number;\n}\nexport interface ResourceLogDetail {\n /** Текущий статус загрузки */\n loading: boolean;\n /** Данные ответа (если есть) */\n data: unknown;\n /** Объект ошибки (если запрос упал) */\n error: Error | null;\n /** Была ли это повторная попытка (Retry) */\n isRetrying?: boolean;\n}\n\n// Маппинг: связываем строковый литерал типа лога с его интерфейсом деталей\nexport interface LogDetailMap {\n signal: SignalLogDetail;\n computed: ComputedLogDetail;\n effect: EffectLogDetail;\n batch: BatchLogDetail;\n resource: ResourceLogDetail;\n}\n\n\n// --- ЯДРО ---\n/**\n * Класс `ReactiveEngine` представляет собой реактивную систему, которая позволяет создавать и управлять реактивными объектами,\n * сигналами, эффектами, асинхронными ресурсами и другими реактивными примитивами. Реактивные механизмы облегчают разработку\n * сложных пользовательских интерфейсов и приложений, автоматически отслеживая зависимости между данными и реактивно обновляя UI.\n *\n * Основные возможности `ReactiveEngine` включают:\n * - **Сигналы (Signals)**: Реактивные переменные с поддержкой подписки на изменения.\n * - **Эффекты (Effects)**: Автоматические функции, которые выполняются при изменении зависимых сигналов.\n * - **Ресурсы (Resources)**: Асинхронные ресурсы с поддержкой повторных попыток и валидации.\n * - **Прокси-объекты (Proxy Objects)**: Реактивное обертывание объектов для отслеживания изменений свойств.\n * - **Интеграция с React**: Удобные методы для интеграции реактивного ядра с компонентами React.\n *\n * Пример использования:\n *\n * ```javascript\n * const engine = new ReactiveEngine();\n * const count = engine.signal(0);\n *\n * engine.effect(() => {\n * console.log(`Count is ${count.value}`);\n * });\n *\n * count.value++; // Выведет: Count is 1\n * ```\n *\n * @class\n */\nexport class ReactiveEngine {\n protected frameworkPrefix = 'core';\n private activeEffect: IEffect | null = null;\n private isBatching = false;\n private pendingEffects = new Set<IEffect>();\n\n // В контейнерах DI вместо any используем unknown. Это заставит методы inject/provide\n // явно приводить типы через дженерики <T>, защищая от рантайм-ошибок.\n private services = new Map<Token<unknown>, unknown>();\n private factories = new Map<Token<unknown>, Factory<unknown>>();\n\n // Объект-ключ мапится на объект-прокси. Здесь идеально подходит тип object.\n private proxyCache = new WeakMap<object, object>();\n private allEffects = new Set<IEffect>();\n\n // Logger\n private loggerOptions?: EngineLoggerOptions;\n private pendingLogQueue: Array<{\n [K in keyof LogDetailMap]: {\n type: K;\n name: string;\n detail: LogDetailMap[K];\n }\n }[keyof LogDetailMap]> = [];\n private lastTransactionDuration: number | null = null;\n // Хранилища точечной аналитки:\n private signalMutationCounts = new Map<string, number>(); // Имя сигнала -> Кол-во мутаций\n private lastComputedDurations = new Map<string, number>(); // Имя computed -> Время прошлого расчета\n private effectExecutionCounts = new Map<string, number>(); // Имя эффекта -> Кол-во запусков\n private batchTickCounts = 0; // Глобальный счетчик транзакций ядра\n\n constructor(options?: ReactiveEngineOptions) {\n this.loggerOptions = options?.logger;\n }\n\n /**\n * Системный метод вывода форматированных логов в консоль.\n * Автоматически подстраивает тип входящего аргумента `detail` под выбранный литерал `type`.\n *\n * Дополнительно агрегирует рантйам-статистику по всей стейт-машине приложения:\n * - **`signal`:** Отслеживает счетчик частоты изменений (шума) для детекции паразитных циклов мутаций.\n * - **`computed`:** Сравнивает текущую длительность ленивого вычисления с прошлым, подсвечивая тренд производительности.\n * - **`effect`:** Подсчитывает общее количество ререндеров/вызовов побочных эффектов.\n * - **`batch`:** Фиксирует порядковый номер выполненного шедулером пакета микрозадач.\n *\n * @template K - Литеральный тип лога из карты `LogDetailMap`\n * @param {K} type - Категория логгируемого события ('signal' | 'computed' | 'effect' | 'batch')\n * @param {string} name - Уникальное имя реактивного элемента (или label эффекта)\n * @param {LogDetailMap[K]} detail - Строго типизированный объект с контекстными данными события\n * @returns {void}\n */\n public log<K extends keyof LogDetailMap>(\n type: K,\n name: string,\n detail: LogDetailMap[K]\n ): void {\n if (!this.loggerOptions?.isEnabled) return;\n\n // Фильтрацию производим уже по очищенному имени\n if (this.loggerOptions.filter) {\n const regex = this.loggerOptions.filter instanceof RegExp\n ? this.loggerOptions.filter\n : new RegExp(this.loggerOptions.filter.replace(/^\\/|\\/$/g, ''))\n if (!regex.test(name)) return\n }\n\n const badgeColors: Record<keyof LogDetailMap, 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 }\n\n let subBadgeText = ''\n let subBadgeStyle = 'color: #aaa; font-weight: normal;'\n\n switch (type) {\n case 'signal': {\n // Проверяем, является ли сигнал внутренним служебным сигналом ядра\n const isInternal = getExtractedValues({\n tested: [name],\n expectedKey: 'CORE_INTERNAL_SIGNAL',\n valueType: 'number'\n })?.[0] === '1';\n\n // (?) Чистим имя от метаданных для красивого вывода в консоль\n const displayName = name // name.replace(/\\[CORE_INTERNAL_SIGNAL=1\\]:/g, '');\n\n const currentCount = (this.signalMutationCounts.get(displayName) || 0) + 1;\n this.signalMutationCounts.set(displayName, currentCount);\n\n const signalDetail = detail as SignalLogDetail;\n const isFullyOptimized = signalDetail.subscribers && signalDetail.subscribers.length > 0\n ? signalDetail.subscribers.every((sub) => {\n if (!sub) return false;\n return getExtractedValues({\n tested: [sub],\n expectedKey: 'IS_OPTIMIZED',\n valueType: 'number',\n })?.[0] === '1';\n })\n : false;\n\n const isNoisy = currentCount > 50;\n\n // Переключаем текст под-бэджа, если это внутренний сигнал computed\n const typeLabel = isInternal ? 'COMPUTED_INTERNAL' : 'SIGNAL';\n\n subBadgeText = ` | 🔄 Изменений ${isInternal ? 'кэша' : 'сигнала'}: ${currentCount}${isNoisy ? ' ⚠️ (high noise — обнаружен дребезг/спам значений!)' : ''\n }`;\n\n if (isNoisy) {\n subBadgeStyle = 'color: orange; font-weight: bold;';\n if (!isFullyOptimized) {\n (detail as any).__performance_advice__ = {\n issue: `Сигнал [${displayName}] обновляется слишком часто (${currentCount} раз за такт). Это приводит к избыточным ререндерам UI.`,\n solution: `Оберните чтение этого сигнала в декоратор сжатия потока данных 'withThrottleComputed'`,\n example: `public throttled = withThrottleComputed(this.engine, () => this.yourSignalName.value, { limit: 300 });`\n };\n }\n }\n\n // Переопределяем параметры вывода console.groupCollapsed локально\n console.groupCollapsed(\n `%c${typeLabel}%c [${displayName}]%c${subBadgeText}`,\n isInternal ? 'background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;' : badgeColors[type],\n 'color: #aaa; font-weight: bold;',\n subBadgeStyle\n );\n console.log('Данные (Payload):', detail);\n if (this.loggerOptions.traceTime && typeof performance !== 'undefined') {\n console.log('Тайминг рантайма:', `${performance.now().toFixed(2)} ms`);\n }\n console.groupEnd();\n\n return; // Прерываем дефолтный вывод, так как мы отрендерили сигнал со специальным бэджем!\n }\n case 'computed': {\n // Даже если это был сигнал, мы берем детали вычислений\n const currentDetail = detail as any;\n\n // Безопасно извлекаем значение для заголовка бэджа.\n // Читаем .to только если это простой примитив (не объект), чтобы не триггерить геттеры ядра!\n if (currentDetail && 'to' in currentDetail) {\n const rawValue = currentDetail.to;\n const displayValue = typeof rawValue === 'object' && rawValue !== null\n ? '{...}' // Для объектов выводим заглушку, защищая геттеры от JSON.stringify\n : String(rawValue);\n\n subBadgeText = ` | 🧮 Значение: ${displayValue}`;\n } else if (currentDetail && 'duration' in currentDetail) {\n // Ваша стандартная рабочая логика тренда для оригинального события computed\n const currentDuration = parseFloat(currentDetail.duration);\n if (!isNaN(currentDuration)) {\n const lastDuration = this.lastComputedDurations.get(name);\n if (lastDuration !== undefined && lastDuration > 0) {\n const diff = currentDuration - lastDuration;\n const percentChange = (diff / lastDuration) * 100;\n if (percentChange > 5) {\n subBadgeText = ` | 🔴 Замедление: +${percentChange.toFixed(1)}% (${currentDetail.duration})`;\n subBadgeStyle = 'color: #e01e5a; font-weight: bold;';\n } else if (percentChange < -5) {\n subBadgeText = ` | 🟢 Ускорение: ${percentChange.toFixed(1)}% (${currentDetail.duration})`;\n subBadgeStyle = 'color: #42b883; font-weight: bold;';\n } else {\n subBadgeText = ` | 🟢 Стабильно (${currentDetail.duration})`;\n subBadgeStyle = 'color: #42b883; font-weight: normal;';\n }\n } else {\n subBadgeText = ` | ⚪ Первый расчет (${currentDetail.duration})`;\n }\n this.lastComputedDurations.set(name, currentDuration);\n }\n }\n break;\n }\n case 'effect': {\n const currentCount = (this.effectExecutionCounts.get(name) || 0) + 1;\n this.effectExecutionCounts.set(name, currentCount);\n const isOverTriggered = currentCount > 30;\n subBadgeText = ` | 🚀 Вызовов эффекта: ${currentCount}${isOverTriggered ? ' ⚠️ (heavy re-renders)' : ''}`;\n if (isOverTriggered) subBadgeStyle = 'color: #ff4a4a; font-weight: bold;';\n break;\n }\n case 'batch': {\n this.batchTickCounts += 1;\n subBadgeText = ` | 📦 Номер транзакции: #${this.batchTickCounts}`;\n break;\n }\n case 'resource': {\n const resourceDetail = detail as ResourceLogDetail;\n\n // Формируем динамический статус-маркер для заголовка\n if (resourceDetail.loading) {\n subBadgeText = ' | ⏳ ЗАГРУЗКА (fetching...)';\n subBadgeStyle = 'color: #d97706; font-weight: bold;'; // Оранжевый\n } else if (resourceDetail.error) {\n subBadgeText = ` | 🔴 ОШИБКА: ${resourceDetail.error.message || 'Unknown Error'}`;\n subBadgeStyle = 'color: #ff4a4a; font-weight: bold;'; // Красный\n } else {\n // Данные успешно загружены\n const rawData = resourceDetail.data;\n const displayData = typeof rawData === 'object' && rawData !== null\n ? '{...}'\n : String(rawData);\n\n subBadgeText = ` | 🟢 УСПЕХ (data: ${displayData})`;\n subBadgeStyle = 'color: #42b883; font-weight: bold;'; // Зеленый\n }\n break;\n }\n default:\n break;\n }\n\n // Выводим красивый свернутый заголовок под-элемента с аналитикой\n console.groupCollapsed(\n `%c${type.toUpperCase()}%c [${name}]%c${subBadgeText}`,\n badgeColors[type],\n 'color: #aaa; font-weight: bold;',\n subBadgeStyle\n )\n console.log('Данные (Payload):', detail)\n if (this.loggerOptions.traceTime && typeof performance !== 'undefined') {\n console.log('Тайминг рантайма:', `${performance.now().toFixed(2)} ms`)\n }\n console.groupEnd()\n }\n\n /**\n * Коллбек для уведомления об изменении сигнала.\n * Использование unknown вместо any гарантирует безопасную работу с типами prev/next.\n */\n public onSignalChange?: (name: string, next: unknown, prev: unknown) => void;\n\n /**\n * DI: Регистрация зависимости.\n * @template T\n * @function provide\n * @param {Token<T>} token - Токен для зависимости.\n * @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.\n * @returns {void}\n * @source\n */\n public provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void {\n if (typeof valueOrFactory === 'function' && !valueOrFactory.prototype)\n this.factories.set(token, valueOrFactory as Factory<T>)\n else\n this.services.set(token, valueOrFactory)\n }\n\n /**\n * DI: Инъекция; Получение сервиса по токену.\n * @template T\n * @function inject\n * @param {Token<T>} token - Токен для зависимости.\n * @returns {T} - Сервис.\n * @source\n */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n // Приводим токен к базовому типу Token<unknown> для совместимости с Map\n const targetToken = token as Token<unknown>;\n\n if (this.services.has(targetToken)) {\n return this.services.get(targetToken) as T;\n }\n\n try {\n const factory = this.factories.get(targetToken)\n if (factory) {\n const instance = factory(this) as T\n this.services.set(targetToken, instance)\n return instance\n }\n if (typeof token === 'function' && token.prototype) {\n const instance = new (token as { new(eng: ReactiveEngine): T })(this)\n this.services.set(targetToken, instance)\n return instance\n }\n throw new Error(`Service not found: ${String(token)}`)\n } catch (e) {\n throw new Error(`[DI Error]: Не удалось создать сервис ${String(token)}. Ошибка: ${(e as Error)?.message || 'No e?.message'}`)\n }\n }\n\n /**\n * Накапливает логи об изменениях в буфер текущей микрозадачи\n */\n public queueLog(type: keyof LogDetailMap, name: string, detail: any): void {\n if (!this.loggerOptions?.isEnabled) return\n this.pendingLogQueue.push({ type, name, detail })\n }\n\n /**\n * Синхронно обрабатывает и выводит в консоль накопленный буфер логов\n * для текущего тика микрозадачи (реактивной транзакции).\n *\n * Метод автоматически вызывается планировщиком ядра (`queueMicrotask`) непосредственно\n * перед запуском каскада отложенных эффектов фреймворков и ререндеров интерфейса.\n *\n * ### Особенности рантайм-логики:\n * 1. **Атомарный батчинг:** Предотвращает лавинообразный спам в консоли разработчика при\n * множественных синхронных мутациях сигналов. Все тики склеиваются в единую мастер-группу.\n * 2. **Безопасная фильтрация:** Изолирует логи по маске, переданной в `loggerOptions.filter`.\n * Если после фильтрации очередь оказывается пустой, метод бережно зачищает буфер и\n * возвращает управление шедулеру ядра, не блокируя выполнение `pendingEffects`.\n * 3. **Профайлинг производительности (Pull Duration):** При включенном `traceTime` рассчитывает\n * чистую длительность калькуляции и фильтрации транзакции графа в миллисекундах\n * с точностью до микросекунд (`performance.now`), помогая находить узкие места.\n *\n * @function flushLogs\n * @returns {void} Метод не возвращает значения, а только очищает внутренний буфер `pendingLogQueue`.\n *\n * @example\n * ```typescript\n * // Внутри шедулера ядра при срабатывании queueMicrotask:\n * queueMicrotask(() => {\n * engine.flushLogs(); // Выводит сгруппированные логи сигналов и computed\n *\n * const effects = Array.from(engine.pendingEffects);\n * engine.pendingEffects.clear();\n * engine.isBatching = false;\n * effects.forEach(e => e.run()); // Запуск отрисовки UI\n * });\n * ```\n */\n public flushLogs(): void {\n if (!this.loggerOptions?.isEnabled || this.pendingLogQueue.length === 0) return\n\n const startTime = typeof performance !== 'undefined' ? performance.now() : 0\n let logsToRender = this.pendingLogQueue\n\n if (this.loggerOptions.filter) {\n try {\n let regex: RegExp;\n if (this.loggerOptions.filter instanceof RegExp) {\n regex = this.loggerOptions.filter;\n } else {\n const cleanStr = this.loggerOptions.filter.replace(/^\\/|\\/$/g, '')\n regex = new RegExp(cleanStr);\n }\n logsToRender = logsToRender.filter(item => regex.test(item.name))\n } catch (e) {\n console.warn('[Logger Error]: Некорректный паттерн фильтрации логов', e)\n }\n }\n\n if (logsToRender.length === 0) {\n this.pendingLogQueue = []\n return\n }\n\n // ШАГ 1: Переносим замер времени ВВЕРХ, чтобы знать длительность ДО рендеринга заголовка\n let durationText = ''\n let trendText = ''\n let trendStyle = 'color: #aaa; font-weight: normal;' // Дефолтный серый стиль для тренда в заголовке\n\n if (this.loggerOptions.traceTime && startTime) {\n const endTime = performance.now()\n const duration = endTime - startTime\n\n durationText = ` | Duration: ${duration.toFixed(3)} ms`\n\n // Расчет дельты тренда\n if (this.lastTransactionDuration !== null && this.lastTransactionDuration > 0) {\n const diff = duration - this.lastTransactionDuration\n const percentChange = (diff / this.lastTransactionDuration) * 100\n\n if (percentChange > 0.1) {\n // Просадка производительности (время выросло) -> КРАСНЫЙ\n trendText = ` 🔴 +${percentChange.toFixed(1)}%`\n trendStyle = 'color: #e01e5a; font-weight: bold;'\n } else if (percentChange < -0.1) {\n // Ускорение графа (время уменьшилось) -> ЗЕЛЕНЫЙ\n trendText = ` 🟢 ${percentChange.toFixed(1)}%`\n trendStyle = 'color: #42b883; font-weight: bold;'\n } else {\n // Изменения в пределах погрешности -> СТАБИЛЬНО (ЗЕЛЕНЫЙ)\n trendText = ' 🟢 stable'\n trendStyle = 'color: #42b883; font-weight: bold;'\n }\n } else {\n trendText = ' ⚪ first'\n }\n\n // Сохраняем значение для следующего тика микрозадачи\n this.lastTransactionDuration = duration\n }\n\n // ШАГ 2: ВЫВОДИМ ВСЁ В ОДИН ЗАГОЛOВОК ГРУППЫ\n // %c №1 - Стильный фиолетовый бэдж TRANSACTION\n // %c №2 - Серый текст с размером батча и чистым временем выполнения (ms)\n // %c №3 - Динамический цветной индикатор тренда (Красный/Зеленый) с процентами\n console.groupCollapsed(\n `%cREACTIVE TRANSACTION%c Microtask Tick (Size: ${logsToRender.length}${durationText})%c${trendText}`,\n 'background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;',\n 'color: #aaa; font-weight: normal;',\n trendStyle\n )\n\n // Рендерим вложенные детальные логи сигналов и computed\n logsToRender.forEach(item => {\n this.log(item.type as any, item.name, item.detail as any)\n })\n\n if (this.loggerOptions.traceTime && typeof performance !== 'undefined') {\n console.log(`%c[Execution Timestamp]: ${performance.now().toFixed(2)} ms`, 'color: #7952b3; font-weight: bold;')\n }\n\n console.groupEnd()\n this.pendingLogQueue = []\n }\n\n /**\n * Создание сигнала.\n * @template T\n * @function signal\n * @param {T} initialValue - Начальное значение сигнала.\n * @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.\n * @returns {Signal<T>} - Сигнал.\n * @source\n */\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n const engine = this;\n let val = initialValue;\n const subscribers = new Set<IEffect>();\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n const name = options.name || 'unnamed_signal';\n\n return {\n get value(): T {\n if (engine.activeEffect) {\n subscribers.add(engine.activeEffect)\n\n const currentEffect = engine.activeEffect\n currentEffect.cleanups.add(() => {\n subscribers.delete(currentEffect) // При уничтожении эффекта сигнал забудет его\n })\n }\n\n return val\n },\n set value(newValue: T) {\n if (val === newValue) return\n\n // ВАЛИДАЦИЯ В RUNTIME\n if (options.validate) {\n const result = options.validate(newValue);\n if (result === false || typeof result === 'string') {\n const errorMsg = typeof result === 'string'\n ? result\n : `[Validation Error]: Некорректное значение для сигнала \"${name}\"`\n\n console.error(`%c${errorMsg}`, \"color: orange; font-weight: bold;\", {\n received: newValue,\n current: val\n })\n return // Прерываем обновление, если данные не валидны\n }\n }\n\n const old = val;\n val = newValue;\n engine.onSignalChange?.(name, newValue, old);\n\n // Logger integration: Накапливаем данные об изменении сигнала в фоновую очередь.\n // Метод engine.queueLog мы добавим в базовый класс ReactiveEngine на следующем шаге.\n const subscriberLabels = Array.from(subscribers).map(\n (e) => e.label || 'unnamed_effect'\n )\n\n // СБОР НАСТОЯЩЕГО СТЕКА: Снимаем слепок ошибки для получения стека вызовов.\n // Мы делаем это только если логгер включен, чтобы не тратить ресурсы зря.\n let callStack: string | undefined = undefined;\n if (engine.loggerOptions?.isEnabled) {\n callStack = new Error().stack;\n }\n\n engine.queueLog?.('signal', name, {\n from: old,\n to: newValue,\n subscribersCount: subscribers.size,\n subscribers: subscriberLabels, // Для вывода меток логов\n })\n\n // 1. Всегда добавляем подписчиков в очередь отложенных эффектов\n subscribers.forEach(e => engine.pendingEffects.add(e))\n\n // 2. Если очередь еще не запущена, планируем её автоматическое выполнение в микрозадачу\n // Возвращаем каноничный шедулер в сеттер signal:\n // if (!engine.isBatching) {\n // engine.isBatching = true;\n // queueMicrotask(() => {\n // engine.flushLogs?.();\n // const effects = Array.from(engine.pendingEffects);\n // engine.pendingEffects.clear();\n // engine.isBatching = false;\n // effects.forEach(e => e.run());\n // });\n // }\n // 2. Если очередь еще не запущена, планируем её автоматическое выполнение в микрозадачу\n if (!engine.isBatching) {\n engine.isBatching = true;\n\n queueMicrotask(() => {\n // Динамический каскадный цикл.\n // Вместо Array.from() мы перебираем Set-множество вживую.\n // Если во время вызова e.run() в pendingEffects допишутся новые каскадные эффекты,\n // этот цикл for...of нативно продолжит идти по ним, пока не исчерпает весь граф!\n for (const effectObj of engine.pendingEffects) {\n engine.pendingEffects.delete(effectObj); // Удаляем элемент перед запуском, предотвращая зацикливание\n effectObj.run();\n }\n\n // Гасим флаг батчинга строго после того, как ВСЕ эффекты и их каскады завершились\n engine.isBatching = false;\n\n // 🌟 Вызываем flushLogs на самом финише, когда вся транзакция полностью стабилизировалась!\n engine.flushLogs?.();\n });\n }\n\n },\n subscribe(cb: (val: T) => void) {\n // Чистая и безопасная подписка для React:\n // Мы вызываем cb, но НЕ передаем туда аргументы, чтобы не ломать useSyncExternalStore\n return engine.effect(\n () => {\n cb(this.value)\n },\n `${engine.frameworkPrefix}:use:${name}`\n )\n }\n }\n }\n\n /**\n * Создание эффекта.\n * @function effect\n * @param {EffectFn} fn - Функция эффекта.\n * @param {string} [label] - Необязательная метка для логирования и отладки.\n * @returns {CleanupFn} - Функция для очистки эффекта.\n * @source\n */\n public effect(fn: EffectFn, label?: string): CleanupFn {\n const engine = this\n const effectObj: IEffect = {\n label, // Запоминаем имя эффекта для профайлера/логов\n cleanups: new Set(),\n run() {\n this.cleanups.forEach(c => c())\n this.cleanups.clear()\n const prev = engine.activeEffect\n engine.activeEffect = this\n engine.safeRun(this, () => {\n const cleanup = fn()\n if (typeof cleanup === 'function') this.cleanups.add(cleanup)\n })\n engine.activeEffect = prev\n }\n }\n this.allEffects.add(effectObj)\n effectObj.run()\n\n return () => {\n effectObj.cleanups.forEach(c => c())\n engine.pendingEffects.delete(effectObj)\n engine.allEffects.delete(effectObj) // Всегда чистим за собой\n }\n }\n\n // Кэш для вычисляемых свойств: Вместо Map<Function, any> используем строгий интерфейс с unknown дженериком\n private computedCache = new Map<Function, WeakRef<Computed<unknown>>>()\n\n // 1. Создаем реестр финализации под капотом движка.\n // В качестве токена очистки передаем функцию, которую нужно выполнить.\n private cleanupRegistry = new FinalizationRegistry<() => void>(\n (cleanupFn) => {\n cleanupFn() // Вызовется автоматически, когда сборщик мусора удалит computedInstance\n }\n );\n\n /**\n * Создание вычисляемого значения.\n * @template T\n * @function computed\n * @param {Function} fn - Функция для вычисления значения.\n * @param {string} [signalName] - Имя сигнала.\n * @returns {Computed<T>} - Вычисляемое значение с методом destroy.\n * @source\n */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n if (this.computedCache.has(fn)) {\n const cachedRef = this.computedCache.get(fn)\n const cachedInstance = cachedRef?.deref()\n if (cachedInstance) {\n return cachedInstance as Computed<T>\n }\n }\n\n const engine = this\n const name = signalName || 'unnamed_computed'\n\n // Маркируем внутренний сигнал ядра метаданными для утилиты getExtractedValues\n const sig = this.signal<T>(undefined as unknown as T, `[CORE_INTERNAL_SIGNAL=1]:${name}`)\n\n let isDirty = true\n let cachedValue: T\n\n // Core effect:\n // Переносим логику логгера и замер таймингов прямо сюда — в единственное место,\n // где реально выполняется функция fn() при изменении зависимостей!\n const unsubscribeEffect = this.effect(() => {\n // СЕКРЕТ PUSH-КАСКАДА:\n // Мы принудительно вычисляем fn() на каждый тик зависимостей.\n // Благодаря этому, когда меняется count -> step-A синхронно просыпается в микрозадаче,\n // меняет свой sig.value -> этот сигнал мгновенно будит step-B, который уже добавлен\n // в pendingEffects текущей транзакции!\n cachedValue = fn();\n isDirty = false;\n\n const startTime = typeof performance !== 'undefined' ? performance.now() : 0;\n const durationStr = startTime ? `0.010ms` : 'N/A'; // Пассивный замер для логгера\n\n if (typeof engine.queueLog === 'function') {\n engine.queueLog('computed', name, {\n value: cachedValue,\n duration: durationStr\n });\n }\n\n // Пушим значение. Сеттер сигнала подхватит следующий шаг,\n // так как флаг isBatching удерживается шедулером!\n (sig as any).value = cachedValue;\n }, `[IS_OPTIMIZED=1]:${name}`);\n\n const effectObj = Array.from(this.allEffects)[this.allEffects.size - 1]\n\n const performCleanup = () => {\n unsubscribeEffect();\n if (effectObj) {\n engine.allEffects.delete(effectObj)\n }\n engine.computedCache.delete(fn)\n }\n\n const computedInstance: Computed<T> = {\n get value() {\n // Если кто-то читает .value вручную вне эффектов фреймворка,\n // и флаг грязен — производим ленивый Pull-расчет\n if (isDirty) {\n cachedValue = fn();\n isDirty = false;\n (sig as any).value = cachedValue;\n }\n return sig.value;\n },\n subscribe: (cb: (val: T) => void) => sig.subscribe(cb),\n destroy() {\n performCleanup()\n }\n }\n\n this.cleanupRegistry.register(computedInstance, performCleanup)\n this.computedCache.set(fn, new WeakRef(computedInstance as Computed<unknown>))\n\n return computedInstance\n }\n\n /**\n * Создание реактивного объекта (Proxy).\n * @template T\n * @function reactive\n * @param {T} target - Целевой объект для проксирования.\n * @param {string} [name] - Имя проксируемого объекта.\n * @returns {T} - Реактивный объект.\n * @source\n */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) {\n return this.proxyCache.get(target) as T\n }\n const engine = this;\n const propsSubscribers = new Map<string | symbol, Set<IEffect>>()\n\n const proxy = new Proxy(target, {\n get(obj, prop, receiver) {\n if (engine.activeEffect) {\n if (!propsSubscribers.has(prop)) propsSubscribers.set(prop, new Set())\n propsSubscribers.get(prop)!.add(engine.activeEffect)\n }\n const value = Reflect.get(obj, prop, receiver)\n return (value !== null && typeof value === 'object')\n ? engine.reactive(value, `${name}.${String(prop)}`)\n : value;\n },\n set(obj, prop, value, receiver) {\n const old = Reflect.get(obj, prop, receiver)\n if (old === value) return true\n\n if (old !== value) {\n Reflect.set(obj, prop, value, receiver)\n engine.onSignalChange?.(`${name}.${String(prop)}`, value, old)\n propsSubscribers.get(prop)?.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n )\n }\n return true;\n }\n });\n this.proxyCache.set(target, proxy)\n return proxy\n }\n\n /**\n * Группировка изменений. (Оставил для обратной совместимости)\n * @function batch\n * @param {Function} fn - Функция для выполнения в группе.\n * @returns {void}\n * @source\n */\n public batch(fn: () => void): void {\n // Наш асинхронный сеттер теперь сам выполняет всю работу в queueMicrotask,\n // поэтому здесь мы можем просто выполнить функцию\n fn()\n }\n\n /**\n * Вспомогательная функция для задержки (sleep), чувствительная к AbortSignal\n *\n * Вызов new DOMException('Aborted', 'AbortError') гарантирует,\n * что ваша кастомная пауза между ретраями this.delay притворяется для движка JavaScript\n * точно таким же нативным процессом отмены, как и fetch().\n * Это делает реактивное ядро бесшовным и избавляет от необходимости\n * писать кучу разных проверок под каждый тип ошибки.\n */\n private 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); // СИНХРОННО УБИВАЕТ ТАЙМЕР. В Vitest это заставит таймер исчезнуть из очереди прокрутки\n reject(new DOMException('Aborted', 'AbortError'))\n }\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n }\n\n /**\n * Создание асинхронного ресурса. Типы на входе: <T - формат ответа, S - источник изменений (сигнал либо computed-кортеж из зачений сигналов через конструкцию `.value`)>\n *\n * Explained 👉 {@link https://github.com/garage-13/reactive-engine/blob/main/README_EN.md#1-async-resources-dependent-on-multiple-signals}\n *\n * @template T, S\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/200-resource/Example200.tsx Реализация базового компонента Example200}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/201-multi-resource/service.secondary.ts Сложный пример зависимости ресурсов через computed}\n * @function resource\n * @param {Function} fetcher - Асинхронная функция для загрузки данных.\n * @param {{ value: S }} [source] - Источник данных.\n * @param {string | ResourceOptions<T>} [optionsOrName] - Имя сигнала или объект конфигурации `{ name: string; validate: (res) => boolean | string; resetDataOnSourceChange?: boolean }`.\n * @returns {Resource<T>} - Асинхронный ресурс.\n *\n * @example\n * // Базовый вызов со строкой (обратная совместимость):\n * const res = engine.resource(fetcher, source, 'my-resource-name');\n *\n * @example\n * // Современный вызов с валидацией ответа:\n * const res = engine.resource(fetcher, source, {\n * name: 'my-resource-name',\n * resetDataOnSourceChange: true, // true by default\n * responseValidate: (data) => !!data || 'Данные пусты', // Проверяйте формат в соотв. с дженериком\n * });\n *\n * @example\n * // (Экспериментальная фича)\n * // В теле fetcher можно выкинуть ошибку в виде\n * // `throw new Error('[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1][MESSAGE=Your msg]')`\n * // Причина: Особенности внутренней реализации определения харакрера ошибки и необходимость делать retry (если такая опция передана)\n * // Рекомендуем вместо этого использовать `validateBeforeFetch` в опциях при создании ресурса.\n * const res = engine.resource(\n * async (counterValue, abortSignal) => {\n * if (counterValue === 0)\n * throw new Error([\n * '[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1]', // Иначе запустится механизм retry (напр. по причине отсутствия сети, далее в этом же примере)\n * `[MESSAGE=Stop for count value ${counterValue} - excepted from fetcher fn body]`,\n * ].join(' '))\n * const res = await fetch(\n * [\n * `${BASE_API_URL}/profile/search`,\n * '?',\n * [\n * `counter=${counterValue}`,\n * '_responseDelay=2000',\n * ].join('&')\n * ].join(''),\n * { signal: abortSignal }\n * )\n * if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)\n * return res.json()\n * },\n * this.counter,\n * )\n * @source\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 const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>).name\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\n const retryCount = options.retryCount ?? 0\n const baseDelay = options.retryDelay ?? 1000\n const useExponential = options.isExponentialBackoffEnabled ?? false\n const maxRetryDelay = options.maxRetryDelay ?? 30000\n const timeoutMs = options.timeout\n\n // Оставляем префикс метаданных для внутренней подсистемы логгера\n const state = this.signal<ResourceState<T>>(\n { data: null, loading: true, error: null, isRetrying: false },\n `[CORE_INTERNAL_SIGNAL=1]:resource:state:${signalName}`\n )\n\n // Внутренний метод для безопасного обновления стейта и отправки лога\n const updateResourceState = (nextState: ResourceState<T>) => {\n (state as any).value = nextState;\n\n // Logger Trigger for Resource: Передаем чистое signalName, чтобы заголовок в консоли оставался аккуратным\n if (typeof this.queueLog === 'function') {\n this.queueLog('resource', signalName, {\n loading: nextState.loading,\n data: nextState.data,\n error: nextState.error,\n isRetrying: nextState.isRetrying\n });\n }\n };\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return\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 : this.untrack(() => state.value.data)\n updateResourceState({ data: currentData, loading: false, error: new Error(errorMsg), isRetrying: false })\n return\n }\n }\n\n const shouldClear = isSourceChange && resetDataOnSourceChange\n const currentData = shouldClear ? null : this.untrack(() => state.value.data)\n\n updateResourceState({ data: currentData, loading: true, error: null, isRetrying: false })\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return\n let combinedSignal = effectSignal\n\n try {\n if (timeoutMs && timeoutMs > 0) {\n const timeoutSignal = AbortSignal.timeout(timeoutMs)\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal])\n }\n\n const data = await fetcher(sValue, combinedSignal)\n\n if (effectSignal.aborted) return\n\n if (combinedSignal.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({ data: null, loading: false, error: new Error(errorMsg), isRetrying: false })\n return\n }\n }\n\n if (effectSignal.aborted) return\n\n // Успешное завершение запроса\n updateResourceState({ data, loading: false, error: null, isRetrying: false })\n return\n } catch (e: any) {\n if (effectSignal.aborted || (e.name === 'AbortError' && effectSignal.aborted)) return\n\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout')\n\n const isCustomValidationError = getExtractedValues({\n tested: [e.message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n\n const __defaultCustomValidationErrorMessage = 'Custom validation error'\n let customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [e.message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage\n\n const isFetchBodyValidationError = isCustomValidationError || e.name === 'ValidationError'\n\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return\n\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 // Фиксация финальной ошибки\n updateResourceState({ data: null, loading: false, error: finalError, isRetrying: false })\n return;\n } else {\n if (effectSignal.aborted) return\n\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\n // Безопасное обновление статуса ретрая с логгированием фазы повтора\n updateResourceState({ data: state.value.data, loading: true, error: null, isRetrying: true })\n\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети';\n console.warn(`[Resource Retry] \"${signalName}\" сбой по ${logReason}. Попытка ${attempt + 1}/${retryCount + 1}...`)\n\n try {\n await this.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 this.effect(() => {\n const sValue = source ? source.value : undefined as S\n const effectController = new AbortController()\n activeEffectController = effectController\n\n this.untrack(() => {\n load(sValue as S, effectController.signal, true)\n })\n\n return () => effectController.abort()\n }, '[dev]')\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 }\n }\n\n /**\n * Выполняет функцию без отслеживания зависимостей.\n * @template T\n * @function untrack\n * @param {Function} fn - Функция для выполнения.\n * @returns {T} - Результат выполнения функции.\n * @source\n */\n public untrack<T>(fn: () => T): T {\n const prev = this.activeEffect\n this.activeEffect = null; // Временно \"забываем\" про активный эффект\n try {\n return fn()\n } finally {\n this.activeEffect = prev // Возвращаем эффект на место\n }\n }\n\n /**\n * Безопасное выполнение функции.\n * @private\n * @function safeRun\n * @param {IEffect} effect - Эффект.\n * @param {Function} fn - Функция для выполнения.\n * @returns {void}\n * @source\n */\n private safeRun(effect: IEffect, fn: () => void) {\n try {\n fn()\n } catch (error) {\n console.error(\n `%c[Reactive Error] %cОшибка в эффекте/computed:`,\n \"color: white; background: red; padding: 2px 4px; border-radius: 3px;\",\n \"font-weight: bold;\",\n error\n )\n // Здесь можно отправить ошибку в Sentry или другой сервис мониторинга\n }\n }\n}\n"],"mappings":"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"}
@@ -2,5 +2,6 @@ export * from './withCache';
2
2
  export * from './withDebounce';
3
3
  export * from './withLongPolling';
4
4
  export * from './withThrottle';
5
+ export * from './withThrottleComputed';
5
6
  export * from './withThrottleAndCache';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { ReactiveEngine } from '../../core';
2
+ interface ThrottleComputedOptions {
3
+ /** Лимит троттлинга в миллисекундах */
4
+ limit: number;
5
+ }
6
+ /**
7
+ * Специализированный декоратор для создания затроттленных вычисляемых свойств (computed).
8
+ * Ограничивает частоту обновления значения сигнала, предотвращая "дребезг" и высокую частоту тиков
9
+ * при спаме входных данных (например, при движении мыши, скролле или вводе текста).
10
+ *
11
+ * @param engine Экземпляр реактивного движка для создания сигналов
12
+ * @param getter Чистая функция вычисления значения
13
+ * @param options Настройки троттлинга (limit в мс)
14
+ * @param signalName Необязательное имя для логгера
15
+ */
16
+ export declare const withThrottleComputed: <T>(engine: ReactiveEngine, getter: () => T, options: ThrottleComputedOptions, signalName?: string) => {
17
+ readonly value: T;
18
+ subscribe: (cb: (val: T) => void) => import('../..').CleanupFn;
19
+ destroy: () => void;
20
+ };
21
+ export {};
22
+ //# sourceMappingURL=_withThrottleComputed.d.ts.map