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

@e280/kv

v0.2.0-8

Published

tiny run-anywhere key-value database

Readme

🪇 kv

tiny key-value storage library.
typescript. node or web. memory, leveldb, localstorage, or indexeddb. scoped namespaces and atomic write batches. pass scoped and typed kv instances around to your app components, they don't need to worry about where the data actually lives.

npm install @e280/kv
import {Kv} from "@e280/kv"

🪇 kv is easy.

  • make your kv.
    const kv = new Kv()
  • set and get stuff.
    await kv.set("penguins", 123)
      // setting undefined is the same as delete
    await kv.get("penguins")
      // 123
  • keys are strings. values can be any structured data.
    await kv.set("hello", {alpha: 123, bravo: ["bingus"]})
  • commit batches of ops, atomically.
    await kv.commit([
      kv.op.set("pangolins", 100),
      kv.op.delete("bingus"),
    ])

🪇 plug in your favorite kv magazine.

  • MemoryMagazine (default), ephemeral in-memory storage.

    memory magazine is slow and non-atomic. it's meant for testing.

    import {Kv, MemoryMagazine} from "@e280/kv"
    
    const kv = new Kv(new MemoryMagazine())
  • LevelMagazine, nodejs on-disk leveldb.
    import {Level} from "level"
    import {Kv, LevelMagazine} from "@e280/kv"
    
    const level = new Level("./kv")
    const kv = new Kv(new LevelMagazine(level))
  • IdbMagazine, in-browser indexedDB storage.
    import {Kv, IdbMagazine, idbOpen} from "@e280/kv"
    
    const idb = await idbOpen("kv")
    const kv = new Kv(new IdbMagazine(idb))
  • StorageMagazine, in-browser localStorage/sessionStorage.

    storage magazine is slow and non-atomic. it's meant for small amounts of data.

    import {Kv, StorageMagazine} from "@e280/kv"
    
    const kv = new Kv(new StorageMagazine(localStorage))
  • write your own magazine, you won't believe how easy it is.
    import {Magazine, Op, Scan, Value} from "@e280/kv"
    
    // three methods and you're done!
    export class MyMagazine implements Magazine {
      async commit(ops: Op<Value>[]) {/*...*/}
      async getMany(keys: string[]) {/*...*/}
      async* entries(scan?: Scan) {/*...*/}
    }
    see magazines/memory.ts for inspiration.

🪇 kv scopes.

  • scope makes namespaced Kv instances.
    const users = kv.scope("users")
    const messages = kv.scope("messages")
    await users.set("111", "chase")
    await messages.set("222", ["111", "yo"])
    await kv.get("111") // undefined
      // 👮 parent is blind to child entries
    
    await users.get("222") // undefined
      // 👮 child is blind to sibling and parent entries
  • scopes are nestable, and it's turtles all the way down.
    kv.scope("animals").scope("turtles")
    
    // 🥸 equivalent
    kv.scope("animals", "turtles")
    
    // 🙅 illegal: empty strings, reserved characters "." and ":"
    kv.scope("", "e280.org", "e280:org")
  • all kv operations are isolated to their own scope.

    the root kv is a scope like any other.

    const animals = kv.scope("animals")
    const turtles = animals.scope("turtles")
    const squirrels = animals.scope("squirrels")
    await animals.clear()
      // 👮 turtles and squirrels are safe
    await squirrels.clear()
      // 👮 turtles are safe
  • ☣️ subtree is dangerous, it allows a parent scope to hurt its children.

    it returns a special Subtree instance that only has count and clear methods.

    await animals.subtree.count()
      // count includes animals, turtles, and squirrels
    await animals.subtree.clear()
      // 💀 wipes out the turtles and squirrels. i'm sorry.
  • don't forget you can set strict types, on both Kv and scopes.
    const kv = new Kv<unknown>()
    const users = kv.scope<string>("users")
    const messages = kv.scope<[author: string, text: string]>("messages")
  • 🍋‍🟩 commits can be cross-scoped, don't miss this!
    await kv.commit([
      users.op.set("345", "bingus"),
      messages.op.set("456", ["345", "don't let the raccoons know"]),
    ])

🪇 more kv methods.

  • delete a pair by its key.
    await kv.delete("hello")
    you can also pass multiple keys.
    await kv.delete("123", "234", "345")
  • has checks whether a key exists.
    await kv.has("hello")
      // true
  • setMany sets many key-value pairs at once.
    await kv.setMany([["1", "alpha"], ["2", "bravo"]])
  • getMany retrieves many values at once.
    const values = await kv.getMany(["alpha", "bravo"])
      // [123, undefined]
  • need retrieves a value, or throws if the value is missing/nullish.
    const value = await kv.need("hello")
      // "world" (or throws error)
  • needMany retrieves many values, or throws on missing/nullish values.
    const values = await kv.needMany(["alpha", "bravo"])
      // [123, 234] (or throws error)
  • entries loops over key-value pairs.
    for await (const [key, value] of kv.entries())
      console.log(key, value)
    it's aliased to Symbol.asyncIterator, so you can do this:
    for await (const [key, value] of kv)
      console.log(key, value)
    the entries method accepts scan options.
    for await (const [key, value] of kv.entries({
        limit: 100,
        reverse: false,
        start: "alpha", // inclusive
        end: "omega", // exclusive
      }))
      console.log(key, value)
    💡 you can use collect helper from @e280/stz to get entries/keys/values as an array:
    import {collect} from "@e280/stz"
    
    const entries = await collect(kv)
      // [["123", "alpha"], ["234", "bravo"]]
    
    const keys = await collect(kv.keys())
      // ["123", "234"]
  • keys and values. (accepts scan options)
    for await (const key of kv.keys()) console.log(key)
    for await (const value of kv.values()) console.log(value)
  • count the number of entries in this scope. (accepts scan options)
    await kv.count()
      // 123
  • clear deletes everything in this scope. (accepts scan options)
    await kv.clear()
  • cell makes a little cubby, for storing a single value.

    (it implements @e280/stz's Cubby type)

    const muffins = kv.cell<number>("muffins")
    await muffins.set(99)
    await muffins.has() // true or false
    await muffins.get() // number or undefined
    await muffins.need() // number or throws error
    await muffins.delete()
    you can pass typed Cell<X> instances all around your app.
    import {Cell} from "@e280/kv"
    
    class MuffinCaptain {
      constructor(public muffins: Cell<number>) {}
    }

https://e280.org/