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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/AbstractService.cjs.map +1 -0
  2. package/dist/AbstractService.mjs.map +1 -0
  3. package/dist/BaseREServiceEnhanced.cjs.map +1 -0
  4. package/dist/BaseREServiceEnhanced.mjs.map +1 -0
  5. package/dist/angular/ReactiveEngine4Angular.cjs.map +1 -0
  6. package/dist/angular/ReactiveEngine4Angular.mjs.map +1 -0
  7. package/dist/core.cjs.map +1 -0
  8. package/dist/core.mjs.map +1 -0
  9. package/dist/react/ReactiveEngine4React.cjs.map +1 -0
  10. package/dist/react/ReactiveEngine4React.mjs.map +1 -0
  11. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.cjs.map +1 -0
  12. package/dist/react/hooks/useReactiveSubscription/useReactiveSubscription.mjs.map +1 -0
  13. package/dist/react/hooks/useReactiveValue/useReactiveValue.cjs.map +1 -0
  14. package/dist/react/hooks/useReactiveValue/useReactiveValue.mjs.map +1 -0
  15. package/dist/react/hooks/useReactiveValue0/useReactiveValue0.cjs.map +1 -0
  16. package/dist/react/hooks/useReactiveValue0/useReactiveValue0.mjs.map +1 -0
  17. package/dist/utils/getExtractedValues.cjs.map +1 -0
  18. package/dist/utils/getExtractedValues.mjs.map +1 -0
  19. package/dist/vue/ReactiveEngine4Vue.cjs.map +1 -0
  20. package/dist/vue/ReactiveEngine4Vue.mjs.map +1 -0
  21. package/package.json +16 -16
  22. package/dist/cjs/AbstractService.cjs.map +0 -1
  23. package/dist/cjs/BaseREServiceEnhanced.cjs.map +0 -1
  24. package/dist/cjs/angular/ReactiveEngine4Angular.cjs.map +0 -1
  25. package/dist/cjs/core.cjs.map +0 -1
  26. package/dist/cjs/react/ReactiveEngine4React.cjs.map +0 -1
  27. package/dist/cjs/react/hooks/useReactiveSubscription/useReactiveSubscription.cjs.map +0 -1
  28. package/dist/cjs/react/hooks/useReactiveValue/useReactiveValue.cjs.map +0 -1
  29. package/dist/cjs/react/hooks/useReactiveValue0/useReactiveValue0.cjs.map +0 -1
  30. package/dist/cjs/utils/getExtractedValues.cjs.map +0 -1
  31. package/dist/cjs/vue/ReactiveEngine4Vue.cjs.map +0 -1
  32. package/dist/esm/AbstractService.mjs.map +0 -1
  33. package/dist/esm/BaseREServiceEnhanced.mjs.map +0 -1
  34. package/dist/esm/angular/ReactiveEngine4Angular.mjs.map +0 -1
  35. package/dist/esm/core.mjs.map +0 -1
  36. package/dist/esm/mockServiceWorker.js +0 -361
  37. package/dist/esm/react/ReactiveEngine4React.mjs.map +0 -1
  38. package/dist/esm/react/hooks/useReactiveSubscription/useReactiveSubscription.mjs.map +0 -1
  39. package/dist/esm/react/hooks/useReactiveValue/useReactiveValue.mjs.map +0 -1
  40. package/dist/esm/react/hooks/useReactiveValue0/useReactiveValue0.mjs.map +0 -1
  41. package/dist/esm/utils/getExtractedValues.mjs.map +0 -1
  42. package/dist/esm/vite.svg +0 -1
  43. package/dist/esm/vue/ReactiveEngine4Vue.mjs.map +0 -1
  44. /package/dist/{cjs/AbstractService.cjs → AbstractService.cjs} +0 -0
  45. /package/dist/{esm/AbstractService.mjs → AbstractService.mjs} +0 -0
  46. /package/dist/{cjs/BaseREServiceEnhanced.cjs → BaseREServiceEnhanced.cjs} +0 -0
  47. /package/dist/{esm/BaseREServiceEnhanced.mjs → BaseREServiceEnhanced.mjs} +0 -0
  48. /package/dist/{cjs/angular → angular}/ReactiveEngine4Angular.cjs +0 -0
  49. /package/dist/{esm/angular → angular}/ReactiveEngine4Angular.mjs +0 -0
  50. /package/dist/{cjs/angular → angular}/index.cjs +0 -0
  51. /package/dist/{esm/angular → angular}/index.mjs +0 -0
  52. /package/dist/{cjs/core.cjs → core.cjs} +0 -0
  53. /package/dist/{esm/core.mjs → core.mjs} +0 -0
  54. /package/dist/{cjs/index.cjs → index.cjs} +0 -0
  55. /package/dist/{esm/index.mjs → index.mjs} +0 -0
  56. /package/dist/{cjs/mockServiceWorker.js → mockServiceWorker.js} +0 -0
  57. /package/dist/{cjs/react → react}/ReactiveEngine4React.cjs +0 -0
  58. /package/dist/{esm/react → react}/ReactiveEngine4React.mjs +0 -0
  59. /package/dist/{cjs/react → react}/hooks/index.cjs +0 -0
  60. /package/dist/{esm/react → react}/hooks/index.mjs +0 -0
  61. /package/dist/{cjs/react → react}/hooks/useReactiveSubscription/index.cjs +0 -0
  62. /package/dist/{esm/react → react}/hooks/useReactiveSubscription/index.mjs +0 -0
  63. /package/dist/{cjs/react → react}/hooks/useReactiveSubscription/useReactiveSubscription.cjs +0 -0
  64. /package/dist/{esm/react → react}/hooks/useReactiveSubscription/useReactiveSubscription.mjs +0 -0
  65. /package/dist/{cjs/react → react}/hooks/useReactiveValue/index.cjs +0 -0
  66. /package/dist/{esm/react → react}/hooks/useReactiveValue/index.mjs +0 -0
  67. /package/dist/{cjs/react → react}/hooks/useReactiveValue/useReactiveValue.cjs +0 -0
  68. /package/dist/{esm/react → react}/hooks/useReactiveValue/useReactiveValue.mjs +0 -0
  69. /package/dist/{cjs/react → react}/hooks/useReactiveValue0/index.cjs +0 -0
  70. /package/dist/{esm/react → react}/hooks/useReactiveValue0/index.mjs +0 -0
  71. /package/dist/{cjs/react → react}/hooks/useReactiveValue0/useReactiveValue0.cjs +0 -0
  72. /package/dist/{esm/react → react}/hooks/useReactiveValue0/useReactiveValue0.mjs +0 -0
  73. /package/dist/{cjs/react → react}/index.cjs +0 -0
  74. /package/dist/{esm/react → react}/index.mjs +0 -0
  75. /package/dist/{cjs/utils → utils}/getExtractedValues.cjs +0 -0
  76. /package/dist/{esm/utils → utils}/getExtractedValues.mjs +0 -0
  77. /package/dist/{cjs/vite.svg → vite.svg} +0 -0
  78. /package/dist/{cjs/vue → vue}/ReactiveEngine4Vue.cjs +0 -0
  79. /package/dist/{esm/vue → vue}/ReactiveEngine4Vue.mjs +0 -0
  80. /package/dist/{cjs/vue → vue}/index.cjs +0 -0
  81. /package/dist/{esm/vue → vue}/index.mjs +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbstractService.cjs","names":[],"sources":["../src/core/AbstractService.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\nexport abstract class AbstractService {\n constructor(protected engine: ReactiveEngine) {\n // Вызываем метод инициализации, который можно переопределить в наследниках\n this.onInit();\n }\n\n // NOTE: Полезные хелперы для сокращения кода\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 protected createComputed<T>(fn: () => T) {\n return this.engine.computed(fn);\n }\n\n protected createSignal<T>(val: T, name?: string) {\n return this.engine.signal(val, name);\n }\n\n // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n // Удобно для инициализации сигналов, которые зависят от engine.\n protected onInit(): void { }\n}\n\nexport const BaseREService = AbstractService\n"],"mappings":"AAEA,IAAsB,EAAtB,KAAsC,CACd,OAAtB,YAAY,EAAkC,CAAxB,KAAA,OAAA,EAEpB,KAAK,OAAO,CACd,CAGA,oBAA8B,EAAa,EAAc,CACvD,OAAO,KAAK,OAAO,OAAO,EAAK,CAC7B,OACA,SAAW,GAAM,OAAO,GAAM,UAAY,CAAC,MAAM,CAAC,CACpD,CAAC,CACH,CAEA,eAA4B,EAAa,CACvC,OAAO,KAAK,OAAO,SAAS,CAAE,CAChC,CAEA,aAA0B,EAAQ,EAAe,CAC/C,OAAO,KAAK,OAAO,OAAO,EAAK,CAAI,CACrC,CAIA,QAAyB,CAAE,CAC7B,EAEa,EAAgB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbstractService.mjs","names":[],"sources":["../src/core/AbstractService.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\nexport abstract class AbstractService {\n constructor(protected engine: ReactiveEngine) {\n // Вызываем метод инициализации, который можно переопределить в наследниках\n this.onInit();\n }\n\n // NOTE: Полезные хелперы для сокращения кода\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 protected createComputed<T>(fn: () => T) {\n return this.engine.computed(fn);\n }\n\n protected createSignal<T>(val: T, name?: string) {\n return this.engine.signal(val, name);\n }\n\n // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n // Удобно для инициализации сигналов, которые зависят от engine.\n protected onInit(): void { }\n}\n\nexport const BaseREService = AbstractService\n"],"mappings":";AAEA,IAAsB,IAAtB,MAAsC;CACd;CAAtB,YAAY,GAAkC;EAE5C,AAFoB,KAAA,SAAA,GAEpB,KAAK,OAAO;CACd;CAGA,oBAA8B,GAAa,GAAc;EACvD,OAAO,KAAK,OAAO,OAAO,GAAK;GAC7B;GACA,WAAW,MAAM,OAAO,KAAM,YAAY,CAAC,MAAM,CAAC;EACpD,CAAC;CACH;CAEA,eAA4B,GAAa;EACvC,OAAO,KAAK,OAAO,SAAS,CAAE;CAChC;CAEA,aAA0B,GAAQ,GAAe;EAC/C,OAAO,KAAK,OAAO,OAAO,GAAK,CAAI;CACrC;CAIA,SAAyB,CAAE;AAC7B,GAEa,IAAgB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseREServiceEnhanced.cjs","names":[],"sources":["../src/core/BaseREServiceEnhanced.ts"],"sourcesContent":["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"],"mappings":"AAEA,IAAsB,EAAtB,KAA4C,CACpB,OAAtB,YAAY,EAAkC,CAAxB,KAAA,OAAA,EACpB,eAAiB,KAAK,OAAO,EAAG,CAAC,CACnC,CAEA,QAAyB,CAAE,CAC7B"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseREServiceEnhanced.mjs","names":[],"sources":["../src/core/BaseREServiceEnhanced.ts"],"sourcesContent":["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"],"mappings":";AAEA,IAAsB,IAAtB,MAA4C;CACpB;CAAtB,YAAY,GAAkC;EAC5C,AADoB,KAAA,SAAA,GACpB,iBAAiB,KAAK,OAAO,GAAG,CAAC;CACnC;CAEA,SAAyB,CAAE;AAC7B"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveEngine4Angular.cjs","names":[],"sources":["../../src/angular/ReactiveEngine4Angular.ts"],"sourcesContent":["import { signal, inject, DestroyRef, Injector, runInInjectionContext, type Signal } from '@angular/core';\nimport { ReactiveEngine as OriginalReactiveEngine, type CleanupFn } from '../core/core';\n\n// Описываем интерфейс опций для Angular-адаптера\nexport interface AngularUseOptions {\n /**\n * Кастомный инжектор Angular.\n * Позволяет использовать .use() вне конструктора (например, в асинхронных функциях),\n * если передать текущий Injector вручную.\n */\n injector?: Injector;\n}\n\nexport class ReactiveEngine4Angular extends OriginalReactiveEngine {\n /**\n * Использование реактивного значения в Angular компоненте или сервисе.\n * Возвращает стандартный Angular Signal.\n */\n public use<T>(\n item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn },\n options?: AngularUseOptions\n ): Signal<T> {\n // 1. Создаем локальный Angular Signal со стартовым значением\n const angularSignal = signal<T>(item.value);\n\n // 2. Подписываемся на изменения сигнала вашего ядра\n const unsubscribe = item.subscribe((newValue) => {\n angularSignal.set(newValue); // Обновляем значение внутри Angular\n });\n\n // 3. Логика автоматической отписки через DestroyRef\n const registerCleanup = () => {\n const destroyRef = inject(DestroyRef);\n destroyRef.onDestroy(() => {\n unsubscribe();\n });\n };\n\n // 4. Если пользователь явно передал кастомный инжектор — выполняем регистрацию внутри его контекста\n if (options?.injector) {\n runInInjectionContext(options.injector, registerCleanup);\n } else {\n // Иначе штатно выполняем в текущем контексте (вызовет ошибку NG0203, если контекст не валиден)\n registerCleanup();\n }\n\n return angularSignal.asReadonly();\n }\n}\n"],"mappings":"8DAaA,IAAa,EAAb,cAA4C,EAAA,cAAuB,CAKjE,IACE,EACA,EACW,CAEX,IAAM,GAAA,EAAA,EAAA,OAAA,CAA0B,EAAK,KAAK,EAGpC,EAAc,EAAK,UAAW,GAAa,CAC/C,EAAc,IAAI,CAAQ,CAC5B,CAAC,EAGK,MAAwB,EAE5B,EAAA,EAAA,OAAA,CAD0B,EAAA,UAC1B,CAAA,CAAW,cAAgB,CACzB,EAAY,CACd,CAAC,CACH,EAUA,OAPI,GAAS,UACX,EAAA,EAAA,sBAAA,CAAsB,EAAQ,SAAU,CAAe,EAGvD,EAAgB,EAGX,EAAc,WAAW,CAClC,CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveEngine4Angular.mjs","names":[],"sources":["../../src/angular/ReactiveEngine4Angular.ts"],"sourcesContent":["import { signal, inject, DestroyRef, Injector, runInInjectionContext, type Signal } from '@angular/core';\nimport { ReactiveEngine as OriginalReactiveEngine, type CleanupFn } from '../core/core';\n\n// Описываем интерфейс опций для Angular-адаптера\nexport interface AngularUseOptions {\n /**\n * Кастомный инжектор Angular.\n * Позволяет использовать .use() вне конструктора (например, в асинхронных функциях),\n * если передать текущий Injector вручную.\n */\n injector?: Injector;\n}\n\nexport class ReactiveEngine4Angular extends OriginalReactiveEngine {\n /**\n * Использование реактивного значения в Angular компоненте или сервисе.\n * Возвращает стандартный Angular Signal.\n */\n public use<T>(\n item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn },\n options?: AngularUseOptions\n ): Signal<T> {\n // 1. Создаем локальный Angular Signal со стартовым значением\n const angularSignal = signal<T>(item.value);\n\n // 2. Подписываемся на изменения сигнала вашего ядра\n const unsubscribe = item.subscribe((newValue) => {\n angularSignal.set(newValue); // Обновляем значение внутри Angular\n });\n\n // 3. Логика автоматической отписки через DestroyRef\n const registerCleanup = () => {\n const destroyRef = inject(DestroyRef);\n destroyRef.onDestroy(() => {\n unsubscribe();\n });\n };\n\n // 4. Если пользователь явно передал кастомный инжектор — выполняем регистрацию внутри его контекста\n if (options?.injector) {\n runInInjectionContext(options.injector, registerCleanup);\n } else {\n // Иначе штатно выполняем в текущем контексте (вызовет ошибку NG0203, если контекст не валиден)\n registerCleanup();\n }\n\n return angularSignal.asReadonly();\n }\n}\n"],"mappings":";;;AAaA,IAAa,IAAb,cAA4C,EAAuB;CAKjE,IACE,GACA,GACW;EAEX,IAAM,IAAgB,EAAU,EAAK,KAAK,GAGpC,IAAc,EAAK,WAAW,MAAa;GAC/C,EAAc,IAAI,CAAQ;EAC5B,CAAC,GAGK,UAAwB;GAE5B,EAD0B,CAC1B,CAAA,CAAW,gBAAgB;IACzB,EAAY;GACd,CAAC;EACH;EAUA,OAPI,GAAS,WACX,EAAsB,EAAQ,UAAU,CAAe,IAGvD,EAAgB,GAGX,EAAc,WAAW;CAClC;AACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.cjs","names":[],"sources":["../src/core/core.ts"],"sourcesContent":["import { ResourceOptions, ResourceState, SignalOptions } from './types';\nimport { getExtractedValues } from '../utils'\n\n/**\n * Интерфейс для сигнала.\n * @template T\n * @interface Signal<T>\n */\nexport interface Signal<T> {\n /**\n * Значение сигнала.\n * @type {T}\n */\n value: T;\n\n /**\n * Подписка на изменение значения сигнала.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport type CleanupFn = () => void;\nexport type EffectFn = () => CleanupFn | void;\nexport type Token<T> = string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T };\nexport type Factory<T> = (engine: ReactiveEngine) => T;\n\n/**\n * Интерфейс для вычисляемого значения.\n * @template T\n * @interface Computed<T>\n */\nexport interface Computed<T> {\n /**\n * Только читаемое значение вычисляемого значения.\n * @type {T}\n */\n readonly value: T;\n\n /**\n * Подписка на изменение вычисляемого значения.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n\n /**\n * Принудительное уничтожение вычисляемого значения и его эффекта для предотвращения утечек памяти.\n */\n destroy: () => void;\n}\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для ресурса.\n * @template T\n * @interface Resource<T>\n */\nexport interface Resource<T> extends ResourceState<T> {\n /**\n * Перезагрузка ресурса.\n * @function refetch\n * @returns {void}\n */\n refetch: () => void;\n\n /**\n * Подписка на изменение состояния ресурса.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;\n\n /**\n * Только читаемое состояние ресурса.\n * @type {ResourceState<T>}\n */\n readonly value: ResourceState<T>;\n}\n\n// --- ЯДРО ---\n/**\n * Класс `ReactiveEngine` представляет собой реактивную систему, которая позволяет создавать и управлять реактивными объектами,\n * сигналами, эффектами, асинхронными ресурсами и другими реактивными примитивами. Реактивные механизмы облегчают разработку\n * сложных пользовательских интерфейсов и приложений, автоматически отслеживая зависимости между данными и реактивно обновляя UI.\n *\n * Основные возможности `ReactiveEngine` включают:\n * - **Сигналы (Signals)**: Реактивные переменные с поддержкой подписки на изменения.\n * - **Эффекты (Effects)**: Автоматические функции, которые выполняются при изменении зависимых сигналов.\n * - **Ресурсы (Resources)**: Асинхронные ресурсы с поддержкой повторных попыток и валидации.\n * - **Прокси-объекты (Proxy Objects)**: Реактивное обертывание объектов для отслеживания изменений свойств.\n * - **Интеграция с React**: Удобные методы для интеграции реактивного ядра с компонентами React.\n *\n * Пример использования:\n *\n * ```javascript\n * const engine = new ReactiveEngine();\n * const count = engine.signal(0);\n *\n * engine.effect(() => {\n * console.log(`Count is ${count.value}`);\n * });\n *\n * count.value++; // Выведет: Count is 1\n * ```\n *\n * @class\n */\nexport class ReactiveEngine {\n private activeEffect: IEffect | null = null;\n private isBatching = false;\n private pendingEffects = new Set<IEffect>();\n\n // В контейнерах DI вместо any используем unknown. Это заставит методы inject/provide\n // явно приводить типы через дженерики <T>, защищая от рантайм-ошибок.\n private services = new Map<Token<unknown>, unknown>();\n private factories = new Map<Token<unknown>, Factory<unknown>>();\n\n // Объект-ключ мапится на объект-прокси. Здесь идеально подходит тип object.\n private proxyCache = new WeakMap<object, object>();\n private allEffects = new Set<IEffect>();\n\n /**\n * Коллбек для уведомления об изменении сигнала.\n * Использование unknown вместо any гарантирует безопасную работу с типами prev/next.\n */\n public onSignalChange?: (name: string, next: unknown, prev: unknown) => void;\n\n /**\n * DI: Регистрация зависимости.\n * @template T\n * @function provide\n * @param {Token<T>} token - Токен для зависимости.\n * @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.\n * @returns {void}\n * @source\n */\n public provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void {\n if (typeof valueOrFactory === 'function' && !valueOrFactory.prototype) {\n this.factories.set(token, valueOrFactory as Factory<T>);\n } else {\n this.services.set(token, valueOrFactory);\n }\n }\n\n /**\n * DI: Инъекция; Получение сервиса по токену.\n * @template T\n * @function inject\n * @param {Token<T>} token - Токен для зависимости.\n * @returns {T} - Сервис.\n * @source\n */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n // Приводим токен к базовому типу Token<unknown> для совместимости с Map\n const targetToken = token as Token<unknown>;\n\n if (this.services.has(targetToken)) {\n return this.services.get(targetToken) as T;\n }\n\n try {\n const factory = this.factories.get(targetToken);\n if (factory) {\n const instance = factory(this) as T;\n this.services.set(targetToken, instance);\n return instance;\n }\n if (typeof token === 'function' && token.prototype) {\n const instance = new (token as { new(eng: ReactiveEngine): T })(this);\n this.services.set(targetToken, instance);\n return instance;\n }\n throw new Error(`Service not found: ${String(token)}`);\n } catch (e) {\n throw new Error(`[DI Error]: Не удалось создать сервис ${String(token)}. Ошибка: ${(e as Error)?.message || 'No e?.message'}`);\n }\n }\n\n /**\n * Создание сигнала.\n * @template T\n * @function signal\n * @param {T} initialValue - Начальное значение сигнала.\n * @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.\n * @returns {Signal<T>} - Сигнал.\n * @source\n */\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n const engine = this;\n let val = initialValue;\n const subscribers = new Set<IEffect>();\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n const name = options.name || 'unnamed_signal';\n\n return {\n get value(): T {\n if (engine.activeEffect) subscribers.add(engine.activeEffect);\n return val;\n },\n set value(newValue: T) {\n if (val === newValue) return;\n\n // ВАЛИДАЦИЯ В RUNTIME\n if (options.validate) {\n const result = options.validate(newValue);\n if (result === false || typeof result === 'string') {\n const errorMsg = typeof result === 'string'\n ? result\n : `[Validation Error]: Некорректное значение для сигнала \"${name}\"`;\n\n console.error(`%c${errorMsg}`, \"color: orange; font-weight: bold;\", {\n received: newValue,\n current: val\n });\n return; // Прерываем обновление, если данные не валидны\n }\n }\n\n const old = val;\n val = newValue;\n engine.onSignalChange?.(name, newValue, old);\n\n // 1. Всегда добавляем подписчиков в очередь отложенных эффектов\n subscribers.forEach(e => engine.pendingEffects.add(e));\n\n // 2. Если очередь еще не запущена, планируем её автоматическое выполнение в микрозадачу\n if (!engine.isBatching) {\n engine.isBatching = true;\n\n queueMicrotask(() => {\n const effects = Array.from(engine.pendingEffects);\n engine.pendingEffects.clear();\n engine.isBatching = false;\n\n // Запускаем все накопившиеся эффекты и ререндеры за один раз\n effects.forEach(e => e.run());\n });\n }\n },\n subscribe(cb: (val: T) => void) {\n // Чистая и безопасная подписка для React:\n // Мы вызываем cb, но НЕ передаем туда аргументы, чтобы не ломать useSyncExternalStore\n return engine.effect(() => {\n cb(this.value);\n });\n }\n };\n }\n\n /**\n * Создание эффекта.\n * @function effect\n * @param {EffectFn} fn - Функция эффекта.\n * @param {string} [label] - Необязательная метка для логирования и отладки.\n * @returns {CleanupFn} - Функция для очистки эффекта.\n * @source\n */\n public effect(fn: EffectFn, label?: string): CleanupFn {\n const engine = this;\n const effectObj: IEffect = {\n label, // Запоминаем имя эффекта для профайлера/логов\n cleanups: new Set(),\n run() {\n this.cleanups.forEach(c => c());\n this.cleanups.clear();\n const prev = engine.activeEffect;\n engine.activeEffect = this;\n engine.safeRun(this, () => {\n const cleanup = fn();\n if (typeof cleanup === 'function') this.cleanups.add(cleanup);\n });\n engine.activeEffect = prev;\n }\n };\n this.allEffects.add(effectObj);\n effectObj.run();\n\n return () => {\n effectObj.cleanups.forEach(c => c());\n engine.pendingEffects.delete(effectObj);\n engine.allEffects.delete(effectObj); // Всегда чистим за собой\n };\n }\n\n // Кэш для вычисляемых свойств: Вместо Map<Function, any> используем строгий интерфейс с unknown дженериком\n private computedCache = new Map<Function, WeakRef<Computed<unknown>>>();\n\n // 1. Создаем реестр финализации под капотом движка.\n // В качестве токена очистки передаем функцию, которую нужно выполнить.\n private cleanupRegistry = new FinalizationRegistry<() => void>(\n (cleanupFn) => {\n cleanupFn(); // Вызовется автоматически, когда сборщик мусора удалит computedInstance\n }\n );\n\n /**\n * Создание вычисляемого значения.\n * @template T\n * @function computed\n * @param {Function} fn - Функция для вычисления значения.\n * @param {string} [signalName] - Имя сигнала.\n * @returns {Computed<T>} - Вычисляемое значение с методом destroy.\n * @source\n */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n // 1. Проверяем кэш. Если WeakRef существует и объект внутри него еще не удален GC:\n if (this.computedCache.has(fn)) {\n const cachedRef = this.computedCache.get(fn);\n const cachedInstance = cachedRef?.deref();\n if (cachedInstance) {\n return cachedInstance as Computed<T>;\n }\n }\n\n const engine = this;\n const sig = this.signal<T>(undefined as unknown as T, signalName || 'unnamed_computed');\n\n const unsubscribeEffect = this.effect(() => {\n const newValue = fn();\n sig.value = newValue;\n });\n\n const effectObj = Array.from(this.allEffects)[this.allEffects.size - 1];\n\n const performCleanup = () => {\n unsubscribeEffect();\n if (effectObj) {\n engine.allEffects.delete(effectObj);\n }\n engine.computedCache.delete(fn);\n };\n\n // Создаем инстанс computed\n const computedInstance: Computed<T> = {\n get value() { return sig.value; },\n subscribe: (cb: (val: T) => void) => sig.subscribe(cb),\n destroy() {\n performCleanup();\n }\n };\n\n this.cleanupRegistry.register(computedInstance, performCleanup);\n\n // 2. Сохраняем в кэш СЛАБУЮ ССЫЛКУ (WeakRef) на наш объект.\n // Теперь этот кэш больше НЕ будет удерживать объект в памяти насильно!\n this.computedCache.set(fn, new WeakRef(computedInstance as Computed<unknown>));\n\n return computedInstance;\n }\n\n /**\n * Создание реактивного объекта (Proxy).\n * @template T\n * @function reactive\n * @param {T} target - Целевой объект для проксирования.\n * @param {string} [name] - Имя проксируемого объекта.\n * @returns {T} - Реактивный объект.\n * @source\n */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) {\n return this.proxyCache.get(target) as T;\n }\n const engine = this;\n const propsSubscribers = new Map<string | symbol, Set<IEffect>>();\n\n const proxy = new Proxy(target, {\n get(obj, prop, receiver) {\n if (engine.activeEffect) {\n if (!propsSubscribers.has(prop)) propsSubscribers.set(prop, new Set());\n propsSubscribers.get(prop)!.add(engine.activeEffect);\n }\n const value = Reflect.get(obj, prop, receiver);\n return (value !== null && typeof value === 'object')\n ? engine.reactive(value, `${name}.${String(prop)}`)\n : value;\n },\n set(obj, prop, value, receiver) {\n const old = Reflect.get(obj, prop, receiver);\n // ПРОВЕРКА НА ИДЕНТИЧНОСТЬ\n if (old === value) return true;\n\n if (old !== value) {\n Reflect.set(obj, prop, value, receiver);\n engine.onSignalChange?.(`${name}.${String(prop)}`, value, old);\n propsSubscribers.get(prop)?.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n );\n }\n return true;\n }\n });\n this.proxyCache.set(target, proxy);\n return proxy;\n }\n\n /**\n * Группировка изменений. (Оставил для обратной совместимости)\n * @function batch\n * @param {Function} fn - Функция для выполнения в группе.\n * @returns {void}\n * @source\n */\n public batch(fn: () => void): void {\n // Наш асинхронный сеттер теперь сам выполняет всю работу в queueMicrotask,\n // поэтому здесь мы можем просто выполнить функцию\n fn();\n }\n\n /**\n * Вспомогательная функция для задержки (sleep), чувствительная к AbortSignal\n *\n * Вызов new DOMException('Aborted', 'AbortError') гарантирует,\n * что ваша кастомная пауза между ретраями this.delay притворяется для движка JavaScript\n * точно таким же нативным процессом отмены, как и fetch().\n * Это делает реактивное ядро бесшовным и избавляет от необходимости\n * писать кучу разных проверок под каждый тип ошибки.\n */\n private delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n return reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeoutId = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n const onAbort = () => {\n clearTimeout(timeoutId); // СИНХРОННО УБИВАЕТ ТАЙМЕР. В Vitest это заставит таймер исчезнуть из очереди прокрутки\n reject(new DOMException('Aborted', 'AbortError'));\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n }\n\n /**\n * Создание асинхронного ресурса. Типы на входе: <T - формат ответа, S - источник изменений (сигнал либо computed-кортеж из зачений сигналов через конструкцию `.value`)>\n *\n * Explained 👉 {@link https://github.com/garage-13/reactive-engine/blob/main/README_EN.md#1-async-resources-dependent-on-multiple-signals}\n *\n * @template T, S\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/200-resource/Example200.tsx Реализация базового компонента Example200}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/201-multi-resource/service.secondary.ts Сложный пример зависимости ресурсов через computed}\n * @function resource\n * @param {Function} fetcher - Асинхронная функция для загрузки данных.\n * @param {{ value: S }} [source] - Источник данных.\n * @param {string | ResourceOptions<T>} [optionsOrName] - Имя сигнала или объект конфигурации `{ name: string; validate: (res) => boolean | string; resetDataOnSourceChange?: boolean }`.\n * @returns {Resource<T>} - Асинхронный ресурс.\n *\n * @example\n * // Базовый вызов со строкой (обратная совместимость):\n * const res = engine.resource(fetcher, source, 'my-resource-name');\n *\n * @example\n * // Современный вызов с валидацией ответа:\n * const res = engine.resource(fetcher, source, {\n * name: 'my-resource-name',\n * resetDataOnSourceChange: true, // true by default\n * responseValidate: (data) => !!data || 'Данные пусты', // Проверяйте формат в соотв. с дженериком\n * });\n *\n * @example\n * // (Экспериментальная фича)\n * // В теле fetcher можно выкинуть ошибку в виде\n * // `throw new Error('[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1][MESSAGE=Your msg]')`\n * // Причина: Особенности внутренней реализации определения харакрера ошибки и необходимость делать retry (если такая опция передана)\n * // Рекомендуем вместо этого использовать `validateBeforeFetch` в опциях при создании ресурса.\n * const res = engine.resource(\n * async (counterValue, abortSignal) => {\n * if (counterValue === 0)\n * throw new Error([\n * '[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1]', // Иначе запустится механизм retry (напр. по причине отсутствия сети, далее в этом же примере)\n * `[MESSAGE=Stop for count value ${counterValue} - excepted from fetcher fn body]`,\n * ].join(' '))\n * const res = await fetch(\n * [\n * `${BASE_API_URL}/profile/search`,\n * '?',\n * [\n * `counter=${counterValue}`,\n * '_responseDelay=2000',\n * ].join('&')\n * ].join(''),\n * { signal: abortSignal }\n * )\n * if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)\n * return res.json()\n * },\n * this.counter,\n * )\n * @source\n */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>,\n ): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T, S>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>)\n : {};\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const responseValidate = options.responseValidate;\n const validateBeforeFetch = options.validateBeforeFetch;\n\n const retryCount = options.retryCount ?? 0;\n const baseDelay = options.retryDelay ?? 1000;\n const useExponential = options.isExponentialBackoffEnabled ?? false;\n const maxRetryDelay = options.maxRetryDelay ?? 30000;\n const timeoutMs = options.timeout;\n\n const state = this.signal<ResourceState<T>>(\n { data: null, loading: true, error: null, isRetrying: false },\n signalName\n );\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return;\n\n if (validateBeforeFetch) {\n const preValidationResult = validateBeforeFetch(sValue);\n if (preValidationResult === false || typeof preValidationResult === 'string') {\n if (effectSignal.aborted) return;\n const errorMsg = typeof preValidationResult === 'string'\n ? preValidationResult\n : 'Pre-fetch validation failed for resource';\n\n // При ручном refetch (не смена source) сохраняем старые данные, а не зануляем\n const currentData = isSourceChange ? null : this.untrack(() => state.value.data);\n state.value = { data: currentData, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n const shouldClear = isSourceChange && resetDataOnSourceChange;\n const currentData = shouldClear ? null : this.untrack(() => state.value.data);\n\n state.value = { data: currentData, loading: true, error: null, isRetrying: false };\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return;\n let combinedSignal = effectSignal;\n\n try {\n if (timeoutMs && timeoutMs > 0) {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);\n }\n\n const data = await fetcher(sValue, combinedSignal);\n\n if (effectSignal.aborted) return;\n\n if (combinedSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError');\n }\n\n if (responseValidate) {\n const validationResult = responseValidate(data);\n if (validationResult === false || typeof validationResult === 'string') {\n if (effectSignal.aborted) return;\n const errorMsg = typeof validationResult === 'string' ? validationResult : 'Validation failed';\n state.value = { data: null, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n if (effectSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n } catch (e: any) {\n // ЖЕСТКИЙ ВЫХОД: Если внешняя область (эффект или новый refetch) сделала abort,\n // мы НЕМЕДЛЕННО прекращаем выполнение и ничего не пишем в стейт.\n if (effectSignal.aborted || (e.name === 'AbortError' && effectSignal.aborted)) return;\n\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n const isCustomValidationError = getExtractedValues({\n tested: [e.message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n\n const __defaultCustomValidationErrorMessage = 'Custom validation error';\n let customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [e.message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage;\n\n const isFetchBodyValidationError = isCustomValidationError || e.name === 'ValidationError';\n\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return;\n\n const finalError = isTimeout\n ? new Error(`Request timed out after ${timeoutMs}ms`, { cause: e })\n : isCustomValidationError\n ? new Error(customValidationErrorMessage, { cause: e })\n : e;\n\n state.value = { data: null, loading: false, error: finalError, isRetrying: false };\n return;\n } else {\n if (effectSignal.aborted) return;\n\n let currentDelay = useExponential ? baseDelay * Math.pow(2, attempt) : baseDelay;\n currentDelay = Math.min(currentDelay, maxRetryDelay);\n const jitter = Math.random() * 200;\n currentDelay = currentDelay + jitter;\n\n // Безопасно обновляем статус ретрая, только если сигнал живой\n state.value = { data: state.value.data, loading: true, error: null, isRetrying: true };\n\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети';\n console.warn(`[Resource Retry] \"${signalName}\" сбой по ${logReason}. Попытка ${attempt + 1}/${retryCount + 1}...`);\n\n try {\n await this.delay(currentDelay, effectSignal);\n if (effectSignal.aborted) return;\n } catch (delayError) {\n return;\n }\n }\n }\n }\n };\n\n let activeEffectController: AbortController | null = null;\n\n this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n const effectController = new AbortController();\n activeEffectController = effectController;\n\n this.untrack(() => {\n load(sValue as S, effectController.signal, true);\n });\n\n return () => effectController.abort();\n });\n\n return {\n get data() { return state.value.data; },\n get loading() { return state.value.loading; },\n get error() { return state.value.error; },\n get isRetrying() { return state.value.isRetrying; },\n get value() { return state.value; },\n refetch: () => {\n activeEffectController?.abort();\n activeEffectController = new AbortController();\n load(source ? source.value : undefined as S, activeEffectController.signal, false);\n },\n subscribe: (cb: (val: ResourceState<T>) => void) => state.subscribe(cb)\n };\n }\n\n /**\n * Выполняет функцию без отслеживания зависимостей.\n * @template T\n * @function untrack\n * @param {Function} fn - Функция для выполнения.\n * @returns {T} - Результат выполнения функции.\n * @source\n */\n public untrack<T>(fn: () => T): T {\n const prev = this.activeEffect;\n this.activeEffect = null; // Временно \"забываем\" про активный эффект\n try {\n return fn();\n } finally {\n this.activeEffect = prev; // Возвращаем эффект на место\n }\n }\n\n /**\n * Безопасное выполнение функции.\n * @private\n * @function safeRun\n * @param {IEffect} effect - Эффект.\n * @param {Function} fn - Функция для выполнения.\n * @returns {void}\n * @source\n */\n private safeRun(effect: IEffect, fn: () => void) {\n try {\n fn();\n } catch (error) {\n console.error(\n `%c[Reactive Error] %cОшибка в эффекте/computed:`,\n \"color: white; background: red; padding: 2px 4px; border-radius: 3px;\",\n \"font-weight: bold;\",\n error\n );\n // Здесь можно отправить ошибку в Sentry или другой сервис мониторинга\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.mjs","names":[],"sources":["../src/core/core.ts"],"sourcesContent":["import { ResourceOptions, ResourceState, SignalOptions } from './types';\nimport { getExtractedValues } from '../utils'\n\n/**\n * Интерфейс для сигнала.\n * @template T\n * @interface Signal<T>\n */\nexport interface Signal<T> {\n /**\n * Значение сигнала.\n * @type {T}\n */\n value: T;\n\n /**\n * Подписка на изменение значения сигнала.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport type CleanupFn = () => void;\nexport type EffectFn = () => CleanupFn | void;\nexport type Token<T> = string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T };\nexport type Factory<T> = (engine: ReactiveEngine) => T;\n\n/**\n * Интерфейс для вычисляемого значения.\n * @template T\n * @interface Computed<T>\n */\nexport interface Computed<T> {\n /**\n * Только читаемое значение вычисляемого значения.\n * @type {T}\n */\n readonly value: T;\n\n /**\n * Подписка на изменение вычисляемого значения.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n\n /**\n * Принудительное уничтожение вычисляемого значения и его эффекта для предотвращения утечек памяти.\n */\n destroy: () => void;\n}\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для ресурса.\n * @template T\n * @interface Resource<T>\n */\nexport interface Resource<T> extends ResourceState<T> {\n /**\n * Перезагрузка ресурса.\n * @function refetch\n * @returns {void}\n */\n refetch: () => void;\n\n /**\n * Подписка на изменение состояния ресурса.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;\n\n /**\n * Только читаемое состояние ресурса.\n * @type {ResourceState<T>}\n */\n readonly value: ResourceState<T>;\n}\n\n// --- ЯДРО ---\n/**\n * Класс `ReactiveEngine` представляет собой реактивную систему, которая позволяет создавать и управлять реактивными объектами,\n * сигналами, эффектами, асинхронными ресурсами и другими реактивными примитивами. Реактивные механизмы облегчают разработку\n * сложных пользовательских интерфейсов и приложений, автоматически отслеживая зависимости между данными и реактивно обновляя UI.\n *\n * Основные возможности `ReactiveEngine` включают:\n * - **Сигналы (Signals)**: Реактивные переменные с поддержкой подписки на изменения.\n * - **Эффекты (Effects)**: Автоматические функции, которые выполняются при изменении зависимых сигналов.\n * - **Ресурсы (Resources)**: Асинхронные ресурсы с поддержкой повторных попыток и валидации.\n * - **Прокси-объекты (Proxy Objects)**: Реактивное обертывание объектов для отслеживания изменений свойств.\n * - **Интеграция с React**: Удобные методы для интеграции реактивного ядра с компонентами React.\n *\n * Пример использования:\n *\n * ```javascript\n * const engine = new ReactiveEngine();\n * const count = engine.signal(0);\n *\n * engine.effect(() => {\n * console.log(`Count is ${count.value}`);\n * });\n *\n * count.value++; // Выведет: Count is 1\n * ```\n *\n * @class\n */\nexport class ReactiveEngine {\n private activeEffect: IEffect | null = null;\n private isBatching = false;\n private pendingEffects = new Set<IEffect>();\n\n // В контейнерах DI вместо any используем unknown. Это заставит методы inject/provide\n // явно приводить типы через дженерики <T>, защищая от рантайм-ошибок.\n private services = new Map<Token<unknown>, unknown>();\n private factories = new Map<Token<unknown>, Factory<unknown>>();\n\n // Объект-ключ мапится на объект-прокси. Здесь идеально подходит тип object.\n private proxyCache = new WeakMap<object, object>();\n private allEffects = new Set<IEffect>();\n\n /**\n * Коллбек для уведомления об изменении сигнала.\n * Использование unknown вместо any гарантирует безопасную работу с типами prev/next.\n */\n public onSignalChange?: (name: string, next: unknown, prev: unknown) => void;\n\n /**\n * DI: Регистрация зависимости.\n * @template T\n * @function provide\n * @param {Token<T>} token - Токен для зависимости.\n * @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.\n * @returns {void}\n * @source\n */\n public provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void {\n if (typeof valueOrFactory === 'function' && !valueOrFactory.prototype) {\n this.factories.set(token, valueOrFactory as Factory<T>);\n } else {\n this.services.set(token, valueOrFactory);\n }\n }\n\n /**\n * DI: Инъекция; Получение сервиса по токену.\n * @template T\n * @function inject\n * @param {Token<T>} token - Токен для зависимости.\n * @returns {T} - Сервис.\n * @source\n */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n // Приводим токен к базовому типу Token<unknown> для совместимости с Map\n const targetToken = token as Token<unknown>;\n\n if (this.services.has(targetToken)) {\n return this.services.get(targetToken) as T;\n }\n\n try {\n const factory = this.factories.get(targetToken);\n if (factory) {\n const instance = factory(this) as T;\n this.services.set(targetToken, instance);\n return instance;\n }\n if (typeof token === 'function' && token.prototype) {\n const instance = new (token as { new(eng: ReactiveEngine): T })(this);\n this.services.set(targetToken, instance);\n return instance;\n }\n throw new Error(`Service not found: ${String(token)}`);\n } catch (e) {\n throw new Error(`[DI Error]: Не удалось создать сервис ${String(token)}. Ошибка: ${(e as Error)?.message || 'No e?.message'}`);\n }\n }\n\n /**\n * Создание сигнала.\n * @template T\n * @function signal\n * @param {T} initialValue - Начальное значение сигнала.\n * @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.\n * @returns {Signal<T>} - Сигнал.\n * @source\n */\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n const engine = this;\n let val = initialValue;\n const subscribers = new Set<IEffect>();\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n const name = options.name || 'unnamed_signal';\n\n return {\n get value(): T {\n if (engine.activeEffect) subscribers.add(engine.activeEffect);\n return val;\n },\n set value(newValue: T) {\n if (val === newValue) return;\n\n // ВАЛИДАЦИЯ В RUNTIME\n if (options.validate) {\n const result = options.validate(newValue);\n if (result === false || typeof result === 'string') {\n const errorMsg = typeof result === 'string'\n ? result\n : `[Validation Error]: Некорректное значение для сигнала \"${name}\"`;\n\n console.error(`%c${errorMsg}`, \"color: orange; font-weight: bold;\", {\n received: newValue,\n current: val\n });\n return; // Прерываем обновление, если данные не валидны\n }\n }\n\n const old = val;\n val = newValue;\n engine.onSignalChange?.(name, newValue, old);\n\n // 1. Всегда добавляем подписчиков в очередь отложенных эффектов\n subscribers.forEach(e => engine.pendingEffects.add(e));\n\n // 2. Если очередь еще не запущена, планируем её автоматическое выполнение в микрозадачу\n if (!engine.isBatching) {\n engine.isBatching = true;\n\n queueMicrotask(() => {\n const effects = Array.from(engine.pendingEffects);\n engine.pendingEffects.clear();\n engine.isBatching = false;\n\n // Запускаем все накопившиеся эффекты и ререндеры за один раз\n effects.forEach(e => e.run());\n });\n }\n },\n subscribe(cb: (val: T) => void) {\n // Чистая и безопасная подписка для React:\n // Мы вызываем cb, но НЕ передаем туда аргументы, чтобы не ломать useSyncExternalStore\n return engine.effect(() => {\n cb(this.value);\n });\n }\n };\n }\n\n /**\n * Создание эффекта.\n * @function effect\n * @param {EffectFn} fn - Функция эффекта.\n * @param {string} [label] - Необязательная метка для логирования и отладки.\n * @returns {CleanupFn} - Функция для очистки эффекта.\n * @source\n */\n public effect(fn: EffectFn, label?: string): CleanupFn {\n const engine = this;\n const effectObj: IEffect = {\n label, // Запоминаем имя эффекта для профайлера/логов\n cleanups: new Set(),\n run() {\n this.cleanups.forEach(c => c());\n this.cleanups.clear();\n const prev = engine.activeEffect;\n engine.activeEffect = this;\n engine.safeRun(this, () => {\n const cleanup = fn();\n if (typeof cleanup === 'function') this.cleanups.add(cleanup);\n });\n engine.activeEffect = prev;\n }\n };\n this.allEffects.add(effectObj);\n effectObj.run();\n\n return () => {\n effectObj.cleanups.forEach(c => c());\n engine.pendingEffects.delete(effectObj);\n engine.allEffects.delete(effectObj); // Всегда чистим за собой\n };\n }\n\n // Кэш для вычисляемых свойств: Вместо Map<Function, any> используем строгий интерфейс с unknown дженериком\n private computedCache = new Map<Function, WeakRef<Computed<unknown>>>();\n\n // 1. Создаем реестр финализации под капотом движка.\n // В качестве токена очистки передаем функцию, которую нужно выполнить.\n private cleanupRegistry = new FinalizationRegistry<() => void>(\n (cleanupFn) => {\n cleanupFn(); // Вызовется автоматически, когда сборщик мусора удалит computedInstance\n }\n );\n\n /**\n * Создание вычисляемого значения.\n * @template T\n * @function computed\n * @param {Function} fn - Функция для вычисления значения.\n * @param {string} [signalName] - Имя сигнала.\n * @returns {Computed<T>} - Вычисляемое значение с методом destroy.\n * @source\n */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n // 1. Проверяем кэш. Если WeakRef существует и объект внутри него еще не удален GC:\n if (this.computedCache.has(fn)) {\n const cachedRef = this.computedCache.get(fn);\n const cachedInstance = cachedRef?.deref();\n if (cachedInstance) {\n return cachedInstance as Computed<T>;\n }\n }\n\n const engine = this;\n const sig = this.signal<T>(undefined as unknown as T, signalName || 'unnamed_computed');\n\n const unsubscribeEffect = this.effect(() => {\n const newValue = fn();\n sig.value = newValue;\n });\n\n const effectObj = Array.from(this.allEffects)[this.allEffects.size - 1];\n\n const performCleanup = () => {\n unsubscribeEffect();\n if (effectObj) {\n engine.allEffects.delete(effectObj);\n }\n engine.computedCache.delete(fn);\n };\n\n // Создаем инстанс computed\n const computedInstance: Computed<T> = {\n get value() { return sig.value; },\n subscribe: (cb: (val: T) => void) => sig.subscribe(cb),\n destroy() {\n performCleanup();\n }\n };\n\n this.cleanupRegistry.register(computedInstance, performCleanup);\n\n // 2. Сохраняем в кэш СЛАБУЮ ССЫЛКУ (WeakRef) на наш объект.\n // Теперь этот кэш больше НЕ будет удерживать объект в памяти насильно!\n this.computedCache.set(fn, new WeakRef(computedInstance as Computed<unknown>));\n\n return computedInstance;\n }\n\n /**\n * Создание реактивного объекта (Proxy).\n * @template T\n * @function reactive\n * @param {T} target - Целевой объект для проксирования.\n * @param {string} [name] - Имя проксируемого объекта.\n * @returns {T} - Реактивный объект.\n * @source\n */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) {\n return this.proxyCache.get(target) as T;\n }\n const engine = this;\n const propsSubscribers = new Map<string | symbol, Set<IEffect>>();\n\n const proxy = new Proxy(target, {\n get(obj, prop, receiver) {\n if (engine.activeEffect) {\n if (!propsSubscribers.has(prop)) propsSubscribers.set(prop, new Set());\n propsSubscribers.get(prop)!.add(engine.activeEffect);\n }\n const value = Reflect.get(obj, prop, receiver);\n return (value !== null && typeof value === 'object')\n ? engine.reactive(value, `${name}.${String(prop)}`)\n : value;\n },\n set(obj, prop, value, receiver) {\n const old = Reflect.get(obj, prop, receiver);\n // ПРОВЕРКА НА ИДЕНТИЧНОСТЬ\n if (old === value) return true;\n\n if (old !== value) {\n Reflect.set(obj, prop, value, receiver);\n engine.onSignalChange?.(`${name}.${String(prop)}`, value, old);\n propsSubscribers.get(prop)?.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n );\n }\n return true;\n }\n });\n this.proxyCache.set(target, proxy);\n return proxy;\n }\n\n /**\n * Группировка изменений. (Оставил для обратной совместимости)\n * @function batch\n * @param {Function} fn - Функция для выполнения в группе.\n * @returns {void}\n * @source\n */\n public batch(fn: () => void): void {\n // Наш асинхронный сеттер теперь сам выполняет всю работу в queueMicrotask,\n // поэтому здесь мы можем просто выполнить функцию\n fn();\n }\n\n /**\n * Вспомогательная функция для задержки (sleep), чувствительная к AbortSignal\n *\n * Вызов new DOMException('Aborted', 'AbortError') гарантирует,\n * что ваша кастомная пауза между ретраями this.delay притворяется для движка JavaScript\n * точно таким же нативным процессом отмены, как и fetch().\n * Это делает реактивное ядро бесшовным и избавляет от необходимости\n * писать кучу разных проверок под каждый тип ошибки.\n */\n private delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n return reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeoutId = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n const onAbort = () => {\n clearTimeout(timeoutId); // СИНХРОННО УБИВАЕТ ТАЙМЕР. В Vitest это заставит таймер исчезнуть из очереди прокрутки\n reject(new DOMException('Aborted', 'AbortError'));\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n }\n\n /**\n * Создание асинхронного ресурса. Типы на входе: <T - формат ответа, S - источник изменений (сигнал либо computed-кортеж из зачений сигналов через конструкцию `.value`)>\n *\n * Explained 👉 {@link https://github.com/garage-13/reactive-engine/blob/main/README_EN.md#1-async-resources-dependent-on-multiple-signals}\n *\n * @template T, S\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/200-resource/Example200.tsx Реализация базового компонента Example200}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/201-multi-resource/service.secondary.ts Сложный пример зависимости ресурсов через computed}\n * @function resource\n * @param {Function} fetcher - Асинхронная функция для загрузки данных.\n * @param {{ value: S }} [source] - Источник данных.\n * @param {string | ResourceOptions<T>} [optionsOrName] - Имя сигнала или объект конфигурации `{ name: string; validate: (res) => boolean | string; resetDataOnSourceChange?: boolean }`.\n * @returns {Resource<T>} - Асинхронный ресурс.\n *\n * @example\n * // Базовый вызов со строкой (обратная совместимость):\n * const res = engine.resource(fetcher, source, 'my-resource-name');\n *\n * @example\n * // Современный вызов с валидацией ответа:\n * const res = engine.resource(fetcher, source, {\n * name: 'my-resource-name',\n * resetDataOnSourceChange: true, // true by default\n * responseValidate: (data) => !!data || 'Данные пусты', // Проверяйте формат в соотв. с дженериком\n * });\n *\n * @example\n * // (Экспериментальная фича)\n * // В теле fetcher можно выкинуть ошибку в виде\n * // `throw new Error('[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1][MESSAGE=Your msg]')`\n * // Причина: Особенности внутренней реализации определения харакрера ошибки и необходимость делать retry (если такая опция передана)\n * // Рекомендуем вместо этого использовать `validateBeforeFetch` в опциях при создании ресурса.\n * const res = engine.resource(\n * async (counterValue, abortSignal) => {\n * if (counterValue === 0)\n * throw new Error([\n * '[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1]', // Иначе запустится механизм retry (напр. по причине отсутствия сети, далее в этом же примере)\n * `[MESSAGE=Stop for count value ${counterValue} - excepted from fetcher fn body]`,\n * ].join(' '))\n * const res = await fetch(\n * [\n * `${BASE_API_URL}/profile/search`,\n * '?',\n * [\n * `counter=${counterValue}`,\n * '_responseDelay=2000',\n * ].join('&')\n * ].join(''),\n * { signal: abortSignal }\n * )\n * if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)\n * return res.json()\n * },\n * this.counter,\n * )\n * @source\n */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>,\n ): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T, S>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>)\n : {};\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const responseValidate = options.responseValidate;\n const validateBeforeFetch = options.validateBeforeFetch;\n\n const retryCount = options.retryCount ?? 0;\n const baseDelay = options.retryDelay ?? 1000;\n const useExponential = options.isExponentialBackoffEnabled ?? false;\n const maxRetryDelay = options.maxRetryDelay ?? 30000;\n const timeoutMs = options.timeout;\n\n const state = this.signal<ResourceState<T>>(\n { data: null, loading: true, error: null, isRetrying: false },\n signalName\n );\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return;\n\n if (validateBeforeFetch) {\n const preValidationResult = validateBeforeFetch(sValue);\n if (preValidationResult === false || typeof preValidationResult === 'string') {\n if (effectSignal.aborted) return;\n const errorMsg = typeof preValidationResult === 'string'\n ? preValidationResult\n : 'Pre-fetch validation failed for resource';\n\n // При ручном refetch (не смена source) сохраняем старые данные, а не зануляем\n const currentData = isSourceChange ? null : this.untrack(() => state.value.data);\n state.value = { data: currentData, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n const shouldClear = isSourceChange && resetDataOnSourceChange;\n const currentData = shouldClear ? null : this.untrack(() => state.value.data);\n\n state.value = { data: currentData, loading: true, error: null, isRetrying: false };\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return;\n let combinedSignal = effectSignal;\n\n try {\n if (timeoutMs && timeoutMs > 0) {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);\n }\n\n const data = await fetcher(sValue, combinedSignal);\n\n if (effectSignal.aborted) return;\n\n if (combinedSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError');\n }\n\n if (responseValidate) {\n const validationResult = responseValidate(data);\n if (validationResult === false || typeof validationResult === 'string') {\n if (effectSignal.aborted) return;\n const errorMsg = typeof validationResult === 'string' ? validationResult : 'Validation failed';\n state.value = { data: null, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n if (effectSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n } catch (e: any) {\n // ЖЕСТКИЙ ВЫХОД: Если внешняя область (эффект или новый refetch) сделала abort,\n // мы НЕМЕДЛЕННО прекращаем выполнение и ничего не пишем в стейт.\n if (effectSignal.aborted || (e.name === 'AbortError' && effectSignal.aborted)) return;\n\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n const isCustomValidationError = getExtractedValues({\n tested: [e.message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n\n const __defaultCustomValidationErrorMessage = 'Custom validation error';\n let customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [e.message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage;\n\n const isFetchBodyValidationError = isCustomValidationError || e.name === 'ValidationError';\n\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return;\n\n const finalError = isTimeout\n ? new Error(`Request timed out after ${timeoutMs}ms`, { cause: e })\n : isCustomValidationError\n ? new Error(customValidationErrorMessage, { cause: e })\n : e;\n\n state.value = { data: null, loading: false, error: finalError, isRetrying: false };\n return;\n } else {\n if (effectSignal.aborted) return;\n\n let currentDelay = useExponential ? baseDelay * Math.pow(2, attempt) : baseDelay;\n currentDelay = Math.min(currentDelay, maxRetryDelay);\n const jitter = Math.random() * 200;\n currentDelay = currentDelay + jitter;\n\n // Безопасно обновляем статус ретрая, только если сигнал живой\n state.value = { data: state.value.data, loading: true, error: null, isRetrying: true };\n\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети';\n console.warn(`[Resource Retry] \"${signalName}\" сбой по ${logReason}. Попытка ${attempt + 1}/${retryCount + 1}...`);\n\n try {\n await this.delay(currentDelay, effectSignal);\n if (effectSignal.aborted) return;\n } catch (delayError) {\n return;\n }\n }\n }\n }\n };\n\n let activeEffectController: AbortController | null = null;\n\n this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n const effectController = new AbortController();\n activeEffectController = effectController;\n\n this.untrack(() => {\n load(sValue as S, effectController.signal, true);\n });\n\n return () => effectController.abort();\n });\n\n return {\n get data() { return state.value.data; },\n get loading() { return state.value.loading; },\n get error() { return state.value.error; },\n get isRetrying() { return state.value.isRetrying; },\n get value() { return state.value; },\n refetch: () => {\n activeEffectController?.abort();\n activeEffectController = new AbortController();\n load(source ? source.value : undefined as S, activeEffectController.signal, false);\n },\n subscribe: (cb: (val: ResourceState<T>) => void) => state.subscribe(cb)\n };\n }\n\n /**\n * Выполняет функцию без отслеживания зависимостей.\n * @template T\n * @function untrack\n * @param {Function} fn - Функция для выполнения.\n * @returns {T} - Результат выполнения функции.\n * @source\n */\n public untrack<T>(fn: () => T): T {\n const prev = this.activeEffect;\n this.activeEffect = null; // Временно \"забываем\" про активный эффект\n try {\n return fn();\n } finally {\n this.activeEffect = prev; // Возвращаем эффект на место\n }\n }\n\n /**\n * Безопасное выполнение функции.\n * @private\n * @function safeRun\n * @param {IEffect} effect - Эффект.\n * @param {Function} fn - Функция для выполнения.\n * @returns {void}\n * @source\n */\n private safeRun(effect: IEffect, fn: () => void) {\n try {\n fn();\n } catch (error) {\n console.error(\n `%c[Reactive Error] %cОшибка в эффекте/computed:`,\n \"color: white; background: red; padding: 2px 4px; border-radius: 3px;\",\n \"font-weight: bold;\",\n error\n );\n // Здесь можно отправить ошибку в Sentry или другой сервис мониторинга\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveEngine4React.cjs","names":[],"sources":["../../src/react/ReactiveEngine4React.ts"],"sourcesContent":["import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react'\nimport { ReactiveEngine as OriginalReactiveEngine, CleanupFn } from '../core/core'\n\n/**\n * Класс адаптера для интеграции реактивного движка с React.\n * Наследует функциональность оригинального реактивного движка и предоставляет специализированные методы для работы с React.\n */\nexport class ReactiveEngine4React extends OriginalReactiveEngine {\n // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\n\n /**\n * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\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 * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\n }\n}\n"],"mappings":"sDAOA,IAAa,EAAb,cAA0C,EAAA,cAAuB,CAE/D,cAGI,CACA,SAAU,EAAA,SACV,UAAW,EAAA,SACb,EAUF,iBACE,EACA,EACM,CACN,KAAK,cAAgB,CAAE,WAAU,WAAU,CAC7C,CAUA,IAAc,EAAqE,CACjF,GAAI,CAAC,KAAK,cACR,MAAU,MAAM,sGAAsG,EAIxH,GAAI,CAAC,GAAQ,OAAO,EAAK,WAAc,WAAY,CACjD,IAAM,EAAW;;;;QAMjB,MADA,QAAQ,MAAM,EAAU,CAAE,MAAK,CAAC,EACtB,MAAM,CAAQ,CAC1B,CACA,GAAM,CAAC,EAAK,GAAU,KAAK,cAAc,SAAS,EAAK,KAAK,EAS5D,OAPA,KAAK,cAAc,cAER,EAAK,UAAU,CAAM,EAE9B,CAAC,CAAI,CACP,EAEO,CACT,CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveEngine4React.mjs","names":[],"sources":["../../src/react/ReactiveEngine4React.ts"],"sourcesContent":["import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react'\nimport { ReactiveEngine as OriginalReactiveEngine, CleanupFn } from '../core/core'\n\n/**\n * Класс адаптера для интеграции реактивного движка с React.\n * Наследует функциональность оригинального реактивного движка и предоставляет специализированные методы для работы с React.\n */\nexport class ReactiveEngine4React extends OriginalReactiveEngine {\n // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\n\n /**\n * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\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 * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\n }\n}\n"],"mappings":";;;AAOA,IAAa,IAAb,cAA0C,EAAuB;CAE/D,gBAGI;EACU;EACC;CACb;CAUF,iBACE,GACA,GACM;EACN,KAAK,gBAAgB;GAAE;GAAU;EAAU;CAC7C;CAUA,IAAc,GAAqE;EACjF,IAAI,CAAC,KAAK,eACR,MAAU,MAAM,sGAAsG;EAIxH,IAAI,CAAC,KAAQ,OAAO,EAAK,aAAc,YAAY;GACjD,IAAM,IAAW;GAMjB,MADA,QAAQ,MAAM,GAAU,EAAE,QAAK,CAAC,GACtB,MAAM,CAAQ;EAC1B;EACA,IAAM,CAAC,GAAK,KAAU,KAAK,cAAc,SAAS,EAAK,KAAK;EAS5D,OAPA,KAAK,cAAc,gBAER,EAAK,UAAU,CAAM,GAE9B,CAAC,CAAI,CACP,GAEO;CACT;AACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReactiveSubscription.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveSubscription/useReactiveSubscription.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { Signal, CleanupFn } from '../../../core'\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback)\n\n useEffect(() => {\n callbackRef.current = callback\n }, [callback])\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val)\n })\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe\n }, [signal])\n}\n"],"mappings":"uBAUA,IAAa,GACX,EACA,IACS,CAET,IAAM,GAAA,EAAA,EAAA,OAAA,CAAqB,CAAQ,GAEnC,EAAA,EAAA,UAAA,KAAgB,CACd,EAAY,QAAU,CACxB,EAAG,CAAC,CAAQ,CAAC,GAEb,EAAA,EAAA,UAAA,KAEiC,EAAO,UAAW,GAAQ,CACvD,EAAY,QAAQ,CAAG,CACzB,CAGO,EACN,CAAC,CAAM,CAAC,CACb"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReactiveSubscription.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveSubscription/useReactiveSubscription.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { Signal, CleanupFn } from '../../../core'\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback)\n\n useEffect(() => {\n callbackRef.current = callback\n }, [callback])\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val)\n })\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe\n }, [signal])\n}\n"],"mappings":";;AAUA,IAAa,KACX,GACA,MACS;CAET,IAAM,IAAc,EAAO,CAAQ;CAMnC,AAJA,QAAgB;EACd,EAAY,UAAU;CACxB,GAAG,CAAC,CAAQ,CAAC,GAEb,QAEiC,EAAO,WAAW,MAAQ;EACvD,EAAY,QAAQ,CAAG;CACzB,CAGO,GACN,CAAC,CAAM,CAAC;AACb"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReactiveValue.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"sourcesContent":["import { useSyncExternalStore, useCallback, useMemo, useEffect, useRef } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Нативный React-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и автоматического управления подпиской на рендеринг.\n *\n * Опирается на каноничную шим-прослойку `useSyncExternalStore`, что гарантирует 100%\n * совместимость с механизмами батчинга обновлений React 18+ и Concurrent Mode (защита от Tearing).\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса\n * export const CounterDisplay = () => {\n * const counter = useReactiveValue(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики для локальных вычислений (авто-очистка ядра при анмаунте)\n * export const FilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ФАБРИКИ: Сохраняем ссылку на функцию в ref,\n // чтобы не перезапускать вычисления, если разработчик передал инлайн-стрелочную функцию.\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго один раз при инициализации, либо при изменении стабильной ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n // Вызываем фабрику только при первом проходе\n return input();\n }\n return input; // Если передан готовый сигнал/ресурс — используем его напрямую\n }, [isFactory ? undefined : input]); // Стабильный массив зависимостей для фабрик!\n\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback)\n },\n [reactiveItem]\n )\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value\n }, [reactiveItem])\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Вызываем .destroy() ТОЛЬКО если объект пришел из локальной фабрики `() => engine.computed(...)`.\n // Если это глобальный сигнал из сервиса — мы делаем только стандартный unmount подписки через useSyncExternalStore.\n useEffect(() => {\n return () => {\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n }\n }, [reactiveItem, isFactory])\n\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n"],"mappings":"uBAkDA,IAAa,EAAuB,GAA+B,CACjE,IAAM,EAAY,OAAO,GAAU,WAI7B,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAK,GAC/B,EAAA,EAAA,UAAA,KAAgB,CACd,EAAW,QAAU,CACvB,EAAG,CAAC,CAAK,CAAC,EAGV,IAAM,GAAA,EAAA,EAAA,QAAA,KACA,OAAO,GAAU,WAEZ,EAAM,EAER,EACN,CAAC,EAAY,IAAA,GAAY,CAAK,CAAC,EAE5B,GAAA,EAAA,EAAA,YAAA,CACH,GACQ,EAAa,UAAU,CAAa,EAE7C,CAAC,CAAY,CACf,EAEM,GAAA,EAAA,EAAA,YAAA,KACG,EAAa,MACnB,CAAC,CAAY,CAAC,EAajB,OARA,EAAA,EAAA,UAAA,SACe,CACP,GAAa,GAAgB,OAAO,EAAa,SAAY,YAC/D,EAAa,QAAQ,CAEzB,EACC,CAAC,EAAc,CAAS,CAAC,GAE5B,EAAA,EAAA,qBAAA,CAA4B,EAAW,CAAW,CACpD"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReactiveValue.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"sourcesContent":["import { useSyncExternalStore, useCallback, useMemo, useEffect, useRef } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Нативный React-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и автоматического управления подпиской на рендеринг.\n *\n * Опирается на каноничную шим-прослойку `useSyncExternalStore`, что гарантирует 100%\n * совместимость с механизмами батчинга обновлений React 18+ и Concurrent Mode (защита от Tearing).\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса\n * export const CounterDisplay = () => {\n * const counter = useReactiveValue(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики для локальных вычислений (авто-очистка ядра при анмаунте)\n * export const FilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ФАБРИКИ: Сохраняем ссылку на функцию в ref,\n // чтобы не перезапускать вычисления, если разработчик передал инлайн-стрелочную функцию.\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго один раз при инициализации, либо при изменении стабильной ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n // Вызываем фабрику только при первом проходе\n return input();\n }\n return input; // Если передан готовый сигнал/ресурс — используем его напрямую\n }, [isFactory ? undefined : input]); // Стабильный массив зависимостей для фабрик!\n\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback)\n },\n [reactiveItem]\n )\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value\n }, [reactiveItem])\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Вызываем .destroy() ТОЛЬКО если объект пришел из локальной фабрики `() => engine.computed(...)`.\n // Если это глобальный сигнал из сервиса — мы делаем только стандартный unmount подписки через useSyncExternalStore.\n useEffect(() => {\n return () => {\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n }\n }, [reactiveItem, isFactory])\n\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n"],"mappings":";;AAkDA,IAAa,KAAuB,MAA+B;CACjE,IAAM,IAAY,OAAO,KAAU,YAI7B,IAAa,EAAO,CAAK;CAC/B,QAAgB;EACd,EAAW,UAAU;CACvB,GAAG,CAAC,CAAK,CAAC;CAGV,IAAM,IAAe,QACf,OAAO,KAAU,aAEZ,EAAM,IAER,GACN,CAAC,IAAY,KAAA,IAAY,CAAK,CAAC,GAE5B,IAAY,GACf,MACQ,EAAa,UAAU,CAAa,GAE7C,CAAC,CAAY,CACf,GAEM,IAAc,QACX,EAAa,OACnB,CAAC,CAAY,CAAC;CAajB,OARA,cACe;EACX,AAAI,KAAa,KAAgB,OAAO,EAAa,WAAY,cAC/D,EAAa,QAAQ;CAEzB,GACC,CAAC,GAAc,CAAS,CAAC,GAErB,EAAqB,GAAW,CAAW;AACpD"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReactiveValue0.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"sourcesContent":["import { useState, useEffect, useRef, useMemo } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Изоморфный фоллбэк-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и управления подпиской в окружениях **React 16.8+ и React 17**.\n *\n * В отличие от современной версии `useReactiveValue`, данный хук спроектирован на базе классической\n * связки `useState` + `useEffect`. Он содержит встроенный механизм упреждающей синхронизации\n * (Pre-emptive Synchronization Pattern), предотвращающий рассинхронизацию данных (Tearing) и мерцание UI\n * во время асинхронных микрозадач планировщика React.\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue0 } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса в legacy-компоненте\n * export const LegacyCounterDisplay = () => {\n * const counter = useReactiveValue0(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики во React 17 проектах (авто-очистка ядра при анмаунте)\n * export const LegacyFilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue0(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ИНПУТА: защищает от бесконечных циклов при инлайн-стрелочных фабриках\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго 1 раз при монтировании, либо при смене ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n return input(); // Ленивый вызов фабрики ровно один раз за жизнь компонента\n }\n return input;\n }, [isFactory ? undefined : input]); // Игнорируем ссылки инлайн-функций\n\n const [state, setState] = useState<T>(reactiveItem.value)\n const setStateRef = useRef(setState)\n\n // Держим ссылку на setState всегда актуальной для безопасных асинхронных вызовов\n useEffect(() => {\n setStateRef.current = setState\n }, [setState])\n\n // NOTE: СИНХРОНИЗАЦИЯ НА ОПЕРЕЖЕНИЕ (Теккущее значение):\n // Если между фазой рендера и выполнением useEffect значение в ядре библиотеки успело измениться,\n // мы синхронно подтягиваем актуальный стейт, исключая \"слепые пятна\" и мерцание UI.\n if (state !== reactiveItem.value) {\n setState(reactiveItem.value);\n }\n\n useEffect(() => {\n // Привязываем подписку ядра к триггеру обновлений React\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue)\n })\n\n return () => {\n unsubscribe();\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Метод .destroy() вызывается СТРОГО если объект был порожден локальной фабрикой.\n // Глобальные сигналы сервисов остаются в безопасности и не стираются из движка!\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy()\n }\n }\n }, [reactiveItem, isFactory])\n\n return state\n}\n"],"mappings":"uBAoDA,IAAa,EAAwB,GAA+B,CAClE,IAAM,EAAY,OAAO,GAAU,WAG7B,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAK,GAC/B,EAAA,EAAA,UAAA,KAAgB,CACd,EAAW,QAAU,CACvB,EAAG,CAAC,CAAK,CAAC,EAGV,IAAM,GAAA,EAAA,EAAA,QAAA,KACA,OAAO,GAAU,WACZ,EAAM,EAER,EACN,CAAC,EAAY,IAAA,GAAY,CAAK,CAAC,EAE5B,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAwB,EAAa,KAAK,EAClD,GAAA,EAAA,EAAA,OAAA,CAAqB,CAAQ,EAgCnC,OA7BA,EAAA,EAAA,UAAA,KAAgB,CACd,EAAY,QAAU,CACxB,EAAG,CAAC,CAAQ,CAAC,EAKT,IAAU,EAAa,OACzB,EAAS,EAAa,KAAK,GAG7B,EAAA,EAAA,UAAA,KAAgB,CAEd,IAAM,EAAc,EAAa,UAAW,GAAa,CACvD,EAAY,QAAQ,CAAQ,CAC9B,CAAC,EAED,UAAa,CACX,EAAY,EAKR,GAAa,GAAgB,OAAO,EAAa,SAAY,YAC/D,EAAa,QAAQ,CAEzB,CACF,EAAG,CAAC,EAAc,CAAS,CAAC,EAErB,CACT"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReactiveValue0.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"sourcesContent":["import { useState, useEffect, useRef, useMemo } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Изоморфный фоллбэк-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и управления подпиской в окружениях **React 16.8+ и React 17**.\n *\n * В отличие от современной версии `useReactiveValue`, данный хук спроектирован на базе классической\n * связки `useState` + `useEffect`. Он содержит встроенный механизм упреждающей синхронизации\n * (Pre-emptive Synchronization Pattern), предотвращающий рассинхронизацию данных (Tearing) и мерцание UI\n * во время асинхронных микрозадач планировщика React.\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue0 } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса в legacy-компоненте\n * export const LegacyCounterDisplay = () => {\n * const counter = useReactiveValue0(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики во React 17 проектах (авто-очистка ядра при анмаунте)\n * export const LegacyFilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue0(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ИНПУТА: защищает от бесконечных циклов при инлайн-стрелочных фабриках\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго 1 раз при монтировании, либо при смене ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n return input(); // Ленивый вызов фабрики ровно один раз за жизнь компонента\n }\n return input;\n }, [isFactory ? undefined : input]); // Игнорируем ссылки инлайн-функций\n\n const [state, setState] = useState<T>(reactiveItem.value)\n const setStateRef = useRef(setState)\n\n // Держим ссылку на setState всегда актуальной для безопасных асинхронных вызовов\n useEffect(() => {\n setStateRef.current = setState\n }, [setState])\n\n // NOTE: СИНХРОНИЗАЦИЯ НА ОПЕРЕЖЕНИЕ (Теккущее значение):\n // Если между фазой рендера и выполнением useEffect значение в ядре библиотеки успело измениться,\n // мы синхронно подтягиваем актуальный стейт, исключая \"слепые пятна\" и мерцание UI.\n if (state !== reactiveItem.value) {\n setState(reactiveItem.value);\n }\n\n useEffect(() => {\n // Привязываем подписку ядра к триггеру обновлений React\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue)\n })\n\n return () => {\n unsubscribe();\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Метод .destroy() вызывается СТРОГО если объект был порожден локальной фабрикой.\n // Глобальные сигналы сервисов остаются в безопасности и не стираются из движка!\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy()\n }\n }\n }, [reactiveItem, isFactory])\n\n return state\n}\n"],"mappings":";;AAoDA,IAAa,KAAwB,MAA+B;CAClE,IAAM,IAAY,OAAO,KAAU,YAG7B,IAAa,EAAO,CAAK;CAC/B,QAAgB;EACd,EAAW,UAAU;CACvB,GAAG,CAAC,CAAK,CAAC;CAGV,IAAM,IAAe,QACf,OAAO,KAAU,aACZ,EAAM,IAER,GACN,CAAC,IAAY,KAAA,IAAY,CAAK,CAAC,GAE5B,CAAC,GAAO,KAAY,EAAY,EAAa,KAAK,GAClD,IAAc,EAAO,CAAQ;CAgCnC,OA7BA,QAAgB;EACd,EAAY,UAAU;CACxB,GAAG,CAAC,CAAQ,CAAC,GAKT,MAAU,EAAa,SACzB,EAAS,EAAa,KAAK,GAG7B,QAAgB;EAEd,IAAM,IAAc,EAAa,WAAW,MAAa;GACvD,EAAY,QAAQ,CAAQ;EAC9B,CAAC;EAED,aAAa;GAMX,AALA,EAAY,GAKR,KAAa,KAAgB,OAAO,EAAa,WAAY,cAC/D,EAAa,QAAQ;EAEzB;CACF,GAAG,CAAC,GAAc,CAAS,CAAC,GAErB;AACT"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getExtractedValues.cjs","names":[],"sources":["../../src/utils/getExtractedValues.ts"],"sourcesContent":["export const getExtractedValues = ({\n tested, expectedKey, valueType\n}: {\n tested: string[];\n expectedKey: string;\n valueType: 'number' | 'string';\n}): string[] => {\n const results: string[] = [];\n for (let i = 0, max = tested.length; i < max; i++) {\n const t = tested[i];\n let regex;\n switch (valueType) {\n case 'number':\n regex = new RegExp(`\\\\[${expectedKey}=(?<value>\\\\d+)\\\\]`, 'g');\n break;\n case 'string':\n default:\n // NOTE: \"Ленивый\" квантификатор -> \".*?\" будет искать минимальное количество символов до следующего экранированного \"]\"\n regex = new RegExp(`\\\\[${expectedKey}=(?<value>.*?)\\\\]`, 'g');\n break;\n }\n\n // eslint-disable-next-line no-restricted-syntax\n for (const n of t.matchAll(regex)) {\n if (n?.groups) { results.push(n.groups.value); }\n }\n }\n return results;\n};\n"],"mappings":"AAAA,IAAa,GAAsB,CACjC,SAAQ,cAAa,eAKP,CACd,IAAM,EAAoB,CAAC,EAC3B,IAAK,IAAI,EAAI,EAAG,EAAM,EAAO,OAAQ,EAAI,EAAK,IAAK,CACjD,IAAM,EAAI,EAAO,GACb,EACJ,OAAQ,EAAR,CACE,IAAK,SACH,EAAY,OAAO,MAAM,EAAY,oBAAqB,GAAG,EAC7D,MAEF,QAEE,EAAY,OAAO,MAAM,EAAY,mBAAoB,GAAG,EAC5D,KACJ,CAGA,IAAK,IAAM,KAAK,EAAE,SAAS,CAAK,EAC1B,GAAG,QAAU,EAAQ,KAAK,EAAE,OAAO,KAAK,CAEhD,CACA,OAAO,CACT"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getExtractedValues.mjs","names":[],"sources":["../../src/utils/getExtractedValues.ts"],"sourcesContent":["export const getExtractedValues = ({\n tested, expectedKey, valueType\n}: {\n tested: string[];\n expectedKey: string;\n valueType: 'number' | 'string';\n}): string[] => {\n const results: string[] = [];\n for (let i = 0, max = tested.length; i < max; i++) {\n const t = tested[i];\n let regex;\n switch (valueType) {\n case 'number':\n regex = new RegExp(`\\\\[${expectedKey}=(?<value>\\\\d+)\\\\]`, 'g');\n break;\n case 'string':\n default:\n // NOTE: \"Ленивый\" квантификатор -> \".*?\" будет искать минимальное количество символов до следующего экранированного \"]\"\n regex = new RegExp(`\\\\[${expectedKey}=(?<value>.*?)\\\\]`, 'g');\n break;\n }\n\n // eslint-disable-next-line no-restricted-syntax\n for (const n of t.matchAll(regex)) {\n if (n?.groups) { results.push(n.groups.value); }\n }\n }\n return results;\n};\n"],"mappings":";AAAA,IAAa,KAAsB,EACjC,WAAQ,gBAAa,mBAKP;CACd,IAAM,IAAoB,CAAC;CAC3B,KAAK,IAAI,IAAI,GAAG,IAAM,EAAO,QAAQ,IAAI,GAAK,KAAK;EACjD,IAAM,IAAI,EAAO,IACb;EACJ,QAAQ,GAAR;GACE,KAAK;IACH,IAAY,OAAO,MAAM,EAAY,qBAAqB,GAAG;IAC7D;GAEF;IAEE,IAAY,OAAO,MAAM,EAAY,oBAAoB,GAAG;IAC5D;EACJ;EAGA,KAAK,IAAM,KAAK,EAAE,SAAS,CAAK,GAC9B,AAAI,GAAG,UAAU,EAAQ,KAAK,EAAE,OAAO,KAAK;CAEhD;CACA,OAAO;AACT"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveEngine4Vue.cjs","names":[],"sources":["../../src/vue/ReactiveEngine4Vue.ts"],"sourcesContent":["import { shallowRef, onUnmounted, getCurrentInstance, getCurrentScope, onScopeDispose, triggerRef, type ShallowRef } from 'vue'\nimport { ReactiveEngine as OriginalReactiveEngine, type CleanupFn } from '../core/core'\n\nexport class ReactiveEngine4Vue extends OriginalReactiveEngine {\n /**\n * Использование реактивного значения в Vue компоненте или EffectScope.\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): ShallowRef<T> {\n // Создаем локальную Vue-реактивную обертку со стартовым значением сигнала\n const state = shallowRef(item.value)\n\n // Подписываемся на изменения сигнала ядра\n const unsubscribe = item.subscribe((newValue) => {\n state.value = newValue // Обновляем Vue-реактивную переменную\n triggerRef(state) // 👈 ПРИНУДИТЕЛЬНО заставляем Vue перерисовать шаблон\n })\n\n // Проверяем наличие активного компонента ИЛИ active области видимости эффектов\n\n // 1. Если мы находимся внутри жизненного цикла компонента Vue (setup)\n if (getCurrentInstance()) {\n // Автоматически отписываемся при размонтировании Vue-компонента\n onUnmounted(() => {\n unsubscribe()\n })\n }\n // 2. Иначе, если мы находимся внутри изолированного EffectScope (например, в Pinia или в тестах)\n else if (getCurrentScope()) {\n onScopeDispose(() => {\n unsubscribe()\n })\n }\n\n return state\n }\n}\n"],"mappings":"oDAGA,IAAa,EAAb,cAAwC,EAAA,cAAuB,CAI7D,IAAc,EAAiF,CAE7F,IAAM,GAAA,EAAA,EAAA,WAAA,CAAmB,EAAK,KAAK,EAG7B,EAAc,EAAK,UAAW,GAAa,CAC/C,EAAM,MAAQ,GACd,EAAA,EAAA,WAAA,CAAW,CAAK,CAClB,CAAC,EAkBD,OAbA,EAAA,EAAA,mBAAA,CAAuB,GAErB,EAAA,EAAA,YAAA,KAAkB,CAChB,EAAY,CACd,CAAC,GAGE,EAAA,EAAA,gBAAA,CAAoB,IACvB,EAAA,EAAA,eAAA,KAAqB,CACnB,EAAY,CACd,CAAC,EAGI,CACT,CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveEngine4Vue.mjs","names":[],"sources":["../../src/vue/ReactiveEngine4Vue.ts"],"sourcesContent":["import { shallowRef, onUnmounted, getCurrentInstance, getCurrentScope, onScopeDispose, triggerRef, type ShallowRef } from 'vue'\nimport { ReactiveEngine as OriginalReactiveEngine, type CleanupFn } from '../core/core'\n\nexport class ReactiveEngine4Vue extends OriginalReactiveEngine {\n /**\n * Использование реактивного значения в Vue компоненте или EffectScope.\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): ShallowRef<T> {\n // Создаем локальную Vue-реактивную обертку со стартовым значением сигнала\n const state = shallowRef(item.value)\n\n // Подписываемся на изменения сигнала ядра\n const unsubscribe = item.subscribe((newValue) => {\n state.value = newValue // Обновляем Vue-реактивную переменную\n triggerRef(state) // 👈 ПРИНУДИТЕЛЬНО заставляем Vue перерисовать шаблон\n })\n\n // Проверяем наличие активного компонента ИЛИ active области видимости эффектов\n\n // 1. Если мы находимся внутри жизненного цикла компонента Vue (setup)\n if (getCurrentInstance()) {\n // Автоматически отписываемся при размонтировании Vue-компонента\n onUnmounted(() => {\n unsubscribe()\n })\n }\n // 2. Иначе, если мы находимся внутри изолированного EffectScope (например, в Pinia или в тестах)\n else if (getCurrentScope()) {\n onScopeDispose(() => {\n unsubscribe()\n })\n }\n\n return state\n }\n}\n"],"mappings":";;;AAGA,IAAa,IAAb,cAAwC,EAAuB;CAI7D,IAAc,GAAiF;EAE7F,IAAM,IAAQ,EAAW,EAAK,KAAK,GAG7B,IAAc,EAAK,WAAW,MAAa;GAE/C,AADA,EAAM,QAAQ,GACd,EAAW,CAAK;EAClB,CAAC;EAkBD,OAbI,EAAmB,IAErB,QAAkB;GAChB,EAAY;EACd,CAAC,IAGM,EAAgB,KACvB,QAAqB;GACnB,EAAY;EACd,CAAC,GAGI;CACT;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pravosleva/reactive-engine",
3
- "version": "1.3.1-beta",
3
+ "version": "1.3.2-beta",
4
4
  "engines": {
5
5
  "node": ">=22.20.0"
6
6
  },
@@ -17,32 +17,32 @@
17
17
  "state",
18
18
  "manager"
19
19
  ],
20
- "main": "./dist/cjs/index.cjs",
21
- "module": "./dist/esm/index.mjs",
22
- "types": "./dist/esm/index.d.ts",
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.mjs",
22
+ "types": "./dist/index.d.ts",
23
23
  "unpkg": "./dist/index.iife.js",
24
24
  "jsdelivr": "./dist/index.iife.js",
25
25
  "type": "module",
26
26
  "exports": {
27
27
  ".": {
28
- "types": "./dist/esm/index.d.ts",
29
- "import": "./dist/esm/index.mjs",
30
- "require": "./dist/cjs/index.cjs"
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.mjs",
30
+ "require": "./dist/index.cjs"
31
31
  },
32
32
  "./react": {
33
- "types": "./dist/esm/react/index.d.ts",
34
- "import": "./dist/esm/react/index.mjs",
35
- "require": "./dist/cjs/react/index.cjs"
33
+ "types": "./dist/react/index.d.ts",
34
+ "import": "./dist/react/index.mjs",
35
+ "require": "./dist/react/index.cjs"
36
36
  },
37
37
  "./vue": {
38
- "types": "./dist/esm/vue/index.d.ts",
39
- "import": "./dist/esm/vue/index.mjs",
40
- "require": "./dist/cjs/vue/index.cjs"
38
+ "types": "./dist/vue/index.d.ts",
39
+ "import": "./dist/vue/index.mjs",
40
+ "require": "./dist/vue/index.cjs"
41
41
  },
42
42
  "./angular": {
43
- "types": "./dist/esm/angular/index.d.ts",
44
- "import": "./dist/esm/angular/index.mjs",
45
- "require": "./dist/cjs/angular/index.cjs"
43
+ "types": "./dist/angular/index.d.ts",
44
+ "import": "./dist/angular/index.mjs",
45
+ "require": "./dist/angular/index.cjs"
46
46
  }
47
47
  },
48
48
  "files": [
@@ -1 +0,0 @@
1
- {"version":3,"file":"AbstractService.cjs","names":[],"sources":["../../src/core/AbstractService.ts"],"sourcesContent":["import { ReactiveEngine } from './core';\n\nexport abstract class AbstractService {\n constructor(protected engine: ReactiveEngine) {\n // Вызываем метод инициализации, который можно переопределить в наследниках\n this.onInit();\n }\n\n // NOTE: Полезные хелперы для сокращения кода\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 protected createComputed<T>(fn: () => T) {\n return this.engine.computed(fn);\n }\n\n protected createSignal<T>(val: T, name?: string) {\n return this.engine.signal(val, name);\n }\n\n // NOTE: Жизненный цикл: вызывается сразу после создания сервиса.\n // Удобно для инициализации сигналов, которые зависят от engine.\n protected onInit(): void { }\n}\n\nexport const BaseREService = AbstractService\n"],"mappings":"AAEA,IAAsB,EAAtB,KAAsC,CACd,OAAtB,YAAY,EAAkC,CAAxB,KAAA,OAAA,EAEpB,KAAK,OAAO,CACd,CAGA,oBAA8B,EAAa,EAAc,CACvD,OAAO,KAAK,OAAO,OAAO,EAAK,CAC7B,OACA,SAAW,GAAM,OAAO,GAAM,UAAY,CAAC,MAAM,CAAC,CACpD,CAAC,CACH,CAEA,eAA4B,EAAa,CACvC,OAAO,KAAK,OAAO,SAAS,CAAE,CAChC,CAEA,aAA0B,EAAQ,EAAe,CAC/C,OAAO,KAAK,OAAO,OAAO,EAAK,CAAI,CACrC,CAIA,QAAyB,CAAE,CAC7B,EAEa,EAAgB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"BaseREServiceEnhanced.cjs","names":[],"sources":["../../src/core/BaseREServiceEnhanced.ts"],"sourcesContent":["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"],"mappings":"AAEA,IAAsB,EAAtB,KAA4C,CACpB,OAAtB,YAAY,EAAkC,CAAxB,KAAA,OAAA,EACpB,eAAiB,KAAK,OAAO,EAAG,CAAC,CACnC,CAEA,QAAyB,CAAE,CAC7B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ReactiveEngine4Angular.cjs","names":[],"sources":["../../../src/angular/ReactiveEngine4Angular.ts"],"sourcesContent":["import { signal, inject, DestroyRef, Injector, runInInjectionContext, type Signal } from '@angular/core';\nimport { ReactiveEngine as OriginalReactiveEngine, type CleanupFn } from '../core/core';\n\n// Описываем интерфейс опций для Angular-адаптера\nexport interface AngularUseOptions {\n /**\n * Кастомный инжектор Angular.\n * Позволяет использовать .use() вне конструктора (например, в асинхронных функциях),\n * если передать текущий Injector вручную.\n */\n injector?: Injector;\n}\n\nexport class ReactiveEngine4Angular extends OriginalReactiveEngine {\n /**\n * Использование реактивного значения в Angular компоненте или сервисе.\n * Возвращает стандартный Angular Signal.\n */\n public use<T>(\n item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn },\n options?: AngularUseOptions\n ): Signal<T> {\n // 1. Создаем локальный Angular Signal со стартовым значением\n const angularSignal = signal<T>(item.value);\n\n // 2. Подписываемся на изменения сигнала вашего ядра\n const unsubscribe = item.subscribe((newValue) => {\n angularSignal.set(newValue); // Обновляем значение внутри Angular\n });\n\n // 3. Логика автоматической отписки через DestroyRef\n const registerCleanup = () => {\n const destroyRef = inject(DestroyRef);\n destroyRef.onDestroy(() => {\n unsubscribe();\n });\n };\n\n // 4. Если пользователь явно передал кастомный инжектор — выполняем регистрацию внутри его контекста\n if (options?.injector) {\n runInInjectionContext(options.injector, registerCleanup);\n } else {\n // Иначе штатно выполняем в текущем контексте (вызовет ошибку NG0203, если контекст не валиден)\n registerCleanup();\n }\n\n return angularSignal.asReadonly();\n }\n}\n"],"mappings":"8DAaA,IAAa,EAAb,cAA4C,EAAA,cAAuB,CAKjE,IACE,EACA,EACW,CAEX,IAAM,GAAA,EAAA,EAAA,OAAA,CAA0B,EAAK,KAAK,EAGpC,EAAc,EAAK,UAAW,GAAa,CAC/C,EAAc,IAAI,CAAQ,CAC5B,CAAC,EAGK,MAAwB,EAE5B,EAAA,EAAA,OAAA,CAD0B,EAAA,UAC1B,CAAA,CAAW,cAAgB,CACzB,EAAY,CACd,CAAC,CACH,EAUA,OAPI,GAAS,UACX,EAAA,EAAA,sBAAA,CAAsB,EAAQ,SAAU,CAAe,EAGvD,EAAgB,EAGX,EAAc,WAAW,CAClC,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"core.cjs","names":[],"sources":["../../src/core/core.ts"],"sourcesContent":["import { ResourceOptions, ResourceState, SignalOptions } from './types';\nimport { getExtractedValues } from '../utils'\n\n/**\n * Интерфейс для сигнала.\n * @template T\n * @interface Signal<T>\n */\nexport interface Signal<T> {\n /**\n * Значение сигнала.\n * @type {T}\n */\n value: T;\n\n /**\n * Подписка на изменение значения сигнала.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n}\n\nexport type CleanupFn = () => void;\nexport type EffectFn = () => CleanupFn | void;\nexport type Token<T> = string | symbol | { new(engine: ReactiveEngine, ...args: any[]): T };\nexport type Factory<T> = (engine: ReactiveEngine) => T;\n\n/**\n * Интерфейс для вычисляемого значения.\n * @template T\n * @interface Computed<T>\n */\nexport interface Computed<T> {\n /**\n * Только читаемое значение вычисляемого значения.\n * @type {T}\n */\n readonly value: T;\n\n /**\n * Подписка на изменение вычисляемого значения.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: T) => void) => CleanupFn;\n\n /**\n * Принудительное уничтожение вычисляемого значения и его эффекта для предотвращения утечек памяти.\n */\n destroy: () => void;\n}\n\n/**\n * Интерфейс для эффекта.\n * @interface IEffect\n */\nexport interface IEffect {\n run: () => void;\n label?: string;\n cleanups: Set<CleanupFn>;\n}\n\n/**\n * Интерфейс для ресурса.\n * @template T\n * @interface Resource<T>\n */\nexport interface Resource<T> extends ResourceState<T> {\n /**\n * Перезагрузка ресурса.\n * @function refetch\n * @returns {void}\n */\n refetch: () => void;\n\n /**\n * Подписка на изменение состояния ресурса.\n * @function subscribe\n * @param {Function} cb - Коллбек функция для обработки изменения.\n * @returns {CleanupFn} - Функция для очистки подписки.\n */\n subscribe: (cb: (val: ResourceState<T>) => void) => CleanupFn;\n\n /**\n * Только читаемое состояние ресурса.\n * @type {ResourceState<T>}\n */\n readonly value: ResourceState<T>;\n}\n\n// --- ЯДРО ---\n/**\n * Класс `ReactiveEngine` представляет собой реактивную систему, которая позволяет создавать и управлять реактивными объектами,\n * сигналами, эффектами, асинхронными ресурсами и другими реактивными примитивами. Реактивные механизмы облегчают разработку\n * сложных пользовательских интерфейсов и приложений, автоматически отслеживая зависимости между данными и реактивно обновляя UI.\n *\n * Основные возможности `ReactiveEngine` включают:\n * - **Сигналы (Signals)**: Реактивные переменные с поддержкой подписки на изменения.\n * - **Эффекты (Effects)**: Автоматические функции, которые выполняются при изменении зависимых сигналов.\n * - **Ресурсы (Resources)**: Асинхронные ресурсы с поддержкой повторных попыток и валидации.\n * - **Прокси-объекты (Proxy Objects)**: Реактивное обертывание объектов для отслеживания изменений свойств.\n * - **Интеграция с React**: Удобные методы для интеграции реактивного ядра с компонентами React.\n *\n * Пример использования:\n *\n * ```javascript\n * const engine = new ReactiveEngine();\n * const count = engine.signal(0);\n *\n * engine.effect(() => {\n * console.log(`Count is ${count.value}`);\n * });\n *\n * count.value++; // Выведет: Count is 1\n * ```\n *\n * @class\n */\nexport class ReactiveEngine {\n private activeEffect: IEffect | null = null;\n private isBatching = false;\n private pendingEffects = new Set<IEffect>();\n\n // В контейнерах DI вместо any используем unknown. Это заставит методы inject/provide\n // явно приводить типы через дженерики <T>, защищая от рантайм-ошибок.\n private services = new Map<Token<unknown>, unknown>();\n private factories = new Map<Token<unknown>, Factory<unknown>>();\n\n // Объект-ключ мапится на объект-прокси. Здесь идеально подходит тип object.\n private proxyCache = new WeakMap<object, object>();\n private allEffects = new Set<IEffect>();\n\n /**\n * Коллбек для уведомления об изменении сигнала.\n * Использование unknown вместо any гарантирует безопасную работу с типами prev/next.\n */\n public onSignalChange?: (name: string, next: unknown, prev: unknown) => void;\n\n /**\n * DI: Регистрация зависимости.\n * @template T\n * @function provide\n * @param {Token<T>} token - Токен для зависимости.\n * @param {T | Factory<T>} valueOrFactory - Значение или фабрика для создания сервиса.\n * @returns {void}\n * @source\n */\n public provide<T>(token: Token<T>, valueOrFactory: T | Factory<T>): void {\n if (typeof valueOrFactory === 'function' && !valueOrFactory.prototype) {\n this.factories.set(token, valueOrFactory as Factory<T>);\n } else {\n this.services.set(token, valueOrFactory);\n }\n }\n\n /**\n * DI: Инъекция; Получение сервиса по токену.\n * @template T\n * @function inject\n * @param {Token<T>} token - Токен для зависимости.\n * @returns {T} - Сервис.\n * @source\n */\n public inject<T>(token: Token<T>): T {\n if (!token) {\n throw new Error(`[DI Error]: Вы пытаетесь внедрить пустой токен (undefined/null). Проверьте импорты.`);\n }\n\n // Приводим токен к базовому типу Token<unknown> для совместимости с Map\n const targetToken = token as Token<unknown>;\n\n if (this.services.has(targetToken)) {\n return this.services.get(targetToken) as T;\n }\n\n try {\n const factory = this.factories.get(targetToken);\n if (factory) {\n const instance = factory(this) as T;\n this.services.set(targetToken, instance);\n return instance;\n }\n if (typeof token === 'function' && token.prototype) {\n const instance = new (token as { new(eng: ReactiveEngine): T })(this);\n this.services.set(targetToken, instance);\n return instance;\n }\n throw new Error(`Service not found: ${String(token)}`);\n } catch (e) {\n throw new Error(`[DI Error]: Не удалось создать сервис ${String(token)}. Ошибка: ${(e as Error)?.message || 'No e?.message'}`);\n }\n }\n\n /**\n * Создание сигнала.\n * @template T\n * @function signal\n * @param {T} initialValue - Начальное значение сигнала.\n * @param {string | SignalOptions<T>} [optionsOrName] - Имя или опции сигнала.\n * @returns {Signal<T>} - Сигнал.\n * @source\n */\n public signal<T>(initialValue: T, optionsOrName?: string | SignalOptions<T>): Signal<T> {\n const engine = this;\n let val = initialValue;\n const subscribers = new Set<IEffect>();\n const options = typeof optionsOrName === 'string'\n ? { name: optionsOrName }\n : optionsOrName || {};\n const name = options.name || 'unnamed_signal';\n\n return {\n get value(): T {\n if (engine.activeEffect) subscribers.add(engine.activeEffect);\n return val;\n },\n set value(newValue: T) {\n if (val === newValue) return;\n\n // ВАЛИДАЦИЯ В RUNTIME\n if (options.validate) {\n const result = options.validate(newValue);\n if (result === false || typeof result === 'string') {\n const errorMsg = typeof result === 'string'\n ? result\n : `[Validation Error]: Некорректное значение для сигнала \"${name}\"`;\n\n console.error(`%c${errorMsg}`, \"color: orange; font-weight: bold;\", {\n received: newValue,\n current: val\n });\n return; // Прерываем обновление, если данные не валидны\n }\n }\n\n const old = val;\n val = newValue;\n engine.onSignalChange?.(name, newValue, old);\n\n // 1. Всегда добавляем подписчиков в очередь отложенных эффектов\n subscribers.forEach(e => engine.pendingEffects.add(e));\n\n // 2. Если очередь еще не запущена, планируем её автоматическое выполнение в микрозадачу\n if (!engine.isBatching) {\n engine.isBatching = true;\n\n queueMicrotask(() => {\n const effects = Array.from(engine.pendingEffects);\n engine.pendingEffects.clear();\n engine.isBatching = false;\n\n // Запускаем все накопившиеся эффекты и ререндеры за один раз\n effects.forEach(e => e.run());\n });\n }\n },\n subscribe(cb: (val: T) => void) {\n // Чистая и безопасная подписка для React:\n // Мы вызываем cb, но НЕ передаем туда аргументы, чтобы не ломать useSyncExternalStore\n return engine.effect(() => {\n cb(this.value);\n });\n }\n };\n }\n\n /**\n * Создание эффекта.\n * @function effect\n * @param {EffectFn} fn - Функция эффекта.\n * @param {string} [label] - Необязательная метка для логирования и отладки.\n * @returns {CleanupFn} - Функция для очистки эффекта.\n * @source\n */\n public effect(fn: EffectFn, label?: string): CleanupFn {\n const engine = this;\n const effectObj: IEffect = {\n label, // Запоминаем имя эффекта для профайлера/логов\n cleanups: new Set(),\n run() {\n this.cleanups.forEach(c => c());\n this.cleanups.clear();\n const prev = engine.activeEffect;\n engine.activeEffect = this;\n engine.safeRun(this, () => {\n const cleanup = fn();\n if (typeof cleanup === 'function') this.cleanups.add(cleanup);\n });\n engine.activeEffect = prev;\n }\n };\n this.allEffects.add(effectObj);\n effectObj.run();\n\n return () => {\n effectObj.cleanups.forEach(c => c());\n engine.pendingEffects.delete(effectObj);\n engine.allEffects.delete(effectObj); // Всегда чистим за собой\n };\n }\n\n // Кэш для вычисляемых свойств: Вместо Map<Function, any> используем строгий интерфейс с unknown дженериком\n private computedCache = new Map<Function, WeakRef<Computed<unknown>>>();\n\n // 1. Создаем реестр финализации под капотом движка.\n // В качестве токена очистки передаем функцию, которую нужно выполнить.\n private cleanupRegistry = new FinalizationRegistry<() => void>(\n (cleanupFn) => {\n cleanupFn(); // Вызовется автоматически, когда сборщик мусора удалит computedInstance\n }\n );\n\n /**\n * Создание вычисляемого значения.\n * @template T\n * @function computed\n * @param {Function} fn - Функция для вычисления значения.\n * @param {string} [signalName] - Имя сигнала.\n * @returns {Computed<T>} - Вычисляемое значение с методом destroy.\n * @source\n */\n public computed<T>(fn: () => T, signalName?: string): Computed<T> {\n // 1. Проверяем кэш. Если WeakRef существует и объект внутри него еще не удален GC:\n if (this.computedCache.has(fn)) {\n const cachedRef = this.computedCache.get(fn);\n const cachedInstance = cachedRef?.deref();\n if (cachedInstance) {\n return cachedInstance as Computed<T>;\n }\n }\n\n const engine = this;\n const sig = this.signal<T>(undefined as unknown as T, signalName || 'unnamed_computed');\n\n const unsubscribeEffect = this.effect(() => {\n const newValue = fn();\n sig.value = newValue;\n });\n\n const effectObj = Array.from(this.allEffects)[this.allEffects.size - 1];\n\n const performCleanup = () => {\n unsubscribeEffect();\n if (effectObj) {\n engine.allEffects.delete(effectObj);\n }\n engine.computedCache.delete(fn);\n };\n\n // Создаем инстанс computed\n const computedInstance: Computed<T> = {\n get value() { return sig.value; },\n subscribe: (cb: (val: T) => void) => sig.subscribe(cb),\n destroy() {\n performCleanup();\n }\n };\n\n this.cleanupRegistry.register(computedInstance, performCleanup);\n\n // 2. Сохраняем в кэш СЛАБУЮ ССЫЛКУ (WeakRef) на наш объект.\n // Теперь этот кэш больше НЕ будет удерживать объект в памяти насильно!\n this.computedCache.set(fn, new WeakRef(computedInstance as Computed<unknown>));\n\n return computedInstance;\n }\n\n /**\n * Создание реактивного объекта (Proxy).\n * @template T\n * @function reactive\n * @param {T} target - Целевой объект для проксирования.\n * @param {string} [name] - Имя проксируемого объекта.\n * @returns {T} - Реактивный объект.\n * @source\n */\n public reactive<T extends object>(target: T, name: string = 'reactive'): T {\n if (this.proxyCache.has(target)) {\n return this.proxyCache.get(target) as T;\n }\n const engine = this;\n const propsSubscribers = new Map<string | symbol, Set<IEffect>>();\n\n const proxy = new Proxy(target, {\n get(obj, prop, receiver) {\n if (engine.activeEffect) {\n if (!propsSubscribers.has(prop)) propsSubscribers.set(prop, new Set());\n propsSubscribers.get(prop)!.add(engine.activeEffect);\n }\n const value = Reflect.get(obj, prop, receiver);\n return (value !== null && typeof value === 'object')\n ? engine.reactive(value, `${name}.${String(prop)}`)\n : value;\n },\n set(obj, prop, value, receiver) {\n const old = Reflect.get(obj, prop, receiver);\n // ПРОВЕРКА НА ИДЕНТИЧНОСТЬ\n if (old === value) return true;\n\n if (old !== value) {\n Reflect.set(obj, prop, value, receiver);\n engine.onSignalChange?.(`${name}.${String(prop)}`, value, old);\n propsSubscribers.get(prop)?.forEach(e =>\n engine.isBatching ? engine.pendingEffects.add(e) : e.run()\n );\n }\n return true;\n }\n });\n this.proxyCache.set(target, proxy);\n return proxy;\n }\n\n /**\n * Группировка изменений. (Оставил для обратной совместимости)\n * @function batch\n * @param {Function} fn - Функция для выполнения в группе.\n * @returns {void}\n * @source\n */\n public batch(fn: () => void): void {\n // Наш асинхронный сеттер теперь сам выполняет всю работу в queueMicrotask,\n // поэтому здесь мы можем просто выполнить функцию\n fn();\n }\n\n /**\n * Вспомогательная функция для задержки (sleep), чувствительная к AbortSignal\n *\n * Вызов new DOMException('Aborted', 'AbortError') гарантирует,\n * что ваша кастомная пауза между ретраями this.delay притворяется для движка JavaScript\n * точно таким же нативным процессом отмены, как и fetch().\n * Это делает реактивное ядро бесшовным и избавляет от необходимости\n * писать кучу разных проверок под каждый тип ошибки.\n */\n private delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n return reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeoutId = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n const onAbort = () => {\n clearTimeout(timeoutId); // СИНХРОННО УБИВАЕТ ТАЙМЕР. В Vitest это заставит таймер исчезнуть из очереди прокрутки\n reject(new DOMException('Aborted', 'AbortError'));\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n }\n\n /**\n * Создание асинхронного ресурса. Типы на входе: <T - формат ответа, S - источник изменений (сигнал либо computed-кортеж из зачений сигналов через конструкцию `.value`)>\n *\n * Explained 👉 {@link https://github.com/garage-13/reactive-engine/blob/main/README_EN.md#1-async-resources-dependent-on-multiple-signals}\n *\n * @template T, S\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/200-resource/Example200.tsx Реализация базового компонента Example200}\n * @see {@link https://github.com/garage-13/reactive-engine/blob/main/examples/201-multi-resource/service.secondary.ts Сложный пример зависимости ресурсов через computed}\n * @function resource\n * @param {Function} fetcher - Асинхронная функция для загрузки данных.\n * @param {{ value: S }} [source] - Источник данных.\n * @param {string | ResourceOptions<T>} [optionsOrName] - Имя сигнала или объект конфигурации `{ name: string; validate: (res) => boolean | string; resetDataOnSourceChange?: boolean }`.\n * @returns {Resource<T>} - Асинхронный ресурс.\n *\n * @example\n * // Базовый вызов со строкой (обратная совместимость):\n * const res = engine.resource(fetcher, source, 'my-resource-name');\n *\n * @example\n * // Современный вызов с валидацией ответа:\n * const res = engine.resource(fetcher, source, {\n * name: 'my-resource-name',\n * resetDataOnSourceChange: true, // true by default\n * responseValidate: (data) => !!data || 'Данные пусты', // Проверяйте формат в соотв. с дженериком\n * });\n *\n * @example\n * // (Экспериментальная фича)\n * // В теле fetcher можно выкинуть ошибку в виде\n * // `throw new Error('[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1][MESSAGE=Your msg]')`\n * // Причина: Особенности внутренней реализации определения харакрера ошибки и необходимость делать retry (если такая опция передана)\n * // Рекомендуем вместо этого использовать `validateBeforeFetch` в опциях при создании ресурса.\n * const res = engine.resource(\n * async (counterValue, abortSignal) => {\n * if (counterValue === 0)\n * throw new Error([\n * '[THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY=1]', // Иначе запустится механизм retry (напр. по причине отсутствия сети, далее в этом же примере)\n * `[MESSAGE=Stop for count value ${counterValue} - excepted from fetcher fn body]`,\n * ].join(' '))\n * const res = await fetch(\n * [\n * `${BASE_API_URL}/profile/search`,\n * '?',\n * [\n * `counter=${counterValue}`,\n * '_responseDelay=2000',\n * ].join('&')\n * ].join(''),\n * { signal: abortSignal }\n * )\n * if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)\n * return res.json()\n * },\n * this.counter,\n * )\n * @source\n */\n public resource<T, S = void>(\n fetcher: (source: S, signal: AbortSignal) => Promise<T>,\n source?: { value: S },\n optionsOrName?: string | ResourceOptions<T, S>,\n ): Resource<T> {\n const isOptionsObject = optionsOrName && typeof optionsOrName === 'object';\n const signalName = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>).name\n : (optionsOrName as string) || 'unnamed_resource';\n\n const options: Partial<ResourceOptions<T, S>> = isOptionsObject\n ? (optionsOrName as ResourceOptions<T, S>)\n : {};\n\n const resetDataOnSourceChange = options.resetDataOnSourceChange ?? true;\n const responseValidate = options.responseValidate;\n const validateBeforeFetch = options.validateBeforeFetch;\n\n const retryCount = options.retryCount ?? 0;\n const baseDelay = options.retryDelay ?? 1000;\n const useExponential = options.isExponentialBackoffEnabled ?? false;\n const maxRetryDelay = options.maxRetryDelay ?? 30000;\n const timeoutMs = options.timeout;\n\n const state = this.signal<ResourceState<T>>(\n { data: null, loading: true, error: null, isRetrying: false },\n signalName\n );\n\n const load = async (sValue: S, effectSignal: AbortSignal, isSourceChange = false) => {\n if (effectSignal.aborted) return;\n\n if (validateBeforeFetch) {\n const preValidationResult = validateBeforeFetch(sValue);\n if (preValidationResult === false || typeof preValidationResult === 'string') {\n if (effectSignal.aborted) return;\n const errorMsg = typeof preValidationResult === 'string'\n ? preValidationResult\n : 'Pre-fetch validation failed for resource';\n\n // При ручном refetch (не смена source) сохраняем старые данные, а не зануляем\n const currentData = isSourceChange ? null : this.untrack(() => state.value.data);\n state.value = { data: currentData, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n const shouldClear = isSourceChange && resetDataOnSourceChange;\n const currentData = shouldClear ? null : this.untrack(() => state.value.data);\n\n state.value = { data: currentData, loading: true, error: null, isRetrying: false };\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (effectSignal.aborted) return;\n let combinedSignal = effectSignal;\n\n try {\n if (timeoutMs && timeoutMs > 0) {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n combinedSignal = AbortSignal.any([effectSignal, timeoutSignal]);\n }\n\n const data = await fetcher(sValue, combinedSignal);\n\n if (effectSignal.aborted) return;\n\n if (combinedSignal.aborted) {\n throw new DOMException('The operation timed out.', 'TimeoutError');\n }\n\n if (responseValidate) {\n const validationResult = responseValidate(data);\n if (validationResult === false || typeof validationResult === 'string') {\n if (effectSignal.aborted) return;\n const errorMsg = typeof validationResult === 'string' ? validationResult : 'Validation failed';\n state.value = { data: null, loading: false, error: new Error(errorMsg), isRetrying: false };\n return;\n }\n }\n\n if (effectSignal.aborted) return;\n\n state.value = { data, loading: false, error: null, isRetrying: false };\n return;\n } catch (e: any) {\n // ЖЕСТКИЙ ВЫХОД: Если внешняя область (эффект или новый refetch) сделала abort,\n // мы НЕМЕДЛЕННО прекращаем выполнение и ничего не пишем в стейт.\n if (effectSignal.aborted || (e.name === 'AbortError' && effectSignal.aborted)) return;\n\n const isTimeout = e.name === 'TimeoutError' || e.name === 'AbortError' || e.message?.includes('timeout');\n\n const isCustomValidationError = getExtractedValues({\n tested: [e.message], expectedKey: 'THROW_CUSTOM_VALIDATION_ERROR_NO_RETRY', valueType: 'number',\n })?.[0] === '1' || false\n\n const __defaultCustomValidationErrorMessage = 'Custom validation error';\n let customValidationErrorMessage: string = isCustomValidationError\n ? (getExtractedValues({\n tested: [e.message], expectedKey: 'MESSAGE', valueType: 'string',\n })?.[0] || __defaultCustomValidationErrorMessage)\n : __defaultCustomValidationErrorMessage;\n\n const isFetchBodyValidationError = isCustomValidationError || e.name === 'ValidationError';\n\n if (attempt === retryCount || isFetchBodyValidationError) {\n if (effectSignal.aborted) return;\n\n const finalError = isTimeout\n ? new Error(`Request timed out after ${timeoutMs}ms`, { cause: e })\n : isCustomValidationError\n ? new Error(customValidationErrorMessage, { cause: e })\n : e;\n\n state.value = { data: null, loading: false, error: finalError, isRetrying: false };\n return;\n } else {\n if (effectSignal.aborted) return;\n\n let currentDelay = useExponential ? baseDelay * Math.pow(2, attempt) : baseDelay;\n currentDelay = Math.min(currentDelay, maxRetryDelay);\n const jitter = Math.random() * 200;\n currentDelay = currentDelay + jitter;\n\n // Безопасно обновляем статус ретрая, только если сигнал живой\n state.value = { data: state.value.data, loading: true, error: null, isRetrying: true };\n\n const logReason = isTimeout ? 'таймауту' : 'ошибке сети';\n console.warn(`[Resource Retry] \"${signalName}\" сбой по ${logReason}. Попытка ${attempt + 1}/${retryCount + 1}...`);\n\n try {\n await this.delay(currentDelay, effectSignal);\n if (effectSignal.aborted) return;\n } catch (delayError) {\n return;\n }\n }\n }\n }\n };\n\n let activeEffectController: AbortController | null = null;\n\n this.effect(() => {\n const sValue = source ? source.value : undefined as S;\n const effectController = new AbortController();\n activeEffectController = effectController;\n\n this.untrack(() => {\n load(sValue as S, effectController.signal, true);\n });\n\n return () => effectController.abort();\n });\n\n return {\n get data() { return state.value.data; },\n get loading() { return state.value.loading; },\n get error() { return state.value.error; },\n get isRetrying() { return state.value.isRetrying; },\n get value() { return state.value; },\n refetch: () => {\n activeEffectController?.abort();\n activeEffectController = new AbortController();\n load(source ? source.value : undefined as S, activeEffectController.signal, false);\n },\n subscribe: (cb: (val: ResourceState<T>) => void) => state.subscribe(cb)\n };\n }\n\n /**\n * Выполняет функцию без отслеживания зависимостей.\n * @template T\n * @function untrack\n * @param {Function} fn - Функция для выполнения.\n * @returns {T} - Результат выполнения функции.\n * @source\n */\n public untrack<T>(fn: () => T): T {\n const prev = this.activeEffect;\n this.activeEffect = null; // Временно \"забываем\" про активный эффект\n try {\n return fn();\n } finally {\n this.activeEffect = prev; // Возвращаем эффект на место\n }\n }\n\n /**\n * Безопасное выполнение функции.\n * @private\n * @function safeRun\n * @param {IEffect} effect - Эффект.\n * @param {Function} fn - Функция для выполнения.\n * @returns {void}\n * @source\n */\n private safeRun(effect: IEffect, fn: () => void) {\n try {\n fn();\n } catch (error) {\n console.error(\n `%c[Reactive Error] %cОшибка в эффекте/computed:`,\n \"color: white; background: red; padding: 2px 4px; border-radius: 3px;\",\n \"font-weight: bold;\",\n error\n );\n // Здесь можно отправить ошибку в Sentry или другой сервис мониторинга\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ReactiveEngine4React.cjs","names":[],"sources":["../../../src/react/ReactiveEngine4React.ts"],"sourcesContent":["import { useState as useStateFromReact, useEffect as useEffectFromReact } from 'react'\nimport { ReactiveEngine as OriginalReactiveEngine, CleanupFn } from '../core/core'\n\n/**\n * Класс адаптера для интеграции реактивного движка с React.\n * Наследует функциональность оригинального реактивного движка и предоставляет специализированные методы для работы с React.\n */\nexport class ReactiveEngine4React extends OriginalReactiveEngine {\n // Описываем строгие типы для адаптеров React, чтобы не ломать встроенные типы React.\n private reactAdapters: {\n useState: typeof useStateFromReact;\n useEffect: typeof useEffectFromReact;\n } = {\n useState: useStateFromReact,\n useEffect: useEffectFromReact,\n };\n\n /**\n * Установка адаптеров React.\n * @function setReactAdapters\n * @param {Function} useState - Функция useState из React.\n * @param {Function} useEffect - Функция useEffect из React.\n * @returns {void}\n * @source\n */\n public setReactAdapters(\n useState: typeof useStateFromReact,\n useEffect: typeof useEffectFromReact\n ): void {\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 * @source\n */\n public use<T>(item: { value: T; subscribe: (cb: (v: T) => void) => CleanupFn }): T {\n if (!this.reactAdapters) {\n throw new Error(\"[React Error]: Адаптеры React не установлены. Вызовите engine.setReactAdapters(useState, useEffect).\");\n }\n\n // NOTE: (защита) проверяем, что нам передали объект сигнала\n if (!item || typeof item.subscribe !== 'function') {\n const errorMsg = `\n [Reactive Error]: engine.use() получил некорректный объект!\n Скорее всего, вы пытаетесь подписаться на свойство сервиса, которое не было инициализировано.\n Проверьте, что в классе написано: public mySignal = this.engine.signal(...)\n `;\n console.error(errorMsg, { item });\n throw new Error(errorMsg);\n }\n const [val, setVal] = this.reactAdapters.useState(item.value);\n\n this.reactAdapters.useEffect(\n () => {\n return item.subscribe(setVal);\n },\n [item]\n );\n\n return val;\n }\n}\n"],"mappings":"sDAOA,IAAa,EAAb,cAA0C,EAAA,cAAuB,CAE/D,cAGI,CACA,SAAU,EAAA,SACV,UAAW,EAAA,SACb,EAUF,iBACE,EACA,EACM,CACN,KAAK,cAAgB,CAAE,WAAU,WAAU,CAC7C,CAUA,IAAc,EAAqE,CACjF,GAAI,CAAC,KAAK,cACR,MAAU,MAAM,sGAAsG,EAIxH,GAAI,CAAC,GAAQ,OAAO,EAAK,WAAc,WAAY,CACjD,IAAM,EAAW;;;;QAMjB,MADA,QAAQ,MAAM,EAAU,CAAE,MAAK,CAAC,EACtB,MAAM,CAAQ,CAC1B,CACA,GAAM,CAAC,EAAK,GAAU,KAAK,cAAc,SAAS,EAAK,KAAK,EAS5D,OAPA,KAAK,cAAc,cAER,EAAK,UAAU,CAAM,EAE9B,CAAC,CAAI,CACP,EAEO,CACT,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useReactiveSubscription.cjs","names":[],"sources":["../../../../../src/react/hooks/useReactiveSubscription/useReactiveSubscription.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { Signal, CleanupFn } from '../../../core'\n\n/**\n * Универсальный хук для подписки на изменения Signal, Computed или Resource.\n * @template T Тип данных внутри реактивного контейнера\n * @param {Signal<T> | { subscribe: (cb: (val: T) => void) => CleanupFn }} signal Объект подписки\n * @param {(val: T) => void} callback Функция обратного вызова, принимающая новое значение\n * @source\n */\nexport const useReactiveSubscription = <T>(\n signal: Pick<Signal<T>, 'subscribe'>,\n callback: (val: T) => void\n): void => {\n // Сохраняем актуальный callback в ref, чтобы не перезапускать эффект при его изменении\n const callbackRef = useRef(callback)\n\n useEffect(() => {\n callbackRef.current = callback\n }, [callback])\n\n useEffect(() => {\n // Подписываемся один раз при изменении самого signal\n const unsubscribe: CleanupFn = signal.subscribe((val) => {\n callbackRef.current(val)\n })\n\n // Возвращаем функцию очистки для автоматической отписки при размонтировании\n return unsubscribe\n }, [signal])\n}\n"],"mappings":"uBAUA,IAAa,GACX,EACA,IACS,CAET,IAAM,GAAA,EAAA,EAAA,OAAA,CAAqB,CAAQ,GAEnC,EAAA,EAAA,UAAA,KAAgB,CACd,EAAY,QAAU,CACxB,EAAG,CAAC,CAAQ,CAAC,GAEb,EAAA,EAAA,UAAA,KAEiC,EAAO,UAAW,GAAQ,CACvD,EAAY,QAAQ,CAAG,CACzB,CAGO,EACN,CAAC,CAAM,CAAC,CACb"}