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

reffx

v1.1.0

Published

A tiny utility for creating reference count-aware effects

Downloads

285

Readme

reffx is a tiny utility for creating reference count-aware effects (reffx) requiring clean-ups ("disposing").

When you create a reffx, the effect is automatically invoked only at "first reference" and is cleaned up only upon the disposal of the "last reference".

Example

import { reffx } from "reffx";

const clock = reffx(() => {
  const intervalId = setInterval(() = console.log("tick"), 1000);
  return () => clearInterval(intervalId);
})

Now suppose we call clock() the first time, this would get the console logging tick every one second.

const disposeClock = clock();

Now we have one active reference to the effect clock. Later, we call clock() again, this would simply leave the current interval (rather than starting another setInterval) because the effect is already active with a ref count of one.

const disposeClock2 = clock(); // nothing happens

In order to stop the clock, we must call both disposeClock() and disposeClock2() to bring the active reference to the effect clock to zero.

Usage

import { reffx } from "reffx";
const fx = reffx(() => {
  /* ... effectful behavior here ... */
  return () => {
    /* ... effect disposer here .. */
  }
})

The reference count-aware effect fx now will keep track of the number of calls. Each call returns a disposer function that must be called to neutralize that reference.

const dispose1 = fx(); // ref count = 1, effect started
const dispose2 = fx(); // ref count = 2, nothing happens
const dispose3 = fx(); // ref count = 3, nothing happens
dispose3(); // ref count = 2, nothing happens
dispose3(); // ref count still = 2 because disposers are idempotent, nothing happens
dispose1(); // ref count = 1, nothing happens
dispose2(); // ref count = 0, effect disposed.

Keyed Reffx

keyedReffx is similar to reffx except it produces a map of effects that are maintained, and the reference count is maintained for each referentially distinct map key.

import { keyedReffx } from "reffx";
const subscribeToTopic = keyedReffx(
  (topicId: string) => /* ... some API calls here ... */);

const disposeTopicA1 = fx("topicA"); // topic A subscription starts
const disposeTopicB1 = fx("topicB"); // topic B subscription starts
const disposeTopicB2 = fx("topicB");
const disposeTopicA2 = fx("topicA");

disposeTopicA1();
disposeTopicB2();
disposeTopicB1(); // topic B subscription cleaned up
disposeTopicA2(); // topic A subscription cleaned up

Reffx with Effect Object

It can be useful for the effect to expose an interface with functionality that can be used as long as the effect is active. In this case, use objectReffx, which now returns a tuple of the effect object and the disposer.

import { objectReffx } from "reffx";
const clock = objectReffx(() => {
  let time = new Date();
  const getTime = () => time;
  const intervalId = setInterval(() => new Date(), 1000);
  return [getTime, () => clearInterval(intervalId)];
});

const [getTime, disposeClock] = clock();
getTime(); // some date object here
disposeClock(); // reduces reference count to clock effect by 1.

A keyed version of objectReffx, called keyedObjectReffx, is also availble.

Effect object decorator.

The tuple of effect object/disposer returned by objectReffx and keyedObjectReffx is the default behavior. It is also possible to pass another argument to these functions that customizes how the effect object and the disposer are combined.

import { objectReffx } from "reffx";
const clock = objectReffx(() => {
  let time = new Date();
  const getTime = () => time;
  const intervalId = setInterval(() => new Date(), 1000);
  return [getTime, () => clearInterval(intervalId)];
}, (getTime, disposeClock) => ({ getTime, disposeClock }));

const clockObject = clock();
clockObject.getTime(); // some date object here
clockObject.disposeClock(); // reduces reference count to clock effect by 1.

Reference-counted subscription

Using the idea of effect object, one can create a reference-counted effect that exposes a subscriber that automatically destroys the effect is no longer used.

import { objectReffx } from "reffx";
import { atomicEvent } from "atomic-event";

const subClock = objectReffx(() => {
  let time = new Date();
  const [sub, pub] = atomicEvent();
  const intervalId = setInterval(() => pub(new Date()), 1000);
  return [sub, () => clearInterval(intervalId)];
}, (sub, disposeClock) => (callback) => {
  const unsub = sub(callback);
  return () => { unsub(); disposeClock(); }
});

const unsubClock1 = subClock(time => console.log(time));
const unsubsubClock2 = clock(time => console.log(time));
unsubClock1(); // clock still ticking
unsubClock2(); // clock disposed

This pattern can be quite useful, so the package provides a shortcut, namely subReffx and keyedSubReffx, that assumes your effect function to return a subscriber/effect disposer pair. It then automatically decorates your effect object in the manner shown above. The decorator itself is also available as the import named asSubscriber.

import { subReffx } from "reffx";
import { atomicEvent } from "atomic-event";

const subClock = subReffx(() => {
  let time = new Date();
  const [sub, pub] = atomicEvent();
  const intervalId = setInterval(() => pub(new Date()), 1000);
  return [sub, () => clearInterval(intervalId)];
});

const unsubClock1 = subClock(time => console.log(time));
const unsubsubClock2 = clock(time => console.log(time));
unsubClock1(); // clock still ticking
unsubClock2(); // clock disposed

Use Cases

Reference count-aware effects can be used to perform effectful behavior such as subscription in a way that different consumers can simply invoke the effect without worrying about creating the same effects. For instance, if different parts of the UI require the same effect at different time, reffx can make sure that the effect only happens once.