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 🙏

© 2026 – Pkg Stats / Ryan Hefner

promise-stateful-rest

v1.0.3

Published

This is a wrapper to make network objects & collections more friendly to state-tracking frameworks like Vue, React etc

Readme

promise-stateful-rest

Overview

This is a package built to make it easier to work with promises and, more generally, fetch()-like interfaces, in code which tracks state. In particular, that means reactive UI frameworks like Vue, React etc.

Simple example:

import { Collected, LoadBuffer, Type } from "promise-stateful-rest"

/**
 * Class which fetches content for `n` books at once
 */
class BatchBookContentHandler extends Collected.Batch {
    /**
     * @type {LoadBuffer<id, T> | null}
     */
    static loadBuffer = null

    delayMs = 100

    loadBufferStorage = BatchBookContentHandler

    async loadItems(ids) {
        const params = new URLSearchParams(
            ids.map(id => ["filter[id][]", id])
        )
        const response = await fetch(`${this.identity}?${params}`)
        const body = await response.responseBody()
        return response.json()
    }

    constructor() {
        super("/book")
    }
}
/**
 * Class which understands how to fetch its content in batch form
 */
class BatchBook {
    /**
     * @type {BatchBookContentHandler}
     */
    static contentHandler = new BatchBookContentHandler()

    /**
     * @type {BatchBookContentHandler}
     */
    contentHandler = BatchBook.contentHandler

    /**
     * @param {number} id
     */
    constructor(id) {
        this.id = id
    }

    get name() {
        return this.contentHandler.get(this.id)?.name
    }
}
const myCollection = new Map(
    [1, 2, 3].map(id => [id, new BatchBook(id)])
)

So you have a loader class with some shared storage; and an object class which relies on the loader to find its data.

Collection Concepts

The below are described in REST terms, but other network protocols or client-side APIs should be comparable if they use the same kind of concepts.

Cheap IDs

This is for REST-like systems which can get the IDs of a collection very easily, but will take a while to get other object content. In general with these you would fetch all the IDs of the collection in one call, but would then have n calls to fetch the content for several objects.

If a GET on a collection looks something like the below, you probably have cheap IDs:

{"items":[1,2,3,4,5]}

Some of these systems also support fetching a batch of items, since that's really needed for decent efficiency, but in all cases they should support directly fetching a single object.

Expensive IDs

This is for REST-like systems which can get the IDs of a collection, but it takes a little while. This would include ones which simply have extremely large or poorly indexed collections, as well as ones will return whole objects for the collection.

If a GET on a collection looks something like the below, you probably have expensive IDs:

{"items":[{"id":1,"name":"One","colour":"red"},{"id":2,"name":"Two","colour":"green"},{"id":3,"name":"Three","colour":"blue"}]}

These systems are very likely to support filtering and pagination; if yours doesn't, you won't get much benefit here.

Note: Hybrid systems like JSON:API exist; these have cheap IDs in relationships only. Most of the time this is equivalent to being cheap overall, but if you don't have a top-level object for the user you may be able to make it behave like a cheap-ID system by requesting the IDs only.

Usage

Write-back properties

You might have existing objects, in particular UI components, which need writable stores to work properly but you have an asynchronously-loaded datum to use with that model.

Async init method

In some cases it'll make sense to delay the object looking "valid" at all until an async-await has loaded your data. If so, you don't have to do anything special: just await your load call in your async init method.

Load-on-start

Sometimes you might want to trigger a load immediately but will accept the object being partly complete until it's done - this might be the case if the loaded resource provides the main functionality of the object but there is some kind of other functionality to use before it's loaded. In this case you can try to make your call in your init method or constructor, using .then() to write the value when the load completes. This can be tricky in terms of dealing with undefined and null, so you might want to use load-on-demand below anyway.

Load-on-demand

If you want to trigger a load only when something is trying to use the resource, you can use WriteBackStore to produce a value which is undefined until loaded, something like:

import { WriteBackStore } from "promise-stateful-rest"
class MyExistingClass {
    public myOnDemandProp = new WriteBackStore(
        () => fetch("/some/value")
            .then(r => r.responseBody())
            .then(b => b.json())
    )

    get myOnDemandValue() {
        return this.myOnDemandProp.value
    }
}

This provides a thing which is always stored (the WriteBackStore itself) as well as a trigger to use it.

Cheap IDs

If you've got a system where IDs are cheap to fetch (and other content is not),

If you don't have a batch endpoint

You can have your own collection object with the list of IDs, and then fetch the specific object when explicitly requested, or iteratively do so through the entire collection. That's perfectly viable, but it means a lot of requests and correspondingly bad performance.

If you have a batch endpoint

If you have a batch endpoint, it will make sense to fetch several items at once; you only have the challenge of working out which items that should be.

If you really want to, you can pre-calculate that, in which case you can use the expensive IDs process. The below just deals with implied load demand.

Using load-on-demand items only

You can have your own collection object with the list of IDs, and from that emit objects which know how to do load-on-demand. In effect, this is a loading collection object, but inside-out.

This works by queueing the ID of the item to be loaded, and once that queue is stale all of the queued items will be fetched at once.

import { Collected, LoadBuffer, Type } from "promise-stateful-rest"

class BatchBookContentHandler extends Collected.Batch {
    static loadBuffer = null

    delayMs = 100

    get loadBufferStorage() {
        return BatchBookContentHandler
    }

    async loadItems(ids) {
        const params = new URLSearchParams({
            filter: {id: ids},
        })
        const response = await fetch(`${this.identity}?${params}`)
        const body = await response.responseBody()
        return response.json()
    }

    constructor() {
        super("/foo/bar")
    }
}

class BatchBook {
    static contentHandler = new BatchBookContentHandler()

    contentHandler = BatchBook.contentHandler

    constructor(id) {
        this.id = id
    }

    get name() {
        return this.contentHandler.get(this.id)?.name
    }
}
Adding a Preloaded Collection

In addition to the item class, you can use a collection object which will wrap the list of IDs. This is just a convenience - if you want to you could equivalently build a Map by mapping the ID list.

const myCollection = new Map(
    [1, 2, 3].map(id => [id, new BatchBook(id)])
)
for(const book of myCollection.values()) {
    // Do something with book
}