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

@rejetto/kvstorage

v0.13.0

Published

Persistent key-value storage functionality with an API inspired by levelDB. All keys are kept in memory. Concurrency not supported.

Downloads

679

Readme

KvStorage

  • Persistent key-value storage
  • Node.js only, 16+ (no browser)
  • API inspired by levelDB
  • All keys are kept in memory
  • Zero dependencies
  • Small bundle size (10KB minified)
  • Typescript + Javascript
  • Concurrency is not supported (i.e. no multiple processes on the same file)
  • Throttled writings to file

This class was designed to store small/medium data sets of JSON-able data types plus Date and Buffer, where you can have the luxury of keeping keys in memory. Small values are kept in memory as well (configurable threshold), while big ones are loaded on-demand. You can fine-tune this to match your memory-usage vs performance on reading.

Installation

npm i @rejetto/kvstorage

File format

Data is stored as a single text file, with each line a json, containing "k" property for they key, and "v" property for the value.

Both new records and updates are appended. If you delete a record, then a json with only "k" is appended, so that the value is undefined.

{"k":"a","v":1}
{"k":"b","v":2}
{"k":"a","v":1.1}  // this is an update
{"k":"c","v":3}
{"k":"b"}          // this is a deletion

Comments are not part of the file, here just for clarity. The whole file is not a valid JSON, but each separate line is.

File size

Updates of existing keys cause wasted space, so the file is rewritten when wasting a configurable percentage of space. You can limit this rewriting to load-time, if you don't want to risk performance degrading later. By delaying put()s you can reduce wasted space, in case same key is rewritten in the meantime.

To keep the main file small and keep operations fast, we have 2 mechanisms:

  • bucket file

    the bucket file is a common file that store big values (by default is >2KB), while the key stays in the main file. In the main file, instead of "v" you will find a "bucket" property, with coordinates inside the bucket file.

  • dedicated files

    By default, values bigger than 100KB (configurable) are kept in a separate file, while the key stays in the main file
    together with a "file" property with the relative path of the file. All these files are kept in a folder.

This is all transparent to you, just keep using get and put without worrying.

Buffer

There are some optimizations for class Buffer:

  • if the value is just a buffer (not nested) and bigger than the threshold, it will be saved without JSON-encoding
  • a b64 method is offered to be space-efficient when the buffer is inside another object

API

By example

Write a value and read it later

const { KvStorage } = require('@rejetto/kvstorage')
let db = new KvStorage()
// ...
await db.open('filename')
db.put('frank', { age: 33 })
setTimeout(async () => {
    console.log(await db.get('frank'))
    db.del('frank')
})

Same as above, but since we know data is not large, we can go sync

db.put('frank', { age: 33 })
setTimeout(() => {
    console.log(db.getSync('frank'))
    db.del('frank')
})

Same as above, but different API

const age = db.singleSync('age', 0)
aget.set(33)
setTimeout(() => {
    console.log(age.get())
})

Methods

The any below actually means a value that can be JSON-encoded (plus Date and Buffer).

  • constructor(options?)
    • options:
      • memoryThreshold: Above this number of bytes, a record is offloaded from memory (default: 1000).
      • bucketThreshold: Above this number of bytes, a record will be stored in a separate but common file, called bucket (simple Buffers are saved as binaries) (default: 10000).
      • fileThreshold: Above this number of bytes, a record will be stored in a dedicated file (simple Buffers are saved as binaries) (default: 100000).
      • rewriteThreshold: Above this percentage (over the file size), a rewrite will be triggered at load time, to remove wasted space (default: 0.3).
      • rewriteOnOpen: Enable rewriteThreshold on open().
      • rewriteLater: Enable rewriteThreshold after open.
      • defaultPutDelay: default value for put(k,v,{delay}).
      • maxPutDelay: default value for put(k,v,{maxDelay}).
      • maxPutDelayCreate: default value for put(k,v,{maxDelayCreate}).
      • reviver: A function passed to JSON.parse for custom deserialization (optional).
      • keyToFileName: A function to customize the name of the files created by fileThreshold (optional). Including path-separator(s) in the name you can divide files in subfolders.
  • open(path: string): Promise<void>
    • Opens the key-value store at the specified path. If clear is true, existing data will be deleted.
  • isOpen(): boolean
    • Current status.
  • isOpening(): undefined | Promise<void>
    • Set while open() is ongoing.
  • get(key: string): Promise<any>
    • Get the value associated with the key, or undefined if not present.
  • getSync(key: string): any
    • Like get, but works only with values that are not offloaded, that are those smaller the memoryThreshold and bucketThreshold and fileThreshold.
  • put(key: string, value: any, { delay, maxDelay, maxDelayCreate }?): Promise<void>
    • Add/overwrite a value. Await-ing this is optional, unless you need to know that the file has been written.
    • Adding a delay may save some writings for fast-changing values. Since the delay is renewed at each put, we can put a limit to that, and can have a generic limit with maxDelay or one specific for keys that weren't written to disk yet, as you may care more about the existence of a (persisted) record than its updates.
  • del(key: string): Promise<void>
    • Remove a value. Await-ing this is optional, unless you need to know that the file has been written.
  • has(key: string): boolean
    • Check for existence
  • size(): number
    • Returns the number of key-value pairs stored.
  • *iterator(options?): AsyncGenerator<[key, value]>
    • Iterates over key-value pairs with optional filtering.
  • *keys(options?): Generator<string>
    • Iterates over keys with optional filtering.
  • firstKey(): string
    • Return first entry of keys.
  • sublevel(prefix: string): KvStorage-like
    • Creates a sublevel key-value store with a specific prefix.
  • close(): Promise<void>
    • Closes the key-value store, releasing resources.
  • flush(): Promise<void>
    • Flushes any pending writes to disk.
  • unlink(): Promise<void>
    • Deletes the entire key-value store at its location.
  • clear(): Promise<void>
    • Equivalent to unlink + open.
  • asObject(): Promise<Object>
    • make all keys and values into a simple Object.
  • singleSync<T=any>(key: string, default: T): { get, set, ready }
    • Make a simple object-api where you can get and set a value of key without passing it as parameter, and in a sync way. Be sure to use this only with values that will not exceed any threshold that will cause it to be offloaded.
    • Default value is not stored, just returned while nothing is stored.
    • Methods:
      • get(): T get the value, but sync.
      • set(value: T | ((currentValue: T) => T)): T equivalent to put, but also offer a callback version to calculate new value based on the old one. Return the new value.
      • ready(): Promise<void> so you can wait for the API to be ready to receive commands.

Ideas

  • diff updates? if an object gets a change in a key, save the diff.