@context-action/react
Advanced tools
| const require_error_handling = require('./error-handling-ibCwx4ki.cjs'); | ||
| let react = require("react"); | ||
| react = require_error_handling.__toESM(react); | ||
| let react_jsx_runtime = require("react/jsx-runtime"); | ||
| react_jsx_runtime = require_error_handling.__toESM(react_jsx_runtime); | ||
| let __context_action_core = require("@context-action/core"); | ||
| __context_action_core = require_error_handling.__toESM(__context_action_core); | ||
| //#region src/stores/core/StoreRegistry.ts | ||
| /** | ||
| * Centralized store registry for managing multiple Store instances | ||
| */ | ||
| var StoreRegistry = class { | ||
| constructor(name = "default") { | ||
| this.stores = /* @__PURE__ */ new Map(); | ||
| this.metadata = /* @__PURE__ */ new WeakMap(); | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._snapshot = []; | ||
| this.name = name; | ||
| } | ||
| /** | ||
| * Subscribe to registry changes for reactive updates | ||
| */ | ||
| subscribe(listener) { | ||
| this.listeners.add(listener); | ||
| return () => { | ||
| this.listeners.delete(listener); | ||
| }; | ||
| } | ||
| /** | ||
| * Register a new store in the registry | ||
| */ | ||
| register(name, store, metadata) { | ||
| if (this.stores.has(name)) { | ||
| if (this.stores.get(name)) {} | ||
| } | ||
| this.stores.set(name, store); | ||
| if (metadata) this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name, | ||
| ...metadata | ||
| }); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Unregister a store from the registry | ||
| */ | ||
| unregister(name) { | ||
| if (!this.stores.get(name)) return false; | ||
| this.stores.delete(name); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get a store by name | ||
| */ | ||
| getStore(name) { | ||
| return this.stores.get(name); | ||
| } | ||
| /** | ||
| * Check if a store exists | ||
| */ | ||
| hasStore(name) { | ||
| return this.stores.has(name); | ||
| } | ||
| /** | ||
| * Get all registered store names | ||
| */ | ||
| getStoreNames() { | ||
| return Array.from(this.stores.keys()); | ||
| } | ||
| /** | ||
| * Get all stores as entries | ||
| */ | ||
| getAllStores() { | ||
| return new Map(this.stores); | ||
| } | ||
| /** | ||
| * Get store metadata | ||
| */ | ||
| getStoreMetadata(nameOrStore) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| return store ? this.metadata.get(store) : void 0; | ||
| } | ||
| /** | ||
| * Update store metadata | ||
| */ | ||
| updateStoreMetadata(nameOrStore, metadata) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| if (!store) return false; | ||
| const currentMetadata = this.metadata.get(store); | ||
| this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name: typeof nameOrStore === "string" ? nameOrStore : currentMetadata?.name || "unknown", | ||
| ...currentMetadata, | ||
| ...metadata | ||
| }); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get registry snapshot for React integration | ||
| */ | ||
| getSnapshot() { | ||
| return this._snapshot; | ||
| } | ||
| /** | ||
| * Clear all stores from registry | ||
| */ | ||
| clear() { | ||
| this.stores.clear(); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Dispose registry and cleanup resources | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Get count of registered stores | ||
| */ | ||
| getStoreCount() { | ||
| return this.stores.size; | ||
| } | ||
| /** | ||
| * Iterate over all stores | ||
| */ | ||
| forEach(callback) { | ||
| this.stores.forEach((store, name) => { | ||
| callback(store, name); | ||
| }); | ||
| } | ||
| /** | ||
| * Get registry statistics | ||
| */ | ||
| getStats() { | ||
| return { | ||
| totalStores: this.stores.size, | ||
| storeNames: this.getStoreNames(), | ||
| registryName: this.name | ||
| }; | ||
| } | ||
| /** | ||
| * Update internal snapshot | ||
| */ | ||
| _updateSnapshot() { | ||
| this._snapshot = Array.from(this.stores.entries()); | ||
| } | ||
| /** | ||
| * Notify all listeners of registry changes | ||
| */ | ||
| _notifyListeners() { | ||
| this.listeners.forEach((listener) => { | ||
| try { | ||
| listener(); | ||
| } catch (error) { | ||
| console.error("Error in registry listener:", error); | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Global default registry instance | ||
| */ | ||
| const globalStoreRegistry = new StoreRegistry("global"); | ||
| //#endregion | ||
| //#region src/stores/utils/type-guards.ts | ||
| function isRefState(value) { | ||
| return typeof value === "object" && value !== null && "target" in value && "isReady" in value && "isMounted" in value && "mountPromise" in value && typeof value.isReady === "boolean" && typeof value.isMounted === "boolean"; | ||
| } | ||
| /** | ||
| * DOM Event 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMEvent(value) { | ||
| return value instanceof Event; | ||
| } | ||
| /** | ||
| * Event-like 객체인지 확인하는 타입 가드 (preventDefault 메서드를 가진 객체) | ||
| */ | ||
| function isEventLike(value) { | ||
| return typeof value === "object" && value !== null && typeof value.preventDefault === "function"; | ||
| } | ||
| /** | ||
| * target 프로퍼티를 가진 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function hasTargetProperty(value) { | ||
| return typeof value === "object" && value !== null && "target" in value; | ||
| } | ||
| /** | ||
| * DOM Element인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMElement(value) { | ||
| return value instanceof Element; | ||
| } | ||
| /** | ||
| * 객체인지 확인하는 타입 가드 (null 제외) | ||
| */ | ||
| function isObject(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| } | ||
| /** | ||
| * 복합 타입 가드: Event 객체로 의심되는 객체인지 확인 | ||
| * RefState가 아니면서 Event 관련 속성을 가진 객체를 감지 | ||
| */ | ||
| function isSuspiciousEventObject(value, checkNested = true) { | ||
| if (!isObject(value) || isRefState(value)) return false; | ||
| if (isEventLikeObject(value)) return true; | ||
| if (checkNested) { | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const nestedValue = value[key]; | ||
| if (isEventLikeObject(nestedValue)) return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| /** | ||
| * 단일 객체가 이벤트와 같은지 확인 | ||
| */ | ||
| function isEventLikeObject(value) { | ||
| if (!isObject(value)) return false; | ||
| const hasEventTarget = hasTargetProperty(value); | ||
| const hasPreventDefault = isEventLike(value); | ||
| const isEvent = isDOMEvent(value); | ||
| const hasEventProperties = "type" in value && typeof value.type === "string" && (hasEventTarget || hasPreventDefault); | ||
| const hasReactMarkers = "nativeEvent" in value || "persist" in value || "$$typeof" in value || "_reactInternalFiber" in value || "_owner" in value; | ||
| const constructorName = value?.constructor?.name; | ||
| const hasEventConstructor = constructorName ? constructorName.includes("Event") || constructorName === "SyntheticEvent" || constructorName.includes("MouseEvent") || constructorName.includes("KeyboardEvent") || constructorName.includes("TouchEvent") || constructorName.includes("FocusEvent") || constructorName.includes("SubmitEvent") : false; | ||
| return isEvent || hasEventProperties || hasReactMarkers || hasEventConstructor; | ||
| } | ||
| /** | ||
| * 문제가 될 수 있는 속성들을 찾아내는 함수 | ||
| */ | ||
| function findProblematicProperties(value) { | ||
| if (!isObject(value)) return []; | ||
| const problematicKeys = []; | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const prop = value[key]; | ||
| if (isDOMElement(prop) || isDOMEvent(prop) || isObject(prop) && hasTargetProperty(prop)) problematicKeys.push(key); | ||
| } | ||
| return problematicKeys; | ||
| } | ||
| /** | ||
| * 통합 타입 가드 객체 | ||
| * 모든 타입 가드 함수들을 하나의 객체로 export | ||
| */ | ||
| const TypeGuards = { | ||
| isRefState, | ||
| isDOMEvent, | ||
| isEventLike, | ||
| hasTargetProperty, | ||
| isDOMElement, | ||
| isObject, | ||
| isSuspiciousEventObject, | ||
| findProblematicProperties | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/core/Store.ts | ||
| /** | ||
| * Core Store class for centralized state management with memory leak prevention | ||
| * | ||
| * Provides reactive state management with subscription capabilities, optimized for | ||
| * React integration through useSyncExternalStore. Features advanced cleanup mechanisms, | ||
| * automatic resource management, and comprehensive memory leak prevention. | ||
| * | ||
| * Key Features: | ||
| * - Automatic cleanup task registration and execution | ||
| * - Memory leak prevention with disposal patterns | ||
| * - Race condition protection for async operations | ||
| * - Advanced error recovery with exponential backoff | ||
| * - Resource monitoring and threshold management | ||
| * | ||
| * @template T - The type of value stored in this store | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const userStore = createStore('user', { name: '', age: 0 }); | ||
| * | ||
| * // Register cleanup tasks | ||
| * const unregister = userStore.registerCleanup(() => { | ||
| * console.log('Cleaning up user store resources'); | ||
| * }); | ||
| * | ||
| * // Automatic cleanup on disposal | ||
| * userStore.dispose(); | ||
| * ``` | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * @public | ||
| */ | ||
| var Store = class { | ||
| constructor(name, initialValue) { | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._lastClonedValue = null; | ||
| this._lastClonedVersion = 0; | ||
| this._version = 0; | ||
| this.isUpdating = false; | ||
| this.updateQueue = []; | ||
| this.notificationMode = "batched"; | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| this.pendingUpdatesCount = 0; | ||
| this.cleanupTasks = /* @__PURE__ */ new Set(); | ||
| this.isDisposed = false; | ||
| this.errorCount = 0; | ||
| this.lastErrorTime = 0; | ||
| this.MAX_ERROR_COUNT = 5; | ||
| this.ERROR_RESET_TIME = 6e4; | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.cloningEnabled = true; | ||
| this.subscribe = (listener) => { | ||
| if (this.isDisposed) { | ||
| console.warn(`Cannot subscribe to disposed store "${this.name}"`); | ||
| return () => {}; | ||
| } | ||
| const enhancedListener = () => { | ||
| if (this.isDisposed) return; | ||
| try { | ||
| listener(); | ||
| if (this.errorCount > 0) this.errorCount = 0; | ||
| } catch (error) { | ||
| this._handleListenerError(error, listener); | ||
| } | ||
| }; | ||
| this.subscriptionRegistry.set(listener, { | ||
| subscribedAt: Date.now(), | ||
| errorCount: 0, | ||
| enhancedListener | ||
| }); | ||
| this.listeners.add(enhancedListener); | ||
| return () => { | ||
| this.listeners.delete(enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| }; | ||
| }; | ||
| this.getSnapshot = () => { | ||
| return this._snapshot; | ||
| }; | ||
| this.name = name; | ||
| this._value = initialValue; | ||
| this._snapshot = this._createSnapshot(); | ||
| } | ||
| /** | ||
| * 현재 값 직접 가져오기 (액션 핸들러용) | ||
| * 핵심 로직: 불변성을 보장하는 깊은 복사본 반환 | ||
| * | ||
| * @implements lazy-evaluation | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 사용 시나리오: Action handler에서 최신 상태 읽기 | ||
| * 보안 강화: 외부에서 반환된 값을 수정해도 Store 내부 상태는 보호됨 | ||
| */ | ||
| getValue() { | ||
| if (typeof this._value !== "object" || this._value === null) return this._value; | ||
| if (this.cloningEnabled) { | ||
| if (this._lastClonedVersion === this._version && this._lastClonedValue !== null) return this._lastClonedValue; | ||
| this._lastClonedValue = require_error_handling.safeGet(this._value, this.cloningEnabled); | ||
| this._lastClonedVersion = this._version; | ||
| return this._lastClonedValue; | ||
| } | ||
| return this._value; | ||
| } | ||
| /** | ||
| * Store 값 설정 및 구독자 알림 | ||
| * 핵심 로직: | ||
| * 1. 입력값의 불변성 보장을 위한 깊은 복사 (선택적 skip 가능) | ||
| * 2. 강화된 값 비교 시스템으로 불필요한 리렌더링 방지 | ||
| * 3. Structural sharing을 통한 성능 최적화 | ||
| * 4. 값 변경 시에만 스냅샷 재생성 및 알림 | ||
| * | ||
| * @implements unidirectional-data-flow | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 보안 강화: 입력값을 복사하여 Store 내부 상태가 외부 참조에 의해 변경되지 않도록 보호 | ||
| * 성능 강화: 다층 비교 시스템으로 정확한 변경 감지 및 렌더링 최적화 | ||
| */ | ||
| setValue(value, options) { | ||
| if (TypeGuards.isObject(value)) { | ||
| if (!TypeGuards.isRefState(value) && TypeGuards.isSuspiciousEventObject(value)) { | ||
| const eventHandling = options?.eventHandling || "block"; | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(value); | ||
| const hasPreventDefault = TypeGuards.isEventLike(value); | ||
| const isEvent = TypeGuards.isDOMEvent(value); | ||
| switch (eventHandling) { | ||
| case "allow": break; | ||
| case "transform": | ||
| if (options?.eventTransform) try { | ||
| value = options.eventTransform(value); | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Event transformation failed in Store.setValue", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }); | ||
| return; | ||
| } | ||
| else { | ||
| require_error_handling.ErrorHandlers.store("Event transformation requested but no transform function provided", { | ||
| storeName: this.name, | ||
| valueType: typeof value | ||
| }); | ||
| return; | ||
| } | ||
| break; | ||
| case "block": | ||
| default: | ||
| require_error_handling.ErrorHandlers.store("Event object detected in Store.setValue - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| constructorName: value?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(value) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| const safeValue = options?.skipClone ? value : require_error_handling.safeSet(value, this.cloningEnabled); | ||
| let hasChanged = true; | ||
| if (!options?.skipComparison) hasChanged = this._compareValues(this._value, safeValue); | ||
| if (hasChanged) { | ||
| this._value = safeValue; | ||
| this._version++; | ||
| this._snapshot = this._createSnapshot(); | ||
| this._scheduleNotification(); | ||
| } | ||
| } | ||
| /** | ||
| * Update value using updater function with Immer integration | ||
| * 핵심 로직: | ||
| * 1. Immer produce를 사용하여 draft 객체 제공 | ||
| * 2. updater 결과를 불변성을 보장하며 설정 | ||
| * | ||
| * @implements store-immutability | ||
| * 보안 강화: Immer draft를 통한 안전한 상태 수정 | ||
| */ | ||
| update(updater) { | ||
| if (this.isUpdating) { | ||
| this.updateQueue.push(() => this.update(updater)); | ||
| return; | ||
| } | ||
| try { | ||
| this.isUpdating = true; | ||
| let updatedValue; | ||
| try { | ||
| updatedValue = require_error_handling.produce(this._value, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (immerError) { | ||
| if (process.env.NODE_ENV === "development") console.warn("[Store] Immer update failed, falling back to safe copy method", immerError); | ||
| const safeCurrentValue = require_error_handling.safeGet(this._value, this.cloningEnabled); | ||
| try { | ||
| updatedValue = require_error_handling.produce(safeCurrentValue, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (secondImmerError) { | ||
| if (process.env.NODE_ENV === "development") console.warn("[Store] Immer completely failed, using direct update (immutability not guaranteed)", secondImmerError); | ||
| updatedValue = updater(safeCurrentValue); | ||
| } | ||
| } | ||
| if (TypeGuards.isObject(updatedValue)) { | ||
| if (!TypeGuards.isRefState(updatedValue) && TypeGuards.isSuspiciousEventObject(updatedValue)) { | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(updatedValue); | ||
| const hasPreventDefault = TypeGuards.isEventLike(updatedValue); | ||
| const isEvent = TypeGuards.isDOMEvent(updatedValue); | ||
| require_error_handling.ErrorHandlers.store("Event object detected in Store.update result - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| updatedValueType: typeof updatedValue, | ||
| constructorName: updatedValue?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(updatedValue) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| this.setValue(updatedValue); | ||
| } finally { | ||
| this.isUpdating = false; | ||
| if (this.updateQueue.length > 0) { | ||
| const nextUpdate = this.updateQueue.shift(); | ||
| if (nextUpdate) Promise.resolve().then(nextUpdate); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Get number of active listeners | ||
| */ | ||
| getListenerCount() { | ||
| return this.listeners.size; | ||
| } | ||
| /** | ||
| * Clear all listeners | ||
| */ | ||
| clearListeners() { | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Register cleanup task for automatic execution on disposal | ||
| * | ||
| * Registers a cleanup function that will be automatically called when the store | ||
| * is disposed. This prevents memory leaks and ensures proper resource cleanup. | ||
| * | ||
| * @param task - Cleanup function to register | ||
| * @returns Unregister function to remove the cleanup task | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const timer = setInterval(() => {}, 1000); | ||
| * const unregister = store.registerCleanup(() => clearInterval(timer)); | ||
| * | ||
| * // Later, remove the cleanup task if needed | ||
| * unregister(); | ||
| * ``` | ||
| */ | ||
| registerCleanup(task) { | ||
| if (this.isDisposed) { | ||
| console.warn(`Store "${this.name}" is already disposed, cleanup task ignored`); | ||
| return () => {}; | ||
| } | ||
| this.cleanupTasks.add(task); | ||
| return () => this.cleanupTasks.delete(task); | ||
| } | ||
| /** | ||
| * Enhanced Store disposal with comprehensive cleanup | ||
| * | ||
| * Performs complete cleanup of all store resources including listeners, | ||
| * timers, cleanup tasks, and internal state. Prevents memory leaks and | ||
| * ensures proper resource disposal. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Manual disposal | ||
| * store.dispose(); | ||
| * | ||
| * // Auto-disposal with useEffect | ||
| * useEffect(() => { | ||
| * return () => store.dispose(); | ||
| * }, [store]); | ||
| * ``` | ||
| */ | ||
| dispose() { | ||
| if (this.isDisposed) return; | ||
| this.isDisposed = true; | ||
| try { | ||
| this.cleanupTasks.forEach((task) => { | ||
| try { | ||
| task(); | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Error during cleanup task execution", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| }); | ||
| this.cleanupTasks.clear(); | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.clearListeners(); | ||
| if (this.animationFrameId !== null) { | ||
| cancelAnimationFrame(this.animationFrameId); | ||
| this.animationFrameId = null; | ||
| } | ||
| this.pendingNotification = false; | ||
| this.updateQueue.length = 0; | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Critical error during store disposal", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| } | ||
| /** | ||
| * Check if store is disposed | ||
| * @returns true if store has been disposed | ||
| */ | ||
| isStoreDisposed() { | ||
| return this.isDisposed; | ||
| } | ||
| /** | ||
| * Store별 커스텀 비교 함수 설정 | ||
| * 이 Store에만 적용되는 특별한 비교 로직 설정 | ||
| * | ||
| * @param comparator - 커스텀 비교 함수 (oldValue, newValue) => boolean | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setCustomComparator(comparator) { | ||
| this.customComparator = comparator; | ||
| } | ||
| /** | ||
| * Store별 비교 옵션 설정 | ||
| * 이 Store에만 적용되는 비교 전략 설정 | ||
| * | ||
| * @param options - 비교 옵션 | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setComparisonOptions(options) { | ||
| this.comparisonOptions = options; | ||
| } | ||
| /** | ||
| * 성능 최적화: Store별 복사 동작 제어 | ||
| * | ||
| * @param enabled - true: 복사 활성화 (안전), false: 복사 비활성화 (성능 우선) | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/performance | ||
| */ | ||
| setCloningEnabled(enabled) { | ||
| this.cloningEnabled = enabled; | ||
| } | ||
| /** | ||
| * 현재 복사 설정 조회 | ||
| */ | ||
| isCloningEnabled() { | ||
| return this.cloningEnabled; | ||
| } | ||
| /** | ||
| * 강화된 값 비교 시스템 | ||
| * 1. 커스텀 비교 함수 우선 사용 | ||
| * 2. Store별 비교 옵션 적용 | ||
| * 3. 성능 최적화된 빠른 비교 fallback | ||
| * 4. 전역 비교 설정 사용 | ||
| * | ||
| * @param oldValue - 이전 값 | ||
| * @param newValue - 새로운 값 | ||
| * @returns true if values are different (change detected), false if same | ||
| * @protected | ||
| */ | ||
| _compareValues(oldValue, newValue) { | ||
| let result; | ||
| try { | ||
| if (this.customComparator) result = !this.customComparator(oldValue, newValue); | ||
| else if (this.comparisonOptions) result = !require_error_handling.compareValues(oldValue, newValue, this.comparisonOptions); | ||
| else result = !require_error_handling.compareValues(oldValue, newValue, { strategy: "reference" }); | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Error during value comparison, falling back to reference comparison", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| result = !Object.is(oldValue, newValue); | ||
| } | ||
| return result; | ||
| } | ||
| _createSnapshot() { | ||
| return { | ||
| value: require_error_handling.safeGet(this._value, this.cloningEnabled), | ||
| name: this.name, | ||
| lastUpdate: Date.now() | ||
| }; | ||
| } | ||
| /** | ||
| * requestAnimationFrame 기반 알림 스케줄링 | ||
| * 브라우저의 다음 프레임에서 리스너 알림 실행 | ||
| */ | ||
| _scheduleNotification() { | ||
| if (this.notificationMode === "immediate") this._notifyListeners(); | ||
| else this._scheduleWithRAF(); | ||
| } | ||
| /** | ||
| * requestAnimationFrame을 사용한 알림 스케줄링 | ||
| * 누적 가능한 배치 시스템으로 개선 | ||
| */ | ||
| _scheduleWithRAF() { | ||
| this.pendingUpdatesCount++; | ||
| if (!this.pendingNotification) { | ||
| this.pendingNotification = true; | ||
| this.animationFrameId = requestAnimationFrame(() => { | ||
| this._executeNotification(); | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * 스케줄된 알림 실행 | ||
| */ | ||
| _executeNotification() { | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| const batchedUpdates = this.pendingUpdatesCount; | ||
| this.pendingUpdatesCount = 0; | ||
| this._notifyListeners(); | ||
| if (process.env.NODE_ENV === "development" && batchedUpdates > 1) console.debug(`[Store:${this.name}] Batched ${batchedUpdates} updates in single frame`); | ||
| } | ||
| /** | ||
| * Handle listener execution errors with recovery strategies | ||
| */ | ||
| _handleListenerError(error, listener) { | ||
| const now = Date.now(); | ||
| if (now - this.lastErrorTime > this.ERROR_RESET_TIME) this.errorCount = 0; | ||
| this.errorCount++; | ||
| this.lastErrorTime = now; | ||
| const metadata = this.subscriptionRegistry.get(listener); | ||
| if (metadata) metadata.errorCount++; | ||
| require_error_handling.ErrorHandlers.store("Error in store listener execution", { | ||
| storeName: this.name, | ||
| listenerCount: this.listeners.size, | ||
| errorCount: this.errorCount, | ||
| subscriptionAge: metadata ? now - metadata.subscribedAt : "unknown" | ||
| }, error instanceof Error ? error : void 0); | ||
| if (metadata && metadata.errorCount >= 3) { | ||
| console.warn(`Removing problematic listener from store "${this.name}" after ${metadata.errorCount} errors`); | ||
| this.listeners.delete(metadata.enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| } | ||
| if (this.errorCount >= this.MAX_ERROR_COUNT) { | ||
| console.error(`Store "${this.name}" disabled due to excessive errors (${this.errorCount})`); | ||
| this.clearListeners(); | ||
| } | ||
| } | ||
| _notifyListeners() { | ||
| if (this.isDisposed) return; | ||
| this.listeners.forEach((listener) => { | ||
| if (this.isDisposed) return; | ||
| listener(); | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Factory function for creating type-safe Store instances | ||
| * | ||
| * Creates a new Store instance with the specified name and initial value. | ||
| * Provides type safety and integrates seamlessly with React hooks and | ||
| * the Context-Action framework patterns. | ||
| * | ||
| * @template T - The type of values stored in this store | ||
| * | ||
| * @param name - Unique identifier for the store (used for debugging) | ||
| * @param initialValue - Initial value to store | ||
| * | ||
| * @returns Configured Store instance ready for use | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function createStore(name, initialValue) { | ||
| return new Store(name, initialValue); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/utils/sync-external-store-utils.ts | ||
| /** | ||
| * 향상된 구독 함수 생성 (내부 사용) | ||
| * 디바운싱, 스로틀링, 조건부 구독 기능 제공 | ||
| */ | ||
| function createEnhancedSubscriber(store, options = {}) { | ||
| const { debounce, throttle, condition, debug, name = "unknown" } = options; | ||
| return (callback) => { | ||
| if (!store) return () => {}; | ||
| let debounceTimer = null; | ||
| let throttleTimer = null; | ||
| let lastThrottleTime = 0; | ||
| const enhancedCallback = () => { | ||
| if (condition && !condition()) { | ||
| if (debug) console.debug(`[${name}] Subscription suspended due to condition`); | ||
| return; | ||
| } | ||
| const now = performance.now(); | ||
| if (throttle && throttle > 0) { | ||
| if (now - lastThrottleTime < throttle) { | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| throttleTimer = setTimeout(() => { | ||
| lastThrottleTime = performance.now(); | ||
| callback(); | ||
| }, throttle - (now - lastThrottleTime)); | ||
| return; | ||
| } | ||
| lastThrottleTime = now; | ||
| } | ||
| if (debounce && debounce > 0) { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| debounceTimer = setTimeout(() => { | ||
| callback(); | ||
| if (debug) console.debug(`[${name}] Debounced callback executed after ${debounce}ms`); | ||
| }, debounce); | ||
| return; | ||
| } | ||
| callback(); | ||
| }; | ||
| const unsubscribe = store.subscribe(enhancedCallback); | ||
| return () => { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| unsubscribe(); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Null-safe Store 구독 훅 | ||
| * useSyncExternalStore를 기반으로 한 안전한 구독 | ||
| */ | ||
| function useSafeStoreSubscription(store, selector, options = {}) { | ||
| const { initialValue, equalityFn, debounce, throttle, condition, debug, name } = options; | ||
| const stableSubscriptionOptions = (0, react.useMemo)(() => ({ | ||
| debounce, | ||
| throttle, | ||
| condition, | ||
| debug, | ||
| name | ||
| }), [ | ||
| debounce, | ||
| throttle, | ||
| condition, | ||
| debug, | ||
| name | ||
| ]); | ||
| const needsEnhancedSubscription = Boolean(debounce || throttle || condition); | ||
| const subscribe = (0, react.useCallback)((callback) => { | ||
| if (!store) return () => {}; | ||
| if (needsEnhancedSubscription) return createEnhancedSubscriber(store, stableSubscriptionOptions)(callback); | ||
| return store.subscribe(callback); | ||
| }, [ | ||
| store, | ||
| needsEnhancedSubscription, | ||
| stableSubscriptionOptions | ||
| ]); | ||
| const getSnapshot = (0, react.useCallback)(() => { | ||
| if (!store) return initialValue; | ||
| const snapshot = store.getSnapshot(); | ||
| return selector ? selector(snapshot.value) : snapshot.value; | ||
| }, [ | ||
| store, | ||
| selector, | ||
| initialValue | ||
| ]); | ||
| const cachedSnapshotRef = (0, react.useRef)(); | ||
| const stableGetSnapshot = (0, react.useCallback)(() => { | ||
| const currentSnapshot = getSnapshot(); | ||
| if (equalityFn && cachedSnapshotRef.current !== void 0) { | ||
| if (equalityFn(cachedSnapshotRef.current, currentSnapshot)) return cachedSnapshotRef.current; | ||
| } | ||
| cachedSnapshotRef.current = currentSnapshot; | ||
| return currentSnapshot; | ||
| }, [getSnapshot, equalityFn]); | ||
| const getServerSnapshot = (0, react.useCallback)(() => { | ||
| return initialValue; | ||
| }, [initialValue]); | ||
| return (0, react.useSyncExternalStore)(subscribe, equalityFn ? stableGetSnapshot : getSnapshot, getServerSnapshot); | ||
| } | ||
| /** | ||
| * 기본 동등성 비교 함수들 | ||
| */ | ||
| const equalityFunctions = { | ||
| reference: (a, b) => Object.is(a, b), | ||
| shallow: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) return false; | ||
| return true; | ||
| }, | ||
| deep: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) !== Array.isArray(b)) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) { | ||
| if (!Object.prototype.hasOwnProperty.call(b, key)) return false; | ||
| if (!equalityFunctions.deep(a[key], b[key])) return false; | ||
| } | ||
| return true; | ||
| }, | ||
| smart: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) && Array.isArray(b)) { | ||
| if (a.length !== b.length) return false; | ||
| return a.every((item, index) => { | ||
| const bItem = b[index]; | ||
| if (typeof item === "object" && item !== null && typeof bItem === "object" && bItem !== null) return equalityFunctions.shallow(item, bItem); | ||
| return Object.is(item, bItem); | ||
| }); | ||
| } | ||
| return equalityFunctions.shallow(a, b); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/hooks/useStoreSelector.ts | ||
| const defaultEqualityFn = equalityFunctions.smart; | ||
| const shallowEqual = equalityFunctions.shallow; | ||
| const deepEqual = equalityFunctions.deep; | ||
| const smartEqual = equalityFunctions.smart; | ||
| /** | ||
| * Hook for selective store subscription with performance optimization | ||
| * | ||
| * Subscribes to specific parts of store data using a selector function, | ||
| * triggering re-renders only when the selected value actually changes. | ||
| * Essential for preventing unnecessary re-renders in complex applications. | ||
| * | ||
| * @template T - Type of the store value | ||
| * @template R - Type of the value returned by the selector | ||
| * | ||
| * @param store - Store instance to subscribe to | ||
| * @param selector - Function to extract needed data from store value | ||
| * @param equalityFn - Function to compare previous and new values (default: Object.is) | ||
| * | ||
| * @returns The value returned by the selector function | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-hooks#usestoreselector-advanced-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function useStoreSelector(store, selector, equalityFn = defaultEqualityFn) { | ||
| "use memo"; | ||
| const stableSelector = (0, react.useCallback)(selector, [selector]); | ||
| const stableEqualityFn = (0, react.useCallback)(equalityFn, [equalityFn]); | ||
| const selectorWarningShownRef = (0, react.useRef)(false); | ||
| if (process.env.NODE_ENV === "development") { | ||
| if (selector !== stableSelector && !selectorWarningShownRef.current) { | ||
| console.warn("useStoreSelector: selector function changed. Consider wrapping it with useCallback to avoid unnecessary recalculations.", "Store:", store.name); | ||
| selectorWarningShownRef.current = true; | ||
| } | ||
| } | ||
| const previousValueRef = (0, react.useRef)(); | ||
| const subscribe = (0, react.useCallback)((callback) => { | ||
| return store.subscribe(callback); | ||
| }, [store]); | ||
| const getSnapshot = (0, react.useCallback)(() => { | ||
| try { | ||
| const selectedValue = stableSelector(store.getValue()); | ||
| if (previousValueRef.current !== void 0 && stableEqualityFn(previousValueRef.current, selectedValue)) return previousValueRef.current; | ||
| previousValueRef.current = selectedValue; | ||
| return selectedValue; | ||
| } catch (error) { | ||
| if (process.env.NODE_ENV === "development") console.error("useStoreSelector: Error in selector function:", error); | ||
| throw error; | ||
| } | ||
| }, [ | ||
| store, | ||
| stableSelector, | ||
| stableEqualityFn | ||
| ]); | ||
| return (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/components/StoreErrorBoundary.tsx | ||
| /** | ||
| * Store 시스템을 위한 에러 경계 컴포넌트 | ||
| * | ||
| * Store 관련 에러들을 캐치하고 적절한 fallback UI를 제공합니다. | ||
| * 개발 모드에서는 자세한 에러 정보를 표시하고, 프로덕션에서는 | ||
| * 사용자 친화적인 메시지를 보여줍니다. | ||
| */ | ||
| var StoreErrorBoundary = class extends react.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.resetTimeoutId = null; | ||
| this.resetErrorBoundary = () => { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| this.setState({ | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }); | ||
| }; | ||
| this.state = { | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }; | ||
| } | ||
| static getDerivedStateFromError(error) { | ||
| return { | ||
| hasError: true, | ||
| error: error instanceof Error && error.name === "ContextActionError" ? error : null, | ||
| errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` | ||
| }; | ||
| } | ||
| componentDidCatch(error, errorInfo) { | ||
| if (error.name === "ContextActionError") { | ||
| const contextActionError = error; | ||
| this.setState({ errorInfo }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } else { | ||
| const contextActionError = require_error_handling.ErrorHandlers.store(`Unhandled error in Store component: ${error.message}`, { | ||
| component: "StoreErrorBoundary", | ||
| stack: error.stack, | ||
| componentStack: errorInfo.componentStack | ||
| }, error); | ||
| this.setState({ | ||
| error: contextActionError, | ||
| errorInfo | ||
| }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| const { hasError } = this.state; | ||
| const { resetOnPropsChange, resetKeys } = this.props; | ||
| if (hasError && resetOnPropsChange) { | ||
| if (resetKeys) { | ||
| if (resetKeys.some((key) => { | ||
| return prevProps[key] !== this.props[key]; | ||
| })) this.resetErrorBoundary(); | ||
| } else if (prevProps !== this.props) this.resetErrorBoundary(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| } | ||
| render() { | ||
| const { hasError, error, errorInfo } = this.state; | ||
| const { children, fallback } = this.props; | ||
| if (hasError) { | ||
| if (fallback) { | ||
| if (typeof fallback === "function") return fallback(error, errorInfo); | ||
| return fallback; | ||
| } | ||
| return this.renderDefaultFallback(); | ||
| } | ||
| return children; | ||
| } | ||
| renderDefaultFallback() { | ||
| if (process.env.NODE_ENV === "development") return this.renderDevelopmentFallback(); | ||
| else return this.renderProductionFallback(); | ||
| } | ||
| renderDevelopmentFallback() { | ||
| const { error, errorInfo, errorId } = this.state; | ||
| const stats = require_error_handling.getErrorStatistics(); | ||
| return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { | ||
| padding: "20px", | ||
| margin: "20px", | ||
| border: "2px solid #ff6b6b", | ||
| borderRadius: "8px", | ||
| backgroundColor: "#ffe0e0", | ||
| fontFamily: "monospace" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { | ||
| style: { | ||
| color: "#d63031", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "🚨 Store Error Boundary" | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Error ID:" }), | ||
| " ", | ||
| errorId | ||
| ] | ||
| }), | ||
| error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Error Type:" }), | ||
| " ", | ||
| error.type, | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Message:" }), | ||
| " ", | ||
| error.message, | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Timestamp:" }), | ||
| " ", | ||
| new Date(error.timestamp).toISOString() | ||
| ] | ||
| }), | ||
| error?.context && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Context:" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "12px" | ||
| }, | ||
| children: JSON.stringify(error.context, null, 2) | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Error Statistics:" }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| "Total Errors: ", | ||
| stats.totalErrors, | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| "Recent Errors: ", | ||
| stats.recentErrors.length | ||
| ] | ||
| }), | ||
| errorInfo && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { | ||
| style: { | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Component Stack" | ||
| }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "11px", | ||
| whiteSpace: "pre-wrap" | ||
| }, | ||
| children: errorInfo.componentStack | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#00b894", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| renderProductionFallback() { | ||
| return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { | ||
| padding: "20px", | ||
| textAlign: "center", | ||
| backgroundColor: "#f8f9fa", | ||
| border: "1px solid #dee2e6", | ||
| borderRadius: "8px", | ||
| margin: "20px 0" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "Something went wrong" | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 15px 0" | ||
| }, | ||
| children: "We're sorry, but something unexpected happened. Please try again." | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#007bff", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Enhanced HOC for automatic Store Error Boundary wrapping with security | ||
| * | ||
| * @template P - Component props type | ||
| * @param WrappedComponent - Component to wrap with error boundary | ||
| * @param errorBoundaryProps - Error boundary configuration | ||
| * @returns Enhanced component with comprehensive error handling | ||
| */ | ||
| function withStoreErrorBoundary(WrappedComponent, errorBoundaryProps) { | ||
| const WithStoreErrorBoundaryComponent = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StoreErrorBoundary, { | ||
| ...errorBoundaryProps, | ||
| children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WrappedComponent, { ...props }) | ||
| }); | ||
| WithStoreErrorBoundaryComponent.displayName = `withStoreErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name})`; | ||
| return WithStoreErrorBoundaryComponent; | ||
| } | ||
| /** | ||
| * 특정 Store와 연결된 에러 경계 생성 헬퍼 | ||
| */ | ||
| function createStoreErrorBoundary(storeName, customFallback) { | ||
| return ({ children }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StoreErrorBoundary, { | ||
| fallback: customFallback, | ||
| onError: (error, errorInfo) => { | ||
| console.group(`Store Error in ${storeName}`); | ||
| console.error("Error:", error); | ||
| console.error("Component Stack:", errorInfo.componentStack); | ||
| console.groupEnd(); | ||
| }, | ||
| resetKeys: [storeName], | ||
| children | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/actions/ActionContext.tsx | ||
| function createActionContext(contextNameOrConfig = {}, config) { | ||
| let effectiveConfig; | ||
| let contextName; | ||
| if (typeof contextNameOrConfig === "string") { | ||
| contextName = contextNameOrConfig; | ||
| effectiveConfig = { | ||
| ...config, | ||
| name: config?.name || contextName | ||
| }; | ||
| } else { | ||
| effectiveConfig = contextNameOrConfig; | ||
| contextName = effectiveConfig.name || "ActionContext"; | ||
| } | ||
| const FactoryActionContext = (0, react.createContext)(null); | ||
| const Provider = ({ children }) => { | ||
| const actionRegisterRef = (0, react.useRef)(new __context_action_core.ActionRegister(effectiveConfig)); | ||
| const contextValue = (0, react.useMemo)(() => ({ actionRegisterRef }), []); | ||
| return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FactoryActionContext.Provider, { | ||
| value: contextValue, | ||
| children | ||
| }); | ||
| }; | ||
| const useFactoryActionContext = () => { | ||
| const context = (0, react.useContext)(FactoryActionContext); | ||
| if (!context) throw new Error("useFactoryActionContext must be used within a factory ActionContext Provider"); | ||
| return context; | ||
| }; | ||
| /** | ||
| * Optimized hook to get stable dispatch functions | ||
| * | ||
| * Returns stable dispatch functions that prevent re-renders and maintain | ||
| * reference equality across component renders. | ||
| * | ||
| * @returns Object with dispatch and dispatchWithResult functions | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useActionDispatcher = () => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| return { | ||
| dispatch: (0, react.useCallback)((action, payload, options) => { | ||
| if (process.env.NODE_ENV === "development") console.log(`React dispatch called for '${String(action)}':`, { | ||
| hasPayload: payload !== void 0, | ||
| hasOptions: options !== void 0, | ||
| timestamp: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister is not initialized. Make sure the ActionContext Provider is properly set up."); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]), | ||
| dispatchWithResult: (0, react.useCallback)((action, payload, options) => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]) | ||
| }; | ||
| }; | ||
| const useAction = () => { | ||
| const { dispatch } = useActionDispatcher(); | ||
| return dispatch; | ||
| }; | ||
| const useActionHandler = (action, handler, config$1) => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| const actionId = (0, react.useId)(); | ||
| const handlerRef = (0, react.useRef)(handler); | ||
| handlerRef.current = handler; | ||
| const priority = config$1?.priority ?? 0; | ||
| const id = config$1?.id || `react_${String(action)}_${actionId}`; | ||
| const blocking = config$1?.blocking ?? false; | ||
| const once = config$1?.once ?? false; | ||
| const debounce = config$1?.debounce; | ||
| const throttle = config$1?.throttle; | ||
| const stableConfig = (0, react.useMemo)(() => ({ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| replaceExisting: true, | ||
| ...debounce !== void 0 && { debounce }, | ||
| ...throttle !== void 0 && { throttle } | ||
| }), [ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| debounce, | ||
| throttle | ||
| ]); | ||
| (0, react.useEffect)(() => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) return; | ||
| const wrapperHandler = (payload, controller) => { | ||
| return handlerRef.current(payload, controller); | ||
| }; | ||
| if (process.env.NODE_ENV === "development") console.log(`Registering handler for '${String(action)}'`); | ||
| return register.register(action, wrapperHandler, stableConfig); | ||
| }, [ | ||
| action, | ||
| actionRegisterRef, | ||
| stableConfig | ||
| ]); | ||
| }; | ||
| /** | ||
| * Hook that provides direct access to the ActionRegister instance | ||
| * | ||
| * This hook is useful when you need to: | ||
| * - Register multiple handlers dynamically | ||
| * - Access other ActionRegister methods like clearAction, getHandlers, etc. | ||
| * - Implement complex handler registration logic | ||
| * - Have more control over the registration lifecycle | ||
| * | ||
| * @returns ActionRegister instance or null if not initialized | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useFactoryActionRegister = () => { | ||
| return useFactoryActionContext().actionRegisterRef.current; | ||
| }; | ||
| /** | ||
| * Hook that provides access to the dispatchWithResult function | ||
| * | ||
| * This hook returns a function that dispatches actions and returns detailed | ||
| * execution results including collected handler results, execution metadata, | ||
| * and error information. | ||
| * | ||
| * @returns dispatchWithResult function with full type safety | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-with-result | ||
| */ | ||
| const useFactoryActionDispatchWithResult = () => { | ||
| const context = useFactoryActionContext(); | ||
| const activeControllersRef = (0, react.useRef)(/* @__PURE__ */ new Set()); | ||
| const dispatch = (0, react.useCallback)((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| let createdController; | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| createdController = controller; | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions).finally(() => { | ||
| if (createdController) activeControllersRef.current.delete(createdController); | ||
| }); | ||
| }, [context.actionRegisterRef]); | ||
| const dispatchWithResult = (0, react.useCallback)((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| let createdController; | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| createdController = controller; | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions).finally(() => { | ||
| if (createdController) activeControllersRef.current.delete(createdController); | ||
| }); | ||
| }, [context.actionRegisterRef]); | ||
| const abortAll = (0, react.useCallback)(() => { | ||
| activeControllersRef.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| activeControllersRef.current.clear(); | ||
| }, []); | ||
| const resetAbortScope = (0, react.useCallback)(() => { | ||
| abortAll(); | ||
| }, [abortAll]); | ||
| (0, react.useEffect)(() => { | ||
| const controllers = activeControllersRef; | ||
| return () => { | ||
| controllers.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| controllers.current.clear(); | ||
| }; | ||
| }, []); | ||
| return { | ||
| dispatch, | ||
| dispatchWithResult, | ||
| abortAll, | ||
| resetAbortScope | ||
| }; | ||
| }; | ||
| return { | ||
| Provider, | ||
| useActionContext: useFactoryActionContext, | ||
| useActionDispatch: useAction, | ||
| useActionHandler, | ||
| useActionRegister: useFactoryActionRegister, | ||
| useActionDispatchWithResult: useFactoryActionDispatchWithResult, | ||
| context: FactoryActionContext | ||
| }; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'Store', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return Store; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'StoreErrorBoundary', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return StoreErrorBoundary; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'StoreRegistry', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return StoreRegistry; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createActionContext', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createActionContext; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createStore', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createStore; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createStoreErrorBoundary', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createStoreErrorBoundary; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'defaultEqualityFn', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return defaultEqualityFn; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'useSafeStoreSubscription', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return useSafeStoreSubscription; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'useStoreSelector', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return useStoreSelector; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'withStoreErrorBoundary', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return withStoreErrorBoundary; | ||
| } | ||
| }); |
| import { ErrorHandlers, compareValues, getErrorStatistics, produce, safeGet, safeSet } from "./error-handling-CkdfKCZ0.js"; | ||
| import React, { Component, createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useSyncExternalStore } from "react"; | ||
| import { jsx, jsxs } from "react/jsx-runtime"; | ||
| import { ActionRegister } from "@context-action/core"; | ||
| //#region src/stores/core/StoreRegistry.ts | ||
| /** | ||
| * Centralized store registry for managing multiple Store instances | ||
| */ | ||
| var StoreRegistry = class { | ||
| constructor(name = "default") { | ||
| this.stores = /* @__PURE__ */ new Map(); | ||
| this.metadata = /* @__PURE__ */ new WeakMap(); | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._snapshot = []; | ||
| this.name = name; | ||
| } | ||
| /** | ||
| * Subscribe to registry changes for reactive updates | ||
| */ | ||
| subscribe(listener) { | ||
| this.listeners.add(listener); | ||
| return () => { | ||
| this.listeners.delete(listener); | ||
| }; | ||
| } | ||
| /** | ||
| * Register a new store in the registry | ||
| */ | ||
| register(name, store, metadata) { | ||
| if (this.stores.has(name)) { | ||
| if (this.stores.get(name)) {} | ||
| } | ||
| this.stores.set(name, store); | ||
| if (metadata) this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name, | ||
| ...metadata | ||
| }); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Unregister a store from the registry | ||
| */ | ||
| unregister(name) { | ||
| if (!this.stores.get(name)) return false; | ||
| this.stores.delete(name); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get a store by name | ||
| */ | ||
| getStore(name) { | ||
| return this.stores.get(name); | ||
| } | ||
| /** | ||
| * Check if a store exists | ||
| */ | ||
| hasStore(name) { | ||
| return this.stores.has(name); | ||
| } | ||
| /** | ||
| * Get all registered store names | ||
| */ | ||
| getStoreNames() { | ||
| return Array.from(this.stores.keys()); | ||
| } | ||
| /** | ||
| * Get all stores as entries | ||
| */ | ||
| getAllStores() { | ||
| return new Map(this.stores); | ||
| } | ||
| /** | ||
| * Get store metadata | ||
| */ | ||
| getStoreMetadata(nameOrStore) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| return store ? this.metadata.get(store) : void 0; | ||
| } | ||
| /** | ||
| * Update store metadata | ||
| */ | ||
| updateStoreMetadata(nameOrStore, metadata) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| if (!store) return false; | ||
| const currentMetadata = this.metadata.get(store); | ||
| this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name: typeof nameOrStore === "string" ? nameOrStore : currentMetadata?.name || "unknown", | ||
| ...currentMetadata, | ||
| ...metadata | ||
| }); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get registry snapshot for React integration | ||
| */ | ||
| getSnapshot() { | ||
| return this._snapshot; | ||
| } | ||
| /** | ||
| * Clear all stores from registry | ||
| */ | ||
| clear() { | ||
| this.stores.clear(); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Dispose registry and cleanup resources | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Get count of registered stores | ||
| */ | ||
| getStoreCount() { | ||
| return this.stores.size; | ||
| } | ||
| /** | ||
| * Iterate over all stores | ||
| */ | ||
| forEach(callback) { | ||
| this.stores.forEach((store, name) => { | ||
| callback(store, name); | ||
| }); | ||
| } | ||
| /** | ||
| * Get registry statistics | ||
| */ | ||
| getStats() { | ||
| return { | ||
| totalStores: this.stores.size, | ||
| storeNames: this.getStoreNames(), | ||
| registryName: this.name | ||
| }; | ||
| } | ||
| /** | ||
| * Update internal snapshot | ||
| */ | ||
| _updateSnapshot() { | ||
| this._snapshot = Array.from(this.stores.entries()); | ||
| } | ||
| /** | ||
| * Notify all listeners of registry changes | ||
| */ | ||
| _notifyListeners() { | ||
| this.listeners.forEach((listener) => { | ||
| try { | ||
| listener(); | ||
| } catch (error) { | ||
| console.error("Error in registry listener:", error); | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Global default registry instance | ||
| */ | ||
| const globalStoreRegistry = new StoreRegistry("global"); | ||
| //#endregion | ||
| //#region src/stores/utils/type-guards.ts | ||
| function isRefState(value) { | ||
| return typeof value === "object" && value !== null && "target" in value && "isReady" in value && "isMounted" in value && "mountPromise" in value && typeof value.isReady === "boolean" && typeof value.isMounted === "boolean"; | ||
| } | ||
| /** | ||
| * DOM Event 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMEvent(value) { | ||
| return value instanceof Event; | ||
| } | ||
| /** | ||
| * Event-like 객체인지 확인하는 타입 가드 (preventDefault 메서드를 가진 객체) | ||
| */ | ||
| function isEventLike(value) { | ||
| return typeof value === "object" && value !== null && typeof value.preventDefault === "function"; | ||
| } | ||
| /** | ||
| * target 프로퍼티를 가진 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function hasTargetProperty(value) { | ||
| return typeof value === "object" && value !== null && "target" in value; | ||
| } | ||
| /** | ||
| * DOM Element인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMElement(value) { | ||
| return value instanceof Element; | ||
| } | ||
| /** | ||
| * 객체인지 확인하는 타입 가드 (null 제외) | ||
| */ | ||
| function isObject(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| } | ||
| /** | ||
| * 복합 타입 가드: Event 객체로 의심되는 객체인지 확인 | ||
| * RefState가 아니면서 Event 관련 속성을 가진 객체를 감지 | ||
| */ | ||
| function isSuspiciousEventObject(value, checkNested = true) { | ||
| if (!isObject(value) || isRefState(value)) return false; | ||
| if (isEventLikeObject(value)) return true; | ||
| if (checkNested) { | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const nestedValue = value[key]; | ||
| if (isEventLikeObject(nestedValue)) return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| /** | ||
| * 단일 객체가 이벤트와 같은지 확인 | ||
| */ | ||
| function isEventLikeObject(value) { | ||
| if (!isObject(value)) return false; | ||
| const hasEventTarget = hasTargetProperty(value); | ||
| const hasPreventDefault = isEventLike(value); | ||
| const isEvent = isDOMEvent(value); | ||
| const hasEventProperties = "type" in value && typeof value.type === "string" && (hasEventTarget || hasPreventDefault); | ||
| const hasReactMarkers = "nativeEvent" in value || "persist" in value || "$$typeof" in value || "_reactInternalFiber" in value || "_owner" in value; | ||
| const constructorName = value?.constructor?.name; | ||
| const hasEventConstructor = constructorName ? constructorName.includes("Event") || constructorName === "SyntheticEvent" || constructorName.includes("MouseEvent") || constructorName.includes("KeyboardEvent") || constructorName.includes("TouchEvent") || constructorName.includes("FocusEvent") || constructorName.includes("SubmitEvent") : false; | ||
| return isEvent || hasEventProperties || hasReactMarkers || hasEventConstructor; | ||
| } | ||
| /** | ||
| * 문제가 될 수 있는 속성들을 찾아내는 함수 | ||
| */ | ||
| function findProblematicProperties(value) { | ||
| if (!isObject(value)) return []; | ||
| const problematicKeys = []; | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const prop = value[key]; | ||
| if (isDOMElement(prop) || isDOMEvent(prop) || isObject(prop) && hasTargetProperty(prop)) problematicKeys.push(key); | ||
| } | ||
| return problematicKeys; | ||
| } | ||
| /** | ||
| * 통합 타입 가드 객체 | ||
| * 모든 타입 가드 함수들을 하나의 객체로 export | ||
| */ | ||
| const TypeGuards = { | ||
| isRefState, | ||
| isDOMEvent, | ||
| isEventLike, | ||
| hasTargetProperty, | ||
| isDOMElement, | ||
| isObject, | ||
| isSuspiciousEventObject, | ||
| findProblematicProperties | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/core/Store.ts | ||
| /** | ||
| * Core Store class for centralized state management with memory leak prevention | ||
| * | ||
| * Provides reactive state management with subscription capabilities, optimized for | ||
| * React integration through useSyncExternalStore. Features advanced cleanup mechanisms, | ||
| * automatic resource management, and comprehensive memory leak prevention. | ||
| * | ||
| * Key Features: | ||
| * - Automatic cleanup task registration and execution | ||
| * - Memory leak prevention with disposal patterns | ||
| * - Race condition protection for async operations | ||
| * - Advanced error recovery with exponential backoff | ||
| * - Resource monitoring and threshold management | ||
| * | ||
| * @template T - The type of value stored in this store | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const userStore = createStore('user', { name: '', age: 0 }); | ||
| * | ||
| * // Register cleanup tasks | ||
| * const unregister = userStore.registerCleanup(() => { | ||
| * console.log('Cleaning up user store resources'); | ||
| * }); | ||
| * | ||
| * // Automatic cleanup on disposal | ||
| * userStore.dispose(); | ||
| * ``` | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * @public | ||
| */ | ||
| var Store = class { | ||
| constructor(name, initialValue) { | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._lastClonedValue = null; | ||
| this._lastClonedVersion = 0; | ||
| this._version = 0; | ||
| this.isUpdating = false; | ||
| this.updateQueue = []; | ||
| this.notificationMode = "batched"; | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| this.pendingUpdatesCount = 0; | ||
| this.cleanupTasks = /* @__PURE__ */ new Set(); | ||
| this.isDisposed = false; | ||
| this.errorCount = 0; | ||
| this.lastErrorTime = 0; | ||
| this.MAX_ERROR_COUNT = 5; | ||
| this.ERROR_RESET_TIME = 6e4; | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.cloningEnabled = true; | ||
| this.subscribe = (listener) => { | ||
| if (this.isDisposed) { | ||
| console.warn(`Cannot subscribe to disposed store "${this.name}"`); | ||
| return () => {}; | ||
| } | ||
| const enhancedListener = () => { | ||
| if (this.isDisposed) return; | ||
| try { | ||
| listener(); | ||
| if (this.errorCount > 0) this.errorCount = 0; | ||
| } catch (error) { | ||
| this._handleListenerError(error, listener); | ||
| } | ||
| }; | ||
| this.subscriptionRegistry.set(listener, { | ||
| subscribedAt: Date.now(), | ||
| errorCount: 0, | ||
| enhancedListener | ||
| }); | ||
| this.listeners.add(enhancedListener); | ||
| return () => { | ||
| this.listeners.delete(enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| }; | ||
| }; | ||
| this.getSnapshot = () => { | ||
| return this._snapshot; | ||
| }; | ||
| this.name = name; | ||
| this._value = initialValue; | ||
| this._snapshot = this._createSnapshot(); | ||
| } | ||
| /** | ||
| * 현재 값 직접 가져오기 (액션 핸들러용) | ||
| * 핵심 로직: 불변성을 보장하는 깊은 복사본 반환 | ||
| * | ||
| * @implements lazy-evaluation | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 사용 시나리오: Action handler에서 최신 상태 읽기 | ||
| * 보안 강화: 외부에서 반환된 값을 수정해도 Store 내부 상태는 보호됨 | ||
| */ | ||
| getValue() { | ||
| if (typeof this._value !== "object" || this._value === null) return this._value; | ||
| if (this.cloningEnabled) { | ||
| if (this._lastClonedVersion === this._version && this._lastClonedValue !== null) return this._lastClonedValue; | ||
| this._lastClonedValue = safeGet(this._value, this.cloningEnabled); | ||
| this._lastClonedVersion = this._version; | ||
| return this._lastClonedValue; | ||
| } | ||
| return this._value; | ||
| } | ||
| /** | ||
| * Store 값 설정 및 구독자 알림 | ||
| * 핵심 로직: | ||
| * 1. 입력값의 불변성 보장을 위한 깊은 복사 (선택적 skip 가능) | ||
| * 2. 강화된 값 비교 시스템으로 불필요한 리렌더링 방지 | ||
| * 3. Structural sharing을 통한 성능 최적화 | ||
| * 4. 값 변경 시에만 스냅샷 재생성 및 알림 | ||
| * | ||
| * @implements unidirectional-data-flow | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 보안 강화: 입력값을 복사하여 Store 내부 상태가 외부 참조에 의해 변경되지 않도록 보호 | ||
| * 성능 강화: 다층 비교 시스템으로 정확한 변경 감지 및 렌더링 최적화 | ||
| */ | ||
| setValue(value, options) { | ||
| if (TypeGuards.isObject(value)) { | ||
| if (!TypeGuards.isRefState(value) && TypeGuards.isSuspiciousEventObject(value)) { | ||
| const eventHandling = options?.eventHandling || "block"; | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(value); | ||
| const hasPreventDefault = TypeGuards.isEventLike(value); | ||
| const isEvent = TypeGuards.isDOMEvent(value); | ||
| switch (eventHandling) { | ||
| case "allow": break; | ||
| case "transform": | ||
| if (options?.eventTransform) try { | ||
| value = options.eventTransform(value); | ||
| } catch (error) { | ||
| ErrorHandlers.store("Event transformation failed in Store.setValue", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }); | ||
| return; | ||
| } | ||
| else { | ||
| ErrorHandlers.store("Event transformation requested but no transform function provided", { | ||
| storeName: this.name, | ||
| valueType: typeof value | ||
| }); | ||
| return; | ||
| } | ||
| break; | ||
| case "block": | ||
| default: | ||
| ErrorHandlers.store("Event object detected in Store.setValue - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| constructorName: value?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(value) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| const safeValue = options?.skipClone ? value : safeSet(value, this.cloningEnabled); | ||
| let hasChanged = true; | ||
| if (!options?.skipComparison) hasChanged = this._compareValues(this._value, safeValue); | ||
| if (hasChanged) { | ||
| this._value = safeValue; | ||
| this._version++; | ||
| this._snapshot = this._createSnapshot(); | ||
| this._scheduleNotification(); | ||
| } | ||
| } | ||
| /** | ||
| * Update value using updater function with Immer integration | ||
| * 핵심 로직: | ||
| * 1. Immer produce를 사용하여 draft 객체 제공 | ||
| * 2. updater 결과를 불변성을 보장하며 설정 | ||
| * | ||
| * @implements store-immutability | ||
| * 보안 강화: Immer draft를 통한 안전한 상태 수정 | ||
| */ | ||
| update(updater) { | ||
| if (this.isUpdating) { | ||
| this.updateQueue.push(() => this.update(updater)); | ||
| return; | ||
| } | ||
| try { | ||
| this.isUpdating = true; | ||
| let updatedValue; | ||
| try { | ||
| updatedValue = produce(this._value, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (immerError) { | ||
| console.warn("[Store] Immer update failed, falling back to safe copy method", immerError); | ||
| const safeCurrentValue = safeGet(this._value, this.cloningEnabled); | ||
| try { | ||
| updatedValue = produce(safeCurrentValue, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (secondImmerError) { | ||
| console.warn("[Store] Immer completely failed, using direct update (immutability not guaranteed)", secondImmerError); | ||
| updatedValue = updater(safeCurrentValue); | ||
| } | ||
| } | ||
| if (TypeGuards.isObject(updatedValue)) { | ||
| if (!TypeGuards.isRefState(updatedValue) && TypeGuards.isSuspiciousEventObject(updatedValue)) { | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(updatedValue); | ||
| const hasPreventDefault = TypeGuards.isEventLike(updatedValue); | ||
| const isEvent = TypeGuards.isDOMEvent(updatedValue); | ||
| ErrorHandlers.store("Event object detected in Store.update result - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| updatedValueType: typeof updatedValue, | ||
| constructorName: updatedValue?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(updatedValue) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| this.setValue(updatedValue); | ||
| } finally { | ||
| this.isUpdating = false; | ||
| if (this.updateQueue.length > 0) { | ||
| const nextUpdate = this.updateQueue.shift(); | ||
| if (nextUpdate) Promise.resolve().then(nextUpdate); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Get number of active listeners | ||
| */ | ||
| getListenerCount() { | ||
| return this.listeners.size; | ||
| } | ||
| /** | ||
| * Clear all listeners | ||
| */ | ||
| clearListeners() { | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Register cleanup task for automatic execution on disposal | ||
| * | ||
| * Registers a cleanup function that will be automatically called when the store | ||
| * is disposed. This prevents memory leaks and ensures proper resource cleanup. | ||
| * | ||
| * @param task - Cleanup function to register | ||
| * @returns Unregister function to remove the cleanup task | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const timer = setInterval(() => {}, 1000); | ||
| * const unregister = store.registerCleanup(() => clearInterval(timer)); | ||
| * | ||
| * // Later, remove the cleanup task if needed | ||
| * unregister(); | ||
| * ``` | ||
| */ | ||
| registerCleanup(task) { | ||
| if (this.isDisposed) { | ||
| console.warn(`Store "${this.name}" is already disposed, cleanup task ignored`); | ||
| return () => {}; | ||
| } | ||
| this.cleanupTasks.add(task); | ||
| return () => this.cleanupTasks.delete(task); | ||
| } | ||
| /** | ||
| * Enhanced Store disposal with comprehensive cleanup | ||
| * | ||
| * Performs complete cleanup of all store resources including listeners, | ||
| * timers, cleanup tasks, and internal state. Prevents memory leaks and | ||
| * ensures proper resource disposal. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Manual disposal | ||
| * store.dispose(); | ||
| * | ||
| * // Auto-disposal with useEffect | ||
| * useEffect(() => { | ||
| * return () => store.dispose(); | ||
| * }, [store]); | ||
| * ``` | ||
| */ | ||
| dispose() { | ||
| if (this.isDisposed) return; | ||
| this.isDisposed = true; | ||
| try { | ||
| this.cleanupTasks.forEach((task) => { | ||
| try { | ||
| task(); | ||
| } catch (error) { | ||
| ErrorHandlers.store("Error during cleanup task execution", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| }); | ||
| this.cleanupTasks.clear(); | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.clearListeners(); | ||
| if (this.animationFrameId !== null) { | ||
| cancelAnimationFrame(this.animationFrameId); | ||
| this.animationFrameId = null; | ||
| } | ||
| this.pendingNotification = false; | ||
| this.updateQueue.length = 0; | ||
| } catch (error) { | ||
| ErrorHandlers.store("Critical error during store disposal", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| } | ||
| /** | ||
| * Check if store is disposed | ||
| * @returns true if store has been disposed | ||
| */ | ||
| isStoreDisposed() { | ||
| return this.isDisposed; | ||
| } | ||
| /** | ||
| * Store별 커스텀 비교 함수 설정 | ||
| * 이 Store에만 적용되는 특별한 비교 로직 설정 | ||
| * | ||
| * @param comparator - 커스텀 비교 함수 (oldValue, newValue) => boolean | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setCustomComparator(comparator) { | ||
| this.customComparator = comparator; | ||
| } | ||
| /** | ||
| * Store별 비교 옵션 설정 | ||
| * 이 Store에만 적용되는 비교 전략 설정 | ||
| * | ||
| * @param options - 비교 옵션 | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setComparisonOptions(options) { | ||
| this.comparisonOptions = options; | ||
| } | ||
| /** | ||
| * 성능 최적화: Store별 복사 동작 제어 | ||
| * | ||
| * @param enabled - true: 복사 활성화 (안전), false: 복사 비활성화 (성능 우선) | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/performance | ||
| */ | ||
| setCloningEnabled(enabled) { | ||
| this.cloningEnabled = enabled; | ||
| } | ||
| /** | ||
| * 현재 복사 설정 조회 | ||
| */ | ||
| isCloningEnabled() { | ||
| return this.cloningEnabled; | ||
| } | ||
| /** | ||
| * 강화된 값 비교 시스템 | ||
| * 1. 커스텀 비교 함수 우선 사용 | ||
| * 2. Store별 비교 옵션 적용 | ||
| * 3. 성능 최적화된 빠른 비교 fallback | ||
| * 4. 전역 비교 설정 사용 | ||
| * | ||
| * @param oldValue - 이전 값 | ||
| * @param newValue - 새로운 값 | ||
| * @returns true if values are different (change detected), false if same | ||
| * @protected | ||
| */ | ||
| _compareValues(oldValue, newValue) { | ||
| let result; | ||
| try { | ||
| if (this.customComparator) result = !this.customComparator(oldValue, newValue); | ||
| else if (this.comparisonOptions) result = !compareValues(oldValue, newValue, this.comparisonOptions); | ||
| else result = !compareValues(oldValue, newValue, { strategy: "reference" }); | ||
| } catch (error) { | ||
| ErrorHandlers.store("Error during value comparison, falling back to reference comparison", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| result = !Object.is(oldValue, newValue); | ||
| } | ||
| return result; | ||
| } | ||
| _createSnapshot() { | ||
| return { | ||
| value: safeGet(this._value, this.cloningEnabled), | ||
| name: this.name, | ||
| lastUpdate: Date.now() | ||
| }; | ||
| } | ||
| /** | ||
| * requestAnimationFrame 기반 알림 스케줄링 | ||
| * 브라우저의 다음 프레임에서 리스너 알림 실행 | ||
| */ | ||
| _scheduleNotification() { | ||
| if (this.notificationMode === "immediate") this._notifyListeners(); | ||
| else this._scheduleWithRAF(); | ||
| } | ||
| /** | ||
| * requestAnimationFrame을 사용한 알림 스케줄링 | ||
| * 누적 가능한 배치 시스템으로 개선 | ||
| */ | ||
| _scheduleWithRAF() { | ||
| this.pendingUpdatesCount++; | ||
| if (!this.pendingNotification) { | ||
| this.pendingNotification = true; | ||
| this.animationFrameId = requestAnimationFrame(() => { | ||
| this._executeNotification(); | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * 스케줄된 알림 실행 | ||
| */ | ||
| _executeNotification() { | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| const batchedUpdates = this.pendingUpdatesCount; | ||
| this.pendingUpdatesCount = 0; | ||
| this._notifyListeners(); | ||
| if (batchedUpdates > 1) console.debug(`[Store:${this.name}] Batched ${batchedUpdates} updates in single frame`); | ||
| } | ||
| /** | ||
| * Handle listener execution errors with recovery strategies | ||
| */ | ||
| _handleListenerError(error, listener) { | ||
| const now = Date.now(); | ||
| if (now - this.lastErrorTime > this.ERROR_RESET_TIME) this.errorCount = 0; | ||
| this.errorCount++; | ||
| this.lastErrorTime = now; | ||
| const metadata = this.subscriptionRegistry.get(listener); | ||
| if (metadata) metadata.errorCount++; | ||
| ErrorHandlers.store("Error in store listener execution", { | ||
| storeName: this.name, | ||
| listenerCount: this.listeners.size, | ||
| errorCount: this.errorCount, | ||
| subscriptionAge: metadata ? now - metadata.subscribedAt : "unknown" | ||
| }, error instanceof Error ? error : void 0); | ||
| if (metadata && metadata.errorCount >= 3) { | ||
| console.warn(`Removing problematic listener from store "${this.name}" after ${metadata.errorCount} errors`); | ||
| this.listeners.delete(metadata.enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| } | ||
| if (this.errorCount >= this.MAX_ERROR_COUNT) { | ||
| console.error(`Store "${this.name}" disabled due to excessive errors (${this.errorCount})`); | ||
| this.clearListeners(); | ||
| } | ||
| } | ||
| _notifyListeners() { | ||
| if (this.isDisposed) return; | ||
| this.listeners.forEach((listener) => { | ||
| if (this.isDisposed) return; | ||
| listener(); | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Factory function for creating type-safe Store instances | ||
| * | ||
| * Creates a new Store instance with the specified name and initial value. | ||
| * Provides type safety and integrates seamlessly with React hooks and | ||
| * the Context-Action framework patterns. | ||
| * | ||
| * @template T - The type of values stored in this store | ||
| * | ||
| * @param name - Unique identifier for the store (used for debugging) | ||
| * @param initialValue - Initial value to store | ||
| * | ||
| * @returns Configured Store instance ready for use | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function createStore(name, initialValue) { | ||
| return new Store(name, initialValue); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/utils/sync-external-store-utils.ts | ||
| /** | ||
| * 향상된 구독 함수 생성 (내부 사용) | ||
| * 디바운싱, 스로틀링, 조건부 구독 기능 제공 | ||
| */ | ||
| function createEnhancedSubscriber(store, options = {}) { | ||
| const { debounce, throttle, condition, debug, name = "unknown" } = options; | ||
| return (callback) => { | ||
| if (!store) return () => {}; | ||
| let debounceTimer = null; | ||
| let throttleTimer = null; | ||
| let lastThrottleTime = 0; | ||
| const enhancedCallback = () => { | ||
| if (condition && !condition()) { | ||
| if (debug) console.debug(`[${name}] Subscription suspended due to condition`); | ||
| return; | ||
| } | ||
| const now = performance.now(); | ||
| if (throttle && throttle > 0) { | ||
| if (now - lastThrottleTime < throttle) { | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| throttleTimer = setTimeout(() => { | ||
| lastThrottleTime = performance.now(); | ||
| callback(); | ||
| }, throttle - (now - lastThrottleTime)); | ||
| return; | ||
| } | ||
| lastThrottleTime = now; | ||
| } | ||
| if (debounce && debounce > 0) { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| debounceTimer = setTimeout(() => { | ||
| callback(); | ||
| if (debug) console.debug(`[${name}] Debounced callback executed after ${debounce}ms`); | ||
| }, debounce); | ||
| return; | ||
| } | ||
| callback(); | ||
| }; | ||
| const unsubscribe = store.subscribe(enhancedCallback); | ||
| return () => { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| unsubscribe(); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Null-safe Store 구독 훅 | ||
| * useSyncExternalStore를 기반으로 한 안전한 구독 | ||
| */ | ||
| function useSafeStoreSubscription(store, selector, options = {}) { | ||
| const { initialValue, equalityFn, debounce, throttle, condition, debug, name } = options; | ||
| const stableSubscriptionOptions = useMemo(() => ({ | ||
| debounce, | ||
| throttle, | ||
| condition, | ||
| debug, | ||
| name | ||
| }), [ | ||
| debounce, | ||
| throttle, | ||
| condition, | ||
| debug, | ||
| name | ||
| ]); | ||
| const needsEnhancedSubscription = Boolean(debounce || throttle || condition); | ||
| const subscribe = useCallback((callback) => { | ||
| if (!store) return () => {}; | ||
| if (needsEnhancedSubscription) return createEnhancedSubscriber(store, stableSubscriptionOptions)(callback); | ||
| return store.subscribe(callback); | ||
| }, [ | ||
| store, | ||
| needsEnhancedSubscription, | ||
| stableSubscriptionOptions | ||
| ]); | ||
| const getSnapshot = useCallback(() => { | ||
| if (!store) return initialValue; | ||
| const snapshot = store.getSnapshot(); | ||
| return selector ? selector(snapshot.value) : snapshot.value; | ||
| }, [ | ||
| store, | ||
| selector, | ||
| initialValue | ||
| ]); | ||
| const cachedSnapshotRef = useRef(); | ||
| const stableGetSnapshot = useCallback(() => { | ||
| const currentSnapshot = getSnapshot(); | ||
| if (equalityFn && cachedSnapshotRef.current !== void 0) { | ||
| if (equalityFn(cachedSnapshotRef.current, currentSnapshot)) return cachedSnapshotRef.current; | ||
| } | ||
| cachedSnapshotRef.current = currentSnapshot; | ||
| return currentSnapshot; | ||
| }, [getSnapshot, equalityFn]); | ||
| const getServerSnapshot = useCallback(() => { | ||
| return initialValue; | ||
| }, [initialValue]); | ||
| return useSyncExternalStore(subscribe, equalityFn ? stableGetSnapshot : getSnapshot, getServerSnapshot); | ||
| } | ||
| /** | ||
| * 기본 동등성 비교 함수들 | ||
| */ | ||
| const equalityFunctions = { | ||
| reference: (a, b) => Object.is(a, b), | ||
| shallow: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) return false; | ||
| return true; | ||
| }, | ||
| deep: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) !== Array.isArray(b)) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) { | ||
| if (!Object.prototype.hasOwnProperty.call(b, key)) return false; | ||
| if (!equalityFunctions.deep(a[key], b[key])) return false; | ||
| } | ||
| return true; | ||
| }, | ||
| smart: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) && Array.isArray(b)) { | ||
| if (a.length !== b.length) return false; | ||
| return a.every((item, index) => { | ||
| const bItem = b[index]; | ||
| if (typeof item === "object" && item !== null && typeof bItem === "object" && bItem !== null) return equalityFunctions.shallow(item, bItem); | ||
| return Object.is(item, bItem); | ||
| }); | ||
| } | ||
| return equalityFunctions.shallow(a, b); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/hooks/useStoreSelector.ts | ||
| const defaultEqualityFn = equalityFunctions.smart; | ||
| const shallowEqual = equalityFunctions.shallow; | ||
| const deepEqual = equalityFunctions.deep; | ||
| const smartEqual = equalityFunctions.smart; | ||
| /** | ||
| * Hook for selective store subscription with performance optimization | ||
| * | ||
| * Subscribes to specific parts of store data using a selector function, | ||
| * triggering re-renders only when the selected value actually changes. | ||
| * Essential for preventing unnecessary re-renders in complex applications. | ||
| * | ||
| * @template T - Type of the store value | ||
| * @template R - Type of the value returned by the selector | ||
| * | ||
| * @param store - Store instance to subscribe to | ||
| * @param selector - Function to extract needed data from store value | ||
| * @param equalityFn - Function to compare previous and new values (default: Object.is) | ||
| * | ||
| * @returns The value returned by the selector function | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-hooks#usestoreselector-advanced-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function useStoreSelector(store, selector, equalityFn = defaultEqualityFn) { | ||
| "use memo"; | ||
| const stableSelector = useCallback(selector, [selector]); | ||
| const stableEqualityFn = useCallback(equalityFn, [equalityFn]); | ||
| const selectorWarningShownRef = useRef(false); | ||
| if (selector !== stableSelector && !selectorWarningShownRef.current) { | ||
| console.warn("useStoreSelector: selector function changed. Consider wrapping it with useCallback to avoid unnecessary recalculations.", "Store:", store.name); | ||
| selectorWarningShownRef.current = true; | ||
| } | ||
| const previousValueRef = useRef(); | ||
| const subscribe = useCallback((callback) => { | ||
| return store.subscribe(callback); | ||
| }, [store]); | ||
| const getSnapshot = useCallback(() => { | ||
| try { | ||
| const selectedValue = stableSelector(store.getValue()); | ||
| if (previousValueRef.current !== void 0 && stableEqualityFn(previousValueRef.current, selectedValue)) return previousValueRef.current; | ||
| previousValueRef.current = selectedValue; | ||
| return selectedValue; | ||
| } catch (error) { | ||
| console.error("useStoreSelector: Error in selector function:", error); | ||
| throw error; | ||
| } | ||
| }, [ | ||
| store, | ||
| stableSelector, | ||
| stableEqualityFn | ||
| ]); | ||
| return useSyncExternalStore(subscribe, getSnapshot, getSnapshot); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/components/StoreErrorBoundary.tsx | ||
| /** | ||
| * Store 시스템을 위한 에러 경계 컴포넌트 | ||
| * | ||
| * Store 관련 에러들을 캐치하고 적절한 fallback UI를 제공합니다. | ||
| * 개발 모드에서는 자세한 에러 정보를 표시하고, 프로덕션에서는 | ||
| * 사용자 친화적인 메시지를 보여줍니다. | ||
| */ | ||
| var StoreErrorBoundary = class extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.resetTimeoutId = null; | ||
| this.resetErrorBoundary = () => { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| this.setState({ | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }); | ||
| }; | ||
| this.state = { | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }; | ||
| } | ||
| static getDerivedStateFromError(error) { | ||
| return { | ||
| hasError: true, | ||
| error: error instanceof Error && error.name === "ContextActionError" ? error : null, | ||
| errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` | ||
| }; | ||
| } | ||
| componentDidCatch(error, errorInfo) { | ||
| if (error.name === "ContextActionError") { | ||
| const contextActionError = error; | ||
| this.setState({ errorInfo }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } else { | ||
| const contextActionError = ErrorHandlers.store(`Unhandled error in Store component: ${error.message}`, { | ||
| component: "StoreErrorBoundary", | ||
| stack: error.stack, | ||
| componentStack: errorInfo.componentStack | ||
| }, error); | ||
| this.setState({ | ||
| error: contextActionError, | ||
| errorInfo | ||
| }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| const { hasError } = this.state; | ||
| const { resetOnPropsChange, resetKeys } = this.props; | ||
| if (hasError && resetOnPropsChange) { | ||
| if (resetKeys) { | ||
| if (resetKeys.some((key) => { | ||
| return prevProps[key] !== this.props[key]; | ||
| })) this.resetErrorBoundary(); | ||
| } else if (prevProps !== this.props) this.resetErrorBoundary(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| } | ||
| render() { | ||
| const { hasError, error, errorInfo } = this.state; | ||
| const { children, fallback } = this.props; | ||
| if (hasError) { | ||
| if (fallback) { | ||
| if (typeof fallback === "function") return fallback(error, errorInfo); | ||
| return fallback; | ||
| } | ||
| return this.renderDefaultFallback(); | ||
| } | ||
| return children; | ||
| } | ||
| renderDefaultFallback() { | ||
| return this.renderDevelopmentFallback(); | ||
| } | ||
| renderDevelopmentFallback() { | ||
| const { error, errorInfo, errorId } = this.state; | ||
| const stats = getErrorStatistics(); | ||
| return /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| padding: "20px", | ||
| margin: "20px", | ||
| border: "2px solid #ff6b6b", | ||
| borderRadius: "8px", | ||
| backgroundColor: "#ffe0e0", | ||
| fontFamily: "monospace" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("h2", { | ||
| style: { | ||
| color: "#d63031", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "🚨 Store Error Boundary" | ||
| }), | ||
| /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("strong", { children: "Error ID:" }), | ||
| " ", | ||
| errorId | ||
| ] | ||
| }), | ||
| error && /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("strong", { children: "Error Type:" }), | ||
| " ", | ||
| error.type, | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| /* @__PURE__ */ jsx("strong", { children: "Message:" }), | ||
| " ", | ||
| error.message, | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| /* @__PURE__ */ jsx("strong", { children: "Timestamp:" }), | ||
| " ", | ||
| new Date(error.timestamp).toISOString() | ||
| ] | ||
| }), | ||
| error?.context && /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ jsx("strong", { children: "Context:" }), /* @__PURE__ */ jsx("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "12px" | ||
| }, | ||
| children: JSON.stringify(error.context, null, 2) | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("strong", { children: "Error Statistics:" }), | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| "Total Errors: ", | ||
| stats.totalErrors, | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| "Recent Errors: ", | ||
| stats.recentErrors.length | ||
| ] | ||
| }), | ||
| errorInfo && /* @__PURE__ */ jsxs("details", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ jsx("summary", { | ||
| style: { | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Component Stack" | ||
| }), /* @__PURE__ */ jsx("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "11px", | ||
| whiteSpace: "pre-wrap" | ||
| }, | ||
| children: errorInfo.componentStack | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ jsx("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#00b894", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| renderProductionFallback() { | ||
| return /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| padding: "20px", | ||
| textAlign: "center", | ||
| backgroundColor: "#f8f9fa", | ||
| border: "1px solid #dee2e6", | ||
| borderRadius: "8px", | ||
| margin: "20px 0" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("h3", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "Something went wrong" | ||
| }), | ||
| /* @__PURE__ */ jsx("p", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 15px 0" | ||
| }, | ||
| children: "We're sorry, but something unexpected happened. Please try again." | ||
| }), | ||
| /* @__PURE__ */ jsx("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#007bff", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Enhanced HOC for automatic Store Error Boundary wrapping with security | ||
| * | ||
| * @template P - Component props type | ||
| * @param WrappedComponent - Component to wrap with error boundary | ||
| * @param errorBoundaryProps - Error boundary configuration | ||
| * @returns Enhanced component with comprehensive error handling | ||
| */ | ||
| function withStoreErrorBoundary(WrappedComponent, errorBoundaryProps) { | ||
| const WithStoreErrorBoundaryComponent = (props) => /* @__PURE__ */ jsx(StoreErrorBoundary, { | ||
| ...errorBoundaryProps, | ||
| children: /* @__PURE__ */ jsx(WrappedComponent, { ...props }) | ||
| }); | ||
| WithStoreErrorBoundaryComponent.displayName = `withStoreErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name})`; | ||
| return WithStoreErrorBoundaryComponent; | ||
| } | ||
| /** | ||
| * 특정 Store와 연결된 에러 경계 생성 헬퍼 | ||
| */ | ||
| function createStoreErrorBoundary(storeName, customFallback) { | ||
| return ({ children }) => /* @__PURE__ */ jsx(StoreErrorBoundary, { | ||
| fallback: customFallback, | ||
| onError: (error, errorInfo) => { | ||
| console.group(`Store Error in ${storeName}`); | ||
| console.error("Error:", error); | ||
| console.error("Component Stack:", errorInfo.componentStack); | ||
| console.groupEnd(); | ||
| }, | ||
| resetKeys: [storeName], | ||
| children | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/actions/ActionContext.tsx | ||
| function createActionContext(contextNameOrConfig = {}, config) { | ||
| let effectiveConfig; | ||
| let contextName; | ||
| if (typeof contextNameOrConfig === "string") { | ||
| contextName = contextNameOrConfig; | ||
| effectiveConfig = { | ||
| ...config, | ||
| name: config?.name || contextName | ||
| }; | ||
| } else { | ||
| effectiveConfig = contextNameOrConfig; | ||
| contextName = effectiveConfig.name || "ActionContext"; | ||
| } | ||
| const FactoryActionContext = createContext(null); | ||
| const Provider = ({ children }) => { | ||
| const actionRegisterRef = useRef(new ActionRegister(effectiveConfig)); | ||
| const contextValue = useMemo(() => ({ actionRegisterRef }), []); | ||
| return /* @__PURE__ */ jsx(FactoryActionContext.Provider, { | ||
| value: contextValue, | ||
| children | ||
| }); | ||
| }; | ||
| const useFactoryActionContext = () => { | ||
| const context = useContext(FactoryActionContext); | ||
| if (!context) throw new Error("useFactoryActionContext must be used within a factory ActionContext Provider"); | ||
| return context; | ||
| }; | ||
| /** | ||
| * Optimized hook to get stable dispatch functions | ||
| * | ||
| * Returns stable dispatch functions that prevent re-renders and maintain | ||
| * reference equality across component renders. | ||
| * | ||
| * @returns Object with dispatch and dispatchWithResult functions | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useActionDispatcher = () => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| return { | ||
| dispatch: useCallback((action, payload, options) => { | ||
| console.log(`React dispatch called for '${String(action)}':`, { | ||
| hasPayload: payload !== void 0, | ||
| hasOptions: options !== void 0, | ||
| timestamp: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister is not initialized. Make sure the ActionContext Provider is properly set up."); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]), | ||
| dispatchWithResult: useCallback((action, payload, options) => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]) | ||
| }; | ||
| }; | ||
| const useAction = () => { | ||
| const { dispatch } = useActionDispatcher(); | ||
| return dispatch; | ||
| }; | ||
| const useActionHandler = (action, handler, config$1) => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| const actionId = useId(); | ||
| const handlerRef = useRef(handler); | ||
| handlerRef.current = handler; | ||
| const priority = config$1?.priority ?? 0; | ||
| const id = config$1?.id || `react_${String(action)}_${actionId}`; | ||
| const blocking = config$1?.blocking ?? false; | ||
| const once = config$1?.once ?? false; | ||
| const debounce = config$1?.debounce; | ||
| const throttle = config$1?.throttle; | ||
| const stableConfig = useMemo(() => ({ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| replaceExisting: true, | ||
| ...debounce !== void 0 && { debounce }, | ||
| ...throttle !== void 0 && { throttle } | ||
| }), [ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| debounce, | ||
| throttle | ||
| ]); | ||
| useEffect(() => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) return; | ||
| const wrapperHandler = (payload, controller) => { | ||
| return handlerRef.current(payload, controller); | ||
| }; | ||
| console.log(`Registering handler for '${String(action)}'`); | ||
| return register.register(action, wrapperHandler, stableConfig); | ||
| }, [ | ||
| action, | ||
| actionRegisterRef, | ||
| stableConfig | ||
| ]); | ||
| }; | ||
| /** | ||
| * Hook that provides direct access to the ActionRegister instance | ||
| * | ||
| * This hook is useful when you need to: | ||
| * - Register multiple handlers dynamically | ||
| * - Access other ActionRegister methods like clearAction, getHandlers, etc. | ||
| * - Implement complex handler registration logic | ||
| * - Have more control over the registration lifecycle | ||
| * | ||
| * @returns ActionRegister instance or null if not initialized | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useFactoryActionRegister = () => { | ||
| return useFactoryActionContext().actionRegisterRef.current; | ||
| }; | ||
| /** | ||
| * Hook that provides access to the dispatchWithResult function | ||
| * | ||
| * This hook returns a function that dispatches actions and returns detailed | ||
| * execution results including collected handler results, execution metadata, | ||
| * and error information. | ||
| * | ||
| * @returns dispatchWithResult function with full type safety | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-with-result | ||
| */ | ||
| const useFactoryActionDispatchWithResult = () => { | ||
| const context = useFactoryActionContext(); | ||
| const activeControllersRef = useRef(/* @__PURE__ */ new Set()); | ||
| const dispatch = useCallback((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| let createdController; | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| createdController = controller; | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions).finally(() => { | ||
| if (createdController) activeControllersRef.current.delete(createdController); | ||
| }); | ||
| }, [context.actionRegisterRef]); | ||
| const dispatchWithResult = useCallback((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| let createdController; | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| createdController = controller; | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions).finally(() => { | ||
| if (createdController) activeControllersRef.current.delete(createdController); | ||
| }); | ||
| }, [context.actionRegisterRef]); | ||
| const abortAll = useCallback(() => { | ||
| activeControllersRef.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| activeControllersRef.current.clear(); | ||
| }, []); | ||
| const resetAbortScope = useCallback(() => { | ||
| abortAll(); | ||
| }, [abortAll]); | ||
| useEffect(() => { | ||
| const controllers = activeControllersRef; | ||
| return () => { | ||
| controllers.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| controllers.current.clear(); | ||
| }; | ||
| }, []); | ||
| return { | ||
| dispatch, | ||
| dispatchWithResult, | ||
| abortAll, | ||
| resetAbortScope | ||
| }; | ||
| }; | ||
| return { | ||
| Provider, | ||
| useActionContext: useFactoryActionContext, | ||
| useActionDispatch: useAction, | ||
| useActionHandler, | ||
| useActionRegister: useFactoryActionRegister, | ||
| useActionDispatchWithResult: useFactoryActionDispatchWithResult, | ||
| context: FactoryActionContext | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { Store, StoreErrorBoundary, StoreRegistry, createActionContext, createStore, createStoreErrorBoundary, defaultEqualityFn, useSafeStoreSubscription, useStoreSelector, withStoreErrorBoundary }; | ||
| //# sourceMappingURL=ActionContext-BIIFgZEq.js.map |
Sorry, the diff of this file is too big to display
| const require_error_handling = require('./error-handling-ibCwx4ki.cjs'); | ||
| const require_ActionContext = require('./ActionContext-B6V0n1wh.cjs'); | ||
| const require_ActionContext = require('./ActionContext-Bbkccg3d.cjs'); | ||
| const require_utils = require('./utils-ZdkTBqFK.cjs'); | ||
@@ -4,0 +4,0 @@ let react = require("react"); |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { StoreErrorBoundary, StoreRegistry, createActionContext, createStore, createStoreErrorBoundary, defaultEqualityFn, useSafeStoreSubscription, withStoreErrorBoundary } from "./ActionContext-BuVbb-bA.js"; | ||
| import { StoreErrorBoundary, StoreRegistry, createActionContext, createStore, createStoreErrorBoundary, defaultEqualityFn, useSafeStoreSubscription, withStoreErrorBoundary } from "./ActionContext-BIIFgZEq.js"; | ||
| import { ContextActionError, ContextActionErrorType, ImmerUtils, compareValues, deepClone, deepCloneWithImmer, getGlobalComparisonOptions, handleError, preloadImmer, produce, safeGet, safeSet, setGlobalComparisonOptions } from "./error-handling-CkdfKCZ0.js"; | ||
@@ -3,0 +3,0 @@ import { SubscriptionManager, TypeUtils, composeProviders, createSafeEqualityFn, createStoreConfig, extractStoreValue, extractStoreValues, isStore, isValidStoreValue, useSubscriptionManager } from "./utils-B_r1_2dw.js"; |
+1
-1
| const require_error_handling = require('./error-handling-ibCwx4ki.cjs'); | ||
| const require_ActionContext = require('./ActionContext-B6V0n1wh.cjs'); | ||
| const require_ActionContext = require('./ActionContext-Bbkccg3d.cjs'); | ||
| let react = require("react"); | ||
@@ -4,0 +4,0 @@ react = require_error_handling.__toESM(react); |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { Store, StoreErrorBoundary, StoreRegistry, createActionContext, createStore, defaultEqualityFn, useSafeStoreSubscription, useStoreSelector } from "./ActionContext-BuVbb-bA.js"; | ||
| import { Store, StoreErrorBoundary, StoreRegistry, createActionContext, createStore, defaultEqualityFn, useSafeStoreSubscription, useStoreSelector } from "./ActionContext-BIIFgZEq.js"; | ||
| import { ErrorHandlers } from "./error-handling-CkdfKCZ0.js"; | ||
@@ -3,0 +3,0 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Store--dj9U0mo.d.cts","names":[],"sources":["../src/stores/core/types.ts","../src/stores/core/StoreRegistry.ts","../src/stores/utils/comparison.ts","../src/stores/core/Store.ts"],"sourcesContent":[],"mappings":"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"} | ||
| {"version":3,"file":"Store--dj9U0mo.d.cts","names":[],"sources":["../src/stores/core/types.ts","../src/stores/core/StoreRegistry.ts","../src/stores/utils/comparison.ts","../src/stores/core/Store.ts"],"sourcesContent":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Store-CoQcuZnp.d.ts","names":[],"sources":["../src/stores/core/types.ts","../src/stores/core/StoreRegistry.ts","../src/stores/utils/comparison.ts","../src/stores/core/Store.ts"],"sourcesContent":[],"mappings":"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"} | ||
| {"version":3,"file":"Store-CoQcuZnp.d.ts","names":[],"sources":["../src/stores/core/types.ts","../src/stores/core/StoreRegistry.ts","../src/stores/utils/comparison.ts","../src/stores/core/Store.ts"],"sourcesContent":[],"mappings":"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"} |
+2
-2
| { | ||
| "name": "@context-action/react", | ||
| "version": "0.8.3", | ||
| "version": "0.8.4", | ||
| "type": "module", | ||
@@ -72,3 +72,3 @@ "description": "React integration for @context-action/core - Context and hooks for type-safe action management", | ||
| "dependencies": { | ||
| "@context-action/core": "^0.8.1", | ||
| "@context-action/core": "^0.8.4", | ||
| "immer": "^10.1.3", | ||
@@ -75,0 +75,0 @@ "react-compiler-runtime": "1.0.0" |
| const require_error_handling = require('./error-handling-ibCwx4ki.cjs'); | ||
| let react = require("react"); | ||
| react = require_error_handling.__toESM(react); | ||
| let react_jsx_runtime = require("react/jsx-runtime"); | ||
| react_jsx_runtime = require_error_handling.__toESM(react_jsx_runtime); | ||
| let __context_action_core = require("@context-action/core"); | ||
| __context_action_core = require_error_handling.__toESM(__context_action_core); | ||
| //#region src/stores/core/StoreRegistry.ts | ||
| /** | ||
| * Centralized store registry for managing multiple Store instances | ||
| */ | ||
| var StoreRegistry = class { | ||
| constructor(name = "default") { | ||
| this.stores = /* @__PURE__ */ new Map(); | ||
| this.metadata = /* @__PURE__ */ new WeakMap(); | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._snapshot = []; | ||
| this.name = name; | ||
| } | ||
| /** | ||
| * Subscribe to registry changes for reactive updates | ||
| */ | ||
| subscribe(listener) { | ||
| this.listeners.add(listener); | ||
| return () => { | ||
| this.listeners.delete(listener); | ||
| }; | ||
| } | ||
| /** | ||
| * Register a new store in the registry | ||
| */ | ||
| register(name, store, metadata) { | ||
| if (this.stores.has(name)) { | ||
| if (this.stores.get(name)) {} | ||
| } | ||
| this.stores.set(name, store); | ||
| if (metadata) this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name, | ||
| ...metadata | ||
| }); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Unregister a store from the registry | ||
| */ | ||
| unregister(name) { | ||
| if (!this.stores.get(name)) return false; | ||
| this.stores.delete(name); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get a store by name | ||
| */ | ||
| getStore(name) { | ||
| return this.stores.get(name); | ||
| } | ||
| /** | ||
| * Check if a store exists | ||
| */ | ||
| hasStore(name) { | ||
| return this.stores.has(name); | ||
| } | ||
| /** | ||
| * Get all registered store names | ||
| */ | ||
| getStoreNames() { | ||
| return Array.from(this.stores.keys()); | ||
| } | ||
| /** | ||
| * Get all stores as entries | ||
| */ | ||
| getAllStores() { | ||
| return new Map(this.stores); | ||
| } | ||
| /** | ||
| * Get store metadata | ||
| */ | ||
| getStoreMetadata(nameOrStore) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| return store ? this.metadata.get(store) : void 0; | ||
| } | ||
| /** | ||
| * Update store metadata | ||
| */ | ||
| updateStoreMetadata(nameOrStore, metadata) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| if (!store) return false; | ||
| const currentMetadata = this.metadata.get(store); | ||
| this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name: typeof nameOrStore === "string" ? nameOrStore : currentMetadata?.name || "unknown", | ||
| ...currentMetadata, | ||
| ...metadata | ||
| }); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get registry snapshot for React integration | ||
| */ | ||
| getSnapshot() { | ||
| return this._snapshot; | ||
| } | ||
| /** | ||
| * Clear all stores from registry | ||
| */ | ||
| clear() { | ||
| this.stores.clear(); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Dispose registry and cleanup resources | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Get count of registered stores | ||
| */ | ||
| getStoreCount() { | ||
| return this.stores.size; | ||
| } | ||
| /** | ||
| * Iterate over all stores | ||
| */ | ||
| forEach(callback) { | ||
| this.stores.forEach((store, name) => { | ||
| callback(store, name); | ||
| }); | ||
| } | ||
| /** | ||
| * Get registry statistics | ||
| */ | ||
| getStats() { | ||
| return { | ||
| totalStores: this.stores.size, | ||
| storeNames: this.getStoreNames(), | ||
| registryName: this.name | ||
| }; | ||
| } | ||
| /** | ||
| * Update internal snapshot | ||
| */ | ||
| _updateSnapshot() { | ||
| this._snapshot = Array.from(this.stores.entries()); | ||
| } | ||
| /** | ||
| * Notify all listeners of registry changes | ||
| */ | ||
| _notifyListeners() { | ||
| this.listeners.forEach((listener) => { | ||
| try { | ||
| listener(); | ||
| } catch (error) { | ||
| console.error("Error in registry listener:", error); | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Global default registry instance | ||
| */ | ||
| const globalStoreRegistry = new StoreRegistry("global"); | ||
| //#endregion | ||
| //#region src/stores/utils/type-guards.ts | ||
| function isRefState(value) { | ||
| return typeof value === "object" && value !== null && "target" in value && "isReady" in value && "isMounted" in value && "mountPromise" in value && typeof value.isReady === "boolean" && typeof value.isMounted === "boolean"; | ||
| } | ||
| /** | ||
| * DOM Event 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMEvent(value) { | ||
| return value instanceof Event; | ||
| } | ||
| /** | ||
| * Event-like 객체인지 확인하는 타입 가드 (preventDefault 메서드를 가진 객체) | ||
| */ | ||
| function isEventLike(value) { | ||
| return typeof value === "object" && value !== null && typeof value.preventDefault === "function"; | ||
| } | ||
| /** | ||
| * target 프로퍼티를 가진 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function hasTargetProperty(value) { | ||
| return typeof value === "object" && value !== null && "target" in value; | ||
| } | ||
| /** | ||
| * DOM Element인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMElement(value) { | ||
| return value instanceof Element; | ||
| } | ||
| /** | ||
| * 객체인지 확인하는 타입 가드 (null 제외) | ||
| */ | ||
| function isObject(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| } | ||
| /** | ||
| * 복합 타입 가드: Event 객체로 의심되는 객체인지 확인 | ||
| * RefState가 아니면서 Event 관련 속성을 가진 객체를 감지 | ||
| */ | ||
| function isSuspiciousEventObject(value, checkNested = true) { | ||
| if (!isObject(value) || isRefState(value)) return false; | ||
| if (isEventLikeObject(value)) return true; | ||
| if (checkNested) { | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const nestedValue = value[key]; | ||
| if (isEventLikeObject(nestedValue)) return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| /** | ||
| * 단일 객체가 이벤트와 같은지 확인 | ||
| */ | ||
| function isEventLikeObject(value) { | ||
| if (!isObject(value)) return false; | ||
| const hasEventTarget = hasTargetProperty(value); | ||
| const hasPreventDefault = isEventLike(value); | ||
| const isEvent = isDOMEvent(value); | ||
| const hasEventProperties = "type" in value && typeof value.type === "string" && (hasEventTarget || hasPreventDefault); | ||
| const hasReactMarkers = "nativeEvent" in value || "persist" in value || "$$typeof" in value || "_reactInternalFiber" in value || "_owner" in value; | ||
| const constructorName = value?.constructor?.name; | ||
| const hasEventConstructor = constructorName ? constructorName.includes("Event") || constructorName === "SyntheticEvent" || constructorName.includes("MouseEvent") || constructorName.includes("KeyboardEvent") || constructorName.includes("TouchEvent") || constructorName.includes("FocusEvent") || constructorName.includes("SubmitEvent") : false; | ||
| return isEvent || hasEventProperties || hasReactMarkers || hasEventConstructor; | ||
| } | ||
| /** | ||
| * 문제가 될 수 있는 속성들을 찾아내는 함수 | ||
| */ | ||
| function findProblematicProperties(value) { | ||
| if (!isObject(value)) return []; | ||
| const problematicKeys = []; | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const prop = value[key]; | ||
| if (isDOMElement(prop) || isDOMEvent(prop) || isObject(prop) && hasTargetProperty(prop)) problematicKeys.push(key); | ||
| } | ||
| return problematicKeys; | ||
| } | ||
| /** | ||
| * 통합 타입 가드 객체 | ||
| * 모든 타입 가드 함수들을 하나의 객체로 export | ||
| */ | ||
| const TypeGuards = { | ||
| isRefState, | ||
| isDOMEvent, | ||
| isEventLike, | ||
| hasTargetProperty, | ||
| isDOMElement, | ||
| isObject, | ||
| isSuspiciousEventObject, | ||
| findProblematicProperties | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/core/Store.ts | ||
| /** | ||
| * Core Store class for centralized state management with memory leak prevention | ||
| * | ||
| * Provides reactive state management with subscription capabilities, optimized for | ||
| * React integration through useSyncExternalStore. Features advanced cleanup mechanisms, | ||
| * automatic resource management, and comprehensive memory leak prevention. | ||
| * | ||
| * Key Features: | ||
| * - Automatic cleanup task registration and execution | ||
| * - Memory leak prevention with disposal patterns | ||
| * - Race condition protection for async operations | ||
| * - Advanced error recovery with exponential backoff | ||
| * - Resource monitoring and threshold management | ||
| * | ||
| * @template T - The type of value stored in this store | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const userStore = createStore('user', { name: '', age: 0 }); | ||
| * | ||
| * // Register cleanup tasks | ||
| * const unregister = userStore.registerCleanup(() => { | ||
| * console.log('Cleaning up user store resources'); | ||
| * }); | ||
| * | ||
| * // Automatic cleanup on disposal | ||
| * userStore.dispose(); | ||
| * ``` | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * @public | ||
| */ | ||
| var Store = class { | ||
| constructor(name, initialValue) { | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._lastClonedValue = null; | ||
| this._lastClonedVersion = 0; | ||
| this._version = 0; | ||
| this.isUpdating = false; | ||
| this.updateQueue = []; | ||
| this.notificationMode = "batched"; | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| this.pendingUpdatesCount = 0; | ||
| this.cleanupTasks = /* @__PURE__ */ new Set(); | ||
| this.isDisposed = false; | ||
| this.errorCount = 0; | ||
| this.lastErrorTime = 0; | ||
| this.MAX_ERROR_COUNT = 5; | ||
| this.ERROR_RESET_TIME = 6e4; | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.cloningEnabled = true; | ||
| this.subscribe = (listener) => { | ||
| if (this.isDisposed) { | ||
| console.warn(`Cannot subscribe to disposed store "${this.name}"`); | ||
| return () => {}; | ||
| } | ||
| const enhancedListener = () => { | ||
| if (this.isDisposed) return; | ||
| try { | ||
| listener(); | ||
| if (this.errorCount > 0) this.errorCount = 0; | ||
| } catch (error) { | ||
| this._handleListenerError(error, listener); | ||
| } | ||
| }; | ||
| this.subscriptionRegistry.set(listener, { | ||
| subscribedAt: Date.now(), | ||
| errorCount: 0, | ||
| enhancedListener | ||
| }); | ||
| this.listeners.add(enhancedListener); | ||
| return () => { | ||
| this.listeners.delete(enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| }; | ||
| }; | ||
| this.getSnapshot = () => { | ||
| return this._snapshot; | ||
| }; | ||
| this.name = name; | ||
| this._value = initialValue; | ||
| this._snapshot = this._createSnapshot(); | ||
| } | ||
| /** | ||
| * 현재 값 직접 가져오기 (액션 핸들러용) | ||
| * 핵심 로직: 불변성을 보장하는 깊은 복사본 반환 | ||
| * | ||
| * @implements lazy-evaluation | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 사용 시나리오: Action handler에서 최신 상태 읽기 | ||
| * 보안 강화: 외부에서 반환된 값을 수정해도 Store 내부 상태는 보호됨 | ||
| */ | ||
| getValue() { | ||
| if (this.cloningEnabled) { | ||
| if (this._lastClonedVersion === this._version && this._lastClonedValue !== null) return this._lastClonedValue; | ||
| this._lastClonedValue = require_error_handling.safeGet(this._value, this.cloningEnabled); | ||
| this._lastClonedVersion = this._version; | ||
| return this._lastClonedValue; | ||
| } | ||
| return this._value; | ||
| } | ||
| /** | ||
| * Store 값 설정 및 구독자 알림 | ||
| * 핵심 로직: | ||
| * 1. 입력값의 불변성 보장을 위한 깊은 복사 (선택적 skip 가능) | ||
| * 2. 강화된 값 비교 시스템으로 불필요한 리렌더링 방지 | ||
| * 3. Structural sharing을 통한 성능 최적화 | ||
| * 4. 값 변경 시에만 스냅샷 재생성 및 알림 | ||
| * | ||
| * @implements unidirectional-data-flow | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 보안 강화: 입력값을 복사하여 Store 내부 상태가 외부 참조에 의해 변경되지 않도록 보호 | ||
| * 성능 강화: 다층 비교 시스템으로 정확한 변경 감지 및 렌더링 최적화 | ||
| */ | ||
| setValue(value, options) { | ||
| if (TypeGuards.isObject(value)) { | ||
| if (!TypeGuards.isRefState(value) && TypeGuards.isSuspiciousEventObject(value)) { | ||
| const eventHandling = options?.eventHandling || "block"; | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(value); | ||
| const hasPreventDefault = TypeGuards.isEventLike(value); | ||
| const isEvent = TypeGuards.isDOMEvent(value); | ||
| switch (eventHandling) { | ||
| case "allow": break; | ||
| case "transform": | ||
| if (options?.eventTransform) try { | ||
| value = options.eventTransform(value); | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Event transformation failed in Store.setValue", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }); | ||
| return; | ||
| } | ||
| else { | ||
| require_error_handling.ErrorHandlers.store("Event transformation requested but no transform function provided", { | ||
| storeName: this.name, | ||
| valueType: typeof value | ||
| }); | ||
| return; | ||
| } | ||
| break; | ||
| case "block": | ||
| default: | ||
| require_error_handling.ErrorHandlers.store("Event object detected in Store.setValue - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| constructorName: value?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(value) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| const safeValue = options?.skipClone ? value : require_error_handling.safeSet(value, this.cloningEnabled); | ||
| let hasChanged = true; | ||
| if (!options?.skipComparison) hasChanged = this._compareValues(this._value, safeValue); | ||
| if (hasChanged) { | ||
| this._value = safeValue; | ||
| this._version++; | ||
| this._snapshot = this._createSnapshot(); | ||
| this._scheduleNotification(); | ||
| } | ||
| } | ||
| /** | ||
| * Update value using updater function with Immer integration | ||
| * 핵심 로직: | ||
| * 1. Immer produce를 사용하여 draft 객체 제공 | ||
| * 2. updater 결과를 불변성을 보장하며 설정 | ||
| * | ||
| * @implements store-immutability | ||
| * 보안 강화: Immer draft를 통한 안전한 상태 수정 | ||
| */ | ||
| update(updater) { | ||
| if (this.isUpdating) { | ||
| this.updateQueue.push(() => this.update(updater)); | ||
| return; | ||
| } | ||
| try { | ||
| this.isUpdating = true; | ||
| let updatedValue; | ||
| try { | ||
| updatedValue = require_error_handling.produce(this._value, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (immerError) { | ||
| if (process.env.NODE_ENV === "development") console.warn("[Store] Immer update failed, falling back to safe copy method", immerError); | ||
| const safeCurrentValue = require_error_handling.safeGet(this._value, this.cloningEnabled); | ||
| try { | ||
| updatedValue = require_error_handling.produce(safeCurrentValue, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (secondImmerError) { | ||
| if (process.env.NODE_ENV === "development") console.warn("[Store] Immer completely failed, using direct update (immutability not guaranteed)", secondImmerError); | ||
| updatedValue = updater(safeCurrentValue); | ||
| } | ||
| } | ||
| if (TypeGuards.isObject(updatedValue)) { | ||
| if (!TypeGuards.isRefState(updatedValue) && TypeGuards.isSuspiciousEventObject(updatedValue)) { | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(updatedValue); | ||
| const hasPreventDefault = TypeGuards.isEventLike(updatedValue); | ||
| const isEvent = TypeGuards.isDOMEvent(updatedValue); | ||
| require_error_handling.ErrorHandlers.store("Event object detected in Store.update result - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| updatedValueType: typeof updatedValue, | ||
| constructorName: updatedValue?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(updatedValue) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| this.setValue(updatedValue); | ||
| } finally { | ||
| this.isUpdating = false; | ||
| if (this.updateQueue.length > 0) { | ||
| const nextUpdate = this.updateQueue.shift(); | ||
| if (nextUpdate) Promise.resolve().then(nextUpdate); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Get number of active listeners | ||
| */ | ||
| getListenerCount() { | ||
| return this.listeners.size; | ||
| } | ||
| /** | ||
| * Clear all listeners | ||
| */ | ||
| clearListeners() { | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Register cleanup task for automatic execution on disposal | ||
| * | ||
| * Registers a cleanup function that will be automatically called when the store | ||
| * is disposed. This prevents memory leaks and ensures proper resource cleanup. | ||
| * | ||
| * @param task - Cleanup function to register | ||
| * @returns Unregister function to remove the cleanup task | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const timer = setInterval(() => {}, 1000); | ||
| * const unregister = store.registerCleanup(() => clearInterval(timer)); | ||
| * | ||
| * // Later, remove the cleanup task if needed | ||
| * unregister(); | ||
| * ``` | ||
| */ | ||
| registerCleanup(task) { | ||
| if (this.isDisposed) { | ||
| console.warn(`Store "${this.name}" is already disposed, cleanup task ignored`); | ||
| return () => {}; | ||
| } | ||
| this.cleanupTasks.add(task); | ||
| return () => this.cleanupTasks.delete(task); | ||
| } | ||
| /** | ||
| * Enhanced Store disposal with comprehensive cleanup | ||
| * | ||
| * Performs complete cleanup of all store resources including listeners, | ||
| * timers, cleanup tasks, and internal state. Prevents memory leaks and | ||
| * ensures proper resource disposal. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Manual disposal | ||
| * store.dispose(); | ||
| * | ||
| * // Auto-disposal with useEffect | ||
| * useEffect(() => { | ||
| * return () => store.dispose(); | ||
| * }, [store]); | ||
| * ``` | ||
| */ | ||
| dispose() { | ||
| if (this.isDisposed) return; | ||
| this.isDisposed = true; | ||
| try { | ||
| this.cleanupTasks.forEach((task) => { | ||
| try { | ||
| task(); | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Error during cleanup task execution", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| }); | ||
| this.cleanupTasks.clear(); | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.clearListeners(); | ||
| if (this.animationFrameId !== null) { | ||
| cancelAnimationFrame(this.animationFrameId); | ||
| this.animationFrameId = null; | ||
| } | ||
| this.pendingNotification = false; | ||
| this.updateQueue.length = 0; | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Critical error during store disposal", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| } | ||
| /** | ||
| * Check if store is disposed | ||
| * @returns true if store has been disposed | ||
| */ | ||
| isStoreDisposed() { | ||
| return this.isDisposed; | ||
| } | ||
| /** | ||
| * Store별 커스텀 비교 함수 설정 | ||
| * 이 Store에만 적용되는 특별한 비교 로직 설정 | ||
| * | ||
| * @param comparator - 커스텀 비교 함수 (oldValue, newValue) => boolean | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setCustomComparator(comparator) { | ||
| this.customComparator = comparator; | ||
| } | ||
| /** | ||
| * Store별 비교 옵션 설정 | ||
| * 이 Store에만 적용되는 비교 전략 설정 | ||
| * | ||
| * @param options - 비교 옵션 | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setComparisonOptions(options) { | ||
| this.comparisonOptions = options; | ||
| } | ||
| /** | ||
| * 성능 최적화: Store별 복사 동작 제어 | ||
| * | ||
| * @param enabled - true: 복사 활성화 (안전), false: 복사 비활성화 (성능 우선) | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/performance | ||
| */ | ||
| setCloningEnabled(enabled) { | ||
| this.cloningEnabled = enabled; | ||
| } | ||
| /** | ||
| * 현재 복사 설정 조회 | ||
| */ | ||
| isCloningEnabled() { | ||
| return this.cloningEnabled; | ||
| } | ||
| /** | ||
| * 강화된 값 비교 시스템 | ||
| * 1. 커스텀 비교 함수 우선 사용 | ||
| * 2. Store별 비교 옵션 적용 | ||
| * 3. 성능 최적화된 빠른 비교 fallback | ||
| * 4. 전역 비교 설정 사용 | ||
| * | ||
| * @param oldValue - 이전 값 | ||
| * @param newValue - 새로운 값 | ||
| * @returns true if values are different (change detected), false if same | ||
| * @protected | ||
| */ | ||
| _compareValues(oldValue, newValue) { | ||
| let result; | ||
| try { | ||
| if (this.customComparator) result = !this.customComparator(oldValue, newValue); | ||
| else if (this.comparisonOptions) result = !require_error_handling.compareValues(oldValue, newValue, this.comparisonOptions); | ||
| else result = !require_error_handling.compareValues(oldValue, newValue, { strategy: "reference" }); | ||
| } catch (error) { | ||
| require_error_handling.ErrorHandlers.store("Error during value comparison, falling back to reference comparison", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| result = !Object.is(oldValue, newValue); | ||
| } | ||
| return result; | ||
| } | ||
| _createSnapshot() { | ||
| return { | ||
| value: require_error_handling.safeGet(this._value, this.cloningEnabled), | ||
| name: this.name, | ||
| lastUpdate: Date.now() | ||
| }; | ||
| } | ||
| /** | ||
| * requestAnimationFrame 기반 알림 스케줄링 | ||
| * 브라우저의 다음 프레임에서 리스너 알림 실행 | ||
| */ | ||
| _scheduleNotification() { | ||
| if (this.notificationMode === "immediate") this._notifyListeners(); | ||
| else this._scheduleWithRAF(); | ||
| } | ||
| /** | ||
| * requestAnimationFrame을 사용한 알림 스케줄링 | ||
| * 누적 가능한 배치 시스템으로 개선 | ||
| */ | ||
| _scheduleWithRAF() { | ||
| this.pendingUpdatesCount++; | ||
| if (!this.pendingNotification) { | ||
| this.pendingNotification = true; | ||
| this.animationFrameId = requestAnimationFrame(() => { | ||
| this._executeNotification(); | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * 스케줄된 알림 실행 | ||
| */ | ||
| _executeNotification() { | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| const batchedUpdates = this.pendingUpdatesCount; | ||
| this.pendingUpdatesCount = 0; | ||
| this._notifyListeners(); | ||
| if (process.env.NODE_ENV === "development" && batchedUpdates > 1) console.debug(`[Store:${this.name}] Batched ${batchedUpdates} updates in single frame`); | ||
| } | ||
| /** | ||
| * Handle listener execution errors with recovery strategies | ||
| */ | ||
| _handleListenerError(error, listener) { | ||
| const now = Date.now(); | ||
| if (now - this.lastErrorTime > this.ERROR_RESET_TIME) this.errorCount = 0; | ||
| this.errorCount++; | ||
| this.lastErrorTime = now; | ||
| const metadata = this.subscriptionRegistry.get(listener); | ||
| if (metadata) metadata.errorCount++; | ||
| require_error_handling.ErrorHandlers.store("Error in store listener execution", { | ||
| storeName: this.name, | ||
| listenerCount: this.listeners.size, | ||
| errorCount: this.errorCount, | ||
| subscriptionAge: metadata ? now - metadata.subscribedAt : "unknown" | ||
| }, error instanceof Error ? error : void 0); | ||
| if (metadata && metadata.errorCount >= 3) { | ||
| console.warn(`Removing problematic listener from store "${this.name}" after ${metadata.errorCount} errors`); | ||
| this.listeners.delete(metadata.enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| } | ||
| if (this.errorCount >= this.MAX_ERROR_COUNT) { | ||
| console.error(`Store "${this.name}" disabled due to excessive errors (${this.errorCount})`); | ||
| this.clearListeners(); | ||
| } | ||
| } | ||
| _notifyListeners() { | ||
| if (this.isDisposed) return; | ||
| this.listeners.forEach((listener) => { | ||
| if (this.isDisposed) return; | ||
| listener(); | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Factory function for creating type-safe Store instances | ||
| * | ||
| * Creates a new Store instance with the specified name and initial value. | ||
| * Provides type safety and integrates seamlessly with React hooks and | ||
| * the Context-Action framework patterns. | ||
| * | ||
| * @template T - The type of values stored in this store | ||
| * | ||
| * @param name - Unique identifier for the store (used for debugging) | ||
| * @param initialValue - Initial value to store | ||
| * | ||
| * @returns Configured Store instance ready for use | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function createStore(name, initialValue) { | ||
| return new Store(name, initialValue); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/utils/sync-external-store-utils.ts | ||
| /** | ||
| * 향상된 구독 함수 생성 (내부 사용) | ||
| * 디바운싱, 스로틀링, 조건부 구독 기능 제공 | ||
| */ | ||
| function createEnhancedSubscriber(store, options = {}) { | ||
| const { debounce, throttle, condition, debug, name = "unknown" } = options; | ||
| return (callback) => { | ||
| if (!store) return () => {}; | ||
| let debounceTimer = null; | ||
| let throttleTimer = null; | ||
| let lastThrottleTime = 0; | ||
| const enhancedCallback = () => { | ||
| if (condition && !condition()) { | ||
| if (debug) console.debug(`[${name}] Subscription suspended due to condition`); | ||
| return; | ||
| } | ||
| const now = performance.now(); | ||
| if (throttle && throttle > 0) { | ||
| if (now - lastThrottleTime < throttle) { | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| throttleTimer = setTimeout(() => { | ||
| lastThrottleTime = performance.now(); | ||
| callback(); | ||
| }, throttle - (now - lastThrottleTime)); | ||
| return; | ||
| } | ||
| lastThrottleTime = now; | ||
| } | ||
| if (debounce && debounce > 0) { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| debounceTimer = setTimeout(() => { | ||
| callback(); | ||
| if (debug) console.debug(`[${name}] Debounced callback executed after ${debounce}ms`); | ||
| }, debounce); | ||
| return; | ||
| } | ||
| callback(); | ||
| }; | ||
| const unsubscribe = store.subscribe(enhancedCallback); | ||
| return () => { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| unsubscribe(); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Null-safe Store 구독 훅 | ||
| * useSyncExternalStore를 기반으로 한 안전한 구독 | ||
| */ | ||
| function useSafeStoreSubscription(store, selector, options = {}) { | ||
| const { initialValue, equalityFn,...subscriptionOptions } = options; | ||
| const subscribe = (0, react.useCallback)((callback) => { | ||
| if (!store) return () => {}; | ||
| if (subscriptionOptions.debounce || subscriptionOptions.throttle || subscriptionOptions.condition) return createEnhancedSubscriber(store, subscriptionOptions)(callback); | ||
| return store.subscribe(callback); | ||
| }, [store, subscriptionOptions]); | ||
| const getSnapshot = (0, react.useCallback)(() => { | ||
| if (!store) return initialValue; | ||
| const snapshot = store.getSnapshot(); | ||
| return selector ? selector(snapshot.value) : snapshot.value; | ||
| }, [ | ||
| store, | ||
| selector, | ||
| initialValue | ||
| ]); | ||
| const cachedSnapshotRef = (0, react.useRef)(); | ||
| const stableGetSnapshot = (0, react.useCallback)(() => { | ||
| const currentSnapshot = getSnapshot(); | ||
| if (equalityFn && cachedSnapshotRef.current !== void 0) { | ||
| if (equalityFn(cachedSnapshotRef.current, currentSnapshot)) return cachedSnapshotRef.current; | ||
| } | ||
| cachedSnapshotRef.current = currentSnapshot; | ||
| return currentSnapshot; | ||
| }, [getSnapshot, equalityFn]); | ||
| const getServerSnapshot = (0, react.useCallback)(() => { | ||
| return initialValue; | ||
| }, [initialValue]); | ||
| return (0, react.useSyncExternalStore)(subscribe, equalityFn ? stableGetSnapshot : getSnapshot, getServerSnapshot); | ||
| } | ||
| /** | ||
| * 기본 동등성 비교 함수들 | ||
| */ | ||
| const equalityFunctions = { | ||
| reference: (a, b) => Object.is(a, b), | ||
| shallow: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) return false; | ||
| return true; | ||
| }, | ||
| deep: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) !== Array.isArray(b)) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) { | ||
| if (!Object.prototype.hasOwnProperty.call(b, key)) return false; | ||
| if (!equalityFunctions.deep(a[key], b[key])) return false; | ||
| } | ||
| return true; | ||
| }, | ||
| smart: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) && Array.isArray(b)) { | ||
| if (a.length !== b.length) return false; | ||
| return a.every((item, index) => { | ||
| const bItem = b[index]; | ||
| if (typeof item === "object" && item !== null && typeof bItem === "object" && bItem !== null) return equalityFunctions.shallow(item, bItem); | ||
| return Object.is(item, bItem); | ||
| }); | ||
| } | ||
| return equalityFunctions.shallow(a, b); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/hooks/useStoreSelector.ts | ||
| const defaultEqualityFn = equalityFunctions.smart; | ||
| const shallowEqual = equalityFunctions.shallow; | ||
| const deepEqual = equalityFunctions.deep; | ||
| const smartEqual = equalityFunctions.smart; | ||
| /** | ||
| * Hook for selective store subscription with performance optimization | ||
| * | ||
| * Subscribes to specific parts of store data using a selector function, | ||
| * triggering re-renders only when the selected value actually changes. | ||
| * Essential for preventing unnecessary re-renders in complex applications. | ||
| * | ||
| * @template T - Type of the store value | ||
| * @template R - Type of the value returned by the selector | ||
| * | ||
| * @param store - Store instance to subscribe to | ||
| * @param selector - Function to extract needed data from store value | ||
| * @param equalityFn - Function to compare previous and new values (default: Object.is) | ||
| * | ||
| * @returns The value returned by the selector function | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-hooks#usestoreselector-advanced-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function useStoreSelector(store, selector, equalityFn = defaultEqualityFn) { | ||
| "use memo"; | ||
| const stableSelector = (0, react.useCallback)(selector, [selector]); | ||
| const stableEqualityFn = (0, react.useCallback)(equalityFn, [equalityFn]); | ||
| const selectorWarningShownRef = (0, react.useRef)(false); | ||
| if (process.env.NODE_ENV === "development") { | ||
| if (selector !== stableSelector && !selectorWarningShownRef.current) { | ||
| console.warn("useStoreSelector: selector function changed. Consider wrapping it with useCallback to avoid unnecessary recalculations.", "Store:", store.name); | ||
| selectorWarningShownRef.current = true; | ||
| } | ||
| } | ||
| const previousValueRef = (0, react.useRef)(); | ||
| const subscribe = (0, react.useCallback)((callback) => { | ||
| return store.subscribe(callback); | ||
| }, [store]); | ||
| const getSnapshot = (0, react.useCallback)(() => { | ||
| try { | ||
| const selectedValue = stableSelector(store.getValue()); | ||
| if (previousValueRef.current !== void 0 && stableEqualityFn(previousValueRef.current, selectedValue)) return previousValueRef.current; | ||
| previousValueRef.current = selectedValue; | ||
| return selectedValue; | ||
| } catch (error) { | ||
| if (process.env.NODE_ENV === "development") console.error("useStoreSelector: Error in selector function:", error); | ||
| throw error; | ||
| } | ||
| }, [ | ||
| store, | ||
| stableSelector, | ||
| stableEqualityFn | ||
| ]); | ||
| return (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/components/StoreErrorBoundary.tsx | ||
| /** | ||
| * Store 시스템을 위한 에러 경계 컴포넌트 | ||
| * | ||
| * Store 관련 에러들을 캐치하고 적절한 fallback UI를 제공합니다. | ||
| * 개발 모드에서는 자세한 에러 정보를 표시하고, 프로덕션에서는 | ||
| * 사용자 친화적인 메시지를 보여줍니다. | ||
| */ | ||
| var StoreErrorBoundary = class extends react.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.resetTimeoutId = null; | ||
| this.resetErrorBoundary = () => { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| this.setState({ | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }); | ||
| }; | ||
| this.state = { | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }; | ||
| } | ||
| static getDerivedStateFromError(error) { | ||
| return { | ||
| hasError: true, | ||
| error: error instanceof Error && error.name === "ContextActionError" ? error : null, | ||
| errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` | ||
| }; | ||
| } | ||
| componentDidCatch(error, errorInfo) { | ||
| if (error.name === "ContextActionError") { | ||
| const contextActionError = error; | ||
| this.setState({ errorInfo }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } else { | ||
| const contextActionError = require_error_handling.ErrorHandlers.store(`Unhandled error in Store component: ${error.message}`, { | ||
| component: "StoreErrorBoundary", | ||
| stack: error.stack, | ||
| componentStack: errorInfo.componentStack | ||
| }, error); | ||
| this.setState({ | ||
| error: contextActionError, | ||
| errorInfo | ||
| }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| const { hasError } = this.state; | ||
| const { resetOnPropsChange, resetKeys } = this.props; | ||
| if (hasError && resetOnPropsChange) { | ||
| if (resetKeys) { | ||
| if (resetKeys.some((key) => { | ||
| return prevProps[key] !== this.props[key]; | ||
| })) this.resetErrorBoundary(); | ||
| } else if (prevProps !== this.props) this.resetErrorBoundary(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| } | ||
| render() { | ||
| const { hasError, error, errorInfo } = this.state; | ||
| const { children, fallback } = this.props; | ||
| if (hasError) { | ||
| if (fallback) { | ||
| if (typeof fallback === "function") return fallback(error, errorInfo); | ||
| return fallback; | ||
| } | ||
| return this.renderDefaultFallback(); | ||
| } | ||
| return children; | ||
| } | ||
| renderDefaultFallback() { | ||
| if (process.env.NODE_ENV === "development") return this.renderDevelopmentFallback(); | ||
| else return this.renderProductionFallback(); | ||
| } | ||
| renderDevelopmentFallback() { | ||
| const { error, errorInfo, errorId } = this.state; | ||
| const stats = require_error_handling.getErrorStatistics(); | ||
| return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { | ||
| padding: "20px", | ||
| margin: "20px", | ||
| border: "2px solid #ff6b6b", | ||
| borderRadius: "8px", | ||
| backgroundColor: "#ffe0e0", | ||
| fontFamily: "monospace" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { | ||
| style: { | ||
| color: "#d63031", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "🚨 Store Error Boundary" | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Error ID:" }), | ||
| " ", | ||
| errorId | ||
| ] | ||
| }), | ||
| error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Error Type:" }), | ||
| " ", | ||
| error.type, | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Message:" }), | ||
| " ", | ||
| error.message, | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Timestamp:" }), | ||
| " ", | ||
| new Date(error.timestamp).toISOString() | ||
| ] | ||
| }), | ||
| error?.context && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Context:" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "12px" | ||
| }, | ||
| children: JSON.stringify(error.context, null, 2) | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Error Statistics:" }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| "Total Errors: ", | ||
| stats.totalErrors, | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}), | ||
| "Recent Errors: ", | ||
| stats.recentErrors.length | ||
| ] | ||
| }), | ||
| errorInfo && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { | ||
| style: { | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Component Stack" | ||
| }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "11px", | ||
| whiteSpace: "pre-wrap" | ||
| }, | ||
| children: errorInfo.componentStack | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#00b894", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| renderProductionFallback() { | ||
| return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { | ||
| style: { | ||
| padding: "20px", | ||
| textAlign: "center", | ||
| backgroundColor: "#f8f9fa", | ||
| border: "1px solid #dee2e6", | ||
| borderRadius: "8px", | ||
| margin: "20px 0" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "Something went wrong" | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 15px 0" | ||
| }, | ||
| children: "We're sorry, but something unexpected happened. Please try again." | ||
| }), | ||
| /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#007bff", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Enhanced HOC for automatic Store Error Boundary wrapping with security | ||
| * | ||
| * @template P - Component props type | ||
| * @param WrappedComponent - Component to wrap with error boundary | ||
| * @param errorBoundaryProps - Error boundary configuration | ||
| * @returns Enhanced component with comprehensive error handling | ||
| */ | ||
| function withStoreErrorBoundary(WrappedComponent, errorBoundaryProps) { | ||
| const WithStoreErrorBoundaryComponent = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StoreErrorBoundary, { | ||
| ...errorBoundaryProps, | ||
| children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WrappedComponent, { ...props }) | ||
| }); | ||
| WithStoreErrorBoundaryComponent.displayName = `withStoreErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name})`; | ||
| return WithStoreErrorBoundaryComponent; | ||
| } | ||
| /** | ||
| * 특정 Store와 연결된 에러 경계 생성 헬퍼 | ||
| */ | ||
| function createStoreErrorBoundary(storeName, customFallback) { | ||
| return ({ children }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StoreErrorBoundary, { | ||
| fallback: customFallback, | ||
| onError: (error, errorInfo) => { | ||
| console.group(`Store Error in ${storeName}`); | ||
| console.error("Error:", error); | ||
| console.error("Component Stack:", errorInfo.componentStack); | ||
| console.groupEnd(); | ||
| }, | ||
| resetKeys: [storeName], | ||
| children | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/actions/ActionContext.tsx | ||
| function createActionContext(contextNameOrConfig = {}, config) { | ||
| let effectiveConfig; | ||
| let contextName; | ||
| if (typeof contextNameOrConfig === "string") { | ||
| contextName = contextNameOrConfig; | ||
| effectiveConfig = { | ||
| ...config, | ||
| name: config?.name || contextName | ||
| }; | ||
| } else { | ||
| effectiveConfig = contextNameOrConfig; | ||
| contextName = effectiveConfig.name || "ActionContext"; | ||
| } | ||
| const FactoryActionContext = (0, react.createContext)(null); | ||
| const Provider = ({ children }) => { | ||
| const actionRegisterRef = (0, react.useRef)(new __context_action_core.ActionRegister(effectiveConfig)); | ||
| const contextValue = (0, react.useMemo)(() => ({ actionRegisterRef }), []); | ||
| return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FactoryActionContext.Provider, { | ||
| value: contextValue, | ||
| children | ||
| }); | ||
| }; | ||
| const useFactoryActionContext = () => { | ||
| const context = (0, react.useContext)(FactoryActionContext); | ||
| if (!context) throw new Error("useFactoryActionContext must be used within a factory ActionContext Provider"); | ||
| return context; | ||
| }; | ||
| /** | ||
| * Optimized hook to get stable dispatch functions | ||
| * | ||
| * Returns stable dispatch functions that prevent re-renders and maintain | ||
| * reference equality across component renders. | ||
| * | ||
| * @returns Object with dispatch and dispatchWithResult functions | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useActionDispatcher = () => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| return { | ||
| dispatch: (0, react.useCallback)((action, payload, options) => { | ||
| if (process.env.NODE_ENV === "development") console.log(`React dispatch called for '${String(action)}':`, { | ||
| hasPayload: payload !== void 0, | ||
| hasOptions: options !== void 0, | ||
| timestamp: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister is not initialized. Make sure the ActionContext Provider is properly set up."); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]), | ||
| dispatchWithResult: (0, react.useCallback)((action, payload, options) => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]) | ||
| }; | ||
| }; | ||
| const useAction = () => { | ||
| const { dispatch } = useActionDispatcher(); | ||
| return dispatch; | ||
| }; | ||
| const useActionHandler = (action, handler, config$1) => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| const actionId = (0, react.useId)(); | ||
| const handlerRef = (0, react.useRef)(handler); | ||
| handlerRef.current = handler; | ||
| const priority = config$1?.priority ?? 0; | ||
| const id = config$1?.id || `react_${String(action)}_${actionId}`; | ||
| const blocking = config$1?.blocking ?? false; | ||
| const once = config$1?.once ?? false; | ||
| const debounce = config$1?.debounce; | ||
| const throttle = config$1?.throttle; | ||
| const stableConfig = (0, react.useMemo)(() => ({ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| replaceExisting: true, | ||
| ...debounce !== void 0 && { debounce }, | ||
| ...throttle !== void 0 && { throttle } | ||
| }), [ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| debounce, | ||
| throttle | ||
| ]); | ||
| (0, react.useEffect)(() => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) return; | ||
| const wrapperHandler = (payload, controller) => { | ||
| return handlerRef.current(payload, controller); | ||
| }; | ||
| if (process.env.NODE_ENV === "development") console.log(`Registering handler for '${String(action)}'`); | ||
| return register.register(action, wrapperHandler, stableConfig); | ||
| }, [ | ||
| action, | ||
| actionRegisterRef, | ||
| stableConfig | ||
| ]); | ||
| }; | ||
| /** | ||
| * Hook that provides direct access to the ActionRegister instance | ||
| * | ||
| * This hook is useful when you need to: | ||
| * - Register multiple handlers dynamically | ||
| * - Access other ActionRegister methods like clearAction, getHandlers, etc. | ||
| * - Implement complex handler registration logic | ||
| * - Have more control over the registration lifecycle | ||
| * | ||
| * @returns ActionRegister instance or null if not initialized | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useFactoryActionRegister = () => { | ||
| return useFactoryActionContext().actionRegisterRef.current; | ||
| }; | ||
| /** | ||
| * Hook that provides access to the dispatchWithResult function | ||
| * | ||
| * This hook returns a function that dispatches actions and returns detailed | ||
| * execution results including collected handler results, execution metadata, | ||
| * and error information. | ||
| * | ||
| * @returns dispatchWithResult function with full type safety | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-with-result | ||
| */ | ||
| const useFactoryActionDispatchWithResult = () => { | ||
| const context = useFactoryActionContext(); | ||
| const activeControllersRef = (0, react.useRef)(/* @__PURE__ */ new Set()); | ||
| const dispatch = (0, react.useCallback)((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions); | ||
| }, [context.actionRegisterRef]); | ||
| const dispatchWithResult = (0, react.useCallback)((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions); | ||
| }, [context.actionRegisterRef]); | ||
| const abortAll = (0, react.useCallback)(() => { | ||
| activeControllersRef.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| activeControllersRef.current.clear(); | ||
| }, []); | ||
| const resetAbortScope = (0, react.useCallback)(() => { | ||
| abortAll(); | ||
| }, [abortAll]); | ||
| (0, react.useEffect)(() => { | ||
| const controllers = activeControllersRef; | ||
| return () => { | ||
| controllers.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| controllers.current.clear(); | ||
| }; | ||
| }, []); | ||
| return { | ||
| dispatch, | ||
| dispatchWithResult, | ||
| abortAll, | ||
| resetAbortScope | ||
| }; | ||
| }; | ||
| return { | ||
| Provider, | ||
| useActionContext: useFactoryActionContext, | ||
| useActionDispatch: useAction, | ||
| useActionHandler, | ||
| useActionRegister: useFactoryActionRegister, | ||
| useActionDispatchWithResult: useFactoryActionDispatchWithResult, | ||
| context: FactoryActionContext | ||
| }; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'Store', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return Store; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'StoreErrorBoundary', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return StoreErrorBoundary; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'StoreRegistry', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return StoreRegistry; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createActionContext', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createActionContext; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createStore', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createStore; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createStoreErrorBoundary', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createStoreErrorBoundary; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'defaultEqualityFn', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return defaultEqualityFn; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'useSafeStoreSubscription', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return useSafeStoreSubscription; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'useStoreSelector', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return useStoreSelector; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'withStoreErrorBoundary', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return withStoreErrorBoundary; | ||
| } | ||
| }); |
| import { ErrorHandlers, compareValues, getErrorStatistics, produce, safeGet, safeSet } from "./error-handling-CkdfKCZ0.js"; | ||
| import React, { Component, createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useSyncExternalStore } from "react"; | ||
| import { jsx, jsxs } from "react/jsx-runtime"; | ||
| import { ActionRegister } from "@context-action/core"; | ||
| //#region src/stores/core/StoreRegistry.ts | ||
| /** | ||
| * Centralized store registry for managing multiple Store instances | ||
| */ | ||
| var StoreRegistry = class { | ||
| constructor(name = "default") { | ||
| this.stores = /* @__PURE__ */ new Map(); | ||
| this.metadata = /* @__PURE__ */ new WeakMap(); | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._snapshot = []; | ||
| this.name = name; | ||
| } | ||
| /** | ||
| * Subscribe to registry changes for reactive updates | ||
| */ | ||
| subscribe(listener) { | ||
| this.listeners.add(listener); | ||
| return () => { | ||
| this.listeners.delete(listener); | ||
| }; | ||
| } | ||
| /** | ||
| * Register a new store in the registry | ||
| */ | ||
| register(name, store, metadata) { | ||
| if (this.stores.has(name)) { | ||
| if (this.stores.get(name)) {} | ||
| } | ||
| this.stores.set(name, store); | ||
| if (metadata) this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name, | ||
| ...metadata | ||
| }); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Unregister a store from the registry | ||
| */ | ||
| unregister(name) { | ||
| if (!this.stores.get(name)) return false; | ||
| this.stores.delete(name); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get a store by name | ||
| */ | ||
| getStore(name) { | ||
| return this.stores.get(name); | ||
| } | ||
| /** | ||
| * Check if a store exists | ||
| */ | ||
| hasStore(name) { | ||
| return this.stores.has(name); | ||
| } | ||
| /** | ||
| * Get all registered store names | ||
| */ | ||
| getStoreNames() { | ||
| return Array.from(this.stores.keys()); | ||
| } | ||
| /** | ||
| * Get all stores as entries | ||
| */ | ||
| getAllStores() { | ||
| return new Map(this.stores); | ||
| } | ||
| /** | ||
| * Get store metadata | ||
| */ | ||
| getStoreMetadata(nameOrStore) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| return store ? this.metadata.get(store) : void 0; | ||
| } | ||
| /** | ||
| * Update store metadata | ||
| */ | ||
| updateStoreMetadata(nameOrStore, metadata) { | ||
| const store = typeof nameOrStore === "string" ? this.stores.get(nameOrStore) : nameOrStore; | ||
| if (!store) return false; | ||
| const currentMetadata = this.metadata.get(store); | ||
| this.metadata.set(store, { | ||
| registeredAt: Date.now(), | ||
| name: typeof nameOrStore === "string" ? nameOrStore : currentMetadata?.name || "unknown", | ||
| ...currentMetadata, | ||
| ...metadata | ||
| }); | ||
| return true; | ||
| } | ||
| /** | ||
| * Get registry snapshot for React integration | ||
| */ | ||
| getSnapshot() { | ||
| return this._snapshot; | ||
| } | ||
| /** | ||
| * Clear all stores from registry | ||
| */ | ||
| clear() { | ||
| this.stores.clear(); | ||
| this._updateSnapshot(); | ||
| this._notifyListeners(); | ||
| } | ||
| /** | ||
| * Dispose registry and cleanup resources | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Get count of registered stores | ||
| */ | ||
| getStoreCount() { | ||
| return this.stores.size; | ||
| } | ||
| /** | ||
| * Iterate over all stores | ||
| */ | ||
| forEach(callback) { | ||
| this.stores.forEach((store, name) => { | ||
| callback(store, name); | ||
| }); | ||
| } | ||
| /** | ||
| * Get registry statistics | ||
| */ | ||
| getStats() { | ||
| return { | ||
| totalStores: this.stores.size, | ||
| storeNames: this.getStoreNames(), | ||
| registryName: this.name | ||
| }; | ||
| } | ||
| /** | ||
| * Update internal snapshot | ||
| */ | ||
| _updateSnapshot() { | ||
| this._snapshot = Array.from(this.stores.entries()); | ||
| } | ||
| /** | ||
| * Notify all listeners of registry changes | ||
| */ | ||
| _notifyListeners() { | ||
| this.listeners.forEach((listener) => { | ||
| try { | ||
| listener(); | ||
| } catch (error) { | ||
| console.error("Error in registry listener:", error); | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Global default registry instance | ||
| */ | ||
| const globalStoreRegistry = new StoreRegistry("global"); | ||
| //#endregion | ||
| //#region src/stores/utils/type-guards.ts | ||
| function isRefState(value) { | ||
| return typeof value === "object" && value !== null && "target" in value && "isReady" in value && "isMounted" in value && "mountPromise" in value && typeof value.isReady === "boolean" && typeof value.isMounted === "boolean"; | ||
| } | ||
| /** | ||
| * DOM Event 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMEvent(value) { | ||
| return value instanceof Event; | ||
| } | ||
| /** | ||
| * Event-like 객체인지 확인하는 타입 가드 (preventDefault 메서드를 가진 객체) | ||
| */ | ||
| function isEventLike(value) { | ||
| return typeof value === "object" && value !== null && typeof value.preventDefault === "function"; | ||
| } | ||
| /** | ||
| * target 프로퍼티를 가진 객체인지 확인하는 타입 가드 | ||
| */ | ||
| function hasTargetProperty(value) { | ||
| return typeof value === "object" && value !== null && "target" in value; | ||
| } | ||
| /** | ||
| * DOM Element인지 확인하는 타입 가드 | ||
| */ | ||
| function isDOMElement(value) { | ||
| return value instanceof Element; | ||
| } | ||
| /** | ||
| * 객체인지 확인하는 타입 가드 (null 제외) | ||
| */ | ||
| function isObject(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| } | ||
| /** | ||
| * 복합 타입 가드: Event 객체로 의심되는 객체인지 확인 | ||
| * RefState가 아니면서 Event 관련 속성을 가진 객체를 감지 | ||
| */ | ||
| function isSuspiciousEventObject(value, checkNested = true) { | ||
| if (!isObject(value) || isRefState(value)) return false; | ||
| if (isEventLikeObject(value)) return true; | ||
| if (checkNested) { | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const nestedValue = value[key]; | ||
| if (isEventLikeObject(nestedValue)) return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| /** | ||
| * 단일 객체가 이벤트와 같은지 확인 | ||
| */ | ||
| function isEventLikeObject(value) { | ||
| if (!isObject(value)) return false; | ||
| const hasEventTarget = hasTargetProperty(value); | ||
| const hasPreventDefault = isEventLike(value); | ||
| const isEvent = isDOMEvent(value); | ||
| const hasEventProperties = "type" in value && typeof value.type === "string" && (hasEventTarget || hasPreventDefault); | ||
| const hasReactMarkers = "nativeEvent" in value || "persist" in value || "$$typeof" in value || "_reactInternalFiber" in value || "_owner" in value; | ||
| const constructorName = value?.constructor?.name; | ||
| const hasEventConstructor = constructorName ? constructorName.includes("Event") || constructorName === "SyntheticEvent" || constructorName.includes("MouseEvent") || constructorName.includes("KeyboardEvent") || constructorName.includes("TouchEvent") || constructorName.includes("FocusEvent") || constructorName.includes("SubmitEvent") : false; | ||
| return isEvent || hasEventProperties || hasReactMarkers || hasEventConstructor; | ||
| } | ||
| /** | ||
| * 문제가 될 수 있는 속성들을 찾아내는 함수 | ||
| */ | ||
| function findProblematicProperties(value) { | ||
| if (!isObject(value)) return []; | ||
| const problematicKeys = []; | ||
| for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) { | ||
| const prop = value[key]; | ||
| if (isDOMElement(prop) || isDOMEvent(prop) || isObject(prop) && hasTargetProperty(prop)) problematicKeys.push(key); | ||
| } | ||
| return problematicKeys; | ||
| } | ||
| /** | ||
| * 통합 타입 가드 객체 | ||
| * 모든 타입 가드 함수들을 하나의 객체로 export | ||
| */ | ||
| const TypeGuards = { | ||
| isRefState, | ||
| isDOMEvent, | ||
| isEventLike, | ||
| hasTargetProperty, | ||
| isDOMElement, | ||
| isObject, | ||
| isSuspiciousEventObject, | ||
| findProblematicProperties | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/core/Store.ts | ||
| /** | ||
| * Core Store class for centralized state management with memory leak prevention | ||
| * | ||
| * Provides reactive state management with subscription capabilities, optimized for | ||
| * React integration through useSyncExternalStore. Features advanced cleanup mechanisms, | ||
| * automatic resource management, and comprehensive memory leak prevention. | ||
| * | ||
| * Key Features: | ||
| * - Automatic cleanup task registration and execution | ||
| * - Memory leak prevention with disposal patterns | ||
| * - Race condition protection for async operations | ||
| * - Advanced error recovery with exponential backoff | ||
| * - Resource monitoring and threshold management | ||
| * | ||
| * @template T - The type of value stored in this store | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const userStore = createStore('user', { name: '', age: 0 }); | ||
| * | ||
| * // Register cleanup tasks | ||
| * const unregister = userStore.registerCleanup(() => { | ||
| * console.log('Cleaning up user store resources'); | ||
| * }); | ||
| * | ||
| * // Automatic cleanup on disposal | ||
| * userStore.dispose(); | ||
| * ``` | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * @public | ||
| */ | ||
| var Store = class { | ||
| constructor(name, initialValue) { | ||
| this.listeners = /* @__PURE__ */ new Set(); | ||
| this._lastClonedValue = null; | ||
| this._lastClonedVersion = 0; | ||
| this._version = 0; | ||
| this.isUpdating = false; | ||
| this.updateQueue = []; | ||
| this.notificationMode = "batched"; | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| this.pendingUpdatesCount = 0; | ||
| this.cleanupTasks = /* @__PURE__ */ new Set(); | ||
| this.isDisposed = false; | ||
| this.errorCount = 0; | ||
| this.lastErrorTime = 0; | ||
| this.MAX_ERROR_COUNT = 5; | ||
| this.ERROR_RESET_TIME = 6e4; | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.cloningEnabled = true; | ||
| this.subscribe = (listener) => { | ||
| if (this.isDisposed) { | ||
| console.warn(`Cannot subscribe to disposed store "${this.name}"`); | ||
| return () => {}; | ||
| } | ||
| const enhancedListener = () => { | ||
| if (this.isDisposed) return; | ||
| try { | ||
| listener(); | ||
| if (this.errorCount > 0) this.errorCount = 0; | ||
| } catch (error) { | ||
| this._handleListenerError(error, listener); | ||
| } | ||
| }; | ||
| this.subscriptionRegistry.set(listener, { | ||
| subscribedAt: Date.now(), | ||
| errorCount: 0, | ||
| enhancedListener | ||
| }); | ||
| this.listeners.add(enhancedListener); | ||
| return () => { | ||
| this.listeners.delete(enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| }; | ||
| }; | ||
| this.getSnapshot = () => { | ||
| return this._snapshot; | ||
| }; | ||
| this.name = name; | ||
| this._value = initialValue; | ||
| this._snapshot = this._createSnapshot(); | ||
| } | ||
| /** | ||
| * 현재 값 직접 가져오기 (액션 핸들러용) | ||
| * 핵심 로직: 불변성을 보장하는 깊은 복사본 반환 | ||
| * | ||
| * @implements lazy-evaluation | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 사용 시나리오: Action handler에서 최신 상태 읽기 | ||
| * 보안 강화: 외부에서 반환된 값을 수정해도 Store 내부 상태는 보호됨 | ||
| */ | ||
| getValue() { | ||
| if (this.cloningEnabled) { | ||
| if (this._lastClonedVersion === this._version && this._lastClonedValue !== null) return this._lastClonedValue; | ||
| this._lastClonedValue = safeGet(this._value, this.cloningEnabled); | ||
| this._lastClonedVersion = this._version; | ||
| return this._lastClonedValue; | ||
| } | ||
| return this._value; | ||
| } | ||
| /** | ||
| * Store 값 설정 및 구독자 알림 | ||
| * 핵심 로직: | ||
| * 1. 입력값의 불변성 보장을 위한 깊은 복사 (선택적 skip 가능) | ||
| * 2. 강화된 값 비교 시스템으로 불필요한 리렌더링 방지 | ||
| * 3. Structural sharing을 통한 성능 최적화 | ||
| * 4. 값 변경 시에만 스냅샷 재생성 및 알림 | ||
| * | ||
| * @implements unidirectional-data-flow | ||
| * @implements store-immutability | ||
| * @memberof architecture-terms | ||
| * | ||
| * 보안 강화: 입력값을 복사하여 Store 내부 상태가 외부 참조에 의해 변경되지 않도록 보호 | ||
| * 성능 강화: 다층 비교 시스템으로 정확한 변경 감지 및 렌더링 최적화 | ||
| */ | ||
| setValue(value, options) { | ||
| if (TypeGuards.isObject(value)) { | ||
| if (!TypeGuards.isRefState(value) && TypeGuards.isSuspiciousEventObject(value)) { | ||
| const eventHandling = options?.eventHandling || "block"; | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(value); | ||
| const hasPreventDefault = TypeGuards.isEventLike(value); | ||
| const isEvent = TypeGuards.isDOMEvent(value); | ||
| switch (eventHandling) { | ||
| case "allow": break; | ||
| case "transform": | ||
| if (options?.eventTransform) try { | ||
| value = options.eventTransform(value); | ||
| } catch (error) { | ||
| ErrorHandlers.store("Event transformation failed in Store.setValue", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }); | ||
| return; | ||
| } | ||
| else { | ||
| ErrorHandlers.store("Event transformation requested but no transform function provided", { | ||
| storeName: this.name, | ||
| valueType: typeof value | ||
| }); | ||
| return; | ||
| } | ||
| break; | ||
| case "block": | ||
| default: | ||
| ErrorHandlers.store("Event object detected in Store.setValue - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| valueType: typeof value, | ||
| constructorName: value?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(value) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| const safeValue = options?.skipClone ? value : safeSet(value, this.cloningEnabled); | ||
| let hasChanged = true; | ||
| if (!options?.skipComparison) hasChanged = this._compareValues(this._value, safeValue); | ||
| if (hasChanged) { | ||
| this._value = safeValue; | ||
| this._version++; | ||
| this._snapshot = this._createSnapshot(); | ||
| this._scheduleNotification(); | ||
| } | ||
| } | ||
| /** | ||
| * Update value using updater function with Immer integration | ||
| * 핵심 로직: | ||
| * 1. Immer produce를 사용하여 draft 객체 제공 | ||
| * 2. updater 결과를 불변성을 보장하며 설정 | ||
| * | ||
| * @implements store-immutability | ||
| * 보안 강화: Immer draft를 통한 안전한 상태 수정 | ||
| */ | ||
| update(updater) { | ||
| if (this.isUpdating) { | ||
| this.updateQueue.push(() => this.update(updater)); | ||
| return; | ||
| } | ||
| try { | ||
| this.isUpdating = true; | ||
| let updatedValue; | ||
| try { | ||
| updatedValue = produce(this._value, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (immerError) { | ||
| console.warn("[Store] Immer update failed, falling back to safe copy method", immerError); | ||
| const safeCurrentValue = safeGet(this._value, this.cloningEnabled); | ||
| try { | ||
| updatedValue = produce(safeCurrentValue, (draft) => { | ||
| const result = updater(draft); | ||
| return result !== void 0 ? result : draft; | ||
| }); | ||
| } catch (secondImmerError) { | ||
| console.warn("[Store] Immer completely failed, using direct update (immutability not guaranteed)", secondImmerError); | ||
| updatedValue = updater(safeCurrentValue); | ||
| } | ||
| } | ||
| if (TypeGuards.isObject(updatedValue)) { | ||
| if (!TypeGuards.isRefState(updatedValue) && TypeGuards.isSuspiciousEventObject(updatedValue)) { | ||
| const hasEventTarget = TypeGuards.hasTargetProperty(updatedValue); | ||
| const hasPreventDefault = TypeGuards.isEventLike(updatedValue); | ||
| const isEvent = TypeGuards.isDOMEvent(updatedValue); | ||
| ErrorHandlers.store("Event object detected in Store.update result - this may cause memory leaks", { | ||
| storeName: this.name, | ||
| updatedValueType: typeof updatedValue, | ||
| constructorName: updatedValue?.constructor?.name, | ||
| isEvent, | ||
| hasTargetProperty: hasEventTarget, | ||
| hasPreventDefault, | ||
| problematicProperties: TypeGuards.findProblematicProperties(updatedValue) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| this.setValue(updatedValue); | ||
| } finally { | ||
| this.isUpdating = false; | ||
| if (this.updateQueue.length > 0) { | ||
| const nextUpdate = this.updateQueue.shift(); | ||
| if (nextUpdate) Promise.resolve().then(nextUpdate); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Get number of active listeners | ||
| */ | ||
| getListenerCount() { | ||
| return this.listeners.size; | ||
| } | ||
| /** | ||
| * Clear all listeners | ||
| */ | ||
| clearListeners() { | ||
| this.listeners.clear(); | ||
| } | ||
| /** | ||
| * Register cleanup task for automatic execution on disposal | ||
| * | ||
| * Registers a cleanup function that will be automatically called when the store | ||
| * is disposed. This prevents memory leaks and ensures proper resource cleanup. | ||
| * | ||
| * @param task - Cleanup function to register | ||
| * @returns Unregister function to remove the cleanup task | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const timer = setInterval(() => {}, 1000); | ||
| * const unregister = store.registerCleanup(() => clearInterval(timer)); | ||
| * | ||
| * // Later, remove the cleanup task if needed | ||
| * unregister(); | ||
| * ``` | ||
| */ | ||
| registerCleanup(task) { | ||
| if (this.isDisposed) { | ||
| console.warn(`Store "${this.name}" is already disposed, cleanup task ignored`); | ||
| return () => {}; | ||
| } | ||
| this.cleanupTasks.add(task); | ||
| return () => this.cleanupTasks.delete(task); | ||
| } | ||
| /** | ||
| * Enhanced Store disposal with comprehensive cleanup | ||
| * | ||
| * Performs complete cleanup of all store resources including listeners, | ||
| * timers, cleanup tasks, and internal state. Prevents memory leaks and | ||
| * ensures proper resource disposal. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Manual disposal | ||
| * store.dispose(); | ||
| * | ||
| * // Auto-disposal with useEffect | ||
| * useEffect(() => { | ||
| * return () => store.dispose(); | ||
| * }, [store]); | ||
| * ``` | ||
| */ | ||
| dispose() { | ||
| if (this.isDisposed) return; | ||
| this.isDisposed = true; | ||
| try { | ||
| this.cleanupTasks.forEach((task) => { | ||
| try { | ||
| task(); | ||
| } catch (error) { | ||
| ErrorHandlers.store("Error during cleanup task execution", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| }); | ||
| this.cleanupTasks.clear(); | ||
| this.subscriptionRegistry = /* @__PURE__ */ new WeakMap(); | ||
| this.clearListeners(); | ||
| if (this.animationFrameId !== null) { | ||
| cancelAnimationFrame(this.animationFrameId); | ||
| this.animationFrameId = null; | ||
| } | ||
| this.pendingNotification = false; | ||
| this.updateQueue.length = 0; | ||
| } catch (error) { | ||
| ErrorHandlers.store("Critical error during store disposal", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| } | ||
| } | ||
| /** | ||
| * Check if store is disposed | ||
| * @returns true if store has been disposed | ||
| */ | ||
| isStoreDisposed() { | ||
| return this.isDisposed; | ||
| } | ||
| /** | ||
| * Store별 커스텀 비교 함수 설정 | ||
| * 이 Store에만 적용되는 특별한 비교 로직 설정 | ||
| * | ||
| * @param comparator - 커스텀 비교 함수 (oldValue, newValue) => boolean | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setCustomComparator(comparator) { | ||
| this.customComparator = comparator; | ||
| } | ||
| /** | ||
| * Store별 비교 옵션 설정 | ||
| * 이 Store에만 적용되는 비교 전략 설정 | ||
| * | ||
| * @param options - 비교 옵션 | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-config | ||
| */ | ||
| setComparisonOptions(options) { | ||
| this.comparisonOptions = options; | ||
| } | ||
| /** | ||
| * 성능 최적화: Store별 복사 동작 제어 | ||
| * | ||
| * @param enabled - true: 복사 활성화 (안전), false: 복사 비활성화 (성능 우선) | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/performance | ||
| */ | ||
| setCloningEnabled(enabled) { | ||
| this.cloningEnabled = enabled; | ||
| } | ||
| /** | ||
| * 현재 복사 설정 조회 | ||
| */ | ||
| isCloningEnabled() { | ||
| return this.cloningEnabled; | ||
| } | ||
| /** | ||
| * 강화된 값 비교 시스템 | ||
| * 1. 커스텀 비교 함수 우선 사용 | ||
| * 2. Store별 비교 옵션 적용 | ||
| * 3. 성능 최적화된 빠른 비교 fallback | ||
| * 4. 전역 비교 설정 사용 | ||
| * | ||
| * @param oldValue - 이전 값 | ||
| * @param newValue - 새로운 값 | ||
| * @returns true if values are different (change detected), false if same | ||
| * @protected | ||
| */ | ||
| _compareValues(oldValue, newValue) { | ||
| let result; | ||
| try { | ||
| if (this.customComparator) result = !this.customComparator(oldValue, newValue); | ||
| else if (this.comparisonOptions) result = !compareValues(oldValue, newValue, this.comparisonOptions); | ||
| else result = !compareValues(oldValue, newValue, { strategy: "reference" }); | ||
| } catch (error) { | ||
| ErrorHandlers.store("Error during value comparison, falling back to reference comparison", { storeName: this.name }, error instanceof Error ? error : void 0); | ||
| result = !Object.is(oldValue, newValue); | ||
| } | ||
| return result; | ||
| } | ||
| _createSnapshot() { | ||
| return { | ||
| value: safeGet(this._value, this.cloningEnabled), | ||
| name: this.name, | ||
| lastUpdate: Date.now() | ||
| }; | ||
| } | ||
| /** | ||
| * requestAnimationFrame 기반 알림 스케줄링 | ||
| * 브라우저의 다음 프레임에서 리스너 알림 실행 | ||
| */ | ||
| _scheduleNotification() { | ||
| if (this.notificationMode === "immediate") this._notifyListeners(); | ||
| else this._scheduleWithRAF(); | ||
| } | ||
| /** | ||
| * requestAnimationFrame을 사용한 알림 스케줄링 | ||
| * 누적 가능한 배치 시스템으로 개선 | ||
| */ | ||
| _scheduleWithRAF() { | ||
| this.pendingUpdatesCount++; | ||
| if (!this.pendingNotification) { | ||
| this.pendingNotification = true; | ||
| this.animationFrameId = requestAnimationFrame(() => { | ||
| this._executeNotification(); | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * 스케줄된 알림 실행 | ||
| */ | ||
| _executeNotification() { | ||
| this.pendingNotification = false; | ||
| this.animationFrameId = null; | ||
| const batchedUpdates = this.pendingUpdatesCount; | ||
| this.pendingUpdatesCount = 0; | ||
| this._notifyListeners(); | ||
| if (batchedUpdates > 1) console.debug(`[Store:${this.name}] Batched ${batchedUpdates} updates in single frame`); | ||
| } | ||
| /** | ||
| * Handle listener execution errors with recovery strategies | ||
| */ | ||
| _handleListenerError(error, listener) { | ||
| const now = Date.now(); | ||
| if (now - this.lastErrorTime > this.ERROR_RESET_TIME) this.errorCount = 0; | ||
| this.errorCount++; | ||
| this.lastErrorTime = now; | ||
| const metadata = this.subscriptionRegistry.get(listener); | ||
| if (metadata) metadata.errorCount++; | ||
| ErrorHandlers.store("Error in store listener execution", { | ||
| storeName: this.name, | ||
| listenerCount: this.listeners.size, | ||
| errorCount: this.errorCount, | ||
| subscriptionAge: metadata ? now - metadata.subscribedAt : "unknown" | ||
| }, error instanceof Error ? error : void 0); | ||
| if (metadata && metadata.errorCount >= 3) { | ||
| console.warn(`Removing problematic listener from store "${this.name}" after ${metadata.errorCount} errors`); | ||
| this.listeners.delete(metadata.enhancedListener); | ||
| this.subscriptionRegistry.delete(listener); | ||
| } | ||
| if (this.errorCount >= this.MAX_ERROR_COUNT) { | ||
| console.error(`Store "${this.name}" disabled due to excessive errors (${this.errorCount})`); | ||
| this.clearListeners(); | ||
| } | ||
| } | ||
| _notifyListeners() { | ||
| if (this.isDisposed) return; | ||
| this.listeners.forEach((listener) => { | ||
| if (this.isDisposed) return; | ||
| listener(); | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Factory function for creating type-safe Store instances | ||
| * | ||
| * Creates a new Store instance with the specified name and initial value. | ||
| * Provides type safety and integrates seamlessly with React hooks and | ||
| * the Context-Action framework patterns. | ||
| * | ||
| * @template T - The type of values stored in this store | ||
| * | ||
| * @param name - Unique identifier for the store (used for debugging) | ||
| * @param initialValue - Initial value to store | ||
| * | ||
| * @returns Configured Store instance ready for use | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/basic-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function createStore(name, initialValue) { | ||
| return new Store(name, initialValue); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/utils/sync-external-store-utils.ts | ||
| /** | ||
| * 향상된 구독 함수 생성 (내부 사용) | ||
| * 디바운싱, 스로틀링, 조건부 구독 기능 제공 | ||
| */ | ||
| function createEnhancedSubscriber(store, options = {}) { | ||
| const { debounce, throttle, condition, debug, name = "unknown" } = options; | ||
| return (callback) => { | ||
| if (!store) return () => {}; | ||
| let debounceTimer = null; | ||
| let throttleTimer = null; | ||
| let lastThrottleTime = 0; | ||
| const enhancedCallback = () => { | ||
| if (condition && !condition()) { | ||
| if (debug) console.debug(`[${name}] Subscription suspended due to condition`); | ||
| return; | ||
| } | ||
| const now = performance.now(); | ||
| if (throttle && throttle > 0) { | ||
| if (now - lastThrottleTime < throttle) { | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| throttleTimer = setTimeout(() => { | ||
| lastThrottleTime = performance.now(); | ||
| callback(); | ||
| }, throttle - (now - lastThrottleTime)); | ||
| return; | ||
| } | ||
| lastThrottleTime = now; | ||
| } | ||
| if (debounce && debounce > 0) { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| debounceTimer = setTimeout(() => { | ||
| callback(); | ||
| if (debug) console.debug(`[${name}] Debounced callback executed after ${debounce}ms`); | ||
| }, debounce); | ||
| return; | ||
| } | ||
| callback(); | ||
| }; | ||
| const unsubscribe = store.subscribe(enhancedCallback); | ||
| return () => { | ||
| if (debounceTimer) clearTimeout(debounceTimer); | ||
| if (throttleTimer) clearTimeout(throttleTimer); | ||
| unsubscribe(); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Null-safe Store 구독 훅 | ||
| * useSyncExternalStore를 기반으로 한 안전한 구독 | ||
| */ | ||
| function useSafeStoreSubscription(store, selector, options = {}) { | ||
| const { initialValue, equalityFn,...subscriptionOptions } = options; | ||
| const subscribe = useCallback((callback) => { | ||
| if (!store) return () => {}; | ||
| if (subscriptionOptions.debounce || subscriptionOptions.throttle || subscriptionOptions.condition) return createEnhancedSubscriber(store, subscriptionOptions)(callback); | ||
| return store.subscribe(callback); | ||
| }, [store, subscriptionOptions]); | ||
| const getSnapshot = useCallback(() => { | ||
| if (!store) return initialValue; | ||
| const snapshot = store.getSnapshot(); | ||
| return selector ? selector(snapshot.value) : snapshot.value; | ||
| }, [ | ||
| store, | ||
| selector, | ||
| initialValue | ||
| ]); | ||
| const cachedSnapshotRef = useRef(); | ||
| const stableGetSnapshot = useCallback(() => { | ||
| const currentSnapshot = getSnapshot(); | ||
| if (equalityFn && cachedSnapshotRef.current !== void 0) { | ||
| if (equalityFn(cachedSnapshotRef.current, currentSnapshot)) return cachedSnapshotRef.current; | ||
| } | ||
| cachedSnapshotRef.current = currentSnapshot; | ||
| return currentSnapshot; | ||
| }, [getSnapshot, equalityFn]); | ||
| const getServerSnapshot = useCallback(() => { | ||
| return initialValue; | ||
| }, [initialValue]); | ||
| return useSyncExternalStore(subscribe, equalityFn ? stableGetSnapshot : getSnapshot, getServerSnapshot); | ||
| } | ||
| /** | ||
| * 기본 동등성 비교 함수들 | ||
| */ | ||
| const equalityFunctions = { | ||
| reference: (a, b) => Object.is(a, b), | ||
| shallow: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) return false; | ||
| return true; | ||
| }, | ||
| deep: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) !== Array.isArray(b)) return false; | ||
| const keysA = Object.keys(a); | ||
| const keysB = Object.keys(b); | ||
| if (keysA.length !== keysB.length) return false; | ||
| for (const key of keysA) { | ||
| if (!Object.prototype.hasOwnProperty.call(b, key)) return false; | ||
| if (!equalityFunctions.deep(a[key], b[key])) return false; | ||
| } | ||
| return true; | ||
| }, | ||
| smart: (a, b) => { | ||
| if (Object.is(a, b)) return true; | ||
| if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; | ||
| if (Array.isArray(a) && Array.isArray(b)) { | ||
| if (a.length !== b.length) return false; | ||
| return a.every((item, index) => { | ||
| const bItem = b[index]; | ||
| if (typeof item === "object" && item !== null && typeof bItem === "object" && bItem !== null) return equalityFunctions.shallow(item, bItem); | ||
| return Object.is(item, bItem); | ||
| }); | ||
| } | ||
| return equalityFunctions.shallow(a, b); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/stores/hooks/useStoreSelector.ts | ||
| const defaultEqualityFn = equalityFunctions.smart; | ||
| const shallowEqual = equalityFunctions.shallow; | ||
| const deepEqual = equalityFunctions.deep; | ||
| const smartEqual = equalityFunctions.smart; | ||
| /** | ||
| * Hook for selective store subscription with performance optimization | ||
| * | ||
| * Subscribes to specific parts of store data using a selector function, | ||
| * triggering re-renders only when the selected value actually changes. | ||
| * Essential for preventing unnecessary re-renders in complex applications. | ||
| * | ||
| * @template T - Type of the store value | ||
| * @template R - Type of the value returned by the selector | ||
| * | ||
| * @param store - Store instance to subscribe to | ||
| * @param selector - Function to extract needed data from store value | ||
| * @param equalityFn - Function to compare previous and new values (default: Object.is) | ||
| * | ||
| * @returns The value returned by the selector function | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/store/advanced-hooks#usestoreselector-advanced-usage | ||
| * | ||
| * @public | ||
| */ | ||
| function useStoreSelector(store, selector, equalityFn = defaultEqualityFn) { | ||
| "use memo"; | ||
| const stableSelector = useCallback(selector, [selector]); | ||
| const stableEqualityFn = useCallback(equalityFn, [equalityFn]); | ||
| const selectorWarningShownRef = useRef(false); | ||
| if (selector !== stableSelector && !selectorWarningShownRef.current) { | ||
| console.warn("useStoreSelector: selector function changed. Consider wrapping it with useCallback to avoid unnecessary recalculations.", "Store:", store.name); | ||
| selectorWarningShownRef.current = true; | ||
| } | ||
| const previousValueRef = useRef(); | ||
| const subscribe = useCallback((callback) => { | ||
| return store.subscribe(callback); | ||
| }, [store]); | ||
| const getSnapshot = useCallback(() => { | ||
| try { | ||
| const selectedValue = stableSelector(store.getValue()); | ||
| if (previousValueRef.current !== void 0 && stableEqualityFn(previousValueRef.current, selectedValue)) return previousValueRef.current; | ||
| previousValueRef.current = selectedValue; | ||
| return selectedValue; | ||
| } catch (error) { | ||
| console.error("useStoreSelector: Error in selector function:", error); | ||
| throw error; | ||
| } | ||
| }, [ | ||
| store, | ||
| stableSelector, | ||
| stableEqualityFn | ||
| ]); | ||
| return useSyncExternalStore(subscribe, getSnapshot, getSnapshot); | ||
| } | ||
| //#endregion | ||
| //#region src/stores/components/StoreErrorBoundary.tsx | ||
| /** | ||
| * Store 시스템을 위한 에러 경계 컴포넌트 | ||
| * | ||
| * Store 관련 에러들을 캐치하고 적절한 fallback UI를 제공합니다. | ||
| * 개발 모드에서는 자세한 에러 정보를 표시하고, 프로덕션에서는 | ||
| * 사용자 친화적인 메시지를 보여줍니다. | ||
| */ | ||
| var StoreErrorBoundary = class extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.resetTimeoutId = null; | ||
| this.resetErrorBoundary = () => { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| this.setState({ | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }); | ||
| }; | ||
| this.state = { | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| errorId: null | ||
| }; | ||
| } | ||
| static getDerivedStateFromError(error) { | ||
| return { | ||
| hasError: true, | ||
| error: error instanceof Error && error.name === "ContextActionError" ? error : null, | ||
| errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` | ||
| }; | ||
| } | ||
| componentDidCatch(error, errorInfo) { | ||
| if (error.name === "ContextActionError") { | ||
| const contextActionError = error; | ||
| this.setState({ errorInfo }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } else { | ||
| const contextActionError = ErrorHandlers.store(`Unhandled error in Store component: ${error.message}`, { | ||
| component: "StoreErrorBoundary", | ||
| stack: error.stack, | ||
| componentStack: errorInfo.componentStack | ||
| }, error); | ||
| this.setState({ | ||
| error: contextActionError, | ||
| errorInfo | ||
| }); | ||
| this.props.onError?.(contextActionError, errorInfo); | ||
| } | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| const { hasError } = this.state; | ||
| const { resetOnPropsChange, resetKeys } = this.props; | ||
| if (hasError && resetOnPropsChange) { | ||
| if (resetKeys) { | ||
| if (resetKeys.some((key) => { | ||
| return prevProps[key] !== this.props[key]; | ||
| })) this.resetErrorBoundary(); | ||
| } else if (prevProps !== this.props) this.resetErrorBoundary(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| if (this.resetTimeoutId) clearTimeout(this.resetTimeoutId); | ||
| } | ||
| render() { | ||
| const { hasError, error, errorInfo } = this.state; | ||
| const { children, fallback } = this.props; | ||
| if (hasError) { | ||
| if (fallback) { | ||
| if (typeof fallback === "function") return fallback(error, errorInfo); | ||
| return fallback; | ||
| } | ||
| return this.renderDefaultFallback(); | ||
| } | ||
| return children; | ||
| } | ||
| renderDefaultFallback() { | ||
| return this.renderDevelopmentFallback(); | ||
| } | ||
| renderDevelopmentFallback() { | ||
| const { error, errorInfo, errorId } = this.state; | ||
| const stats = getErrorStatistics(); | ||
| return /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| padding: "20px", | ||
| margin: "20px", | ||
| border: "2px solid #ff6b6b", | ||
| borderRadius: "8px", | ||
| backgroundColor: "#ffe0e0", | ||
| fontFamily: "monospace" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("h2", { | ||
| style: { | ||
| color: "#d63031", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "🚨 Store Error Boundary" | ||
| }), | ||
| /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("strong", { children: "Error ID:" }), | ||
| " ", | ||
| errorId | ||
| ] | ||
| }), | ||
| error && /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("strong", { children: "Error Type:" }), | ||
| " ", | ||
| error.type, | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| /* @__PURE__ */ jsx("strong", { children: "Message:" }), | ||
| " ", | ||
| error.message, | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| /* @__PURE__ */ jsx("strong", { children: "Timestamp:" }), | ||
| " ", | ||
| new Date(error.timestamp).toISOString() | ||
| ] | ||
| }), | ||
| error?.context && /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ jsx("strong", { children: "Context:" }), /* @__PURE__ */ jsx("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "12px" | ||
| }, | ||
| children: JSON.stringify(error.context, null, 2) | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ jsxs("div", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("strong", { children: "Error Statistics:" }), | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| "Total Errors: ", | ||
| stats.totalErrors, | ||
| /* @__PURE__ */ jsx("br", {}), | ||
| "Recent Errors: ", | ||
| stats.recentErrors.length | ||
| ] | ||
| }), | ||
| errorInfo && /* @__PURE__ */ jsxs("details", { | ||
| style: { marginBottom: "15px" }, | ||
| children: [/* @__PURE__ */ jsx("summary", { | ||
| style: { | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Component Stack" | ||
| }), /* @__PURE__ */ jsx("pre", { | ||
| style: { | ||
| background: "#f8f9fa", | ||
| padding: "10px", | ||
| borderRadius: "4px", | ||
| overflow: "auto", | ||
| fontSize: "11px", | ||
| whiteSpace: "pre-wrap" | ||
| }, | ||
| children: errorInfo.componentStack | ||
| })] | ||
| }), | ||
| /* @__PURE__ */ jsx("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#00b894", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer", | ||
| fontWeight: "bold" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| renderProductionFallback() { | ||
| return /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| padding: "20px", | ||
| textAlign: "center", | ||
| backgroundColor: "#f8f9fa", | ||
| border: "1px solid #dee2e6", | ||
| borderRadius: "8px", | ||
| margin: "20px 0" | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("h3", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 10px 0" | ||
| }, | ||
| children: "Something went wrong" | ||
| }), | ||
| /* @__PURE__ */ jsx("p", { | ||
| style: { | ||
| color: "#6c757d", | ||
| margin: "0 0 15px 0" | ||
| }, | ||
| children: "We're sorry, but something unexpected happened. Please try again." | ||
| }), | ||
| /* @__PURE__ */ jsx("button", { | ||
| onClick: this.resetErrorBoundary, | ||
| style: { | ||
| padding: "8px 16px", | ||
| backgroundColor: "#007bff", | ||
| color: "white", | ||
| border: "none", | ||
| borderRadius: "4px", | ||
| cursor: "pointer" | ||
| }, | ||
| children: "Try Again" | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Enhanced HOC for automatic Store Error Boundary wrapping with security | ||
| * | ||
| * @template P - Component props type | ||
| * @param WrappedComponent - Component to wrap with error boundary | ||
| * @param errorBoundaryProps - Error boundary configuration | ||
| * @returns Enhanced component with comprehensive error handling | ||
| */ | ||
| function withStoreErrorBoundary(WrappedComponent, errorBoundaryProps) { | ||
| const WithStoreErrorBoundaryComponent = (props) => /* @__PURE__ */ jsx(StoreErrorBoundary, { | ||
| ...errorBoundaryProps, | ||
| children: /* @__PURE__ */ jsx(WrappedComponent, { ...props }) | ||
| }); | ||
| WithStoreErrorBoundaryComponent.displayName = `withStoreErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name})`; | ||
| return WithStoreErrorBoundaryComponent; | ||
| } | ||
| /** | ||
| * 특정 Store와 연결된 에러 경계 생성 헬퍼 | ||
| */ | ||
| function createStoreErrorBoundary(storeName, customFallback) { | ||
| return ({ children }) => /* @__PURE__ */ jsx(StoreErrorBoundary, { | ||
| fallback: customFallback, | ||
| onError: (error, errorInfo) => { | ||
| console.group(`Store Error in ${storeName}`); | ||
| console.error("Error:", error); | ||
| console.error("Component Stack:", errorInfo.componentStack); | ||
| console.groupEnd(); | ||
| }, | ||
| resetKeys: [storeName], | ||
| children | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/actions/ActionContext.tsx | ||
| function createActionContext(contextNameOrConfig = {}, config) { | ||
| let effectiveConfig; | ||
| let contextName; | ||
| if (typeof contextNameOrConfig === "string") { | ||
| contextName = contextNameOrConfig; | ||
| effectiveConfig = { | ||
| ...config, | ||
| name: config?.name || contextName | ||
| }; | ||
| } else { | ||
| effectiveConfig = contextNameOrConfig; | ||
| contextName = effectiveConfig.name || "ActionContext"; | ||
| } | ||
| const FactoryActionContext = createContext(null); | ||
| const Provider = ({ children }) => { | ||
| const actionRegisterRef = useRef(new ActionRegister(effectiveConfig)); | ||
| const contextValue = useMemo(() => ({ actionRegisterRef }), []); | ||
| return /* @__PURE__ */ jsx(FactoryActionContext.Provider, { | ||
| value: contextValue, | ||
| children | ||
| }); | ||
| }; | ||
| const useFactoryActionContext = () => { | ||
| const context = useContext(FactoryActionContext); | ||
| if (!context) throw new Error("useFactoryActionContext must be used within a factory ActionContext Provider"); | ||
| return context; | ||
| }; | ||
| /** | ||
| * Optimized hook to get stable dispatch functions | ||
| * | ||
| * Returns stable dispatch functions that prevent re-renders and maintain | ||
| * reference equality across component renders. | ||
| * | ||
| * @returns Object with dispatch and dispatchWithResult functions | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useActionDispatcher = () => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| return { | ||
| dispatch: useCallback((action, payload, options) => { | ||
| console.log(`React dispatch called for '${String(action)}':`, { | ||
| hasPayload: payload !== void 0, | ||
| hasOptions: options !== void 0, | ||
| timestamp: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister is not initialized. Make sure the ActionContext Provider is properly set up."); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]), | ||
| dispatchWithResult: useCallback((action, payload, options) => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions); | ||
| }, [actionRegisterRef]) | ||
| }; | ||
| }; | ||
| const useAction = () => { | ||
| const { dispatch } = useActionDispatcher(); | ||
| return dispatch; | ||
| }; | ||
| const useActionHandler = (action, handler, config$1) => { | ||
| const { actionRegisterRef } = useFactoryActionContext(); | ||
| const actionId = useId(); | ||
| const handlerRef = useRef(handler); | ||
| handlerRef.current = handler; | ||
| const priority = config$1?.priority ?? 0; | ||
| const id = config$1?.id || `react_${String(action)}_${actionId}`; | ||
| const blocking = config$1?.blocking ?? false; | ||
| const once = config$1?.once ?? false; | ||
| const debounce = config$1?.debounce; | ||
| const throttle = config$1?.throttle; | ||
| const stableConfig = useMemo(() => ({ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| replaceExisting: true, | ||
| ...debounce !== void 0 && { debounce }, | ||
| ...throttle !== void 0 && { throttle } | ||
| }), [ | ||
| priority, | ||
| id, | ||
| blocking, | ||
| once, | ||
| debounce, | ||
| throttle | ||
| ]); | ||
| useEffect(() => { | ||
| const register = actionRegisterRef.current; | ||
| if (!register) return; | ||
| const wrapperHandler = (payload, controller) => { | ||
| return handlerRef.current(payload, controller); | ||
| }; | ||
| console.log(`Registering handler for '${String(action)}'`); | ||
| return register.register(action, wrapperHandler, stableConfig); | ||
| }, [ | ||
| action, | ||
| actionRegisterRef, | ||
| stableConfig | ||
| ]); | ||
| }; | ||
| /** | ||
| * Hook that provides direct access to the ActionRegister instance | ||
| * | ||
| * This hook is useful when you need to: | ||
| * - Register multiple handlers dynamically | ||
| * - Access other ActionRegister methods like clearAction, getHandlers, etc. | ||
| * - Implement complex handler registration logic | ||
| * - Have more control over the registration lifecycle | ||
| * | ||
| * @returns ActionRegister instance or null if not initialized | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-access | ||
| */ | ||
| const useFactoryActionRegister = () => { | ||
| return useFactoryActionContext().actionRegisterRef.current; | ||
| }; | ||
| /** | ||
| * Hook that provides access to the dispatchWithResult function | ||
| * | ||
| * This hook returns a function that dispatches actions and returns detailed | ||
| * execution results including collected handler results, execution metadata, | ||
| * and error information. | ||
| * | ||
| * @returns dispatchWithResult function with full type safety | ||
| * | ||
| * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-with-result | ||
| */ | ||
| const useFactoryActionDispatchWithResult = () => { | ||
| const context = useFactoryActionContext(); | ||
| const activeControllersRef = useRef(/* @__PURE__ */ new Set()); | ||
| const dispatch = useCallback((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatch(action, payload, dispatchOptions); | ||
| }, [context.actionRegisterRef]); | ||
| const dispatchWithResult = useCallback((action, payload, options) => { | ||
| const register = context.actionRegisterRef.current; | ||
| if (!register) throw new Error("ActionRegister not initialized"); | ||
| const dispatchOptions = { | ||
| ...options, | ||
| ...options?.signal ? {} : { autoAbort: { | ||
| enabled: true, | ||
| allowHandlerAbort: true, | ||
| onControllerCreated: (controller) => { | ||
| activeControllersRef.current.add(controller); | ||
| } | ||
| } } | ||
| }; | ||
| return register.dispatchWithResult(action, payload, dispatchOptions); | ||
| }, [context.actionRegisterRef]); | ||
| const abortAll = useCallback(() => { | ||
| activeControllersRef.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| activeControllersRef.current.clear(); | ||
| }, []); | ||
| const resetAbortScope = useCallback(() => { | ||
| abortAll(); | ||
| }, [abortAll]); | ||
| useEffect(() => { | ||
| const controllers = activeControllersRef; | ||
| return () => { | ||
| controllers.current.forEach((controller) => { | ||
| if (!controller.signal.aborted) controller.abort(); | ||
| }); | ||
| controllers.current.clear(); | ||
| }; | ||
| }, []); | ||
| return { | ||
| dispatch, | ||
| dispatchWithResult, | ||
| abortAll, | ||
| resetAbortScope | ||
| }; | ||
| }; | ||
| return { | ||
| Provider, | ||
| useActionContext: useFactoryActionContext, | ||
| useActionDispatch: useAction, | ||
| useActionHandler, | ||
| useActionRegister: useFactoryActionRegister, | ||
| useActionDispatchWithResult: useFactoryActionDispatchWithResult, | ||
| context: FactoryActionContext | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { Store, StoreErrorBoundary, StoreRegistry, createActionContext, createStore, createStoreErrorBoundary, defaultEqualityFn, useSafeStoreSubscription, useStoreSelector, withStoreErrorBoundary }; | ||
| //# sourceMappingURL=ActionContext-BuVbb-bA.js.map |
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
588784
0.56%7280
0.75%Updated