Create a Generator

Generate documents and other files from your routes, like the OpenAPI document, and keep them in step on every kizuna generate.

A generator walks your routes and writes a file from them: a document, a manifest, a list for a gateway. The OpenAPI document is one. For code your apps call, write a client instead.

defineGenerator

A route manifest for an API gateway, listing every route it should forward:

@example/kizuna-gateway/src/index.ts
import { z } from 'zod';
import { defineGenerator } from 'kizunajs/generator';

export const gatewayManifest = defineGenerator({
    options: z.object({
        includeDeprecated: z.boolean().default(false),
    }),
    generate: ({ options }) => {
        const entries: Array<{ method: string; path: string }> = [];

        return {
            processRoute: ({ route, deprecated }) => {
                if (deprecated && !options.includeDeprecated) {
                    return;
                }

                entries.push({
                    method: route.method,
                    path: route.path,
                });
            },
            finalize: () => JSON.stringify(entries, null, 4),
        };
    },
});
FieldWhat it is
optionsA Zod schema for what the plugin passes in, beside output
generateReturns the walk: processModel and processRoute for each item, then finalize

finalize returns the file as one string. Kizuna writes it to output, and kizuna generate --check compares it byte for byte.

Installing it

A plugin brings generators from its setup, the same way it brings routes and jobs. A package that only generates a file is a plugin with nothing else in it:

@example/kizuna-gateway/src/plugin.ts
import { z } from 'zod';
import { definePlugin } from 'kizunajs/plugin';
import { gatewayManifest } from './generator';

export const gatewayPlugin = definePlugin({
    slug: 'gateway',
    options: z.object({
        output: z.string(),
    }),
    setup: ({ options }) => ({
        generators: [
            gatewayManifest({
                output: options.output,
            }),
        ],
    }),
});
kizuna.config.ts
plugins: [
    gatewayPlugin({
        output: './gateway/routes.json',
    }),
],

openApiPlugin works the same way: its output option becomes a generator it returns, beside the routes that serve the document.

The walk

processRoute is called once for every route the server serves, plugin routes included. processModel is called once for every named model, and is optional.

PropertyTypeDescription
routeKeystringDot-separated key path, e.g. users.getUser
routeRouteDefinitionThe full route definition
routeGroupsstring[]Every group the route is filed under, as dotted paths: its own first, then its groups
hiddenbooleantrue when the route is hidden
deprecatedbooleantrue when the route is deprecated
deprecationMessagestring | undefinedThe route's deprecated string, or the object form's message
plugin{ slug } | undefinedThe plugin whose route this is, under the slug the app installed it at

A generator sees every served route and decides what to include. The OpenAPI document leaves hidden routes out unless it's told otherwise.

Deprecation

Route deprecation arrives on the context. Fields declare theirs in Zod metadata, read with readDeprecation:

import { readDeprecation } from 'kizunajs/generator';

const deprecation = readDeprecation(fieldSchema);
if (deprecation) {
    // deprecation.message is the string form's message, or undefined
}

See Deprecations for the authoring side.

Examples

A schema's declared example values read with readMetaExamples. It takes example first and then examples, each of which holds one value or a list:

import { readMetaExamples } from 'kizunajs/generator';

const [example] = readMetaExamples(fieldSchema);

What kizuna generate expects

  • The output is deterministic. --check re-renders and compares byte for byte, so a timestamp or an unsorted map in the output fails every CI run after the first.
  • One generator, one file. For two files, install it twice with two outputs.
  • The walk is synchronous. Read whatever you need before you return from generate.
  • output resolves against the config's directory, not the shell's, so ./gateway/routes.json lands in the same place wherever the command runs.