diff --git a/website/docs/reference/openapi-generator/cli.md b/website/docs/reference/openapi-generator/cli.md new file mode 100644 index 00000000..26041f17 --- /dev/null +++ b/website/docs/reference/openapi-generator/cli.md @@ -0,0 +1,53 @@ +--- +sidebar_position: 2 +--- + +# Command-line Interface + +## Overview + +The `openapi-generator` CLI tool converts your `@api-ts/io-ts-http` `apiSpec` definition +into an OpenAPI 3.0 specification. When you run this tool, it reads a TypeScript file +containing your API definition and outputs the OpenAPI specification to stdout. + +## Usage Syntax + +```shell +openapi-generator [OPTIONS] [FLAGS] +``` + +## Arguments + +- ``: (Required) Path to the TypeScript file containing the exported `apiSpec` + definition. + +## Options + +- `--name`, `-n `: Specifies the API name in the generated specification. +- `--version`, `-v `: Specifies the API version in the generated OpenAPI + specification. If an `@version` JSDoc tag is present on the `apiSpec` export, that + value takes precedence. +- `--codec-file`, `-c `: Path to a JavaScript configuration file defining + schemas for custom or external io-ts codecs. See + [Defining custom codec schemas](./configuration#defining-custom-codec-schemas) for + details. + +## Flags + +- `--internal`, `-i`: Includes routes marked with the `@private` JSDoc tag in the + generated output. By default, private routes are excluded. +- `--help`, `-h`: Displays the help message describing arguments, options, and flags. + +## Examples + +You can generate an OpenAPI specification and save it to a file: + +```shell +npx openapi-generator src/index.ts > openapi-spec.json +``` + +You can specify API name, version, and custom codec definitions: + +```shell +npx openapi-generator --name "My Service API" --version "2.1.0" --codec-file ./custom-codecs.js src/api.ts +``` diff --git a/website/docs/reference/openapi-generator/configuration.md b/website/docs/reference/openapi-generator/configuration.md new file mode 100644 index 00000000..670a698e --- /dev/null +++ b/website/docs/reference/openapi-generator/configuration.md @@ -0,0 +1,142 @@ +--- +sidebar_position: 3 +--- + +# Configuration + +## Overview + +You need to configure the generator to: + +- Correctly interpret `io-ts` types from external packages. +- Define OpenAPI schemas for custom `io-ts` codecs that can't be automatically derived + from the Abstract Syntax Tree (AST). + +## Preparing External Types Packages + +To process `io-ts` types imported from other npm packages, ensure the following in the +external package's `package.json`: + +1. Include source code in the published npm bundle by adding the source directory to the + `files` array: + + ```json + // package.json of the external types package + { + "name": "my-external-types", + "version": "1.0.0", + "files": [ + "dist/", + "src/" // Include source code + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "source": "src/index.ts" // Add this field + // ... rest of package.json + } + ``` + +2. Specify the source entry point using the `source` field (for example, + `"source": "src/index.ts"`). + +## Defining Custom Codec Schemas + +For custom `io-ts` codecs (such as those using `new t.Type(...)` or complex types not +directly supported), you must define schemas manually using one of these methods: + +### Method 1: Via `openapi-gen.config.js` (Recommended For Type Authors) + +You can define schemas directly within the package that declares the custom codecs: + +1. Create a file named `openapi-gen.config.js` in the root of the types package. + +2. Update the package's `package.json` to include: + +- The `customCodecFile` field pointing to this file. +- The config file in the `files` array. + + ```json + // package.json of the types package defining custom codecs + { + "name": "my-custom-codec-package", + // ... + "files": [ + "dist/", + "src/", + "openapi-gen.config.js" // Include the config file + ], + "customCodecFile": "openapi-gen.config.js" // Point to the file + // ... + } + ``` + +3. Structure the `openapi-gen.config.js` file as follows: + + ```javascript + // openapi-gen.config.js + module.exports = (E) => { + return { + // Key matches the exported codec name (e.g., export const MyCustomString = ...) + MyCustomString: () => + E.right({ + type: 'string', + format: 'custom-format', + description: 'A custom string type definition', + }), + AnotherCustomType: () => + E.right({ + type: 'object', + properties: { + /* ... */ + }, + }), + // ... other custom codec definitions + }; + }; + ``` + +The exported function receives the `fp-ts/Either` namespace (`E`) as an argument. You +should return an object where: + +- Keys are the exported names of your custom codecs. +- Values are functions that return `E.right()` with an OpenAPI schema object. + +### Method 2: Via `--codec-file` Option (For Consumers) + +You can define schemas in a configuration file within your project and pass the file +path via the `--codec-file` option: + +1. Create a JavaScript file (for example, `custom-codecs.js`). + +2. Structure the file similarly to Method 1, but group definitions by package: + + ```javascript + // custom-codecs.js + module.exports = (E) => { + return { + 'io-ts-bigint': { + // Package name + BigIntFromString: () => E.right({ type: 'string', format: 'bigint' }), + NonZeroBigIntFromString: () => + E.right({ type: 'string', format: 'bigint' /* constraints */ }), + // ... other codecs from 'io-ts-bigint' + }, + 'my-other-custom-package': { + // Another package + SomeType: () => E.right({ type: 'number', format: 'float' }), + }, + // ... other packages + }; + }; + ``` + +In this structure: + +- Keys of the top-level object are package names. +- Values are objects that map codec names to their schema definitions. + +3. Run the generator with the `--codec-file` option: + + ```shell + npx openapi-generator --codec-file ./custom-codecs.js src/index.ts + ``` diff --git a/website/docs/reference/openapi-generator/index.md b/website/docs/reference/openapi-generator/index.md new file mode 100644 index 00000000..bd45766c --- /dev/null +++ b/website/docs/reference/openapi-generator/index.md @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +--- + +# OpenAPI Generator + +This section provides technical reference for the `@api-ts/openapi-generator` +command-line utility. The documentation covers: + +- CLI usage and options. +- Configuration settings. +- Supported `io-ts` primitives. +- JSDoc annotation system. + +You can use this reference to generate OpenAPI 3.0 specifications from your +`@api-ts/io-ts-http` API definitions. diff --git a/website/docs/reference/openapi-generator/jsdoc.md b/website/docs/reference/openapi-generator/jsdoc.md new file mode 100644 index 00000000..6b9ad700 --- /dev/null +++ b/website/docs/reference/openapi-generator/jsdoc.md @@ -0,0 +1,253 @@ +# JSDoc Annotations + +You can use JSDoc comments to enrich the generated OpenAPI specification. This reference +describes the supported annotations for both endpoint definitions and schema +definitions. + +## Endpoint Annotations + +You can add JSDoc comments to variables holding `h.httpRoute` definitions to provide +metadata for the corresponding OpenAPI operation object. + +### Summary + +When you add a JSDoc comment, the first line becomes the `summary` field: + +```typescript +/** + * Retrieve a user by their ID. + */ +const GetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +### Description + +Any subsequent untagged lines following the summary become the `description` field +(newlines preserved): + +```typescript +/** + * Retrieve a user by their ID. + * Provides detailed information about the user, + * including profile and settings. + */ +const GetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +### @operationId + +This tag specifies the `operationId` field. It is required for unique identification: + +```typescript +/** + * @operationId getUserById + */ +const GetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +### @tag + +This tag assigns the endpoint to a specific tag group. It populates the `tags` array +field. You can use multiple `@tag` lines: + +```typescript +/** + * @tag User + * @tag Profile + */ +const GetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +### @private + +This tag marks the endpoint as internal. It adds `x-internal: true` to the operation +object. These routes are omitted by default unless you use the `--internal` flag: + +```typescript +/** + * Admin-only route. + * @private + * @operationId adminGetUser + * @tag Admin + */ +const AdminGetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +### @unstable + +This tag marks the endpoint as unstable or under development. It adds `x-unstable: true` +to the operation object: + +```typescript +/** + * New feature, API may change. + * @unstable + * @operationId betaFeature + * @tag Beta + */ +const BetaRoute = h.httpRoute({ + /* ... */ +}); +``` + +### @example + +This tag provides an example payload. It adds the JSON object to the `example` field: + +```typescript +/** + * @example { "userId": "user-123", "name": "Example User" } + */ +const GetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +### Unknown Tags + +The generator treats any JSDoc tag not listed above as an "unknown tag". It collects +these tags into the `x-unknown-tags` object: + +```typescript +/** + * @version 2 + * @department Billing + */ +const GetUserRoute = h.httpRoute({ + /* ... */ +}); +``` + +In this example, `@version 2` becomes `{ "version": "2" }` and `@department Billing` +becomes `{ "department": "Billing" }` in the `x-unknown-tags` object. + +## Schema Annotations + +When you add JSDoc comments to `io-ts` type definitions or fields within `t.type`, +`t.partial`, etc., they add detail to the corresponding OpenAPI schema object or +property. + +### Description + +When you add JSDoc text directly above a type definition or field, it becomes the +`description` field in the schema: + +```typescript +import * as t from 'io-ts'; + +/** + * Unique identifier for the resource. + */ +const id = t.string; + +/** Represents a user profile. */ +const UserProfile = t.type({ + /** User's primary email address. */ + email: t.string, + id: id, +}); +``` + +### Supported OpenAPI Tags + +You can use the following JSDoc tags, which map directly to standard OpenAPI schema +keywords: + +| JSDoc Tag | OpenAPI Property | Description | +| ------------------------- | ------------------ | ---------------------------------- | +| `@default ` | `default` | Default value | +| `@example ` | `example` | Example value | +| `@minLength ` | `minLength` | Minimum string length | +| `@maxLength ` | `maxLength` | Maximum string length | +| `@pattern ` | `pattern` | Regex pattern string | +| `@minimum ` | `minimum` | Minimum numeric value | +| `@maximum ` | `maximum` | Maximum numeric value | +| `@minItems ` | `minItems` | Minimum array items | +| `@maxItems ` | `maxItems` | Maximum array items | +| `@minProperties ` | `minProperties` | Minimum object properties | +| `@maxProperties ` | `maxProperties` | Maximum object properties | +| `@exclusiveMinimum` | `exclusiveMinimum` | Exclusive minimum flag | +| `@exclusiveMaximum` | `exclusiveMaximum` | Exclusive maximum flag | +| `@multipleOf ` | `multipleOf` | Multiple of value | +| `@uniqueItems` | `uniqueItems` | Unique array items flag | +| `@readOnly` | `readOnly` | Read-only flag | +| `@writeOnly` | `writeOnly` | Write-only flag | +| `@format ` | `format` | Format (e.g., `uuid`, `date-time`) | +| `@title ` | `title` | Schema title | + +### Custom Tags + +| JSDoc Tag | OpenAPI Property | Description | +| ------------- | ------------------ | -------------------------------- | +| `@private` | `x-internal: true` | Marks schema/field as internal | +| `@deprecated` | `deprecated: true` | Marks schema/field as deprecated | + +## Example Schema With Annotations + +```typescript +import * as t from 'io-ts'; + +/** + * @title Detailed Item Schema + * Describes an item with various constraints. + */ +const ItemSchema = t.type({ + /** + * The unique identifier for the item. + * @format uuid + * @readOnly + * @example "f47ac10b-58cc-4372-a567-0e02b2c3d479" + */ + id: t.string, + + /** + * Name of the item. + * @minLength 1 + * @maxLength 100 + * @default "Unnamed Item" + */ + name: t.string, + + /** + * Item quantity in stock. + * @minimum 0 + * @example 25 + */ + quantity: t.number, + + /** + * Tags associated with the item. Must contain unique tags. + * @minItems 1 + * @maxItems 10 + * @uniqueItems + */ + tags: t.array(t.string), + + /** @deprecated Use 'tags' instead. */ + legacyCategory: t.string, + + /** + * Internal tracking code. + * @private + * @pattern ^[A-Z]{3}-[0-9]{4}$ + */ + internalCode: t.string, + + /** + * Price of the item. Must be greater than 0. + * @minimum 0 + * @exclusiveMinimum + */ + price: t.number, +}); +``` diff --git a/website/docs/reference/openapi-generator/support.md b/website/docs/reference/openapi-generator/support.md new file mode 100644 index 00000000..b48f889d --- /dev/null +++ b/website/docs/reference/openapi-generator/support.md @@ -0,0 +1,34 @@ +# Supported `io-ts` Primitives + +When you use the OpenAPI generator, it automatically derives schemas from the following +`io-ts` primitives and combinators: + +- `string` +- `number` +- `bigint` +- `boolean` +- `null` +- `nullType` +- `undefined` +- `unknown` +- `any` +- `array` +- `readonlyArray` +- `object` +- `type` +- `partial` +- `exact` +- `strict` +- `record` +- `union` +- `intersection` +- `literal` +- `keyof` +- `brand` +- `UnknownRecord` +- `void` + +For codecs not built using these primitives, you may need to define schemas manually. +You can see +[Defining custom codec schemas](./configuration#defining-custom-codec-schemas) for +instructions. diff --git a/website/sidebars.js b/website/sidebars.js index 35b149ae..91d742dc 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -64,6 +64,20 @@ const sidebars = { 'reference/io-ts-http/interfaces', ], }, + { + type: 'category', + label: 'openapi-generator', + link: { + type: 'doc', + id: 'reference/openapi-generator/index', + }, + items: [ + 'reference/openapi-generator/cli', + 'reference/openapi-generator/configuration', + 'reference/openapi-generator/support', + 'reference/openapi-generator/jsdoc', + ], + }, ], }, ],