@pravosleva/reactive-engine 1.5.5-beta → 1.5.6-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/reactive-engine.umd.js +11 -1
- package/dist/vue/composables/index.d.ts +2 -0
- package/dist/vue/composables/index.d.ts.map +1 -0
- package/dist/vue/composables/useReactiveValue.cjs +2 -0
- package/dist/vue/composables/useReactiveValue.cjs.map +1 -0
- package/dist/vue/composables/useReactiveValue.d.ts +53 -0
- package/dist/vue/composables/useReactiveValue.d.ts.map +1 -0
- package/dist/vue/composables/useReactiveValue.mjs +14 -0
- package/dist/vue/composables/useReactiveValue.mjs.map +1 -0
- package/dist/vue/composables/useReactiveValue.test.d.ts +2 -0
- package/dist/vue/composables/useReactiveValue.test.d.ts.map +1 -0
- package/dist/vue/index.cjs +1 -1
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.mjs +2 -1
- package/package.json +1 -1
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ReactiveEngineLib={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=({tested:e,expectedKey:t,valueType:n})=>{let r=[];if(!e||!Array.isArray(e))return r;for(let i=0,a=e.length;i<a;i++){let a=e[i];if(typeof a!=`string`)continue;let o;switch(n){case`number`:o=RegExp(`\\[${t}=(?<value>\\d+)\\]`,`g`);break;default:o=RegExp(`\\[${t}=(?<value>.*?)\\]`,`g`);break}for(let e of a.matchAll(o))e?.groups&&r.push(e.groups.value)}return r},n=class{frameworkPrefix=`core`;activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;loggerOptions;pendingLogQueue=[];lastTransactionDuration=null;signalMutationCounts=new Map;lastComputedDurations=new Map;effectExecutionCounts=new Map;batchTickCounts=0;constructor(e){this.loggerOptions=e?.logger}log(e,n,r){if(!this.loggerOptions?.isEnabled)return;if(!this.loggerOptions?.isCoreOptimizationDebugEnabled){let e=t({expectedKey:`CORE_INTERNAL_SIGNAL`,tested:[n],valueType:`number`})?.[0]===`1`,r=t({expectedKey:`CORE_INTERNAL_EFFECT`,tested:[n],valueType:`number`})?.[0]===`1`;if(e||r)return}if(this.loggerOptions.filter&&!(this.loggerOptions.filter instanceof RegExp?this.loggerOptions.filter:new RegExp(this.loggerOptions.filter.replace(/^\/|\/$/g,``))).test(n))return;let i={signal:`background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,computed:`background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,effect:`background: #e01e5a; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,batch:`background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,resource:`background: #d97706; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`},a=``,o=`color: #aaa; font-weight: normal;`;switch(e){case`signal`:{let s=t({tested:[n],expectedKey:`CORE_INTERNAL_SIGNAL`,valueType:`number`})?.[0]===`1`,c=n,l=(this.signalMutationCounts.get(c)||0)+1;this.signalMutationCounts.set(c,l);let u=r,d=u.subscribers&&u.subscribers.length>0?u.subscribers.every(e=>e?t({tested:[e],expectedKey:`IS_OPTIMIZED`,valueType:`number`})?.[0]===`1`:!1):!1,f=l>50,p=s?`COMPUTED_INTERNAL`:`SIGNAL`;a=` 🔄 Изменений ${s?`кэша`:`сигнала`}: ${l}${f?` ⚠️ (high noise — обнаружен дребезг/спам значений!)`:``}`,f&&(o=`color: orange; font-weight: bold;`,d||(r.__performance_advice__={issue:`Сигнал [${c}] обновляется слишком часто (${l} раз за такт). Это приводит к избыточным ререндерам UI.`,solution:`Оберните чтение этого сигнала в декоратор сжатия потока данных 'withThrottleComputed'`,example:`public throttled = withThrottleComputed(this.engine, () => this.yourSignalName.value, { limit: 300 });`})),console.groupCollapsed(`%c${p}%c [${c}]%c${a}`,s?`background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`:i[e],`color: #aaa; font-weight: bold;`,o),console.log(`Данные (Payload):`,r),this.loggerOptions.traceTime&&typeof performance<`u`&&console.log(`Тайминг рантайма:`,`${performance.now().toFixed(2)} ms`),console.groupEnd();return}case`computed`:{let e=r;if(e&&`to`in e){let t=e.to;a=` 🧮 Значение: ${typeof t==`object`&&t?`{...}`:String(t)}`}else if(e&&`duration`in e){let t=parseFloat(e.duration);if(!isNaN(t)){let r=this.lastComputedDurations.get(n);if(r!==void 0&&r>0){let n=(t-r)/r*100;n>5?(a=` 🔴 Замедление: +${n.toFixed(1)}% (${e.duration})`,o=`color: #e01e5a; font-weight: bold;`):n<-5?(a=` 🟢 Ускорение: ${n.toFixed(1)}% (${e.duration})`,o=`color: #42b883; font-weight: bold;`):(a=` 🟢 Стабильно (${e.duration})`,o=`color: #42b883; font-weight: bold;`)}else a=` ⚪ Первый расчет (${e.duration})`;this.lastComputedDurations.set(n,t)}}break}case`effect`:{let e=(this.effectExecutionCounts.get(n)||0)+1;this.effectExecutionCounts.set(n,e);let t=e>30,i=r;a=` 🟢 Стабильно${i&&`duration`in i?` (${i.duration})`:``} | 🚀 Вызовов: ${e}${t?` ⚠️ (heavy re-renders)`:``}`,o=`color: #42b883; font-weight: bold;`,t&&(o=`color: #ff4a4a; font-weight: bold;`);break}case`batch`:this.batchTickCounts+=1,a=` 📦 Номер транзакции: #${this.batchTickCounts}`;break;case`resource`:{let e=r;if(e.loading)a=` ⏳ ЗАГРУЗКА (fetching...)`,o=`color: #d97706; font-weight: bold;`;else if(e.error)a=` 🔴 ОШИБКА: ${e.error.message||`Unknown Error`}`,o=`color: #ff4a4a; font-weight: bold;`;else{let t=e.data;a=` 🟢 УСПЕХ (data: ${typeof t==`object`&&t?`{...}`:String(t)})`,o=`color: #42b883; font-weight: bold;`}break}default:break}console.groupCollapsed(`%c${e.toUpperCase()}%c [${n}]%c${a}`,i[e],`color: #aaa; font-weight: bold;`,o),console.log(`Данные (Payload):`,r),this.loggerOptions.traceTime&&typeof performance<`u`&&console.log(`Тайминг рантайма:`,`${performance.now().toFixed(2)} ms`),console.groupEnd()}onSignalChange;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`}`)}}queueLog(e,t,n){this.loggerOptions?.isEnabled&&this.pendingLogQueue.push({type:e,name:t,detail:n})}flushLogs(){if(!this.loggerOptions?.isEnabled||this.pendingLogQueue.length===0)return;let e=typeof performance<`u`?performance.now():0,t=this.pendingLogQueue;if(this.loggerOptions.filter)try{let e;if(this.loggerOptions.filter instanceof RegExp)e=this.loggerOptions.filter;else{let t=this.loggerOptions.filter.replace(/^\/|\/$/g,``);e=new RegExp(t)}t=t.filter(t=>e.test(t.name))}catch(e){console.warn(`[Logger Error]: Некорректный паттерн фильтрации логов`,e)}if(t.length===0){this.pendingLogQueue=[];return}let n=``,r=``,i=`color: #aaa; font-weight: normal;`;if(this.loggerOptions.traceTime&&e){let t=performance.now()-e;if(n=` | Duration: ${t.toFixed(3)} ms`,this.lastTransactionDuration!==null&&this.lastTransactionDuration>0){let e=(t-this.lastTransactionDuration)/this.lastTransactionDuration*100;e>.1?(r=` 🔴 +${e.toFixed(1)}%`,i=`color: #e01e5a; font-weight: bold;`):e<-.1?(r=` 🟢 ${e.toFixed(1)}%`,i=`color: #42b883; font-weight: bold;`):(r=` 🟢 stable`,i=`color: #42b883; font-weight: bold;`)}else r=` ⚪ first`;this.lastTransactionDuration=t}console.groupCollapsed(`%cREACTIVE TRANSACTION${this.loggerOptions.instanceName?` ${this.loggerOptions.instanceName}`:``}%c Microtask Tick (Size: ${t.length}${n})%c${r}`,`background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,`color: #aaa; font-weight: normal;`,i),t.forEach(e=>{this.log(e.type,e.name,e.detail)}),this.loggerOptions.traceTime&&typeof performance<`u`&&console.log(`%c[Execution Timestamp]: ${performance.now().toFixed(2)} ms`,`color: #7952b3; font-weight: bold;`),console.groupEnd(),this.pendingLogQueue=[]}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(){if(n.activeEffect){let e=n.activeEffect;i.has(e)||(i.add(e),e.cleanups.add(()=>{i.delete(e)}))}return 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);let s=Array.from(i).map(e=>e.label||`unnamed_effect`);n.queueLog?.(`signal`,o,{from:t,to:e,subscribersCount:i.size,subscribers:s}),i.forEach(e=>n.pendingEffects.add(e)),n.isBatching||(n.isBatching=!0,queueMicrotask(()=>{for(let e of n.pendingEffects)n.pendingEffects.delete(e),e.run();n.isBatching=!1,n.flushLogs?.()}))},subscribe(e){return n.effect(()=>{e(this.value)},`${n.frameworkPrefix}:use:${o}`)}}}effect(e,t){let n=this,r=!0,i={label:t,cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let t=n.activeEffect;n.activeEffect=this;let i=performance.now();n.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),n.activeEffect=t;let a=performance.now()-i;if(!r&&n.loggerOptions?.isEnabled){let e=n.loggerOptions.filter;(!e||e instanceof RegExp&&this.label&&e.test(this.label))&&typeof n.queueLog==`function`&&n.queueLog(`effect`,this.label||`unnamed-efect-0`,{name:this.label||`unnamed-efect-0`,type:`effect`,detail:{status:`stable`,duration:`${a.toFixed(3)}ms`,timestamp:i}})}r=!1}};return this.allEffects.add(i),i.run(),()=>{i.cleanups.forEach(e=>e()),n.pendingEffects.delete(i),n.allEffects.delete(i)}}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=t||`unnamed_computed`,i=this.signal(void 0,`[CORE_INTERNAL_SIGNAL=1]:${r}`),a=!0,o,s=this.effect(()=>{o=e(),a=!1;let t=typeof performance<`u`&&performance.now()?`0.010ms`:`N/A`;typeof n.queueLog==`function`&&n.queueLog(`computed`,r,{value:o,duration:t}),i.value=o},`[CORE_INTERNAL_EFFECT=1]:${r}`),c=Array.from(this.allEffects)[this.allEffects.size-1],l=()=>{s(),c&&n.allEffects.delete(c),n.computedCache.delete(e)},u={get value(){return a&&(o=e(),a=!1,i.value=o),i.value},subscribe:e=>i.subscribe(e),destroy(){l()}};return this.cleanupRegistry.register(u,l),this.computedCache.set(e,new WeakRef(u)),u}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,n,r){let i=r&&typeof r==`object`,a=i?r.name:r||`unnamed_resource`,o=i?r:{},s=o.resetDataOnSourceChange??!0,c=o.responseValidate,l=o.validateBeforeFetch,u=o.retryCount??0,d=o.retryDelay??1e3,f=o.isExponentialBackoffEnabled??!1,p=o.maxRetryDelay??3e4,m=o.timeout,h=this.signal({data:null,loading:!0,error:null,isRetrying:!1},`[CORE_INTERNAL_SIGNAL=1]:resource:state:${a}`),g=e=>{h.value=e,typeof this.queueLog==`function`&&this.queueLog(`resource`,a,{loading:e.loading,data:e.data,error:e.error,isRetrying:e.isRetrying})},_=async(n,r,i=!1)=>{if(r.aborted)return;if(l){let e=l(n);if(e===!1||typeof e==`string`){if(r.aborted)return;let t=typeof e==`string`?e:`Pre-fetch validation failed for resource`,n=i?null:this.untrack(()=>h.value.data);g({data:n,loading:!1,error:Error(t),isRetrying:!1});return}}let o=i&&s?null:this.untrack(()=>h.value.data);g({data:o,loading:!0,error:null,isRetrying:!1});for(let i=0;i<=u;i++){if(r.aborted)return;let o=r;try{if(m&&m>0){let e=AbortSignal.timeout(m);o=AbortSignal.any([r,e])}let t=await e(n,o);if(r.aborted)return;if(o.aborted)throw new DOMException(`The operation timed out.`,`TimeoutError`);if(c){let e=c(t);if(e===!1||typeof e==`string`){if(r.aborted)return;g({data:null,loading:!1,error:Error(typeof e==`string`?e:`Validation failed`),isRetrying:!1});return}}if(r.aborted)return;g({data:t,loading:!1,error:null,isRetrying:!1});return}catch(e){if(r.aborted||e.name===`AbortError`&&r.aborted)return;let n=e.name===`TimeoutError`||e.name===`AbortError`||e.message?.includes(`timeout`),o=t({tested:[e.message],expectedKey:`THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY`,valueType:`number`})?.[0]===`1`||!1,s=o&&t({tested:[e.message],expectedKey:`MESSAGE`,valueType:`string`})?.[0]||`Custom validation error`,c=o||e.name===`ValidationError`;if(i===u||c){if(r.aborted)return;let t=n?Error(`Request timed out after ${m}ms`,{cause:e}):o?Error(s,{cause:e}):e;g({data:null,loading:!1,error:t,isRetrying:!1});return}else{if(r.aborted)return;let e=f?d*2**i:d;e=Math.min(e,p);let t=Math.random()*200;e+=t,g({data:h.value.data,loading:!0,error:null,isRetrying:!0}),console.warn(`[Resource Retry] "${a}" сбой по ${n?`таймауту`:`ошибке сети`}. Попытка ${i+1}/${u+1}...`);try{if(await this.delay(e,r),r.aborted)return}catch{return}}}}},v=null;return this.effect(()=>{let e=n?n.value:void 0,t=new AbortController;return v=t,this.untrack(()=>{_(e,t.signal,!0)}),()=>t.abort()},`[dev]`),{get data(){return h.value.data},get loading(){return h.value.loading},get error(){return h.value.error},get isRetrying(){return h.value.isRetrying},get value(){return h.value},refetch:()=>{v?.abort(),v=new AbortController,_(n?n.value:void 0,v.signal,!1)},subscribe:e=>h.subscribe(e)}}untrack(e){let t=this.activeEffect;this.activeEffect=null;try{return e()}finally{this.activeEffect=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)}}},r=class{engine;constructor(e){this.engine=e,this.onInit()}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}createComputed(e){return this.engine.computed(e)}createSignal(e,t){return this.engine.signal(e,t)}onInit(){}},i=r,a=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}};e.AbstractService=r,e.BaseREService=i,e.BaseREServiceEnhanced=a,e.ReactiveEngine=n});
|
|
1
|
+
/*!
|
|
2
|
+
* @pravosleva/reactive-engine v1.5.6-beta
|
|
3
|
+
* High-performance transactional reactive engine with microtask batching.
|
|
4
|
+
*
|
|
5
|
+
* @license MIT
|
|
6
|
+
* @author pravosleva <selection4test@google.com>
|
|
7
|
+
* @homepage https://pravosleva.pro/reactive-engine
|
|
8
|
+
*
|
|
9
|
+
* @contact Telegram Channel: https://t.me/bash_exp_ru/3393
|
|
10
|
+
* @contact Developer Telegram: https://t.me/pravosleva
|
|
11
|
+
*/(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ReactiveEngineLib={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=({tested:e,expectedKey:t,valueType:n})=>{let r=[];if(!e||!Array.isArray(e))return r;for(let i=0,a=e.length;i<a;i++){let a=e[i];if(typeof a!=`string`)continue;let o;switch(n){case`number`:o=RegExp(`\\[${t}=(?<value>\\d+)\\]`,`g`);break;default:o=RegExp(`\\[${t}=(?<value>.*?)\\]`,`g`);break}for(let e of a.matchAll(o))e?.groups&&r.push(e.groups.value)}return r},n=class{frameworkPrefix=`core`;activeEffect=null;isBatching=!1;pendingEffects=new Set;services=new Map;factories=new Map;proxyCache=new WeakMap;allEffects=new Set;loggerOptions;pendingLogQueue=[];lastTransactionDuration=null;signalMutationCounts=new Map;lastComputedDurations=new Map;effectExecutionCounts=new Map;batchTickCounts=0;constructor(e){this.loggerOptions=e?.logger}log(e,n,r){if(!this.loggerOptions?.isEnabled)return;if(!this.loggerOptions?.isCoreOptimizationDebugEnabled){let e=t({expectedKey:`CORE_INTERNAL_SIGNAL`,tested:[n],valueType:`number`})?.[0]===`1`,r=t({expectedKey:`CORE_INTERNAL_EFFECT`,tested:[n],valueType:`number`})?.[0]===`1`;if(e||r)return}if(this.loggerOptions.filter&&!(this.loggerOptions.filter instanceof RegExp?this.loggerOptions.filter:new RegExp(this.loggerOptions.filter.replace(/^\/|\/$/g,``))).test(n))return;let i={signal:`background: #007acc; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,computed:`background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,effect:`background: #e01e5a; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,batch:`background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,resource:`background: #d97706; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`},a=``,o=`color: #aaa; font-weight: normal;`;switch(e){case`signal`:{let s=t({tested:[n],expectedKey:`CORE_INTERNAL_SIGNAL`,valueType:`number`})?.[0]===`1`,c=n,l=(this.signalMutationCounts.get(c)||0)+1;this.signalMutationCounts.set(c,l);let u=r,d=u.subscribers&&u.subscribers.length>0?u.subscribers.every(e=>e?t({tested:[e],expectedKey:`IS_OPTIMIZED`,valueType:`number`})?.[0]===`1`:!1):!1,f=l>50,p=s?`COMPUTED_INTERNAL`:`SIGNAL`;a=` 🔄 Изменений ${s?`кэша`:`сигнала`}: ${l}${f?` ⚠️ (high noise — обнаружен дребезг/спам значений!)`:``}`,f&&(o=`color: orange; font-weight: bold;`,d||(r.__performance_advice__={issue:`Сигнал [${c}] обновляется слишком часто (${l} раз за такт). Это приводит к избыточным ререндерам UI.`,solution:`Оберните чтение этого сигнала в декоратор сжатия потока данных 'withThrottleComputed'`,example:`public throttled = withThrottleComputed(this.engine, () => this.yourSignalName.value, { limit: 300 });`})),console.groupCollapsed(`%c${p}%c [${c}]%c${a}`,s?`background: #42b883; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`:i[e],`color: #aaa; font-weight: bold;`,o),console.log(`Данные (Payload):`,r),this.loggerOptions.traceTime&&typeof performance<`u`&&console.log(`Тайминг рантайма:`,`${performance.now().toFixed(2)} ms`),console.groupEnd();return}case`computed`:{let e=r;if(e&&`to`in e){let t=e.to;a=` 🧮 Значение: ${typeof t==`object`&&t?`{...}`:String(t)}`}else if(e&&`duration`in e){let t=parseFloat(e.duration);if(!isNaN(t)){let r=this.lastComputedDurations.get(n);if(r!==void 0&&r>0){let n=(t-r)/r*100;n>5?(a=` 🔴 Замедление: +${n.toFixed(1)}% (${e.duration})`,o=`color: #e01e5a; font-weight: bold;`):n<-5?(a=` 🟢 Ускорение: ${n.toFixed(1)}% (${e.duration})`,o=`color: #42b883; font-weight: bold;`):(a=` 🟢 Стабильно (${e.duration})`,o=`color: #42b883; font-weight: bold;`)}else a=` ⚪ Первый расчет (${e.duration})`;this.lastComputedDurations.set(n,t)}}break}case`effect`:{let e=(this.effectExecutionCounts.get(n)||0)+1;this.effectExecutionCounts.set(n,e);let t=e>30,i=r;a=` 🟢 Стабильно${i&&`duration`in i?` (${i.duration})`:``} | 🚀 Вызовов: ${e}${t?` ⚠️ (heavy re-renders)`:``}`,o=`color: #42b883; font-weight: bold;`,t&&(o=`color: #ff4a4a; font-weight: bold;`);break}case`batch`:this.batchTickCounts+=1,a=` 📦 Номер транзакции: #${this.batchTickCounts}`;break;case`resource`:{let e=r;if(e.loading)a=` ⏳ ЗАГРУЗКА (fetching...)`,o=`color: #d97706; font-weight: bold;`;else if(e.error)a=` 🔴 ОШИБКА: ${e.error.message||`Unknown Error`}`,o=`color: #ff4a4a; font-weight: bold;`;else{let t=e.data;a=` 🟢 УСПЕХ (data: ${typeof t==`object`&&t?`{...}`:String(t)})`,o=`color: #42b883; font-weight: bold;`}break}default:break}console.groupCollapsed(`%c${e.toUpperCase()}%c [${n}]%c${a}`,i[e],`color: #aaa; font-weight: bold;`,o),console.log(`Данные (Payload):`,r),this.loggerOptions.traceTime&&typeof performance<`u`&&console.log(`Тайминг рантайма:`,`${performance.now().toFixed(2)} ms`),console.groupEnd()}onSignalChange;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`}`)}}queueLog(e,t,n){this.loggerOptions?.isEnabled&&this.pendingLogQueue.push({type:e,name:t,detail:n})}flushLogs(){if(!this.loggerOptions?.isEnabled||this.pendingLogQueue.length===0)return;let e=typeof performance<`u`?performance.now():0,t=this.pendingLogQueue;if(this.loggerOptions.filter)try{let e;if(this.loggerOptions.filter instanceof RegExp)e=this.loggerOptions.filter;else{let t=this.loggerOptions.filter.replace(/^\/|\/$/g,``);e=new RegExp(t)}t=t.filter(t=>e.test(t.name))}catch(e){console.warn(`[Logger Error]: Некорректный паттерн фильтрации логов`,e)}if(t.length===0){this.pendingLogQueue=[];return}let n=``,r=``,i=`color: #aaa; font-weight: normal;`;if(this.loggerOptions.traceTime&&e){let t=performance.now()-e;if(n=` | Duration: ${t.toFixed(3)} ms`,this.lastTransactionDuration!==null&&this.lastTransactionDuration>0){let e=(t-this.lastTransactionDuration)/this.lastTransactionDuration*100;e>.1?(r=` 🔴 +${e.toFixed(1)}%`,i=`color: #e01e5a; font-weight: bold;`):e<-.1?(r=` 🟢 ${e.toFixed(1)}%`,i=`color: #42b883; font-weight: bold;`):(r=` 🟢 stable`,i=`color: #42b883; font-weight: bold;`)}else r=` ⚪ first`;this.lastTransactionDuration=t}console.groupCollapsed(`%cREACTIVE TRANSACTION${this.loggerOptions.instanceName?` ${this.loggerOptions.instanceName}`:``}%c Microtask Tick (Size: ${t.length}${n})%c${r}`,`background: #7952b3; color: #fff; padding: 2px 6px; border-radius: 4px; font-weight: bold;`,`color: #aaa; font-weight: normal;`,i),t.forEach(e=>{this.log(e.type,e.name,e.detail)}),this.loggerOptions.traceTime&&typeof performance<`u`&&console.log(`%c[Execution Timestamp]: ${performance.now().toFixed(2)} ms`,`color: #7952b3; font-weight: bold;`),console.groupEnd(),this.pendingLogQueue=[]}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(){if(n.activeEffect){let e=n.activeEffect;i.has(e)||(i.add(e),e.cleanups.add(()=>{i.delete(e)}))}return 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);let s=Array.from(i).map(e=>e.label||`unnamed_effect`);n.queueLog?.(`signal`,o,{from:t,to:e,subscribersCount:i.size,subscribers:s}),i.forEach(e=>n.pendingEffects.add(e)),n.isBatching||(n.isBatching=!0,queueMicrotask(()=>{for(let e of n.pendingEffects)n.pendingEffects.delete(e),e.run();n.isBatching=!1,n.flushLogs?.()}))},subscribe(e){return n.effect(()=>{e(this.value)},`${n.frameworkPrefix}:use:${o}`)}}}effect(e,t){let n=this,r=!0,i={label:t,cleanups:new Set,run(){this.cleanups.forEach(e=>e()),this.cleanups.clear();let t=n.activeEffect;n.activeEffect=this;let i=performance.now();n.safeRun(this,()=>{let t=e();typeof t==`function`&&this.cleanups.add(t)}),n.activeEffect=t;let a=performance.now()-i;if(!r&&n.loggerOptions?.isEnabled){let e=n.loggerOptions.filter;(!e||e instanceof RegExp&&this.label&&e.test(this.label))&&typeof n.queueLog==`function`&&n.queueLog(`effect`,this.label||`unnamed-efect-0`,{name:this.label||`unnamed-efect-0`,type:`effect`,detail:{status:`stable`,duration:`${a.toFixed(3)}ms`,timestamp:i}})}r=!1}};return this.allEffects.add(i),i.run(),()=>{i.cleanups.forEach(e=>e()),n.pendingEffects.delete(i),n.allEffects.delete(i)}}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=t||`unnamed_computed`,i=this.signal(void 0,`[CORE_INTERNAL_SIGNAL=1]:${r}`),a=!0,o,s=this.effect(()=>{o=e(),a=!1;let t=typeof performance<`u`&&performance.now()?`0.010ms`:`N/A`;typeof n.queueLog==`function`&&n.queueLog(`computed`,r,{value:o,duration:t}),i.value=o},`[CORE_INTERNAL_EFFECT=1]:${r}`),c=Array.from(this.allEffects)[this.allEffects.size-1],l=()=>{s(),c&&n.allEffects.delete(c),n.computedCache.delete(e)},u={get value(){return a&&(o=e(),a=!1,i.value=o),i.value},subscribe:e=>i.subscribe(e),destroy(){l()}};return this.cleanupRegistry.register(u,l),this.computedCache.set(e,new WeakRef(u)),u}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,n,r){let i=r&&typeof r==`object`,a=i?r.name:r||`unnamed_resource`,o=i?r:{},s=o.resetDataOnSourceChange??!0,c=o.responseValidate,l=o.validateBeforeFetch,u=o.retryCount??0,d=o.retryDelay??1e3,f=o.isExponentialBackoffEnabled??!1,p=o.maxRetryDelay??3e4,m=o.timeout,h=this.signal({data:null,loading:!0,error:null,isRetrying:!1},`[CORE_INTERNAL_SIGNAL=1]:resource:state:${a}`),g=e=>{h.value=e,typeof this.queueLog==`function`&&this.queueLog(`resource`,a,{loading:e.loading,data:e.data,error:e.error,isRetrying:e.isRetrying})},_=async(n,r,i=!1)=>{if(r.aborted)return;if(l){let e=l(n);if(e===!1||typeof e==`string`){if(r.aborted)return;let t=typeof e==`string`?e:`Pre-fetch validation failed for resource`,n=i?null:this.untrack(()=>h.value.data);g({data:n,loading:!1,error:Error(t),isRetrying:!1});return}}let o=i&&s?null:this.untrack(()=>h.value.data);g({data:o,loading:!0,error:null,isRetrying:!1});for(let i=0;i<=u;i++){if(r.aborted)return;let o=r;try{if(m&&m>0){let e=AbortSignal.timeout(m);o=AbortSignal.any([r,e])}let t=await e(n,o);if(r.aborted)return;if(o.aborted)throw new DOMException(`The operation timed out.`,`TimeoutError`);if(c){let e=c(t);if(e===!1||typeof e==`string`){if(r.aborted)return;g({data:null,loading:!1,error:Error(typeof e==`string`?e:`Validation failed`),isRetrying:!1});return}}if(r.aborted)return;g({data:t,loading:!1,error:null,isRetrying:!1});return}catch(e){if(r.aborted||e.name===`AbortError`&&r.aborted)return;let n=e.name===`TimeoutError`||e.name===`AbortError`||e.message?.includes(`timeout`),o=t({tested:[e.message],expectedKey:`THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY`,valueType:`number`})?.[0]===`1`||!1,s=o&&t({tested:[e.message],expectedKey:`MESSAGE`,valueType:`string`})?.[0]||`Custom validation error`,c=o||e.name===`ValidationError`;if(i===u||c){if(r.aborted)return;let t=n?Error(`Request timed out after ${m}ms`,{cause:e}):o?Error(s,{cause:e}):e;g({data:null,loading:!1,error:t,isRetrying:!1});return}else{if(r.aborted)return;let e=f?d*2**i:d;e=Math.min(e,p);let t=Math.random()*200;e+=t,g({data:h.value.data,loading:!0,error:null,isRetrying:!0}),console.warn(`[Resource Retry] "${a}" сбой по ${n?`таймауту`:`ошибке сети`}. Попытка ${i+1}/${u+1}...`);try{if(await this.delay(e,r),r.aborted)return}catch{return}}}}},v=null;return this.effect(()=>{let e=n?n.value:void 0,t=new AbortController;return v=t,this.untrack(()=>{_(e,t.signal,!0)}),()=>t.abort()},`[dev]`),{get data(){return h.value.data},get loading(){return h.value.loading},get error(){return h.value.error},get isRetrying(){return h.value.isRetrying},get value(){return h.value},refetch:()=>{v?.abort(),v=new AbortController,_(n?n.value:void 0,v.signal,!1)},subscribe:e=>h.subscribe(e)}}untrack(e){let t=this.activeEffect;this.activeEffect=null;try{return e()}finally{this.activeEffect=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)}}},r=class{engine;constructor(e){this.engine=e,this.onInit()}createNumericSignal(e,t){return this.engine.signal(e,{name:t,validate:e=>typeof e==`number`&&!isNaN(e)})}createComputed(e){return this.engine.computed(e)}createSignal(e,t){return this.engine.signal(e,t)}onInit(){}},i=r,a=class{engine;constructor(e){this.engine=e,setTimeout(()=>this.onInit(),0)}onInit(){}};e.AbstractService=r,e.BaseREService=i,e.BaseREServiceEnhanced=a,e.ReactiveEngine=n});
|
|
2
12
|
//# sourceMappingURL=reactive-engine.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("vue");function t(t){let n=(0,e.shallowRef)(t.value),r=typeof window>`u`||typeof process<`u`&&process.server,i=(0,e.getCurrentInstance)()||(0,e.getCurrentScope)();if(r||!i)return n;let a=t.subscribe(t=>{n.value=t,(0,e.triggerRef)(n)});return(0,e.getCurrentInstance)()?(0,e.onUnmounted)(a):(0,e.getCurrentScope)()&&(0,e.onScopeDispose)(a),n}exports.useReactiveValue=t;
|
|
2
|
+
//# sourceMappingURL=useReactiveValue.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useReactiveValue.cjs","names":[],"sources":["../../../src/vue/composables/useReactiveValue.ts"],"sourcesContent":["import { shallowRef, onUnmounted, getCurrentInstance, getCurrentScope, onScopeDispose, triggerRef, type ShallowRef } from 'vue'\n\ntype CleanupFn = () => void\ninterface ReactiveItem<T> {\n value: T\n subscribe: (cb: (v: T) => void) => CleanupFn\n}\n\n/**\n * Хук-композибл для бесшовной интеграции примитивов реактивного ядра с системой рендеринга Vue 3.\n * Нативно поддерживает как атомарные сигналы (`signal`), так и ленивые вычисляемые свойства (`computed`).\n *\n * @template T Тип данных, содержащихся внутри реактивного элемента.\n *\n * @param {Object} item Реактивный примитив ядра, на который необходимо подписаться.\n * @param {T} item.value Текущее значение элемента.\n * @param {(cb: (v: T) => void) => () => void} item.subscribe Метод ядра для оформления подписки на изменения.\n *\n * @returns {ShallowRef<T>} Возвращает стандартную для Vue 3 переменную типа `ShallowRef`.\n * При мутации данных в ядре автоматически вызывается `triggerRef`, инициируя точечный перерасчет шаблона.\n *\n * @example\n * ```vue\n * <script setup>\n * import { useEngine } from '~/composables/useEngine';\n * import { useReactiveValue } from '@pravosleva/reactive-engine/vue';\n *\n * const engine = useEngine();\n * const counterSignal = engine.signal(0, 'ui:counter');\n *\n * // countState теперь обычный Vue Ref, но связанный с ядром данных\n * const countState = useReactiveValue(counterSignal);\n * </script>\n *\n * <template>\n * <button @click=\"counterSignal.value++\">Кликнули: {{ countState }}</button>\n * </template>\n * ```\n *\n * @abstract\n * ### 🚨 Поведение в SSR и Универсальном рендеринге (Nuxt 3 / Nitro)\n * Композибл полностью безопасен для работы на стороне сервера (Node.js). Благодаря внутренним\n * проверкам окружения, на этапе серверного пре-рендеринга он переходит в **пассивный режим**:\n * - Выполняется только однократное чтение `.value` для сборки начального HTML-кода страницы.\n * - Активные подписки и замыкания **не регистрируются**, что полностью предотвращает утечки памяти на сервере.\n * - Исключает появление ошибок несовпадения разметки (`Hydration Mismatch`) при гидратации в браузере.\n *\n * ### 🧹 Управление памятью на клиенте\n * В браузере композибл самостоятельно управляет своим жизненным циклом:\n * - Если вызван внутри компонента, подписка автоматически аннулируется на хуке `onUnmounted`.\n * - Если вызван внутри независимого контекста эффектов, отписка произойдет через `onScopeDispose`.\n */\nexport function useReactiveValue<T>(item: ReactiveItem<T>): ShallowRef<T> {\n const state = shallowRef(item.value)\n\n // SSR: Если мы на сервере Node.js (нет window) или вне контекста Vue —\n // просто возвращаем пассивный ref, не создавая подписку!\n const isServer = typeof window === 'undefined' || (typeof process !== 'undefined' && process.server)\n const hasInstance = getCurrentInstance() || getCurrentScope()\n\n if (isServer || !hasInstance) {\n return state // Пассивный режим однократного чтения для HTML-рендеринга\n }\n\n const unsubscribe = item.subscribe((newValue) => {\n state.value = newValue\n triggerRef(state)\n })\n\n if (getCurrentInstance()) {\n onUnmounted(unsubscribe)\n } else if (getCurrentScope()) {\n onScopeDispose(unsubscribe)\n }\n\n return state\n}\n"],"mappings":"qBAoDA,SAAgB,EAAoB,EAAsC,CACxE,IAAM,GAAA,EAAA,EAAA,WAAA,CAAmB,EAAK,KAAK,EAI7B,EAAW,OAAO,OAAW,KAAgB,OAAO,QAAY,KAAe,QAAQ,OACvF,GAAA,EAAA,EAAA,mBAAA,CAAiC,IAAA,EAAA,EAAA,gBAAA,CAAqB,EAE5D,GAAI,GAAY,CAAC,EACf,OAAO,EAGT,IAAM,EAAc,EAAK,UAAW,GAAa,CAC/C,EAAM,MAAQ,GACd,EAAA,EAAA,WAAA,CAAW,CAAK,CAClB,CAAC,EAQD,OANA,EAAA,EAAA,mBAAA,CAAuB,GACrB,EAAA,EAAA,YAAA,CAAY,CAAW,GAClB,EAAA,EAAA,gBAAA,CAAoB,IACzB,EAAA,EAAA,eAAA,CAAe,CAAW,EAGrB,CACT"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ShallowRef } from 'vue';
|
|
2
|
+
type CleanupFn = () => void;
|
|
3
|
+
interface ReactiveItem<T> {
|
|
4
|
+
value: T;
|
|
5
|
+
subscribe: (cb: (v: T) => void) => CleanupFn;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Хук-композибл для бесшовной интеграции примитивов реактивного ядра с системой рендеринга Vue 3.
|
|
9
|
+
* Нативно поддерживает как атомарные сигналы (`signal`), так и ленивые вычисляемые свойства (`computed`).
|
|
10
|
+
*
|
|
11
|
+
* @template T Тип данных, содержащихся внутри реактивного элемента.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} item Реактивный примитив ядра, на который необходимо подписаться.
|
|
14
|
+
* @param {T} item.value Текущее значение элемента.
|
|
15
|
+
* @param {(cb: (v: T) => void) => () => void} item.subscribe Метод ядра для оформления подписки на изменения.
|
|
16
|
+
*
|
|
17
|
+
* @returns {ShallowRef<T>} Возвращает стандартную для Vue 3 переменную типа `ShallowRef`.
|
|
18
|
+
* При мутации данных в ядре автоматически вызывается `triggerRef`, инициируя точечный перерасчет шаблона.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```vue
|
|
22
|
+
* <script setup>
|
|
23
|
+
* import { useEngine } from '~/composables/useEngine';
|
|
24
|
+
* import { useReactiveValue } from '@pravosleva/reactive-engine/vue';
|
|
25
|
+
*
|
|
26
|
+
* const engine = useEngine();
|
|
27
|
+
* const counterSignal = engine.signal(0, 'ui:counter');
|
|
28
|
+
*
|
|
29
|
+
* // countState теперь обычный Vue Ref, но связанный с ядром данных
|
|
30
|
+
* const countState = useReactiveValue(counterSignal);
|
|
31
|
+
* </script>
|
|
32
|
+
*
|
|
33
|
+
* <template>
|
|
34
|
+
* <button @click="counterSignal.value++">Кликнули: {{ countState }}</button>
|
|
35
|
+
* </template>
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @abstract
|
|
39
|
+
* ### 🚨 Поведение в SSR и Универсальном рендеринге (Nuxt 3 / Nitro)
|
|
40
|
+
* Композибл полностью безопасен для работы на стороне сервера (Node.js). Благодаря внутренним
|
|
41
|
+
* проверкам окружения, на этапе серверного пре-рендеринга он переходит в **пассивный режим**:
|
|
42
|
+
* - Выполняется только однократное чтение `.value` для сборки начального HTML-кода страницы.
|
|
43
|
+
* - Активные подписки и замыкания **не регистрируются**, что полностью предотвращает утечки памяти на сервере.
|
|
44
|
+
* - Исключает появление ошибок несовпадения разметки (`Hydration Mismatch`) при гидратации в браузере.
|
|
45
|
+
*
|
|
46
|
+
* ### 🧹 Управление памятью на клиенте
|
|
47
|
+
* В браузере композибл самостоятельно управляет своим жизненным циклом:
|
|
48
|
+
* - Если вызван внутри компонента, подписка автоматически аннулируется на хуке `onUnmounted`.
|
|
49
|
+
* - Если вызван внутри независимого контекста эффектов, отписка произойдет через `onScopeDispose`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function useReactiveValue<T>(item: ReactiveItem<T>): ShallowRef<T>;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=useReactiveValue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useReactiveValue.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/useReactiveValue.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4F,KAAK,UAAU,EAAE,MAAM,KAAK,CAAA;AAE/H,KAAK,SAAS,GAAG,MAAM,IAAI,CAAA;AAC3B,UAAU,YAAY,CAAC,CAAC;IACtB,KAAK,EAAE,CAAC,CAAA;IACR,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;CAC7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAwBxE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getCurrentInstance as e, getCurrentScope as t, onScopeDispose as n, onUnmounted as r, shallowRef as i, triggerRef as a } from "vue";
|
|
2
|
+
//#region src/vue/composables/useReactiveValue.ts
|
|
3
|
+
function o(o) {
|
|
4
|
+
let s = i(o.value), c = typeof window > "u" || typeof process < "u" && process.server, l = e() || t();
|
|
5
|
+
if (c || !l) return s;
|
|
6
|
+
let u = o.subscribe((e) => {
|
|
7
|
+
s.value = e, a(s);
|
|
8
|
+
});
|
|
9
|
+
return e() ? r(u) : t() && n(u), s;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { o as useReactiveValue };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=useReactiveValue.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useReactiveValue.mjs","names":[],"sources":["../../../src/vue/composables/useReactiveValue.ts"],"sourcesContent":["import { shallowRef, onUnmounted, getCurrentInstance, getCurrentScope, onScopeDispose, triggerRef, type ShallowRef } from 'vue'\n\ntype CleanupFn = () => void\ninterface ReactiveItem<T> {\n value: T\n subscribe: (cb: (v: T) => void) => CleanupFn\n}\n\n/**\n * Хук-композибл для бесшовной интеграции примитивов реактивного ядра с системой рендеринга Vue 3.\n * Нативно поддерживает как атомарные сигналы (`signal`), так и ленивые вычисляемые свойства (`computed`).\n *\n * @template T Тип данных, содержащихся внутри реактивного элемента.\n *\n * @param {Object} item Реактивный примитив ядра, на который необходимо подписаться.\n * @param {T} item.value Текущее значение элемента.\n * @param {(cb: (v: T) => void) => () => void} item.subscribe Метод ядра для оформления подписки на изменения.\n *\n * @returns {ShallowRef<T>} Возвращает стандартную для Vue 3 переменную типа `ShallowRef`.\n * При мутации данных в ядре автоматически вызывается `triggerRef`, инициируя точечный перерасчет шаблона.\n *\n * @example\n * ```vue\n * <script setup>\n * import { useEngine } from '~/composables/useEngine';\n * import { useReactiveValue } from '@pravosleva/reactive-engine/vue';\n *\n * const engine = useEngine();\n * const counterSignal = engine.signal(0, 'ui:counter');\n *\n * // countState теперь обычный Vue Ref, но связанный с ядром данных\n * const countState = useReactiveValue(counterSignal);\n * </script>\n *\n * <template>\n * <button @click=\"counterSignal.value++\">Кликнули: {{ countState }}</button>\n * </template>\n * ```\n *\n * @abstract\n * ### 🚨 Поведение в SSR и Универсальном рендеринге (Nuxt 3 / Nitro)\n * Композибл полностью безопасен для работы на стороне сервера (Node.js). Благодаря внутренним\n * проверкам окружения, на этапе серверного пре-рендеринга он переходит в **пассивный режим**:\n * - Выполняется только однократное чтение `.value` для сборки начального HTML-кода страницы.\n * - Активные подписки и замыкания **не регистрируются**, что полностью предотвращает утечки памяти на сервере.\n * - Исключает появление ошибок несовпадения разметки (`Hydration Mismatch`) при гидратации в браузере.\n *\n * ### 🧹 Управление памятью на клиенте\n * В браузере композибл самостоятельно управляет своим жизненным циклом:\n * - Если вызван внутри компонента, подписка автоматически аннулируется на хуке `onUnmounted`.\n * - Если вызван внутри независимого контекста эффектов, отписка произойдет через `onScopeDispose`.\n */\nexport function useReactiveValue<T>(item: ReactiveItem<T>): ShallowRef<T> {\n const state = shallowRef(item.value)\n\n // SSR: Если мы на сервере Node.js (нет window) или вне контекста Vue —\n // просто возвращаем пассивный ref, не создавая подписку!\n const isServer = typeof window === 'undefined' || (typeof process !== 'undefined' && process.server)\n const hasInstance = getCurrentInstance() || getCurrentScope()\n\n if (isServer || !hasInstance) {\n return state // Пассивный режим однократного чтения для HTML-рендеринга\n }\n\n const unsubscribe = item.subscribe((newValue) => {\n state.value = newValue\n triggerRef(state)\n })\n\n if (getCurrentInstance()) {\n onUnmounted(unsubscribe)\n } else if (getCurrentScope()) {\n onScopeDispose(unsubscribe)\n }\n\n return state\n}\n"],"mappings":";;AAoDA,SAAgB,EAAoB,GAAsC;CACxE,IAAM,IAAQ,EAAW,EAAK,KAAK,GAI7B,IAAW,OAAO,SAAW,OAAgB,OAAO,UAAY,OAAe,QAAQ,QACvF,IAAc,EAAmB,KAAK,EAAgB;CAE5D,IAAI,KAAY,CAAC,GACf,OAAO;CAGT,IAAM,IAAc,EAAK,WAAW,MAAa;EAE/C,AADA,EAAM,QAAQ,GACd,EAAW,CAAK;CAClB,CAAC;CAQD,OANI,EAAmB,IACrB,EAAY,CAAW,IACd,EAAgB,KACzB,EAAe,CAAW,GAGrB;AACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useReactiveValue.test.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/useReactiveValue.test.ts"],"names":[],"mappings":""}
|
package/dist/vue/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./ReactiveEngine4Vue.cjs");exports.ReactiveEngine=e.ReactiveEngine4Vue;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./ReactiveEngine4Vue.cjs"),t=require("./composables/useReactiveValue.cjs");exports.ReactiveEngine=e.ReactiveEngine4Vue,exports.useReactiveValue=t.useReactiveValue;
|
package/dist/vue/index.d.ts
CHANGED
package/dist/vue/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,IAAI,cAAc,EAAE,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA"}
|
package/dist/vue/index.mjs
CHANGED