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

tr-pg-name-value-store

v0.0.0

Published

Persistent name/value store on PostgreSQL. JSONB values, self-maintaining never-migrated schema, atomic read-modify-write, multiple named stores per database.

Readme

tr-pg-name-value-store

A persistent name → value store backed by a single PostgreSQL table. A thin, durable getter/setter: values are stored as JSONB, so a value may be anything JSON can carry (number, string, boolean, object, array, or null) and is returned as the corresponding JavaScript value.

  • Self-maintaining schema. The table is created idempotently by the class itself on first use — no manual setup, no migrations. A pre-existing table is verified, never altered.
  • Multiple named stores. A namespace gives each store its own table in the same database.
  • Atomic read-modify-write. update() runs a callback inside a transaction, serialized per name, so concurrent updates compose correctly.
  • Presence vs. null. The store distinguishes no value (undefined) from the value null.

Install

npm install tr-pg-name-value-store pg

pg is a peer dependency (>= 8). Requires Node >= 18 and PostgreSQL >= 9.5 (for INSERT … ON CONFLICT).

Quick start

import { Pool } from 'pg';
import { PgNameValueStore } from 'tr-pg-name-value-store';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const store = new PgNameValueStore(pool, { namespace: 'myapp' });

await store.set('greeting', 'hello');
await store.get('greeting');            // → 'hello'

await store.set('config', { retries: 3, tags: ['a', 'b'] });
await store.get('config');              // → { retries: 3, tags: ['a', 'b'] }

const previous = await store.set('greeting', 'hi'); // → 'hello' (the old value)
await store.remove('greeting');         // → 'hi'   (the removed value)
await store.get('greeting');            // → undefined

Value model

A stored value is any JSON-serializable JavaScript value: number, string, boolean, object, array, or null.

  • Presence vs. null. A name that has never been set (or has been removed) reads back as undefined. A name explicitly set to JSON null reads back as null. "The value does not exist" is always represented as the row being absent — there is no SQL NULL value.
  • undefined is not a value. set(name, undefined) throws TypeError; use remove(name) to delete. (Inside update, a callback that returns undefined is the one place undefined is meaningful — it means delete.)
  • JSONB normalization. PostgreSQL normalizes JSONB on storage: object keys are reordered, insignificant whitespace is dropped, duplicate keys collapse to the last, and numbers are canonicalized. A value read back is semantically equal but may not be textually identical to the value written (e.g. key order).

API

new PgNameValueStore(pool, options?)

Constructs a store over a pg Pool. Performs no I/O. Options:

| Option | Type | Default | Meaning | |-------------|----------|---------|---------| | namespace | string | none | Backing table is <namespace>_name_value_store. Must match /^[a-z][a-z0-9_]*$/, ≤ 41 chars. Throws TypeError if invalid. Omit for the bare table name_value_store. |

init(): Promise<void>

Idempotently ensures the schema (creating the table if absent, verifying it if present). Called automatically on first use of any method; call it explicitly to surface schema/connection errors at startup. Safe to call repeatedly and concurrently (across processes too).

get(name): Promise<value>

Resolves to the current value, or undefined if name has no value.

set(name, value): Promise<previous>

Stores value (insert or overwrite); resolves to the previous value, or undefined if there was none. Throws TypeError if value is undefined or not JSON-serializable (writing nothing).

remove(name): Promise<previous>

Deletes name; resolves to its previous value, or undefined if it had none.

update(name, callback): Promise<previous>

Atomic read-modify-write. Fetches the current value, calls callback(current) (awaiting a returned promise), then commits one outcome based on what the callback does:

| Callback… | Effect | update… | |--------------------------------------------------------|---------------------------------|------------------------------------| | returns a JSON-serializable value (not undefined) | stores it | resolves to the previous value | | returns undefined | removes the name | resolves to the previous value | | returns a non-serializable value (function, BigInt, …) | nothing (rolled back) | throws TypeError | | throws null or undefined | nothing (rolled back) | resolves to the previous value | | throws anything else | nothing (rolled back) | re-throws that value unchanged |

The whole sequence runs in one transaction on a dedicated pooled connection. Concurrent updates of the same name are serialized by a per-name advisory lock, so each callback sees the committed result of the previous one — including the create-from-absent case.

// atomic counter (creates from absent, then increments)
await store.update('hits', (n) => (typeof n === 'number' ? n : 0) + 1);

// conditional update; abort with no change and no error
await store.update('config', (cfg) => {
  if (!cfg) throw null;                 // graceful cancel
  return { ...(cfg as object), seen: true };
});

// delete via update
await store.update('stale', () => undefined);

removeAll(): Promise<void>

Removes every pair from this store's namespace. Other namespaces are untouched.

Multiple stores

Each namespace is an independent store with its own table:

const sessions = new PgNameValueStore(pool, { namespace: 'sessions' });
const settings = new PgNameValueStore(pool, { namespace: 'settings' });
// sessions.* and settings.* never collide; removeAll() on one leaves the other intact

Errors

| Error | When | |-----------------------|------| | TypeError | Invalid namespace; invalid name (not a non-empty string, or > 1024 chars); a set value or update return that is not JSON-serializable. | | SchemaMismatchError | A table with this namespace's name already exists with a different shape. It is left untouched. Exported by the package. | | re-thrown value | update re-throws any non-null/undefined value its callback throws. |

Operational/connection failures propagate from the underlying pg calls.

Schema

One table per namespace ({{ns}} is <namespace>_, or empty):

CREATE TABLE {{ns}}name_value_store (
  name       TEXT        NOT NULL,
  v          JSONB       NOT NULL,
  updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (name)
);

The module issues only CREATE TABLE IF NOT EXISTS and verifies the columns of a pre-existing table against this shape — it never runs ALTER or DROP.

License

MIT © Timo J. Rinne <[email protected]>