question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature: inline type aliases (@tsoaInline)

See original GitHub issue

Sorting

  • I’m submitting a …

    • bug report
    • feature request
    • support request
  • I confirm that I

    • used the search to make sure that a similar issue hasn’t already been submit

I did, and I found a lot of other similar issues, for example :

Repro project

https://github.com/jeremyVignelles/repro-tsoa/tree/repro/pick-partial-omit

Expected Behavior

Expected swagger.json
{
	"components": {
		"examples": {},
		"headers": {},
		"parameters": {},
		"requestBodies": {},
		"responses": {},
		"schemas": {
			"User": {
				"properties": {
					"id": {
						"type": "number",
						"format": "double"
					},
					"email": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"status": {
						"type": "string",
						"enum": [
							"Happy",
							"Sad"
						]
					},
					"phoneNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array"
					}
				},
				"required": [
					"id",
					"email",
					"name",
					"phoneNumbers"
				],
				"type": "object",
				"additionalProperties": false
			},
			"UserCreationParams": {
				"properties": {
					"email": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"phoneNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array"
					}
				},
				"required": [
					"email",
					"name",
					"phoneNumbers"
				],
				"type": "object"
			},
			"UserPatchParams": {
				"properties": {
					"email": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"phoneNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array"
					},
					"status": {
						"type": "string",
						"enum": [
							"Happy",
							"Sad"
						]
					}
				},
				"type": "object"
			}
		},
		"securitySchemes": {}
	},
	"info": {
		"title": "repro-tsoa",
		"version": "1.0.0",
		"license": {
			"name": "MIT"
		},
		"contact": {
			"name": "John doe",
			"email": "example@example.com"
		}
	},
	"openapi": "3.0.0",
	"paths": {
		"/users/{userId}": {
			"get": {
				"operationId": "GetUser",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/User"
								}
							}
						}
					}
				},
				"security": [],
				"parameters": [
					{
						"in": "path",
						"name": "userId",
						"required": true,
						"schema": {
							"format": "double",
							"type": "number"
						}
					},
					{
						"in": "query",
						"name": "name",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				]
			},
			"patch": {
				"operationId": "PatchUser",
				"responses": {
					"204": {
						"description": "No content"
					}
				},
				"security": [],
				"parameters": [
					{
						"in": "path",
						"name": "userId",
						"required": true,
						"schema": {
							"format": "double",
							"type": "number"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/UserPatchParams"
							}
						}
					}
				}
			}
		},
		"/users": {
			"post": {
				"operationId": "CreateUser",
				"responses": {
					"201": {
						"description": "Created"
					}
				},
				"security": [],
				"parameters": [],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/UserCreationParams"
							}
						}
					}
				}
			}
		}
	},
	"servers": [
		{
			"url": "/"
		}
	]
}
/**
 * @tsoaInline
 */
export type UserCreationParams = Pick<User, "email" | "name" | "phoneNumbers">;

/**
 * @tsoaInline
 */
export type UserPatchParams = Partial<Omit<User, "id">>

Current Behavior

Generated swagger.json
{
	"components": {
		"examples": {},
		"headers": {},
		"parameters": {},
		"requestBodies": {},
		"responses": {},
		"schemas": {
			"User": {
				"properties": {
					"id": {
						"type": "number",
						"format": "double"
					},
					"email": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"status": {
						"type": "string",
						"enum": [
							"Happy",
							"Sad"
						]
					},
					"phoneNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array"
					}
				},
				"required": [
					"id",
					"email",
					"name",
					"phoneNumbers"
				],
				"type": "object",
				"additionalProperties": false
			},
			"Pick_User.email-or-name-or-phoneNumbers_": {
				"properties": {
					"email": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"phoneNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array"
					}
				},
				"required": [
					"email",
					"name",
					"phoneNumbers"
				],
				"type": "object",
				"description": "From T, pick a set of properties whose keys are in the union K"
			},
			"UserCreationParams": {
				"$ref": "#/components/schemas/Pick_User.email-or-name-or-phoneNumbers_"
			},
			"Partial_Omit_User.id__": {
				"properties": {
					"email": {
						"type": "string"
					},
					"name": {
						"type": "string"
					},
					"phoneNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array"
					},
					"status": {
						"type": "string",
						"enum": [
							"Happy",
							"Sad"
						]
					}
				},
				"type": "object",
				"description": "Make all properties in T optional"
			},
			"UserPatchParams": {
				"$ref": "#/components/schemas/Partial_Omit_User.id__"
			}
		},
		"securitySchemes": {}
	},
	"info": {
		"title": "repro-tsoa",
		"version": "1.0.0",
		"license": {
			"name": "MIT"
		},
		"contact": {
			"name": "John doe",
			"email": "example@example.com"
		}
	},
	"openapi": "3.0.0",
	"paths": {
		"/users/{userId}": {
			"get": {
				"operationId": "GetUser",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/User"
								}
							}
						}
					}
				},
				"security": [],
				"parameters": [
					{
						"in": "path",
						"name": "userId",
						"required": true,
						"schema": {
							"format": "double",
							"type": "number"
						}
					},
					{
						"in": "query",
						"name": "name",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				]
			},
			"patch": {
				"operationId": "PatchUser",
				"responses": {
					"204": {
						"description": "No content"
					}
				},
				"security": [],
				"parameters": [
					{
						"in": "path",
						"name": "userId",
						"required": true,
						"schema": {
							"format": "double",
							"type": "number"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/UserPatchParams"
							}
						}
					}
				}
			}
		},
		"/users": {
			"post": {
				"operationId": "CreateUser",
				"responses": {
					"201": {
						"description": "Created"
					}
				},
				"security": [],
				"parameters": [],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/UserCreationParams"
							}
						}
					}
				}
			}
		}
	},
	"servers": [
		{
			"url": "/"
		}
	]
}

Note that the type alias are generated as

	"UserCreationParams": {
		"$ref": "#/components/schemas/Pick_User.email-or-name-or-phoneNumbers_"
	},

which could be simplified as client code generators (like NSwag) are unable to handle this properly.

Possible Solution

As mentionned above, there could be a JSDoc annotation that would allow to inline the type alias to flatten its type aliases indirections.

Steps to Reproduce

Context (Environment)

Version of the library: 3.5.2 Version of NodeJS: 14

  • Confirm you were using yarn not npm: [X]

Detailed Description

The jsdoc annotation can be placed on a type declaration, like so:

export interface User {
  id: number;
  email: string;
  name: string;
  status?: "Happy" | "Sad";
  phoneNumbers: string[];
}

/**
 * A post request should not contain an id.
 * @tsoaInline
 */
export type UserCreationParams = Pick<User, "email" | "name" | "phoneNumbers">;

If both types are used, the above code would generate the same thing as if they were in fact two separate interfaces, that is, the same as :

export interface User {
  id: number;
  email: string;
  name: string;
  status?: "Happy" | "Sad";
  phoneNumbers: string[];
}

/**
 * A post request should not contain an id.
 */
export interface UserCreationParams {
  email: string;
  name: string;
  phoneNumbers: string[];
}

Breaking change?

No, the new syntax would be opt-in

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:11
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

13reactions
vincentvanderweelecommented, Feb 19, 2021

There is a workaround for this, although not pretty. You first need to make sure that you only use interfaces on your API, so replace

export type UserCreationParams = Pick<User, "email" | "name" | "phoneNumbers">;

by

export interface UserCreationParams extends Pick<User, "email" | "name" | "phoneNumbers"> {};

Then, you need to flatten the base type on the right, for which you can use a helper type like this:

type Expand<T> = { [K in keyof T]: T[K] };

export interface UserCreationParams extends Expand<Pick<User, "email" | "name" | "phoneNumbers">> {};

Now UserCreationParams gets generated the way you would expect.

0reactions
afaragocommented, Nov 20, 2022

+1 to flatten unneeded utility types or at least add a naming option

Read more comments on GitHub >

github_iconTop Results From Across the Web

How To Use Type Aliases in TypeScript - DigitalOcean
In this tutorial, you will refactor code that uses string literals to include aliases. You will be able to use and understand TypeScript...
Read more >
Feature request - inline type aliases : r/typescript - Reddit
Is there a problem with the function not returning the alias, only returning the inferred object? You are still getting all the type...
Read more >
Kotlin: Type Aliases and Inline Classes | by Forketyfork - Medium
The Kotlin programming language has two distinct features that may be used for similar purposes. Those are inline classes and type aliases.
Read more >
Type Aliases vs Inline Classes - GeeksforGeeks
Inline classes add the features of TypeAliases with value range of primitive data types. In some situations it is needed to create a...
Read more >
Kotlin: Type aliases and inline classes | by Filipe Batista
Kotlin is a modern programming language and so today we are going to see two cool features: type aliases and inline classes.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found