diff --git a/.eslintrc.cjs b/.eslintrc.cjs index cada6b46..b734c520 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -44,7 +44,7 @@ module.exports = { ], // To encourage good usage of RxJS: "rxjs/no-exposed-subjects": "error", - "rxjs/finnish": "error", + "rxjs/finnish": ["error", { names: { "^this$": false } }], }, settings: { react: { diff --git a/src/state/Behavior.ts b/src/state/Behavior.ts new file mode 100644 index 00000000..8b2ce9a5 --- /dev/null +++ b/src/state/Behavior.ts @@ -0,0 +1,52 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { BehaviorSubject, Observable } from "rxjs"; + +import { type ObservableScope } from "./ObservableScope"; + +/** + * A stateful, read-only reactive value. As an Observable, it is "hot" and + * always replays the current value upon subscription. + * + * A Behavior is to BehaviorSubject what Observable is to Subject; it does not + * provide a way to imperatively set new values. For more info on the + * distinction between Behaviors and Observables, see + * https://monoid.dk/post/behaviors-and-streams-why-both/. + */ +export type Behavior = Omit, "next" | "observers">; + +/** + * Creates a Behavior which never changes in value. + */ +export function constant(value: T): Behavior { + return new BehaviorSubject(value); +} + +declare module "rxjs" { + interface Observable { + /** + * Converts this Observable into a Behavior. This requires the Observable to + * synchronously emit an initial value. + */ + behavior(scope: ObservableScope): Behavior; + } +} + +const nothing = Symbol("nothing"); + +Observable.prototype.behavior = function ( + this: Observable, + scope: ObservableScope, +): Behavior { + const subject$ = new BehaviorSubject(nothing); + // Push values from the Observable into the BehaviorSubject + this.pipe(scope.bind()).subscribe(subject$); + if (subject$.value === nothing) + throw new Error("Behavior failed to synchronously emit an initial value"); + return subject$ as Behavior; +};