├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── code-review.yml │ └── deploy.yml ├── .gitignore ├── README.md ├── docs ├── .gitignore ├── .vitepress │ ├── cache │ │ └── deps │ │ │ ├── _metadata.json │ │ │ ├── package.json │ │ │ ├── vue.js │ │ │ └── vue.js.map │ ├── config.ts │ └── theme │ │ ├── custom.css │ │ └── index.ts ├── api-examples.md ├── assets │ ├── icons │ │ ├── ts-logo-128.png │ │ └── ts-logo-round-128.png │ └── images │ │ ├── api-test-1.png │ │ ├── api-test-2.png │ │ ├── api-test-3.png │ │ ├── authorization-popup.png │ │ ├── authorization-request.png │ │ ├── authorize-button.png │ │ ├── crud-swagger-ui.png │ │ ├── file-download.png │ │ ├── file-upload.png │ │ ├── getting-started-swagger-ui-1.png │ │ └── getting-started-swagger-ui-2.png ├── guide │ ├── api-testing.md │ ├── authentication.md │ ├── cli.md │ ├── crud-api-example.md │ ├── defining-api-spec.md │ ├── describing-schema.md │ ├── express-integration.md │ ├── file-upload-download.md │ ├── generating-document.md │ ├── getting-started.md │ ├── index.md │ ├── troubleshooting.md │ └── tspec-config.md ├── index.md ├── introduction.md ├── markdown-examples.md ├── package.json └── yarn.lock ├── examples ├── book-advanced │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── openapi.json │ ├── tsconfig.json │ ├── tspec.config.json │ └── yarn.lock ├── file-upload-download-example │ ├── openapi.json │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── yarn.lock ├── tspec-basic-example │ ├── index.ts │ ├── openapi.json │ ├── package.json │ ├── tsconfig.json │ └── yarn.lock └── tspec-express-example │ ├── index.ts │ ├── openapi.json │ ├── package.json │ ├── tsconfig.json │ └── yarn.lock └── packages └── tspec ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── rollup.config.ts ├── src ├── cli │ └── index.ts ├── generator │ ├── config.ts │ ├── index.ts │ ├── openapiGenerator.ts │ ├── openapiSchemaConverter.ts │ ├── schemaParser.ts │ └── types.ts ├── index.ts ├── server │ └── index.ts ├── types │ ├── index.ts │ ├── json-schema-to-openapi-schema.d.ts │ └── tspec.ts └── utils │ ├── merge.ts │ └── types.ts ├── tsconfig.json └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/code-review.yml: -------------------------------------------------------------------------------- 1 | name: Code Review 2 | permissions: 3 | contents: read 4 | pull-requests: write 5 | on: 6 | pull_request: 7 | types: 8 | - opened 9 | - reopened 10 | - synchronize 11 | jobs: 12 | code-review: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: anc95/ChatGPT-CodeReview@main 16 | env: 17 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 18 | OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}" 19 | LANGUAGE: English 20 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | defaults: 10 | run: 11 | working-directory: docs 12 | permissions: 13 | pages: write 14 | id-token: write 15 | environment: 16 | name: github-pages 17 | url: ${{ steps.deployment.outputs.page_url }} 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | - name: Install dependencies 23 | run: yarn --frozen-lockfile --silent 24 | - name: Build 25 | run: yarn docs:build 26 | - uses: actions/configure-pages@v3 27 | - uses: actions/upload-pages-artifact@v1 28 | with: 29 | path: docs/.vitepress/dist 30 | - name: Deploy 31 | id: deployment 32 | uses: actions/deploy-pages@v2 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/tspec/README.md -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # Logs 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # ESLint 9 | .eslintcache 10 | 11 | # Node 12 | dist/ 13 | -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "0e3be5e7", 3 | "browserHash": "01d499ed", 4 | "optimized": { 5 | "vue": { 6 | "src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", 7 | "file": "vue.js", 8 | "fileHash": "ef88558e", 9 | "needsInterop": false 10 | } 11 | }, 12 | "chunks": {} 13 | } -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/package.json: -------------------------------------------------------------------------------- 1 | {"type":"module"} -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | base: '/tspec/', // Reference: https://vitepress.dev/guide/deploy#github-pages 6 | title: "Tspec", 7 | description: "Auto-generating OpenAPI Document with TypeScript Types", 8 | lastUpdated: true, 9 | cleanUrls: true, 10 | themeConfig: { 11 | // https://vitepress.dev/reference/default-theme-config 12 | nav: [ 13 | { text: 'Home', link: '/' }, 14 | { text: 'Guide', link: '/guide/getting-started' } 15 | ], 16 | 17 | sidebar: [ 18 | { 19 | text: 'Introduction', link: '/introduction' 20 | }, 21 | { 22 | text: 'Guides', 23 | items: [ 24 | { text: 'Getting Started', link: '/guide/getting-started' }, 25 | { text: 'Generating Document', link: '/guide/generating-document' }, 26 | { text: 'Describing Schema', link: '/guide/describing-schema' }, 27 | { text: 'Defining API Spec', link: '/guide/defining-api-spec' }, 28 | { text: 'CRUD API Example', link: '/guide/crud-api-example' }, 29 | { text: 'Express Integration', link: '/guide/express-integration' }, // with Request validation 30 | { text: 'API Testing', link: '/guide/api-testing' }, 31 | { text: 'Authentication', link: '/guide/authentication' }, 32 | { text: 'File Upload/Download', link: '/guide/file-upload-download' }, 33 | ] 34 | }, 35 | { 36 | text: 'Reference', 37 | items: [ 38 | { text: 'Troubleshooting', link: '/guide/troubleshooting' }, 39 | // { text: 'CLI', link: '/guide/cli' }, 40 | // { text: 'tspec.config Options', link: '/guide/tspec-config' }, 41 | ] 42 | }, 43 | ], 44 | 45 | socialLinks: [ 46 | { icon: 'github', link: 'https://github.com/ts-spec/tspec' } 47 | ], 48 | 49 | search: { 50 | provider: 'local', 51 | }, 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-home-hero-name-color: transparent; 3 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff); 4 | /* --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #42E595, #3BB2B8); */ 5 | /* --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #85C48D, #2BB6C7); */ 6 | } 7 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import './custom.css' 3 | 4 | export default DefaultTheme 5 | -------------------------------------------------------------------------------- /docs/api-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Runtime API Examples 6 | 7 | This page demonstrates usage of some of the runtime APIs provided by VitePress. 8 | 9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: 10 | 11 | ```md 12 | 17 | 18 | ## Results 19 | 20 | ### Theme Data 21 |
{{ theme }}
22 | 23 | ### Page Data 24 |
{{ page }}
25 | 26 | ### Page Frontmatter 27 |
{{ frontmatter }}
28 | ``` 29 | 30 | 35 | 36 | ## Results 37 | 38 | ### Theme Data 39 |
{{ theme }}
40 | 41 | ### Page Data 42 |
{{ page }}
43 | 44 | ### Page Frontmatter 45 |
{{ frontmatter }}
46 | 47 | ## More 48 | 49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). 50 | -------------------------------------------------------------------------------- /docs/assets/icons/ts-logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/icons/ts-logo-128.png -------------------------------------------------------------------------------- /docs/assets/icons/ts-logo-round-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/icons/ts-logo-round-128.png -------------------------------------------------------------------------------- /docs/assets/images/api-test-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/api-test-1.png -------------------------------------------------------------------------------- /docs/assets/images/api-test-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/api-test-2.png -------------------------------------------------------------------------------- /docs/assets/images/api-test-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/api-test-3.png -------------------------------------------------------------------------------- /docs/assets/images/authorization-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/authorization-popup.png -------------------------------------------------------------------------------- /docs/assets/images/authorization-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/authorization-request.png -------------------------------------------------------------------------------- /docs/assets/images/authorize-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/authorize-button.png -------------------------------------------------------------------------------- /docs/assets/images/crud-swagger-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/crud-swagger-ui.png -------------------------------------------------------------------------------- /docs/assets/images/file-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/file-download.png -------------------------------------------------------------------------------- /docs/assets/images/file-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/file-upload.png -------------------------------------------------------------------------------- /docs/assets/images/getting-started-swagger-ui-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/getting-started-swagger-ui-1.png -------------------------------------------------------------------------------- /docs/assets/images/getting-started-swagger-ui-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/assets/images/getting-started-swagger-ui-2.png -------------------------------------------------------------------------------- /docs/guide/api-testing.md: -------------------------------------------------------------------------------- 1 | # API Testing 2 | 3 | Tspec provides a way to test your API using [Swagger UI](https://swagger.io/tools/swagger-ui/). 4 | 5 | ## Setup 6 | 7 | Let's assume that you have the following Express application: 8 | 9 | ::: code-group 10 | ```ts[index.ts] 11 | import express, { RequestHandler } from "express"; 12 | import { Tspec, TspecDocsMiddleware } from "tspec"; 13 | 14 | interface Book { 15 | id: number; 16 | title: string; 17 | } 18 | 19 | const getBookById: RequestHandler<{ id: string }, Book> = (req, res) => { 20 | res.json({ 21 | id: +req.params.id, 22 | title: 'Under the Oak Tree', 23 | }) 24 | } 25 | 26 | export type BookApiSpec = Tspec.DefineApiSpec<{ 27 | tags: ['Book'], 28 | paths: { 29 | '/books/{id}': { 30 | get: { 31 | summary: 'Get book by id', 32 | handler: typeof getBookById 33 | }, 34 | }, 35 | } 36 | }>; 37 | 38 | const initServer = async () => { 39 | const app = express(); 40 | app.get('/books/:id', getBookById); 41 | app.use('/docs', await TspecDocsMiddleware()); 42 | app.listen(3000, () => { 43 | console.log(`Tspec docs is running on http://localhost:3000/docs`); 44 | }); 45 | } 46 | initServer(); 47 | ``` 48 | ::: 49 | 50 | Run the following command to start the server: 51 | 52 | ```bash 53 | ts-node index.ts 54 | ``` 55 | 56 | Then, you can access Swagger UI at `http://localhost:3000/docs` 57 | 58 | 59 | ## Try it out 60 | 61 | Now, you can try out the API in Swagger UI. 62 | 63 | The `Try it out` button is located at the top right of the API section. 64 | 65 | ![try-it-out-1](/assets/images/api-test-1.png) 66 | 67 | 68 | Click the `Try it out` button and enter the `id` parameter. 69 | 70 | ![try-it-out-2](/assets/images/api-test-2.png) 71 | 72 | Then, click the `Execute` button to send the request. 73 | 74 | You can see the response in the `Response body` section. 75 | 76 | ![try-it-out-3](/assets/images/api-test-3.png) 77 | -------------------------------------------------------------------------------- /docs/guide/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | Tspec supports the authentication in the [Swagger UI](https://swagger.io/tools/swagger-ui/). 4 | 5 | Swagger UI provides the `Authorize` button to authenticate the user. 6 | 7 | On this page, we will learn how to use authentication in Tspec. 8 | 9 | ## Defining security scheme 10 | 11 | To use authentication in Tspec, you need to define `securityDefinitions` in the `Tspec.GenerateParams`. 12 | 13 | The `securityDefinitions` is same as the [securitySchemes](https://swagger.io/docs/specification/authentication/) in the OpenAPI spec. 14 | 15 | ::: code-group 16 | ```ts[index.ts]{3} 17 | const tspecParams: Tspec.GenerateParams = { 18 | openapi: { 19 | securityDefinitions: { 20 | jwt: { 21 | type: 'http', 22 | scheme: 'bearer', 23 | bearerFormat: 'JWT', 24 | }, 25 | }, 26 | }, 27 | }; 28 | ``` 29 | ::: 30 | 31 | Then, you can use the `security` property in the `Tspec.DefineApiSpec` to specify the security scheme for the API: 32 | 33 | ::: code-group 34 | ```ts[index.ts]{2} 35 | export type UserApiSpec = Tspec.DefineApiSpec<{ 36 | security: 'jwt', 37 | paths: { 38 | '/my': { 39 | get: { 40 | summary: 'Get my info', 41 | handler: typeof getMyInfo 42 | }, 43 | }, 44 | } 45 | }>; 46 | ``` 47 | ::: 48 | 49 | And that's it! Now, you can see the `Authorize` button in the Swagger UI: 50 | 51 | ![Authorize button](/assets/images/authorize-button.png) 52 | 53 | If you click the button, you can see the `Authorization` popup: 54 | 55 | ![Authorization popup](/assets/images/authorization-popup.png) 56 | 57 | You can enter the JWT token in the popup and click the `Authorize` button to authenticate the user. 58 | 59 | And if you send the request, you can see the `Authorization` header in the request: 60 | 61 | ![Authorization request](/assets/images/authorization-request.png) 62 | -------------------------------------------------------------------------------- /docs/guide/cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | # Command Line Interface 5 | 6 | ## Generation 7 | 8 | ### **`generate`** 9 | 10 | Generate OpenAPI schema from TypeScript types. 11 | 12 | #### Usage 13 | 14 | ```sh 15 | generate [options] 16 | ``` 17 | 18 | #### Options 19 | 20 | |Option|Type|Description|Example| 21 | |-|-|-|-| 22 | |`--specPathGlobs [path]`|string[]|Path of TypeScript files which you want to generate OpenAPI schema|`src/**/*.ts`| 23 | |`--tsconfigPath [path]`|string|Path of tsconfig|`./tsconfig.json`| 24 | |`--outputPath [path]`|string|Path of output OpenAPI schema|`./generate/openapi.yaml`| 25 | |`--specVersion [version]`|number|Version to use for OpenAPI schema (Currently supports only 3)|`3`| 26 | |`--openapiTitle [title]`|string|`title` property of OpenAPI schema|`Tspec`| 27 | |`--openapiVersion [version]`|string|`version` property of OpenAPI schema|`1.0.0`| 28 | |`--debug [true / false]`|boolean|Print debug information for Tspec|`false`| 29 | |`--ignoreErrors [true / false]`|boolean|Whether ignore errors in Tspec or not|`false`| 30 | 31 | ## Server 32 | 33 | ### **`server`** 34 | 35 | Start Tspec server for display OpenAPI schema with Swagger. 36 | 37 | #### Usage 38 | 39 | ```sh 40 | server [options] 41 | ``` 42 | 43 | #### Options 44 | 45 | **You can also use the CLI options for [`generate`](#generate) command.** 46 | 47 | |Option|Type|Description|Example| 48 | |-|-|-|-| 49 | |`--port [port]`|number|Specify port number for Tspec server|`7080`| 50 | |`--proxyHost [host]`|string|Specify proxy host for Tspec server|`https://tspec.org`| 51 | -------------------------------------------------------------------------------- /docs/guide/crud-api-example.md: -------------------------------------------------------------------------------- 1 | # CRUD Example 2 | 3 | Let's define a CRUD API spec for `Author`: 4 | 5 | ::: code-group 6 | ```ts[index.ts] 7 | import { Tspec } from 'tspec'; 8 | 9 | interface Author { 10 | id: number; 11 | name: string; 12 | } 13 | 14 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 15 | basePath: '/authors', 16 | tags: ['Author'], 17 | paths: { 18 | '/': { 19 | get: { 20 | summary: 'List of authors', 21 | responses: { 22 | 200: Author[], 23 | }, 24 | }, 25 | post: { 26 | summary: 'Create an author', 27 | requestBody: Author, 28 | responses: { 29 | 201: Author, 30 | }, 31 | }, 32 | }, 33 | '/{id}': { 34 | get: { 35 | summary: 'Get author by id', 36 | path: { id: number }, 37 | responses: { 38 | 200: Author, 39 | }, 40 | }, 41 | put: { 42 | summary: 'Update an author', 43 | path: { id: number }, 44 | requestBody: Author, 45 | responses: { 46 | 200: Author, 47 | }, 48 | }, 49 | delete: { 50 | summary: 'Delete an author', 51 | path: { id: number }, 52 | responses: { 53 | 204: void, 54 | }, 55 | }, 56 | }, 57 | } 58 | }>; 59 | ``` 60 | ::: 61 | 62 | :::details Generated OpenAPI Spec 63 | ```yaml 64 | paths: 65 | "/authors/": 66 | get: 67 | operationId: AuthorApiSpec_get_/ 68 | tags: 69 | - Author 70 | summary: List of authors 71 | responses: 72 | '200': 73 | content: 74 | application/json: 75 | schema: 76 | type: array 77 | items: 78 | "$ref": "#/components/schemas/Author" 79 | post: 80 | operationId: AuthorApiSpec_post_/ 81 | tags: 82 | - Author 83 | summary: Create an author 84 | responses: 85 | '201': 86 | content: 87 | application/json: 88 | schema: 89 | "$ref": "#/components/schemas/Author" 90 | "/authors/{id}": 91 | get: 92 | operationId: AuthorApiSpec_get_/{id} 93 | tags: 94 | - Author 95 | summary: Get author by id 96 | parameters: 97 | - name: id 98 | in: path 99 | required: true 100 | schema: 101 | type: number 102 | responses: 103 | '200': 104 | content: 105 | application/json: 106 | schema: 107 | "$ref": "#/components/schemas/Author" 108 | put: 109 | operationId: AuthorApiSpec_put_/{id} 110 | tags: 111 | - Author 112 | summary: Update an author 113 | parameters: 114 | - name: id 115 | in: path 116 | required: true 117 | schema: 118 | type: number 119 | responses: 120 | '200': 121 | content: 122 | application/json: 123 | schema: 124 | "$ref": "#/components/schemas/Author" 125 | delete: 126 | operationId: AuthorApiSpec_delete_/{id} 127 | tags: 128 | - Author 129 | summary: Delete an author 130 | parameters: 131 | - name: id 132 | in: path 133 | required: true 134 | schema: 135 | type: number 136 | responses: 137 | '204': 138 | content: 139 | application/json: 140 | schema: 141 | type: undefined 142 | ``` 143 | ::: 144 | 145 | The generated Swagger UI looks like this: 146 | 147 | ![CRUD Example](/assets/images/crud-swagger-ui.png) 148 | -------------------------------------------------------------------------------- /docs/guide/defining-api-spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | # Defining API Spec 5 | You can easily define API specifications by passing types. To build an Open API schema, simply pass the necessary information as a generic type parameter of `Tspec.DefineApiSpec`. 6 | 7 | 8 | ## Basic Usage 9 | 10 | Let's define a simple `AuthorApiSpec`: 11 | 12 | ```ts{8} 13 | import { Tspec } from 'tspec'; 14 | 15 | interface Author { 16 | id: number; 17 | name: string; 18 | } 19 | 20 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 21 | paths: { 22 | '/authors': { 23 | get: { 24 | summary: 'List of authors', 25 | description: 'Returns a single author', 26 | responses: { 27 | 200: Author[], 28 | }, 29 | }, 30 | }, 31 | } 32 | }>; 33 | ``` 34 | 35 | :::details Generated OpenAPI Spec 36 | 37 | The paths and schemas in the generated OpenAPI Spec are described as follows: 38 | 39 | ```yaml{2} 40 | paths: 41 | "/authors": 42 | get: 43 | operationId: AuthorApiSpec_get_/authors 44 | summary: List of authors 45 | description: Returns a single author 46 | responses: 47 | '200': 48 | content: 49 | application/json: 50 | schema: 51 | type: array 52 | items: 53 | "$ref": "#/components/schemas/Author" 54 | components: 55 | schemas: 56 | Author: 57 | type: object 58 | properties: 59 | id: 60 | type: number 61 | name: 62 | type: string 63 | additionalProperties: false 64 | required: 65 | - id 66 | - name 67 | ``` 68 | ::: 69 | 70 | 71 | ## Parameters 72 | ### Path Parameters 73 | 74 | You can define path parameters by using the `path` property of the operation type. 75 | 76 | ::: code-group 77 | 78 | ```ts[Tspec]{6} 79 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 80 | paths: { 81 | '/authors/{id}': { 82 | get: { 83 | summary: 'Get author by id', 84 | path: { id: number }, 85 | responses: { 86 | 200: Author, 87 | }, 88 | }, 89 | }, 90 | } 91 | }>; 92 | ``` 93 | ::: 94 | 95 | :::details Generated OpenAPI Spec 96 | ```yaml{6-11} 97 | paths: 98 | "/authors/{id}": 99 | get: 100 | operationId: AuthorApiSpec_get_/authors/{id} 101 | summary: Get author by id 102 | parameters: 103 | - name: id 104 | in: path 105 | required: true 106 | schema: 107 | type: number 108 | responses: 109 | '200': 110 | content: 111 | application/json: 112 | schema: 113 | "$ref": "#/components/schemas/Author" 114 | ``` 115 | ::: 116 | 117 | 118 | Possible path parameter types are as follows: 119 | 120 | - `string` 121 | - `number` 122 | 123 | 124 | ### Query Parameters 125 | 126 | You can define query parameters by using the `query` property of the operation type. 127 | 128 | ::: code-group 129 | ```ts[Tspec]{6-9} 130 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 131 | paths: { 132 | '/authors': { 133 | get: { 134 | summary: 'List of authors', 135 | query: { 136 | limit: number, 137 | offset: number, 138 | }, 139 | responses: { 140 | 200: Author[], 141 | }, 142 | }, 143 | }, 144 | } 145 | }>; 146 | ``` 147 | ::: 148 | 149 | :::details Generated OpenAPI Spec 150 | ```yaml{6-14} 151 | paths: 152 | "/authors": 153 | get: 154 | operationId: AuthorApiSpec_get_/authors 155 | summary: List of authors 156 | parameters: 157 | - name: limit 158 | in: query 159 | schema: 160 | type: number 161 | - name: offset 162 | in: query 163 | schema: 164 | type: number 165 | responses: 166 | '200': 167 | content: 168 | application/json: 169 | schema: 170 | type: array 171 | items: 172 | "$ref": "#/components/schemas/Author" 173 | ``` 174 | ::: 175 | 176 | Possible query parameter types are as follows: 177 | 178 | - `string` 179 | - `number` 180 | - `boolean` 181 | - `string[]` 182 | - `number[]` 183 | - `boolean[]` 184 | 185 | ### Header and Cookie Parameters 186 | 187 | You can define header and cookie parameters by using the `header` and `cookie` properties of the operation type. 188 | 189 | ::: code-group 190 | ```ts[Tspec]{6-11} 191 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 192 | paths: { 193 | '/authors': { 194 | get: { 195 | summary: 'List of authors', 196 | header: { 197 | 'x-api-key': string, 198 | }, 199 | cookie: { 200 | 'debug': 0 | 1, 201 | }, 202 | responses: { 203 | 200: Author[], 204 | }, 205 | }, 206 | }, 207 | } 208 | }>; 209 | ``` 210 | ::: 211 | 212 | :::details Generated OpenAPI Spec 213 | ```yaml{7-18} 214 | paths: 215 | "/authors": 216 | get: 217 | operationId: AuthorApiSpec_get_/authors 218 | summary: List of authors 219 | parameters: 220 | - name: x-api-key 221 | in: header 222 | schema: 223 | type: string 224 | required: true 225 | - name: debug 226 | in: cookie 227 | schema: 228 | enum: 229 | - 0 230 | - 1 231 | required: true 232 | responses: 233 | '200': 234 | content: 235 | application/json: 236 | schema: 237 | type: array 238 | items: 239 | "$ref": "#/components/schemas/Author" 240 | ``` 241 | ::: 242 | 243 | Possible header and cookie parameter types are as follows: 244 | - `string` 245 | - `number` 246 | 247 | 248 | ### Request Body 249 | 250 | You can define request body by using the `body` property of the operation type. 251 | 252 | ::: code-group 253 | ```ts[Tspec]{6} 254 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 255 | paths: { 256 | '/authors': { 257 | post: { 258 | summary: 'Create an author', 259 | body: Author, 260 | responses: { 261 | 201: Author, 262 | }, 263 | }, 264 | }, 265 | } 266 | }>; 267 | ``` 268 | ::: 269 | 270 | :::details Generated OpenAPI Spec 271 | ```yaml{6-11} 272 | paths: 273 | "/authors": 274 | post: 275 | operationId: AuthorApiSpec_post_/authors 276 | summary: Create an author 277 | requestBody: 278 | required: true 279 | content: 280 | application/json: 281 | schema: 282 | "$ref": "#/components/schemas/Author" 283 | responses: 284 | '201': 285 | content: 286 | application/json: 287 | schema: 288 | "$ref": "#/components/schemas/Author" 289 | ``` 290 | ::: 291 | 292 | 293 | Body parameter types can be any object type. 294 | 295 | ## Base Path 296 | 297 | You can define the base path by using the `basePath` property of the API spec type. 298 | 299 | ::: code-group 300 | ```ts[Tspec]{2,4,12} 301 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 302 | basePath: '/authors', 303 | paths: { 304 | '/': { 305 | get: { 306 | summary: 'List of authors', 307 | responses: { 308 | 200: Author[], 309 | }, 310 | }, 311 | }, 312 | '/{id}': { 313 | get: { 314 | summary: 'Get author by id', 315 | path: { id: number }, 316 | responses: { 317 | 200: Author, 318 | }, 319 | }, 320 | }, 321 | } 322 | }>; 323 | ``` 324 | ::: 325 | 326 | :::details Generated OpenAPI Spec 327 | ```yaml{2,14} 328 | paths: 329 | "/authors": 330 | get: 331 | operationId: AuthorApiSpec_get_/authors 332 | summary: List of authors 333 | responses: 334 | '200': 335 | content: 336 | application/json: 337 | schema: 338 | type: array 339 | items: 340 | "$ref": "#/components/schemas/Author" 341 | "/authors/{id}": 342 | get: 343 | operationId: AuthorApiSpec_get_/authors/{id} 344 | summary: Get author by id 345 | parameters: 346 | - name: id 347 | in: path 348 | required: true 349 | schema: 350 | type: number 351 | responses: 352 | '200': 353 | content: 354 | application/json: 355 | schema: 356 | "$ref": "#/components/schemas/Author" 357 | ``` 358 | ::: 359 | 360 | ## Tags 361 | 362 | You can define tags by using the `tags` property of the API spec type. 363 | 364 | ::: code-group 365 | ```ts[Tspec]{2} 366 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 367 | tags: ['Author'], 368 | paths: { 369 | '/authors': { 370 | get: { 371 | summary: 'List of authors', 372 | responses: { 373 | 200: Author[], 374 | }, 375 | }, 376 | }, 377 | } 378 | }>; 379 | ``` 380 | ::: 381 | 382 | :::details Generated OpenAPI Spec 383 | ```yaml{5-6} 384 | paths: 385 | "/authors": 386 | get: 387 | operationId: AuthorApiSpec_get_/authors 388 | tags: 389 | - Author 390 | summary: List of authors 391 | responses: 392 | '200': 393 | content: 394 | application/json: 395 | schema: 396 | type: array 397 | items: 398 | "$ref": "#/components/schemas/Author" 399 | ``` 400 | ::: 401 | 402 | If you want to define tags for each operation, you can use the `tags` property of the operation type. 403 | 404 | ::: code-group 405 | ```ts[Tspec]{6} 406 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 407 | paths: { 408 | '/authors': { 409 | get: { 410 | summary: 'List of authors', 411 | tags: ['Author'], 412 | responses: { 413 | 200: Author[], 414 | }, 415 | }, 416 | }, 417 | } 418 | }>; 419 | ``` 420 | ::: 421 | 422 | :::details Generated OpenAPI Spec 423 | ```yaml{5-6} 424 | paths: 425 | "/authors": 426 | get: 427 | operationId: AuthorApiSpec_get_/authors 428 | tags: 429 | - Author 430 | summary: List of authors 431 | responses: 432 | '200': 433 | content: 434 | application/json: 435 | schema: 436 | type: array 437 | items: 438 | "$ref": "#/components/schemas/Author" 439 | ``` 440 | ::: 441 | 442 | ## OperationId 443 | 444 | You can define operationId by using the `operationId` property of the operation type. 445 | 446 | ::: code-group 447 | ```ts[Tspec]{5} 448 | export type AuthorApiSpec = Tspec.DefineApiSpec<{ 449 | paths: { 450 | '/authors': { 451 | get: { 452 | operationId: 'listAuthors', 453 | summary: 'List of authors', 454 | responses: { 455 | 200: Author[], 456 | }, 457 | }, 458 | }, 459 | } 460 | }>; 461 | ``` 462 | ::: 463 | 464 | :::details Generated OpenAPI Spec 465 | ```yaml{4} 466 | paths: 467 | "/authors": 468 | get: 469 | operationId: listAuthors 470 | summary: List of authors 471 | responses: 472 | '200': 473 | content: 474 | application/json: 475 | schema: 476 | type: array 477 | items: 478 | "$ref": "#/components/schemas/Author" 479 | ``` 480 | ::: 481 | 482 | -------------------------------------------------------------------------------- /docs/guide/describing-schema.md: -------------------------------------------------------------------------------- 1 | # Defining Schema 2 | 3 | On this page, we will learn how to define schemas with [JSDoc](https://jsdoc.app/about-getting-started.html) comments. 4 | 5 | ## Description 6 | 7 | You can describe the schema with JSDoc comments. 8 | 9 | ```ts{1,3,5} 10 | /** Book schema Info */ 11 | interface Book { 12 | /** Book ID */ 13 | id: number; 14 | /** Title of the book */ 15 | title: string; 16 | } 17 | ``` 18 | 19 | :::details Generated OpenAPI Spec 20 | ```yaml{4,9,12} 21 | components: 22 | schemas: 23 | Book: 24 | description: Book schema Info 25 | type: object 26 | properties: 27 | id: 28 | type: number 29 | description: Book ID 30 | title: 31 | type: string 32 | description: Title of the book 33 | required: 34 | - id 35 | - title 36 | ``` 37 | ::: 38 | 39 | ## Example 40 | 41 | If you want to add an example to the schema, you can use the `@example` tag. 42 | 43 | ```ts{4,9} 44 | interface Book { 45 | /** 46 | * Book ID 47 | * @example 123456789 48 | * */ 49 | id: number; 50 | /** 51 | * Title of the book 52 | * @example The Great Gatsby 53 | */ 54 | title: string; 55 | } 56 | ``` 57 | 58 | :::details Generated OpenAPI Spec 59 | 60 | ```yaml{9,13} 61 | components: 62 | schemas: 63 | Book: 64 | type: object 65 | properties: 66 | id: 67 | type: number 68 | description: Book ID 69 | example: 123456789 70 | title: 71 | type: string 72 | description: Title of the book 73 | example: The Great Gatsby 74 | required: 75 | - id 76 | - title 77 | ``` 78 | ::: 79 | 80 | ## Format 81 | 82 | If you want to specify the format of the schema, you can use the utility types provided by Tspec. 83 | 84 | ```ts{4,5,6} 85 | import { Tspec } from 'tspec'; 86 | 87 | interface Book { 88 | id: Tspec.Integer; 89 | coverImage: Tspec.ImageUriString; 90 | publishedAt: Tspec.DateTimeString; 91 | } 92 | ``` 93 | 94 | :::details Generated OpenAPI Spec 95 | ```yaml{7-8,10-12,14-16} 96 | components: 97 | schemas: 98 | Book: 99 | type: object 100 | properties: 101 | id: 102 | type: integer 103 | example: 1 104 | coverImage: 105 | format: uri 106 | type: string 107 | example: https://picsum.photos/200/300 108 | publishedAt: 109 | format: date-time 110 | type: string 111 | example: '2023-03-30T12:00:00Z' 112 | additionalProperties: false 113 | required: 114 | - coverImage 115 | - id 116 | - publishedAt 117 | ``` 118 | ::: 119 | 120 | The following utility types are provided by Tspec. 121 | 122 | | Type | Description | 123 | | --- | --- | 124 | | `Tspec.Integer` | Integer | 125 | | `Tspec.DateString` | Date string (e.g. `2021-01-01`) | 126 | | `Tspec.DateTimeString` | Date and time string (e.g. `2021-01-01T00:00:00Z`) | 127 | | `Tspec.PasswordString` | Password string (e.g. `password`) | 128 | | `Tspec.ByteString` | Byte string (e.g. `dGVzdA==`) | 129 | | `Tspec.BinaryString` | Binary string (e.g. `0x1234`) | 130 | | `Tspec.BinaryStringArray` | Binary string array (e.g. `[0x1234, 0x5678]`) | 131 | | `Tspec.EmailString` | Email string (e.g. `test@test.com`) | 132 | | `Tspec.UuidString` | UUID string (e.g. `123e4567-e89b-12d3-a456-426614174000`) | 133 | | `Tspec.UrlString` | URL string (e.g. `http://localhost`) | 134 | | `Tspec.ImageUriString` | Image URI string (e.g. `https://picsum.photos/200/300`) | 135 | | `Tspec.HostnameString` | Hostname string (e.g. `localhost`) | 136 | | `Tspec.Ipv4String` | IPv4 string (e.g. `127.0.0.1`) | 137 | | `Tspec.Ipv6String` | IPv6 string (e.g. `::1`) | 138 | | `Tspec.JsonPointerString` | JSON Pointer string (e.g. `/`) | 139 | 140 | -------------------------------------------------------------------------------- /docs/guide/express-integration.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Express Integration 6 | 7 | If you are using the [Express framework](https://expressjs.com/), you can integrate Tspec into your project easily. 8 | Tspec automatically parses your [Express](https://expressjs.com/) handler type to generate parameters and responses schemas and provides middleware to serve Swagger UI. 9 | 10 | ## Setup Express Application 11 | 12 | Before integrating Tspec, assume that you have the following Express controller: 13 | 14 | ::: code-group 15 | ```ts[controller.ts] 16 | import { Request, Response } from 'express'; 17 | 18 | type PathParams = { id: string }; 19 | type AuthorRes = { id: string, name: string }; 20 | 21 | export const getAuthor = async ( 22 | req: Request, 23 | res: Response, 24 | ) => { 25 | res.json({ 26 | id: req.params.id, 27 | name: 'Author Name', 28 | }); 29 | }; 30 | ``` 31 | ::: 32 | 33 | 34 | :::tip 35 | The controller type can be written in a more concise way using `Express.RequestHandler` type: 36 | 37 | ```ts{3,5} 38 | import { RequestHandler } from 'express'; 39 | 40 | type GetAuthor = RequestHandler; 41 | 42 | export const getAuthor: GetAuthor = async (req, res) => { 43 | res.json({ 44 | id: req.params.id, 45 | name: 'Author Name', 46 | }); 47 | }; 48 | ``` 49 | ::: 50 | 51 | ## Defining API Spec 52 | 53 | Now, let's define the API spec in using `Tspec.DefineApiSpec` type. 54 | 55 | ::: code-group 56 | ```ts[index.ts]{10} 57 | import { Tspec } from 'tspec'; 58 | 59 | import * as controller from './controller' 60 | 61 | export type ApiSpec = Tspec.DefineApiSpec<{ 62 | paths: { 63 | '/authors/{id}': { 64 | get: { 65 | summary: 'Get author by id', 66 | handler: typeof controller.getAuthor, 67 | }, 68 | }, 69 | }, 70 | }>; 71 | ``` 72 | ::: 73 | 74 | You can pass the controllers type to the `handler` property of the spec to generate parameters(`path`, `query`, `body`) and responses schemas automatically. 75 | 76 | 77 | 78 | ## Serving API Document 79 | 80 | Tspec provides `TspecDocsMiddleware` to serve Swagger UI. 81 | 82 | Let's add the middleware to your Express server: 83 | 84 | ::: code-group 85 | ```ts[index.ts]{2,20} 86 | import express from 'express'; 87 | import { Tspec, TspecDocsMiddleware } from 'tspec'; 88 | 89 | import * as controller from './controller' 90 | 91 | export type ApiSpec = Tspec.DefineApiSpec<{ 92 | paths: { 93 | '/authors/{id}': { 94 | get: { 95 | summary: 'Get author by id', 96 | handler: typeof controller.getAuthor, 97 | }, 98 | }, 99 | }, 100 | }>; 101 | 102 | const initServer = async () => { 103 | const app = express() 104 | app.get('/authors/:id', controller.getAuthor); 105 | app.use('/docs', await TspecDocsMiddleware()); 106 | app.listen(3000); 107 | } 108 | initServer(); 109 | ``` 110 | ::: 111 | 112 | ::: tip 113 | You can pass `Tspec.GenerateParams` options to the first parameter of `TspecDocsMiddleware`. 114 | ::: 115 | 116 | 117 | 118 | Then, you can access Swagger UI at `http://localhost:3000/docs` 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/guide/file-upload-download.md: -------------------------------------------------------------------------------- 1 | # File Upload and Download 2 | 3 | On this page, we will learn how to generate OpenAPI Spec for file upload and download. 4 | 5 | ## File Upload 6 | 7 | You can describe the file upload schema with JSDoc tag `@mediaType multipart/form-data`. 8 | :::tip 9 | Also, you can use other media types such as `image/png` or `application/pdf`. 10 | See OpenAPI File Upload Documentation [here](https://swagger.io/docs/specification/describing-request-body/file-upload/). 11 | ::: 12 | 13 | 14 | ```ts{6-9} 15 | export type FileApiSpec = Tspec.DefineApiSpec<{ 16 | paths: { 17 | '/files/upload': { 18 | post: { 19 | summary: 'Upload File', 20 | /** @mediaType multipart/form-data */ 21 | body: { 22 | file: Tspec.BinaryString; 23 | }, 24 | responses: { 200: { fileName: string } }, 25 | }, 26 | }, 27 | }, 28 | }>; 29 | ``` 30 | 31 | :::details Generated OpenAPI Spec 32 | ```yaml{6-15} 33 | paths: 34 | "/files/upload": 35 | post: 36 | operationId: FileApiSpec_post_/files/upload 37 | summary: Upload File 38 | requestBody: 39 | required: true 40 | content: 41 | multipart/form-data: 42 | schema: 43 | type: object 44 | properties: 45 | file: 46 | type: string 47 | format: binary 48 | responses: 49 | '200': 50 | content: 51 | application/json: 52 | schema: 53 | type: object 54 | properties: 55 | fileName: 56 | type: string 57 | ``` 58 | ::: 59 | 60 | Then, you can see the file upload form in Swagger UI. 61 | ![File Upload](/assets/images/file-upload.png) 62 | 63 | 64 | ## File Download 65 | 66 | You can describe the file download schema with JSDoc tag `@mediaType application/octet-stream`. 67 | :::tip 68 | Also, you can use other media types such as `image/png` or `application/pdf`. 69 | See OpenAPI Describing Responses Documentation [here](https://swagger.io/docs/specification/describing-responses/#file). 70 | ::: 71 | 72 | ```ts{6-9} 73 | export type FileApiSpec = Tspec.DefineApiSpec<{ 74 | paths: { 75 | '/files/download': { 76 | get: { 77 | summary: 'Download File', 78 | responses: { 79 | /** @mediaType application/octet-stream */ 80 | 200: Tspec.BinaryString 81 | }, 82 | }, 83 | }, 84 | }, 85 | }>; 86 | ``` 87 | 88 | :::details Generated OpenAPI Spec 89 | ```yaml{8-12} 90 | paths: 91 | "/files/download": 92 | get: 93 | operationId: FileApiSpec_get_/files/download 94 | summary: Download File 95 | responses: 96 | '200': 97 | content: 98 | application/octet-stream: 99 | schema: 100 | type: string 101 | format: binary 102 | ``` 103 | ::: 104 | 105 | Then, you can download the file in Swagger UI. 106 | ![File Download](/assets/images/file-download.png) 107 | 108 | ## Multiple File Uploads 109 | 110 | If you want to upload multiple files, you can use `Tspec.BinaryStringArray` type. 111 | ```ts{6-9} 112 | export type FileApiSpec = Tspec.DefineApiSpec<{ 113 | paths: { 114 | '/files/multiple-upload': { 115 | post: { 116 | summary: 'Upload Files', 117 | /** @mediaType multipart/form-data */ 118 | body: { 119 | files: Tspec.BinaryStringArray; 120 | }, 121 | responses: { 200: { fileNames: string[] } }, 122 | }, 123 | }, 124 | }, 125 | }>; 126 | ``` 127 | 128 | -------------------------------------------------------------------------------- /docs/guide/generating-document.md: -------------------------------------------------------------------------------- 1 | # Generating Document 2 | 3 | On this page, we will learn how to generate OpenAPI Spec and serve it with [Swagger UI](https://swagger.io/tools/swagger-ui/) using `Tspec`. 4 | 5 | ## CLI Usage 6 | 7 | ### **`generate`** 8 | 9 | Generate OpenAPI Spec from TypeScript types. 10 | 11 | **Usage** 12 | 13 | ::: code-group 14 | ```bash [yarn] 15 | yarn tspec generate [options] 16 | ``` 17 | 18 | ```bash [npm] 19 | npx tspec generate [options] 20 | ``` 21 | 22 | ```bash [pnpm] 23 | pnpx tspec generate [options] 24 | ``` 25 | ::: 26 | 27 | ::: details options 28 | 29 | |Option|Type|Description|Example| 30 | |-|-|-|-| 31 | |`--specPathGlobs [path]`|string[]|Path of TypeScript files which you want to generate OpenAPI schema|`src/**/*.ts`| 32 | |`--tsconfigPath [path]`|string|Path of tsconfig|`./tsconfig.json`| 33 | |`--outputPath [path]`|string|Path of output OpenAPI schema|`./generate/openapi.yaml`| 34 | |`--specVersion [version]`|number|Version to use for OpenAPI schema (Currently supports only 3)|`3`| 35 | |`--openapiTitle [title]`|string|`title` property of OpenAPI schema|`Tspec`| 36 | |`--openapiVersion [version]`|string|`version` property of OpenAPI schema|`1.0.0`| 37 | |`--openapiDescription [description]`|string|`description` property of OpenAPI schema|`This is Tspec API`| 38 | |`--debug [true / false]`|boolean|Print debug information for Tspec|`false`| 39 | |`--ignoreErrors [true / false]`|boolean|Whether ignore errors in Tspec or not|`false`| 40 | ::: 41 | 42 | ### **`server`** 43 | 44 | Start Tspec server for display OpenAPI Spec with Swagger UI. 45 | 46 | **Usage** 47 | 48 | ::: code-group 49 | ```bash [yarn] 50 | yarn tspec server [options] 51 | ``` 52 | 53 | ```bash [npm] 54 | npx tspec server [options] 55 | ``` 56 | 57 | ```bash [pnpm] 58 | pnpx tspec server [options] 59 | ``` 60 | ::: 61 | 62 | ::: details options 63 | 64 | **You can also use the CLI options for [`generate`](#generate) command.** 65 | 66 | |Option|Type|Description|Example| 67 | |-|-|-|-| 68 | |`--port [port]`|number|Specify port number for Tspec server|`7080`| 69 | |`--proxyHost [host]`|string|Specify proxy host for Tspec server|`https://tspec.org`| 70 | 71 | ::: 72 | 73 | ### Configuration file 74 | 75 | You can also use configuration file for `generate` and `server` command. 76 | 77 | Create `tspec.config.json` in your project root directory. 78 | 79 | ```json 80 | { 81 | "specPathGlobs": ["src/**/*.ts"], 82 | "tsconfigPath": "./tsconfig.json", 83 | "outputPath": "./generate/openapi.json", 84 | "specVersion": 3, 85 | "openapi": { 86 | "title": "Tspec API", 87 | "version": "0.0.1", 88 | "description": "This is Tspec API", 89 | }, 90 | "debug": false, 91 | "ignoreErrors": true, 92 | } 93 | ``` 94 | 95 | The type of configuration file is `Tspec.GenerateParams` 96 | 97 | 98 | 99 | 100 | ## Programmatic Usage 101 | 102 | ### **`generate`** 103 | 104 | 105 | ```ts 106 | import { generateTspec, Tspec } from 'tspec'; 107 | 108 | const options: Tspec.GenerateParams = { 109 | specPathGlobs: ['src/**/*.ts'], 110 | tsconfigPath: './tsconfig.json', 111 | outputPath: './generate/openapi.json', 112 | specVersion: 3, 113 | openapi: { 114 | title: 'Tspec API', 115 | version: '0.0.1', 116 | description: "This is Tspec API", 117 | }, 118 | debug: false, 119 | ignoreErrors: true, 120 | }; 121 | 122 | const openApiSpec = await generateTspec(options); 123 | ``` 124 | 125 | 126 | ### **`server`** 127 | 128 | ```ts 129 | import { initTspecServer, Tspec } from 'tspec'; 130 | 131 | const options: Tspec.InitTspecServerOptions = { 132 | specPathGlobs: ['src/**/*.ts'], 133 | tsconfigPath: './tsconfig.json', 134 | outputPath: './generate/openapi.json', 135 | specVersion: 3, 136 | openapi: { 137 | title: 'Tspec API', 138 | version: '0.0.1', 139 | description: "This is Tspec API", 140 | }, 141 | debug: false, 142 | ignoreErrors: true, 143 | port: 3000, 144 | proxyHost: 'https://tspec.org', 145 | }; 146 | 147 | initTspecServer(options); 148 | ``` 149 | 150 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Getting Started 6 | 7 | On this page, we will learn how to use tspec to generate [OpenAPI Specification](https://swagger.io/specification/) from TypeScript types and serve it with [Swagger UI](https://swagger.io/tools/swagger-ui/). 8 | 9 | 10 | ## Installing 11 | 12 | Assuming you have [Node.js](https://nodejs.org/en/) and [TypeScript](https://www.typescriptlang.org/) installed, create a new typescript project: 13 | 14 | ```bash 15 | mkdir my-project 16 | cd my-project 17 | ``` 18 | 19 | Then, initialize `tsconfig.json`: 20 | 21 | ```bash 22 | tsc --init 23 | ``` 24 | 25 | Now, initialize `package.json` and install tspec: 26 | 27 | ::: code-group 28 | ```bash [yarn] 29 | yarn init -y 30 | yarn add tspec 31 | ``` 32 | 33 | ```bash [npm] 34 | npm init -y 35 | npm install tspec 36 | ``` 37 | 38 | ```bash [pnpm] 39 | pnpm init -y 40 | pnpm add tspec 41 | ``` 42 | ::: 43 | 44 | ## Define ApiSpec 45 | 46 | Let's define a simple `Book` type and `BookApiSpec`: 47 | 48 | ::: code-group 49 | ```ts[index.ts]{4,11} 50 | import { Tspec } from "tspec"; 51 | 52 | /** Schema description defined by JSDoc */ 53 | interface Book { 54 | /** Field description defined by JSDoc */ 55 | id: number; 56 | title: string; 57 | description?: string; 58 | } 59 | 60 | export type BookApiSpec = Tspec.DefineApiSpec<{ 61 | paths: { 62 | '/books/{id}': { 63 | get: { 64 | summary: 'Get book by id', 65 | path: { id: number }, 66 | responses: { 200: Book }, 67 | }, 68 | }, 69 | } 70 | }>; 71 | ``` 72 | ::: 73 | 74 | ## Generate OpenAPI Spec 75 | 76 | Now, let's generate OpenAPI Spec from `BookApiSpec`: 77 | 78 | ::: code-group 79 | ```bash [yarn] 80 | yarn tspec generate --outputPath openapi.json 81 | ``` 82 | 83 | ```bash [npm] 84 | npx tspec generate --outputPath openapi.json 85 | ``` 86 | 87 | ```bash [pnpm] 88 | pnpm tspec generate --outputPath openapi.json 89 | ``` 90 | ::: 91 | 92 | :::details Generated OpenAPI Spec 93 | (For readability, the generated OpenAPI Spec is formatted with yaml) 94 | 95 | ```yaml{6,27} 96 | openapi: 3.0.3 97 | info: 98 | title: Tspec API 99 | version: 0.0.1 100 | paths: 101 | /books/{id}: 102 | get: 103 | operationId: BookApiSpec_get_/books/{id} 104 | tags: 105 | - Book 106 | summary: Get book by id 107 | parameters: 108 | - name: id 109 | in: path 110 | required: true 111 | schema: 112 | type: number 113 | responses: 114 | '200': 115 | description: OK 116 | content: 117 | application/json: 118 | schema: 119 | $ref: '#/components/schemas/Book' 120 | components: 121 | schemas: 122 | Book: 123 | description: Schema description defined by JSDoc 124 | type: object 125 | properties: 126 | id: 127 | description: Field description defined by JSDoc 128 | type: number 129 | title: 130 | type: string 131 | description: 132 | type: string 133 | required: 134 | - id 135 | - title 136 | ``` 137 | ::: 138 | 139 | ::: tip 140 | Tspec automatically parses ApiSpec from any files that match the glob pattern `**/*.ts` in the current working directory. 141 | 142 | If you want to specify a different spec path, you can use the `--specPathGlobs` option. 143 | ::: 144 | 145 | 146 | ## Serve OpenAPI Spec 147 | 148 | Now, let's serve the OpenAPI Spec with [Swagger UI](https://swagger.io/tools/swagger-ui/): 149 | 150 | ::: code-group 151 | ```bash [yarn] 152 | yarn tspec server --port 3000 153 | ``` 154 | 155 | ```bash [npm] 156 | npx tspec server --port 3000 157 | ``` 158 | 159 | ```bash [pnpm] 160 | pnpm tspec server --port 3000 161 | ``` 162 | ::: 163 | 164 | Then, open `http://localhost:3000/docs` in your browser. 165 | 166 | You will see the Swagger UI page: 167 | 168 | ![Swagger UI API](/assets/images/getting-started-swagger-ui-1.png) 169 | 170 | And you can see schema definitions in the `Schemas` tab. 171 | 172 | ![Swagger UI Schema](/assets/images/getting-started-swagger-ui-2.png) 173 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-spec/tspec/00bc48de816c90cae1aa65aaaf0a35bd06ff14ae/docs/guide/index.md -------------------------------------------------------------------------------- /docs/guide/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # TroubleShooting 6 | 7 | ## Debug 8 | If you are having trouble, deubgging might help. Turn on the debug mode using `generateTspec()` parameters. 9 | 10 | ```js{3} 11 | await generateTspec({ 12 | ... 13 | debug: true 14 | }) 15 | ``` 16 | 17 | 18 | ## Loading program 19 | If you are getting empty OpenAPI documents, it is possible that you failed to pass the program to `generateTspec` method. Here are some settings that might help. 20 | 21 | **Basic setting** 22 | Pass the path to the `tsconfig.json` file using `tsconfigPath` parmater. 23 | ```js{3} 24 | await generateTspec({ 25 | ... 26 | tsconfigPath: '/path/to/program/tsconfig.json' 27 | }) 28 | ``` 29 | 30 | **Loading programs with errors** 31 | Set `ignoreErrors` to `true` in order to use programs that includes errors. 32 | ```js{3} 33 | await generateTspec({ 34 | ... 35 | ignoreErrors: true 36 | }) 37 | ``` 38 | 39 | **Passing program paths** 40 | If the program fails to find the location of the source files, you can directly pass program source file paths using `specPathGlobs` 41 | ```js{3} 42 | await generateTspec({ 43 | ... 44 | specPathGlobs: ['/path/to/src1', '/path/to/src2'] 45 | }) 46 | ``` -------------------------------------------------------------------------------- /docs/guide/tspec-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | # tspec.config Options 5 | 6 | You can use configuration file for Tspec. 7 | 8 | To use it, configure Tspec with `tspec.config.json` file and locate it at the root of your project. 9 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Tspec" 7 | text: "Type-driven\nAPI Documentation" 8 | tagline: Auto-generating REST API document based on TypeScript types 9 | actions: 10 | - theme: brand 11 | text: Get Started 12 | link: /guide/getting-started 13 | - theme: alt 14 | text: View on GitHub ⭐️ 15 | link: https://github.com/ts-spec/tspec 16 | 17 | features: 18 | - title: Type-driven OpenAPI 19 | # - title: TypeScript ♥ OpenAPI 20 | icon: 📝 21 | details: Automatically parses your TypeScript types and generates up-to-date OpenAPI specification. 22 | - title: Swagger UI Integration 23 | icon: 💎 24 | details: Tspec integrates Swagger UI to provide a beautiful and interactive API documentation. 25 | - title: Zero Configuration 26 | icon: ⚡️ 27 | details: Tspec is designed to be zero-configuration. You can start using it right away without any configuration. 28 | --- 29 | 30 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Introduction 6 | 7 | ## Overview 8 | 9 | Tspec is a library to generate API document from [TypeScript](https://www.typescriptlang.org/) types. 10 | 11 | It automatically parses your TypeScript types and generates up-to-date [OpenAPI Specification](https://swagger.io/specification/) with beautiful [Swagger UI](https://swagger.io/tools/swagger-ui/). 12 | 13 | 14 | ## Why tspec? 15 | 16 | - **Code First**: Rely on TypeScript type and JSDoc to generate OpenAPI Specification. 17 | - **Easy to learn**: No need to learn new OpenAPI Spec syntax. Just use TypeScript types. 18 | - **Easy to use**: Only few lines of code are needed to generate OpenAPI Specification. 19 | - **Flexible**: You can use any framework you want. It doesn't impose any framework-specific constraints. 20 | -------------------------------------------------------------------------------- /docs/markdown-examples.md: -------------------------------------------------------------------------------- 1 | # Markdown Extension Examples 2 | 3 | This page demonstrates some of the built-in markdown extensions provided by VitePress. 4 | 5 | ## Syntax Highlighting 6 | 7 | VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: 8 | 9 | **Input** 10 | 11 | ```` 12 | ```js{4} 13 | export default { 14 | data () { 15 | return { 16 | msg: 'Highlighted!' 17 | } 18 | } 19 | } 20 | ``` 21 | ```` 22 | 23 | **Output** 24 | 25 | ```js{4} 26 | export default { 27 | data () { 28 | return { 29 | msg: 'Highlighted!' 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ## Custom Containers 36 | 37 | **Input** 38 | 39 | ```md 40 | ::: info 41 | This is an info box. 42 | ::: 43 | 44 | ::: tip 45 | This is a tip. 46 | ::: 47 | 48 | ::: warning 49 | This is a warning. 50 | ::: 51 | 52 | ::: danger 53 | This is a dangerous warning. 54 | ::: 55 | 56 | ::: details 57 | This is a details block. 58 | ::: 59 | ``` 60 | 61 | **Output** 62 | 63 | ::: info 64 | This is an info box. 65 | ::: 66 | 67 | ::: tip 68 | This is a tip. 69 | ::: 70 | 71 | ::: warning 72 | This is a warning. 73 | ::: 74 | 75 | ::: danger 76 | This is a dangerous warning. 77 | ::: 78 | 79 | ::: details 80 | This is a details block. 81 | ::: 82 | 83 | ## More 84 | 85 | Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). 86 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vitepress dev", 4 | "docs:build": "vitepress build", 5 | "docs:preview": "vitepress preview" 6 | }, 7 | "devDependencies": { 8 | "vitepress": "^1.0.0-alpha.72" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@algolia/autocomplete-core@1.7.4": 6 | version "1.7.4" 7 | resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.7.4.tgz#85ff36b2673654a393c8c505345eaedd6eaa4f70" 8 | integrity sha512-daoLpQ3ps/VTMRZDEBfU8ixXd+amZcNJ4QSP3IERGyzqnL5Ch8uSRFt/4G8pUvW9c3o6GA4vtVv4I4lmnkdXyg== 9 | dependencies: 10 | "@algolia/autocomplete-shared" "1.7.4" 11 | 12 | "@algolia/autocomplete-preset-algolia@1.7.4": 13 | version "1.7.4" 14 | resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.4.tgz#610ee1d887962f230b987cba2fd6556478000bc3" 15 | integrity sha512-s37hrvLEIfcmKY8VU9LsAXgm2yfmkdHT3DnA3SgHaY93yjZ2qL57wzb5QweVkYuEBZkT2PIREvRoLXC2sxTbpQ== 16 | dependencies: 17 | "@algolia/autocomplete-shared" "1.7.4" 18 | 19 | "@algolia/autocomplete-shared@1.7.4": 20 | version "1.7.4" 21 | resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.4.tgz#78aea1140a50c4d193e1f06a13b7f12c5e2cbeea" 22 | integrity sha512-2VGCk7I9tA9Ge73Km99+Qg87w0wzW4tgUruvWAn/gfey1ZXgmxZtyIRBebk35R1O8TbK77wujVtCnpsGpRy1kg== 23 | 24 | "@algolia/cache-browser-local-storage@4.17.0": 25 | version "4.17.0" 26 | resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.17.0.tgz#4c54a9b1795dcc1cd9f9533144f7df3057984d39" 27 | integrity sha512-myRSRZDIMYB8uCkO+lb40YKiYHi0fjpWRtJpR/dgkaiBlSD0plRyB6lLOh1XIfmMcSeBOqDE7y9m8xZMrXYfyQ== 28 | dependencies: 29 | "@algolia/cache-common" "4.17.0" 30 | 31 | "@algolia/cache-common@4.17.0": 32 | version "4.17.0" 33 | resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.17.0.tgz#bc3da15548df585b44d76c55e66b0056a2b3f917" 34 | integrity sha512-g8mXzkrcUBIPZaulAuqE7xyHhLAYAcF2xSch7d9dABheybaU3U91LjBX6eJTEB7XVhEsgK4Smi27vWtAJRhIKQ== 35 | 36 | "@algolia/cache-in-memory@4.17.0": 37 | version "4.17.0" 38 | resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.17.0.tgz#eb55a92cb8eb8641903a2b23fd6d05ebdaca2010" 39 | integrity sha512-PT32ciC/xI8z919d0oknWVu3kMfTlhQn3MKxDln3pkn+yA7F7xrxSALysxquv+MhFfNAcrtQ/oVvQVBAQSHtdw== 40 | dependencies: 41 | "@algolia/cache-common" "4.17.0" 42 | 43 | "@algolia/client-account@4.17.0": 44 | version "4.17.0" 45 | resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.17.0.tgz#4b13e5a8e50a06be1f3289d9db337096ebc66b73" 46 | integrity sha512-sSEHx9GA6m7wrlsSMNBGfyzlIfDT2fkz2u7jqfCCd6JEEwmxt8emGmxAU/0qBfbhRSuGvzojoLJlr83BSZAKjA== 47 | dependencies: 48 | "@algolia/client-common" "4.17.0" 49 | "@algolia/client-search" "4.17.0" 50 | "@algolia/transporter" "4.17.0" 51 | 52 | "@algolia/client-analytics@4.17.0": 53 | version "4.17.0" 54 | resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.17.0.tgz#1b36ffbe913b7b4d8900bc15982ca431f47a473c" 55 | integrity sha512-84ooP8QA3mQ958hQ9wozk7hFUbAO+81CX1CjAuerxBqjKIInh1fOhXKTaku05O/GHBvcfExpPLIQuSuLYziBXQ== 56 | dependencies: 57 | "@algolia/client-common" "4.17.0" 58 | "@algolia/client-search" "4.17.0" 59 | "@algolia/requester-common" "4.17.0" 60 | "@algolia/transporter" "4.17.0" 61 | 62 | "@algolia/client-common@4.17.0": 63 | version "4.17.0" 64 | resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.17.0.tgz#67fd898006e3ac359ea3e3ed61abfc26147ffa53" 65 | integrity sha512-jHMks0ZFicf8nRDn6ma8DNNsdwGgP/NKiAAL9z6rS7CymJ7L0+QqTJl3rYxRW7TmBhsUH40wqzmrG6aMIN/DrQ== 66 | dependencies: 67 | "@algolia/requester-common" "4.17.0" 68 | "@algolia/transporter" "4.17.0" 69 | 70 | "@algolia/client-personalization@4.17.0": 71 | version "4.17.0" 72 | resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.17.0.tgz#428d9f4762c22856b6062bb54351eb31834db6c1" 73 | integrity sha512-RMzN4dZLIta1YuwT7QC9o+OeGz2cU6eTOlGNE/6RcUBLOU3l9tkCOdln5dPE2jp8GZXPl2yk54b2nSs1+pAjqw== 74 | dependencies: 75 | "@algolia/client-common" "4.17.0" 76 | "@algolia/requester-common" "4.17.0" 77 | "@algolia/transporter" "4.17.0" 78 | 79 | "@algolia/client-search@4.17.0": 80 | version "4.17.0" 81 | resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.17.0.tgz#0053c682f5f588e006c20791c27e8bcb0aa5b53c" 82 | integrity sha512-x4P2wKrrRIXszT8gb7eWsMHNNHAJs0wE7/uqbufm4tZenAp+hwU/hq5KVsY50v+PfwM0LcDwwn/1DroujsTFoA== 83 | dependencies: 84 | "@algolia/client-common" "4.17.0" 85 | "@algolia/requester-common" "4.17.0" 86 | "@algolia/transporter" "4.17.0" 87 | 88 | "@algolia/logger-common@4.17.0": 89 | version "4.17.0" 90 | resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.17.0.tgz#0fcea39c9485554edb4cdbfd965c5748b0b837ac" 91 | integrity sha512-DGuoZqpTmIKJFDeyAJ7M8E/LOenIjWiOsg1XJ1OqAU/eofp49JfqXxbfgctlVZVmDABIyOz8LqEoJ6ZP4DTyvw== 92 | 93 | "@algolia/logger-console@4.17.0": 94 | version "4.17.0" 95 | resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.17.0.tgz#8ac56ef4259c4fa3eb9eb6586c7b4b4ed942e8da" 96 | integrity sha512-zMPvugQV/gbXUvWBCzihw6m7oxIKp48w37QBIUu/XqQQfxhjoOE9xyfJr1KldUt5FrYOKZJVsJaEjTsu+bIgQg== 97 | dependencies: 98 | "@algolia/logger-common" "4.17.0" 99 | 100 | "@algolia/requester-browser-xhr@4.17.0": 101 | version "4.17.0" 102 | resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.17.0.tgz#f52fdeeac2f3c531f00838920af33a73066a159b" 103 | integrity sha512-aSOX/smauyTkP21Pf52pJ1O2LmNFJ5iHRIzEeTh0mwBeADO4GdG94cAWDILFA9rNblq/nK3EDh3+UyHHjplZ1A== 104 | dependencies: 105 | "@algolia/requester-common" "4.17.0" 106 | 107 | "@algolia/requester-common@4.17.0": 108 | version "4.17.0" 109 | resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.17.0.tgz#746020d2cbc829213e7cede8eef2182c7a71e32b" 110 | integrity sha512-XJjmWFEUlHu0ijvcHBoixuXfEoiRUdyzQM6YwTuB8usJNIgShua8ouFlRWF8iCeag0vZZiUm4S2WCVBPkdxFgg== 111 | 112 | "@algolia/requester-node-http@4.17.0": 113 | version "4.17.0" 114 | resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.17.0.tgz#262276d94c25a4ec2128b1bdfb9471529528d8b9" 115 | integrity sha512-bpb/wDA1aC6WxxM8v7TsFspB7yBN3nqCGs2H1OADolQR/hiAIjAxusbuMxVbRFOdaUvAIqioIIkWvZdpYNIn8w== 116 | dependencies: 117 | "@algolia/requester-common" "4.17.0" 118 | 119 | "@algolia/transporter@4.17.0": 120 | version "4.17.0" 121 | resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.17.0.tgz#6aabdbc20c475d72d83c8e6519f1191f1a51fb37" 122 | integrity sha512-6xL6H6fe+Fi0AEP3ziSgC+G04RK37iRb4uUUqVAH9WPYFI8g+LYFq6iv5HS8Cbuc5TTut+Bwj6G+dh/asdb9uA== 123 | dependencies: 124 | "@algolia/cache-common" "4.17.0" 125 | "@algolia/logger-common" "4.17.0" 126 | "@algolia/requester-common" "4.17.0" 127 | 128 | "@babel/parser@^7.16.4": 129 | version "7.21.4" 130 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" 131 | integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== 132 | 133 | "@docsearch/css@3.3.3", "@docsearch/css@^3.3.3": 134 | version "3.3.3" 135 | resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.3.3.tgz#f9346c9e24602218341f51b8ba91eb9109add434" 136 | integrity sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg== 137 | 138 | "@docsearch/js@^3.3.3": 139 | version "3.3.3" 140 | resolved "https://registry.yarnpkg.com/@docsearch/js/-/js-3.3.3.tgz#70725a7a8fe92d221fcf0593263b936389d3728f" 141 | integrity sha512-2xAv2GFuHzzmG0SSZgf8wHX0qZX8n9Y1ZirKUk5Wrdc+vH9CL837x2hZIUdwcPZI9caBA+/CzxsS68O4waYjUQ== 142 | dependencies: 143 | "@docsearch/react" "3.3.3" 144 | preact "^10.0.0" 145 | 146 | "@docsearch/react@3.3.3": 147 | version "3.3.3" 148 | resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.3.3.tgz#907b6936a565f880b4c0892624b4f7a9f132d298" 149 | integrity sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q== 150 | dependencies: 151 | "@algolia/autocomplete-core" "1.7.4" 152 | "@algolia/autocomplete-preset-algolia" "1.7.4" 153 | "@docsearch/css" "3.3.3" 154 | algoliasearch "^4.0.0" 155 | 156 | "@esbuild/android-arm64@0.17.17": 157 | version "0.17.17" 158 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz#164b054d58551f8856285f386e1a8f45d9ba3a31" 159 | integrity sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg== 160 | 161 | "@esbuild/android-arm@0.17.17": 162 | version "0.17.17" 163 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.17.tgz#1b3b5a702a69b88deef342a7a80df4c894e4f065" 164 | integrity sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg== 165 | 166 | "@esbuild/android-x64@0.17.17": 167 | version "0.17.17" 168 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.17.tgz#6781527e3c4ea4de532b149d18a2167f06783e7f" 169 | integrity sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA== 170 | 171 | "@esbuild/darwin-arm64@0.17.17": 172 | version "0.17.17" 173 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz#c5961ef4d3c1cc80dafe905cc145b5a71d2ac196" 174 | integrity sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ== 175 | 176 | "@esbuild/darwin-x64@0.17.17": 177 | version "0.17.17" 178 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz#b81f3259cc349691f67ae30f7b333a53899b3c20" 179 | integrity sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg== 180 | 181 | "@esbuild/freebsd-arm64@0.17.17": 182 | version "0.17.17" 183 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz#db846ad16cf916fd3acdda79b85ea867cb100e87" 184 | integrity sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA== 185 | 186 | "@esbuild/freebsd-x64@0.17.17": 187 | version "0.17.17" 188 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz#4dd99acbaaba00949d509e7c144b1b6ef9e1815b" 189 | integrity sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw== 190 | 191 | "@esbuild/linux-arm64@0.17.17": 192 | version "0.17.17" 193 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz#7f9274140b2bb9f4230dbbfdf5dc2761215e30f6" 194 | integrity sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw== 195 | 196 | "@esbuild/linux-arm@0.17.17": 197 | version "0.17.17" 198 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz#5c8e44c2af056bb2147cf9ad13840220bcb8948b" 199 | integrity sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg== 200 | 201 | "@esbuild/linux-ia32@0.17.17": 202 | version "0.17.17" 203 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz#18a6b3798658be7f46e9873fa0c8d4bec54c9212" 204 | integrity sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q== 205 | 206 | "@esbuild/linux-loong64@0.17.17": 207 | version "0.17.17" 208 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz#a8d93514a47f7b4232716c9f02aeb630bae24c40" 209 | integrity sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw== 210 | 211 | "@esbuild/linux-mips64el@0.17.17": 212 | version "0.17.17" 213 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz#4784efb1c3f0eac8133695fa89253d558149ee1b" 214 | integrity sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A== 215 | 216 | "@esbuild/linux-ppc64@0.17.17": 217 | version "0.17.17" 218 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz#ef6558ec5e5dd9dc16886343e0ccdb0699d70d3c" 219 | integrity sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ== 220 | 221 | "@esbuild/linux-riscv64@0.17.17": 222 | version "0.17.17" 223 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz#13a87fdbcb462c46809c9d16bcf79817ecf9ce6f" 224 | integrity sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA== 225 | 226 | "@esbuild/linux-s390x@0.17.17": 227 | version "0.17.17" 228 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz#83cb16d1d3ac0dca803b3f031ba3dc13f1ec7ade" 229 | integrity sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ== 230 | 231 | "@esbuild/linux-x64@0.17.17": 232 | version "0.17.17" 233 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz#7bc400568690b688e20a0c94b2faabdd89ae1a79" 234 | integrity sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg== 235 | 236 | "@esbuild/netbsd-x64@0.17.17": 237 | version "0.17.17" 238 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz#1b5dcfbc4bfba80e67a11e9148de836af5b58b6c" 239 | integrity sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA== 240 | 241 | "@esbuild/openbsd-x64@0.17.17": 242 | version "0.17.17" 243 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz#e275098902291149a5dcd012c9ea0796d6b7adff" 244 | integrity sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA== 245 | 246 | "@esbuild/sunos-x64@0.17.17": 247 | version "0.17.17" 248 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz#10603474866f64986c0370a2d4fe5a2bb7fee4f5" 249 | integrity sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q== 250 | 251 | "@esbuild/win32-arm64@0.17.17": 252 | version "0.17.17" 253 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz#521a6d97ee0f96b7c435930353cc4e93078f0b54" 254 | integrity sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q== 255 | 256 | "@esbuild/win32-ia32@0.17.17": 257 | version "0.17.17" 258 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz#56f88462ebe82dad829dc2303175c0e0ccd8e38e" 259 | integrity sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ== 260 | 261 | "@esbuild/win32-x64@0.17.17": 262 | version "0.17.17" 263 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz#2b577b976e6844106715bbe0cdc57cd1528063f9" 264 | integrity sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg== 265 | 266 | "@types/web-bluetooth@^0.0.16": 267 | version "0.0.16" 268 | resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8" 269 | integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ== 270 | 271 | "@vitejs/plugin-vue@^4.1.0": 272 | version "4.1.0" 273 | resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz#b6a9d83cd91575f7ee15593f6444397f68751073" 274 | integrity sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ== 275 | 276 | "@vue/compiler-core@3.2.47": 277 | version "3.2.47" 278 | resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8" 279 | integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig== 280 | dependencies: 281 | "@babel/parser" "^7.16.4" 282 | "@vue/shared" "3.2.47" 283 | estree-walker "^2.0.2" 284 | source-map "^0.6.1" 285 | 286 | "@vue/compiler-dom@3.2.47": 287 | version "3.2.47" 288 | resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305" 289 | integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ== 290 | dependencies: 291 | "@vue/compiler-core" "3.2.47" 292 | "@vue/shared" "3.2.47" 293 | 294 | "@vue/compiler-sfc@3.2.47": 295 | version "3.2.47" 296 | resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d" 297 | integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ== 298 | dependencies: 299 | "@babel/parser" "^7.16.4" 300 | "@vue/compiler-core" "3.2.47" 301 | "@vue/compiler-dom" "3.2.47" 302 | "@vue/compiler-ssr" "3.2.47" 303 | "@vue/reactivity-transform" "3.2.47" 304 | "@vue/shared" "3.2.47" 305 | estree-walker "^2.0.2" 306 | magic-string "^0.25.7" 307 | postcss "^8.1.10" 308 | source-map "^0.6.1" 309 | 310 | "@vue/compiler-ssr@3.2.47": 311 | version "3.2.47" 312 | resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee" 313 | integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw== 314 | dependencies: 315 | "@vue/compiler-dom" "3.2.47" 316 | "@vue/shared" "3.2.47" 317 | 318 | "@vue/devtools-api@^6.5.0": 319 | version "6.5.0" 320 | resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" 321 | integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q== 322 | 323 | "@vue/reactivity-transform@3.2.47": 324 | version "3.2.47" 325 | resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e" 326 | integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA== 327 | dependencies: 328 | "@babel/parser" "^7.16.4" 329 | "@vue/compiler-core" "3.2.47" 330 | "@vue/shared" "3.2.47" 331 | estree-walker "^2.0.2" 332 | magic-string "^0.25.7" 333 | 334 | "@vue/reactivity@3.2.47": 335 | version "3.2.47" 336 | resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6" 337 | integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ== 338 | dependencies: 339 | "@vue/shared" "3.2.47" 340 | 341 | "@vue/runtime-core@3.2.47": 342 | version "3.2.47" 343 | resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d" 344 | integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA== 345 | dependencies: 346 | "@vue/reactivity" "3.2.47" 347 | "@vue/shared" "3.2.47" 348 | 349 | "@vue/runtime-dom@3.2.47": 350 | version "3.2.47" 351 | resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382" 352 | integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA== 353 | dependencies: 354 | "@vue/runtime-core" "3.2.47" 355 | "@vue/shared" "3.2.47" 356 | csstype "^2.6.8" 357 | 358 | "@vue/server-renderer@3.2.47": 359 | version "3.2.47" 360 | resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0" 361 | integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA== 362 | dependencies: 363 | "@vue/compiler-ssr" "3.2.47" 364 | "@vue/shared" "3.2.47" 365 | 366 | "@vue/shared@3.2.47": 367 | version "3.2.47" 368 | resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c" 369 | integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ== 370 | 371 | "@vueuse/core@^10.0.2": 372 | version "10.0.2" 373 | resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.0.2.tgz#d2cd237e19bda278ae8d4f2e4a9e2e8c273bdcef" 374 | integrity sha512-/UGc2cXbxbeIFLDSJyHUjI9QZ4CJJkhiJe9TbKNPSofcWmYhhUgJ+7iw9njXTKu/Xc3Z6UeXVR9fosW1+cyrnQ== 375 | dependencies: 376 | "@types/web-bluetooth" "^0.0.16" 377 | "@vueuse/metadata" "10.0.2" 378 | "@vueuse/shared" "10.0.2" 379 | vue-demi ">=0.14.0" 380 | 381 | "@vueuse/metadata@10.0.2": 382 | version "10.0.2" 383 | resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.0.2.tgz#538bfe28d60335456084e783508361e07ef1e613" 384 | integrity sha512-APSjlABrV+Q74c+FR0kFETvcN9W2pAaT3XF3WwqWUuk4srmVxv7DY4WshZxK2KYk1+MVY0Fus6J1Hk/JXVm6Aw== 385 | 386 | "@vueuse/shared@10.0.2": 387 | version "10.0.2" 388 | resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.0.2.tgz#82a90e22a0809428e0710206bb7761acaa057ddf" 389 | integrity sha512-7W2l6qZaFvla3zAeEVo8hNHkNRKCezJa3JjZAKv3K4KsevXobHhVNr+RHaOVNK/6ETpFmtqiK+0pMIADbHjjag== 390 | dependencies: 391 | vue-demi ">=0.14.0" 392 | 393 | algoliasearch@^4.0.0: 394 | version "4.17.0" 395 | resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.17.0.tgz#46ed58b2b99509d041f11cd1ea83623edf84355f" 396 | integrity sha512-JMRh2Mw6sEnVMiz6+APsi7lx9a2jiDFF+WUtANaUVCv6uSU9UOLdo5h9K3pdP6frRRybaM2fX8b1u0nqICS9aA== 397 | dependencies: 398 | "@algolia/cache-browser-local-storage" "4.17.0" 399 | "@algolia/cache-common" "4.17.0" 400 | "@algolia/cache-in-memory" "4.17.0" 401 | "@algolia/client-account" "4.17.0" 402 | "@algolia/client-analytics" "4.17.0" 403 | "@algolia/client-common" "4.17.0" 404 | "@algolia/client-personalization" "4.17.0" 405 | "@algolia/client-search" "4.17.0" 406 | "@algolia/logger-common" "4.17.0" 407 | "@algolia/logger-console" "4.17.0" 408 | "@algolia/requester-browser-xhr" "4.17.0" 409 | "@algolia/requester-common" "4.17.0" 410 | "@algolia/requester-node-http" "4.17.0" 411 | "@algolia/transporter" "4.17.0" 412 | 413 | ansi-sequence-parser@^1.1.0: 414 | version "1.1.0" 415 | resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz#4d790f31236ac20366b23b3916b789e1bde39aed" 416 | integrity sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ== 417 | 418 | body-scroll-lock@4.0.0-beta.0: 419 | version "4.0.0-beta.0" 420 | resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-4.0.0-beta.0.tgz#4f78789d10e6388115c0460cd6d7d4dd2bbc4f7e" 421 | integrity sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ== 422 | 423 | csstype@^2.6.8: 424 | version "2.6.21" 425 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" 426 | integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== 427 | 428 | esbuild@^0.17.5: 429 | version "0.17.17" 430 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.17.tgz#fa906ab11b11d2ed4700f494f4f764229b25c916" 431 | integrity sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA== 432 | optionalDependencies: 433 | "@esbuild/android-arm" "0.17.17" 434 | "@esbuild/android-arm64" "0.17.17" 435 | "@esbuild/android-x64" "0.17.17" 436 | "@esbuild/darwin-arm64" "0.17.17" 437 | "@esbuild/darwin-x64" "0.17.17" 438 | "@esbuild/freebsd-arm64" "0.17.17" 439 | "@esbuild/freebsd-x64" "0.17.17" 440 | "@esbuild/linux-arm" "0.17.17" 441 | "@esbuild/linux-arm64" "0.17.17" 442 | "@esbuild/linux-ia32" "0.17.17" 443 | "@esbuild/linux-loong64" "0.17.17" 444 | "@esbuild/linux-mips64el" "0.17.17" 445 | "@esbuild/linux-ppc64" "0.17.17" 446 | "@esbuild/linux-riscv64" "0.17.17" 447 | "@esbuild/linux-s390x" "0.17.17" 448 | "@esbuild/linux-x64" "0.17.17" 449 | "@esbuild/netbsd-x64" "0.17.17" 450 | "@esbuild/openbsd-x64" "0.17.17" 451 | "@esbuild/sunos-x64" "0.17.17" 452 | "@esbuild/win32-arm64" "0.17.17" 453 | "@esbuild/win32-ia32" "0.17.17" 454 | "@esbuild/win32-x64" "0.17.17" 455 | 456 | estree-walker@^2.0.2: 457 | version "2.0.2" 458 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 459 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 460 | 461 | fsevents@~2.3.2: 462 | version "2.3.2" 463 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 464 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 465 | 466 | function-bind@^1.1.1: 467 | version "1.1.1" 468 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 469 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 470 | 471 | has@^1.0.3: 472 | version "1.0.3" 473 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 474 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 475 | dependencies: 476 | function-bind "^1.1.1" 477 | 478 | is-core-module@^2.11.0: 479 | version "2.12.0" 480 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" 481 | integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== 482 | dependencies: 483 | has "^1.0.3" 484 | 485 | jsonc-parser@^3.2.0: 486 | version "3.2.0" 487 | resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" 488 | integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== 489 | 490 | magic-string@^0.25.7: 491 | version "0.25.9" 492 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" 493 | integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== 494 | dependencies: 495 | sourcemap-codec "^1.4.8" 496 | 497 | mark.js@^8.11.1: 498 | version "8.11.1" 499 | resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" 500 | integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== 501 | 502 | minisearch@^6.0.1: 503 | version "6.0.1" 504 | resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-6.0.1.tgz#55e40135e7e6be60f1c1c2f5ee890c334e179a86" 505 | integrity sha512-Ly1w0nHKnlhAAh6/BF/+9NgzXfoJxaJ8nhopFhQ3NcvFJrFIL+iCg9gw9e9UMBD+XIsp/RyznJ/o5UIe5Kw+kg== 506 | 507 | nanoid@^3.3.6: 508 | version "3.3.6" 509 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" 510 | integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== 511 | 512 | path-parse@^1.0.7: 513 | version "1.0.7" 514 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 515 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 516 | 517 | picocolors@^1.0.0: 518 | version "1.0.0" 519 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 520 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 521 | 522 | postcss@^8.1.10, postcss@^8.4.21: 523 | version "8.4.22" 524 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.22.tgz#c29e6776b60ab3af602d4b513d5bd2ff9aa85dc1" 525 | integrity sha512-XseknLAfRHzVWjCEtdviapiBtfLdgyzExD50Rg2ePaucEesyh8Wv4VPdW0nbyDa1ydbrAxV19jvMT4+LFmcNUA== 526 | dependencies: 527 | nanoid "^3.3.6" 528 | picocolors "^1.0.0" 529 | source-map-js "^1.0.2" 530 | 531 | preact@^10.0.0: 532 | version "10.13.2" 533 | resolved "https://registry.yarnpkg.com/preact/-/preact-10.13.2.tgz#2c40c73d57248b57234c4ae6cd9ab9d8186ebc0a" 534 | integrity sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw== 535 | 536 | resolve@^1.22.1: 537 | version "1.22.2" 538 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" 539 | integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== 540 | dependencies: 541 | is-core-module "^2.11.0" 542 | path-parse "^1.0.7" 543 | supports-preserve-symlinks-flag "^1.0.0" 544 | 545 | rollup@^3.18.0: 546 | version "3.20.6" 547 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.6.tgz#53c0fd73e397269d2ce5f0ec12851457dd53cacd" 548 | integrity sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw== 549 | optionalDependencies: 550 | fsevents "~2.3.2" 551 | 552 | shiki@^0.14.1: 553 | version "0.14.1" 554 | resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.1.tgz#9fbe082d0a8aa2ad63df4fbf2ee11ec924aa7ee1" 555 | integrity sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw== 556 | dependencies: 557 | ansi-sequence-parser "^1.1.0" 558 | jsonc-parser "^3.2.0" 559 | vscode-oniguruma "^1.7.0" 560 | vscode-textmate "^8.0.0" 561 | 562 | source-map-js@^1.0.2: 563 | version "1.0.2" 564 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 565 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 566 | 567 | source-map@^0.6.1: 568 | version "0.6.1" 569 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 570 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 571 | 572 | sourcemap-codec@^1.4.8: 573 | version "1.4.8" 574 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 575 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== 576 | 577 | supports-preserve-symlinks-flag@^1.0.0: 578 | version "1.0.0" 579 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 580 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 581 | 582 | vite@^4.2.1: 583 | version "4.2.2" 584 | resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.2.tgz#014c30e5163844f6e96d7fe18fbb702236516dc6" 585 | integrity sha512-PcNtT5HeDxb3QaSqFYkEum8f5sCVe0R3WK20qxgIvNBZPXU/Obxs/+ubBMeE7nLWeCo2LDzv+8hRYSlcaSehig== 586 | dependencies: 587 | esbuild "^0.17.5" 588 | postcss "^8.4.21" 589 | resolve "^1.22.1" 590 | rollup "^3.18.0" 591 | optionalDependencies: 592 | fsevents "~2.3.2" 593 | 594 | vitepress@^1.0.0-alpha.72: 595 | version "1.0.0-alpha.72" 596 | resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-alpha.72.tgz#83c90e46420c57e9d0fac3888681e145e3d28650" 597 | integrity sha512-Ou7fNE/OVYLrKGQMHSTVG6AcNsdv7tm4ACrdhx93SPMzEDj8UgIb4RFa5CTTowaYf3jeDGi2EAJlzXVC+IE3dg== 598 | dependencies: 599 | "@docsearch/css" "^3.3.3" 600 | "@docsearch/js" "^3.3.3" 601 | "@vitejs/plugin-vue" "^4.1.0" 602 | "@vue/devtools-api" "^6.5.0" 603 | "@vueuse/core" "^10.0.2" 604 | body-scroll-lock "4.0.0-beta.0" 605 | mark.js "^8.11.1" 606 | minisearch "^6.0.1" 607 | shiki "^0.14.1" 608 | vite "^4.2.1" 609 | vue "^3.2.47" 610 | 611 | vscode-oniguruma@^1.7.0: 612 | version "1.7.0" 613 | resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" 614 | integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== 615 | 616 | vscode-textmate@^8.0.0: 617 | version "8.0.0" 618 | resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" 619 | integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== 620 | 621 | vue-demi@>=0.14.0: 622 | version "0.14.0" 623 | resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.0.tgz#dcfd9a9cf9bb62ada1582ec9042372cf67ca6190" 624 | integrity sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg== 625 | 626 | vue@^3.2.47: 627 | version "3.2.47" 628 | resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0" 629 | integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ== 630 | dependencies: 631 | "@vue/compiler-dom" "3.2.47" 632 | "@vue/compiler-sfc" "3.2.47" 633 | "@vue/runtime-dom" "3.2.47" 634 | "@vue/server-renderer" "3.2.47" 635 | "@vue/shared" "3.2.47" 636 | -------------------------------------------------------------------------------- /examples/book-advanced/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # Logs 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # ESLint 9 | .eslintcache 10 | 11 | # Node 12 | dist/ 13 | -------------------------------------------------------------------------------- /examples/book-advanced/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tspec-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node src/index.ts" 8 | }, 9 | "keywords": [], 10 | "author": "Hyeonss", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/node": "^18.15.5", 14 | "typescript": "^4.8.3" 15 | }, 16 | "dependencies": { 17 | "tspec": "^0.1.114" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/book-advanced/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Tspec, initTspecServer } from "tspec"; 2 | 3 | /** Book Schema */ 4 | interface Book { 5 | /** Book ID */ 6 | id: Tspec.Integer; 7 | /** Book Title */ 8 | title: string; 9 | /** Tag List */ 10 | tags: Tag[]; 11 | /** Published Date */ 12 | publishedDate?: Tspec.DateString; 13 | } 14 | 15 | /** Tag Schema */ 16 | type Tag = 'Romance' | 'Fantasy' | 'Adventure'; 17 | 18 | export type BookApiSpec = Tspec.DefineApiSpec<{ 19 | security: 'jwt', 20 | tags: ['Book'], 21 | basePath: '/books', 22 | paths: { 23 | '/': { 24 | get: { 25 | operationId: 'searchBooks', 26 | summary: 'Search Books', 27 | description: 'Search books by keyword', 28 | query: { 29 | /** 30 | * Search keyword 31 | * @allowEmptyValue 32 | * @allowReserved 33 | * */ 34 | q: string; 35 | }, 36 | responses: { 200: Book[] }, 37 | }, 38 | }, 39 | '/{id}': { 40 | patch: { 41 | operationId: 'updateBook', 42 | summary: 'Update Book', 43 | path: { id: Tspec.Integer }, 44 | body: Omit, 45 | responses: { 200: Book }, 46 | }, 47 | delete: { 48 | operationId: 'deleteBook', 49 | summary: 'Delete Book', 50 | path: { id: Tspec.Integer }, 51 | responses: { 204: Tspec.NoContent }, 52 | }, 53 | }, 54 | } 55 | }>; 56 | 57 | initTspecServer({ 58 | openapi: { 59 | title: 'Book API', 60 | description: 'This is a sample Book API', 61 | securityDefinitions: { 62 | jwt: { 63 | type: "http", 64 | scheme: "bearer", 65 | bearerFormat: "JWT", 66 | }, 67 | }, 68 | }, 69 | outputPath: 'src/openapi.json', 70 | port: 3000, 71 | }); 72 | -------------------------------------------------------------------------------- /examples/book-advanced/src/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "title": "Book Example", 4 | "version": "1.0.0", 5 | "description": "Description" 6 | }, 7 | "openapi": "3.0.3", 8 | "paths": { 9 | "/books/": { 10 | "get": { 11 | "operationId": "searchBooks", 12 | "tags": [ 13 | "Book" 14 | ], 15 | "summary": "Search Books", 16 | "description": "Search books by keyword", 17 | "security": [ 18 | { 19 | "jwt": [] 20 | } 21 | ], 22 | "parameters": [ 23 | { 24 | "description": "Search keyword", 25 | "name": "q", 26 | "in": "query", 27 | "required": true, 28 | "schema": { 29 | "type": "string" 30 | }, 31 | "allowReserved": true, 32 | "allowEmptyValue": true 33 | } 34 | ], 35 | "responses": { 36 | "200": { 37 | "description": "", 38 | "content": { 39 | "application/json": { 40 | "schema": { 41 | "type": "array", 42 | "items": { 43 | "$ref": "#/components/schemas/Book" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | }, 52 | "/books/{id}": { 53 | "patch": { 54 | "operationId": "updateBook", 55 | "tags": [ 56 | "Book" 57 | ], 58 | "summary": "Update Book", 59 | "security": [ 60 | { 61 | "jwt": [] 62 | } 63 | ], 64 | "parameters": [ 65 | { 66 | "name": "id", 67 | "in": "path", 68 | "required": true, 69 | "schema": { 70 | "type": "integer", 71 | "example": 1 72 | } 73 | } 74 | ], 75 | "requestBody": { 76 | "required": true, 77 | "content": { 78 | "application/json": { 79 | "schema": { 80 | "type": "object", 81 | "properties": { 82 | "title": { 83 | "description": "Book Title", 84 | "type": "string" 85 | }, 86 | "tags": { 87 | "description": "Tag List", 88 | "type": "array", 89 | "items": { 90 | "$ref": "#/components/schemas/Tag" 91 | } 92 | }, 93 | "publishedDate": { 94 | "format": "date", 95 | "description": "Published Date", 96 | "type": "string", 97 | "example": "2023-03-30" 98 | } 99 | }, 100 | "additionalProperties": false, 101 | "required": [ 102 | "tags", 103 | "title" 104 | ] 105 | } 106 | } 107 | } 108 | }, 109 | "responses": { 110 | "200": { 111 | "description": "Book Schema", 112 | "content": { 113 | "application/json": { 114 | "schema": { 115 | "$ref": "#/components/schemas/Book" 116 | } 117 | } 118 | } 119 | } 120 | } 121 | }, 122 | "delete": { 123 | "operationId": "deleteBook", 124 | "tags": [ 125 | "Book" 126 | ], 127 | "summary": "Delete Book", 128 | "security": [ 129 | { 130 | "jwt": [] 131 | } 132 | ], 133 | "parameters": [ 134 | { 135 | "name": "id", 136 | "in": "path", 137 | "required": true, 138 | "schema": { 139 | "type": "integer", 140 | "example": 1 141 | } 142 | } 143 | ], 144 | "responses": { 145 | "204": { 146 | "description": "Empty Response Body" 147 | } 148 | } 149 | } 150 | } 151 | }, 152 | "components": { 153 | "schemas": { 154 | "Book": { 155 | "description": "Book Schema", 156 | "type": "object", 157 | "properties": { 158 | "id": { 159 | "type": "integer", 160 | "description": "Book ID", 161 | "example": 1 162 | }, 163 | "title": { 164 | "description": "Book Title", 165 | "type": "string" 166 | }, 167 | "tags": { 168 | "description": "Tag List", 169 | "type": "array", 170 | "items": { 171 | "$ref": "#/components/schemas/Tag" 172 | } 173 | }, 174 | "publishedDate": { 175 | "format": "date", 176 | "description": "Published Date", 177 | "type": "string", 178 | "example": "2023-03-30" 179 | } 180 | }, 181 | "additionalProperties": false, 182 | "required": [ 183 | "id", 184 | "tags", 185 | "title" 186 | ] 187 | }, 188 | "Tag": { 189 | "description": "Tag Schema", 190 | "enum": [ 191 | "Adventure", 192 | "Fantasy", 193 | "Romance" 194 | ], 195 | "type": "string" 196 | }, 197 | "Omit_Book__id__": { 198 | "type": "object", 199 | "properties": { 200 | "title": { 201 | "description": "Book Title", 202 | "type": "string" 203 | }, 204 | "tags": { 205 | "description": "Tag List", 206 | "type": "array", 207 | "items": { 208 | "$ref": "#/components/schemas/Tag" 209 | } 210 | }, 211 | "publishedDate": { 212 | "format": "date", 213 | "description": "Published Date", 214 | "type": "string", 215 | "example": "2023-03-30" 216 | } 217 | }, 218 | "additionalProperties": false, 219 | "required": [ 220 | "tags", 221 | "title" 222 | ] 223 | } 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /examples/book-advanced/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./build", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/book-advanced/tspec.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "specPathGlobs": ["src/**/*.ts"], 3 | "tsconfigPath": "./tsconfig.json", 4 | "outputPath": "src/openapi.json", 5 | "specVersion": 3, 6 | "openapi": { 7 | "title": "Book Example", 8 | "version": "1.0.0" 9 | }, 10 | "debug": false, 11 | "ignoreErrors": true 12 | } -------------------------------------------------------------------------------- /examples/file-upload-download-example/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "title": "Tspec API", 4 | "version": "0.0.1" 5 | }, 6 | "openapi": "3.0.3", 7 | "paths": { 8 | "/files/upload": { 9 | "post": { 10 | "operationId": "FileApiSpec_post_/upload", 11 | "tags": [ 12 | "File" 13 | ], 14 | "summary": "Upload File", 15 | "parameters": [], 16 | "requestBody": { 17 | "required": true, 18 | "content": { 19 | "multipart/form-data": { 20 | "schema": { 21 | "type": "object", 22 | "properties": { 23 | "file": { 24 | "format": "binary", 25 | "type": "string", 26 | "example": "[\"\\x00\\x00\\x00\\x02\"]" 27 | } 28 | }, 29 | "additionalProperties": false, 30 | "required": [ 31 | "file" 32 | ] 33 | } 34 | } 35 | } 36 | }, 37 | "responses": { 38 | "200": { 39 | "description": "", 40 | "content": { 41 | "application/json": { 42 | "schema": { 43 | "type": "object", 44 | "properties": { 45 | "fileName": { 46 | "type": "string" 47 | } 48 | }, 49 | "additionalProperties": false, 50 | "required": [ 51 | "fileName" 52 | ] 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | }, 60 | "/files/download/{fileName}": { 61 | "get": { 62 | "operationId": "FileApiSpec_get_/download/{fileName}", 63 | "tags": [ 64 | "File" 65 | ], 66 | "summary": "Download File", 67 | "parameters": [ 68 | { 69 | "name": "fileName", 70 | "in": "path", 71 | "required": true, 72 | "schema": { 73 | "type": "string" 74 | } 75 | } 76 | ], 77 | "responses": { 78 | "200": { 79 | "description": "", 80 | "content": { 81 | "application/octet-stream": { 82 | "schema": { 83 | "format": "binary", 84 | "mediaType": "application/octet-stream", 85 | "type": "string", 86 | "example": "[\"\\x00\\x00\\x00\\x02\"]" 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | }, 94 | "/files/multiple-upload": { 95 | "post": { 96 | "operationId": "FileApiSpec_post_/multiple-upload", 97 | "tags": [ 98 | "File" 99 | ], 100 | "summary": "Upload Multiple Files", 101 | "parameters": [], 102 | "requestBody": { 103 | "required": true, 104 | "content": { 105 | "multipart/form-data": { 106 | "schema": { 107 | "type": "object", 108 | "properties": { 109 | "files": { 110 | "items": { 111 | "type": "string", 112 | "format": "binary" 113 | }, 114 | "type": "array" 115 | } 116 | }, 117 | "additionalProperties": false, 118 | "required": [ 119 | "files" 120 | ] 121 | } 122 | } 123 | } 124 | }, 125 | "responses": { 126 | "200": { 127 | "description": "", 128 | "content": { 129 | "application/json": { 130 | "schema": { 131 | "type": "object", 132 | "properties": { 133 | "fileNames": { 134 | "type": "array", 135 | "items": { 136 | "type": "string" 137 | } 138 | } 139 | }, 140 | "additionalProperties": false, 141 | "required": [ 142 | "fileNames" 143 | ] 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | }, 152 | "components": { 153 | "schemas": {} 154 | } 155 | } -------------------------------------------------------------------------------- /examples/file-upload-download-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-upload-download-example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Hyeonss", 6 | "license": "MIT", 7 | "dependencies": { 8 | "tspec": "^0.1.114" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/file-upload-download-example/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Tspec, initTspecServer } from "tspec"; 2 | 3 | export type FileApiSpec = Tspec.DefineApiSpec<{ 4 | tags: ['File'], 5 | basePath: '/files', 6 | paths: { 7 | '/upload': { 8 | post: { 9 | summary: 'Upload File', 10 | /** @mediaType multipart/form-data */ 11 | body: { 12 | file: Tspec.BinaryString; 13 | }, 14 | responses: { 200: { fileName: string } }, 15 | }, 16 | }, 17 | '/download/{fileName}': { 18 | get: { 19 | summary: 'Download File', 20 | path: { fileName: string }, 21 | responses: { 22 | /** @mediaType application/octet-stream */ 23 | 200: Tspec.BinaryString; 24 | }, 25 | }, 26 | }, 27 | '/multiple-upload': { 28 | post: { 29 | summary: 'Upload Multiple Files', 30 | /** @mediaType multipart/form-data */ 31 | body: { 32 | files: Tspec.BinaryStringArray; 33 | }, 34 | responses: { 200: { fileNames: string[] } }, 35 | }, 36 | } 37 | }, 38 | }>; 39 | 40 | initTspecServer({ outputPath: 'openapi.json', port: 3000 }); 41 | -------------------------------------------------------------------------------- /examples/file-upload-download-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/tspec-basic-example/index.ts: -------------------------------------------------------------------------------- 1 | import { Tspec } from "tspec"; 2 | 3 | /** Schema description defined by JSDoc */ 4 | interface Book { 5 | /** Field description defined by JSDoc */ 6 | id: number; 7 | title: string; 8 | description?: string; 9 | } 10 | 11 | export type BookApiSpec = Tspec.DefineApiSpec<{ 12 | tags: ['Book'] 13 | paths: { 14 | '/books/{id}': { 15 | get: { 16 | summary: 'Get book by id', 17 | path: { id: number }, 18 | header: { 'X-Request-ID': string }, 19 | cookie: { debug: 0 | 1 }, 20 | responses: { 200: Book }, 21 | }, 22 | }, 23 | } 24 | }>; 25 | -------------------------------------------------------------------------------- /examples/tspec-basic-example/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "title": "Tspec API", 4 | "version": "0.0.1" 5 | }, 6 | "openapi": "3.0.3", 7 | "paths": { 8 | "/books/{id}": { 9 | "get": { 10 | "operationId": "BookApiSpec_get_/books/{id}", 11 | "tags": [ 12 | "Book" 13 | ], 14 | "summary": "Get book by id", 15 | "parameters": [ 16 | { 17 | "name": "id", 18 | "in": "path", 19 | "required": true, 20 | "schema": { 21 | "type": "number" 22 | } 23 | }, 24 | { 25 | "name": "X-Request-ID", 26 | "in": "header", 27 | "required": true, 28 | "schema": { 29 | "type": "string" 30 | } 31 | }, 32 | { 33 | "name": "debug", 34 | "in": "cookie", 35 | "required": true, 36 | "schema": { 37 | "enum": [ 38 | 0, 39 | 1 40 | ], 41 | "type": "number" 42 | } 43 | } 44 | ], 45 | "responses": { 46 | "200": { 47 | "description": "Schema description defined by JSDoc", 48 | "content": { 49 | "application/json": { 50 | "schema": { 51 | "$ref": "#/components/schemas/Book" 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | }, 60 | "components": { 61 | "schemas": { 62 | "Book": { 63 | "description": "Schema description defined by JSDoc", 64 | "type": "object", 65 | "properties": { 66 | "id": { 67 | "description": "Field description defined by JSDoc", 68 | "type": "number" 69 | }, 70 | "title": { 71 | "type": "string" 72 | }, 73 | "description": { 74 | "type": "string" 75 | } 76 | }, 77 | "additionalProperties": false, 78 | "required": [ 79 | "id", 80 | "title" 81 | ] 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /examples/tspec-basic-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tspec-basic-example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Hyeonss", 6 | "license": "MIT", 7 | "dependencies": { 8 | "tspec": "^0.1.115" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/tspec-basic-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/tspec-express-example/index.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response, Router } from "express"; 2 | import { Tspec, TspecDocsMiddleware } from "tspec"; 3 | 4 | /** Book Schema */ 5 | interface Book { 6 | /** Book ID */ 7 | id: number; 8 | /** 9 | * Book Title 10 | * @example Under the Oak Tree 11 | * */ 12 | title: string; 13 | tags: Tag[]; 14 | } 15 | 16 | /** Tag Schema */ 17 | type Tag = 'Romance' | 'Fantasy'; 18 | 19 | const getBookById = (req: Request<{ id: string }>, res: Response) => { 20 | res.json({ 21 | id: +req.params.id, 22 | title: 'Under the Oak Tree', 23 | tags: ['Romance', 'Fantasy'], 24 | }) 25 | } 26 | const router = Router().get('/books/:id', getBookById); 27 | 28 | export type BookApiSpec = Tspec.DefineApiSpec<{ 29 | tags: ['Book'], 30 | paths: { 31 | '/books/{id}': { 32 | get: { 33 | summary: 'Get book by id', 34 | handler: typeof getBookById 35 | }, 36 | }, 37 | } 38 | }>; 39 | 40 | const initServer = async () => { 41 | const app = express(); 42 | app.use(router); 43 | app.use('/docs', await TspecDocsMiddleware()); 44 | app.listen(3000, () => { 45 | console.log(`Tspec docs is running on http://localhost:3000/docs`); 46 | }); 47 | } 48 | initServer(); 49 | -------------------------------------------------------------------------------- /examples/tspec-express-example/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "title": "Tspec API", 4 | "version": "0.0.1" 5 | }, 6 | "openapi": "3.0.3", 7 | "paths": { 8 | "/books": { 9 | "get": { 10 | "operationId": "BookApiSpec_get_/books", 11 | "tags": [ 12 | "도서" 13 | ], 14 | "summary": "단일 도서 조회", 15 | "parameters": [ 16 | { 17 | "name": "id", 18 | "in": "path", 19 | "required": true, 20 | "schema": { 21 | "type": "number" 22 | } 23 | } 24 | ], 25 | "responses": { 26 | "200": { 27 | "description": "", 28 | "content": { 29 | "application/json": { 30 | "schema": { 31 | "$ref": "#/components/schemas/Book" 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | }, 40 | "components": { 41 | "schemas": { 42 | "Book": { 43 | "description": "도서 정보", 44 | "type": "object", 45 | "properties": { 46 | "id": { 47 | "description": "도서 ID", 48 | "type": "number" 49 | }, 50 | "title": { 51 | "description": "도서명", 52 | "example": "상수리 나무 아래", 53 | "type": "string" 54 | }, 55 | "tags": { 56 | "type": "array", 57 | "items": { 58 | "$ref": "#/components/schemas/Tag" 59 | } 60 | } 61 | }, 62 | "additionalProperties": false, 63 | "required": [ 64 | "id", 65 | "tags", 66 | "title" 67 | ] 68 | }, 69 | "Tag": { 70 | "description": "태그 정보", 71 | "enum": [ 72 | "로맨스", 73 | "판타지" 74 | ], 75 | "type": "string" 76 | }, 77 | "qs.ParsedQs": { 78 | "type": "object", 79 | "additionalProperties": { 80 | "anyOf": [ 81 | { 82 | "type": "array", 83 | "items": { 84 | "type": "string" 85 | } 86 | }, 87 | { 88 | "$ref": "#/components/schemas/qs.ParsedQs" 89 | }, 90 | { 91 | "type": "array", 92 | "items": { 93 | "$ref": "#/components/schemas/qs.ParsedQs" 94 | } 95 | }, 96 | { 97 | "type": "string" 98 | } 99 | ] 100 | } 101 | } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /examples/tspec-express-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tspec-example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Hyeonss", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "ts-node index.ts" 9 | }, 10 | "dependencies": { 11 | "express": "^4.18.2", 12 | "tspec": "^0.1.97" 13 | }, 14 | "devDependencies": { 15 | "@types/express": "^4.17.17" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/tspec-express-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/tspec/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2020: true, 4 | mocha: true, 5 | }, 6 | extends: [ 7 | 'airbnb-base', 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: [ 11 | '@typescript-eslint', 12 | ], 13 | rules: { 14 | '@typescript-eslint/member-delimiter-style': ['error', { 15 | multiline: { 16 | delimiter: 'comma', 17 | requireLast: true, 18 | }, 19 | singleline: { 20 | delimiter: 'comma', 21 | requireLast: false, 22 | }, 23 | }], 24 | '@typescript-eslint/no-unused-vars': 'error', 25 | 'import/no-extraneous-dependencies': ['error', { 26 | devDependencies: ['**/test.ts', '**/*.test.ts', 'src/common/test/**/*.ts'], 27 | }], 28 | 'import/extensions': ['error', 'ignorePackages', { 29 | ts: 'never', 30 | }], 31 | 'import/order': ['error', { /* Import Rule: https://github.com/ridi/manta/pull/9#issue-1094003608 */ 32 | groups: [ 33 | 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 34 | ], 35 | 'newlines-between': 'always', 36 | alphabetize: { 37 | order: 'asc', /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */ 38 | caseInsensitive: false, 39 | }, 40 | }], 41 | 'sort-imports': ['error', { 42 | ignoreCase: false, 43 | ignoreDeclarationSort: true, 44 | ignoreMemberSort: false, 45 | allowSeparatedGroups: true, 46 | }], 47 | 'no-unused-vars': 'off', 48 | 'import/prefer-default-export': 'off', // https://github.com/ridi/manta/pull/59 49 | 'max-len': ['error', { 50 | code: 100, 51 | ignoreComments: true, 52 | ignoreUrls: true, 53 | }], 54 | 'no-shadow': 'off', 55 | }, 56 | overrides: [ 57 | { 58 | // Allow some rules for unit test files. 59 | files: ['**/test.ts', '**/*.test.ts', '**/apiTest/**/*.ts'], 60 | rules: { 61 | 'import/no-extraneous-dependencies': 'off', // Allow devDependency for unit tests. 62 | 'no-console': 'off', // Allow console for unit tests. 63 | 'no-underscore-dangle': 'off', // Allow underscore dangle like __get__, __set__. 64 | }, 65 | }, 66 | { 67 | // Allow some rules for route files to use tsoa. 68 | files: ['**/route.ts'], 69 | rules: { 70 | 'class-methods-use-this': 'off', 71 | '@typescript-eslint/no-unused-vars': 'off', 72 | }, 73 | }, 74 | ], 75 | settings: { 76 | 'import/resolver': { 77 | node: { 78 | extensions: ['.ts'], 79 | paths: ['./src'], 80 | }, 81 | }, 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /packages/tspec/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # Logs 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # ESLint 9 | .eslintcache 10 | 11 | # Node 12 | dist/ 13 | tsconfig.tsbuildinfo 14 | -------------------------------------------------------------------------------- /packages/tspec/.npmignore: -------------------------------------------------------------------------------- 1 | ./src 2 | ./exapmels -------------------------------------------------------------------------------- /packages/tspec/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ts-spec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/tspec/README.md: -------------------------------------------------------------------------------- 1 | # Tspec 2 | 3 | Type-driven API Documentation library for [TypeScript](https://www.typescriptlang.org/). 4 | 5 | > Automatically parses your TypeScript types and generates up-to-date OpenAPI specification with beautiful Swagger UI. 6 | 7 | 8 | [![npm](https://badge.fury.io/js/tspec.svg)](https://badge.fury.io/js/tspec) [![downloads](https://img.shields.io/npm/dm/tspec.svg)](https://www.npmjs.com/package/tspec) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 9 | 10 | ## Why tspec? 11 | - **Code First**: Rely on TypeScript type and JSDoc to generate OpenAPI Specification. 12 | - **Easy to learn**: No need to learn new OpenAPI Spec syntax. Just use TypeScript types. 13 | - **Easy to use**: Only few lines of code are needed to generate OpenAPI Specification. 14 | - **Flexible**: You can use any framework you want. It doesn't impose any framework-specific constraints. 15 | 16 | ## Installation 17 | ```bash 18 | npm install tspec 19 | ``` 20 | 21 | 22 | ## Usage 23 | ```ts 24 | import { Tspec } from "tspec"; 25 | 26 | /** Schema description defined by JSDoc */ 27 | interface Book { 28 | /** Field description defined by JSDoc */ 29 | id: number; 30 | title: string; 31 | description?: string; 32 | } 33 | 34 | export type BookApiSpec = Tspec.DefineApiSpec<{ 35 | paths: { 36 | '/books/{id}': { 37 | get: { 38 | summary: 'Get book by id', 39 | path: { id: number }, 40 | responses: { 200: Book }, 41 | }, 42 | }, 43 | } 44 | }>; 45 | ``` 46 | 47 | Run the following command to generate OpenAPI Spec: 48 | 49 | ```bash 50 | npx tspec generate --outputPath openapi.json 51 | ``` 52 | (For readability, the generated OpenAPI Spec is formatted with yaml) 53 | 54 | ```yaml 55 | openapi: 3.0.3 56 | info: 57 | title: Tspec API 58 | version: 0.0.1 59 | paths: 60 | /books/{id}: 61 | get: 62 | operationId: BookApiSpec_get_/books/{id} 63 | tags: 64 | - Book 65 | summary: Get book by id 66 | parameters: 67 | - name: id 68 | in: path 69 | required: true 70 | schema: 71 | type: number 72 | responses: 73 | '200': 74 | description: OK 75 | content: 76 | application/json: 77 | schema: 78 | $ref: '#/components/schemas/Book' 79 | components: 80 | schemas: 81 | Book: 82 | description: Schema description defined by JSDoc 83 | type: object 84 | properties: 85 | id: 86 | description: Field description defined by JSDoc 87 | type: number 88 | title: 89 | type: string 90 | description: 91 | type: string 92 | required: 93 | - id 94 | - title 95 | ``` 96 | 97 | If you want to serve Swagger UI, run the following command: 98 | 99 | ```bash 100 | npx tspec server --port 3000 101 | ``` 102 | 103 | Then, you can access Swagger UI at `http://localhost:3000` 104 | 105 | ![getting-started-swagger-ui-1](https://github.com/ts-spec/tspec/assets/13609011/149817a2-fe74-451a-a429-66f4674510e3) 106 | 107 | And you can see schema definitions in the `Schemas` tab. 108 | 109 | ![getting-started-swagger-ui-2](https://github.com/ts-spec/tspec/assets/13609011/b7cebc87-c930-43f6-85d7-92ae5734ad9d) 110 | 111 | 112 | ## Express Integration 113 | 114 | Tspec automatically parses your [Express](https://expressjs.com/) handler type to generate parameters(`path`, `query`, `body`) and responses schemas. 115 | And you can use `TspecDocsMiddleware` to serve Swagger UI. 116 | 117 | ```ts 118 | import { Tspec, TspecDocsMiddleware } from "tspec"; 119 | import express, { Request, Response } from "express"; 120 | 121 | const getBookById = ( 122 | req: Request<{ id: string }>, res: Response, 123 | ) => { 124 | res.json({ 125 | id: +req.params.id, 126 | title: 'Book Title', 127 | description: 'Book Description', 128 | }); 129 | } 130 | 131 | export type BookApiSpec = Tspec.DefineApiSpec<{ 132 | tags: ['Book'], 133 | paths: { 134 | '/books/{id}': { 135 | get: { 136 | summary: 'Get book by id', 137 | handler: typeof getBookById 138 | }, 139 | }, 140 | } 141 | }>; 142 | 143 | const initServer = async () => { 144 | const app = express() 145 | app.get('/books/:id', getBookById); 146 | app.use('/docs', await TspecDocsMiddleware()); 147 | app.listen(3000); 148 | } 149 | initServer(); 150 | ``` 151 | 152 | ## Documentation 153 | https://ts-spec.github.io/tspec 154 | 155 | 156 | --- 157 | 158 | ## Stargazers over time 159 | Give a ⭐️ if you find this project useful and to show your appreciation! [![Stars](https://img.shields.io/github/stars/ts-spec/tspec?style=social)](https://github.com/ts-spec/tspec/stargazers) 160 | 161 | [![Stargazers over time](https://starchart.cc/ts-spec/tspec.svg?variant=adaptive)](https://starchart.cc/ts-spec/tspec) 162 | 163 | 164 | -------------------------------------------------------------------------------- /packages/tspec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tspec", 3 | "description": "Generate OpenAPI 3.0 spec from TypeScript code.", 4 | "version": "0.1.116", 5 | "main": "dist/index.js", 6 | "type": "module", 7 | "typings": "dist/index.d.ts", 8 | "license": "MIT", 9 | "author": "Hyeonseong Jeon ", 10 | "devDependencies": { 11 | "@rollup/plugin-node-resolve": "^15.0.2", 12 | "@rollup/plugin-sucrase": "^5.0.1", 13 | "@types/debug": "^4.1.7", 14 | "@types/express": "^4.17.14", 15 | "@types/glob": "^8.0.1", 16 | "@types/node": "^18.7.18", 17 | "@types/swagger-ui-express": "^4.1.3", 18 | "@types/yargs": "^17.0.24", 19 | "@typescript-eslint/eslint-plugin": "^5.50.0", 20 | "@typescript-eslint/parser": "^5.50.0", 21 | "concurrently": "^8.0.1", 22 | "eslint": "^8.33.0", 23 | "eslint-config-airbnb-base": "^15.0.0", 24 | "eslint-config-airbnb-typescript": "^17.0.0", 25 | "eslint-plugin-import": "^2.27.5", 26 | "rollup": "^3.21.6", 27 | "rollup-plugin-dts": "^6.1.1", 28 | "tsx": "^3.12.7" 29 | }, 30 | "peerDependencies": { 31 | "express": "^4.17.1" 32 | }, 33 | "scripts": { 34 | "start": "tsx src/index.ts", 35 | "build": "concurrently npm:build:*", 36 | "build:rollup": "yarn rollup", 37 | "rollup": "rollup --config rollup.config.ts --configPlugin 'sucrase={transforms: [\"typescript\"]}'", 38 | "watch": "yarn rollup -w" 39 | }, 40 | "dependencies": { 41 | "debug": "^4.3.4", 42 | "express": "^4.18.2", 43 | "glob": "^8.1.0", 44 | "http-proxy-middleware": "^2.0.6", 45 | "json-schema-to-openapi-schema": "^0.4.0", 46 | "openapi-types": "^12.0.2", 47 | "swagger-ui-express": "^4.6.2", 48 | "typescript": "~5.1.0", 49 | "typescript-json-schema": "^0.62.0", 50 | "yargs": "^17.7.1" 51 | }, 52 | "exports": { 53 | ".": { 54 | "types": "./dist/index.d.ts", 55 | "require": "./dist/index.cjs", 56 | "default": "./dist/index.js" 57 | }, 58 | "./cli": { 59 | "types": "./dist/cli/index.d.ts", 60 | "require": "./dist/cli.cjs", 61 | "default": "./dist/cli.js" 62 | } 63 | }, 64 | "files": [ 65 | "dist" 66 | ], 67 | "repository": { 68 | "type": "git", 69 | "url": "https://github.com/ts-spec/tspec" 70 | }, 71 | "bin": { 72 | "tspec": "dist/cli.js" 73 | }, 74 | "engines": { 75 | "node": ">=12.0.0" 76 | }, 77 | "publishConfig": { 78 | "access": "public" 79 | }, 80 | "homepage": "https://ts-spec.github.io/tspec/", 81 | "keywords": [ 82 | "typescript", 83 | "openapi", 84 | "swagger", 85 | "server", 86 | "node", 87 | "node.js", 88 | "generation", 89 | "express" 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /packages/tspec/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | import sucrase from '@rollup/plugin-sucrase'; 3 | import dts from 'rollup-plugin-dts'; 4 | import { defineConfig } from 'rollup'; 5 | 6 | export default defineConfig([ 7 | { 8 | input: { 9 | index: 'src/index.ts', 10 | cli: 'src/cli/index.ts', 11 | }, 12 | output: [ 13 | { 14 | banner(chunk) { 15 | if (chunk.isEntry && chunk.name === 'cli') { 16 | return '#!/usr/bin/env node'; 17 | } 18 | 19 | return ''; 20 | }, 21 | dir: 'dist', 22 | entryFileNames: '[name].js', 23 | chunkFileNames: 'chunks/[name].js', 24 | format: 'es', 25 | }, 26 | { 27 | banner(chunk) { 28 | if (chunk.isEntry && chunk.name === 'cli') { 29 | return '#!/usr/bin/env node'; 30 | } 31 | 32 | return ''; 33 | }, 34 | dir: 'dist', 35 | entryFileNames: '[name].cjs', 36 | chunkFileNames: 'chunks/[name].cjs', 37 | format: 'cjs', 38 | }, 39 | ], 40 | external: [ 41 | /node_modules/, 42 | ], 43 | plugins: [ 44 | sucrase({ 45 | transforms: ['typescript'], 46 | }), 47 | nodeResolve(), 48 | ], 49 | }, 50 | // Type Definitions 51 | { 52 | input: { 53 | index: 'src/index.ts', 54 | cli: 'src/cli/index.ts', 55 | }, 56 | output: [ 57 | { 58 | dir: 'dist', 59 | format: 'es', 60 | }, 61 | ], 62 | plugins: [dts()], 63 | } 64 | ]); 65 | -------------------------------------------------------------------------------- /packages/tspec/src/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { realpathSync } from 'node:fs'; 3 | import { fileURLToPath } from 'node:url'; 4 | 5 | // eslint-disable-next-line import/no-extraneous-dependencies 6 | import yargs from 'yargs'; 7 | // eslint-disable-next-line import/no-extraneous-dependencies 8 | import { hideBin } from 'yargs/helpers'; 9 | 10 | import { Tspec } from 'types/tspec'; 11 | 12 | import { defaultGenerateParams as defaultArgs, generateTspec } from '../generator'; 13 | import { initTspecServer } from '../server'; 14 | 15 | enum SupportedSpecVersion { 16 | THREE = 3, 17 | } 18 | 19 | interface GeneratorOptions { 20 | specPathGlobs: (string | number)[], 21 | tsconfigPath: string, 22 | configPath?: string, 23 | outputPath?: string, 24 | specVersion?: SupportedSpecVersion, 25 | openapiTitle?: string, 26 | openapiVersion?: string, 27 | openapiDescription?: string, 28 | debug?: boolean, 29 | ignoreErrors?: boolean, 30 | } 31 | 32 | interface RunServerOptions extends GeneratorOptions { 33 | port?: number, 34 | proxyHost?: string, 35 | } 36 | 37 | const baseOptions = { 38 | specPathGlobs: { type: 'array', default: defaultArgs.specPathGlobs }, 39 | tsconfigPath: { type: 'string', default: defaultArgs.tsconfigPath }, 40 | configPath: { type: 'string', default: defaultArgs.configPath }, 41 | outputPath: { type: 'string' }, 42 | specVersion: { type: 'number' }, 43 | openapiTitle: { type: 'string', default: defaultArgs.openapi.title }, 44 | openapiVersion: { type: 'string', default: defaultArgs.openapi.version }, 45 | openapiDescription: { type: 'string', default: defaultArgs.openapi.description }, 46 | debug: { type: 'boolean', default: defaultArgs.debug }, 47 | ignoreErrors: { type: 'boolean', default: defaultArgs.ignoreErrors }, 48 | } as const; 49 | 50 | const generatorOptions = { 51 | ...baseOptions, 52 | outputPath: { type: 'string', default: 'openapi.json' }, 53 | } as const; 54 | 55 | const runServerOptions = { 56 | ...baseOptions, 57 | port: { type: 'number', default: 7000 }, 58 | proxyHost: { type: 'string' }, 59 | } as const; 60 | 61 | const validateGeneratorOptions = (args: GeneratorOptions): Tspec.GenerateParams => { 62 | if (args.specVersion && !Object.values(SupportedSpecVersion).includes(args.specVersion)) { 63 | // eslint-disable-next-line max-len 64 | throw new Error(`Tspec currently supports only OpenAPI Spec with version ${Object.values(SupportedSpecVersion).join(', ')}.`); 65 | } 66 | 67 | return { 68 | specPathGlobs: args.specPathGlobs !== defaultArgs.specPathGlobs 69 | ? args.specPathGlobs.map((glob) => glob.toString()) 70 | : undefined, 71 | tsconfigPath: args.tsconfigPath !== defaultArgs.tsconfigPath ? args.tsconfigPath : undefined, 72 | configPath: args.configPath !== defaultArgs.configPath ? args.configPath : undefined, 73 | outputPath: args.outputPath, 74 | specVersion: args.specVersion !== defaultArgs.specVersion ? args.specVersion : undefined, 75 | openapi: { 76 | title: args.openapiTitle !== defaultArgs.openapi.title ? args.openapiTitle : undefined, 77 | version: args.openapiVersion !== defaultArgs.openapi.version ? args.openapiVersion : undefined, 78 | description: args.openapiDescription !== defaultArgs.openapi.description 79 | ? args.openapiDescription 80 | : undefined, 81 | }, 82 | debug: args.debug !== defaultArgs.debug ? args.debug : undefined, 83 | ignoreErrors: args.ignoreErrors !== defaultArgs.ignoreErrors ? args.ignoreErrors : undefined, 84 | }; 85 | }; 86 | 87 | const specGenerator = async (args: RunServerOptions) => { 88 | const generateTspecParams = await validateGeneratorOptions(args); 89 | await generateTspec(generateTspecParams); 90 | }; 91 | 92 | const startTspecServer = async (args: RunServerOptions) => { 93 | const generateTspecParams = await validateGeneratorOptions(args); 94 | initTspecServer({ ...generateTspecParams, port: args.port, proxyHost: args.proxyHost }); 95 | }; 96 | 97 | export const runCli = async () => yargs(hideBin(process.argv)) 98 | .usage('Usage: $0 [options]') 99 | .command( 100 | 'generate', 101 | 'Generate OpenAPI Spec from Tspec', 102 | generatorOptions, 103 | (yargs) => specGenerator(yargs), 104 | ) 105 | .command( 106 | 'server', 107 | 'Start Tspec server', 108 | runServerOptions, 109 | (yargs) => startTspecServer(yargs), 110 | ) 111 | .help('help') 112 | .alias('help', 'h') 113 | .parse(); 114 | 115 | if (import.meta.url.startsWith('file:')) { 116 | const modulePath = realpathSync(fileURLToPath(import.meta.url)); 117 | if (realpathSync(process.argv[1]) === modulePath) { 118 | runCli(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /packages/tspec/src/generator/config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | 4 | import { Tspec } from '../types/tspec'; 5 | import { assertIsDefined } from '../utils/types'; 6 | 7 | const readTspecConfig = (path: string) => { 8 | try { 9 | return fs.readFile(path, { encoding: 'utf8' }); 10 | } catch (err) { 11 | console.error('Cannot read Tspec config'); 12 | throw err; 13 | } 14 | }; 15 | 16 | const parseTspecConfig = (config: string) => { 17 | try { 18 | return JSON.parse(config); 19 | } catch (err) { 20 | console.error('Cannot parse Tspec config'); 21 | throw err; 22 | } 23 | }; 24 | 25 | type TspecConfigError = { 26 | message: string, 27 | property: string, 28 | } 29 | 30 | type TspecConfigValidationFunction = ( 31 | property: string, 32 | value: any, 33 | type?: 'string' | 'boolean', 34 | ) => void; 35 | 36 | function validateTspecConfig(config: Tspec.GenerateParams): asserts config is Tspec.GenerateParams { 37 | const errors: TspecConfigError[] = []; 38 | 39 | const validatePrimitive: TspecConfigValidationFunction = (property, value, type) => { 40 | assertIsDefined(type); 41 | if (typeof value !== type) { // eslint-disable-line valid-typeof 42 | errors.push({ 43 | message: `property is not a ${type}.`, 44 | property, 45 | }); 46 | } 47 | }; 48 | 49 | const validateStringArray: TspecConfigValidationFunction = (property, value) => { 50 | if (!Array.isArray(value)) { 51 | errors.push({ 52 | message: 'property is not an array.', 53 | property, 54 | }); 55 | } else if (value.some((glob) => typeof glob !== 'string')) { 56 | errors.push({ 57 | message: 'property contains more than one non-string value.', 58 | property, 59 | }); 60 | } 61 | }; 62 | 63 | if (config.specPathGlobs) { 64 | validateStringArray('specPathGlobs', config.specPathGlobs); 65 | } 66 | if (config.tsconfigPath) { 67 | validatePrimitive('tsconfigPath', config.tsconfigPath, 'string'); 68 | } 69 | if (config.outputPath) { 70 | validatePrimitive('outputPath', config.outputPath, 'string'); 71 | } 72 | if (config.openapi?.title) { 73 | validatePrimitive('openapiTitle', config.openapi.title, 'string'); 74 | } 75 | if (config.openapi?.version) { 76 | validatePrimitive('openapiVersion', config.openapi.version, 'string'); 77 | } 78 | if (config.openapi?.description){ 79 | validatePrimitive('openapiDescription', config.openapi.description, 'string'); 80 | } 81 | if (config.debug) { 82 | validatePrimitive('debug', config.debug, 'boolean'); 83 | } 84 | if (config.ignoreErrors) { 85 | validatePrimitive('ignoreErrors', config.ignoreErrors, 'boolean'); 86 | } 87 | 88 | if (errors.length) { 89 | const message = `Tspec configuration file is not valid.\n${ 90 | errors.map((error) => `${error.property}: ${error.message}`).join('\n') 91 | }\n`; 92 | throw new Error(message); 93 | } 94 | } 95 | 96 | const getConfigPath = (inputPath: string) => { 97 | const filePath = inputPath; 98 | return path.join(process.cwd(), filePath); 99 | }; 100 | 101 | export const isTspecFileConfigAvailable = async (inputPath: string) => { 102 | const configPath = getConfigPath(inputPath); 103 | return fs.access(configPath) 104 | .then(() => true) 105 | .catch(() => false); 106 | }; 107 | 108 | export const getTspecConfigFromConfigFile = async ( 109 | inputPath: string, 110 | ): Promise => { 111 | const configPath = getConfigPath(inputPath); 112 | const fileResult = await readTspecConfig(configPath); 113 | 114 | const config = parseTspecConfig(fileResult); 115 | validateTspecConfig(config); 116 | 117 | return config; 118 | }; 119 | -------------------------------------------------------------------------------- /packages/tspec/src/generator/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import { dirname } from 'path'; 3 | 4 | import debug from 'debug'; 5 | import glob from 'glob'; 6 | import { OpenAPIV3 } from 'openapi-types'; 7 | // eslint-disable-next-line import/no-extraneous-dependencies 8 | import ts from 'typescript'; 9 | import * as TJS from 'typescript-json-schema'; 10 | 11 | import { Tspec } from '../types/tspec'; 12 | import { assertIsDefined, isDefined } from '../utils/types'; 13 | 14 | import { getOpenapiPaths } from './openapiGenerator'; 15 | import { convertToOpenapiSchemas } from './openapiSchemaConverter'; 16 | import { SchemaMapping } from './types'; 17 | import { getTspecConfigFromConfigFile, isTspecFileConfigAvailable } from './config'; 18 | import { mergeDeep } from '../utils/merge'; 19 | 20 | export const DEBUG = debug('tspec'); 21 | 22 | const isNodeExported = (node: ts.Node): boolean => ( 23 | // eslint-disable-next-line no-bitwise 24 | (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 25 | || (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) 26 | ); 27 | 28 | const getTspecSignatures = (p: ts.Program) => { 29 | const entryPoints = p 30 | .getRootFileNames() 31 | .map((entryPointName) => p.getSourceFile(entryPointName)).filter(isDefined); 32 | 33 | const names: string[] = []; 34 | entryPoints.forEach((srcFile) => { 35 | srcFile.forEachChild((node) => { 36 | if (!isNodeExported(node)) { 37 | return; 38 | } 39 | 40 | // NOTE(hyeonseong): typescript 5.0 changed node kind of type alias declaration. 41 | // if ( 42 | // !ts.isTypeAliasDeclaration(node) 43 | // || !ts.isTypeReferenceNode(node.type) 44 | // ) { 45 | // return; 46 | // } 47 | 48 | if ((node as any).type?.typeName?.right?.escapedText !== 'DefineApiSpec') { 49 | return; 50 | } 51 | const name = (node as any).name.escapedText as string; 52 | if (names.includes(name)) { 53 | throw new Error(`Duplicate name: ${name}`); 54 | } 55 | names.push(name); 56 | }); 57 | }); 58 | 59 | return names; 60 | }; 61 | 62 | const getCompilerOptions = (tsconfigPath: string): ts.CompilerOptions => { 63 | const { config, error } = ts.readConfigFile(tsconfigPath, ts.sys.readFile); 64 | if (error) { 65 | throw new Error(error.messageText as string); 66 | } 67 | return { 68 | ...config.compilerOptions, 69 | noEmit: true, 70 | }; 71 | }; 72 | 73 | const getDefaultProgramFiles = (compilerOptions: ts.CompilerOptions) => { 74 | const { rootDir, rootDirs } = compilerOptions; 75 | const globs = [rootDir, ...(rootDirs || [])].filter(isDefined) 76 | .flatMap((r) => [`${r}/*.ts`, `${r}/**/*.ts`]); 77 | if (globs.length === 0) { 78 | return ['**/*.ts']; 79 | } 80 | return globs; 81 | }; 82 | 83 | const getProgramFiles = (compilerOptions: ts.CompilerOptions, specPathGlobs?: string[]) => { 84 | const srcGlobs = specPathGlobs || getDefaultProgramFiles(compilerOptions); 85 | const programFils = [...new Set(srcGlobs.flatMap((g) => glob.sync(g, { 86 | ignore: ['**/node_modules/**'], 87 | })))]; 88 | DEBUG({ programFils }); 89 | return programFils; 90 | }; 91 | 92 | /** 93 | * 제대로 동작하지 않는 케이스..? 94 | * 1. Partial of Record 95 | * export type BlockRegions = Partial>; 96 | */ 97 | const getOpenapiSchemas = async ( 98 | tsconfigPath: string, 99 | specPathGlobs?: string[], 100 | ignoreErrors?: boolean, 101 | ) => { 102 | const compilerOptions = getCompilerOptions(tsconfigPath); 103 | DEBUG({ compilerOptions }); 104 | const files = getProgramFiles(compilerOptions, specPathGlobs); 105 | DEBUG({ files }); 106 | const program = TJS.getProgramFromFiles(files, compilerOptions); 107 | 108 | const tjsSettings: TJS.PartialArgs = { 109 | required: true, 110 | noExtraProps: true, 111 | strictNullChecks: true, 112 | ignoreErrors: ignoreErrors || true, 113 | esModuleInterop: compilerOptions.esModuleInterop, 114 | constAsEnum: true, 115 | // rejectDateType: true, 116 | validationKeywords: [ 117 | /** NOTE: JSON schema keywords. see https://swagger.io/docs/specification/data-models/keywords/ */ 118 | 'title', 'pattern', 119 | 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 120 | 'minLength', 'maxLength', 'pattern', 121 | 'minItems', 'maxItems', 'uniqueItems', 122 | 'minProperties', 'maxProperties', 123 | /** NOTE: These keywords are supported with minor differences */ 124 | /** 'type', */ 'format', 'description', 'default', 125 | /** NOTE: Additional keywords */ 126 | 'deprecated', 'discriminator', 'example', 'externalDocs', 'nullable', /** 'readOnly', 'writeOnly', */ 127 | /** NOTE: parameter validation. see https://swagger.io/docs/specification/describing-parameters/ */ 128 | 'allowReserved', 'style', 'form', 'allowEmptyValue', 'explode', 129 | /** NOTE: media type. see https://swagger.io/docs/specification/media-types/ */ 130 | 'mediaType', 131 | ], 132 | }; 133 | DEBUG({ tjsSettings }); 134 | const generator = TJS.buildGenerator(program, tjsSettings); 135 | assertIsDefined(generator); 136 | 137 | const tspecSymbols = getTspecSignatures(program as ts.Program); 138 | DEBUG({ tspecSymbols }); 139 | const { definitions: jsonSchemas } = generator.getSchemaForSymbols(tspecSymbols); 140 | assertIsDefined(jsonSchemas); 141 | DEBUG({ schemaKeys: Object.keys(jsonSchemas) }); 142 | 143 | const openapiSchemas = await convertToOpenapiSchemas(jsonSchemas); 144 | 145 | return { openapiSchemas, tspecSymbols }; 146 | }; 147 | 148 | const getOpenapiSchemasOnly = (openapiSchemas: SchemaMapping, tspecSymbols: string[]) => { 149 | const tspecPathSchemas = tspecSymbols.flatMap((tspecSymbol) => { 150 | const paths = openapiSchemas[tspecSymbol].properties || {}; 151 | DEBUG({ tspecSymbol, paths }); 152 | return Object.keys(paths).map((path) => { 153 | const obj = paths[path]; 154 | if ('$ref' in obj) { 155 | const [, schemaName] = obj.$ref.split('#/components/schemas/'); 156 | return schemaName; 157 | } 158 | return undefined; 159 | }); 160 | }); 161 | 162 | const isTspecSchema = (key: string) => ( 163 | tspecSymbols.includes(key) || tspecPathSchemas.includes(key) 164 | ) 165 | 166 | const omitPathSchemaFields = (schema: OpenAPIV3.SchemaObject & { mediaType?: string }) => { 167 | const { mediaType, ...rest } = schema; 168 | return rest; 169 | } 170 | 171 | return Object.fromEntries( 172 | Object.entries(openapiSchemas) 173 | .filter(([key]) => !isTspecSchema(key)) 174 | .map(([key, value]) => [key, omitPathSchemaFields(value)]), 175 | ); 176 | }; 177 | 178 | export const defaultGenerateParams = { 179 | specPathGlobs: ['**/*.ts'], 180 | tsconfigPath: 'tsconfig.json', 181 | configPath: 'tspec.config.json', 182 | specVersion: 3, 183 | openapi: { 184 | title: 'Tspec API', 185 | version: '0.0.1', 186 | description: '', 187 | }, 188 | debug: false, 189 | ignoreErrors: true, 190 | } satisfies Tspec.GenerateParams; 191 | 192 | const getGenerateTspecParams = async ( 193 | overrideParams: Tspec.GenerateParams = {}, 194 | ): Promise => { 195 | const configPath = overrideParams.configPath || defaultGenerateParams.configPath; 196 | 197 | if (await isTspecFileConfigAvailable(configPath)) { 198 | const fileConfig = await getTspecConfigFromConfigFile(configPath); 199 | return mergeDeep(mergeDeep(defaultGenerateParams, fileConfig), overrideParams); 200 | } 201 | 202 | return mergeDeep(defaultGenerateParams, overrideParams); 203 | }; 204 | 205 | export const createJsonFile = async (filePath: string, json: any) => { 206 | await fs.mkdir(dirname(filePath), { recursive: true }); 207 | await fs.writeFile(filePath, JSON.stringify(json, null, 2)); 208 | } 209 | 210 | export const generateTspec = async ( 211 | generateParams: Tspec.GenerateParams = {}, 212 | ): Promise => { 213 | const params = await getGenerateTspecParams(generateParams); 214 | 215 | const { 216 | openapiSchemas, tspecSymbols, 217 | } = await getOpenapiSchemas( 218 | params.tsconfigPath || 'tsconfig.json', 219 | params.specPathGlobs, 220 | params.ignoreErrors, 221 | ); 222 | 223 | const paths = getOpenapiPaths(openapiSchemas, tspecSymbols); 224 | const schemas = getOpenapiSchemasOnly(openapiSchemas, tspecSymbols); 225 | 226 | const openapi: OpenAPIV3.Document = { 227 | info: { 228 | title: params.openapi?.title || 'Tspec API', 229 | version: params.openapi?.version || '0.0.1', 230 | description: params.openapi?.description || '', 231 | }, 232 | openapi: (params.specVersion === 3 && '3.0.3') || '3.0.3', 233 | paths, 234 | components: { 235 | schemas, 236 | securitySchemes: params.openapi?.securityDefinitions, 237 | }, 238 | servers: params.openapi?.servers, 239 | }; 240 | 241 | if (params.outputPath) { 242 | await createJsonFile(params.outputPath, openapi); 243 | } 244 | 245 | return openapi; 246 | }; 247 | -------------------------------------------------------------------------------- /packages/tspec/src/generator/openapiGenerator.ts: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import { OpenAPIV3 } from 'openapi-types'; 3 | import * as TJS from 'typescript-json-schema'; 4 | 5 | import { assertIsDefined } from '../utils/types'; 6 | 7 | import { 8 | accessSchema, getObjectPropertyByPath, getPropertyByPath, getTextListPropertyByPath, getTextPropertyByPath, 9 | } from './schemaParser'; 10 | import { SchemaMapping } from './types'; 11 | 12 | export const DEBUG = debug('tspec'); 13 | 14 | type ParameterSchema = TJS.Definition & { 15 | example?: any; 16 | style?: any; 17 | explode?: any; 18 | allowReserved?: any; 19 | allowEmptyValue?: any; 20 | } 21 | 22 | const parseBooleanAnnotation = (value: any) => { 23 | if (value === undefined) { 24 | return undefined; 25 | } 26 | if (value === '' || value === 'true' || value === true) { 27 | return true; 28 | } 29 | return false; 30 | }; 31 | 32 | const getParameters = (obj: TJS.Definition, inType: 'query' | 'path' | 'header' | 'cookie') => { 33 | const { properties, required } = obj; 34 | if (!properties) { 35 | return undefined; 36 | } 37 | return Object.entries(properties).map(([key, schema]) => { 38 | const { 39 | description, style, explode, allowReserved, allowEmptyValue,...rest 40 | } = schema as ParameterSchema; 41 | return { 42 | description, 43 | name: key, 44 | in: inType, 45 | required: inType === 'path' ? true : (required || []).includes(key), 46 | schema: rest, 47 | style, 48 | explode: parseBooleanAnnotation(explode), 49 | allowReserved: parseBooleanAnnotation(allowReserved), 50 | allowEmptyValue: parseBooleanAnnotation(allowEmptyValue), 51 | }; 52 | }); 53 | }; 54 | 55 | interface ResolveParametersParams { 56 | path: TJS.Definition | undefined; 57 | query: TJS.Definition | undefined; 58 | header: TJS.Definition | undefined; 59 | cookie: TJS.Definition | undefined; 60 | }; 61 | 62 | const resolveParameters = ({ path, query, header, cookie }: ResolveParametersParams) => { 63 | const pathParams = (path && getParameters(path, 'path')) || []; 64 | const queryParams = (query && getParameters(query, 'query')) || []; 65 | const headerParams = (header && getParameters(header, 'header')) || []; 66 | const cookieParams = (cookie && getParameters(cookie, 'cookie')) || []; 67 | return [...pathParams, ...queryParams, ...headerParams, ...cookieParams]; 68 | }; 69 | 70 | const omitPathSchemaFields = (schema: OpenAPIV3.SchemaObject & { mediaType?: string }) => { 71 | const { mediaType, ...rest } = schema; 72 | return rest; 73 | } 74 | 75 | const isNoContentSchema = (schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject): boolean => ( 76 | 'type' in schema && schema.type === 'string' 77 | && 'enum' in schema && Array.isArray(schema.enum) 78 | && schema.enum.length === 1 && schema.enum[0] === '' 79 | ) 80 | 81 | 82 | export const getOpenapiPaths = ( 83 | openapiSchemas: SchemaMapping, 84 | tspecSymbols: string[], 85 | ): OpenAPIV3.PathsObject => { 86 | const openapiPaths: OpenAPIV3.PathsObject = {}; 87 | 88 | const specs = tspecSymbols.flatMap((tspecSymbol) => { 89 | const paths = openapiSchemas[tspecSymbol].properties || {}; 90 | return Object.keys(paths).flatMap((path) => { 91 | const methods = accessSchema(paths[path], openapiSchemas)?.properties || {}; 92 | return Object.keys(methods).map((method) => { 93 | const spec = accessSchema(methods[method], openapiSchemas); 94 | assertIsDefined(spec); 95 | return { 96 | controllerName: tspecSymbol, path, method, spec, 97 | }; 98 | }); 99 | }); 100 | }); 101 | 102 | specs.forEach(({ 103 | controllerName, path, method, spec, 104 | }) => { 105 | DEBUG({ controllerName, path, method }); 106 | DEBUG({ spec: JSON.stringify(spec, null, 2) }); 107 | const url = getTextPropertyByPath(spec, 'url', openapiSchemas, { required: true }); 108 | const operationId = getTextPropertyByPath(spec, 'operationId', openapiSchemas); 109 | const summary = getTextPropertyByPath(spec, 'summary', openapiSchemas); 110 | const description = getTextPropertyByPath(spec, 'description', openapiSchemas); 111 | const security = getTextPropertyByPath(spec, 'security', openapiSchemas); 112 | const tags = getTextListPropertyByPath(spec, 'tags', openapiSchemas); 113 | const responses = getObjectPropertyByPath( 114 | spec, 115 | 'responses', 116 | openapiSchemas, 117 | ) || { properties: {} }; 118 | 119 | const pathParams = getObjectPropertyByPath(spec, 'path', openapiSchemas) as any; 120 | const queryParams = getObjectPropertyByPath(spec, 'query', openapiSchemas) as any; 121 | const headerParams = getObjectPropertyByPath(spec, 'header', openapiSchemas) as any; 122 | const cookieParams = getObjectPropertyByPath(spec, 'cookie', openapiSchemas) as any; 123 | const bodyParams = getObjectPropertyByPath(spec, 'body', openapiSchemas) as any; 124 | 125 | const operation = { 126 | operationId: operationId || `${controllerName}_${method}_${path}`, 127 | tags, 128 | summary, 129 | description, 130 | security: security && [{ [security]: [] }], 131 | parameters: resolveParameters({ 132 | path: pathParams, 133 | query: queryParams, 134 | header: headerParams, 135 | cookie: cookieParams, 136 | }), 137 | requestBody: bodyParams && { 138 | description: bodyParams.description, 139 | required: true, 140 | content: { 141 | [bodyParams?.mediaType || 'application/json']: { 142 | schema: omitPathSchemaFields(bodyParams), 143 | }, 144 | }, 145 | }, 146 | responses: Object.fromEntries( 147 | Object.keys(responses.properties).map((code) => { 148 | const schema = getPropertyByPath(responses, code, openapiSchemas); 149 | const { description = '', mediaType } = schema as any; 150 | const contentSchema = responses.properties[code]; 151 | const isNoContent = isNoContentSchema(contentSchema); 152 | const resSchema = { 153 | description, 154 | content: isNoContent ? undefined : { 155 | [mediaType || 'application/json']: { 156 | schema: contentSchema, 157 | }, 158 | }, 159 | }; 160 | return [code, resSchema]; 161 | }), 162 | ), 163 | }; 164 | (openapiPaths[url] ||= {})[method as OpenAPIV3.HttpMethods] = operation as any; 165 | }); 166 | 167 | return openapiPaths; 168 | }; 169 | -------------------------------------------------------------------------------- /packages/tspec/src/generator/openapiSchemaConverter.ts: -------------------------------------------------------------------------------- 1 | import convert from 'json-schema-to-openapi-schema'; // TODO: 이게 정말 필요한건지 체크 필요. 2 | import * as TJS from 'typescript-json-schema'; 3 | 4 | import { SchemaMapping } from './types'; 5 | 6 | const isSchemaNullableOnly = (s: any) => ( 7 | Object.keys(s).filter((key) => s[key] !== undefined).length === 1 && s.nullable 8 | ); 9 | 10 | const convertCombinedNullableInner = (schema: any, field: 'anyOf' | 'oneOf') => { 11 | const types = schema[field] || []; 12 | const nullable = types.some((s: any) => isSchemaNullableOnly(s)) || undefined; 13 | return { 14 | ...schema, 15 | [field]: types.filter((s: any) => !isSchemaNullableOnly(s)), 16 | nullable, 17 | }; 18 | }; 19 | 20 | /** NOTE(hyeonseong): when anyOf or oneOf contains null, it should be nullable. */ 21 | const handleCombinedNullable = (schema: any): any => { // TODO: fix types 22 | if (schema.anyOf) { 23 | return convertCombinedNullableInner(schema, 'anyOf'); 24 | } 25 | if (schema.oneOf) { 26 | return convertCombinedNullableInner(schema, 'oneOf'); 27 | } 28 | return schema; 29 | }; 30 | 31 | const convertToNullableSchema = (schema: any): any => { 32 | if (schema.type && !Array.isArray(schema.type) && schema.type === 'null') { 33 | return { 34 | ...schema, 35 | type: undefined, 36 | nullable: true, 37 | }; 38 | } 39 | if (schema.type && Array.isArray(schema.type) && schema.type.length > 1) { 40 | const nullable = schema.type.includes('null'); 41 | const types = schema.type.filter((type: any) => type !== 'null'); 42 | if (types.length === 1) { 43 | return { 44 | ...schema, 45 | type: types[0], 46 | nullable, 47 | }; 48 | } 49 | return { 50 | ...schema, 51 | type: undefined, 52 | oneOf: schema.type 53 | .filter((type: any) => type !== 'null') 54 | .map((type: any) => ({ ...schema, type })), 55 | nullable, 56 | }; 57 | } 58 | return schema; 59 | }; 60 | 61 | const handleExamples = (schema: any): any => { // TODO: fix types 62 | if (schema.examples) { 63 | const { examples, ...rest } = schema; 64 | return { 65 | ...rest, 66 | example: Array.isArray(examples) ? examples[0] : examples, 67 | }; 68 | } 69 | return schema; 70 | }; 71 | 72 | const handleDeprecated = (schema: any): any => { // TODO: fix types 73 | if (schema.deprecated !== undefined && schema.deprecated !== false) { 74 | const { deprecated, ...rest } = schema; 75 | return { 76 | ...rest, 77 | deprecated: true, 78 | }; 79 | } 80 | return schema; 81 | }; 82 | 83 | const convertToOpenapiTypes = (schema: any): any => { // TODO: fix types 84 | if (Array.isArray(schema)) { 85 | return schema.map((s) => convertToOpenapiTypes(s)); 86 | } 87 | if (schema && typeof schema === 'object') { 88 | const nullableSchema = convertToNullableSchema(schema); 89 | const convertedSchema = Object.fromEntries( 90 | Object.entries(nullableSchema).map(([key, value]) => [key, convertToOpenapiTypes(value)]), 91 | ); 92 | const handlers = [handleCombinedNullable, handleExamples, handleDeprecated]; 93 | return handlers.reduce((acc, handler) => handler(acc), convertedSchema); 94 | } 95 | return schema; 96 | }; 97 | 98 | const findAllRefAndReplace = (schema: any, nameMapping: any): any => { // TODO: fix types 99 | if (Array.isArray(schema)) { 100 | return schema.map((s) => findAllRefAndReplace(s, nameMapping)); 101 | } 102 | if (schema && typeof schema === 'object') { 103 | if (schema.$ref) { 104 | const [, schemaName] = schema.$ref.split('#/definitions/'); 105 | return { 106 | ...schema, 107 | $ref: `#/components/schemas/${nameMapping[schemaName]}`, 108 | }; 109 | } 110 | return Object.fromEntries( 111 | Object.entries(schema).map(([key, value]) => [key, findAllRefAndReplace(value, nameMapping)]), 112 | ); 113 | } 114 | return schema; 115 | }; 116 | 117 | const escapeSchemaNames = (schemas: SchemaMapping) => { 118 | const escapedNameMapping = Object.fromEntries(Object.keys(schemas).map((schemaName) => ( 119 | // only contain the characters A-Z a-z 0-9 - . _ 120 | [schemaName, schemaName.replace(/[^A-Za-z0-9_.-]/g, '_')] 121 | ))); 122 | const escapedSchemas = Object.fromEntries(Object.entries(schemas).map(([schemaName, schema]) => ( 123 | [escapedNameMapping[schemaName], schema] 124 | ))); 125 | // eslint-disable-next-line max-len 126 | return findAllRefAndReplace(escapedSchemas, escapedNameMapping) as SchemaMapping; // TODO: fix types 127 | }; 128 | 129 | export const convertToOpenapiSchemas = async ( 130 | jsonSchemas: TJS.Definition, 131 | ): Promise => { 132 | const convertedJsonSchemas = convertToOpenapiTypes(jsonSchemas); 133 | const openapiSchemas = await convert(convertedJsonSchemas) as SchemaMapping; 134 | return escapeSchemaNames(openapiSchemas); 135 | }; 136 | -------------------------------------------------------------------------------- /packages/tspec/src/generator/schemaParser.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIV3 } from 'openapi-types'; 2 | 3 | import { Schema, SchemaMapping } from './types'; 4 | 5 | export const accessSchema = ( 6 | obj: Schema | undefined, 7 | schemas: SchemaMapping, 8 | ): OpenAPIV3.SchemaObject | undefined => { 9 | if (!obj) { 10 | return undefined; 11 | } 12 | if ('$ref' in obj) { 13 | const [, schemaName] = obj.$ref.split('#/components/schemas/'); 14 | return schemas[schemaName]; 15 | } 16 | return obj; 17 | }; 18 | 19 | export const accessProperty = ( 20 | obj: Schema | undefined, 21 | key: string, 22 | schemas: SchemaMapping, 23 | ): Schema | undefined => { 24 | const schema = accessSchema(obj, schemas); 25 | if (!schema) { 26 | return undefined; 27 | } 28 | const combinedSchema = schema.allOf || schema.oneOf || schema.anyOf; 29 | if (combinedSchema) { 30 | return combinedSchema.map((o) => accessProperty(o, key, schemas)).find((o) => o); 31 | } 32 | const value = schema.properties?.[key]; 33 | return value && accessSchema(value, schemas); 34 | }; 35 | 36 | export const getPropertyByPath = ( 37 | obj: Schema | undefined, 38 | path: string, 39 | schemas: SchemaMapping, 40 | ): Schema | undefined => { 41 | const [first, ...rest] = path.split('.'); 42 | const firstValue = accessProperty(obj, first, schemas); 43 | if (rest.length === 0) { 44 | return firstValue; 45 | } 46 | return getPropertyByPath(firstValue, rest.join('.'), schemas); 47 | }; 48 | 49 | const getText = (obj: Schema | undefined): string | undefined => { 50 | if (!obj || '$ref' in obj || obj.type !== 'string' || obj.enum?.length !== 1) { 51 | return undefined; 52 | } 53 | return obj.enum[0]; 54 | }; 55 | 56 | export const getTextPropertyByPath = ( 57 | obj: Schema, path: string, schemas: SchemaMapping, options?: O, 58 | ): O extends { required: true } ? string : string | undefined => { 59 | const text = getText(getPropertyByPath(obj, path, schemas)); 60 | if (options?.required === true && !text) { 61 | throw new Error(`Invalid '${path}' in ApiSpec`); 62 | } 63 | return text as string; 64 | }; 65 | 66 | export const getTextListPropertyByPath = ( 67 | obj: Schema, 68 | path: string, 69 | schemas: SchemaMapping, 70 | options?: { required: boolean }, 71 | ): string[] => { 72 | const value = getPropertyByPath(obj, path, schemas); 73 | if (!value || '$ref' in value || value.type !== 'array' || !value.items) { 74 | if (options?.required === true) { 75 | throw new Error(`Invalid '${path}' in ApiSpec`); 76 | } 77 | return []; 78 | } 79 | return (value.items as Schema[]) 80 | .map((item) => getText(item)).filter((item): item is string => !!item); 81 | }; 82 | 83 | export const getObjectPropertyByPath = ( 84 | obj: Schema, path: string, schemas: SchemaMapping, options?: O, 85 | ) => { 86 | const value = getPropertyByPath(obj, path, schemas); 87 | if (!value || '$ref' in value || value.type !== 'object' || !value.properties) { 88 | if (options?.required === true) { 89 | throw new Error( 90 | `Invalid '${path}' in ${JSON.stringify(obj)}; value: ${JSON.stringify(value)}`, 91 | ); 92 | } 93 | return undefined; 94 | } 95 | return { ...value, properties: value.properties }; 96 | }; 97 | -------------------------------------------------------------------------------- /packages/tspec/src/generator/types.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIV3 } from 'openapi-types'; 2 | 3 | export type Schema = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; 4 | export type SchemaMapping = Record; 5 | -------------------------------------------------------------------------------- /packages/tspec/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generator'; 2 | export * from './server'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/tspec/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | import { createProxyMiddleware } from 'http-proxy-middleware'; 4 | import swaggerUI from 'swagger-ui-express'; 5 | 6 | import { generateTspec } from '../generator'; 7 | import { Tspec } from '../types'; 8 | 9 | export const initTspecServer = async (options?: Tspec.InitTspecServerOptions) => { 10 | const { port = 7000, proxyHost, ...generateOptions } = options || {}; 11 | const app = express(); 12 | const openapiSpec = await generateTspec(generateOptions); 13 | app.use('/docs', swaggerUI.serve, swaggerUI.setup(openapiSpec)); 14 | if (proxyHost) { 15 | app.use('/', createProxyMiddleware({ 16 | target: proxyHost, 17 | changeOrigin: true, 18 | logLevel: 'warn', 19 | })); 20 | } 21 | app.listen(port, () => { 22 | // eslint-disable-next-line no-console 23 | console.log(`Tspec API server is running on http://localhost:${port}/docs`); 24 | if (proxyHost) { 25 | // eslint-disable-next-line no-console 26 | console.log(`Tspec API server is proxying to ${proxyHost}`); 27 | } 28 | }); 29 | }; 30 | 31 | export const TspecDocsMiddleware = async ( 32 | generateOptions?: Tspec.GenerateParams, 33 | ): Promise => { 34 | const openapiSpec = await generateTspec(generateOptions); 35 | return [...swaggerUI.serve, swaggerUI.setup(openapiSpec)]; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/tspec/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tspec'; 2 | -------------------------------------------------------------------------------- /packages/tspec/src/types/json-schema-to-openapi-schema.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'json-schema-to-openapi-schema' { 2 | export default function convert(schema: any): any; 3 | } 4 | -------------------------------------------------------------------------------- /packages/tspec/src/types/tspec.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-unresolved 2 | import { OpenAPIV3 } from 'openapi-types'; 3 | 4 | export namespace Tspec { 5 | type PathUrl = `/${string}`; 6 | export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options' | 'head'; 7 | 8 | type PathParamValue = string | number; 9 | export type PathParam = { [key: string]: PathParamValue } 10 | 11 | type QueryParamValue = string | number | boolean | string[] | number[] | boolean[]; 12 | export type QueryParam = { [key: string]: QueryParamValue } 13 | 14 | type HeaderParamValue = string | number; 15 | export type HeaderParam = { [name: string]: HeaderParamValue } 16 | 17 | type CookieParamValue = string | number; 18 | export type CookieParam = { [name: string]: CookieParamValue } 19 | 20 | export interface ApiSpecBase< 21 | Res extends any = any, 22 | P extends PathParam = PathParam, 23 | Q extends QueryParam = QueryParam, 24 | H extends HeaderParam = HeaderParam, 25 | C extends CookieParam = CookieParam, 26 | > { 27 | operationId?: string, 28 | summary?: string, 29 | description?: string, 30 | tags?: string[], 31 | security?: string, 32 | path?: P, query?: Q, body?: {}, header?: H, cookie?: C, 33 | responses?: { [code: number]: Res }, error?: { [key: string]: {} }, 34 | } 35 | 36 | type ExpressHandler = (req: any, res: any, next: any, ...args: any[]) => any; 37 | 38 | type ApiSpecInput = ApiSpecBase & { 39 | handler?: ExpressHandler, 40 | }; 41 | 42 | type ReqOf = Parameters[0]; 43 | type ResOf = Parameters[1]; 44 | type PathOf = ReqOf['params']; 45 | type QueryOf = ReqOf['query']; 46 | type BodyOf = ReqOf['body']; 47 | type ResBodyOf = NonNullable['json']>[0]>; 48 | 49 | export type ApiSpec = T & { 50 | path: T['path'] extends {} ? T['path'] 51 | : T['handler'] extends ExpressHandler ? PathOf 52 | : never, 53 | responses: T['responses'] extends { [code: number]: any } ? T['responses'] 54 | : T['handler'] extends ExpressHandler ? { 200: ResBodyOf } 55 | : never, 56 | body: T['body'] extends {} ? T['body'] 57 | : T['handler'] extends ExpressHandler ? BodyOf 58 | : never, 59 | query: T['query'] extends {} ? T['query'] 60 | : T['handler'] extends ExpressHandler ? QueryOf 61 | : never, 62 | __handler: T['handler'], 63 | } 64 | 65 | type Path = { [method in HttpMethod]?: ApiSpecInput }; 66 | type Paths = { [path: PathUrl]: Path }; 67 | 68 | interface Controller

extends Pick { 69 | basePath?: string, 70 | paths: P, 71 | } 72 | 73 | type WithBasePath = Base extends string 74 | ? `${Base}${U}` 75 | : U; 76 | 77 | type ParsePathKeys = U extends `${string}/{${infer P}}${infer R}` 78 | ? R extends string 79 | ? P | ParsePathKeys 80 | : P 81 | : never; 82 | 83 | export type DefineApiSpec]: { 85 | [M in HttpMethod]?: Omit & { 86 | path?: { [key in ParsePathKeys>]: PathParamValue }, 87 | handler?: ExpressHandler, 88 | } 89 | } 90 | }>> = { 91 | [P in Extract]: { 92 | [M in Extract]: T['paths'][P][M] extends ApiSpecInput ? ( 93 | Omit, 'tags' | 'security'> & { 94 | method: M, 95 | url: WithBasePath, 96 | security: T['paths'][P][M]['security'] extends string 97 | ? T['paths'][P][M]['security'] 98 | : T['security'], 99 | // concat tuple type T['tags'] and T['specs'][P][M]['tags'] 100 | tags: [ 101 | ...(T['tags'] extends string[] ? T['tags'] : []), 102 | ...(T['paths'][P][M]['tags'] extends string[] ? T['paths'][P][M]['tags'] : []) 103 | ], 104 | } 105 | ) : never 106 | } 107 | }; 108 | 109 | export interface GenerateParams { 110 | specPathGlobs?: string[], 111 | tsconfigPath?: string, 112 | configPath?: string, 113 | outputPath?: string, 114 | specVersion?: 3, 115 | openapi?: { 116 | title?: string, 117 | version?: string, 118 | description?: string, 119 | securityDefinitions?: OpenAPIV3.ComponentsObject['securitySchemes'], 120 | servers?: OpenAPIV3.ServerObject[], 121 | }, 122 | debug?: boolean, 123 | ignoreErrors?: boolean, 124 | } 125 | 126 | export interface InitTspecServerOptions extends GenerateParams { 127 | port?: number, 128 | proxyHost?: string, 129 | } 130 | 131 | export type OpenapiDocument = OpenAPIV3.Document; 132 | 133 | /** 134 | * Empty Response Body 135 | */ 136 | export type NoContent = ""; 137 | 138 | /** 139 | * @TJS-type integer 140 | * @examples [1] 141 | * */ 142 | export type Integer = number; 143 | 144 | /** 145 | * @TJS-format date 146 | * @examples ["2023-03-30"] 147 | */ 148 | export type DateString = string; 149 | 150 | /** 151 | * @TJS-format date-time 152 | * @examples ["2023-03-30T12:00:00Z"] 153 | * */ 154 | export type DateTimeString = string; 155 | 156 | /** 157 | * @TJS-format password 158 | * @examples ["password"] 159 | */ 160 | export type PasswordString = string; 161 | 162 | /** 163 | * @TJS-format byte 164 | * @examples ["U3dhZ2dlciByb2Nrcw=="] 165 | */ 166 | export type ByteString = string; 167 | 168 | /** 169 | * @TJS-format binary 170 | * @examples ["\x00\x00\x00\x02"] 171 | */ 172 | export type BinaryString = string; 173 | 174 | /** 175 | * @items.type string 176 | * @items.format binary 177 | */ 178 | export type BinaryStringArray = string[]; 179 | 180 | /** 181 | * @TJS-format email 182 | * @examples ["test@test.com"] 183 | * */ 184 | export type EmailString = string; 185 | 186 | /** 187 | * @TJS-format uuid 188 | * @examples ["00000000-0000-0000-0000-000000000000"] 189 | * */ 190 | export type UuidString = string; 191 | 192 | /** 193 | * @TJS-format uri 194 | * @examples ["http://localhost"] 195 | * */ 196 | export type UrlString = string; 197 | 198 | /** 199 | * @TJS-format uri 200 | * @examples ["https://picsum.photos/200/300"] 201 | */ 202 | export type ImageUrlString = string; 203 | 204 | /** 205 | * @TJS-format hostname 206 | * @examples ["localhost"] 207 | * */ 208 | export type HostnameString = string; 209 | 210 | /** 211 | * @TJS-format ipv4 212 | * @examples ["127.0.0.1"] 213 | * */ 214 | export type Ipv4String = string; 215 | 216 | /** 217 | * @TJS-format ipv6 218 | * @examples ["::1"] 219 | * */ 220 | export type Ipv6String = string; 221 | 222 | /** 223 | * @TJS-format json-pointer 224 | * @examples ["/"] 225 | */ 226 | export type JsonPointerString = string; 227 | } 228 | -------------------------------------------------------------------------------- /packages/tspec/src/utils/merge.ts: -------------------------------------------------------------------------------- 1 | type Object = { [key: string]: any }; 2 | 3 | const isObject = (item: any) => ( 4 | item && typeof item === 'object' && !Array.isArray(item) 5 | ); 6 | 7 | export const mergeDeep = (target: T, source: S): T & S => { 8 | let output = { ...target } as T & S; 9 | if (isObject(target) && isObject(source)) { 10 | Object.keys(source).forEach(key => { 11 | const value = source[key]; 12 | if (value === undefined) { 13 | return; 14 | } 15 | if (isObject(value)) { 16 | if (!(key in target)) { 17 | Object.assign(output, { [key]: value }); 18 | } else { 19 | output[key as keyof S] = mergeDeep(target[key], value); 20 | } 21 | } else { 22 | Object.assign(output, { [key]: value }); 23 | } 24 | }); 25 | } 26 | return output; 27 | } 28 | -------------------------------------------------------------------------------- /packages/tspec/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export function isDefined(val: T): val is NonNullable { 2 | return val !== undefined && val !== null; 3 | } 4 | 5 | export function assertIsDefined( 6 | val: T, 7 | msg?: string, 8 | ): asserts val is NonNullable { 9 | if (!isDefined(val)) { 10 | throw new Error( 11 | `Expected 'val' to be defined, but received: ${val};${msg || ''}`, 12 | ); 13 | } 14 | } 15 | 16 | /** 17 | * NOTE(hyeonseong): This function does exhaustiveness checking to ensure 18 | * that you have discriminated a union so that no type remains. 19 | * Use this to get the typescript compiler to help discover cases that were not considered. 20 | */ 21 | export function checkNever(x: never) { 22 | throw new Error(`Unexpected object: ${JSON.stringify(x)}`); 23 | } 24 | -------------------------------------------------------------------------------- /packages/tspec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | "composite": false, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "esnext", /* Specify what module code is generated. */ 29 | "rootDir": "src", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | "baseUrl": "src", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | "typeRoots": ["node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "dist", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | "newLine": "lf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | "declarationDir": "./dist", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "include": ["src/**/*"], 104 | "exclude": ["rollup.config.ts", "dist"] 105 | } 106 | --------------------------------------------------------------------------------