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

svelte-cache-store

v0.5.7

Published

A Svelte store for caching API data compatible with Svelte 4.

Downloads

115

Readme

CacheStore for Svelte

This library provides a cache store for Svelte applications that simplifies fetching, creating, updating, and deleting items from a REST API that adheres to the simple-json-api specification. It also supports side-loading of related data and automatic cache management.

Features

  • Centralized Cache Management: Manage different types of data with separate caches.
  • Lazy Fetching: Data is only fetched from the server when needed, and cached for future use.
  • Side-Loading Support: Automatically handles side-loaded data returned by the API and caches it accordingly.
  • Sorting: Supports sorting of fetched data based on specified columns.
  • Comprehensive CRUD Operations: Simplified methods to create, read, update, and delete items.

Side-Loading Support

The cache store automatically handles side-loaded data. When an API response includes side-loaded data (e.g., related images returned along with blog posts), the store will add this data to the appropriate cache if the type is registered

Rationale

I find myself implementing similar backend APIs when I develop applications. These APis are more often than not based on REST principles with JSON payloads. As a result, I have publised the start of a simple JSON API spec at http://simple-json-api.com. This specification makes it easy to write both backend and frontend code to handle the data communication between the applications in a uniform manner. This approach makes it possible to write a fairly small amount of code to get advanced functionality on the frontend-data-layer.

Installation

npm install svelte-cache-store

Usage

Registering the data entities to cache

Each of the data-types that the CacheStore can handle needs to be registered, so that the CacheStore knows how to query the backend API. This means that it needs to provide the CacheStore with:

  • the singular form of the entity to cache
  • the plural form of the entity to cache
  • the URL prefix that your backend uses

If you are writing a Blog, then you might have two data types - BlogPost and BlogPostImages. Both of these needs to be a Typescript type and registered with the store - typically in src/+layout.svelte:

BlogPost.ts

export interface BlogPost extends CacheItem {
    title: string;
    preamble: string;
    content: string;
    createdDate: string;
    username: string;
    isVisible: boolean;
    images: string[];
}

BlogPostImage.ts

export interface BlogPostImage extends CacheItem{
    imagePath: string;
    blogPostId: string;
}

+layout.svelte

<script lang="ts">
import {cacheStore} from "svelte-cache-store";
import type {BlogPost} from "./BlogPost";
import type {BlogPostImage} from "./BlogPostImage";

cacheStore.registerType<BlogPost>('blogPost', 'blogPosts', '/my-api');
cacheStore.registerType<BlogPostImage>('blogPostImage', 'blogPostImages', '/my-api');
</script>

<slot></slot>

The code above registeres the types blogPost and blogPostImage with CacheStore. If you server live on http://myurl, then the URL to fetch all blogPosts would be: http://myurl/my-api/blogPosts

Fetching data

Data can be fetched in two ways:

  • fetchAll(singularName)
  • fetchById(singularName, id)

fetchById(singularName, id)

This method fetches an item by its ID. If the item is already in the cache, it will be returned immediately; otherwise, it will be fetched from the API. Sideloaded data will be automatically added to the cache.

let blogPost : BlogPost = await cacheStore.fetchById<BlogPost>('blogPost', '123');

The data will be fetched via a GET to /my-api/blogPosts/123.

The return JSON will be something like this:

{
  "blogPost": {
    "id": "123",
    "title": "My Blog Post",
    "preamble": "This is a blog post",
    "content": "This is the content of the blog post",
    "createdDate": "2021-10-10",
    "username": "user",
    "isVisible": true,
    "images": ["456"]
  }
}

fetchAll(singularName)

This method fetches all items of a specific type. If the items are already in the cache, they will be returned immediately; otherwise, they will be fetched from the API. Sideloaded data will be automatically added to the cache.

sortColumns: An optional array of sorting criteria.

let blogPosts : BlogPost[] = await cacheStore.fetchAll<BlogPost>('blogPost', [{ sortColumn: 
'createdDate', sortOrder: 'desc' }]);

The data will be fetched via a GET to /my-api/blogPosts.

The return JSON will be something like this:

{
  "blogPosts": [
    {
      "id": "123",
      "title": "My Blog Post",
      "preamble": "This is a blog post",
      "content": "This is the content of the blog post",
      "createdDate": "2021-10-10",
      "username": "user",
      "isVisible": true,
      "images": ["456"]
    },
    {
      "id": "124",
      "title": "My Second Blog Post",
      "preamble": "This is a blog post",
      "content": "This is the content of the blog post",
      "createdDate": "2021-10-10",
      "username": "user",
      "isVisible": true,
      "images": ["457"]
    }
  ]
}

reloadById(singularName, id)

This method forces a re-fetch of the item from the API, even if it is already in the cache. The updated data will be placed in the cache

let refreshedBlogPost = await cacheStore.reloadById('blogPost', '123');

reloadAll(singularName)

This method forces a re-fetch of all items from the API, even if they are already in the cache. The updated data will be placed in the cache

let refreshedBlogPosts = await cacheStore.reloadAll('blogPost', [{ sortColumn: 'createdDate', sortOrder: 'desc' }]);

create(singularName, object)

This method creates a new item via the API and adds it to the cache.

let blogPost : BlogPost = await cacheStore.create('blogPost', myBlogPost);

The data will be stored via a POST to /my-api/blogPosts.

update(singualrName, id, object)

This method updates an item by its ID via the API and updates the cache with the new data.

blogPost = await cacheStore.update('blogPost', '123', myBlogPost);

The data will be fetched via a PUT to /my-api/blogPost/123s.

remove(singularName, id)

This method deletes an item by its ID via the API and removes it from the cache.

await cacheStore.remove('blogPost', '123');

The data will be deleted via a DELETE to /my-api/blogPosts/123.

Example

Each data type that CacheStore should handle, needs to be registered. This is typically done application-wide in src/+layout.svelte:

<script>
import {cacheStore} from "svelte-cache-store";
import type {BlogPost} from "./BlogPost";
import type {BlogPostImage} from "./BlogPostImage";
    
cacheStore.registerType<BlogPost>('blogPost', 'blogPosts', '/my-api');
cacheStore.registerType<BlogPostImage>('blogPostImage', 'blogPostImages', '/my-api');
</script>

<slot></slot>

To fetch, create, update and delete data, this is normally done in the page-components. For the blog application, all blog posts might fetched in the /src/routes/+page.svelte component.

<script>
    import {cacheStore} from "svelte-cache-store";
    import {onMount} from "svelte";
    import type {BlogPost} from "$lib/models/BlogPost";
    
    let blogPosts : BlogPost[] = [];
    onMount(async () => {
        // Fetch all blog posts, sorted by 'createdDate' in descending order
        const result = await cacheStore.fetchAll<BlogPost>('blogPost', [{ sort: 'createdDate', 
        order: 'desc' }]);

        // Filter the blog posts to include only those where data.isVisible is true
        blogPosts = result.filter(post => post.data && post.data.isVisible);
    });
</script>

<div class="header">
    <!-- Your logo goes here -->
</div>

<h1>Blog Posts</h1>

{#if blogPosts.length === 0}
    <p>No blog posts found.</p>
{:else}
    <ul>
        {#each blogPosts as blogPost}
            <li>
                <a href={`/blogPost/${blogPost.id}`}>{blogPost.title}</a>
            </li>
        {/each}
    </ul>
{/if}

From the cache, the items state (loading, updating, deleting, loaded, error) is kept in the state-property. If any error message is received while fetching data from the backend, it can be found in the errorMessage-property.

When the user navigates to a single blogPost, the blog should be presented, along with any images that belong to the blogPost, in /src/routes/blogPost/[blogId]/+page.svelte:

<script lang="ts">
    import {cacheStore} from "svelte-cache-store";
    import {onMount} from "svelte";
    import {page} from "$app/stores";
    import Markdown from "$lib/components/Markdown.svelte";
    import type {BlogPost} from "$lib/models/BlogPost";
    import type {BlogPostImage} from "$lib/models/BlogPostImage";
    
    let blogPost : BlogPost;
    let blogImages : BlogPost[] = [];
    onMount(async () => {
        blogPost = await cacheStore.fetchById<BlogPost>('blogPost', $page.params.id);
        if (blogPost.state === 'loaded' && blogPost.images) {
            for (const image of blogPost.images) {
                let img : BlogPostImage = await cacheStore.fetchById<BlogPostImage>
    ('blogPostImage', image);
                console.log(img);
                blogImages = [...blogImages, img];
            }
        }
    });

</script>

<div><a href="/">Back</a></div>

{#if blogPost?.state === 'error'}
    <p style="color: red;">Error: {blogPost.errorMessage || 'Unknown error'}</p>
{:else if blogPost?.state === 'loaded'}
    <h1>{blogPost.title}</h1>

    {#each blogImages as image}
        <img src={image.imagePath} />
    {/each}
    <p>Created by {blogPost.username} on {blogPost.createdDate}</p>

    <p>{blogPost.preamble}</p>

    <p><Markdown toHtml={blogPost.content} /></p>
{:else if blogPost?.state === 'loading'}
    <p>Loading...</p>
{/if}

As you can see from the above code, we can implement loading indicators, as well as error messages fairly easily with this setup. Fetching data is also streamlined accross each data type.