@contract-kit/openapi
v0.1.1
Published
OpenAPI 3.1 generation from contract-kit contracts (requires Zod schemas for JSON Schema conversion)
Maintainers
Readme
@contract-kit/openapi
OpenAPI 3.1 generation from Contract Kit contracts
This package generates OpenAPI 3.1 specifications from your contracts. Currently supports Zod schemas for JSON Schema conversion.
Installation
npm install @contract-kit/openapi @contract-kit/core zodTypeScript Requirements
This package requires TypeScript 5.0 or higher for proper type inference.
Usage
Generating an OpenAPI Spec
import { contractsToOpenAPI } from "@contract-kit/openapi";
import { getTodo, listTodos, createTodo, updateTodo, deleteTodo } from "./contracts/todos";
const spec = contractsToOpenAPI(
[getTodo, listTodos, createTodo, updateTodo, deleteTodo],
{
title: "Todo API",
version: "1.0.0",
description: "A simple todo API built with Contract Kit",
servers: [
{ url: "https://api.example.com", description: "Production" },
{ url: "http://localhost:3000", description: "Development" },
],
}
);
// Write to file
import { writeFileSync } from "fs";
writeFileSync("openapi.json", JSON.stringify(spec, null, 2));Adding OpenAPI Metadata to Contracts
Use the fluent builder methods to add OpenAPI-specific metadata:
import { createContractGroup } from "@contract-kit/core";
import { z } from "zod";
const todos = createContractGroup().namespace("todos");
export const getTodo = todos
.get("/api/todos/:id")
.path(z.object({ id: z.string().describe("Todo ID") }))
.response(200, TodoSchema)
.errors({
404: z.object({
code: z.literal("TODO_NOT_FOUND"),
message: z.string(),
}).describe("Todo not found"),
})
// OpenAPI metadata
.summary("Get a todo by ID")
.description("Retrieves a single todo item by its unique identifier")
.tags("todos")
.operationId("getTodoById")
.externalDocs("https://docs.example.com/todos", "Todo documentation")
.security({ bearerAuth: [] });
export const listTodos = todos
.get("/api/todos")
.query(z.object({
completed: z.boolean().optional().describe("Filter by completion status"),
limit: z.coerce.number().optional().describe("Maximum number of results"),
offset: z.coerce.number().optional().describe("Pagination offset"),
}))
.response(200, z.array(TodoSchema).describe("List of todos"))
.summary("List all todos")
.description("Returns a paginated list of todos with optional filtering")
.tags("todos")
.operationId("listTodos");
export const createTodo = todos
.post("/api/todos")
.body(CreateTodoSchema.describe("Todo creation payload"))
.response(201, TodoSchema.describe("Created todo"))
.summary("Create a new todo")
.tags("todos")
.operationId("createTodo");Security Schemes
Define authentication schemes in the generator options:
const spec = contractsToOpenAPI(contracts, {
title: "Todo API",
version: "1.0.0",
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
description: "JWT authentication",
},
apiKey: {
type: "apiKey",
name: "X-API-Key",
in: "header",
description: "API key authentication",
},
},
// Global security (applied to all operations by default)
security: [{ bearerAuth: [] }],
});Per-Operation Security
Override security for specific operations:
// Public endpoint (no auth)
export const healthCheck = api
.get("/api/health")
.response(200, HealthSchema)
.security({}); // Empty object = no security
// Admin-only endpoint
export const deleteUser = api
.delete("/api/users/:id")
.response(204, z.null())
.security({ bearerAuth: [], apiKey: [] }); // Requires bothTagging Operations
Use tags to group operations in the generated documentation:
// Add tags to individual operations
export const getTodo = todos
.get("/api/todos/:id")
.tags("todos", "read-operations");
// Tags accumulate across the chain
export const createTodo = todos
.post("/api/todos")
.tags("todos")
.tags("write-operations"); // Now has both tagsOpenAPI Builder Methods
| Method | Description |
|--------|-------------|
| .summary(text) | Brief summary of the operation |
| .description(text) | Detailed description |
| .tags(...tags) | Tags for grouping (accumulates) |
| .deprecated(flag) | Mark operation as deprecated |
| .operationId(id) | Custom operation ID (defaults to contract name) |
| .externalDocs(url, description?) | Link to external documentation |
| .security(requirement) | Security requirements (accumulates) |
Generated Schema References
Schemas are automatically extracted and referenced in components/schemas:
{
"openapi": "3.1.0",
"paths": {
"/api/todos/{id}": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/todos_get_response_200" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"todos_get_response_200": {
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"completed": { "type": "boolean" }
}
}
}
}
}API Reference
contractsToOpenAPI(contracts, options)
Converts an array of contracts to an OpenAPI 3.1 document.
function contractsToOpenAPI(
contracts: ContractInput[],
options: OpenAPIGeneratorOptions
): OpenAPIObject;OpenAPIGeneratorOptions
type OpenAPIGeneratorOptions = {
// Required
title: string;
version: string;
// Optional
description?: string;
servers?: OpenAPIServer[];
securitySchemes?: Record<string, OpenAPISecurityScheme>;
security?: Array<Record<string, string[]>>;
jsonMediaType?: string; // default: "application/json"
};OpenAPIServer
type OpenAPIServer = {
url: string;
description?: string;
variables?: Record<string, {
default: string;
enum?: string[];
description?: string;
}>;
};OpenAPISecurityScheme
type OpenAPISecurityScheme =
| { type: "apiKey"; name: string; in: "query" | "header" | "cookie" }
| { type: "http"; scheme: string; bearerFormat?: string }
| { type: "oauth2"; flows: Record<string, unknown> }
| { type: "openIdConnect"; openIdConnectUrl: string };CLI Usage (Coming Soon)
Generate OpenAPI specs from the command line:
# Not yet implemented
npx contract-kit openapi --contracts ./contracts --output ./openapi.jsonServing the Spec
With Next.js
// app/api/openapi/route.ts
import { contractsToOpenAPI } from "@contract-kit/openapi";
import { allContracts } from "@/contracts";
const spec = contractsToOpenAPI(allContracts, {
title: "My API",
version: "1.0.0",
});
export function GET() {
return Response.json(spec);
}With Swagger UI
// app/api/docs/route.ts
export function GET() {
const html = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: "/api/openapi",
dom_id: "#swagger-ui",
});
</script>
</body>
</html>
`;
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
}Limitations
- Currently requires Zod schemas (uses
zod-to-json-schemafor conversion) - Other Standard Schema libraries will be supported in future versions
Related Packages
@contract-kit/core- Core contract definitions@contract-kit/client- HTTP client@contract-kit/next- Next.js adapter@contract-kit/server- Hexagonal framework
License
MIT
