diff --git a/OpenAPI.ts b/OpenAPI.ts new file mode 100644 index 0000000..c30fc92 --- /dev/null +++ b/OpenAPI.ts @@ -0,0 +1,275 @@ +// tslint:disable:array-type no-empty-interface +// Overwrite openapi3-ts to support OpenAPI 3.1 +import { JSONSchema } from 'json-schema-typed' + +// Specification Extensions +// ^x- +export interface ISpecificationExtension { + // Cannot constraint to "^x-" but can filter them later to access to them + [extensionName: string]: any +} + +export interface OpenAPIObject extends ISpecificationExtension { + openapi: string + info: InfoObject + servers?: ServerObject[] + paths: PathsObject + components?: ComponentsObject + security?: SecurityRequirementObject[] + tags?: TagObject[] + externalDocs?: ExternalDocumentationObject +} +export interface InfoObject extends ISpecificationExtension { + title: string + description?: string + termsOfService?: string + contact?: ContactObject + license?: LicenseObject + version: string +} +export interface ContactObject extends ISpecificationExtension { + name?: string + url?: string + email?: string +} +export interface LicenseObject extends ISpecificationExtension { + name: string + url?: string +} +export interface ServerObject extends ISpecificationExtension { + url: string + description?: string + variables?: { + [v: string]: ServerVariableObject + } +} +export interface ServerVariableObject extends ISpecificationExtension { + enum?: string[] | boolean[] | number[] + default: string | boolean | number + description?: string +} +export interface ComponentsObject extends ISpecificationExtension { + schemas?: { + [schema: string]: SchemaObject | ReferenceObject + } + responses?: { + [response: string]: ResponseObject | ReferenceObject + } + parameters?: { + [parameter: string]: ParameterObject | ReferenceObject + } + examples?: { + [example: string]: ExampleObject | ReferenceObject + } + requestBodies?: { + [request: string]: RequestBodyObject | ReferenceObject + } + headers?: { + [heaer: string]: HeaderObject | ReferenceObject + } + securitySchemes?: { + [securityScheme: string]: SecuritySchemeObject | ReferenceObject + } + links?: { + [link: string]: LinkObject | ReferenceObject + } + callbacks?: { + [callback: string]: CallbackObject | ReferenceObject + } +} +export interface PathsObject extends ISpecificationExtension { + [path: string]: PathItemObject | any +} +export declare type PathObject = PathsObject +export interface PathItemObject extends ISpecificationExtension { + $ref?: string + summary?: string + description?: string + get?: OperationObject + put?: OperationObject + post?: OperationObject + delete?: OperationObject + options?: OperationObject + head?: OperationObject + patch?: OperationObject + trace?: OperationObject + servers?: ServerObject[] + parameters?: (ParameterObject | ReferenceObject)[] +} +export interface OperationObject extends ISpecificationExtension { + tags?: string[] + summary?: string + description?: string + externalDocs?: ExternalDocumentationObject + operationId?: string + parameters?: (ParameterObject | ReferenceObject)[] + requestBody?: RequestBodyObject | ReferenceObject + responses: ResponsesObject + callbacks?: CallbacksObject + deprecated?: boolean + security?: SecurityRequirementObject[] + servers?: ServerObject[] +} +export interface ExternalDocumentationObject extends ISpecificationExtension { + description?: string + url: string +} +export declare type ParameterLocation = 'query' | 'header' | 'path' | 'cookie' +export declare type ParameterStyle = + | 'matrix' + | 'label' + | 'form' + | 'simple' + | 'spaceDelimited' + | 'pipeDelimited' + | 'deepObject' +export interface BaseParameterObject extends ISpecificationExtension { + description?: string + required?: boolean + deprecated?: boolean + allowEmptyValue?: boolean + style?: ParameterStyle + explode?: boolean + allowReserved?: boolean + schema?: SchemaObject | ReferenceObject + examples?: { + [param: string]: ExampleObject | ReferenceObject + } + example?: any + content?: ContentObject +} +export interface ParameterObject extends BaseParameterObject { + name: string + in: ParameterLocation +} +export interface RequestBodyObject extends ISpecificationExtension { + description?: string + content: ContentObject + required?: boolean +} +export interface ContentObject { + [mediatype: string]: MediaTypeObject +} +export interface MediaTypeObject extends ISpecificationExtension { + schema?: SchemaObject | ReferenceObject + examples?: ExamplesObject + example?: any + encoding?: EncodingObject +} +export interface EncodingObject extends ISpecificationExtension { + [property: string]: EncodingPropertyObject | any +} +export interface EncodingPropertyObject { + contentType?: string + headers?: { + [key: string]: HeaderObject | ReferenceObject + } + style?: string + explode?: boolean + allowReserved?: boolean + [key: string]: any +} +export interface ResponsesObject extends ISpecificationExtension { + default?: ResponseObject | ReferenceObject + [statuscode: string]: ResponseObject | ReferenceObject | any +} +export interface ResponseObject extends ISpecificationExtension { + description: string + headers?: HeadersObject + content?: ContentObject + links?: LinksObject +} +export interface CallbacksObject extends ISpecificationExtension { + [name: string]: CallbackObject | ReferenceObject | any +} +export interface CallbackObject extends ISpecificationExtension { + [name: string]: PathItemObject | any +} +export interface HeadersObject { + [name: string]: HeaderObject | ReferenceObject +} +export interface ExampleObject { + summary?: string + description?: string + value?: any + externalValue?: string + [property: string]: any +} +export interface LinksObject { + [name: string]: LinkObject | ReferenceObject +} +export interface LinkObject extends ISpecificationExtension { + operationRef?: string + operationId?: string + parameters?: LinkParametersObject + requestBody?: any | string + description?: string + server?: ServerObject + [property: string]: any +} +export interface LinkParametersObject { + [name: string]: any | string +} +export interface HeaderObject extends BaseParameterObject {} +export interface TagObject extends ISpecificationExtension { + name: string + description?: string + externalDocs?: ExternalDocumentationObject + [extension: string]: any +} +export interface ExamplesObject { + [name: string]: ExampleObject | ReferenceObject +} +export interface ReferenceObject { + $ref: string +} +export type SchemaObject = JSONSchema +export interface SchemasObject { + [schema: string]: SchemaObject +} +export interface DiscriminatorObject { + propertyName: string + mapping?: { + [key: string]: string + } +} +export interface XmlObject extends ISpecificationExtension { + name?: string + namespace?: string + prefix?: string + attribute?: boolean + wrapped?: boolean +} +export declare type SecuritySchemeType = + | 'apiKey' + | 'http' + | 'oauth2' + | 'openIdConnect' +export interface SecuritySchemeObject extends ISpecificationExtension { + type: SecuritySchemeType + description?: string + name?: string + in?: string + scheme?: string + bearerFormat?: string + flows?: OAuthFlowsObject + openIdConnectUrl?: string +} +export interface OAuthFlowsObject extends ISpecificationExtension { + implicit?: OAuthFlowObject + password?: OAuthFlowObject + clientCredentials?: OAuthFlowObject + authorizationCode?: OAuthFlowObject +} +export interface OAuthFlowObject extends ISpecificationExtension { + authorizationUrl?: string + tokenUrl?: string + refreshUrl?: string + scopes: ScopesObject +} +export interface ScopesObject extends ISpecificationExtension { + [scope: string]: any +} +export interface SecurityRequirementObject { + [name: string]: string[] +} diff --git a/PetStoreSchema.ts b/PetStoreSchema.ts new file mode 100644 index 0000000..a384c88 --- /dev/null +++ b/PetStoreSchema.ts @@ -0,0 +1,236 @@ +import { OpenAPIObject } from './OpenAPI' + +export default interface OpenAPISchema extends OpenAPIObject { + openapi: '3.0.0' + info: { + version: '1.0.0' + title: 'Swagger Petstore' + // description: 'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification' + termsOfService: 'http://swagger.io/terms/' + contact: { + name: 'Swagger API Team' + email: 'apiteam@swagger.io' + url: 'http://swagger.io' + } + license: { + name: 'Apache 2.0' + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' + } + } + servers: [ + { + url: 'http://petstore.swagger.io/api' + } + ] + paths: { + '/pets': { + get: { + // description: 'Returns all pets from the system that the user has access to\nNam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.\n\nSed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.\n' + operationId: 'findPets' + parameters: [ + { + name: 'tags' + in: 'query' + // description: 'tags to filter by' + required: false + style: 'form' + schema: { + type: 'array' + items: { + type: 'string' + } + } + }, + { + name: 'limit' + in: 'query' + // description: 'maximum number of results to return' + required: false + schema: { + type: 'integer' + } + } + ] + responses: { + '200': { + // description: 'pet response' + content: { + 'application/json': { + schema: { + type: 'array' + items: { + $ref: '#Pet' + } + } + } + } + } + default: { + // description: 'unexpected error' + content: { + 'application/json': { + schema: { + $ref: '#Error' + } + } + } + } + } + } + post: { + // description: 'Creates a new pet in the store. Duplicates are allowed' + operationId: 'addPet' + requestBody: { + // description: 'Pet to add to the store' + required: true + content: { + 'application/json': { + schema: { + $ref: '#NewPet' + } + } + } + } + responses: { + '200': { + // description: 'pet response' + content: { + 'application/json': { + schema: { + $ref: '#Pet' + } + } + } + } + default: { + // description: 'unexpected error' + content: { + 'application/json': { + schema: { + $ref: '#Error' + } + } + } + } + } + } + } + '/pets/{id}': { + get: { + // description: 'Returns a user based on a single ID, if the user does not have access to the pet' + operationId: 'find pet by id' + parameters: [ + { + name: 'id' + in: 'path' + // description: 'ID of pet to fetch' + required: true + schema: { + type: 'integer' + } + } + ] + responses: { + '200': { + // description: 'pet response' + content: { + 'application/json': { + schema: { + $ref: '#Pet' + } + } + } + } + default: { + // description: 'unexpected error' + content: { + 'application/json': { + schema: { + $ref: '#Error' + } + } + } + } + } + } + delete: { + // description: 'deletes a single pet based on the ID supplied' + operationId: 'deletePet' + parameters: [ + { + name: 'id' + in: 'path' + // description: 'ID of pet to delete' + required: true + schema: { + type: 'integer' + } + } + ] + responses: { + '204': { + // description: 'pet deleted' + } + default: { + // description: 'unexpected error' + content: { + 'application/json': { + schema: { + $ref: '#Error' + } + } + } + } + } + } + } + } + components: { + schemas: { + Pet: { + $id: '#Pet' + allOf: [ + { + $ref: '#NewPet' + }, + { + type: 'object' + required: ['id'] + properties: { + id: { + type: 'integer' + } + } + } + ] + } + NewPet: { + $id: '#NewPet' + type: 'object' + required: ['name'] + properties: { + name: { + type: 'string' + } + tag: { + type: 'string' + } + } + } + Error: { + $id: '#Error' + type: 'object' + required: ['code', 'message'] + additionalProperties: false + properties: { + code: { + type: 'integer' + } + message: { + type: 'string' + } + } + } + } + } +} diff --git a/README.md b/README.md index 486e17e..d6c550e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ # express-openapi-typer -Code-generation-free conversion of OpenAPI schema into typed Express request handlers +Code-generation-free conversion of OpenAPI schema into typed Express request handlers. + +**Schema-first API development!** There's a bunch of libraries out there for generating OpenAPI schemas from source code. This one works the opposite way, and with no code generation involved. How it works is you draft your OpenAPI schema, and let `express-openapi-typer`'s type constraints ensure that your code actually follows the schema. Finally, you'd add something like https://github.com/Hilzu/express-openapi-validate to do runtime validation based on the same OpenAPI schema. + +TODO just validating that your (already written) API follows the schema? + +**Requires OpenAPI v3.1** (yet unpublished, track progress at https://github.com/OAI/OpenAPI-Specification/issues/2025) since earlier versions use the [OpenAPI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) in favour of pure JSON Schema. Read more about the divergence at https://apisyouwonthate.com/blog/openapi-and-json-schema-divergence-part-1 and how `v3.1` solves it at https://phil.tech/2019/09/07/update-openapi-json-schema/. + +## Work in progress! + +## Usage + +No need to modify existing Express handlers/handler interfaces stay the same + +Drop in/plug and play, just run `yarn install express-openapi-typer` and change + +```typescript +const router = express.Router() +``` + +into + +```typescript +import { OpenAPIRouter } from 'express-openapi-typer' + +interface MySchema { + paths: { + '/pets': { + get: { + // ... + } + } + } +} + +const router = (express.Router() as unknown) as OpenAPIRouter +``` + +and your handler functions get type-checked as per `MySchema`! + +### Allow additional paths + +By default `OpenAPIRouter` doesn't allow any additional handlers not defined in the OpenAPI schema. To loosen this restriction you can expand the type as follows: + +```typescript +import * as express from 'express' + +const router = express.Router() as OpenAPIRouter & express.Router +``` + +You can also select a subset of `express.Router` with [`Pick`/`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktk) when allowing additional methods only for a specific HTTP method, for example. + +## TODO +- path parameters +- query parameters +- response and request headers +- async handlers +- API client types, axios? + - or just pick something like https://github.com/anttiviljami/openapi-client-axios, https://github.com/Manweill/swagger-axios-codegen - take advantage of the OpenAPI ecosystem! +- handle different response bodies per content type and status code +- figure out if we can get rid of the unfortunate `as unknown` cast +- expand to KoaJS et al? +- support path-based `$ref`s, not just `$id`-based ones + - requires some sort of manual mapping as we can't take `"#/components/schemas/NewUser"` apart at type-level + +## Related projects +- https://github.com/rawrmaan/restyped +- https://github.com/hmil/rest.ts +- https://stoplight.io/open-source/prism/ Prism is an open source mock server that can mimic your API’s behavior as if you already built it. Servers are generated from your OpenAPI v2/v3 (formerly known as Swagger) documents. +- apiaryio/dredd diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..73636db --- /dev/null +++ b/index.ts @@ -0,0 +1,130 @@ +import * as express from 'express-serve-static-core' // tslint:disable-line:no-implicit-dependencies +import { Schema as JSONSchema } from 'json-schema-type-mapper' +import * as oa from './OpenAPI' + +// TODO: for now assuming schema.components.schemas is populated and doesn't contain `$ref` objects +type OpenAPIObject = oa.OpenAPIObject & { + components: { + schemas: Record + } +} + +// Digging up schemas by hand since json-schema-type-mapper expects to find them +// under `/definitions`, not in OpenAPI's `/components/schemas`: + +export type Schemas = S['components']['schemas'] + +export type SchemaIds = Exclude< + ValueOf>['$id'], + undefined +> + +export type SchemasById = { + [Id in SchemaIds]: ValueOf< + { + [P in keyof Schemas]: Schemas[P]['$id'] extends Id + ? Schemas[P] + : never + } + > +} + +/** + * Force TS to load a type that has not been computed + * https://github.com/pirix-gh/ts-toolbelt + */ +export type Compute = A extends Function // tslint:disable-line:ban-types + ? A + : { [K in keyof A]: A[K] } & {} + +/** + * Courtesy of @jcalz at https://stackoverflow.com/a/50375286/1763012 + */ +type UnionToIntersection = (U extends any +? (k: U) => void +: never) extends (k: infer I) => void + ? I + : never + +type ValueOf = T[keyof T] + +export type RequestBody< + S extends OpenAPIObject, + Path extends keyof S['paths'], + Method extends keyof S['paths'][Path], + O extends S['paths'][Path][Method] = S['paths'][Path][Method] +> = Compute< + | JSONSchema['schema'], SchemasById> + | (O['requestBody']['required'] extends true ? never : undefined) +> + +/** Gather OpenAPI operations under a single flat union for easier processing */ +export type Operation = ValueOf< + { + [Path in keyof S['paths']]: ValueOf< + { + [Method in keyof S['paths'][Path]]: { + method: Method + operationId: S['paths'][Path][Method]['operationId'] + path: Path + params: {} // TODO dig up params + + // TODO handle different content types and `required`: + requestBody: RequestBody + + // TODO handle different content types, headers and status codes: + responseBody: JSONSchema< + ValueOf< + S['paths'][Path][Method]['responses'] + >['content']['application/json']['schema'], + SchemasById + > + } + } + > + } +> + +export type PathsByMethod< + S extends OpenAPIObject, + O extends Operation = Operation +> = { + [Method in O['method']]: { + [Path in O['path']]: Extract + } +} + +interface OperationObject { + params: any + path: string + responseBody: any + requestBody: any +} + +export type RouterMatcher = ( + path: O['path'], + ...handlers: Array< + express.RequestHandler + > +) => any + +type HTTPMethod = + | 'get' + | 'post' + | 'put' + | 'delete' + | 'patch' + | 'options' + | 'head' + +export type OpenAPIRouter< + S extends OpenAPIObject, + P extends PathsByMethod = PathsByMethod +> = Compute< + { + [Method in keyof P]: UnionToIntersection< + ValueOf<{ [Path in keyof P[Method]]: RouterMatcher }> + > + } & + Omit +> diff --git a/package.json b/package.json index 00311a7..3bf5852 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,18 @@ "semi": false, "singleQuote": true }, + "dependencies": { + "express": "^4.17.1", + "json-schema-type-mapper": "../json-schema-type-mapper", + "json-schema-typed": "^7.0.3" + }, "devDependencies": { + "@types/express": "^4.17.2", + "@types/express-serve-static-core": "^4.17.0", "prettier": "^1.19.1", "tslint": "^5.20.1", "tslint-config-prettier": "^1.18.0", "tslint-config-standard": "^9.0.0", - "typescript": "^3.7.2" + "typescript": "^3.7.3" } } diff --git a/sample.ts b/sample.ts new file mode 100644 index 0000000..eda2fc5 --- /dev/null +++ b/sample.ts @@ -0,0 +1,15 @@ +import * as express from 'express' +import { OpenAPIRouter } from '.' +import PetStoreSchema from './PetStoreSchema' + +const router = (express.Router() as unknown) as OpenAPIRouter + +router.get('/pets/{id}', (req, res, next) => undefined) +router.get('/pets', (req, res, next) => undefined) + +router.delete('/pets/{id}', (req, res, next) => undefined) + +router.post('/pets', (req, res, next) => { + // req.body autocompletes to `{ [x: string]: JSONValue; name: string; tag?: string | undefined; }` + res.send({ name: req.body.name, id: 1 }) +}) diff --git a/yarn.lock b/yarn.lock index 0f26635..927c9bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,6 +16,60 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@types/body-parser@*": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897" + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.32" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.0": + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz#e80c25903df5800e926402b7e8267a675c54a281" + dependencies: + "@types/node" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.2": + version "4.17.2" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c" + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" + +"@types/node@*": + version "12.12.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.11.tgz#bec2961975888d964196bf0016a2f984d793d3ce" + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + +"@types/serve-static@*": + version "1.13.3" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -28,10 +82,29 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -43,6 +116,10 @@ builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + chalk@^2.0.0, chalk@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -69,6 +146,38 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + diff@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" @@ -80,6 +189,18 @@ doctrine@0.7.2: esutils "^1.1.6" isarray "0.0.1" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -96,6 +217,65 @@ esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -115,6 +295,32 @@ has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + dependencies: + safer-buffer ">= 2.1.2 < 3" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -122,10 +328,18 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ipaddr.js@1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -141,6 +355,45 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +json-schema-type-mapper@../json-schema-type-mapper: + version "0.0.2" + dependencies: + json-schema-typed "^7.0.2" + +json-schema-typed@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.2.tgz#926deb7535cfb321613ee136eaed70c1419c89b4" + +json-schema-typed@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +mime-db@1.42.0: + version "1.42.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" + +mime-types@~2.1.24: + version "2.1.25" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437" + dependencies: + mime-db "1.42.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -157,12 +410,34 @@ mkdirp@^0.5.1: dependencies: minimist "0.0.8" +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: wrappy "1" +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -171,30 +446,105 @@ path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + prettier@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" +proxy-addr@~2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.0" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + resolve@^1.3.2: version "1.12.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" dependencies: path-parse "^1.0.6" +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + semver@^5.3.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: has-flag "^3.0.0" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + tslib@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" @@ -251,9 +601,28 @@ tsutils@^3.0.0: dependencies: tslib "^1.8.1" -typescript@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" wrappy@1: version "1.0.2"