New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

typera-openapi

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

typera-openapi - npm Package Compare versions

Comparing version 1.0.3 to 2.0.0

dist/components.d.ts

1

dist/cli.d.ts

@@ -1,2 +0,1 @@

#!/usr/bin/env node
export {};

@@ -1,2 +0,1 @@

#!/usr/bin/env node
"use strict";

@@ -27,6 +26,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

})
.option('format', {
description: 'Output file format',
choices: ['ts', 'json'],
default: 'ts',
.option('outfile', {
alias: 'o',
description: 'Output file. Must end in `.ts` or `.json`.',
default: 'openapi.ts',
coerce: (arg) => {
if (arg.endsWith('.ts'))
return [arg, 'ts'];
if (arg.endsWith('.json'))
return [arg, 'json'];
throw new Error('outfile must end in `.ts` or `.json`');
},
})

@@ -39,3 +45,3 @@ .option('tsconfig', {

alias: 'p',
description: 'Apply prettier to output files',
description: 'Apply prettier to the output file',
type: 'boolean',

@@ -46,11 +52,14 @@ default: false,

alias: 'c',
description: 'Exit with an error if output files are not up-to-date (useful for CI)',
description: 'Exit with an error if the output file is not up-to-date (useful for CI)',
type: 'boolean',
default: false,
}).argv;
const outputFileName = (sourceFileName, ext) => sourceFileName.slice(0, -path.extname(sourceFileName).length) + ext;
const main = () => __awaiter(void 0, void 0, void 0, function* () {
const args = parseArgs();
const sourceFiles = args._.map((x) => path.resolve(x.toString()));
const ext = `.openapi.${args.format}`;
const sourceFiles = args._.map((x) => x.toString());
if (sourceFiles.length === 0) {
console.error('error: No source files given');
return 1;
}
const [outfile, format] = args.outfile;
const compilerOptions = readCompilerOptions(args.tsconfig);

@@ -62,22 +71,21 @@ if (!compilerOptions)

}
const results = _1.generate(sourceFiles, compilerOptions, {
const { output, unseenFileNames } = (0, _1.generate)(sourceFiles, compilerOptions, {
log,
}).map((result) => (Object.assign(Object.assign({}, result), { outputFileName: outputFileName(result.fileName, ext) })));
let success = true;
for (const { outputFileName, paths } of results) {
let content = args.format === 'ts' ? tsString(paths) : jsonString(paths);
if (args.prettify) {
content = yield prettify_1.runPrettier(outputFileName, content);
}
if (args.check) {
if (!checkOutput(outputFileName, content))
success = false;
}
else {
writeOutput(outputFileName, content);
}
});
if (unseenFileNames.length > 0) {
console.error(`error: The following files don't exist or didn't contain any routes: ${unseenFileNames.join(', ')}`);
return 1;
}
if (!success) {
process.exit(1);
let content = format === 'ts' ? tsString(output) : jsonString(output);
if (args.prettify) {
content = yield (0, prettify_1.runPrettier)(outfile, content);
}
if (args.check) {
if (!checkOutput(outfile, content))
return 1;
}
else {
writeOutput(outfile, content);
}
return 0;
});

@@ -114,10 +122,13 @@ const readCompilerOptions = (tsconfigPath) => {

};
const tsString = (paths) => `\
const tsString = (result) => `\
import { OpenAPIV3 } from 'openapi-types'
const spec: { paths: OpenAPIV3.PathsObject } = ${JSON.stringify({ paths })};
const spec: { paths: OpenAPIV3.PathsObject, components: OpenAPIV3.ComponentsObject } = ${JSON.stringify(result)};
export default spec;
`;
const jsonString = (paths) => JSON.stringify({ paths });
main();
const jsonString = (result) => JSON.stringify(result);
main().then((status) => {
if (status !== 0)
process.exit(status);
});

@@ -7,7 +7,11 @@ import * as ts from 'typescript';

}
interface Result {
fileName: string;
export interface GenerateOutput {
paths: OpenAPIV3.PathsObject;
components: OpenAPIV3.ComponentsObject;
}
export declare const generate: (fileNames: string[], compilerOptions: ts.CompilerOptions, options?: GenerateOptions | undefined) => Result[];
export interface GenerateResult {
output: GenerateOutput;
unseenFileNames: string[];
}
export declare const generate: (fileNames: string[], compilerOptions: ts.CompilerOptions, options?: GenerateOptions | undefined) => GenerateResult;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = void 0;
const path = require("path");
const ts = require("typescript");

@@ -8,2 +9,3 @@ const statuses = require("statuses");

const utils_1 = require("./utils");
const components_1 = require("./components");
const generate = (fileNames, compilerOptions, options) => {

@@ -13,20 +15,34 @@ const log = (options === null || options === void 0 ? void 0 : options.log) || (() => undefined);

const checker = program.getTypeChecker();
const result = [];
const seenFileNames = new Set();
const components = new components_1.Components();
let paths = {};
for (const sourceFile of program.getSourceFiles()) {
if (!fileNames.includes(sourceFile.fileName))
continue;
if (sourceFile.isDeclarationFile)
continue;
const foundFile = fileNames.find((fileName) => isSameFile(fileName, sourceFile.fileName));
if (foundFile === undefined)
continue;
let containsRoutes = false;
ts.forEachChild(sourceFile, (node) => {
const paths = visitTopLevelNode(context_1.context(checker, sourceFile, log, node), node);
if (paths) {
result.push({ fileName: sourceFile.fileName, paths });
const newPaths = visitTopLevelNode((0, context_1.context)(checker, sourceFile, log, node), components, node);
if (newPaths) {
// TODO: What if a route is defined multiple times?
paths = Object.assign(Object.assign({}, paths), newPaths);
containsRoutes = true;
}
});
if (containsRoutes) {
seenFileNames.add(foundFile);
}
}
return result;
return {
output: { paths, components: components.build() },
unseenFileNames: fileNames.filter((fileName) => !seenFileNames.has(fileName)),
};
};
exports.generate = generate;
const visitTopLevelNode = (ctx, node) => {
const isSameFile = (a, b) => path.resolve(a) === path.resolve(b);
const visitTopLevelNode = (ctx, components, node) => {
if (ts.isExportAssignment(node) && !node.isExportEquals) {
const prefix = getRouterPrefix(node);
// 'export default' statement

@@ -47,8 +63,9 @@ const argSymbols = getRouterCallArgSymbols(ctx, node.expression);

}
const routeDeclaration = getRouteDeclaration(context_1.withLocation(ctx, location), symbol);
const routeDeclaration = getRouteDeclaration((0, context_1.withLocation)(ctx, location), components, symbol);
if (routeDeclaration) {
const [path, method, operation] = routeDeclaration;
const pathsItemObject = paths[path];
const prefixedPath = prefix + path;
const pathsItemObject = paths[prefixedPath];
if (!pathsItemObject) {
paths[path] = { [method]: operation };
paths[prefixedPath] = { [method]: operation };
}

@@ -79,3 +96,3 @@ else {

};
const getRouteDeclaration = (ctx, symbol) => {
const getRouteDeclaration = (ctx, components, symbol) => {
const description = getDescriptionFromComment(ctx, symbol);

@@ -89,8 +106,11 @@ const summary = getRouteSummary(symbol);

}
const { method, path, requestNode, body, query, headers, routeParams, cookies, } = routeInput;
const responses = getResponseTypes(ctx, symbol);
const { method, path, requestNode, body, query, headers, routeParams, cookies, contentType, } = routeInput;
const contentTypeString = contentType && (0, utils_1.isStringLiteralType)(contentType)
? ctx.checker.typeToString(contentType).replace(/"/g, '')
: undefined;
const responses = getResponseTypes(ctx, components, symbol);
if (!responses)
return;
const requestBody = requestNode && body
? typeToSchema(context_1.withLocation(ctx, requestNode), body)
? typeToSchema((0, context_1.withLocation)(ctx, requestNode), components, body)
: undefined;

@@ -107,3 +127,3 @@ const parameters = [

method,
Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (summary ? { summary } : undefined)), (description ? { description } : undefined)), (tags && tags.length > 0 ? { tags } : undefined)), (parameters.length > 0 ? { parameters } : undefined)), operationRequestBody(requestBody)), { responses }),
Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (summary ? { summary } : undefined)), (description ? { description } : undefined)), (tags && tags.length > 0 ? { tags } : undefined)), (parameters.length > 0 ? { parameters } : undefined)), operationRequestBody(requestBody, contentTypeString)), { responses }),
];

@@ -128,7 +148,14 @@ };

.map((tag) => tag.trim());
const operationRequestBody = (contentSchema) => {
const getRouterPrefix = (node) => {
var _a;
return (_a = ts
.getJSDocTags(node)
.filter((tag) => tag.tagName.escapedText === 'prefix')
.flatMap((tag) => typeof tag.comment === 'string' ? [tag.comment] : [])[0]) !== null && _a !== void 0 ? _a : '';
};
const operationRequestBody = (contentSchema, contentType = 'application/json') => {
if (!contentSchema)
return;
return {
requestBody: { content: { 'application/json': { schema: contentSchema } } },
requestBody: { content: { [contentType]: { schema: contentSchema } } },
};

@@ -157,3 +184,3 @@ };

return;
let method, path, requestNode, body, query, routeParams, headers, cookies;
let method, path, requestNode, body, query, routeParams, headers, cookies, contentType;
while (ts.isCallExpression(expr)) {

@@ -234,3 +261,3 @@ const lhs = expr.expression;

const reqType = routeHandlerParamTypes[0];
[body, query, headers, routeParams, cookies] = [
[body, query, headers, routeParams, cookies, contentType] = [
'body',

@@ -241,3 +268,4 @@ 'query',

'cookies',
].map((property) => utils_1.getPropertyType(ctx.checker, routeConstructor, reqType, property));
'contentType',
].map((property) => (0, utils_1.getPropertyType)(ctx.checker, routeConstructor, reqType, property));
requestNode = routeConstructor;

@@ -268,5 +296,6 @@ }

cookies,
contentType,
};
};
const getResponseTypes = (ctx, symbol) => {
const getResponseTypes = (ctx, components, symbol) => {
const descriptions = getResponseDescriptions(symbol);

@@ -297,3 +326,3 @@ const location = symbol.valueDeclaration;

// returnType is a Promise
const responseType = utils_1.getPromisePayloadType(context_1.withLocation(ctx, location), returnTypes[0]);
const responseType = (0, utils_1.getPromisePayloadType)((0, context_1.withLocation)(ctx, location), returnTypes[0]);
if (!responseType) {

@@ -304,4 +333,4 @@ ctx.log('warn', 'Not a valid route: routeHandler does not return a promise');

const result = {};
if (utils_1.isObjectType(responseType)) {
const responseDef = getResponseDefinition(ctx, descriptions, responseType);
if ((0, utils_1.isObjectType)(responseType)) {
const responseDef = getResponseDefinition(ctx, components, descriptions, responseType);
if (responseDef)

@@ -312,3 +341,3 @@ result[responseDef.status] = responseDef.response;

responseType.types.forEach((type) => {
const responseDef = getResponseDefinition(ctx, descriptions, type);
const responseDef = getResponseDefinition(ctx, components, descriptions, type);
if (responseDef)

@@ -337,3 +366,3 @@ result[responseDef.status] = responseDef.response;

.filter(utils_1.isDefined));
const getResponseDefinition = (ctx, responseDescriptions, responseType) => {
const getResponseDefinition = (ctx, components, responseDescriptions, responseType) => {
const statusSymbol = responseType.getProperty('status');

@@ -352,15 +381,13 @@ const bodySymbol = responseType.getProperty('body');

const status = ctx.checker.typeToString(statusType);
if (!utils_1.isNumberLiteralType(statusType)) {
if (!(0, utils_1.isNumberLiteralType)(statusType)) {
ctx.log('warn', `Status code is not a number literal: ${status}`);
return;
}
// TODO: If bodyType is an interface (or type alias?), generate a schema
// component object and a reference to it?
let bodySchema;
if (!utils_1.isUndefinedType(bodyType)) {
bodySchema = typeToSchema(ctx, bodyType);
if (!(0, utils_1.isUndefinedType)(bodyType)) {
bodySchema = typeToSchema(ctx, components, bodyType);
if (!bodySchema)
return;
}
const headers = !utils_1.isUndefinedType(headersType)
const headers = !(0, utils_1.isUndefinedType)(headersType)
? typeToHeaders(ctx, headersType)

@@ -380,5 +407,5 @@ : undefined;

? {
content: utils_1.isStringType(bodyType) || utils_1.isNumberType(bodyType)
content: (0, utils_1.isStringType)(bodyType) || (0, utils_1.isNumberType)(bodyType)
? { 'text/plain': { schema: bodySchema } }
: utils_1.isBufferType(bodyType)
: (0, utils_1.isBufferType)(bodyType)
? { 'application/octet-stream': { schema: bodySchema } }

@@ -400,3 +427,3 @@ : { 'application/json': { schema: bodySchema } },

const description = getDescriptionFromComment(ctx, prop);
return Object.assign({ name: prop.name, in: in_, required: in_ === 'path' ? true : !utils_1.isOptional(prop) }, (description ? { description } : undefined));
return Object.assign({ name: prop.name, in: in_, required: in_ === 'path' ? true : !(0, utils_1.isOptional)(prop) }, (description ? { description } : undefined));
});

@@ -409,3 +436,3 @@ };

result[prop.name] = {
required: !utils_1.isOptional(prop),
required: !(0, utils_1.isOptional)(prop),
};

@@ -419,107 +446,117 @@ });

};
const typeToSchema = (ctx, type, options = {}) => {
let base = getBaseSchema(ctx, options.symbol);
if (type.isUnion()) {
let elems = type.types;
if (options.optional) {
elems = type.types.filter((elem) => !utils_1.isUndefinedType(elem));
const typeToSchema = (ctx, components, type, options = {}) => {
var _a;
return components.withSymbol((_a = type.aliasSymbol) !== null && _a !== void 0 ? _a : type.getSymbol(), (addComponent) => {
let base = getBaseSchema(ctx, options.propSymbol);
if (type.isUnion()) {
let elems = type.types;
if (options.optional) {
elems = type.types.filter((elem) => !(0, utils_1.isUndefinedType)(elem));
}
if (elems.some(utils_1.isNullType)) {
// One of the union elements is null
base = Object.assign(Object.assign({}, base), { nullable: true });
elems = elems.filter((elem) => !(0, utils_1.isNullType)(elem));
}
if (elems.every(utils_1.isBooleanLiteralType)) {
// All elements are boolean literals => boolean
return Object.assign({ type: 'boolean' }, base);
}
if (elems.every(utils_1.isNumberLiteralType)) {
// All elements are number literals => enum
addComponent();
return Object.assign({ type: 'number', enum: elems.map((elem) => elem.value) }, base);
}
else if (elems.every(utils_1.isStringLiteralType)) {
// All elements are string literals => enum
addComponent();
return Object.assign({ type: 'string', enum: elems.map((elem) => elem.value) }, base);
}
else if (elems.length >= 2) {
// 2 or more types remain => anyOf
addComponent();
return Object.assign({ anyOf: elems
.map((elem) => typeToSchema(ctx, components, elem))
.filter(utils_1.isDefined) }, base);
}
else {
// Only one element left in the union. Fall through and consider it as the
// sole type.
type = elems[0];
}
}
if (elems.some(utils_1.isNullType)) {
// One of the union elements is null
base = Object.assign(Object.assign({}, base), { nullable: true });
elems = elems.filter((elem) => !utils_1.isNullType(elem));
if ((0, utils_1.isArrayType)(type)) {
const elemType = type.getNumberIndexType();
if (!elemType) {
ctx.log('warn', 'Could not get array element type');
return;
}
const elemSchema = typeToSchema(ctx, components, elemType);
if (!elemSchema)
return;
return Object.assign({ type: 'array', items: elemSchema }, base);
}
if (elems.every(utils_1.isBooleanLiteralType)) {
// All elements are boolean literals => boolean
return Object.assign({ type: 'boolean' }, base);
if ((0, utils_1.isDateType)(type)) {
// TODO: dates are always represented as date-time strings. It should be
// possible to override this.
return Object.assign({ type: 'string', format: 'date-time' }, base);
}
else if (elems.every(utils_1.isNumberLiteralType)) {
// All elements are number literals => enum
return Object.assign({ type: 'number', enum: elems.map((elem) => elem.value) }, base);
if ((0, utils_1.isBufferType)(type)) {
return Object.assign({ type: 'string', format: 'binary' }, base);
}
else if (elems.every(utils_1.isStringLiteralType)) {
// All elements are string literals => enum
return Object.assign({ type: 'string', enum: elems.map((elem) => elem.value) }, base);
if ((0, utils_1.isObjectType)(type) ||
(type.isIntersection() &&
type.types.every((part) => (0, utils_1.isObjectType)(part)))) {
addComponent();
const props = ctx.checker.getPropertiesOfType(type);
return Object.assign(Object.assign({ type: 'object', required: props
.filter((prop) => !(0, utils_1.isOptional)(prop))
.map((prop) => prop.name) }, base), { properties: Object.fromEntries(props
.map((prop) => {
const propType = ctx.checker.getTypeOfSymbolAtLocation(prop, ctx.location);
if (!propType) {
ctx.log('warn', 'Could not get type for property', prop.name);
return;
}
const propSchema = typeToSchema(ctx, components, propType, {
propSymbol: prop,
optional: (0, utils_1.isOptional)(prop),
});
if (!propSchema) {
ctx.log('warn', 'Could not get schema for property', prop.name);
return;
}
return [prop.name, propSchema];
})
.filter(utils_1.isDefined)) });
}
else if (elems.length >= 2) {
// 2 or more types remain => anyOf
return Object.assign({ anyOf: elems.map((elem) => typeToSchema(ctx, elem)).filter(utils_1.isDefined) }, base);
if ((0, utils_1.isStringType)(type)) {
return Object.assign({ type: 'string' }, base);
}
else {
// Only one element left in the union. Fall through and consider it as the
// sole type.
type = elems[0];
if ((0, utils_1.isNumberType)(type)) {
return Object.assign({ type: 'number' }, base);
}
}
if (utils_1.isArrayType(type)) {
const elemType = type.getNumberIndexType();
if (!elemType) {
ctx.log('warn', 'Could not get array element type');
return;
if ((0, utils_1.isBooleanType)(type)) {
return Object.assign({ type: 'boolean' }, base);
}
const elemSchema = typeToSchema(ctx, elemType);
if (!elemSchema)
return;
return Object.assign({ type: 'array', items: elemSchema }, base);
}
if (utils_1.isDateType(type)) {
// TODO: dates are always represented as date-time strings. It should be
// possible to override this.
return Object.assign({ type: 'string', format: 'date-time' }, base);
}
if (utils_1.isBufferType(type)) {
return Object.assign({ type: 'string', format: 'binary' }, base);
}
if (utils_1.isObjectType(type) ||
(type.isIntersection() && type.types.every((part) => utils_1.isObjectType(part)))) {
const props = ctx.checker.getPropertiesOfType(type);
return Object.assign(Object.assign({ type: 'object', required: props
.filter((prop) => !utils_1.isOptional(prop))
.map((prop) => prop.name) }, base), { properties: Object.fromEntries(props
.map((prop) => {
const propType = ctx.checker.getTypeOfSymbolAtLocation(prop, ctx.location);
if (!propType) {
ctx.log('warn', 'Could not get type for property', prop.name);
return;
}
const propSchema = typeToSchema(ctx, propType, {
symbol: prop,
optional: utils_1.isOptional(prop),
});
if (!propSchema) {
ctx.log('warn', 'Could not get schema for property', prop.name);
return;
}
return [prop.name, propSchema];
})
.filter(utils_1.isDefined)) });
}
if (utils_1.isStringType(type)) {
return Object.assign({ type: 'string' }, base);
}
if (utils_1.isNumberType(type)) {
return Object.assign({ type: 'number' }, base);
}
if (utils_1.isBooleanType(type)) {
return Object.assign({ type: 'boolean' }, base);
}
if (utils_1.isStringLiteralType(type)) {
return Object.assign({ type: 'string', enum: [type.value] }, base);
}
if (utils_1.isNumberLiteralType(type)) {
return Object.assign({ type: 'number', enum: [type.value] }, base);
}
const branded = utils_1.getBrandedType(ctx, type);
if (branded) {
// io-ts branded type
const { brandName, brandedType } = branded;
if (brandName === 'Brand<IntBrand>') {
// io-ts Int
return Object.assign({ type: 'integer' }, base);
if ((0, utils_1.isStringLiteralType)(type)) {
return Object.assign({ type: 'string', enum: [type.value] }, base);
}
// other branded type
return typeToSchema(ctx, brandedType, options);
}
ctx.log('warn', `Ignoring an unknown type: ${ctx.checker.typeToString(type)}`);
return;
if ((0, utils_1.isNumberLiteralType)(type)) {
return Object.assign({ type: 'number', enum: [type.value] }, base);
}
const branded = (0, utils_1.getBrandedType)(ctx, type);
if (branded) {
// io-ts branded type
const { brandName, brandedType } = branded;
if (brandName === 'Brand<IntBrand>') {
// io-ts Int
return Object.assign({ type: 'integer' }, base);
}
// other branded type
return typeToSchema(ctx, components, brandedType, options);
}
ctx.log('warn', `Ignoring an unknown type: ${ctx.checker.typeToString(type)}`);
return;
});
};

@@ -1,4 +0,2 @@

export { generate } from './generate';
export { GenerateResult, GenerateOutput, generate } from './generate';
export { LogLevel } from './context';
import { OpenAPIV3 } from 'openapi-types';
export declare const prefix: (prefix: string, paths: OpenAPIV3.PathsObject) => OpenAPIV3.PathsObject;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.prefix = exports.generate = void 0;
exports.generate = void 0;
var generate_1 = require("./generate");
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return generate_1.generate; } });
const prefix = (prefix, paths) => Object.fromEntries(Object.entries(paths).map(([path, value]) => [prefix + path, value]));
exports.prefix = prefix;

@@ -15,2 +15,4 @@ import * as ts from 'typescript';

export declare const isNullType: (type: ts.Type) => boolean;
export declare const isInterface: (symbol: ts.Symbol) => boolean;
export declare const isTypeAlias: (symbol: ts.Symbol) => boolean;
export declare const isDateType: (type: ts.Type) => boolean;

@@ -17,0 +19,0 @@ export declare const isBufferType: (type: ts.Type) => boolean;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPromisePayloadType = exports.getBrandedType = exports.getPropertyType = exports.isBufferType = exports.isDateType = exports.isNullType = exports.isUndefinedType = exports.isStringLiteralType = exports.isNumberLiteralType = exports.isBooleanLiteralType = exports.isBooleanType = exports.isNumberType = exports.isStringType = exports.isObjectType = exports.isArrayType = exports.isOptional = exports.isDefined = void 0;
exports.getPromisePayloadType = exports.getBrandedType = exports.getPropertyType = exports.isBufferType = exports.isDateType = exports.isTypeAlias = exports.isInterface = exports.isNullType = exports.isUndefinedType = exports.isStringLiteralType = exports.isNumberLiteralType = exports.isBooleanLiteralType = exports.isBooleanType = exports.isNumberType = exports.isStringType = exports.isObjectType = exports.isArrayType = exports.isOptional = exports.isDefined = void 0;
const ts = require("typescript");

@@ -29,6 +29,10 @@ const isDefined = (value) => value !== undefined;

exports.isNullType = isNullType;
const isInterface = (symbol) => !!(symbol.flags & ts.SymbolFlags.Interface);
exports.isInterface = isInterface;
const isTypeAlias = (symbol) => !!(symbol.flags & ts.SymbolFlags.TypeAlias);
exports.isTypeAlias = isTypeAlias;
// Check for a specific object type based on type name and property names
const duckTypeChecker = (name, properties) => (type) => {
const symbol = type.symbol;
return (exports.isObjectType(type) &&
return ((0, exports.isObjectType)(type) &&
symbol.escapedName === name &&

@@ -86,3 +90,3 @@ // If it walks like a duck and it quacks like a duck, then it must be a duck

const onResolvedType = thenParamType.isUnion()
? thenParamType.types.find((t) => !exports.isUndefinedType(t) && !exports.isNullType(t))
? thenParamType.types.find((t) => !(0, exports.isUndefinedType)(t) && !(0, exports.isNullType)(t))
: thenParamType;

@@ -89,0 +93,0 @@ if (!onResolvedType)

{
"name": "typera-openapi",
"version": "1.0.3",
"version": "2.0.0",
"description": "Generate OpenAPI spec from typera routes",

@@ -17,3 +17,3 @@ "repository": "https://github.com/akheron/typera-openapi",

"scripts": {
"build": "tsc",
"build": "tsc -p tsconfig.build.json",
"clean": "rm -rf dist",

@@ -27,3 +27,3 @@ "lint": "eslint --max-warnings 0 '**/*.ts' && prettier --check \"**/*.{json,md}\"",

"dependencies": {
"openapi-types": "^9.0.0",
"openapi-types": "^10.0.0",
"statuses": "^2.0.1",

@@ -34,3 +34,3 @@ "typescript": ">=4.3.0",

"devDependencies": {
"@types/jest": "^26.0.20",
"@types/jest": "^27.0.1",
"@types/node": "*",

@@ -43,3 +43,3 @@ "@types/statuses": "^2.0.0",

"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-prettier": "^4.0.0",
"io-ts": "^2.2.13",

@@ -46,0 +46,0 @@ "io-ts-types": "^0.5.12",

@@ -8,2 +8,4 @@ # typera-openapi - OpenAPI generator for typera

Upgrading to v2? See the [upgrading instructions](docs/upgrading.md).
<!-- START doctoc generated TOC please keep comment here to allow auto update -->

@@ -18,2 +20,3 @@ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [The `Date` type](#the-date-type)
- [Reference](#reference)
- [Releasing](#releasing)

@@ -64,3 +67,8 @@

/**
* @prefix /api
*/
export default router(myRoute, ...)
// The optional @prefix JSDoc tag prepends the prefix to all route paths.
```

@@ -88,3 +96,3 @@

import myRoutes from './my-routes'
import myRouteDefs from './my-routes.openapi'
import openapi from './openapi'

@@ -97,5 +105,3 @@ const openapiDoc: OpenAPIV3.Document = {

},
paths: {
...prefix('/api', myRouteDefs.paths),
},
...openapi,
}

@@ -109,5 +115,2 @@

The `prefix` function is used to move OpenAPI path definitions to a different
prefix, because the `myRoutes` are served from the `/api` prefix.
## CLI

@@ -121,10 +124,7 @@

For each input file `file.ts`, writes a `file.openapi.ts` or
`file.openapi.json`, depending on `--format`.
Options:
`--format`
`-o OUTFILE`, `--outfile OUTFILE`
Output file format. Either `ts` or `json`. Default: `ts`.
Output file name. Must end in either `.ts` or `.json`.

@@ -137,4 +137,4 @@ `--prettify`, `-p`

Check that generated files are up-to-date without actually generating them. If
any file is outdated, print an error and exit with status 1. Useful for CI.
Check that the output file is up-to-date without actually writing it. If the
file is outdated, print an error and exit with status 1. Useful for CI.

@@ -219,2 +219,66 @@ ## How it works?

## Reference
Terms:
- Route means an OpenAPI operation, i.e. an endpoint you can request.
- `route` is the typera object that lets you create routes, see
[the docs](https://akheron.github.io/typera/apiref/#route).
- Route handler is the function passed to `.handler()` when defining a route
- `request` is the sole parameter of the route handler function.
- Route type is the type of the route variable returned by `route.get()` etc.
For each route, typera-openapi determines the following information:
| Information | Source |
| ------------ | -------------------------------------------------- |
| method | Which `route` method is called, e.g. `route.get()` |
| path | The parameter of e.g. `route.get()` |
| summary | JSDoc comment's `@summary` tag |
| description | JSDoc comment's text |
| tags | JSDoc comment's `@tags` |
| parameters | See table below |
| request body | See table below |
| responses | See table below |
Additionally, if the parent `router()` call has a `@prefix` tag in the JSDoc
comment, it's prepended to the path of each of the routes.
OpenAPI parameters covers all the other input expect the request body:
| Parameter | Source |
| --------- | ---------------------------------------------------------------------------------------------- |
| path | [Route parameter captures](https://akheron.github.io/typera/apiref/#route-parameter-capturing) |
| query | The type of `request.query` (the output of `Parser.query` or custom middleware) |
| header | The type of `request.headers` (the output of `Parser.headers` or custom middleware) |
| cookie | The type of `request.cookies` (the output of `Parser.cookies` or custom middleware) |
OpenAPI allows different request body types per content type, but typera-openapi
only allows one.
| Body field | Source |
| ------------ | ----------------------------------------------------------------------------- |
| content type | `request.contentType`, or `'application/json'` if not defined |
| schema | The type of `request.body` (the output of `Parser.body` or custom middleware) |
A route can have multiple responses. These are modeled in the route type as
`Route<Response1 | Response2 | ...>`. See
[the docs](https://akheron.github.io/typera/apiref/#responses). Each response
type adds a response to the OpenAPI document.
| Response field | Source |
| -------------- | ---------------------------------------------- |
| status | Response's `Status` type (number literal type) |
| description | JSDoc comment's `@response` tag |
| content type | See below |
| content schema | Response's `Body` type |
| headers | Response's `Headers` type |
Response's content type is determined as follows:
- `text/plain` if the body type is string or number
- `application/octet-stream` for
[streaming responses](https://akheron.github.io/typera/apiref/#streaming-responses)
- `application/json` otherwise
## Releasing

@@ -221,0 +285,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc