Skill

Owner: Forge · Team: Dev Team · Source: ~/.openclaw/dev-shared/skills/trpc-endpoint/SKILL.md

Add or change a tRPC procedure in silver-castle apps/api — shared Zod schema, router/repo split, permissions, Hebrew errors, audit+notify, and gates.


Playbook (mirrored from disk)

tRPC Endpoint

Order of work:

  1. Shared schema firstpackages/shared/src/schemas/<domain>.ts, house idiom:
    export const CreateThingInput = z.object({...}); export type CreateThingInput = z.infer<typeof CreateThingInput>
    (.uuid() for ids, .max() everywhere). Barrel-export it.
  2. Repoapps/api/src/repos/<domain>.repo.ts: plain async fns, db: SupabaseClient first
    arg, const COLS = \…`select constant, throw plainnew Error(error.message)`;
    enrichment joins in TS (Promise.all + Map), not SQL.
  3. Routerapps/api/src/routers/<domain>.ts:
    requireAction('<domain>.<verb>').input(Schema).mutation(async ({ ctx, input }): Promise<Ret> => {...})
    — NO .output(); return-type annotation; mutations return { ok: true as const } or the row.
    Errors: throw new TRPCError({ code: 'FORBIDDEN', message: '<Hebrew>' }) — sanctioned codes;
    PRECONDITION_FAILED for invalid state transitions.
  4. Privileged write? REQUIRED: audit(ctx.db, { actor_id, action, target_type, target_id, project_id?, meta?, ip: ctx.ip }) + notify({ db, recipient_ids, kind, title, body, link })
    (Hebrew title/body; link = app route). Best-effort — never let them break the main action.
  5. Compose in _root.ts if a new router. ESM: ALL relative imports end in .js.
  6. New permission action → seed row in the SAME migration + update the frontend mirror
    apps/web/src/lib/auth/permissions.ts (lockstep or drift).

Gates:

cd ~/silver-castle/apps/api && npx tsc -p tsconfig.json && npm test
# shared changed? also: cd ../web && npx tsc -p tsconfig.app.json --noEmit