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

cachebranch

v1.2.0

Published

Not only caches data in a key-value format but also supports efficient data management with a hierarchical structure.

Downloads

135

Readme

cachebranch

Node.js workflow

Manage your cache in a hierarchical structure, similar to storing files in directories!
Supports both asynchronous and synchronous operations.

import { CacheBranchAsync } from 'cachebranch'

const branch = new CacheBranchAsync()

// Set cache
await branch.set('user', async (b) => {
  const name = await b.ensure('user/name', async () => 'Unknown').raw
  const html = await b.ensure('user/name/html', async () => <div>Loading...</div>).clone()
  return {
    name,
    html,
  }
})

await branch.set('user/name', async () => (
  await fetch('...').then(res => res.text())
))

await branch.set('user/name/html', async (b) => {
  const name = await b.ensure('user/name', async () => 'Unknown').raw
  return (
    <div>{name}</div>
  )
})


// Re-cache 'user', 'user/name', 'user/name/html'
await branch.cache('user', 'bottom-up')

const user = await branch
  .ensure('user', getUser)
  .then(cache => cache.clone())

/**
 * {
 *   name: 'user-name',
 *   html: <div>user-name</div>
 * }
 * 
 */
console.log(user) 

Why should I use it?

In application development, there are situations where caching values becomes necessary for performance reasons. Often, cached values have dependencies on other cached values.

In such cases, the cachebranch library can assist you in managing these dependencies effectively.

How does it work?

To address cache dependency issues, cachebranch manages caches in a hierarchical structure. For example, if the user cache utilizes the age cache, you can create caches with keys 'user' and 'user/age'.

In this scenario, when you re-cache user, 'user/age' being a sub-branch will also be re-cached automatically. This hierarchical structure facilitates swift resolution of dependency problems.

Usage

Node.js (cjs)

npm i cachebranch
import { CacheBranchSync, CacheBranchAsync } from 'cachebranch'

Browser (esm)

<script type="module">
  import {
    CacheBranchSync,
    CacheBranchAsync
  } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/esm/index.min.js'
</script>

Conceptualization

Hierarchical Structure

cachebranch operates similarly to organizing files in directories. Just as deleting a directory removes its subdirectories and files, deleting a cache in cachebranch also removes its sub-level caches.

For example, if you delete the 'user' cache, the sub-level cache 'user/age' will also be deleted. Keep this structure in mind when setting keys. There is no limit to the depth of hierarchy, so you can have structures as deep as 'user/1/2/3/4/5/6...' and beyond.

Cache Creation Function

The cachebranch library operates slightly differently from other caching libraries. You should not assign values directly to keys for caching. Instead, you need to assign a function that returns the value to be cached.

branch.set('user/age', async () => {
  const age = await fetch('get-your-age.com').then(res => res.text())
  return age
})

Now when a situation arises where you need to re-cache due to dependency issues, the library will automatically help modify the value by recalling the corresponding function.

Cache Update Following Hierarchical Structure

You may want to update all cached content related to 'user'. Try using it like this:

branch.cache('user', 'bottom-up')

This code will update not only 'user', but also caches in the lower hierarchy such as 'user/age', 'user/nickname', etc. If you only want to update 'user', omit the second argument.

branch.cache('user')

Preventing Pollution of Cached Values

These cached values are not just primitive types. They can also be objects or arrays. Cached values should not be modified to maintain reliability, but they can become polluted due to developer mistakes. See the example below.

const user = branch.get('user').raw

user.name = 'test' // Error! You must not pollute the value!

This happens because the value is shallowly copied. To solve this issue, cachebranch supports a clone method. This method deeply copies the value and returns it. You can use it like this:

const user = branch.get('user').clone()

user.name = 'test' // Since this is a deeply copied object, it does not modify the cached value.

Good practice

Here are some efficient ways to use cachebranch. Reading through them in order will help understanding.

Use the ensure method instead of the get method

If the cache creation function has not been assigned yet, the get method may return undefined. This is not null-safe, and if caches are interdependent, it can lead to application errors. Always use the ensure method, which can always return a value.

const age = await branch.get('user/age').raw // If the value is not present, an error may occur!
const age = await branch.ensure('user/age', getAge).raw // Instead, use it like this.

Do not overuse the set method

The set method overrides the existing cache creation function and creates a new cache value. However, it's not designed to 'update' cache values and should only be used when you want to change the cache creation function. It's mostly used to overwrite cache creation functions with ensure methods for null safety. See the example below.

await branch.set('user', async (b) => {
  // Here, a temporary function is created for null safety
  const age = await b.ensure('user/age', () => 0).raw
  return {
    age
  }
})

// ...and then it is overwritten with a function created for null safety
await branch.set('user/age', async () => {
  const age = await fetch('get-your-age.com').then(res => res.text())
  return age
})

Also, the set method cannot update caches in other layers that it depends on. Therefore, for cache updates, use the cache method.

Be mindful of the order when calling the cache method

When using the cache method to update cache values, pay attention to the selection between top-down and bottom-up.

top-down re-caches from the current layer down to the sub-layers. bottom-up re-caches from the lowest layer up to the current layer.

Let's see an example below.

await branch.set('user', async (b) => {
  const name = await b.ensure('user/name', async () => 'Unknown').raw
  const age = await b.ensure('user/age', async () => 0).raw
  return {
    name,
    age,
  }
})

await user.cache('user', 'bottom-up')

In this example, 'user' depends on 'user/name' and 'user/age'. Therefore, to obtain the latest values, 'user/name' and 'user/age' should be updated first. Therefore, caching should be done in the bottom-up manner.

Writing in TypeScript

If you want to infer types for the return values of each cache, you can use TypeScript as follows:

Pass a generic type representing the shape of Key: return type record.

import { CacheBranchAsync } from 'cachebranch'

const branch = new CacheBranchAsync<{
  'user': {
    'name': string
    'html': HTMLElement
  },
  'user/name': string
  'user/name/html': HTMLElement
}>()

await branch.set('user', async (b) => {
  const name = await b.ensure('user/name', async () => 'Unknown').raw
  const html = await b.ensure('user/name/html', async () => <div>Loading...</div>).clone()
  return {
    name,
    html,
  }
})

await branch.set('user/name', async () => (
  await fetch('...').then(res => res.text())
))

await branch.set('user/name/html', async (b) => {
  const name = await b.ensure('user/name', async () => 'Unknown').raw
  return (
    <div>{name}</div>
  )
})

License

MIT license