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

@kyneta/postgres-store

v3.0.0

Published

Postgres storage backend for @kyneta/exchange

Readme

@kyneta/postgres-store

Postgres storage backend for @kyneta/exchange — async-native, JSONB meta, BYTEA blobs.

Installation

pnpm add @kyneta/postgres-store pg

Peer dependencies: @kyneta/exchange, @kyneta/schema, @kyneta/sql-store-core, pg.

Usage

Recommended: createPostgresStore factory

import { Pool } from "pg"
import { Exchange } from "@kyneta/exchange"
import { createPostgresStore, fromPool } from "@kyneta/postgres-store"

const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const store = await createPostgresStore(fromPool(pool))

const exchange = new Exchange({
  stores: [store],
  // ...
})

// On shutdown:
// await exchange.shutdown()
// await pool.end()

createPostgresStore takes a PgAdapter — wrap your connection with fromPool(pool) (pooled; each transaction checks out and releases a connection) or fromClient(client) (a single dedicated connection). This injection mirrors @kyneta/sqlite-store's fromBetterSqlite3 and keeps the store free of any runtime pg-class coupling. The factory queries information_schema.columns to validate that the canonical schema exists with compatible column types, then returns a ready Store; a curated error tells you which column is missing or has the wrong type.

Sync constructor (advanced)

For callers that validate the schema separately at process start:

import { PostgresStore, fromPool } from "@kyneta/postgres-store"

const store = new PostgresStore(fromPool(pool))

Schema

Run schema.sql once before constructing the store, or include the canonical DDL as a step in your migration pipeline. The store does not auto-DDL — Postgres convention is migrations-as-deployment-step.

CREATE TABLE IF NOT EXISTS kyneta_doc_meta (
  doc_id TEXT  PRIMARY KEY,
  data   JSONB NOT NULL
);
CREATE TABLE IF NOT EXISTS kyneta_records (
  doc_id  TEXT    NOT NULL,
  seq     INTEGER NOT NULL,
  kind    TEXT    NOT NULL,
  payload TEXT,
  blob    BYTEA,
  PRIMARY KEY (doc_id, seq)
);
-- Store-global metadata (e.g. the on-disk format version). Distinct from
-- the per-document kyneta_doc_meta.
CREATE TABLE IF NOT EXISTS kyneta_store_meta (
  key   TEXT  PRIMARY KEY,
  value JSONB NOT NULL
);

createPostgresStore validates all three tables exist and runs the store-format gate on open: it stamps a { major, minor } version into kyneta_store_meta (a one-row idempotent write — not DDL) and, on a later open, throws StoreFormatVersionError for an incompatible major or an unversioned store that already holds documents. No automatic migration is performed. Adding kyneta_store_meta (and the kyneta_metakyneta_doc_meta rename) to an existing deployment is an explicit migration step.

JSONB on doc_meta.data enables operator queryability for admin tooling (data->>'syncMode', data->>'replicaType'). Round-trip through loadAll is structurally portable with @kyneta/sqlite-store (both consume toRow/fromRow from @kyneta/sql-store-core); JSONB normalizes whitespace and key order, so a byte-level dump comparison would diverge.

Options

tables

const store = await createPostgresStore(pool, {
  tables: { docMeta: "app_doc_meta", records: "app_records", storeMeta: "app_store_meta" },
})

Default: { docMeta: "kyneta_doc_meta", records: "kyneta_records", storeMeta: "kyneta_store_meta" }. Use to run multiple isolated Exchange instances against the same database — each owns one tables set.

listDocIds(prefix) uses a range scan (doc_id >= prefix AND doc_id < successor(prefix)), not LIKE. Doc IDs containing % and _ are matched literally.

Lifecycle

The caller owns the connection lifecycle:

  • fromPool(pool): each transaction checks out a connection via pool.connect() and release()s it; the caller calls pool.end() on shutdown.
  • fromClient(client): transactions run against the one connection; the caller calls client.end() on shutdown.

PostgresStore.close() is a no-op.

Runtime schema drift

Schema validation runs once at createPostgresStore time. If a DBA alters the schema while the Exchange is running, the change is not detected — re-run createPostgresStore after migrations (which means restarting the Exchange). Build a revalidate() API only if your operational pattern actually requires it.

See also