Skip to main content
Home
This package has been archived, and as such it is read-only.

Built and signed on GitHub Actions

Works with
This package works with Node.js, BrowsersIt is unknown whether this package works with Cloudflare Workers, Bun
It is unknown whether this package works with Cloudflare Workers
This package works with Node.js
It is unknown whether this package works with Bun
This package works with Browsers
JSR Score100%
Publisheda month ago (0.1.0)

A lightweight, RxJS-inspired library implementing the Observer pattern in JavaScript. Features BroadcastSubjects with AbortController-based unsubscription, supporting both synchronous and asynchronous producers.

import { isObserver, type Observer } from "jsr:@xan/observer@^0.1.0"; import { Subject } from "jsr:@xan/subject@^0.1.0"; import type { BroadcastSubjectConstructor } from "./broadcast-subject-constructor.ts"; /** * Object type that acts as a variant of [`Subject`](https://jsr.io/@xan/subject/doc/~/Subject). */ export type BroadcastSubject<Value = unknown> = Subject<Value>; /** * A fixed UUID that is used to scope the name of the underlying {@linkcode BroadcastChannel}. This ensures that our * {@linkcode BroadcastSubject}'s only communicate with other {@linkcode BroadcastSubject}'s from this library * with _almost_ complete certainty. * * @internal Do NOT export. */ const namespace = "394068c9-9d2c-45cb-81d2-a09197594a9d"; export const BroadcastSubject: BroadcastSubjectConstructor = class { readonly [Symbol.toStringTag] = "BroadcastSubject"; readonly #subject = new Subject(); readonly signal = this.#subject.signal; readonly #channel: BroadcastChannel; constructor(name: string) { if (arguments.length === 0) { throw new TypeError("1 argument required but 0 present"); } if (typeof name !== "string") { throw new TypeError("Parameter 1 is not of type 'String'"); } Object.freeze(this); this.#channel = new BroadcastChannel(`${namespace}:${name}`); this.signal.addEventListener("abort", () => this.#channel.close(), { once: true, }); this.#channel.onmessage = (event) => this.#subject.next(event.data); this.#channel.onmessageerror = (event) => this.#subject.throw(event); } next(value: unknown): void { if (!(this instanceof BroadcastSubject)) { throw new TypeError("'this' is not instanceof 'BroadcastSubject'"); } try { this.#channel.postMessage(value); } catch (error) { this.#subject.throw(error); } } return(): void { if (this instanceof BroadcastSubject) this.#subject.return(); else throw new TypeError("'this' is not instanceof 'BroadcastSubject'"); } throw(value: unknown): void { if (this instanceof BroadcastSubject) this.#subject.throw(value); else throw new TypeError("'this' is not instanceof 'BroadcastSubject'"); } subscribe(observer: Observer): void { if (!(this instanceof BroadcastSubject)) { throw new TypeError("'this' is not instanceof 'BroadcastSubject'"); } if (arguments.length === 0) { throw new TypeError("1 argument required but 0 present"); } if (!isObserver(observer)) { throw new TypeError("Parameter 1 is not of type 'Observer'"); } this.#subject.subscribe(observer); } }; Object.freeze(BroadcastSubject); Object.freeze(BroadcastSubject.prototype);