@pravosleva/reactive-engine 0.2.9-beta → 0.3.0-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.
package/dist/index.mjs CHANGED
@@ -1,320 +1,256 @@
1
- import { useEffect as h, useState as g } from "react";
2
- class v {
3
- activeEffect = null;
4
- isBatching = !1;
5
- pendingEffects = /* @__PURE__ */ new Set();
6
- services = /* @__PURE__ */ new Map();
7
- factories = /* @__PURE__ */ new Map();
8
- proxyCache = /* @__PURE__ */ new WeakMap();
9
- allEffects = /* @__PURE__ */ new Set();
10
- /**
11
- * Коллбек для уведомления о изменении сигнала.
12
- * @type {Function}
13
- */
14
- onSignalChange;
15
- reactAdapters = {
16
- useState: g,
17
- useEffect: h
18
- };
19
- /**
20
- * DI: Регистрация зависимости.
21
- * @template T
22
- * @function provide
23
- * @param {Token<T>} token - Токен для зависимости.
24
- * @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.
25
- * @returns {void}
26
- */
27
- provide(e, t) {
28
- typeof t == "function" && !t.prototype ? this.factories.set(e, t) : this.services.set(e, t);
29
- }
30
- /**
31
- * DI: Инъекция; Получение сервиса по токену.
32
- * @template T
33
- * @function inject
34
- * @param {Token<T>} token - Токен для зависимости.
35
- * @returns {T} - Сервис.
36
- */
37
- inject(e) {
38
- if (!e)
39
- throw new Error("[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.");
40
- if (this.services.has(e)) return this.services.get(e);
41
- try {
42
- const t = this.factories.get(e);
43
- if (t) {
44
- const r = t(this);
45
- return this.services.set(e, r), r;
46
- }
47
- if (typeof e == "function" && e.prototype) {
48
- const r = new e(this);
49
- return this.services.set(e, r), r;
50
- }
51
- throw new Error(`Service not found: ${String(e)}`);
52
- } catch (t) {
53
- throw new Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message || "No e?.message"}`);
54
- }
55
- }
56
- /**
57
- * Создание сигнала.
58
- * @template T
59
- * @function signal
60
- * @param {T} initialValue - Начальное значение сигнала.
61
- * @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.
62
- * @returns {Signal<T>} - Сигнал.
63
- */
64
- signal(e, t) {
65
- const r = this;
66
- let n = e;
67
- const i = /* @__PURE__ */ new Set(), o = typeof t == "string" ? { name: t } : t || {}, s = o.name || "unnamed_signal";
68
- return {
69
- get value() {
70
- return r.activeEffect && i.add(r.activeEffect), n;
71
- },
72
- set value(a) {
73
- if (n === a) return;
74
- if (o.validate) {
75
- const f = o.validate(a);
76
- if (f === !1 || typeof f == "string") {
77
- const u = typeof f == "string" ? f : `[Validation Error]: Некорректное значение для сигнала "${s}"`;
78
- console.error(`%c${u}`, "color: orange; font-weight: bold;", {
79
- received: a,
80
- current: n
81
- });
82
- return;
83
- }
84
- }
85
- const c = n;
86
- n = a, r.onSignalChange?.(s, a, c), i.forEach(
87
- (f) => r.isBatching ? r.pendingEffects.add(f) : f.run()
88
- );
89
- },
90
- subscribe(a) {
91
- return r.effect(() => a(this.value));
92
- }
93
- };
94
- }
95
- /**
96
- * Создание эффекта.
97
- * @function effect
98
- * @param {EffectFn} fn - Функция эффекта.
99
- * @returns {CleanupFn} - Функция для очистки эффекта.
100
- */
101
- effect(e) {
102
- const t = this, r = {
103
- cleanups: /* @__PURE__ */ new Set(),
104
- run() {
105
- this.cleanups.forEach((i) => i()), this.cleanups.clear();
106
- const n = t.activeEffect;
107
- t.activeEffect = this, t.safeRun(this, () => {
108
- const i = e();
109
- typeof i == "function" && this.cleanups.add(i);
110
- }), t.activeEffect = n;
111
- }
112
- };
113
- return this.allEffects.add(r), r.run(), () => {
114
- r.cleanups.forEach((n) => n()), t.pendingEffects.delete(r);
115
- };
116
- }
117
- /**
118
- * Создание вычисляемого значения.
119
- * @template T
120
- * @function computed
121
- * @param {Function} fn - Функция для вычисления значения.
122
- * @param {string} [signalName] - Имя сигнала.
123
- * @returns {Computed<T>} - Вычисляемое значение.
124
- */
125
- computed(e, t) {
126
- const r = this.signal(void 0, t || "unnamed_computed");
127
- return this.effect(() => {
128
- const n = e();
129
- r.value = n;
130
- }), {
131
- get value() {
132
- return r.value;
133
- },
134
- // Подписываемся на внутренний сигнал
135
- subscribe: (n) => r.subscribe(n)
136
- };
137
- }
138
- /**
139
- * Создание реактивного объекта (Proxy).
140
- * @template T
141
- * @function reactive
142
- * @param {T} target - Целевой объект для проксирования.
143
- * @param {string} [name] - Имя проксируемого объекта.
144
- * @returns {T} - Реактивный объект.
145
- */
146
- reactive(e, t = "reactive") {
147
- if (this.proxyCache.has(e)) return this.proxyCache.get(e);
148
- const r = this, n = /* @__PURE__ */ new Map(), i = new Proxy(e, {
149
- get(o, s, a) {
150
- r.activeEffect && (n.has(s) || n.set(s, /* @__PURE__ */ new Set()), n.get(s).add(r.activeEffect));
151
- const c = Reflect.get(o, s, a);
152
- return c !== null && typeof c == "object" ? r.reactive(c, `${t}.${String(s)}`) : c;
153
- },
154
- set(o, s, a, c) {
155
- const f = Reflect.get(o, s, c);
156
- return f === a || f !== a && (Reflect.set(o, s, a, c), r.onSignalChange?.(`${t}.${String(s)}`, a, f), n.get(s)?.forEach(
157
- (u) => r.isBatching ? r.pendingEffects.add(u) : u.run()
158
- )), !0;
159
- }
160
- });
161
- return this.proxyCache.set(e, i), i;
162
- }
163
- /**
164
- * Группировка изменений.
165
- * @function batch
166
- * @param {Function} fn - Функция для выполнения в группе.
167
- * @returns {void}
168
- */
169
- batch(e) {
170
- this.isBatching = !0;
171
- try {
172
- e();
173
- } finally {
174
- queueMicrotask(() => {
175
- const t = Array.from(this.pendingEffects);
176
- this.pendingEffects.clear(), this.isBatching = !1, t.forEach((r) => r.run());
177
- });
178
- }
179
- }
180
- /**
181
- * Создание асинхронного ресурса.
182
- * @template T
183
- * @function resource
184
- * @param {Function} fetcher - Функция для загрузки данных.
185
- * @param {{ value: S }} [source] - Источник данных.
186
- * @param {string} [signalName] - Имя сигнала ресурса.
187
- * @returns {Resource<T>} - Асинхронный ресурс.
188
- */
189
- resource(e, t, r) {
190
- const n = this.signal({ data: null, loading: !0, error: null }, r || "unnamed_resource");
191
- let i = null;
192
- const o = async (s) => {
193
- i?.abort(), i = new AbortController();
194
- const a = this.untrack(() => n.value.data);
195
- n.value = { data: a, loading: !0, error: null };
196
- try {
197
- const c = await e(s, i.signal);
198
- i.signal.aborted || (n.value = { data: c, loading: !1, error: null });
199
- } catch (c) {
200
- c.name !== "AbortError" && (n.value = { data: null, loading: !1, error: c });
201
- }
202
- };
203
- return this.effect(() => {
204
- const s = t ? t.value : void 0;
205
- return o(s), () => i?.abort();
206
- }), {
207
- get data() {
208
- return n.value.data;
209
- },
210
- get loading() {
211
- return n.value.loading;
212
- },
213
- get error() {
214
- return n.value.error;
215
- },
216
- get value() {
217
- return n.value;
218
- },
219
- refetch: () => o(t ? t.value : void 0),
220
- subscribe: (s) => n.subscribe(s)
221
- };
222
- }
223
- /**
224
- * Выполняет функцию без отслеживания зависимостей.
225
- * @template T
226
- * @function untrack
227
- * @param {Function} fn - Функция для выполнения.
228
- * @returns {T} - Результат выполнения функции.
229
- */
230
- untrack(e) {
231
- const t = this.activeEffect;
232
- this.activeEffect = null;
233
- try {
234
- return e();
235
- } finally {
236
- this.activeEffect = t;
237
- }
238
- }
239
- /**
240
- * Установка адаптеров React.
241
- * @function setReactAdapters
242
- * @param {Function} useState - Функция useState из React.
243
- * @param {Function} useEffect - Функция useEffect из React.
244
- * @returns {void}
245
- */
246
- setReactAdapters(e, t) {
247
- this.reactAdapters = { useState: e, useEffect: t };
248
- }
249
- /**
250
- * Использование реактивного значения в React компоненте.
251
- * @template T
252
- * @function use
253
- * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.
254
- * @returns {T} - Значение реактивного объекта.
255
- */
256
- use(e) {
257
- if (!this.reactAdapters)
258
- throw new Error("[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).");
259
- if (!e || typeof e.subscribe != "function") {
260
- const n = `
261
- [Reactive Error]: engine.use() получил некорректный объект!
262
- Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.
263
- Проверьте, что в классе написано: public mySignal = this.engine.signal(...)
264
- `;
265
- throw console.error(n, { item: e }), new Error(n);
266
- }
267
- const [t, r] = this.reactAdapters.useState(e.value);
268
- return this.reactAdapters.useEffect(
269
- () => e.subscribe(r),
270
- [e]
271
- ), t;
272
- }
273
- /**
274
- * Безопасное выполнение функции.
275
- * @private
276
- * @function safeRun
277
- * @param {IEffect} effect - Эффект.
278
- * @param {Function} fn - Функция для выполнения.
279
- * @returns {void}
280
- */
281
- safeRun(e, t) {
282
- try {
283
- t();
284
- } catch (r) {
285
- console.error(
286
- "%c[Reactive Error] %cОшибка в эффекте/computed:",
287
- "color: white; background: red; padding: 2px 4px; border-radius: 3px;",
288
- "font-weight: bold;",
289
- r
290
- );
291
- }
292
- }
293
- }
294
- class E {
295
- constructor(e) {
296
- this.engine = e;
297
- }
298
- engine;
299
- // Просто сохраняем engine
300
- createNumericSignal(e, t) {
301
- return this.engine.signal(e, {
302
- name: t,
303
- validate: (r) => typeof r == "number" && !isNaN(r)
304
- });
305
- }
306
- }
307
- class p {
308
- constructor(e) {
309
- this.engine = e, setTimeout(() => this.onInit(), 0);
310
- }
311
- engine;
312
- onInit() {
313
- }
314
- }
315
- export {
316
- E as BaseREService,
317
- p as BaseREServiceEnhanced,
318
- v as ReactiveEngine
1
+ import { useCallback as e, useEffect as t, useMemo as n, useRef as r, useState as i, useSyncExternalStore as a } from "react";
2
+ //#region src/BaseREService.ts
3
+ var o = class {
4
+ engine;
5
+ constructor(e) {
6
+ this.engine = e;
7
+ }
8
+ createNumericSignal(e, t) {
9
+ return this.engine.signal(e, {
10
+ name: t,
11
+ validate: (e) => typeof e == "number" && !isNaN(e)
12
+ });
13
+ }
14
+ }, s = class {
15
+ engine;
16
+ constructor(e) {
17
+ this.engine = e, setTimeout(() => this.onInit(), 0);
18
+ }
19
+ onInit() {}
20
+ }, c = class {
21
+ activeEffect = null;
22
+ isBatching = !1;
23
+ pendingEffects = /* @__PURE__ */ new Set();
24
+ services = /* @__PURE__ */ new Map();
25
+ factories = /* @__PURE__ */ new Map();
26
+ proxyCache = /* @__PURE__ */ new WeakMap();
27
+ allEffects = /* @__PURE__ */ new Set();
28
+ onSignalChange;
29
+ reactAdapters = {
30
+ useState: i,
31
+ useEffect: t
32
+ };
33
+ provide(e, t) {
34
+ typeof t == "function" && !t.prototype ? this.factories.set(e, t) : this.services.set(e, t);
35
+ }
36
+ inject(e) {
37
+ if (!e) throw Error("[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.");
38
+ if (this.services.has(e)) return this.services.get(e);
39
+ try {
40
+ let t = this.factories.get(e);
41
+ if (t) {
42
+ let n = t(this);
43
+ return this.services.set(e, n), n;
44
+ }
45
+ if (typeof e == "function" && e.prototype) {
46
+ let t = new e(this);
47
+ return this.services.set(e, t), t;
48
+ }
49
+ throw Error(`Service not found: ${String(e)}`);
50
+ } catch (t) {
51
+ throw Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message || "No e?.message"}`);
52
+ }
53
+ }
54
+ signal(e, t) {
55
+ let n = this, r = e, i = /* @__PURE__ */ new Set(), a = typeof t == "string" ? { name: t } : t || {}, o = a.name || "unnamed_signal";
56
+ return {
57
+ get value() {
58
+ return n.activeEffect && i.add(n.activeEffect), r;
59
+ },
60
+ set value(e) {
61
+ if (r === e) return;
62
+ if (a.validate) {
63
+ let t = a.validate(e);
64
+ if (t === !1 || typeof t == "string") {
65
+ let n = typeof t == "string" ? t : `[Validation Error]: Некорректное значение для сигнала "${o}"`;
66
+ console.error(`%c${n}`, "color: orange; font-weight: bold;", {
67
+ received: e,
68
+ current: r
69
+ });
70
+ return;
71
+ }
72
+ }
73
+ let t = r;
74
+ r = e, n.onSignalChange?.(o, e, t), i.forEach((e) => n.pendingEffects.add(e)), n.isBatching || (n.isBatching = !0, queueMicrotask(() => {
75
+ let e = Array.from(n.pendingEffects);
76
+ n.pendingEffects.clear(), n.isBatching = !1, e.forEach((e) => e.run());
77
+ }));
78
+ },
79
+ subscribe(e) {
80
+ return n.effect(() => {
81
+ e(this.value);
82
+ });
83
+ }
84
+ };
85
+ }
86
+ effect(e) {
87
+ let t = this, n = {
88
+ cleanups: /* @__PURE__ */ new Set(),
89
+ run() {
90
+ this.cleanups.forEach((e) => e()), this.cleanups.clear();
91
+ let n = t.activeEffect;
92
+ t.activeEffect = this, t.safeRun(this, () => {
93
+ let t = e();
94
+ typeof t == "function" && this.cleanups.add(t);
95
+ }), t.activeEffect = n;
96
+ }
97
+ };
98
+ return this.allEffects.add(n), n.run(), () => {
99
+ n.cleanups.forEach((e) => e()), t.pendingEffects.delete(n), t.allEffects.delete(n);
100
+ };
101
+ }
102
+ computedCache = /* @__PURE__ */ new Map();
103
+ computed(e, t) {
104
+ if (this.computedCache.has(e)) return this.computedCache.get(e);
105
+ let n = this, r = this.signal(void 0, t || "unnamed_computed"), i = this.effect(() => {
106
+ let t = e();
107
+ r.value = t;
108
+ }), a = Array.from(this.allEffects)[this.allEffects.size - 1], o = {
109
+ get value() {
110
+ return r.value;
111
+ },
112
+ subscribe: (e) => r.subscribe(e),
113
+ destroy() {
114
+ i(), a && n.allEffects.delete(a), n.computedCache.delete(e);
115
+ }
116
+ };
117
+ return this.computedCache.set(e, o), o;
118
+ }
119
+ reactive(e, t = "reactive") {
120
+ if (this.proxyCache.has(e)) return this.proxyCache.get(e);
121
+ let n = this, r = /* @__PURE__ */ new Map(), i = new Proxy(e, {
122
+ get(e, i, a) {
123
+ n.activeEffect && (r.has(i) || r.set(i, /* @__PURE__ */ new Set()), r.get(i).add(n.activeEffect));
124
+ let o = Reflect.get(e, i, a);
125
+ return typeof o == "object" && o ? n.reactive(o, `${t}.${String(i)}`) : o;
126
+ },
127
+ set(e, i, a, o) {
128
+ let s = Reflect.get(e, i, o);
129
+ return s === a || s !== a && (Reflect.set(e, i, a, o), n.onSignalChange?.(`${t}.${String(i)}`, a, s), r.get(i)?.forEach((e) => n.isBatching ? n.pendingEffects.add(e) : e.run())), !0;
130
+ }
131
+ });
132
+ return this.proxyCache.set(e, i), i;
133
+ }
134
+ batch(e) {
135
+ e();
136
+ }
137
+ resource(e, t, n) {
138
+ let r = this.signal({
139
+ data: null,
140
+ loading: !0,
141
+ error: null
142
+ }, n || "unnamed_resource"), i = null, a = async (t) => {
143
+ i?.abort(), i = new AbortController();
144
+ let n = this.untrack(() => r.value.data);
145
+ r.value = {
146
+ data: n,
147
+ loading: !0,
148
+ error: null
149
+ };
150
+ try {
151
+ let n = await e(t, i.signal);
152
+ i.signal.aborted || (r.value = {
153
+ data: n,
154
+ loading: !1,
155
+ error: null
156
+ });
157
+ } catch (e) {
158
+ e.name !== "AbortError" && (r.value = {
159
+ data: null,
160
+ loading: !1,
161
+ error: e
162
+ });
163
+ }
164
+ };
165
+ return this.effect(() => {
166
+ let e = t ? t.value : void 0;
167
+ return a(e), () => i?.abort();
168
+ }), {
169
+ get data() {
170
+ return r.value.data;
171
+ },
172
+ get loading() {
173
+ return r.value.loading;
174
+ },
175
+ get error() {
176
+ return r.value.error;
177
+ },
178
+ get value() {
179
+ return r.value;
180
+ },
181
+ refetch: () => a(t ? t.value : void 0),
182
+ subscribe: (e) => r.subscribe(e)
183
+ };
184
+ }
185
+ untrack(e) {
186
+ let t = this.activeEffect;
187
+ this.activeEffect = null;
188
+ try {
189
+ return e();
190
+ } finally {
191
+ this.activeEffect = t;
192
+ }
193
+ }
194
+ setReactAdapters(e, t) {
195
+ this.reactAdapters = {
196
+ useState: e,
197
+ useEffect: t
198
+ };
199
+ }
200
+ use(e) {
201
+ if (!this.reactAdapters) throw Error("[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).");
202
+ if (!e || typeof e.subscribe != "function") {
203
+ let t = "\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n ";
204
+ throw console.error(t, { item: e }), Error(t);
205
+ }
206
+ let [t, n] = this.reactAdapters.useState(e.value);
207
+ return this.reactAdapters.useEffect(() => e.subscribe(n), [e]), t;
208
+ }
209
+ safeRun(e, t) {
210
+ try {
211
+ t();
212
+ } catch (e) {
213
+ console.error("%c[Reactive Error] %cОшибка в эффекте/computed:", "color: white; background: red; padding: 2px 4px; border-radius: 3px;", "font-weight: bold;", e);
214
+ }
215
+ }
216
+ }, l = (e, t = {}) => {
217
+ let n = t.ttl ?? 300 * 1e3, r = /* @__PURE__ */ new Map();
218
+ return async (t, i) => {
219
+ let a = typeof t == "object" && t ? JSON.stringify(t) : String(t), o = Date.now(), s = r.get(a);
220
+ if (s && o - s.timestamp < n) return s.data;
221
+ let c = await e(t, i);
222
+ return r.set(a, {
223
+ data: c,
224
+ timestamp: Date.now()
225
+ }), c;
226
+ };
227
+ }, u = (e, n) => {
228
+ let i = r(n);
229
+ t(() => {
230
+ i.current = n;
231
+ }, [n]), t(() => e.subscribe((e) => {
232
+ i.current(e);
233
+ }), [e]);
234
+ }, d = (r) => {
235
+ let i = n(() => typeof r == "function" ? r() : r, [r]), o = e((e) => i.subscribe(e), [i]), s = e(() => i.value, [i]);
236
+ return t(() => () => {
237
+ i && typeof i.destroy == "function" && i.destroy();
238
+ }, [i]), a(o, s);
239
+ }, f = (e) => {
240
+ let a = n(() => typeof e == "function" ? e() : e, [e]), [o, s] = i(a.value), c = r(s);
241
+ return t(() => {
242
+ c.current = s;
243
+ }, [s]), t(() => {
244
+ c.current(a.value);
245
+ let e = a.subscribe((e) => {
246
+ c.current(e);
247
+ });
248
+ return () => {
249
+ e(), a && typeof a.destroy == "function" && a.destroy();
250
+ };
251
+ }, [a]), o;
319
252
  };
320
- //# sourceMappingURL=index.mjs.map
253
+ //#endregion
254
+ export { o as BaseREService, s as BaseREServiceEnhanced, c as ReactiveEngine, u as useReactiveSubscription, d as useReactiveValue, f as useReactiveValue0, l as withCache };
255
+
256
+ //# sourceMappingURL=index.mjs.map