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

@solid-primitives/resource

v0.3.1

Published

A template primitive example.

Downloads

19,720

Readme

@solid-primitives/resource

turborepo size version stage

A collection of composable primitives to augment createResource

Installation

npm install @solid-primitives/resource
# or
yarn add @solid-primitives/resource
# or
pnpm add @solid-primitives/resource

How to use it

Here's an example of all of them combined:

// abort signal will abort the resource in-flight if it takes more then 10000ms
const [signal, abort] = makeAbortable({ timeout: 10000 });

const fetcher = (url: string) => fetch(url, { signal: signal() }).then(r => r.json());

// cached fetcher will not be called if something for the same URL is still in cache
const [cachedFetcher, invalidate] = makeCache(fetcher, { storage: localStorage });

// works with createResource, or any wrapping API with the same interface
const [data, { refetch }] = createResource(address, fetcher);
const aggregatedData = createAggregated(data);

Obviously, you're not limited to resources using fetch. You can use them on any resource you want.

createAggregated

Aggregates the output of a resource:

const aggregated: Accessor<T> = createAggregated(
  resource: Resource<T>, initialValue?: T | U
);
const pages = createAggregated(currentPage, []);
  • null will not overwrite undefined
  • if the previous value is an Array, incoming values will be appended
  • if any of the values are Objects, the current one will be shallow-merged into the previous one
  • if the previous value is a string, more string data will be appended
  • otherwise the incoming data will be put into an array

Objects and Arrays are re-created on each operation, but the values will be left untouched, so <For> should work fine.

createDeepSignal

Usually resources in Solid.js are immutable. Every time the resource updates, every subscriber of it is updated. Starting with Solid.js 1.5, createResource allows to receive a function returning something akin to a signal in options.storage. This allows to provide the underlying storage for the resource in order to change its reactivity. This allows to add fine-grained reactivity to resources so that you can subscribe to nested properties and only trigger updates as they actually occur:

// this adds fine-grained reactivity to the contents of data():
const [data, { refetch }] = createResource(fetcher, { storage: createDeepSignal });

Warning if your resource is a deep signal, you can no longer rely on reactive changes to the base signal. If you want to combine this with createAggregated, you will need to wrap the resource to either call deepTrack or read one of the reactive parts that you know for certain will change every time:

import { deepTrack } from "@solid-primitives/deep";

const [data] = createResource(source, fetcher, { storage: createDeepSignal });
const aggregated = makeAggregated(() => deepTrack(data()));

makeAbortable

Orchestrates AbortController creation and aborting of abortable fetchers, either on refetch or after a timeout, depending on configuration:

// definition
const [
  signal: AbortSignal,
  abort: () => void,
  filterErrors: <E>(err: E) => E instanceof AbortError ? void : E
] = makeAbortable({
  timeout?: 10000,
  noAutoAbort?: true,
});

// usage
const fetcher = (url: string) => fetch(
  url, { signal: signal() }
).then(r => r.json(), filterErrors);
  • The signal function always returns a signal that is not yet aborted; if noAutoAbort is not set to true, calling it will also abort a previous signal, if present
  • The abort callback will always abort the current signal
  • If timeout is set, the signal will be aborted after that many Milliseconds
  • The filterErrors function can be used to filter out abort errors

createAbortable

This function does exactly the same as makeAbortable, but also automatically aborts on cleanup. Only use within a reactive scope.

makeCache

Creates a caching fetcher, with the ability to persist the cache, to invalidate entries and manage expired entries:

const [
  fetcher: ResouceFetcher<S, T>,
  invalidate: ((source?: S) => void) & { all: () => void },
  expired: Accessor<{ source: S, data: T }>
] =
  makeCache(
    fetcher: ResourceFetcher<S, T>,
    options?: {
      cache?: Record<string, { source: S, data: T }>,
      expires?: number | (entry: { source: S, data: T }) => number,
      storage?: Storage,
      storageKey?: string,
    }
  );

Wraps the fetcher to use a cache. Returns the wrapped fetcher, an invalidate callback that requires the source to invalidate the request and a signal accessor with the last automatically invalidated request.

Can be customized with the following optional options:

  • cache - allows to use a local cache instead of the global one
  • expires - allows to define a custom timeout; either accepts a number or a function that receives an object with source and data of the request and returns a number in Milliseconds
  • serialize - a tuple [serialize, deserialize] used for persistence, default is [JSON.stringify, JSON.parse]
  • sourceHash - a function receiving the source (true if none is used) and returns a hash string
  • storage - a storage like localStorage to persist the cache over reloads
  • storageKey - the key which is used to store the cache in the storage

⚠ the default sourceHash function works with simple types as well as Headers and Maps, but will fail on recursive objects and will throw on Symbols. It should work for simple RequestInit type objects and is pretty small.

makeRetrying

Creates a fetcher that retries multiple times in case of errors.

const fetcher = makeRetrying(url => fetch(url).then(r => r.json()), { retries: 5, delay: 500 });

Receives the fetcher and an optional options object and returns a wrapped fetcher that retries on error after a delay multiple times.

The optional options object contains the following optional properties:

  • delay - number of Milliseconds to wait before retrying; default is 5s
  • retries - number of times a request should be repeated before giving up throwing the last error; default is 3 times

Recipes: the missing pieces

Maybe you have considered using TanStack Query instead of this collection and if you have a lot of complex requirements around requests, this might even be a better fit. However, if only a few crucial pieces of it are missing for you, here are some recipes to fill them in:

Query keys

Nothing stops you from using keys as Source for your fetch request through makeCache. Those keys will be serialized as identifiers for caching, too. All you need is a translation table from keys to the actual request in the fetcher.

Network mode

Just filter your source with isOnline from the connectivity package:

import { createConnectivitySignal } from "@solid-primitives/connectivity";

const isOnline = createConnectivitySignal();
const source = () => isOnline() && url();
const [data] = createResource(source, url => fetch(url));

Window focus refetching

The createEventListener primitive from the event-listener pacakge comes in helpful to do that:

import { createEventListener } from "@solid-primitives/event-listener";

const [data, { refetch }] = createResource(() => fetch("url"));
createEventListener(document, "visibilitychange", () => document.hidden || refetch());

You could also augment this code with the scheduled package to throttle the refetch calls not to happen again sooner than after 5s:

import { createEventListener } from "@solid-primitives/event-listener";
import { throttle } from "@solid-primitives/scheduled";

const [data, { refetch }] = createResource(() => fetch("url"));
const runRefetch = throttle(refetch, 5000);
createEventListener(document, "visibilitychange", () => document.hidden || runRefetch());

Stopping a refetch interval when hidden or offline

If you are polling, this approach might come useful:

import { createConnectivitySignal } from "@solid-primitives/connectivity";
import { createEventSignal } from "@solid-primitives/event-listener";
import { createTimer } from "@solid-primitives/timer";

const [data, { refetch }] = createResource(() => fetch("url").then(r => r.json()));
const [setPaused] = createTimer(refetch, 5000, setInterval);
const visibilityChange = createEventSignal(document, "visibilitychange");
const isOnline = createConnectivitySignal();
createEffect(() => setPaused((visibilityChange(), document.hidden) || !isOnline());

Mutations

In TanStack Query, mutations are requests that mutate data on the server, so we are less interested in the answer and more in the timing. Fortunately, Solid already comes with a mutate action in the createResource return value:

const [todos, { mutate, refetch }] = createResource(getTodos);

const addTodo = todo => {
  [mutation] = createResource(() => addTodo(todo));
  const current = todos();
  // optimistic update
  mutate(todos => [...current, todo]);
  // refetch after mutating on the server or error
  createRoot(done =>
    createEffect(() => {
      if (mutation.error || !mutation.loading) {
        refetch();
        done();
      }
    }),
  );
  return () => mutation.state;
};

Scroll Restoration

This is already covered in @solidjs/router.

Polling

Just use an interval with refetch; ideally, also use makeAbortable.

Demo

You may view a working example of our resource primitives here: https://primitives.solidjs.community/playground/resource/

Changelog

See CHANGELOG.md