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

@suluk/hono

v0.7.0

Published

The Suluk derivation engine: minimal Hono+Zod RouteContracts in; v4 doc (dynamic per principal+time), validation, contract tests, and doc-coverage audit out. CANDIDATE tooling. Emits the `x-suluk-scenario` BDD facet (authored Given/When/Then steps) for @s

Readme


CANDIDATE tooling — not official OpenAPI. Suluk is a single-contributor candidate for OpenAPI Specification v4.0 ("Moonwalk"), unaffiliated with the OpenAPI Initiative and unable to ratify anything on the SIG's behalf.

Install

bun add @suluk/hono

Peer deps you supply: hono, zod (v4), and @hono/zod-validator.

What it does

You author one thing — an array of RouteContracts (a Hono path + method + Zod schemas). From that single source, @suluk/hono projects:

  • The v4 documentemitV4(routes, ctx). Not a static file: the document is a pure function of the contracts × the requesting principal (the who — scope filtering) × time (the whendeprecatedSince/removedSince). It also synthesizes RFC-9457 error responses + a shared ProblemDetails schema.
  • The live appmount(app, routes), the only file that touches Hono. Request validation is derived from the same Zod schemas, so the doc and the running server cannot drift.
  • Contract testsrunContractChecks(routes) auto-generates checks (every schema is valid JSON Schema 2020-12, every example satisfies its schema, the emitted document passes the meta-schema, no two routes provably collide).
  • A doc-coverage auditaudit(doc) / coverage(doc) flag under-documented operations (advisory, never a gate); autofill(doc) fills the obvious gaps.
  • Server-side authorization — the row-level CRUD engine (gate / policyFor / DEFAULT_POLICIES) plus facet-driven, fail-closed wire middleware (enforceAccess, enforceRateLimit) and a typed RFC-9457 error model (HttpErrors + onError).

When to reach for it

Reach for @suluk/hono when you are defining HTTP routes and want the contract, the v4 document, request validation, and access/rate-limit enforcement to all derive from one declaration rather than being maintained by hand.

It is the producer end of the Suluk walk — the thing the rest of the toolchain consumes. The v4 document it emits is what @suluk/scalar, @suluk/swagger, @suluk/reference, @suluk/sdk, and @suluk/testgen project out. If you already have a v4 document and only want to render or generate from it, reach for those packages instead.

enforceAccess / enforceRateLimit are the only server-side enforcement home — Suluk's x-suluk-* facets are advisory, so without this middleware an access facet on a custom op is decorative. Apply these to make every operation's facet load-bearing on the wire.

Usage

1. Author contracts → emit the v4 document

import * as z from "zod";
import { contract, emitV4 } from "@suluk/hono";

const Pet = z.object({ id: z.number().int().optional(), name: z.string().min(1), tags: z.array(z.string()) });

const routes = contract([
  {
    method: "get", path: "/pet", name: "listPets",
    summary: "List pets", tags: ["pets"],
    responses: [{ status: 200, description: "ok", schema: z.array(Pet) }],
  },
  {
    method: "post", path: "/pet", name: "createPet",
    summary: "Create a pet", scopes: ["write:pets"],
    request: { json: Pet, examples: [{ name: "Rex", tags: [] }] },
    responses: [{ status: 201, description: "created", schema: Pet }],
  },
  {
    method: "get", path: "/pet/:petId", name: "getPet",
    summary: "Get a pet by id",
    request: { params: z.object({ petId: z.string() }) },
    responses: [{ status: 200, description: "ok", schema: Pet }],
  },
]);

const { document, diagnostics } = emitV4(routes, { info: { title: "Pets", version: "1.0.0" } });
// `document` is a v4 (4.0.0-candidate) OpenAPIv4Document — Hono ":petId" became uriTemplate "pet/{petId}".

2. The document is a function of who + when

// WHO — a principal is filtered to the operations whose required scopes it holds:
emitV4(routes, { principal: { scopes: [] } });            // createPet (requires write:pets) is omitted
emitV4(routes, { principal: { scopes: ["write:pets"] } }); // createPet is included
emitV4(routes);                                            // no principal ⇒ the full public doc

// WHEN — `now` drives deprecatedSince/removedSince:
emitV4(routes, { now: "2031-06-01" });   // a route past its removedSince is hidden
// a route past deprecatedSince (but before removedSince) stays, marked `deprecated: true`.

// Map scopes onto a security scheme to synthesize 401/403 + security requirements:
emitV4(routes, { securityScheme: "bearerAuth", securitySchemes: { bearerAuth: { type: "http", scheme: "bearer" } } });

3. Mount the same contracts onto a live Hono app

import { Hono } from "hono";
import { mount } from "@suluk/hono";

// each contract's `handler` is wired with @hono/zod-validator derived from its own schemas.
const app = mount(new Hono(), routes);
// POST /pet with { name: "" } → 400 (name.min(1) fails); valid body → the handler runs.

4. Contract tests + the coverage audit

import { runContractChecks, audit, coverage, autofill } from "@suluk/hono";

const run = runContractChecks(routes);
// run.total / run.passed / run.failures — wire this into `bun test` or CI.

const { document } = emitV4(routes);
audit(document);              // Finding[] — under-documented operations (advisory)
coverage(document);           // a [0,1] score; 1 = no warn-level gaps
coverage(autofill(document)); // autofill synthesizes summaries/descriptions → raises coverage

5. Server-side enforcement (the wire is the boundary)

enforceAccess reads each operation's declared x-suluk-access and enforces it; identity is injected (you own your principal/scope model). It is fail-closed — a missing facet denies by default, an unknown/mis-cased requires denies, a named scope is enforced even under requires: "anyone".

import { enforceAccess, enforceRateLimit } from "@suluk/hono";

app.use("*", enforceAccess({
  operationOf: (c) => /* resolve the contract op for this request */ resolveOp(c),
  accessOf: (op) => accessFacets[op],   // the op's x-suluk-access ({ requires, scope })
  principal: (c) => c.get("userId") ?? null,
  isAdmin: (c) => c.get("isAdmin") === true,
  scopes: (c) => c.get("scopes"),
}));

app.use("*", enforceRateLimit({
  operationOf: (c) => resolveOp(c),
  rateLimitOf: (op) => budgets[op],      // the op's x-suluk-ratelimit ({ windowMs, maxRequests, key })
  defaultFacet: { windowMs: 60_000, maxRequests: 300, key: "ip" }, // blanket floor (default: unmetered)
}));

For hand-applied per-route gating instead of one blanket middleware, use createGuard:

import { createGuard } from "@suluk/hono";

const guard = createGuard({ principal: (c) => c.get("userId") ?? null, isAdmin: (c) => c.get("isAdmin") === true });
app.get("/me",     guard.requireAuth, handler);                 // 401 unless signed in
app.get("/admin",  guard.requireAdmin, handler);                // 401 anon, 403 non-admin
app.post("/write", guard.requireScopes("write:pets"), handler); // 403 unless holds every scope

6. The row-level CRUD access engine

gate is pure (no Hono Context) — it decides whether a caller may run a CRUD op and whether the query should be scoped to their own rows. ruleToRequires projects the same rule onto the wire requires level, so one declaration drives both the gate and the x-suluk-access facet.

import { gate, policyFor, ruleToRequires, DEFAULT_POLICIES } from "@suluk/hono";

const policy = policyFor("owned");                  // one of 7 modes in DEFAULT_POLICIES (or pass your own matrix)
const decision = gate(policy.update, { isAdmin: false, principal: "user_123" });
// → { ok: true, scopeOwner: true }  — signed-in caller may update, scoped to their own rows; admin sees all.

ruleToRequires("owner"); // "authenticated"  — feeds the same op's x-suluk-access facet/docs

7. Typed errors → RFC-9457 on the wire

import { Hono } from "hono";
import { HttpErrors, onError } from "@suluk/hono";

const app = new Hono();
app.onError(onError());                       // maps a thrown SulukHttpError → its status + problem+json body
app.get("/pet/:id", (c) => {
  throw HttpErrors.notFound("Pet", c.req.param("id")); // → 404 { status, title, detail, instance, error }
});
// An untyped throw becomes a 500 defect; its cause is logged server-side, never leaked on the wire.
// HttpErrors.rateLimited(ms) additionally emits a Retry-After header.

API

| Export | What it does | | --- | --- | | contract / responseList | identity helper to author a RouteContract[] with literal inference; normalize a responses list/map | | emitV4 | project contracts → a v4 document for a given principal (who) + time (when) | | mount | wire the same contracts onto a live Hono app (the only Hono-touching file) | | audit / coverage / autofill | doc-coverage findings, a [0,1] score, gap-filling | | contractChecks / runContractChecks | auto-generated contract tests (schema-valid, example⊨schema, meta-schema, no collisions) | | validateSchema2020 | assert a value is well-formed JSON Schema 2020-12 | | enforceAccess / createGuard | facet-driven wire authz middleware; explicit per-route guards | | gate / policyFor / ruleToRequires / DEFAULT_POLICIES | the pure row-level CRUD access engine | | enforceRateLimit / MemoryRateLimitStore | facet-driven fixed-window rate limiting (swappable store) | | SulukHttpError / HttpErrors / onError | the typed, throwable RFC-9457 error model + the Hono error handler |

Boundary

This is the producer end of the projection — contract in → v4 document + a mounted app out. It renders and generates; it does not host. The seams are injected:

  • Identity is injected. enforceAccess / createGuard never trust a header they didn't verify — your principal / isAdmin / scopes callbacks own that (Better Auth session, API token, etc.).
  • The rate-limit store is a swappable binding. MemoryRateLimitStore is a per-process dev default only. The durable production store (KV / Durable Object) lives app-side / in @suluk/deploy.
  • Row scoping stays app-side. gate tells you whether to scope to the owner; your CRUD layer applies that to the actual query.

Access enforcement is the one server-side enforcement home — new enforcement primitives or access modes belong here; apps keep their per-op access/budget tables as plain data.

License

Apache-2.0