Deprecations
Mark routes and fields @deprecated and have it reach your IDE, OpenAPI, and Swift client.
Deprecations are new and still settling. What the CLI reads and how deprecations reach each generated client may change before v2, so pin your version if you depend on them.
A /** @deprecated */ JSDoc tag on a route or field produces a strikethrough in editors, deprecated: true in the OpenAPI spec, and @available(*, deprecated) in the Swift client. Text after @deprecated becomes the Swift @available message.
export const usersRoutes = k.routes('users', {
/**
* @deprecated use `archiveUser` instead
*/
deleteUser: {
method: 'DELETE',
path: '/users/:id',
responses: {
200: z.object({
ok: z.boolean(),
}),
},
},
getUser: {
method: 'GET',
path: '/users/:id',
responses: {
200: z.object({
id: z.string(),
/**
* @deprecated use `email_address`
*/
email: z.string(),
}),
},
},
});Setup
The kizuna CLI ships in @ts-kizuna/cli, a build-time dev dependency:
pnpm add -D @ts-kizuna/clibun add -d @ts-kizuna/clinpm install --save-dev @ts-kizuna/cliGenerate the deprecation data into .kizuna/deprecations.json:
kizuna deprecations src/contract.tsgenerateOpenApi(contract) reads it:
const spec = generateOpenApi(contract);Prepend it to your existing dev and build scripts. For example, with Next.js:
{
"scripts": {
"dev": "kizuna deprecations src/contract.ts && next dev",
"build": "kizuna deprecations src/contract.ts && next build"
}
}Gitignore the output:
.kizuna/A predev/prebuild lifecycle hook works too if you prefer.
Generators read .kizuna/deprecations.json from the working directory.
Multiple contracts
Pass each contract. They are merged into one deprecations.json; each generator applies only the entries whose routes exist in the contract it is generating.
kizuna deprecations src/app.ts src/workspace.ts src/admin.tsPublished packages
When you publish your contract or schemas as a package, the JSDoc you wrote on Zod fields is lost: declaration emit serializes the inferred z.ZodObject<{...}>, whose properties carry no comments. Consumers in other repos using z.infer<typeof Schema> get no field docs and no strikethrough.
Pass --dts <dir> to re-inject each field's full JSDoc block back into the emitted .d.ts after your build, including descriptions, @example, and @deprecated.
Whether you need this depends on what consumers resolve: if they read your built .d.ts, run --dts; if they read your .ts source directly, the JSDoc is already there and you can skip it.
{
"scripts": {
"build": "tsdown src/index.ts --dts && kizuna deprecations src/index.ts --dts dist"
}
}Deployment
When your app starts, generateOpenApi reads the .kizuna/ folder from wherever the app runs. Your build's kizuna deprecations step created it, and it just has to still be there at runtime.
On most hosts it already is: a VM, a single container, next start, or Railway/Render (Nixpacks) all run from the same files the build produced, so .kizuna/ comes along with no extra work.
Docker (multi-stage)
If your Dockerfile builds in one stage and then copies the result into a smaller final image, .kizuna/ gets left behind, just like dist/ would. Copy it across too:
COPY --from=builder /app/.kizuna ./.kizunaTurborepo
If you cache builds with Turborepo, add .kizuna/** to the build task's outputs, otherwise a cached build leaves it out:
{
"tasks": {
"build": {
"outputs": ["dist/**", ".kizuna/**"]
}
}
}Vercel and serverless
Vercel functions and Next's output: 'standalone' bundle only the files the tracer sees, not your whole project, and the tracer can't see a process.cwd() read. Wrap your config with withKizuna to trace .kizuna/ in:
import type { NextConfig } from 'next';
import { withKizuna } from '@ts-kizuna/next/config';
const nextConfig: NextConfig = {
// your config
};
export default withKizuna(nextConfig);It merges into outputFileTracingIncludes, so it composes with any other config wrapper. Run kizuna deprecations from the same directory as next build so .kizuna/ sits at the project root.
The route that serves the OpenAPI spec must run on the Node runtime, not Edge. The Edge runtime has no filesystem to read .kizuna/
from.
Next.js monorepos
.kizuna/ lands wherever you run kizuna deprecations. If you run it from the repo root instead of the app, set outputFileTracingRoot to that root. withKizuna derives the include path from it, with no extra arguments:
import path from 'node:path';
import type { NextConfig } from 'next';
import { withKizuna } from '@ts-kizuna/next/config';
const nextConfig: NextConfig = {
outputFileTracingRoot: path.join(import.meta.dirname, '../..'),
};
export default withKizuna(nextConfig);Swift
The Swift CLI reads the contract source directly, with no separate step.
ts-kizuna-swift generate --contract src/contract.ts --output APIClient.swift --namespace-name API