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

pgrls-test

v0.6.2

Published

Code-first RLS testing for Postgres — TypeScript port of pgrls.testing. Implements the cross-language Layer 1 protocol (PROTOCOL_VERSION 1).

Readme

pgrls-test

Code-first RLS testing for Postgres — TypeScript port of pgrls.testing (Python). Implements the cross-language Layer 1 protocol (PROTOCOL_VERSION = 1).

pgrls-test lets you write RLS tests with idiomatic JS/TS ergonomics: per-test transactions, role + JWT-claims switching, parameterized SQL execution, and five RLS-specific assertion helpers — all in a small package that wraps your existing Postgres client (no replacement, no fork).

import { Client } from 'pg';
import { PgrlsTestClient, pgDriver } from 'pgrls-test';

const pg = new Client({ connectionString: process.env.DATABASE_URL });
await pg.connect();
const client = new PgrlsTestClient(pgDriver(pg));

await client.transaction(async () => {
  await client.seed('app.invoices', [
    { tenant_id: 'tenant-a', amount: 100 },
    { tenant_id: 'tenant-b', amount: 200 },
  ]);

  await client.asRole(
    'app_authenticated',
    { claims: { sub: 'user-a', tenant_id: 'tenant-a' } },
    async () => {
      await client.assertRows(
        'SELECT id FROM app.invoices',
        { count: 1 },
      );
      await client.assertRejected(
        "INSERT INTO app.invoices (tenant_id, amount) VALUES ('tenant-b', 999)",
      );
    },
  );
});

Install

npm install --save-dev pgrls-test pg
# or
npm install --save-dev pgrls-test postgres

pg and postgres (postgres.js) are optional peer dependencies — install only the driver you actually use.

| Package | Node | TypeScript | |---|---|---| | pgrls-test | ≥ 20 | strict mode supported, ESM-only |

Drivers

Both pg (node-postgres) and postgres.js are first-class:

// node-postgres:
import { Client } from 'pg';
import { PgrlsTestClient, pgDriver } from 'pgrls-test';

const pg = new Client({ connectionString: url });
await pg.connect();
const client = new PgrlsTestClient(pgDriver(pg));

// postgres.js (v0.6.2+ pins one pool connection internally
// via sql.reserve() — no need for { max: 1 } anymore):
import postgres from 'postgres';
import { PgrlsTestClient, postgresJsDriver } from 'pgrls-test';

const sql = postgres(url);
const client = new PgrlsTestClient(postgresJsDriver(sql));
try {
  // ... run tests ...
} finally {
  await client.close(); // release the pinned pool connection
}

The cross-language conformance suite runs the same tests against both adapters in CI.

Drizzle ORM

Drizzle wraps either pg.Pool or postgres.Sqlpgrls-test works alongside either with no extra plumbing. Get a dedicated connection (or sql instance) for the test client; share the rest of your test setup with Drizzle:

// Drizzle on node-postgres
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import { PgrlsTestClient, pgDriver } from 'pgrls-test';

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool); // your app code keeps using this

// For pgrls-test: pull a dedicated client out of the pool so
// BEGIN / SET LOCAL ROLE / queries / ROLLBACK all land on one
// connection. `pgClient.release()` (sync, void) returns the
// connection to the pool when the test ends.
const pgClient = await pool.connect();
const test = new PgrlsTestClient(pgDriver(pgClient));
try {
  // ... assertions ...
} finally {
  pgClient.release(); // sync, void — returns connection to pool
}
// Drizzle on postgres.js
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import { PgrlsTestClient, postgresJsDriver } from 'pgrls-test';

const sql = postgres(process.env.DATABASE_URL);
const db = drizzle(sql); // your app code keeps using this

// `pgrls-test`'s postgres.js adapter pins one pool connection
// internally; `test.close()` releases it. No `{ max: 1 }` config
// or separate sql instance needed.
const test = new PgrlsTestClient(postgresJsDriver(sql));
try {
  // ... assertions ...
} finally {
  await test.close();
}

The Drizzle db object and the pgrls-test client are independent — Drizzle owns the ORM surface, pgrls-test owns the raw-SQL test transaction. Both run against the same database; neither blocks the other.

API

Lifecycle

| Method | Behaviour | |---|---| | new PgrlsTestClient(driver) | Wrap a driver into the client surface. | | client.transaction(body) | Run body inside a transaction; ROLLBACK on exit (always). | | client.close() | Release driver-pinned resources (e.g. the postgres.js reserved connection). Idempotent. No-op for the pg adapter — the caller owns the Client. |

SQL

| Method | Behaviour | |---|---| | client.exec(sql, params?) | Execute one statement; discard rows. | | client.fetchAll<T>(sql, params?) | Execute and return rows as T[]. | | client.asRole(role, { claims }, body) | Switch role + claims for the duration of body. Nests cleanly. | | client.seed(table, rows) | Bulk-insert plain objects. |

Assertions (five RLS-specific helpers)

| Method | Pass when… | |---|---| | client.assertRows(sql, { count }) | Query returns exactly count rows. | | client.assertVisible(sql) | Query returns at least one row. | | client.assertInvisible(sql) | Query returns zero rows. | | client.assertRejected(sql) | Query throws Postgres InsufficientPrivilege (SQLSTATE 42501). | | client.assertSilentlyDropped(sql) | UPDATE/DELETE … RETURNING returns zero rows (USING filtered them out). |

All five are also exported as standalone functions taking a client argument first — usable from any test framework, not just Vitest.

Vitest setup recipe

// vitest.setup.ts
import { afterAll, beforeAll } from 'vitest';
import { Client } from 'pg';
import { PgrlsTestClient, pgDriver } from 'pgrls-test';

let pg: Client;
let client: PgrlsTestClient;

beforeAll(async () => {
  pg = new Client({ connectionString: process.env.DATABASE_URL });
  await pg.connect();
  client = new PgrlsTestClient(pgDriver(pg));
});

afterAll(async () => {
  await pg.end();
});

export { client };
// my-feature.test.ts
import { describe, it } from 'vitest';
import { client } from './vitest.setup.js';

describe('invoices RLS', () => {
  it('a user can only see their tenant', async () => {
    await client.transaction(async () => {
      await client.seed('app.invoices', [
        { tenant_id: 't1', amount: 100 },
        { tenant_id: 't2', amount: 200 },
      ]);
      await client.asRole(
        'app_authenticated',
        { claims: { tenant_id: 't1' } },
        async () => {
          await client.assertRows('SELECT id FROM app.invoices', { count: 1 });
          await client.assertInvisible(
            "SELECT id FROM app.invoices WHERE tenant_id = 't2'",
          );
        },
      );
    });
  });
});

Each test runs inside client.transaction(async () => …), which rolls back at the end — no fixture cleanup, no test order dependencies.

Errors

The error hierarchy mirrors the Python client byte-for-byte:

Error
└── PgrlsTestError              — base for any pgrls-test error
    ├── PgrlsTestAssertionError — thrown by assert* helpers
    └── PgrlsTestConfigError    — thrown when the driver isn't usable

Catch any pgrls-test failure with if (e instanceof PgrlsTestError) ….

Cross-language guarantee

pgrls-test and Python's pgrls.testing implement the same Layer 1 protocol. The wire-level sequence — SET LOCAL ROLE, set_config('request.jwt.claims', …, true), SAVEPOINT pgrls_actor_<random>, the four-case claim restoration logic — is byte-for-byte equivalent.

The RESERVED_KEYWORDS set used for identifier quoting is also pinned across languages: RESERVED_KEYWORDS.size === 78 matches Python's len(_RESERVED_KEYWORDS) (Postgres 16 fully-reserved keywords, appendix C). Each implementation's CI runs its own conformance suite against a Postgres testcontainer; drift protection comes from the shared protocol doc and matching wire-level test cases on both sides, not a single shared CI job.

Comparison to Python

| Operation | Python (pgrls.testing) | TypeScript (pgrls-test) | |---|---|---| | Open client | PgrlsTestClient(conn) | new PgrlsTestClient(driver) | | Per-test tx | with client.transaction(): | await client.transaction(async () => …) | | Switch role | with client.as_role(r, claims=c): | await client.asRole(r, { claims: c }, async () => …) | | Bulk insert | client.seed(table, rows) | await client.seed(table, rows) | | Assert rows | client.assert_rows(sql, count=n) | await client.assertRows(sql, { count: n }) | | Assert reject | client.assert_rejected(sql) | await client.assertRejected(sql) |

Naming follows JS conventions (camelCase) rather than mirroring snake_case — protocol invariants are language-neutral, but the surface API is per-language idiomatic.

License

MIT. See LICENSE at the repo root.

Source

The TS port is developed alongside the Python client in the pgrls/pgrls repo, under ts/. Issues, PRs, and discussions for both clients live there.