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

@veridot/databases

v3.0.0

Published

SQL-backed implementations for Veridot — metadata broker, refresh-token store and revocation store on PostgreSQL, MySQL, MariaDB and SQLite.

Readme

@veridot/databases

SQL backends for Veridot — metadata broker and persistent stores.

npm License: MIT Node.js

This package ships three SQL-backed implementations of Veridot's pluggable contracts:

| Class | Implements | Use it when… | | -------------------------------- | ----------------------- | -------------------------------------------------------------- | | DatabaseMetadataBroker | MetadataBroker | You don't want to operate Kafka — public keys live in SQL. | | DatabaseRefreshTokenStore | RefreshTokenStore | You want refresh tokens persisted across replicas. | | DatabaseRevocationStore | RevocationStore | You want revocations to survive restarts and span replicas. |

Supported flavors: PostgreSQL, MySQL, MariaDB, SQLite.

Installation

pnpm add @veridot/core @veridot/databases
# Pick the driver(s) you need:
pnpm add pg                 # PostgreSQL
pnpm add mysql2             # MySQL / MariaDB
pnpm add sqlite3 sqlite     # SQLite (tests / single-node only)

Quick start (with the Veridot facade)

import { Pool } from 'pg';
import { Veridot } from '@veridot/core';
import {
  DatabaseMetadataBroker,
  DatabaseRefreshTokenStore,
  DatabaseRevocationStore,
} from '@veridot/databases';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

const broker = await DatabaseMetadataBroker.of({
  type: 'postgresql',
  host: 'db',
  port: 5432,
  database: 'veridot',
  user: 'veridot',
  password: process.env.DB_PASSWORD!,
});

const refreshStore = new DatabaseRefreshTokenStore({
  type: 'postgresql',
  client: pool,
  autoCreateSchema: true,
});

const revocationStore = new DatabaseRevocationStore({
  type: 'postgresql',
  client: pool,
  autoCreateSchema: true,
});

const veridot = await Veridot.create({
  metadataBroker: broker,
  refreshTokenStore: refreshStore,
  revocationStore: revocationStore,
  salt: process.env.VDOT_SALT!,
  hashPepper: process.env.VDOT_PEPPER!,
  expectedIssuer: 'https://auth.example.com',
  expectedAudience: 'billing-api',
});

DatabaseMetadataBroker.of(options)

Distributes Veridot's public keys via a single SQL table.

| Option | Type | Required | Default | | ------------ | ------------------------------------------ | -------- | -------------------- | | type | 'postgresql' \| 'mysql' \| 'mariadb' \| 'sqlite' | yes | — | | host | string | no | 'localhost' | | port | number | no | 5432 / 3306 | | database | string | yes | — | | user | string | conditional | (required except SQLite) | | password | string | conditional | (required except SQLite) | | tableName | string | no | 'veridot_metadata' | | poolSize | number | no | driver default | | logger | Logger (@veridot/core) | no | ConsoleLogger |

The broker creates its table automatically on first use; no manual migration needed.

const broker = await DatabaseMetadataBroker.of({
  type: 'postgresql',
  host: 'localhost',
  port: 5432,
  database: 'veridot',
  user: 'postgres',
  password: 'password',
  poolSize: 20,
});

Compatible with the Kafka broker

You can mix and match: one service uses KafkaMetadataBroker, another uses DatabaseMetadataBroker, as long as they share the same table or topic schema. Tokens signed on one side verify on the other.

DatabaseRefreshTokenStore

Persists hashed refresh tokens with rotation metadata.

const refreshStore = new DatabaseRefreshTokenStore({
  type: 'postgresql',
  client: pgPool,                  // any { query: (text, params?) => Promise<{ rows }> }
  tableName: 'app_refresh_tokens', // optional, defaults to 'veridot_refresh_tokens'
  autoCreateSchema: true,          // optional, runs ensureSchema() on first call
});

If you prefer to manage migrations yourself, omit autoCreateSchema and create the table:

CREATE TABLE veridot_refresh_tokens (
  token_hash        VARCHAR(128) PRIMARY KEY,
  family_id         VARCHAR(128) NOT NULL,
  subject           VARCHAR(255) NOT NULL,
  payload           TEXT NOT NULL,
  created_at        BIGINT NOT NULL,
  expires_at        BIGINT NOT NULL,
  status            VARCHAR(16) NOT NULL,
  replaced_by_hash  VARCHAR(128),
  revoked_at        BIGINT
);
CREATE INDEX veridot_refresh_tokens_family_idx  ON veridot_refresh_tokens(family_id);
CREATE INDEX veridot_refresh_tokens_expires_idx ON veridot_refresh_tokens(expires_at);

| Option | Type | Required | Default | | ------------------ | ---------------- | -------- | ---------------------------- | | type | 'postgresql' \| 'mysql' \| 'mariadb' | yes | — | | client | SqlClient | yes | — | | tableName | string | no | 'veridot_refresh_tokens' | | autoCreateSchema | boolean | no | false |

Tokens are stored as SHA-256 hashes; the original value never touches the database. Reuse of an already-rotated token revokes the entire family.

DatabaseRevocationStore

Persists revocations (token IDs, tracking IDs, JWT IDs) with TTL semantics.

const revocationStore = new DatabaseRevocationStore({
  type: 'postgresql',
  client: pgPool,
  tableName: 'app_revocations',
  autoCreateSchema: true,
});

Schema (auto-created when autoCreateSchema: true):

CREATE TABLE veridot_revocations (
  target      VARCHAR(255) PRIMARY KEY,
  reason      VARCHAR(255),
  expires_at  BIGINT NOT NULL,
  created_at  BIGINT NOT NULL
);
CREATE INDEX veridot_revocations_expires_idx ON veridot_revocations(expires_at);

isRevoked(target) is an indexed primary-key lookup — O(log n) on the table size.

Custom client adapter

SqlClient is intentionally minimal so any driver works:

import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// pg.Pool already exposes .query(text, params) → use it directly:
const refreshStore = new DatabaseRefreshTokenStore({
  type: 'postgresql',
  client: pool,
});

// MySQL2 example:
import mysql from 'mysql2/promise';
const myPool = await mysql.createPool({ uri: process.env.MYSQL_URL });
const refreshStoreMy = new DatabaseRefreshTokenStore({
  type: 'mysql',
  client: {
    query: async (text, params) => {
      const [rows] = await myPool.execute(text, params);
      return { rows: rows as unknown[] };
    },
  },
});

Cleanup

expires_at is honored on every read — expired records are filtered out — but nothing prunes them automatically. Schedule a daily cleanup:

DELETE FROM veridot_refresh_tokens WHERE expires_at < EXTRACT(EPOCH FROM NOW())*1000;
DELETE FROM veridot_revocations    WHERE expires_at < EXTRACT(EPOCH FROM NOW())*1000;

Or use RefreshTokenService.deleteExpiredRefreshTokens() from @veridot/core.

Logger injection

Both the broker and the stores accept a logger matching the @veridot/core Logger contract:

const broker = await DatabaseMetadataBroker.of({
  type: 'postgresql',
  database: 'veridot',
  user: 'veridot',
  password: process.env.DB_PASSWORD!,
  logger: pinoLoggerAdapter,
});

Graceful shutdown

process.on('SIGTERM', async () => {
  await veridot.shutdown();
  await broker.disconnect();
  await pool.end();
  process.exit(0);
});

Related packages

License

MIT — see LICENSE.