diff --git a/.eslintrc.js b/.eslintrc.js index 5aa8146..04e17c2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,23 +1,23 @@ -module.exports = { - "env": { - "es2021": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/ban-types": 'off', - '@typescript-eslint/no-explicit-any': 'off' - }, - "ignorePatterns": ["example/*", "tests/**/*"] -} +module.exports = { + "env": { + "es2021": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/ban-types": 'off', + '@typescript-eslint/no-explicit-any': 'off' + }, + "ignorePatterns": ["example/*", "tests/**/*"] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 886beac..39087ca 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,11 @@ -version: 2 -updates: - - package-ecosystem: 'npm' - directory: './' - schedule: - interval: 'daily' - - - package-ecosystem: 'github-actions' - directory: './' - schedule: - interval: 'daily' +version: 2 +updates: + - package-ecosystem: 'npm' + directory: './' + schedule: + interval: 'daily' + + - package-ecosystem: 'github-actions' + directory: './' + schedule: + interval: 'daily' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35d12dc..a1c8612 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,28 +1,28 @@ -name: Code CI - -on: - push: - pull_request: - -jobs: - build: - name: Build and test code - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup bun - uses: oven-sh/setup-bun@v1 - with: - bun-version: latest - - - name: Install packages - run: bun install - - - name: Build code - run: bun run build - - - name: Test - run: bun run test +name: Code CI + +on: + push: + pull_request: + +jobs: + build: + name: Build and test code + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install packages + run: bun install + + - name: Build code + run: bun run build + + - name: Test + run: bun run test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f20844b..a663f35 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,50 +1,50 @@ -name: Publish - -on: - release: - types: [published] - -defaults: - run: - shell: bash - -permissions: - id-token: write - -env: - # Enable debug logging for actions - ACTIONS_RUNNER_DEBUG: true - -jobs: - publish-npm: - name: 'Publish: npm Registry' - runs-on: ubuntu-latest - steps: - - name: 'Checkout' - uses: actions/checkout@v4 - - - name: 'Setup Bun' - uses: oven-sh/setup-bun@v1 - with: - bun-version: latest - registry-url: "https://registry.npmjs.org" - - - uses: actions/setup-node@v4 - with: - node-version: '20.x' - registry-url: 'https://registry.npmjs.org' - - - name: Install packages - run: bun install - - - name: Build code - run: bun run build - - - name: Test - run: bun run test - - - name: 'Publish' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - npm publish --provenance --access=public +name: Publish + +on: + release: + types: [published] + +defaults: + run: + shell: bash + +permissions: + id-token: write + +env: + # Enable debug logging for actions + ACTIONS_RUNNER_DEBUG: true + +jobs: + publish-npm: + name: 'Publish: npm Registry' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@v4 + + - name: 'Setup Bun' + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + registry-url: "https://registry.npmjs.org" + + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + + - name: Install packages + run: bun install + + - name: Build code + run: bun run build + + - name: Test + run: bun run test + + - name: 'Publish' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + npm publish --provenance --access=public diff --git a/.gitignore b/.gitignore index 7c4dffa..c2f9149 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -.DS_Store - -node_modules -.pnpm-debug.log -dist - +.DS_Store + +node_modules +.pnpm-debug.log +dist + build \ No newline at end of file diff --git a/.npmignore b/.npmignore index 0c33676..cac5319 100644 --- a/.npmignore +++ b/.npmignore @@ -1,26 +1,26 @@ -.git -.github -.gitignore -.prettierrc -.cjs.swcrc -.es.swcrc -bun.lockb - -node_modules -tsconfig.json -pnpm-lock.yaml -jest.config.js -nodemon.json - -example -src -tests -test -CHANGELOG.md -.eslintrc.js -tsconfig.cjs.json -tsconfig.esm.json -tsconfig.dts.json - -src -build.ts +.git +.github +.gitignore +.prettierrc +.cjs.swcrc +.es.swcrc +bun.lockb + +node_modules +tsconfig.json +pnpm-lock.yaml +jest.config.js +nodemon.json + +example +src +tests +test +CHANGELOG.md +.eslintrc.js +tsconfig.cjs.json +tsconfig.esm.json +tsconfig.dts.json + +src +build.ts diff --git a/.prettierrc b/.prettierrc index de30132..1353abf 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ -{ - "useTabs": true, - "tabWidth": 4, - "semi": false, - "singleQuote": true, - "trailingComma": "none" -} +{ + "useTabs": true, + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "trailingComma": "none" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9af20..901326a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,158 @@ +# 1.4.11 - 25 Sep 2025 +Improvement: +- add `embedSpec` option to embed spec into documentation page +- add `data-configuration` for adding custom Scalar configuration + +# 1.4.10 - 22 Sep 2025 +Improvement: +- [#267](https://github.com/elysiajs/elysia-openapi/pull/267) enum eupport for OpenAPI +- populate params from path when no schema is provided +- type gen: accept number as path segment +- when failed to convert type to OpenAPI, log the error and continue +- type gen: use `process.getBuiltinModule` to import native node module conditionally +- type gen: `fromTypes` now accept direct declaration +- export `fromTypes` from index +- add test case for type gen, and OpenAPI schema + +Bug fix: +- [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId +- [#230](https://github.com/elysiajs/elysia-openapi/issues/230) do not inline Response if is Cloudflare Worker + +# 1.4.9 - 21 Sep 2025 +Improvement: +- type gen: match special, ad non-english character + +# 1.4.8 - 21 Sep 2025 +Improvement: +- type gen: handle array delimiter correctly + +# 1.4.7 - 21 Sep 2025 +Improvement: +- type gen: remove readonly from generated type to fix readonly tuple + +# 1.4.6 - 21 Sep 2025 +Bug fix: +- type gen: handle inline 200 response schema assignment + +# 1.4.5 - 20 Sep 2025 +Improvement: +- reference model now handle content type correctly +- type doesn't show up when body is primitive type + +Bug fix: +- remove unintentional console.log +- reference model doesn't show up when using as response + +# 1.4.4 - 20 Sep 2025 +Improvement: +- cast `exclude.methods` to lowercase when checking for method exclusion +- type generator: handle non-intersect routes eg. group/guard + +Change: +- type generator: enable type error log +- type generator: do not remove temp files when debug is enabled + +Bug fix: +- exclude `options` method by default + +# 1.4.3 - 18 Sep 2025 +Improvement: +- unwrap model reference into parameter schema +- add warning for standard schema without `toJSONSchema` method +- remove `Provider` from generic type to allow auto-completion +- auto-completion for `mapJSONSchema` +- log error when failed to OpenAPI JSON + +# 1.4.2 - 14 Sep 2025 +Bug fix: +- remove xsschema from dependencies + +# 1.4.1 - 14 Sep 2025 +Feature: +- add `mapJsonSchema` to add custom JSON Schema mapping + +Bug fix: +- build error when using --compile + +Change: +- remove xsschema + +# 1.4.0 - 13 Sep 2025 +Improvement: +- support Standard Schema to OpenAPI +- use respective content type based on schema + +# 1.3.12 - 10 Sep 2025 +Improvement: +- type generator: add `compilerOptions`, `tmpRoot` options + +Bug fix: +- type generator: use absolute path to generate types + +# 1.3.11 - 9 Sep 2025 +Bug fix: +- type generator: convert Windows path to Unix for TypeScript CLI + +# 1.3.10 - 5 Sep 2025 +Feature: +- type generator: accept `.d.ts` to prevent type generation in production + +Bug fix: +- type generator: loose path generated file mapping + +# 1.3.9 - 4 Sep 2025 +Bug fix: +- type generator: loose path generated file mapping + +# 1.3.8 - 4 Sep 2025 +Bug fix: +- type generator: if failed, do not generate empty JSON +- type generator: friendly error message, and better error handling +- type generator: overrideOutputPath can be string + +# 1.3.7 - 3 Sep 2025 +Improvement: +- type generator: clean up temp files after generation + +Bug fix: +- type generator: imbalance bracket or something + +# 1.3.6 - 3 Sep 2025 +Improvement: +- type generator: add loose path type matching +- type generator: try loose matching for schema type + +# 1.3.5 - 3 Sep 2025 +Bug fix: +- type generator: merge references with existing response status +- type generator: handle union type + +# 1.3.4 - 3 Sep 2025 +Bug fix: +- type generator: exclude unknown type + +# 1.3.3 - 3 Sep 2025 +Bug fix: +- type generator: collapse path when trying to access from dist + +# 1.3.2 - 2 Sep 2025 +Feature: +- add `withHeader` for adding custom headers to response schema +- spread all possible path for optional params +- provider can be `null` to disable provider +- export `toOpenAPI` to generate spec programmatically +- add `openapi/gen` to automatically generate OpenAPI spec from types + +Breaking change: +- rename `@elysiajs/swagger` to `@elysiajs/openapi` +- map all `swagger`, and `scalar` prefix to respective `swagger` and `scalar` properties +- rename `swaggerConfig`, and `scalarConfig` to `swagger` and `scalar` respectively +- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles` to property of `excludes` + +# 1.3.1 - 28 Jun 2025 +Bug fix: +- Using relative path for specPath + # 1.3.0-exp.1 - 1 May 2025 Improvement: - use static response for documentation page diff --git a/LICENSE b/LICENSE index 7419435..ffe9394 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ -Copyright 2022 saltyAom - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright 2022 saltyAom + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 5993cc0..757a382 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,101 @@ -# @elysiajs/swagger -Plugin for [elysia](https://github.com/elysiajs/elysia) to auto-generate Swagger page. - -## Installation -```bash -bun add @elysiajs/swagger -``` - -## Example -```typescript -import { Elysia, t } from 'elysia' -import { swagger } from '@elysiajs/swagger' - -const app = new Elysia() - .use(swagger()) - .get('/', () => 'hi', { response: t.String({ description: 'sample description' }) }) - .post( - '/json/:id', - ({ body, params: { id }, query: { name } }) => ({ - ...body, - id, - name - }), - { - params: t.Object({ - id: t.String() - }), - query: t.Object({ - name: t.String() - }), - body: t.Object({ - username: t.String(), - password: t.String() - }), - response: t.Object({ - username: t.String(), - password: t.String(), - id: t.String(), - name: t.String() - }, { description: 'sample description' }) - } - ) - .listen(8080); -``` - -Then go to `http://localhost:8080/swagger`. - -# config - -## provider -@default 'scalar' -Choose between [Scalar](https://github.com/scalar/scalar) & [SwaggerUI](https://github.com/swagger-api/swagger-ui) - -## scalar -Customize scalarConfig, refers to [Scalar config](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) - -## swagger -Customize Swagger config, refers to [Swagger 3.0.3 config](https://swagger.io/specification/v3) - -## path -@default '/swagger' - -The endpoint to expose Swagger - -## excludeStaticFile -@default true - -Determine if Swagger should exclude static files. - -## exclude -@default [] - -Paths to exclude from the Swagger endpoint +# @elysiajs/openapi +Plugin for [elysia](https://github.com/elysiajs/elysia) to auto-generate API documentation page. + +## Installation +```bash +bun add @elysiajs/openapi +``` + +## Example +```typescript +import { Elysia, t } from 'elysia' +import { openapi } from '@elysiajs/openapi' + +const app = new Elysia() + .use(openapi()) + .get('/', () => 'hi', { response: t.String({ description: 'sample description' }) }) + .post( + '/json/:id', + ({ body, params: { id }, query: { name } }) => ({ + ...body, + id, + name + }), + { + params: t.Object({ + id: t.String() + }), + query: t.Object({ + name: t.String() + }), + body: t.Object({ + username: t.String(), + password: t.String() + }), + response: t.Object({ + username: t.String(), + password: t.String(), + id: t.String(), + name: t.String() + }, { description: 'sample description' }) + } + ) + .listen(8080); +``` + +Then go to `http://localhost:8080/openapi`. + +# config + +## enabled +@default true +Enable/Disable the plugin + +## documentation +OpenAPI documentation information + +@see https://spec.openapis.org/oas/v3.0.3.html + +## exclude +Configuration to exclude paths or methods from documentation + +## exclude.methods +List of methods to exclude from documentation + +## exclude.paths +List of paths to exclude from documentation + +## exclude.staticFile +@default true + +Exclude static file routes from documentation + +## exclude.tags +List of tags to exclude from documentation + +## path +@default '/openapi' + +The endpoint to expose OpenAPI documentation frontend + +## provider +@default 'scalar' + +OpenAPI documentation frontend between: +- [Scalar](https://github.com/scalar/scalar) +- [SwaggerUI](https://github.com/swagger-api/swagger-ui) +- null: disable frontend + +## references +Additional OpenAPI reference for each endpoint + +## scalar +Scalar configuration, refers to [Scalar config](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) + +## specPath +@default '/${path}/json' + +The endpoint to expose OpenAPI specification in JSON format + +## swagger +Swagger config, refers to [Swagger config](https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/) diff --git a/build.ts b/build.ts index e1305dc..0202ec0 100644 --- a/build.ts +++ b/build.ts @@ -1,37 +1,37 @@ -import { $ } from 'bun' -import { build, type Options } from 'tsup' - -await $`rm -rf dist` - -const tsupConfig: Options = { - entry: ['src/**/*.ts'], - splitting: false, - sourcemap: false, - clean: true, - bundle: true -} satisfies Options - -await Promise.all([ - // ? tsup esm - build({ - outDir: 'dist', - format: 'esm', - target: 'node20', - cjsInterop: false, - ...tsupConfig - }), - // ? tsup cjs - build({ - outDir: 'dist/cjs', - format: 'cjs', - target: 'node20', - // dts: true, - ...tsupConfig - }) -]) - -await $`tsc --project tsconfig.dts.json` - -await Promise.all([$`cp dist/*.d.ts dist/cjs`]) - -process.exit() +import { $ } from 'bun' +import { build, type Options } from 'tsup' + +await $`rm -rf dist` + +const tsupConfig: Options = { + entry: ['src/**/*.ts'], + splitting: false, + sourcemap: false, + clean: true, + bundle: true +} satisfies Options + +await Promise.all([ + // ? tsup esm + build({ + outDir: 'dist', + format: 'esm', + target: 'node20', + cjsInterop: false, + ...tsupConfig + }), + // ? tsup cjs + build({ + outDir: 'dist/cjs', + format: 'cjs', + target: 'node20', + // dts: true, + ...tsupConfig + }) +]) + +await $`tsc --project tsconfig.dts.json` + +await Promise.all([$`cp dist/*.d.ts dist/cjs`]) + +process.exit() diff --git a/bun.lock b/bun.lock index 95ca0d9..18de831 100644 --- a/bun.lock +++ b/bun.lock @@ -2,86 +2,89 @@ "lockfileVersion": 1, "workspaces": { "": { - "name": "@elysiajs/swagger", - "dependencies": { - "@scalar/themes": "^0.9.52", - "@scalar/types": "^0.0.12", - "openapi-types": "^12.1.3", - "pathe": "^1.1.2", - }, + "name": "@elysiajs/openapi", "devDependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "@types/bun": "1.1.14", - "elysia": "1.3.0-exp.71", + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@sinclair/typemap": "^0.10.1", + "@types/bun": "1.2.20", + "effect": "^3.17.13", + "elysia": "1.4.6", "eslint": "9.6.0", - "tsup": "^8.1.0", - "typescript": "^5.5.3", + "openapi-types": "^12.1.3", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "zod": "^4.1.5", }, "peerDependencies": { - "elysia": ">= 1.3.0", + "elysia": ">= 1.4.0", }, }, }, "packages": { - "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.7.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA=="], + "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@14.0.1", "", { "dependencies": { "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw=="], "@apidevtools/openapi-schemas": ["@apidevtools/openapi-schemas@2.1.0", "", {}, "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ=="], "@apidevtools/swagger-methods": ["@apidevtools/swagger-methods@3.0.2", "", {}, "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="], - "@apidevtools/swagger-parser": ["@apidevtools/swagger-parser@10.1.1", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "11.7.2", "@apidevtools/openapi-schemas": "^2.1.0", "@apidevtools/swagger-methods": "^3.0.2", "@jsdevtools/ono": "^7.1.3", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "call-me-maybe": "^1.0.2" }, "peerDependencies": { "openapi-types": ">=7" } }, "sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA=="], + "@apidevtools/swagger-parser": ["@apidevtools/swagger-parser@12.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "14.0.1", "@apidevtools/openapi-schemas": "^2.1.0", "@apidevtools/swagger-methods": "^3.0.2", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "call-me-maybe": "^1.0.2" }, "peerDependencies": { "openapi-types": ">=7" } }, "sha512-WLJIWcfOXrSKlZEM+yhA2Xzatgl488qr1FoOxixYmtWapBzwSC0gVGq4WObr4hHClMIiFFdOBdixNkvWqkWIWA=="], + + "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.3", "", { "os": "android", "cpu": "arm" }, "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.3", "", { "os": "android", "cpu": "arm64" }, "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.3", "", { "os": "android", "cpu": "x64" }, "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.3", "", { "os": "linux", "cpu": "arm" }, "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64" }, "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.3", "", { "os": "none", "cpu": "arm64" }, "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.3", "", { "os": "none", "cpu": "x64" }, "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.6.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], @@ -99,17 +102,13 @@ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], - - "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -119,67 +118,73 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.1", "", { "os": "android", "cpu": "arm" }, "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.1", "", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.1", "", { "os": "linux", "cpu": "arm" }, "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w=="], - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ=="], + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q=="], - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.1", "", { "os": "linux", "cpu": "x64" }, "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.1", "", { "os": "none", "cpu": "arm64" }, "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A=="], - "@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.1", "", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="], - "@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="], + "@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="], - "@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="], + "@scalar/types": ["@scalar/types@0.2.15", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-x2aCNmkDqr3VXUHjw7wPXK9KZwHbGGMs4NuxJIzy9MbAxUS9li8HXGG0K82Q5fDm47SAM+68z0/tnWkJpu+kzg=="], - "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], - "@types/bun": ["@types/bun@1.1.14", "", { "dependencies": { "bun-types": "1.1.37" } }, "sha512-opVYiFGtO2af0dnWBdZWlioLBoxSdDO5qokaazLhq8XQtGZbY4pY3/JxY8Zdf/hEwGubbp7ErZXoN1+h2yesxA=="], + "@sinclair/typemap": ["@sinclair/typemap@0.10.1", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.30", "valibot": "^1.0.0", "zod": "^3.24.1" } }, "sha512-UXR0fhu/n3c9B6lB+SLI5t1eVpt9i9CdDrp2TajRe3LbKiUhCTZN2kSfJhjPnpc3I59jMRIhgew7+0HlMi08mg=="], - "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], - "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], - "@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -197,9 +202,9 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "bun-types": ["bun-types@1.1.37", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-C65lv6eBr3LPJWFZ2gswyrGZ82ljnH8flVE03xeXxKhi2ZGtFiO4isRKTKnitbSqtRAcaqYSR6djt1whI66AbA=="], + "bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], @@ -221,33 +226,39 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - "elysia": ["elysia@1.3.0-exp.71", "", { "dependencies": { "@sinclair/typebox": "^0.34.33", "cookie": "^1.0.2", "exact-mirror": "0.1.1", "fast-decode-uri-component": "^1.0.1", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["file-type", "typescript"] }, "sha512-jL7z5OzJgs8pCzEXRmzzYu972S9hILiab7bVD3VBJHAE/9EMdG5uzxWA++3rxJXPEW7HvK3E31zaJKv6TtKgqA=="], + "effect": ["effect@3.17.13", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-JMz5oBxs/6mu4FP9Csjub4jYMUwMLrp+IzUmSDVIzn2NoeoyOXMl7x1lghfr3dLKWffWrdnv/d8nFFdgrHXPqw=="], + + "elysia": ["elysia@1.4.6", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "openapi-types": ">= 12.0.0" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-u2CorXLPs5ZXyWP+tQR+bgka/lJA4vNpB8lDE2w/sTmdaIwoPQmHEL4J3ai6OAlluWR1kfG7T9gO3EYT9D8viQ=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "esbuild": ["esbuild@0.25.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.3", "@esbuild/android-arm": "0.25.3", "@esbuild/android-arm64": "0.25.3", "@esbuild/android-x64": "0.25.3", "@esbuild/darwin-arm64": "0.25.3", "@esbuild/darwin-x64": "0.25.3", "@esbuild/freebsd-arm64": "0.25.3", "@esbuild/freebsd-x64": "0.25.3", "@esbuild/linux-arm": "0.25.3", "@esbuild/linux-arm64": "0.25.3", "@esbuild/linux-ia32": "0.25.3", "@esbuild/linux-loong64": "0.25.3", "@esbuild/linux-mips64el": "0.25.3", "@esbuild/linux-ppc64": "0.25.3", "@esbuild/linux-riscv64": "0.25.3", "@esbuild/linux-s390x": "0.25.3", "@esbuild/linux-x64": "0.25.3", "@esbuild/netbsd-arm64": "0.25.3", "@esbuild/netbsd-x64": "0.25.3", "@esbuild/openbsd-arm64": "0.25.3", "@esbuild/openbsd-x64": "0.25.3", "@esbuild/sunos-x64": "0.25.3", "@esbuild/win32-arm64": "0.25.3", "@esbuild/win32-ia32": "0.25.3", "@esbuild/win32-x64": "0.25.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q=="], + "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.6.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/config-array": "^0.17.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "9.6.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.0.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.1.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w=="], - "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], @@ -257,7 +268,9 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "exact-mirror": ["exact-mirror@0.1.1", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-jygrs/z9JT3UBDVPsu4vLy8gqtTLTxVzoxLmDzkVXHizRGixDMdkdLF98ChZxsqHL0F7IcpTf8GUFRqa2qt3uw=="], + "exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], @@ -267,16 +280,22 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - "fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], @@ -293,7 +312,7 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -341,10 +360,14 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], @@ -373,20 +396,24 @@ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -397,7 +424,7 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rollup": ["rollup@4.40.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.1", "@rollup/rollup-android-arm64": "4.40.1", "@rollup/rollup-darwin-arm64": "4.40.1", "@rollup/rollup-darwin-x64": "4.40.1", "@rollup/rollup-freebsd-arm64": "4.40.1", "@rollup/rollup-freebsd-x64": "4.40.1", "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", "@rollup/rollup-linux-arm-musleabihf": "4.40.1", "@rollup/rollup-linux-arm64-gnu": "4.40.1", "@rollup/rollup-linux-arm64-musl": "4.40.1", "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", "@rollup/rollup-linux-riscv64-gnu": "4.40.1", "@rollup/rollup-linux-riscv64-musl": "4.40.1", "@rollup/rollup-linux-s390x-gnu": "4.40.1", "@rollup/rollup-linux-x64-gnu": "4.40.1", "@rollup/rollup-linux-x64-musl": "4.40.1", "@rollup/rollup-win32-arm64-msvc": "4.40.1", "@rollup/rollup-win32-ia32-msvc": "4.40.1", "@rollup/rollup-win32-x64-msvc": "4.40.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw=="], + "rollup": ["rollup@4.50.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.1", "@rollup/rollup-android-arm64": "4.50.1", "@rollup/rollup-darwin-arm64": "4.50.1", "@rollup/rollup-darwin-x64": "4.50.1", "@rollup/rollup-freebsd-arm64": "4.50.1", "@rollup/rollup-freebsd-x64": "4.50.1", "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", "@rollup/rollup-linux-arm-musleabihf": "4.50.1", "@rollup/rollup-linux-arm64-gnu": "4.50.1", "@rollup/rollup-linux-arm64-musl": "4.50.1", "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", "@rollup/rollup-linux-ppc64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-musl": "4.50.1", "@rollup/rollup-linux-s390x-gnu": "4.50.1", "@rollup/rollup-linux-x64-gnu": "4.50.1", "@rollup/rollup-linux-x64-musl": "4.50.1", "@rollup/rollup-openharmony-arm64": "4.50.1", "@rollup/rollup-win32-arm64-msvc": "4.50.1", "@rollup/rollup-win32-ia32-msvc": "4.50.1", "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -419,6 +446,8 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -431,7 +460,9 @@ "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="], "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], @@ -439,18 +470,22 @@ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - "tsup": ["tsup@8.4.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ=="], + "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@4.40.1", "", {}, "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="], + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], - "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + + "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "valibot": ["valibot@1.1.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw=="], + "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], @@ -465,17 +500,17 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], - - "zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], + "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "@scalar/openapi-types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], - "@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="], + "@scalar/types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], "eslint/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -483,30 +518,28 @@ "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - - "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], } } diff --git a/example/gen.ts b/example/gen.ts new file mode 100644 index 0000000..a1f64b7 --- /dev/null +++ b/example/gen.ts @@ -0,0 +1,77 @@ +import { Elysia, t } from 'elysia' +import { openapi, withHeaders } from '../src/index' +import { fromTypes } from '../src/gen' +import z from 'zod' + +export const app = new Elysia() + .use( + openapi({ + mapJsonSchema: { + zod: z.toJSONSchema + }, + references: fromTypes('example/gen.ts', { + debug: true + }) + }) + ) + .model({ + 'character.name': t.String(), + 'character.thing': t.Object({ + name: t.String() + }) + }) + .get( + '/const', + () => + ({ + name: 'Lilith', + friends: ['Sartre', 'Fouco'] + }) as const + ) + .get( + '/', + () => + ({ test: 'hello' as const }) as any as + | { test: 'hello' } + | undefined, + { + response: { + 204: withHeaders( + t.Void({ + title: 'Thing', + description: 'Void response' + }), + { + 'X-Custom-Header': t.Literal('Elysia') + } + ) + } + } + ) + .get('/hello/2', () => 'hello') + .post( + '/json', + ({ body, status }) => (Math.random() > 0.5 ? status(418) : body), + { + body: t.Object({ + hello: t.String() + }) + } + ) + .get('/id/:id/name/:name', ({ params }) => params) + .post( + '/character', + () => ({ + name: 'Lilith' as const + }), + { + body: 'character.name', + response: z.object({ + name: z.literal('Lilith') + }) + } + ) + .get('/no-manual', () => ({ + name: 'lilith' + })) + .listen(3000) diff --git a/example/index.ts b/example/index.ts index c507cc1..4b5350c 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,59 +1,21 @@ import { Elysia, t } from 'elysia' -import { swagger } from '../src/index' +import z from 'zod' +import { JSONSchema, Schema } from 'effect' -const schema = t.Object({ - test: t.Literal('hello') -}) +import { openapi, withHeaders } from '../src/index' const app = new Elysia() .use( - swagger({ - provider: 'scalar', - documentation: { - info: { - title: 'Elysia Scalar', - version: '0.8.1' - }, - tags: [ - { - name: 'Test', - description: 'Hello' - } - ], - components: { - schemas: { - User: { - description: 'string' - } - }, - securitySchemes: { - JwtAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'Enter JWT Bearer token **_only_**' - } - } - } - }, - swaggerOptions: { - persistAuthorization: true + openapi({ + embedSchema: true, + mapJsonSchema: { + zod: z.toJSONSchema } }) ) - .model({ schema }) - .get( - '/', - () => { - return { test: 'hello' as const } - }, - { - response: 'schema' + .get('/test', ({ status }) => status(204, undefined), { + response: { + 204: z.void() } - ) - .post('/json', ({ body }) => body, { - parse: 'formdata', - body: 'schema', - response: 'schema' }) .listen(3000) diff --git a/example/index2.ts b/example/index2.ts deleted file mode 100644 index 19e75b4..0000000 --- a/example/index2.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Elysia } from 'elysia' -import { swagger } from '../src/index' -import { plugin } from './plugin' - -const app = new Elysia({ - // aot: false -}) - .use( - swagger({ - documentation: { - info: { - title: 'Elysia', - version: '0.6.10' - }, - tags: [ - { - name: 'Test', - description: 'Hello' - } - ], - security: [ - {JwtAuth: []} - ], - components: { - schemas: { - User: { - description: 'string' - } - }, - securitySchemes: { - JwtAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'Enter JWT Bearer token **_only_**' - } - } - } - }, - swaggerOptions: { - persistAuthorization: true - }, - }) - ) - .use(plugin) - .listen(3000) diff --git a/example/index3.ts b/example/index3.ts deleted file mode 100644 index 047339e..0000000 --- a/example/index3.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Elysia, InternalRoute } from 'elysia' -import { swagger } from '../src/index' -import { plugin } from './plugin' -import { registerSchemaPath } from '../src/utils' - -const app = new Elysia() - .use( - swagger({ - provider: 'scalar', - documentation: { - info: { - title: 'Elysia Scalar', - version: '0.8.1' - }, - tags: [ - { - name: 'Test', - description: 'Hello' - } - ], - paths: { - "/b/": { - get: { - operationId: "getB", - summary: "Ping Pong B", - description: "Lorem Ipsum Dolar", - tags: [ "Test" ], - responses: { - "200": { - description: "test" - }, - }, - }, - }, - }, - components: { - schemas: { - User: { - description: 'string' - } - }, - securitySchemes: { - JwtAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'Enter JWT Bearer token **_only_**' - } - } - } - }, - swaggerOptions: { - persistAuthorization: true - } - }) - ) - .use(plugin) - .listen(3000) - -console.log(app.rsaoutes) diff --git a/example/plugin.ts b/example/plugin.ts index fbb14d2..993de14 100644 --- a/example/plugin.ts +++ b/example/plugin.ts @@ -1,116 +1,116 @@ -import { Elysia, t } from 'elysia' - -export const plugin = new Elysia({ - prefix: '/a' -}) - .model({ - sign: t.Object( - { - username: t.String(), - password: t.String() - }, - { - description: 'Models for handling authentication' - } - ), - number: t.Number() - }) - .get('/', ({ set }) => 'hi', { - detail: { - summary: 'Ping Pong', - description: 'Lorem Ipsum Dolar', - tags: ['Test'] - } - }) - .get('/unpath/:id', ({ params: { id } }) => id, { - params: t.Object({ - id: t.String({ - description: 'Extract value from path parameter' - }) - }), - detail: { - deprecated: true - } - }) - .post('/json', ({ body }) => body, { - type: 'json', - body: 'sign', - response: { - 200: 'sign' - }, - detail: { - summary: 'Using reference model' - } - }) - .post( - '/json/:id', - ({ body, params: { id }, query: { name, email, } }) => ({ - ...body, - id - }), - { - body: 'sign', - params: t.Object({ - id: t.Numeric() - }), - query: t.Object({ - name: t.String(), - email: t.String({ - description: 'sample email description', - format: 'email', - examples: ['test@test.com'] - }), - birthday: t.String({ - description: 'sample birthday description', - pattern: '\\d{4}-\\d{2}-\\d{2}', - minLength: 10, - maxLength: 10, - examples: ['2024-01-01'] - }), - gender: t.Union([t.Literal('M'), t.Literal('F')]) - }), - response: { - 200: t.Object( - { - id: t.Number(), - username: t.String(), - password: t.String() - }, - { - title: 'User', - description: "Contains user's confidential metadata" - } - ), - 418: t.Array( - t.Object({ - error: t.String() - }) - ), - }, - detail: { - summary: 'Complex JSON' - } - } - ) - .post('/file', ({ body: { file } }) => file, { - type: 'formdata', - body: t.Object({ - file: t.File({ - type: ['image/jpeg', 'image/'], - minSize: '1k', - maxSize: '5m' - }) - }), - response: t.File() - }) -// .post('/files', ({ body: { files } }) => files[0], { -// schema: { -// body: t.Object({ -// files: t.Files({ -// type: 'image', -// maxSize: '5m' -// }) -// }), -// response: t.File() -// } -// }) +import { Elysia, t } from 'elysia' + +export const plugin = new Elysia({ + prefix: '/a' +}) + .model({ + sign: t.Object( + { + username: t.String(), + password: t.String() + }, + { + description: 'Models for handling authentication' + } + ), + number: t.Number() + }) + .get('/', ({ set }) => 'hi', { + detail: { + summary: 'Ping Pong', + description: 'Lorem Ipsum Dolar', + tags: ['Test'] + } + }) + .get('/unpath/:id', ({ params: { id } }) => id, { + params: t.Object({ + id: t.String({ + description: 'Extract value from path parameter' + }) + }), + detail: { + deprecated: true + } + }) + .post('/json', ({ body }) => body, { + type: 'json', + body: 'sign', + response: { + 200: 'sign' + }, + detail: { + summary: 'Using reference model' + } + }) + .post( + '/json/:id', + ({ body, params: { id }, query: { name, email, } }) => ({ + ...body, + id + }), + { + body: 'sign', + params: t.Object({ + id: t.Numeric() + }), + query: t.Object({ + name: t.String(), + email: t.String({ + description: 'sample email description', + format: 'email', + examples: ['test@test.com'] + }), + birthday: t.String({ + description: 'sample birthday description', + pattern: '\\d{4}-\\d{2}-\\d{2}', + minLength: 10, + maxLength: 10, + examples: ['2024-01-01'] + }), + gender: t.Union([t.Literal('M'), t.Literal('F')]) + }), + response: { + 200: t.Object( + { + id: t.Number(), + username: t.String(), + password: t.String() + }, + { + title: 'User', + description: "Contains user's confidential metadata" + } + ), + 418: t.Array( + t.Object({ + error: t.String() + }) + ), + }, + detail: { + summary: 'Complex JSON' + } + } + ) + .post('/file', ({ body: { file } }) => file, { + type: 'formdata', + body: t.Object({ + file: t.File({ + type: ['image/jpeg', 'image/'], + minSize: '1k', + maxSize: '5m' + }) + }), + response: t.File() + }) +// .post('/files', ({ body: { files } }) => files[0], { +// schema: { +// body: t.Object({ +// files: t.Files({ +// type: 'image', +// maxSize: '5m' +// }) +// }), +// response: t.File() +// } +// }) diff --git a/package.json b/package.json index de1a9ad..ee4473a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@elysiajs/swagger", - "version": "1.3.0", - "description": "Plugin for Elysia to auto-generate Swagger page", + "name": "@elysiajs/openapi", + "version": "1.4.11", + "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", @@ -17,15 +17,15 @@ "import": "./dist/index.mjs", "require": "./dist/cjs/index.js" }, - "./types": { - "types": "./dist/types.d.ts", - "import": "./dist/types.mjs", - "require": "./dist/cjs/types.js" + "./gen": { + "types": "./dist/gen/index.d.ts", + "import": "./dist/gen/index.mjs", + "require": "./dist/cjs/gen/index.js" }, - "./utils": { - "types": "./dist/utils.d.ts", - "import": "./dist/utils.mjs", - "require": "./dist/cjs/utils.js" + "./openapi": { + "types": "./dist/openapi.d.ts", + "import": "./dist/openapi.mjs", + "require": "./dist/cjs/openapi.js" }, "./scalar": { "types": "./dist/scalar/index.d.ts", @@ -36,18 +36,35 @@ "types": "./dist/scalar/theme.d.ts", "import": "./dist/scalar/theme.mjs", "require": "./dist/cjs/scalar/theme.js" + }, + "./swagger": { + "types": "./dist/swagger/index.d.ts", + "import": "./dist/swagger/index.mjs", + "require": "./dist/cjs/swagger/index.js" + }, + "./swagger/types": { + "types": "./dist/swagger/types.d.ts", + "import": "./dist/swagger/types.mjs", + "require": "./dist/cjs/swagger/types.js" + }, + "./types": { + "types": "./dist/types.d.ts", + "import": "./dist/types.mjs", + "require": "./dist/cjs/types.js" } }, "keywords": [ "elysia", - "swagger" + "openapi", + "swagger", + "scalar" ], - "homepage": "https://github.com/elysiajs/elysia-swagger", + "homepage": "https://github.com/elysiajs/elysia-openapi", "repository": { "type": "git", - "url": "https://github.com/elysiajs/elysia-swagger" + "url": "https://github.com/elysiajs/elysia-openapi" }, - "bugs": "https://github.com/elysiajs/elysia-swagger/issues", + "bugs": "https://github.com/elysiajs/elysia-openapi/issues", "license": "MIT", "scripts": { "dev": "bun run --watch example/index.ts", @@ -56,21 +73,21 @@ "build": "bun build.ts", "release": "npm run build && npm run test && npm publish --access public" }, - "peerDependencies": { - "elysia": ">= 1.3.0" - }, + "dependencies": {}, "devDependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "@types/bun": "1.1.14", - "elysia": "1.3.0-exp.71", + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@sinclair/typemap": "^0.10.1", + "@types/bun": "1.2.20", + "effect": "^3.17.13", + "elysia": "1.4.6", "eslint": "9.6.0", - "tsup": "^8.1.0", - "typescript": "^5.5.3" - }, - "dependencies": { - "@scalar/themes": "^0.9.52", - "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", - "pathe": "^1.1.2" + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "zod": "^4.1.5" + }, + "peerDependencies": { + "elysia": ">= 1.4.0" } -} \ No newline at end of file +} diff --git a/src/gen/index.ts b/src/gen/index.ts new file mode 100644 index 0000000..6847f63 --- /dev/null +++ b/src/gen/index.ts @@ -0,0 +1,420 @@ +import { TypeBox } from '@sinclair/typemap' +import type { AdditionalReference } from '../types' + +const matchRoute = /: Elysia<(.*)>/gs +const numberKey = /(\d+):/g + +export interface OpenAPIGeneratorOptions { + /** + * Path to tsconfig.json + * @default tsconfig.json + */ + tsconfigPath?: string + + /** + * Name of the Elysia instance + * + * If multiple instances are found, + * instanceName should be provided + */ + instanceName?: string + + /** + * Project root directory + * + * @default process.cwd() + */ + projectRoot?: string + + /** + * Override output path + * + * Under any circumstance, that Elysia failed to find a correct schema, + * Put your own schema in this path + */ + overrideOutputPath?: string | ((tempDir: string) => string) + + /** + * don't remove temporary files + * for debugging purpose + * @default false + */ + debug?: boolean + + /** + * compilerOptions + * + * Override tsconfig.json compilerOptions + */ + compilerOptions?: Record + + /** + * Temporary root + * + * a folder where temporary files are stored + * @default os.tmpdir()/.ElysiaAutoOpenAPI + * + * ! be careful that the folder will be removed after the process ends + */ + tmpRoot?: string + + /** + * disable log + * @default false + */ + silent?: boolean +} + +/** + * Polyfill path join for environments without Node.js path module + */ +const join = (...parts: string[]) => parts.join('/').replace(/\/{1,}/g, '/') + +export function extractRootObjects(code: string) { + const results = [] + let i = 0 + + while (i < code.length) { + // find the next colon + const colonIdx = code.indexOf(':', i) + if (colonIdx === -1) break + + // walk backwards from colon to find start of key + let keyEnd = colonIdx - 1 + while (keyEnd >= 0 && /\s/.test(code[keyEnd])) keyEnd-- + + let keyStart = keyEnd + // keep going back until we hit a delimiter (whitespace, brace, semicolon, comma, or start of file) + while (keyStart >= 0 && !/[\s{};,\n]/.test(code[keyStart])) { + keyStart-- + } + + // find the opening brace after colon + const braceIdx = code.indexOf('{', colonIdx) + if (braceIdx === -1) break + + // scan braces + let depth = 0 + let end = braceIdx + for (; end < code.length; end++) { + if (code[end] === '{') depth++ + else if (code[end] === '}') { + depth-- + if (depth === 0) { + end++ // move past closing brace + break + } + } + } + + results.push(`{${code.slice(keyStart + 1, end)};}`) + + i = end + } + + return results +} + +export function declarationToJSONSchema(declaration: string) { + const routes: AdditionalReference = {} + + // Treaty is a collection of { ... } & { ... } & { ... } + for (const route of extractRootObjects( + declaration.replace(numberKey, '"$1":') + )) { + let schema = TypeBox(route.replaceAll(/readonly/g, '')) + if (schema.type !== 'object') continue + + const paths = [] + + while (true) { + const keys = Object.keys(schema.properties) + if (keys.length !== 1) break + + paths.push(keys[0]) + + schema = schema.properties[keys[0]] as any + if (!schema?.properties) break + } + + const method = paths.pop()! + // For whatever reason, if failed to infer route correctly + if (!method) continue + + const path = '/' + paths.join('/') + schema = schema.properties + + if (schema?.response?.type === 'object') { + const responseSchema: Record = {} + + for (const key in schema.response.properties) + responseSchema[key] = schema.response.properties[key] + + schema.response = responseSchema + } + + if (!routes[path]) routes[path] = {} + // @ts-ignore + routes[path][method.toLowerCase()] = schema + } + + return routes +} + +/** + * Auto generate OpenAPI schema from Elysia instance + * + * It's expected that this command should run in project root + * + * @experimental use at your own risk + */ +export const fromTypes = + ( + /** + * Path to file where Elysia instance is + * + * The path must export an Elysia instance + * or a literal TypeScript declaration + */ + targetFilePath = 'src/index.ts', + { + tsconfigPath = 'tsconfig.json', + instanceName, + projectRoot = process.cwd(), + overrideOutputPath, + debug = false, + compilerOptions, + tmpRoot, + silent = false + }: OpenAPIGeneratorOptions = {} + ) => + () => { + // targetFilePath is an actual TypeScript declaration + if ( + targetFilePath.trimStart().startsWith('{') && + targetFilePath.trimEnd().endsWith('}') + ) + return declarationToJSONSchema(targetFilePath) + + if ( + typeof process === 'undefined' || + typeof process.getBuiltinModule !== 'function' + ) + throw new Error( + '[@elysiajs/openapi/gen] `fromTypes` from file path is only available in Node.js/Bun environment or environments' + ) + + const fs = process.getBuiltinModule('fs') + if (!fs) + throw new Error( + '[@elysiajs/openapi/gen] `fromTypes` require `fs` module which is not available in this environment' + ) + + try { + if ( + !targetFilePath.endsWith('.ts') && + !targetFilePath.endsWith('.tsx') + ) + throw new Error('Only .ts files are supported') + + if (targetFilePath.startsWith('./')) + targetFilePath = targetFilePath.slice(2) + + let src = targetFilePath.startsWith('/') + ? targetFilePath + : join(projectRoot, targetFilePath) + + if (!fs.existsSync(src)) + throw new Error( + `Couldn't find "${targetFilePath}" from ${projectRoot}` + ) + + let targetFile: string + + if (!tmpRoot) { + const os = process.getBuiltinModule('os') + + tmpRoot = join( + os && typeof os.tmpdir === 'function' + ? os.tmpdir() + : projectRoot, + '.ElysiaAutoOpenAPI' + ) + } + + // Since it's already a declaration file + // We can just read it directly + if (targetFilePath.endsWith('.d.ts')) targetFile = targetFilePath + else { + if (fs.existsSync(tmpRoot)) + fs.rmSync(tmpRoot, { recursive: true, force: true }) + + fs.mkdirSync(tmpRoot, { recursive: true }) + + const tsconfig = tsconfigPath.startsWith('/') + ? tsconfigPath + : join(projectRoot, tsconfigPath) + + let extendsRef = fs.existsSync(tsconfig) + ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` + : '' + + let distDir = join(tmpRoot, 'dist') + + // Convert Windows path to Unix for TypeScript CLI + if ( + typeof process !== 'undefined' && + process.platform === 'win32' + ) { + extendsRef = extendsRef.replace(/\\/g, '/') + src = src.replace(/\\/g, '/') + distDir = distDir.replace(/\\/g, '/') + } + + fs.writeFileSync( + join(tmpRoot, 'tsconfig.json'), + `{ + ${extendsRef} + "compilerOptions": ${ + compilerOptions + ? JSON.stringify(compilerOptions) + : `{ + "lib": ["ESNext"], + "module": "ESNext", + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "outDir": "${distDir}" +}` + }, + "include": ["${src}"] +}` + ) + + const child_process = process.getBuiltinModule('child_process') + if (!child_process) + throw new Error( + '[@elysiajs/openapi/gen] `fromTypes` declaration generation require `child_process` module which is not available in this environment' + ) + const { spawnSync } = child_process + if (typeof spawnSync !== 'function') + throw new Error( + '[@elysiajs/openapi/gen] `fromTypes` declaration generation require child_process.spawnSync which is not available in this environment' + ) + + spawnSync(`tsc`, { + shell: true, + cwd: tmpRoot, + stdio: silent ? undefined : 'inherit' + }) + + const fileName = targetFilePath + .replace(/.tsx$/, '.ts') + .replace(/.ts$/, '.d.ts') + + targetFile = + (overrideOutputPath + ? typeof overrideOutputPath === 'string' + ? overrideOutputPath.startsWith('/') + ? overrideOutputPath + : join(tmpRoot, 'dist', overrideOutputPath) + : overrideOutputPath(tmpRoot) + : undefined) ?? + join( + tmpRoot, + 'dist', + // remove leading like src or something similar + fileName.slice(fileName.indexOf('/') + 1) + ) + + let existed = fs.existsSync(targetFile) + + if (!existed && !overrideOutputPath) { + targetFile = join( + tmpRoot, + 'dist', + // use original file name as-is eg. in monorepo + fileName + ) + + existed = fs.existsSync(targetFile) + } + + if (!existed) { + fs.rmSync(join(tmpRoot, 'tsconfig.json')) + + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn("Couldn't find generated declaration file") + + if (fs.existsSync(join(tmpRoot, 'dist'))) { + const tempFiles = fs + .readdirSync(join(tmpRoot, 'dist'), { + recursive: true + }) + .filter((x) => x.toString().endsWith('.d.ts')) + .map((x) => `- ${x}`) + .join('\n') + + if (tempFiles) { + console.warn( + 'You can override with `overrideOutputPath` with one of the following:' + ) + console.warn(tempFiles) + } + } else { + console.warn( + "reason: root folder doesn't exists", + join(tmpRoot, 'dist') + ) + } + + return + } + } + + const declaration = fs.readFileSync(targetFile, 'utf8') + + // Check just in case of race-condition + if (!debug && fs.existsSync(tmpRoot)) + fs.rmSync(tmpRoot, { recursive: true, force: true }) + + let instance = declaration.match( + instanceName + ? new RegExp(`${instanceName}: Elysia<(.*)`, 'gs') + : matchRoute + )?.[0] + + if (!instance) return + + // Get 5th generic parameter + // Elysia<'', {}, {}, {}, Routes> + // ------------------------^ + // 1 2 3 4 5 + // We want the 4th one + for (let i = 0; i < 3; i++) + instance = instance.slice( + instance.indexOf( + '}, {', + // remove just `}, `, leaving `{` + 3 + ) + ) + + return declarationToJSONSchema(instance.slice(2)) + } catch (error) { + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn(error) + + return + } finally { + if (!debug && tmpRoot && fs.existsSync(tmpRoot)) + fs.rmSync(tmpRoot, { recursive: true, force: true }) + } + } diff --git a/src/index.ts b/src/index.ts index 4fc45df..b6b1b89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,42 +1,59 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { Elysia, type InternalRoute } from 'elysia' +import { Elysia } from 'elysia' import { SwaggerUIRender } from './swagger' import { ScalarRender } from './scalar' -import { filterPaths, registerSchemaPath } from './utils' +import { toOpenAPISchema } from './openapi' import type { OpenAPIV3 } from 'openapi-types' -import type { ReferenceConfiguration } from '@scalar/types' -import type { ElysiaSwaggerConfig } from './types' +import type { ApiReferenceConfiguration } from '@scalar/types' +import type { ElysiaOpenAPIConfig } from './types' + +function isCloudflareWorker() { + try { + // Check for the presence of caches.default, which is a global in Workers + if ( + // @ts-ignore + typeof caches !== 'undefined' && + // @ts-ignore + typeof caches.default !== 'undefined' + ) + return true + + // @ts-ignore + if (typeof WebSocketPair !== 'undefined') { + return true + } + } catch (e) { + // If accessing these globals throws an error, it's likely not a Worker + return false + } + + return false +} /** - * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate Swagger page. + * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page. * * @see https://github.com/elysiajs/elysia-swagger */ -export const swagger = ({ +export const openapi = < + const Enabled extends boolean = true, + const Path extends string = '/openapi' +>({ + enabled = true as Enabled, + path = '/openapi' as Path, provider = 'scalar', - scalarVersion = 'latest', - scalarCDN = '', - scalarConfig = {}, - documentation = {}, - version = '5.9.0', - excludeStaticFile = true, - path = '/swagger' as Path, specPath = `${path}/json`, - exclude = [], - swaggerOptions = {}, - theme = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`, - autoDarkMode = true, - excludeMethods = ['OPTIONS'], - excludeTags = [] -}: ElysiaSwaggerConfig = {}) => { - const schema = {} - let totalRoutes = 0 - - if (!version) - version = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css` + documentation = {}, + exclude, + swagger, + scalar, + references, + mapJsonSchema, + embedSpec +}: ElysiaOpenAPIConfig = {}) => { + if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) const info = { title: 'Elysia Documentation', @@ -45,147 +62,126 @@ export const swagger = ({ ...documentation.info } - const relativePath = path.startsWith('/') ? path.slice(1) : path - - const app = new Elysia({ name: '@elysiajs/swagger' }) - - const page = new Response( - provider === 'swagger-ui' - ? SwaggerUIRender( - info, - version, - theme, - JSON.stringify( - { - url: specPath, - dom_id: '#swagger-ui', - ...swaggerOptions - }, - (_, value) => - typeof value === 'function' ? undefined : value + const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath + + let totalRoutes = 0 + let cachedSchema: OpenAPIV3.Document | undefined + + const toFullSchema = ({ + paths, + components: { schemas } + }: ReturnType): OpenAPIV3.Document => { + return (cachedSchema = { + openapi: '3.0.3', + ...documentation, + tags: !exclude?.tags + ? documentation.tags + : documentation.tags?.filter( + (tag) => !exclude.tags?.includes(tag.name) ), - autoDarkMode - ) - : ScalarRender( - info, - scalarVersion, - { - spec: { - ...scalarConfig.spec, - url: specPath - }, - ...scalarConfig, - // so we can showcase the elysia theme - // @ts-expect-error - _integration: 'elysiajs' - } satisfies ReferenceConfiguration, - scalarCDN - ), - { - headers: { - 'content-type': 'text/html; charset=utf8' + info: { + title: 'Elysia Documentation', + description: 'Development documentation', + version: '0.0.0', + ...documentation.info + }, + paths: { + ...paths, + ...documentation.paths + }, + components: { + ...documentation.components, + schemas: { + ...schemas, + ...(documentation.components?.schemas as any) + } } - } - ) + }) + } - app.get(path, page, { - detail: { - hide: true - } - }).get( - specPath, - function openAPISchema() { - // @ts-expect-error Private property - const routes = app.getGlobalRoutes() as InternalRoute[] - - if (routes.length !== totalRoutes) { - const ALLOWED_METHODS = [ - 'GET', - 'PUT', - 'POST', - 'DELETE', - 'OPTIONS', - 'HEAD', - 'PATCH', - 'TRACE' - ] - totalRoutes = routes.length - - // forEach create a clone of a route (can't use for-of) - routes.forEach((route: InternalRoute) => { - if (route.hooks?.detail?.hide === true) return - if (excludeMethods.includes(route.method)) return - if ( - ALLOWED_METHODS.includes(route.method) === false && - route.method !== 'ALL' - ) - return - - if (route.method === 'ALL') - ALLOWED_METHODS.forEach((method) => { - registerSchemaPath({ - schema, - hook: route.hooks, - method, - path: route.path, - // @ts-ignore - models: app.getGlobalDefinitions?.().type, - contentType: route.hooks.type + const app = new Elysia({ name: '@elysiajs/openapi' }) + .use((app) => { + if (provider === null) return app + + const page = () => + new Response( + provider === 'swagger-ui' + ? SwaggerUIRender(info, { + url: relativePath, + dom_id: '#swagger-ui', + version: 'latest', + autoDarkMode: true, + ...swagger }) - }) - else - registerSchemaPath({ - schema, - hook: route.hooks, - method: route.method, - path: route.path, - // @ts-ignore - models: app.getGlobalDefinitions?.().type, - contentType: route.hooks.type - }) - }) - } + : ScalarRender( + info, + { + url: relativePath, + version: 'latest', + cdn: `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${scalar?.version ?? 'latest'}/dist/browser/standalone.min.js`, + ...(scalar as ApiReferenceConfiguration), + _integration: 'elysiajs' + }, + embedSpec + ? JSON.stringify( + totalRoutes === app.routes.length + ? cachedSchema + : toFullSchema( + toOpenAPISchema( + app, + exclude, + references, + mapJsonSchema + ) + ) + ) + : undefined + ), + { + headers: { + 'content-type': 'text/html; charset=utf8' + } + } + ) - return { - openapi: '3.0.3', - ...{ - ...documentation, - tags: documentation.tags?.filter( - (tag) => !excludeTags?.includes(tag?.name) - ), - info: { - title: 'Elysia Documentation', - description: 'Development documentation', - version: '0.0.0', - ...documentation.info + return app.get( + path, + embedSpec || isCloudflareWorker() ? page : page(), + { + detail: { + hide: true } + } + ) + }) + .get( + specPath, + function openAPISchema(): OpenAPIV3.Document { + if (totalRoutes === app.routes.length && cachedSchema) + return cachedSchema + + totalRoutes = app.routes.length + + return toFullSchema( + toOpenAPISchema(app, exclude, references, mapJsonSchema) + ) + }, + { + error({ error }) { + console.log('[@elysiajs/openapi] error at specPath') + console.warn(error) }, - paths: { - ...filterPaths(schema, { - excludeStaticFile, - exclude: Array.isArray(exclude) ? exclude : [exclude] - }), - ...documentation.paths - }, - components: { - ...documentation.components, - schemas: { - // @ts-ignore - ...app.getGlobalDefinitions?.().type, - ...documentation.components?.schemas - } + detail: { + hide: true } - } satisfies OpenAPIV3.Document - }, - { - detail: { - hide: true } - } - ) + ) return app } -export type { ElysiaSwaggerConfig } -export default swagger +export { fromTypes } from './gen' +export { toOpenAPISchema, withHeaders } from './openapi' +export type { ElysiaOpenAPIConfig } + +export default openapi diff --git a/src/openapi.ts b/src/openapi.ts new file mode 100644 index 0000000..d99e9c7 --- /dev/null +++ b/src/openapi.ts @@ -0,0 +1,715 @@ +import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia' +import type { HookContainer, StandardSchemaV1Like } from 'elysia/types' + +import type { OpenAPIV3 } from 'openapi-types' +import { Kind, TAnySchema, type TProperties } from '@sinclair/typebox' + +import type { + AdditionalReference, + AdditionalReferences, + ElysiaOpenAPIConfig, + MapJsonSchema +} from './types' +import { defineConfig } from 'tsup' + +export const capitalize = (word: string) => + word.charAt(0).toUpperCase() + word.slice(1) + +const toRef = (name: string) => t.Ref(`#/components/schemas/${name}`) + +const toOperationId = (method: string, paths: string) => { + let operationId = method.toLowerCase() + + if (!paths || paths === '/') return operationId + 'Index' + + for (const path of paths.split('/')) + operationId += path.includes(':') + ? 'By' + capitalize(path.replace(':', '')) + : capitalize(path) + + operationId = operationId.replace(/\?/g, 'Optional') + + return operationId +} + +const optionalParamsRegex = /(\/:\w+\?)/g + +/** + * Get all possible paths of a path with optional parameters + * @param {string} path + * @returns {string[]} paths + */ +export const getPossiblePath = (path: string): string[] => { + const optionalParams = path.match(optionalParamsRegex) + if (!optionalParams) return [path] + + const originalPath = path.replace(/\?/g, '') + const paths = [originalPath] + + for (let i = 0; i < optionalParams.length; i++) { + const newPath = path.replace(optionalParams[i], '') + + paths.push(...getPossiblePath(newPath)) + } + + return paths +} + +const isValidSchema = (schema: any): schema is TSchema => + schema && + typeof schema === 'object' && + ((Kind in schema && schema[Kind] !== 'Unknown') || + schema.type || + schema.properties || + schema.items) + +export const getLoosePath = (path: string) => { + if (path.charCodeAt(path.length - 1) === 47) + return path.slice(0, path.length - 1) + + return path + '/' +} + +const warnings = { + zod4: `import openapi from '@elysiajs/openapi' +import * as z from 'zod' + +openapi({ + mapJsonSchema: { + zod: z.toJSONSchema + } +})`, + zod3: `import openapi from '@elysiajs/openapi' +import { zodToJsonSchema } from 'zod-to-json-schema' + +openapi({ + mapJsonSchema: { + zod: zodToJsonSchema + } +})`, + valibot: `import openapi from '@elysiajs/openapi' +import { toJsonSchema } from '@valibot/to-json-schema' + +openapi({ + mapJsonSchema: { + valibot: toJsonSchema + } +})`, + effect: `import { JSONSchema } from 'effect' + +openapi({ + mapJsonSchema: { + effect: JSONSchema.make + } +})` +} as const + +const warned = {} as Record + +const unwrapReference = ( + schema: T, + definitions: Record +): + | Exclude + | (Omit, 'type'> & { + $ref: string + type: string | undefined + }) => { + // @ts-ignore + const ref = schema?.$ref + if (!ref) return schema as any + + const name = ref.slice(ref.lastIndexOf('/') + 1) + if (ref && definitions[name]) schema = definitions[name] as T + + return enumToOpenApi(schema) as any +} + +export const unwrapSchema = ( + schema: InputSchema['body'], + mapJsonSchema?: MapJsonSchema +): OpenAPIV3.SchemaObject | undefined => { + if (!schema) return + + if (typeof schema === 'string') schema = toRef(schema) + if (Kind in schema) return enumToOpenApi(schema) + + if (Kind in schema || !schema?.['~standard']) return + + // @ts-ignore + const vendor = schema['~standard'].vendor + + try { + if ( + mapJsonSchema?.[vendor] && + typeof mapJsonSchema[vendor] === 'function' + ) + return enumToOpenApi(mapJsonSchema[vendor](schema)) + + switch (vendor) { + case 'zod': + if (warned.zod4 || warned.zod3) break + + console.warn( + "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema" + ) + + if ('_zod' in schema) { + warned.zod4 = true + + console.warn( + 'For Zod v4, please provide z.toJSONSchema as follows:\n' + ) + console.warn(warnings.zod4) + } else { + warned.zod3 = true + + console.warn( + 'For Zod v3, please install zod-to-json-schema package and use it like this:\n' + ) + console.warn(warnings.zod3) + } + break + + case 'valibot': + if (warned.valibot) break + warned.valibot = true + + console.warn( + '[@elysiajs/openapi] Valibot require a separate package for JSON Schema conversion' + ) + console.warn( + 'Please install @valibot/to-json-schema package and use it like this:\n' + ) + console.warn(warnings.valibot) + break + + case 'effect': + // Effect does not support toJsonSchema method + // Users have to use third party library like effect-zod + if (warned.effect) break + warned.effect = true + + console.warn( + "[@elysiajs/openapi] Effect Schema doesn't provide JSON Schema method on the schema" + ) + console.warn( + "please provide JSONSchema from 'effect' package as follows:\n" + ) + console.warn(warnings.effect) + break + } + + if (vendor === 'arktype') + // @ts-ignore + return enumToOpenApi(schema?.toJsonSchema?.()) + + return enumToOpenApi( + // @ts-ignore + schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() + ) + } catch (error) { + console.warn(error) + } +} + +export const enumToOpenApi = < + T extends + | TAnySchema + | OpenAPIV3.SchemaObject + | OpenAPIV3.ReferenceObject + | undefined +>( + _schema: T +): T => { + if (!_schema || typeof _schema !== 'object') return _schema + + if (Kind in _schema) { + const schema = _schema as TAnySchema + + if ( + schema[Kind] === 'Union' && + schema.anyOf && + Array.isArray(schema.anyOf) && + schema.anyOf.length > 0 && + schema.anyOf.every( + (item) => + item && typeof item === 'object' && item.const !== undefined + ) + ) + return { + type: 'string', + enum: schema.anyOf.map((item) => item.const) + } as any + } + + const schema = _schema as OpenAPIV3.SchemaObject + + if (schema.type === 'object' && schema.properties) { + const properties: Record = {} + for (const [key, value] of Object.entries(schema.properties)) + properties[key] = enumToOpenApi(value) + + return { + ...schema, + properties + } as T + } + + if (schema.type === 'array' && schema.items) + return { + ...schema, + items: enumToOpenApi(schema.items) + } as T + + return schema as T +} + +/** + * Converts Elysia routes to OpenAPI 3.0.3 paths schema + * @param routes Array of Elysia route objects + * @returns OpenAPI paths object + */ +export function toOpenAPISchema( + app: AnyElysia, + exclude?: ElysiaOpenAPIConfig['exclude'], + references?: AdditionalReferences, + vendors?: MapJsonSchema +) { + let { + methods: excludeMethods = ['options'], + staticFile: excludeStaticFile = true, + tags: excludeTags + } = exclude ?? {} + + excludeMethods = excludeMethods.map((method) => method.toLowerCase()) + + const excludePaths = Array.isArray(exclude?.paths) + ? exclude.paths + : typeof exclude?.paths !== 'undefined' + ? [exclude.paths] + : [] + + const paths: OpenAPIV3.PathsObject = Object.create(null) + // @ts-ignore + const definitions = app.getGlobalDefinitions?.().type + + // @ts-ignore private property + const routes = app.getGlobalRoutes() + + if (references) { + if (!Array.isArray(references)) references = [references] + + for (let i = 0; i < references.length; i++) { + const reference = references[i] + + if (typeof reference === 'function') references[i] = reference() + } + } + + for (const route of routes) { + if (route.hooks?.detail?.hide) continue + + const method = route.method.toLowerCase() + + if ( + (excludeStaticFile && route.path.includes('.')) || + excludePaths.includes(route.path) || + excludeMethods.includes(method) + ) + continue + + const hooks: InputSchema & { + detail: Partial + } = route.hooks ?? {} + + if (references?.length) + for (const reference of references as AdditionalReference[]) { + if (!reference) continue + + const refer = + reference[route.path]?.[method] ?? + reference[getLoosePath(route.path)]?.[method] + + if (!refer) continue + + if (!hooks.body && isValidSchema(refer.body)) + hooks.body = refer.body + + if (!hooks.query && isValidSchema(refer.query)) + hooks.query = refer.query + + if (!hooks.params && isValidSchema(refer.params)) + hooks.params = refer.params + + if (!hooks.headers && isValidSchema(refer.headers)) + hooks.headers = refer.headers + + if (refer.response) + for (const [status, schema] of Object.entries( + refer.response + )) + if (isValidSchema(schema)) { + if (!hooks.response) hooks.response = {} + else if ( + typeof hooks.response !== 'object' || + (hooks.response as TSchema).type || + (hooks.response as TSchema).$ref || + (hooks.response as any)['~standard'] + ) + hooks.response = { + 200: hooks.response as any + } + + if ( + !hooks.response[ + status as keyof (typeof hooks)['response'] + ] + ) + try { + // @ts-ignore + hooks.response[status] = schema + } catch (error) { + console.log( + '[@elysiajs/openapi/gen] Failed to assigned response schema' + ) + console.log(error) + } + } + } + + if ( + excludeTags && + hooks.detail.tags?.some((tag) => excludeTags?.includes(tag)) + ) + continue + + // Start building the operation object + const operation: Partial = { + ...hooks.detail + } + + const parameters: Array<{ + name: string + in: 'path' | 'query' | 'header' | 'cookie' + required?: boolean + schema: any + }> = [] + + // Handle path parameters + if (hooks.params) { + const params = unwrapReference( + unwrapSchema(hooks.params, vendors), + definitions + ) + + if (params && params.type === 'object' && params.properties) + for (const [name, schema] of Object.entries(params.properties)) + parameters.push({ + name, + in: 'path', + required: true, // Path parameters are always required + schema + }) + } else { + for (const match of route.path.matchAll(/:([^/]+)/g)) { + const name = match[1].replace('?', '') + + parameters.push({ + name, + in: 'path', + required: true, + schema: { type: 'string' } + }) + } + } + + // Handle query parameters + if (hooks.query) { + const query = unwrapReference( + unwrapSchema(hooks.query, vendors), + definitions + ) + + if (query && query.type === 'object' && query.properties) { + const required = query.required || [] + for (const [name, schema] of Object.entries(query.properties)) + parameters.push({ + name, + in: 'query', + required: required.includes(name), + schema + }) + } + } + + // Handle header parameters + if (hooks.headers) { + const headers = unwrapReference( + unwrapSchema(hooks.headers, vendors), + definitions + ) + + if (headers && headers.type === 'object' && headers.properties) { + const required = headers.required || [] + for (const [name, schema] of Object.entries(headers.properties)) + parameters.push({ + name, + in: 'header', + required: required.includes(name), + schema + }) + } + } + + // Handle cookie parameters + if (hooks.cookie) { + const cookie = unwrapReference( + unwrapSchema(hooks.cookie, vendors), + definitions + ) + + if (cookie && cookie.type === 'object' && cookie.properties) { + const required = cookie.required || [] + for (const [name, schema] of Object.entries(cookie.properties)) + parameters.push({ + name, + in: 'cookie', + required: required.includes(name), + schema + }) + } + } + + // Add parameters if any exist + if (parameters.length > 0) operation.parameters = parameters + + // Handle request body + if (hooks.body && method !== 'get' && method !== 'head') { + const body = unwrapSchema(hooks.body, vendors) + + if (body) { + // @ts-ignore + const { type, description, $ref, ...options } = unwrapReference( + body, + definitions + ) + + // @ts-ignore + if (hooks.parse) { + const content: Record< + string, + { schema: OpenAPIV3.SchemaObject } + > = {} + + // @ts-ignore + const parsers = hooks.parse as HookContainer[] + + for (const parser of parsers) { + if (typeof parser.fn === 'function') continue + + switch (parser.fn) { + case 'text': + case 'text/plain': + content['text/plain'] = { schema: body } + continue + + case 'urlencoded': + case 'application/x-www-form-urlencoded': + content['application/x-www-form-urlencoded'] = { + schema: body + } + continue + + case 'json': + case 'application/json': + content['application/json'] = { schema: body } + continue + + case 'formdata': + case 'multipart/form-data': + content['multipart/form-data'] = { + schema: body + } + continue + } + } + + operation.requestBody = { + description, + content, + required: true + } + } else { + operation.requestBody = { + description, + required: true, + content: + type === 'string' || + type === 'number' || + type === 'integer' || + type === 'boolean' + ? { + 'text/plain': { + schema: body + } + } + : { + 'application/json': { + schema: body + }, + 'application/x-www-form-urlencoded': { + schema: body + }, + 'multipart/form-data': { + schema: body + } + } + } + } + } + } + + // Handle responses + if (hooks.response) { + operation.responses = {} + + if ( + typeof hooks.response === 'object' && + // TypeBox + !(hooks.response as TSchema).type && + !(hooks.response as TSchema).$ref && + !(hooks.response as any)['~standard'] + ) { + for (let [status, schema] of Object.entries(hooks.response)) { + const response = unwrapSchema(schema, vendors) + + if (!response) continue + + // @ts-ignore Must exclude $ref from root options + const { type, description, $ref, ..._options } = + unwrapReference(response, definitions) + + operation.responses[status] = { + description: + description ?? `Response for status ${status}`, + content: + type === 'void' || + type === 'null' || + type === 'undefined' + ? ({ type, description } as any) + : type === 'string' || + type === 'number' || + type === 'integer' || + type === 'boolean' + ? { + 'text/plain': { + schema: response + } + } + : { + 'application/json': { + schema: response + } + } + } + } + } else { + const response = unwrapSchema(hooks.response as any, vendors) + + if (response) { + // @ts-ignore + const { + type: _type, + description, + ...options + } = unwrapReference(response, definitions) + const type = _type as string | undefined + + // It's a single schema, default to 200 + operation.responses['200'] = { + description: description ?? `Response for status 200`, + content: + type === 'void' || + type === 'null' || + type === 'undefined' + ? ({ type, description } as any) + : type === 'string' || + type === 'number' || + type === 'integer' || + type === 'boolean' + ? { + 'text/plain': { + schema: response + } + } + : { + 'application/json': { + schema: response + } + } + } + } + } + } + + for (let path of getPossiblePath(route.path)) { + const operationId = + hooks.detail?.operationId ?? toOperationId(route.method, path) + + path = path.replace(/:([^/]+)/g, '{$1}') + + if (!paths[path]) paths[path] = {} + + const current = paths[path] as any + + if (method !== 'all') { + current[method] = { + ...operation, + operationId + } + continue + } + + // Handle 'ALL' method by assigning operation to all standard methods + for (const method of [ + 'get', + 'post', + 'put', + 'delete', + 'patch', + 'head', + 'options', + 'trace' + ]) + current[method] = { + ...operation, + operationId + } + } + } + + // @ts-ignore private property + const schemas = Object.create(null) + + if (definitions) + for (const [name, schema] of Object.entries(definitions)) { + const jsonSchema = unwrapSchema(schema as any, vendors) as + | OpenAPIV3.SchemaObject + | undefined + + if (jsonSchema) schemas[name] = jsonSchema + } + + return { + components: { + schemas + }, + paths + } satisfies Pick +} + +export const withHeaders = (schema: TSchema, headers: TProperties) => + Object.assign(schema, { + headers: headers + }) diff --git a/src/scalar/index.ts b/src/scalar/index.ts index 883ea66..f32101d 100644 --- a/src/scalar/index.ts +++ b/src/scalar/index.ts @@ -1,12 +1,132 @@ -import { elysiajsTheme } from '@scalar/themes' import type { OpenAPIV3 } from 'openapi-types' -import type { ReferenceConfiguration } from '@scalar/types' +import type { ApiReferenceConfiguration } from '@scalar/types' +import { ElysiaOpenAPIConfig } from '../types' + +const elysiaCSS = `.light-mode { + --scalar-color-1: #2a2f45; + --scalar-color-2: #757575; + --scalar-color-3: #8e8e8e; + --scalar-color-accent: #f06292; + + --scalar-background-1: #fff; + --scalar-background-2: #f6f6f6; + --scalar-background-3: #e7e7e7; + + --scalar-border-color: rgba(0, 0, 0, 0.1); +} +.dark-mode { + --scalar-color-1: rgba(255, 255, 255, 0.9); + --scalar-color-2: rgba(156, 163, 175, 1); + --scalar-color-3: rgba(255, 255, 255, 0.44); + --scalar-color-accent: #f06292; + + --scalar-background-1: #111728; + --scalar-background-2: #1e293b; + --scalar-background-3: #334155; + --scalar-background-accent: #f062921f; + + --scalar-border-color: rgba(255, 255, 255, 0.1); +} + +/* Document Sidebar */ +.light-mode .t-doc__sidebar, +.dark-mode .t-doc__sidebar { + --scalar-sidebar-background-1: var(--scalar-background-1); + --scalar-sidebar-color-1: var(--scalar-color-1); + --scalar-sidebar-color-2: var(--scalar-color-2); + --scalar-sidebar-border-color: var(--scalar-border-color); + + --scalar-sidebar-item-hover-background: var(--scalar-background-2); + --scalar-sidebar-item-hover-color: currentColor; + + --scalar-sidebar-item-active-background: #f062921f; + --scalar-sidebar-color-active: var(--scalar-color-accent); + + --scalar-sidebar-search-background: transparent; + --scalar-sidebar-search-color: var(--scalar-color-3); + --scalar-sidebar-search-border-color: var(--scalar-border-color); +} + +/* advanced */ +.light-mode { + --scalar-button-1: rgb(49 53 56); + --scalar-button-1-color: #fff; + --scalar-button-1-hover: rgb(28 31 33); + + --scalar-color-green: #069061; + --scalar-color-red: #ef0006; + --scalar-color-yellow: #edbe20; + --scalar-color-blue: #0082d0; + --scalar-color-orange: #fb892c; + --scalar-color-purple: #5203d1; + + --scalar-scrollbar-color: rgba(0, 0, 0, 0.18); + --scalar-scrollbar-color-active: rgba(0, 0, 0, 0.36); +} +.dark-mode { + --scalar-button-1: #f6f6f6; + --scalar-button-1-color: #000; + --scalar-button-1-hover: #e7e7e7; + + --scalar-color-green: #a3ffa9; + --scalar-color-red: #ffa3a3; + --scalar-color-yellow: #fffca3; + --scalar-color-blue: #a5d6ff; + --scalar-color-orange: #e2ae83; + --scalar-color-purple: #d2a8ff; + + --scalar-scrollbar-color: rgba(255, 255, 255, 0.24); + --scalar-scrollbar-color-active: rgba(255, 255, 255, 0.48); +} +.section-flare { + width: 100%; + height: 400px; + position: absolute; +} +.section-flare-item:first-of-type:before { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + --stripes: repeating-linear-gradient(100deg, #fff 0%, #fff 0%, transparent 2%, transparent 12%, #fff 17%); + --stripesDark: repeating-linear-gradient(100deg, #000 0%, #000 0%, transparent 10%, transparent 12%, #000 17%); + --rainbow: repeating-linear-gradient(100deg, #60a5fa 10%, #e879f9 16%, #5eead4 22%, #60a5fa 30%); + contain: strict; + contain-intrinsic-size: 100vw 40vh; + background-image: var(--stripesDark), var(--rainbow); + background-size: 300%, 200%; + background-position: 50% 50%, 50% 50%; + filter: opacity(20%) saturate(200%); + -webkit-mask-image: radial-gradient(ellipse at 100% 0%, black 40%, transparent 70%); + mask-image: radial-gradient(ellipse at 100% 0%, black 40%, transparent 70%); + pointer-events: none; +} +.section-flare-item:first-of-type:after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-image: var(--stripes), var(--rainbow); + background-size: 200%, 100%; + background-attachment: fixed; + mix-blend-mode: difference; + background-image: var(--stripesDark), var(--rainbow); + pointer-events: none; +} +.light-mode .section-flare-item:first-of-type:after, +.light-mode .section-flare-item:first-of-type:before { + background-image: var(--stripes), var(--rainbow); + filter: opacity(4%) saturate(200%); +}` export const ScalarRender = ( - info: OpenAPIV3.InfoObject, - version: string, - config: ReferenceConfiguration, - cdn: string + info: OpenAPIV3.InfoObject, + config: NonNullable, + embedSpec?: string ) => ` @@ -29,20 +149,22 @@ export const ScalarRender = ( } - + ` diff --git a/src/swagger/index.ts b/src/swagger/index.ts index 60a0144..dcaa6de 100644 --- a/src/swagger/index.ts +++ b/src/swagger/index.ts @@ -1,127 +1,148 @@ -import { OpenAPIV3 } from 'openapi-types'; - -type DateTimeSchema = { - type: 'string'; - format: 'date-time'; - default?: string; -}; - -type SchemaObject = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; - -function isSchemaObject(schema: SchemaObject): schema is OpenAPIV3.SchemaObject { - return 'type' in schema || 'properties' in schema || 'items' in schema; -} - -function isDateTimeProperty(key: string, schema: OpenAPIV3.SchemaObject): boolean { - return (key === 'createdAt' || key === 'updatedAt') && - 'anyOf' in schema && - Array.isArray(schema.anyOf); -} - -function transformDateProperties(schema: SchemaObject): SchemaObject { - if (!isSchemaObject(schema) || typeof schema !== 'object' || schema === null) { - return schema; - } - - const newSchema: OpenAPIV3.SchemaObject = { ...schema }; - - Object.entries(newSchema).forEach(([key, value]) => { - if (isSchemaObject(value)) { - if (isDateTimeProperty(key, value)) { - const dateTimeFormat = value.anyOf?.find((item): item is OpenAPIV3.SchemaObject => - isSchemaObject(item) && item.format === 'date-time' - ); - if (dateTimeFormat) { - const dateTimeSchema: DateTimeSchema = { - type: 'string', - format: 'date-time', - default: dateTimeFormat.default - }; - (newSchema as Record)[key] = dateTimeSchema; - } - } else { - (newSchema as Record)[key] = transformDateProperties(value); - } - } - }); - - return newSchema; -} - -export const SwaggerUIRender = ( - info: OpenAPIV3.InfoObject, - version: string, - theme: - | string - | { - light: string - dark: string - }, - stringifiedSwaggerOptions: string, - autoDarkMode?: boolean -): string => { - const swaggerOptions: OpenAPIV3.Document = JSON.parse(stringifiedSwaggerOptions); - - if (swaggerOptions.components && swaggerOptions.components.schemas) { - swaggerOptions.components.schemas = Object.fromEntries( - Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [ - key, - transformDateProperties(schema) - ]) - ); - } - - const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions); - - return ` - - - - - ${info.title} - - - ${ - autoDarkMode && typeof theme === 'string' - ? ` - ` - : '' - } - ${ - typeof theme === 'string' - ? `` - : ` -` - } - - -
- - - -`; -}; +import { OpenAPIV3 } from 'openapi-types' +import { ElysiaOpenAPIConfig } from '../types' +import { SwaggerUIOptions } from './types' + +type DateTimeSchema = { + type: 'string' + format: 'date-time' + default?: string +} + +type SchemaObject = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject + +function isSchemaObject( + schema: SchemaObject +): schema is OpenAPIV3.SchemaObject { + return 'type' in schema || 'properties' in schema || 'items' in schema +} + +function isDateTimeProperty( + key: string, + schema: OpenAPIV3.SchemaObject +): boolean { + return ( + (key === 'createdAt' || key === 'updatedAt') && + 'anyOf' in schema && + Array.isArray(schema.anyOf) + ) +} + +export function transformDateProperties(schema: SchemaObject): SchemaObject { + if ( + !isSchemaObject(schema) || + typeof schema !== 'object' || + schema === null + ) + return schema + + const newSchema: OpenAPIV3.SchemaObject = { ...schema } + + Object.entries(newSchema).forEach(([key, value]) => { + if (isSchemaObject(value)) { + if (isDateTimeProperty(key, value)) { + const dateTimeFormat = value.anyOf?.find( + (item): item is OpenAPIV3.SchemaObject => + isSchemaObject(item) && item.format === 'date-time' + ) + + if (dateTimeFormat) { + const dateTimeSchema: DateTimeSchema = { + type: 'string', + format: 'date-time', + default: dateTimeFormat.default + } + ;(newSchema as Record)[key] = + dateTimeSchema + } + } else { + ;(newSchema as Record)[key] = + transformDateProperties(value) + } + } + }) + + return newSchema +} + +export const SwaggerUIRender = ( + info: OpenAPIV3.InfoObject, + config: NonNullable & SwaggerUIOptions +): string => { + const { + version = 'latest', + theme = `https://unpkg.com/swagger-ui-dist@${version ?? 'latest'}/swagger-ui.css`, + cdn = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js`, + autoDarkMode = true, + ...rest + } = config + + // remove function in rest + const stringifiedOptions = JSON.stringify( + { + dom_id: '#swagger-ui', + ...rest + }, + (_, value) => (typeof value === 'function' ? undefined : value) + ) + + const options: OpenAPIV3.Document = JSON.parse(stringifiedOptions) + + if (options.components && options.components.schemas) + options.components.schemas = Object.fromEntries( + Object.entries(options.components.schemas).map(([key, schema]) => [ + key, + transformDateProperties(schema) + ]) + ) + + return ` + + + + + ${info.title} + + + ${ + autoDarkMode && typeof theme === 'string' + ? `` + : '' + } + ${ + typeof theme === 'string' + ? `` + : ` +` + } + + +
+ + + +` +} diff --git a/src/swagger/types.ts b/src/swagger/types.ts index e18eb92..671d45b 100644 --- a/src/swagger/types.ts +++ b/src/swagger/types.ts @@ -1,331 +1,331 @@ -/** - * Swagger UI type because swagger-ui doesn't export an interface so here's copy paste - * - * @see swagger-ui/index.d.ts - **/ -export interface SwaggerUIOptions { - // Core - - /** - * URL to fetch external configuration document from. - */ - configUrl?: string | undefined - /** - * REQUIRED if domNode is not provided. The ID of a DOM element inside which SwaggerUI will put its user interface. - */ - dom_id?: string | undefined - /** - * A JavaScript object describing the OpenAPI definition. When used, the url parameter will not be parsed. This is useful for testing manually-generated definitions without hosting them - */ - spec?: { [propName: string]: any } | undefined - /** - * The URL pointing to API definition (normally swagger.json or swagger.yaml). Will be ignored if urls or spec is used. - */ - url?: string | undefined - /** - * An array of API definition objects ([{url: "", name: ""},{url: "", name: ""}]) - * used by Topbar plugin. When used and Topbar plugin is enabled, the url parameter will not be parsed. - * Names and URLs must be unique among all items in this array, since they're used as identifiers. - */ - urls?: - | Array<{ - url: string - name: string - }> - | undefined - - // Plugin system - - /** - * The name of a component available via the plugin system to use as the top-level layout - * for Swagger UI. - */ - layout?: string | undefined - /** - * A Javascript object to configure plugin integration and behaviors - */ - pluginsOptions?: PluginsOptions - /** - * An array of plugin functions to use in Swagger UI. - */ - plugins?: SwaggerUIPlugin[] | undefined - /** - * An array of presets to use in Swagger UI. - * Usually, you'll want to include ApisPreset if you use this option. - */ - presets?: SwaggerUIPlugin[] | undefined - - // Display - - /** - * If set to true, enables deep linking for tags and operations. - * See the Deep Linking documentation for more information. - */ - deepLinking?: boolean | undefined - /** - * Controls the display of operationId in operations list. The default is false. - */ - displayOperationId?: boolean | undefined - /** - * The default expansion depth for models (set to -1 completely hide the models). - */ - defaultModelsExpandDepth?: number | undefined - /** - * The default expansion depth for the model on the model-example section. - */ - defaultModelExpandDepth?: number | undefined - /** - * Controls how the model is shown when the API is first rendered. - * (The user can always switch the rendering for a given model by clicking the - * 'Model' and 'Example Value' links.) - */ - defaultModelRendering?: 'example' | 'model' | undefined - /** - * Controls the display of the request duration (in milliseconds) for "Try it out" requests. - */ - displayRequestDuration?: boolean | undefined - /** - * Controls the default expansion setting for the operations and tags. - * It can be 'list' (expands only the tags), 'full' (expands the tags and operations) - * or 'none' (expands nothing). - */ - docExpansion?: 'list' | 'full' | 'none' | undefined - /** - * If set, enables filtering. - * The top bar will show an edit box that you can use to filter the tagged operations that are shown. - * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled - * using that string as the filter expression. - * Filtering is case sensitive matching the filter expression anywhere inside the tag. - */ - filter?: boolean | string | undefined - /** - * If set, limits the number of tagged operations displayed to at most this many. - * The default is to show all operations. - */ - maxDisplayedTags?: number | undefined - /** - * Apply a sort to the operation list of each API. - * It can be 'alpha' (sort by paths alphanumerically), - * 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). - * Default is the order returned by the server unchanged. - */ - operationsSorter?: SorterLike | undefined - /** - * Controls the display of vendor extension (x-) fields and values for Operations, - * Parameters, Responses, and Schema. - */ - showExtensions?: boolean | undefined - /** - * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields - * and values for Parameters. - */ - showCommonExtensions?: boolean | undefined - /** - * Apply a sort to the tag list of each API. - * It can be 'alpha' (sort by paths alphanumerically) - * or a function (see Array.prototype.sort() to learn how to write a sort function). - * Two tag name strings are passed to the sorter for each pass. - * Default is the order determined by Swagger UI. - */ - tagsSorter?: SorterLike | undefined - /** - * When enabled, sanitizer will leave style, class and data-* attributes untouched - * on all HTML Elements declared inside markdown strings. - * This parameter is Deprecated and will be removed in 4.0.0. - * @deprecated - */ - useUnsafeMarkdown?: boolean | undefined - /** - * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. - */ - onComplete?: (() => any) | undefined - /** - * Set to false to deactivate syntax highlighting of payloads and cURL command, - * can be otherwise an object with the activate and theme properties. - */ - syntaxHighlight?: - | false - | { - /** - * Whether syntax highlighting should be activated or not. - */ - activate?: boolean | undefined - /** - * Highlight.js syntax coloring theme to use. (Only these 6 styles are available.) - */ - theme?: - | 'agate' - | 'arta' - | 'idea' - | 'monokai' - | 'nord' - | 'obsidian' - | 'tomorrow-night' - | undefined - } - | undefined - /** - * Controls whether the "Try it out" section should be enabled by default. - */ - tryItOutEnabled?: boolean | undefined - /** - * This is the default configuration section for the the requestSnippets plugin. - */ - requestSnippets?: - | { - generators?: - | { - [genName: string]: { - title: string - syntax: string - } - } - | undefined - defaultExpanded?: boolean | undefined - /** - * e.g. only show curl bash = ["curl_bash"] - */ - languagesMask?: string[] | undefined - } - | undefined - - // Network - - /** - * OAuth redirect URL. - */ - oauth2RedirectUrl?: string | undefined - /** - * MUST be a function. Function to intercept remote definition, - * "Try it out", and OAuth 2.0 requests. - * Accepts one argument requestInterceptor(request) and must return the modified request, - * or a Promise that resolves to the modified request. - */ - requestInterceptor?: - | ((a: Request) => Request | Promise) - | undefined - /** - * MUST be a function. Function to intercept remote definition, - * "Try it out", and OAuth 2.0 responses. - * Accepts one argument responseInterceptor(response) and must return the modified response, - * or a Promise that resolves to the modified response. - */ - responseInterceptor?: - | ((a: Response) => Response | Promise) - | undefined - /** - * If set to true, uses the mutated request returned from a requestInterceptor - * to produce the curl command in the UI, otherwise the request - * beforethe requestInterceptor was applied is used. - */ - showMutatedRequest?: boolean | undefined - /** - * List of HTTP methods that have the "Try it out" feature enabled. - * An empty array disables "Try it out" for all operations. - * This does not filter the operations from the display. - */ - supportedSubmitMethods?: SupportedHTTPMethods[] | undefined - /** - * By default, Swagger UI attempts to validate specs against swagger.io's online validator. - * You can use this parameter to set a different validator URL, - * for example for locally deployed validators (Validator Badge). - * Setting it to either none, 127.0.0.1 or localhost will disable validation. - */ - validatorUrl?: string | undefined - /** - * If set to true, enables passing credentials, as defined in the Fetch standard, - * in CORS requests that are sent by the browser. - * Note that Swagger UI cannot currently set cookies cross-domain (see swagger-js#1163) - * - as a result, you will have to rely on browser-supplied - * cookies (which this setting enables sending) that Swagger UI cannot control. - */ - withCredentials?: boolean | undefined - - // Macros - - /** - * Function to set default values to each property in model. - * Accepts one argument modelPropertyMacro(property), property is immutable - */ - modelPropertyMacro?: ((propName: Readonly) => any) | undefined - /** - * Function to set default value to parameters. - * Accepts two arguments parameterMacro(operation, parameter). - * Operation and parameter are objects passed for context, both remain immutable - */ - parameterMacro?: - | ((operation: Readonly, parameter: Readonly) => any) - | undefined - - // Authorization - - /** - * If set to true, it persists authorization data and it would not be lost on browser close/refresh - */ - persistAuthorization?: boolean | undefined -} -interface PluginsOptions { - /** - * Control behavior of plugins when targeting the same component with wrapComponent.
- * - `legacy` (default) : last plugin takes precedence over the others
- * - `chain` : chain wrapComponents when targeting the same core component, - * allowing multiple plugins to wrap the same component - * @default 'legacy' - */ - pluginLoadType?: PluginLoadType -} - -type PluginLoadType = 'legacy' | 'chain' - -type SupportedHTTPMethods = - | 'get' - | 'put' - | 'post' - | 'delete' - | 'options' - | 'head' - | 'patch' - | 'trace' - -type SorterLike = - | 'alpha' - | 'method' - | { - (name1: string, name2: string): number - } - -interface Request { - [prop: string]: any -} - -interface Response { - [prop: string]: any -} - -/** - * See https://swagger.io/docs/open-source-tools/swagger-ui/customization/plugin-api/ - */ -interface SwaggerUIPlugin { - (system: any): { - statePlugins?: - | { - [stateKey: string]: { - actions?: Indexable | undefined - reducers?: Indexable | undefined - selectors?: Indexable | undefined - wrapActions?: Indexable | undefined - wrapSelectors?: Indexable | undefined - } - } - | undefined - components?: Indexable | undefined - wrapComponents?: Indexable | undefined - rootInjects?: Indexable | undefined - afterLoad?: ((system: any) => any) | undefined - fn?: Indexable | undefined - } -} - -interface Indexable { - [index: string]: any -} +/** + * Swagger UI type because swagger-ui doesn't export an interface so here's copy paste + * + * @see swagger-ui/index.d.ts + **/ +export interface SwaggerUIOptions { + // Core + + /** + * URL to fetch external configuration document from. + */ + configUrl?: string | undefined + /** + * REQUIRED if domNode is not provided. The ID of a DOM element inside which SwaggerUI will put its user interface. + */ + dom_id?: string | undefined + /** + * A JavaScript object describing the OpenAPI definition. When used, the url parameter will not be parsed. This is useful for testing manually-generated definitions without hosting them + */ + spec?: { [propName: string]: any } | undefined + /** + * The URL pointing to API definition (normally swagger.json or swagger.yaml). Will be ignored if urls or spec is used. + */ + url?: string | undefined + /** + * An array of API definition objects ([{url: "", name: ""},{url: "", name: ""}]) + * used by Topbar plugin. When used and Topbar plugin is enabled, the url parameter will not be parsed. + * Names and URLs must be unique among all items in this array, since they're used as identifiers. + */ + urls?: + | Array<{ + url: string + name: string + }> + | undefined + + // Plugin system + + /** + * The name of a component available via the plugin system to use as the top-level layout + * for Swagger UI. + */ + layout?: string | undefined + /** + * A Javascript object to configure plugin integration and behaviors + */ + pluginsOptions?: PluginsOptions + /** + * An array of plugin functions to use in Swagger UI. + */ + plugins?: SwaggerUIPlugin[] | undefined + /** + * An array of presets to use in Swagger UI. + * Usually, you'll want to include ApisPreset if you use this option. + */ + presets?: SwaggerUIPlugin[] | undefined + + // Display + + /** + * If set to true, enables deep linking for tags and operations. + * See the Deep Linking documentation for more information. + */ + deepLinking?: boolean | undefined + /** + * Controls the display of operationId in operations list. The default is false. + */ + displayOperationId?: boolean | undefined + /** + * The default expansion depth for models (set to -1 completely hide the models). + */ + defaultModelsExpandDepth?: number | undefined + /** + * The default expansion depth for the model on the model-example section. + */ + defaultModelExpandDepth?: number | undefined + /** + * Controls how the model is shown when the API is first rendered. + * (The user can always switch the rendering for a given model by clicking the + * 'Model' and 'Example Value' links.) + */ + defaultModelRendering?: 'example' | 'model' | undefined + /** + * Controls the display of the request duration (in milliseconds) for "Try it out" requests. + */ + displayRequestDuration?: boolean | undefined + /** + * Controls the default expansion setting for the operations and tags. + * It can be 'list' (expands only the tags), 'full' (expands the tags and operations) + * or 'none' (expands nothing). + */ + docExpansion?: 'list' | 'full' | 'none' | undefined + /** + * If set, enables filtering. + * The top bar will show an edit box that you can use to filter the tagged operations that are shown. + * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled + * using that string as the filter expression. + * Filtering is case sensitive matching the filter expression anywhere inside the tag. + */ + filter?: boolean | string | undefined + /** + * If set, limits the number of tagged operations displayed to at most this many. + * The default is to show all operations. + */ + maxDisplayedTags?: number | undefined + /** + * Apply a sort to the operation list of each API. + * It can be 'alpha' (sort by paths alphanumerically), + * 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). + * Default is the order returned by the server unchanged. + */ + operationsSorter?: SorterLike | undefined + /** + * Controls the display of vendor extension (x-) fields and values for Operations, + * Parameters, Responses, and Schema. + */ + showExtensions?: boolean | undefined + /** + * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields + * and values for Parameters. + */ + showCommonExtensions?: boolean | undefined + /** + * Apply a sort to the tag list of each API. + * It can be 'alpha' (sort by paths alphanumerically) + * or a function (see Array.prototype.sort() to learn how to write a sort function). + * Two tag name strings are passed to the sorter for each pass. + * Default is the order determined by Swagger UI. + */ + tagsSorter?: SorterLike | undefined + /** + * When enabled, sanitizer will leave style, class and data-* attributes untouched + * on all HTML Elements declared inside markdown strings. + * This parameter is Deprecated and will be removed in 4.0.0. + * @deprecated + */ + useUnsafeMarkdown?: boolean | undefined + /** + * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. + */ + onComplete?: (() => any) | undefined + /** + * Set to false to deactivate syntax highlighting of payloads and cURL command, + * can be otherwise an object with the activate and theme properties. + */ + syntaxHighlight?: + | false + | { + /** + * Whether syntax highlighting should be activated or not. + */ + activate?: boolean | undefined + /** + * Highlight.js syntax coloring theme to use. (Only these 6 styles are available.) + */ + theme?: + | 'agate' + | 'arta' + | 'idea' + | 'monokai' + | 'nord' + | 'obsidian' + | 'tomorrow-night' + | undefined + } + | undefined + /** + * Controls whether the "Try it out" section should be enabled by default. + */ + tryItOutEnabled?: boolean | undefined + /** + * This is the default configuration section for the the requestSnippets plugin. + */ + requestSnippets?: + | { + generators?: + | { + [genName: string]: { + title: string + syntax: string + } + } + | undefined + defaultExpanded?: boolean | undefined + /** + * e.g. only show curl bash = ["curl_bash"] + */ + languagesMask?: string[] | undefined + } + | undefined + + // Network + + /** + * OAuth redirect URL. + */ + oauth2RedirectUrl?: string | undefined + /** + * MUST be a function. Function to intercept remote definition, + * "Try it out", and OAuth 2.0 requests. + * Accepts one argument requestInterceptor(request) and must return the modified request, + * or a Promise that resolves to the modified request. + */ + requestInterceptor?: + | ((a: Request) => Request | Promise) + | undefined + /** + * MUST be a function. Function to intercept remote definition, + * "Try it out", and OAuth 2.0 responses. + * Accepts one argument responseInterceptor(response) and must return the modified response, + * or a Promise that resolves to the modified response. + */ + responseInterceptor?: + | ((a: Response) => Response | Promise) + | undefined + /** + * If set to true, uses the mutated request returned from a requestInterceptor + * to produce the curl command in the UI, otherwise the request + * beforethe requestInterceptor was applied is used. + */ + showMutatedRequest?: boolean | undefined + /** + * List of HTTP methods that have the "Try it out" feature enabled. + * An empty array disables "Try it out" for all operations. + * This does not filter the operations from the display. + */ + supportedSubmitMethods?: SupportedHTTPMethods[] | undefined + /** + * By default, Swagger UI attempts to validate specs against swagger.io's online validator. + * You can use this parameter to set a different validator URL, + * for example for locally deployed validators (Validator Badge). + * Setting it to either none, 127.0.0.1 or localhost will disable validation. + */ + validatorUrl?: string | undefined + /** + * If set to true, enables passing credentials, as defined in the Fetch standard, + * in CORS requests that are sent by the browser. + * Note that Swagger UI cannot currently set cookies cross-domain (see swagger-js#1163) + * - as a result, you will have to rely on browser-supplied + * cookies (which this setting enables sending) that Swagger UI cannot control. + */ + withCredentials?: boolean | undefined + + // Macros + + /** + * Function to set default values to each property in model. + * Accepts one argument modelPropertyMacro(property), property is immutable + */ + modelPropertyMacro?: ((propName: Readonly) => any) | undefined + /** + * Function to set default value to parameters. + * Accepts two arguments parameterMacro(operation, parameter). + * Operation and parameter are objects passed for context, both remain immutable + */ + parameterMacro?: + | ((operation: Readonly, parameter: Readonly) => any) + | undefined + + // Authorization + + /** + * If set to true, it persists authorization data and it would not be lost on browser close/refresh + */ + persistAuthorization?: boolean | undefined +} +interface PluginsOptions { + /** + * Control behavior of plugins when targeting the same component with wrapComponent.
+ * - `legacy` (default) : last plugin takes precedence over the others
+ * - `chain` : chain wrapComponents when targeting the same core component, + * allowing multiple plugins to wrap the same component + * @default 'legacy' + */ + pluginLoadType?: PluginLoadType +} + +type PluginLoadType = 'legacy' | 'chain' + +type SupportedHTTPMethods = + | 'get' + | 'put' + | 'post' + | 'delete' + | 'options' + | 'head' + | 'patch' + | 'trace' + +type SorterLike = + | 'alpha' + | 'method' + | { + (name1: string, name2: string): number + } + +interface Request { + [prop: string]: any +} + +interface Response { + [prop: string]: any +} + +/** + * See https://swagger.io/docs/open-source-tools/swagger-ui/customization/plugin-api/ + */ +interface SwaggerUIPlugin { + (system: any): { + statePlugins?: + | { + [stateKey: string]: { + actions?: Indexable | undefined + reducers?: Indexable | undefined + selectors?: Indexable | undefined + wrapActions?: Indexable | undefined + wrapSelectors?: Indexable | undefined + } + } + | undefined + components?: Indexable | undefined + wrapComponents?: Indexable | undefined + rootInjects?: Indexable | undefined + afterLoad?: ((system: any) => any) | undefined + fn?: Indexable | undefined + } +} + +interface Indexable { + [index: string]: any +} diff --git a/src/types.ts b/src/types.ts index ce16c26..23f19ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,18 +1,92 @@ +import type { TSchema } from 'elysia' import type { OpenAPIV3 } from 'openapi-types' -import type { ReferenceConfiguration } from '@scalar/types' +import type { ApiReferenceConfiguration } from '@scalar/types' import type { SwaggerUIOptions } from './swagger/types' -export interface ElysiaSwaggerConfig { +export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null + +type MaybeArray = T | T[] + +export type MapJsonSchema = { [vendor: string]: Function } & { + [vendor in // schema['~standard'].vendor + | 'zod' + | 'effect' + | 'valibot' + | 'arktype' + | 'typemap' + | 'yup' + | 'joi']?: Function +} + +export type AdditionalReference = { + [path in string]: { + [method in string]: { + params: TSchema + query: TSchema + headers: TSchema + body: TSchema + response: { [status in number]: TSchema } + } + } +} + +export type AdditionalReferences = MaybeArray< + AdditionalReference | undefined | (() => AdditionalReference | undefined) +> + +export interface ElysiaOpenAPIConfig< + Enabled extends boolean = true, + Path extends string = '/swagger' +> { /** - * Customize Swagger config, refers to Swagger 2.0 config + * @default true + */ + enabled?: Enabled + + /** + * OpenAPI config * - * @see https://swagger.io/specification/v2/ + * @see https://spec.openapis.org/oas/v3.0.3.html */ documentation?: Omit< Partial, | 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict' > + + exclude?: { + /** + * Exclude methods from OpenAPI + */ + methods?: string[] + + /** + * Paths to exclude from OpenAPI endpoint + * + * @default [] + */ + paths?: string | RegExp | (string | RegExp)[] + + /** + * Determine if OpenAPI should exclude static files. + * + * @default true + */ + staticFile?: boolean + + /** + * Exclude tags from OpenAPI + */ + tags?: string[] + } + + /** + * The endpoint to expose OpenAPI Documentation + * + * @default '/openapi' + */ + path?: Path + /** * Choose your provider, Scalar or Swagger UI * @@ -20,71 +94,78 @@ export interface ElysiaSwaggerConfig { * @see https://github.com/scalar/scalar * @see https://github.com/swagger-api/swagger-ui */ - provider?: 'scalar' | 'swagger-ui' + provider?: OpenAPIProvider + /** - * Version to use for Scalar cdn bundle - * - * @default 'latest' - * @see https://github.com/scalar/scalar + * Additional reference for each endpoint */ - scalarVersion?: string + references?: AdditionalReferences + /** - * Optional override to specifying the path for the Scalar bundle - * - * Custom URL or path to locally hosted Scalar bundle + * Embed OpenAPI schema to provider body if possible * - * Lease blank to use default jsdeliver.net CDN + * This is highly discouraged, unless you really have to inline OpenAPI schema * - * @default '' - * @example 'https://unpkg.com/@scalar/api-reference@1.13.10/dist/browser/standalone.js' - * @example '/public/standalone.js' - * @see https://github.com/scalar/scalar + * @default false */ - scalarCDN?: string - /** - * Scalar configuration to customize scalar - *' - * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md - */ - scalarConfig?: ReferenceConfiguration + embedSpec?: boolean + /** - * Version to use for swagger cdn bundle + * Mapping function from Standard schema to OpenAPI schema * - * @see unpkg.com/swagger-ui-dist + * @example + * ```ts + * import { openapi } from '@elysiajs/openapi' + * import { toJsonSchema } from '@valibot/to-json-schema' * - * @default 4.18.2 - */ - version?: string - /** - * Determine if Swagger should exclude static files. - * - * @default true + * openapi({ + * mapJsonSchema: { + * valibot: toJsonSchema + * } + * }) */ - excludeStaticFile?: boolean + mapJsonSchema?: MapJsonSchema + /** - * The endpoint to expose OpenAPI Documentation - * - * @default '/swagger' + * Scalar configuration to customize scalar + *' + * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md */ - path?: Path + scalar?: ApiReferenceConfiguration & { + /** + * Version to use for Scalar cdn bundle + * + * @default 'latest' + * @see https://github.com/scalar/scalar + */ + version?: string + /** + * Optional override to specifying the path for the Scalar bundle + * + * Custom URL or path to locally hosted Scalar bundle + * + * Lease blank to use default jsdeliver.net CDN + * + * @default '' + * @example 'https://unpkg.com/@scalar/api-reference@1.13.10/dist/browser/standalone.js' + * @example '/public/standalone.js' + * @see https://github.com/scalar/scalar + */ + cdn?: string + } /** * The endpoint to expose OpenAPI JSON specification * * @default '/${path}/json' */ specPath?: string - /** - * Paths to exclude from Swagger endpoint - * - * @default [] - */ - exclude?: string | RegExp | (string | RegExp)[] + /** * Options to send to SwaggerUIBundle * Currently, options that are defined as functions such as requestInterceptor * and onComplete are not supported. */ - swaggerOptions?: Omit< + swagger?: Omit< Partial, | 'dom_id' | 'dom_node' @@ -100,28 +181,35 @@ export interface ElysiaSwaggerConfig { | 'responseInterceptor' | 'modelPropertyMacro' | 'parameterMacro' - > - /** - * Custom Swagger CSS - */ - theme?: - | string - | { - light: string - dark: string - } - /** - * Using poor man dark mode 😭 - */ - autoDarkMode?: boolean + > & { + /** + * Custom Swagger CSS + */ + theme?: + | string + | { + light: string + dark: string + } - /** - * Exclude methods from Swagger - */ - excludeMethods?: string[] + /** + * Version to use for swagger cdn bundle + * + * @see unpkg.com/swagger-ui-dist + * + * @default 4.18.2 + */ + version?: string - /** - * Exclude tags from Swagger or Scalar - */ - excludeTags?: string[] + /** + * Using poor man dark mode 😭 + */ + autoDarkMode?: boolean + + /** + * Optional override to specifying the path for the Swagger UI bundle + * Custom URL or path to locally hosted Swagger UI bundle + */ + cdn?: string + } } diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 19f5170..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,430 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { normalize } from 'pathe' -import { replaceSchemaType, t, type HTTPMethod, type LocalHook } from 'elysia' - -import { Kind, type TSchema } from '@sinclair/typebox' -import type { OpenAPIV3 } from 'openapi-types' - -export const toOpenAPIPath = (path: string) => - path - .split('/') - .map((x) => { - if (x.startsWith(':')) { - x = x.slice(1, x.length) - if (x.endsWith('?')) x = x.slice(0, -1) - x = `{${x}}` - } - - return x - }) - .join('/') - -export const mapProperties = ( - name: string, - schema: TSchema | string | undefined, - models: Record -) => { - if (schema === undefined) return [] - - if (typeof schema === 'string') - if (schema in models) schema = models[schema] - else throw new Error(`Can't find model ${schema}`) - - return Object.entries(schema?.properties ?? []).map(([key, value]) => { - const { - type: valueType = undefined, - description, - examples, - ...schemaKeywords - } = value as any - return { - // @ts-ignore - description, - examples, - schema: { type: valueType, ...schemaKeywords }, - in: name, - name: key, - // @ts-ignore - required: schema!.required?.includes(key) ?? false - } - }) -} - -const mapTypesResponse = ( - types: string[], - schema: - | string - | { - type: string - properties: Object - required: string[] - } -) => { - if ( - typeof schema === 'object' && - ['void', 'undefined', 'null'].includes(schema.type) - ) - return - - const responses: Record = {} - - for (const type of types) { - responses[type] = { - schema: - typeof schema === 'string' - ? { - $ref: `#/components/schemas/${schema}` - } - : '$ref' in schema && - Kind in schema && - schema[Kind] === 'Ref' - ? { - ...schema, - $ref: `#/components/schemas/${schema.$ref}` - } - : replaceSchemaType( - { ...(schema as any) }, - { - from: t.Ref(''), - // @ts-expect-error - to: ({ $ref, ...options }) => { - if ( - !$ref.startsWith( - '#/components/schemas/' - ) - ) - return t.Ref( - `#/components/schemas/${$ref}`, - options - ) - - return t.Ref($ref, options) - } - } - ) - } - } - - return responses -} - -export const capitalize = (word: string) => - word.charAt(0).toUpperCase() + word.slice(1) - -export const generateOperationId = (method: string, paths: string) => { - let operationId = method.toLowerCase() - - if (paths === '/') return operationId + 'Index' - - for (const path of paths.split('/')) { - if (path.charCodeAt(0) === 123) { - operationId += 'By' + capitalize(path.slice(1, -1)) - } else { - operationId += capitalize(path) - } - } - - return operationId -} - -const cloneHook = (hook: T) => { - if (!hook) return - if (typeof hook === 'string') return hook - if (Array.isArray(hook)) return [...hook] - return { ...hook } -} - -export const registerSchemaPath = ({ - schema, - path, - method, - hook, - models -}: { - schema: Partial - contentType?: string | string[] - path: string - method: HTTPMethod - hook?: LocalHook - models: Record -}) => { - hook = cloneHook(hook) - - if (hook.parse && !Array.isArray(hook.parse)) hook.parse = [hook.parse] - - let contentType = (hook.parse as unknown[]) - ?.map((x) => { - switch (typeof x) { - case 'string': - return x - - case 'object': - if ( - x && - typeof (x as { fn: string | Function })?.fn !== 'string' - ) - return - - switch ((x as { fn: string | Function })?.fn) { - case 'json': - case 'application/json': - return 'application/json' - - case 'text': - case 'text/plain': - return 'text/plain' - - case 'urlencoded': - case 'application/x-www-form-urlencoded': - return 'application/x-www-form-urlencoded' - - case 'arrayBuffer': - case 'application/octet-stream': - return 'application/octet-stream' - - case 'formdata': - case 'multipart/form-data': - return 'multipart/form-data' - } - } - }) - .filter((x) => x !== undefined) - - if (!contentType || contentType.length === 0) - contentType = ['application/json', 'multipart/form-data', 'text/plain'] - - path = toOpenAPIPath(path) - - const contentTypes = - typeof contentType === 'string' - ? [contentType] - : (contentType ?? ['application/json']) - - const bodySchema = cloneHook(hook?.body) - const paramsSchema = cloneHook(hook?.params) - const headerSchema = cloneHook(hook?.headers) - const querySchema = cloneHook(hook?.query) - let responseSchema: OpenAPIV3.ResponsesObject = cloneHook(hook?.response) - - if (typeof responseSchema === 'object') { - if (Kind in responseSchema) { - const { - type, - properties, - required, - additionalProperties, - patternProperties, - $ref, - ...rest - } = responseSchema as typeof responseSchema & { - type: string - properties: Object - required: string[] - } - - responseSchema = { - '200': { - ...rest, - description: rest.description as any, - content: mapTypesResponse( - contentTypes, - type === 'object' || type === 'array' - ? ({ - type, - properties, - patternProperties, - items: responseSchema.items, - required - } as any) - : responseSchema - ) - } - } - } else { - Object.entries(responseSchema as Record).forEach( - ([key, value]) => { - if (typeof value === 'string') { - if (!models[value]) return - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { - type, - properties, - required, - additionalProperties: _1, - patternProperties: _2, - ...rest - } = models[value] as TSchema & { - type: string - properties: Object - required: string[] - } - - responseSchema[key] = { - ...rest, - description: rest.description as any, - content: mapTypesResponse(contentTypes, value) - } - } else { - const { - type, - properties, - required, - additionalProperties, - patternProperties, - ...rest - } = value as typeof value & { - type: string - properties: Object - required: string[] - } - - responseSchema[key] = { - ...rest, - description: rest.description as any, - content: mapTypesResponse( - contentTypes, - type === 'object' || type === 'array' - ? ({ - type, - properties, - patternProperties, - items: value.items, - required - } as any) - : value - ) - } - } - } - ) - } - } else if (typeof responseSchema === 'string') { - if (!(responseSchema in models)) return - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { - type, - properties, - required, - $ref, - additionalProperties: _1, - patternProperties: _2, - ...rest - } = models[responseSchema] as TSchema & { - type: string - properties: Object - required: string[] - } - - responseSchema = { - // @ts-ignore - '200': { - ...rest, - content: mapTypesResponse(contentTypes, responseSchema) - } - } - } - - const parameters = [ - ...mapProperties('header', headerSchema, models), - ...mapProperties('path', paramsSchema, models), - ...mapProperties('query', querySchema, models) - ] - - schema[path] = { - ...(schema[path] ? schema[path] : {}), - [method.toLowerCase()]: { - ...((headerSchema || paramsSchema || querySchema || bodySchema - ? ({ parameters } as any) - : {}) satisfies OpenAPIV3.ParameterObject), - ...(responseSchema - ? { - responses: responseSchema - } - : {}), - operationId: - hook?.detail?.operationId ?? generateOperationId(method, path), - ...hook?.detail, - ...(bodySchema - ? { - requestBody: { - required: true, - content: mapTypesResponse( - contentTypes, - typeof bodySchema === 'string' - ? { - $ref: `#/components/schemas/${bodySchema}` - } - : (bodySchema as any) - ) - } - } - : null) - } satisfies OpenAPIV3.OperationObject - } -} - -export const filterPaths = ( - paths: Record, - { - excludeStaticFile = true, - exclude = [] - }: { - excludeStaticFile: boolean - exclude: (string | RegExp)[] - } -) => { - const newPaths: Record = {} - - for (const [key, value] of Object.entries(paths)) - if ( - !exclude.some((x) => { - if (typeof x === 'string') return key === x - - return x.test(key) - }) && - !key.includes('*') && - (excludeStaticFile ? !key.includes('.') : true) - ) { - Object.keys(value).forEach((method) => { - const schema = value[method] - - if (key.includes('{')) { - if (!schema.parameters) schema.parameters = [] - - schema.parameters = [ - ...key - .split('/') - .filter( - (x) => - x.startsWith('{') && - !schema.parameters.find( - (params: Record) => - params.in === 'path' && - params.name === - x.slice(1, x.length - 1) - ) - ) - .map((x) => ({ - schema: { type: 'string' }, - in: 'path', - name: x.slice(1, x.length - 1), - required: true - })), - ...schema.parameters - ] - } - - if (!schema.responses) - schema.responses = { - 200: {} - } - }) - - newPaths[key] = value - } - - return newPaths -} diff --git a/test/gen/index.test.ts b/test/gen/index.test.ts new file mode 100644 index 0000000..850b654 --- /dev/null +++ b/test/gen/index.test.ts @@ -0,0 +1,680 @@ +import { describe, it, expect } from 'bun:test' + +import { declarationToJSONSchema, fromTypes } from '../../src/gen' + +const serializable = ( + a: Record | undefined +): Record | undefined => JSON.parse(JSON.stringify(a)) + +describe('Gen > Type Gen', () => { + it('parse declaration to TypeScript', () => { + const reference = declarationToJSONSchema(` + { + hello: { + world: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + } + } + } + } + }`) + + expect(serializable(reference)!).toEqual({ + '/hello/world': { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + } + } + }) + }) + + it('parse multiple declaration to TypeScript', () => { + const reference = declarationToJSONSchema(` + { + hello: { + world: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + } + } + } + } + hi: { + world: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + } + } + } + } + }`) + + const property = { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + } + } + + expect(serializable(reference)!).toEqual({ + '/hello/world': property, + '/hi/world': property + }) + }) + + it('parse intersect declaration to TypeScript', () => { + const reference = declarationToJSONSchema(` + { + hello: { + world: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + } + } + } + } + } & { + hi: { + world: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + } + } + } + } + }`) + + const property = { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + } + } + + expect(serializable(reference)!).toEqual({ + '/hello/world': property, + '/hi/world': property + }) + }) + + it('add quote to special character while parsing declaration to TypeScript', () => { + const reference = declarationToJSONSchema(` + { + "hello-world": { + 2: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + } + } + } + } + "ไม่ใช่อังกฤษ": { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + name: string + } + 404: { + message: string + } + } + } + } + }`) + + const property = { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + } + } + + expect(serializable(reference)!).toEqual({ + '/hello-world/2': { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + } + }, + '/ไม่ใช่อังกฤษ': { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + }, + '404': { + properties: { + message: { + type: 'string' + } + }, + required: ['message'], + type: 'object' + } + } + } + } + }) + }) + + it('handle readonly property, and readonly array', () => { + const reference = declarationToJSONSchema(` + { + hello: { + world: { + get: { + params: {} + query: {} + headers: {} + body: {} + response: { + 200: { + readonly name: "Lilith" + readonly friends: readonly ["Sartre", "Fouco"] + } + } + } + } + } + }`) + + expect(serializable(reference)!).toEqual({ + '/hello/world': { + get: { + body: { + properties: {}, + type: 'object' + }, + headers: { + properties: {}, + type: 'object' + }, + params: { + properties: {}, + type: 'object' + }, + query: { + properties: {}, + type: 'object' + }, + response: { + '200': { + properties: { + friends: { + additionalItems: false, + items: [ + { + const: 'Sartre', + type: 'string' + }, + { + const: 'Fouco', + type: 'string' + } + ], + maxItems: 2, + minItems: 2, + type: 'array' + }, + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name', 'friends'], + type: 'object' + } + } + } + } + }) + }) + + it('integrate', async () => { + const reference = fromTypes('test/gen/sample.ts')() + + expect(serializable(reference)!).toEqual({ + '/': { + derive: {}, + get: { + body: {}, + headers: {}, + params: { + properties: {}, + type: 'object' + }, + query: {}, + response: { + '204': {}, + '422': { + properties: { + expected: { + type: 'string' + }, + found: {}, + message: { + type: 'string' + }, + on: { + type: 'string' + }, + property: { + type: 'string' + }, + summary: { + type: 'string' + }, + type: { + const: 'validation', + type: 'string' + } + }, + required: ['type', 'on'], + type: 'object' + } + } + }, + resolve: {}, + response: {}, + schema: {}, + standaloneschema: {} + }, + '/character': { + post: { + body: { + type: 'string' + }, + headers: {}, + params: { + properties: {}, + type: 'object' + }, + query: {}, + response: { + '200': { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + }, + '422': { + properties: { + expected: { + type: 'string' + }, + found: {}, + message: { + type: 'string' + }, + on: { + type: 'string' + }, + property: { + type: 'string' + }, + summary: { + type: 'string' + }, + type: { + const: 'validation', + type: 'string' + } + }, + required: ['type', 'on'], + type: 'object' + } + } + } + }, + '/const': { + get: { + body: {}, + headers: {}, + params: { + properties: {}, + type: 'object' + }, + query: {}, + response: { + '200': { + properties: { + friends: { + additionalItems: false, + items: [ + { + const: 'Sartre', + type: 'string' + }, + { + const: 'Fouco', + type: 'string' + } + ], + maxItems: 2, + minItems: 2, + type: 'array' + }, + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name', 'friends'], + type: 'object' + } + } + } + }, + '/id/:id/name/:name': { + get: { + body: {}, + headers: {}, + params: { + properties: { + id: { + type: 'string' + }, + name: { + type: 'string' + } + }, + required: ['name', 'id'], + type: 'object' + }, + query: {}, + response: { + '200': { + patternProperties: { + '^(.*)$': { + type: 'string' + } + }, + type: 'object' + } + } + } + }, + '/json': { + post: { + body: { + properties: { + hello: { + type: 'string' + } + }, + required: ['hello'], + type: 'object' + }, + headers: {}, + params: { + properties: {}, + type: 'object' + }, + query: {}, + response: { + '200': { + properties: { + hello: { + type: 'string' + } + }, + required: ['hello'], + type: 'object' + }, + '418': { + const: "I'm a teapot", + type: 'string' + }, + '422': { + properties: { + expected: { + type: 'string' + }, + found: {}, + message: { + type: 'string' + }, + on: { + type: 'string' + }, + property: { + type: 'string' + }, + summary: { + type: 'string' + }, + type: { + const: 'validation', + type: 'string' + } + }, + required: ['type', 'on'], + type: 'object' + } + } + } + }, + '/no-manual': { + get: { + body: {}, + headers: {}, + params: { + properties: {}, + type: 'object' + }, + query: {}, + response: { + '200': { + properties: { + name: { + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + } + } + }) + }) +}) diff --git a/test/gen/sample.ts b/test/gen/sample.ts new file mode 100644 index 0000000..47aef29 --- /dev/null +++ b/test/gen/sample.ts @@ -0,0 +1,61 @@ +import { Elysia, t } from 'elysia' +import { openapi, withHeaders } from '../../src' +import { fromTypes } from '../../src/gen' + +export const app = new Elysia() + .model({ + 'character.name': t.String(), + 'character.thing': t.Object({ + name: t.String() + }) + }) + .get( + '/const', + () => + ({ + name: 'Lilith', + friends: ['Sartre', 'Fouco'] + }) as const + ) + .get( + '/', + () => + ({ test: 'hello' as const }) as any as + | { test: 'hello' } + | undefined, + { + response: { + 204: withHeaders( + t.Void({ + title: 'Thing', + description: 'Void response' + }), + { + 'X-Custom-Header': t.Literal('Elysia') + } + ) + } + } + ) + .post( + '/json', + ({ body, status }) => (Math.random() > 0.5 ? status(418) : body), + { + body: t.Object({ + hello: t.String() + }) + } + ) + .get('/id/:id/name/:name', ({ params }) => params) + .post( + '/character', + () => ({ + name: 'Lilith' as const + }), + { + body: 'character.name' + } + ) + .get('/no-manual', () => ({ + name: 'lilith' + })) diff --git a/test/index.test.ts b/test/index.test.ts index 868be79..9c61e47 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,6 @@ import { Elysia, t } from 'elysia' import SwaggerParser from '@apidevtools/swagger-parser' -import { swagger } from '../src' +import { openapi } from '../src' import { describe, expect, it } from 'bun:test' import { fail } from 'assert' @@ -9,35 +9,37 @@ const req = (path: string) => new Request(`http://localhost${path}`) describe('Swagger', () => { it('show Swagger page', async () => { - const app = new Elysia().use(swagger()) + const app = new Elysia().use(openapi()) await app.modules - const res = await app.handle(req('/swagger')) + const res = await app.handle(req('/openapi')) expect(res.status).toBe(200) }) - it('returns a valid Swagger/OpenAPI json config', async () => { - const app = new Elysia().use(swagger()) + it('returns a valid OpenAPI json config', async () => { + const app = new Elysia().use(openapi()) await app.modules - const res = await app.handle(req('/swagger/json')).then((x) => x.json()) + const res = await app.handle(req('/openapi/json')).then((x) => x.json()) expect(res.openapi).toBe('3.0.3') await SwaggerParser.validate(res).catch((err) => fail(err)) }) it('use custom Swagger version', async () => { const app = new Elysia().use( - swagger({ + openapi({ provider: 'swagger-ui', - version: '4.5.0' + swagger: { + version: '4.5.0' + } }) ) await app.modules - const res = await app.handle(req('/swagger')).then((x) => x.text()) + const res = await app.handle(req('/openapi')).then((x) => x.text()) expect( res.includes( 'https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-bundle.js' @@ -47,9 +49,11 @@ describe('Swagger', () => { it('follow title and description with Swagger-UI provider', async () => { const app = new Elysia().use( - swagger({ - version: '4.5.0', + openapi({ provider: 'swagger-ui', + swagger: { + version: '4.5.0' + }, documentation: { info: { title: 'Elysia Documentation', @@ -62,7 +66,7 @@ describe('Swagger', () => { await app.modules - const res = await app.handle(req('/swagger')).then((x) => x.text()) + const res = await app.handle(req('/openapi')).then((x) => x.text()) expect(res.includes('Elysia Documentation')).toBe(true) expect( @@ -77,9 +81,11 @@ describe('Swagger', () => { it('follow title and description with Scalar provider', async () => { const app = new Elysia().use( - swagger({ - version: '4.5.0', + openapi({ provider: 'scalar', + scalar: { + version: '4.5.0' + }, documentation: { info: { title: 'Elysia Documentation', @@ -92,7 +98,7 @@ describe('Swagger', () => { await app.modules - const res = await app.handle(req('/swagger')).then((x) => x.text()) + const res = await app.handle(req('/openapi')).then((x) => x.text()) expect(res.includes('Elysia Documentation')).toBe(true) expect( @@ -107,25 +113,25 @@ describe('Swagger', () => { it('use custom path', async () => { const app = new Elysia().use( - swagger({ - path: '/v2/swagger' + openapi({ + path: '/v2/openapi' }) ) await app.modules - const res = await app.handle(req('/v2/swagger')) + const res = await app.handle(req('/v2/openapi')) expect(res.status).toBe(200) - const resJson = await app.handle(req('/v2/swagger/json')) + const resJson = await app.handle(req('/v2/openapi/json')) expect(resJson.status).toBe(200) }) it('Swagger UI options', async () => { const app = new Elysia().use( - swagger({ + openapi({ provider: 'swagger-ui', - swaggerOptions: { + swagger: { persistAuthorization: true } }) @@ -133,14 +139,14 @@ describe('Swagger', () => { await app.modules - const res = await app.handle(req('/swagger')).then((x) => x.text()) + const res = await app.handle(req('/openapi')).then((x) => x.text()) const expected = `"persistAuthorization":true` expect(res.trim().includes(expected.trim())).toBe(true) }) it('should not return content response when using Void type', async () => { - const app = new Elysia().use(swagger()).get('/void', () => {}, { + const app = new Elysia().use(openapi()).get('/void', () => {}, { response: { 204: t.Void({ description: 'Void response' @@ -150,20 +156,21 @@ describe('Swagger', () => { await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() expect(response.paths['/void'].get.responses['204'].description).toBe( 'Void response' ) - expect( - response.paths['/void'].get.responses['204'].content - ).toBeUndefined() + expect(response.paths['/void'].get.responses['204'].content).toEqual({ + description: 'Void response', + type: 'void' + }) }) it('should not return content response when using Undefined type', async () => { const app = new Elysia() - .use(swagger()) + .use(openapi()) .get('/undefined', () => undefined, { response: { 204: t.Undefined({ @@ -174,7 +181,7 @@ describe('Swagger', () => { await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() expect( @@ -182,11 +189,14 @@ describe('Swagger', () => { ).toBe('Undefined response') expect( response.paths['/undefined'].get.responses['204'].content - ).toBeUndefined() + ).toEqual({ + type: 'undefined', + description: 'Undefined response' + }) }) it('should not return content response when using Null type', async () => { - const app = new Elysia().use(swagger()).get('/null', () => null, { + const app = new Elysia().use(openapi()).get('/null', () => null, { response: { 204: t.Null({ description: 'Null response' @@ -196,84 +206,70 @@ describe('Swagger', () => { await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() expect(response.paths['/null'].get.responses['204'].description).toBe( 'Null response' ) - expect( - response.paths['/null'].get.responses['204'].content - ).toBeUndefined() + expect(response.paths['/null'].get.responses['204'].content).toEqual({ + type: 'null', + description: 'Null response' + }) }) it('should set the required field to true when a request body is present', async () => { - const app = new Elysia().use(swagger()).post('/post', () => {}, { + const app = new Elysia().use(openapi()).post('/post', () => {}, { body: t.Object({ name: t.String() }) }) await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() expect(response.paths['/post'].post.requestBody.required).toBe(true) }) it('resolve optional param to param', async () => { - const app = new Elysia().use(swagger()).get('/id/:id?', () => {}) + const app = new Elysia().use(openapi()).get('/id/:id?', () => {}) await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() expect(response.paths).toContainKey('/id/{id}') }) it('should hide routes with hide = true from paths', async () => { - const app = new Elysia().use(swagger()) - .get("/public", "omg") + const app = new Elysia() + .use(openapi()) + .get('/public', 'omg') .guard({ detail: { hide: true } }) - .get("/hidden", "ok") + .get('/hidden', 'ok') await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() - expect(response.paths['/public']).not.toBeUndefined(); - expect(response.paths['/hidden']).toBeUndefined(); + expect(response.paths['/public']).not.toBeUndefined() + expect(response.paths['/hidden']).toBeUndefined() }) it('should expand .all routes', async () => { - const app = new Elysia().use(swagger()) - .all("/all", "woah") + const app = new Elysia().use(openapi()).all('/all', 'woah') await app.modules - const res = await app.handle(req('/swagger/json')) + const res = await app.handle(req('/openapi/json')) expect(res.status).toBe(200) const response = await res.json() expect(Object.keys(response.paths['/all'])).toBeArrayOfSize(8) }) - - it('should hide routes that are invalid', async () => { - const app = new Elysia().use(swagger()) - .get("/valid", "ok") - .route("LOCK", "/invalid", "nope") - - await app.modules - - const res = await app.handle(req('/swagger/json')) - expect(res.status).toBe(200) - const response = await res.json() - expect(response.paths['/valid']).not.toBeUndefined(); - expect(response.paths['/invalid']).toBeUndefined(); - - }) }) diff --git a/test/node/cjs/index.js b/test/node/cjs/index.js index 9d7dc4b..512725e 100644 --- a/test/node/cjs/index.js +++ b/test/node/cjs/index.js @@ -1,11 +1,7 @@ -if ('Bun' in globalThis) { - throw new Error('❌ Use Node.js to run this test!'); -} +if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') -const { swagger } = require('@elysiajs/swagger'); +const { openapi } = require('@elysiajs/openapi') -if (typeof swagger !== 'function') { - throw new Error('❌ CommonJS Node.js failed'); -} +if (typeof openapi !== 'function') throw new Error('❌ CommonJS Node.js failed') -console.log('✅ CommonJS Node.js works!'); +console.log('✅ CommonJS Node.js works!') diff --git a/test/node/cjs/package.json b/test/node/cjs/package.json index 53c8a2a..69161da 100644 --- a/test/node/cjs/package.json +++ b/test/node/cjs/package.json @@ -1,6 +1,6 @@ { "type": "commonjs", "dependencies": { - "@elysiajs/swagger": "../../.." + "@elysiajs/openapi": "file:../../.." } -} \ No newline at end of file +} diff --git a/test/node/esm/index.js b/test/node/esm/index.js index ec25d72..dd80c27 100644 --- a/test/node/esm/index.js +++ b/test/node/esm/index.js @@ -1,11 +1,7 @@ -if ('Bun' in globalThis) { - throw new Error('❌ Use Node.js to run this test!'); -} +if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') -import { swagger } from '@elysiajs/swagger'; +import { openapi } from '@elysiajs/openapi' -if (typeof swagger !== 'function') { - throw new Error('❌ ESM Node.js failed'); -} +if (typeof openapi !== 'function') throw new Error('❌ ESM Node.js failed') -console.log('✅ ESM Node.js works!'); +console.log('✅ ESM Node.js works!') diff --git a/test/node/esm/package.json b/test/node/esm/package.json index 587ca48..c4bea16 100644 --- a/test/node/esm/package.json +++ b/test/node/esm/package.json @@ -1,6 +1,6 @@ { "type": "module", "dependencies": { - "@elysiajs/swagger": "../../.." + "@elysiajs/openapi": "file:../../.." } -} \ No newline at end of file +} diff --git a/test/openapi.test.ts b/test/openapi.test.ts new file mode 100644 index 0000000..7929f8d --- /dev/null +++ b/test/openapi.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'bun:test' +import { Kind } from '@sinclair/typebox' + +import { enumToOpenApi } from '../src/openapi' + +describe('convertEnumToOpenApi', () => { + it('should convert enum schema to OpenAPI enum format', () => { + const expectedSchema = { + [Kind]: 'Union', + anyOf: [{ const: 'male' }, { const: 'female' }] + } + + const result = enumToOpenApi(expectedSchema as any) + + expect(result).toEqual({ + type: 'string', + enum: ['male', 'female'] + }) + }) + + it('should convert nested enums in object properties', () => { + const expectedSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + gender: { + [Kind]: 'Union', + anyOf: [{ const: 'male' }, { const: 'female' }] + } + } + } + + const result = enumToOpenApi(expectedSchema as any) + + expect(result).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + gender: { + type: 'string', + enum: ['male', 'female'] + } + } + }) + }) + + it('should return original schema if not enum', () => { + const expectedSchema = { + type: 'string', + description: 'Regular string field' + } + + const result = enumToOpenApi(expectedSchema as any) + + expect(result).toEqual(expectedSchema) + }) +}) diff --git a/test/openapi/enum-to-openapi.test.ts b/test/openapi/enum-to-openapi.test.ts new file mode 100644 index 0000000..32e3331 --- /dev/null +++ b/test/openapi/enum-to-openapi.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'bun:test' +import { Kind } from '@sinclair/typebox' + +import { enumToOpenApi } from '../../src/openapi' + +describe('OpenAPI > enumToOpenAPI', () => { + it('should convert enum schema to OpenAPI enum format', () => { + const expectedSchema = { + [Kind]: 'Union', + anyOf: [{ const: 'male' }, { const: 'female' }] + } + + const result = enumToOpenApi(expectedSchema as any) + + expect(result).toEqual({ + type: 'string', + enum: ['male', 'female'] + }) + }) + + it('should convert nested enums in object properties', () => { + const expectedSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + gender: { + [Kind]: 'Union', + anyOf: [{ const: 'male' }, { const: 'female' }] + } + } + } + + const result = enumToOpenApi(expectedSchema as any) + + expect(result).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + gender: { + type: 'string', + enum: ['male', 'female'] + } + } + }) + }) + + it('should return original schema if not enum', () => { + const expectedSchema = { + type: 'string', + description: 'Regular string field' + } + + const result = enumToOpenApi(expectedSchema as any) + + expect(result).toEqual(expectedSchema) + }) +}) diff --git a/test/openapi/get-possible-path.test.ts b/test/openapi/get-possible-path.test.ts new file mode 100644 index 0000000..7d4d919 --- /dev/null +++ b/test/openapi/get-possible-path.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'bun:test' + +import { getPossiblePath } from '../../src/openapi' + +describe('OpenAPI > getPossiblePath', () => { + it('remain the same if no optional', () => { + expect(getPossiblePath('/user/:user/name/:name')).toEqual([ + '/user/:user/name/:name' + ]) + }) + + it('list all possibility from optional', () => { + expect(getPossiblePath('/user/:user?/name/:name?')).toEqual([ + '/user/:user/name/:name', + '/user/name/:name', + '/user/name', + '/user/:user/name', + '/user/name' + ]) + }) +}) diff --git a/test/openapi/references.test.ts b/test/openapi/references.test.ts new file mode 100644 index 0000000..cd46dd0 --- /dev/null +++ b/test/openapi/references.test.ts @@ -0,0 +1,208 @@ +import { describe, it, expect } from 'bun:test' +import { Elysia, t } from 'elysia' + +import { toOpenAPISchema } from '../../src/openapi' + +const serializable = ( + a: Record | undefined +): Record | undefined => JSON.parse(JSON.stringify(a)) + +describe('OpenAPI > references', () => { + it('use references when schema is not available', () => { + const app = new Elysia().get('/', () => {}) + + const schema = toOpenAPISchema(app, undefined, { + '/': { + get: { + params: {} as any, + query: {} as any, + headers: {} as any, + body: {} as any, + response: { + 200: t.Object({ + name: t.Literal('lilith') + }) + } + } + } + }) + + expect(serializable(schema)).toEqual({ + components: { + schemas: {} + }, + paths: { + '/': { + get: { + operationId: 'getIndex', + responses: { + '200': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 200' + } + } + } + } + } + }) + }) + + it('prefers schema over definition', () => { + const app = new Elysia().get('/', () => ({ name: 'fouco' }) as const, { + query: t.Object({ + id: t.Number() + }), + response: t.Object({ + name: t.Literal('fouco') + }) + }) + + const schema = toOpenAPISchema(app, undefined, { + '/': { + get: { + params: {} as any, + query: {} as any, + headers: {} as any, + body: {} as any, + response: { + 200: t.Object({ + name: t.Literal('lilith') + }) + } + } + } + }) + + expect(serializable(schema)).toEqual({ + components: { + schemas: {} + }, + paths: { + '/': { + get: { + operationId: 'getIndex', + parameters: [ + { + in: 'query', + name: 'id', + required: true, + schema: { + type: 'number' + } + } + ], + responses: { + '200': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'fouco', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 200' + } + } + } + } + } + }) + }) + + it('use multiple references', () => { + const app = new Elysia().get('/', () => {}) + + const schema = toOpenAPISchema(app, undefined, [ + { + '/': { + get: { + params: {} as any, + query: {} as any, + headers: {} as any, + body: {} as any, + response: { + 200: t.Object({ + name: t.Literal('lilith') + }) + } + } + } + }, + { + '/': { + get: { + params: {} as any, + query: t.Object({ + id: t.Number() + }), + headers: {} as any, + body: {} as any, + response: {} + } + } + } + ]) + + expect(serializable(schema)).toEqual({ + components: { + schemas: {} + }, + paths: { + '/': { + get: { + operationId: 'getIndex', + parameters: [ + { + in: 'query', + name: 'id', + required: true, + schema: { + type: 'number' + } + } + ], + responses: { + '200': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 200' + } + } + } + } + } + }) + }) +}) diff --git a/test/openapi/to-openapi-schema.test.ts b/test/openapi/to-openapi-schema.test.ts new file mode 100644 index 0000000..642d974 --- /dev/null +++ b/test/openapi/to-openapi-schema.test.ts @@ -0,0 +1,1222 @@ +import { describe, it, expect } from 'bun:test' +import { AnyElysia, Elysia, t } from 'elysia' + +import { toOpenAPISchema } from '../../src/openapi' + +const is = ( + app: T, + schema: { + paths: Record + components: Record + } +) => { + expect(JSON.parse(JSON.stringify(toOpenAPISchema(app)))).toEqual(schema) + + expect(JSON.parse(JSON.stringify(toOpenAPISchema(app)))).not.toEqual({ + ...schema, + paths: { + ...schema.paths, + '/non-existent-path': {} + } + }) +} + +describe('OpenAPI > toOpenAPISchema', () => { + it('work', () => { + const app = new Elysia().get('/user', () => 'hello') + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser' + } + } + } + }) + }) + + it('handle params', () => { + const app = new Elysia().get('/user/:user', () => 'hello', { + params: t.Object({ + user: t.Number() + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user/{user}': { + get: { + operationId: 'getUserByUser', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'number' + } + } + ] + } + } + } + }) + }) + + it('handle headers', () => { + const app = new Elysia().get('/user', () => 'hello', { + headers: t.Object({ + 'x-user-name': t.Literal('Lilith') + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + parameters: [ + { + in: 'header', + name: 'x-user-name', + required: true, + schema: { + type: 'string', + const: 'Lilith' + } + } + ] + } + } + } + }) + }) + + it('handle query', () => { + const app = new Elysia().get('/user', () => 'hello', { + query: t.Object({ + name: t.Literal('Lilith') + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + parameters: [ + { + in: 'query', + name: 'name', + required: true, + schema: { + type: 'string', + const: 'Lilith' + } + } + ] + } + } + } + }) + }) + + it('handle cookie', () => { + const app = new Elysia().get('/user', () => 'hello', { + cookie: t.Object({ + name: t.Literal('Lilith') + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + parameters: [ + { + in: 'cookie', + name: 'name', + required: true, + schema: { + type: 'string', + const: 'Lilith' + } + } + ] + } + } + } + }) + }) + + it('handle body', () => { + const app = new Elysia().post('/user', () => 'hello', { + body: t.Object({ + name: t.Literal('Lilith') + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + post: { + operationId: 'postUser', + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + }, + 'application/x-www-form-urlencoded': { + schema: { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + }, + 'multipart/form-data': { + schema: { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + required: true + } + } + } + } + }) + }) + + it('handle response', () => { + const app = new Elysia().get( + '/user', + () => ({ name: 'Lilith' }) as const, + { + response: t.Object({ + name: t.Literal('Lilith') + }) + } + ) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + responses: { + '200': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 200' + } + } + } + } + } + }) + }) + + it('handle multiple response status', () => { + const app = new Elysia().get( + '/user', + () => ({ name: 'Lilith' }) as const, + { + response: { + 200: t.Object({ + name: t.Literal('Fouco') + }), + 404: t.Object({ + name: t.Literal('Lilith') + }) + } + } + ) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + responses: { + '200': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'Fouco', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 200' + }, + '404': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 404' + } + } + } + } + } + }) + }) + + it('handle every parameters together', () => { + const app = new Elysia().post( + '/id/:id', + () => ({ name: 'Lilith' }) as const, + { + body: t.Object({ + age: t.Number() + }), + params: t.Object({ + id: t.Number() + }), + query: t.Object({ + name: t.Literal('Lilith') + }), + headers: t.Object({ + 'x-user-name': t.Literal('Lilith') + }), + cookie: t.Object({ + session: t.String() + }), + response: { + 200: t.Object({ + name: t.Literal('Fouco') + }), + 404: t.Object({ + name: t.Literal('Lilith') + }) + } + } + ) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/id/{id}': { + post: { + operationId: 'postIdById', + parameters: [ + { + in: 'path', + name: 'id', + required: true, + schema: { + type: 'number' + } + }, + { + in: 'query', + name: 'name', + required: true, + schema: { + const: 'Lilith', + type: 'string' + } + }, + { + in: 'header', + name: 'x-user-name', + required: true, + schema: { + const: 'Lilith', + type: 'string' + } + }, + { + in: 'cookie', + name: 'session', + required: true, + schema: { + type: 'string' + } + } + ], + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + age: { + type: 'number' + } + }, + required: ['age'], + type: 'object' + } + }, + 'application/x-www-form-urlencoded': { + schema: { + properties: { + age: { + type: 'number' + } + }, + required: ['age'], + type: 'object' + } + }, + 'multipart/form-data': { + schema: { + properties: { + age: { + type: 'number' + } + }, + required: ['age'], + type: 'object' + } + } + }, + required: true + }, + responses: { + '200': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'Fouco', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 200' + }, + '404': { + content: { + 'application/json': { + schema: { + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Response for status 404' + } + } + } + } + } + }) + }) + + it('handle params', () => { + const app = new Elysia().get('/user/:user', () => 'hello', { + params: t.Object({ + user: t.Number() + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user/{user}': { + get: { + operationId: 'getUserByUser', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'number' + } + } + ] + } + } + } + }) + }) + + it('inline reference params', () => { + const model = new Elysia().model( + 'headers', + t.Object({ + 'x-user-name': t.Literal('Lilith') + }) + ) + + const app = new Elysia().use(model).get('/user/:user', () => 'hello', { + headers: 'headers' + }) + + is(app, { + components: { + schemas: { + headers: { + $id: '#/components/schemas/headers', + properties: { + 'x-user-name': { + const: 'Lilith', + type: 'string' + } + }, + required: ['x-user-name'], + type: 'object' + } + } + }, + paths: { + '/user/{user}': { + get: { + operationId: 'getUserByUser', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'string' + } + }, + { + in: 'header', + name: 'x-user-name', + required: true, + schema: { + const: 'Lilith', + type: 'string' + } + } + ] + } + } + } + }) + }) + + it('inline reference query', () => { + const model = new Elysia().model( + 'query', + t.Object({ + name: t.Literal('Lilith') + }) + ) + + const app = new Elysia().use(model).get('/user', () => 'hello', { + query: 'query' + }) + + is(app, { + components: { + schemas: { + query: { + $id: '#/components/schemas/query', + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + parameters: [ + { + in: 'query', + name: 'name', + required: true, + schema: { + const: 'Lilith', + type: 'string' + } + } + ] + } + } + } + }) + }) + + it('inline reference cookie', () => { + const model = new Elysia().model( + 'cookie', + t.Object({ + name: t.Literal('Lilith') + }) + ) + + const app = new Elysia().use(model).get('/user', () => 'hello', { + cookie: 'cookie' + }) + + is(app, { + components: { + schemas: { + cookie: { + $id: '#/components/schemas/cookie', + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + paths: { + '/user': { + get: { + operationId: 'getUser', + parameters: [ + { + in: 'cookie', + name: 'name', + required: true, + schema: { + const: 'Lilith', + type: 'string' + } + } + ] + } + } + } + }) + }) + + it('reference body', () => { + const model = new Elysia().model( + 'body', + t.Object({ + name: t.Literal('Lilith') + }) + ) + + const app = new Elysia().use(model).post('/user', () => 'hello', { + body: 'body' + }) + + is(app, { + components: { + schemas: { + body: { + $id: '#/components/schemas/body', + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + paths: { + '/user': { + post: { + operationId: 'postUser', + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/body' + } + }, + 'application/x-www-form-urlencoded': { + schema: { + $ref: '#/components/schemas/body' + } + }, + 'multipart/form-data': { + schema: { + $ref: '#/components/schemas/body' + } + } + }, + required: true + } + } + } + } + }) + }) + + it('reference response', () => { + const model = new Elysia().model({ + lilith: t.Object({ + name: t.Literal('Lilith') + }) + }) + + const app = new Elysia().use(model).post( + '/user', + () => + ({ + name: 'Lilith' + }) as const, + { + response: 'lilith' + } + ) + + is(app, { + components: { + schemas: { + lilith: { + $id: '#/components/schemas/lilith', + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + paths: { + '/user': { + post: { + operationId: 'postUser', + responses: { + '200': { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/lilith' + } + } + }, + description: 'Response for status 200' + } + } + } + } + } + }) + }) + + it('reference multiple response', () => { + const model = new Elysia().model({ + lilith: t.Object({ + name: t.Literal('Lilith') + }), + fouco: t.Object({ + name: t.Literal('Fouco') + }) + }) + + const app = new Elysia().use(model).post( + '/user', + () => + ({ + name: 'Lilith' + }) as const, + { + response: { + 200: 'fouco', + 404: 'lilith' + } + } + ) + + is(app, { + components: { + schemas: { + lilith: { + $id: '#/components/schemas/lilith', + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + }, + fouco: { + $id: '#/components/schemas/fouco', + properties: { + name: { + const: 'Fouco', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + paths: { + '/user': { + post: { + operationId: 'postUser', + responses: { + '200': { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/fouco' + } + } + }, + description: 'Response for status 200' + }, + '404': { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/lilith' + } + } + }, + description: 'Response for status 404' + } + } + } + } + } + }) + }) + + it('accept detail', () => { + const app = new Elysia().get('/user', () => 'hello', { + detail: { + summary: 'Get User', + description: 'Hello User', + tags: ['User'] + } + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + summary: 'Get User', + operationId: 'getUser', + description: 'Hello User', + tags: ['User'] + } + } + } + }) + }) + + it('use custom operationId', () => { + const app = new Elysia().get('/user', () => 'hello', { + detail: { + operationId: 'helloUser' + } + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'helloUser' + } + } + } + }) + }) + + it('has path parameter without schema argument', () => { + const app = new Elysia().get('/user/:user/id/:id', () => 'hello') + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user/{user}/id/{id}': { + get: { + operationId: 'getUserByUserIdById', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'string' + } + }, + { + in: 'path', + name: 'id', + required: true, + schema: { + type: 'string' + } + } + ] + } + } + } + }) + }) + + it('list all possible path', () => { + const app = new Elysia().get('/user/:user?/id/:id?', () => 'hello') + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user/id': { + get: { + operationId: 'getUserId', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'string' + } + }, + { + in: 'path', + name: 'id', + required: true, + schema: { + type: 'string' + } + } + ] + } + }, + '/user/id/{id}': { + get: { + operationId: 'getUserIdById', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'string' + } + }, + { + in: 'path', + name: 'id', + required: true, + schema: { + type: 'string' + } + } + ] + } + }, + '/user/{user}/id': { + get: { + operationId: 'getUserByUserId', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'string' + } + }, + { + in: 'path', + name: 'id', + required: true, + schema: { + type: 'string' + } + } + ] + } + }, + '/user/{user}/id/{id}': { + get: { + operationId: 'getUserByUserIdById', + parameters: [ + { + in: 'path', + name: 'user', + required: true, + schema: { + type: 'string' + } + }, + { + in: 'path', + name: 'id', + required: true, + schema: { + type: 'string' + } + } + ] + } + } + } + }) + }) + + it('exclude handle body get and head', () => { + const app = new Elysia() + .get('/user', () => 'hello', { + body: t.Object({ + name: t.Literal('Lilith') + }) + }) + .head('/user', () => 'hello', { + body: t.Object({ + name: t.Literal('Lilith') + }) + }) + + is(app, { + components: { + schemas: {} + }, + paths: { + '/user': { + get: { + operationId: 'getUser' + }, + head: { + operationId: 'headUser' + } + } + } + }) + }) + + it('response accept annotation', () => { + const model = new Elysia().model({ + lilith: t.Object( + { + name: t.Literal('Lilith') + }, + { + description: 'Existed' + } + ) + }) + + const app = new Elysia().use(model).post( + '/user', + () => + ({ + name: 'Lilith' + }) as const, + { + response: { + 200: t.Object( + { + name: t.Literal('Fouco') + }, + { + description: 'Demon Lord and Rhythm Gamer' + } + ), + 404: 'lilith' + } + } + ) + + is(app, { + components: { + schemas: { + lilith: { + $id: '#/components/schemas/lilith', + description: 'Existed', + properties: { + name: { + const: 'Lilith', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + paths: { + '/user': { + post: { + operationId: 'postUser', + responses: { + '200': { + content: { + 'application/json': { + schema: { + description: + 'Demon Lord and Rhythm Gamer', + properties: { + name: { + const: 'Fouco', + type: 'string' + } + }, + required: ['name'], + type: 'object' + } + } + }, + description: 'Demon Lord and Rhythm Gamer' + }, + '404': { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/lilith' + } + } + }, + description: 'Existed' + } + } + } + } + } + }) + }) + + it('body should be text/plain on primitive value', () => { + const model = new Elysia().model('lilith', t.Literal('Lilith')) + + const app = new Elysia().use(model).post('/user', () => 'hello', { + body: 'lilith' + }) + + is(app, { + components: { + schemas: { + lilith: { + $id: '#/components/schemas/lilith', + const: 'Lilith', + type: 'string' + } + } + }, + paths: { + '/user': { + post: { + operationId: 'postUser', + requestBody: { + content: { + 'text/plain': { + schema: { + $ref: '#/components/schemas/lilith' + } + } + }, + required: true + } + } + } + } + }) + }) +}) diff --git a/test/validate-schema.test.ts b/test/validate-schema.test.ts index ee9558e..64bf96e 100644 --- a/test/validate-schema.test.ts +++ b/test/validate-schema.test.ts @@ -1,86 +1,84 @@ -import { Elysia, t } from 'elysia' -import SwaggerParser from '@apidevtools/swagger-parser' -import { swagger } from '../src' - -import { it } from 'bun:test' -import { fail } from 'assert' - -const req = (path: string) => new Request(`http://localhost${path}`) - -it('returns a valid Swagger/OpenAPI json config for many routes', async () => { - const app = new Elysia() - .use(swagger()) - .get('/', () => 'hi', { - response: t.String({ description: 'sample description' }) - }) - .get('/unpath/:id', ({ params: { id } }) => id, { - response: t.String({ description: 'sample description' }) - }) - .get( - '/unpath/:id/:name/:age', - ({ params: { id, name } }) => `${id} ${name}`, - { - type: 'json', - response: t.String({ description: 'sample description' }), - params: t.Object({ id: t.String(), name: t.String() }) - } - ) - .post( - '/json/:id', - ({ body, params: { id }, query: { name, email, birthday } }) => ({ - ...body, - id, - name, - email, - birthday - }), - { - params: t.Object({ - id: t.String() - }), - query: t.Object({ - name: t.String(), - email: t.String({ - description: 'sample email description', - format: 'email' - }), - birthday: t.String({ - description: 'sample birthday description', - pattern: '\\d{4}-\\d{2}-\\d{2}', - minLength: 10, - maxLength: 10 - }), - - }), - body: t.Object({ - username: t.String(), - password: t.String() - }), - response: t.Object( - { - username: t.String(), - password: t.String(), - id: t.String(), - name: t.String(), - email: t.String({ - description: 'sample email description', - format: 'email' - }), - birthday: t.String({ - description: 'sample birthday description', - pattern: '\\d{4}-\\d{2}-\\d{2}', - minLength: 10, - maxLength: 10 - }), - }, - { description: 'sample description 3' } - ) - } - ) - .route('LOCK', '/lock', () => 'locked') - - await app.modules - - const res = await app.handle(req('/swagger/json')).then((x) => x.json()) - await SwaggerParser.validate(res).catch((err) => fail(err)) -}) +import { Elysia, t } from 'elysia' +import SwaggerParser from '@apidevtools/swagger-parser' +import { openapi } from '../src' + +import { it } from 'bun:test' +import { fail } from 'assert' + +const req = (path: string) => new Request(`http://localhost${path}`) + +it('returns a valid Swagger/OpenAPI json config for many routes', async () => { + const app = new Elysia() + .use(openapi()) + .get('/', () => 'hi', { + response: t.String({ description: 'sample description' }) + }) + .get('/unpath/:id', ({ params: { id } }) => id, { + response: t.String({ description: 'sample description' }) + }) + .get( + '/unpath/:id/:name/:age', + ({ params: { id, name } }) => `${id} ${name}`, + { + type: 'json', + response: t.String({ description: 'sample description' }), + params: t.Object({ id: t.String(), name: t.String() }) + } + ) + .post( + '/json/:id', + ({ body, params: { id }, query: { name, email, birthday } }) => ({ + ...body, + id, + name, + email, + birthday + }), + { + params: t.Object({ + id: t.String() + }), + query: t.Object({ + name: t.String(), + email: t.String({ + description: 'sample email description', + format: 'email' + }), + birthday: t.String({ + description: 'sample birthday description', + pattern: '\\d{4}-\\d{2}-\\d{2}', + minLength: 10, + maxLength: 10 + }) + }), + body: t.Object({ + username: t.String(), + password: t.String() + }), + response: t.Object( + { + username: t.String(), + password: t.String(), + id: t.String(), + name: t.String(), + email: t.String({ + description: 'sample email description', + format: 'email' + }), + birthday: t.String({ + description: 'sample birthday description', + pattern: '\\d{4}-\\d{2}-\\d{2}', + minLength: 10, + maxLength: 10 + }) + }, + { description: 'sample description 3' } + ) + } + ) + + await app.modules + + const res = await app.handle(req('/openapi/json')).then((x) => x.json()) + await SwaggerParser.validate(res).catch((err) => fail(err)) +}) diff --git a/tsconfig.dts.json b/tsconfig.dts.json index cd54e87..f62ecce 100644 --- a/tsconfig.dts.json +++ b/tsconfig.dts.json @@ -1,106 +1,106 @@ -{ - "compilerOptions": { - "preserveSymlinks": true, - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "ES2022", /* Specify what module code is generated. */ - "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "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. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ - }, - "exclude": ["node_modules", "test", "example", "dist", "build.ts"] - // "include": ["src/**/*"] -} +{ + "compilerOptions": { + "preserveSymlinks": true, + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "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. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + }, + "exclude": ["node_modules", "test", "example", "dist", "build.ts"] + // "include": ["src/**/*"] +} diff --git a/tsconfig.json b/tsconfig.json index e7cb18d..6439e61 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,103 +1,103 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - "lib": ["ESNext", "DOM", "ScriptHost"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "ES2022", /* Specify what module code is generated. */ - // "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "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. */ - // "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ - }, - // "include": ["src/**/*"] -} +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["ESNext", "DOM", "ScriptHost"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + // "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "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. */ + // "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + }, + // "include": ["src/**/*"] +}