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

@woodfell/utils

v0.1.3

Published

Reusable TypeScript utilities shared across Woodfell projects.

Readme

@woodfell/utils

Reusable, runtime-neutral TypeScript utilities for Woodfell projects. The root entrypoint is intentionally small; the Supabase test double is available from its own subpath:

import { createSchema, init } from "@woodfell/utils/supabase-mock";

This package is published to npm. Pin the version in production repositories. For Supabase Edge Functions, use Deno's npm specifier instead:

import { createSchema, init } from "npm:@woodfell/utils@<version>/supabase-mock";

supabase-mock

supabase-mock is an in-memory, schema-aware test double for the database portion of an injected SupabaseClient. It is for unit tests that need to exercise real PostgREST-style query chains without starting Supabase.

It reads the consumer's migrations to learn table, column, and function names. Tests then seed rows and run the same .from(...).select()... code their service uses in production. This catches misspelled tables and columns instead of accepting arbitrary fixtures or query strings.

It is not an emulator for Postgres or the Supabase platform. Use the local Supabase stack / integration tests for database constraints, generated values, triggers, RLS and auth, SQL functions, Storage, Realtime, or any query shape the mock does not support.

Install and initialise

Projects that import this subpath need @supabase/supabase-js available. It is an optional peer dependency of this package so projects that only use the root entrypoint do not need it.

Parse the migrations once in a Vitest setupFiles module, then create a fresh mock in every test. setupFiles runs in each worker; do not put createSchema() in Vitest globalSetup, which runs in a separate process.

// test/setup-supabase-mock.ts
import { createSchema } from "@woodfell/utils/supabase-mock";

await createSchema("supabase/migrations");
// permits-service.test.ts
import { beforeEach, expect, test } from "vitest";
import { init } from "@woodfell/utils/supabase-mock";

let db: ReturnType<typeof init>;

beforeEach(() => {
  db = init(); // an empty store, bound to the migrations schema
  db.table("permits").data([
    { id: 1, ref: "TE0001", status: "active", site_id: 10 },
  ]);
});

test("finds an active permit", async () => {
  const { data, error } = await db
    .from("permits")
    .select("id, ref")
    .eq("status", "active")
    .single();

  expect(error).toBeNull();
  expect(data).toEqual({ id: 1, ref: "TE0001" });
});

init() throws if createSchema() has not run. The parsed schema is shared in the module, but each init() call gets independent row state. If one process needs different migration folders, build schemas explicitly with schemaFromMigrations() and pass each to createSupabaseMock() instead.

Seed data and inspect writes

All setup methods validate table and column names against the migrations and clone their inputs. This means mutating a fixture later cannot affect the mock.

const db = init();

db.seed({
  permits: [{ id: 1, ref: "TE0001", status: "active" }],
});

db.table("permits").data([{ id: 2, ref: "TE0002", status: "active" }]);
// `data` replaces all existing rows for that table.

db.table("permits").insert({ id: 3, ref: "TE0003", status: "expired" });
// `insert` appends setup rows.

expect(db.rowsFor("permits")).toHaveLength(2); // returns cloned current rows

Seeded rows use Row (Record<string, unknown>). Supabase-generated row type aliases work directly. A handwritten interface needs an index signature or a cast at the seed call because TypeScript does not treat it as a Record<string, unknown>.

Supported query contract

The mock evaluates these database query operations against its in-memory rows:

  • Reads and writes: select, insert, update, delete, single, and maybeSingle. A write changes the store once per query builder; chain .select() to receive the affected rows, as with Supabase.
  • Filters: eq, neq, gt, gte, lt, lte, ilike, in, is, not, and the supported flat form of or.
  • Result shaping: simple comma-separated projections, order, limit, range, and select(..., { count, head }) for reads. Counts represent the filtered total before range or limit.

Null comparisons follow Postgres three-valued logic: use .is("column", null) to find nulls; .eq("column", null) matches no rows. Ascending order puts nulls last and descending order puts them first, unless nullsFirst is set.

The mock returns cloned results and PostgREST-shaped query errors, so service code can use its normal { data, error } handling.

| Situation | Behaviour | | --- | --- | | Query unknown table | Resolves with PGRST205 | | Read/filter/order/projection of unknown column | Resolves with 42703 | | Insert/update unknown column | Resolves with PGRST204 and does not mutate the store | | Seed unknown table or column | Throws immediately: the fixture is invalid | | single() with zero or multiple visible rows | Resolves with PGRST116 |

Schemas and RPCs

Unqualified queries use the public schema. A migration object is identified by schema.name, so same-named tables in two schemas have separate rows. Only public is exposed to the simulated Data API by default. Add every extra PostgREST-exposed schema when initialising the mock, and use .schema() for both queries and mock setup:

const db = init({ exposedSchemas: ["public", "private"] });

db.schema("private").table("secrets").data([{ id: 1, value: "test" }]);
const { data } = await db.schema("private").from("secrets").select("*");

A query or RPC against a schema outside this allowlist resolves with PGRST106. Setup is not exposure-gated: a non-public table may still be seeded through its schema facade.

RPCs cannot be calculated from rows, so they are the one deliberate stub seam. The function must exist in migrations, and every call must be registered:

db.onRpc("expire_old_permits").thenReturn({ expired: 4 });
// or: db.onRpc("expire_old_permits").thenError({ message: "unavailable", code: "XX000" });

const { data, error } = await db.rpc("expire_old_permits");

An unstubbed RPC rejects, making an omitted test setup obvious. There is no query-stubbing API: express table-query scenarios with seeded data instead.

Deliberate limits

Unsupported behaviour fails loudly rather than returning a plausible but wrong answer. In particular, do not use this mock for:

  • embedded-resource selects / joins, JSON-path columns, nested or groups, or or(... in.(...));
  • write count options, unsupported operators, or unrecognised method options;
  • database constraints, defaults, generated values, foreign keys, triggers, RLS, authentication, or executing SQL/RPC function bodies.

For those cases, write an integration test against a local Supabase instance.

Public API

@woodfell/utils/supabase-mock exports:

  • createSchema(migrationsFolder) and init(options?) — the standard filesystem-backed test harness.
  • schemaFromMigrations(migrations) and createSupabaseMock(schema, options?) — use these when migration SQL is already in memory or multiple schemas are needed in one process.
  • SupabaseMock plus RpcStub, TableSetup, Schema, TableSchema, ColumnSchema, Row, QueryResult, SingleResult, MockError, and MigrationSource types.

The returned mock is assignable to SupabaseClient, so it can be passed to application code that accepts an injected real Supabase client. The setup helpers (seed, table, rowsFor, onRpc) are mock-only conveniences.

Development

From the repository root:

npm ci
npm run typecheck
npm run test
npm run build

The package's public API is its root barrel and declared export subpaths. Do not import files below src/ directly from consumers.