Huge News!Announcing our $40M Series B led by Abstract Ventures.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 0.2.0 to 0.3.0

4

dist/cli.js

@@ -17,5 +17,5 @@ #!/usr/bin/env node

const args = parseArgs();
const sourceFiles = args._.map(x => x.toString());
const sourceFiles = args._.map((x) => x.toString());
const ext = args.format === 'ts' ? '.openapi.ts' : '.json';
const results = _1.generate(sourceFiles, { strict: true }, { log }).map(result => (Object.assign(Object.assign({}, result), { outputFileName: outputFileName(result.fileName, ext) })));
const results = _1.generate(sourceFiles, { strict: true }, { log }).map((result) => (Object.assign(Object.assign({}, result), { outputFileName: outputFileName(result.fileName, ext) })));
results.forEach(({ outputFileName, paths }) => {

@@ -22,0 +22,0 @@ const resultObject = JSON.stringify({ paths });

import * as ts from 'typescript';
declare type LogLevel = 'verbose' | 'info' | 'warn' | 'error';
export declare type LogLevel = 'verbose' | 'info' | 'warn' | 'error';
export declare type Logger = (location: string, level: LogLevel, ...messages: any[]) => void;

@@ -12,2 +12,1 @@ export interface Context {

export declare const withLocation: (ctx: Context, location: ts.Node) => Context;
export {};

@@ -17,3 +17,3 @@ "use strict";

continue;
ts.forEachChild(sourceFile, node => {
ts.forEachChild(sourceFile, (node) => {
const paths = visit(context_1.context(checker, sourceFile, log, node), node);

@@ -35,3 +35,3 @@ if (paths) {

const paths = {};
argSymbols.forEach(symbol => {
argSymbols.forEach((symbol) => {
const location = symbol.valueDeclaration;

@@ -57,3 +57,3 @@ const routeDeclaration = getRouteDeclaration(context_1.withLocation(ctx, location), symbol);

.filter(ts.isIdentifier)
.map(arg => ctx.checker.getSymbolAtLocation(arg))
.map((arg) => ctx.checker.getSymbolAtLocation(arg))
.filter(utils_1.isDefined);

@@ -65,2 +65,3 @@ if (argSymbols.length !== args.length)

const getRouteDeclaration = (ctx, symbol) => {
const description = getRouteDescription(ctx, symbol);
const routeInput = getRouteInput(ctx, symbol);

@@ -84,6 +85,10 @@ if (!routeInput)

{
[method]: Object.assign(Object.assign(Object.assign({}, (parameters.length > 0 ? { parameters } : undefined)), operationRequestBody(requestBody)), { responses }),
[method]: Object.assign(Object.assign(Object.assign(Object.assign({}, (description ? { description } : undefined)), (parameters.length > 0 ? { parameters } : undefined)), operationRequestBody(requestBody)), { responses }),
},
];
};
const getRouteDescription = (ctx, symbol) => symbol
.getDocumentationComment(ctx.checker)
.map((part) => part.text)
.join('');
const operationRequestBody = (contentSchema) => {

@@ -188,2 +193,3 @@ if (!contentSchema)

const getResponseTypes = (ctx, symbol) => {
const descriptions = getResponseDescriptions(symbol);
const routeType = ctx.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);

@@ -201,3 +207,3 @@ if (!routeType.aliasSymbol ||

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

@@ -207,4 +213,4 @@ result[responseDef.status] = responseDef.response;

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

@@ -220,3 +226,15 @@ result[responseDef.status] = responseDef.response;

};
const getResponseDefinition = (ctx, responseType) => {
const getResponseDescriptions = (symbol) => Object.fromEntries(symbol
.getJsDocTags()
.filter((tag) => tag.name === 'response')
.map((tag) => tag.text)
.filter(utils_1.isDefined)
.map((text) => {
const match = /(\d{3}) (.+)/.exec(text);
if (!match)
return undefined;
return [match[1], match[2]];
})
.filter(utils_1.isDefined));
const getResponseDefinition = (ctx, descriptions, responseType) => {
const statusSymbol = responseType.getProperty('status');

@@ -247,5 +265,10 @@ const bodySymbol = responseType.getProperty('body');

: undefined;
let description = descriptions[status];
if (!description) {
ctx.log('warn', `No description for response ${status}`);
description = status;
}
return {
status,
response: Object.assign(Object.assign({ description: status }, (bodySchema
response: Object.assign(Object.assign({ description }, (bodySchema
? {

@@ -271,3 +294,3 @@ content: utils_1.isStringType(bodyType) || utils_1.isNumberType(bodyType)

const props = ctx.checker.getPropertiesOfType(type);
return props.map(prop => ({
return props.map((prop) => ({
name: prop.name,

@@ -281,3 +304,3 @@ in: in_,

const props = ctx.checker.getPropertiesOfType(type);
props.forEach(prop => {
props.forEach((prop) => {
result[prop.name] = {

@@ -294,3 +317,3 @@ required: !utils_1.isOptional(prop),

if (optional) {
elems = type.types.filter(elem => !utils_1.isUndefinedType(elem));
elems = type.types.filter((elem) => !utils_1.isUndefinedType(elem));
}

@@ -300,3 +323,3 @@ if (elems.some(utils_1.isNullType)) {

nullable = { nullable: true };
elems = elems.filter(elem => !utils_1.isNullType(elem));
elems = elems.filter((elem) => !utils_1.isNullType(elem));
}

@@ -309,11 +332,11 @@ if (elems.every(utils_1.isBooleanLiteralType)) {

// All elements are number literals => enum
return Object.assign({ type: 'number', enum: elems.map(elem => elem.value) }, nullable);
return Object.assign({ type: 'number', enum: elems.map((elem) => elem.value) }, nullable);
}
else if (elems.every(utils_1.isStringLiteralType)) {
// All elements are string literals => enum
return Object.assign({ type: 'string', enum: elems.map(elem => elem.value) }, nullable);
return Object.assign({ type: 'string', enum: elems.map((elem) => elem.value) }, nullable);
}
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) }, nullable);
return Object.assign({ anyOf: elems.map((elem) => typeToSchema(ctx, elem)).filter(utils_1.isDefined) }, nullable);
}

@@ -327,6 +350,8 @@ else {

if (utils_1.isObjectType(type) ||
(type.isIntersection() && type.types.every(part => utils_1.isObjectType(part)))) {
(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) }, nullable), { properties: Object.fromEntries(props
.map(prop => {
return Object.assign(Object.assign({ type: 'object', required: props
.filter((prop) => !utils_1.isOptional(prop))
.map((prop) => prop.name) }, nullable), { properties: Object.fromEntries(props
.map((prop) => {
const propType = ctx.checker.getTypeOfSymbolAtLocation(prop, ctx.location);

@@ -333,0 +358,0 @@ if (!propType) {

export { generate } from './generate';
export { LogLevel } from './context';
import { OpenAPIV3 } from 'openapi-types';
export declare const prefix: (prefix: string, paths: OpenAPIV3.PathsObject) => OpenAPIV3.PathsObject;
{
"name": "typera-openapi",
"version": "0.2.0",
"version": "0.3.0",
"description": "Generate OpenAPI spec from typera routes",

@@ -18,3 +18,4 @@ "main": "index.js",

"build": "tsc",
"lint": "eslint '**/*.ts'",
"lint": "eslint --max-warnings 0 '**/*.ts' && prettier --check \"**/*.{json,md}\"",
"lint:fix": "eslint --fix '**/*.ts' && prettier --write '**/*.{json,md}'",
"test": "jest",

@@ -41,4 +42,4 @@ "prepublishOnly": "yarn build"

"ts-jest": "^26.4.4",
"typera-express": "2.0.0-alpha.2"
"typera-express": "2.0.0"
}
}

@@ -16,4 +16,4 @@ # typera-openapi - typera to OpenAPI generator

Your route files must have a single default export that exports a typera router,
like this:
Your route files must have a single default export that exports a typera router.
JSDoc comments serve as additional documentation:

@@ -23,3 +23,12 @@ ```typescript

const myRoute: Route<...> = route.get(...).handler(...)
/**
* The JSDoc text is used as a description for the route (optional).
*
* @response 200 Success response description.
* @response 400 Another description for a response. This one
* spans multile lines.
*/
const myRoute: Route<Response.Ok<string> | Response.BadRequest<string>> =
route.get(...).handler(...)
// ...

@@ -30,2 +39,7 @@

In the OpenAPI v3 spec, the `description` field of a
[Response Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responseObject)
is required, so `typera-openapi` prints a warning if a JSDoc tag for a response
is not found.
Run the `typera-openapi` tool giving paths to your route files as command line

@@ -32,0 +46,0 @@ arguments. Assuming you have two route files in your project:

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