This library provides two high-level bridges and one low-level bridge:
toSignaltoObservabletoSubscriberThe library depends on @kayahr/observable for the observable API surface. For RxJS and other implementations, toSubscriber is the low-level escape hatch.
toSignal converts a Subscribable<T> to a signal getter.
import { toSignal } from "@kayahr/signal";
import { BehaviorSubject } from "rxjs";
const source = new BehaviorSubject(1);
const value = toSignal(source, {
requireSync: true
});
value(); // 1
Without options, the returned getter yields undefined until the observable emits for the first time.
Use initialValue when the observable may emit later, but the signal should already expose a concrete value immediately.
const count = toSignal(source, {
initialValue: 0
});
That does two things:
count() returns 0 until the first observable emission arrives.Getter<number> instead of Getter<number | undefined>.This is the right option for observables that do not emit synchronously on subscription but where a local fallback value is acceptable.
Use requireSync when the observable is expected to emit a current value immediately during subscription.
const value = toSignal(source, {
requireSync: true
});
This is stricter than initialValue:
Getter<T>toSignal throws if the observable does not emit synchronously during subscriptionThat is a good fit for current-value observables such as BehaviorSubject.
requireSync and initialValue cannot be combined. They express different contracts:
initialValue says "use this fallback until the first value arrives"requireSync says "there must already be a value now"equals works exactly like it does for signals, memos, and resources. By default, toSignal uses Object.is.
Use a custom equality function when the observable emits new object instances but dependents should only react when a meaningful part actually changed.
const user = toSignal(source, {
equals: (previous, next) => previous.id === next.id
});
In that example, a new object with the same id does not update the signal, so dependent effects and memos stay clean.
Set equals to false when every emission should count as a change, even if the emitted value compares equal to the previous one.
const tick = toSignal(source, {
equals: false
});
Observable errors are stored and rethrown when the signal getter is read.
Observable completion does not dispose the signal conversion automatically.
When the source completes:
dispose(value) still exists, but usually has nothing left to do because the source has already removed the subscriptionIn practice, that means the getter simply keeps returning the last value until the signal itself becomes unreachable and can be garbage collected.
The returned getter can be manually disposed with dispose(...) and is also registered on the active scope, if there is one.
import { dispose } from "@kayahr/scope";
import { toSignal } from "@kayahr/signal";
const value = toSignal(source, {
initialValue: 0
});
dispose(value);
toObservable converts a signal getter or memo getter to an observable from @kayahr/observable.
import { toObservable } from "@kayahr/signal";
const observable = toObservable(count);
const subscription = observable.subscribe(value => {
console.log(value);
});
Each subscription gets its own effect. The current value is emitted immediately, then subsequent updates are forwarded synchronously.
toSubscriber is the low-level bridge for foreign observable constructors.
import { Observable } from "rxjs";
import { toSubscriber } from "@kayahr/signal";
const observable = new Observable<number>(toSubscriber(count));
That is the intended path when you want to stay inside another observable ecosystem without forcing this library to expose every foreign observable type directly.