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

@construkt-kit/api

v0.2.3

Published

HTTP client, query factories, typed errors, and generated API types for Construkt Kit apps

Readme

@construkt-kit/api

HTTP client, typed error classes, and data-table types for Construkt Kit frontend apps.

Exports

Client

| Export | Description | | --------------------- | --------------------------------------------------------------------- | | createApiClient | Factory — creates fetch-based HTTP client with Bearer token injection | | setApiConfig | Configure the Kubb client (base URL, etc.) | | Client | HTTP client type (re-exported from @kubb/plugin-client) | | RequestConfig | Request configuration type | | ResponseConfig | Response configuration type | | ResponseErrorConfig | Error response configuration type |

Error Classes

| Export | Description | | ------------------- | ---------------------------------------------- | | ApiError | Base error class (status, code, message) | | ValidationError | 422 error (extends ApiError) | | NotFoundError | 404 error (extends ApiError) | | UnauthorizedError | 401 error (extends ApiError) | | ApiErrorResponse | Interface — { Message: string } |

Data-Table Types

| Export | Description | | ------------------- | ------------------------------------------------- | | DataTableFilters | Record<string, string[] \| undefined> | | DataTableSortType | "asc" \| "desc" \| "" | | DataTableParams | { page, pageSize, orderBy, orderType, filters } |

Usage

import { ApiError, NotFoundError, createApiClient } from "@construkt-kit/api";
import type { DataTableParams } from "@construkt-kit/api";

if (error instanceof NotFoundError) {
  /* 404 */
}
if (error instanceof ApiError) {
  /* any API error */
}

Key Patterns

Token callback

createApiClient(getToken) accepts a synchronous callback (() => string | null | undefined), not a static token. The token is fetched at call time (not client creation), supporting token refresh.

Sync/async gap: AuthProvider.getToken from @construkt-kit/pages returns Promise<string | null>, but createApiClient expects a sync getter. The recommended pattern is to cache the token synchronously in the app and pass the cached value:

let cachedToken: string | null = null;
// Update cachedToken when auth state changes
const client = createApiClient(() => cachedToken);

Binary responses

Non-JSON/text responses (Excel, PDF exports) return a Response-like object:

{ blob: () => Promise<Blob>, headers: Headers }

Pair with saveBlobResponse() or downloadFile() from @construkt-kit/utils for file downloads.

Error hierarchy

All errors extend ApiError which uses Object.setPrototypeOf(this, new.target.prototype) — required for proper instanceof checks in transpiled TypeScript. Subclasses hardcode their status: ValidationError → 422, NotFoundError → 404, UnauthorizedError → 401.

createApiClient classifies non-2xx responses onto the narrowest class available — 401 → UnauthorizedError, 404 → NotFoundError, 422 → ValidationError, anything else → ApiError. So instanceof works directly:

try {
  await apiCall();
} catch (error) {
  if (error instanceof NotFoundError) return null;
  if (error instanceof UnauthorizedError) return signOut();
  if (error instanceof ApiError) reportError(error.code, error.message);
}

code is a stable screaming-snake identifier (NOT_FOUND, VALIDATION_ERROR, INTERNAL_SERVER_ERROR), derived from the status text when there is no dedicated subclass.

Param normalization

createApiClient converts config.params to URLSearchParams. Rules:

  • undefined values are omitted; no ? is appended when nothing survives
  • null becomes the string "null"
  • Arrays are repeated per element (ids=1&ids=2)
  • Date values become ISO strings; other objects are JSON-encoded
  • Primitives are stringified

Headers and body

Headers layer in this order, later wins: setApiConfig({ headers }), the per-request headers (record or tuple form), then Authorization from getToken.

  • FormData, URLSearchParams, Blob, ArrayBuffer, typed arrays and streams pass through untouched; for FormData the Content-Type header is removed so fetch can set the boundary
  • With an application/x-www-form-urlencoded content type, a plain object is form-encoded with the param rules above, except that null is omitted
  • A string body is sent as-is when a non-JSON content type is given
  • Anything else is JSON-encoded (bigint as a string) and, if no content type was given, sent as application/json

Responses: JSON content types are parsed, text/* is read as text, 204/205/304 and empty bodies yield {}, and everything else is exposed as a blob. Non-2xx statuses other than 304 throw an ApiError.

Kubb codegen integration

Consuming apps generate typed API code using createKubbConfig() from @construkt-kit/config/kubb. The config produces 3 output directories from an OpenAPI spec:

| Output dir | Contents | | ---------- | ----------------------------------------------- | | dtos/ | TypeScript types generated from OpenAPI schemas | | calls/ | API call functions (typed fetch wrappers) | | hooks/ | React Query hooks grouped by API path |

How it connects to createApiClient:

  1. App creates a client: const client = createApiClient(() => authToken)
  2. App calls setApiConfig({ baseURL: "https://api.example.com" })
  3. App re-exports the configured client from a known path (default: @/api/client)
  4. Kubb clientImportPath option points generated calls/ to that re-export
  5. Generated hooks/ import from calls/, which use the configured client

Query keys in generated hooks are prefixed with "v5" — bump this in @construkt-kit/config/kubb when making breaking API changes to invalidate all caches.

Key Kubb options (via createKubbConfig()):

  • inputPath — OpenAPI spec location (default: ./src/api/openapi.json)
  • outputPath — generated output root (default: ./src/api/gen)
  • clientImportPath — where generated code imports the client from (default: @/api/client)

CLI: construkt-kit-api-gen

The package ships a construkt-kit-api-gen binary that automates the full codegen workflow: fetch an OpenAPI spec from a running API, run Kubb codegen, and clean up.

Usage

# Uses API_URL env var or specUrl from config
npx construkt-kit-api-gen

# Override the API base URL
npx construkt-kit-api-gen --url https://api.example.com

# Use a custom config file (default: api.config.ts)
npx construkt-kit-api-gen --config my-api.config.ts

Config file (api.config.ts)

import { createKubbConfig } from "@construkt-kit/config/kubb";

export const specUrl = "https://api.example.com";
export default createKubbConfig({ clientImportPath: "@/api/client" });

URL resolution priority

  1. --url CLI flag
  2. API_URL environment variable
  3. specUrl named export from config file

The spec is fetched from {baseUrl}/openapi/v1.json, saved temporarily, passed to Kubb, then deleted.