rxdb
Version:
A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/
86 lines (76 loc) • 3.02 kB
text/typescript
import { signal, Signal } from "@preact/signals-core";
import type {
Observable,
Subscription
} from 'rxjs';
import type {
WeakRef,
FinalizationRegistry,
RxReactivityFactory,
ReactivityLambda
} from '../../types/index.d.ts';
/**
* Type-level function (ReactivityLambda) for Preact signals.
* Use this as the Reactivity type parameter for properly typed signals.
*
* @example
* const db = await createRxDatabase<MyCollections, any, any, PreactSignalReactivityLambda>({
* reactivity: PreactSignalsRxReactivityFactory
* });
* const signal = doc.age$$; // Signal<number>
*/
export interface PreactSignalReactivityLambda extends ReactivityLambda {
readonly _result: Signal<this['_data']>;
}
export type PreactSignal<T = any> = Signal<T>;
/**
* Exported to debug and test
* the behavior
*/
export const PREACT_SIGNAL_STATE = {
subscribeCount: 0,
signalBySubscription: new WeakMap<Subscription, PreactSignal>(),
aliveSubscription: new Set<Subscription>(),
};
function removeSubscription(sub: Subscription) {
const isAlive = PREACT_SIGNAL_STATE.aliveSubscription.has(sub);
if (isAlive) {
PREACT_SIGNAL_STATE.aliveSubscription.delete(sub);
PREACT_SIGNAL_STATE.signalBySubscription.delete(sub);
PREACT_SIGNAL_STATE.subscribeCount = PREACT_SIGNAL_STATE.subscribeCount - 1;
sub.unsubscribe();
}
}
function cleanupCallback(sub: Subscription) {
// called when a signal becomes garbage collected
removeSubscription(sub);
}
const cleanupRegistry: FinalizationRegistry<Subscription> = new FinalizationRegistry(cleanupCallback) as any;
export const PreactSignalsRxReactivityFactory: RxReactivityFactory<PreactSignalReactivityLambda> = {
fromObservable<Data, InitData>(
obs: Observable<Data>,
initialValue: InitData
): PreactSignal<Data | InitData> {
const mySignal = signal<Data | InitData>(initialValue);
const sigRef: WeakRef = new WeakRef(mySignal);
const sub = obs.subscribe((value: Data) => {
const sig = PREACT_SIGNAL_STATE.signalBySubscription.get(sub);
if (sig && sigRef.deref()) {
sig.value = value;
} else {
/**
* The callback of FinalizationRegistry is not reliable by definition
* so we have this fallback to still clean unused signals subscriptions
* when they emit but the signal is already not used anymore.
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry#notes_on_cleanup_callbacks
*/
removeSubscription(sub);
}
});
PREACT_SIGNAL_STATE.aliveSubscription.add(sub);
PREACT_SIGNAL_STATE.signalBySubscription.set(sub, mySignal);
PREACT_SIGNAL_STATE.subscribeCount = PREACT_SIGNAL_STATE.subscribeCount + 1;
cleanupRegistry.register({}, sub);
return mySignal;
}
};