/** * MIT License * * Copyright (c) 2025 Chris M. Perez * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { Effect, Predicate, SubscriptionRef } from 'effect'; import type { Signal, ReadonlySignal } from '../types/index.js'; import { Dep } from './dep.js'; import { traceSignalCreate, traceSignalUpdate, } from '../layers/tracing/signals.js'; export type { Signal }; interface SignalInternal extends Signal { readonly _ref: SubscriptionRef.SubscriptionRef; readonly _dep: Dep; readonly _version: { value: number }; readonly _traceId: string; } // Initialize reactive signal export function signal(initialValue: T, name?: string): Signal { const refEffect = SubscriptionRef.make(initialValue); const ref = Effect.runSync(refEffect); const dep = new Dep(); const version = { value: 0 }; let cached = initialValue; const traceId = traceSignalCreate(name, initialValue); const signalObj: SignalInternal = { get _ref() { return ref; }, get _dep() { return dep; }, get _version() { return version; }, get _traceId() { return traceId; }, get value(): T { dep.track(); return cached; }, set value(newValue: T) { if (!Object.is(cached, newValue)) { const prevValue = cached; cached = newValue; version.value++; Effect.runSync(SubscriptionRef.set(ref, newValue)); dep.trigger(); traceSignalUpdate(traceId, prevValue, newValue); } }, }; return signalObj; } // Build readonly signal view export function readonlySignal(source: Signal): ReadonlySignal { return { get value() { return source.value; }, }; } // Detect reactive signal export function isSignal(value: unknown): value is Signal { if (!Predicate.isObject(value)) { return false; } const obj = value as Record; if (!('value' in obj)) { return false; } if ('_ref' in obj || '_dep' in obj) { return true; } return false; } // Resolve signal value export function unref(maybeSignal: T | Signal): T { return isSignal(maybeSignal) ? maybeSignal.value : maybeSignal; } // Access internal subscription ref export function getSignalRef( sig: Signal ): SubscriptionRef.SubscriptionRef | null { const internal = sig as SignalInternal; return '_ref' in internal ? internal._ref : null; } // Access internal dependency tracker export function getSignalDep(sig: Signal): Dep | null { const internal = sig as SignalInternal; return '_dep' in internal ? internal._dep : null; }