responsibleapi
Use when working on `*.responsible.ts` files
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
Use when working on `*.responsible.ts` files
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
| name | responsibleapi |
| description | Use when working on `*.responsible.ts` files |
Use this skill for *.responsible.ts files and other TypeScript that builds
OpenAPI with @responsibleapi/ts.
Target is OpenAPI 3.1+ only.
Keep this skill focused on authoring expressive TypeScript DSL code. State which DSL form to use and when. Avoid discussion of runtime tooling or downstream representation.
responsibleapi is TypeScript DSL for declaring OpenAPI 3.1 APIs.
responsibleAPI({ partialDoc, routes, ...defaults }).partialDoc only for the base document metadata and deliberately authored
raw OpenAPI fields. Keep components and top-level security out unless you
know the exact reason.routes is path map.GET(...), POST(...), PUT(...), DELETE(...),
HEAD(...) for single-method top-level paths.scope({ ... }) when path has nested routes, shared params, shared
security, or multiple methods.scope, direct methods are plain object keys like GET: { ... },
POST: { ... }.scope, nested single-method paths can still use method helpers:
"/items": GET({ ... })."/users/:id".Use the richest DSL construct that states author intent directly:
scope defaults for a route group, operation fields for one endpoint.scope(...) or scope-level forEachOp for a single operation. Use
the method helper directly and keep one-operation behavior on that operation.named(...). When the desired component name is
a valid TypeScript/JavaScript identifier, name the thunk with that identifier
and pass the thunk itself. Use named(...) only when an exact stable
component name cannot be an identifier and component reuse is genuinely
required.named(...) merely to match another named
value. For response headers whose exact HTTP spelling cannot be an identifier,
prefer an inline headers entry at the narrowest shared res.defaults or
operation level over a named component.Config, not Config().headers: TraceHeaders() and
headers: { ...TraceHeaders() }. Either inline a one-off value or model each
reusable value with the semantic helper and pass its thunk through the DSL's
reuse slot.description, examples,
format, pattern, numeric bounds, deprecated, operation id, tags,
response headers, cookies, and MIME choices.resp(...) when a response needs a description, headers, cookies, MIME
details, or other response-level metadata.ref(...) when reusing a named value with local metadata.responsibleAPI(...) as immutable output.
Never inspect or mutate paths, components, responses, content, schemas,
references, or any other compiled field to repair the document after DSL
compilation. After responsibleAPI(...), only serialize or return the
document.itemSchema, injecting $ref, or walking a compiled response to
change it. If the DSL cannot express a required construct, report or fix the
DSL limitation instead of patching its output.import { GET, object, responsibleAPI, resp, string } from "@responsibleapi/ts"
responsibleAPI({
partialDoc: {
openapi: "3.1.0",
info: {
title: "Example API",
version: "1.0.0",
},
},
routes: {
"/hello": GET({
res: {
200: resp({
description: "OK",
body: object({
message: string(),
}),
}),
},
}),
},
})
Use schema builders for OpenAPI schemas and prefer tiny schema thunks for reusable shapes:
const UserID = () => int64({ minimum: 1 })
const User = () =>
object({
id: UserID,
name: string(),
"nickname?": string(),
})
Common schema builders:
object, array, dictstring, boolean, number, integerint32, int64, uint32, uint64, float, doubleunknown, nullable, oneOf, anyOf, allOfemail, httpURL, isoDuration, unixMillisRules:
?, quoted as needed for TypeScript object
syntax, for example "nickname?".nullable() for
optional fields unless the user explicitly asks to allow JSON null.Thunk(), when nesting it in
another schema, request, response, or parameter map.string(...), object(...), resp(...), and responseHeader(...) are
called when constructing an inline value or defining a reusable thunk.missingSchemas when routes is non-empty. Every schema component
must be reachable from the request or response graph of a declared route.missingSchemas is permitted only for a schema-only document whose routes
map is empty. Include the top-level schema thunk that reaches the rest of the
graph; do not list every nested schema when one top-level schema already uses
them all.named("component-name", value) only when
schema/parameter/security/header needs a stable component name that cannot be
expressed as a valid TypeScript/JavaScript identifier.named("NonEmptyString", NonEmptyString()).
NonEmptyString is already a valid identifier, so pass NonEmptyString
directly.ref(NamedValue, { description }) when you want to reuse a named value
and add local metadata.Use oneOf with an OpenAPI discriminator for tagged object unions:
const TcpListenerConfig = () =>
object({
kind: string({ const: "tcp" }),
addr: NonEmptyString,
})
const UnixListenerConfig = () =>
object({
kind: string({ const: "unix" }),
path: NonEmptyString,
})
const ListenerConfig = () =>
oneOf([TcpListenerConfig, UnixListenerConfig], {
discriminator: {
propertyName: "kind",
mapping: {
tcp: TcpListenerConfig,
unix: UnixListenerConfig,
},
},
})
Rules:
string({ const: "tag-value" }).kind, mode, or type.oneOf([...]) list and discriminator
mapping; do not call them there.Use semantic helpers or small named objects for common domain values.
Use isoDuration(...) for RFC 3339 / ISO 8601 duration intervals:
const BillingPeriod = () =>
isoDuration({
description: "Subscription billing period length.",
examples: ["P1M"],
})
For money, prefer a tiny object over loose sibling fields:
const Money = () =>
object({
amount: int64({ description: "Minor units, e.g. cents." }),
currency: string({ pattern: "^[A-Z]{3}$" }),
})
Microservices should not share implementation models, but they can share
contract vocabulary. Put boundary concepts such as Money, CurrencyCode,
error shapes, pagination, and file responses in a shared TypeScript module like
packages/openapi/spec/shared.responsible.ts.
Rules:
Money or
WorkbookExportResponse.*.responsible.ts file and pass the
thunk itself when nesting it.components.schemas or components.responses.$defs as the public shared model namespace for generated SDKs;
OpenAPI 3.1 allows it, but local #/components/... refs are more reliable.partialDoc.components; let the DSL discover
usage and emit local components for each service document.responsibleAPI({
partialDoc,
security?,
forEachOp?,
forEachPath?,
routes,
})
Use root defaults for cross-cutting behavior:
forEachOp for shared operation defaults.forEachPath for shared path-level params.security for global auth requirements.Typical forEachOp uses:
req.mimereq.securityres.mimeres.defaultsres.addtagsTypical forEachPath use:
paramsSingle top-level route:
"/users": GET({
res: { 200: Users },
})
Top-level route with explicit operation id:
"/users": POST("createUser", {
req: CreateUser,
res: { 201: User },
})
Nested/shared path behavior:
"/users": scope({
"/:id": scope({
pathParams: {
id: UserID,
},
GET: {
res: { 200: User },
},
DELETE: {
res: { 204: resp({ description: "Deleted" }) },
},
}),
})
Rules:
scope(...) wrappers, including wrappers created only to
attach forEachOp. forEachOp requires multiple operations that genuinely
share its behavior.scope object that directly owns the methods.pathParams, params, forEachOp, and security on nearest
scope that owns them.GET operations can declare headID to pair with HEAD:
"/feed": GET({
id: "getFeed",
headID: "headFeed",
res: { 200: Feed },
})
Rules:
headID belongs on GET.headID when GET and HEAD should stay aligned.HEAD when headers, statuses, or other behavior must diverge.For non-GET operations, request body shorthand is allowed:
req: CreateUser
Expanded request forms:
req: {
body: CreateUser,
}
req: {
"body?": CreateUser,
}
Inline params:
req.queryreq.headersreq.pathParamsRules:
?.forEachPath.Reusable params:
const cursor = () => queryParam(...)const userID = () => pathParam(...)const requestID = () => headerParam(...)Use named(...) only when the exact reusable parameter name cannot be a
TypeScript/JavaScript identifier.
Then reuse with:
req.paramsforEachPath.paramsResponses are status maps:
res: {
200: User,
404: resp({ description: "Not found" }),
}
Detailed response:
res: {
200: resp({
description: "OK",
body: {
"application/json": User,
},
headers: {
"x-request-id": string(),
},
}),
}
Rules:
body can be schema or MIME map.res.defaults for shared headers/mime across status ranges like
"100..599".res.add for default statuses inherited by many operations.headers.responseHeader(...) and
pass those thunks through headerParams. Do not mix named values and
response-header thunks in one API.headers at the narrowest common res.defaults or
operation level. Prefer repeating a small inline header declaration over
converting otherwise valid response-header thunks to named(...).named("header", responseHeader(...)) only as a last resort when the
exact component name and component reuse are both required and inline
placement cannot express the contract. If that last resort is necessary, keep
other response headers inline rather than combining the named header with
reusable response-header thunks.headers map. Never abstract or spread a reusable headers map. Define each
shared header with responseHeader(...) and pass its thunk in headerParams;
keep one-off headers inline in headers.cookies for response cookies.Three main patterns:
req: {
query: {
"cursor?": string(),
},
}
forEachPath: {
params: [CursorParam],
}
pathParams: {
id: UserID,
}
Use nearest shared level that keeps intent obvious.
Security helpers:
headerSecurityquerySecurityhttpSecurityoauth2Securityoauth2RequirementsecurityANDsecurityORRules:
security means authenticated request required."security?" means operation may be authenticated or anonymous.forEachOp when most operations need it.Prefer declared tags:
const tags = declareTags({
Users: {},
Admin: {},
} as const)
Then:
Object.values(tags) into partialDoc.tagstags: [tags.Users] on operationsGood *.responsible.ts files usually:
forEachOp or nearest scopeUse these as source-of-truth patterns:
http-benchmark.ts:
compact request/response defaults, schema thunksexceptions.ts:
scoped path params, inherited JSON defaultslistenbox.ts:
nested scopes, optional security, cookies, HEADyoutube.ts:
forEachPath.params, OAuth2 requirements, large named schema graphpachca.ts:
large API surface, inline params, raw OpenAPI 3.1 escape hatches