@pravosleva/reactive-engine 0.2.8-beta → 0.2.10-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 +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +257 -11
- package/dist/index.iife.js +3 -3
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +242 -238
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -7
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react");var t=class{engine;constructor(e){this.engine=e}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}},n=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}},r=class{activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;onSignalChange;reactAdapters={useState:e.useState,useEffect:e.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). Проверьте импорты.`);if(this.services.has(e))return this.services.get(e);try{let t=this.factories.get(e);if(t){let n=t(this);return this.services.set(e,n),n}if(typeof e==`function`&&e.prototype){let t=new e(this);return this.services.set(e,t),t}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.isBatching?n.pendingEffects.add(e):e.run())},subscribe(e){return n.effect(()=>e(this.value))}}}effect(e){let t=this,n={cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let n=t.activeEffect;t.activeEffect=this,t.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),t.activeEffect=n}};return this.allEffects.add(n),n.run(),()=>{n.cleanups.forEach(e=>e()),t.pendingEffects.delete(n)}}computed(e,t){let n=this.signal(void 0,t||`unnamed_computed`);return this.effect(()=>{let t=e();n.value=t}),{get value(){return n.value},subscribe:e=>n.subscribe(e)}}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){this.isBatching=!0;try{e()}finally{queueMicrotask(()=>{let e=Array.from(this.pendingEffects);this.pendingEffects.clear(),this.isBatching=!1,e.forEach(e=>e.run())})}}resource(e,t,n){let r=this.signal({data:null,loading:!0,error:null},n||`unnamed_resource`),i=null,a=async t=>{i?.abort(),i=new AbortController;let n=this.untrack(()=>r.value.data);r.value={data:n,loading:!0,error:null};try{let n=await e(t,i.signal);i.signal.aborted||(r.value={data:n,loading:!1,error:null})}catch(e){e.name!==`AbortError`&&(r.value={data:null,loading:!1,error:e})}};return this.effect(()=>{let e=t?t.value:void 0;return a(e),()=>i?.abort()}),{get data(){return r.value.data},get loading(){return r.value.loading},get error(){return r.value.error},get value(){return r.value},refetch:()=>a(t?t.value:void 0),subscribe:e=>r.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(...)
|
|
5
|
-
`;throw console.error(
|
|
6
|
-
//# sourceMappingURL=index.cjs.map
|
|
5
|
+
`;throw console.error(t,{item:e}),Error(t)}let[t,n]=this.reactAdapters.useState(e.value);return this.reactAdapters.useEffect(()=>e.subscribe(n),[e]),t}safeRun(e,t){try{t()}catch(e){console.error(`%c[Reactive Error] %cОшибка в эффекте/computed:`,`color: white; background: red; padding: 2px 4px; border-radius: 3px;`,`font-weight: bold;`,e)}}},i=(e,t={})=>{let n=t.ttl??300*1e3,r=new Map;return async(t,i)=>{let a=typeof t==`object`&&t?JSON.stringify(t):String(t),o=Date.now(),s=r.get(a);if(s&&o-s.timestamp<n)return s.data;let c=await e(t,i);return r.set(a,{data:c,timestamp:Date.now()}),c}},a=(t,n)=>{let r=(0,e.useRef)(n);(0,e.useEffect)(()=>{r.current=n},[n]),(0,e.useEffect)(()=>t.subscribe(e=>{r.current(e)}),[t])},o=t=>(0,e.useSyncExternalStore)(t.subscribe,()=>t.value),s=t=>{let[n,r]=(0,e.useState)(t.value),i=(0,e.useRef)(r);return(0,e.useEffect)(()=>{i.current=r},[r]),(0,e.useEffect)(()=>(i.current(t.value),t.subscribe(e=>{i.current(e)})),[t]),n};exports.BaseREService=t,exports.BaseREServiceEnhanced=n,exports.ReactiveEngine=r,exports.useReactiveSubscription=a,exports.useReactiveValue=o,exports.useReactiveValue0=s,exports.withCache=i;
|
|
6
|
+
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/core.ts","../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts"],"sourcesContent":["import { useState, useEffect } 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 cleanups: Set<CleanupFn>;\n}\n\nexport interface ResourceState<T> {\n data: T | null;\n loading: boolean;\n error: any | null;\n}\n\nexport interface Signal<T> {\n value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport interface Computed<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport interface Resource<T> extends ResourceState<T> {\n refetch: () => void;\n subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;\n readonly value: ResourceState<T>;\n}\n\ninterface SignalOptions<T> {\n name?: string;\n validate?: (val: T) => boolean | string;\n}\n\n// --- ЯДРО ---\nexport class ReactiveEngine {\n private activeEffect: IEffect | null = null;\n private isBatching = false;\n private pendingEffects = new Set<IEffect>();\n private services = new Map<Token<any>, any>();\n private factories = new Map<Token<any>, Factory<any>>();\n private proxyCache = new WeakMap<object, any>();\n private allEffects = new Set<IEffect>();\n\n public onSignalChange?: (name: string, next: any, prev: any) => void;\n\n private reactAdapters = {\n useState: useState as any,\n useEffect: useEffect as any,\n };\n\n /** DI: Регистрация */\n public provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void {\n if (typeof valueOrFactory === 'function' && !valueOrFactory.prototype) {\n this.factories.set(token, valueOrFactory as Factory<T>);\n } else {\n this.services.set(token, valueOrFactory);\n }\n }\n\n /** DI: Инъекция */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n if (this.services.has(token)) return this.services.get(token);\n\n // NOTE: (new way) Если это класс, но мы забыли его унаследовать от BaseService или передать engine\n try {\n // NOTE: Логика создания инстанса\n const factory = this.factories.get(token);\n if (factory) {\n const instance = factory(this);\n this.services.set(token, 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(token, 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 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\n // Парсим аргументы для обратной совместимости\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n\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 subscribers.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n );\n },\n subscribe(cb) {\n return engine.effect(() => cb(this.value));\n }\n };\n }\n\n /** Эффект */\n public effect(fn: EffectFn): CleanupFn {\n const engine = this;\n const effectObj: IEffect = {\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 return () => {\n effectObj.cleanups.forEach(c => c());\n engine.pendingEffects.delete(effectObj);\n };\n }\n\n /** Вычисляемое значение */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n const sig = this.signal<T>(undefined as any, signalName || 'unnamed_computed');\n this.effect(() => {\n const newValue = fn();\n // Сигнал сам внутри себя проверит (sig.value === newValue)\n // благодаря правке в пункте №1\n sig.value = newValue;\n });\n return {\n get value() { return sig.value; },\n // Подписываемся на внутренний сигнал\n subscribe: (cb) => sig.subscribe(cb)\n };\n }\n\n /** Реактивный объект (Proxy) */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) return this.proxyCache.get(target);\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 public batch(fn: () => void): void {\n this.isBatching = true;\n try { fn(); } finally {\n queueMicrotask(() => {\n const effects = Array.from(this.pendingEffects);\n this.pendingEffects.clear();\n this.isBatching = false;\n effects.forEach(e => e.run());\n });\n }\n }\n\n /** Асинхронный ресурс */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n signalName?: string,\n ): Resource<T> {\n const state = this.signal<ResourceState<T>>({ data: null, loading: true, error: null }, signalName || 'unnamed_resource');\n let controller: AbortController | null = null;\n\n const load = async (sValue: S) => {\n controller?.abort();\n controller = new AbortController();\n\n // Читаем текущие данные без создания зависимости!\n const currentData = this.untrack(() => state.value.data);\n\n // Теперь обновление состояния не вызовет перезапуск эффекта\n state.value = { data: currentData, loading: true, error: null };\n\n try {\n const data = await fetcher(sValue, controller.signal);\n if (!controller.signal.aborted) {\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 // Чтобы эффект не подписывался на state,\n // нам нужно изолировать чтение source от чтения state\n this.effect(() => {\n // Читаем ТОЛЬКО source. Это наша единственная зависимость.\n const sValue = source ? source.value : undefined as S;\n\n // Вызываем load. Внутри load чтение state.value\n // не должно регистрироваться как зависимость этого эффекта.\n load(sValue);\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),\n subscribe: (cb) => state.subscribe(cb)\n };\n }\n\n /**\n * Выполняет функцию без отслеживания зависимостей\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 /** React Хук */\n public setReactAdapters(useState: any, useEffect: any) {\n this.reactAdapters = { useState, useEffect };\n }\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 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 { 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"],"names":["ReactiveEngine","useState","useEffect","token","valueOrFactory","factory","instance","e","initialValue","optionsOrName","engine","val","subscribers","options","name","newValue","result","errorMsg","old","cb","fn","effectObj","c","prev","cleanup","signalName","sig","target","propsSubscribers","proxy","obj","prop","receiver","value","effects","fetcher","source","state","controller","load","sValue","currentData","data","item","setVal","effect","error","BaseREService","v","BaseREServiceEnhanced"],"mappings":"yGAyCO,MAAMA,CAAe,CAClB,aAA+B,KAC/B,WAAa,GACb,mBAAqB,IACrB,aAAe,IACf,cAAgB,IAChB,eAAiB,QACjB,eAAiB,IAElB,eAEC,cAAgB,CAAA,SACtBC,EAAAA,SAAA,UACAC,EAAAA,SAAA,EAIK,QAAWC,EAAiBC,EAAsC,CACnE,OAAOA,GAAmB,YAAc,CAACA,EAAe,UAC1D,KAAK,UAAU,IAAID,EAAOC,CAA4B,EAEtD,KAAK,SAAS,IAAID,EAAOC,CAAc,CAE3C,CAGO,OAAUD,EAAoB,CACnC,GAAI,CAACA,EACH,MAAM,IAAI,MAAM,qFAAqF,EAGvG,GAAI,KAAK,SAAS,IAAIA,CAAK,EAAG,OAAO,KAAK,SAAS,IAAIA,CAAK,EAG5D,GAAI,CAEF,MAAME,EAAU,KAAK,UAAU,IAAIF,CAAK,EACxC,GAAIE,EAAS,CACX,MAAMC,EAAWD,EAAQ,IAAI,EAC7B,YAAK,SAAS,IAAIF,EAAOG,CAAQ,EAC1BA,CACT,CACA,GAAI,OAAOH,GAAU,YAAcA,EAAM,UAAW,CAClD,MAAMG,EAAW,IAAKH,EAA0C,IAAI,EACpE,YAAK,SAAS,IAAIA,EAAOG,CAAQ,EAC1BA,CACT,CACA,MAAM,IAAI,MAAM,sBAAsB,OAAOH,CAAK,CAAC,EAAE,CACvD,OAASI,EAAG,CACV,MAAM,IAAI,MAAM,yCAAyC,OAAOJ,CAAK,CAAC,aAAcI,GAAa,SAAW,eAAe,EAAE,CAC/H,CACF,CAGO,OAAUC,EAAiBC,EAAsD,CACtF,MAAMC,EAAS,KACf,IAAIC,EAAMH,EACV,MAAMI,MAAkB,IAGlBC,EAAU,OAAOJ,GAAkB,SACrC,CAAE,KAAMA,CAAA,EACRA,GAAiB,CAAA,EAEfK,EAAOD,EAAQ,MAAQ,iBAE7B,MAAO,CACL,IAAI,OAAW,CACb,OAAIH,EAAO,cAAcE,EAAY,IAAIF,EAAO,YAAY,EACrDC,CACT,EACA,IAAI,MAAMI,EAAa,CACrB,GAAIJ,IAAQI,EAAU,OAGtB,GAAIF,EAAQ,SAAU,CACpB,MAAMG,EAASH,EAAQ,SAASE,CAAQ,EACxC,GAAIC,IAAW,IAAS,OAAOA,GAAW,SAAU,CAClD,MAAMC,EAAW,OAAOD,GAAW,SAC/BA,EACA,0DAA0DF,CAAI,IAElE,QAAQ,MAAM,KAAKG,CAAQ,GAAI,oCAAqC,CAClE,SAAUF,EACV,QAASJ,CAAA,CACV,EACD,MACF,CACF,CAEA,MAAMO,EAAMP,EACZA,EAAMI,EACNL,EAAO,iBAAiBI,EAAMC,EAAUG,CAAG,EAC3CN,EAAY,QAAQL,GAClBG,EAAO,WAAaA,EAAO,eAAe,IAAIH,CAAC,EAAIA,EAAE,IAAA,CAAI,CAE7D,EACA,UAAUY,EAAI,CACZ,OAAOT,EAAO,OAAO,IAAMS,EAAG,KAAK,KAAK,CAAC,CAC3C,CAAA,CAEJ,CAGO,OAAOC,EAAyB,CACrC,MAAMV,EAAS,KACTW,EAAqB,CACzB,aAAc,IACd,KAAM,CACJ,KAAK,SAAS,QAAQC,GAAKA,EAAA,CAAG,EAC9B,KAAK,SAAS,MAAA,EACd,MAAMC,EAAOb,EAAO,aACpBA,EAAO,aAAe,KACtBA,EAAO,QAAQ,KAAM,IAAM,CACzB,MAAMc,EAAUJ,EAAA,EACZ,OAAOI,GAAY,YAAY,KAAK,SAAS,IAAIA,CAAO,CAC9D,CAAC,EACDd,EAAO,aAAea,CACxB,CAAA,EAEF,YAAK,WAAW,IAAIF,CAAS,EAC7BA,EAAU,IAAA,EACH,IAAM,CACXA,EAAU,SAAS,QAAQC,GAAKA,EAAA,CAAG,EACnCZ,EAAO,eAAe,OAAOW,CAAS,CACxC,CACF,CAGO,SAAYD,EAAaK,EAAkC,CAChE,MAAMC,EAAM,KAAK,OAAU,OAAkBD,GAAc,kBAAkB,EAC7E,YAAK,OAAO,IAAM,CAChB,MAAMV,EAAWK,EAAA,EAGjBM,EAAI,MAAQX,CACd,CAAC,EACM,CACL,IAAI,OAAQ,CAAE,OAAOW,EAAI,KAAO,EAEhC,UAAYP,GAAOO,EAAI,UAAUP,CAAE,CAAA,CAEvC,CAGO,SAA2BQ,EAAWb,EAAe,WAAe,CACzE,GAAI,KAAK,WAAW,IAAIa,CAAM,EAAG,OAAO,KAAK,WAAW,IAAIA,CAAM,EAClE,MAAMjB,EAAS,KACTkB,MAAuB,IAEvBC,EAAQ,IAAI,MAAMF,EAAQ,CAC9B,IAAIG,EAAKC,EAAMC,EAAU,CACnBtB,EAAO,eACJkB,EAAiB,IAAIG,CAAI,KAAoB,IAAIA,EAAM,IAAI,GAAK,EACrEH,EAAiB,IAAIG,CAAI,EAAG,IAAIrB,EAAO,YAAY,GAErD,MAAMuB,EAAQ,QAAQ,IAAIH,EAAKC,EAAMC,CAAQ,EAC7C,OAAQC,IAAU,MAAQ,OAAOA,GAAU,SACvCvB,EAAO,SAASuB,EAAO,GAAGnB,CAAI,IAAI,OAAOiB,CAAI,CAAC,EAAE,EAChDE,CACN,EACA,IAAIH,EAAKC,EAAME,EAAOD,EAAU,CAC9B,MAAMd,EAAM,QAAQ,IAAIY,EAAKC,EAAMC,CAAQ,EAE3C,OAAId,IAAQe,GAERf,IAAQe,IACV,QAAQ,IAAIH,EAAKC,EAAME,EAAOD,CAAQ,EACtCtB,EAAO,iBAAiB,GAAGI,CAAI,IAAI,OAAOiB,CAAI,CAAC,GAAIE,EAAOf,CAAG,EAC7DU,EAAiB,IAAIG,CAAI,GAAG,QAAQxB,GAClCG,EAAO,WAAaA,EAAO,eAAe,IAAIH,CAAC,EAAIA,EAAE,IAAA,CAAI,GAGtD,EACT,CAAA,CACD,EACD,YAAK,WAAW,IAAIoB,EAAQE,CAAK,EAC1BA,CACT,CAGO,MAAMT,EAAsB,CACjC,KAAK,WAAa,GAClB,GAAI,CAAEA,EAAA,CAAM,QAAA,CACV,eAAe,IAAM,CACnB,MAAMc,EAAU,MAAM,KAAK,KAAK,cAAc,EAC9C,KAAK,eAAe,MAAA,EACpB,KAAK,WAAa,GAClBA,EAAQ,QAAQ3B,GAAKA,EAAE,IAAA,CAAK,CAC9B,CAAC,CACH,CACF,CAGO,SACL4B,EACAC,EACAX,EACa,CACb,MAAMY,EAAQ,KAAK,OAAyB,CAAE,KAAM,KAAM,QAAS,GAAM,MAAO,MAAQZ,GAAc,kBAAkB,EACxH,IAAIa,EAAqC,KAEzC,MAAMC,EAAO,MAAOC,GAAc,CAChCF,GAAY,MAAA,EACZA,EAAa,IAAI,gBAGjB,MAAMG,EAAc,KAAK,QAAQ,IAAMJ,EAAM,MAAM,IAAI,EAGvDA,EAAM,MAAQ,CAAE,KAAMI,EAAa,QAAS,GAAM,MAAO,IAAA,EAEzD,GAAI,CACF,MAAMC,EAAO,MAAMP,EAAQK,EAAQF,EAAW,MAAM,EAC/CA,EAAW,OAAO,UACrBD,EAAM,MAAQ,CAAE,KAAAK,EAAM,QAAS,GAAO,MAAO,IAAA,EAEjD,OAASnC,EAAQ,CACXA,EAAE,OAAS,eACb8B,EAAM,MAAQ,CAAE,KAAM,KAAM,QAAS,GAAO,MAAO9B,CAAA,EAEvD,CACF,EAIA,YAAK,OAAO,IAAM,CAEhB,MAAMiC,EAASJ,EAASA,EAAO,MAAQ,OAIvC,OAAAG,EAAKC,CAAM,EAEJ,IAAMF,GAAY,MAAA,CAC3B,CAAC,EAEM,CACL,IAAI,MAAO,CAAE,OAAOD,EAAM,MAAM,IAAM,EACtC,IAAI,SAAU,CAAE,OAAOA,EAAM,MAAM,OAAS,EAC5C,IAAI,OAAQ,CAAE,OAAOA,EAAM,MAAM,KAAO,EACxC,IAAI,OAAQ,CAAE,OAAOA,EAAM,KAAO,EAClC,QAAS,IAAME,EAAKH,EAASA,EAAO,MAAQ,MAAc,EAC1D,UAAYjB,GAAOkB,EAAM,UAAUlB,CAAE,CAAA,CAEzC,CAKO,QAAWC,EAAgB,CAChC,MAAMG,EAAO,KAAK,aAClB,KAAK,aAAe,KACpB,GAAI,CACF,OAAOH,EAAA,CACT,QAAA,CACE,KAAK,aAAeG,CACtB,CACF,CAGO,iBAAiBtB,EAAeC,EAAgB,CACrD,KAAK,cAAgB,CAAE,SAAAD,EAAU,UAAAC,CAAAA,CACnC,CAEO,IAAOyC,EAAqE,CACjF,GAAI,CAAC,KAAK,cACR,MAAM,IAAI,MAAM,sGAAsG,EAIxH,GAAI,CAACA,GAAQ,OAAOA,EAAK,WAAc,WAAY,CACjD,MAAM1B,EAAW;AAAA;AAAA;AAAA;AAAA,QAKjB,cAAQ,MAAMA,EAAU,CAAE,KAAA0B,CAAA,CAAM,EAC1B,IAAI,MAAM1B,CAAQ,CAC1B,CACA,KAAM,CAACN,EAAKiC,CAAM,EAAI,KAAK,cAAc,SAASD,EAAK,KAAK,EAE5D,YAAK,cAAc,UACjB,IACSA,EAAK,UAAUC,CAAM,EAE9B,CAACD,CAAI,CAAA,EAGAhC,CACT,CAEQ,QAAQkC,EAAiBzB,EAAgB,CAC/C,GAAI,CACFA,EAAA,CACF,OAAS0B,EAAO,CACd,QAAQ,MACN,kDACA,uEACA,qBACAA,CAAA,CAGJ,CACF,CACF,CCpUO,MAAeC,CAAc,CAClC,YAAsBrC,EAAwB,CAAxB,KAAA,OAAAA,CAA0B,CAA1B,OAEZ,oBAAoBC,EAAaG,EAAc,CACvD,OAAO,KAAK,OAAO,OAAOH,EAAK,CAC7B,KAAAG,EACA,SAAWkC,GAAM,OAAOA,GAAM,UAAY,CAAC,MAAMA,CAAC,CAAA,CACnD,CACH,CACF,CC7BO,MAAeC,CAAsB,CAC1C,YAAsBvC,EAAwB,CAAxB,KAAA,OAAAA,EACpB,WAAW,IAAM,KAAK,OAAA,EAAU,CAAC,CACnC,CAFsB,OAIZ,QAAe,CAAE,CAC7B"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/BaseREService.ts","../src/BaseREServiceEnhanced.ts","../src/core.ts","../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, useEffect } 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 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 * @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 private services = new Map<Token<any>, any>();\n private factories = new Map<Token<any>, Factory<any>>();\n private proxyCache = new WeakMap<object, any>();\n private allEffects = new Set<IEffect>();\n\n /**\n * Коллбек для уведомления о изменении сигнала.\n * @type {Function}\n */\n public onSignalChange?: (name: string, next: any, prev: any) => void;\n\n private reactAdapters = {\n useState: useState as any,\n useEffect: useEffect as any,\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 */\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 */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n if (this.services.has(token)) return this.services.get(token);\n\n // NOTE: (new way) Если это класс, но мы забыли его унаследовать от BaseService или передать engine\n try {\n // NOTE: Логика создания инстанса\n const factory = this.factories.get(token);\n if (factory) {\n const instance = factory(this);\n this.services.set(token, 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(token, 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 */\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\n // Парсим аргументы для обратной совместимости\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n\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 subscribers.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n );\n },\n subscribe(cb) {\n return engine.effect(() => cb(this.value));\n }\n };\n }\n\n /**\n * Создание эффекта.\n * @function effect\n * @param {EffectFn} fn - Функция эффекта.\n * @returns {CleanupFn} - Функция для очистки эффекта.\n */\n public effect(fn: EffectFn): CleanupFn {\n const engine = this;\n const effectObj: IEffect = {\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 return () => {\n effectObj.cleanups.forEach(c => c());\n engine.pendingEffects.delete(effectObj);\n };\n }\n\n /**\n * Создание вычисляемого значения.\n * @template T\n * @function computed\n * @param {Function} fn - Функция для вычисления значения.\n * @param {string} [signalName] - Имя сигнала.\n * @returns {Computed<T>} - Вычисляемое значение.\n */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n const sig = this.signal<T>(undefined as any, signalName || 'unnamed_computed');\n this.effect(() => {\n const newValue = fn();\n // Сигнал сам внутри себя проверит (sig.value === newValue)\n // благодаря правке в пункте №1\n sig.value = newValue;\n });\n return {\n get value() { return sig.value; },\n // Подписываемся на внутренний сигнал\n subscribe: (cb) => sig.subscribe(cb)\n };\n }\n\n /**\n * Создание реактивного объекта (Proxy).\n * @template T\n * @function reactive\n * @param {T} target - Целевой объект для проксирования.\n * @param {string} [name] - Имя проксируемого объекта.\n * @returns {T} - Реактивный объект.\n */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) return this.proxyCache.get(target);\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 */\n public batch(fn: () => void): void {\n this.isBatching = true;\n try { fn(); } finally {\n queueMicrotask(() => {\n const effects = Array.from(this.pendingEffects);\n this.pendingEffects.clear();\n this.isBatching = false;\n effects.forEach(e => e.run());\n });\n }\n }\n\n /**\n * Создание асинхронного ресурса.\n * @template T\n * @function resource\n * @param {Function} fetcher - Функция для загрузки данных.\n * @param {{ value: S }} [source] - Источник данных.\n * @param {string} [signalName] - Имя сигнала ресурса.\n * @returns {Resource<T>} - Асинхронный ресурс.\n */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n signalName?: string,\n ): Resource<T> {\n const state = this.signal<ResourceState<T>>({ data: null, loading: true, error: null }, signalName || 'unnamed_resource');\n let controller: AbortController | null = null;\n\n const load = async (sValue: S) => {\n controller?.abort();\n controller = new AbortController();\n\n // Читаем текущие данные без создания зависимости!\n const currentData = this.untrack(() => state.value.data);\n\n // Теперь обновление состояния не вызовет перезапуск эффекта\n state.value = { data: currentData, loading: true, error: null };\n\n try {\n const data = await fetcher(sValue, controller.signal);\n if (!controller.signal.aborted) {\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 // Чтобы эффект не подписывался на state,\n // нам нужно изолировать чтение source от чтения state\n this.effect(() => {\n // Читаем ТОЛЬКО source. Это наша единственная зависимость.\n const sValue = source ? source.value : undefined as S;\n\n // Вызываем load. Внутри load чтение state.value\n // не должно регистрироваться как зависимость этого эффекта.\n load(sValue);\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),\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 */\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 */\n public setReactAdapters(useState: any, useEffect: any) {\n this.reactAdapters = { useState, useEffect };\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 */\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 */\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 { CleanupFn } from '../core'; // импорт типа отписки, если нужен\n\ninterface 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 */\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 */\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 } from \"react\";\nimport { Signal } from '../core';\n\n/**\n * NOTE: (v2) Современный (React 18+) через useSyncExternalStore\n * Это самый надежный, производительный и лаконичный способ подружить реактивные сигналы с React.\n *\n * Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n *\n * @template T Тип данных в контейнере\n * @param {Signal<T> | Computed<T> | Resource<T>} reactiveItem Любой реактивный объект с .value и .subscribe\n */\nexport const useReactiveValue = <T>(\n reactiveItem: Pick<Signal<T>, 'value' | 'subscribe'>\n): T => {\n return useSyncExternalStore(\n // 1. Функция подписки (React сам вызывает её при монтировании и очищает при размонтировании)\n reactiveItem.subscribe,\n // 2. Функция получения текущего snapshot значения\n () => reactiveItem.value\n );\n};\n","import { useState, useEffect, useRef } from \"react\";\nimport { Signal, CleanupFn } from '../core';\n\n/**\n * NOTE: (v1) Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.\n *\n * Классический (React 16.8+) через useState + useEffect\n * Если вам нужна обратная совместимость со старыми версиями React, где нет useSyncExternalStore, используем классическую связку.\n */\nexport const useReactiveValue0 = <T>(\n reactiveItem: Pick<Signal<T>, 'value' | 'subscribe'>\n): T => {\n // Инициализируем стейт текущим значением сигнала\n const [state, setState] = useState<T>(reactiveItem.value);\n\n // Храним ссылку на актуальный setter стейта\n const setStateRef = useRef(setState);\n useEffect(() => {\n setStateRef.current = setState;\n }, [setState]);\n\n useEffect(() => {\n // При изменении реактивного элемента синхронизируем локальный стейт\n setStateRef.current(reactiveItem.value);\n\n // Подписываемся на будущие изменения\n const unsubscribe: CleanupFn = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue);\n });\n\n return unsubscribe;\n }, [reactiveItem]);\n\n return state;\n};\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,10 +10,30 @@ export declare abstract class BaseREServiceEnhanced {
|
|
|
10
10
|
protected onInit(): void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
declare interface CacheOptions {
|
|
14
|
+
/** Время жизни кэша в миллисекундах. По умолчанию 5 минут (300000 мс) */
|
|
15
|
+
ttl?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
export declare type CleanupFn = () => void;
|
|
14
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Интерфейс для вычисляемого значения.
|
|
22
|
+
* @template T
|
|
23
|
+
* @interface Computed<T>
|
|
24
|
+
*/
|
|
15
25
|
export declare interface Computed<T> {
|
|
26
|
+
/**
|
|
27
|
+
* Только читаемое значение вычисляемого значения.
|
|
28
|
+
* @type {T}
|
|
29
|
+
*/
|
|
16
30
|
readonly value: T;
|
|
31
|
+
/**
|
|
32
|
+
* Подписка на изменение вычисляемого значения.
|
|
33
|
+
* @function subscribe
|
|
34
|
+
* @param {Function} cb - Коллбек функция для обработки изменения.
|
|
35
|
+
* @returns {CleanupFn} - Функция для очистки подписки.
|
|
36
|
+
*/
|
|
17
37
|
subscribe: (cb: (val: T) => void) => CleanupFn;
|
|
18
38
|
}
|
|
19
39
|
|
|
@@ -26,6 +46,44 @@ export declare interface IEffect {
|
|
|
26
46
|
cleanups: Set<CleanupFn>;
|
|
27
47
|
}
|
|
28
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Функция для очистки эффекта.
|
|
51
|
+
* @typedef {() => void} CleanupFn
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Функция эффекта, которая может возвращать функцию очистки.
|
|
55
|
+
* @typedef {() => CleanupFn | void} EffectFn
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Токен для зависимости.
|
|
59
|
+
* @typedef {string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T }} Token<T>
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Фабрика для создания сервиса.
|
|
63
|
+
* @template T
|
|
64
|
+
* @typedef {(engine: ReactiveEngine) => T} Factory<T>
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* Интерфейс для эффекта.
|
|
68
|
+
* @interface IEffect
|
|
69
|
+
*/
|
|
70
|
+
export declare interface IEffect {
|
|
71
|
+
/**
|
|
72
|
+
* Запуск эффекта.
|
|
73
|
+
* @function run
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
run: () => void;
|
|
77
|
+
/**
|
|
78
|
+
* Коллекция функций очистки.
|
|
79
|
+
* @type {Set<CleanupFn>}
|
|
80
|
+
*/
|
|
81
|
+
cleanups: Set<CleanupFn>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Класс реактивного движка.
|
|
86
|
+
*/
|
|
29
87
|
export declare class ReactiveEngine {
|
|
30
88
|
private activeEffect;
|
|
31
89
|
private isBatching;
|
|
@@ -34,42 +92,143 @@ export declare class ReactiveEngine {
|
|
|
34
92
|
private factories;
|
|
35
93
|
private proxyCache;
|
|
36
94
|
private allEffects;
|
|
95
|
+
/**
|
|
96
|
+
* Коллбек для уведомления о изменении сигнала.
|
|
97
|
+
* @type {Function}
|
|
98
|
+
*/
|
|
37
99
|
onSignalChange?: (name: string, next: any, prev: any) => void;
|
|
38
100
|
private reactAdapters;
|
|
39
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* DI: Регистрация зависимости.
|
|
103
|
+
* @template T
|
|
104
|
+
* @function provide
|
|
105
|
+
* @param {Token<T>} token - Токен для зависимости.
|
|
106
|
+
* @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
40
109
|
provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void;
|
|
41
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* DI: Инъекция; Получение сервиса по токену.
|
|
112
|
+
* @template T
|
|
113
|
+
* @function inject
|
|
114
|
+
* @param {Token<T>} token - Токен для зависимости.
|
|
115
|
+
* @returns {T} - Сервис.
|
|
116
|
+
*/
|
|
42
117
|
inject<T>(token: Token<T>): T;
|
|
43
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Создание сигнала.
|
|
120
|
+
* @template T
|
|
121
|
+
* @function signal
|
|
122
|
+
* @param {T} initialValue - Начальное значение сигнала.
|
|
123
|
+
* @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.
|
|
124
|
+
* @returns {Signal<T>} - Сигнал.
|
|
125
|
+
*/
|
|
44
126
|
signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T>;
|
|
45
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Создание эффекта.
|
|
129
|
+
* @function effect
|
|
130
|
+
* @param {EffectFn} fn - Функция эффекта.
|
|
131
|
+
* @returns {CleanupFn} - Функция для очистки эффекта.
|
|
132
|
+
*/
|
|
46
133
|
effect(fn: EffectFn): CleanupFn;
|
|
47
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Создание вычисляемого значения.
|
|
136
|
+
* @template T
|
|
137
|
+
* @function computed
|
|
138
|
+
* @param {Function} fn - Функция для вычисления значения.
|
|
139
|
+
* @param {string} [signalName] - Имя сигнала.
|
|
140
|
+
* @returns {Computed<T>} - Вычисляемое значение.
|
|
141
|
+
*/
|
|
48
142
|
computed<T>(fn: () => T, signalName?: string): Computed<T>;
|
|
49
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Создание реактивного объекта (Proxy).
|
|
145
|
+
* @template T
|
|
146
|
+
* @function reactive
|
|
147
|
+
* @param {T} target - Целевой объект для проксирования.
|
|
148
|
+
* @param {string} [name] - Имя проксируемого объекта.
|
|
149
|
+
* @returns {T} - Реактивный объект.
|
|
150
|
+
*/
|
|
50
151
|
reactive<T extends object>(target: T, name?: string): T;
|
|
51
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Группировка изменений.
|
|
154
|
+
* @function batch
|
|
155
|
+
* @param {Function} fn - Функция для выполнения в группе.
|
|
156
|
+
* @returns {void}
|
|
157
|
+
*/
|
|
52
158
|
batch(fn: () => void): void;
|
|
53
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Создание асинхронного ресурса.
|
|
161
|
+
* @template T
|
|
162
|
+
* @function resource
|
|
163
|
+
* @param {Function} fetcher - Функция для загрузки данных.
|
|
164
|
+
* @param {{ value: S }} [source] - Источник данных.
|
|
165
|
+
* @param {string} [signalName] - Имя сигнала ресурса.
|
|
166
|
+
* @returns {Resource<T>} - Асинхронный ресурс.
|
|
167
|
+
*/
|
|
54
168
|
resource<T, S = void>(fetcher: (source: S, signal: AbortSignal) => Promise<T>, source?: {
|
|
55
169
|
value: S;
|
|
56
170
|
}, signalName?: string): Resource<T>;
|
|
57
171
|
/**
|
|
58
|
-
* Выполняет функцию без отслеживания
|
|
172
|
+
* Выполняет функцию без отслеживания зависимостей.
|
|
173
|
+
* @template T
|
|
174
|
+
* @function untrack
|
|
175
|
+
* @param {Function} fn - Функция для выполнения.
|
|
176
|
+
* @returns {T} - Результат выполнения функции.
|
|
59
177
|
*/
|
|
60
178
|
untrack<T>(fn: () => T): T;
|
|
61
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* Установка адаптеров React.
|
|
181
|
+
* @function setReactAdapters
|
|
182
|
+
* @param {Function} useState - Функция useState из React.
|
|
183
|
+
* @param {Function} useEffect - Функция useEffect из React.
|
|
184
|
+
* @returns {void}
|
|
185
|
+
*/
|
|
62
186
|
setReactAdapters(useState: any, useEffect: any): void;
|
|
187
|
+
/**
|
|
188
|
+
* Использование реактивного значения в React компоненте.
|
|
189
|
+
* @template T
|
|
190
|
+
* @function use
|
|
191
|
+
* @param {{ value: T; subscribe: (cb: (v: T) => void) => CleanupFn }} item - Реактивный объект.
|
|
192
|
+
* @returns {T} - Значение реактивного объекта.
|
|
193
|
+
*/
|
|
63
194
|
use<T>(item: {
|
|
64
195
|
value: T;
|
|
65
196
|
subscribe: (cb: (v: T) => void) => CleanupFn;
|
|
66
197
|
}): T;
|
|
198
|
+
/**
|
|
199
|
+
* Безопасное выполнение функции.
|
|
200
|
+
* @private
|
|
201
|
+
* @function safeRun
|
|
202
|
+
* @param {IEffect} effect - Эффект.
|
|
203
|
+
* @param {Function} fn - Функция для выполнения.
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
67
206
|
private safeRun;
|
|
68
207
|
}
|
|
69
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Интерфейс для ресурса.
|
|
211
|
+
* @template T
|
|
212
|
+
* @interface Resource<T>
|
|
213
|
+
*/
|
|
70
214
|
export declare interface Resource<T> extends ResourceState<T> {
|
|
215
|
+
/**
|
|
216
|
+
* Перезагрузка ресурса.
|
|
217
|
+
* @function refetch
|
|
218
|
+
* @returns {void}
|
|
219
|
+
*/
|
|
71
220
|
refetch: () => void;
|
|
221
|
+
/**
|
|
222
|
+
* Подписка на изменение состояния ресурса.
|
|
223
|
+
* @function subscribe
|
|
224
|
+
* @param {Function} cb - Коллбек функция для обработки изменения.
|
|
225
|
+
* @returns {CleanupFn} - Функция для очистки подписки.
|
|
226
|
+
*/
|
|
72
227
|
subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;
|
|
228
|
+
/**
|
|
229
|
+
* Только читаемое состояние ресурса.
|
|
230
|
+
* @type {ResourceState<T>}
|
|
231
|
+
*/
|
|
73
232
|
readonly value: ResourceState<T>;
|
|
74
233
|
}
|
|
75
234
|
|
|
@@ -79,13 +238,66 @@ export declare interface ResourceState<T> {
|
|
|
79
238
|
error: any | null;
|
|
80
239
|
}
|
|
81
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Интерфейс для состояния ресурса.
|
|
243
|
+
* @template T Data format
|
|
244
|
+
* @interface ResourceState<T>
|
|
245
|
+
*/
|
|
246
|
+
export declare interface ResourceState<T> {
|
|
247
|
+
/**
|
|
248
|
+
* Данные ресурса.
|
|
249
|
+
* @type {T | null}
|
|
250
|
+
*/
|
|
251
|
+
data: T | null;
|
|
252
|
+
/**
|
|
253
|
+
* Состояние загрузки ресурса.
|
|
254
|
+
* @type {boolean}
|
|
255
|
+
*/
|
|
256
|
+
loading: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Ошибка ресурса.
|
|
259
|
+
* @type {any | null}
|
|
260
|
+
*/
|
|
261
|
+
error: any | null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Интерфейс для сигнала.
|
|
266
|
+
* @template T
|
|
267
|
+
* @interface Signal<T>
|
|
268
|
+
*/
|
|
82
269
|
export declare interface Signal<T> {
|
|
270
|
+
/**
|
|
271
|
+
* Значение сигнала.
|
|
272
|
+
* @type {T}
|
|
273
|
+
*/
|
|
83
274
|
value: T;
|
|
275
|
+
/**
|
|
276
|
+
* Подписка на изменение значения сигнала.
|
|
277
|
+
* @function subscribe
|
|
278
|
+
* @param {Function} cb - Коллбек функция для обработки изменения.
|
|
279
|
+
* @returns {CleanupFn} - Функция для очистки подписки.
|
|
280
|
+
*/
|
|
84
281
|
subscribe: (cb: (val: T) => void) => CleanupFn;
|
|
85
282
|
}
|
|
86
283
|
|
|
87
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Интерфейс для опций сигнала.
|
|
286
|
+
* @template T
|
|
287
|
+
* @interface SignalOptions<T>
|
|
288
|
+
*/
|
|
289
|
+
export declare interface SignalOptions<T> {
|
|
290
|
+
/**
|
|
291
|
+
* Имя сигнала.
|
|
292
|
+
* @type {string}
|
|
293
|
+
*/
|
|
88
294
|
name?: string;
|
|
295
|
+
/**
|
|
296
|
+
* Валидатор значения сигнала.
|
|
297
|
+
* @function validate
|
|
298
|
+
* @param {T} val - Значение для валидации.
|
|
299
|
+
* @returns {boolean | string} - Результат валидации.
|
|
300
|
+
*/
|
|
89
301
|
validate?: (val: T) => boolean | string;
|
|
90
302
|
}
|
|
91
303
|
|
|
@@ -93,4 +305,38 @@ export declare type Token<T> = string | symbol | {
|
|
|
93
305
|
new (engine: ReactiveEngine, ...args: any[]): T;
|
|
94
306
|
};
|
|
95
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Универсальный хук для подписки на изменения Signal, Computed или Resource.
|
|
310
|
+
* @template T Тип данных внутри реактивного контейнера
|
|
311
|
+
* @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки
|
|
312
|
+
* @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение
|
|
313
|
+
*/
|
|
314
|
+
export declare const useReactiveSubscription: <T>(signal: Pick<Signal<T>, "subscribe">, callback: (val: T) => void) => void;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* NOTE: (v2) Современный (React 18+) через useSyncExternalStore
|
|
318
|
+
* Это самый надежный, производительный и лаконичный способ подружить реактивные сигналы с React.
|
|
319
|
+
*
|
|
320
|
+
* Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.
|
|
321
|
+
*
|
|
322
|
+
* @template T Тип данных в контейнере
|
|
323
|
+
* @param {Signal<T> | Computed<T> | Resource<T>} reactiveItem Любой реактивный объект с .value и .subscribe
|
|
324
|
+
*/
|
|
325
|
+
export declare const useReactiveValue: <T>(reactiveItem: Pick<Signal<T>, "value" | "subscribe">) => T;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* NOTE: (v1) Хук для извлечения значения из Signal/Computed/Resource и авто-ререндера компонента.
|
|
329
|
+
*
|
|
330
|
+
* Классический (React 16.8+) через useState + useEffect
|
|
331
|
+
* Если вам нужна обратная совместимость со старыми версиями React, где нет useSyncExternalStore, используем классическую связку.
|
|
332
|
+
*/
|
|
333
|
+
export declare const useReactiveValue0: <T>(reactiveItem: Pick<Signal<T>, "value" | "subscribe">) => T;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Декоратор для создания кэширующего fetcher-а для engine.resource
|
|
337
|
+
* @param fetcher Оригинальная асинхронная функция запроса
|
|
338
|
+
* @param options Настройки кэширования
|
|
339
|
+
*/
|
|
340
|
+
export declare const withCache: <S, T>(fetcher: (source: S, signal: AbortSignal) => Promise<T>, options?: CacheOptions) => (source: S, signal: AbortSignal) => Promise<T>;
|
|
341
|
+
|
|
96
342
|
export { }
|
package/dist/index.iife.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
1
|
+
var ReactiveEngineLib=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=class{engine;constructor(e){this.engine=e}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}},r=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}},i=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). Проверьте импорты.`);if(this.services.has(e))return this.services.get(e);try{let t=this.factories.get(e);if(t){let n=t(this);return this.services.set(e,n),n}if(typeof e==`function`&&e.prototype){let t=new e(this);return this.services.set(e,t),t}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.isBatching?n.pendingEffects.add(e):e.run())},subscribe(e){return n.effect(()=>e(this.value))}}}effect(e){let t=this,n={cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let n=t.activeEffect;t.activeEffect=this,t.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),t.activeEffect=n}};return this.allEffects.add(n),n.run(),()=>{n.cleanups.forEach(e=>e()),t.pendingEffects.delete(n)}}computed(e,t){let n=this.signal(void 0,t||`unnamed_computed`);return this.effect(()=>{let t=e();n.value=t}),{get value(){return n.value},subscribe:e=>n.subscribe(e)}}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){this.isBatching=!0;try{e()}finally{queueMicrotask(()=>{let e=Array.from(this.pendingEffects);this.pendingEffects.clear(),this.isBatching=!1,e.forEach(e=>e.run())})}}resource(e,t,n){let r=this.signal({data:null,loading:!0,error:null},n||`unnamed_resource`),i=null,a=async t=>{i?.abort(),i=new AbortController;let n=this.untrack(()=>r.value.data);r.value={data:n,loading:!0,error:null};try{let n=await e(t,i.signal);i.signal.aborted||(r.value={data:n,loading:!1,error:null})}catch(e){e.name!==`AbortError`&&(r.value={data:null,loading:!1,error:e})}};return this.effect(()=>{let e=t?t.value:void 0;return a(e),()=>i?.abort()}),{get data(){return r.value.data},get loading(){return r.value.loading},get error(){return r.value.error},get value(){return r.value},refetch:()=>a(t?t.value:void 0),subscribe:e=>r.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(...)
|
|
5
|
-
`;throw console.error(
|
|
6
|
-
//# sourceMappingURL=index.iife.js.map
|
|
5
|
+
`;throw console.error(t,{item:e}),Error(t)}let[t,n]=this.reactAdapters.useState(e.value);return this.reactAdapters.useEffect(()=>e.subscribe(n),[e]),t}safeRun(e,t){try{t()}catch(e){console.error(`%c[Reactive Error] %cОшибка в эффекте/computed:`,`color: white; background: red; padding: 2px 4px; border-radius: 3px;`,`font-weight: bold;`,e)}}};return e.BaseREService=n,e.BaseREServiceEnhanced=r,e.ReactiveEngine=i,e.useReactiveSubscription=(e,n)=>{let r=(0,t.useRef)(n);(0,t.useEffect)(()=>{r.current=n},[n]),(0,t.useEffect)(()=>e.subscribe(e=>{r.current(e)}),[e])},e.useReactiveValue=e=>(0,t.useSyncExternalStore)(e.subscribe,()=>e.value),e.useReactiveValue0=e=>{let[n,r]=(0,t.useState)(e.value),i=(0,t.useRef)(r);return(0,t.useEffect)(()=>{i.current=r},[r]),(0,t.useEffect)(()=>(i.current(e.value),e.subscribe(e=>{i.current(e)})),[e]),n},e.withCache=(e,t={})=>{let n=t.ttl??300*1e3,r=new Map;return async(t,i)=>{let a=typeof t==`object`&&t?JSON.stringify(t):String(t),o=Date.now(),s=r.get(a);if(s&&o-s.timestamp<n)return s.data;let c=await e(t,i);return r.set(a,{data:c,timestamp:Date.now()}),c}},e})({},React);
|
|
6
|
+
//# sourceMappingURL=index.iife.js.map
|