TypeScript to OpenAPI CLI Usage
Use this CLI to generate an OpenAPI document from TypeScript REST route metadata and schema definitions.
Running
To install and run the CLI locally use the following commands:
npm install @twin.org/ts-to-openapi -g
ts-to-openapi
or run directly using NPX:
npx "@twin.org/ts-to-openapi"
Help
⚙️ TWIN TypeScript To OpenAPI v0.0.3-next.15
Usage: ts-to-openapi
Arguments:
config Path to the JSON configuration file.
output-file The JSON file to write the OpenAPI spec.
Options:
-V, --version output the version number
--lang <lang> The language to display the output in. (default: "en")
-h, --help display help for command
Example
ts-to-openapi ./config/openapi.json ./dist/openapi.json
TypeScript to OpenAPI
=====================
Config JSON: config.json
Output API JSON: output.json
Loading Config JSON: config.json
Creating security schemas
Loading Modules: @twin.org/logging-service@next @twin.org/identity-service@next
Reading Package JSON: @twin.org/logging-service
Importing Module: @twin.org/logging-service
Reading Package JSON: @twin.org/identity-service
Importing Module: @twin.org/identity-service
Route loggingEntryCreate POST /logging/
Route loggingListEntries GET /logging/
Route identityCreate POST /identity/
Route identityUpdate PUT /identity/:identity
Route identityGet GET /identity/:identity
Route identitiesList GET /identity/
Generating Schemas
Processing Models //work/node_modules/@twin.org/api-models/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/core/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/core/dist/types/errors/**/*.ts
Processing Models //work/node_modules/@twin.org/entity/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/logging-models/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/services/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/web/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/web/dist/types/errors/**/*.ts
Processing Models //work/node_modules/@twin.org/identity-service/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/api-models/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/core/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/core/dist/types/errors/**/*.ts
Processing Models //work/node_modules/@twin.org/crypto/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/entity/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/entity-storage-connector-memory/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/entity-storage-models/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/identity-connector-entity-storage/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/identity-models/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/services/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/vault-connector-entity-storage/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/vault-models/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/web/dist/types/models/**/*.ts
Processing Models //work/node_modules/@twin.org/web/dist/types/errors/**/*.ts
Finalising Schemas
Writing Output: output.json
The generated output.json should be:
{
"openapi": "3.1.0",
"info": {
"title": "TWIN - Test Endpoints",
"description": "REST API for TWIN - Test Endpoints.",
"version": "1.0.0",
"license": {
"name": "Apache 2.0 License",
"url": "https://opensource.org/licenses/Apache-2.0"
}
},
"servers": [
{
"url": "https://localhost"
}
],
"tags": [],
"paths": {
"/logging": {
"post": {
"operationId": "loggingEntryCreate",
"summary": "Create a log entry",
"tags": ["Logging"],
"security": [
{
"jwtBearerAuthScheme": []
}
],
"responses": {
"200": {
"description": "The rest request ended in created response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreatedResponse"
}
}
},
"headers": {
"location": {
"schema": {
"type": "string"
},
"description": "e.g. c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
}
}
}
},
"requestBody": {
"description": "Create a new log entry.",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LogEntry"
},
"examples": {
"loggingEntryCreateInfoExample": {
"value": {
"level": "info",
"message": "This is an information message",
"source": "source",
"ts": 1715252922273
}
},
"loggingEntryCreateErrorExample": {
"value": {
"level": "info",
"message": "This is an error message",
"source": "source",
"ts": 1715252922273,
"error": {
"name": "GeneralError",
"message": "component.error",
"properties": {
"foo": "bar"
}
}
}
}
}
}
}
}
},
"get": {
"operationId": "loggingListEntries",
"summary": "Get a list of the log entries",
"tags": ["Logging"],
"parameters": [
{
"name": "level",
"description": "The level of the log entries to retrieve.",
"in": "query",
"required": false,
"schema": {},
"example": "info"
},
{
"name": "source",
"description": "The source of the log entries to retrieve.",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "timeStart",
"description": "The start time of the metrics to retrieve as a timestamp in ms.",
"in": "query",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "timeEnd",
"description": "The end time of the metrics to retrieve as a timestamp in ms.",
"in": "query",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "cursor",
"description": "The optional cursor to get next chunk.",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"description": "The maximum number of entities in a page.",
"in": "query",
"required": false,
"schema": {
"type": "number"
}
}
],
"security": [
{
"jwtBearerAuthScheme": []
}
],
"responses": {
"200": {
"description": "Response for log entry list request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoggingListResponse"
},
"examples": {
"listResponseExample": {
"value": {
"entities": [
{
"level": "info",
"message": "This is an information message",
"source": "source",
"ts": 1715252922273
}
],
"cursor": "1",
"pageSize": 10,
"totalEntities": 20
}
}
}
}
}
}
}
}
},
"/identity": {
"post": {
"operationId": "identityCreate",
"summary": "Create a new identity",
"tags": ["Identity"],
"security": [
{
"jwtBearerAuthScheme": []
}
],
"responses": {
"200": {
"description": "The rest request ended in created response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreatedResponse"
}
}
},
"headers": {
"location": {
"schema": {
"type": "string"
},
"description": "e.g. did:twin:0xc57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
}
}
}
},
"requestBody": {
"description": "Create a new identity.",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IdentityCreateRequest"
},
"examples": {
"identityCreateRequestExample": {
"value": {
"role": "user",
"properties": [
{
"key": "email",
"type": "https://schema.org/Text",
},
{
"key": "name",
"type": "https://schema.org/Text",
"value": "John Doe"
}
]
}
}
}
}
}
}
},
"get": {
"operationId": "identitiesList",
"summary": "Get the list of identities based on the provided criteria",
"tags": ["Identity"],
"parameters": [
{
"name": "role",
"description": "The property name to use for lookup.",
"in": "query",
"required": false,
"schema": {},
"example": "user"
},
{
"name": "propertyNames",
"description": "The properties to get for the profile, defaults to all. should be a comma separated list.",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "cursor",
"description": "The cursor for paged requests.",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "pageSize",
"description": "Number of items to return.",
"in": "query",
"required": false,
"schema": {
"type": "number"
}
}
],
"security": [
{
"jwtBearerAuthScheme": []
}
],
"responses": {
"200": {
"description": "Response to get a list of identities.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IdentityListResponse"
},
"examples": {
"identitiesListResponseExample": {
"value": {
"identities": [
{
"identity": "did:twin:0xc57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
"properties": [
{
"key": "email",
"type": "https://schema.org/Text",
}
]
}
],
"cursor": "1",
"pageSize": 10,
"totalEntities": 20
}
}
}
}
}
}
}
}
},
"/identity/{identity}": {
"put": {
"operationId": "identityUpdate",
"summary": "Update an identity",
"tags": ["Identity"],
"parameters": [
{
"name": "identity",
"description": "The identity to update the profile for.",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"style": "simple",
"example": "did:twin:0xc57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
}
],
"security": [
{
"jwtBearerAuthScheme": []
}
],
"responses": {
"200": {
"description": "The resource you tried to access does not exist, see the error field for more details.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResponse"
}
}
}
}
},
"requestBody": {
"description": "Request to update an identity.",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IdentityUpdateRequest"
},
"examples": {
"identityUpdateRequestExample": {
"value": {
"properties": [
{
"key": "email",
"type": "https://schema.org/Text",
},
{
"key": "name",
"type": "https://schema.org/Text",
"value": "John Smith"
}
]
}
}
}
}
}
}
},
"get": {
"operationId": "identityGet",
"summary": "Get the identity details",
"tags": ["Identity"],
"parameters": [
{
"name": "identity",
"description": "The identity to get the profile for.",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"style": "simple",
"example": "did:twin:0xc57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
},
{
"name": "propertyNames",
"description": "The properties to get for the profile, defaults to all. should be a comma separated list.",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"example": "email,name"
}
],
"security": [
{
"jwtBearerAuthScheme": []
}
],
"responses": {
"200": {
"description": "The resource you tried to access does not exist, see the error field for more details.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"LogEntry": {
"type": "object",
"properties": {
"level": {
"$ref": "#/components/schemas/LogLevel"
},
"source": {
"type": "string",
"description": "The source of the log entry."
},
"ts": {
"type": "number",
"description": "The timestamp of the log entry, if left blank will be populated by the connector."
},
"message": {
"type": "string",
"description": "The message."
},
"error": {
"$ref": "#/components/schemas/Error"
},
"data": {
"description": "Optional data for the message."
}
},
"required": ["level", "source", "message"],
"additionalProperties": false,
"description": "Interface describing a log entry."
},
"LogLevel": {
"type": "string",
"enum": ["info", "error", "warn", "trace", "debug"],
"description": "Log level."
},
"Error": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name for the error."
},
"message": {
"type": "string",
"description": "The message for the error."
},
"source": {
"type": "string",
"description": "The source of the error."
},
"properties": {
"type": "object",
"additionalProperties": {},
"description": "Any additional information for the error."
},
"stack": {
"type": "string",
"description": "The stack trace for the error."
},
"inner": {
"$ref": "#/components/schemas/Error"
}
},
"required": ["name", "message"],
"additionalProperties": false,
"description": "Model to describe serialized error."
},
"CreatedResponse": {
"type": "object",
"properties": {
"statusCode": {
"$ref": "#/components/schemas/HttpStatusCodes"
},
"headers": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The location where the resource was created."
}
},
"required": ["location"],
"additionalProperties": false,
"description": "Additional response headers."
}
},
"required": ["statusCode", "headers"],
"additionalProperties": false,
"description": "The rest request ended in created response."
},
"HttpStatusCodes": {
"type": "object",
"additionalProperties": false,
"description": "Standard HTTP status codes."
},
"LoggingListResponse": {
"type": "object",
"properties": {
"entities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogEntry"
},
"description": "The entities, which can be partial if a limited keys list was provided."
},
"cursor": {
"type": "string",
"description": "An optional cursor, when defined can be used to call find to get more entities."
},
"pageSize": {
"type": "number",
"description": "Number of entities to return."
},
"totalEntities": {
"type": "number",
"description": "Total entities length."
}
},
"required": ["entities", "totalEntities"],
"additionalProperties": false,
"description": "The response payload."
},
"IdentityCreateRequest": {
"type": "object",
"properties": {
"role": {
"$ref": "#/components/schemas/IdentityRole"
},
"properties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Property"
},
"description": "Initial properties for the identity."
}
},
"required": ["role"],
"additionalProperties": false,
"description": "The data for the request."
},
"IdentityRole": {
"type": "string",
"enum": ["node", "organization", "user"],
"description": "The roles that an identity can have."
},
"Property": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "The key for the item."
},
"type": {
"type": "string",
"description": "The type for the item."
},
"value": {
"description": "The value for the item."
}
},
"required": ["key", "type", "value"],
"additionalProperties": false,
"description": "Interface describing a property."
},
"IdentityUpdateRequest": {
"type": "object",
"properties": {
"properties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Property"
},
"description": "Properties for the identity."
}
},
"required": ["properties"],
"additionalProperties": false,
"description": "The data for the request."
},
"NoContentResponse": {
"type": "object",
"additionalProperties": false,
"description": "The rest request ended in success with no data."
},
"NotFoundResponse": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name for the error."
},
"message": {
"type": "string",
"description": "The message for the error."
},
"source": {
"type": "string",
"description": "The source of the error."
},
"properties": {
"type": "object",
"additionalProperties": {},
"description": "Any additional information for the error."
},
"stack": {
"type": "string",
"description": "The stack trace for the error."
},
"inner": {
"$ref": "#/components/schemas/Error"
},
"notFoundId": {
"type": "string",
"description": "The id if the item that was not found."
}
},
"additionalProperties": false,
"required": ["message", "name"],
"description": "The resource you tried to access does not exist, see the error field for more details."
},
"IdentityGetResponse": {
"type": "object",
"properties": {
"role": {
"$ref": "#/components/schemas/IdentityRole"
},
"properties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Property"
},
"description": "The properties for the identity."
}
},
"required": ["role"],
"additionalProperties": false,
"description": "The response payload."
},
"IdentityListResponse": {
"type": "object",
"properties": {
"identities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"identity": {
"type": "string"
},
"properties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Property"
}
}
},
"required": ["identity"],
"additionalProperties": false
},
"description": "The identities."
},
"cursor": {
"type": "string",
"description": "An optional cursor, when defined can be used to call find to get more entities."
},
"pageSize": {
"type": "number",
"description": "Number of entities to return."
},
"totalEntities": {
"type": "number",
"description": "Total entities length."
}
},
"required": ["identities", "totalEntities"],
"additionalProperties": false,
"description": "The response payload."
}
},
"securitySchemes": {
"jwtBearerAuthScheme": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
}
}
You can use a tool such as https://editor-next.swagger.io/ to validate that the schema is correct.
Programatically
You can also use the package programatically as follows:
import { rm, mkdir } from 'node:fs/promises';
import { CLI, type ITsToOpenApiConfig } from '@twin.org/ts-to-openapi';
const config: ITsToOpenApiConfig = {
title: 'TWIN - Test Endpoints',
version: '1.0.0',
description: 'REST API for TWIN - Test Endpoints.',
licenseName: 'Apache 2.0 License',
licenseUrl: 'https://opensource.org/licenses/Apache-2.0',
servers: ['https://localhost'],
authMethods: ['jwtBearer'],
restRoutes: [
{
package: '@twin.org/logging-service',
version: 'next'
},
{
package: '@twin.org/identity-service',
version: 'next'
}
]
};
// Create a working directory for the processing.
await mkdir('./working');
try {
// Process the config and store in output.json
const cli = new CLI();
await cli.process(config, './output.json', './working');
} catch (err) {
console.debug(err);
} finally {
// Remove the working directory
await rm('./working');
}