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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@marianmeres/fetch-store

v3.0.3

Published

A reactive [store](https://github.com/marianmeres/store) utility for managing async fetch operations with built-in state tracking for loading, errors, and success counts. Designed for Svelte-compatible reactivity but works with any framework.

Readme

@marianmeres/fetch-store

A reactive store utility for managing async fetch operations with built-in state tracking for loading, errors, and success counts. Designed for Svelte-compatible reactivity but works with any framework.

Install

deno add jsr:@marianmeres/fetch-store
npm i @marianmeres/fetch-store

Basic Example

// user-store.ts
import { createFetchStore } from "@marianmeres/fetch-store";

const userStore = createFetchStore(async (userId: string) => {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) throw new Error("Failed to fetch user");
    return await response.json();
});

// Fetch user data
await userStore.fetch("123");

// Access current state
const { data, isFetching, lastFetchError } = userStore.get();

Svelte Integration

<!-- UserProfile.svelte -->
<script>
    import { onMount } from 'svelte';
    import { userStore } from './user-store.ts';

    export let userId;
    onMount(() => userStore.fetch(userId));
</script>

{#if $userStore.isFetching}
    <Spinner />
{:else if $userStore.lastFetchError}
    <Error error={$userStore.lastFetchError} />
{:else}
    <UserCard user={$userStore.data} />
{/if}

Features

  • Reactive state management - Subscribe to loading, error, and data states
  • Silent fetching - Update data without triggering loading indicators
  • Fetch-once with caching - Prevent redundant fetches within a time threshold
  • Polling support - Built-in recursive fetching for real-time updates
  • Request deduplication - Optionally return in-flight promises for concurrent calls
  • Abort support - Cancel in-flight requests with AbortController integration
  • Stream support - Handle SSE, WebSocket, or push-based data sources

Abortable Requests

Enable automatic request cancellation when a new fetch starts:

import { createFetchStore } from "@marianmeres/fetch-store";

const searchStore = createFetchStore(
    async (query: string, signal: AbortSignal) => {
        const response = await fetch(`/api/search?q=${query}`, { signal });
        if (!response.ok) throw new Error("Search failed");
        return await response.json();
    },
    null,
    null,
    { abortable: true }
);

// Rapid calls - each new fetch aborts the previous one
searchStore.fetch("h");      // Aborted
searchStore.fetch("he");     // Aborted
searchStore.fetch("hel");    // Aborted
searchStore.fetch("hello");  // This one completes

// Manual abort
searchStore.abort();

Request Deduplication

Prevent duplicate requests when multiple calls happen while one is in-flight:

const userStore = createFetchStore(
    async (userId) => fetch(`/api/users/${userId}`).then(r => r.json()),
    null,
    null,
    { dedupeInflight: true }
);

// These all return the same promise (only one HTTP request is made)
const p1 = userStore.fetch("123");
const p2 = userStore.fetch("123");
const p3 = userStore.fetch("123");

p1 === p2 && p2 === p3; // true

Streaming Data

For streaming data sources, use createFetchStreamStore:

import { createFetchStreamStore } from "@marianmeres/fetch-store";

const sseStore = createFetchStreamStore((emit, url) => {
    const eventSource = new EventSource(url);

    eventSource.onmessage = (event) => {
        emit("data", JSON.parse(event.data));
    };

    eventSource.onerror = (error) => {
        emit("error", error);
    };

    // Return cleanup function
    return () => {
        eventSource.close();
        emit("end");
    };
});

// Start streaming (with optional auto-restart after 5s delay)
const stop = sseStore.fetchStream(["/api/events"], 5000);

// Later: stop the stream
stop();

Breaking Changes (v3.0.0)

This major release includes the following breaking changes:

Simplified Generic Types

The FetchStore<T> and FetchStreamStore<T> interfaces have been simplified from a two-generic pattern to a single generic:

// Before (v2.x)
interface FetchStore<T, V = T extends FetchStoreValue<infer D> ? D : T> { ... }

// After (v3.x)
interface FetchStore<T> extends StoreReadable<FetchStoreValue<T>> { ... }

Migration: If you were using explicit generic parameters, simply use the data type directly: FetchStore<User> instead of FetchStore<FetchStoreValue<User>, User>.

Simplified API Signature

The createFetchStore and createFetchStreamStore functions now have a simpler signature. The dataFactory parameter has been moved into the options object:

// Before (v2.x)
createFetchStore(fetchWorker, initial, dataFactory, options)

// After (v3.x)
createFetchStore(fetchWorker, initial, options)
// dataFactory is now in options: { dataFactory: (data, old) => transformedData }

Migration: Move your dataFactory from the third parameter into the options object:

// Before
const store = createFetchStore(
    async () => fetchData(),
    null,
    (data, old) => ({ ...old, ...data }),
    { dedupeInflight: true }
);

// After
const store = createFetchStore(
    async () => fetchData(),
    null,
    {
        dataFactory: (data, old) => ({ ...old, ...data }),
        dedupeInflight: true
    }
);

DataFactory Type Change

The DataFactory<T> type now receives properly typed data instead of unknown:

// Before (v2.x)
type DataFactory<T> = (raw: any, old?: T) => T;

// After (v3.x)
type DataFactory<T> = (data: T, old?: T) => T;

The fetchWorker must now return Promise<T> directly, making the types consistent throughout the API.

API Reference

createFetchStore<T>(fetchWorker, initial?, options?)

Creates a reactive store for async fetch operations.

Parameters:

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | fetchWorker | (...args) => Promise<T> | required | Async function performing the fetch. Must return data of type T. When abortable: true, receives AbortSignal as the last argument. | | initial | T \| null | null | Initial data value | | options | FetchStoreOptions<T> | {} | Configuration options |

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | fetchOnceDefaultThresholdMs | number | 300000 | Default threshold (5 min) for fetchOnce before allowing re-fetch | | dedupeInflight | boolean | false | If true, concurrent fetch() calls return the same promise | | abortable | boolean | false | If true, creates AbortController for each fetch, aborting previous requests | | dataFactory | (data: T, old?: T) => T | - | Transform fetched data (useful for merge strategies) | | onReset | () => void | - | Callback invoked when reset() is called |

Returns: FetchStore<T>

FetchStore Methods

| Method | Signature | Description | |--------|-----------|-------------| | subscribe | (fn: (value) => void) => () => void | Subscribe to store changes (Svelte-compatible) | | get | () => FetchStoreValue<T> | Get current store value | | fetch | (...args) => Promise<T \| null> | Execute fetch, sets isFetching to true | | fetchSilent | (...args) => Promise<T \| null> | Execute fetch without updating isFetching | | fetchOnce | (args?, thresholdMs?) => Promise<T \| null> | Fetch only if not already fetched or threshold passed | | fetchOnceSilent | (args?, thresholdMs?) => Promise<T \| null> | Silent version of fetchOnce | | fetchRecursive | (args?, delayMs?) => () => void | Start polling, returns cancel function | | reset | () => void | Reset store to initial state | | resetError | () => void | Clear lastFetchError only | | touch | (data?: T) => void | Update timestamps (tricks fetchOnce), optionally set data | | abort | () => void | Abort in-flight requests (requires abortable: true) | | getInternalDataStore | () => StoreLike<T> | Access internal data store |

FetchStoreValue Properties

The store value contains both data and metadata:

| Property | Type | Description | |----------|------|-------------| | data | T \| null | The fetched data (null before first fetch or after reset) | | isFetching | boolean | Whether a fetch is in progress | | lastFetchStart | Date \| null | When the last fetch started | | lastFetchEnd | Date \| null | When the last fetch completed | | lastFetchError | Error \| null | Error from the last fetch, if any | | lastFetchSilentError | Error \| null | Error from the last silent fetch, if any | | successCounter | number | Number of successful fetches |

createFetchStreamStore<T>(fetchStreamWorker, initial?, options?)

Creates a reactive store for streaming data sources.

Parameters:

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | fetchStreamWorker | (emit, ...args) => (() => void) \| void | required | Worker receiving emit callback for events. Emit receives data of type T. Should return cleanup function. | | initial | T \| null | null | Initial data value | | options | FetchStreamStoreOptions<T> | {} | Configuration options |

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | dataFactory | (data: T, old?: T) => T | - | Transform received data (useful for merge strategies) | | onReset | () => void | - | Callback invoked when reset() is called |

Emit Events:

  • emit("data", value) - Push new data
  • emit("error", error) - Report an error
  • emit("end") - Signal stream completion

Returns: FetchStreamStore<T>

FetchStreamStore Methods

| Method | Signature | Description | |--------|-----------|-------------| | subscribe | (fn: (value) => void) => () => void | Subscribe to store changes | | get | () => FetchStreamStoreValue<T> | Get current store value | | fetchStream | (args?, recursiveDelayMs?) => () => void | Start stream, returns stop function. If recursiveDelayMs > 0, restarts after "end" event. | | reset | () => void | Reset store to initial state | | resetError | () => void | Clear lastFetchError only | | getInternalDataStore | () => StoreLike<T> | Access internal data store |

Package Identity

  • Name: @marianmeres/fetch-store
  • Author: Marian Meres
  • Repository: https://github.com/marianmeres/fetch-store
  • License: MIT