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

@sapporta/server

v0.10.0

Published

Sapporta server — schema-as-code tables, auto-generated CRUD, route primitives, and an OpenAPI-emitting Hono app, all wired up with Drizzle and SQLite.

Readme

@sapporta/server

The server half of Sapporta: schema-as-code tables, the HTTP surface generated from them, row-level security, and the sapporta CLI.

Full documentation is at sapporta.com/docs.

Table declaration to REST APIs + Row Security

In a Sapporta project, a table is declared once, as an ordinary Drizzle table plus Sapporta metadata. This package uses them to provide the following:

  • REST endpoints for list, get, create, update, delete, lookup, scoped counts, and streaming CSV export, under /api/tables/<table>.
  • Row security. Every generated read and write is bounded by the signed-in user's workspace and row scope, and the same bound is available to custom code through scopedRows().
  • Schema metadata — labels, value kinds, relationships, search configuration — served over /api/meta and consumed by the generated grids, forms, and lookups in @sapporta/frontend.
  • OpenAPI and CLI access. Mounted routes are emitted as OpenAPI 3.1 and are callable from sapporta on the command line, so a person or a coding agent can inspect and drive a running app without reading its source.

Custom endpoints are registered beside the generated ones and share the same auth context, row helpers, and OpenAPI document.

Install

Most projects arrive here through the scaffold rather than a direct install:

pnpm dlx sapporta@latest init my-app

Requires Node.js and pnpm 11 or later — a generated project keeps its workspace settings in pnpm-workspace.yaml, which earlier pnpm versions ignore, so sapporta init checks the installed version before writing anything.

Adding the package to a project by hand means supplying its peers as well — @sapporta/honest and @sapporta/rest-core — plus the stack a backend needs directly: hono, drizzle-orm, better-sqlite3, and zod. A scaffolded project's packages/api/package.json is the worked example of that dependency set.

The shape of the code

A schema file exports the raw Drizzle table and the Sapporta wrapper:

import {
  integer,
  select,
  sqliteTable,
  sapportaTable,
  text,
} from "@sapporta/server/table";

export const invoicesTable = sqliteTable("invoices", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  workspace_id: text("workspace_id").notNull(),
  reference: text("reference").notNull(),
  status: select("status", ["draft", "paid"] as const).notNull(),
});

export const invoices = sapportaTable({
  drizzle: invoicesTable,
  meta: { label: "Invoices", rowLabelColumns: ["reference"] },
});

That is enough for the generated endpoints and UI. A custom endpoint reads the same table through scopedRows(), which takes the request database handle, the authenticated auth context, and one table definition, and returns row operations bounded by the caller's row scope:

import { eq } from "drizzle-orm";
import { scopedRows } from "@sapporta/server";
import { invoices, invoicesTable } from "../schema/invoices.js";

api.register("listPaidInvoices", contract.listPaidInvoices, async ({ c }) => {
  const auth = projectAuth.requireWorkspaceUser(c);
  const rows = scopedRows(c.get("db"), auth, invoices);

  const paid = await rows.findMany({
    where: eq(invoicesTable.status, "paid"),
    limit: 50,
  });

  return { status: 200, body: { data: paid } };
});

The where and orderBy arguments are ordinary Drizzle expressions. The row scope is added to them before the query reaches SQLite, so a handler cannot widen its own visibility by accident.

Alongside findMany(), scopedRows() provides paged reads, an async cursor for large sequential scans, typed lookups, and scalar and grouped counts. See Row-scoped data helpers for the complete surface and its bounds.

Modules

Each row is a package export subpath.

| Module | Purpose | | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | | . | Runtime surface for boot.ts and endpoint code: project loading and mounting, middleware, scopedRows, auth | | ./table | Column builders and the sapportaTable wrapper used by schema files | | ./errors | Error vocabulary: ErrorCode, OperationError, validation and query-parse errors, SQLite error classification | | ./data-dir | databasePath() and dataPath(): the SQLite file and other files in SAPPORTA_DATA_DIR, for drizzle.config.ts | | ./testing | createTestDb, createTestConnection (in-memory SQLite, production PRAGMAs), createTestAuthContext | | ./create-project | Programmatic project creation, the engine behind sapporta init | | ./source-link-runtime | Node module-resolution preload used when developing against Sapporta sources rather than published packages | | ./cli | The sapporta command, plus ./cli/commands, ./cli/client, ./cli/http-client, ./cli/format, ./cli/render |

For every exported symbol and its exact declaration, read the generated API reference rather than the declaration files under node_modules — it names the specifier to import from, which a file path does not.

Documentation

Contributing

This package lives in the Sapporta monorepo. See DEVELOPMENT.md for the build and test commands, and ARCHITECTURE.md for the package roster, the module index of each package, and the dependency rules between them.

Note that src/templates/ is the project scaffold — the files sapporta init copies into a generated project. src/vendored-package-snapshots/ holds package.json snapshots used to resolve scaffold dependency versions.

License

MIT