From cb3425627651ec561e50e3ff259a061bfb92a760 Mon Sep 17 00:00:00 2001 From: Alexander Zinn Date: Thu, 30 Oct 2025 03:14:53 -0400 Subject: [PATCH] Add observable pattern implementation with Subject and ReadOnlySubject classes - Introduced Subject class for managing state and notifying listeners - Added ReadOnlySubject class to provide read-only access to Subject values - Created assertUnreachable utility function for unreachable code handling - Updated observables index file to export Subject and ReadOnlySubject --- src/lang/assertUnreachable.ts | 3 ++ src/lang/observables/ReadOnlySubject.ts | 18 ++++++++++++ src/lang/observables/Subject.ts | 39 +++++++++++++++++++++++++ src/lang/observables/index.ts | 4 +++ 4 files changed, 64 insertions(+) create mode 100644 src/lang/assertUnreachable.ts create mode 100644 src/lang/observables/ReadOnlySubject.ts create mode 100644 src/lang/observables/Subject.ts create mode 100644 src/lang/observables/index.ts diff --git a/src/lang/assertUnreachable.ts b/src/lang/assertUnreachable.ts new file mode 100644 index 0000000..35e5943 --- /dev/null +++ b/src/lang/assertUnreachable.ts @@ -0,0 +1,3 @@ +export default function assertUnreachable(x: never): never { + throw new Error(`Unreachable code [${x}]`); +} diff --git a/src/lang/observables/ReadOnlySubject.ts b/src/lang/observables/ReadOnlySubject.ts new file mode 100644 index 0000000..72242d8 --- /dev/null +++ b/src/lang/observables/ReadOnlySubject.ts @@ -0,0 +1,18 @@ +import Disposable from '../disposables/Disposable'; +import Subject from './Subject'; + +export default class ReadOnlySubject { + private readonly _subject: Subject; + + constructor(subject: Subject) { + this._subject = subject; + } + + get value(): T { + return this._subject.value; + } + + public subscribe(listener: (value: T) => void): Disposable { + return this._subject.subscribe(listener); + } +} diff --git a/src/lang/observables/Subject.ts b/src/lang/observables/Subject.ts new file mode 100644 index 0000000..08f2da4 --- /dev/null +++ b/src/lang/observables/Subject.ts @@ -0,0 +1,39 @@ +import Disposable from '../disposables/Disposable'; + +export default class Subject { + private readonly _listeners: Array<(value: T) => void> = []; + private _value: T; + + constructor(value: T) { + this._value = value; + } + + set value(value: T) { + if (this._value === value) { + return; + } + + this._value = value; + this.notifyListeners(value); + } + + get value(): T { + return this._value; + } + + public subscribe(listener: (value: T) => void): Disposable { + const listenerIndex = this._listeners.push(listener); + + listener(this.value); + + return new Disposable(() => { + this._listeners.splice(listenerIndex, 1); + }); + } + + private notifyListeners(value: T): void { + for (const listener of this._listeners) { + listener(value); + } + } +} diff --git a/src/lang/observables/index.ts b/src/lang/observables/index.ts new file mode 100644 index 0000000..fae5a89 --- /dev/null +++ b/src/lang/observables/index.ts @@ -0,0 +1,4 @@ +import Subject from './Subject'; +import ReadOnlySubject from './ReadOnlySubject'; + +export {Subject, ReadOnlySubject};