npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

derive-state

v0.1.0-alpha.8

Published

State library that implements the Observer pattern, built with reactivity and composition in mind.

Downloads

1,513

Readme

DeriveState

State library that implements the Observer pattern, built with reactivity and composition in mind.

Heavily inspired from RxJS, but while RxJS focuses on asynchronous values, DeriveState focuses on stateful boxes, (aka cells in a spreadsheet) - As if everything were a BehaviourSubject, and you create new states deriving them from other states.

Experimental

Usage

State

import { State } from 'derive-state';

const apples = new State(0);

apples.subscribe(apples => console.log(`We now have ${apples} apple(s)`));
// Logs "We now have 0 apple(s)"

apples.setValue(2);
// Logs "We now have 2 apple(s)"

console.log('apples: ' + apples.getValue());
// Logs "apples: 2"

The initial value is optional: if omitted the state will be empty

import { State } from 'derive-state';

const apples = new State();

apples.subscribe(apples => console.log(`We now have ${apples} apple(s)`));
// Doesn't log anything

// We can't read the value synchronously in this case
expect(() => apples.getValue()).toThrow();

// We have a function that tell us whether it holds a value or not
expect(apples.hasValue()).toBe(false);

// Or we can get a promise for when it finally has a value
apples.value.then(v => console.log(`Promise resolved with ${v}`));

apples.setValue(2);
// Logs "We now have 2 apple(s)" followed by "Promise resolved with 2"

expect(apples.hasValue()).toBe(true);

Subscriptions and state can both get cleaned up:

import { State } from 'derive-state';

const apples = new State(0);

const unsub1 = apples.subscribe(
  apples => console.log(`We now have ${apples} apple(s)`)
  () => console.log("complete")
);
// Logs "We now have 0 apple(s)"

unsub1();
apples.setValue(2);
// Doesn't log anything

apples.subscribe(
  apples => console.log(`We now have ${apples} apple(s)`)
  () => console.log("complete")
);
// Logs "We now have 2 apple(s)"

apples.close();
// Logs "complete"

// We can't set the value of a closed state.
//     As semantically it means this value won't ever change again.
expect(() => apples.setValue(5)).toThrow();

// But we can still read the value
console.log('apples: ' + apples.getValue());
// Logs "apples: 2"

// And subscribe (it will just call back and close the subscription immediately)
apples.subscribe(
  apples => console.log(`We now have ${apples} apple(s)`)
  () => console.log("complete")
);
// Logs "We now have 2 apple(s)" followed by "complete"

Stateless

An stateless observable doesn't hold any value. It has two main ways it can be used:

Similar to an event emitter

import { Stateless } from 'derive-state';

const clicks = new Stateless();
clicks.emit('click');
clicks.subscribe(() => console.log('received a click'));
// Logs nothing

clicks.emit('click');
// Logs "received a click"

Derived from another observable

import { Stateless } from 'derive-state';
const apples = new State(2);
const squaredApples = new Stateless(obs => {});

const squaredApples = new Stateless(obs =>
  apples.subscribe(apples => obs.next(apples * apples), obs.complete)
);
squaredApples.subscribe(apples =>
  console.log(`We now have ${apples} squared apple(s)`)
);
// Logs "We now have 4 squared apple(s)"

// Equivalent when using `pipe`:
const pipedSquaredApples = apples.pipe(map(apples => apples * apples));

A stateless is lazy: Meaning it won't run the derive function until someone subscribes to it.

A stateless is multicast: It will share the derive function along all the subscribers in the chain.

Stateless are designed to make composition easily: This way we can write operators that define their behaviour, and we can compose them easily with pipe.

Note that this has an apparent issue:

const squaredApples = apples.pipe(map(apples => apples * apples));
squaredApples.subscribe(apples =>
  console.log(`We now have ${apples} squared apple(s)`)
);
// Logs "We now have 4 squared apple(s)"

squaredApples.subscribe(apples =>
  console.log(`We now have ${apples} squared apple(s)`)
);
// Doesn't log anything

This is due to the nature of squaredApples being stateless and multicast: By the time the second subscriber comes in, apples has already emitted its value, so it won't receive it. When apple changes, both subscribers will receive the new value.

That's why usually you would capture these stateless observables into stateful observables. The trade-off is that while stateless observables get cleaned up when they don't have subscriptions, stateful observables as they hold a value they need cleaning up, that's why you'd call capture only after you've applied the composed operator chain

const pears = new State(10);

// eq. combineLatest in rxjs
const totalFruits = combine([apples, pears])
  .pipe(map(([apples, pears]) => apples + pears))
  .capture();

totalFruits.subscribe(fruits => console.log(`We now have ${fruits} fruits`));
// Logs "We now have 12 fruits"

totalFruits.subscribe(fruits => console.log(`We now have ${fruits} fruits`));
// Logs "We now have 12 fruits"

Operators

Creation operators

  • just(value): Emits value on the first subscription.
  • combine(obj): subscribes to every observable in obj, and emits the value of all of them in the same structure as obj (works with arrays too)
  • merge(array): subscribes to all the observables in array and emits every value from them.

Pipeable operators

  • map(fn): maps the values from the source stream by using the map function.
  • filter(fn): filters changes based on the filter function.
  • distinctUntilChanged(): prevents emitting the same value twice in a row.
  • switchMap(fn): flattens out the observable returned by the map function, unsubscribing from the previous ones.
  • take(n): updates at most N times.
  • skip(n): skips the first N values.
  • withDefault(value): adds in a value if none is present.
  • scan(accumulator, initialValue?): accumulates and emits values using the accumulator function