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

@structure-ai/eventsourcing-nisshi

v0.2.1

Published

Nisshi (Kafka-API broker) event store with a SQL sidecar for optimistic concurrency, snapshots, checkpoints, and inbox dedupe.

Readme

@structure-ai/eventsourcing-nisshi

Nisshi (Kafka-API compatible broker) as the event store, with a small SQL sidecar (SQLite or PostgreSQL via @effect/sql) for everything a log cannot do on its own: optimistic concurrency, snapshots, checkpoints, and inbox dedupe.

Implements four ports: EventStore, SnapshotStore, CheckpointStore, Inbox. There is no Outbox by design — see ADR-0015: the event topic itself is the publication; cross-context consumers read it directly and dedupe via Inbox.

How it works

  • Events live in one single-partition topic (default events), key = stream name, value = a JSON envelope {type, schemaVersion, version, payload, metadata}. Infinite retention, no compaction — the topic is the raw history.
  • Positions are Kafka offsets + 1 — a true global total order (single Kafka partition; unrelated to the envelope's optional partition key, which readAll({ partition }) filters on record by record, since the topic has no index on it).
  • Optimistic concurrency via the sidecar ledger: append(stream, expectedVersion, events) reserves expectedVersion+1..n in one SQL transaction (conditional UPDATE / unique INSERT), produces with acks=all, then confirms. A lost race fails with ConcurrencyConflict before anything is written. A crash between reservation and produce leaves pending rows that drainPending re-produces (at-least-once; readers dedupe by (stream, version)).
  • Snapshots / checkpoints / inbox are sidecar tables (pure cache and dedupe state; losing them costs performance, never correctness).
  • Protocol: an in-package minimal Kafka wire client (no runtime dependencies) pinned to non-flexible API versions — Produce v3, Fetch v4, Metadata v0, CreateTopics v4 — negotiated and verified at connect. No consumer groups, no transactions: reads are positioned fetches, progress tracking is the sidecar checkpoint.

Usage

SQLite sidecar:

import { layer, runPendingRelay } from "@structure-ai/eventsourcing-nisshi";

const durable = layer({
  brokerUrl: "tcp://127.0.0.1:9092", // must equal the broker's advertised listener
  filename: "./sidecar.db",           // ":memory:" for tests
  topic: "events",                    // single partition, created when missing
});

PostgreSQL sidecar:

import { layerPg } from "@structure-ai/eventsourcing-nisshi";

const durable = layerPg({
  brokerUrl: "tcp://127.0.0.1:9092",
  url: "postgres://app:secret@db:5432/app", // defaults to DATABASE_URL
  applicationName: "orders",
  maxConnections: 10,
  topic: "events",
});

On an existing SQLite or PostgreSQL SqlClient plus NisshiClient, use storesLayer(options) and run migrate(options) first.

Run the orphan relay in a worker (or every app instance, it is idempotent):

Effect.runFork(runPendingRelay({ pollInterval: 500 }));

Options

| Option | Default | Notes | | --- | --- | --- | | brokerUrl | — | Broker listener; must match --kafka-advertised-listener-url. | | filename (layer) | — | Sidecar SQLite file (":memory:" works). | | url (layerPg) | DATABASE_URL / libpq defaults | Sidecar PostgreSQL connection URL. | | applicationName / maxConnections (layerPg) | driver defaults | PostgreSQL pool options. | | topic | "events" | Must have exactly one partition (verified; ADR-0015). | | createTopic | true | Create the topic at layer start when missing. | | schemaValidation | true | Validate envelopes client-side before produce. | | tablePrefix | — | Namespace sidecar tables. |

Schema files (broker-side validation, forward path)

writeSchemaFiles(dir, topics) writes one JSON Schema (<topic>.json) per topic describing the envelope. Mount the directory with the broker's --schema-registry file://./<dir> (relative paths only — Nisshi collapses leading slashes). Note: broker-side enforcement did not engage on Nisshi v0.7.0-pre.2 (its own CLI accepts records violating its own sample schemas); client-side validation is the effective guard until a Nisshi release enforces it.

Production topologies

The sidecar may be SQLite (layer) or PostgreSQL (layerPg). Use PostgreSQL when multiple app instances can command the same aggregate: the concurrency ledger must be shared. A process-local SQLite sidecar is only safe for a single writer instance.

Separately, Nisshi backs the broker with pluggable storage:

  • PostgreSQL — nisshi --storage-engine postgres://user:pass@host:5432/db
  • S3-compatible — nisshi --storage-engine s3://bucket/prefix (11-nines durability class)
  • libSQL/SQLite — nisshi --storage-engine sqlite://nisshi.db (single host)

The sidecar and broker storage may share one PostgreSQL server, but use separate schemas/credentials and backup policies. Run one broker process per availability need — brokers are stateless, all are leaders; durability comes from the storage engine, not broker replication. Set --kafka-advertised-listener-url to the address clients dial.

Tests

bun test skips broker suites unless NISSHI_URL is set; PostgreSQL-sidecar scenarios additionally require DATABASE_URL. CI installs a pinned Nisshi release, starts PostgreSQL, and runs both sidecar suites. The wire quirks this client encodes (trailing throttle_time_ms in Produce, metadata-triggered topic auto-creation, empty-topic high watermark of 1, whole-batch fetch granularity) are pinned by tests in test/protocol.test.ts.

Persistence failures

The event store and sidecar adapters expose PersistenceError for SQL, broker and envelope failures, retaining the operation and diagnostic cause. Version races remain ConcurrencyConflict. See the shared error contract.

runPendingRelay retries only connection failures and broker errors marked retriable. SQL and permanent broker failures stop the relay with a typed error; supervise the returned effect. Produce failures wrap NisshiProduceError in PersistenceError. Pending rows may still be published later, so a failed append is not proof that no events will appear. See ADR-0020.