Files
skills/docs/api-stack/api/api-plugin/src/define/define.md
2026-05-29 08:20:56 +01:00

3.3 KiB

define

(no submodules)

Purpose

The define module exports a single function — definePlugin<TConfig>(descriptor) — that is the canonical entry point for authoring a plugin. The function returns its input unchanged at runtime; the value is entirely in its type signature, which threads the TConfig generic through every place the descriptor references it: configSchema?: ZodType<TConfig>, defaults?: Partial<TConfig>, and the four lifecycle hooks (onRegister/onInit/onReady/onClose). Inside each hook, ctx.getOwnConfig() returns TConfig with no cast required.

Using definePlugin is preferred over building a raw PluginDescriptor literal because it pins the generic at the call site. TypeScript will then flag mismatches at the source of the error (the hook body), not at the registration site one or two files away.

Features

  • Zero runtime overhead — the function body is return descriptor.
  • Full TConfig inference flows into hooks via PluginContext<TConfig>.
  • Compatible with descriptor field defaults (description, dependencies, optionalDependencies, priority, enabledByDefault, tags).
  • Plays nicely with as const literals — readers preserve readonly arrays for dependencies etc.

Usage: Basic

import { definePlugin } from "@suaveplan/api-plugin/define";
import z from "zod";

export const auth = definePlugin({
    name: "auth",
    version: "0.1.0",
    configSchema: z.object({ sessionTtlSeconds: z.number().int().positive() }),
    defaults: { sessionTtlSeconds: 3600 },
    onInit(ctx) {
        const { sessionTtlSeconds } = ctx.getOwnConfig();
        ctx.logger.info("auth ready", { sessionTtlSeconds });
    },
});

Usage: Advanced

Multiple hooks with await and the abort signal:

export const queue = definePlugin<{ concurrency: number }>({
    name: "queue",
    version: "0.1.0",
    dependencies: ["db"],
    defaults: { concurrency: 4 },
    async onInit(ctx) {
        const { concurrency } = ctx.getOwnConfig();
        ctx.logger.info("starting queue", { concurrency });
    },
    async onReady(ctx) {
        if (ctx.signal.aborted) return;
        ctx.logger.info("queue ready");
    },
    async onClose(ctx) {
        ctx.logger.info("queue draining");
    },
});

API Reference

Export Signature
definePlugin<TConfig>(descriptor) (PluginDescriptor<TConfig>) => PluginDescriptor<TConfig>

Implementation Notes

The function is intentionally an identity at runtime so it can be constant-folded by bundlers — the dependency graph rewards leaf helpers that the optimiser can drop. Because the descriptor parameter is typed as PluginDescriptor<TConfig>, TypeScript will not narrow TConfig away if the call site supplies an explicit generic (definePlugin<MyConfig>({...})). Implicit inference also works when configSchema is provided as a Zod schema, because the descriptor field is typed ZodType<TConfig> and Zod's schema generic is contravariant in this position.

Calling definePlugin is preferred over a bare object literal even though both are runtime-equivalent: the call site is the right place for an editor to surface "Quick Info" on the generic, and it keeps the public author surface consistent across the 40+ in-flight @suaveplan/api-plugin-* packages.