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

hono-takibi

v0.9.9997

Published

Hono Takibi is a code generator from OpenAPI to @hono/zod-openapi

Readme

img

Hono Takibi

img

npm install -D hono-takibi

OpenAPI to Hono Code Generator

Hono Takibi generates type-safe Hono code from OpenAPI / TypeSpec specifications.

  • OpenAPI schemas to Zod schemas
  • @hono/zod-openapi route definitions
  • App entry point + handler stubs + test files
  • Client library hooks (SWR, TanStack Query, Preact Query, Solid Query, Vue Query, Svelte Query, Angular Query)
  • RPC client, mock server, TypeScript types
  • API reference docs with hono-cli commands

Quick Start

CLI

npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts

Configuration File

Create hono-takibi.config.ts:

import { defineConfig } from 'hono-takibi/config'

export default defineConfig({
  input: 'openapi.yaml',
  'zod-openapi': {
    output: './src/routes.ts',
  },
})
npx hono-takibi

Example

input:

openapi: 3.1.0
info:
  title: Hono Takibi API
  version: '1.0.0'
paths:
  /:
    get:
      summary: Welcome
      description: Returns a welcome message from Hono Takibi.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Hono Takibi🔥
                required:
                  - message

output:

import { createRoute, z } from '@hono/zod-openapi'

export const getRoute = createRoute({
  method: 'get',
  path: '/',
  summary: 'Welcome',
  description: 'Returns a welcome message from Hono Takibi.',
  responses: {
    200: {
      description: 'OK',
      content: {
        'application/json': {
          schema: z
            .object({
              message: z.string().openapi({ example: 'Hono Takibi🔥' }),
            })
            .openapi({ required: ['message'] }),
        },
      },
    },
  },
})

Vite Plugin

Watches your OpenAPI spec and hono-takibi.config.ts for changes, then auto-regenerates code on save.

Requires hono-takibi.config.ts in your project root.

// vite.config.ts
import { honoTakibiVite } from 'hono-takibi/vite-plugin'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [honoTakibiVite()],
})

Template & Test Generation

Generate a complete app structure with handler stubs and test files:

export default defineConfig({
  input: 'openapi.yaml',
  'zod-openapi': {
    output: './src/routes.ts',
    template: {
      test: true,
      pathAlias: '@/',
      testFramework: 'bun', // "vitest" (default) | "vite-plus" | "bun"
    },
  },
})

This generates:

  • src/index.ts - App entry point with route registrations
  • src/handlers/*.ts - Handler stubs for each resource
  • src/handlers/*.test.ts - Test files with @faker-js/faker mock data (imports from vitest, vite-plus/test, or bun:test)

Re-running after updating your OpenAPI spec is safe — your hand-written handler logic and test customizations are preserved. Only new routes are added as stubs.

Handler Generation Modes

routeHandler: false (default)

Each handler creates its own sub-router and registers routes:

// src/handlers/health.ts
import { OpenAPIHono } from '@hono/zod-openapi'
import { getHealthRoute } from '@/routes'

const app = new OpenAPIHono()

export const healthHandler = app.openapi(getHealthRoute, (c) => {})

The app mounts handlers via .route():

// src/index.ts
import { OpenAPIHono } from '@hono/zod-openapi'
import { healthHandler } from './handlers'

const app = new OpenAPIHono()

export const api = app.route('/', healthHandler)

export default app

routeHandler: true

Handlers export typed RouteHandler functions, and index.ts centralizes route registration:

// src/handlers/health.ts
import type { RouteHandler } from '@hono/zod-openapi'
import type { getHealthRoute } from '../routes'

export const getHealthRouteHandler: RouteHandler<typeof getHealthRoute> = async (c) => {}
// src/index.ts
import { OpenAPIHono } from '@hono/zod-openapi'
import { getHealthRoute } from './routes'
import { getHealthRouteHandler } from './handlers'

const app = new OpenAPIHono()

export const api = app.openapi(getHealthRoute, getHealthRouteHandler)

export default app

Client Library Integrations

Supported: SWR, TanStack Query, Preact Query, Solid Query, Vue Query, Svelte Query, Angular Query, RPC Client.

export default defineConfig({
  input: 'openapi.yaml',
  'tanstack-query': {
    output: './src/tanstack-query',
    import: '../client',
    split: true,
    client: 'client',
  },
})

Infinite Query (x-pagination)

Set x-pagination: true on a GET operation to generate infinite query hooks.

paths:
  /posts:
    get:
      x-pagination: true

Test & Mock Generation

Test Generation

export default defineConfig({
  input: 'openapi.yaml',
  test: {
    output: './src/test.ts',
    import: '../index',
    testFramework: 'bun', // "vitest" (default) | "vite-plus" | "bun"
  },
})

Mock Server Generation

export default defineConfig({
  input: 'openapi.yaml',
  mock: {
    output: './src/mock.ts',
  },
})

API Reference Docs

Generate API reference Markdown with hono-cli hono request commands that can be run directly without starting a server:

export default defineConfig({
  input: 'openapi.yaml',
  docs: {
    output: './docs/api.md',
    entry: 'src/index.ts',
  },
})

To generate curl commands instead of hono request:

export default defineConfig({
  input: 'openapi.yaml',
  docs: {
    output: './docs/api.md',
    curl: true,
    baseUrl: 'http://localhost:3000',
  },
})

Full Config Reference

// hono-takibi.config.ts
import { defineConfig } from 'hono-takibi/config'

export default defineConfig({
  // OpenAPI spec file (.yaml, .json, or .tsp)
  input: 'openapi.yaml',

  // Base path prefix for all routes
  basePath: '/api',

  // oxfmt FormatConfig for generated code output
  // @see https://www.npmjs.com/package/oxfmt
  // format: {},

  // Main code generation (Zod + OpenAPI + Hono)
  'zod-openapi': {
    // Output: use 'output' for single file, or 'routes' for split mode (mutually exclusive)
    output: './src/routes.ts',
    readonly: true, // Add 'as const' to generated schemas

    // Template generation (app entry point + handler stubs + tests)
    template: {
      test: true, // Generate test files
      routeHandler: false, // false: inline .openapi() (default), true: RouteHandler exports
      pathAlias: '@/', // TypeScript path alias for imports
      testFramework: 'vitest', // "vitest" (default) | "vite-plus" | "bun" — test import source
    },

    // Export options (OpenAPI Components Object)
    exportSchemas: true,
    exportSchemasTypes: true,
    exportResponses: true,
    exportParameters: true,
    exportParametersTypes: true,
    exportExamples: true,
    exportRequestBodies: true,
    exportHeaders: true,
    exportHeadersTypes: true,
    exportSecuritySchemes: true,
    exportLinks: true,
    exportCallbacks: true,
    exportPathItems: true,
    exportMediaTypes: true,
    exportMediaTypesTypes: true,

    // Split routes into separate files
    routes: {
      output: './src/routes',
      split: true,
      import: '@packages/routes',
    },

    // Split webhooks into separate files
    webhooks: {
      output: './src/webhooks',
      split: true,
      import: '@packages/webhooks',
    },

    // Split components into separate files
    components: {
      schemas: {
        output: './src/schemas',
        exportTypes: true,
        split: true,
        import: '../schemas',
      },
      responses: {
        output: './src/responses',
        split: true,
        import: '../responses',
      },
      parameters: {
        output: './src/parameters',
        exportTypes: true,
        split: true,
        import: '../parameters',
      },
      examples: {
        output: './src/examples',
        split: true,
        import: '../examples',
      },
      requestBodies: {
        output: './src/requestBodies',
        split: true,
        import: '../requestBodies',
      },
      headers: {
        output: './src/headers',
        exportTypes: true,
        split: true,
        import: '../headers',
      },
      securitySchemes: {
        output: './src/securitySchemes',
        split: true,
        import: '../securitySchemes',
      },
      links: {
        output: './src/links',
        split: true,
        import: '../links',
      },
      callbacks: {
        output: './src/callbacks',
        split: true,
        import: '../callbacks',
      },
      pathItems: {
        output: './src/pathItems',
        split: true,
        import: '../pathItems',
      },
      mediaTypes: {
        output: './src/mediaTypes',
        exportTypes: true,
        split: true,
        import: '../mediaTypes',
      },
    },
  },

  // TypeScript type generation
  type: {
    output: './src/types.ts',
    readonly: true,
  },

  // RPC client generation
  rpc: {
    output: './src/rpc',
    import: '../client', // Import path for the Hono RPC client
    split: true,
    client: 'client', // Export name of the client instance
    parseResponse: true, // Use parseResponse for type-safe responses
  },

  // Client library integrations (SWR, TanStack Query, Preact Query, Solid Query, Vue Query, Svelte Query, Angular Query)
  swr: {
    output: './src/swr',
    import: '../client',
    split: true,
    client: 'client',
  },
  'tanstack-query': {
    output: './src/tanstack-query',
    import: '../client',
    split: true,
    client: 'client',
  },
  'preact-query': {
    output: './src/preact-query',
    import: '../client',
    split: true,
    client: 'client',
  },
  'solid-query': {
    output: './src/solid-query',
    import: '../client',
    split: true,
    client: 'client',
  },
  'vue-query': {
    output: './src/vue-query',
    import: '../client',
    split: true,
    client: 'client',
  },
  'svelte-query': {
    output: './src/svelte-query',
    import: '../client',
    split: true,
    client: 'client',
  },
  'angular-query': {
    output: './src/angular-query',
    import: '../client',
    split: true,
    client: 'client',
  },

  // Test generation
  test: {
    output: './src/test.ts',
    import: '../index', // Import path for the app instance
    testFramework: 'vitest', // "vitest" (default) | "vite-plus" | "bun" — test import source
  },

  // Mock server generation
  mock: {
    output: './src/mock.ts',
  },

  // API reference docs generation
  docs: {
    output: './docs/api.md',
    entry: 'src/index.ts', // App entry point for hono request commands
    curl: false, // true: generate curl commands (requires baseUrl), false: hono request (default)
    baseUrl: 'http://localhost:3000', // Base URL for curl commands (required when curl: true)
  },
})

Custom Validation Error Messages

Use x-* vendor extensions to attach custom Zod error messages, with one extension per JSON Schema keyword (1:1 mapping). The extension name follows the pattern x-<jsonSchemaKeyword>-message (e.g. x-minLength-message, x-pattern-message), plus four generic forms: x-error-message, x-required-message, x-const-message, x-enum-message.

name:
  type: string
  minLength: 1
  maxLength: 50
  x-error-message: 'Name must be a string'
  x-minLength-message: 'Name cannot be empty'
  x-maxLength-message: 'Name must be at most 50 characters'
z.string({ error: 'Name must be a string' })
  .min(1, { error: 'Name cannot be empty' })
  .max(50, { error: 'Name must be at most 50 characters' })

Extension Reference

All custom message extensions follow the x-<keyword>-message naming convention and map directly to Zod validator error messages.

Common (any schema type)

| Extension | Applies to | | -------------------- | -------------------------------------------------------------- | | x-error-message | All schemas (fallback when keyword-specific message is absent) | | x-required-message | Required properties | | x-const-message | const | | x-enum-message | enum |

Numeric (number / integer)

| Extension | Applies to | | ---------------------------- | ------------------ | | x-minimum-message | minimum | | x-maximum-message | maximum | | x-exclusiveMinimum-message | exclusiveMinimum | | x-exclusiveMaximum-message | exclusiveMaximum | | x-multipleOf-message | multipleOf |

String

| Extension | Applies to | | --------------------- | ------------------------------------------ | | x-minLength-message | minLength | | x-maxLength-message | maxLength | | x-pattern-message | pattern | | x-length-message | Exact length (minLength === maxLength) |

Array

| Extension | Applies to | | ----------------------- | ------------- | | x-minItems-message | minItems | | x-maxItems-message | maxItems | | x-uniqueItems-message | uniqueItems | | x-contains-message | contains | | x-minContains-message | minContains | | x-maxContains-message | maxContains |

Object

| Extension | Applies to | | -------------------------------- | ---------------------- | | x-minProperties-message | minProperties | | x-maxProperties-message | maxProperties | | x-additionalProperties-message | additionalProperties | | x-propertyNames-message | propertyNames | | x-patternProperties-message | patternProperties | | x-dependentRequired-message | dependentRequired | | x-dependentSchemas-message | dependentSchemas |

Combinators

| Extension | Applies to | | ----------------------- | ---------------------------------------------------------------------------------------------------------------- | | x-allOf-message | allOf | | x-anyOf-message | anyOf | | x-oneOf-message | oneOf | | x-not-message | not | | x-implication-message | Implication pattern (A → B) encoded as anyOf:[{not:A},{required:B}]; takes precedence over x-anyOf-message |

Conditional

| Extension | Applies to | | ---------------- | ---------- | | x-if-message | if | | x-then-message | then | | x-else-message | else |

Typeless / Array Applicator

| Extension | Applies to | | --------------------------------- | ------------------------------- | | x-properties-message | properties (typeless schemas) | | x-prefixItems-message | prefixItems | | x-items-message | items | | x-unevaluatedProperties-message | unevaluatedProperties | | x-unevaluatedItems-message | unevaluatedItems |

Behavior Extensions

String Pre-validation Transforms

| Extension | Generated | Value | | --------------- | ----------------------------- | --------------------------------------- | | x-trim | z.string().trim() | true | | x-toLowerCase | z.string().toLowerCase() | true | | x-toUpperCase | z.string().toUpperCase() | true | | x-normalize | z.string().normalize('NFC') | 'NFC' / 'NFD' / 'NFKC' / 'NFKD' |

homepage:
  type: string
  format: url
  x-trim: true
z.string().trim().pipe(z.url())

String Validation Checks

| Extension | Generated | Value | | ------------- | ------------------------ | ------ | | x-lowercase | z.string().lowercase() | true | | x-uppercase | z.string().uppercase() | true |

slug:
  type: string
  x-lowercase: true
z.string().lowercase()

Preprocess (Input Normalization)

x-preprocess

username:
  type: string
  x-preprocess: 'z.preprocess((val) => typeof val === "string" ? val.trim() : val, z.string())'
z.preprocess((val) => (typeof val === 'string' ? val.trim() : val), z.string())

Type Coercion

x-coerce

asNumber:
  type: number
  x-coerce: true
asDate:
  type: string
  format: date-time
  x-coerce: true
z.coerce.number()
z.coerce.date()

x-stringbool

notify:
  type: boolean
  x-stringbool: true
z.stringbool()
notify:
  type: boolean
  x-stringbool:
    truthy: ['yes', 'on']
    falsy: ['no', 'off']
    case: 'sensitive'
z.stringbool({ truthy: ['yes', 'on'], falsy: ['no', 'off'], case: 'sensitive' })

Codec (Bidirectional Transform)

x-codec

updatedAt:
  type: string
  format: date-time
  x-codec: 'z.codec(z.iso.datetime(), z.date(), { decode: (val) => new Date(val), encode: (val) => val.toISOString() })'
z.codec(z.iso.datetime(), z.date(), {
  decode: (val) => new Date(val),
  encode: (val) => val.toISOString(),
})

Custom Validation

x-refine

password:
  type: string
  x-refine: '.refine((val) => val.length >= 8, { message: "Password must be at least 8 characters" }).refine((val) => /[A-Z]/.test(val), { message: "Password must contain an uppercase letter" })'
z.string()
  .refine((val) => val.length >= 8, { message: 'Password must be at least 8 characters' })
  .refine((val) => /[A-Z]/.test(val), { message: 'Password must contain an uppercase letter' })

x-superRefine

normalizedEmail:
  type: string
  format: email
  x-superRefine: '.superRefine((val, ctx) => { if (val.endsWith("@blocked.example")) ctx.addIssue({ code: "custom", message: "Blocked domain" }) })'
z.email().superRefine((val, ctx) => {
  if (val.endsWith('@blocked.example')) {
    ctx.addIssue({ code: 'custom', message: 'Blocked domain' })
  }
})

Transform & Pipe

x-transform

code:
  type: string
  x-transform: 'z.string().transform((val) => val.toUpperCase())'
z.string().transform((val) => val.toUpperCase())

x-pipe

port:
  type: string
  x-pipe: 'z.string().pipe(z.number().int().positive())'
z.string().pipe(z.number().int().positive())

Default & Fallback Values

x-prefault

greeting:
  type: string
  x-prefault: 'hello'
z.string().prefault('hello')

x-catch

retries:
  type: integer
  x-catch: 0
z.int().catch(0)

Immutability

x-readonly

config:
  type: object
  properties:
    name:
      type: string
  x-readonly: true
z.object({ name: z.string() }).readonly()

String Content Checks

x-startsWith / x-endsWith / x-includes

url:
  type: string
  x-startsWith: 'https://'
  x-endsWith: '.com'
path:
  type: string
  x-includes: '/api/'
z.string().startsWith('https://').endsWith('.com')
z.string().includes('/api/')

Format-Specific Options

htmlEmail:
  type: string
  format: email
  x-emailPattern: 'html5' # email pattern preset
uuidV7:
  type: string
  format: uuid
  x-uuidVersion: v7 # uuid version
httpsUrl:
  type: string
  format: uri
  x-urlProtocol: '^https$' # url protocol regex
  x-urlNormalize: true
preciseDatetime:
  type: string
  format: date-time
  x-isoPrecision: 3 # iso datetime precision / offset / local
  x-isoOffset: true

| Extension | Maps to | Values | | ---------------- | ------------------------------- | -------------------------------- | | x-emailPattern | z.email({ pattern }) | html5 / browser / unicode | | x-emailRegex | z.email({ pattern: /.../ }) | custom regex string | | x-uuidVersion | z.uuid({ version }) | v1 / v4 / v6 / v7 / v8 | | x-urlProtocol | z.url({ protocol: /.../ }) | regex string | | x-urlHostname | z.url({ hostname: /.../ }) | regex string | | x-urlNormalize | z.url({ normalize }) | true / false | | x-isoPrecision | z.iso.datetime({ precision }) | fractional second digits | | x-isoOffset | z.iso.datetime({ offset }) | true / false | | x-isoLocal | z.iso.datetime({ local }) | true / false | | x-macDelimiter | z.mac({ delimiter }) | : / - / . | | x-jwtAlg | z.jwt({ alg }) | HS256 etc. | | x-hashAlg | z.hash(alg, ...) | sha256 etc. | | x-hashEnc | z.hash(alg, { enc }) | hex / base64 / base64url |

Branded Types

Use the x-brand vendor extension to generate Zod branded types, creating nominal types that are structurally identical but semantically distinct:

components:
  schemas:
    Cat:
      type: object
      properties:
        name:
          type: string
      required:
        - name
      x-brand: Cat
    Dog:
      type: object
      properties:
        name:
          type: string
      required:
        - name
      x-brand: Dog
// Generated output
const CatSchema = z.object({ name: z.string() }).brand<'Cat'>().openapi('Cat')

const DogSchema = z.object({ name: z.string() }).brand<'Dog'>().openapi('Dog')

Projects Using Hono Takibi

Contributing

We welcome feedback and contributions to improve the tool!

If you find any issues with the generated code or have suggestions for improvements, please:

  • Open an issue at GitHub Issues
  • Submit a pull request with your improvements

License

Distributed under the MIT License. See LICENSE for more information.