ESLint
Catch ts-kizuna mistakes in your editor with the official ESLint plugin.
@ts-kizuna/eslint-plugin catches ts-kizuna mistakes in your editor. These are things the type system can't express on its own, surfaced as you type instead of at runtime.
pnpm add @ts-kizuna/eslint-pluginbun add @ts-kizuna/eslint-pluginnpm install @ts-kizuna/eslint-pluginSetup
Add the recommended config to your eslint.config.js, alongside whatever else you already run:
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import kizuna from '@ts-kizuna/eslint-plugin';
export default [js.configs.recommended, ...tseslint.configs.recommended, kizuna.configs.recommended];That's it. Every rule below is now active.
Turning a rule off
Override it after the recommended config:
import kizuna from '@ts-kizuna/eslint-plugin';
export default [
kizuna.configs.recommended,
{
rules: {
'@ts-kizuna/no-unsupported-schema': 'off',
},
},
];Or silence a single line inline:
query: z.object({
// eslint-disable-next-line @ts-kizuna/no-unsupported-schema
page: z.coerce.number(),
}),no-unsupported-schema
Flags anything using something ts-kizuna can't support: body, query, pathParams, headers, and response schemas in your routes, plus Kizuna.model schemas, inline or imported. It follows imports, so the error lands on the offending field when it's local, or on the reference when the schema comes from another file.
z.coerce
kizuna already coerces query, path, and header params to their declared types (see Coercion), so z.coerce is redundant, and k.routes throws on it. Use the plain schema:
// flagged
query: z.object({
page: z.coerce.number(),
});
// good, kizuna coerces the string for you
query: z.object({
page: z.number(),
});JSDoc tags in @deprecated
kizuna serializes the text after @deprecated verbatim and surfaces it to every generated client. Native and mobile clients can't parse JSDoc inline tags ({@link}, {@linkcode}, any {@…}), so they'd show the raw markup. Write plain text, and backticks are fine:
// flagged
/**
* @deprecated Use {@link createUser} instead.
*/
// good
/**
* @deprecated Use `createUser` instead.
*/Duplicate @deprecated
kizuna serializes only the first @deprecated message. Any later one is silently dropped and never reaches a client, so collapse them into one:
// flagged, clients never see the second line
/**
* @deprecated Use `createUser` instead.
* @deprecated Numeric ids are no longer accepted.
*/
// good
/**
* @deprecated Use `createUser` instead. Numeric ids are no longer accepted.
*/Across files
A schema is often defined elsewhere, such as a shared PaginationQuery in its own zod-only module that no per-file linter would ever connect to your contract. Because the rule resolves what your contract references, it catches it anyway and reports on the reference:
import { z } from 'zod';
export const PaginationQuery = z.object({
page: z.coerce.number(),
});import { PaginationQuery } from './pagination.js';
export const users = k.routes('users', {
listUsers: {
method: 'GET',
path: '/users',
query: PaginationQuery, // flagged here, PaginationQuery uses z.coerce
responses: {
200: UserListSchema,
},
},
});