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

hazo_api

v1.1.1

Published

Foundation-layer toolkit for hazo_* HTTP APIs: response envelopes, error codes, Zod→OpenAPI generation, Swagger UI, and request context.

Readme

hazo_api

Foundation-layer toolkit for hazo_* HTTP APIs: response envelopes, error codes, Zod→OpenAPI generation, Swagger UI, and request context.

Install

npm install hazo_api zod
# Required peers:
npm install hazo_logs hazo_config hazo_connect hazo_ui react react-dom
# Optional peers:
npm install hazo_auth   # for withApiKey({bind_user:true})
npm install hazo_debug  # for hazo_debug request panel

Quick start

1. Wrap your handler with the request context

// app/api/v1/runs/route.ts
import { ok, fail, withRequestContext, zodIssuesToDetails } from 'hazo_api';
import { z } from 'zod';

const SubmitRunSchema = z.object({
  site_slug: z.string(),
  run_date: z.string().date(),
  status: z.enum(['success', 'partial', 'failed']),
});

export const POST = withRequestContext(async (req) => {
  const parsed = SubmitRunSchema.safeParse(await req.json());
  if (!parsed.success) {
    return fail('VALIDATION_FAILED', 'Bad body', {
      details: zodIssuesToDetails(parsed.error.issues),
    });
  }
  // ...business logic...
  return ok({ run_id: 'run_xxx' }, { status: 201 });
});

Every response gets a meta block:

{
  "ok": true,
  "data": { "run_id": "run_xxx" },
  "meta": { "version": "v1", "request_id": "req_abc123", "elapsed_ms": 12 }
}

2. Register routes for OpenAPI

// lib/routes.ts
import { defineRoute } from 'hazo_api';
import { z } from 'zod';

export const submitRunRoute = defineRoute({
  method: 'POST',
  path: '/api/v1/runs',
  summary: 'Submit a daily run',
  request: { body: SubmitRunSchema },
  responses: {
    201: { description: 'Run accepted' },
    400: { description: 'Validation failed' },
  },
});

3. Serve OpenAPI JSON

// app/api/v1/docs/route.ts
import { generateOpenAPI } from 'hazo_api';
import { submitRunRoute, /* ... */ } from '@/lib/routes';

export const GET = () =>
  Response.json(
    generateOpenAPI({
      info: { title: 'My API', version: '1.0.0' },
      servers: [{ url: 'http://localhost:3000' }],
      routes: [submitRunRoute],
    }),
  );

4. Serve Swagger UI

// app/api/v1/docs/ui/route.ts
import { swaggerUiHtml } from 'hazo_api/client';

export const GET = () =>
  new Response(
    swaggerUiHtml({ spec_url: '/api/v1/docs' }),
    { headers: { 'Content-Type': 'text/html' } },
  );

Exports

Server entry (hazo_api)

  • ok(data, opts?) / fail(code, message, opts?) — build native Response.
  • ErrorCodes, ErrorCode — frozen enum + HTTP status map.
  • withRequestContext(handler), getRequestContext() — ALS-backed request id and timing.
  • defineRoute(opts), getAllRoutes(), resetRouteRegistry() — route registration for OpenAPI.
  • generateOpenAPI(opts) — produces an OpenAPI 3.1 document.
  • zodIssuesToDetails(issues) — flattens Zod errors into { field, issue }.
  • resolveApiConfig(), ApiConfigDefaults — INI-backed config.
  • createApiKeyService({ getHazoConnect }) — issue/list/revoke/validate API keys.
  • withApiKey({ service, require_scopes?, bind_user? }) — auth middleware (parses Bearer then X-Api-Key).
  • createApiKeyRoutes({ service }) — list/create/revoke route factories.
  • createRateLimitService({ getHazoConnect }) — token-bucket service.
  • withRateLimit({ service, bucket_key, limit, window_sec, cost? }) — rate-limit middleware (fail-open).
  • okStream(iterable, { format: 'sse' | 'ndjson' }) — streaming envelopes.
  • Additional types: IssuedApiKey, ApiKeyService, RateLimitService, RateLimitConsumeInput, RateLimitResult, WithApiKeyOptions, WithRateLimitOptions.
  • Types: ApiKey, ApiKeyValidator, AuthMode, ResponseMeta, RouteDefinition, ...

Client entry (hazo_api/client)

  • swaggerUiHtml(opts) — self-contained HTML page loading swagger-ui-dist from CDN.
  • ApiKeyManager React component — full issue/list/reveal-once/revoke UI built on hazo_ui primitives.
  • Shared types only.

Configuration

config/hazo_api_config.ini:

[api]
version = v1
mode = internal

[logging]
enabled = true
level = info

[debug]
enabled = false

[swagger_ui]
cdn = unpkg
version = 5.17.14

See config/hazo_api_config.ini.sample for the full template.

v1.1.0 features

API key auth

import { createApiKeyService, withApiKey, createApiKeyRoutes } from 'hazo_api';

const apiKeys = createApiKeyService({ getHazoConnect: () => myAdapter });

// Protect a route:
export const GET = withRequestContext(
  withApiKey({ service: apiKeys, require_scopes: ['runs:write'] },
    async (_req, key) => ok({ greeting: `Hello, ${key.label}` }),
  ),
);

// Mount admin routes (wrap with your own session/role check):
const routes = createApiKeyRoutes({ service: apiKeys });
export const POST = withRequestContext(routes.create);

Rate limiting

import { createRateLimitService, withRateLimit } from 'hazo_api';

const rateLimit = createRateLimitService({ getHazoConnect: () => myAdapter });

export const GET = withRequestContext(
  withRateLimit({
    service: rateLimit,
    bucket_key: (req) => `ip:${req.headers.get('x-forwarded-for') ?? 'local'}`,
    limit: 60, window_sec: 60,
  }, async () => ok({ ok: true })),
);

Token-bucket. Fail-open on adapter errors. Returns 429 + Retry-After header on block.

Streaming (SSE / NDJSON)

import { okStream } from 'hazo_api';

export const GET = withRequestContext(async () =>
  okStream(myAsyncIterable, { format: 'sse' }),  // or 'ndjson'
);

Admin UI

'use client';
import { ApiKeyManager } from 'hazo_api/client';

export default function KeysPage() {
  return <ApiKeyManager api_base="/api/admin/keys" />;
}

Customizing the scopes input

By default the create dialog renders a comma-separated text input for scopes. For end-user-facing admin UIs you usually want friendlier copy or a curated catalog instead. Pass scopes_field:

import { ApiKeyManager, type ScopesFieldConfig } from 'hazo_api/client';

const CATALOG: ScopesFieldConfig = {
  mode: 'catalog',
  label: 'What can this key do?',
  options: [
    { value: 'runs:write',   label: 'Submit runs',  description: 'Start new model runs.' },
    { value: 'actions:read', label: 'Read actions', description: 'Inspect available actions.' },
  ],
};

<ApiKeyManager api_base="/api/admin/keys" scopes_field={CATALOG} />;

Modes:

  • { mode: 'text' } — comma-separated text input (default).
  • { mode: 'catalog', options, label? } — checkbox list backed by a fixed catalog.
  • { mode: 'hidden' } — no scopes UI; the server fills them in.
  • { mode: 'custom', render } — fully bring-your-own (segmented control, multi-select, etc.). Receives { selected, onChange }.

Binding to a user

The dialog never asks the end user for a UUID. Pass user_id from your session and the issued key gets bound to that user:

<ApiKeyManager api_base="/api/admin/keys" user_id={session.user.id} />

Omit user_id to issue an unbound key (e.g. service tokens).

Future (v1.2.0)

  • API-key rotation (re-issue raw under same id).
  • Per-route automatic rate-limit via INI.
  • Webhook signing helpers.
  • Background bucket compaction worker.
  • AbortSignal threading into okStream.
  • Optional Content-Encoding: gzip for streaming envelopes.
  • withApiKey audit log.

Design

See design/2026-05-16-hazo_api-v1.0.0-design.md (v1.0.0) and design/2026-05-17-hazo_api-v1.1.0-design.md (v1.1.0) for full specs.