diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 5d45bf7f..91966286 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -1271,6 +1271,18 @@ "authentication": "ON_INSTALL" }, "category": "Framework" + }, + { + "name": "orpc", + "source": { + "source": "local", + "path": "./plugins/orpc" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Development" } ] } diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3ace2da6..00bc53b7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1680,6 +1680,23 @@ ] } } + }, + { + "name": "orpc", + "description": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "category": "development", + "keywords": ["orpc", "typesafe", "api", "rpc", "openapi", "typescript"], + "tags": ["api", "typescript", "skills"], + "source": "./plugins/orpc", + "homepage": "https://orpc.dev", + "relevance": { + "topic": "oRPC", + "signals": { + "manifestDeps": [ + { "file": "[/\\\\]package\\.json$", "pattern": "\"@orpc/[a-z-]+\"\\s*:" } + ] + } + } } ] } diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 8c1b1899..efe6f211 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -373,6 +373,11 @@ "name": "shadcn-ui", "source": "./plugins/shadcn-ui", "description": "Manages shadcn/ui components and projects — adding, searching, fixing, styling, and composing UI — plus migrating from Radix UI to Base UI." + }, + { + "name": "orpc", + "source": "./plugins/orpc", + "description": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration" } ] } diff --git a/.release-please-manifest.json b/.release-please-manifest.json index eb994527..a127efc2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -68,5 +68,6 @@ "plugins/playwright-cli": "1.0.0", "plugins/greptile": "1.1.1", "plugins/shadcn-ui": "1.1.0", - "plugins/tanstack": "0.2.0" + "plugins/tanstack": "0.2.0", + "plugins/orpc": "1.0.0" } diff --git a/README.md b/README.md index 895ad07b..3633ab2a 100644 --- a/README.md +++ b/README.md @@ -469,6 +469,12 @@ Manages shadcn/ui components and projects — adding, searching, fixing, styling **Install:** `/plugin install shadcn-ui@pleaseai` | **Source:** [plugins/shadcn-ui](https://github.com/pleaseai/claude-code-plugins/tree/main/plugins/shadcn-ui) +#### oRPC + +End-to-end typesafe APIs with oRPC v2 — the core `os` builder, routers, middleware and clients (`orpc`), contract-first design with `@orpc/contract` (`orpc-contract`), REST/OpenAPI exposure and spec generation (`orpc-openapi`), and tRPC → oRPC / v1 → v2 migrations (`orpc-migrate`). + +**Install:** `/plugin install orpc@pleaseai` | **Source:** [plugins/orpc](https://github.com/pleaseai/claude-code-plugins/tree/main/plugins/orpc) + ## Quick Start The fastest way to get started — install the marketplace and let the plugin recommender auto-detect what you need: diff --git a/plugins/orpc/.agents/skills/orpc-contract/SKILL.md b/plugins/orpc/.agents/skills/orpc-contract/SKILL.md new file mode 100644 index 00000000..e785b85a --- /dev/null +++ b/plugins/orpc/.agents/skills/orpc-contract/SKILL.md @@ -0,0 +1,159 @@ +--- +name: orpc-contract +description: "Design oRPC v2 APIs contract-first, defining the API shape with oc from `@orpc/contract`, implementing it with implement from `@orpc/server`, and consuming the contract from typesafe clients. Use when a project depends on `@orpc/contract`, when defining a contract with oc, implementing a contract with implement, sharing an API contract between server and client packages, generating a contract from an existing OpenAPI spec, or publishing a typed API client to npm. Biases toward retrieval from the oRPC docs over pre-trained knowledge. For core builder, serving, and client work without a contract, use the orpc skill; for REST/OpenAPI exposure, spec generation, and OpenAPILink details, use the orpc-openapi skill." +license: MIT +--- + +# oRPC Contract-First + +Contract-first oRPC splits an API into two artifacts: a contract (schemas, errors, metadata, no business logic) defined with `oc` from `@orpc/contract`, and an implementation built from it with `implement` from `@orpc/server`. Server and client both depend on the contract, never on each other, so the API shape can live in its own package, be reviewed on its own, and ship to consumers as a typed SDK. Prefer it when server and client are separate packages or teams, when the shape starts from an existing OpenAPI spec, or when publishing a client to npm. Stay with the `os`-first flow when one codebase holds both sides and the client can import the router type directly; converting later is cheap because a plain router already works as a router contract (resolve lazy routers with `unlazyRouter` first). + +This skill targets oRPC v2. The `orpc` skill carries the v2 install and version check plus core builder, middleware, serving, and client concepts. Pretrained oRPC knowledge describes v1 and is often wrong for v2: when unsure of any API below, fetch its docs page first (see [Full documentation](#full-documentation)). + +## Define the contract with oc + +Every chain is optional and each call returns a new instance, so share base contracts freely. A contract has no `.handler`. Zod, Valibot, ArkType, and any other Standard Schema library work. + +```ts +import { oc } from '@orpc/contract' +import * as z from 'zod' + +export const contract = { + planet: { + list: oc + .output(z.array(z.object({ id: z.number(), name: z.string() }))), + find: oc + .errors({ NOT_FOUND: {} }) + .input(z.object({ id: z.number() })) + .output(z.object({ id: z.number(), name: z.string() })), + }, +} +``` + +- Always define `.output`: without it clients infer the output as `unknown`. +- A router contract is a plain object mapping keys to procedure contracts or nested objects. Avoid the keys `then`, `bind`, `valueOf`, `toString`, `toJSON`. +- Attach shared metadata to a whole subtree with `oc.meta(someMeta).router({...})`. +- `.errors({ NOT_FOUND: {} })` declares typesafe errors; implementations throw them via `errors.NOT_FOUND()` and clients infer their shapes. +- Repeated `.input`/`.output` calls stack schemas instead of replacing them: object input schemas compose into one flat value, output schemas pipe. Use this to extend a base contract without repeating fields. +- Schema-library-free contracts use the `type` utility from `@orpc/contract`: `oc.input(type<{ value: number }>())`, optionally with a mapping function as `type(fn)`. +- REST routes attach via `.meta(openapi({ method: 'GET', path: '/planets/{id}' }))` from `@orpc/openapi`, exactly as on `os`; routing rules, `prefix`, and spec generation belong to the `orpc-openapi` skill. + +Infer types with `InferRouterContractInputs`, `InferRouterContractOutputs`, and `InferRouterContractErrors` from `@orpc/contract`. + +## Implement with implement + +`implement` turns the contract into an implementer that mirrors its shape and type-checks every handler; `.router` also enforces the contract at runtime. + +```ts +import { implement } from '@orpc/server' + +const implementer = implement(contract).$context<{ db: DB }>() + +const listPlanets = implementer.planet.list.handler(async ({ context }) => context.db.list()) + +const findPlanet = implementer.planet.find.handler(async ({ input, context, errors }) => { + const planet = await context.db.find(input.id) + if (!planet) + throw errors.NOT_FOUND() + return planet +}) + +export const router = implementer.router({ + planet: { list: listPlanets, find: findPlanet }, +}) +``` + +- `.$context` declares the initial context the procedures require, as on `os`. +- Apply middleware per procedure with `.use(mw)` before `.handler`. That runs after input validation (the contract already registered `.input`); to wrap validation, apply it at router level: `implementer.use(mw)` for every procedure, or `implementer.planet.use(mw).list` for a subtree. Router-level plus procedure-level `.use` can run the same middleware twice; use the dedupe pattern from the `orpc` skill. +- `implementer.middleware(fn)` creates middleware that infers the contract's typesafe errors. When not every procedure defines a code, guard with the `in` operator: `if ('TOO_MANY_REQUESTS' in errors) throw errors.TOO_MANY_REQUESTS()`. Any type-compatible middleware also works. +- The result is a normal router: serve it with `RPCHandler` (`orpc` skill) or `OpenAPIHandler` (`orpc-openapi` skill), call it in-process with `call` or `createRouterClient` from `@orpc/server`. + +## Consume the contract from clients + +`RPCLink` needs only the contract type; `OpenAPILink` takes the contract as a runtime value to read each procedure's route. Get the client types exactly right: + +```ts +import type { RouterContractClient } from '@orpc/contract' +import type { JsonifiedClient } from '@orpc/openapi' +import { createORPCClient } from '@orpc/client' +import { RPCLink } from '@orpc/client/fetch' +import { OpenAPILink } from '@orpc/openapi/fetch' + +// RPC protocol (server side is RPCHandler) +const rpcLink = new RPCLink({ origin: 'https://api.example.com', url: '/rpc' }) +const client: RouterContractClient = createORPCClient(rpcLink) + +// OpenAPI protocol (OpenAPIHandler or any spec-compliant server) +const openapiLink = new OpenAPILink(contract, { origin: 'https://api.example.com', url: '/api' }) +const apiClient: JsonifiedClient> = createORPCClient(openapiLink) +``` + +- Router-first equivalents use `RouterClient` from `@orpc/server` in the same positions. +- `JsonifiedClient` is required over `OpenAPILink` because OpenAPI serialization is one-way (a `Date` returns as a string); dropping it via Smart Coercion, plus `OpenAPILink` options and CORS caveats, are in the `orpc-openapi` skill. +- Per-call client context is the second type parameter, `RouterContractClient`, then `client.planet.find(input, { context: { token } })`; link options like `headers` accept functions of that context. +- Export `RouterContractClient` as a type from the server package so clients never import the contract module itself (still needed as a runtime value for `OpenAPILink`; ship the minified JSON below). +- In very large codebases, skip the root client: pin each procedure with `.meta(meta.path([...]))` and build per-procedure clients with `createContractClientFactory` from `@orpc/contract` (`createContractJsonifiedClientFactory` from `@orpc/openapi` when the link needs `JsonifiedClient`); fetch `contract/client-factory` before adopting it. + +## Ship the contract + +When the contract is derived from a router, importing it on the client is heavy and may expose internals. Minify and export JSON instead: + +```ts +import fs from 'node:fs' +import { minifyRouterContract } from '@orpc/contract' +import { unlazyRouter } from '@orpc/server' + +const minified = minifyRouterContract(await unlazyRouter(router)) +fs.writeFileSync('./contract.json', JSON.stringify(minified)) +``` + +`minifyRouterContract` keeps only client-needed metadata. On the client, import the JSON and cast, since schemas do not survive serialization: `new OpenAPILink(contract as typeof router, ...)`. + +To publish a typed SDK to npm, export a factory that pairs the contract with a link: + +```ts +import type { RouterContractClient } from '@orpc/contract' +import { createORPCClient } from '@orpc/client' +import { RPCLink } from '@orpc/client/fetch' + +export function createMyApi(apiKey: string): RouterContractClient { + const link = new RPCLink({ + origin: 'https://example.com', + url: '/rpc', + headers: { 'x-api-key': apiKey }, + }) + return createORPCClient(link) +} +``` + +Bundle with `tsdown --dts src/index.ts`, point `exports` at `dist` types plus import entries, list `@orpc/client` and `@orpc/contract` as dependencies, and publish. Consumers get a fully typed client that works with every oRPC client integration (TanStack Query included). Fetch `recipes/publish-client-to-npm` for the complete `package.json`. + +## Generate the contract from an existing OpenAPI spec + +Use Hey API's `orpc` plugin instead of hand-writing the contract. Install `@hey-api/openapi-ts@next` as a dev dependency (oRPC v2 output requires the `next` tag until the next stable Hey API release) and create `openapi-ts.config.ts`: + +```ts +import { defineConfig } from '@hey-api/openapi-ts' + +export default defineConfig({ + input: 'https://example.com/openapi.json', // local file or URL + output: 'src/contract', + plugins: [{ name: 'orpc', compatibilityVersion: '2', validator: 'zod' }], +}) +``` + +Then run `npx @hey-api/openapi-ts`. It writes `orpc.gen.ts` (one procedure contract per operation, routed via `.meta(openapi({...}))` with `inputStructure: 'detailed'`, plus a combined `contract` router) and `zod.gen.ts`. The generated files import `@orpc/contract`, `@orpc/openapi`, and `zod`, so install those too. From there, implement the contract on your own server, or point `OpenAPILink` at the existing spec-compliant server. + +## Full documentation + +If this skill and a fetched docs page disagree, trust the page: this skill is a summary and v2 is still moving. The docs are served at https://orpc.dev (the v1 docs stay at https://v1.orpc.dev): + +- https://orpc.dev/llms.txt : index of every page with descriptions +- https://orpc.dev/llms-full.txt : the entire docs in one file (large; prefer single pages) +- Append `.md` to any docs URL for that page's exact source markdown (for example https://orpc.dev/docs/contract/procedure.md) + +Pages to fetch when you need details beyond this skill: + +- Contract: `contract/procedure`, `contract/router`, `contract/implementation`, `contract/generate-from-openapi` +- Clients: `client/client-side`, `client/server-side`, `client/error-handling`, `openapi/link` +- Workflows: `recipes/publish-client-to-npm`, `contract/client-factory`, `recipes/monorepo-setup` diff --git a/plugins/orpc/.agents/skills/orpc-migrate/SKILL.md b/plugins/orpc/.agents/skills/orpc-migrate/SKILL.md new file mode 100644 index 00000000..279adfad --- /dev/null +++ b/plugins/orpc/.agents/skills/orpc-migrate/SKILL.md @@ -0,0 +1,123 @@ +--- +name: orpc-migrate +description: "Migrate existing codebases to current oRPC, covering tRPC to oRPC (incremental wrapping via the @orpc/trpc integration or a full rewrite with the concept mapping) and oRPC v1 to v2 (package renames, breaking changes, and a safe order of operations). Use when asked to migrate from tRPC to oRPC, convert or wrap a tRPC router, upgrade oRPC v1 to v2, fix oRPC v2 breaking changes, or swap `@trpc/*` packages for `@orpc/*` equivalents. Biases toward retrieval from the oRPC docs over pre-trained knowledge. Not for greenfield oRPC work or new features in an already-migrated codebase: use the orpc skill for those." +license: MIT +--- + +# Migrating to oRPC + +Playbook for two migrations: tRPC to oRPC, and oRPC v1 to v2. Work in small mechanical steps and run the project's typecheck and test suite after each one, so any failure points at the last step. Pretrained knowledge of oRPC describes v1 and is often wrong for v2: derive every import path, builder method, and option name from the docs pages listed at the end, never from memory. For core v2 concepts while rewriting (the `os` builder, routers, middleware, clients), load the `orpc` skill. v2 currently ships under the `beta` npm dist-tag and plain installs get v1; drop the `@beta` suffix once `npm view @orpc/server dist-tags` shows `latest` at 2.x. + +## tRPC to oRPC + +Two paths. Pick incremental when the app must keep shipping or the tRPC router is large; pick the full rewrite when the router is small enough to convert in one pass. + +### Incremental: wrap the existing tRPC router + +Install `@orpc/trpc@beta` and convert. The result is a regular oRPC router: expose it through an RPC or OpenAPI handler, or call it with a server-side client, while the tRPC code keeps working untouched. + +```ts +import { toORPCRouter } from '@orpc/trpc' + +const orpcRouter = toORPCRouter(trpcRouter) +``` + +- tRPC error formatting is not supported: tRPC errors arrive wrapped in `ORPCError` with the `TRPCError` as `cause` (and a `ZodError` below that for validation failures). Reshape them in a handler interceptor if consumers need structured errors. +- `toTRPCMeta` bridges oRPC `openapi()` metadata into tRPC `.meta()` so converted procedures get OpenAPI routing. Chained tRPC `.meta()` calls merge shallowly, so keep all oRPC metadata inside a single `toTRPCMeta` call. + +Then rewrite leaf routers to native oRPC one at a time, mounting each next to the converted router in one plain object. + +### Full rewrite: concept map + +| Concept | tRPC | oRPC | +| ----------------- | ---------------------------------------------- | ------------------------------ | +| Router | `t.router({...})` | plain object | +| Procedure builder | `t.procedure` | `os` | +| Context | `initTRPC.context()` | `os.$context()` | +| Create middleware | `t.middleware(fn)` | `os.middleware(fn)` | +| Use middleware | `.use(mw)` | `.use(mw)` | +| Validation | `.input(schema)` / `.output(schema)` | same names | +| Implementation | `.query()` / `.mutation()` / `.subscription()` | `.handler()` for all three | +| Errors | `new TRPCError({ code, ... })` | `new ORPCError(code, { ... })` | +| Serializer | `superjson` transformer | built in, remove `superjson` | + +Steps, in order, verifying after each: + +1. **Packages.** Remove `@trpc/server`, `@trpc/client`, `@trpc/tanstack-react-query`; install `@orpc/server@beta`, `@orpc/client@beta`, `@orpc/tanstack-query@beta`. +2. **Base file.** Port the context factory unchanged, then rebuild the shared procedures. In handlers and middleware, `ctx` becomes `context`: + + ```ts + import { ORPCError, os } from '@orpc/server' + + const o = os.$context>>() + + export const publicProcedure = o.use(timingMiddleware) + export const protectedProcedure = publicProcedure.use(({ context, next }) => { + if (!context.session?.user) + throw new ORPCError('UNAUTHORIZED') + return next({ context: { session: context.session } }) + }) + ``` + +3. **Procedures.** Replace `.query`/`.mutation`/`.subscription` with `.handler`; `.input` and `.output` carry over as is. +4. **App router.** Delete every `createTRPCRouter()` wrapper; nested plain objects are the router. +5. **Server.** Replace the tRPC adapter with an oRPC handler for the runtime (fetch shown; other adapters exist for Node, Fastify, AWS Lambda, WebSocket): + + ```ts + import { RPCHandler } from '@orpc/server/fetch' + + const handler = new RPCHandler(appRouter) + + const { response } = await handler.handle(request, { + prefix: '/api/orpc', + context: await createContext({ headers: request.headers }), + }) + ``` + +6. **Client.** `RPCLink` plus `createORPCClient`, typed by `RouterClient`. Call sites drop the `.query()`/`.mutate()` suffixes: + + ```ts + import type { RouterClient } from '@orpc/server' + import { createORPCClient } from '@orpc/client' + import { RPCLink } from '@orpc/client/fetch' + + const link = new RPCLink({ origin: 'http://localhost:3000', url: '/api/orpc' }) + export const client: RouterClient = createORPCClient(link) + + const { planets } = await client.planet.list({ cursor: 0 }) + ``` + +7. **TanStack Query.** `createTanstackQueryUtils(client)` replaces the provider and `useTRPC` hook entirely; use the utils object directly. Input moves inside an `input` key: `orpc.planet.list.queryOptions({ input: { cursor: 0 } })`, `orpc.planet.create.mutationOptions()`. For infinite queries, `infiniteOptions` takes `input` as a function of the page param. + +## oRPC v1 to v2 + +Most v1 names still compile through deprecated aliases (strike-through hints, not errors), so migrate in passes. Order of operations: + +1. **Update packages.** Install every `@orpc/*` package from the `beta` dist-tag (`npm install @orpc/server@beta @orpc/client@beta`, and so on). Swap renamed ones first: `@orpc/react-query`/`@orpc/vue-query`/`@orpc/solid-query`/`@orpc/svelte-query` all became `@orpc/tanstack-query`; `@orpc/openapi-client` merged into `@orpc/openapi`; `@orpc/react` became `@orpc/next`; `@orpc/otel` became `@orpc/opentelemetry`; the `experimental-` packages were promoted (`@orpc/publisher`, `@orpc/ratelimit`, `@orpc/pino`, `@orpc/swr`); `@orpc/vue-colada` became `@orpc/pinia-colada`. Typecheck: the remaining errors are the hard breaks. +2. **Fix the hard breaks** (no aliases): + - Routing: `.route`, `.prefix`, `.tag`, `.$route` are gone from the builder. Use `.meta(openapi({ method, path, prefix, tags }))` from `@orpc/openapi`, or restore `.route` with `import '@orpc/openapi/extensions/route'`. + - `.callable` and `.actionable`: use `call`/`createRouterClient` from `@orpc/server` and `createServerFunctionable` from `@orpc/next`, or the corresponding extension imports. + - `RPCLink`: the single `url` split into `origin` plus a path-only `url`. + - Errors: `status` was removed from `ORPCError` and `.errors` definitions; map codes to HTTP status with `errorStatusMap` on the handler. + - `safe()`: the third tuple element is now the typed error itself (or `null`) and a fourth `isSuccess` element was added. + - Option renames, scoped: handler `rootInterceptors` to `routingInterceptors` (handler `clientInterceptors` still exists, unchanged); link `clientInterceptors` to `transportInterceptors`; on both, `adapterInterceptors` is renamed after the adapter, e.g. `fetchInterceptors` on the fetch adapter. Flat `eventIterator*` options moved under the adapter's request/response mapping: `toFetchResponse.eventStream` on the fetch handler, `sendStandardResponse.eventStream` on Node, `toFetchRequest.eventStream` on the link. +3. **Audit silent behavior changes** (compile fine, behave differently): + - **Wire format changed:** a v1 link cannot talk to a v2 server, in either direction. Deploy the upgraded server and clients together. + - **Automatic middleware deduplication removed:** middleware applied at both router and procedure level now runs twice, with no warning. Guard shared middleware with the context-flag pattern from the dedupe-middleware recipe. + - **Batch Plugin `exclude` became `filter` with the opposite meaning.** Usually delete `exclude`; if skipping is still needed, negate the predicate. + - **`RPCHandler` rejects GET by default** (`allowMethods` defaults to POST/PUT/PATCH/DELETE). Simplest fix: stop sending GET from the link; only allow GET deliberately, with CSRF protection. + - **Handler `filter` takes positional arguments now;** the v1 destructured form still type-checks but reads wrong values. + - **`.input`/`.output` now stack:** a repeated call adds a schema instead of replacing the previous one. +4. **Sweep deprecated aliases** last: `eventIterator` to `asyncIteratorObject`, handler plugins gained a `HandlerPlugin` suffix and link plugins a `LinkPlugin` suffix, `ContractRouter*` types became `RouterContract*`. The from-v1 guide ends with the full alias cheat sheet. + +Verification: typecheck and unit tests after steps 1, 2, and 4; step 3 needs integration or e2e tests, since those changes never surface at compile time. Before finishing, grep for old package names and remaining deprecation strike-throughs. + +## Docs retrieval + +Fetch pages instead of recalling them, and if this skill and a fetched page disagree, trust the page. The v2 docs live at https://orpc.dev and the v1 docs at https://v1.orpc.dev; slugs look alike across both hosts, so check which host a page came from before copying anything from it. The index of every v2 docs page is at https://orpc.dev/llms.txt, https://orpc.dev/llms-full.txt bundles the entire docs in one large file, and appending `.md` to any page URL returns its exact source markdown. + +Authoritative pages to consult during the migration (this skill deliberately omits their full mapping tables): + +- https://orpc.dev/docs/migrations/from-trpc : side-by-side tRPC/oRPC code for every step, including server setup per framework +- https://orpc.dev/docs/migrations/from-v1 : every v2 breaking change with v1/v2 comparisons, package rename table, and the deprecated alias cheat sheet +- https://orpc.dev/docs/integrations/trpc : `toORPCRouter` and `toTRPCMeta` reference for the incremental path diff --git a/plugins/orpc/.agents/skills/orpc-openapi/SKILL.md b/plugins/orpc/.agents/skills/orpc-openapi/SKILL.md new file mode 100644 index 00000000..fe541b59 --- /dev/null +++ b/plugins/orpc/.agents/skills/orpc-openapi/SKILL.md @@ -0,0 +1,151 @@ +--- +name: orpc-openapi +description: "Expose an oRPC router as a spec-compliant OpenAPI HTTP API. Use when a project depends on @orpc/openapi, or for defining REST-style routes on oRPC procedures (openapi() metadata or .route with method, path, successStatus), serving them with OpenAPIHandler alongside RPCHandler, coercing query and path strings with Smart Coercion, calling an OpenAPI-shaped API with OpenAPILink, generating an OpenAPI 3.2 (or 3.1, 3.0) document with OpenAPIGenerator, or serving Scalar or Swagger docs with the OpenAPI Reference plugin. Biases toward retrieval from the oRPC docs over pre-trained knowledge. For contract-first work (defining contracts with oc, implementing them with implement, or generating a contract from an existing OpenAPI spec), use the orpc-contract skill; for plain RPC serving, core builder, middleware, or client work with no REST exposure, use the orpc skill instead." +license: MIT +--- + +# oRPC over OpenAPI + +oRPC procedures speak two protocols from one router: the RPC protocol (`RPCHandler`/`RPCLink`) and plain OpenAPI HTTP (`OpenAPIHandler`/`OpenAPILink`). This skill covers the OpenAPI side. For core builder, middleware, context, and client concepts, load the `orpc` skill (it also carries the v2 install and version check); for contract-first workflows with `@orpc/contract`, load the `orpc-contract` skill. + +This skill targets oRPC v2. Pretrained oRPC knowledge describes v1 and is often wrong for v2: when unsure of any API below, fetch its exact docs page first (see [Full documentation](#full-documentation)). + +## Routing + +A procedure defaults to `POST` with a path derived from the router structure (`planet.create` becomes `POST /planet/create`). Override with `openapi` metadata, on server (`os`) and contract (`oc`) builders alike: + +```ts +import { openapi } from '@orpc/openapi' +import { os } from '@orpc/server' +import { z } from 'zod' + +const getPlanet = os + .meta(openapi({ method: 'GET', path: '/planets/{id}', successStatus: 200 })) + .input(z.object({ id: z.string() })) + .handler(async ({ input }) => ({ id: input.id, name: 'Earth' })) +``` + +- Path params: put `{id}` in `path` and the same key as a required field in the input schema. Use `{+path}` for catch-all segments that may contain `/`. +- `prefix` prepends a path to a procedure or a whole router: `os.meta(openapi({ prefix: '/api/v2' })).router({...})`. Always set a `prefix` on lazy routers so lazy loading only triggers for relevant requests. +- Merging across repeated `.meta(openapi(...))` calls: `prefix` and `tags` concatenate, `method`/`path`/`successStatus` last-wins, and setting a field to `undefined` resets it to the default. +- `successStatus` defaults to `200` and must be below 400. + +For direct routing without `.meta(openapi(...))`, enable the `.route` extension with a side-effect import in a module that always runs at startup (base builder file or server entry): + +```ts +import '@orpc/openapi/extensions/route' + +const ping = os.route({ method: 'GET', path: '/ping' }).handler(async () => 'pong') +``` + +## Input and output mapping + +Compact mode (default): path params merge with query params or the request body depending on the HTTP method, so `GET /planets/earth?q=life` yields input `{ id: 'earth', q: 'life' }`. The handler's return value becomes the response body with status `successStatus`. + +Set `inputStructure: 'detailed'` in the metadata to receive `{ params, query, headers, body }` instead (define only the fields you need in the input schema). Set `outputStructure: 'detailed'` to return `{ status?, headers?, body? }` and vary the status per response. + +Query strings and form data are decoded with bracket notation (fetch `openapi/bracket-notation` before designing schemas for nested query input; its limits are not guessable): + +- Repeated keys become arrays: `?color=red&color=blue` gives `['red', 'blue']`; `color[]=red` pushes too. +- `[number]` targets an array index; `[key]` targets an object property: `?filter[status]=active` gives `{ filter: { status: 'active' } }`. +- It cannot represent empty objects or arrays, root-level arrays, or objects whose keys are all numbers, and query/form values always arrive as strings (or files, in form data). + +Override decoding per parameter with `paramsStyles` (`'primitive'`, `'comma-delimited-array'`, `'comma-delimited-object'`) and `queryStyles` (those plus `'array'`, `'json'`, and space/pipe-delimited variants). Use `requestBodyHint`/`responseBodyHint` (`'json'`, `'form-data'`, `'event-stream'`, `'octet-stream'`, `'file'`, ...) when headers alone cannot tell the parser how to handle the body, for example a raw `ReadableStream` upload. + +## Serving: OpenAPIHandler + +Import from the adapter subpath (`@orpc/openapi/fetch`, `@orpc/openapi/node`, ...): + +```ts +import { SmartCoercionHandlerPlugin } from '@orpc/json-schema' +import { OpenAPIHandler } from '@orpc/openapi/fetch' +import { ZodToJsonSchemaConverter } from '@orpc/zod' + +const handler = new OpenAPIHandler(router, { + plugins: [ + new SmartCoercionHandlerPlugin({ converters: [new ZodToJsonSchemaConverter()] }), + ], +}) + +export async function fetch(request: Request): Promise { + const { matched, response } = await handler.handle(request, { prefix: '/api', context: {} }) + return matched ? response : new Response('Not Found', { status: 404 }) +} +``` + +`OpenAPIHandler` coexists with `RPCHandler`: both accept the same router, so mount them on different prefixes (for example `/api` and `/rpc`) and try each in turn, returning the first `matched` response. + +Smart Coercion: query, path, and form values arrive as strings, so add `SmartCoercionHandlerPlugin` whenever input schemas expect non-string types from those sources. It coerces schema-driven, lossless conversions only (`'123'` to `123`, `'true'`/`'on'` to `true`, ISO strings to `Date`, arrays to `Set`/`Map` via `x-native-type`) and leaves ambiguous values untouched. Skip it when you already coerce in the schema or performance is critical; it adds runtime overhead. + +Other handler options: `interceptors`/`routingInterceptors`/`clientInterceptors` for logging and error mapping, `filter` to exclude procedures from matching, and `errorStatusMap` plus `customErrorResponseBodyEncoder` to customize error responses (by default `ORPCError` codes map to statuses via `COMMON_ERROR_STATUS_MAP`, for example `NOT_FOUND` to 404). + +## Calling: OpenAPILink + +`OpenAPILink` calls an OpenAPI-shaped oRPC API (or any spec-compliant server) through a typesafe client. It needs the contract or router type to know each procedure's route: + +```ts +import type { RouterContractClient } from '@orpc/contract' +import type { JsonifiedClient } from '@orpc/openapi' +import { createORPCClient } from '@orpc/client' +import { OpenAPILink } from '@orpc/openapi/fetch' + +const link = new OpenAPILink(contract, { + origin: 'https://api.example.com', + url: '/api', + headers: ({ context }) => ({ + authorization: context?.token ? `Bearer ${context.token}` : undefined, + }), +}) + +const client: JsonifiedClient> = createORPCClient(link) +``` + +With a router instead of a contract, type the client as `JsonifiedClient>` (`RouterClient` from `@orpc/server`). `JsonifiedClient` exists because OpenAPI serialization is one-way: a `Date` returns as a string. Add `SmartCoercionLinkPlugin` from `@orpc/json-schema` (same converters, first argument is the contract) to restore native types on responses, then drop the `JsonifiedClient` wrapper from the client type. + +To ship a contract to clients without bundling server code, minify it to JSON and import it with a cast; the flow is in the `orpc-contract` skill under "Ship the contract". + +## Spec document and interactive docs + +`OpenAPIGenerator` turns a router or contract into an OpenAPI 3.2 document (pass any `3.1.x` or `3.0.x` `version` for tools that stop at an older version; `QUERY` procedures need 3.2). `OpenAPIReferenceHandlerPlugin` serves the spec at `/spec.json` and a Scalar UI at `/` under the handler prefix (change with `specPath`/`docsPath`, or set `provider: 'swagger'` for Swagger UI): + +```ts +import { OpenAPIGenerator } from '@orpc/openapi' +import { OpenAPIReferenceHandlerPlugin } from '@orpc/openapi/plugins' +import { ZodToJsonSchemaConverter } from '@orpc/zod' + +const generator = new OpenAPIGenerator({ converters: [new ZodToJsonSchemaConverter()] }) + +const handler = new OpenAPIHandler(router, { + plugins: [ + new OpenAPIReferenceHandlerPlugin({ + spec: () => generator.generate(router, { + base: { + info: { title: 'Planet API', version: '1.0.0' }, + servers: [{ url: '/api' }], // absolute URL in production + }, + }), + }), + ], +}) +``` + +Enrich the document through `openapi` metadata: `operationId`, `summary`, `description`, `tags`, `successDescription`, and a `spec` callback that receives the generated operation object and returns an extended one (security requirements, extra responses). Write `spec` and `base` as OpenAPI 3.2 objects even when generating 3.1 or 3.0; the generator downgrades the whole document. Converters also exist for Valibot (`@orpc/valibot`) and ArkType (`@orpc/arktype`); schemas without a matching converter fall back to Standard JSON Schema conversion. + +Verify the wiring before declaring success: request `/spec.json` under the handler prefix and one routed endpoint, and confirm the method, path, and status you configured. + +## Contract-first + +Contract-first workflows belong to the `orpc-contract` skill: defining the shape with `oc` from `@orpc/contract`, implementing it with `implement` from `@orpc/server`, consuming the contract from clients, shipping it as minified JSON, and generating it from an existing OpenAPI spec with Hey API. On the OpenAPI side a contract behaves exactly like a router: attach routes with `.meta(openapi({...}))` on `oc` as shown in [Routing](#routing), serve the implemented router with `OpenAPIHandler` as usual, generate the spec document from the contract directly, and give `OpenAPILink` the contract as its runtime value. + +## Full documentation + +If this skill and a fetched docs page disagree, trust the page: this skill is a summary and v2 is still moving. The docs are served at https://orpc.dev (the v1 docs stay at https://v1.orpc.dev): + +- https://orpc.dev/llms.txt : index of every page with descriptions +- https://orpc.dev/llms-full.txt : the entire docs in one file (large; prefer single pages) +- Append `.md` to any docs URL for that page's exact source markdown (for example https://orpc.dev/docs/openapi/routing.md) + +Pages to fetch when you need details beyond this skill: + +- OpenAPI: `openapi/routing`, `openapi/input-and-output-mapping`, `openapi/bracket-notation`, `openapi/serializer`, `openapi/handler`, `openapi/link`, `openapi/specification`, `openapi/scalar` +- Plugins: `plugins/smart-coercion`, `plugins/openapi-reference` diff --git a/plugins/orpc/.agents/skills/orpc/SKILL.md b/plugins/orpc/.agents/skills/orpc/SKILL.md new file mode 100644 index 00000000..81f7f86d --- /dev/null +++ b/plugins/orpc/.agents/skills/orpc/SKILL.md @@ -0,0 +1,237 @@ +--- +name: orpc +description: "Build, serve, and call end-to-end typesafe APIs with oRPC v2. Use for any task in a project that depends on `@orpc/*` packages, even a one-procedure change: defining procedures with the os builder (.input/.output/.handler, any Standard Schema validator), assembling routers, middleware and context, typesafe errors with ORPCError, serving via RPCHandler on any runtime adapter, calling from server-side clients (call, createRouterClient) or client-side clients (createORPCClient with RPCLink), integrating TanStack Query, or streaming over SSE. Pretrained oRPC knowledge describes v1 and is wrong for v2, so load this skill even when the change looks trivial. Biases toward retrieval from the oRPC docs over pre-trained knowledge. For REST/OpenAPI exposure, prefer the orpc-openapi skill; for contract-first design, the orpc-contract skill; for tRPC or oRPC v1 migrations, the orpc-migrate skill." +license: MIT +--- + +# oRPC + +oRPC is a typesafe API framework: write plain TypeScript functions on the server, call them from clients like local functions. Input is validated at runtime, types flow end to end, and there is no code generation step. The same router can also be served as a REST API with an OpenAPI spec. + +This skill targets oRPC v2. Check what is installed before writing code: `npm ls @orpc/server` (or any `@orpc/*` package). A 1.x version means v1, where this skill's guidance does not apply; use the `orpc-migrate` skill to upgrade. v2 currently ships under the `beta` dist-tag (`npm install @orpc/server@beta @orpc/client@beta`; a plain install silently gets v1). If `npm view @orpc/server dist-tags` shows `latest` at 2.x, the beta has ended: install normally. + +Pretrained oRPC knowledge describes v1 and is often wrong for v2 (routing moved to `.meta(openapi(...))`, `RPCLink` split `url` into `origin` plus a path, automatic middleware dedupe was removed). Prefer retrieval: the index of every docs page is at https://orpc.dev/llms.txt; see [Full documentation](#full-documentation) for the mechanics. + +Package map: + +- `@orpc/server`: the `os` builder, routers, middleware, `RPCHandler`, server-side clients (`call`, `createRouterClient`), `implement` for mocks +- `@orpc/client`: `createORPCClient`, `RPCLink`, `safe`, `createSafeClient`, `isDefinedError` +- `@orpc/contract`: contract-first API definitions implemented separately from their logic (see the `orpc-contract` skill) +- `@orpc/openapi`: `OpenAPIHandler`, `OpenAPILink`, OpenAPI 3.2 spec generation +- Integration packages such as `@orpc/tanstack-query` and `@orpc/nest` + +## Define procedures + +Build a procedure with the `os` builder: describe input with a schema, implement with `.handler`. Zod, Valibot, ArkType, and any other [Standard Schema](https://standardschema.dev/) library work for `.input`, `.output`, and error `data`. + +```ts +import { os } from '@orpc/server' +import * as z from 'zod' + +export const listPlanets = os + .handler(async () => [{ id: 1, name: 'Earth' }]) // no .input: takes no arguments + +export const findPlanet = os + .input(z.object({ id: z.number() })) + .handler(async ({ input }) => ({ id: input.id, name: 'Earth' })) +``` + +`.handler` is the only required step. The full chain, every other step optional: + +```ts +const example = os + .$context<{ headers: Headers }>() // initial context this procedure requires + .errors({ NOT_FOUND: {} }) // typed errors + .use(requireAuth) // middleware + .input(z.object({ id: z.number() })) + .output(z.object({ id: z.number(), name: z.string() })) // optional; also speeds up type checking + .handler(async ({ input, context, errors }) => ({ id: input.id, name: 'Earth' })) +``` + +Every builder step returns a new instance, so share base builders freely: `const authed = os.use(requireAuth)` then build many procedures from `authed`. + +## Assemble a router + +A router is a plain object mapping keys to procedures (or nested routers). Do not use the keys `then`, `bind`, `valueOf`, `toString`, `toJSON`. + +```ts +export const router = { + planet: { list: listPlanets, find: findPlanet }, + admin: os.use(requireAuth).router({ deletePlanet }), // apply shared middleware to a subtree + planetLazy: os.lazy(() => import('./planet')), // code-split; module's default export is a router +} +``` + +Infer types with `InferRouterInputs` / `InferRouterOutputs` from `@orpc/server`. Applying `.use` at both router and procedure level can run the same middleware twice; see the dedupe pattern below. + +## Middleware and context + +Context comes from two places. Initial context is declared with `.$context` and passed explicitly when serving or calling (environment values: headers, env, db). Injected context is added at runtime by middleware via `next({ context })` (runtime values: the authenticated user). + +```ts +import { ORPCError, os } from '@orpc/server' + +const base = os.$context<{ headers: Headers }>() + +const requireAuth = base.middleware(async ({ context, next }) => { + const user = await parseUser(context.headers) + if (!user) { + throw new ORPCError('UNAUTHORIZED') + } + return next({ context: { user } }) // handler now sees context.user, typed non-null +}) +``` + +`.use` accepts named middleware or inline functions. Middleware registered before `.input` runs before validation, the rest after. Middleware can also declare typed input (`os.middleware(async ({ next }, id: number) => ...)`); adapt mismatched shapes with `.use(mw.adaptInput(input => input.id))`. + +Best practice, dedupe expensive middleware: the same middleware can run twice in one call (router-level plus procedure-level `.use`, or a procedure `call`ing another). Cache the result in context: + +```ts +const authProvider = os + .$context<{ headers: Headers, auth?: { id: string }, authLoaded?: boolean }>() + .middleware(async ({ context, next }) => { + const auth = context.authLoaded ? context.auth : await loadAuth(context.headers) + return next({ context: { auth, authLoaded: true } }) + }) +``` + +## Typesafe errors + +Throw `ORPCError` (a `code` plus optional `message` and `data`). Both `message` and `data` are sent to the client, so never put secrets in them. Throw only `Error` instances, never literals. + +Define errors with `.errors` so clients can infer each error's shape: + +```ts +const find = os + .errors({ + NOT_FOUND: { message: 'Planet not found' }, // default message + RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) }, + }) + .handler(async ({ input, errors }) => { + throw errors.NOT_FOUND() + }) +``` + +`throw new ORPCError('NOT_FOUND')` inside that handler is converted to the matching typed error when code and data match. Convert custom error classes to `ORPCError` in a middleware `try/catch`. + +## Serve with RPCHandler + +`RPCHandler` matches requests to procedures, validates input, runs handlers, and encodes results. Pick the adapter for your runtime. Fetch API (Bun, Deno, Cloudflare Workers): + +```ts +import { onError } from '@orpc/server' +import { RPCHandler } from '@orpc/server/fetch' +import { CORSHandlerPlugin } from '@orpc/server/plugins' + +const handler = new RPCHandler(router, { + plugins: [new CORSHandlerPlugin()], + interceptors: [onError(error => console.error(error))], +}) + +export async function fetch(request: Request): Promise { + const { matched, response } = await handler.handle(request, { + prefix: '/rpc', + context: { headers: request.headers }, // provide the router's initial context here + }) + return matched ? response : new Response('Not found', { status: 404 }) +} +// Bun.serve({ fetch }) / Deno.serve(fetch) / export default { fetch } on Workers +``` + +Node HTTP: + +```ts +import { createServer } from 'node:http' +import { RPCHandler } from '@orpc/server/node' + +const handler = new RPCHandler(router) + +const server = createServer(async (req, res) => { + const { matched } = await handler.handle(req, res, { prefix: '/rpc', context: {} }) + if (matched) + return + res.statusCode = 404 + res.end('Not found') +}) + +server.listen(3000) +``` + +Unmatched requests fall through to your own handling. By default `RPCHandler` accepts only `POST`, `PUT`, `PATCH`, and `DELETE`; enabling `GET` via `allowMethods` is a CSRF risk with cookie auth, see [RPC Handler](https://orpc.dev/docs/rpc/handler). Handler options also include `interceptors`, `routingInterceptors`, `clientInterceptors`, `plugins`, `filter`, and `errorStatusMap`. Adapters also exist for [AWS Lambda](https://orpc.dev/docs/adapters/aws-lambda), [Fastify](https://orpc.dev/docs/adapters/fastify), [WebSocket](https://orpc.dev/docs/adapters/websocket), [Message Port](https://orpc.dev/docs/adapters/message-port), and [Expo](https://orpc.dev/docs/adapters/expo). + +## Call procedures + +Server side (same process, no HTTP; also the fastest way to test procedures): + +```ts +import { call, createRouterClient } from '@orpc/server' + +const planet = await call(findPlanet, { id: 1 }, { context: { headers } }) + +const client = createRouterClient(router, { context: { headers } }) // context can be a function +const planets = await client.planet.list() +``` + +Client side, `RPCLink` turns calls into HTTP requests. Import the router as a type only so no server code reaches the client bundle: + +```ts +import type { RouterClient } from '@orpc/server' +import type { router } from '../server/router' +import { createORPCClient } from '@orpc/client' +import { RPCLink } from '@orpc/client/fetch' + +const link = new RPCLink({ + origin: 'http://127.0.0.1:3000', + url: '/rpc', // must match the server's prefix + headers: () => ({ authorization: `Bearer ${token}` }), // options accept functions +}) + +export const orpc: RouterClient = createORPCClient(link) + +const planet = await orpc.planet.find({ id: 1 }) +``` + +Client error handling: plain `try/catch` works, but `safe` preserves typed error inference: + +```ts +import { createSafeClient, isDefinedError, safe } from '@orpc/client' + +const [error, data] = await safe(orpc.planet.find({ id: 1 })) +if (isDefinedError(error)) { + console.log(error.code, error.data) // typed from the procedure's .errors +} +else if (error) { + // unknown error +} + +const safeClient = createSafeClient(orpc) // every call returns [error, data] +``` + +## Beyond the basics + +- Streaming / SSE: return an async generator from `.handler`, validate events with `asyncIteratorObject`, resume via `lastEventId`: [AsyncIteratorObject](https://orpc.dev/docs/async-iterator-object) +- OpenAPI: serve the same router as REST with routing metadata plus `OpenAPIHandler`, and generate a spec (covered in depth by the `orpc-openapi` skill): [OpenAPI Handler](https://orpc.dev/docs/openapi/handler) +- Contract-first: define contracts with `@orpc/contract`, implement with `implement` (covered in depth by the `orpc-contract` skill): [Contracts](https://orpc.dev/docs/contract/procedure) +- Plugins for handler and link: batch, CORS, dedupe, retry, compression, request limits, smart coercion, static files, timeout, tmp file upload, and more. Fetch a plugin's docs page before configuring it; option names are not guessable: [Plugins](https://orpc.dev/docs/plugins/batch) +- Integrations: [TanStack Query](https://orpc.dev/docs/integrations/tanstack-query) (`createTanstackQueryUtils`), [SWR](https://orpc.dev/docs/integrations/swr), [Pinia Colada](https://orpc.dev/docs/integrations/pinia-colada), [Next.js](https://orpc.dev/docs/integrations/next), [NestJS](https://orpc.dev/docs/integrations/nest), [AI SDK](https://orpc.dev/docs/integrations/ai-sdk), [OpenTelemetry](https://orpc.dev/docs/integrations/opentelemetry) +- Testing: `call` procedures directly; mock with `implement(router.planet.list).handler(() => [])`, and run the project's typecheck before declaring success, since end-to-end types are oRPC's first correctness signal: [Testing and Mocking](https://orpc.dev/docs/recipes/testing-and-mocking) +- Monorepos: TypeScript project references keep client types resolvable: [Monorepo Setup](https://orpc.dev/docs/recipes/monorepo-setup) +- Migrating from tRPC or oRPC v1: use the `orpc-migrate` skill + +## Full documentation + +This skill is an overview; fetch exact docs instead of guessing APIs, and if this skill and a fetched page disagree, trust the page. The docs are served at https://orpc.dev (the v1 docs stay at https://v1.orpc.dev): + +- https://orpc.dev/llms.txt : index of every page with descriptions +- https://orpc.dev/llms-full.txt : the entire docs in one file (large; prefer single pages) +- Append `.md` to any docs URL for that page's exact source markdown (for example https://orpc.dev/docs/procedure.md) + +Doc map, all under `https://orpc.dev/docs/`: + +- Top level: `procedure`, `router`, `middleware`, `context`, `error-handling`, `metadata`, plus `binary-data` (file uploads) and `async-iterator-object` (streaming/SSE) +- `rpc/*`, `openapi/*`: protocol details, handlers, links; `contract/*`: contract-first (the `orpc-contract` skill) +- `client/*`: server- and client-side clients, error handling, `DynamicLink` +- `adapters/*`: per-runtime serving quirks (fetch-api, node-http, aws-lambda, fastify, websocket, message-port, expo) +- `plugins/*`: twenty handler/link plugins; `helpers/*`: cookie, encryption, form-data, lock, publisher, ratelimit, signing, base64url +- `integrations/*`: framework glue; `recipes/*`: guidance (testing, SSR, monorepos, validation) +- `migrations/from-v1`, `migrations/from-trpc`: upgrades (use the `orpc-migrate` skill) diff --git a/plugins/orpc/.claude-plugin/plugin.json b/plugins/orpc/.claude-plugin/plugin.json new file mode 100644 index 00000000..43135dfd --- /dev/null +++ b/plugins/orpc/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "orpc", + "version": "1.0.0", + "description": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "author": { + "name": "middleapi", + "url": "https://github.com/middleapi" + }, + "homepage": "https://orpc.dev", + "repository": "https://github.com/middleapi/orpc", + "license": "MIT", + "keywords": [ + "orpc", + "typesafe", + "api", + "rpc", + "openapi", + "typescript" + ], + "skills": "./.agents/skills/" +} diff --git a/plugins/orpc/.codex-plugin/plugin.json b/plugins/orpc/.codex-plugin/plugin.json new file mode 100644 index 00000000..e1b75264 --- /dev/null +++ b/plugins/orpc/.codex-plugin/plugin.json @@ -0,0 +1,34 @@ +{ + "name": "orpc", + "version": "1.0.0", + "description": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "author": { + "name": "middleapi", + "url": "https://github.com/middleapi" + }, + "interface": { + "displayName": "Orpc", + "shortDescription": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "longDescription": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "developerName": "middleapi", + "category": "Development", + "capabilities": [ + "Skill" + ], + "defaultPrompt": [ + "Help me use Orpc for my current task." + ], + "websiteURL": "https://orpc.dev" + }, + "homepage": "https://orpc.dev", + "repository": "https://github.com/middleapi/orpc", + "license": "MIT", + "keywords": [ + "orpc", + "typesafe", + "api", + "rpc", + "openapi", + "typescript" + ] +} diff --git a/plugins/orpc/.cursor-plugin/plugin.json b/plugins/orpc/.cursor-plugin/plugin.json new file mode 100644 index 00000000..391bcefe --- /dev/null +++ b/plugins/orpc/.cursor-plugin/plugin.json @@ -0,0 +1,26 @@ +{ + "name": "orpc", + "displayName": "Orpc", + "description": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "version": "1.0.0", + "author": { + "name": "middleapi" + }, + "category": "Development", + "homepage": "https://orpc.dev", + "repository": "https://github.com/middleapi/orpc", + "license": "MIT", + "keywords": [ + "orpc", + "typesafe", + "api", + "rpc", + "openapi", + "typescript" + ], + "tags": [ + "api", + "typescript", + "skills" + ] +} diff --git a/plugins/orpc/plugin.json b/plugins/orpc/plugin.json new file mode 100644 index 00000000..2b01da97 --- /dev/null +++ b/plugins/orpc/plugin.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://antigravity.google/schemas/v1/plugin.json", + "name": "orpc", + "version": "1.0.0", + "description": "End-to-end typesafe APIs with oRPC v2 - core builder, contract-first design, OpenAPI/REST exposure, and tRPC/v1 migration", + "author": { + "name": "middleapi", + "url": "https://github.com/middleapi" + }, + "homepage": "https://orpc.dev", + "repository": "https://github.com/middleapi/orpc", + "license": "MIT", + "keywords": [ + "orpc", + "typesafe", + "api", + "rpc", + "openapi", + "typescript" + ] +} diff --git a/plugins/orpc/skills-lock.json b/plugins/orpc/skills-lock.json new file mode 100644 index 00000000..39b4bb2b --- /dev/null +++ b/plugins/orpc/skills-lock.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "skills": { + "orpc": { + "source": "middleapi/orpc", + "sourceType": "github", + "skillPath": "skills/orpc/SKILL.md", + "computedHash": "24aeb625eeb54572e2dc862648085a4f2270c9a0a6167162b7fc53290bb3acb2" + }, + "orpc-contract": { + "source": "middleapi/orpc", + "sourceType": "github", + "skillPath": "skills/orpc-contract/SKILL.md", + "computedHash": "14a425e58cdd2857acd197620025dc5f778c158036fcbf9fef48d42191d62f94" + }, + "orpc-migrate": { + "source": "middleapi/orpc", + "sourceType": "github", + "skillPath": "skills/orpc-migrate/SKILL.md", + "computedHash": "b8045826a9a78eb51d79b4a0037feca9480b1824aee3095752454fad8bc5d65d" + }, + "orpc-openapi": { + "source": "middleapi/orpc", + "sourceType": "github", + "skillPath": "skills/orpc-openapi/SKILL.md", + "computedHash": "3375d89d95a869df9c1bad94363bebca3539a7ae0973a20e7e0089882932ac43" + } + } +} diff --git a/release-please-config.json b/release-please-config.json index e7fb2205..98867173 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1245,6 +1245,32 @@ "jsonpath": "$.version" } ] + }, + "plugins/orpc": { + "release-type": "simple", + "component": "orpc", + "extra-files": [ + { + "type": "json", + "path": ".claude-plugin/plugin.json", + "jsonpath": "$.version" + }, + { + "type": "json", + "path": ".codex-plugin/plugin.json", + "jsonpath": "$.version" + }, + { + "type": "json", + "path": "plugin.json", + "jsonpath": "$.version" + }, + { + "type": "json", + "path": ".cursor-plugin/plugin.json", + "jsonpath": "$.version" + } + ] } }, "release-type": "node",