One kilobyte reactive state management library for JavaScript and TypeScript.
It's heavily inspired by RxJS, but it's much more lightweight.
npm install cuprumimport { Cuprum } from "cuprum";
const count$ = new Cuprum<number>();
count$.subscribe((value) => {
console.log(value); // 1, 2, 3, ...
});
count$.dispatch(1);
count$.dispatch(2);
count$.dispatch(3);The core class. Represents an observable stream that can also dispatch values.
const obs$ = new Cuprum<T>();Emits a value to all current subscribers. Returns this for chaining.
obs$.dispatch("hello");Subscribes to future values. If a value has already been dispatched, fn is called immediately with the current value. The callback receives both the new value and the previous value.
obs$.subscribe((value, oldValue) => {
console.log(value, oldValue);
});Removes a previously subscribed function.
function handler(value: string) { ... }
obs$.subscribe(handler);
obs$.unsubscribe(handler);Returns the last dispatched value without subscribing.
const current = obs$.value();Returns a new observable whose values are transformed by fn.
const doubled$ = number$.map((n) => n * 2);Returns a new observable that only emits values for which fn returns true.
const positive$ = number$.filter((n) => n > 0);Returns a Promise that resolves with the next dispatched value.
const value = await obs$.promise();Returns a read-only view of this observable. The returned object has no dispatch method — calling it throws an error. Useful for exposing a stream publicly without allowing external dispatches.
const readOnly$ = obs$.observable();Removes all subscribers.
obs$.clear();Creates a Cuprum that emits DOM events from the given element. Supports Window, Document, and HTMLElement with full TypeScript event map inference. The event listener is added only while there is at least one subscriber.
import { fromEvent } from "cuprum";
const click$ = fromEvent(document.getElementById("btn"), "click");
click$.subscribe((event) => console.log(event));Like fromEvent but for custom or non-standard event names, without type inference.
import { fromCustomEvent } from "cuprum";
const open$ = fromCustomEvent(element, "open");
open$.subscribe(() => console.log("opened"));Combines up to 7 observables into a single observable that emits a tuple of the latest values from each source. Emits whenever any source emits. Sources that have not yet emitted contribute undefined to the tuple.
import { combine } from "cuprum";
const combined$ = combine(name$, age$);
combined$.subscribe(([name, age]) => {
console.log(name, age);
});Merges multiple observables into one. Emits each value as-is from whichever source emits.
import { merge } from "cuprum";
const all$ = merge(stream1$, stream2$, stream3$);
all$.subscribe((value) => console.log(value));Creates an observable that emits an incrementing integer starting from 0 at the given interval in milliseconds. The timer only runs while there is at least one subscriber.
import { interval } from "cuprum";
const timer$ = interval(1000);
const sub = timer$.subscribe((i) => console.log(i)); // 0, 1, 2, ...
// Stop the timer
sub.unsubscribe();A read-only view of Cuprum<T> — all methods except dispatch are available. Returned by .observable(), combine(), and merge().
type Observable<T> = Omit<Cuprum<T>, "dispatch">;Returned by .subscribe() and .subscribeHot(). Call .unsubscribe() to stop receiving values.
interface Subscription {
unsubscribe(): void;
}const pipe$ = new Cuprum<string>();
pipe$.subscribe((value) => console.log(value));
pipe$.dispatch("hello"); // logs: helloconst source$ = new Cuprum<string>();
const upper$ = source$.map((s) => s.toUpperCase());
upper$.subscribe((v) => console.log(v));
source$.dispatch("hello"); // logs: HELLOconst numbers$ = new Cuprum<number>();
numbers$.filter((n) => n % 2 === 0).subscribe((n) => console.log(n));
numbers$.dispatch(1); // ignored
numbers$.dispatch(2); // logs: 2
numbers$.dispatch(3); // ignored
numbers$.dispatch(4); // logs: 4const value$ = new Cuprum<string>();
value$.subscribe((value, oldValue) => {
console.log(`${oldValue} → ${value}`);
});
value$.dispatch("a"); // undefined → a
value$.dispatch("b"); // a → bconst obs$ = new Cuprum<string>();
setTimeout(() => obs$.dispatch("done"), 1000);
const result = await obs$.promise();
console.log(result); // "done"const a$ = new Cuprum<string>();
const b$ = new Cuprum<number>();
combine(a$, b$).subscribe(([a, b]) => console.log(a, b));
a$.dispatch("x"); // "x", undefined
b$.dispatch(1); // "x", 1
a$.dispatch("y"); // "y", 1const a$ = new Cuprum<string>();
const b$ = new Cuprum<string>();
merge(a$, b$).subscribe((v) => console.log(v));
a$.dispatch("from a"); // "from a"
b$.dispatch("from b"); // "from b"const tick$ = interval(500);
const sub = tick$.subscribe((i) => console.log(i)); // 0, 1, 2, ...
setTimeout(() => sub.unsubscribe(), 3000); // stop after 3 secondsCopyright 2023 Edwin Martin and released under the MIT license.