AGENT SKILL

type-safe-api-contracts

暂无简介

License 许可
Apache-2.0
最近更新
2026-11-11
Stars
★ 13
Forks
1

03 SKILL.md 正文

/type-safe-api-contracts

When to Use

  • TypeScript monorepo where frontend + backend share the same contract
  • "The API changed and the frontend silently broke" — need compile-time safety
  • Generating OpenAPI spec from Zod schemas (single source of truth)
  • External clients need an OpenAPI doc but you don't want to maintain it manually

Do NOT use for

  • Pure REST API consumed by non-TypeScript clients only (use OpenAPI spec directly)
  • GraphQL systems (use code-first GraphQL instead)

Decision: tRPC vs ts-rest vs Zodios

Same TypeScript monorepo, full type inference end-to-end?
  YES → tRPC (no schema, types inferred from router definition)
  NO  →
    Need REST endpoints compatible with non-TS clients?
      YES → ts-rest (typed contract, real HTTP verbs, OpenAPI export)
      Need Axios with typed responses from existing OpenAPI?
        YES → Zodios (Axios + Zod runtime validation from OpenAPI)

tRPC (end-to-end type inference)

// server/router.ts
import { initTRPC, TRPCError } from '@trpc/server'
import { z } from 'zod'

const t = initTRPC.context<{ userId: string }>().create()
export const router  = t.router
export const publicProcedure    = t.procedure
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' })
  return next({ ctx })
})

export const appRouter = router({
  agent: router({
    run: protectedProcedure
      .input(z.object({ task: z.string().min(1), depth: z.number().int().max(5).default(1) }))
      .output(z.object({ result: z.string(), tokensUsed: z.number() }))
      .mutation(async ({ input, ctx }) => {
        const result = await runAgent(input.task, ctx.userId, input.depth)
        return result
      }),

    history: protectedProcedure
      .input(z.object({ limit: z.number().default(20) }))
      .query(async ({ input, ctx }) => getHistory(ctx.userId, input.limit)),
  }),
})

export type AppRouter = typeof appRouter
// client/trpc.ts — full type inference, no codegen
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from '../server/router'

const trpc = createTRPCProxyClient<AppRouter>({
  links: [httpBatchLink({ url: '/api/trpc', headers: () => ({ Authorization: `Bearer ${token}` }) })],
})

// Fully typed — IDE autocomplete on every call
const { result, tokensUsed } = await trpc.agent.run.mutate({ task: 'summarize repo' })

// Rule: always define .output() schema — prevents leaking server-internal fields
// Rule: use httpBatchLink for SPA (batches multiple calls into one HTTP request)
// Rule: TRPCError code maps to HTTP status — UNAUTHORIZED=401, NOT_FOUND=404, etc.

ts-rest (typed REST contracts)

// contracts/agent.ts — shared between server and client
import { initContract } from '@ts-rest/core'
import { z } from 'zod'

const c = initContract()

export const agentContract = c.router({
  run: {
    method:   'POST',
    path:     '/agents/run',
    body:     z.object({ task: z.string(), sessionId: z.string().uuid() }),
    responses: {
      200: z.object({ result: z.string(), elapsedMs: z.number() }),
      429: z.object({ retryAfter: z.number() }),
    },
    summary: 'Run an agent task',
  },
  getResult: {
    method:   'GET',
    path:     '/agents/:id/result',
    pathParams: z.object({ id: z.string().uuid() }),
    responses: {
      200: z.object({ status: z.enum(['pending', 'done', 'failed']), result: z.string().optional() }),
      404: z.object({ message: z.string() }),
    },
  },
})
// server/routes.ts — Next.js / Express handler
import { createNextRouter } from '@ts-rest/next'
import { agentContract }    from '../contracts/agent'

export default createNextRouter(agentContract, {
  run: async ({ body }) => {
    const result = await executeAgentTask(body.task, body.sessionId)
    return { status: 200, body: result }
  },
  getResult: async ({ params }) => {
    const task = await getTask(params.id)
    if (!task) return { status: 404, body: { message: 'Not found' } }
    return { status: 200, body: task }
  },
})
// client/api.ts — types inferred from same contract
import { initQueryClient } from '@ts-rest/react-query'
import { agentContract }   from '../contracts/agent'

const client = initQueryClient(agentContract, { baseUrl: '/api' })

// Typed response — status 200 body is always z.infer<typeof responses[200]>
const { data } = client.getResult.useQuery(['agent', id], { params: { id } })

zod-to-openapi (spec generation)

import { OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'
import { z } from 'zod'

const registry = new OpenAPIRegistry()

// Register schemas
const AgentRunBody = registry.register('AgentRunBody',
  z.object({
    task:      z.string().min(1).openapi({ example: 'summarize the PR' }),
    sessionId: z.string().uuid(),
  })
)

const AgentRunResult = registry.register('AgentRunResult',
  z.object({
    result:    z.string(),
    elapsedMs: z.number().int(),
  })
)

// Register path
registry.registerPath({
  method:  'post',
  path:    '/agents/run',
  summary: 'Execute an agent task',
  request: { body: { content: { 'application/json': { schema: AgentRunBody } } } },
  responses: {
    200: { description: 'Success', content: { 'application/json': { schema: AgentRunResult } } },
    429: { description: 'Rate limited' },
  },
})

// Generate spec
const generator = new OpenApiGeneratorV3(registry.definitions)
const spec      = generator.generateDocument({
  openapi: '3.0.0',
  info:    { title: 'YAMTAM Agent API', version: '1.3.44' },
  servers: [{ url: '/api' }],
})

// Rule: write spec to releases/openapi.json in CI — never hand-edit
// Rule: Zod schemas ARE the source of truth; spec is a derived artifact

Zodios (Axios + Zod typed client)

import { makeApi, Zodios } from '@zodios/core'
import { z } from 'zod'

const api = makeApi([
  {
    method:   'post',
    path:     '/agents/run',
    alias:    'runAgent',
    response: z.object({ result: z.string(), elapsedMs: z.number() }),
    parameters: [{
      name: 'body',
      type: 'Body',
      schema: z.object({ task: z.string(), sessionId: z.string().uuid() }),
    }],
    errors: [
      { status: 429, schema: z.object({ retryAfter: z.number() }) },
    ],
  },
])

const client = new Zodios('/api', api)

// Fully typed call — response is inferred from schema
const { result } = await client.runAgent({ task: 'list PRs', sessionId: crypto.randomUUID() })

// Rule: Zodios validates response at runtime — throws ZodError on mismatch
// Rule: use alias to get named method on client (not positional array indexing)

Anti-Fake-Pass Checklist

❌ tRPC .output() missing (server-internal fields can leak to client)
❌ ts-rest path params not in pathParams schema (runtime crash on mismatch)
❌ OpenAPI spec hand-edited instead of generated from Zod (drift guaranteed)
❌ Zodios client used without error schema (429/500 untyped)
❌ tRPC httpBatchLink missing — each call = separate HTTP round-trip
❌ Contract defined in server package, not shared package (client can't import it)
❌ Zod .openapi() metadata omitted — spec examples missing, docs useless

04 同仓库更多 Skills

Horonomos-laps
Horonomos-laps/Yana-AI

openai--sentry--sentry

Use when the user asks to inspect Sentry issues or events, summarize recent production errors, or pull basic Sentry health data via the Sentry API; perform read-only queries with the bundled script and require `SENTRY_AUTH_TOKEN`.

★ 13
Horonomos-laps
Horonomos-laps/Yana-AI

ito-basket-compare

Compare Itô prediction-market baskets against a user's knowledge base, portfolio notes, financial context, watchlist, or research thesis. Use for read-only basket comparison and gap analysis without investment advice or live trading.

★ 13
Horonomos-laps
Horonomos-laps/Yana-AI

implementing-network-traffic-baselining

Build network traffic baselines from NetFlow/IPFIX data using Python pandas for statistical analysis, z-score anomaly detection, and hourly/daily traffic pattern profiling

★ 13
Horonomos-laps
Horonomos-laps/Yana-AI

detecting-evasion-techniques-in-endpoint-logs

Detects defense evasion techniques used by adversaries in endpoint logs including log tampering, timestomping, process injection, and security tool disabling. Use when investigating suspicious endpoint behavior, building detection rules for evasion tactics, or conducting threat hunting for stealthy adversary activity. Activates for requests involving evasion detection, defense evasion analysis, log tampering detection, or MITRE ATT&CK TA0005.

★ 13
Horonomos-laps
Horonomos-laps/Yana-AI

implementing-microsegmentation-with-guardicore

Implementing microsegmentation using Akamai Guardicore Segmentation to map application dependencies, create granular network policies, visualize east-west traffic flows, and enforce least-privilege communication between workloads across data centers and cloud.

★ 13
Horonomos-laps
Horonomos-laps/Yana-AI

ramdisk-tmpfs-patterns

暂无简介

★ 13