kitcn
Version:
kitcn - React Query integration and CLI tools for Convex
154 lines (109 loc) • 6.84 kB
Markdown
# Create Plugins
Canonical patterns for new kitcn plugins.
## Goals
1. Keep runtime bundles entry-local.
2. Keep schema extension ownership explicit and local.
3. Keep scaffolds predictable and user-owned.
4. Keep CLI generic; plugin behavior comes from plugin manifests.
## Package Layout
Use split ownership:
1. `@kitcn/<plugin>`: runtime capability, middleware, stable helpers, shared types.
2. `packages/kitcn/src/cli/plugins/<plugin>/...`: scaffold templates for local schema/runtime files.
3. `convex/lib/plugins/<plugin>/schema.ts`: app-owned schema extension generated by the CLI.
Do not ship first-party schema entrypoints from plugin packages.
## Runtime API Contract
Use middleware-scoped access with one runtime primitive:
```ts
// app code
import { MyPlugin } from '@kitcn/my-plugin';
import { createPluginsMyPluginCaller } from './generated/plugins/my-plugin.runtime';
import { privateMutation } from './lib/crpc';
export const myPlugin = MyPlugin.configure({
enabled: true,
});
export const myProcedure = privateMutation.use(myPlugin.middleware())
.mutation(async ({ ctx }) => {
const caller = createPluginsMyPluginCaller(ctx);
await caller.doWork({});
});
```
Plugin packages should define their runtime entry with `definePlugin('<key>', ...)` from `kitcn/plugins` and expose a named plugin value such as `ResendPlugin`.
Rules:
1. No `ctx.getApi(...)` runtime path.
2. Middleware injects plugin runtime surface at `ctx.api.<plugin>`.
3. Use generated callers (`createPlugins<Plugin>Caller(ctx)`) for internal function composition.
4. Reusable defaults go on plugin chain via `.configure(...)`.
5. App env resolution belongs in scaffold/app code, not inside the package plugin. Package plugins should not read `process.env` for app secrets.
6. Extend plugins with `.extend(({ middleware }) => ({ middleware: () => middleware().pipe(...), ...namedPresets }))`.
7. `plugin.middleware()` still injects `ctx.api.<plugin>`; middleware overrides should build on the helper `middleware()`, not replace injection from scratch.
8. Prefer object config.
9. Use callback config only when context is required.
10. No helper alternatives like `getMyPlugin(...)`.
11. Plugin runtime code must use ORM context (`ctx.orm`) when it touches kitcn schema.
## Private Procedure Contract
Plugin internal Convex functions should be scaffold-owned cRPC private procedures.
Rules:
1. Reuse project cRPC builders from `convex/<paths.lib>/crpc.ts`.
2. Define plugin internals with `privateQuery`, `privateMutation`, `privateAction` and chain plugin middleware per procedure.
3. Do not define plugin internals with vanilla `internalQuery/internalMutation/internalAction`.
4. Do not ship `create*Runtime` factory patterns from plugin packages.
5. Do not ship `build*Handlers` wrapper factories from plugin packages.
6. Do not use `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction` for plugin internal composition. Use `create<Module>Caller(ctx)` from `generated/<module>.runtime`.
7. Hook/callback fanout should be scaffold-owned internal procedures, not dynamic function-handle config.
## Schema Extension Contract
Schema lives in local scaffolded files and is defined with `defineSchemaExtension(...)`.
Expected shape:
1. `defineSchemaExtension('<key>', { ...tables })`
2. optional chained `.relations((r) => ({ ... }))`
3. optional chained `.triggers({ ... })`
Canonical install:
```ts
import { defineSchema } from 'kitcn/orm';
import { myExtension } from '../lib/plugins/my-plugin/schema';
export default defineSchema(tables).extend(myExtension());
```
Relation composition rules:
1. Extension `relations(...)` is merged before app `defineSchema(...).relations(...)`.
2. Duplicate relation fields (`table.field`) throw.
3. Use extension relation defaults for baseline wiring; app-level relations should extend, not duplicate fields.
Trigger composition rules:
1. Extension triggers are merged before app triggers.
2. Duplicate trigger hooks for the same table/event throw.
## Scaffold Rules
1. Local schema file lives at `convex/<paths.lib>/plugins/<plugin>/schema.ts`.
2. Plugin Convex function exports live in `<functionsDir>/plugins/<plugin>.ts`.
3. Non-function helpers live under `convex/<paths.lib>/plugins/<plugin>/...`.
4. `kitcn codegen` must not generate plugin runtime modules.
5. Scaffold templates need stable template IDs.
6. `add` can merge/upsert scaffold mappings; never clobber custom files unless overwrite is explicit.
7. `add --dry-run`, `add --diff [path]`, and `add --view [path]` preview one shared install plan: scaffold files, env bootstrap, `kitcn.json`, schema registration, lockfile write, dependency install status, codegen/hooks, env reminders.
8. Preview comparisons for `.ts`, `.tsx`, `.js`, `.jsx`, and `.json` should be semantic enough to ignore formatter-only churn.
9. `view` is read-only plan inspection. Default template source is lockfile mappings, fallback is the resolved preset, `--preset` forces preset selection.
10. `info` audits installed plugins from schema + lockfile and reports scaffold drift / missing dependencies.
11. Runtime packages should stay focused on stable logic; function wiring and schema stay local.
12. If `paths.env` is missing, `kitcn add <plugin>` bootstraps `${paths.lib}/get-env.ts` and writes `paths.env` into `kitcn.json`.
13. `envFields` can attach reminder metadata; `kitcn add <plugin>` prints those reminders against `<functionsDir>/.env` and includes them in JSON output.
14. Scaffolded Convex files should read env through `getEnv()` only. Do not generate `process.env` access in plugin scaffolds.
## Lockfile Rules
Lockfile path is fixed:
`<functionsDir>/plugins.lock.json`
Current contract:
1. `plugins.<plugin>.package = <packageName>` (required)
2. optional `plugins.<plugin>.files.<templateId> = <relativePath>`
No `version` or timestamp fields in lockfile.
## CLI Manifest Guidance
Each plugin should provide:
1. `label` / `description` for prompts plus `view` / `info` output
2. `docs` links and `keywords` for `kitcn docs`
3. preset definitions
4. scaffold template resolver
5. local schema registration metadata (`importName`, file path, target root)
CLI stays plugin-agnostic; plugin-specific flags should not be added globally.
## Test Checklist
1. plugin ctx typing: `ctx.api.<plugin>` available only after `.use(plugin.middleware())`
2. codegen: no plugin runtime artifacts are generated
3. stale cleanup: `generated/plugins/**` artifacts are removed
4. add flow: idempotent scaffold writes + lockfile mapping
5. preview flow: plan captures changed/missing scaffold files plus schema/lockfile/config/env updates
6. runtime parity tests for plugin API methods exposed via middleware
7. schema registration: local `convex/lib/plugins/<plugin>/schema.ts` is scaffolded and imported once in root schema