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

@rivergen/witness

v0.1.2

Published

Field continuity type contracts for RiverGen — DomainWitness interface and Layer runner shapes.

Readme

@rivergen/witness

npm License: Apache 2.0

Type contracts for RiverGen field continuity audits.


What this is

RiverGen's gates (#1–#11) verify that your realtime pipeline is wired: mutations go through EventFactory, events reach listeners, broadcasts reach projections, projections update the cache.

Gate #12 — Witness — verifies that the data survived the pipeline. Every field. Every hop. Every ghost reconciliation.

This package exports the TypeScript types that every generated <domain>.witness.ts file is built on. It has zero runtime code — types only. You fill the generated scaffold; rivergen verify runs it.


Install

npm install @rivergen/witness

Install alongside @rivergen/cli:

npm install -g @rivergen/cli
npm install @rivergen/witness

How it works

rivergen gen specs/task.json generates a task.witness.ts scaffold that imports from this package. You fill in the assertions; rivergen verify runs them as Gate #12.

Gate #12 has three layers:

| Layer | What it checks | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | Layer 1 — Required fields | Every event in requiredFields has all listed keys in its testPayloads entry | | Layer 2 — Schema/broadcast contract | Required fields align with the registered Zod schema and broadcast shape | | Layer 3 — Projection proof | Your lifecycle() and signals{} assertions pass — field values survive create/update/delete/ghost reconciliation |

Layer 1 and 2 pass immediately after generation. Layer 3 is a stub — it shows as a warning until you fill lifecycle().


Usage

A generated witness looks like this. You fill in lifecycle() and signals{}:

import type { DomainWitness, WitnessAssertion } from "@rivergen/witness";

export interface TaskPayload {
  taskId: string;
  title: string;
  projectId: string;
  assigneeId: string | null;
  status: "todo" | "in-progress" | "done";
  createdAt: string;
  updatedAt: string;
  clientTempId: string | null;
}

type MinimalQC = {
  prefetchQuery(opts: {
    queryKey: unknown[];
    queryFn: () => unknown;
  }): Promise<void>;
  getQueryData<T>(key: unknown[]): T | undefined;
  setQueryData(key: unknown[], data: unknown): void;
};

const LIST_KEY = ["tasks", "list", "project-id-here"];

export const taskWitness: DomainWitness<TaskPayload> = {
  domain: "task",
  events: ["task.created", "task.updated", "task.deleted"],

  requiredFields: {
    "task.created": [
      "taskId",
      "title",
      "projectId",
      "assigneeId",
      "status",
      "createdAt",
      "updatedAt",
      "clientTempId",
    ],
    "task.updated": [
      "taskId",
      "title",
      "projectId",
      "assigneeId",
      "status",
      "updatedAt",
      "clientTempId",
    ],
    "task.deleted": ["taskId", "projectId"],
  },

  testPayloads: {
    "task.created": {
      taskId: "test-task-001",
      title: "Build the witness",
      projectId: "proj-001",
      assigneeId: null,
      status: "todo",
      createdAt: "2026-01-01T00:00:00.000Z",
      updatedAt: "2026-01-01T00:00:00.000Z",
      clientTempId: null,
      _meta: {
        resourceId: "test-task-001",
        actor: { id: "user-001", type: "user" },
        context: { realmId: "proj-001" },
        correlationId: "corr-001",
        eventVersion: "1.0",
      },
    },
    // ... other events
  },

  async lifecycle(queryClient): Promise<WitnessAssertion[]> {
    const qc = queryClient as MinimalQC;
    const assertions: WitnessAssertion[] = [];

    // Seed the cache
    await qc.prefetchQuery({ queryKey: LIST_KEY, queryFn: () => [] });

    // Simulate create
    qc.setQueryData(LIST_KEY, [
      { id: "test-task-001", title: "Build the witness" },
    ]);
    const afterCreate =
      qc.getQueryData<{ id: string; title: string }[]>(LIST_KEY) ?? [];
    const created = afterCreate.find((x) => x.id === "test-task-001");
    assertions.push({ name: "task.created lands in list", ok: !!created });
    assertions.push({
      name: "task.created.title preserved",
      ok: created?.title === "Build the witness",
    });

    // Simulate delete
    qc.setQueryData(
      LIST_KEY,
      afterCreate.filter((x) => x.id !== "test-task-001"),
    );
    const afterDelete = qc.getQueryData<{ id: string }[]>(LIST_KEY) ?? [];
    assertions.push({
      name: "task.deleted removes from list",
      ok: !afterDelete.find((x) => x.id === "test-task-001"),
    });

    return assertions;
  },

  signals: {},
};

The queryClient passed to lifecycle()

The gate runner passes a minimal in-memory query client. It supports getQueryData, setQueryData, prefetchQuery, setQueriesData, and invalidateQueries. Use setQueryData to simulate cache mutations directly — this is intentional. The witness proves field continuity in the projection layer, not the network layer (gates #1–#4 verify the wiring).

Ghost reconciliation

Optimistic UIs create a ghost entry while the mutation is in flight. When the confirmed WS event arrives, the ghost is replaced. Witness the full cycle:

// Ghost appears
const ghostId = "ghost-temp-001";
qc.setQueryData(LIST_KEY, [
  { id: ghostId, title: "...", clientTempId: ghostId },
]);

// Confirmed entity arrives — ghost gone, real entity present
const prev = qc.getQueryData<{ id: string }[]>(LIST_KEY) ?? [];
qc.setQueryData(
  LIST_KEY,
  prev.filter((x) => x.id !== ghostId).concat({ id: "real-001", title: "..." }),
);

const after = qc.getQueryData<{ id: string }[]>(LIST_KEY) ?? [];
assertions.push({
  name: "ghost replaced by confirmed entity",
  ok:
    !after.find((x) => x.id === ghostId) &&
    !!after.find((x) => x.id === "real-001"),
});

Types

DomainWitness<TPayload>

The shape every <domain>.witness.ts export must satisfy.

interface DomainWitness<TPayload> {
  domain: string;
  events: string[];
  requiredFields: Record<string, (keyof TPayload)[]>;
  testPayloads: Record<string, TPayload & { _meta: WitnessMeta }>;
  lifecycle(queryClient: unknown): Promise<WitnessAssertion[]>;
  signals: Record<
    string,
    (queryClient: unknown) => Promise<WitnessAssertion[]>
  >;
}
  • domain — matches domain.key in the spec
  • events — all events the domain broadcasts
  • requiredFields — per-event list of payload keys that must survive to the cache (Layer 1)
  • testPayloads — representative payload + _meta envelope for each event (Layer 2)
  • lifecycle() — receives an in-memory QueryClient; simulate create/update/delete/ghost; return assertions (Layer 3)
  • signals{} — additional Layer 3 assertions keyed by event name (for events that don't follow the main CRUD path)

WitnessAssertion

interface WitnessAssertion {
  name: string; // human-readable assertion label shown in verify output
  ok: boolean; // pass or fail
  detail?: string; // optional failure detail
}

LayerResult

interface LayerResult {
  ok: boolean;
  violations: {
    file: string;
    message: string;
    severity: "error" | "warning";
  }[];
}

DomainWitnessReport

interface DomainWitnessReport {
  domain: string;
  layer1: LayerResult;
  layer2: LayerResult;
  layer3: LayerResult;
}

WitnessResult

interface WitnessResult {
  ok: boolean;
  reports: DomainWitnessReport[];
}

Related

| | | | -------------------- | ---------------------------------------------------------------------------------------- | | CLI | @rivergen/cli — scaffold + all 12 gates | | GitHub (CLI) | Mithun-Chandar/rivergen | | GitHub (Witness) | Mithun-Chandar/rivergen-witness |


License

Apache 2.0