@pravosleva/reactive-engine 0.3.7-beta → 0.3.9-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.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.iife.js +1 -1
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +89 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("react");c=s(c,1);var l=class{engine;constructor(e){this.engine=e}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}},u=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}},d=class{activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;onSignalChange;reactAdapters={useState:c.useState,useEffect:c.useEffect};provide(e,t){typeof t==`function`&&!t.prototype?this.factories.set(e,t):this.services.set(e,t)}inject(e){if(!e)throw Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);let t=e;if(this.services.has(t))return this.services.get(t);try{let n=this.factories.get(t);if(n){let e=n(this);return this.services.set(t,e),e}if(typeof e==`function`&&e.prototype){let n=new e(this);return this.services.set(t,n),n}throw Error(`Service not found: ${String(e)}`)}catch(t){throw Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message||`No e?.message`}`)}}signal(e,t){let n=this,r=e,i=new Set,a=typeof t==`string`?{name:t}:t||{},o=a.name||`unnamed_signal`;return{get value(){return n.activeEffect&&i.add(n.activeEffect),r},set value(e){if(r===e)return;if(a.validate){let t=a.validate(e);if(t===!1||typeof t==`string`){let n=typeof t==`string`?t:`[Validation Error]: Некорректное значение для сигнала "${o}"`;console.error(`%c${n}`,`color: orange; font-weight: bold;`,{received:e,current:r});return}}let t=r;r=e,n.onSignalChange?.(o,e,t),i.forEach(e=>n.pendingEffects.add(e)),n.isBatching||(n.isBatching=!0,queueMicrotask(()=>{let e=Array.from(n.pendingEffects);n.pendingEffects.clear(),n.isBatching=!1,e.forEach(e=>e.run())}))},subscribe(e){return n.effect(()=>{e(this.value)})}}}effect(e,t){let n=this,r={label:t,cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let t=n.activeEffect;n.activeEffect=this,n.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),n.activeEffect=t}};return this.allEffects.add(r),r.run(),()=>{r.cleanups.forEach(e=>e()),n.pendingEffects.delete(r),n.allEffects.delete(r)}}computedCache=new Map;cleanupRegistry=new FinalizationRegistry(e=>{e()});computed(e,t){if(this.computedCache.has(e)){let t=this.computedCache.get(e)?.deref();if(t)return t}let n=this,r=this.signal(void 0,t||`unnamed_computed`),i=this.effect(()=>{let t=e();r.value=t}),a=Array.from(this.allEffects)[this.allEffects.size-1],o=()=>{i(),a&&n.allEffects.delete(a),n.computedCache.delete(e)},s={get value(){return r.value},subscribe:e=>r.subscribe(e),destroy(){o()}};return this.cleanupRegistry.register(s,o),this.computedCache.set(e,new WeakRef(s)),s}reactive(e,t=`reactive`){if(this.proxyCache.has(e))return this.proxyCache.get(e);let n=this,r=new Map,i=new Proxy(e,{get(e,i,a){n.activeEffect&&(r.has(i)||r.set(i,new Set),r.get(i).add(n.activeEffect));let o=Reflect.get(e,i,a);return typeof o==`object`&&o?n.reactive(o,`${t}.${String(i)}`):o},set(e,i,a,o){let s=Reflect.get(e,i,o);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}});return this.proxyCache.set(e,i),i}batch(e){e()}resource(e,t,n){let r=n&&typeof n==`object`,i=r?n.name:n||`unnamed_resource`,a=r?n:{},o=a.resetDataOnSourceChange??!0,s=a.responseValidate,c=this.signal({data:null,loading:!0,error:null},i),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("react");c=s(c,1);var l=class{engine;constructor(e){this.engine=e}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}},u=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}},d=class{activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;onSignalChange;reactAdapters={useState:c.useState,useEffect:c.useEffect};provide(e,t){typeof t==`function`&&!t.prototype?this.factories.set(e,t):this.services.set(e,t)}inject(e){if(!e)throw Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);let t=e;if(this.services.has(t))return this.services.get(t);try{let n=this.factories.get(t);if(n){let e=n(this);return this.services.set(t,e),e}if(typeof e==`function`&&e.prototype){let n=new e(this);return this.services.set(t,n),n}throw Error(`Service not found: ${String(e)}`)}catch(t){throw Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message||`No e?.message`}`)}}signal(e,t){let n=this,r=e,i=new Set,a=typeof t==`string`?{name:t}:t||{},o=a.name||`unnamed_signal`;return{get value(){return n.activeEffect&&i.add(n.activeEffect),r},set value(e){if(r===e)return;if(a.validate){let t=a.validate(e);if(t===!1||typeof t==`string`){let n=typeof t==`string`?t:`[Validation Error]: Некорректное значение для сигнала "${o}"`;console.error(`%c${n}`,`color: orange; font-weight: bold;`,{received:e,current:r});return}}let t=r;r=e,n.onSignalChange?.(o,e,t),i.forEach(e=>n.pendingEffects.add(e)),n.isBatching||(n.isBatching=!0,queueMicrotask(()=>{let e=Array.from(n.pendingEffects);n.pendingEffects.clear(),n.isBatching=!1,e.forEach(e=>e.run())}))},subscribe(e){return n.effect(()=>{e(this.value)})}}}effect(e,t){let n=this,r={label:t,cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let t=n.activeEffect;n.activeEffect=this,n.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),n.activeEffect=t}};return this.allEffects.add(r),r.run(),()=>{r.cleanups.forEach(e=>e()),n.pendingEffects.delete(r),n.allEffects.delete(r)}}computedCache=new Map;cleanupRegistry=new FinalizationRegistry(e=>{e()});computed(e,t){if(this.computedCache.has(e)){let t=this.computedCache.get(e)?.deref();if(t)return t}let n=this,r=this.signal(void 0,t||`unnamed_computed`),i=this.effect(()=>{let t=e();r.value=t}),a=Array.from(this.allEffects)[this.allEffects.size-1],o=()=>{i(),a&&n.allEffects.delete(a),n.computedCache.delete(e)},s={get value(){return r.value},subscribe:e=>r.subscribe(e),destroy(){o()}};return this.cleanupRegistry.register(s,o),this.computedCache.set(e,new WeakRef(s)),s}reactive(e,t=`reactive`){if(this.proxyCache.has(e))return this.proxyCache.get(e);let n=this,r=new Map,i=new Proxy(e,{get(e,i,a){n.activeEffect&&(r.has(i)||r.set(i,new Set),r.get(i).add(n.activeEffect));let o=Reflect.get(e,i,a);return typeof o==`object`&&o?n.reactive(o,`${t}.${String(i)}`):o},set(e,i,a,o){let s=Reflect.get(e,i,o);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}});return this.proxyCache.set(e,i),i}batch(e){e()}delay(e,t){return new Promise((n,r)=>{if(t.aborted)return r(new DOMException(`Aborted`,`AbortError`));let i=setTimeout(()=>{t.removeEventListener(`abort`,a),n()},e),a=()=>{clearTimeout(i),r(new DOMException(`Aborted`,`AbortError`))};t.addEventListener(`abort`,a,{once:!0})})}resource(e,t,n){let r=n&&typeof n==`object`,i=r?n.name:n||`unnamed_resource`,a=r?n:{},o=a.resetDataOnSourceChange??!0,s=a.responseValidate,c=a.retryCount??0,l=a.retryDelay??1e3,u=a.isExponentialBackoffEnabled??!1,d=a.maxRetryDelay??3e4,f=a.timeout,p=this.signal({data:null,loading:!0,error:null,isRetrying:!1},i),m=async(t,n,r=!1)=>{let a=r&&o?null:this.untrack(()=>p.value.data);p.value={data:a,loading:!0,error:null,isRetrying:!1};for(let r=0;r<=c;r++){let a=n;try{if(n.aborted)return;if(f&&f>0){let e=AbortSignal.timeout(f);a=AbortSignal.any([n,e])}let r=await e(t,a);if(a.aborted){if(!n.aborted)throw new DOMException(`The operation timed out.`,`TimeoutError`);return}if(s){let e=s(r);if(e===!1||typeof e==`string`){if(a.aborted)return;p.value={data:null,loading:!1,error:Error(typeof e==`string`?e:`Validation failed`),isRetrying:!1};return}}if(a.aborted)return;p.value={data:r,loading:!1,error:null,isRetrying:!1};return}catch(e){if(n.aborted||e.name===`AbortError`&&n.aborted)return;let t=e.name===`TimeoutError`||e.name===`AbortError`||e.message?.includes(`timeout`);if(r===c){if(n.aborted)return;let r=t?Error(`Request timed out after ${f}ms`):e;p.value={data:null,loading:!1,error:r,isRetrying:!1}}else{if(n.aborted)return;let e=u?l*2**r:l;e=Math.min(e,d);let a=Math.random()*200;e+=a,p.value={data:p.value.data,loading:!0,error:null,isRetrying:!0},console.warn(`[Resource Retry] "${i}" сбой по ${t?`таймауту`:`ошибке сети`}. Попытка ${r+1}/${c+1}...`);try{if(await this.delay(e,n),n.aborted)return}catch{return}}}}},h=null;return this.effect(()=>{let e=t?t.value:void 0,n=new AbortController;return h=n,this.untrack(()=>{m(e,n.signal,!0)}),()=>n.abort()}),{get data(){return p.value.data},get loading(){return p.value.loading},get error(){return p.value.error},get isRetrying(){return p.value.isRetrying},get value(){return p.value},refetch:()=>{h?.abort(),h=new AbortController,m(t?t.value:void 0,h.signal,!1)},subscribe:e=>p.subscribe(e)}}untrack(e){let t=this.activeEffect;this.activeEffect=null;try{return e()}finally{this.activeEffect=t}}setReactAdapters(e,t){this.reactAdapters={useState:e,useEffect:t}}use(e){if(!this.reactAdapters)throw Error(`[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).`);if(!e||typeof e.subscribe!=`function`){let t=`
|
|
2
2
|
[Reactive Error]: engine.use() получил некорректный объект!
|
|
3
3
|
Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.
|
|
4
4
|
Проверьте, что в классе написано: public mySignal = this.engine.signal(...)
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../src/decorators/createObserver.tsx","../src/decorators/withCache.ts","../src/hooks/useReactiveSubscription.ts","../src/hooks/useReactiveValue.ts","../src/hooks/useReactiveValue0.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\n// export abstract class BaseService {\n// constructor(protected engine: ReactiveEngine) {\n// // Вызываем метод инициализации, который можно переопределить в наследниках\n// this.onInit();\n// }\n\n// // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n// // Удобно для инициализации сигналов, которые зависят от engine.\n// protected onInit(): void {}\n\n// // NOTE: Полезные хелперы для сокращения кода\n// protected createSignal<T>(val: T, name?: string) {\n// return this.engine.signal(val, name);\n// }\n\n// protected createComputed<T>(fn: () => T) {\n// return this.engine.computed(fn);\n// }\n// }\n\nexport abstract class BaseREService {\n constructor(protected engine: ReactiveEngine) { } // Просто сохраняем engine\n\n protected createNumericSignal(val: number, name: string) {\n return this.engine.signal(val, {\n name,\n validate: (v) => typeof v === 'number' && !isNaN(v)\n });\n }\n}\n","import { ReactiveEngine } from './core';\n\nexport abstract class BaseREServiceEnhanced {\n constructor(protected engine: ReactiveEngine) {\n setTimeout(() => this.onInit(), 0)\n }\n\n protected onInit(): void { }\n}\n","import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react';\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\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\n/**\n * Функция для очистки эффекта.\n * @typedef {() => void} CleanupFn\n */\n\n/**\n * Функция эффекта, которая может возвращать функцию очистки.\n * @typedef {() => CleanupFn | void} EffectFn\n */\n\n/**\n * Токен для зависимости.\n * @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>\n */\n\n/**\n * Фабрика для создания сервиса.\n * @template T\n * @typedef {(engine: ReactiveEngine) => T} Factory<T>\n */\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n /**\n * Запуск эффекта.\n * @function run\n * @returns {void}\n */\n run: () => void;\n\n /**\n * Коллекция функций очистки.\n * @type {Set<CleanupFn>}\n */\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для состояния ресурса.\n * @template T Data format\n * @interface ResourceState<T>\n */\nexport interface ResourceState<T> {\n /**\n * Данные ресурса.\n * @type {T | null}\n */\n data: T | null;\n\n /**\n * Состояние загрузки ресурса.\n * @type {boolean}\n */\n loading: boolean;\n\n /**\n * Ошибка ресурса.\n * @type {any | null}\n */\n error: any | null;\n}\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\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\nexport interface ResourceOptions<T> {\n name: string;\n /** Автоматически сбрасывать data в null при изменении source. По умолчанию: true */\n resetDataOnSourceChange?: boolean;\n /** Функция для валидации успешного ответа сервера перед его сохранением в стейт.\n *\n * Варианты возвращаемого значения:\n * - true 👉 все ок\n * - false 👉 в error попадет стандартный текст ошибки\n * - string 👉 попадет в поле error\n * */\n responseValidate?: (responseData: T) => boolean | string;\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 * @template T\n * @interface SignalOptions<T>\n */\nexport interface SignalOptions<T> {\n /**\n * Имя сигнала.\n * @type {string}\n */\n name?: string;\n\n /**\n * Валидатор значения сигнала.\n * @function validate\n * @param {T} val - Значение для валидации.\n * @returns {boolean | string} - Результат валидации.\n */\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\n/**\n * Класс реактивного движка.\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 // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\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) {\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) => 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 * Создание асинхронного ресурса. Типы на входе: <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/src/examples/20-resource/Example20.tsx Реализация базового компонента Example20}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/src/examples/21-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 * @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>,\n ): Resource<T> {\n // 1. Обработка полиморфного аргумента (строка или объект) для обратной совместимости\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>)\n : {};\n\n // Дефолтные настройки\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const validate = options.responseValidate;\n\n const state = this.signal<ResourceState<T>>({ data: null, loading: true, error: null }, signalName);\n let controller: AbortController | null = null;\n\n const load = async (sValue: S, isSourceChange = false) => {\n controller?.abort();\n controller = new AbortController();\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 };\n\n try {\n const data = await fetcher(sValue, controller.signal);\n\n if (!controller.signal.aborted) {\n // 2. ВАЛИДАЦИЯ ОТВЕТА СЕРВЕРА\n if (validate) {\n const validationResult = validate(data);\n\n // Если валидация вернула false или строку с ошибкой\n if (validationResult === false || typeof validationResult === 'string') {\n const errorMsg = typeof validationResult === 'string'\n ? validationResult\n : 'Validation failed for response data';\n\n state.value = { data: null, loading: false, error: new Error(errorMsg) };\n return;\n }\n }\n\n // Если валидация пройдена (или отсутствует), записываем данные\n state.value = { data, loading: false, error: null };\n }\n } catch (e: any) {\n if (e.name !== 'AbortError') {\n state.value = { data: null, loading: false, error: e };\n }\n }\n };\n\n this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n load(sValue, true); // При изменении источника передаем true\n\n return () => controller?.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 value() { return state.value; },\n refetch: () => load(source ? source.value : undefined as S, false), // При ручном рефетче передаем false\n subscribe: (cb) => 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 * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n // Импортируйте useState и useEffect на верхнем уровне файла из 'react'\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\n this.reactAdapters = { useState, useEffect };\n }\n\n\n /**\n * Использование реактивного значения в React компоненте.\n * @template T\n * @function use\n * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.\n * @returns {T} - Значение реактивного объекта.\n * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\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","import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react';\nimport { ReactiveEngine } from '../core';\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n","interface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\ninterface CacheOptions {\n /** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */\n ttl?: number;\n}\n\n/**\n * Декоратор для создания кэширующего fetcher-а для engine.resource\n * @param fetcher Оригинальная асинхронная функция запроса\n * @param options Настройки кэширования\n * @source\n */\nexport const withCache = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: CacheOptions = {}\n) => {\n const ttl = options.ttl ?? 5 * 60 * 1000; // Дефолтный TTL: 5 минут\n const cache = new Map<string, CacheEntry<T>>();\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n // 1. Генерируем уникальный ключ на основе зависимостей (массива или объекта)\n const cacheKey = typeof source === 'object' && source !== null\n ? JSON.stringify(source)\n : String(source);\n\n const now = Date.now();\n const cached = cache.get(cacheKey);\n\n // 2. Проверяем, есть ли валидный (не устаревший) кэш\n if (cached && (now - cached.timestamp < ttl)) {\n return cached.data;\n }\n\n // 3. Если кэша нет или он устарел — делаем реальный сетевой запрос\n const freshData = await fetcher(source, signal);\n\n // 4. Сохраняем свежие данные и метку времени в кэш\n cache.set(cacheKey, {\n data: freshData,\n timestamp: Date.now() // берем актуальное время после завершения запроса\n });\n\n return freshData;\n };\n};\n","import { useEffect, useRef } from 'react';\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val);\n });\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe;\n }, [signal]);\n};\n","import { useSyncExternalStore, useCallback, useMemo, useEffect } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n * Поддерживает React 18+ и ленивые фабрики без утечек памяти.\n *\n * @source\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n // 1. Всегда получаем чистый объект, даже если передали функцию-фабрику () => engine.computed(...)\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n // 2. Стабилизируем функции для useSyncExternalStore\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback);\n },\n [reactiveItem]\n );\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value;\n }, [reactiveItem]);\n\n // 3. Автоматически подчищаем динамические вычисления при размонтировании\n useEffect(() => {\n return () => {\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n","import { useState, useEffect, useRef, useMemo } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Fallback хук для извлечения значения из сигналов. Поддерживает React 16.8+.\n *\n * @source\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n const [state, setState] = useState<T>(reactiveItem.value);\n const setStateRef = useRef(setState);\n\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n setStateRef.current(reactiveItem.value);\n\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return () => {\n unsubscribe();\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../src/decorators/createObserver.tsx","../src/decorators/withCache.ts","../src/hooks/useReactiveSubscription.ts","../src/hooks/useReactiveValue.ts","../src/hooks/useReactiveValue0.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\n// export abstract class BaseService {\n// constructor(protected engine: ReactiveEngine) {\n// // Вызываем метод инициализации, который можно переопределить в наследниках\n// this.onInit();\n// }\n\n// // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n// // Удобно для инициализации сигналов, которые зависят от engine.\n// protected onInit(): void {}\n\n// // NOTE: Полезные хелперы для сокращения кода\n// protected createSignal<T>(val: T, name?: string) {\n// return this.engine.signal(val, name);\n// }\n\n// protected createComputed<T>(fn: () => T) {\n// return this.engine.computed(fn);\n// }\n// }\n\nexport abstract class BaseREService {\n constructor(protected engine: ReactiveEngine) { } // Просто сохраняем engine\n\n protected createNumericSignal(val: number, name: string) {\n return this.engine.signal(val, {\n name,\n validate: (v) => typeof v === 'number' && !isNaN(v)\n });\n }\n}\n","import { ReactiveEngine } from './core';\n\nexport abstract class BaseREServiceEnhanced {\n constructor(protected engine: ReactiveEngine) {\n setTimeout(() => this.onInit(), 0)\n }\n\n protected onInit(): void { }\n}\n","import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react';\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\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\n/**\n * Функция для очистки эффекта.\n * @typedef {() => void} CleanupFn\n */\n\n/**\n * Функция эффекта, которая может возвращать функцию очистки.\n * @typedef {() => CleanupFn | void} EffectFn\n */\n\n/**\n * Токен для зависимости.\n * @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>\n */\n\n/**\n * Фабрика для создания сервиса.\n * @template T\n * @typedef {(engine: ReactiveEngine) => T} Factory<T>\n */\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n /**\n * Запуск эффекта.\n * @function run\n * @returns {void}\n */\n run: () => void;\n\n /**\n * Коллекция функций очистки.\n * @type {Set<CleanupFn>}\n */\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для состояния ресурса.\n * @template T Data format\n * @interface ResourceState<T>\n */\nexport interface ResourceState<T> {\n /**\n * Данные ресурса.\n * @type {T | null}\n */\n data: T | null;\n\n /**\n * Состояние загрузки ресурса.\n * @type {boolean}\n */\n loading: boolean;\n\n /**\n * Ошибка ресурса.\n * @type {any | null}\n */\n error: any | null;\n\n /** Флаг активного процесса повторных попыток после сбоя сети */\n isRetrying: boolean;\n}\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\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\nexport interface ResourceOptions<T> {\n name: string;\n /** Автоматически сбрасывать data в null при изменении source. По умолчанию: true */\n resetDataOnSourceChange?: boolean;\n /** Функция для валидации успешного ответа сервера перед его сохранением в стейт.\n *\n * Варианты возвращаемого значения:\n * - true 👉 все ок\n * - false 👉 в error попадет стандартный текст ошибки\n * - string 👉 попадет в поле error\n * */\n responseValidate?: (responseData: T) => boolean | string;\n\n // НАСТРОЙКИ RETRY:\n retryCount?: number;\n retryDelay?: number;\n /** Включить экспоненциальное увеличение задержки (каждая попытка ждет в 2 раза дольше). По умолчанию: false */\n isExponentialBackoffEnabled?: boolean;\n /** Максимальный лимит ожидания между попытками в миллисекундах. По умолчанию: 30000 (30 секунд) */\n maxRetryDelay?: number;\n /** Максимальное время ожидания ответа сервера в миллисекундах. По умолчанию: отсутствует (бесконечно) */\n timeout?: number;\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 * @template T\n * @interface SignalOptions<T>\n */\nexport interface SignalOptions<T> {\n /**\n * Имя сигнала.\n * @type {string}\n */\n name?: string;\n\n /**\n * Валидатор значения сигнала.\n * @function validate\n * @param {T} val - Значение для валидации.\n * @returns {boolean | string} - Результат валидации.\n */\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\n/**\n * Класс реактивного движка.\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 // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\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) {\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) => 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/src/examples/20-resource/Example20.tsx Реализация базового компонента Example20}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/src/examples/21-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 * @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>,\n ): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>)\n : {};\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const validate = options.responseValidate;\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 // Извлекаем настройку таймаута\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 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 // Локальные переменные для очистки слушателей таймаута\n let timeoutController: AbortController | null = null;\n let combinedSignal = effectSignal;\n\n try {\n if (effectSignal.aborted) return; // Внешний guard-шлагбаум\n\n // ИСПРАВЛЕНИЕ: Интеграция таймаута для текущей попытки фетча\n if (timeoutMs && timeoutMs > 0) {\n // Создаем нативный сигнал таймаута\n const timeoutSignal = AbortSignal.timeout(timeoutMs);[1, 2]\n\n // Объединяем сигнал эффекта (смена source) и сигнал таймаута [3]\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);[3]\n }\n\n // Передаем объединенный сигнал в пользовательский fetcher\n const data = await fetcher(sValue, combinedSignal);\n\n // Проверяем, не прилетал ли abort (внешний или таймаут) во время фетча\n if (combinedSignal.aborted) {\n // Если это был таймаут, а не внешняя смена источника — пробрасываем в catch для запуска ретраев!\n if (!effectSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError');\n }\n return; // Если внешняя отмена — просто тихо выходим\n }\n\n if (validate) {\n const validationResult = validate(data);\n if (validationResult === false || typeof validationResult === 'string') {\n if (combinedSignal.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 (combinedSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n\n } catch (e: any) {\n // Если это внешняя отмена (смена источника или refetch) — мгновенно умираем\n if (effectSignal.aborted || e.name === 'AbortError' && effectSignal.aborted) {\n return;\n }\n\n // Если это был таймаут запроса ИЛИ обычная сетевая ошибка — идем на круг ретраев!\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n if (attempt === retryCount) {\n if (effectSignal.aborted) return;\n // Если попытки исчерпаны, записываем ошибку таймаута или сети в стейт\n const finalError = isTimeout ? new Error(`Request timed out after ${timeoutMs}ms`) : e;\n state.value = { data: null, loading: false, error: finalError, isRetrying: false };\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 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 // Паузу задержки слушаем ТОЛЬКО по внешнему сигналу эффекта (смена источника)\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) => 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 * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n // Импортируйте useState и useEffect на верхнем уровне файла из 'react'\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\n this.reactAdapters = { useState, useEffect };\n }\n\n\n /**\n * Использование реактивного значения в React компоненте.\n * @template T\n * @function use\n * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.\n * @returns {T} - Значение реактивного объекта.\n * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\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","import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react';\nimport { ReactiveEngine } from '../core';\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n","interface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\ninterface CacheOptions {\n /** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */\n ttl?: number;\n}\n\n/**\n * Декоратор для создания кэширующего fetcher-а для engine.resource\n * @param fetcher Оригинальная асинхронная функция запроса\n * @param options Настройки кэширования\n * @source\n */\nexport const withCache = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: CacheOptions = {}\n) => {\n const ttl = options.ttl ?? 5 * 60 * 1000; // Дефолтный TTL: 5 минут\n const cache = new Map<string, CacheEntry<T>>();\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n // 1. Генерируем уникальный ключ на основе зависимостей (массива или объекта)\n const cacheKey = typeof source === 'object' && source !== null\n ? JSON.stringify(source)\n : String(source);\n\n const now = Date.now();\n const cached = cache.get(cacheKey);\n\n // 2. Проверяем, есть ли валидный (не устаревший) кэш\n if (cached && (now - cached.timestamp < ttl)) {\n return cached.data;\n }\n\n // 3. Если кэша нет или он устарел — делаем реальный сетевой запрос\n const freshData = await fetcher(source, signal);\n\n // 4. Сохраняем свежие данные и метку времени в кэш\n cache.set(cacheKey, {\n data: freshData,\n timestamp: Date.now() // берем актуальное время после завершения запроса\n });\n\n return freshData;\n };\n};\n","import { useEffect, useRef } from 'react';\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val);\n });\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe;\n }, [signal]);\n};\n","import { useSyncExternalStore, useCallback, useMemo, useEffect } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n * Поддерживает React 18+ и ленивые фабрики без утечек памяти.\n *\n * @source\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n // 1. Всегда получаем чистый объект, даже если передали функцию-фабрику () => engine.computed(...)\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n // 2. Стабилизируем функции для useSyncExternalStore\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback);\n },\n [reactiveItem]\n );\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value;\n }, [reactiveItem]);\n\n // 3. Автоматически подчищаем динамические вычисления при размонтировании\n useEffect(() => {\n return () => {\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n","import { useState, useEffect, useRef, useMemo } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Fallback хук для извлечения значения из сигналов. Поддерживает React 16.8+.\n *\n * @source\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n const [state, setState] = useState<T>(reactiveItem.value);\n const setStateRef = useRef(setState);\n\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n setStateRef.current(reactiveItem.value);\n\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return () => {\n unsubscribe();\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -256,6 +256,16 @@ export declare class ReactiveEngine {
|
|
|
256
256
|
* @source
|
|
257
257
|
*/
|
|
258
258
|
batch(fn: () => void): void;
|
|
259
|
+
/**
|
|
260
|
+
* Вспомогательная функция для задержки (sleep), чувствительная к AbortSignal
|
|
261
|
+
*
|
|
262
|
+
* Вызов new DOMException('Aborted', 'AbortError') гарантирует,
|
|
263
|
+
* что ваша кастомная пауза между ретраями this.delay притворяется для движка JavaScript
|
|
264
|
+
* точно таким же нативным процессом отмены, как и fetch().
|
|
265
|
+
* Это делает реактивное ядро бесшовным и избавляет от необходимости
|
|
266
|
+
* писать кучу разных проверок под каждый тип ошибки.
|
|
267
|
+
*/
|
|
268
|
+
private delay;
|
|
259
269
|
/**
|
|
260
270
|
* Создание асинхронного ресурса. Типы на входе: <T - формат ответа, S - источник изменений (сигнал либо computed-кортеж из зачений сигналов .value)>
|
|
261
271
|
*
|
|
@@ -370,6 +380,14 @@ export declare interface ResourceOptions<T> {
|
|
|
370
380
|
* - string 👉 попадет в поле error
|
|
371
381
|
* */
|
|
372
382
|
responseValidate?: (responseData: T) => boolean | string;
|
|
383
|
+
retryCount?: number;
|
|
384
|
+
retryDelay?: number;
|
|
385
|
+
/** Включить экспоненциальное увеличение задержки (каждая попытка ждет в 2 раза дольше). По умолчанию: false */
|
|
386
|
+
isExponentialBackoffEnabled?: boolean;
|
|
387
|
+
/** Максимальный лимит ожидания между попытками в миллисекундах. По умолчанию: 30000 (30 секунд) */
|
|
388
|
+
maxRetryDelay?: number;
|
|
389
|
+
/** Максимальное время ожидания ответа сервера в миллисекундах. По умолчанию: отсутствует (бесконечно) */
|
|
390
|
+
timeout?: number;
|
|
373
391
|
}
|
|
374
392
|
|
|
375
393
|
export declare interface ResourceState<T> {
|
|
@@ -399,6 +417,8 @@ export declare interface ResourceState<T> {
|
|
|
399
417
|
* @type {any | null}
|
|
400
418
|
*/
|
|
401
419
|
error: any | null;
|
|
420
|
+
/** Флаг активного процесса повторных попыток после сбоя сети */
|
|
421
|
+
isRetrying: boolean;
|
|
402
422
|
}
|
|
403
423
|
|
|
404
424
|
/**
|
package/dist/index.iife.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var ReactiveEngineLib=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t,1);var l=class{engine;constructor(e){this.engine=e}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}},u=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}},d=class{activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;onSignalChange;reactAdapters={useState:t.useState,useEffect:t.useEffect};provide(e,t){typeof t==`function`&&!t.prototype?this.factories.set(e,t):this.services.set(e,t)}inject(e){if(!e)throw Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);let t=e;if(this.services.has(t))return this.services.get(t);try{let n=this.factories.get(t);if(n){let e=n(this);return this.services.set(t,e),e}if(typeof e==`function`&&e.prototype){let n=new e(this);return this.services.set(t,n),n}throw Error(`Service not found: ${String(e)}`)}catch(t){throw Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message||`No e?.message`}`)}}signal(e,t){let n=this,r=e,i=new Set,a=typeof t==`string`?{name:t}:t||{},o=a.name||`unnamed_signal`;return{get value(){return n.activeEffect&&i.add(n.activeEffect),r},set value(e){if(r===e)return;if(a.validate){let t=a.validate(e);if(t===!1||typeof t==`string`){let n=typeof t==`string`?t:`[Validation Error]: Некорректное значение для сигнала "${o}"`;console.error(`%c${n}`,`color: orange; font-weight: bold;`,{received:e,current:r});return}}let t=r;r=e,n.onSignalChange?.(o,e,t),i.forEach(e=>n.pendingEffects.add(e)),n.isBatching||(n.isBatching=!0,queueMicrotask(()=>{let e=Array.from(n.pendingEffects);n.pendingEffects.clear(),n.isBatching=!1,e.forEach(e=>e.run())}))},subscribe(e){return n.effect(()=>{e(this.value)})}}}effect(e,t){let n=this,r={label:t,cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let t=n.activeEffect;n.activeEffect=this,n.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),n.activeEffect=t}};return this.allEffects.add(r),r.run(),()=>{r.cleanups.forEach(e=>e()),n.pendingEffects.delete(r),n.allEffects.delete(r)}}computedCache=new Map;cleanupRegistry=new FinalizationRegistry(e=>{e()});computed(e,t){if(this.computedCache.has(e)){let t=this.computedCache.get(e)?.deref();if(t)return t}let n=this,r=this.signal(void 0,t||`unnamed_computed`),i=this.effect(()=>{let t=e();r.value=t}),a=Array.from(this.allEffects)[this.allEffects.size-1],o=()=>{i(),a&&n.allEffects.delete(a),n.computedCache.delete(e)},s={get value(){return r.value},subscribe:e=>r.subscribe(e),destroy(){o()}};return this.cleanupRegistry.register(s,o),this.computedCache.set(e,new WeakRef(s)),s}reactive(e,t=`reactive`){if(this.proxyCache.has(e))return this.proxyCache.get(e);let n=this,r=new Map,i=new Proxy(e,{get(e,i,a){n.activeEffect&&(r.has(i)||r.set(i,new Set),r.get(i).add(n.activeEffect));let o=Reflect.get(e,i,a);return typeof o==`object`&&o?n.reactive(o,`${t}.${String(i)}`):o},set(e,i,a,o){let s=Reflect.get(e,i,o);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}});return this.proxyCache.set(e,i),i}batch(e){e()}resource(e,t,n){let r=n&&typeof n==`object`,i=r?n.name:n||`unnamed_resource`,a=r?n:{},o=a.resetDataOnSourceChange??!0,s=a.responseValidate,c=this.signal({data:null,loading:!0,error:null},i),
|
|
1
|
+
var ReactiveEngineLib=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t,1);var l=class{engine;constructor(e){this.engine=e}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}},u=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}},d=class{activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;onSignalChange;reactAdapters={useState:t.useState,useEffect:t.useEffect};provide(e,t){typeof t==`function`&&!t.prototype?this.factories.set(e,t):this.services.set(e,t)}inject(e){if(!e)throw Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);let t=e;if(this.services.has(t))return this.services.get(t);try{let n=this.factories.get(t);if(n){let e=n(this);return this.services.set(t,e),e}if(typeof e==`function`&&e.prototype){let n=new e(this);return this.services.set(t,n),n}throw Error(`Service not found: ${String(e)}`)}catch(t){throw Error(`[DI Error]: Не удалось создать сервис ${String(e)}. Ошибка: ${t?.message||`No e?.message`}`)}}signal(e,t){let n=this,r=e,i=new Set,a=typeof t==`string`?{name:t}:t||{},o=a.name||`unnamed_signal`;return{get value(){return n.activeEffect&&i.add(n.activeEffect),r},set value(e){if(r===e)return;if(a.validate){let t=a.validate(e);if(t===!1||typeof t==`string`){let n=typeof t==`string`?t:`[Validation Error]: Некорректное значение для сигнала "${o}"`;console.error(`%c${n}`,`color: orange; font-weight: bold;`,{received:e,current:r});return}}let t=r;r=e,n.onSignalChange?.(o,e,t),i.forEach(e=>n.pendingEffects.add(e)),n.isBatching||(n.isBatching=!0,queueMicrotask(()=>{let e=Array.from(n.pendingEffects);n.pendingEffects.clear(),n.isBatching=!1,e.forEach(e=>e.run())}))},subscribe(e){return n.effect(()=>{e(this.value)})}}}effect(e,t){let n=this,r={label:t,cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let t=n.activeEffect;n.activeEffect=this,n.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),n.activeEffect=t}};return this.allEffects.add(r),r.run(),()=>{r.cleanups.forEach(e=>e()),n.pendingEffects.delete(r),n.allEffects.delete(r)}}computedCache=new Map;cleanupRegistry=new FinalizationRegistry(e=>{e()});computed(e,t){if(this.computedCache.has(e)){let t=this.computedCache.get(e)?.deref();if(t)return t}let n=this,r=this.signal(void 0,t||`unnamed_computed`),i=this.effect(()=>{let t=e();r.value=t}),a=Array.from(this.allEffects)[this.allEffects.size-1],o=()=>{i(),a&&n.allEffects.delete(a),n.computedCache.delete(e)},s={get value(){return r.value},subscribe:e=>r.subscribe(e),destroy(){o()}};return this.cleanupRegistry.register(s,o),this.computedCache.set(e,new WeakRef(s)),s}reactive(e,t=`reactive`){if(this.proxyCache.has(e))return this.proxyCache.get(e);let n=this,r=new Map,i=new Proxy(e,{get(e,i,a){n.activeEffect&&(r.has(i)||r.set(i,new Set),r.get(i).add(n.activeEffect));let o=Reflect.get(e,i,a);return typeof o==`object`&&o?n.reactive(o,`${t}.${String(i)}`):o},set(e,i,a,o){let s=Reflect.get(e,i,o);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}});return this.proxyCache.set(e,i),i}batch(e){e()}delay(e,t){return new Promise((n,r)=>{if(t.aborted)return r(new DOMException(`Aborted`,`AbortError`));let i=setTimeout(()=>{t.removeEventListener(`abort`,a),n()},e),a=()=>{clearTimeout(i),r(new DOMException(`Aborted`,`AbortError`))};t.addEventListener(`abort`,a,{once:!0})})}resource(e,t,n){let r=n&&typeof n==`object`,i=r?n.name:n||`unnamed_resource`,a=r?n:{},o=a.resetDataOnSourceChange??!0,s=a.responseValidate,c=a.retryCount??0,l=a.retryDelay??1e3,u=a.isExponentialBackoffEnabled??!1,d=a.maxRetryDelay??3e4,f=a.timeout,p=this.signal({data:null,loading:!0,error:null,isRetrying:!1},i),m=async(t,n,r=!1)=>{let a=r&&o?null:this.untrack(()=>p.value.data);p.value={data:a,loading:!0,error:null,isRetrying:!1};for(let r=0;r<=c;r++){let a=n;try{if(n.aborted)return;if(f&&f>0){let e=AbortSignal.timeout(f);a=AbortSignal.any([n,e])}let r=await e(t,a);if(a.aborted){if(!n.aborted)throw new DOMException(`The operation timed out.`,`TimeoutError`);return}if(s){let e=s(r);if(e===!1||typeof e==`string`){if(a.aborted)return;p.value={data:null,loading:!1,error:Error(typeof e==`string`?e:`Validation failed`),isRetrying:!1};return}}if(a.aborted)return;p.value={data:r,loading:!1,error:null,isRetrying:!1};return}catch(e){if(n.aborted||e.name===`AbortError`&&n.aborted)return;let t=e.name===`TimeoutError`||e.name===`AbortError`||e.message?.includes(`timeout`);if(r===c){if(n.aborted)return;let r=t?Error(`Request timed out after ${f}ms`):e;p.value={data:null,loading:!1,error:r,isRetrying:!1}}else{if(n.aborted)return;let e=u?l*2**r:l;e=Math.min(e,d);let a=Math.random()*200;e+=a,p.value={data:p.value.data,loading:!0,error:null,isRetrying:!0},console.warn(`[Resource Retry] "${i}" сбой по ${t?`таймауту`:`ошибке сети`}. Попытка ${r+1}/${c+1}...`);try{if(await this.delay(e,n),n.aborted)return}catch{return}}}}},h=null;return this.effect(()=>{let e=t?t.value:void 0,n=new AbortController;return h=n,this.untrack(()=>{m(e,n.signal,!0)}),()=>n.abort()}),{get data(){return p.value.data},get loading(){return p.value.loading},get error(){return p.value.error},get isRetrying(){return p.value.isRetrying},get value(){return p.value},refetch:()=>{h?.abort(),h=new AbortController,m(t?t.value:void 0,h.signal,!1)},subscribe:e=>p.subscribe(e)}}untrack(e){let t=this.activeEffect;this.activeEffect=null;try{return e()}finally{this.activeEffect=t}}setReactAdapters(e,t){this.reactAdapters={useState:e,useEffect:t}}use(e){if(!this.reactAdapters)throw Error(`[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).`);if(!e||typeof e.subscribe!=`function`){let t=`
|
|
2
2
|
[Reactive Error]: engine.use() получил некорректный объект!
|
|
3
3
|
Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.
|
|
4
4
|
Проверьте, что в классе написано: public mySignal = this.engine.signal(...)
|
package/dist/index.iife.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.iife.js","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../src/decorators/createObserver.tsx","../src/decorators/withCache.ts","../src/hooks/useReactiveSubscription.ts","../src/hooks/useReactiveValue.ts","../src/hooks/useReactiveValue0.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\n// export abstract class BaseService {\n// constructor(protected engine: ReactiveEngine) {\n// // Вызываем метод инициализации, который можно переопределить в наследниках\n// this.onInit();\n// }\n\n// // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n// // Удобно для инициализации сигналов, которые зависят от engine.\n// protected onInit(): void {}\n\n// // NOTE: Полезные хелперы для сокращения кода\n// protected createSignal<T>(val: T, name?: string) {\n// return this.engine.signal(val, name);\n// }\n\n// protected createComputed<T>(fn: () => T) {\n// return this.engine.computed(fn);\n// }\n// }\n\nexport abstract class BaseREService {\n constructor(protected engine: ReactiveEngine) { } // Просто сохраняем engine\n\n protected createNumericSignal(val: number, name: string) {\n return this.engine.signal(val, {\n name,\n validate: (v) => typeof v === 'number' && !isNaN(v)\n });\n }\n}\n","import { ReactiveEngine } from './core';\n\nexport abstract class BaseREServiceEnhanced {\n constructor(protected engine: ReactiveEngine) {\n setTimeout(() => this.onInit(), 0)\n }\n\n protected onInit(): void { }\n}\n","import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react';\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\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\n/**\n * Функция для очистки эффекта.\n * @typedef {() => void} CleanupFn\n */\n\n/**\n * Функция эффекта, которая может возвращать функцию очистки.\n * @typedef {() => CleanupFn | void} EffectFn\n */\n\n/**\n * Токен для зависимости.\n * @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>\n */\n\n/**\n * Фабрика для создания сервиса.\n * @template T\n * @typedef {(engine: ReactiveEngine) => T} Factory<T>\n */\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n /**\n * Запуск эффекта.\n * @function run\n * @returns {void}\n */\n run: () => void;\n\n /**\n * Коллекция функций очистки.\n * @type {Set<CleanupFn>}\n */\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для состояния ресурса.\n * @template T Data format\n * @interface ResourceState<T>\n */\nexport interface ResourceState<T> {\n /**\n * Данные ресурса.\n * @type {T | null}\n */\n data: T | null;\n\n /**\n * Состояние загрузки ресурса.\n * @type {boolean}\n */\n loading: boolean;\n\n /**\n * Ошибка ресурса.\n * @type {any | null}\n */\n error: any | null;\n}\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\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\nexport interface ResourceOptions<T> {\n name: string;\n /** Автоматически сбрасывать data в null при изменении source. По умолчанию: true */\n resetDataOnSourceChange?: boolean;\n /** Функция для валидации успешного ответа сервера перед его сохранением в стейт.\n *\n * Варианты возвращаемого значения:\n * - true 👉 все ок\n * - false 👉 в error попадет стандартный текст ошибки\n * - string 👉 попадет в поле error\n * */\n responseValidate?: (responseData: T) => boolean | string;\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 * @template T\n * @interface SignalOptions<T>\n */\nexport interface SignalOptions<T> {\n /**\n * Имя сигнала.\n * @type {string}\n */\n name?: string;\n\n /**\n * Валидатор значения сигнала.\n * @function validate\n * @param {T} val - Значение для валидации.\n * @returns {boolean | string} - Результат валидации.\n */\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\n/**\n * Класс реактивного движка.\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 // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\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) {\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) => 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 * Создание асинхронного ресурса. Типы на входе: <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/src/examples/20-resource/Example20.tsx Реализация базового компонента Example20}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/src/examples/21-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 * @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>,\n ): Resource<T> {\n // 1. Обработка полиморфного аргумента (строка или объект) для обратной совместимости\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>)\n : {};\n\n // Дефолтные настройки\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const validate = options.responseValidate;\n\n const state = this.signal<ResourceState<T>>({ data: null, loading: true, error: null }, signalName);\n let controller: AbortController | null = null;\n\n const load = async (sValue: S, isSourceChange = false) => {\n controller?.abort();\n controller = new AbortController();\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 };\n\n try {\n const data = await fetcher(sValue, controller.signal);\n\n if (!controller.signal.aborted) {\n // 2. ВАЛИДАЦИЯ ОТВЕТА СЕРВЕРА\n if (validate) {\n const validationResult = validate(data);\n\n // Если валидация вернула false или строку с ошибкой\n if (validationResult === false || typeof validationResult === 'string') {\n const errorMsg = typeof validationResult === 'string'\n ? validationResult\n : 'Validation failed for response data';\n\n state.value = { data: null, loading: false, error: new Error(errorMsg) };\n return;\n }\n }\n\n // Если валидация пройдена (или отсутствует), записываем данные\n state.value = { data, loading: false, error: null };\n }\n } catch (e: any) {\n if (e.name !== 'AbortError') {\n state.value = { data: null, loading: false, error: e };\n }\n }\n };\n\n this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n load(sValue, true); // При изменении источника передаем true\n\n return () => controller?.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 value() { return state.value; },\n refetch: () => load(source ? source.value : undefined as S, false), // При ручном рефетче передаем false\n subscribe: (cb) => 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 * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n // Импортируйте useState и useEffect на верхнем уровне файла из 'react'\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\n this.reactAdapters = { useState, useEffect };\n }\n\n\n /**\n * Использование реактивного значения в React компоненте.\n * @template T\n * @function use\n * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.\n * @returns {T} - Значение реактивного объекта.\n * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\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","import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react';\nimport { ReactiveEngine } from '../core';\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n","interface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\ninterface CacheOptions {\n /** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */\n ttl?: number;\n}\n\n/**\n * Декоратор для создания кэширующего fetcher-а для engine.resource\n * @param fetcher Оригинальная асинхронная функция запроса\n * @param options Настройки кэширования\n * @source\n */\nexport const withCache = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: CacheOptions = {}\n) => {\n const ttl = options.ttl ?? 5 * 60 * 1000; // Дефолтный TTL: 5 минут\n const cache = new Map<string, CacheEntry<T>>();\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n // 1. Генерируем уникальный ключ на основе зависимостей (массива или объекта)\n const cacheKey = typeof source === 'object' && source !== null\n ? JSON.stringify(source)\n : String(source);\n\n const now = Date.now();\n const cached = cache.get(cacheKey);\n\n // 2. Проверяем, есть ли валидный (не устаревший) кэш\n if (cached && (now - cached.timestamp < ttl)) {\n return cached.data;\n }\n\n // 3. Если кэша нет или он устарел — делаем реальный сетевой запрос\n const freshData = await fetcher(source, signal);\n\n // 4. Сохраняем свежие данные и метку времени в кэш\n cache.set(cacheKey, {\n data: freshData,\n timestamp: Date.now() // берем актуальное время после завершения запроса\n });\n\n return freshData;\n };\n};\n","import { useEffect, useRef } from 'react';\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val);\n });\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe;\n }, [signal]);\n};\n","import { useSyncExternalStore, useCallback, useMemo, useEffect } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n * Поддерживает React 18+ и ленивые фабрики без утечек памяти.\n *\n * @source\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n // 1. Всегда получаем чистый объект, даже если передали функцию-фабрику () => engine.computed(...)\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n // 2. Стабилизируем функции для useSyncExternalStore\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback);\n },\n [reactiveItem]\n );\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value;\n }, [reactiveItem]);\n\n // 3. Автоматически подчищаем динамические вычисления при размонтировании\n useEffect(() => {\n return () => {\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n","import { useState, useEffect, useRef, useMemo } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Fallback хук для извлечения значения из сигналов. Поддерживает React 16.8+.\n *\n * @source\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n const [state, setState] = useState<T>(reactiveItem.value);\n const setStateRef = useRef(setState);\n\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n setStateRef.current(reactiveItem.value);\n\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return () => {\n unsubscribe();\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.iife.js","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../src/decorators/createObserver.tsx","../src/decorators/withCache.ts","../src/hooks/useReactiveSubscription.ts","../src/hooks/useReactiveValue.ts","../src/hooks/useReactiveValue0.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\n// export abstract class BaseService {\n// constructor(protected engine: ReactiveEngine) {\n// // Вызываем метод инициализации, который можно переопределить в наследниках\n// this.onInit();\n// }\n\n// // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n// // Удобно для инициализации сигналов, которые зависят от engine.\n// protected onInit(): void {}\n\n// // NOTE: Полезные хелперы для сокращения кода\n// protected createSignal<T>(val: T, name?: string) {\n// return this.engine.signal(val, name);\n// }\n\n// protected createComputed<T>(fn: () => T) {\n// return this.engine.computed(fn);\n// }\n// }\n\nexport abstract class BaseREService {\n constructor(protected engine: ReactiveEngine) { } // Просто сохраняем engine\n\n protected createNumericSignal(val: number, name: string) {\n return this.engine.signal(val, {\n name,\n validate: (v) => typeof v === 'number' && !isNaN(v)\n });\n }\n}\n","import { ReactiveEngine } from './core';\n\nexport abstract class BaseREServiceEnhanced {\n constructor(protected engine: ReactiveEngine) {\n setTimeout(() => this.onInit(), 0)\n }\n\n protected onInit(): void { }\n}\n","import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react';\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\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\n/**\n * Функция для очистки эффекта.\n * @typedef {() => void} CleanupFn\n */\n\n/**\n * Функция эффекта, которая может возвращать функцию очистки.\n * @typedef {() => CleanupFn | void} EffectFn\n */\n\n/**\n * Токен для зависимости.\n * @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>\n */\n\n/**\n * Фабрика для создания сервиса.\n * @template T\n * @typedef {(engine: ReactiveEngine) => T} Factory<T>\n */\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n /**\n * Запуск эффекта.\n * @function run\n * @returns {void}\n */\n run: () => void;\n\n /**\n * Коллекция функций очистки.\n * @type {Set<CleanupFn>}\n */\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для состояния ресурса.\n * @template T Data format\n * @interface ResourceState<T>\n */\nexport interface ResourceState<T> {\n /**\n * Данные ресурса.\n * @type {T | null}\n */\n data: T | null;\n\n /**\n * Состояние загрузки ресурса.\n * @type {boolean}\n */\n loading: boolean;\n\n /**\n * Ошибка ресурса.\n * @type {any | null}\n */\n error: any | null;\n\n /** Флаг активного процесса повторных попыток после сбоя сети */\n isRetrying: boolean;\n}\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\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\nexport interface ResourceOptions<T> {\n name: string;\n /** Автоматически сбрасывать data в null при изменении source. По умолчанию: true */\n resetDataOnSourceChange?: boolean;\n /** Функция для валидации успешного ответа сервера перед его сохранением в стейт.\n *\n * Варианты возвращаемого значения:\n * - true 👉 все ок\n * - false 👉 в error попадет стандартный текст ошибки\n * - string 👉 попадет в поле error\n * */\n responseValidate?: (responseData: T) => boolean | string;\n\n // НАСТРОЙКИ RETRY:\n retryCount?: number;\n retryDelay?: number;\n /** Включить экспоненциальное увеличение задержки (каждая попытка ждет в 2 раза дольше). По умолчанию: false */\n isExponentialBackoffEnabled?: boolean;\n /** Максимальный лимит ожидания между попытками в миллисекундах. По умолчанию: 30000 (30 секунд) */\n maxRetryDelay?: number;\n /** Максимальное время ожидания ответа сервера в миллисекундах. По умолчанию: отсутствует (бесконечно) */\n timeout?: number;\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 * @template T\n * @interface SignalOptions<T>\n */\nexport interface SignalOptions<T> {\n /**\n * Имя сигнала.\n * @type {string}\n */\n name?: string;\n\n /**\n * Валидатор значения сигнала.\n * @function validate\n * @param {T} val - Значение для валидации.\n * @returns {boolean | string} - Результат валидации.\n */\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\n/**\n * Класс реактивного движка.\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 // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\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) {\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) => 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/src/examples/20-resource/Example20.tsx Реализация базового компонента Example20}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/src/examples/21-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 * @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>,\n ): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>)\n : {};\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const validate = options.responseValidate;\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 // Извлекаем настройку таймаута\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 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 // Локальные переменные для очистки слушателей таймаута\n let timeoutController: AbortController | null = null;\n let combinedSignal = effectSignal;\n\n try {\n if (effectSignal.aborted) return; // Внешний guard-шлагбаум\n\n // ИСПРАВЛЕНИЕ: Интеграция таймаута для текущей попытки фетча\n if (timeoutMs && timeoutMs > 0) {\n // Создаем нативный сигнал таймаута\n const timeoutSignal = AbortSignal.timeout(timeoutMs);[1, 2]\n\n // Объединяем сигнал эффекта (смена source) и сигнал таймаута [3]\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);[3]\n }\n\n // Передаем объединенный сигнал в пользовательский fetcher\n const data = await fetcher(sValue, combinedSignal);\n\n // Проверяем, не прилетал ли abort (внешний или таймаут) во время фетча\n if (combinedSignal.aborted) {\n // Если это был таймаут, а не внешняя смена источника — пробрасываем в catch для запуска ретраев!\n if (!effectSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError');\n }\n return; // Если внешняя отмена — просто тихо выходим\n }\n\n if (validate) {\n const validationResult = validate(data);\n if (validationResult === false || typeof validationResult === 'string') {\n if (combinedSignal.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 (combinedSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n\n } catch (e: any) {\n // Если это внешняя отмена (смена источника или refetch) — мгновенно умираем\n if (effectSignal.aborted || e.name === 'AbortError' && effectSignal.aborted) {\n return;\n }\n\n // Если это был таймаут запроса ИЛИ обычная сетевая ошибка — идем на круг ретраев!\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n if (attempt === retryCount) {\n if (effectSignal.aborted) return;\n // Если попытки исчерпаны, записываем ошибку таймаута или сети в стейт\n const finalError = isTimeout ? new Error(`Request timed out after ${timeoutMs}ms`) : e;\n state.value = { data: null, loading: false, error: finalError, isRetrying: false };\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 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 // Паузу задержки слушаем ТОЛЬКО по внешнему сигналу эффекта (смена источника)\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) => 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 * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n // Импортируйте useState и useEffect на верхнем уровне файла из 'react'\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\n this.reactAdapters = { useState, useEffect };\n }\n\n\n /**\n * Использование реактивного значения в React компоненте.\n * @template T\n * @function use\n * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.\n * @returns {T} - Значение реактивного объекта.\n * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\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","import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react';\nimport { ReactiveEngine } from '../core';\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n","interface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\ninterface CacheOptions {\n /** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */\n ttl?: number;\n}\n\n/**\n * Декоратор для создания кэширующего fetcher-а для engine.resource\n * @param fetcher Оригинальная асинхронная функция запроса\n * @param options Настройки кэширования\n * @source\n */\nexport const withCache = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: CacheOptions = {}\n) => {\n const ttl = options.ttl ?? 5 * 60 * 1000; // Дефолтный TTL: 5 минут\n const cache = new Map<string, CacheEntry<T>>();\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n // 1. Генерируем уникальный ключ на основе зависимостей (массива или объекта)\n const cacheKey = typeof source === 'object' && source !== null\n ? JSON.stringify(source)\n : String(source);\n\n const now = Date.now();\n const cached = cache.get(cacheKey);\n\n // 2. Проверяем, есть ли валидный (не устаревший) кэш\n if (cached && (now - cached.timestamp < ttl)) {\n return cached.data;\n }\n\n // 3. Если кэша нет или он устарел — делаем реальный сетевой запрос\n const freshData = await fetcher(source, signal);\n\n // 4. Сохраняем свежие данные и метку времени в кэш\n cache.set(cacheKey, {\n data: freshData,\n timestamp: Date.now() // берем актуальное время после завершения запроса\n });\n\n return freshData;\n };\n};\n","import { useEffect, useRef } from 'react';\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val);\n });\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe;\n }, [signal]);\n};\n","import { useSyncExternalStore, useCallback, useMemo, useEffect } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n * Поддерживает React 18+ и ленивые фабрики без утечек памяти.\n *\n * @source\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n // 1. Всегда получаем чистый объект, даже если передали функцию-фабрику () => engine.computed(...)\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n // 2. Стабилизируем функции для useSyncExternalStore\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback);\n },\n [reactiveItem]\n );\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value;\n }, [reactiveItem]);\n\n // 3. Автоматически подчищаем динамические вычисления при размонтировании\n useEffect(() => {\n return () => {\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n","import { useState, useEffect, useRef, useMemo } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Fallback хук для извлечения значения из сигналов. Поддерживает React 16.8+.\n *\n * @source\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n const [state, setState] = useState<T>(reactiveItem.value);\n const setStateRef = useRef(setState);\n\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n setStateRef.current(reactiveItem.value);\n\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return () => {\n unsubscribe();\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -144,65 +144,122 @@ var s = class {
|
|
|
144
144
|
batch(e) {
|
|
145
145
|
e();
|
|
146
146
|
}
|
|
147
|
+
delay(e, t) {
|
|
148
|
+
return new Promise((n, r) => {
|
|
149
|
+
if (t.aborted) return r(new DOMException("Aborted", "AbortError"));
|
|
150
|
+
let i = setTimeout(() => {
|
|
151
|
+
t.removeEventListener("abort", a), n();
|
|
152
|
+
}, e), a = () => {
|
|
153
|
+
clearTimeout(i), r(new DOMException("Aborted", "AbortError"));
|
|
154
|
+
};
|
|
155
|
+
t.addEventListener("abort", a, { once: !0 });
|
|
156
|
+
});
|
|
157
|
+
}
|
|
147
158
|
resource(e, t, n) {
|
|
148
|
-
let r = n && typeof n == "object", i = r ? n.name : n || "unnamed_resource", a = r ? n : {}, o = a.resetDataOnSourceChange ?? !0, s = a.responseValidate, c = this.signal({
|
|
159
|
+
let r = n && typeof n == "object", i = r ? n.name : n || "unnamed_resource", a = r ? n : {}, o = a.resetDataOnSourceChange ?? !0, s = a.responseValidate, c = a.retryCount ?? 0, l = a.retryDelay ?? 1e3, u = a.isExponentialBackoffEnabled ?? !1, d = a.maxRetryDelay ?? 3e4, f = a.timeout, p = this.signal({
|
|
149
160
|
data: null,
|
|
150
161
|
loading: !0,
|
|
151
|
-
error: null
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
let
|
|
155
|
-
|
|
156
|
-
data:
|
|
162
|
+
error: null,
|
|
163
|
+
isRetrying: !1
|
|
164
|
+
}, i), m = async (t, n, r = !1) => {
|
|
165
|
+
let a = r && o ? null : this.untrack(() => p.value.data);
|
|
166
|
+
p.value = {
|
|
167
|
+
data: a,
|
|
157
168
|
loading: !0,
|
|
158
|
-
error: null
|
|
169
|
+
error: null,
|
|
170
|
+
isRetrying: !1
|
|
159
171
|
};
|
|
160
|
-
|
|
161
|
-
let
|
|
162
|
-
|
|
172
|
+
for (let r = 0; r <= c; r++) {
|
|
173
|
+
let a = n;
|
|
174
|
+
try {
|
|
175
|
+
if (n.aborted) return;
|
|
176
|
+
if (f && f > 0) {
|
|
177
|
+
let e = AbortSignal.timeout(f);
|
|
178
|
+
a = AbortSignal.any([n, e]);
|
|
179
|
+
}
|
|
180
|
+
let r = await e(t, a);
|
|
181
|
+
if (a.aborted) {
|
|
182
|
+
if (!n.aborted) throw new DOMException("The operation timed out.", "TimeoutError");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
163
185
|
if (s) {
|
|
164
|
-
let e = s(
|
|
186
|
+
let e = s(r);
|
|
165
187
|
if (e === !1 || typeof e == "string") {
|
|
166
|
-
|
|
188
|
+
if (a.aborted) return;
|
|
189
|
+
p.value = {
|
|
167
190
|
data: null,
|
|
168
191
|
loading: !1,
|
|
169
|
-
error: Error(typeof e == "string" ? e : "Validation failed
|
|
192
|
+
error: Error(typeof e == "string" ? e : "Validation failed"),
|
|
193
|
+
isRetrying: !1
|
|
170
194
|
};
|
|
171
195
|
return;
|
|
172
196
|
}
|
|
173
197
|
}
|
|
174
|
-
|
|
175
|
-
|
|
198
|
+
if (a.aborted) return;
|
|
199
|
+
p.value = {
|
|
200
|
+
data: r,
|
|
176
201
|
loading: !1,
|
|
177
|
-
error: null
|
|
202
|
+
error: null,
|
|
203
|
+
isRetrying: !1
|
|
178
204
|
};
|
|
205
|
+
return;
|
|
206
|
+
} catch (e) {
|
|
207
|
+
if (n.aborted || e.name === "AbortError" && n.aborted) return;
|
|
208
|
+
let t = e.name === "TimeoutError" || e.name === "AbortError" || e.message?.includes("timeout");
|
|
209
|
+
if (r === c) {
|
|
210
|
+
if (n.aborted) return;
|
|
211
|
+
let r = t ? /* @__PURE__ */ Error(`Request timed out after ${f}ms`) : e;
|
|
212
|
+
p.value = {
|
|
213
|
+
data: null,
|
|
214
|
+
loading: !1,
|
|
215
|
+
error: r,
|
|
216
|
+
isRetrying: !1
|
|
217
|
+
};
|
|
218
|
+
} else {
|
|
219
|
+
if (n.aborted) return;
|
|
220
|
+
let e = u ? l * 2 ** r : l;
|
|
221
|
+
e = Math.min(e, d);
|
|
222
|
+
let a = Math.random() * 200;
|
|
223
|
+
e += a, p.value = {
|
|
224
|
+
data: p.value.data,
|
|
225
|
+
loading: !0,
|
|
226
|
+
error: null,
|
|
227
|
+
isRetrying: !0
|
|
228
|
+
}, console.warn(`[Resource Retry] "${i}" сбой по ${t ? "таймауту" : "ошибке сети"}. Попытка ${r + 1}/${c + 1}...`);
|
|
229
|
+
try {
|
|
230
|
+
if (await this.delay(e, n), n.aborted) return;
|
|
231
|
+
} catch {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
179
235
|
}
|
|
180
|
-
} catch (e) {
|
|
181
|
-
e.name !== "AbortError" && (c.value = {
|
|
182
|
-
data: null,
|
|
183
|
-
loading: !1,
|
|
184
|
-
error: e
|
|
185
|
-
});
|
|
186
236
|
}
|
|
187
|
-
};
|
|
237
|
+
}, h = null;
|
|
188
238
|
return this.effect(() => {
|
|
189
|
-
let e = t ? t.value : void 0;
|
|
190
|
-
return
|
|
239
|
+
let e = t ? t.value : void 0, n = new AbortController();
|
|
240
|
+
return h = n, this.untrack(() => {
|
|
241
|
+
m(e, n.signal, !0);
|
|
242
|
+
}), () => n.abort();
|
|
191
243
|
}), {
|
|
192
244
|
get data() {
|
|
193
|
-
return
|
|
245
|
+
return p.value.data;
|
|
194
246
|
},
|
|
195
247
|
get loading() {
|
|
196
|
-
return
|
|
248
|
+
return p.value.loading;
|
|
197
249
|
},
|
|
198
250
|
get error() {
|
|
199
|
-
return
|
|
251
|
+
return p.value.error;
|
|
252
|
+
},
|
|
253
|
+
get isRetrying() {
|
|
254
|
+
return p.value.isRetrying;
|
|
200
255
|
},
|
|
201
256
|
get value() {
|
|
202
|
-
return
|
|
257
|
+
return p.value;
|
|
258
|
+
},
|
|
259
|
+
refetch: () => {
|
|
260
|
+
h?.abort(), h = new AbortController(), m(t ? t.value : void 0, h.signal, !1);
|
|
203
261
|
},
|
|
204
|
-
|
|
205
|
-
subscribe: (e) => c.subscribe(e)
|
|
262
|
+
subscribe: (e) => p.subscribe(e)
|
|
206
263
|
};
|
|
207
264
|
}
|
|
208
265
|
untrack(e) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../src/decorators/createObserver.tsx","../src/decorators/withCache.ts","../src/hooks/useReactiveSubscription.ts","../src/hooks/useReactiveValue.ts","../src/hooks/useReactiveValue0.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\n// export abstract class BaseService {\n// constructor(protected engine: ReactiveEngine) {\n// // Вызываем метод инициализации, который можно переопределить в наследниках\n// this.onInit();\n// }\n\n// // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n// // Удобно для инициализации сигналов, которые зависят от engine.\n// protected onInit(): void {}\n\n// // NOTE: Полезные хелперы для сокращения кода\n// protected createSignal<T>(val: T, name?: string) {\n// return this.engine.signal(val, name);\n// }\n\n// protected createComputed<T>(fn: () => T) {\n// return this.engine.computed(fn);\n// }\n// }\n\nexport abstract class BaseREService {\n constructor(protected engine: ReactiveEngine) { } // Просто сохраняем engine\n\n protected createNumericSignal(val: number, name: string) {\n return this.engine.signal(val, {\n name,\n validate: (v) => typeof v === 'number' && !isNaN(v)\n });\n }\n}\n","import { ReactiveEngine } from './core';\n\nexport abstract class BaseREServiceEnhanced {\n constructor(protected engine: ReactiveEngine) {\n setTimeout(() => this.onInit(), 0)\n }\n\n protected onInit(): void { }\n}\n","import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react';\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\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\n/**\n * Функция для очистки эффекта.\n * @typedef {() => void} CleanupFn\n */\n\n/**\n * Функция эффекта, которая может возвращать функцию очистки.\n * @typedef {() => CleanupFn | void} EffectFn\n */\n\n/**\n * Токен для зависимости.\n * @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>\n */\n\n/**\n * Фабрика для создания сервиса.\n * @template T\n * @typedef {(engine: ReactiveEngine) => T} Factory<T>\n */\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n /**\n * Запуск эффекта.\n * @function run\n * @returns {void}\n */\n run: () => void;\n\n /**\n * Коллекция функций очистки.\n * @type {Set<CleanupFn>}\n */\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для состояния ресурса.\n * @template T Data format\n * @interface ResourceState<T>\n */\nexport interface ResourceState<T> {\n /**\n * Данные ресурса.\n * @type {T | null}\n */\n data: T | null;\n\n /**\n * Состояние загрузки ресурса.\n * @type {boolean}\n */\n loading: boolean;\n\n /**\n * Ошибка ресурса.\n * @type {any | null}\n */\n error: any | null;\n}\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\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\nexport interface ResourceOptions<T> {\n name: string;\n /** Автоматически сбрасывать data в null при изменении source. По умолчанию: true */\n resetDataOnSourceChange?: boolean;\n /** Функция для валидации успешного ответа сервера перед его сохранением в стейт.\n *\n * Варианты возвращаемого значения:\n * - true 👉 все ок\n * - false 👉 в error попадет стандартный текст ошибки\n * - string 👉 попадет в поле error\n * */\n responseValidate?: (responseData: T) => boolean | string;\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 * @template T\n * @interface SignalOptions<T>\n */\nexport interface SignalOptions<T> {\n /**\n * Имя сигнала.\n * @type {string}\n */\n name?: string;\n\n /**\n * Валидатор значения сигнала.\n * @function validate\n * @param {T} val - Значение для валидации.\n * @returns {boolean | string} - Результат валидации.\n */\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\n/**\n * Класс реактивного движка.\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 // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\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) {\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) => 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 * Создание асинхронного ресурса. Типы на входе: <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/src/examples/20-resource/Example20.tsx Реализация базового компонента Example20}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/src/examples/21-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 * @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>,\n ): Resource<T> {\n // 1. Обработка полиморфного аргумента (строка или объект) для обратной совместимости\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>)\n : {};\n\n // Дефолтные настройки\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const validate = options.responseValidate;\n\n const state = this.signal<ResourceState<T>>({ data: null, loading: true, error: null }, signalName);\n let controller: AbortController | null = null;\n\n const load = async (sValue: S, isSourceChange = false) => {\n controller?.abort();\n controller = new AbortController();\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 };\n\n try {\n const data = await fetcher(sValue, controller.signal);\n\n if (!controller.signal.aborted) {\n // 2. ВАЛИДАЦИЯ ОТВЕТА СЕРВЕРА\n if (validate) {\n const validationResult = validate(data);\n\n // Если валидация вернула false или строку с ошибкой\n if (validationResult === false || typeof validationResult === 'string') {\n const errorMsg = typeof validationResult === 'string'\n ? validationResult\n : 'Validation failed for response data';\n\n state.value = { data: null, loading: false, error: new Error(errorMsg) };\n return;\n }\n }\n\n // Если валидация пройдена (или отсутствует), записываем данные\n state.value = { data, loading: false, error: null };\n }\n } catch (e: any) {\n if (e.name !== 'AbortError') {\n state.value = { data: null, loading: false, error: e };\n }\n }\n };\n\n this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n load(sValue, true); // При изменении источника передаем true\n\n return () => controller?.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 value() { return state.value; },\n refetch: () => load(source ? source.value : undefined as S, false), // При ручном рефетче передаем false\n subscribe: (cb) => 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 * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n // Импортируйте useState и useEffect на верхнем уровне файла из 'react'\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\n this.reactAdapters = { useState, useEffect };\n }\n\n\n /**\n * Использование реактивного значения в React компоненте.\n * @template T\n * @function use\n * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.\n * @returns {T} - Значение реактивного объекта.\n * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\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","import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react';\nimport { ReactiveEngine } from '../core';\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n","interface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\ninterface CacheOptions {\n /** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */\n ttl?: number;\n}\n\n/**\n * Декоратор для создания кэширующего fetcher-а для engine.resource\n * @param fetcher Оригинальная асинхронная функция запроса\n * @param options Настройки кэширования\n * @source\n */\nexport const withCache = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: CacheOptions = {}\n) => {\n const ttl = options.ttl ?? 5 * 60 * 1000; // Дефолтный TTL: 5 минут\n const cache = new Map<string, CacheEntry<T>>();\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n // 1. Генерируем уникальный ключ на основе зависимостей (массива или объекта)\n const cacheKey = typeof source === 'object' && source !== null\n ? JSON.stringify(source)\n : String(source);\n\n const now = Date.now();\n const cached = cache.get(cacheKey);\n\n // 2. Проверяем, есть ли валидный (не устаревший) кэш\n if (cached && (now - cached.timestamp < ttl)) {\n return cached.data;\n }\n\n // 3. Если кэша нет или он устарел — делаем реальный сетевой запрос\n const freshData = await fetcher(source, signal);\n\n // 4. Сохраняем свежие данные и метку времени в кэш\n cache.set(cacheKey, {\n data: freshData,\n timestamp: Date.now() // берем актуальное время после завершения запроса\n });\n\n return freshData;\n };\n};\n","import { useEffect, useRef } from 'react';\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val);\n });\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe;\n }, [signal]);\n};\n","import { useSyncExternalStore, useCallback, useMemo, useEffect } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n * Поддерживает React 18+ и ленивые фабрики без утечек памяти.\n *\n * @source\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n // 1. Всегда получаем чистый объект, даже если передали функцию-фабрику () => engine.computed(...)\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n // 2. Стабилизируем функции для useSyncExternalStore\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback);\n },\n [reactiveItem]\n );\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value;\n }, [reactiveItem]);\n\n // 3. Автоматически подчищаем динамические вычисления при размонтировании\n useEffect(() => {\n return () => {\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n","import { useState, useEffect, useRef, useMemo } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Fallback хук для извлечения значения из сигналов. Поддерживает React 16.8+.\n *\n * @source\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n const [state, setState] = useState<T>(reactiveItem.value);\n const setStateRef = useRef(setState);\n\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n setStateRef.current(reactiveItem.value);\n\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return () => {\n unsubscribe();\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../src/decorators/createObserver.tsx","../src/decorators/withCache.ts","../src/hooks/useReactiveSubscription.ts","../src/hooks/useReactiveValue.ts","../src/hooks/useReactiveValue0.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\n// export abstract class BaseService {\n// constructor(protected engine: ReactiveEngine) {\n// // Вызываем метод инициализации, который можно переопределить в наследниках\n// this.onInit();\n// }\n\n// // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n// // Удобно для инициализации сигналов, которые зависят от engine.\n// protected onInit(): void {}\n\n// // NOTE: Полезные хелперы для сокращения кода\n// protected createSignal<T>(val: T, name?: string) {\n// return this.engine.signal(val, name);\n// }\n\n// protected createComputed<T>(fn: () => T) {\n// return this.engine.computed(fn);\n// }\n// }\n\nexport abstract class BaseREService {\n constructor(protected engine: ReactiveEngine) { } // Просто сохраняем engine\n\n protected createNumericSignal(val: number, name: string) {\n return this.engine.signal(val, {\n name,\n validate: (v) => typeof v === 'number' && !isNaN(v)\n });\n }\n}\n","import { ReactiveEngine } from './core';\n\nexport abstract class BaseREServiceEnhanced {\n constructor(protected engine: ReactiveEngine) {\n setTimeout(() => this.onInit(), 0)\n }\n\n protected onInit(): void { }\n}\n","import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react';\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\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\n/**\n * Функция для очистки эффекта.\n * @typedef {() => void} CleanupFn\n */\n\n/**\n * Функция эффекта, которая может возвращать функцию очистки.\n * @typedef {() => CleanupFn | void} EffectFn\n */\n\n/**\n * Токен для зависимости.\n * @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>\n */\n\n/**\n * Фабрика для создания сервиса.\n * @template T\n * @typedef {(engine: ReactiveEngine) => T} Factory<T>\n */\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n /**\n * Запуск эффекта.\n * @function run\n * @returns {void}\n */\n run: () => void;\n\n /**\n * Коллекция функций очистки.\n * @type {Set<CleanupFn>}\n */\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для состояния ресурса.\n * @template T Data format\n * @interface ResourceState<T>\n */\nexport interface ResourceState<T> {\n /**\n * Данные ресурса.\n * @type {T | null}\n */\n data: T | null;\n\n /**\n * Состояние загрузки ресурса.\n * @type {boolean}\n */\n loading: boolean;\n\n /**\n * Ошибка ресурса.\n * @type {any | null}\n */\n error: any | null;\n\n /** Флаг активного процесса повторных попыток после сбоя сети */\n isRetrying: boolean;\n}\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\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\nexport interface ResourceOptions<T> {\n name: string;\n /** Автоматически сбрасывать data в null при изменении source. По умолчанию: true */\n resetDataOnSourceChange?: boolean;\n /** Функция для валидации успешного ответа сервера перед его сохранением в стейт.\n *\n * Варианты возвращаемого значения:\n * - true 👉 все ок\n * - false 👉 в error попадет стандартный текст ошибки\n * - string 👉 попадет в поле error\n * */\n responseValidate?: (responseData: T) => boolean | string;\n\n // НАСТРОЙКИ RETRY:\n retryCount?: number;\n retryDelay?: number;\n /** Включить экспоненциальное увеличение задержки (каждая попытка ждет в 2 раза дольше). По умолчанию: false */\n isExponentialBackoffEnabled?: boolean;\n /** Максимальный лимит ожидания между попытками в миллисекундах. По умолчанию: 30000 (30 секунд) */\n maxRetryDelay?: number;\n /** Максимальное время ожидания ответа сервера в миллисекундах. По умолчанию: отсутствует (бесконечно) */\n timeout?: number;\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 * @template T\n * @interface SignalOptions<T>\n */\nexport interface SignalOptions<T> {\n /**\n * Имя сигнала.\n * @type {string}\n */\n name?: string;\n\n /**\n * Валидатор значения сигнала.\n * @function validate\n * @param {T} val - Значение для валидации.\n * @returns {boolean | string} - Результат валидации.\n */\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\n/**\n * Класс реактивного движка.\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 // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\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) {\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) => 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/src/examples/20-resource/Example20.tsx Реализация базового компонента Example20}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/src/examples/21-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 * @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>,\n ): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T>)\n : {};\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const validate = options.responseValidate;\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 // Извлекаем настройку таймаута\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 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 // Локальные переменные для очистки слушателей таймаута\n let timeoutController: AbortController | null = null;\n let combinedSignal = effectSignal;\n\n try {\n if (effectSignal.aborted) return; // Внешний guard-шлагбаум\n\n // ИСПРАВЛЕНИЕ: Интеграция таймаута для текущей попытки фетча\n if (timeoutMs && timeoutMs > 0) {\n // Создаем нативный сигнал таймаута\n const timeoutSignal = AbortSignal.timeout(timeoutMs);[1, 2]\n\n // Объединяем сигнал эффекта (смена source) и сигнал таймаута [3]\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);[3]\n }\n\n // Передаем объединенный сигнал в пользовательский fetcher\n const data = await fetcher(sValue, combinedSignal);\n\n // Проверяем, не прилетал ли abort (внешний или таймаут) во время фетча\n if (combinedSignal.aborted) {\n // Если это был таймаут, а не внешняя смена источника — пробрасываем в catch для запуска ретраев!\n if (!effectSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError');\n }\n return; // Если внешняя отмена — просто тихо выходим\n }\n\n if (validate) {\n const validationResult = validate(data);\n if (validationResult === false || typeof validationResult === 'string') {\n if (combinedSignal.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 (combinedSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n\n } catch (e: any) {\n // Если это внешняя отмена (смена источника или refetch) — мгновенно умираем\n if (effectSignal.aborted || e.name === 'AbortError' && effectSignal.aborted) {\n return;\n }\n\n // Если это был таймаут запроса ИЛИ обычная сетевая ошибка — идем на круг ретраев!\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n if (attempt === retryCount) {\n if (effectSignal.aborted) return;\n // Если попытки исчерпаны, записываем ошибку таймаута или сети в стейт\n const finalError = isTimeout ? new Error(`Request timed out after ${timeoutMs}ms`) : e;\n state.value = { data: null, loading: false, error: finalError, isRetrying: false };\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 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 // Паузу задержки слушаем ТОЛЬКО по внешнему сигналу эффекта (смена источника)\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) => 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 * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n // Импортируйте useState и useEffect на верхнем уровне файла из 'react'\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\n this.reactAdapters = { useState, useEffect };\n }\n\n\n /**\n * Использование реактивного значения в React компоненте.\n * @template T\n * @function use\n * @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.\n * @returns {T} - Значение реактивного объекта.\n * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\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","import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react';\nimport { ReactiveEngine } from '../core';\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n","interface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\ninterface CacheOptions {\n /** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */\n ttl?: number;\n}\n\n/**\n * Декоратор для создания кэширующего fetcher-а для engine.resource\n * @param fetcher Оригинальная асинхронная функция запроса\n * @param options Настройки кэширования\n * @source\n */\nexport const withCache = <S, T>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n options: CacheOptions = {}\n) => {\n const ttl = options.ttl ?? 5 * 60 * 1000; // Дефолтный TTL: 5 минут\n const cache = new Map<string, CacheEntry<T>>();\n\n return async (source: S, signal: AbortSignal): Promise<T> => {\n // 1. Генерируем уникальный ключ на основе зависимостей (массива или объекта)\n const cacheKey = typeof source === 'object' && source !== null\n ? JSON.stringify(source)\n : String(source);\n\n const now = Date.now();\n const cached = cache.get(cacheKey);\n\n // 2. Проверяем, есть ли валидный (не устаревший) кэш\n if (cached && (now - cached.timestamp < ttl)) {\n return cached.data;\n }\n\n // 3. Если кэша нет или он устарел — делаем реальный сетевой запрос\n const freshData = await fetcher(source, signal);\n\n // 4. Сохраняем свежие данные и метку времени в кэш\n cache.set(cacheKey, {\n data: freshData,\n timestamp: Date.now() // берем актуальное время после завершения запроса\n });\n\n return freshData;\n };\n};\n","import { useEffect, useRef } from 'react';\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val);\n });\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe;\n }, [signal]);\n};\n","import { useSyncExternalStore, useCallback, useMemo, useEffect } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n * Поддерживает React 18+ и ленивые фабрики без утечек памяти.\n *\n * @source\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n // 1. Всегда получаем чистый объект, даже если передали функцию-фабрику () => engine.computed(...)\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n // 2. Стабилизируем функции для useSyncExternalStore\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback);\n },\n [reactiveItem]\n );\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value;\n }, [reactiveItem]);\n\n // 3. Автоматически подчищаем динамические вычисления при размонтировании\n useEffect(() => {\n return () => {\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n","import { useState, useEffect, useRef, useMemo } from \"react\";\nimport { CleanupFn } from '../core';\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n destroy?: () => void;\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);\n\n/**\n * Fallback хук для извлечения значения из сигналов. Поддерживает React 16.8+.\n *\n * @source\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const reactiveItem = useMemo(() => {\n return typeof input === 'function' ? input() : input;\n }, [input]);\n\n const [state, setState] = useState<T>(reactiveItem.value);\n const setStateRef = useRef(setState);\n\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n setStateRef.current(reactiveItem.value);\n\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return () => {\n unsubscribe();\n if (reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n };\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":";;AAsBA,IAAsB,IAAtB,MAAoC;CACZ;CAAtB,YAAY,GAAkC;EAAxB,KAAA,SAAA;CAA0B;CAEhD,oBAA8B,GAAa,GAAc;EACvD,OAAO,KAAK,OAAO,OAAO,GAAK;GAC7B;GACA,WAAW,MAAM,OAAO,KAAM,YAAY,CAAC,MAAM,CAAC;EACpD,CAAC;CACH;AACF,GC7BsB,IAAtB,MAA4C;CACpB;CAAtB,YAAY,GAAkC;EAC5C,AADoB,KAAA,SAAA,GACpB,iBAAiB,KAAK,OAAO,GAAG,CAAC;CACnC;CAEA,SAAyB,CAAE;AAC7B,GC4Ma,IAAb,MAA4B;CAC1B,eAAuC;CACvC,aAAqB;CACrB,iCAAyB,IAAI,IAAa;CAI1C,2BAAmB,IAAI,IAA6B;CACpD,4BAAoB,IAAI,IAAsC;CAG9D,6BAAqB,IAAI,QAAwB;CACjD,6BAAqB,IAAI,IAAa;CAMtC;CAGA,gBAGI;EACU;EACC;CACb;CAWF,QAAkB,GAAiB,GAAsC;EACvE,AAAI,OAAO,KAAmB,cAAc,CAAC,EAAe,YAC1D,KAAK,UAAU,IAAI,GAAO,CAA4B,IAEtD,KAAK,SAAS,IAAI,GAAO,CAAc;CAE3C;CAUA,OAAiB,GAAoB;EACnC,IAAI,CAAC,GACH,MAAU,MAAM,qFAAqF;EAIvG,IAAM,IAAc;EAEpB,IAAI,KAAK,SAAS,IAAI,CAAW,GAC/B,OAAO,KAAK,SAAS,IAAI,CAAW;EAGtC,IAAI;GACF,IAAM,IAAU,KAAK,UAAU,IAAI,CAAW;GAC9C,IAAI,GAAS;IACX,IAAM,IAAW,EAAQ,IAAI;IAE7B,OADA,KAAK,SAAS,IAAI,GAAa,CAAQ,GAChC;GACT;GACA,IAAI,OAAO,KAAU,cAAc,EAAM,WAAW;IAClD,IAAM,IAAW,IAAK,EAA0C,IAAI;IAEpE,OADA,KAAK,SAAS,IAAI,GAAa,CAAQ,GAChC;GACT;GACA,MAAU,MAAM,sBAAsB,OAAO,CAAK,GAAG;EACvD,SAAS,GAAG;GACV,MAAU,MAAM,yCAAyC,OAAO,CAAK,EAAE,YAAa,GAAa,WAAW,iBAAiB;EAC/H;CACF;CAWA,OAAiB,GAAiB,GAAsD;EACtF,IAAM,IAAS,MACX,IAAM,GACJ,oBAAc,IAAI,IAAa,GAC/B,IAAU,OAAO,KAAkB,WACrC,EAAE,MAAM,EAAc,IACtB,KAAiB,CAAC,GAChB,IAAO,EAAQ,QAAQ;EAE7B,OAAO;GACL,IAAI,QAAW;IAEb,OADI,EAAO,gBAAc,EAAY,IAAI,EAAO,YAAY,GACrD;GACT;GACA,IAAI,MAAM,GAAa;IACrB,IAAI,MAAQ,GAAU;IAGtB,IAAI,EAAQ,UAAU;KACpB,IAAM,IAAS,EAAQ,SAAS,CAAQ;KACxC,IAAI,MAAW,MAAS,OAAO,KAAW,UAAU;MAClD,IAAM,IAAW,OAAO,KAAW,WAC/B,IACA,0DAA0D,EAAK;MAEnE,QAAQ,MAAM,KAAK,KAAY,qCAAqC;OAClE,UAAU;OACV,SAAS;MACX,CAAC;MACD;KACF;IACF;IAEA,IAAM,IAAM;IAQZ,AAPA,IAAM,GACN,EAAO,iBAAiB,GAAM,GAAU,CAAG,GAG3C,EAAY,SAAQ,MAAK,EAAO,eAAe,IAAI,CAAC,CAAC,GAGhD,EAAO,eACV,EAAO,aAAa,IAEpB,qBAAqB;KACnB,IAAM,IAAU,MAAM,KAAK,EAAO,cAAc;KAKhD,AAJA,EAAO,eAAe,MAAM,GAC5B,EAAO,aAAa,IAGpB,EAAQ,SAAQ,MAAK,EAAE,IAAI,CAAC;IAC9B,CAAC;GAEL;GACA,UAAU,GAAI;IAGZ,OAAO,EAAO,aAAa;KACzB,EAAG,KAAK,KAAK;IACf,CAAC;GACH;EACF;CACF;CAUA,OAAc,GAAc,GAA2B;EACrD,IAAM,IAAS,MACT,IAAqB;GACzB;GACA,0BAAU,IAAI,IAAI;GAClB,MAAM;IAEJ,AADA,KAAK,SAAS,SAAQ,MAAK,EAAE,CAAC,GAC9B,KAAK,SAAS,MAAM;IACpB,IAAM,IAAO,EAAO;IAMpB,AALA,EAAO,eAAe,MACtB,EAAO,QAAQ,YAAY;KACzB,IAAM,IAAU,EAAG;KACnB,AAAI,OAAO,KAAY,cAAY,KAAK,SAAS,IAAI,CAAO;IAC9D,CAAC,GACD,EAAO,eAAe;GACxB;EACF;EAIA,OAHA,KAAK,WAAW,IAAI,CAAS,GAC7B,EAAU,IAAI,SAED;GAGX,AAFA,EAAU,SAAS,SAAQ,MAAK,EAAE,CAAC,GACnC,EAAO,eAAe,OAAO,CAAS,GACtC,EAAO,WAAW,OAAO,CAAS;EACpC;CACF;CAGA,gCAAwB,IAAI,IAA0C;CAItE,kBAA0B,IAAI,sBAC3B,MAAc;EACb,EAAU;CACZ,CACF;CAWA,SAAmB,GAAa,GAAkC;EAEhE,IAAI,KAAK,cAAc,IAAI,CAAE,GAAG;GAE9B,IAAM,IADY,KAAK,cAAc,IAAI,CAClB,CAAA,EAAW,MAAM;GACxC,IAAI,GACF,OAAO;EAEX;EAEA,IAAM,IAAS,MACT,IAAM,KAAK,OAAU,KAAA,GAA2B,KAAc,kBAAkB,GAEhF,IAAoB,KAAK,aAAa;GAC1C,IAAM,IAAW,EAAG;GACpB,EAAI,QAAQ;EACd,CAAC,GAEK,IAAY,MAAM,KAAK,KAAK,UAAU,CAAC,CAAC,KAAK,WAAW,OAAO,IAE/D,UAAuB;GAK3B,AAJA,EAAkB,GACd,KACF,EAAO,WAAW,OAAO,CAAS,GAEpC,EAAO,cAAc,OAAO,CAAE;EAChC,GAGM,IAAgC;GACpC,IAAI,QAAQ;IAAE,OAAO,EAAI;GAAO;GAChC,YAAY,MAAO,EAAI,UAAU,CAAE;GACnC,UAAU;IACR,EAAe;GACjB;EACF;EAQA,OANA,KAAK,gBAAgB,SAAS,GAAkB,CAAc,GAI9D,KAAK,cAAc,IAAI,GAAI,IAAI,QAAQ,CAAqC,CAAC,GAEtE;CACT;CAWA,SAAkC,GAAW,IAAe,YAAe;EACzE,IAAI,KAAK,WAAW,IAAI,CAAM,GAC5B,OAAO,KAAK,WAAW,IAAI,CAAM;EAEnC,IAAM,IAAS,MACT,oBAAmB,IAAI,IAAmC,GAE1D,IAAQ,IAAI,MAAM,GAAQ;GAC9B,IAAI,GAAK,GAAM,GAAU;IACvB,AAAI,EAAO,iBACJ,EAAiB,IAAI,CAAI,KAAG,EAAiB,IAAI,mBAAM,IAAI,IAAI,CAAC,GACrE,EAAiB,IAAI,CAAI,CAAC,CAAE,IAAI,EAAO,YAAY;IAErD,IAAM,IAAQ,QAAQ,IAAI,GAAK,GAAM,CAAQ;IAC7C,OAA0B,OAAO,KAAU,YAAnC,IACJ,EAAO,SAAS,GAAO,GAAG,EAAK,GAAG,OAAO,CAAI,GAAG,IAChD;GACN;GACA,IAAI,GAAK,GAAM,GAAO,GAAU;IAC9B,IAAM,IAAM,QAAQ,IAAI,GAAK,GAAM,CAAQ;IAW3C,OATI,MAAQ,KAER,MAAQ,MACV,QAAQ,IAAI,GAAK,GAAM,GAAO,CAAQ,GACtC,EAAO,iBAAiB,GAAG,EAAK,GAAG,OAAO,CAAI,KAAK,GAAO,CAAG,GAC7D,EAAiB,IAAI,CAAI,CAAC,EAAE,SAAQ,MAClC,EAAO,aAAa,EAAO,eAAe,IAAI,CAAC,IAAI,EAAE,IAAI,CAC3D,IAPwB;GAU5B;EACF,CAAC;EAED,OADA,KAAK,WAAW,IAAI,GAAQ,CAAK,GAC1B;CACT;CASA,MAAa,GAAsB;EAGjC,EAAG;CACL;CAWA,MAAc,GAAY,GAAoC;EAC5D,OAAO,IAAI,SAAS,GAAS,MAAW;GACtC,IAAI,EAAO,SACT,OAAO,EAAO,IAAI,aAAa,WAAW,YAAY,CAAC;GAGzD,IAAM,IAAY,iBAAiB;IAEjC,AADA,EAAO,oBAAoB,SAAS,CAAO,GAC3C,EAAQ;GACV,GAAG,CAAE,GAEC,UAAgB;IAEpB,AADA,aAAa,CAAS,GACtB,EAAO,IAAI,aAAa,WAAW,YAAY,CAAC;GAClD;GAEA,EAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC;EAC1D,CAAC;CACH;CA6BA,SACE,GACA,GACA,GACa;EACb,IAAM,IAAkB,KAAiB,OAAO,KAAkB,UAC5D,IAAa,IACd,EAAqC,OACrC,KAA4B,oBAE3B,IAAuC,IACxC,IACD,CAAC,GAEC,IAA0B,EAAQ,2BAA2B,IAC7D,IAAW,EAAQ,kBAEnB,IAAa,EAAQ,cAAc,GACnC,IAAY,EAAQ,cAAc,KAClC,IAAiB,EAAQ,+BAA+B,IACxD,IAAgB,EAAQ,iBAAiB,KAEzC,IAAY,EAAQ,SAEpB,IAAQ,KAAK,OACjB;GAAE,MAAM;GAAM,SAAS;GAAM,OAAO;GAAM,YAAY;EAAM,GAC5D,CACF,GAEM,IAAO,OAAO,GAAW,GAA2B,IAAiB,OAAU;GAEnF,IAAM,IADc,KAAkB,IACJ,OAAO,KAAK,cAAc,EAAM,MAAM,IAAI;GAE5E,EAAM,QAAQ;IAAE,MAAM;IAAa,SAAS;IAAM,OAAO;IAAM,YAAY;GAAM;GAEjF,KAAK,IAAI,IAAU,GAAG,KAAW,GAAY,KAAW;IAGtD,IAAI,IAAiB;IAErB,IAAI;KACF,IAAI,EAAa,SAAS;KAG1B,IAAI,KAAa,IAAY,GAAG;MAE9B,IAAM,IAAgB,YAAY,QAAQ,CAAS;MAGnD,IAAiB,YAAY,IAAI,CAAC,GAAc,CAAa,CAAC;KAChE;KAGA,IAAM,IAAO,MAAM,EAAQ,GAAQ,CAAc;KAGjD,IAAI,EAAe,SAAS;MAE1B,IAAI,CAAC,EAAa,SAChB,MAAM,IAAI,aAAa,4BAA4B,cAAc;MAEnE;KACF;KAEA,IAAI,GAAU;MACZ,IAAM,IAAmB,EAAS,CAAI;MACtC,IAAI,MAAqB,MAAS,OAAO,KAAqB,UAAU;OACtE,IAAI,EAAe,SAAS;OAE5B,EAAM,QAAQ;QAAE,MAAM;QAAM,SAAS;QAAO,OAAW,MADtC,OAAO,KAAqB,WAAW,IAAmB,mBACN;QAAG,YAAY;OAAM;OAC1F;MACF;KACF;KAEA,IAAI,EAAe,SAAS;KAE5B,EAAM,QAAQ;MAAE;MAAM,SAAS;MAAO,OAAO;MAAM,YAAY;KAAM;KACrE;IAEF,SAAS,GAAQ;KAEf,IAAI,EAAa,WAAW,EAAE,SAAS,gBAAgB,EAAa,SAClE;KAIF,IAAM,IAAY,EAAE,SAAS,kBAAkB,EAAE,SAAS,gBAAgB,EAAE,SAAS,SAAS,SAAS;KAEvG,IAAI,MAAY,GAAY;MAC1B,IAAI,EAAa,SAAS;MAE1B,IAAM,IAAa,IAAY,gBAAI,MAAM,2BAA2B,EAAU,GAAG,IAAI;MACrF,EAAM,QAAQ;OAAE,MAAM;OAAM,SAAS;OAAO,OAAO;OAAY,YAAY;MAAM;KACnF,OAAO;MACL,IAAI,EAAa,SAAS;MAE1B,IAAI,IAAe,IAAiB,IAAqB,KAAG,IAAW;MACvE,IAAe,KAAK,IAAI,GAAc,CAAa;MACnD,IAAM,IAAS,KAAK,OAAO,IAAI;MAM/B,AALA,KAA8B,GAE9B,EAAM,QAAQ;OAAE,MAAM,EAAM,MAAM;OAAM,SAAS;OAAM,OAAO;OAAM,YAAY;MAAK,GAGrF,QAAQ,KAAK,qBAAqB,EAAW,YAD3B,IAAY,aAAa,cACwB,YAAY,IAAU,EAAE,GAAG,IAAa,EAAE,IAAI;MAEjH,IAAI;OAGF,IADA,MAAM,KAAK,MAAM,GAAc,CAAY,GACvC,EAAa,SAAS;MAC5B,QAAqB;OACnB;MACF;KACF;IACF;GACF;EACF,GAEI,IAAiD;EAcrD,OAZA,KAAK,aAAa;GAChB,IAAM,IAAS,IAAS,EAAO,QAAQ,KAAA,GACjC,IAAmB,IAAI,gBAAgB;GAO7C,OANA,IAAyB,GAEzB,KAAK,cAAc;IACjB,EAAK,GAAa,EAAiB,QAAQ,EAAI;GACjD,CAAC,SAEY,EAAiB,MAAM;EACtC,CAAC,GAEM;GACL,IAAI,OAAO;IAAE,OAAO,EAAM,MAAM;GAAM;GACtC,IAAI,UAAU;IAAE,OAAO,EAAM,MAAM;GAAS;GAC5C,IAAI,QAAQ;IAAE,OAAO,EAAM,MAAM;GAAO;GACxC,IAAI,aAAa;IAAE,OAAO,EAAM,MAAM;GAAY;GAClD,IAAI,QAAQ;IAAE,OAAO,EAAM;GAAO;GAClC,eAAe;IAGb,AAFA,GAAwB,MAAM,GAC9B,IAAyB,IAAI,gBAAgB,GAC7C,EAAK,IAAS,EAAO,QAAQ,KAAA,GAAgB,EAAuB,QAAQ,EAAK;GACnF;GACA,YAAY,MAAO,EAAM,UAAU,CAAE;EACvC;CACF;CAUA,QAAkB,GAAgB;EAChC,IAAM,IAAO,KAAK;EAClB,KAAK,eAAe;EACpB,IAAI;GACF,OAAO,EAAG;EACZ,UAAU;GACR,KAAK,eAAe;EACtB;CACF;CAWA,iBACE,GACA,GACM;EACN,KAAK,gBAAgB;GAAE;GAAU;EAAU;CAC7C;CAWA,IAAc,GAAqE;EACjF,IAAI,CAAC,KAAK,eACR,MAAU,MAAM,sGAAsG;EAIxH,IAAI,CAAC,KAAQ,OAAO,EAAK,aAAc,YAAY;GACjD,IAAM,IAAW;GAMjB,MADA,QAAQ,MAAM,GAAU,EAAE,QAAK,CAAC,GACtB,MAAM,CAAQ;EAC1B;EACA,IAAM,CAAC,GAAK,KAAU,KAAK,cAAc,SAAS,EAAK,KAAK;EAS5D,OAPA,KAAK,cAAc,gBAER,EAAK,UAAU,CAAM,GAE9B,CAAC,CAAI,CACP,GAEO;CACT;CAWA,QAAgB,GAAiB,GAAgB;EAC/C,IAAI;GACF,EAAG;EACL,SAAS,GAAO;GACd,QAAQ,MACN,mDACA,wEACA,sBACA,CACF;EAEF;CACF;AACF,GCjxBa,KAA2B,OAC9B,EAAE,kBAAmC;CAC3C,IAAM,IAAa,EAAO,CAAC,GACrB,IAAmB,EAA4B,IAAI,GAGnD,IAAiB,EAA4B,IAAI,GACjD,IAAe,EAAY,IAAI,GAG/B,IAAc,SACX;EACL,UAAU,GAA2B;GAEnC,OADA,EAAiB,UAAU,SACd;IAEX,AADA,EAAiB,UAAU,MACvB,EAAe,YACjB,EAAe,QAAQ,GACvB,EAAe,UAAU,MACzB,EAAa,UAAU;GAE3B;EACF;EACA,cAAc;GACZ,OAAO,EAAW;EACpB;CACF,IACC,CAAC,CAAC;CAWL,AARA,cACe;EACX,AAAI,EAAe,WAAS,EAAe,QAAQ;CACrD,GACC,CAAC,CAAC,GAIA,EAAe,YAClB,EAAe,UAAU,EAAO,aAAa;EAE3C,AAAI,EAAiB,YACnB,EAAW,WAGX,qBAAqB;GACnB,AAAI,EAAiB,WACnB,EAAiB,QAAQ;EAE7B,CAAC;CAEL,CAAC,GAGD,EAAa,UAAU,MAAM,KAAM,EAAe,UAAU,CAAC,CAAC,IAAI;CAMpE,IAAM,IAAoB,EAAe;CACzC,EAAgB,eAAe,EAAa;CAE5C,IAAI,IAA0B;CAC9B,IAAI;EACF,IAAS,EAAS;CACpB,UAAU;EAER,EAAgB,eAAe;CACjC;CAKA,OAFA,EAAqB,EAAY,WAAW,EAAY,WAAW,GAE5D;AACT,GAqFW,KAAkB,OACH,MAA8D;CAEtF,IAAM,KAAqB,MAAa;EACtC,IAAM,IAAa,EAAO,CAAC,GACrB,IAAgB,GAGhB,IAAkB,kBAAkB,IAAgB,iBAAiB,iBAGrE,IAAe,EAAY,IAAI,GAG/B,EAAE,cAAW,mBAAgB,SAC1B;GACL,YAAY,MAA8B;IAGxC,IAAM,IAAQ,EAAO,aAAa;KAGhC,AADA,EAAW,WACX,EAAc;IAChB,CAAC,GAIK,IAAkB,MAAM,KAAK,EAAc,cAAc,CAAC,CAAC;IAGjE,OAFA,EAAa,UAAU,EAAgB,EAAgB,SAAS,UAEnD;KAEX,AADA,EAAM,GACN,EAAa,UAAU;IACzB;GACF;GACA,mBAAmB,EAAW;EAChC,IACC,CAAC,CAAC;EAGL,EAAqB,GAAW,GAAa,CAAW;EAMxD,IAAM,IAAiB;GAAE,0BAAU,IAAI,IAAI;GAAG,WAAW,CAAE;EAAE,GACvD,IAAgB,EAAa,WAAW,GAGxC,IAAa,EAAc;EACjC,EAAc,KAAmB;EAEjC,IAAI,IAAmC;EACvC,IAAI;GAGF,AAGE,IAHE,OAAO,KAAc,cAAc,CAAC,EAAU,WAAW,mBACxC,EAAuB,CAAK,IAE7B,EAAM,cAAc,GAAW,CAAK;EAE1D,UAAU;GAER,EAAc,KAAmB;EACnC;EAEA,OAAO;CACT;CAGA,OADA,EAAkB,cAAc,YAAY,EAAU,eAAe,EAAU,QAAQ,YAAY,IAC5F;AACT,GC/PW,KACX,GACA,IAAwB,CAAC,MACtB;CACH,IAAM,IAAM,EAAQ,OAAO,MAAS,KAC9B,oBAAQ,IAAI,IAA2B;CAE7C,OAAO,OAAO,GAAW,MAAoC;EAE3D,IAAM,IAAW,OAAO,KAAW,YAAY,IAC3C,KAAK,UAAU,CAAM,IACrB,OAAO,CAAM,GAEX,IAAM,KAAK,IAAI,GACf,IAAS,EAAM,IAAI,CAAQ;EAGjC,IAAI,KAAW,IAAM,EAAO,YAAY,GACtC,OAAO,EAAO;EAIhB,IAAM,IAAY,MAAM,EAAQ,GAAQ,CAAM;EAQ9C,OALA,EAAM,IAAI,GAAU;GAClB,MAAM;GACN,WAAW,KAAK,IAAI;EACtB,CAAC,GAEM;CACT;AACF,GCtCa,KACX,GACA,MACS;CAET,IAAM,IAAc,EAAO,CAAQ;CAMnC,AAJA,QAAgB;EACd,EAAY,UAAU;CACxB,GAAG,CAAC,CAAQ,CAAC,GAEb,QAEiC,EAAO,WAAW,MAAQ;EACvD,EAAY,QAAQ,CAAG;CACzB,CAGO,GACN,CAAC,CAAM,CAAC;AACb,GCba,KAAuB,MAA+B;CAEjE,IAAM,IAAe,QACZ,OAAO,KAAU,aAAa,EAAM,IAAI,GAC9C,CAAC,CAAK,CAAC,GAGJ,IAAY,GACf,MACQ,EAAa,UAAU,CAAa,GAE7C,CAAC,CAAY,CACf,GAEM,IAAc,QACX,EAAa,OACnB,CAAC,CAAY,CAAC;CAWjB,OARA,cACe;EACX,AAAI,KAAgB,OAAO,EAAa,WAAY,cAClD,EAAa,QAAQ;CAEzB,GACC,CAAC,CAAY,CAAC,GAEV,EAAqB,GAAW,CAAW;AACpD,GC7Ba,KAAwB,MAA+B;CAClE,IAAM,IAAe,QACZ,OAAO,KAAU,aAAa,EAAM,IAAI,GAC9C,CAAC,CAAK,CAAC,GAEJ,CAAC,GAAO,KAAY,EAAY,EAAa,KAAK,GAClD,IAAc,EAAO,CAAQ;CAqBnC,OAnBA,QAAgB;EACd,EAAY,UAAU;CACxB,GAAG,CAAC,CAAQ,CAAC,GAEb,QAAgB;EACd,EAAY,QAAQ,EAAa,KAAK;EAEtC,IAAM,IAAc,EAAa,WAAW,MAAa;GACvD,EAAY,QAAQ,CAAQ;EAC9B,CAAC;EAED,aAAa;GAEX,AADA,EAAY,GACR,KAAgB,OAAO,EAAa,WAAY,cAClD,EAAa,QAAQ;EAEzB;CACF,GAAG,CAAC,CAAY,CAAC,GAEV;AACT"}
|