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

@i.un/openapi-kit

v0.1.1

Published

Framework-agnostic, zero-runtime type toolkit that binds an openapi-typescript `paths` definition to typed API client signatures.

Readme

@i.un/openapi-kit

npm version License

框架无关、零运行时、零依赖的纯类型工具集 —— 把 openapi-typescript 生成的 paths 定义,绑定成 typed API client 的签名。给定一对 (path, method),推导出 query / body / path 参数类型与解包后的响应类型。整个包只导出 type,打包后近乎为空。

Framework-agnostic, zero-runtime, zero-dependency type toolkit that binds an openapi-typescript paths definition to typed API client signatures. Given a (path, method) pair it derives the query / body / path parameter types and the unwrapped response type. The whole package only exports types, so it adds nothing to your bundle.

它是 @i.un/nuxt-api@i.un/api-client 背后共享的类型内核。 It's the shared type core behind @i.un/nuxt-api and @i.un/api-client.


安装 / Install

npm install -D @i.un/openapi-kit    # 纯类型,放 devDependencies / types-only, dev dep
# pnpm add -D @i.un/openapi-kit

先用 openapi-typescript 生成 paths: First generate paths with openapi-typescript:

pnpm dlx openapi-typescript http://localhost:3000/api/openapi.json -o types/openapi.ts

快速上手 / Quick start

最省事的用法:ApiKit 把整张 spec 折叠成索引映射,三常量绑一次,之后按 path → method → 槽位 取类型。 The easiest path: ApiKit folds the whole spec into an index map — bind the three constants once, then read types via path → method → slot.

import type { paths } from '~/types/openapi';
import type { ApiKit } from '@i.un/openapi-kit';

type Api = ApiKit<paths, '/api', 'data'>;

type UserBalance    = Api['/me/credits']['get']['response'];     // 解包后的 data
type CreateRecharge = Api['/me/credits/recharges']['post']['body'];
type ListQuery      = Api['/me/credits/queries']['get']['query'];
type SessionPath    = Api['/me/sessions/{id}']['delete']['path']; // { id: string }

path 与 method 都有自动补全;response 已按 DataKey 解包信封。零别名样板。 Path and method autocomplete; response is already envelope-unwrapped by DataKey. No per-alias boilerplate.


泛型常量 / Generics

由消费方一次性绑定的四个常量: Four constants, bound once per project by the consuming client:

| 泛型 / Generic | 含义 / Meaning | 默认 / Default | | --- | --- | --- | | Paths | openapi-typescript 的 paths;unknown = 未类型化(legacy) / the paths; unknown = untyped | unknown | | Prefix | 从 spec 路径剥掉的前缀(如 NestJS /api)/ prefix stripped from spec keys | '' | | DataKey | 从 200 响应信封解包的字段名;never = 不解包 / envelope field to unwrap; never = no unwrap | 'data' | | RequestOptions | transport 选项类型(仅 client 层)/ transport options type (client layer only) | unknown |

RequestOptions 是唯一的"可插拔"轴 —— 让 client 透传 headers / timeout / signal 等底层选项,而 kit 本身不依赖任何 fetch 实现。 RequestOptions is the only "pluggable" axis: it lets a client pass through transport options without the kit depending on any fetch implementation.

import type { ApiMethod } from '@i.un/openapi-kit';
import type { FetchOptions } from 'ofetch';

type Get       = ApiMethod<paths, '/api', 'data', 'get', FetchOptions>; // ofetch
type GetNative = ApiMethod<paths, '/api', 'data', 'get', RequestInit>;  // 原生 fetch
type GetBare   = ApiMethod<paths, '/api', 'data', 'get'>;               // 无透传 / no passthrough

导出一览 / Exports

// 路径 / 方法 helpers — path / method helpers
StripPrefix, AllPaths, EndpointFor, OperationFor, MethodsFor, DefaultMethod

// 参数 / 请求体槽位 — parameter & request-body slots
QueryFor, PathParamsFor, HeaderParamsFor, BodyFor, RequestContentFor

// 响应 — response
ResponseFor,         // 200 + application/json + 按 DataKey 解包(高层 / high level)
ResponseContentFor   // 任意 status + 任意 media,不解包(低层 / low level)

// 便捷映射 — convenience map
ApiKit

// 客户端层(带 RequestOptions)— client layer (RequestOptions-parameterized)
CallOptions, RequiredPathCallOptions, OptionsArgsFor, ApiMethod

| 类型 / Type | 给定 / Given | 产出 / Yields | | --- | --- | --- | | AllPaths | — | 所有 path 键并集(去前缀)/ union of all path keys | | MethodsFor | path | 该 path 真实支持的方法(排除 never)/ methods a path actually supports | | DefaultMethod | path [, fallback] | 智能默认方法 / smart default method | | QueryFor | path + method | parameters.query | | PathParamsFor | path + method | parameters.path | | HeaderParamsFor | path + method | parameters.header | | BodyFor | path + method | JSON 请求体 / JSON request body | | RequestContentFor | + media type | 任意 media 的请求体 / request body for any media | | ResponseFor | path + method | 200 JSON,按 DataKey 解包 / 200 JSON, unwrapped | | ResponseContentFor | + status + media | 任意状态/媒体的裸响应(错误码、csv …)/ raw body for any status/media | | ApiKit | — | path → method → { response, body, query, path, header } 映射 / index map |


两种消费风格 / Two consuming styles

1. ApiKit 索引访问(推荐,零样板)/ Indexed access (recommended)

见上面快速上手。绑一次 type Api = ApiKit<...>,之后全是 Api['/x']['get']['response']。 See Quick start. Bind once, then everything is Api['/x']['get']['response'].

2. 具名别名(若你偏好 ApiResponse<'/x'>)/ Named aliases

DefaultMethod 把"默认 GET、否则该 path 的方法"那段逻辑藏起来,每个别名保持一行: DefaultMethod hides the "default to GET, else the path's methods" logic, keeping each alias a one-liner:

import type {
  ResponseFor, BodyFor, AllPaths, MethodsFor, DefaultMethod,
} from '@i.un/openapi-kit';

export type ApiResponse<
  P extends AllPaths<paths, '/api'>,
  M extends MethodsFor<paths, '/api', P> = DefaultMethod<paths, '/api', P>,
> = ResponseFor<paths, '/api', P, M, unknown, 'data'>;

export type ApiBody<
  P extends AllPaths<paths, '/api'>,
  M extends MethodsFor<paths, '/api', P> = DefaultMethod<paths, '/api', P, 'post'>,
> = BodyFor<paths, '/api', P, M>;

怎么选 / Which one — 想保留 ApiResponse<'/x'> 别名、或 spec 很大(几百端点),用风格 2(轻、按需算)。想彻底不写别名,用风格 1(ApiKit)。别两个都上(既建大映射又包别名)。 Keep ApiResponse<'/x'> aliases or have a huge spec → style 2 (lean). Want no aliases at all → style 1 (ApiKit). Don't do both.


适配你的后端 / Adapting to your backend

ResponseFor(及 ApiKitresponse 槽)编码了一个约定:200 + application/json + 按 DataKey 解包信封。不符合约定也能用,有逃生口:

ResponseFor (and ApiKit's response slot) encode a convention: 200 + application/json + envelope-unwrap by DataKey. Off-convention backends are still supported via escape hatches:

后端没有信封 / No envelope

如果后端直接返裸 JSON(没有 { code, message, data } 包一层),把 DataKey 设成 never —— response 就是完整的 200 body,原样不解包: If your backend returns raw JSON (no { code, message, data } wrapper), set DataKey = neverresponse is the full 200 body, unwrapped untouched:

type Api = ApiKit<paths, '', never>;        // DataKey = never
type UserList = Api['/users']['get']['response']; // 完整 200 body / full 200 body

信封字段名不同 / Different envelope key

type Api = ApiKit<paths, '', 'payload'>;    // 解包 payload 而非 data / unwrap `payload`

信封 / 非信封混用 / Mixed envelope & non-envelope

即使 DataKey = 'data',ResponseFor 也会先用 keyof 守卫确认该响应确实有 data 键,否则原样返回。所以同一个 API 里信封端点和非信封端点(如 /health{status:'ok'})混着也精确。 Even with DataKey = 'data', ResponseFor first checks (via a keyof guard) that the response actually has a data key, otherwise returns it as-is. So an API can mix envelope and non-envelope endpoints (e.g. /health returning {status:'ok'}) and stay precise.

非 200 状态码 / 非 JSON 响应 / Non-200 status or non-JSON

ResponseFor 只看 200 + application/json。如果你的端点用 201(Created)、204、或返回 text/csv / application/x-protobuf,用低层的 ResponseContentFor(status 与 media 都可指定,不解包): ResponseFor only looks at 200 + application/json. For endpoints using 201 / 204, or returning text/csv / application/x-protobuf, use the low-level ResponseContentFor (status and media are parameters, no unwrap):

import type { ResponseContentFor } from '@i.un/openapi-kit';

type Created = ResponseContentFor<paths, '/api', '/users', 'post', 201>;
type Csv     = ResponseContentFor<paths, '/api', '/export', 'get', 200, 'text/csv'>;
type Error4xx = ResponseContentFor<paths, '/api', '/users', 'get', 400>;

设计上故意把"约定层"(ResponseFor,解包)和"裸层"(ResponseContentFor,任意 status/media)分开 —— 解包只对 JSON 信封有意义,二者不该揉在一起。 The "convention layer" (ResponseFor, unwrapping) and the "raw layer" (ResponseContentFor, any status/media) are deliberately separate — unwrapping only makes sense for JSON envelopes.


自建类型 kit / Build a project type kit

不想直接用库原语?在项目里组装一组短别名(只引一次 paths / Prefix / DataKey): Prefer your own short aliases? Compose them once per project:

import type { paths } from '~/types/openapi';
import type { QueryFor, AllPaths, MethodsFor, DefaultMethod } from '@i.un/openapi-kit';

export type ApiQuery<
  P extends AllPaths<paths, '/api'>,
  M extends MethodsFor<paths, '/api', P> = DefaultMethod<paths, '/api', P>,
> = QueryFor<paths, '/api', P, M>;

包一层 client / Build a client wrapper

@i.un/nuxt-api / @i.un/api-clientApiMethod + RequestOptions 声明返回的方法形态。每个包只在一处绑定自己的 transport 类型: @i.un/nuxt-api / @i.un/api-client use ApiMethod + RequestOptions to declare the returned method shape. Each package binds its transport type in one place:

import type { ApiMethod } from '@i.un/openapi-kit';
import type { FetchOptions } from 'ofetch';

// 在每个方法处,把 transport 类型(这里是 ofetch 的 FetchOptions)传给 ApiMethod 的第 5 个泛型;
// 无需在本地再造别名 —— 直接用 kit 的 ApiMethod。
// pass your transport type (here ofetch's FetchOptions) as ApiMethod's 5th generic at each
// method — no local alias needed; use the kit's ApiMethod directly.
const useApi = <Paths = unknown, Prefix extends string = '', DataKey extends string = 'data'>() => ({
  get:  _get  as ApiMethod<Paths, Prefix, DataKey, 'get',  FetchOptions>,
  post: _post as ApiMethod<Paths, Prefix, DataKey, 'post', FetchOptions>,
  // put / patch / del …
});

RequestOptions = unknownCallOptionsOmit<unknown, …> 退化成 {},即零透传;原生 fetch 绑 RequestInit 即可。 With RequestOptions = unknown, CallOptions's Omit<unknown, …> collapses to {} (no passthrough); for native fetch bind RequestInit.


设计说明 / Design notes

  • 零运行时、零依赖 —— 只导出 type,dist 的 JS 近乎为空(~33B)。 Zero runtime, zero deps — only types; the emitted JS is ~33 bytes.
  • Paths = unknown(未类型化) —— endpoint 接受任意 string、响应由 <T> 指定,完全兼容无 spec 的旧调用。 Paths = unknown — endpoint accepts any string, response from <T>; fully compatible with spec-less legacy calls.
  • Prefix / DataKey 不做运行时校验 —— 它们是类型层约定,需与你 client 的运行时配置(baseURL 前缀、解包字段)保持一致。 Prefix / DataKey aren't runtime-checked — they're type-level conventions that must match your client's runtime config.

许可证 / License

MIT