From 6df191c6e6159f2d6b0d4462e4cf1f080591d029 Mon Sep 17 00:00:00 2001 From: John Cudd Date: Thu, 22 May 2025 17:47:32 -0400 Subject: [PATCH 01/58] Added scalarCDN to docs --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5993cc0..bb1a75d 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ Customize Swagger config, refers to [Swagger 3.0.3 config](https://swagger.io/sp The endpoint to expose Swagger +## scalarCDN +Self-host the scalar bundle or point to a different CDN. + ## excludeStaticFile @default true From 761b5d4e56657244248fa86a57e143c3dab1ff13 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 18 Jun 2025 05:49:44 +0700 Subject: [PATCH 02/58] :tada: feat: use relative path for swagger when using prefix --- example/index.ts | 2 +- src/index.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/index.ts b/example/index.ts index c507cc1..19e8d5d 100644 --- a/example/index.ts +++ b/example/index.ts @@ -5,7 +5,7 @@ const schema = t.Object({ test: t.Literal('hello') }) -const app = new Elysia() +const app = new Elysia({ prefix: '/api' }) .use( swagger({ provider: 'scalar', diff --git a/src/index.ts b/src/index.ts index 4fc45df..7dbecc2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,7 +45,7 @@ export const swagger = ({ ...documentation.info } - const relativePath = path.startsWith('/') ? path.slice(1) : path + const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath const app = new Elysia({ name: '@elysiajs/swagger' }) @@ -57,7 +57,7 @@ export const swagger = ({ theme, JSON.stringify( { - url: specPath, + url: relativePath, dom_id: '#swagger-ui', ...swaggerOptions }, @@ -71,8 +71,8 @@ export const swagger = ({ scalarVersion, { spec: { - ...scalarConfig.spec, - url: specPath + url: relativePath, + ...scalarConfig.spec }, ...scalarConfig, // so we can showcase the elysia theme From eb790a2810ab8ee0cbc97d545256995728751249 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sat, 28 Jun 2025 04:22:50 +0700 Subject: [PATCH 03/58] :wrench: fix: relative path endpoint --- CHANGELOG.md | 4 ++++ example/index.ts | 2 +- package.json | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9af20..c3f8e5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.3.1 +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/example/index.ts b/example/index.ts index 19e8d5d..681bf86 100644 --- a/example/index.ts +++ b/example/index.ts @@ -52,7 +52,7 @@ const app = new Elysia({ prefix: '/api' }) } ) .post('/json', ({ body }) => body, { - parse: 'formdata', + parse: ['json', 'formdata'], body: 'schema', response: 'schema' }) diff --git a/package.json b/package.json index de1a9ad..da4315d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/swagger", - "version": "1.3.0", + "version": "1.3.1", "description": "Plugin for Elysia to auto-generate Swagger page", "author": { "name": "saltyAom", @@ -73,4 +73,4 @@ "openapi-types": "^12.1.3", "pathe": "^1.1.2" } -} \ No newline at end of file +} From 5e58a600774c90b758dad2ae819483af692adfda Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 19:35:58 +0700 Subject: [PATCH 04/58] :tada: feat: refactor --- CHANGELOG.md | 13 +- bun.lock | 5 +- example/index.ts | 78 ++++--- example/index2.ts | 46 ---- example/index3.ts | 60 ----- package.json | 49 ++-- src/index.ts | 265 ++++++++++----------- src/openapi.ts | 335 +++++++++++++++++++++++++++ src/swagger/index.ts | 2 +- src/types.ts | 144 ++++++------ src/utils.ts | 430 ----------------------------------- test/index.test.ts | 122 +++++----- test/openapi.test.ts | 14 ++ test/validate-schema.test.ts | 146 ++++++------ tsconfig.json | 2 +- 15 files changed, 769 insertions(+), 942 deletions(-) delete mode 100644 example/index2.ts delete mode 100644 example/index3.ts create mode 100644 src/openapi.ts delete mode 100644 src/utils.ts create mode 100644 test/openapi.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f8e5a..b3e214c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ -# 1.3.1 +# 1.3.2 - 22 Aug 2025 +Feature: +- add `withHeader` for adding custom headers to response schema +- spread all possible path for optional params + +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 diff --git a/bun.lock b/bun.lock index 95ca0d9..231cb35 100644 --- a/bun.lock +++ b/bun.lock @@ -5,12 +5,11 @@ "name": "@elysiajs/swagger", "dependencies": { "@scalar/themes": "^0.9.52", - "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", - "pathe": "^1.1.2", }, "devDependencies": { "@apidevtools/swagger-parser": "^10.1.0", + "@scalar/types": "^0.0.12", "@types/bun": "1.1.14", "elysia": "1.3.0-exp.71", "eslint": "9.6.0", @@ -373,8 +372,6 @@ "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=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], diff --git a/example/index.ts b/example/index.ts index 681bf86..7868119 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,18 +1,28 @@ import { Elysia, t } from 'elysia' -import { swagger } from '../src/index' +import { openapi, withHeaders } from '../src/index' const schema = t.Object({ test: t.Literal('hello') }) -const app = new Elysia({ prefix: '/api' }) +const schema2 = t.Object({ + test: t.Literal('world') +}) + +const user = t.Object({ + name: t.String({ + example: 'saltyaom' + }) +}) + +const app = new Elysia() .use( - swagger({ + openapi({ provider: 'scalar', documentation: { info: { title: 'Elysia Scalar', - version: '0.8.1' + version: '1.3.1a' }, tags: [ { @@ -21,39 +31,55 @@ const app = new Elysia({ prefix: '/api' }) } ], components: { - schemas: { - User: { - description: 'string' - } - }, securitySchemes: { - JwtAuth: { + bearer: { type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'Enter JWT Bearer token **_only_**' + scheme: 'bearer' + }, + cookie: { + type: 'apiKey', + in: 'cookie', + name: 'session_id' } } } - }, - swaggerOptions: { - persistAuthorization: true } }) ) - .model({ schema }) + .model({ schema, schema2, user }) .get( '/', - () => { - return { test: 'hello' as const } - }, + { test: 'hello' as const }, + { + response: { + 200: t.Object({ + test: t.Literal('hello') + }), + 204: withHeaders( + t.Void({ + title: 'Thing', + description: 'Void response' + }), + { + 'X-Custom-Header': t.String() + } + ) + } + } + ) + .post( + '/json', + ({ body }) => ({ + test: 'world' + }), { - response: 'schema' + parse: ['json', 'formdata'], + body: 'user', + response: { + 200: 'schema', + 400: 'schema2' + } } ) - .post('/json', ({ body }) => body, { - parse: ['json', 'formdata'], - body: 'schema', - response: 'schema' - }) + .get('/id/:id?/name/:name?', () => {}) .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/package.json b/package.json index da4315d..1a52443 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@elysiajs/swagger", - "version": "1.3.1", - "description": "Plugin for Elysia to auto-generate Swagger page", + "name": "@elysiajs/openapi", + "version": "1.3.2", + "description": "Plugin for Elysia to auto-generate OpenAPI documentation", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", @@ -17,15 +17,10 @@ "import": "./dist/index.mjs", "require": "./dist/cjs/index.js" }, - "./types": { - "types": "./dist/types.d.ts", - "import": "./dist/types.mjs", - "require": "./dist/cjs/types.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 +31,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", @@ -62,6 +74,7 @@ "devDependencies": { "@apidevtools/swagger-parser": "^10.1.0", "@types/bun": "1.1.14", + "@scalar/types": "^0.0.12", "elysia": "1.3.0-exp.71", "eslint": "9.6.0", "tsup": "^8.1.0", @@ -69,8 +82,6 @@ }, "dependencies": { "@scalar/themes": "^0.9.52", - "@scalar/types": "^0.0.12", - "openapi-types": "^12.1.3", - "pathe": "^1.1.2" + "openapi-types": "^12.1.3" } } diff --git a/src/index.ts b/src/index.ts index 7dbecc2..546fc7c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,42 +1,43 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ import { Elysia, type InternalRoute } 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 { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' /** * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate Swagger page. * * @see https://github.com/elysiajs/elysia-swagger */ -export const swagger = ({ +export const openapi = < + const Path extends string = '/openapi', + const Provider extends OpenAPIProvider = 'scalar' +>({ provider = 'scalar', - scalarVersion = 'latest', - scalarCDN = '', - scalarConfig = {}, - documentation = {}, - version = '5.9.0', - excludeStaticFile = true, - path = '/swagger' as Path, + path = '/openapi' 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 +}: ElysiaOpenAPIConfig = {}) => { + const { + version: swaggerVersion = '5.9.0', + theme: swaggerTheme = `https://unpkg.com/swagger-ui-dist@${swaggerVersion}/swagger-ui.css`, + autoDarkMode = true, + ...swaggerOptions + } = swagger ?? {} + + const { + version: scalarVersion = 'latest', + cdn: scalarCDN = '', + ...scalarConfig + } = scalar ?? {} const info = { title: 'Elysia Documentation', @@ -47,145 +48,111 @@ export const swagger = ({ const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath - const app = new Elysia({ name: '@elysiajs/swagger' }) + let totalRoutes = 0 + let cachedSchema: OpenAPIV3.Document | undefined - const page = new Response( - provider === 'swagger-ui' - ? SwaggerUIRender( - info, - version, - theme, - JSON.stringify( - { - url: relativePath, - dom_id: '#swagger-ui', - ...swaggerOptions - }, - (_, value) => - typeof value === 'function' ? undefined : value - ), - autoDarkMode - ) - : ScalarRender( - info, - scalarVersion, + const app = new Elysia({ name: '@elysiajs/swagger' }) + .use((app) => { + if (provider === null) return app + + return app.get( + path, + new Response( + provider === 'swagger-ui' + ? SwaggerUIRender( + info, + swaggerVersion, + swaggerTheme, + JSON.stringify( + { + url: relativePath, + dom_id: '#swagger-ui', + ...swaggerOptions + }, + (_, value) => + typeof value === 'function' + ? undefined + : value + ), + autoDarkMode + ) + : ScalarRender( + info, + scalarVersion, + { + spec: { + url: relativePath, + ...scalarConfig.spec + }, + ...scalarConfig, + // so we can showcase the elysia theme + // @ts-expect-error + _integration: 'elysiajs' + } satisfies ReferenceConfiguration, + scalarCDN + ), { - spec: { - url: relativePath, - ...scalarConfig.spec - }, - ...scalarConfig, - // so we can showcase the elysia theme - // @ts-expect-error - _integration: 'elysiajs' - } satisfies ReferenceConfiguration, - scalarCDN + headers: { + 'content-type': 'text/html; charset=utf8' + } + } ), - { - headers: { - 'content-type': 'text/html; charset=utf8' - } - } - ) - - 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 - }) - }) - else - registerSchemaPath({ - schema, - hook: route.hooks, - method: route.method, - path: route.path, - // @ts-ignore - models: app.getGlobalDefinitions?.().type, - contentType: route.hooks.type - }) - }) - } - - return { - openapi: '3.0.3', - ...{ + { + detail: { + hide: true + } + } + ) + }) + .get( + specPath, + function openAPISchema() { + if (totalRoutes === app.routes.length) return cachedSchema + + totalRoutes = app.routes.length + + const { + paths, + components: { schemas } + } = toOpenAPISchema(app, exclude) + + return (cachedSchema = { + openapi: '3.0.3', ...documentation, - tags: documentation.tags?.filter( - (tag) => !excludeTags?.includes(tag?.name) - ), + tags: !exclude?.tags + ? documentation.tags + : documentation.tags?.filter( + (tag) => !exclude.tags?.includes(tag.name) + ), 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 + } } - }, - paths: { - ...filterPaths(schema, { - excludeStaticFile, - exclude: Array.isArray(exclude) ? exclude : [exclude] - }), - ...documentation.paths - }, - components: { - ...documentation.components, - schemas: { - // @ts-ignore - ...app.getGlobalDefinitions?.().type, - ...documentation.components?.schemas - } + } satisfies OpenAPIV3.Document) + }, + { + detail: { + hide: true } - } satisfies OpenAPIV3.Document - }, - { - detail: { - hide: true } - } - ) + ) return app } -export type { ElysiaSwaggerConfig } -export default swagger +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..70fa8c3 --- /dev/null +++ b/src/openapi.ts @@ -0,0 +1,335 @@ +import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia' +import type { HookContainer } from 'elysia/types' + +import type { OpenAPIV3 } from 'openapi-types' +import type { TProperties } from '@sinclair/typebox' + +import type { ElysiaOpenAPIConfig } from './types' + +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.charCodeAt(0) === 123 + ? 'By' + capitalize(path.slice(1, -1)) + : 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.replaceAll('?', '') + const paths = [originalPath] + + for (let i = 0; i < optionalParams.length; i++) { + const newPath = path.replace(optionalParams[i], '') + + paths.push(...getPossiblePath(newPath)) + } + + return paths +} + +/** + * 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'] +) { + const { + methods: excludeMethods = ['OPTIONS'], + staticFile: excludeStaticFile = true, + tags: excludeTags + } = exclude ?? {} + + const excludePaths = Array.isArray(exclude?.paths) + ? exclude.paths + : typeof exclude?.paths !== 'undefined' + ? [exclude.paths] + : [] + + const paths: OpenAPIV3.PathsObject = {} + + // @ts-ignore private property + const routes = app.getGlobalRoutes() + + for (const route of routes) { + if (route.hooks?.detail?.hide) continue + + const method = route.method.toLowerCase() + + if (excludePaths.includes(route.path)) continue + if (excludeMethods.includes(method)) continue + + const hooks: InputSchema & { + detail: Partial + } = route.hooks ?? {} + + if ( + excludeTags && + hooks.detail.tags?.some((tag) => excludeTags?.includes(tag)) + ) + continue + + // Start building the operation object + const operation: Partial = { + ...hooks.detail, + operationId: toOperationId(route.method, route.path) + } + + const parameters: Array<{ + name: string + in: 'path' | 'query' | 'header' | 'cookie' + required?: boolean + schema: any + }> = [] + + // Handle path parameters + if (hooks.params) { + // const pathParamNames = + // route.path.match(/:([^/]+)/g)?.map((param) => param.slice(1)) || + // [] + + if (typeof hooks.params === 'string') + hooks.params = toRef(hooks.params) + + if (hooks.params.type === 'object' && hooks.params.properties) { + for (const [paramName, paramSchema] of Object.entries( + hooks.params.properties + )) + parameters.push({ + name: paramName, + in: 'path', + required: true, // Path parameters are always required + schema: paramSchema + }) + } + } + + // Handle query parameters + if (hooks.query) { + if (typeof hooks.query === 'string') + hooks.query = toRef(hooks.query) + + if (hooks.query.type === 'object' && hooks.query.properties) { + const required = hooks.query.required || [] + for (const [queryName, querySchema] of Object.entries( + hooks.query.properties + )) + parameters.push({ + name: queryName, + in: 'query', + required: required.includes(queryName), + schema: querySchema + }) + } + } + + // Handle header parameters + if (hooks.headers) { + if (typeof hooks.headers === 'string') + hooks.headers = toRef(hooks.headers) + + if (hooks.headers.type === 'object' && hooks.headers.properties) { + const required = hooks.headers.required || [] + for (const [headerName, headerSchema] of Object.entries( + hooks.headers.properties + )) + parameters.push({ + name: headerName, + in: 'header', + required: required.includes(headerName), + schema: headerSchema + }) + } + } + + // Handle cookie parameters + if (hooks.cookie) { + if (typeof hooks.cookie === 'string') + hooks.cookie = toRef(hooks.cookie) + + if (hooks.cookie.type === 'object' && hooks.cookie.properties) { + const required = hooks.cookie.required || [] + for (const [cookieName, cookieSchema] of Object.entries( + hooks.cookie.properties + )) + parameters.push({ + name: cookieName, + in: 'cookie', + required: required.includes(cookieName), + schema: cookieSchema + }) + } + } + + // Add parameters if any exist + if (parameters.length > 0) operation.parameters = parameters + + // Handle request body + if (hooks.body) { + if (typeof hooks.body === 'string') hooks.body = toRef(hooks.body) + + // @ts-ignore + if (hooks.parse) { + const content: Record = {} + + // @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: hooks.body } + continue + + case 'urlencoded': + case 'application/x-www-form-urlencoded': + content['application/x-www-form-urlencoded'] = { + schema: hooks.body + } + continue + + case 'json': + case 'application/json': + content['application/json'] = { schema: hooks.body } + continue + + case 'formdata': + case 'multipart/form-data': + content['multipart/form-data'] = { + schema: hooks.body + } + continue + } + } + + operation.requestBody = { content, required: true } + } else { + operation.requestBody = { + content: { + 'application/json': { + schema: hooks.body + }, + 'application/x-www-form-urlencoded': { + schema: hooks.body + }, + 'multipart/form-data': { + schema: hooks.body + } + }, + required: true + } + } + } + + // Handle responses + if (hooks.response) { + operation.responses = {} + + if ( + typeof hooks.response === 'object' && + !(hooks.response as TSchema).type && + !(hooks.response as TSchema).$ref + ) { + for (let [status, schema] of Object.entries(hooks.response)) { + if (typeof schema === 'string') schema = toRef(schema) + + const { type, examples, ...options } = schema + + operation.responses[status] = { + description: `Response for status ${status}`, + ...options, + content: + type === 'void' || + type === 'null' || + type === 'undefined' + ? schema + : { + 'application/json': { + schema + } + } + } + } + } else { + if (typeof hooks.response === 'string') + hooks.response = toRef(hooks.response) + + // It's a single schema, default to 200 + operation.responses['200'] = { + description: 'Successful response', + content: { + 'application/json': { + schema: hooks.response + } + } + } + } + } + + for (let path of getPossiblePath(route.path)) { + path = path.replace(/:([^/]+)/g, '{$1}') + + if (!paths[path]) paths[path] = {} + + const current = paths[path] as any + + if (method !== 'all') { + current[method] = operation + continue + } + + // Handle 'ALL' method by assigning operation to all standard methods + current.get = operation + current.post = operation + current.put = operation + current.delete = operation + current.patch = operation + current.head = operation + current.options = operation + current.trace = operation + } + } + + // @ts-ignore private property + const schemas = app.getGlobalDefinitions?.().type + + return { + components: { + schemas + }, + paths + } satisfies Pick +} + +export const withHeaders = (schema: TSchema, headers: TProperties) => + Object.assign(schema, { + headers: headers + }) diff --git a/src/swagger/index.ts b/src/swagger/index.ts index 60a0144..c7e2e7e 100644 --- a/src/swagger/index.ts +++ b/src/swagger/index.ts @@ -28,7 +28,7 @@ function transformDateProperties(schema: SchemaObject): SchemaObject { Object.entries(newSchema).forEach(([key, value]) => { if (isSchemaObject(value)) { if (isDateTimeProperty(key, value)) { - const dateTimeFormat = value.anyOf?.find((item): item is OpenAPIV3.SchemaObject => + const dateTimeFormat = value.anyOf?.find((item): item is OpenAPIV3.SchemaObject => isSchemaObject(item) && item.format === 'date-time' ); if (dateTimeFormat) { diff --git a/src/types.ts b/src/types.ts index ce16c26..8a674f1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,12 @@ import type { OpenAPIV3 } from 'openapi-types' import type { ReferenceConfiguration } from '@scalar/types' import type { SwaggerUIOptions } from './swagger/types' -export interface ElysiaSwaggerConfig { +export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null + +export interface ElysiaOpenAPIConfig< + Path extends string = '/swagger', + Provider extends OpenAPIProvider = 'scalar' +> { /** * Customize Swagger config, refers to Swagger 2.0 config * @@ -20,51 +25,38 @@ export interface ElysiaSwaggerConfig { * @see https://github.com/scalar/scalar * @see https://github.com/swagger-api/swagger-ui */ - provider?: 'scalar' | 'swagger-ui' - /** - * Version to use for Scalar cdn bundle - * - * @default 'latest' - * @see https://github.com/scalar/scalar - */ - scalarVersion?: 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 - */ - scalarCDN?: string + provider?: OpenAPIProvider /** * Scalar configuration to customize scalar *' * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md */ - scalarConfig?: ReferenceConfiguration - /** - * Version to use for swagger cdn bundle - * - * @see unpkg.com/swagger-ui-dist - * - * @default 4.18.2 - */ - version?: string - /** - * Determine if Swagger should exclude static files. - * - * @default true - */ - excludeStaticFile?: boolean + scalar?: ReferenceConfiguration & { + /** + * 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 Documentation * - * @default '/swagger' + * @default '/openapi' */ path?: Path /** @@ -73,18 +65,12 @@ export interface ElysiaSwaggerConfig { * @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 +86,50 @@ 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 + } + /** + * Version to use for swagger cdn bundle + * + * @see unpkg.com/swagger-ui-dist + * + * @default 4.18.2 + */ + version?: string + /** + * Using poor man dark mode 😭 + */ + autoDarkMode?: boolean + } - /** - * Exclude methods from Swagger - */ - excludeMethods?: string[] - - /** - * Exclude tags from Swagger or Scalar - */ - excludeTags?: string[] + exclude?: { + /** + * Paths to exclude from OpenAPI endpoint + * + * @default [] + */ + paths?: string | RegExp | (string | RegExp)[] + /** + * Exclude methods from OpenAPI + */ + methods?: string[] + /** + * Exclude tags from OpenAPI + */ + tags?: string[] + /** + * Determine if OpenAPI should exclude static files. + * + * @default true + */ + staticFile?: boolean + } } 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/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/openapi.test.ts b/test/openapi.test.ts new file mode 100644 index 0000000..4af691c --- /dev/null +++ b/test/openapi.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect } from 'bun:test' +import { getPossiblePath } from '../src/openapi' + +describe('OpenAPI utilities', () => { + it('getPossiblePath', () => { + 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/validate-schema.test.ts b/test/validate-schema.test.ts index ee9558e..05991b6 100644 --- a/test/validate-schema.test.ts +++ b/test/validate-schema.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 { it } from 'bun:test' import { fail } from 'assert' @@ -8,79 +8,77 @@ 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 - }), + 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' } + ) + } + ) - }), - 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 - await app.modules - - const res = await app.handle(req('/swagger/json')).then((x) => x.json()) - await SwaggerParser.validate(res).catch((err) => fail(err)) + const res = await app.handle(req('/openapi/json')).then((x) => x.json()) + await SwaggerParser.validate(res).catch((err) => fail(err)) }) diff --git a/tsconfig.json b/tsconfig.json index e7cb18d..ebd1539 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,7 @@ /* 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. */ + "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. */ From c3c91bb8a503eccf340e7a106ba8368051feb010 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 19:37:39 +0700 Subject: [PATCH 05/58] :tada: feat: refactor --- tsconfig.dts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.dts.json b/tsconfig.dts.json index cd54e87..3fc33fe 100644 --- a/tsconfig.dts.json +++ b/tsconfig.dts.json @@ -28,7 +28,7 @@ /* 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. */ + "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. */ From 598fea4e096072fc904c36f6fd5db670445bf457 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 19:40:02 +0700 Subject: [PATCH 06/58] :tada: feat: operationId per possible path --- src/openapi.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/openapi.ts b/src/openapi.ts index 70fa8c3..73350bd 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -96,8 +96,7 @@ export function toOpenAPISchema( // Start building the operation object const operation: Partial = { - ...hooks.detail, - operationId: toOperationId(route.method, route.path) + ...hooks.detail } const parameters: Array<{ @@ -295,6 +294,8 @@ export function toOpenAPISchema( } for (let path of getPossiblePath(route.path)) { + const operationId = toOperationId(route.method, route.path) + path = path.replace(/:([^/]+)/g, '{$1}') if (!paths[path]) paths[path] = {} @@ -302,19 +303,19 @@ export function toOpenAPISchema( const current = paths[path] as any if (method !== 'all') { - current[method] = operation + current[method] = { + ...operation, + operationId + } continue } // Handle 'ALL' method by assigning operation to all standard methods - current.get = operation - current.post = operation - current.put = operation - current.delete = operation - current.patch = operation - current.head = operation - current.options = operation - current.trace = operation + for(const method of ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']) + current[method] = { + ...operation, + operationId + } } } From d028a9d4c503e7932de8427132afb91cbf9b2d4c Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 20:28:15 +0700 Subject: [PATCH 07/58] :tada: feat: operationId per possible path --- CHANGELOG.md | 2 + build.ts | 42 ++++----- bun.lock | 89 +++++++++++------- example/index.ts | 2 +- package.json | 13 ++- src/index.ts | 72 +++++--------- src/openapi.ts | 33 ++++--- src/scalar/index.ts | 142 +++++++++++++++++++++++++--- src/swagger/index.ts | 187 +++++++++++++++++++++---------------- src/types.ts | 20 +++- test/node/cjs/index.js | 18 ++-- test/node/cjs/package.json | 4 +- test/node/esm/index.js | 18 ++-- test/node/esm/package.json | 4 +- 14 files changed, 397 insertions(+), 249 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e214c..3bfb02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ 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 Breaking change: - rename `@elysiajs/swagger` to `@elysiajs/openapi` diff --git a/build.ts b/build.ts index e1305dc..5628259 100644 --- a/build.ts +++ b/build.ts @@ -4,30 +4,30 @@ import { build, type Options } from 'tsup' await $`rm -rf dist` const tsupConfig: Options = { - entry: ['src/**/*.ts'], - splitting: false, - sourcemap: false, - clean: true, - bundle: true + 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 - }) + // ? 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` diff --git a/bun.lock b/bun.lock index 231cb35..ea4ebff 100644 --- a/bun.lock +++ b/bun.lock @@ -4,17 +4,16 @@ "": { "name": "@elysiajs/swagger", "dependencies": { - "@scalar/themes": "^0.9.52", "openapi-types": "^12.1.3", }, "devDependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "@scalar/types": "^0.0.12", - "@types/bun": "1.1.14", - "elysia": "1.3.0-exp.71", + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@types/bun": "1.2.20", + "elysia": "1.3.21", "eslint": "9.6.0", - "tsup": "^8.1.0", - "typescript": "^5.5.3", + "tsup": "^8.5.0", + "typescript": "^5.9.2", }, "peerDependencies": { "elysia": ">= 1.3.0", @@ -22,13 +21,15 @@ }, }, "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=="], @@ -104,12 +105,10 @@ "@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=="], - "@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=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -158,15 +157,17 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA=="], - "@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="], - - "@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.13", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-rO6KGMJqOsBnN/2R4fErMFLpRSPVJElni+HABDpf+ZlLJp2lvxuPn0IXLumK5ytfplUH9iqKgSXjndnZfxSYLQ=="], "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], - "@types/bun": ["@types/bun@1.1.14", "", { "dependencies": { "bun-types": "1.1.37" } }, "sha512-opVYiFGtO2af0dnWBdZWlioLBoxSdDO5qokaazLhq8XQtGZbY4pY3/JxY8Zdf/hEwGubbp7ErZXoN1+h2yesxA=="], + "@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=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], @@ -174,9 +175,7 @@ "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], - - "@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="], + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], @@ -198,7 +197,7 @@ "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "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=="], @@ -220,19 +219,23 @@ "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=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], "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=="], + "elysia": ["elysia@1.3.21", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.6", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-LLfDSoVA5fBoqKQfMJyzmHLkya8zMbEYwd7DS7v2iQB706mgzWg0gufXl58cFALErcvSayplrkDvjkmlYTkIZQ=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -256,7 +259,7 @@ "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.1.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-EXGDixoDotCGrXCce63zmGHDA+3Id6PPkIwshBHuB10dwVc4YV4gfaYLuysHOxyURmwyt4UL186ann0oYa2CFQ=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], @@ -272,10 +275,16 @@ "fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], + "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=="], @@ -292,7 +301,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=="], @@ -340,10 +349,14 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], + "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=="], @@ -372,12 +385,16 @@ "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@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=="], "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=="], @@ -416,6 +433,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=="], @@ -430,19 +449,23 @@ "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], + "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=="], "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], "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=="], + + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -462,9 +485,7 @@ "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@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -472,7 +493,9 @@ "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - "@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=="], + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], "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=="], @@ -480,6 +503,8 @@ "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "mlly/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -494,8 +519,6 @@ "@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=="], - "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=="], diff --git a/example/index.ts b/example/index.ts index 7868119..5707659 100644 --- a/example/index.ts +++ b/example/index.ts @@ -61,7 +61,7 @@ const app = new Elysia() description: 'Void response' }), { - 'X-Custom-Header': t.String() + 'X-Custom-Header': t.Literal('Elysia') } ) } diff --git a/package.json b/package.json index 1a52443..620cb97 100644 --- a/package.json +++ b/package.json @@ -72,16 +72,15 @@ "elysia": ">= 1.3.0" }, "devDependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "@types/bun": "1.1.14", - "@scalar/types": "^0.0.12", - "elysia": "1.3.0-exp.71", + "@apidevtools/swagger-parser": "^12.0.0", + "@types/bun": "1.2.20", + "@scalar/types": "^0.2.13", + "elysia": "1.3.21", "eslint": "9.6.0", - "tsup": "^8.1.0", - "typescript": "^5.5.3" + "tsup": "^8.5.0", + "typescript": "^5.9.2" }, "dependencies": { - "@scalar/themes": "^0.9.52", "openapi-types": "^12.1.3" } } diff --git a/src/index.ts b/src/index.ts index 546fc7c..35be3fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,38 +6,29 @@ import { ScalarRender } from './scalar' import { toOpenAPISchema } from './openapi' import type { OpenAPIV3 } from 'openapi-types' -import type { ReferenceConfiguration } from '@scalar/types' +import type { ApiReferenceConfiguration } from '@scalar/types' import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' /** - * 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 openapi = < + const Enabled extends boolean = true, const Path extends string = '/openapi', const Provider extends OpenAPIProvider = 'scalar' >({ - provider = 'scalar', + enabled = true as Enabled, path = '/openapi' as Path, + provider = 'scalar' as Provider, specPath = `${path}/json`, documentation = {}, exclude, swagger, scalar -}: ElysiaOpenAPIConfig = {}) => { - const { - version: swaggerVersion = '5.9.0', - theme: swaggerTheme = `https://unpkg.com/swagger-ui-dist@${swaggerVersion}/swagger-ui.css`, - autoDarkMode = true, - ...swaggerOptions - } = swagger ?? {} - - const { - version: scalarVersion = 'latest', - cdn: scalarCDN = '', - ...scalarConfig - } = scalar ?? {} +}: ElysiaOpenAPIConfig = {}) => { + if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) const info = { title: 'Elysia Documentation', @@ -51,7 +42,7 @@ export const openapi = < let totalRoutes = 0 let cachedSchema: OpenAPIV3.Document | undefined - const app = new Elysia({ name: '@elysiajs/swagger' }) + const app = new Elysia({ name: '@elysiajs/openapi' }) .use((app) => { if (provider === null) return app @@ -59,38 +50,20 @@ export const openapi = < path, new Response( provider === 'swagger-ui' - ? SwaggerUIRender( - info, - swaggerVersion, - swaggerTheme, - JSON.stringify( - { - url: relativePath, - dom_id: '#swagger-ui', - ...swaggerOptions - }, - (_, value) => - typeof value === 'function' - ? undefined - : value - ), - autoDarkMode - ) - : ScalarRender( - info, - scalarVersion, - { - spec: { - url: relativePath, - ...scalarConfig.spec - }, - ...scalarConfig, - // so we can showcase the elysia theme - // @ts-expect-error - _integration: 'elysiajs' - } satisfies ReferenceConfiguration, - scalarCDN - ), + ? SwaggerUIRender(info, { + url: relativePath, + dom_id: '#swagger-ui', + version: 'latest', + autoDarkMode: true, + ...swagger + }) + : 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' + }), { headers: { 'content-type': 'text/html; charset=utf8' @@ -155,4 +128,5 @@ export const openapi = < export { toOpenAPISchema, withHeaders } from './openapi' export type { ElysiaOpenAPIConfig } + export default openapi diff --git a/src/openapi.ts b/src/openapi.ts index 73350bd..144020d 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -17,10 +17,9 @@ const toOperationId = (method: string, paths: string) => { if (!paths || paths === '/') return operationId + 'Index' for (const path of paths.split('/')) - operationId += - path.charCodeAt(0) === 123 - ? 'By' + capitalize(path.slice(1, -1)) - : capitalize(path) + operationId += path.includes(':') + ? 'By' + capitalize(path.replace(':', '')) + : capitalize(path) operationId = operationId.replace(/\?/g, 'Optional') @@ -71,7 +70,7 @@ export function toOpenAPISchema( ? [exclude.paths] : [] - const paths: OpenAPIV3.PathsObject = {} + const paths: OpenAPIV3.PathsObject = Object.create(null) // @ts-ignore private property const routes = app.getGlobalRoutes() @@ -260,7 +259,8 @@ export function toOpenAPISchema( for (let [status, schema] of Object.entries(hooks.response)) { if (typeof schema === 'string') schema = toRef(schema) - const { type, examples, ...options } = schema + // Must exclude $ref from root options + const { type, examples, $ref, ...options } = schema operation.responses[status] = { description: `Response for status ${status}`, @@ -294,7 +294,7 @@ export function toOpenAPISchema( } for (let path of getPossiblePath(route.path)) { - const operationId = toOperationId(route.method, route.path) + const operationId = toOperationId(route.method, path) path = path.replace(/:([^/]+)/g, '{$1}') @@ -311,11 +311,20 @@ export function toOpenAPISchema( } // 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 - } + for (const method of [ + 'get', + 'post', + 'put', + 'delete', + 'patch', + 'head', + 'options', + 'trace' + ]) + current[method] = { + ...operation, + operationId + } } } diff --git a/src/scalar/index.ts b/src/scalar/index.ts index 883ea66..962f3fc 100644 --- a/src/scalar/index.ts +++ b/src/scalar/index.ts @@ -1,12 +1,131 @@ -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 ) => ` @@ -29,20 +148,15 @@ export const ScalarRender = ( } - + ` diff --git a/src/swagger/index.ts b/src/swagger/index.ts index c7e2e7e..01d3e3a 100644 --- a/src/swagger/index.ts +++ b/src/swagger/index.ts @@ -1,79 +1,101 @@ -import { OpenAPIV3 } from 'openapi-types'; +import { OpenAPIV3 } from 'openapi-types' +import { ElysiaOpenAPIConfig } from '../types' +import { SwaggerUIOptions } from './types' type DateTimeSchema = { - type: 'string'; - format: 'date-time'; - default?: string; -}; + type: 'string' + format: 'date-time' + default?: string +} -type SchemaObject = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; +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 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 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; - } +export function transformDateProperties(schema: SchemaObject): SchemaObject { + if ( + !isSchemaObject(schema) || + typeof schema !== 'object' || + schema === null + ) + return schema + + const newSchema: OpenAPIV3.SchemaObject = { ...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' + ) - 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); - } - } - }); + 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; + return newSchema } export const SwaggerUIRender = ( - info: OpenAPIV3.InfoObject, - version: string, - theme: - | string - | { - light: string - dark: string - }, - stringifiedSwaggerOptions: string, - autoDarkMode?: boolean + info: OpenAPIV3.InfoObject, + config: NonNullable & SwaggerUIOptions ): string => { - const swaggerOptions: OpenAPIV3.Document = JSON.parse(stringifiedSwaggerOptions); + 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 - if (swaggerOptions.components && swaggerOptions.components.schemas) { - swaggerOptions.components.schemas = Object.fromEntries( - Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [ - key, - transformDateProperties(schema) - ]) - ); - } + // 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) - const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions); + if (options.components && options.components.schemas) + options.components.schemas = Object.fromEntries( + Object.entries(options.components.schemas).map(([key, schema]) => [ + key, + transformDateProperties(schema) + ]) + ) - return ` + return ` @@ -88,40 +110,39 @@ export const SwaggerUIRender = ( content="${info.description}" /> ${ - autoDarkMode && typeof theme === 'string' - ? ` - ` - : '' + .swagger-ui .microlight { + filter: invert(100%) hue-rotate(180deg); } +} +` + : '' + } ${ - typeof theme === 'string' - ? `` - : ` + typeof theme === 'string' + ? `` + : ` ` - } + }
- + -`; -}; +` +} diff --git a/src/types.ts b/src/types.ts index 8a674f1..2558e87 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,13 +1,19 @@ 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 type OpenAPIProvider = 'scalar' | 'swagger-ui' | null export interface ElysiaOpenAPIConfig< + Enabled extends boolean = true, Path extends string = '/swagger', Provider extends OpenAPIProvider = 'scalar' > { + /** + * @default true + */ + enabled?: Enabled + /** * Customize Swagger config, refers to Swagger 2.0 config * @@ -25,13 +31,13 @@ export interface ElysiaOpenAPIConfig< * @see https://github.com/scalar/scalar * @see https://github.com/swagger-api/swagger-ui */ - provider?: OpenAPIProvider + provider?: Provider /** * Scalar configuration to customize scalar *' * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md */ - scalar?: ReferenceConfiguration & { + scalar?: ApiReferenceConfiguration & { /** * Version to use for Scalar cdn bundle * @@ -96,6 +102,7 @@ export interface ElysiaOpenAPIConfig< light: string dark: string } + /** * Version to use for swagger cdn bundle * @@ -104,10 +111,17 @@ export interface ElysiaOpenAPIConfig< * @default 4.18.2 */ version?: 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 } exclude?: { diff --git a/test/node/cjs/index.js b/test/node/cjs/index.js index 9d7dc4b..e963635 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!'); -} - -const { swagger } = require('@elysiajs/swagger'); - -if (typeof swagger !== 'function') { - throw new Error('❌ CommonJS Node.js failed'); -} - -console.log('✅ CommonJS Node.js works!'); +if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') + +const { openapi } = require('@elysiajs/openapi') + +if (typeof openapi !== 'function') throw new Error('❌ CommonJS Node.js failed') + +console.log('✅ CommonJS Node.js works!') diff --git a/test/node/cjs/package.json b/test/node/cjs/package.json index 53c8a2a..e05f237 100644 --- a/test/node/cjs/package.json +++ b/test/node/cjs/package.json @@ -1,6 +1,6 @@ { "type": "commonjs", "dependencies": { - "@elysiajs/swagger": "../../.." + "@elysiajs/openapi": "../../.." } -} \ No newline at end of file +} diff --git a/test/node/esm/index.js b/test/node/esm/index.js index ec25d72..7212dd3 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!'); -} - -import { swagger } from '@elysiajs/swagger'; - -if (typeof swagger !== 'function') { - throw new Error('❌ ESM Node.js failed'); -} - -console.log('✅ ESM Node.js works!'); +if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') + +import { openapi } from '@elysiajs/openapi' + +if (typeof openapi !== 'function') throw new Error('❌ ESM Node.js failed') + +console.log('✅ ESM Node.js works!') diff --git a/test/node/esm/package.json b/test/node/esm/package.json index 587ca48..56bc298 100644 --- a/test/node/esm/package.json +++ b/test/node/esm/package.json @@ -1,6 +1,6 @@ { "type": "module", "dependencies": { - "@elysiajs/swagger": "../../.." + "@elysiajs/openapi": "../../.." } -} \ No newline at end of file +} From 2f1df4f0266d8643bf476d1476fbc6a658e92a90 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 22:58:26 +0700 Subject: [PATCH 08/58] :tada: feat: generator --- bun.lock | 5 ++ example/gen.ts | 22 ++++++ example/index.ts | 4 +- package.json | 1 + src/gen/index.ts | 192 +++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 5 +- src/openapi.ts | 46 ++++++++++-- src/types.ts | 21 ++++++ 8 files changed, 285 insertions(+), 11 deletions(-) create mode 100644 example/gen.ts create mode 100644 src/gen/index.ts diff --git a/bun.lock b/bun.lock index ea4ebff..fd67186 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "": { "name": "@elysiajs/swagger", "dependencies": { + "@sinclair/typemap": "^0.10.1", "openapi-types": "^12.1.3", }, "devDependencies": { @@ -163,6 +164,8 @@ "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], + "@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=="], + "@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=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -471,6 +474,8 @@ "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=="], diff --git a/example/gen.ts b/example/gen.ts new file mode 100644 index 0000000..3b72f36 --- /dev/null +++ b/example/gen.ts @@ -0,0 +1,22 @@ +import { Elysia, t } from 'elysia' +import { openapi } from '../src/index' +import { fromTypes } from '../src/gen' + +export const app = new Elysia() + .use( + openapi({ + references: fromTypes('example/gen.ts') + }) + ) + .get('/', { test: 'hello' as const }) + .post( + '/json', + ({ body, status }) => (Math.random() > 0.5 ? status(418) : body), + { + body: t.Object({ + hello: t.String() + }) + } + ) + .get('/id/:id/name/:name', ({ params }) => params) + .listen(3000) diff --git a/example/index.ts b/example/index.ts index 5707659..7e8439a 100644 --- a/example/index.ts +++ b/example/index.ts @@ -15,7 +15,7 @@ const user = t.Object({ }) }) -const app = new Elysia() +export const app = new Elysia() .use( openapi({ provider: 'scalar', @@ -81,5 +81,5 @@ const app = new Elysia() } } ) - .get('/id/:id?/name/:name?', () => {}) + .get('/id/:id/name/:name', () => {}) .listen(3000) diff --git a/package.json b/package.json index 620cb97..10304fa 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "typescript": "^5.9.2" }, "dependencies": { + "@sinclair/typemap": "^0.10.1", "openapi-types": "^12.1.3" } } diff --git a/src/gen/index.ts b/src/gen/index.ts new file mode 100644 index 0000000..52a2821 --- /dev/null +++ b/src/gen/index.ts @@ -0,0 +1,192 @@ +import type { InternalRoute } from 'elysia' +import { + readFileSync, + mkdirSync, + writeFileSync, + rmSync, + existsSync, + cpSync +} from 'fs' +import { TypeBox } from '@sinclair/typemap' + +import { tmpdir } from 'os' +import { join } from 'path' +import { spawnSync } from 'child_process' +import { AdditionalReference, AdditionalReferences } from '../types' + +const matchRoute = /: Elysia<(.*)>/gs +const matchStatus = /(\d{3}):/gs +const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":') + +const exec = (command: string, cwd: string) => + spawnSync(command, { + shell: true, + cwd, + stdio: 'inherit' + }) + +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 +} + +/** + * 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 + */ + targetFilePath: string, + { + tsconfigPath = 'tsconfig.json', + instanceName, + projectRoot = process.cwd() + }: OpenAPIGeneratorOptions = {} + ) => + () => { + if (!targetFilePath.endsWith('.ts') && !targetFilePath.endsWith('.tsx')) + throw new Error('Only .ts files are supported') + + const tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') + + if (existsSync(tmpRoot)) + rmSync(tmpRoot, { recursive: true, force: true }) + mkdirSync(tmpRoot, { recursive: true }) + + const extendsRef = existsSync(join(projectRoot, 'tsconfig.json')) + ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` + : '' + + if (!join(projectRoot, targetFilePath)) + throw new Error('Target file does not exist') + + writeFileSync( + join(tmpRoot, tsconfigPath), + `{ + ${extendsRef} + "compilerOptions": { + "lib": ["ESNext"], + "module": "ESNext", + "noEmit": false, + "moduleResolution": "bundler", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "emitDeclarationOnly": true, + "outDir": "./dist" + }, + "include": ["${join(projectRoot, targetFilePath)}"] + }` + ) + + exec(`tsc`, tmpRoot) + + try { + const declaration = readFileSync( + join( + tmpRoot, + 'dist', + targetFilePath + .replace(/.tsx$/, '.ts') + .replace(/.ts$/, '.d.ts') + ), + 'utf8' + ) + + // Check just in case of race-condition + if (existsSync(tmpRoot)) + 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('}, {', 3)) + + const routesString = + wrapStatusInQuote(instance).slice( + 3, + instance.indexOf('}, {', 3) + ) + '}\n}\n' + + const routes: AdditionalReference = {} + + for (let route of routesString.slice(1).split('} & {')) { + route = '{' + route + '}' + let schema = TypeBox(route) + + if (schema.type !== 'object') continue + + const paths = [] + + while (true) { + const keys = Object.keys(schema.properties) + if (!keys.length || keys.length > 1) break + + paths.push(keys[0]) + + schema = schema.properties[keys[0]] as any + if (!schema?.properties) break + } + + const method = paths.pop()! + 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] = schema + } + + return routes + } catch (error) { + console.warn('Failed to generate OpenAPI schema') + console.warn(error) + + return + } + } diff --git a/src/index.ts b/src/index.ts index 35be3fc..15d5483 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,8 @@ export const openapi = < documentation = {}, exclude, swagger, - scalar + scalar, + references }: ElysiaOpenAPIConfig = {}) => { if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) @@ -87,7 +88,7 @@ export const openapi = < const { paths, components: { schemas } - } = toOpenAPISchema(app, exclude) + } = toOpenAPISchema(app, exclude, references) return (cachedSchema = { openapi: '3.0.3', diff --git a/src/openapi.ts b/src/openapi.ts index 144020d..18f9423 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -4,7 +4,11 @@ import type { HookContainer } from 'elysia/types' import type { OpenAPIV3 } from 'openapi-types' import type { TProperties } from '@sinclair/typebox' -import type { ElysiaOpenAPIConfig } from './types' +import type { + AdditionalReference, + AdditionalReferences, + ElysiaOpenAPIConfig +} from './types' export const capitalize = (word: string) => word.charAt(0).toUpperCase() + word.slice(1) @@ -37,7 +41,7 @@ export const getPossiblePath = (path: string): string[] => { const optionalParams = path.match(optionalParamsRegex) if (!optionalParams) return [path] - const originalPath = path.replaceAll('?', '') + const originalPath = path.replace(/\?/g, '') const paths = [originalPath] for (let i = 0; i < optionalParams.length; i++) { @@ -56,7 +60,8 @@ export const getPossiblePath = (path: string): string[] => { */ export function toOpenAPISchema( app: AnyElysia, - exclude?: ElysiaOpenAPIConfig['exclude'] + exclude?: ElysiaOpenAPIConfig['exclude'], + references?: AdditionalReferences ) { const { methods: excludeMethods = ['OPTIONS'], @@ -75,6 +80,16 @@ export function toOpenAPISchema( // @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 @@ -87,6 +102,27 @@ export function toOpenAPISchema( detail: Partial } = route.hooks ?? {} + if (references) + for (const reference of references as AdditionalReference[]) { + const refer = reference[route.path]?.[method] + if (!refer) continue + + if (!hooks.body && refer.body) hooks.body = refer.body + if (!hooks.query && refer.query) hooks.query = refer.query + if (!hooks.params && refer.params) hooks.params = refer.params + if (!hooks.headers && refer.headers) + hooks.headers = refer.headers + if (!hooks.response && refer.response) { + hooks.response = {} + + for (const [status, schema] of Object.entries( + refer.response + )) + if (!hooks.response[status as any]) + hooks.response[status as any] = schema + } + } + if ( excludeTags && hooks.detail.tags?.some((tag) => excludeTags?.includes(tag)) @@ -107,10 +143,6 @@ export function toOpenAPISchema( // Handle path parameters if (hooks.params) { - // const pathParamNames = - // route.path.match(/:([^/]+)/g)?.map((param) => param.slice(1)) || - // [] - if (typeof hooks.params === 'string') hooks.params = toRef(hooks.params) diff --git a/src/types.ts b/src/types.ts index 2558e87..913ef83 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,28 @@ +import type { TSchema } from 'elysia' import type { OpenAPIV3 } from 'openapi-types' import type { ApiReferenceConfiguration } from '@scalar/types' import type { SwaggerUIOptions } from './swagger/types' export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null +type MaybeArray = T | T[] + +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', @@ -146,4 +165,6 @@ export interface ElysiaOpenAPIConfig< */ staticFile?: boolean } + + references?: AdditionalReferences } From f410e319394069b7e3efd5f9fef8d982cd6a750e Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 23:00:25 +0700 Subject: [PATCH 09/58] :tada: feat: export generator --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 10304fa..4ae0080 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,11 @@ "import": "./dist/index.mjs", "require": "./dist/cjs/index.js" }, + "./gen": { + "types": "./dist/gen/index.d.ts", + "import": "./dist/gen/index.mjs", + "require": "./dist/cjs/gen/index.js" + }, "./openapi": { "types": "./dist/openapi.d.ts", "import": "./dist/openapi.mjs", From a49b0f05ff5b02799530b276a3fbdedf73701d7e Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 23:11:56 +0700 Subject: [PATCH 10/58] :blue_book: doc: update doc from swagger to OpenAPI --- README.md | 70 +++++++++++++++++++++++++++++++------------- package.json | 2 +- src/gen/index.ts | 2 +- src/openapi.ts | 8 +++-- src/types.ts | 76 +++++++++++++++++++++++++++--------------------- 5 files changed, 100 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 5993cc0..ed22331 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# @elysiajs/swagger -Plugin for [elysia](https://github.com/elysiajs/elysia) to auto-generate Swagger page. +# @elysiajs/openapi +Plugin for [elysia](https://github.com/elysiajs/elysia) to auto-generate API documentation page. ## Installation ```bash -bun add @elysiajs/swagger +bun add @elysiajs/openapi ``` ## Example ```typescript import { Elysia, t } from 'elysia' -import { swagger } from '@elysiajs/swagger' +import { openapi } from '@elysiajs/openapi' const app = new Elysia() - .use(swagger()) + .use(openapi()) .get('/', () => 'hi', { response: t.String({ description: 'sample description' }) }) .post( '/json/:id', @@ -43,31 +43,59 @@ const app = new Elysia() .listen(8080); ``` -Then go to `http://localhost:8080/swagger`. +Then go to `http://localhost:8080/openapi`. # config -## provider -@default 'scalar' -Choose between [Scalar](https://github.com/scalar/scalar) & [SwaggerUI](https://github.com/swagger-api/swagger-ui) +## enabled +@default true +Enable/Disable the plugin -## scalar -Customize scalarConfig, refers to [Scalar config](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) +## documentation +OpenAPI documentation information -## swagger -Customize Swagger config, refers to [Swagger 3.0.3 config](https://swagger.io/specification/v3) +@see https://spec.openapis.org/oas/v3.0.3.html -## path -@default '/swagger' +## exclude +Configuration to exclude paths or methods from documentation + +## exclude.methods +List of methods to exclude from documentation -The endpoint to expose Swagger +## exclude.paths +List of paths to exclude from documentation -## excludeStaticFile +## exclude.staticFile @default true -Determine if Swagger should exclude static files. +Exclude static file routes from documentation -## exclude -@default [] +## exclude.tags +List of tags to exclude from documentation + +## path +@default '/openapi' -Paths to exclude from the Swagger endpoint +The endpoint to expose OpenAPI documentation frontend + +## provider +@default 'scalar' + +OpenAPI documentation frontend between: +- [Scalar](https://github.com/scalar/scalar) +- [SwaggerUI](https://github.com/openapi-api/openapi-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/package.json b/package.json index 4ae0080..d360ffb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@elysiajs/openapi", "version": "1.3.2", - "description": "Plugin for Elysia to auto-generate OpenAPI documentation", + "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index 52a2821..84be13c 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -179,7 +179,7 @@ export const fromTypes = if (!routes[path]) routes[path] = {} // @ts-ignore - routes[path][method] = schema + routes[path][method.toLowerCase()] = schema } return routes diff --git a/src/openapi.ts b/src/openapi.ts index 18f9423..d4ff9d0 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -95,8 +95,12 @@ export function toOpenAPISchema( const method = route.method.toLowerCase() - if (excludePaths.includes(route.path)) continue - if (excludeMethods.includes(method)) continue + if ( + (excludeStaticFile && route.path.includes('.')) || + excludePaths.includes(route.path) || + excludeMethods.includes(method) + ) + continue const hooks: InputSchema & { detail: Partial diff --git a/src/types.ts b/src/types.ts index 913ef83..66d2387 100644 --- a/src/types.ts +++ b/src/types.ts @@ -34,15 +34,49 @@ export interface ElysiaOpenAPIConfig< enabled?: Enabled /** - * Customize Swagger config, refers to Swagger 2.0 config + * 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 * @@ -51,6 +85,12 @@ export interface ElysiaOpenAPIConfig< * @see https://github.com/swagger-api/swagger-ui */ provider?: Provider + + /** + * Additional reference for each endpoint + */ + references?: AdditionalReferences + /** * Scalar configuration to customize scalar *' @@ -78,18 +118,13 @@ export interface ElysiaOpenAPIConfig< */ cdn?: string } - /** - * The endpoint to expose OpenAPI Documentation - * - * @default '/openapi' - */ - path?: Path /** * The endpoint to expose OpenAPI JSON specification * * @default '/${path}/json' */ specPath?: string + /** * Options to send to SwaggerUIBundle * Currently, options that are defined as functions such as requestInterceptor @@ -142,29 +177,4 @@ export interface ElysiaOpenAPIConfig< */ cdn?: string } - - exclude?: { - /** - * Paths to exclude from OpenAPI endpoint - * - * @default [] - */ - paths?: string | RegExp | (string | RegExp)[] - /** - * Exclude methods from OpenAPI - */ - methods?: string[] - /** - * Exclude tags from OpenAPI - */ - tags?: string[] - /** - * Determine if OpenAPI should exclude static files. - * - * @default true - */ - staticFile?: boolean - } - - references?: AdditionalReferences } From 371c3765ed9954925f6ff4b4be65edde02b66668 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 2 Sep 2025 23:17:17 +0700 Subject: [PATCH 11/58] :blue_book: doc: update doc --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bfb02f..4ac96b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ -# 1.3.2 - 22 Aug 2025 +# 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` From 79afbdcdae52138d43ac55f4776358707db29f5a Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 00:12:09 +0700 Subject: [PATCH 12/58] :wrench: fix: Bug fix: type generator: collapse path when trying to access from dist --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/gen/index.ts | 38 +++++++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac96b3..642414d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/package.json b/package.json index d360ffb..5cc9842 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.2", + "version": "1.3.3", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index 84be13c..e22e293 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -5,7 +5,8 @@ import { writeFileSync, rmSync, existsSync, - cpSync + cpSync, + exists } from 'fs' import { TypeBox } from '@sinclair/typemap' @@ -46,6 +47,14 @@ interface OpenAPIGeneratorOptions { * @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?(tempDir: string): string } /** @@ -66,7 +75,8 @@ export const fromTypes = { tsconfigPath = 'tsconfig.json', instanceName, - projectRoot = process.cwd() + projectRoot = process.cwd(), + overrideOutputPath }: OpenAPIGeneratorOptions = {} ) => () => { @@ -107,16 +117,22 @@ export const fromTypes = exec(`tsc`, tmpRoot) try { - const declaration = readFileSync( - join( + const fileName = targetFilePath + .replace(/.tsx$/, '.ts') + .replace(/.ts$/, '.d.ts') + + let targetFile = + overrideOutputPath?.(tmpRoot) ?? join(tmpRoot, 'dist', fileName) + + // Sometime TypeScript doesn't include the first level directory, eg. src/file.ts -> dist/file.d.ts + if (!existsSync(targetFile)) + targetFile = join( tmpRoot, 'dist', - targetFilePath - .replace(/.tsx$/, '.ts') - .replace(/.ts$/, '.d.ts') - ), - 'utf8' - ) + fileName.slice(fileName.indexOf('/') + 1) + ) + + const declaration = readFileSync(targetFile, 'utf8') // Check just in case of race-condition if (existsSync(tmpRoot)) @@ -184,7 +200,7 @@ export const fromTypes = return routes } catch (error) { - console.warn('Failed to generate OpenAPI schema') + console.warn('[@elysiajs/openapi/gen] Failed to generate OpenAPI schema') console.warn(error) return From a1cef23d9a9458994dbd32c0201547455ac3def3 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 02:35:18 +0700 Subject: [PATCH 13/58] :wrench: fix(type-generator): exclude unknown type --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/gen/index.ts | 12 ++++++++---- src/openapi.ts | 13 +++++++------ 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 642414d..6a826f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/package.json b/package.json index 5cc9842..7f8216f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.3", + "version": "1.3.4", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index e22e293..b545ec9 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -124,14 +124,16 @@ export const fromTypes = let targetFile = overrideOutputPath?.(tmpRoot) ?? join(tmpRoot, 'dist', fileName) - // Sometime TypeScript doesn't include the first level directory, eg. src/file.ts -> dist/file.d.ts - if (!existsSync(targetFile)) - targetFile = join( + { + const _targetFile = join( tmpRoot, 'dist', fileName.slice(fileName.indexOf('/') + 1) ) + if (existsSync(_targetFile)) targetFile = _targetFile + } + const declaration = readFileSync(targetFile, 'utf8') // Check just in case of race-condition @@ -200,7 +202,9 @@ export const fromTypes = return routes } catch (error) { - console.warn('[@elysiajs/openapi/gen] Failed to generate OpenAPI schema') + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) console.warn(error) return diff --git a/src/openapi.ts b/src/openapi.ts index d4ff9d0..b7128ba 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -111,10 +111,11 @@ export function toOpenAPISchema( const refer = reference[route.path]?.[method] if (!refer) continue - if (!hooks.body && refer.body) hooks.body = refer.body - if (!hooks.query && refer.query) hooks.query = refer.query - if (!hooks.params && refer.params) hooks.params = refer.params - if (!hooks.headers && refer.headers) + if (!hooks.body && refer.body?.type) hooks.body = refer.body + if (!hooks.query && refer.query?.type) hooks.query = refer.query + if (!hooks.params && refer.params?.type) + hooks.params = refer.params + if (!hooks.headers && refer.headers?.type) hooks.headers = refer.headers if (!hooks.response && refer.response) { hooks.response = {} @@ -122,7 +123,7 @@ export function toOpenAPISchema( for (const [status, schema] of Object.entries( refer.response )) - if (!hooks.response[status as any]) + if (!hooks.response[status as any] && schema?.type) hooks.response[status as any] = schema } } @@ -224,7 +225,7 @@ export function toOpenAPISchema( if (parameters.length > 0) operation.parameters = parameters // Handle request body - if (hooks.body) { + if (hooks.body && method !== 'get' && method !== 'head') { if (typeof hooks.body === 'string') hooks.body = toRef(hooks.body) // @ts-ignore From 29c5bc2745797f2704a231c6228bddaf9d512964 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 02:37:06 +0700 Subject: [PATCH 14/58] :wrench: fix(type-generator): exclude unknown type --- src/gen/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gen/index.ts b/src/gen/index.ts index b545ec9..7ce93d4 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -104,10 +104,11 @@ export const fromTypes = "lib": ["ESNext"], "module": "ESNext", "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, "moduleResolution": "bundler", "skipLibCheck": true, "skipDefaultLibCheck": true, - "emitDeclarationOnly": true, "outDir": "./dist" }, "include": ["${join(projectRoot, targetFilePath)}"] From e721283a17497eed3d7489e974d48fde3945a655 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 03:17:47 +0700 Subject: [PATCH 15/58] :wrench: fix(type-generator): handle union type --- CHANGELOG.md | 5 +++++ example/gen.ts | 23 +++++++++++++++++++++-- package.json | 2 +- src/openapi.ts | 39 +++++++++++++++++++++++++++++---------- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a826f3..5af3619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 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 diff --git a/example/gen.ts b/example/gen.ts index 3b72f36..8fc447b 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -1,5 +1,5 @@ import { Elysia, t } from 'elysia' -import { openapi } from '../src/index' +import { openapi, withHeaders } from '../src/index' import { fromTypes } from '../src/gen' export const app = new Elysia() @@ -8,7 +8,26 @@ export const app = new Elysia() references: fromTypes('example/gen.ts') }) ) - .get('/', { test: 'hello' 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), diff --git a/package.json b/package.json index 7f8216f..9c81e66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.4", + "version": "1.3.5", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/openapi.ts b/src/openapi.ts index b7128ba..a8ca51a 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -2,7 +2,7 @@ import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia' import type { HookContainer } from 'elysia/types' import type { OpenAPIV3 } from 'openapi-types' -import type { TProperties } from '@sinclair/typebox' +import { Kind, type TProperties } from '@sinclair/typebox' import type { AdditionalReference, @@ -53,6 +53,13 @@ export const getPossiblePath = (path: string): string[] => { return paths } +const isValidSchema = (schema: any): schema is TSchema => + typeof schema === 'object' && + ((Kind in schema && schema[Kind] !== 'Unknown') || + schema.type || + schema.properties || + schema.items) + /** * Converts Elysia routes to OpenAPI 3.0.3 paths schema * @param routes Array of Elysia route objects @@ -111,21 +118,33 @@ export function toOpenAPISchema( const refer = reference[route.path]?.[method] if (!refer) continue - if (!hooks.body && refer.body?.type) hooks.body = refer.body - if (!hooks.query && refer.query?.type) hooks.query = refer.query - if (!hooks.params && refer.params?.type) + 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 && refer.headers?.type) + + if (!hooks.headers && isValidSchema(refer.headers)) hooks.headers = refer.headers - if (!hooks.response && refer.response) { - hooks.response = {} + if (refer.response) for (const [status, schema] of Object.entries( refer.response )) - if (!hooks.response[status as any] && schema?.type) - hooks.response[status as any] = schema - } + if (isValidSchema(schema)) { + if (!hooks.response) hooks.response = {} + + if ( + !hooks.response[ + status as keyof (typeof hooks)['response'] + ] + ) + // @ts-ignore + hooks.response[status] = schema + } } if ( From 0e21f068bcdc6ca8d3aef697c1368ec8701c42aa Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 04:04:27 +0700 Subject: [PATCH 16/58] :wrench: fix(type generator): add loose path type matching --- CHANGELOG.md | 4 ++++ src/openapi.ts | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5af3619..340db17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.3.6 - 3 Sep 2025 +Improvement: +- type generator: add loose path type matching + # 1.3.5 - 3 Sep 2025 Bug fix: - type generator: merge references with existing response status diff --git a/src/openapi.ts b/src/openapi.ts index a8ca51a..5f1cc89 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -60,6 +60,13 @@ const isValidSchema = (schema: any): schema is TSchema => 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 + '/' +} + /** * Converts Elysia routes to OpenAPI 3.0.3 paths schema * @param routes Array of Elysia route objects @@ -115,7 +122,10 @@ export function toOpenAPISchema( if (references) for (const reference of references as AdditionalReference[]) { - const refer = reference[route.path]?.[method] + const refer = + reference[route.path]?.[method] ?? + reference[getLoosePath(route.path)]?.[method] + if (!refer) continue if (!hooks.body && isValidSchema(refer.body)) From 30bcb8824d9658a526264641165e3dc7bda79f18 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 05:34:52 +0700 Subject: [PATCH 17/58] :wrench: fix(type generator): try loose matching for schema type --- CHANGELOG.md | 1 + example/gen.ts | 20 ++++++++++---------- package.json | 2 +- src/gen/index.ts | 22 +++++++++++++++------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 340db17..5e28a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 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: diff --git a/example/gen.ts b/example/gen.ts index 8fc447b..2dcecd3 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -28,14 +28,14 @@ export const app = new 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( + // '/json', + // ({ body, status }) => (Math.random() > 0.5 ? status(418) : body), + // { + // body: t.Object({ + // hello: t.String() + // }) + // } + // ) + // .get('/id/:id/name/:name', ({ params }) => params) .listen(3000) diff --git a/package.json b/package.json index 9c81e66..0df5c08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.5", + "version": "1.3.6", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index 7ce93d4..bff8949 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -1,4 +1,4 @@ -import type { InternalRoute } from 'elysia' +import type { InputSchema, InternalRoute, TSchema } from 'elysia' import { readFileSync, mkdirSync, @@ -14,6 +14,7 @@ import { tmpdir } from 'os' import { join } from 'path' import { spawnSync } from 'child_process' import { AdditionalReference, AdditionalReferences } from '../types' +import { Kind, TObject } from '@sinclair/typebox/type' const matchRoute = /: Elysia<(.*)>/gs const matchStatus = /(\d{3}):/gs @@ -165,17 +166,24 @@ export const fromTypes = const routes: AdditionalReference = {} - for (let route of routesString.slice(1).split('} & {')) { - route = '{' + route + '}' - let schema = TypeBox(route) - - if (schema.type !== 'object') continue + // Treaty is a collection of { ... } & { ... } & { ... } + // Each route will be intersected with each other + // instead of being nested in a route object + for (const route of routesString.slice(1).split('} & {')) { + // as '} & {' is removed, we need to add it back + let schema = TypeBox(`{${route}}}`) + if (schema.type !== 'object') { + // just in case + schema = TypeBox(`{${route}}`) + + if (schema.type !== 'object') continue + } const paths = [] while (true) { const keys = Object.keys(schema.properties) - if (!keys.length || keys.length > 1) break + if (keys.length !== 1) break paths.push(keys[0]) From a8455fefb0bfd7013535cbf735fb7d141006ea18 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 05:37:53 +0700 Subject: [PATCH 18/58] :wrench: fix(type generator): clean up temp files after generation --- CHANGELOG.md | 4 ++++ src/gen/index.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e28a1c..df4252e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.3.7 +Improvement: +- type generator: clean up temp files after generation + # 1.3.6 - 3 Sep 2025 Improvement: - type generator: add loose path type matching diff --git a/src/gen/index.ts b/src/gen/index.ts index bff8949..df2e8cf 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -217,5 +217,7 @@ export const fromTypes = console.warn(error) return + } finally { + rmSync(tmpRoot, { recursive: true, force: true }) } } From 6953d2be6078c9f02d99275a8acb5bea11724940 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 06:44:44 +0700 Subject: [PATCH 19/58] :wrench: fix(type generator): imbalance bracket or something --- CHANGELOG.md | 3 ++- package.json | 2 +- src/gen/index.ts | 29 +++++++++++++++-------------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df4252e..7c46295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ -# 1.3.7 +# 1.3.7 - 3 Sep 2025 Improvement: - type generator: clean up temp files after generation +- type generator: imbalance bracket or something # 1.3.6 - 3 Sep 2025 Improvement: diff --git a/package.json b/package.json index 0df5c08..f4df7a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.6", + "version": "1.3.7", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index df2e8cf..8a06c31 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -17,7 +17,7 @@ import { AdditionalReference, AdditionalReferences } from '../types' import { Kind, TObject } from '@sinclair/typebox/type' const matchRoute = /: Elysia<(.*)>/gs -const matchStatus = /(\d{3}):/gs +const matchStatus = /(\d{3}):/g const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":') const exec = (command: string, cwd: string) => @@ -156,13 +156,19 @@ export const fromTypes = // 1 2 3 4 5 // We want the 4th one for (let i = 0; i < 3; i++) - instance = instance.slice(instance.indexOf('}, {', 3)) + instance = instance.slice( + instance.indexOf( + '}, {', + // remove just `}, `, leaving `{` + 3 + ) + ) - const routesString = - wrapStatusInQuote(instance).slice( - 3, - instance.indexOf('}, {', 3) - ) + '}\n}\n' + const routesString = wrapStatusInQuote( + // Intentionally not adding "}" + // to avoid mismatched bracket in loop below + instance.slice(3, instance.indexOf('}, {', 4)) + ) const routes: AdditionalReference = {} @@ -171,13 +177,8 @@ export const fromTypes = // instead of being nested in a route object for (const route of routesString.slice(1).split('} & {')) { // as '} & {' is removed, we need to add it back - let schema = TypeBox(`{${route}}}`) - if (schema.type !== 'object') { - // just in case - schema = TypeBox(`{${route}}`) - - if (schema.type !== 'object') continue - } + let schema = TypeBox(`{${route}}`) + if (schema.type !== 'object') continue const paths = [] From 593c78d9d68525fa7b5c3628c0eb9f80ce0ace35 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 3 Sep 2025 06:44:55 +0700 Subject: [PATCH 20/58] :wrench: fix(type generator): imbalance bracket or something --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c46295..9c3f2f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # 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 From 36de73543aacdc156547b53e5281cbdf0eac99d3 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 4 Sep 2025 17:37:33 +0700 Subject: [PATCH 21/58] :wrench: fix(type generator): friendly error message, and better error handling --- CHANGELOG.md | 5 ++ example/gen.ts | 2 +- src/gen/index.ts | 120 +++++++++++++++++++++++++++++++++++------------ src/openapi.ts | 2 + 4 files changed, 97 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3f2f4..a94aab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 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 + # 1.3.7 - 3 Sep 2025 Improvement: - type generator: clean up temp files after generation diff --git a/example/gen.ts b/example/gen.ts index 2dcecd3..459cd59 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -5,7 +5,7 @@ import { fromTypes } from '../src/gen' export const app = new Elysia() .use( openapi({ - references: fromTypes('example/gen.ts') + references: fromTypes('example/gen3.ts') }) ) .get( diff --git a/src/gen/index.ts b/src/gen/index.ts index 8a06c31..8422198 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -6,7 +6,8 @@ import { rmSync, existsSync, cpSync, - exists + exists, + readdirSync } from 'fs' import { TypeBox } from '@sinclair/typemap' @@ -15,18 +16,12 @@ import { join } from 'path' import { spawnSync } from 'child_process' import { AdditionalReference, AdditionalReferences } from '../types' import { Kind, TObject } from '@sinclair/typebox/type' +import { readdir } from 'fs/promises' const matchRoute = /: Elysia<(.*)>/gs const matchStatus = /(\d{3}):/g const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":') -const exec = (command: string, cwd: string) => - spawnSync(command, { - shell: true, - cwd, - stdio: 'inherit' - }) - interface OpenAPIGeneratorOptions { /** * Path to tsconfig.json @@ -55,7 +50,14 @@ interface OpenAPIGeneratorOptions { * Under any circumstance, that Elysia failed to find a correct schema, * Put your own schema in this path */ - overrideOutputPath?(tempDir: string): string + overrideOutputPath?: string | ((tempDir: string) => string) + + /** + * don't remove temporary files + * for debugging purpose + * @default false + */ + debug?: boolean } /** @@ -77,29 +79,48 @@ export const fromTypes = tsconfigPath = 'tsconfig.json', instanceName, projectRoot = process.cwd(), - overrideOutputPath + overrideOutputPath, + debug = false }: OpenAPIGeneratorOptions = {} ) => () => { - if (!targetFilePath.endsWith('.ts') && !targetFilePath.endsWith('.tsx')) - throw new Error('Only .ts files are supported') - const tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') - if (existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) - mkdirSync(tmpRoot, { recursive: true }) + try { + if ( + !targetFilePath.endsWith('.ts') && + !targetFilePath.endsWith('.tsx') + ) + throw new Error('Only .ts files are supported') + + if (targetFilePath.startsWith('./')) + targetFilePath = targetFilePath.slice(2) + + const src = targetFilePath.startsWith('/') + ? targetFilePath + : join(projectRoot, targetFilePath) + + if (!existsSync(src)) + throw new Error( + `Couldn't find "${targetFilePath}" from ${projectRoot}` + ) + + if (existsSync(tmpRoot)) + rmSync(tmpRoot, { recursive: true, force: true }) + + mkdirSync(tmpRoot, { recursive: true }) - const extendsRef = existsSync(join(projectRoot, 'tsconfig.json')) - ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` - : '' + const tsconfig = tsconfigPath.startsWith('/') + ? tsconfigPath + : join(projectRoot, tsconfigPath) - if (!join(projectRoot, targetFilePath)) - throw new Error('Target file does not exist') + const extendsRef = existsSync(tsconfig) + ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` + : '' - writeFileSync( - join(tmpRoot, tsconfigPath), - `{ + writeFileSync( + join(tmpRoot, 'tsconfig.json'), + `{ ${extendsRef} "compilerOptions": { "lib": ["ESNext"], @@ -112,19 +133,28 @@ export const fromTypes = "skipDefaultLibCheck": true, "outDir": "./dist" }, - "include": ["${join(projectRoot, targetFilePath)}"] + "include": ["${src}"] }` - ) + ) - exec(`tsc`, tmpRoot) + spawnSync(`tsc`, { + shell: true, + cwd: tmpRoot, + stdio: debug ? 'inherit' : undefined + }) - try { const fileName = targetFilePath .replace(/.tsx$/, '.ts') .replace(/.ts$/, '.d.ts') let targetFile = - overrideOutputPath?.(tmpRoot) ?? join(tmpRoot, 'dist', fileName) + (overrideOutputPath + ? typeof overrideOutputPath === 'string' + ? overrideOutputPath.startsWith('/') + ? overrideOutputPath + : join(tmpRoot, 'dist', overrideOutputPath) + : overrideOutputPath(tmpRoot) + : undefined) ?? join(tmpRoot, 'dist', fileName) { const _targetFile = join( @@ -133,7 +163,34 @@ export const fromTypes = fileName.slice(fileName.indexOf('/') + 1) ) - if (existsSync(_targetFile)) targetFile = _targetFile + if (!existsSync(_targetFile)) { + rmSync(join(tmpRoot, 'tsconfig.json')) + + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn("Couldn't find generated declaration file") + + if (existsSync(join(tmpRoot, 'dist'))) { + const tempFiles = 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) + } + } + + return + } + + targetFile = _targetFile } const declaration = readFileSync(targetFile, 'utf8') @@ -219,6 +276,7 @@ export const fromTypes = return } finally { - rmSync(tmpRoot, { recursive: true, force: true }) + if (!debug && existsSync(tmpRoot)) + rmSync(tmpRoot, { recursive: true, force: true }) } } diff --git a/src/openapi.ts b/src/openapi.ts index 5f1cc89..821b31f 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -122,6 +122,8 @@ export function toOpenAPISchema( if (references) for (const reference of references as AdditionalReference[]) { + if(!reference) continue + const refer = reference[route.path]?.[method] ?? reference[getLoosePath(route.path)]?.[method] From 4f3e93e939516233802fca2eeac2b4b4eca268ba Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 4 Sep 2025 17:50:25 +0700 Subject: [PATCH 22/58] :wrench: fix(type generator): overrideOutputPath can be string --- CHANGELOG.md | 1 + example/gen.ts | 6 +++++- package.json | 2 +- src/gen/index.ts | 54 ++++++++++++++++++++---------------------------- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a94aab0..7fe602b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ 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: diff --git a/example/gen.ts b/example/gen.ts index 459cd59..010136a 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -5,7 +5,11 @@ import { fromTypes } from '../src/gen' export const app = new Elysia() .use( openapi({ - references: fromTypes('example/gen3.ts') + references: fromTypes('gen.ts', { + projectRoot: import.meta.dirname, + overrideOutputPath: 'example/gen.d.ts' + // debug: true + }) }) ) .get( diff --git a/package.json b/package.json index f4df7a1..c60d30b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.7", + "version": "1.3.8", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index 8422198..c43b4a1 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -147,7 +147,7 @@ export const fromTypes = .replace(/.tsx$/, '.ts') .replace(/.ts$/, '.d.ts') - let targetFile = + const targetFile = (overrideOutputPath ? typeof overrideOutputPath === 'string' ? overrideOutputPath.startsWith('/') @@ -156,41 +156,31 @@ export const fromTypes = : overrideOutputPath(tmpRoot) : undefined) ?? join(tmpRoot, 'dist', fileName) - { - const _targetFile = join( - tmpRoot, - 'dist', - fileName.slice(fileName.indexOf('/') + 1) - ) - - if (!existsSync(_targetFile)) { - rmSync(join(tmpRoot, 'tsconfig.json')) + if (!existsSync(targetFile)) { + rmSync(join(tmpRoot, 'tsconfig.json')) - console.warn( - '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' - ) - console.warn("Couldn't find generated declaration file") - - if (existsSync(join(tmpRoot, 'dist'))) { - const tempFiles = 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) - } + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn("Couldn't find generated declaration file") + + if (existsSync(join(tmpRoot, 'dist'))) { + const tempFiles = 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) } - - return } - targetFile = _targetFile + return } const declaration = readFileSync(targetFile, 'utf8') From 2f149ff9c84bd4099354321fa152783cbf57a2f6 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 4 Sep 2025 18:00:08 +0700 Subject: [PATCH 23/58] :wrench: fix(type generator): loose path generated file mapping --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/gen/index.ts | 25 ++++++++++++++++++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe602b..1d4b476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/package.json b/package.json index c60d30b..98fc3b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.8", + "version": "1.3.9", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index c43b4a1..fa23e75 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -147,16 +147,35 @@ export const fromTypes = .replace(/.tsx$/, '.ts') .replace(/.ts$/, '.d.ts') - const targetFile = + let targetFile = (overrideOutputPath ? typeof overrideOutputPath === 'string' ? overrideOutputPath.startsWith('/') ? overrideOutputPath : join(tmpRoot, 'dist', overrideOutputPath) : overrideOutputPath(tmpRoot) - : undefined) ?? join(tmpRoot, 'dist', fileName) + : undefined) ?? + join( + tmpRoot, + 'dist', + // remove leading like src or something similar + fileName.slice(fileName.indexOf('/') + 1) + ) + + let existed = existsSync(targetFile) + + if (!existed && overrideOutputPath) { + targetFile = join( + tmpRoot, + 'dist', + // use original file name as-is eg. in monorepo + fileName + ) + + existed = existsSync(targetFile) + } - if (!existsSync(targetFile)) { + if (!existed) { rmSync(join(tmpRoot, 'tsconfig.json')) console.warn( From f55cf5ce5ad7afdff66c7dc22d188690ffb33773 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Fri, 5 Sep 2025 00:15:03 +0700 Subject: [PATCH 24/58] :tada: feat(type generator): accept `.d.ts` to prevent type generation in production --- CHANGELOG.md | 7 ++ example/gen.d.ts | 106 +++++++++++++++++++++++++++ example/gen.ts | 26 ++++--- package.json | 2 +- src/gen/index.ts | 181 ++++++++++++++++++++++++----------------------- 5 files changed, 220 insertions(+), 102 deletions(-) create mode 100644 example/gen.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d4b476..cbc4a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 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 diff --git a/example/gen.d.ts b/example/gen.d.ts new file mode 100644 index 0000000..6309af1 --- /dev/null +++ b/example/gen.d.ts @@ -0,0 +1,106 @@ +import { Elysia } from 'elysia'; +export declare const app: Elysia<"", { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + typebox: {}; + error: {}; +}, { + schema: {}; + standaloneSchema: {}; + macro: {}; + macroFn: {}; + parser: {}; +}, { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + test: "hello"; + } | undefined; + readonly 204: unknown; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; +} & { + json: { + post: { + body: { + hello: string; + }; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + hello: string; + }; + 418: "I'm a teapot"; + 422: { + type: "validation"; + on: string; + summary?: string; message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; +} & { + id: { + ":id": { + name: { + ":name": { + get: { + body: unknown; + params: { + name: string; + id: string; + }; + query: unknown; + headers: unknown; + response: { + 200: { + name: string; + id: string; + }; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; + }; + }; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; +}>; diff --git a/example/gen.ts b/example/gen.ts index 010136a..1c8e87b 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -5,10 +5,8 @@ import { fromTypes } from '../src/gen' export const app = new Elysia() .use( openapi({ - references: fromTypes('gen.ts', { - projectRoot: import.meta.dirname, - overrideOutputPath: 'example/gen.d.ts' - // debug: true + references: fromTypes('example/gen.d.ts', { + debug: true }) }) ) @@ -32,14 +30,14 @@ export const app = new 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( + '/json', + ({ body, status }) => (Math.random() > 0.5 ? status(418) : body), + { + body: t.Object({ + hello: t.String() + }) + } + ) + .get('/id/:id/name/:name', ({ params }) => params) .listen(3000) diff --git a/package.json b/package.json index 98fc3b4..3a6e4c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.9", + "version": "1.3.10", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index fa23e75..433f5b8 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -105,101 +105,108 @@ export const fromTypes = `Couldn't find "${targetFilePath}" from ${projectRoot}` ) - if (existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) - - mkdirSync(tmpRoot, { recursive: true }) - - const tsconfig = tsconfigPath.startsWith('/') - ? tsconfigPath - : join(projectRoot, tsconfigPath) - - const extendsRef = existsSync(tsconfig) - ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` - : '' - - writeFileSync( - join(tmpRoot, 'tsconfig.json'), - `{ - ${extendsRef} - "compilerOptions": { - "lib": ["ESNext"], - "module": "ESNext", - "noEmit": false, - "declaration": true, - "emitDeclarationOnly": true, - "moduleResolution": "bundler", - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "outDir": "./dist" - }, - "include": ["${src}"] - }` - ) - - spawnSync(`tsc`, { - shell: true, - cwd: tmpRoot, - stdio: debug ? 'inherit' : undefined - }) - - const fileName = targetFilePath - .replace(/.tsx$/, '.ts') - .replace(/.ts$/, '.d.ts') - - let 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 targetFile: string + + // Since it's already a declaration file + // We can just read it directly + if (targetFilePath.endsWith('.d.ts')) targetFile = targetFilePath + else { + if (existsSync(tmpRoot)) + rmSync(tmpRoot, { recursive: true, force: true }) + + mkdirSync(tmpRoot, { recursive: true }) + + const tsconfig = tsconfigPath.startsWith('/') + ? tsconfigPath + : join(projectRoot, tsconfigPath) + + const extendsRef = existsSync(tsconfig) + ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` + : '' + + writeFileSync( + join(tmpRoot, 'tsconfig.json'), + `{ + ${extendsRef} + "compilerOptions": { + "lib": ["ESNext"], + "module": "ESNext", + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "outDir": "./dist" + }, + "include": ["${src}"] +}` ) - let existed = existsSync(targetFile) + spawnSync(`tsc`, { + shell: true, + cwd: tmpRoot, + stdio: debug ? 'inherit' : undefined + }) + + 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) + ) - if (!existed && overrideOutputPath) { - targetFile = join( - tmpRoot, - 'dist', - // use original file name as-is eg. in monorepo - fileName - ) + let existed = existsSync(targetFile) - existed = existsSync(targetFile) - } + if (!existed && !overrideOutputPath) { + targetFile = join( + tmpRoot, + 'dist', + // use original file name as-is eg. in monorepo + fileName + ) + + existed = existsSync(targetFile) + } - if (!existed) { - rmSync(join(tmpRoot, 'tsconfig.json')) + if (!existed) { + rmSync(join(tmpRoot, 'tsconfig.json')) - console.warn( - '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' - ) - console.warn("Couldn't find generated declaration file") - - if (existsSync(join(tmpRoot, 'dist'))) { - const tempFiles = 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) + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn("Couldn't find generated declaration file") + + if (existsSync(join(tmpRoot, 'dist'))) { + const tempFiles = 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) + } } - } - return + return + } } const declaration = readFileSync(targetFile, 'utf8') From 59a142b6cb74aef125036a3003d0562bb698c345 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sat, 6 Sep 2025 18:20:29 +0700 Subject: [PATCH 25/58] :wrench: fix(type generator): convert Windows path to Unix for TypeScript CLI --- .eslintrc.js | 46 +- .github/dependabot.yml | 22 +- .github/workflows/ci.yml | 56 +- .github/workflows/publish.yml | 100 +-- .gitignore | 12 +- .npmignore | 52 +- .prettierrc | 14 +- CHANGELOG.md | 550 ++++++++--------- LICENSE | 14 +- README.md | 202 +++---- build.ts | 74 +-- bun.lock | 1074 ++++++++++++++++----------------- example/gen.d.ts | 212 +++---- example/gen.ts | 86 +-- example/index.ts | 170 +++--- example/plugin.ts | 232 +++---- package.json | 184 +++--- src/gen/index.ts | 605 ++++++++++--------- src/index.ts | 266 ++++---- src/openapi.ts | 826 ++++++++++++------------- src/scalar/index.ts | 324 +++++----- src/swagger/index.ts | 296 ++++----- src/swagger/types.ts | 662 ++++++++++---------- src/types.ts | 360 +++++------ test/index.test.ts | 550 ++++++++--------- test/node/cjs/index.js | 14 +- test/node/cjs/package.json | 2 +- test/node/esm/index.js | 14 +- test/node/esm/package.json | 2 +- test/openapi.test.ts | 28 +- test/validate-schema.test.ts | 168 +++--- tsconfig.dts.json | 212 +++---- tsconfig.json | 206 +++---- 33 files changed, 3824 insertions(+), 3811 deletions(-) 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 cbc4a55..7ed7479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,273 +1,277 @@ -# 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 -- plugin is no longer async -- model should be synced globally -- use `parse` instead of `type` to determine content type - -# 1.3.0-exp.0 - 23 Apr 2025 -Change: -- Add support for Elysia 1.3 - -# 1.2.2 - 22 Feb 2024 -Bug fix: -- [#185](https://github.com/elysiajs/elysia-swagger/pull/185) Fix path issue in Scalar config - -# 1.2.1 - 19 Feb 2024 -Bug fix: -- [#154](https://github.com/elysiajs/elysia-swagger/pull/154) prevent failed to fetch spec from URL error -- [elysia#1063](https://github.com/elysiajs/elysia/issues/1063) Using t.Ref as response schema results in invalid OpenAPI specification -- handle unfold recursive Ref to schema - -# 1.2.0-rc.0 - 23 Dec 2024 -Change: -- Add support for Elysia 1.2 - -# 1.1.6 - 17 Nov 2024 -Bug fix: -- [#156](https://github.com/elysiajs/elysia-swagger/pull/156) add type check in cloneHook - -# 1.1.4 - 9 Oct 2024 -Bug fix: -- Fix duplicate object reference - -# 1.1.2 - 5 Sep 2024 -Feature: -- add provenance publish - -# 1.1.1 - 12 Aug 2024 -Feature: -- add hide flag - -# 1.1.0 - 16 Jul 2024 -Change: -- Add support for Elysia 1.1 - - -# 1.1.0-rc.0 - 12 Jul 2024 -Change: -- Add support for Elysia 1.1 - - -# 1.0.2 - 18 Mar 2024 -Change: -- Add support for Elysia 1.0 - - -# 1.0.0 - 16 Mar 2024 -Change: -- Add support for Elysia 1.0 - - -# 1.0.0-rc.0 - 1 Mar 2024 -Change: -- Add support for Elysia 1.0 - - -# 1.0.0-beta.1 - 17 Feb 2024 -Change: -- Add support for Elysia 1.0 - - -# 1.0.0-beta.0 - 6 Feb 2024 -Change: -- Add support for Elysia 1.0 - -# 0.8.5 - 24 Jan 2024 -Bug fix: -- [#39](https://github.com/elysiajs/elysia-swagger/issues/39) Array type does not work - -# 0.8.4 - 24 Jan 2024 -Feature: -- [#96](https://github.com/elysiajs/elysia-swagger/pull/96) move to scalar configuration prop -- [#95](https://github.com/elysiajs/elysia-swagger/pulls?q=is%3Apr+is%3Aclosed) Scalar CDN option -- [#92](https://github.com/elysiajs/elysia-swagger/pull/92) update scalar to 1.13.0 and using latest instead of hardcoded version - -# 0.8.3 - 8 Jan 2024 -Bug fix: -- Using local Scalar API reference instead of leftover one (oppsie 👉👈) - -# 0.8.2 - 8 Jan 2024 -Improvement: -- Extract type inference to reduce bundle-size - -# 0.8.1 - 7 Jan 2024 -Change: -- Using Scalar provider as new default - -# 0.8.0-rc.0 - 15 Dec 2023 -Change: -- Add support for Elysia 0.8 - -# 0.7.5 -Improvement: -- #[59](https://github.com/elysiajs/elysia-swagger/pull/59) use relative path to swagger json #59 - -# 0.7.4 - 27 Oct 2023 -Improvement: -- [#24](https://github.com/elysiajs/elysia-swagger/pull/24) - adding schema validity test - -Change: -- [#48](https://github.com/elysiajs/elysia-swagger/pull/48) update Swagger UI to 4.9.0 -- [#36](https://github.com/elysiajs/elysia-swagger/pull/36 ) point to generated .d.ts instead of raw .ts - -Bug fix: -- [#41](https://github.com/elysiajs/elysia-swagger/pull/41) parameters mapping, fix -- [#43](https://github.com/elysiajs/elysia-swagger/pull/43) typo in default documentation - -# 0.7.3 - 26 Sep 2023 -Feature: -- [#19](https://github.com/elysiajs/elysia-swagger/pull/19) feat: handle nullish response types -- [#18](https://github.com/elysiajs/elysia-swagger/pull/18) swagger ui options - - -Improvement: -- [#23](https://github.com/elysiajs/elysia-swagger/pull/23) Add github action to run bun test -- remove `removeComment` from tsconfig to show JSDoc -- add `theme` to customize Swagger CSS link -- add `autoDarkMode` using poor man Swagger dark mode CSS 😭 - -Change: -- Set default swagger version to 5.7.2 - -Bug fix: -- [#16](https://github.com/elysiajs/elysia-swagger/pull/16) fix: use global prefix - -# 0.7.2 - 21 Sep 2023 -Bug fix: -- Paths is undefined -- Models is not showing - -# 0.7.1 - 20 Sep 2023 -Bug fix: -- Add openapi-types as dependencies -- Fix `any` returned type - -# 0.7.0 - 20 Sep 2023 -- Add support for Elysia 0. - -# 0.7.0-beta.0 - 18 Sep 2023 -- Add support for Elysia 0.7 - -# 0.6.2 - 11 Sep 2023 -- Ship lodash.cloneDeep type - -# 0.6.1 - 17 Aug 2023 -- Add support for user provided components - -# 0.6.0 - 6 Aug 2023 -- Add support for Elysia 0.6 - -# 0.6.0-rc.0 - 6 Aug 2023 -- Add support for Elysia 0.6 -# 0.5.0 - 15 May 2023 -- Add support for Elysia 0.5 -- Add CommonJS support - -# 0.3.0 - 17 Mar 2023 -Improvement: -- Add support for Elysia 0.3.0 - -# 0.3.0-rc.0 - 7 Mar 2023 -Improvement: -- Add support for Elysia 0.3.0-rc.0 - -# 0.3.0-beta.0 - 25 Feb 2023 -Improvement: -- Support Elysia >= 0.3.0-beta.0 - -Breaking Change: -- Update from OpenAPI 2.x to OpenAPI 3.0.3 -- `swagger.swagger` is renamed to `swagger.documentation` - -# 0.1.1 - 8 Jan 2023 -Bug fix: -- Infers path type - -# 0.1.0-rc.3 - 13 Dec 2022 -Improvement: -- Add support for Elysia 0.1.0-rc.5 - -# 0.1.0-rc.2 - 9 Dec 2022 -Improvement: -- Support for Elysia 0.1.0-rc.1 onward - -Fix: -- Add main fields Bundlephobia - -# 0.1.0-rc.1 - 6 Dec 2022 -Improvement: -- Support for Elysia 0.1.0-rc.1 onward - -# 0.0.0-experimental.3 - 29 Nov 2022 -Change: -- Support for KingWorld 0.0.0-experimental.51 - -# 0.0.0-experimental.2 - 22 Nov 2022 -Change: -- Support for KingWorld 0.0.0-experimental.51 - -# 0.0.0-experimental.1 - 12 Nov 2022 -Improvement: -- Auto infers path params if schema is presented -- Auto infers path params now merge with schema.params +# 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 +- plugin is no longer async +- model should be synced globally +- use `parse` instead of `type` to determine content type + +# 1.3.0-exp.0 - 23 Apr 2025 +Change: +- Add support for Elysia 1.3 + +# 1.2.2 - 22 Feb 2024 +Bug fix: +- [#185](https://github.com/elysiajs/elysia-swagger/pull/185) Fix path issue in Scalar config + +# 1.2.1 - 19 Feb 2024 +Bug fix: +- [#154](https://github.com/elysiajs/elysia-swagger/pull/154) prevent failed to fetch spec from URL error +- [elysia#1063](https://github.com/elysiajs/elysia/issues/1063) Using t.Ref as response schema results in invalid OpenAPI specification +- handle unfold recursive Ref to schema + +# 1.2.0-rc.0 - 23 Dec 2024 +Change: +- Add support for Elysia 1.2 + +# 1.1.6 - 17 Nov 2024 +Bug fix: +- [#156](https://github.com/elysiajs/elysia-swagger/pull/156) add type check in cloneHook + +# 1.1.4 - 9 Oct 2024 +Bug fix: +- Fix duplicate object reference + +# 1.1.2 - 5 Sep 2024 +Feature: +- add provenance publish + +# 1.1.1 - 12 Aug 2024 +Feature: +- add hide flag + +# 1.1.0 - 16 Jul 2024 +Change: +- Add support for Elysia 1.1 + + +# 1.1.0-rc.0 - 12 Jul 2024 +Change: +- Add support for Elysia 1.1 + + +# 1.0.2 - 18 Mar 2024 +Change: +- Add support for Elysia 1.0 + + +# 1.0.0 - 16 Mar 2024 +Change: +- Add support for Elysia 1.0 + + +# 1.0.0-rc.0 - 1 Mar 2024 +Change: +- Add support for Elysia 1.0 + + +# 1.0.0-beta.1 - 17 Feb 2024 +Change: +- Add support for Elysia 1.0 + + +# 1.0.0-beta.0 - 6 Feb 2024 +Change: +- Add support for Elysia 1.0 + +# 0.8.5 - 24 Jan 2024 +Bug fix: +- [#39](https://github.com/elysiajs/elysia-swagger/issues/39) Array type does not work + +# 0.8.4 - 24 Jan 2024 +Feature: +- [#96](https://github.com/elysiajs/elysia-swagger/pull/96) move to scalar configuration prop +- [#95](https://github.com/elysiajs/elysia-swagger/pulls?q=is%3Apr+is%3Aclosed) Scalar CDN option +- [#92](https://github.com/elysiajs/elysia-swagger/pull/92) update scalar to 1.13.0 and using latest instead of hardcoded version + +# 0.8.3 - 8 Jan 2024 +Bug fix: +- Using local Scalar API reference instead of leftover one (oppsie 👉👈) + +# 0.8.2 - 8 Jan 2024 +Improvement: +- Extract type inference to reduce bundle-size + +# 0.8.1 - 7 Jan 2024 +Change: +- Using Scalar provider as new default + +# 0.8.0-rc.0 - 15 Dec 2023 +Change: +- Add support for Elysia 0.8 + +# 0.7.5 +Improvement: +- #[59](https://github.com/elysiajs/elysia-swagger/pull/59) use relative path to swagger json #59 + +# 0.7.4 - 27 Oct 2023 +Improvement: +- [#24](https://github.com/elysiajs/elysia-swagger/pull/24) - adding schema validity test + +Change: +- [#48](https://github.com/elysiajs/elysia-swagger/pull/48) update Swagger UI to 4.9.0 +- [#36](https://github.com/elysiajs/elysia-swagger/pull/36 ) point to generated .d.ts instead of raw .ts + +Bug fix: +- [#41](https://github.com/elysiajs/elysia-swagger/pull/41) parameters mapping, fix +- [#43](https://github.com/elysiajs/elysia-swagger/pull/43) typo in default documentation + +# 0.7.3 - 26 Sep 2023 +Feature: +- [#19](https://github.com/elysiajs/elysia-swagger/pull/19) feat: handle nullish response types +- [#18](https://github.com/elysiajs/elysia-swagger/pull/18) swagger ui options + + +Improvement: +- [#23](https://github.com/elysiajs/elysia-swagger/pull/23) Add github action to run bun test +- remove `removeComment` from tsconfig to show JSDoc +- add `theme` to customize Swagger CSS link +- add `autoDarkMode` using poor man Swagger dark mode CSS 😭 + +Change: +- Set default swagger version to 5.7.2 + +Bug fix: +- [#16](https://github.com/elysiajs/elysia-swagger/pull/16) fix: use global prefix + +# 0.7.2 - 21 Sep 2023 +Bug fix: +- Paths is undefined +- Models is not showing + +# 0.7.1 - 20 Sep 2023 +Bug fix: +- Add openapi-types as dependencies +- Fix `any` returned type + +# 0.7.0 - 20 Sep 2023 +- Add support for Elysia 0. + +# 0.7.0-beta.0 - 18 Sep 2023 +- Add support for Elysia 0.7 + +# 0.6.2 - 11 Sep 2023 +- Ship lodash.cloneDeep type + +# 0.6.1 - 17 Aug 2023 +- Add support for user provided components + +# 0.6.0 - 6 Aug 2023 +- Add support for Elysia 0.6 + +# 0.6.0-rc.0 - 6 Aug 2023 +- Add support for Elysia 0.6 +# 0.5.0 - 15 May 2023 +- Add support for Elysia 0.5 +- Add CommonJS support + +# 0.3.0 - 17 Mar 2023 +Improvement: +- Add support for Elysia 0.3.0 + +# 0.3.0-rc.0 - 7 Mar 2023 +Improvement: +- Add support for Elysia 0.3.0-rc.0 + +# 0.3.0-beta.0 - 25 Feb 2023 +Improvement: +- Support Elysia >= 0.3.0-beta.0 + +Breaking Change: +- Update from OpenAPI 2.x to OpenAPI 3.0.3 +- `swagger.swagger` is renamed to `swagger.documentation` + +# 0.1.1 - 8 Jan 2023 +Bug fix: +- Infers path type + +# 0.1.0-rc.3 - 13 Dec 2022 +Improvement: +- Add support for Elysia 0.1.0-rc.5 + +# 0.1.0-rc.2 - 9 Dec 2022 +Improvement: +- Support for Elysia 0.1.0-rc.1 onward + +Fix: +- Add main fields Bundlephobia + +# 0.1.0-rc.1 - 6 Dec 2022 +Improvement: +- Support for Elysia 0.1.0-rc.1 onward + +# 0.0.0-experimental.3 - 29 Nov 2022 +Change: +- Support for KingWorld 0.0.0-experimental.51 + +# 0.0.0-experimental.2 - 22 Nov 2022 +Change: +- Support for KingWorld 0.0.0-experimental.51 + +# 0.0.0-experimental.1 - 12 Nov 2022 +Improvement: +- Auto infers path params if schema is presented +- Auto infers path params now merge with schema.params 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 ed22331..ac9d734 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,101 @@ -# @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/openapi-api/openapi-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/) +# @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/openapi-api/openapi-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 5628259..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 fd67186..8ef722e 100644 --- a/bun.lock +++ b/bun.lock @@ -1,537 +1,537 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@elysiajs/swagger", - "dependencies": { - "@sinclair/typemap": "^0.10.1", - "openapi-types": "^12.1.3", - }, - "devDependencies": { - "@apidevtools/swagger-parser": "^12.0.0", - "@scalar/types": "^0.2.13", - "@types/bun": "1.2.20", - "elysia": "1.3.21", - "eslint": "9.6.0", - "tsup": "^8.5.0", - "typescript": "^5.9.2", - }, - "peerDependencies": { - "elysia": ">= 1.3.0", - }, - }, - }, - "packages": { - "@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@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/android-arm": ["@esbuild/android-arm@0.25.3", "", { "os": "android", "cpu": "arm" }, "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.3", "", { "os": "android", "cpu": "arm64" }, "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.3", "", { "os": "android", "cpu": "x64" }, "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.3", "", { "os": "linux", "cpu": "arm" }, "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64" }, "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.3", "", { "os": "none", "cpu": "arm64" }, "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.3", "", { "os": "none", "cpu": "x64" }, "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="], - - "@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=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], - - "@eslint/config-array": ["@eslint/config-array@0.17.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "@eslint/js": ["@eslint/js@9.6.0", "", {}, "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], - - "@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/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.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=="], - - "@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=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@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-arm64": ["@rollup/rollup-android-arm64@4.40.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q=="], - - "@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-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.1", "", { "os": "linux", "cpu": "arm" }, "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg=="], - - "@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-musl": ["@rollup/rollup-linux-arm64-musl@4.40.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ=="], - - "@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-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg=="], - - "@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-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA=="], - - "@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-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ=="], - - "@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-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg=="], - - "@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-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA=="], - - "@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="], - - "@scalar/types": ["@scalar/types@0.2.13", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-rO6KGMJqOsBnN/2R4fErMFLpRSPVJElni+HABDpf+ZlLJp2lvxuPn0IXLumK5ytfplUH9iqKgSXjndnZfxSYLQ=="], - - "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], - - "@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=="], - - "@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=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], - - "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], - - "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], - - "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - - "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "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=="], - - "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=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "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=="], - - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - - "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "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.21", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.6", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-LLfDSoVA5fBoqKQfMJyzmHLkya8zMbEYwd7DS7v2iQB706mgzWg0gufXl58cFALErcvSayplrkDvjkmlYTkIZQ=="], - - "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=="], - - "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-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], - - "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=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "exact-mirror": ["exact-mirror@0.1.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-EXGDixoDotCGrXCce63zmGHDA+3Id6PPkIwshBHuB10dwVc4YV4gfaYLuysHOxyURmwyt4UL186ann0oYa2CFQ=="], - - "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], - - "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=="], - - "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=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], - - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], - - "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=="], - - "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "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@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=="], - - "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=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - - "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=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["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=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "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=="], - - "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "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=="], - - "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=="], - - "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "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=="], - - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - - "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], - - "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], - - "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "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=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], - - "@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=="], - - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "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=="], - - "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "mlly/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - - "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/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - - "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=="], - - "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=="], - - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - - "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=="], - } -} +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@elysiajs/swagger", + "dependencies": { + "@sinclair/typemap": "^0.10.1", + "openapi-types": "^12.1.3", + }, + "devDependencies": { + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@types/bun": "1.2.20", + "elysia": "1.3.21", + "eslint": "9.6.0", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + }, + "peerDependencies": { + "elysia": ">= 1.3.0", + }, + }, + }, + "packages": { + "@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@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/android-arm": ["@esbuild/android-arm@0.25.3", "", { "os": "android", "cpu": "arm" }, "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.3", "", { "os": "android", "cpu": "arm64" }, "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.3", "", { "os": "android", "cpu": "x64" }, "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.3", "", { "os": "linux", "cpu": "arm" }, "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64" }, "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.3", "", { "os": "none", "cpu": "arm64" }, "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.3", "", { "os": "none", "cpu": "x64" }, "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="], + + "@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=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.17.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.6.0", "", {}, "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@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/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.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=="], + + "@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=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@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-arm64": ["@rollup/rollup-android-arm64@4.40.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q=="], + + "@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-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.1", "", { "os": "linux", "cpu": "arm" }, "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg=="], + + "@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-musl": ["@rollup/rollup-linux-arm64-musl@4.40.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ=="], + + "@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-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg=="], + + "@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-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA=="], + + "@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-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ=="], + + "@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-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg=="], + + "@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-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA=="], + + "@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="], + + "@scalar/types": ["@scalar/types@0.2.13", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-rO6KGMJqOsBnN/2R4fErMFLpRSPVJElni+HABDpf+ZlLJp2lvxuPn0IXLumK5ytfplUH9iqKgSXjndnZfxSYLQ=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], + + "@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=="], + + "@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=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], + + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "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=="], + + "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=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "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=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "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.21", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.6", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-LLfDSoVA5fBoqKQfMJyzmHLkya8zMbEYwd7DS7v2iQB706mgzWg0gufXl58cFALErcvSayplrkDvjkmlYTkIZQ=="], + + "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=="], + + "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-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + + "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=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "exact-mirror": ["exact-mirror@0.1.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-EXGDixoDotCGrXCce63zmGHDA+3Id6PPkIwshBHuB10dwVc4YV4gfaYLuysHOxyURmwyt4UL186ann0oYa2CFQ=="], + + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + + "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=="], + + "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=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], + + "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=="], + + "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "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@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=="], + + "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=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "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=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["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=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "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=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "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=="], + + "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=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "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=="], + + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + + "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "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=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], + + "@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=="], + + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "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=="], + + "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "mlly/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "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/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "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=="], + + "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=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "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=="], + } +} diff --git a/example/gen.d.ts b/example/gen.d.ts index 6309af1..6a102e5 100644 --- a/example/gen.d.ts +++ b/example/gen.d.ts @@ -1,106 +1,106 @@ -import { Elysia } from 'elysia'; -export declare const app: Elysia<"", { - decorator: {}; - store: {}; - derive: {}; - resolve: {}; -}, { - typebox: {}; - error: {}; -}, { - schema: {}; - standaloneSchema: {}; - macro: {}; - macroFn: {}; - parser: {}; -}, { - get: { - body: unknown; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: { - test: "hello"; - } | undefined; - readonly 204: unknown; - 422: { - type: "validation"; - on: string; - summary?: string; - message?: string; - found?: unknown; - property?: string; - expected?: string; - }; - }; - }; -} & { - json: { - post: { - body: { - hello: string; - }; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: { - hello: string; - }; - 418: "I'm a teapot"; - 422: { - type: "validation"; - on: string; - summary?: string; message?: string; - found?: unknown; - property?: string; - expected?: string; - }; - }; - }; - }; -} & { - id: { - ":id": { - name: { - ":name": { - get: { - body: unknown; - params: { - name: string; - id: string; - }; - query: unknown; - headers: unknown; - response: { - 200: { - name: string; - id: string; - }; - 422: { - type: "validation"; - on: string; - summary?: string; - message?: string; - found?: unknown; - property?: string; - expected?: string; - }; - }; - }; - }; - }; - }; - }; -}, { - derive: {}; - resolve: {}; - schema: {}; - standaloneSchema: {}; -}, { - derive: {}; - resolve: {}; - schema: {}; - standaloneSchema: {}; -}>; +import { Elysia } from 'elysia'; +export declare const app: Elysia<"", { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + typebox: {}; + error: {}; +}, { + schema: {}; + standaloneSchema: {}; + macro: {}; + macroFn: {}; + parser: {}; +}, { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + test: "hello"; + } | undefined; + readonly 204: unknown; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; +} & { + json: { + post: { + body: { + hello: string; + }; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + hello: string; + }; + 418: "I'm a teapot"; + 422: { + type: "validation"; + on: string; + summary?: string; message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; +} & { + id: { + ":id": { + name: { + ":name": { + get: { + body: unknown; + params: { + name: string; + id: string; + }; + query: unknown; + headers: unknown; + response: { + 200: { + name: string; + id: string; + }; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; + }; + }; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; +}>; diff --git a/example/gen.ts b/example/gen.ts index 1c8e87b..f5ae0f6 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -1,43 +1,43 @@ -import { Elysia, t } from 'elysia' -import { openapi, withHeaders } from '../src/index' -import { fromTypes } from '../src/gen' - -export const app = new Elysia() - .use( - openapi({ - references: fromTypes('example/gen.d.ts', { - debug: true - }) - }) - ) - .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) - .listen(3000) +import { Elysia, t } from 'elysia' +import { openapi, withHeaders } from '../src/index' +import { fromTypes } from '../src/gen' + +export const app = new Elysia() + .use( + openapi({ + references: fromTypes('example/gen.ts', { + debug: true + }) + }) + ) + .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) + .listen(3000) diff --git a/example/index.ts b/example/index.ts index 7e8439a..7e263e3 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,85 +1,85 @@ -import { Elysia, t } from 'elysia' -import { openapi, withHeaders } from '../src/index' - -const schema = t.Object({ - test: t.Literal('hello') -}) - -const schema2 = t.Object({ - test: t.Literal('world') -}) - -const user = t.Object({ - name: t.String({ - example: 'saltyaom' - }) -}) - -export const app = new Elysia() - .use( - openapi({ - provider: 'scalar', - documentation: { - info: { - title: 'Elysia Scalar', - version: '1.3.1a' - }, - tags: [ - { - name: 'Test', - description: 'Hello' - } - ], - components: { - securitySchemes: { - bearer: { - type: 'http', - scheme: 'bearer' - }, - cookie: { - type: 'apiKey', - in: 'cookie', - name: 'session_id' - } - } - } - } - }) - ) - .model({ schema, schema2, user }) - .get( - '/', - { test: 'hello' as const }, - { - response: { - 200: t.Object({ - test: t.Literal('hello') - }), - 204: withHeaders( - t.Void({ - title: 'Thing', - description: 'Void response' - }), - { - 'X-Custom-Header': t.Literal('Elysia') - } - ) - } - } - ) - .post( - '/json', - ({ body }) => ({ - test: 'world' - }), - { - parse: ['json', 'formdata'], - body: 'user', - response: { - 200: 'schema', - 400: 'schema2' - } - } - ) - .get('/id/:id/name/:name', () => {}) - .listen(3000) +import { Elysia, t } from 'elysia' +import { openapi, withHeaders } from '../src/index' + +const schema = t.Object({ + test: t.Literal('hello') +}) + +const schema2 = t.Object({ + test: t.Literal('world') +}) + +const user = t.Object({ + name: t.String({ + example: 'saltyaom' + }) +}) + +export const app = new Elysia() + .use( + openapi({ + provider: 'scalar', + documentation: { + info: { + title: 'Elysia Scalar', + version: '1.3.1a' + }, + tags: [ + { + name: 'Test', + description: 'Hello' + } + ], + components: { + securitySchemes: { + bearer: { + type: 'http', + scheme: 'bearer' + }, + cookie: { + type: 'apiKey', + in: 'cookie', + name: 'session_id' + } + } + } + } + }) + ) + .model({ schema, schema2, user }) + .get( + '/', + { test: 'hello' as const }, + { + response: { + 200: t.Object({ + test: t.Literal('hello') + }), + 204: withHeaders( + t.Void({ + title: 'Thing', + description: 'Void response' + }), + { + 'X-Custom-Header': t.Literal('Elysia') + } + ) + } + } + ) + .post( + '/json', + ({ body }) => ({ + test: 'world' + }), + { + parse: ['json', 'formdata'], + body: 'user', + response: { + 200: 'schema', + 400: 'schema2' + } + } + ) + .get('/id/:id/name/:name', () => {}) + .listen(3000) 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 3a6e4c6..9d5921d 100644 --- a/package.json +++ b/package.json @@ -1,92 +1,92 @@ -{ - "name": "@elysiajs/openapi", - "version": "1.3.10", - "description": "Plugin for Elysia to auto-generate API documentation", - "author": { - "name": "saltyAom", - "url": "https://github.com/SaltyAom", - "email": "saltyaom@gmail.com" - }, - "main": "./dist/cjs/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/cjs/index.js" - }, - "./gen": { - "types": "./dist/gen/index.d.ts", - "import": "./dist/gen/index.mjs", - "require": "./dist/cjs/gen/index.js" - }, - "./openapi": { - "types": "./dist/openapi.d.ts", - "import": "./dist/openapi.mjs", - "require": "./dist/cjs/openapi.js" - }, - "./scalar": { - "types": "./dist/scalar/index.d.ts", - "import": "./dist/scalar/index.mjs", - "require": "./dist/cjs/scalar/index.js" - }, - "./scalar/theme": { - "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", - "openapi", - "swagger", - "scalar" - ], - "homepage": "https://github.com/elysiajs/elysia-openapi", - "repository": { - "type": "git", - "url": "https://github.com/elysiajs/elysia-openapi" - }, - "bugs": "https://github.com/elysiajs/elysia-openapi/issues", - "license": "MIT", - "scripts": { - "dev": "bun run --watch example/index.ts", - "test": "bun test && npm run test:node", - "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", - "build": "bun build.ts", - "release": "npm run build && npm run test && npm publish --access public" - }, - "peerDependencies": { - "elysia": ">= 1.3.0" - }, - "devDependencies": { - "@apidevtools/swagger-parser": "^12.0.0", - "@types/bun": "1.2.20", - "@scalar/types": "^0.2.13", - "elysia": "1.3.21", - "eslint": "9.6.0", - "tsup": "^8.5.0", - "typescript": "^5.9.2" - }, - "dependencies": { - "@sinclair/typemap": "^0.10.1", - "openapi-types": "^12.1.3" - } -} +{ + "name": "@elysiajs/openapi", + "version": "1.3.11", + "description": "Plugin for Elysia to auto-generate API documentation", + "author": { + "name": "saltyAom", + "url": "https://github.com/SaltyAom", + "email": "saltyaom@gmail.com" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/cjs/index.js" + }, + "./gen": { + "types": "./dist/gen/index.d.ts", + "import": "./dist/gen/index.mjs", + "require": "./dist/cjs/gen/index.js" + }, + "./openapi": { + "types": "./dist/openapi.d.ts", + "import": "./dist/openapi.mjs", + "require": "./dist/cjs/openapi.js" + }, + "./scalar": { + "types": "./dist/scalar/index.d.ts", + "import": "./dist/scalar/index.mjs", + "require": "./dist/cjs/scalar/index.js" + }, + "./scalar/theme": { + "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", + "openapi", + "swagger", + "scalar" + ], + "homepage": "https://github.com/elysiajs/elysia-openapi", + "repository": { + "type": "git", + "url": "https://github.com/elysiajs/elysia-openapi" + }, + "bugs": "https://github.com/elysiajs/elysia-openapi/issues", + "license": "MIT", + "scripts": { + "dev": "bun run --watch example/index.ts", + "test": "bun test && npm run test:node", + "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", + "build": "bun build.ts", + "release": "npm run build && npm run test && npm publish --access public" + }, + "peerDependencies": { + "elysia": ">= 1.3.0" + }, + "devDependencies": { + "@apidevtools/swagger-parser": "^12.0.0", + "@types/bun": "1.2.20", + "@scalar/types": "^0.2.13", + "elysia": "1.3.21", + "eslint": "9.6.0", + "tsup": "^8.5.0", + "typescript": "^5.9.2" + }, + "dependencies": { + "@sinclair/typemap": "^0.10.1", + "openapi-types": "^12.1.3" + } +} diff --git a/src/gen/index.ts b/src/gen/index.ts index 433f5b8..e1818ca 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -1,298 +1,307 @@ -import type { InputSchema, InternalRoute, TSchema } from 'elysia' -import { - readFileSync, - mkdirSync, - writeFileSync, - rmSync, - existsSync, - cpSync, - exists, - readdirSync -} from 'fs' -import { TypeBox } from '@sinclair/typemap' - -import { tmpdir } from 'os' -import { join } from 'path' -import { spawnSync } from 'child_process' -import { AdditionalReference, AdditionalReferences } from '../types' -import { Kind, TObject } from '@sinclair/typebox/type' -import { readdir } from 'fs/promises' - -const matchRoute = /: Elysia<(.*)>/gs -const matchStatus = /(\d{3}):/g -const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":') - -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 -} - -/** - * 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 - */ - targetFilePath: string, - { - tsconfigPath = 'tsconfig.json', - instanceName, - projectRoot = process.cwd(), - overrideOutputPath, - debug = false - }: OpenAPIGeneratorOptions = {} - ) => - () => { - const tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') - - try { - if ( - !targetFilePath.endsWith('.ts') && - !targetFilePath.endsWith('.tsx') - ) - throw new Error('Only .ts files are supported') - - if (targetFilePath.startsWith('./')) - targetFilePath = targetFilePath.slice(2) - - const src = targetFilePath.startsWith('/') - ? targetFilePath - : join(projectRoot, targetFilePath) - - if (!existsSync(src)) - throw new Error( - `Couldn't find "${targetFilePath}" from ${projectRoot}` - ) - - let targetFile: string - - // Since it's already a declaration file - // We can just read it directly - if (targetFilePath.endsWith('.d.ts')) targetFile = targetFilePath - else { - if (existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) - - mkdirSync(tmpRoot, { recursive: true }) - - const tsconfig = tsconfigPath.startsWith('/') - ? tsconfigPath - : join(projectRoot, tsconfigPath) - - const extendsRef = existsSync(tsconfig) - ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` - : '' - - writeFileSync( - join(tmpRoot, 'tsconfig.json'), - `{ - ${extendsRef} - "compilerOptions": { - "lib": ["ESNext"], - "module": "ESNext", - "noEmit": false, - "declaration": true, - "emitDeclarationOnly": true, - "moduleResolution": "bundler", - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "outDir": "./dist" - }, - "include": ["${src}"] -}` - ) - - spawnSync(`tsc`, { - shell: true, - cwd: tmpRoot, - stdio: debug ? 'inherit' : undefined - }) - - 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 = existsSync(targetFile) - - if (!existed && !overrideOutputPath) { - targetFile = join( - tmpRoot, - 'dist', - // use original file name as-is eg. in monorepo - fileName - ) - - existed = existsSync(targetFile) - } - - if (!existed) { - rmSync(join(tmpRoot, 'tsconfig.json')) - - console.warn( - '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' - ) - console.warn("Couldn't find generated declaration file") - - if (existsSync(join(tmpRoot, 'dist'))) { - const tempFiles = 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) - } - } - - return - } - } - - const declaration = readFileSync(targetFile, 'utf8') - - // Check just in case of race-condition - if (existsSync(tmpRoot)) - 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 - ) - ) - - const routesString = wrapStatusInQuote( - // Intentionally not adding "}" - // to avoid mismatched bracket in loop below - instance.slice(3, instance.indexOf('}, {', 4)) - ) - - const routes: AdditionalReference = {} - - // Treaty is a collection of { ... } & { ... } & { ... } - // Each route will be intersected with each other - // instead of being nested in a route object - for (const route of routesString.slice(1).split('} & {')) { - // as '} & {' is removed, we need to add it back - let schema = TypeBox(`{${route}}`) - 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()! - 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 - } catch (error) { - console.warn( - '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' - ) - console.warn(error) - - return - } finally { - if (!debug && existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) - } - } +import type { InputSchema, InternalRoute, TSchema } from 'elysia' +import { + readFileSync, + mkdirSync, + writeFileSync, + rmSync, + existsSync, + cpSync, + exists, + readdirSync +} from 'fs' +import { TypeBox } from '@sinclair/typemap' + +import { tmpdir } from 'os' +import { join } from 'path' +import { spawnSync } from 'child_process' +import { AdditionalReference, AdditionalReferences } from '../types' +import { Kind, TObject } from '@sinclair/typebox/type' +import { readdir } from 'fs/promises' + +const matchRoute = /: Elysia<(.*)>/gs +const matchStatus = /(\d{3}):/g +const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":') + +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 +} + +/** + * 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 + */ + targetFilePath: string, + { + tsconfigPath = 'tsconfig.json', + instanceName, + projectRoot = process.cwd(), + overrideOutputPath, + debug = false + }: OpenAPIGeneratorOptions = {} + ) => + () => { + const tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') + + 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 (!existsSync(src)) + throw new Error( + `Couldn't find "${targetFilePath}" from ${projectRoot}` + ) + + let targetFile: string + + // Since it's already a declaration file + // We can just read it directly + if (targetFilePath.endsWith('.d.ts')) targetFile = targetFilePath + else { + if (existsSync(tmpRoot)) + rmSync(tmpRoot, { recursive: true, force: true }) + + mkdirSync(tmpRoot, { recursive: true }) + + const tsconfig = tsconfigPath.startsWith('/') + ? tsconfigPath + : join(projectRoot, tsconfigPath) + + let extendsRef = existsSync(tsconfig) + ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` + : '' + + // Convert Windows path to Unix for TypeScript CLI + if ( + typeof process !== 'undefined' && + process.platform === 'win32' + ) { + extendsRef = extendsRef.replace(/\\/g, '/') + src = src.replace(/\\/g, '/') + } + + writeFileSync( + join(tmpRoot, 'tsconfig.json'), + `{ + ${extendsRef} + "compilerOptions": { + "lib": ["ESNext"], + "module": "ESNext", + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "outDir": "./dist" + }, + "include": ["${src}"] +}` + ) + + spawnSync(`tsc`, { + shell: true, + cwd: tmpRoot, + stdio: debug ? 'inherit' : undefined + }) + + 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 = existsSync(targetFile) + + if (!existed && !overrideOutputPath) { + targetFile = join( + tmpRoot, + 'dist', + // use original file name as-is eg. in monorepo + fileName + ) + + existed = existsSync(targetFile) + } + + if (!existed) { + rmSync(join(tmpRoot, 'tsconfig.json')) + + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn("Couldn't find generated declaration file") + + if (existsSync(join(tmpRoot, 'dist'))) { + const tempFiles = 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) + } + } + + return + } + } + + const declaration = readFileSync(targetFile, 'utf8') + + // Check just in case of race-condition + if (existsSync(tmpRoot)) + 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 + ) + ) + + const routesString = wrapStatusInQuote( + // Intentionally not adding "}" + // to avoid mismatched bracket in loop below + instance.slice(3, instance.indexOf('}, {', 4)) + ) + + const routes: AdditionalReference = {} + + // Treaty is a collection of { ... } & { ... } & { ... } + // Each route will be intersected with each other + // instead of being nested in a route object + for (const route of routesString.slice(1).split('} & {')) { + // as '} & {' is removed, we need to add it back + let schema = TypeBox(`{${route}}`) + 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()! + 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 + } catch (error) { + console.warn( + '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' + ) + console.warn(error) + + return + } finally { + if (!debug && existsSync(tmpRoot)) + rmSync(tmpRoot, { recursive: true, force: true }) + } + } diff --git a/src/index.ts b/src/index.ts index 15d5483..a13eaf1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,133 +1,133 @@ -import { Elysia, type InternalRoute } from 'elysia' - -import { SwaggerUIRender } from './swagger' -import { ScalarRender } from './scalar' - -import { toOpenAPISchema } from './openapi' - -import type { OpenAPIV3 } from 'openapi-types' -import type { ApiReferenceConfiguration } from '@scalar/types' -import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' - -/** - * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page. - * - * @see https://github.com/elysiajs/elysia-swagger - */ -export const openapi = < - const Enabled extends boolean = true, - const Path extends string = '/openapi', - const Provider extends OpenAPIProvider = 'scalar' ->({ - enabled = true as Enabled, - path = '/openapi' as Path, - provider = 'scalar' as Provider, - specPath = `${path}/json`, - documentation = {}, - exclude, - swagger, - scalar, - references -}: ElysiaOpenAPIConfig = {}) => { - if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) - - const info = { - title: 'Elysia Documentation', - description: 'Development documentation', - version: '0.0.0', - ...documentation.info - } - - const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath - - let totalRoutes = 0 - let cachedSchema: OpenAPIV3.Document | undefined - - const app = new Elysia({ name: '@elysiajs/openapi' }) - .use((app) => { - if (provider === null) return app - - return app.get( - path, - new Response( - provider === 'swagger-ui' - ? SwaggerUIRender(info, { - url: relativePath, - dom_id: '#swagger-ui', - version: 'latest', - autoDarkMode: true, - ...swagger - }) - : 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' - }), - { - headers: { - 'content-type': 'text/html; charset=utf8' - } - } - ), - { - detail: { - hide: true - } - } - ) - }) - .get( - specPath, - function openAPISchema() { - if (totalRoutes === app.routes.length) return cachedSchema - - totalRoutes = app.routes.length - - const { - paths, - components: { schemas } - } = toOpenAPISchema(app, exclude, references) - - return (cachedSchema = { - openapi: '3.0.3', - ...documentation, - tags: !exclude?.tags - ? documentation.tags - : documentation.tags?.filter( - (tag) => !exclude.tags?.includes(tag.name) - ), - 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 - } - } - } satisfies OpenAPIV3.Document) - }, - { - detail: { - hide: true - } - } - ) - - return app -} - -export { toOpenAPISchema, withHeaders } from './openapi' -export type { ElysiaOpenAPIConfig } - -export default openapi +import { Elysia, type InternalRoute } from 'elysia' + +import { SwaggerUIRender } from './swagger' +import { ScalarRender } from './scalar' + +import { toOpenAPISchema } from './openapi' + +import type { OpenAPIV3 } from 'openapi-types' +import type { ApiReferenceConfiguration } from '@scalar/types' +import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' + +/** + * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page. + * + * @see https://github.com/elysiajs/elysia-swagger + */ +export const openapi = < + const Enabled extends boolean = true, + const Path extends string = '/openapi', + const Provider extends OpenAPIProvider = 'scalar' +>({ + enabled = true as Enabled, + path = '/openapi' as Path, + provider = 'scalar' as Provider, + specPath = `${path}/json`, + documentation = {}, + exclude, + swagger, + scalar, + references +}: ElysiaOpenAPIConfig = {}) => { + if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) + + const info = { + title: 'Elysia Documentation', + description: 'Development documentation', + version: '0.0.0', + ...documentation.info + } + + const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath + + let totalRoutes = 0 + let cachedSchema: OpenAPIV3.Document | undefined + + const app = new Elysia({ name: '@elysiajs/openapi' }) + .use((app) => { + if (provider === null) return app + + return app.get( + path, + new Response( + provider === 'swagger-ui' + ? SwaggerUIRender(info, { + url: relativePath, + dom_id: '#swagger-ui', + version: 'latest', + autoDarkMode: true, + ...swagger + }) + : 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' + }), + { + headers: { + 'content-type': 'text/html; charset=utf8' + } + } + ), + { + detail: { + hide: true + } + } + ) + }) + .get( + specPath, + function openAPISchema() { + if (totalRoutes === app.routes.length) return cachedSchema + + totalRoutes = app.routes.length + + const { + paths, + components: { schemas } + } = toOpenAPISchema(app, exclude, references) + + return (cachedSchema = { + openapi: '3.0.3', + ...documentation, + tags: !exclude?.tags + ? documentation.tags + : documentation.tags?.filter( + (tag) => !exclude.tags?.includes(tag.name) + ), + 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 + } + } + } satisfies OpenAPIV3.Document) + }, + { + detail: { + hide: true + } + } + ) + + return app +} + +export { toOpenAPISchema, withHeaders } from './openapi' +export type { ElysiaOpenAPIConfig } + +export default openapi diff --git a/src/openapi.ts b/src/openapi.ts index 821b31f..31ad983 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -1,413 +1,413 @@ -import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia' -import type { HookContainer } from 'elysia/types' - -import type { OpenAPIV3 } from 'openapi-types' -import { Kind, type TProperties } from '@sinclair/typebox' - -import type { - AdditionalReference, - AdditionalReferences, - ElysiaOpenAPIConfig -} from './types' - -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 => - 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 + '/' -} - -/** - * 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 -) { - const { - methods: excludeMethods = ['OPTIONS'], - staticFile: excludeStaticFile = true, - tags: excludeTags - } = exclude ?? {} - - const excludePaths = Array.isArray(exclude?.paths) - ? exclude.paths - : typeof exclude?.paths !== 'undefined' - ? [exclude.paths] - : [] - - const paths: OpenAPIV3.PathsObject = Object.create(null) - - // @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) - 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 = {} - - if ( - !hooks.response[ - status as keyof (typeof hooks)['response'] - ] - ) - // @ts-ignore - hooks.response[status] = schema - } - } - - 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) { - if (typeof hooks.params === 'string') - hooks.params = toRef(hooks.params) - - if (hooks.params.type === 'object' && hooks.params.properties) { - for (const [paramName, paramSchema] of Object.entries( - hooks.params.properties - )) - parameters.push({ - name: paramName, - in: 'path', - required: true, // Path parameters are always required - schema: paramSchema - }) - } - } - - // Handle query parameters - if (hooks.query) { - if (typeof hooks.query === 'string') - hooks.query = toRef(hooks.query) - - if (hooks.query.type === 'object' && hooks.query.properties) { - const required = hooks.query.required || [] - for (const [queryName, querySchema] of Object.entries( - hooks.query.properties - )) - parameters.push({ - name: queryName, - in: 'query', - required: required.includes(queryName), - schema: querySchema - }) - } - } - - // Handle header parameters - if (hooks.headers) { - if (typeof hooks.headers === 'string') - hooks.headers = toRef(hooks.headers) - - if (hooks.headers.type === 'object' && hooks.headers.properties) { - const required = hooks.headers.required || [] - for (const [headerName, headerSchema] of Object.entries( - hooks.headers.properties - )) - parameters.push({ - name: headerName, - in: 'header', - required: required.includes(headerName), - schema: headerSchema - }) - } - } - - // Handle cookie parameters - if (hooks.cookie) { - if (typeof hooks.cookie === 'string') - hooks.cookie = toRef(hooks.cookie) - - if (hooks.cookie.type === 'object' && hooks.cookie.properties) { - const required = hooks.cookie.required || [] - for (const [cookieName, cookieSchema] of Object.entries( - hooks.cookie.properties - )) - parameters.push({ - name: cookieName, - in: 'cookie', - required: required.includes(cookieName), - schema: cookieSchema - }) - } - } - - // Add parameters if any exist - if (parameters.length > 0) operation.parameters = parameters - - // Handle request body - if (hooks.body && method !== 'get' && method !== 'head') { - if (typeof hooks.body === 'string') hooks.body = toRef(hooks.body) - - // @ts-ignore - if (hooks.parse) { - const content: Record = {} - - // @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: hooks.body } - continue - - case 'urlencoded': - case 'application/x-www-form-urlencoded': - content['application/x-www-form-urlencoded'] = { - schema: hooks.body - } - continue - - case 'json': - case 'application/json': - content['application/json'] = { schema: hooks.body } - continue - - case 'formdata': - case 'multipart/form-data': - content['multipart/form-data'] = { - schema: hooks.body - } - continue - } - } - - operation.requestBody = { content, required: true } - } else { - operation.requestBody = { - content: { - 'application/json': { - schema: hooks.body - }, - 'application/x-www-form-urlencoded': { - schema: hooks.body - }, - 'multipart/form-data': { - schema: hooks.body - } - }, - required: true - } - } - } - - // Handle responses - if (hooks.response) { - operation.responses = {} - - if ( - typeof hooks.response === 'object' && - !(hooks.response as TSchema).type && - !(hooks.response as TSchema).$ref - ) { - for (let [status, schema] of Object.entries(hooks.response)) { - if (typeof schema === 'string') schema = toRef(schema) - - // Must exclude $ref from root options - const { type, examples, $ref, ...options } = schema - - operation.responses[status] = { - description: `Response for status ${status}`, - ...options, - content: - type === 'void' || - type === 'null' || - type === 'undefined' - ? schema - : { - 'application/json': { - schema - } - } - } - } - } else { - if (typeof hooks.response === 'string') - hooks.response = toRef(hooks.response) - - // It's a single schema, default to 200 - operation.responses['200'] = { - description: 'Successful response', - content: { - 'application/json': { - schema: hooks.response - } - } - } - } - } - - for (let path of getPossiblePath(route.path)) { - const 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 = app.getGlobalDefinitions?.().type - - return { - components: { - schemas - }, - paths - } satisfies Pick -} - -export const withHeaders = (schema: TSchema, headers: TProperties) => - Object.assign(schema, { - headers: headers - }) +import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia' +import type { HookContainer } from 'elysia/types' + +import type { OpenAPIV3 } from 'openapi-types' +import { Kind, type TProperties } from '@sinclair/typebox' + +import type { + AdditionalReference, + AdditionalReferences, + ElysiaOpenAPIConfig +} from './types' + +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 => + 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 + '/' +} + +/** + * 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 +) { + const { + methods: excludeMethods = ['OPTIONS'], + staticFile: excludeStaticFile = true, + tags: excludeTags + } = exclude ?? {} + + const excludePaths = Array.isArray(exclude?.paths) + ? exclude.paths + : typeof exclude?.paths !== 'undefined' + ? [exclude.paths] + : [] + + const paths: OpenAPIV3.PathsObject = Object.create(null) + + // @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) + 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 = {} + + if ( + !hooks.response[ + status as keyof (typeof hooks)['response'] + ] + ) + // @ts-ignore + hooks.response[status] = schema + } + } + + 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) { + if (typeof hooks.params === 'string') + hooks.params = toRef(hooks.params) + + if (hooks.params.type === 'object' && hooks.params.properties) { + for (const [paramName, paramSchema] of Object.entries( + hooks.params.properties + )) + parameters.push({ + name: paramName, + in: 'path', + required: true, // Path parameters are always required + schema: paramSchema + }) + } + } + + // Handle query parameters + if (hooks.query) { + if (typeof hooks.query === 'string') + hooks.query = toRef(hooks.query) + + if (hooks.query.type === 'object' && hooks.query.properties) { + const required = hooks.query.required || [] + for (const [queryName, querySchema] of Object.entries( + hooks.query.properties + )) + parameters.push({ + name: queryName, + in: 'query', + required: required.includes(queryName), + schema: querySchema + }) + } + } + + // Handle header parameters + if (hooks.headers) { + if (typeof hooks.headers === 'string') + hooks.headers = toRef(hooks.headers) + + if (hooks.headers.type === 'object' && hooks.headers.properties) { + const required = hooks.headers.required || [] + for (const [headerName, headerSchema] of Object.entries( + hooks.headers.properties + )) + parameters.push({ + name: headerName, + in: 'header', + required: required.includes(headerName), + schema: headerSchema + }) + } + } + + // Handle cookie parameters + if (hooks.cookie) { + if (typeof hooks.cookie === 'string') + hooks.cookie = toRef(hooks.cookie) + + if (hooks.cookie.type === 'object' && hooks.cookie.properties) { + const required = hooks.cookie.required || [] + for (const [cookieName, cookieSchema] of Object.entries( + hooks.cookie.properties + )) + parameters.push({ + name: cookieName, + in: 'cookie', + required: required.includes(cookieName), + schema: cookieSchema + }) + } + } + + // Add parameters if any exist + if (parameters.length > 0) operation.parameters = parameters + + // Handle request body + if (hooks.body && method !== 'get' && method !== 'head') { + if (typeof hooks.body === 'string') hooks.body = toRef(hooks.body) + + // @ts-ignore + if (hooks.parse) { + const content: Record = {} + + // @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: hooks.body } + continue + + case 'urlencoded': + case 'application/x-www-form-urlencoded': + content['application/x-www-form-urlencoded'] = { + schema: hooks.body + } + continue + + case 'json': + case 'application/json': + content['application/json'] = { schema: hooks.body } + continue + + case 'formdata': + case 'multipart/form-data': + content['multipart/form-data'] = { + schema: hooks.body + } + continue + } + } + + operation.requestBody = { content, required: true } + } else { + operation.requestBody = { + content: { + 'application/json': { + schema: hooks.body + }, + 'application/x-www-form-urlencoded': { + schema: hooks.body + }, + 'multipart/form-data': { + schema: hooks.body + } + }, + required: true + } + } + } + + // Handle responses + if (hooks.response) { + operation.responses = {} + + if ( + typeof hooks.response === 'object' && + !(hooks.response as TSchema).type && + !(hooks.response as TSchema).$ref + ) { + for (let [status, schema] of Object.entries(hooks.response)) { + if (typeof schema === 'string') schema = toRef(schema) + + // Must exclude $ref from root options + const { type, examples, $ref, ...options } = schema + + operation.responses[status] = { + description: `Response for status ${status}`, + ...options, + content: + type === 'void' || + type === 'null' || + type === 'undefined' + ? schema + : { + 'application/json': { + schema + } + } + } + } + } else { + if (typeof hooks.response === 'string') + hooks.response = toRef(hooks.response) + + // It's a single schema, default to 200 + operation.responses['200'] = { + description: 'Successful response', + content: { + 'application/json': { + schema: hooks.response + } + } + } + } + } + + for (let path of getPossiblePath(route.path)) { + const 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 = app.getGlobalDefinitions?.().type + + 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 962f3fc..f01c715 100644 --- a/src/scalar/index.ts +++ b/src/scalar/index.ts @@ -1,162 +1,162 @@ -import type { OpenAPIV3 } from 'openapi-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, - config: NonNullable -) => ` - - - ${info.title} - - - - - - - - - - - -` +import type { OpenAPIV3 } from 'openapi-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, + config: NonNullable +) => ` + + + ${info.title} + + + + + + + + + + + +` diff --git a/src/swagger/index.ts b/src/swagger/index.ts index 01d3e3a..dcaa6de 100644 --- a/src/swagger/index.ts +++ b/src/swagger/index.ts @@ -1,148 +1,148 @@ -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' - ? `` - : ` -` - } - - -
- - - -` -} +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 66d2387..6812fc3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,180 +1,180 @@ -import type { TSchema } from 'elysia' -import type { OpenAPIV3 } from 'openapi-types' -import type { ApiReferenceConfiguration } from '@scalar/types' -import type { SwaggerUIOptions } from './swagger/types' - -export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null - -type MaybeArray = T | T[] - -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', - Provider extends OpenAPIProvider = 'scalar' -> { - /** - * @default true - */ - enabled?: Enabled - - /** - * OpenAPI config - * - * @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 - * - * @default 'scalar' - * @see https://github.com/scalar/scalar - * @see https://github.com/swagger-api/swagger-ui - */ - provider?: Provider - - /** - * Additional reference for each endpoint - */ - references?: AdditionalReferences - - /** - * Scalar configuration to customize scalar - *' - * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md - */ - 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 - - /** - * Options to send to SwaggerUIBundle - * Currently, options that are defined as functions such as requestInterceptor - * and onComplete are not supported. - */ - swagger?: Omit< - Partial, - | 'dom_id' - | 'dom_node' - | 'spec' - | 'url' - | 'urls' - | 'layout' - | 'pluginsOptions' - | 'plugins' - | 'presets' - | 'onComplete' - | 'requestInterceptor' - | 'responseInterceptor' - | 'modelPropertyMacro' - | 'parameterMacro' - > & { - /** - * Custom Swagger CSS - */ - theme?: - | string - | { - light: string - dark: string - } - - /** - * Version to use for swagger cdn bundle - * - * @see unpkg.com/swagger-ui-dist - * - * @default 4.18.2 - */ - version?: 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 - } -} +import type { TSchema } from 'elysia' +import type { OpenAPIV3 } from 'openapi-types' +import type { ApiReferenceConfiguration } from '@scalar/types' +import type { SwaggerUIOptions } from './swagger/types' + +export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null + +type MaybeArray = T | T[] + +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', + Provider extends OpenAPIProvider = 'scalar' +> { + /** + * @default true + */ + enabled?: Enabled + + /** + * OpenAPI config + * + * @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 + * + * @default 'scalar' + * @see https://github.com/scalar/scalar + * @see https://github.com/swagger-api/swagger-ui + */ + provider?: Provider + + /** + * Additional reference for each endpoint + */ + references?: AdditionalReferences + + /** + * Scalar configuration to customize scalar + *' + * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md + */ + 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 + + /** + * Options to send to SwaggerUIBundle + * Currently, options that are defined as functions such as requestInterceptor + * and onComplete are not supported. + */ + swagger?: Omit< + Partial, + | 'dom_id' + | 'dom_node' + | 'spec' + | 'url' + | 'urls' + | 'layout' + | 'pluginsOptions' + | 'plugins' + | 'presets' + | 'onComplete' + | 'requestInterceptor' + | 'responseInterceptor' + | 'modelPropertyMacro' + | 'parameterMacro' + > & { + /** + * Custom Swagger CSS + */ + theme?: + | string + | { + light: string + dark: string + } + + /** + * Version to use for swagger cdn bundle + * + * @see unpkg.com/swagger-ui-dist + * + * @default 4.18.2 + */ + version?: 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/test/index.test.ts b/test/index.test.ts index 9c61e47..e9593c0 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,275 +1,275 @@ -import { Elysia, t } from 'elysia' -import SwaggerParser from '@apidevtools/swagger-parser' -import { openapi } from '../src' - -import { describe, expect, it } from 'bun:test' -import { fail } from 'assert' - -const req = (path: string) => new Request(`http://localhost${path}`) - -describe('Swagger', () => { - it('show Swagger page', async () => { - const app = new Elysia().use(openapi()) - - await app.modules - - const res = await app.handle(req('/openapi')) - expect(res.status).toBe(200) - }) - - it('returns a valid OpenAPI json config', async () => { - const app = new Elysia().use(openapi()) - - await app.modules - - 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( - openapi({ - provider: 'swagger-ui', - swagger: { - version: '4.5.0' - } - }) - ) - - await app.modules - - 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' - ) - ).toBe(true) - }) - - it('follow title and description with Swagger-UI provider', async () => { - const app = new Elysia().use( - openapi({ - provider: 'swagger-ui', - swagger: { - version: '4.5.0' - }, - documentation: { - info: { - title: 'Elysia Documentation', - description: 'Herrscher of Human', - version: '1.0.0' - } - } - }) - ) - - await app.modules - - const res = await app.handle(req('/openapi')).then((x) => x.text()) - - expect(res.includes('Elysia Documentation')).toBe(true) - expect( - res.includes( - `` - ) - ).toBe(true) - }) - - it('follow title and description with Scalar provider', async () => { - const app = new Elysia().use( - openapi({ - provider: 'scalar', - scalar: { - version: '4.5.0' - }, - documentation: { - info: { - title: 'Elysia Documentation', - description: 'Herrscher of Human', - version: '1.0.0' - } - } - }) - ) - - await app.modules - - const res = await app.handle(req('/openapi')).then((x) => x.text()) - - expect(res.includes('Elysia Documentation')).toBe(true) - expect( - res.includes( - `` - ) - ).toBe(true) - }) - - it('use custom path', async () => { - const app = new Elysia().use( - openapi({ - path: '/v2/openapi' - }) - ) - - await app.modules - - const res = await app.handle(req('/v2/openapi')) - expect(res.status).toBe(200) - - const resJson = await app.handle(req('/v2/openapi/json')) - expect(resJson.status).toBe(200) - }) - - it('Swagger UI options', async () => { - const app = new Elysia().use( - openapi({ - provider: 'swagger-ui', - swagger: { - persistAuthorization: true - } - }) - ) - - await app.modules - - 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(openapi()).get('/void', () => {}, { - response: { - 204: t.Void({ - description: 'Void response' - }) - } - }) - - await app.modules - - 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).toEqual({ - description: 'Void response', - type: 'void' - }) - }) - - it('should not return content response when using Undefined type', async () => { - const app = new Elysia() - .use(openapi()) - .get('/undefined', () => undefined, { - response: { - 204: t.Undefined({ - description: 'Undefined response' - }) - } - }) - - await app.modules - - const res = await app.handle(req('/openapi/json')) - expect(res.status).toBe(200) - const response = await res.json() - expect( - response.paths['/undefined'].get.responses['204'].description - ).toBe('Undefined response') - expect( - response.paths['/undefined'].get.responses['204'].content - ).toEqual({ - type: 'undefined', - description: 'Undefined response' - }) - }) - - it('should not return content response when using Null type', async () => { - const app = new Elysia().use(openapi()).get('/null', () => null, { - response: { - 204: t.Null({ - description: 'Null response' - }) - } - }) - - await app.modules - - 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).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(openapi()).post('/post', () => {}, { - body: t.Object({ name: t.String() }) - }) - - await app.modules - - 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(openapi()).get('/id/:id?', () => {}) - - await app.modules - - 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(openapi()) - .get('/public', 'omg') - .guard({ - detail: { - hide: true - } - }) - .get('/hidden', 'ok') - - await app.modules - - 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() - }) - - it('should expand .all routes', async () => { - const app = new Elysia().use(openapi()).all('/all', 'woah') - - await app.modules - - 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) - }) -}) +import { Elysia, t } from 'elysia' +import SwaggerParser from '@apidevtools/swagger-parser' +import { openapi } from '../src' + +import { describe, expect, it } from 'bun:test' +import { fail } from 'assert' + +const req = (path: string) => new Request(`http://localhost${path}`) + +describe('Swagger', () => { + it('show Swagger page', async () => { + const app = new Elysia().use(openapi()) + + await app.modules + + const res = await app.handle(req('/openapi')) + expect(res.status).toBe(200) + }) + + it('returns a valid OpenAPI json config', async () => { + const app = new Elysia().use(openapi()) + + await app.modules + + 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( + openapi({ + provider: 'swagger-ui', + swagger: { + version: '4.5.0' + } + }) + ) + + await app.modules + + 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' + ) + ).toBe(true) + }) + + it('follow title and description with Swagger-UI provider', async () => { + const app = new Elysia().use( + openapi({ + provider: 'swagger-ui', + swagger: { + version: '4.5.0' + }, + documentation: { + info: { + title: 'Elysia Documentation', + description: 'Herrscher of Human', + version: '1.0.0' + } + } + }) + ) + + await app.modules + + const res = await app.handle(req('/openapi')).then((x) => x.text()) + + expect(res.includes('Elysia Documentation')).toBe(true) + expect( + res.includes( + `` + ) + ).toBe(true) + }) + + it('follow title and description with Scalar provider', async () => { + const app = new Elysia().use( + openapi({ + provider: 'scalar', + scalar: { + version: '4.5.0' + }, + documentation: { + info: { + title: 'Elysia Documentation', + description: 'Herrscher of Human', + version: '1.0.0' + } + } + }) + ) + + await app.modules + + const res = await app.handle(req('/openapi')).then((x) => x.text()) + + expect(res.includes('Elysia Documentation')).toBe(true) + expect( + res.includes( + `` + ) + ).toBe(true) + }) + + it('use custom path', async () => { + const app = new Elysia().use( + openapi({ + path: '/v2/openapi' + }) + ) + + await app.modules + + const res = await app.handle(req('/v2/openapi')) + expect(res.status).toBe(200) + + const resJson = await app.handle(req('/v2/openapi/json')) + expect(resJson.status).toBe(200) + }) + + it('Swagger UI options', async () => { + const app = new Elysia().use( + openapi({ + provider: 'swagger-ui', + swagger: { + persistAuthorization: true + } + }) + ) + + await app.modules + + 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(openapi()).get('/void', () => {}, { + response: { + 204: t.Void({ + description: 'Void response' + }) + } + }) + + await app.modules + + 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).toEqual({ + description: 'Void response', + type: 'void' + }) + }) + + it('should not return content response when using Undefined type', async () => { + const app = new Elysia() + .use(openapi()) + .get('/undefined', () => undefined, { + response: { + 204: t.Undefined({ + description: 'Undefined response' + }) + } + }) + + await app.modules + + const res = await app.handle(req('/openapi/json')) + expect(res.status).toBe(200) + const response = await res.json() + expect( + response.paths['/undefined'].get.responses['204'].description + ).toBe('Undefined response') + expect( + response.paths['/undefined'].get.responses['204'].content + ).toEqual({ + type: 'undefined', + description: 'Undefined response' + }) + }) + + it('should not return content response when using Null type', async () => { + const app = new Elysia().use(openapi()).get('/null', () => null, { + response: { + 204: t.Null({ + description: 'Null response' + }) + } + }) + + await app.modules + + 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).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(openapi()).post('/post', () => {}, { + body: t.Object({ name: t.String() }) + }) + + await app.modules + + 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(openapi()).get('/id/:id?', () => {}) + + await app.modules + + 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(openapi()) + .get('/public', 'omg') + .guard({ + detail: { + hide: true + } + }) + .get('/hidden', 'ok') + + await app.modules + + 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() + }) + + it('should expand .all routes', async () => { + const app = new Elysia().use(openapi()).all('/all', 'woah') + + await app.modules + + 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) + }) +}) diff --git a/test/node/cjs/index.js b/test/node/cjs/index.js index e963635..512725e 100644 --- a/test/node/cjs/index.js +++ b/test/node/cjs/index.js @@ -1,7 +1,7 @@ -if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') - -const { openapi } = require('@elysiajs/openapi') - -if (typeof openapi !== 'function') throw new Error('❌ CommonJS Node.js failed') - -console.log('✅ CommonJS Node.js works!') +if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') + +const { openapi } = require('@elysiajs/openapi') + +if (typeof openapi !== 'function') throw new Error('❌ CommonJS Node.js failed') + +console.log('✅ CommonJS Node.js works!') diff --git a/test/node/cjs/package.json b/test/node/cjs/package.json index e05f237..69161da 100644 --- a/test/node/cjs/package.json +++ b/test/node/cjs/package.json @@ -1,6 +1,6 @@ { "type": "commonjs", "dependencies": { - "@elysiajs/openapi": "../../.." + "@elysiajs/openapi": "file:../../.." } } diff --git a/test/node/esm/index.js b/test/node/esm/index.js index 7212dd3..dd80c27 100644 --- a/test/node/esm/index.js +++ b/test/node/esm/index.js @@ -1,7 +1,7 @@ -if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') - -import { openapi } from '@elysiajs/openapi' - -if (typeof openapi !== 'function') throw new Error('❌ ESM Node.js failed') - -console.log('✅ ESM Node.js works!') +if ('Bun' in globalThis) throw new Error('❌ Use Node.js to run this test!') + +import { openapi } from '@elysiajs/openapi' + +if (typeof openapi !== 'function') throw new Error('❌ ESM Node.js failed') + +console.log('✅ ESM Node.js works!') diff --git a/test/node/esm/package.json b/test/node/esm/package.json index 56bc298..c4bea16 100644 --- a/test/node/esm/package.json +++ b/test/node/esm/package.json @@ -1,6 +1,6 @@ { "type": "module", "dependencies": { - "@elysiajs/openapi": "../../.." + "@elysiajs/openapi": "file:../../.." } } diff --git a/test/openapi.test.ts b/test/openapi.test.ts index 4af691c..1a54bad 100644 --- a/test/openapi.test.ts +++ b/test/openapi.test.ts @@ -1,14 +1,14 @@ -import { describe, it, expect } from 'bun:test' -import { getPossiblePath } from '../src/openapi' - -describe('OpenAPI utilities', () => { - it('getPossiblePath', () => { - expect(getPossiblePath('/user/:user?/name/:name?')).toEqual([ - '/user/:user/name/:name', - '/user/name/:name', - '/user/name', - '/user/:user/name', - '/user/name' - ]) - }) -}) +import { describe, it, expect } from 'bun:test' +import { getPossiblePath } from '../src/openapi' + +describe('OpenAPI utilities', () => { + it('getPossiblePath', () => { + 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/validate-schema.test.ts b/test/validate-schema.test.ts index 05991b6..64bf96e 100644 --- a/test/validate-schema.test.ts +++ b/test/validate-schema.test.ts @@ -1,84 +1,84 @@ -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)) -}) +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 3fc33fe..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": "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/**/*"] -} +{ + "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 ebd1539..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": "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/**/*"] -} +{ + "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/**/*"] +} From 5e54ce2f6bd96cc5cc9fe00633382258057309fa Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 10 Sep 2025 09:19:06 +0700 Subject: [PATCH 26/58] :tada: feat: use absolute path to generate declaration file --- CHANGELOG.md | 7 +++++++ package.json | 2 +- src/gen/index.ts | 47 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbc4a55..cc6eeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.3.11 - 10 Sep 2025 +Improvement: +- type generator: add `compilerOptions`, `tmpRoot` options + +Bug fix: +- type generator: use absolute path to generate types + # 1.3.10 - 5 Sep 2025 Feature: - type generator: accept `.d.ts` to prevent type generation in production diff --git a/package.json b/package.json index 3a6e4c6..c3660ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.3.10", + "version": "1.3.11", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index 433f5b8..da2e46f 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -58,6 +58,23 @@ interface OpenAPIGeneratorOptions { * @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 } /** @@ -80,12 +97,12 @@ export const fromTypes = instanceName, projectRoot = process.cwd(), overrideOutputPath, - debug = false + debug = false, + compilerOptions, + tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') }: OpenAPIGeneratorOptions = {} ) => () => { - const tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') - try { if ( !targetFilePath.endsWith('.ts') && @@ -128,16 +145,20 @@ export const fromTypes = join(tmpRoot, 'tsconfig.json'), `{ ${extendsRef} - "compilerOptions": { - "lib": ["ESNext"], - "module": "ESNext", - "noEmit": false, - "declaration": true, - "emitDeclarationOnly": true, - "moduleResolution": "bundler", - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "outDir": "./dist" + "compilerOptions": ${ + compilerOptions + ? JSON.stringify(compilerOptions) + : `{ + "lib": ["ESNext"], + "module": "ESNext", + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "outDir": "${join(tmpRoot, 'dist')}" +}` }, "include": ["${src}"] }` From 9390b8ada2438a52931871de916aee16bf6f254d Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 10 Sep 2025 09:25:59 +0700 Subject: [PATCH 27/58] :tada: feat: use absolute path to generate declaration file --- src/gen/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gen/index.ts b/src/gen/index.ts index edaea94..c831235 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -234,6 +234,11 @@ export const fromTypes = ) console.warn(tempFiles) } + } else { + console.log( + "reason: root folter doesn't exists", + join(tmpRoot, 'dist') + ) } return From 005fbee458f50b4d02369acf83e477c59038c8e3 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 11 Sep 2025 05:50:55 +0700 Subject: [PATCH 28/58] :tada: feat: standard schema --- CHANGELOG.md | 5 + bun.lock | 1082 +++++++++++++++++++++++----------------------- example/index.ts | 226 ++++++---- package.json | 180 ++++---- src/gen/index.ts | 2 +- src/index.ts | 266 ++++++------ src/openapi.ts | 911 ++++++++++++++++++++------------------ 7 files changed, 1415 insertions(+), 1257 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68cb911..a03597b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.4.0 +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 diff --git a/bun.lock b/bun.lock index 8ef722e..1be3316 100644 --- a/bun.lock +++ b/bun.lock @@ -1,537 +1,545 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@elysiajs/swagger", - "dependencies": { - "@sinclair/typemap": "^0.10.1", - "openapi-types": "^12.1.3", - }, - "devDependencies": { - "@apidevtools/swagger-parser": "^12.0.0", - "@scalar/types": "^0.2.13", - "@types/bun": "1.2.20", - "elysia": "1.3.21", - "eslint": "9.6.0", - "tsup": "^8.5.0", - "typescript": "^5.9.2", - }, - "peerDependencies": { - "elysia": ">= 1.3.0", - }, - }, - }, - "packages": { - "@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@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/android-arm": ["@esbuild/android-arm@0.25.3", "", { "os": "android", "cpu": "arm" }, "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.3", "", { "os": "android", "cpu": "arm64" }, "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.3", "", { "os": "android", "cpu": "x64" }, "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.3", "", { "os": "linux", "cpu": "arm" }, "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64" }, "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.3", "", { "os": "none", "cpu": "arm64" }, "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.3", "", { "os": "none", "cpu": "x64" }, "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="], - - "@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=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], - - "@eslint/config-array": ["@eslint/config-array@0.17.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "@eslint/js": ["@eslint/js@9.6.0", "", {}, "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], - - "@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/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.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=="], - - "@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=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@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-arm64": ["@rollup/rollup-android-arm64@4.40.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q=="], - - "@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-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.1", "", { "os": "linux", "cpu": "arm" }, "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg=="], - - "@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-musl": ["@rollup/rollup-linux-arm64-musl@4.40.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ=="], - - "@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-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg=="], - - "@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-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA=="], - - "@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-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ=="], - - "@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-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg=="], - - "@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-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA=="], - - "@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="], - - "@scalar/types": ["@scalar/types@0.2.13", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-rO6KGMJqOsBnN/2R4fErMFLpRSPVJElni+HABDpf+ZlLJp2lvxuPn0IXLumK5ytfplUH9iqKgSXjndnZfxSYLQ=="], - - "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], - - "@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=="], - - "@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=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], - - "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], - - "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], - - "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - - "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "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=="], - - "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=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "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=="], - - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - - "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "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.21", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.6", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-LLfDSoVA5fBoqKQfMJyzmHLkya8zMbEYwd7DS7v2iQB706mgzWg0gufXl58cFALErcvSayplrkDvjkmlYTkIZQ=="], - - "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=="], - - "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-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], - - "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=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "exact-mirror": ["exact-mirror@0.1.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-EXGDixoDotCGrXCce63zmGHDA+3Id6PPkIwshBHuB10dwVc4YV4gfaYLuysHOxyURmwyt4UL186ann0oYa2CFQ=="], - - "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], - - "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=="], - - "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=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], - - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], - - "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=="], - - "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "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@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=="], - - "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=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - - "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=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["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=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "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=="], - - "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "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=="], - - "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=="], - - "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "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=="], - - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - - "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], - - "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], - - "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "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=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], - - "@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=="], - - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "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=="], - - "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "mlly/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - - "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/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - - "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=="], - - "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=="], - - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - - "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=="], - } -} +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@elysiajs/swagger", + "dependencies": { + "@sinclair/typemap": "^0.10.1", + "openapi-types": "^12.1.3", + "xsschema": "^0.4.0-beta.3", + }, + "devDependencies": { + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@types/bun": "1.2.20", + "elysia": "1.4.0-exp.16", + "eslint": "9.6.0", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "zod": "^4.1.5", + }, + "peerDependencies": { + "elysia": ">= 1.3.0", + }, + }, + }, + "packages": { + "@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@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/android-arm": ["@esbuild/android-arm@0.25.3", "", { "os": "android", "cpu": "arm" }, "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.3", "", { "os": "android", "cpu": "arm64" }, "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.3", "", { "os": "android", "cpu": "x64" }, "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.3", "", { "os": "linux", "cpu": "arm" }, "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64" }, "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.3", "", { "os": "none", "cpu": "arm64" }, "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.3", "", { "os": "none", "cpu": "x64" }, "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="], + + "@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=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.17.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.6.0", "", {}, "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@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/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.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=="], + + "@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=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@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-arm64": ["@rollup/rollup-android-arm64@4.40.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q=="], + + "@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-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.1", "", { "os": "linux", "cpu": "arm" }, "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg=="], + + "@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-musl": ["@rollup/rollup-linux-arm64-musl@4.40.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ=="], + + "@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-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg=="], + + "@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-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA=="], + + "@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-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ=="], + + "@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-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg=="], + + "@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-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA=="], + + "@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="], + + "@scalar/types": ["@scalar/types@0.2.13", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-rO6KGMJqOsBnN/2R4fErMFLpRSPVJElni+HABDpf+ZlLJp2lvxuPn0IXLumK5ytfplUH9iqKgSXjndnZfxSYLQ=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], + + "@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=="], + + "@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=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], + + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "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=="], + + "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=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "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=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "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.4.0-exp.16", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-b0wOFVnm72zY0wYhX2/mBZKiUQHd8gOu6bV5XsfGiOaGPVJ6aogryb7XCpJ3uu6fJAsb5SnQO+wA2qyJ7jtudg=="], + + "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=="], + + "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-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + + "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=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="], + + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + + "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=="], + + "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=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], + + "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=="], + + "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "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@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=="], + + "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=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "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=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["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=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "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=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "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=="], + + "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=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "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=="], + + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + + "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "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=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "xsschema": ["xsschema@0.4.0-beta.3", "", { "peerDependencies": { "@valibot/to-json-schema": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.16.0", "sury": "^10.0.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "zod", "zod-to-json-schema"] }, "sha512-+vKxPksAH0QDk2YXRT25LY+Cn/lGJHiwC88Hb9BNl76Gso7VZYoOsq0T6nSoeSxKjyWRYCGmS6anrOhDQknasw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@4.1.5", "", {}, "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg=="], + + "@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=="], + + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@scalar/openapi-types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], + + "@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=="], + + "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "mlly/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "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/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "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=="], + + "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=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "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=="], + } +} diff --git a/example/index.ts b/example/index.ts index 7e263e3..6dffe53 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,85 +1,141 @@ -import { Elysia, t } from 'elysia' -import { openapi, withHeaders } from '../src/index' - -const schema = t.Object({ - test: t.Literal('hello') -}) - -const schema2 = t.Object({ - test: t.Literal('world') -}) - -const user = t.Object({ - name: t.String({ - example: 'saltyaom' - }) -}) - -export const app = new Elysia() - .use( - openapi({ - provider: 'scalar', - documentation: { - info: { - title: 'Elysia Scalar', - version: '1.3.1a' - }, - tags: [ - { - name: 'Test', - description: 'Hello' - } - ], - components: { - securitySchemes: { - bearer: { - type: 'http', - scheme: 'bearer' - }, - cookie: { - type: 'apiKey', - in: 'cookie', - name: 'session_id' - } - } - } - } - }) - ) - .model({ schema, schema2, user }) - .get( - '/', - { test: 'hello' as const }, - { - response: { - 200: t.Object({ - test: t.Literal('hello') - }), - 204: withHeaders( - t.Void({ - title: 'Thing', - description: 'Void response' - }), - { - 'X-Custom-Header': t.Literal('Elysia') - } - ) - } - } - ) - .post( - '/json', - ({ body }) => ({ - test: 'world' - }), - { - parse: ['json', 'formdata'], - body: 'user', - response: { - 200: 'schema', - 400: 'schema2' - } - } - ) - .get('/id/:id/name/:name', () => {}) - .listen(3000) +import { Elysia, t } from 'elysia' +import z from 'zod' + +import { openapi, withHeaders } from '../src/index' + +const schema = t.Object({ + test: t.Literal('hello') +}) + +const schema2 = t.Object({ + test: t.Literal('world') +}) + +const user = t.Object({ + name: t.String({ + example: 'saltyaom' + }) +}) + +export const app = new Elysia() + .use( + openapi({ + provider: 'scalar', + documentation: { + info: { + title: 'Elysia Scalar', + version: '1.3.1a' + }, + tags: [ + { + name: 'Test', + description: 'Hello' + } + ], + components: { + securitySchemes: { + bearer: { + type: 'http', + scheme: 'bearer' + }, + cookie: { + type: 'apiKey', + in: 'cookie', + name: 'session_id' + } + } + } + } + }) + ) + .model({ schema, schema2, user }) + .get( + '/', + { test: 'hello' as const }, + { + response: { + 200: t.Object({ + test: t.Literal('hello') + }), + 204: withHeaders( + t.Void({ + title: 'Thing', + description: 'Void response' + }), + { + 'X-Custom-Header': t.Literal('Elysia') + } + ) + } + } + ) + .post( + '/json', + ({ body }) => ({ + test: 'world' + }), + { + parse: ['json', 'formdata'], + body: 'schema', + response: { + 200: 'schema', + 400: z.object({ + a: z.string(), + b: z.literal('a') + }) + } + } + ) + .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' } + ) + } + ) + .get('/id/:id/name/:name', () => {}) + .listen(3000) diff --git a/package.json b/package.json index 4d4855f..f871e68 100644 --- a/package.json +++ b/package.json @@ -1,92 +1,96 @@ { - "name": "@elysiajs/openapi", - "version": "1.3.12", - "description": "Plugin for Elysia to auto-generate API documentation", - "author": { - "name": "saltyAom", - "url": "https://github.com/SaltyAom", - "email": "saltyaom@gmail.com" - }, - "main": "./dist/cjs/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/cjs/index.js" - }, - "./gen": { - "types": "./dist/gen/index.d.ts", + "name": "@elysiajs/openapi", + "version": "1.4.0-exp.0", + "description": "Plugin for Elysia to auto-generate API documentation", + "author": { + "name": "saltyAom", + "url": "https://github.com/SaltyAom", + "email": "saltyaom@gmail.com" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/cjs/index.js" + }, + "./gen": { + "types": "./dist/gen/index.d.ts", "import": "./dist/gen/index.mjs", "require": "./dist/cjs/gen/index.js" - }, - "./openapi": { - "types": "./dist/openapi.d.ts", - "import": "./dist/openapi.mjs", - "require": "./dist/cjs/openapi.js" - }, - "./scalar": { - "types": "./dist/scalar/index.d.ts", - "import": "./dist/scalar/index.mjs", - "require": "./dist/cjs/scalar/index.js" - }, - "./scalar/theme": { - "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", - "openapi", - "swagger", - "scalar" - ], - "homepage": "https://github.com/elysiajs/elysia-openapi", - "repository": { - "type": "git", - "url": "https://github.com/elysiajs/elysia-openapi" - }, - "bugs": "https://github.com/elysiajs/elysia-openapi/issues", - "license": "MIT", - "scripts": { - "dev": "bun run --watch example/index.ts", - "test": "bun test && npm run test:node", - "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", - "build": "bun build.ts", - "release": "npm run build && npm run test && npm publish --access public" - }, - "peerDependencies": { - "elysia": ">= 1.3.0" - }, - "devDependencies": { - "@apidevtools/swagger-parser": "^12.0.0", - "@types/bun": "1.2.20", - "@scalar/types": "^0.2.13", - "elysia": "1.3.21", - "eslint": "9.6.0", - "tsup": "^8.5.0", - "typescript": "^5.9.2" - }, - "dependencies": { - "@sinclair/typemap": "^0.10.1", - "openapi-types": "^12.1.3" - } + }, + "./openapi": { + "types": "./dist/openapi.d.ts", + "import": "./dist/openapi.mjs", + "require": "./dist/cjs/openapi.js" + }, + "./scalar": { + "types": "./dist/scalar/index.d.ts", + "import": "./dist/scalar/index.mjs", + "require": "./dist/cjs/scalar/index.js" + }, + "./scalar/theme": { + "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", + "openapi", + "swagger", + "scalar" + ], + "homepage": "https://github.com/elysiajs/elysia-openapi", + "repository": { + "type": "git", + "url": "https://github.com/elysiajs/elysia-openapi" + }, + "bugs": "https://github.com/elysiajs/elysia-openapi/issues", + "license": "MIT", + "scripts": { + "dev": "bun run --watch example/index.ts", + "test": "bun test && npm run test:node", + "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", + "build": "bun build.ts", + "release": "npm run build && npm run test && npm publish --access public" + }, + "dependencies": {}, + "devDependencies": { + "@sinclair/typemap": "^0.10.1", + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@types/bun": "1.2.20", + "elysia": "1.4.0-exp.16", + "eslint": "9.6.0", + "openapi-types": "^12.1.3", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "xsschema": "^0.4.0-beta.3", + "zod": "^4.1.5" + }, + "peerDependencies": { + "@sinclair/typemap": ">= 0.10.0", + "elysia": ">= 1.3.0", + "openapi-types": ">= 12.0.0", + "xsschema": ">= 0.4.0-beta.0" + } } diff --git a/src/gen/index.ts b/src/gen/index.ts index c831235..eb0f1c5 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -236,7 +236,7 @@ export const fromTypes = } } else { console.log( - "reason: root folter doesn't exists", + "reason: root folder doesn't exists", join(tmpRoot, 'dist') ) } diff --git a/src/index.ts b/src/index.ts index a13eaf1..e5a3cf1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,133 +1,133 @@ -import { Elysia, type InternalRoute } from 'elysia' - -import { SwaggerUIRender } from './swagger' -import { ScalarRender } from './scalar' - -import { toOpenAPISchema } from './openapi' - -import type { OpenAPIV3 } from 'openapi-types' -import type { ApiReferenceConfiguration } from '@scalar/types' -import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' - -/** - * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page. - * - * @see https://github.com/elysiajs/elysia-swagger - */ -export const openapi = < - const Enabled extends boolean = true, - const Path extends string = '/openapi', - const Provider extends OpenAPIProvider = 'scalar' ->({ - enabled = true as Enabled, - path = '/openapi' as Path, - provider = 'scalar' as Provider, - specPath = `${path}/json`, - documentation = {}, - exclude, - swagger, - scalar, - references -}: ElysiaOpenAPIConfig = {}) => { - if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) - - const info = { - title: 'Elysia Documentation', - description: 'Development documentation', - version: '0.0.0', - ...documentation.info - } - - const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath - - let totalRoutes = 0 - let cachedSchema: OpenAPIV3.Document | undefined - - const app = new Elysia({ name: '@elysiajs/openapi' }) - .use((app) => { - if (provider === null) return app - - return app.get( - path, - new Response( - provider === 'swagger-ui' - ? SwaggerUIRender(info, { - url: relativePath, - dom_id: '#swagger-ui', - version: 'latest', - autoDarkMode: true, - ...swagger - }) - : 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' - }), - { - headers: { - 'content-type': 'text/html; charset=utf8' - } - } - ), - { - detail: { - hide: true - } - } - ) - }) - .get( - specPath, - function openAPISchema() { - if (totalRoutes === app.routes.length) return cachedSchema - - totalRoutes = app.routes.length - - const { - paths, - components: { schemas } - } = toOpenAPISchema(app, exclude, references) - - return (cachedSchema = { - openapi: '3.0.3', - ...documentation, - tags: !exclude?.tags - ? documentation.tags - : documentation.tags?.filter( - (tag) => !exclude.tags?.includes(tag.name) - ), - 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 - } - } - } satisfies OpenAPIV3.Document) - }, - { - detail: { - hide: true - } - } - ) - - return app -} - -export { toOpenAPISchema, withHeaders } from './openapi' -export type { ElysiaOpenAPIConfig } - -export default openapi +import { Elysia } from 'elysia' + +import { SwaggerUIRender } from './swagger' +import { ScalarRender } from './scalar' + +import { toOpenAPISchema } from './openapi' + +import type { OpenAPIV3 } from 'openapi-types' +import type { ApiReferenceConfiguration } from '@scalar/types' +import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' + +/** + * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page. + * + * @see https://github.com/elysiajs/elysia-swagger + */ +export const openapi = < + const Enabled extends boolean = true, + const Path extends string = '/openapi', + const Provider extends OpenAPIProvider = 'scalar' +>({ + enabled = true as Enabled, + path = '/openapi' as Path, + provider = 'scalar' as Provider, + specPath = `${path}/json`, + documentation = {}, + exclude, + swagger, + scalar, + references +}: ElysiaOpenAPIConfig = {}) => { + if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) + + const info = { + title: 'Elysia Documentation', + description: 'Development documentation', + version: '0.0.0', + ...documentation.info + } + + const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath + + let totalRoutes = 0 + let cachedSchema: OpenAPIV3.Document | undefined + + const app = new Elysia({ name: '@elysiajs/openapi' }) + .use((app) => { + if (provider === null) return app + + return app.get( + path, + new Response( + provider === 'swagger-ui' + ? SwaggerUIRender(info, { + url: relativePath, + dom_id: '#swagger-ui', + version: 'latest', + autoDarkMode: true, + ...swagger + }) + : 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' + }), + { + headers: { + 'content-type': 'text/html; charset=utf8' + } + } + ), + { + detail: { + hide: true + } + } + ) + }) + .get( + specPath, + async function openAPISchema() { + if (totalRoutes === app.routes.length) return cachedSchema + + totalRoutes = app.routes.length + + const { + paths, + components: { schemas } + } = await toOpenAPISchema(app, exclude, references) + + return (cachedSchema = { + openapi: '3.0.3', + ...documentation, + tags: !exclude?.tags + ? documentation.tags + : documentation.tags?.filter( + (tag) => !exclude.tags?.includes(tag.name) + ), + 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) + } + } + } satisfies OpenAPIV3.Document) + }, + { + detail: { + hide: true + } + } + ) + + return app +} + +export { toOpenAPISchema, withHeaders } from './openapi' +export type { ElysiaOpenAPIConfig } + +export default openapi diff --git a/src/openapi.ts b/src/openapi.ts index 31ad983..cfcaca7 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -1,413 +1,498 @@ -import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia' -import type { HookContainer } from 'elysia/types' - -import type { OpenAPIV3 } from 'openapi-types' -import { Kind, type TProperties } from '@sinclair/typebox' - -import type { - AdditionalReference, - AdditionalReferences, - ElysiaOpenAPIConfig -} from './types' - -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 => - 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 + '/' -} - -/** - * 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 -) { - const { - methods: excludeMethods = ['OPTIONS'], - staticFile: excludeStaticFile = true, - tags: excludeTags - } = exclude ?? {} - - const excludePaths = Array.isArray(exclude?.paths) - ? exclude.paths - : typeof exclude?.paths !== 'undefined' - ? [exclude.paths] - : [] - - const paths: OpenAPIV3.PathsObject = Object.create(null) - - // @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) - 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 = {} - - if ( - !hooks.response[ - status as keyof (typeof hooks)['response'] - ] - ) - // @ts-ignore - hooks.response[status] = schema - } - } - - 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) { - if (typeof hooks.params === 'string') - hooks.params = toRef(hooks.params) - - if (hooks.params.type === 'object' && hooks.params.properties) { - for (const [paramName, paramSchema] of Object.entries( - hooks.params.properties - )) - parameters.push({ - name: paramName, - in: 'path', - required: true, // Path parameters are always required - schema: paramSchema - }) - } - } - - // Handle query parameters - if (hooks.query) { - if (typeof hooks.query === 'string') - hooks.query = toRef(hooks.query) - - if (hooks.query.type === 'object' && hooks.query.properties) { - const required = hooks.query.required || [] - for (const [queryName, querySchema] of Object.entries( - hooks.query.properties - )) - parameters.push({ - name: queryName, - in: 'query', - required: required.includes(queryName), - schema: querySchema - }) - } - } - - // Handle header parameters - if (hooks.headers) { - if (typeof hooks.headers === 'string') - hooks.headers = toRef(hooks.headers) - - if (hooks.headers.type === 'object' && hooks.headers.properties) { - const required = hooks.headers.required || [] - for (const [headerName, headerSchema] of Object.entries( - hooks.headers.properties - )) - parameters.push({ - name: headerName, - in: 'header', - required: required.includes(headerName), - schema: headerSchema - }) - } - } - - // Handle cookie parameters - if (hooks.cookie) { - if (typeof hooks.cookie === 'string') - hooks.cookie = toRef(hooks.cookie) - - if (hooks.cookie.type === 'object' && hooks.cookie.properties) { - const required = hooks.cookie.required || [] - for (const [cookieName, cookieSchema] of Object.entries( - hooks.cookie.properties - )) - parameters.push({ - name: cookieName, - in: 'cookie', - required: required.includes(cookieName), - schema: cookieSchema - }) - } - } - - // Add parameters if any exist - if (parameters.length > 0) operation.parameters = parameters - - // Handle request body - if (hooks.body && method !== 'get' && method !== 'head') { - if (typeof hooks.body === 'string') hooks.body = toRef(hooks.body) - - // @ts-ignore - if (hooks.parse) { - const content: Record = {} - - // @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: hooks.body } - continue - - case 'urlencoded': - case 'application/x-www-form-urlencoded': - content['application/x-www-form-urlencoded'] = { - schema: hooks.body - } - continue - - case 'json': - case 'application/json': - content['application/json'] = { schema: hooks.body } - continue - - case 'formdata': - case 'multipart/form-data': - content['multipart/form-data'] = { - schema: hooks.body - } - continue - } - } - - operation.requestBody = { content, required: true } - } else { - operation.requestBody = { - content: { - 'application/json': { - schema: hooks.body - }, - 'application/x-www-form-urlencoded': { - schema: hooks.body - }, - 'multipart/form-data': { - schema: hooks.body - } - }, - required: true - } - } - } - - // Handle responses - if (hooks.response) { - operation.responses = {} - - if ( - typeof hooks.response === 'object' && - !(hooks.response as TSchema).type && - !(hooks.response as TSchema).$ref - ) { - for (let [status, schema] of Object.entries(hooks.response)) { - if (typeof schema === 'string') schema = toRef(schema) - - // Must exclude $ref from root options - const { type, examples, $ref, ...options } = schema - - operation.responses[status] = { - description: `Response for status ${status}`, - ...options, - content: - type === 'void' || - type === 'null' || - type === 'undefined' - ? schema - : { - 'application/json': { - schema - } - } - } - } - } else { - if (typeof hooks.response === 'string') - hooks.response = toRef(hooks.response) - - // It's a single schema, default to 200 - operation.responses['200'] = { - description: 'Successful response', - content: { - 'application/json': { - schema: hooks.response - } - } - } - } - } - - for (let path of getPossiblePath(route.path)) { - const 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 = app.getGlobalDefinitions?.().type - - return { - components: { - schemas - }, - paths - } satisfies Pick -} - -export const withHeaders = (schema: TSchema, headers: TProperties) => - Object.assign(schema, { - headers: headers - }) +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, type TProperties } from '@sinclair/typebox' + +import { toJsonSchema } from 'xsschema' + +import type { + AdditionalReference, + AdditionalReferences, + ElysiaOpenAPIConfig +} from './types' + +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 => + 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 + '/' +} + +type MaybePromise = T | Promise + +export const unwrapSchema = ( + schema: InputSchema['body'] +): MaybePromise => { + if (!schema) return + + if (typeof schema === 'string') schema = toRef(schema) + if (Kind in schema) return schema + + if (Kind in schema === false && schema['~standard']) + return toJsonSchema(schema as any) as Promise +} + +/** + * Converts Elysia routes to OpenAPI 3.0.3 paths schema + * @param routes Array of Elysia route objects + * @returns OpenAPI paths object + */ +export async function toOpenAPISchema( + app: AnyElysia, + exclude?: ElysiaOpenAPIConfig['exclude'], + references?: AdditionalReferences +) { + const { + methods: excludeMethods = ['OPTIONS'], + staticFile: excludeStaticFile = true, + tags: excludeTags + } = exclude ?? {} + + const excludePaths = Array.isArray(exclude?.paths) + ? exclude.paths + : typeof exclude?.paths !== 'undefined' + ? [exclude.paths] + : [] + + const paths: OpenAPIV3.PathsObject = Object.create(null) + + // @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) + 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 = {} + + if ( + !hooks.response[ + status as keyof (typeof hooks)['response'] + ] + ) + // @ts-ignore + hooks.response[status] = schema + } + } + + 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) { + let params = unwrapSchema(hooks.params) + if (params) params = await params + + if (params && params.type === 'object' && params.properties) + for (const [paramName, paramSchema] of Object.entries( + params.properties + )) + parameters.push({ + name: paramName, + in: 'path', + required: true, // Path parameters are always required + schema: paramSchema + }) + } + + // Handle query parameters + if (hooks.query) { + let query = unwrapSchema(hooks.query) + if (query) query = await query + + if (query && query.type === 'object' && query.properties) { + const required = query.required || [] + for (const [queryName, querySchema] of Object.entries( + query.properties + )) + parameters.push({ + name: queryName, + in: 'query', + required: required.includes(queryName), + schema: querySchema + }) + } + } + + // Handle header parameters + if (hooks.headers) { + let headers = unwrapSchema(hooks.query) + if (headers) headers = await headers + + if (headers && headers.type === 'object' && headers.properties) { + const required = headers.required || [] + for (const [headerName, headerSchema] of Object.entries( + headers.properties + )) + parameters.push({ + name: headerName, + in: 'header', + required: required.includes(headerName), + schema: headerSchema + }) + } + } + + // Handle cookie parameters + if (hooks.cookie) { + let cookie = unwrapSchema(hooks.cookie) + if (cookie) cookie = await cookie + + if (cookie && cookie.type === 'object' && cookie.properties) { + const required = cookie.required || [] + for (const [cookieName, cookieSchema] of Object.entries( + cookie.properties + )) + parameters.push({ + name: cookieName, + in: 'cookie', + required: required.includes(cookieName), + schema: cookieSchema + }) + } + } + + // Add parameters if any exist + if (parameters.length > 0) operation.parameters = parameters + + // Handle request body + if (hooks.body && method !== 'get' && method !== 'head') { + let body = unwrapSchema(hooks.body) + if (body) body = await body + + if (body) { + // @ts-ignore + const { type: _type, description, ...options } = body + const type = _type as string | undefined + + // @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, + content: + type === 'string' || + type === 'number' || + type === 'integer' || + type === 'boolean' + ? { + 'text/plain': body + } + : { + 'application/json': { + schema: body + }, + 'application/x-www-form-urlencoded': { + schema: body + }, + 'multipart/form-data': { + schema: body + } + }, + required: true + } + } + } + } + + // Handle responses + if (hooks.response) { + operation.responses = {} + + if ( + typeof hooks.response === 'object' && + !(hooks.response as TSchema).type && + !(hooks.response as TSchema).$ref + ) { + for (let [status, schema] of Object.entries(hooks.response)) { + let response = unwrapSchema(schema) + if (response) response = await response + + if (!response) continue + + // @ts-ignore Must exclude $ref from root options + const { type: _type, description, ...options } = response + const type = _type as string | undefined + + operation.responses[status] = { + description: + description ?? `Response for status ${status}`, + ...options, + content: + type === 'void' || + type === 'null' || + type === 'undefined' + ? (response as any) + : type === 'string' || + type === 'number' || + type === 'integer' || + type === 'boolean' + ? { + 'text/plain': { + schema: response + } + } + : { + 'application/json': { + schema: response + } + } + } + } + } else { + let response = unwrapSchema(hooks.response as any) + if (response) response = await response + + if (response) { + // @ts-ignore + const { type: _type, description, ...options } = response + 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' + ? (response 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 = 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 = app.getGlobalDefinitions?.().type + + const schemas = Object.create(null) + + if (_schemas) + for (const [name, schema] of Object.entries(_schemas)) { + let jsonSchema = unwrapSchema(schema as any) as + | OpenAPIV3.SchemaObject + | undefined + + if (jsonSchema instanceof Promise) jsonSchema = await jsonSchema + + if (jsonSchema) schemas[name] = jsonSchema + } + + return { + components: { + schemas + }, + paths + } satisfies Pick +} + +export const withHeaders = (schema: TSchema, headers: TProperties) => + Object.assign(schema, { + headers: headers + }) From 7c5f1afc956bdd1f91438cc633e123012d3a5590 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Fri, 12 Sep 2025 23:49:56 +0700 Subject: [PATCH 29/58] :broom: chore: bump version --- bun.lock | 179 ++++++++++++++++++++++++------------------------- package.json | 186 +++++++++++++++++++++++++-------------------------- 2 files changed, 179 insertions(+), 186 deletions(-) diff --git a/bun.lock b/bun.lock index 1be3316..2dbd1dc 100644 --- a/bun.lock +++ b/bun.lock @@ -2,24 +2,23 @@ "lockfileVersion": 1, "workspaces": { "": { - "name": "@elysiajs/swagger", - "dependencies": { - "@sinclair/typemap": "^0.10.1", - "openapi-types": "^12.1.3", - "xsschema": "^0.4.0-beta.3", - }, + "name": "@elysiajs/openapi", "devDependencies": { "@apidevtools/swagger-parser": "^12.0.0", "@scalar/types": "^0.2.13", + "@sinclair/typemap": "^0.10.1", "@types/bun": "1.2.20", - "elysia": "1.4.0-exp.16", + "elysia": "1.4.0", "eslint": "9.6.0", + "openapi-types": "^12.1.3", "tsup": "^8.5.0", "typescript": "^5.9.2", + "xsschema": "^0.4.0-beta.3", "zod": "^4.1.5", }, "peerDependencies": { - "elysia": ">= 1.3.0", + "elysia": ">= 1.4.0", + "xsschema": ">= 0.4.0-beta.0", }, }, }, @@ -34,57 +33,59 @@ "@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=="], @@ -102,15 +103,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.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=="], + "@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=="], @@ -120,51 +119,53 @@ "@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.50.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg=="], - "@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-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw=="], - "@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-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw=="], - "@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-musl": ["@rollup/rollup-linux-arm64-musl@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w=="], - "@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-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q=="], - "@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-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q=="], - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg=="], + "@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-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.1", "", { "os": "linux", "cpu": "none" }, "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg=="], - "@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-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg=="], - "@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-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA=="], - "@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-musl": ["@rollup/rollup-linux-x64-musl@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg=="], - "@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-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.1", "", { "os": "none", "cpu": "arm64" }, "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ=="], - "@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-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A=="], - "@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-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.1", "", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="], "@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="], - "@scalar/types": ["@scalar/types@0.2.13", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-rO6KGMJqOsBnN/2R4fErMFLpRSPVJElni+HABDpf+ZlLJp2lvxuPn0IXLumK5ytfplUH9iqKgSXjndnZfxSYLQ=="], + "@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=="], "@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=="], @@ -174,15 +175,15 @@ "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], - "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], + "@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.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + "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=="], @@ -200,7 +201,7 @@ "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.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], @@ -234,27 +235,27 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "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.4.0-exp.16", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-b0wOFVnm72zY0wYhX2/mBZKiUQHd8gOu6bV5XsfGiOaGPVJ6aogryb7XCpJ3uu6fJAsb5SnQO+wA2qyJ7jtudg=="], + "elysia": ["elysia@1.4.0", "", { "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-AVEq8cWo7+fdkbUtqVXDZ7uhjjv8K6NE6bt9oSzdocaawsW5VhJP2ArPoRYkWdJPYmS4f2FI5xWF+Tvs98yXVw=="], "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=="], @@ -274,11 +275,11 @@ "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=="], @@ -354,7 +355,7 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], + "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=="], @@ -394,7 +395,7 @@ "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=="], @@ -416,7 +417,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=="], @@ -452,7 +453,7 @@ "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=="], @@ -472,7 +473,7 @@ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], - "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "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=="], @@ -494,17 +495,13 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@4.1.5", "", {}, "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg=="], + "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=="], - - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "@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=="], @@ -516,30 +513,28 @@ "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "mlly/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "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=="], + "@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/package.json b/package.json index f871e68..e7235ec 100644 --- a/package.json +++ b/package.json @@ -1,96 +1,94 @@ { - "name": "@elysiajs/openapi", - "version": "1.4.0-exp.0", - "description": "Plugin for Elysia to auto-generate API documentation", - "author": { - "name": "saltyAom", - "url": "https://github.com/SaltyAom", - "email": "saltyaom@gmail.com" - }, - "main": "./dist/cjs/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/cjs/index.js" - }, - "./gen": { - "types": "./dist/gen/index.d.ts", - "import": "./dist/gen/index.mjs", - "require": "./dist/cjs/gen/index.js" - }, - "./openapi": { - "types": "./dist/openapi.d.ts", - "import": "./dist/openapi.mjs", - "require": "./dist/cjs/openapi.js" - }, - "./scalar": { - "types": "./dist/scalar/index.d.ts", - "import": "./dist/scalar/index.mjs", - "require": "./dist/cjs/scalar/index.js" - }, - "./scalar/theme": { - "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", - "openapi", - "swagger", - "scalar" - ], - "homepage": "https://github.com/elysiajs/elysia-openapi", - "repository": { - "type": "git", - "url": "https://github.com/elysiajs/elysia-openapi" - }, - "bugs": "https://github.com/elysiajs/elysia-openapi/issues", - "license": "MIT", - "scripts": { - "dev": "bun run --watch example/index.ts", - "test": "bun test && npm run test:node", - "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", - "build": "bun build.ts", - "release": "npm run build && npm run test && npm publish --access public" - }, - "dependencies": {}, - "devDependencies": { - "@sinclair/typemap": "^0.10.1", - "@apidevtools/swagger-parser": "^12.0.0", - "@scalar/types": "^0.2.13", - "@types/bun": "1.2.20", - "elysia": "1.4.0-exp.16", - "eslint": "9.6.0", - "openapi-types": "^12.1.3", - "tsup": "^8.5.0", - "typescript": "^5.9.2", - "xsschema": "^0.4.0-beta.3", - "zod": "^4.1.5" - }, - "peerDependencies": { - "@sinclair/typemap": ">= 0.10.0", - "elysia": ">= 1.3.0", - "openapi-types": ">= 12.0.0", - "xsschema": ">= 0.4.0-beta.0" - } + "name": "@elysiajs/openapi", + "version": "1.4.0", + "description": "Plugin for Elysia to auto-generate API documentation", + "author": { + "name": "saltyAom", + "url": "https://github.com/SaltyAom", + "email": "saltyaom@gmail.com" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/cjs/index.js" + }, + "./gen": { + "types": "./dist/gen/index.d.ts", + "import": "./dist/gen/index.mjs", + "require": "./dist/cjs/gen/index.js" + }, + "./openapi": { + "types": "./dist/openapi.d.ts", + "import": "./dist/openapi.mjs", + "require": "./dist/cjs/openapi.js" + }, + "./scalar": { + "types": "./dist/scalar/index.d.ts", + "import": "./dist/scalar/index.mjs", + "require": "./dist/cjs/scalar/index.js" + }, + "./scalar/theme": { + "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", + "openapi", + "swagger", + "scalar" + ], + "homepage": "https://github.com/elysiajs/elysia-openapi", + "repository": { + "type": "git", + "url": "https://github.com/elysiajs/elysia-openapi" + }, + "bugs": "https://github.com/elysiajs/elysia-openapi/issues", + "license": "MIT", + "scripts": { + "dev": "bun run --watch example/index.ts", + "test": "bun test && npm run test:node", + "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", + "build": "bun build.ts", + "release": "npm run build && npm run test && npm publish --access public" + }, + "dependencies": {}, + "devDependencies": { + "@apidevtools/swagger-parser": "^12.0.0", + "@scalar/types": "^0.2.13", + "@sinclair/typemap": "^0.10.1", + "@types/bun": "1.2.20", + "elysia": "1.4.0", + "eslint": "9.6.0", + "openapi-types": "^12.1.3", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "xsschema": "^0.4.0-beta.3", + "zod": "^4.1.5" + }, + "peerDependencies": { + "elysia": ">= 1.4.0", + "xsschema": ">= 0.4.0-beta.0" + } } From dfd4325ef3965b02b755dca7a42f04cae1b0d726 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 14 Sep 2025 01:08:48 +0700 Subject: [PATCH 30/58] :wrench: fix: mapJsonSchema --- CHANGELOG.md | 12 +- package.json | 2 +- src/index.ts | 7 +- src/openapi.ts | 63 +++++---- src/types.ts | 378 ++++++++++++++++++++++++++----------------------- 5 files changed, 248 insertions(+), 214 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a03597b..e987a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -# 1.4.0 +# 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 diff --git a/package.json b/package.json index e7235ec..86a2544 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.0", + "version": "1.4.1", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/index.ts b/src/index.ts index e5a3cf1..7b2c3ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,8 @@ export const openapi = < exclude, swagger, scalar, - references + references, + mapJsonSchema }: ElysiaOpenAPIConfig = {}) => { if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) @@ -80,7 +81,7 @@ export const openapi = < }) .get( specPath, - async function openAPISchema() { + function openAPISchema() { if (totalRoutes === app.routes.length) return cachedSchema totalRoutes = app.routes.length @@ -88,7 +89,7 @@ export const openapi = < const { paths, components: { schemas } - } = await toOpenAPISchema(app, exclude, references) + } = toOpenAPISchema(app, exclude, references, mapJsonSchema) return (cachedSchema = { openapi: '3.0.3', diff --git a/src/openapi.ts b/src/openapi.ts index cfcaca7..aa06ef0 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -4,12 +4,11 @@ import type { HookContainer, StandardSchemaV1Like } from 'elysia/types' import type { OpenAPIV3 } from 'openapi-types' import { Kind, type TProperties } from '@sinclair/typebox' -import { toJsonSchema } from 'xsschema' - import type { AdditionalReference, AdditionalReferences, - ElysiaOpenAPIConfig + ElysiaOpenAPIConfig, + MapJsonSchema } from './types' export const capitalize = (word: string) => @@ -69,18 +68,33 @@ export const getLoosePath = (path: string) => { return path + '/' } -type MaybePromise = T | Promise - export const unwrapSchema = ( - schema: InputSchema['body'] -): MaybePromise => { + schema: InputSchema['body'], + mapJsonSchema?: MapJsonSchema +): OpenAPIV3.SchemaObject | undefined => { if (!schema) return if (typeof schema === 'string') schema = toRef(schema) if (Kind in schema) return schema - if (Kind in schema === false && schema['~standard']) - return toJsonSchema(schema as any) as Promise + if (Kind in schema || !schema?.['~standard']) return + + // @ts-ignore + const vendor = schema['~standard'].vendor + + if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === 'function') + return mapJsonSchema[vendor](schema) + + if (vendor === 'zod' || vendor === 'sury') + // @ts-ignore + return schema.toJSONSchema?.() + + if (vendor === 'arktype') + // @ts-ignore + return schema?.toJsonSchema?.() + + // @ts-ignore + return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() } /** @@ -88,10 +102,11 @@ export const unwrapSchema = ( * @param routes Array of Elysia route objects * @returns OpenAPI paths object */ -export async function toOpenAPISchema( +export function toOpenAPISchema( app: AnyElysia, exclude?: ElysiaOpenAPIConfig['exclude'], - references?: AdditionalReferences + references?: AdditionalReferences, + vendors?: MapJsonSchema ) { const { methods: excludeMethods = ['OPTIONS'], @@ -195,8 +210,7 @@ export async function toOpenAPISchema( // Handle path parameters if (hooks.params) { - let params = unwrapSchema(hooks.params) - if (params) params = await params + const params = unwrapSchema(hooks.params, vendors) if (params && params.type === 'object' && params.properties) for (const [paramName, paramSchema] of Object.entries( @@ -212,8 +226,7 @@ export async function toOpenAPISchema( // Handle query parameters if (hooks.query) { - let query = unwrapSchema(hooks.query) - if (query) query = await query + let query = unwrapSchema(hooks.query, vendors) if (query && query.type === 'object' && query.properties) { const required = query.required || [] @@ -231,8 +244,7 @@ export async function toOpenAPISchema( // Handle header parameters if (hooks.headers) { - let headers = unwrapSchema(hooks.query) - if (headers) headers = await headers + const headers = unwrapSchema(hooks.query, vendors) if (headers && headers.type === 'object' && headers.properties) { const required = headers.required || [] @@ -250,8 +262,7 @@ export async function toOpenAPISchema( // Handle cookie parameters if (hooks.cookie) { - let cookie = unwrapSchema(hooks.cookie) - if (cookie) cookie = await cookie + const cookie = unwrapSchema(hooks.cookie, vendors) if (cookie && cookie.type === 'object' && cookie.properties) { const required = cookie.required || [] @@ -272,8 +283,7 @@ export async function toOpenAPISchema( // Handle request body if (hooks.body && method !== 'get' && method !== 'head') { - let body = unwrapSchema(hooks.body) - if (body) body = await body + const body = unwrapSchema(hooks.body, vendors) if (body) { // @ts-ignore @@ -363,8 +373,7 @@ export async function toOpenAPISchema( !(hooks.response as TSchema).$ref ) { for (let [status, schema] of Object.entries(hooks.response)) { - let response = unwrapSchema(schema) - if (response) response = await response + const response = unwrapSchema(schema, vendors) if (!response) continue @@ -398,8 +407,7 @@ export async function toOpenAPISchema( } } } else { - let response = unwrapSchema(hooks.response as any) - if (response) response = await response + const response = unwrapSchema(hooks.response as any, vendors) if (response) { // @ts-ignore @@ -470,17 +478,14 @@ export async function toOpenAPISchema( // @ts-ignore private property const _schemas = app.getGlobalDefinitions?.().type - const schemas = Object.create(null) if (_schemas) for (const [name, schema] of Object.entries(_schemas)) { - let jsonSchema = unwrapSchema(schema as any) as + const jsonSchema = unwrapSchema(schema as any, vendors) as | OpenAPIV3.SchemaObject | undefined - if (jsonSchema instanceof Promise) jsonSchema = await jsonSchema - if (jsonSchema) schemas[name] = jsonSchema } diff --git a/src/types.ts b/src/types.ts index 6812fc3..43e725e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,180 +1,198 @@ -import type { TSchema } from 'elysia' -import type { OpenAPIV3 } from 'openapi-types' -import type { ApiReferenceConfiguration } from '@scalar/types' -import type { SwaggerUIOptions } from './swagger/types' - -export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null - -type MaybeArray = T | T[] - -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', - Provider extends OpenAPIProvider = 'scalar' -> { - /** - * @default true - */ - enabled?: Enabled - - /** - * OpenAPI config - * - * @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 - * - * @default 'scalar' - * @see https://github.com/scalar/scalar - * @see https://github.com/swagger-api/swagger-ui - */ - provider?: Provider - - /** - * Additional reference for each endpoint - */ - references?: AdditionalReferences - - /** - * Scalar configuration to customize scalar - *' - * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md - */ - 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 - - /** - * Options to send to SwaggerUIBundle - * Currently, options that are defined as functions such as requestInterceptor - * and onComplete are not supported. - */ - swagger?: Omit< - Partial, - | 'dom_id' - | 'dom_node' - | 'spec' - | 'url' - | 'urls' - | 'layout' - | 'pluginsOptions' - | 'plugins' - | 'presets' - | 'onComplete' - | 'requestInterceptor' - | 'responseInterceptor' - | 'modelPropertyMacro' - | 'parameterMacro' - > & { - /** - * Custom Swagger CSS - */ - theme?: - | string - | { - light: string - dark: string - } - - /** - * Version to use for swagger cdn bundle - * - * @see unpkg.com/swagger-ui-dist - * - * @default 4.18.2 - */ - version?: 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 - } -} +import type { TSchema } from 'elysia' +import type { OpenAPIV3 } from 'openapi-types' +import type { ApiReferenceConfiguration } from '@scalar/types' +import type { SwaggerUIOptions } from './swagger/types' + +export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null + +type MaybeArray = T | T[] + +export type MapJsonSchema = { [vendor: string]: 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', + Provider extends OpenAPIProvider = 'scalar' +> { + /** + * @default true + */ + enabled?: Enabled + + /** + * OpenAPI config + * + * @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 + * + * @default 'scalar' + * @see https://github.com/scalar/scalar + * @see https://github.com/swagger-api/swagger-ui + */ + provider?: Provider + + /** + * Additional reference for each endpoint + */ + references?: AdditionalReferences + + /** + * Mapping function from Standard schema to OpenAPI schema + * + * @example + * ```ts + * import { openapi } from '@elysiajs/openapi' + * import { toJsonSchema } from '@valibot/to-json-schema' + * + * openapi({ + * vendors: { + * valibot: toJsonSchema + * } + * }) + */ + mapJsonSchema?: MapJsonSchema + + /** + * Scalar configuration to customize scalar + *' + * @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md + */ + 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 + + /** + * Options to send to SwaggerUIBundle + * Currently, options that are defined as functions such as requestInterceptor + * and onComplete are not supported. + */ + swagger?: Omit< + Partial, + | 'dom_id' + | 'dom_node' + | 'spec' + | 'url' + | 'urls' + | 'layout' + | 'pluginsOptions' + | 'plugins' + | 'presets' + | 'onComplete' + | 'requestInterceptor' + | 'responseInterceptor' + | 'modelPropertyMacro' + | 'parameterMacro' + > & { + /** + * Custom Swagger CSS + */ + theme?: + | string + | { + light: string + dark: string + } + + /** + * Version to use for swagger cdn bundle + * + * @see unpkg.com/swagger-ui-dist + * + * @default 4.18.2 + */ + version?: 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 + } +} From 170355fe27445c9831ae1baca7dbeea6721225c0 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 14 Sep 2025 01:10:42 +0700 Subject: [PATCH 31/58] :wrench: fix: remove xsschema from dependency --- CHANGELOG.md | 4 ++++ package.json | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e987a6b..42542ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/package.json b/package.json index 86a2544..8c51479 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.1", + "version": "1.4.2", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", @@ -84,11 +84,9 @@ "openapi-types": "^12.1.3", "tsup": "^8.5.0", "typescript": "^5.9.2", - "xsschema": "^0.4.0-beta.3", "zod": "^4.1.5" }, "peerDependencies": { - "elysia": ">= 1.4.0", - "xsschema": ">= 0.4.0-beta.0" + "elysia": ">= 1.4.0" } } From 480f7f7ac4ec8412a56bfd139fa1b2eb967c65e7 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 14 Sep 2025 01:15:21 +0700 Subject: [PATCH 32/58] :wrench: fix: remove xsschema from dependency --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 43e725e..af95405 100644 --- a/src/types.ts +++ b/src/types.ts @@ -102,7 +102,7 @@ export interface ElysiaOpenAPIConfig< * import { toJsonSchema } from '@valibot/to-json-schema' * * openapi({ - * vendors: { + * mapJsonSchema: { * valibot: toJsonSchema * } * }) From 031696d135e704cef55e27dc3446d83d22688594 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 18 Sep 2025 20:52:12 +0700 Subject: [PATCH 33/58] :tada: feat: 1.4.3 --- CHANGELOG.md | 8 ++++ bun.lock | 17 ++++--- example/index.ts | 40 ++++++++++++++-- package.json | 5 +- src/index.ts | 11 +++-- src/openapi.ts | 122 ++++++++++++++++++++++++++++++++++++++++++----- src/types.ts | 16 +++++-- 7 files changed, 189 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42542ba..453ee10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 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 diff --git a/bun.lock b/bun.lock index 2dbd1dc..18de831 100644 --- a/bun.lock +++ b/bun.lock @@ -8,17 +8,16 @@ "@scalar/types": "^0.2.13", "@sinclair/typemap": "^0.10.1", "@types/bun": "1.2.20", - "elysia": "1.4.0", + "effect": "^3.17.13", + "elysia": "1.4.6", "eslint": "9.6.0", "openapi-types": "^12.1.3", "tsup": "^8.5.0", "typescript": "^5.9.2", - "xsschema": "^0.4.0-beta.3", "zod": "^4.1.5", }, "peerDependencies": { "elysia": ">= 1.4.0", - "xsschema": ">= 0.4.0-beta.0", }, }, }, @@ -169,6 +168,8 @@ "@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=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@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=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -241,7 +242,9 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - "elysia": ["elysia@1.4.0", "", { "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-AVEq8cWo7+fdkbUtqVXDZ7uhjjv8K6NE6bt9oSzdocaawsW5VhJP2ArPoRYkWdJPYmS4f2FI5xWF+Tvs98yXVw=="], + "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=="], @@ -267,6 +270,8 @@ "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=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -407,6 +412,8 @@ "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=="], @@ -491,8 +498,6 @@ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "xsschema": ["xsschema@0.4.0-beta.3", "", { "peerDependencies": { "@valibot/to-json-schema": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.16.0", "sury": "^10.0.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "zod", "zod-to-json-schema"] }, "sha512-+vKxPksAH0QDk2YXRT25LY+Cn/lGJHiwC88Hb9BNl76Gso7VZYoOsq0T6nSoeSxKjyWRYCGmS6anrOhDQknasw=="], - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], diff --git a/example/index.ts b/example/index.ts index 6dffe53..0c62643 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,5 +1,6 @@ import { Elysia, t } from 'elysia' import z from 'zod' +import { JSONSchema, Schema } from 'effect' import { openapi, withHeaders } from '../src/index' @@ -21,6 +22,10 @@ export const app = new Elysia() .use( openapi({ provider: 'scalar', + mapJsonSchema: { + zod: z.toJSONSchema, + effect: JSONSchema.make + }, documentation: { info: { title: 'Elysia Scalar', @@ -49,6 +54,28 @@ export const app = new Elysia() }) ) .model({ schema, schema2, user }) + .model({ + idParam: t.Object({ + id: t.Union([ + t.String({ format: 'uuid' }), + t.Number({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }) + ]), + id2: t.String() + }), + response200: t.Object({ + message: t.String(), + content: t.Array(t.Object({ + id: t.Union([t.String(), t.Number()]) + })) + }) + }) + .get('/test/:id/:id2', ({ params: { id } }) => ({ + message: 'ok', + content: [{ id }] + }), { + params: 'idParam', + response: 'response200' + }) .get( '/', { test: 'hello' as const }, @@ -72,17 +99,24 @@ export const app = new Elysia() .post( '/json', ({ body }) => ({ - test: 'world' + test: 'hello' }), { parse: ['json', 'formdata'], body: 'schema', response: { - 200: 'schema', + 200: t.Object({ + test: t.Literal('hello') + }), 400: z.object({ a: z.string(), b: z.literal('a') - }) + }), + 401: Schema.standardSchemaV1( + Schema.Struct({ + a: Schema.Literal('hi') + }) + ) } } ) diff --git a/package.json b/package.json index 8c51479..0443fff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.2", + "version": "1.4.3", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", @@ -79,7 +79,8 @@ "@scalar/types": "^0.2.13", "@sinclair/typemap": "^0.10.1", "@types/bun": "1.2.20", - "elysia": "1.4.0", + "effect": "^3.17.13", + "elysia": "1.4.6", "eslint": "9.6.0", "openapi-types": "^12.1.3", "tsup": "^8.5.0", diff --git a/src/index.ts b/src/index.ts index 7b2c3ce..f290fac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,12 +16,11 @@ import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' */ export const openapi = < const Enabled extends boolean = true, - const Path extends string = '/openapi', - const Provider extends OpenAPIProvider = 'scalar' + const Path extends string = '/openapi' >({ enabled = true as Enabled, path = '/openapi' as Path, - provider = 'scalar' as Provider, + provider = 'scalar', specPath = `${path}/json`, documentation = {}, exclude, @@ -29,7 +28,7 @@ export const openapi = < scalar, references, mapJsonSchema -}: ElysiaOpenAPIConfig = {}) => { +}: ElysiaOpenAPIConfig = {}) => { if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) const info = { @@ -119,6 +118,10 @@ export const openapi = < } satisfies OpenAPIV3.Document) }, { + error({ error }) { + console.log('[@elysiajs/openapi] error at specPath') + console.warn(error) + }, detail: { hide: true } diff --git a/src/openapi.ts b/src/openapi.ts index aa06ef0..0ea88d4 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -10,6 +10,7 @@ import type { ElysiaOpenAPIConfig, MapJsonSchema } from './types' +import { DefaultErrorFunction } from '@sinclair/typebox/errors' export const capitalize = (word: string) => word.charAt(0).toUpperCase() + word.slice(1) @@ -55,6 +56,7 @@ export const getPossiblePath = (path: string): string[] => { } const isValidSchema = (schema: any): schema is TSchema => + schema && typeof schema === 'object' && ((Kind in schema && schema[Kind] !== 'Unknown') || schema.type || @@ -68,6 +70,50 @@ export const getLoosePath = (path: string) => { 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 { 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: any, definitions: Record) => { + if (!schema?.$ref) return schema + + const name = schema.$ref.slice(schema.$ref.lastIndexOf('/') + 1) + if (schema.$ref && definitions[name]) schema = definitions[name] + + return schema +} + export const unwrapSchema = ( schema: InputSchema['body'], mapJsonSchema?: MapJsonSchema @@ -85,9 +131,59 @@ export const unwrapSchema = ( if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === 'function') return mapJsonSchema[vendor](schema) - if (vendor === 'zod' || vendor === 'sury') - // @ts-ignore - return schema.toJSONSchema?.() + 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.log(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 @@ -121,6 +217,8 @@ export function toOpenAPISchema( : [] const paths: OpenAPIV3.PathsObject = Object.create(null) + // @ts-ignore + const definitions = app.getGlobalDefinitions?.().type // @ts-ignore private property const routes = app.getGlobalRoutes() @@ -151,7 +249,7 @@ export function toOpenAPISchema( detail: Partial } = route.hooks ?? {} - if (references) + if (references?.length) for (const reference of references as AdditionalReference[]) { if (!reference) continue @@ -210,7 +308,10 @@ export function toOpenAPISchema( // Handle path parameters if (hooks.params) { - const params = unwrapSchema(hooks.params, vendors) + const params = unwrapReference( + unwrapSchema(hooks.params, vendors), + definitions + ) if (params && params.type === 'object' && params.properties) for (const [paramName, paramSchema] of Object.entries( @@ -226,7 +327,7 @@ export function toOpenAPISchema( // Handle query parameters if (hooks.query) { - let query = unwrapSchema(hooks.query, vendors) + const query = unwrapReference(unwrapSchema(hooks.query, vendors), definitions) if (query && query.type === 'object' && query.properties) { const required = query.required || [] @@ -244,7 +345,7 @@ export function toOpenAPISchema( // Handle header parameters if (hooks.headers) { - const headers = unwrapSchema(hooks.query, vendors) + const headers = unwrapReference(unwrapSchema(hooks.query, vendors), definitions) if (headers && headers.type === 'object' && headers.properties) { const required = headers.required || [] @@ -262,7 +363,7 @@ export function toOpenAPISchema( // Handle cookie parameters if (hooks.cookie) { - const cookie = unwrapSchema(hooks.cookie, vendors) + const cookie = unwrapReference(unwrapSchema(hooks.cookie, vendors), definitions) if (cookie && cookie.type === 'object' && cookie.properties) { const required = cookie.required || [] @@ -477,11 +578,10 @@ export function toOpenAPISchema( } // @ts-ignore private property - const _schemas = app.getGlobalDefinitions?.().type const schemas = Object.create(null) - if (_schemas) - for (const [name, schema] of Object.entries(_schemas)) { + if (definitions) + for (const [name, schema] of Object.entries(definitions)) { const jsonSchema = unwrapSchema(schema as any, vendors) as | OpenAPIV3.SchemaObject | undefined diff --git a/src/types.ts b/src/types.ts index af95405..b52fcd2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,16 @@ export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null type MaybeArray = T | T[] -export type MapJsonSchema = { [vendor: string]: Function } +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]: { @@ -27,8 +36,7 @@ export type AdditionalReferences = MaybeArray< export interface ElysiaOpenAPIConfig< Enabled extends boolean = true, - Path extends string = '/swagger', - Provider extends OpenAPIProvider = 'scalar' + Path extends string = '/swagger' > { /** * @default true @@ -86,7 +94,7 @@ export interface ElysiaOpenAPIConfig< * @see https://github.com/scalar/scalar * @see https://github.com/swagger-api/swagger-ui */ - provider?: Provider + provider?: OpenAPIProvider /** * Additional reference for each endpoint From 0d9fa4017850e015d90d2980ad5fa11d40af53e2 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 18 Sep 2025 20:52:23 +0700 Subject: [PATCH 34/58] :tada: feat: 1.4.3 --- src/openapi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openapi.ts b/src/openapi.ts index 0ea88d4..328e27b 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -152,7 +152,7 @@ export const unwrapSchema = ( console.warn( 'For Zod v3, please install zod-to-json-schema package and use it like this:\n' ) - console.log(warnings.zod3) + console.warn(warnings.zod3) } break From 128cff33ed44134aa8480dfbaf616e53bee878b4 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sat, 20 Sep 2025 04:07:16 +0700 Subject: [PATCH 35/58] :wrench: fix(type generator): handle non-intersect routes --- CHANGELOG.md | 12 +++++++ example/gen.ts | 87 ++++++++++++++++++++++++------------------------ package.json | 2 +- src/gen/index.ts | 68 ++++++++++++++++++++++++++++++++----- src/index.ts | 2 +- src/openapi.ts | 28 ++++++++++++---- 6 files changed, 139 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 453ee10..d7cd0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 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 diff --git a/example/gen.ts b/example/gen.ts index f5ae0f6..7eae82a 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -1,43 +1,44 @@ -import { Elysia, t } from 'elysia' -import { openapi, withHeaders } from '../src/index' -import { fromTypes } from '../src/gen' - -export const app = new Elysia() - .use( - openapi({ - references: fromTypes('example/gen.ts', { - debug: true - }) - }) - ) - .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) - .listen(3000) +import { Elysia, t } from 'elysia' +import { openapi, withHeaders } from '../src/index' +import { fromTypes } from '../src/gen' + +export const app = new Elysia() + .use( + openapi({ + references: fromTypes('example/gen.ts', { + debug: true + }) + }) + ) + .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) + .get('/a', () => 'hello') + .listen(3000) diff --git a/package.json b/package.json index 0443fff..7d78fcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.3", + "version": "1.4.4", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index eb0f1c5..e340670 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -73,6 +73,53 @@ interface OpenAPIGeneratorOptions { * ! be careful that the folder will be removed after the process ends */ tmpRoot?: string + + /** + * disable log + * @default false + */ + silent?: boolean +} + +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 + + // backtrack to find the key (simple word characters) + let keyEnd = colonIdx - 1 + while (keyEnd >= 0 && /\s/.test(code[keyEnd])) keyEnd-- + let keyStart = keyEnd + while (keyStart >= 0 && /\w/.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 } /** @@ -97,7 +144,8 @@ export const fromTypes = overrideOutputPath, debug = false, compilerOptions, - tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI') + tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI'), + silent = false }: OpenAPIGeneratorOptions = {} ) => () => { @@ -177,7 +225,7 @@ export const fromTypes = spawnSync(`tsc`, { shell: true, cwd: tmpRoot, - stdio: debug ? 'inherit' : undefined + stdio: silent ? undefined : 'inherit' }) const fileName = targetFilePath @@ -235,7 +283,7 @@ export const fromTypes = console.warn(tempFiles) } } else { - console.log( + console.warn( "reason: root folder doesn't exists", join(tmpRoot, 'dist') ) @@ -247,8 +295,10 @@ export const fromTypes = const declaration = readFileSync(targetFile, 'utf8') + // console.log(declaration, targetFile) + // Check just in case of race-condition - if (existsSync(tmpRoot)) + if (!debug && existsSync(tmpRoot)) rmSync(tmpRoot, { recursive: true, force: true }) let instance = declaration.match( @@ -282,11 +332,8 @@ export const fromTypes = const routes: AdditionalReference = {} // Treaty is a collection of { ... } & { ... } & { ... } - // Each route will be intersected with each other - // instead of being nested in a route object - for (const route of routesString.slice(1).split('} & {')) { - // as '} & {' is removed, we need to add it back - let schema = TypeBox(`{${route}}`) + for (const route of extractRootObjects(routesString)) { + let schema = TypeBox(route) if (schema.type !== 'object') continue const paths = [] @@ -302,6 +349,9 @@ export const fromTypes = } const method = paths.pop()! + // For whatever reason, if failed to infer route correctly + if (!method) continue + const path = '/' + paths.join('/') schema = schema.properties diff --git a/src/index.ts b/src/index.ts index f290fac..3b293c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { toOpenAPISchema } from './openapi' import type { OpenAPIV3 } from 'openapi-types' import type { ApiReferenceConfiguration } from '@scalar/types' -import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types' +import type { ElysiaOpenAPIConfig } from './types' /** * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page. diff --git a/src/openapi.ts b/src/openapi.ts index 328e27b..2ed124b 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -87,7 +87,8 @@ openapi({ zod: zodToJsonSchema } })`, - valibot: `import { toJsonSchema } from '@valibot/to-json-schema' + valibot: `import openapi from '@elysiajs/openapi' +import { toJsonSchema } from '@valibot/to-json-schema' openapi({ mapJsonSchema: { @@ -204,12 +205,14 @@ export function toOpenAPISchema( references?: AdditionalReferences, vendors?: MapJsonSchema ) { - const { - methods: excludeMethods = ['OPTIONS'], + 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' @@ -249,6 +252,10 @@ export function toOpenAPISchema( detail: Partial } = route.hooks ?? {} + if (route.path === '/a') { + console.log('H') + } + if (references?.length) for (const reference of references as AdditionalReference[]) { if (!reference) continue @@ -327,7 +334,10 @@ export function toOpenAPISchema( // Handle query parameters if (hooks.query) { - const query = unwrapReference(unwrapSchema(hooks.query, vendors), definitions) + const query = unwrapReference( + unwrapSchema(hooks.query, vendors), + definitions + ) if (query && query.type === 'object' && query.properties) { const required = query.required || [] @@ -345,7 +355,10 @@ export function toOpenAPISchema( // Handle header parameters if (hooks.headers) { - const headers = unwrapReference(unwrapSchema(hooks.query, vendors), definitions) + const headers = unwrapReference( + unwrapSchema(hooks.query, vendors), + definitions + ) if (headers && headers.type === 'object' && headers.properties) { const required = headers.required || [] @@ -363,7 +376,10 @@ export function toOpenAPISchema( // Handle cookie parameters if (hooks.cookie) { - const cookie = unwrapReference(unwrapSchema(hooks.cookie, vendors), definitions) + const cookie = unwrapReference( + unwrapSchema(hooks.cookie, vendors), + definitions + ) if (cookie && cookie.type === 'object' && cookie.properties) { const required = cookie.required || [] From ef764db9c33997bbfcfce5cd821020f0b510740f Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sat, 20 Sep 2025 05:10:23 +0700 Subject: [PATCH 36/58] :wrench: fix: reference model --- CHANGELOG.md | 9 ++++ example/gen.d.ts | 106 ----------------------------------------------- example/gen.ts | 19 ++++++++- package.json | 2 +- src/openapi.ts | 45 +++++++++++++------- 5 files changed, 57 insertions(+), 124 deletions(-) delete mode 100644 example/gen.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d7cd0de..8e4ab81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 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 diff --git a/example/gen.d.ts b/example/gen.d.ts deleted file mode 100644 index 6a102e5..0000000 --- a/example/gen.d.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Elysia } from 'elysia'; -export declare const app: Elysia<"", { - decorator: {}; - store: {}; - derive: {}; - resolve: {}; -}, { - typebox: {}; - error: {}; -}, { - schema: {}; - standaloneSchema: {}; - macro: {}; - macroFn: {}; - parser: {}; -}, { - get: { - body: unknown; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: { - test: "hello"; - } | undefined; - readonly 204: unknown; - 422: { - type: "validation"; - on: string; - summary?: string; - message?: string; - found?: unknown; - property?: string; - expected?: string; - }; - }; - }; -} & { - json: { - post: { - body: { - hello: string; - }; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: { - hello: string; - }; - 418: "I'm a teapot"; - 422: { - type: "validation"; - on: string; - summary?: string; message?: string; - found?: unknown; - property?: string; - expected?: string; - }; - }; - }; - }; -} & { - id: { - ":id": { - name: { - ":name": { - get: { - body: unknown; - params: { - name: string; - id: string; - }; - query: unknown; - headers: unknown; - response: { - 200: { - name: string; - id: string; - }; - 422: { - type: "validation"; - on: string; - summary?: string; - message?: string; - found?: unknown; - property?: string; - expected?: string; - }; - }; - }; - }; - }; - }; - }; -}, { - derive: {}; - resolve: {}; - schema: {}; - standaloneSchema: {}; -}, { - derive: {}; - resolve: {}; - schema: {}; - standaloneSchema: {}; -}>; diff --git a/example/gen.ts b/example/gen.ts index 7eae82a..c36248f 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -10,6 +10,12 @@ export const app = new Elysia() }) }) ) + .model({ + 'character.name': t.String(), + 'character.thing': t.Object({ + name: t.String() + }) + }) .get( '/', () => @@ -40,5 +46,16 @@ export const app = new Elysia() } ) .get('/id/:id/name/:name', ({ params }) => params) - .get('/a', () => 'hello') + .post( + '/character', + () => ({ + name: 'Elysia' + }), + { + body: 'character.name', + response: { + 200: 'character.thing' + } + } + ) .listen(3000) diff --git a/package.json b/package.json index 7d78fcf..733d2fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.4", + "version": "1.4.5", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/openapi.ts b/src/openapi.ts index 2ed124b..3b7f587 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -106,13 +106,23 @@ openapi({ const warned = {} as Record -const unwrapReference = (schema: any, definitions: Record) => { - if (!schema?.$ref) return schema +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 = schema.$ref.slice(schema.$ref.lastIndexOf('/') + 1) - if (schema.$ref && definitions[name]) schema = definitions[name] + const name = ref.slice(ref.lastIndexOf('/') + 1) + if (ref && definitions[name]) schema = definitions[name] as T - return schema + return schema as any } export const unwrapSchema = ( @@ -252,10 +262,6 @@ export function toOpenAPISchema( detail: Partial } = route.hooks ?? {} - if (route.path === '/a') { - console.log('H') - } - if (references?.length) for (const reference of references as AdditionalReference[]) { if (!reference) continue @@ -404,8 +410,10 @@ export function toOpenAPISchema( if (body) { // @ts-ignore - const { type: _type, description, ...options } = body - const type = _type as string | undefined + const { type, description, $ref, ...options } = unwrapReference( + body, + definitions + ) // @ts-ignore if (hooks.parse) { @@ -461,7 +469,9 @@ export function toOpenAPISchema( type === 'integer' || type === 'boolean' ? { - 'text/plain': body + 'text/plain': { + schema: body + } } : { 'application/json': { @@ -495,13 +505,12 @@ export function toOpenAPISchema( if (!response) continue // @ts-ignore Must exclude $ref from root options - const { type: _type, description, ...options } = response - const type = _type as string | undefined + const { type, description, $ref, ...options } = + unwrapReference(response, definitions) operation.responses[status] = { description: description ?? `Response for status ${status}`, - ...options, content: type === 'void' || type === 'null' || @@ -528,7 +537,11 @@ export function toOpenAPISchema( if (response) { // @ts-ignore - const { type: _type, description, ...options } = response + const { + type: _type, + description, + ...options + } = unwrapReference(response, definitions) const type = _type as string | undefined // It's a single schema, default to 200 From 364ec5760d0ec1eb598697a3aaaf3ab2f3b883e4 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 00:33:16 +0700 Subject: [PATCH 37/58] :blue_book: doc: simplify doc --- example/gen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/gen.ts b/example/gen.ts index c36248f..a820ea2 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -2,7 +2,7 @@ import { Elysia, t } from 'elysia' import { openapi, withHeaders } from '../src/index' import { fromTypes } from '../src/gen' -export const app = new Elysia() +export const app = new Elysia({ prefix: '/id' }) .use( openapi({ references: fromTypes('example/gen.ts', { From 816947104b8752bc43219c1a647a097cbe37eee8 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 00:58:36 +0700 Subject: [PATCH 38/58] :wrench: fix(type-gen): handle inline 200 response schema assignment --- CHANGELOG.md | 4 ++++ example/gen.ts | 14 +++++++++----- package.json | 2 +- src/openapi.ts | 25 ++++++++++++++++++++++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4ab81..4589de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/example/gen.ts b/example/gen.ts index a820ea2..4f31477 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -1,10 +1,14 @@ 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({ prefix: '/id' }) +export const app = new Elysia() .use( openapi({ + mapJsonSchema: { + zod: z.toJSONSchema + }, references: fromTypes('example/gen.ts', { debug: true }) @@ -49,13 +53,13 @@ export const app = new Elysia({ prefix: '/id' }) .post( '/character', () => ({ - name: 'Elysia' + name: 'Lilith' as const }), { body: 'character.name', - response: { - 200: 'character.thing' - } + response: z.object({ + name: z.literal('Lilith') + }) } ) .listen(3000) diff --git a/package.json b/package.json index 733d2fa..bd5f269 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.5", + "version": "1.4.6", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/openapi.ts b/src/openapi.ts index 3b7f587..36b6ab5 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -290,14 +290,31 @@ export function toOpenAPISchema( )) 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'] + ) + // @ts-ignore + hooks.response = { + 200: hooks.response as any + } if ( !hooks.response[ status as keyof (typeof hooks)['response'] ] ) - // @ts-ignore - hooks.response[status] = schema + try { + // @ts-ignore + hooks.response[status] = schema + } catch (error) { + console.log( + '[@elysiajs/openapi/gen] Failed to assigned response schema' + ) + console.log(error) + } } } @@ -496,8 +513,10 @@ export function toOpenAPISchema( if ( typeof hooks.response === 'object' && + // TypeBox !(hooks.response as TSchema).type && - !(hooks.response as TSchema).$ref + !(hooks.response as TSchema).$ref && + !(hooks.response as any)['~standard'] ) { for (let [status, schema] of Object.entries(hooks.response)) { const response = unwrapSchema(schema, vendors) From c0b16e674ec4d899e75bde828a966ac60bb682e2 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 04:38:34 +0700 Subject: [PATCH 39/58] :broom: chore(type-gen): remove readonly from generated type to fix readonly tuple --- CHANGELOG.md | 4 +++ example/gen.ts | 84 ++++++++++++++++++++++++++---------------------- package.json | 2 +- src/gen/index.ts | 4 +-- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4589de3..aac6f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/example/gen.ts b/example/gen.ts index 4f31477..c651de4 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -21,45 +21,53 @@ export const app = new Elysia() }) }) .get( - '/', + '/const', () => - ({ 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', - response: z.object({ - name: z.literal('Lilith') + ({ + name: 'Lilith', + friends: ['Sartre', 'Fouco'] }) - } ) + // .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', + // response: z.object({ + // name: z.literal('Lilith') + // }) + // } + // ) .listen(3000) diff --git a/package.json b/package.json index bd5f269..8338567 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.6", + "version": "1.4.7", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index e340670..afe7d1b 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -295,8 +295,6 @@ export const fromTypes = const declaration = readFileSync(targetFile, 'utf8') - // console.log(declaration, targetFile) - // Check just in case of race-condition if (!debug && existsSync(tmpRoot)) rmSync(tmpRoot, { recursive: true, force: true }) @@ -333,7 +331,7 @@ export const fromTypes = // Treaty is a collection of { ... } & { ... } & { ... } for (const route of extractRootObjects(routesString)) { - let schema = TypeBox(route) + let schema = TypeBox(route.replaceAll(/readonly/g, '')) if (schema.type !== 'object') continue const paths = [] From b239366ea03725f6286af0568779c8c0fd51444f Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 04:50:02 +0700 Subject: [PATCH 40/58] :broom: chore(type-gen): handle array delimiter correctly --- CHANGELOG.md | 4 +++ example/gen.ts | 92 ++++++++++++++++++++++++------------------------ package.json | 2 +- src/gen/index.ts | 7 ++-- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aac6f01..05238a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/example/gen.ts b/example/gen.ts index c651de4..8224c32 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -14,6 +14,14 @@ export const app = new Elysia() }) }) ) + .get( + '/const', + () => + ({ + name: 'Lilith', + friends: ['Sartre', 'Fouco'] + }) as const + ) .model({ 'character.name': t.String(), 'character.thing': t.Object({ @@ -21,53 +29,45 @@ export const app = new Elysia() }) }) .get( - '/const', + '/', () => - ({ - name: 'Lilith', - friends: ['Sartre', 'Fouco'] + ({ 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', + response: z.object({ + name: z.literal('Lilith') }) + } ) - // .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', - // response: z.object({ - // name: z.literal('Lilith') - // }) - // } - // ) .listen(3000) diff --git a/package.json b/package.json index 8338567..5cbfc5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.7", + "version": "1.4.8", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index afe7d1b..a2f1760 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -321,10 +321,9 @@ export const fromTypes = ) ) - const routesString = wrapStatusInQuote( - // Intentionally not adding "}" - // to avoid mismatched bracket in loop below - instance.slice(3, instance.indexOf('}, {', 4)) + const routesString = extractRootObjects(instance)[0].replace( + matchStatus, + '"$1":' ) const routes: AdditionalReference = {} From 2340cbc5e26cf3d3af725d0dda0e0815e6c7278e Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 05:17:17 +0700 Subject: [PATCH 41/58] :broom: chore(type-gen): handle array delimiter correctly --- example/gen.ts | 14 +- package.json | 2 +- src/gen/index.ts | 7 +- src/openapi.ts | 5 +- test/index.test.ts | 550 ++++++++++++++++++++++----------------------- 5 files changed, 288 insertions(+), 290 deletions(-) diff --git a/example/gen.ts b/example/gen.ts index 8224c32..5ff456a 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -14,20 +14,20 @@ 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 + }) ) - .model({ - 'character.name': t.String(), - 'character.thing': t.Object({ - name: t.String() - }) - }) .get( '/', () => diff --git a/package.json b/package.json index 5cbfc5f..ee35148 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.8", + "version": "1.4.8-beta.0", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index a2f1760..5df4b05 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -321,15 +321,14 @@ export const fromTypes = ) ) - const routesString = extractRootObjects(instance)[0].replace( - matchStatus, - '"$1":' + const routesString = extractRootObjects( + instance.replace(matchStatus, '"$1":') ) const routes: AdditionalReference = {} // Treaty is a collection of { ... } & { ... } & { ... } - for (const route of extractRootObjects(routesString)) { + for (const route of routesString) { let schema = TypeBox(route.replaceAll(/readonly/g, '')) if (schema.type !== 'object') continue diff --git a/src/openapi.ts b/src/openapi.ts index 36b6ab5..137efd7 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -10,7 +10,6 @@ import type { ElysiaOpenAPIConfig, MapJsonSchema } from './types' -import { DefaultErrorFunction } from '@sinclair/typebox/errors' export const capitalize = (word: string) => word.charAt(0).toUpperCase() + word.slice(1) @@ -534,7 +533,7 @@ export function toOpenAPISchema( type === 'void' || type === 'null' || type === 'undefined' - ? (response as any) + ? ({ type, description } as any) : type === 'string' || type === 'number' || type === 'integer' || @@ -570,7 +569,7 @@ export function toOpenAPISchema( type === 'void' || type === 'null' || type === 'undefined' - ? (response as any) + ? ({ type, description } as any) : type === 'string' || type === 'number' || type === 'integer' || diff --git a/test/index.test.ts b/test/index.test.ts index e9593c0..9c61e47 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,275 +1,275 @@ -import { Elysia, t } from 'elysia' -import SwaggerParser from '@apidevtools/swagger-parser' -import { openapi } from '../src' - -import { describe, expect, it } from 'bun:test' -import { fail } from 'assert' - -const req = (path: string) => new Request(`http://localhost${path}`) - -describe('Swagger', () => { - it('show Swagger page', async () => { - const app = new Elysia().use(openapi()) - - await app.modules - - const res = await app.handle(req('/openapi')) - expect(res.status).toBe(200) - }) - - it('returns a valid OpenAPI json config', async () => { - const app = new Elysia().use(openapi()) - - await app.modules - - 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( - openapi({ - provider: 'swagger-ui', - swagger: { - version: '4.5.0' - } - }) - ) - - await app.modules - - 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' - ) - ).toBe(true) - }) - - it('follow title and description with Swagger-UI provider', async () => { - const app = new Elysia().use( - openapi({ - provider: 'swagger-ui', - swagger: { - version: '4.5.0' - }, - documentation: { - info: { - title: 'Elysia Documentation', - description: 'Herrscher of Human', - version: '1.0.0' - } - } - }) - ) - - await app.modules - - const res = await app.handle(req('/openapi')).then((x) => x.text()) - - expect(res.includes('Elysia Documentation')).toBe(true) - expect( - res.includes( - `` - ) - ).toBe(true) - }) - - it('follow title and description with Scalar provider', async () => { - const app = new Elysia().use( - openapi({ - provider: 'scalar', - scalar: { - version: '4.5.0' - }, - documentation: { - info: { - title: 'Elysia Documentation', - description: 'Herrscher of Human', - version: '1.0.0' - } - } - }) - ) - - await app.modules - - const res = await app.handle(req('/openapi')).then((x) => x.text()) - - expect(res.includes('Elysia Documentation')).toBe(true) - expect( - res.includes( - `` - ) - ).toBe(true) - }) - - it('use custom path', async () => { - const app = new Elysia().use( - openapi({ - path: '/v2/openapi' - }) - ) - - await app.modules - - const res = await app.handle(req('/v2/openapi')) - expect(res.status).toBe(200) - - const resJson = await app.handle(req('/v2/openapi/json')) - expect(resJson.status).toBe(200) - }) - - it('Swagger UI options', async () => { - const app = new Elysia().use( - openapi({ - provider: 'swagger-ui', - swagger: { - persistAuthorization: true - } - }) - ) - - await app.modules - - 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(openapi()).get('/void', () => {}, { - response: { - 204: t.Void({ - description: 'Void response' - }) - } - }) - - await app.modules - - 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).toEqual({ - description: 'Void response', - type: 'void' - }) - }) - - it('should not return content response when using Undefined type', async () => { - const app = new Elysia() - .use(openapi()) - .get('/undefined', () => undefined, { - response: { - 204: t.Undefined({ - description: 'Undefined response' - }) - } - }) - - await app.modules - - const res = await app.handle(req('/openapi/json')) - expect(res.status).toBe(200) - const response = await res.json() - expect( - response.paths['/undefined'].get.responses['204'].description - ).toBe('Undefined response') - expect( - response.paths['/undefined'].get.responses['204'].content - ).toEqual({ - type: 'undefined', - description: 'Undefined response' - }) - }) - - it('should not return content response when using Null type', async () => { - const app = new Elysia().use(openapi()).get('/null', () => null, { - response: { - 204: t.Null({ - description: 'Null response' - }) - } - }) - - await app.modules - - 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).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(openapi()).post('/post', () => {}, { - body: t.Object({ name: t.String() }) - }) - - await app.modules - - 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(openapi()).get('/id/:id?', () => {}) - - await app.modules - - 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(openapi()) - .get('/public', 'omg') - .guard({ - detail: { - hide: true - } - }) - .get('/hidden', 'ok') - - await app.modules - - 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() - }) - - it('should expand .all routes', async () => { - const app = new Elysia().use(openapi()).all('/all', 'woah') - - await app.modules - - 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) - }) -}) +import { Elysia, t } from 'elysia' +import SwaggerParser from '@apidevtools/swagger-parser' +import { openapi } from '../src' + +import { describe, expect, it } from 'bun:test' +import { fail } from 'assert' + +const req = (path: string) => new Request(`http://localhost${path}`) + +describe('Swagger', () => { + it('show Swagger page', async () => { + const app = new Elysia().use(openapi()) + + await app.modules + + const res = await app.handle(req('/openapi')) + expect(res.status).toBe(200) + }) + + it('returns a valid OpenAPI json config', async () => { + const app = new Elysia().use(openapi()) + + await app.modules + + 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( + openapi({ + provider: 'swagger-ui', + swagger: { + version: '4.5.0' + } + }) + ) + + await app.modules + + 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' + ) + ).toBe(true) + }) + + it('follow title and description with Swagger-UI provider', async () => { + const app = new Elysia().use( + openapi({ + provider: 'swagger-ui', + swagger: { + version: '4.5.0' + }, + documentation: { + info: { + title: 'Elysia Documentation', + description: 'Herrscher of Human', + version: '1.0.0' + } + } + }) + ) + + await app.modules + + const res = await app.handle(req('/openapi')).then((x) => x.text()) + + expect(res.includes('Elysia Documentation')).toBe(true) + expect( + res.includes( + `` + ) + ).toBe(true) + }) + + it('follow title and description with Scalar provider', async () => { + const app = new Elysia().use( + openapi({ + provider: 'scalar', + scalar: { + version: '4.5.0' + }, + documentation: { + info: { + title: 'Elysia Documentation', + description: 'Herrscher of Human', + version: '1.0.0' + } + } + }) + ) + + await app.modules + + const res = await app.handle(req('/openapi')).then((x) => x.text()) + + expect(res.includes('Elysia Documentation')).toBe(true) + expect( + res.includes( + `` + ) + ).toBe(true) + }) + + it('use custom path', async () => { + const app = new Elysia().use( + openapi({ + path: '/v2/openapi' + }) + ) + + await app.modules + + const res = await app.handle(req('/v2/openapi')) + expect(res.status).toBe(200) + + const resJson = await app.handle(req('/v2/openapi/json')) + expect(resJson.status).toBe(200) + }) + + it('Swagger UI options', async () => { + const app = new Elysia().use( + openapi({ + provider: 'swagger-ui', + swagger: { + persistAuthorization: true + } + }) + ) + + await app.modules + + 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(openapi()).get('/void', () => {}, { + response: { + 204: t.Void({ + description: 'Void response' + }) + } + }) + + await app.modules + + 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).toEqual({ + description: 'Void response', + type: 'void' + }) + }) + + it('should not return content response when using Undefined type', async () => { + const app = new Elysia() + .use(openapi()) + .get('/undefined', () => undefined, { + response: { + 204: t.Undefined({ + description: 'Undefined response' + }) + } + }) + + await app.modules + + const res = await app.handle(req('/openapi/json')) + expect(res.status).toBe(200) + const response = await res.json() + expect( + response.paths['/undefined'].get.responses['204'].description + ).toBe('Undefined response') + expect( + response.paths['/undefined'].get.responses['204'].content + ).toEqual({ + type: 'undefined', + description: 'Undefined response' + }) + }) + + it('should not return content response when using Null type', async () => { + const app = new Elysia().use(openapi()).get('/null', () => null, { + response: { + 204: t.Null({ + description: 'Null response' + }) + } + }) + + await app.modules + + 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).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(openapi()).post('/post', () => {}, { + body: t.Object({ name: t.String() }) + }) + + await app.modules + + 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(openapi()).get('/id/:id?', () => {}) + + await app.modules + + 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(openapi()) + .get('/public', 'omg') + .guard({ + detail: { + hide: true + } + }) + .get('/hidden', 'ok') + + await app.modules + + 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() + }) + + it('should expand .all routes', async () => { + const app = new Elysia().use(openapi()).all('/all', 'woah') + + await app.modules + + 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) + }) +}) From ae3807a7182551bc74ac17390301499eb3911c55 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 05:22:23 +0700 Subject: [PATCH 42/58] :broom: chore(type-gen): handle array delimiter correctly --- example/gen.ts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/gen.ts b/example/gen.ts index 5ff456a..37f0df0 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -26,7 +26,7 @@ export const app = new Elysia() ({ name: 'Lilith', friends: ['Sartre', 'Fouco'] - }) + }) as const ) .get( '/', diff --git a/package.json b/package.json index ee35148..5cbfc5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.8-beta.0", + "version": "1.4.8", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", From 13503601a60444bf730836a937aab8ffbfc00467 Mon Sep 17 00:00:00 2001 From: Steve Sarjeant Date: Sun, 21 Sep 2025 10:00:54 +1000 Subject: [PATCH 43/58] Fix Swagger UI link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac9d734..757a382 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The endpoint to expose OpenAPI documentation frontend OpenAPI documentation frontend between: - [Scalar](https://github.com/scalar/scalar) -- [SwaggerUI](https://github.com/openapi-api/openapi-ui) +- [SwaggerUI](https://github.com/swagger-api/swagger-ui) - null: disable frontend ## references From 01b4d6acf2ab3beebe2c087559ce2b37433a6904 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 21 Sep 2025 12:15:59 +0700 Subject: [PATCH 44/58] :wrench: fix(type-gen): match special, ad non-english character --- CHANGELOG.md | 4 ++++ example/gen.ts | 3 +++ package.json | 2 +- src/gen/index.ts | 17 ++++++++++------- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05238a8..2760bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 diff --git a/example/gen.ts b/example/gen.ts index 37f0df0..7146693 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -70,4 +70,7 @@ export const app = new Elysia() }) } ) + .get('/no-manual', () => ({ + name: 'lilith' + })) .listen(3000) diff --git a/package.json b/package.json index 5cbfc5f..9296bd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.8", + "version": "1.4.9", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/gen/index.ts b/src/gen/index.ts index 5df4b05..7252957 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -90,11 +90,16 @@ function extractRootObjects(code: string) { const colonIdx = code.indexOf(':', i) if (colonIdx === -1) break - // backtrack to find the key (simple word characters) + // --- find key --- + // walk backwards from colon to find start of key let keyEnd = colonIdx - 1 while (keyEnd >= 0 && /\s/.test(code[keyEnd])) keyEnd-- + let keyStart = keyEnd - while (keyStart >= 0 && /\w/.test(code[keyStart])) keyStart-- + // 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) @@ -321,14 +326,12 @@ export const fromTypes = ) ) - const routesString = extractRootObjects( - instance.replace(matchStatus, '"$1":') - ) - const routes: AdditionalReference = {} // Treaty is a collection of { ... } & { ... } & { ... } - for (const route of routesString) { + for (const route of extractRootObjects( + instance.slice(2).replace(matchStatus, '"$1":') + )) { let schema = TypeBox(route.replaceAll(/readonly/g, '')) if (schema.type !== 'object') continue From 01c237bb84001dfb27ba41324c753ce1c012525e Mon Sep 17 00:00:00 2001 From: Runyasak Chaengnaimuang Date: Sun, 21 Sep 2025 13:01:03 +0700 Subject: [PATCH 45/58] :sparkles: feat: add enum conversion --- src/openapi.ts | 131 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 29 deletions(-) diff --git a/src/openapi.ts b/src/openapi.ts index 137efd7..bdfa82a 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -203,6 +203,48 @@ export const unwrapSchema = ( return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() } +const convertEnumToOpenAPI = (schema: any): any => { + if (!schema || typeof schema !== 'object') return schema + + if ( + schema[Kind] === 'Union' && + schema.anyOf && + Array.isArray(schema.anyOf) && + schema.anyOf.length > 0 && + schema.anyOf.every( + (item: any) => + item && typeof item === 'object' && item.const !== undefined + ) + ) { + const enumValues = schema.anyOf.map((item: any) => item.const) + + return { + type: 'string', + enum: enumValues + } + } + + if (schema.type === 'object' && schema.properties) { + const convertedProperties: any = {} + for (const [key, value] of Object.entries(schema.properties)) { + convertedProperties[key] = convertEnumToOpenAPI(value) + } + return { + ...schema, + properties: convertedProperties + } + } + + if (schema.type === 'array' && schema.items) { + return { + ...schema, + items: convertEnumToOpenAPI(schema.items) + } + } + + return schema +} + /** * Converts Elysia routes to OpenAPI 3.0.3 paths schema * @param routes Array of Elysia route objects @@ -342,9 +384,17 @@ export function toOpenAPISchema( definitions ) - if (params && params.type === 'object' && params.properties) + if (params && params.type === 'object' && params.properties) { + const convertedProperties: any = {} for (const [paramName, paramSchema] of Object.entries( params.properties + )) { + convertedProperties[paramName] = + convertEnumToOpenAPI(paramSchema) + } + + for (const [paramName, paramSchema] of Object.entries( + convertedProperties )) parameters.push({ name: paramName, @@ -352,6 +402,7 @@ export function toOpenAPISchema( required: true, // Path parameters are always required schema: paramSchema }) + } } // Handle query parameters @@ -362,9 +413,17 @@ export function toOpenAPISchema( ) if (query && query.type === 'object' && query.properties) { - const required = query.required || [] + const convertedProperties: any = {} for (const [queryName, querySchema] of Object.entries( query.properties + )) { + convertedProperties[queryName] = + convertEnumToOpenAPI(querySchema) + } + + const required = query.required || [] + for (const [queryName, querySchema] of Object.entries( + convertedProperties )) parameters.push({ name: queryName, @@ -383,9 +442,17 @@ export function toOpenAPISchema( ) if (headers && headers.type === 'object' && headers.properties) { - const required = headers.required || [] + const convertedProperties: any = {} for (const [headerName, headerSchema] of Object.entries( headers.properties + )) { + convertedProperties[headerName] = + convertEnumToOpenAPI(headerSchema) + } + + const required = headers.required || [] + for (const [headerName, headerSchema] of Object.entries( + convertedProperties )) parameters.push({ name: headerName, @@ -404,9 +471,17 @@ export function toOpenAPISchema( ) if (cookie && cookie.type === 'object' && cookie.properties) { - const required = cookie.required || [] + const convertedProperties: any = {} for (const [cookieName, cookieSchema] of Object.entries( cookie.properties + )) { + convertedProperties[cookieName] = + convertEnumToOpenAPI(cookieSchema) + } + + const required = cookie.required || [] + for (const [cookieName, cookieSchema] of Object.entries( + convertedProperties )) parameters.push({ name: cookieName, @@ -425,11 +500,10 @@ export function toOpenAPISchema( const body = unwrapSchema(hooks.body, vendors) if (body) { + const convertedBody = convertEnumToOpenAPI(body) // @ts-ignore - const { type, description, $ref, ...options } = unwrapReference( - body, - definitions - ) + const { type: _type, description, $ref, ...options } = convertedBody + const type = _type as string | undefined // @ts-ignore if (hooks.parse) { @@ -447,26 +521,24 @@ export function toOpenAPISchema( switch (parser.fn) { case 'text': case 'text/plain': - content['text/plain'] = { schema: body } + content['text/plain'] = { schema: convertedBody } continue case 'urlencoded': case 'application/x-www-form-urlencoded': content['application/x-www-form-urlencoded'] = { - schema: body + schema: convertedBody } continue case 'json': case 'application/json': - content['application/json'] = { schema: body } + content['application/json'] = { schema: convertedBody } continue case 'formdata': case 'multipart/form-data': - content['multipart/form-data'] = { - schema: body - } + content['multipart/form-data'] = { schema: convertedBody } continue } } @@ -485,19 +557,17 @@ export function toOpenAPISchema( type === 'integer' || type === 'boolean' ? { - 'text/plain': { - schema: body - } + 'text/plain': convertedBody } : { 'application/json': { - schema: body + schema: convertedBody }, 'application/x-www-form-urlencoded': { - schema: body + schema: convertedBody }, 'multipart/form-data': { - schema: body + schema: convertedBody } }, required: true @@ -522,9 +592,10 @@ export function toOpenAPISchema( if (!response) continue + const convertedResponse = convertEnumToOpenAPI(response) // @ts-ignore Must exclude $ref from root options - const { type, description, $ref, ...options } = - unwrapReference(response, definitions) + const { type: _type, description, $ref, ...options } = convertedResponse + const type = _type as string | undefined operation.responses[status] = { description: @@ -533,19 +604,19 @@ export function toOpenAPISchema( type === 'void' || type === 'null' || type === 'undefined' - ? ({ type, description } as any) + ? (convertedResponse as any) : type === 'string' || type === 'number' || type === 'integer' || type === 'boolean' ? { 'text/plain': { - schema: response + schema: convertedResponse } } : { 'application/json': { - schema: response + schema: convertedResponse } } } @@ -554,12 +625,14 @@ export function toOpenAPISchema( const response = unwrapSchema(hooks.response as any, vendors) if (response) { + const convertedResponse = convertEnumToOpenAPI(response) + // @ts-ignore const { type: _type, description, ...options - } = unwrapReference(response, definitions) + } = convertedResponse const type = _type as string | undefined // It's a single schema, default to 200 @@ -569,19 +642,19 @@ export function toOpenAPISchema( type === 'void' || type === 'null' || type === 'undefined' - ? ({ type, description } as any) + ? (convertedResponse as any) : type === 'string' || type === 'number' || type === 'integer' || type === 'boolean' ? { 'text/plain': { - schema: response + schema: convertedResponse } } : { 'application/json': { - schema: response + schema: convertedResponse } } } From aa3bbdd454270deb902779a0abc93cb912741af9 Mon Sep 17 00:00:00 2001 From: Runyasak Chaengnaimuang Date: Sun, 21 Sep 2025 19:41:37 +0700 Subject: [PATCH 46/58] :wrench: fix: rename function to convertEnumToOpenApi --- src/openapi.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/openapi.ts b/src/openapi.ts index bdfa82a..bd6a0cd 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -203,7 +203,7 @@ export const unwrapSchema = ( return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() } -const convertEnumToOpenAPI = (schema: any): any => { +const convertEnumToOpenApi = (schema: any): any => { if (!schema || typeof schema !== 'object') return schema if ( @@ -227,7 +227,7 @@ const convertEnumToOpenAPI = (schema: any): any => { if (schema.type === 'object' && schema.properties) { const convertedProperties: any = {} for (const [key, value] of Object.entries(schema.properties)) { - convertedProperties[key] = convertEnumToOpenAPI(value) + convertedProperties[key] = convertEnumToOpenApi(value) } return { ...schema, @@ -238,7 +238,7 @@ const convertEnumToOpenAPI = (schema: any): any => { if (schema.type === 'array' && schema.items) { return { ...schema, - items: convertEnumToOpenAPI(schema.items) + items: convertEnumToOpenApi(schema.items) } } @@ -390,7 +390,7 @@ export function toOpenAPISchema( params.properties )) { convertedProperties[paramName] = - convertEnumToOpenAPI(paramSchema) + convertEnumToOpenApi(paramSchema) } for (const [paramName, paramSchema] of Object.entries( @@ -418,7 +418,7 @@ export function toOpenAPISchema( query.properties )) { convertedProperties[queryName] = - convertEnumToOpenAPI(querySchema) + convertEnumToOpenApi(querySchema) } const required = query.required || [] @@ -447,7 +447,7 @@ export function toOpenAPISchema( headers.properties )) { convertedProperties[headerName] = - convertEnumToOpenAPI(headerSchema) + convertEnumToOpenApi(headerSchema) } const required = headers.required || [] @@ -476,7 +476,7 @@ export function toOpenAPISchema( cookie.properties )) { convertedProperties[cookieName] = - convertEnumToOpenAPI(cookieSchema) + convertEnumToOpenApi(cookieSchema) } const required = cookie.required || [] @@ -500,7 +500,8 @@ export function toOpenAPISchema( const body = unwrapSchema(hooks.body, vendors) if (body) { - const convertedBody = convertEnumToOpenAPI(body) + const convertedBody = convertEnumToOpenApi(body) + // @ts-ignore const { type: _type, description, $ref, ...options } = convertedBody const type = _type as string | undefined @@ -592,7 +593,7 @@ export function toOpenAPISchema( if (!response) continue - const convertedResponse = convertEnumToOpenAPI(response) + const convertedResponse = convertEnumToOpenApi(response) // @ts-ignore Must exclude $ref from root options const { type: _type, description, $ref, ...options } = convertedResponse const type = _type as string | undefined @@ -625,7 +626,7 @@ export function toOpenAPISchema( const response = unwrapSchema(hooks.response as any, vendors) if (response) { - const convertedResponse = convertEnumToOpenAPI(response) + const convertedResponse = convertEnumToOpenApi(response) // @ts-ignore const { From f37218866a63bac6507c575c7c7b95aa5b65477d Mon Sep 17 00:00:00 2001 From: Runyasak Chaengnaimuang Date: Sun, 21 Sep 2025 19:42:09 +0700 Subject: [PATCH 47/58] :sparkles: feat: add tests --- src/openapi.ts | 2 +- test/openapi.test.ts | 62 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/openapi.ts b/src/openapi.ts index bd6a0cd..c0e875b 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -203,7 +203,7 @@ export const unwrapSchema = ( return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() } -const convertEnumToOpenApi = (schema: any): any => { +export const convertEnumToOpenApi = (schema: any): any => { if (!schema || typeof schema !== 'object') return schema if ( diff --git a/test/openapi.test.ts b/test/openapi.test.ts index 1a54bad..dfc0571 100644 --- a/test/openapi.test.ts +++ b/test/openapi.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'bun:test' -import { getPossiblePath } from '../src/openapi' +import { Kind } from '@sinclair/typebox' +import { convertEnumToOpenApi, getPossiblePath } from '../src/openapi' describe('OpenAPI utilities', () => { it('getPossiblePath', () => { @@ -12,3 +13,62 @@ describe('OpenAPI utilities', () => { ]) }) }) + +describe('convertEnumToOpenApi', () => { + it('should convert enum schema to OpenAPI enum format', () => { + const expectedSchema = { + [Kind]: 'Union', + anyOf: [ + { const: 'male' }, + { const: 'female' } + ] + } + + const result = convertEnumToOpenApi(expectedSchema) + + 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 = convertEnumToOpenApi(expectedSchema) + + 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 = convertEnumToOpenApi(expectedSchema) + + expect(result).toEqual(expectedSchema) + }) +}) From 303be109255662a5bc3ee1913db43ef0fa8741dd Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 00:12:41 +0700 Subject: [PATCH 48/58] :broom: chore: refactor enumToOpenAPI --- CHANGELOG.md | 4 + src/openapi.ts | 175 +++++++++++++++++++------------------------ test/openapi.test.ts | 143 +++++++++++++++++------------------ 3 files changed, 150 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2760bfd..8366a71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.4.10 - 22 Sep 2025 +Bug fix: +- [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId + # 1.4.9 - 21 Sep 2025 Improvement: - type gen: match special, ad non-english character diff --git a/src/openapi.ts b/src/openapi.ts index c0e875b..9309aa5 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -2,7 +2,7 @@ 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, type TProperties } from '@sinclair/typebox' +import { Kind, TAnySchema, type TProperties } from '@sinclair/typebox' import type { AdditionalReference, @@ -10,6 +10,7 @@ import type { ElysiaOpenAPIConfig, MapJsonSchema } from './types' +import { defineConfig } from 'tsup' export const capitalize = (word: string) => word.charAt(0).toUpperCase() + word.slice(1) @@ -121,7 +122,7 @@ const unwrapReference = ( const name = ref.slice(ref.lastIndexOf('/') + 1) if (ref && definitions[name]) schema = definitions[name] as T - return schema as any + return enumToOpenApi(schema) as any } export const unwrapSchema = ( @@ -131,7 +132,7 @@ export const unwrapSchema = ( if (!schema) return if (typeof schema === 'string') schema = toRef(schema) - if (Kind in schema) return schema + if (Kind in schema) return enumToOpenApi(schema) if (Kind in schema || !schema?.['~standard']) return @@ -139,7 +140,7 @@ export const unwrapSchema = ( const vendor = schema['~standard'].vendor if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === 'function') - return mapJsonSchema[vendor](schema) + return enumToOpenApi(mapJsonSchema[vendor](schema)) switch (vendor) { case 'zod': @@ -197,52 +198,62 @@ export const unwrapSchema = ( if (vendor === 'arktype') // @ts-ignore - return schema?.toJsonSchema?.() + return enumToOpenApi(schema?.toJsonSchema?.()) // @ts-ignore - return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() + return enumToOpenApi(schema.toJSONSchema?.() ?? schema?.toJsonSchema?.()) } -export const convertEnumToOpenApi = (schema: any): any => { - if (!schema || typeof schema !== 'object') return schema - - if ( - schema[Kind] === 'Union' && - schema.anyOf && - Array.isArray(schema.anyOf) && - schema.anyOf.length > 0 && - schema.anyOf.every( - (item: any) => - item && typeof item === 'object' && item.const !== undefined - ) - ) { - const enumValues = schema.anyOf.map((item: any) => item.const) +export const enumToOpenApi = < + T extends + | TAnySchema + | OpenAPIV3.SchemaObject + | OpenAPIV3.ReferenceObject + | undefined +>( + _schema: T +): T => { + if (!_schema || typeof _schema !== 'object') return _schema - return { - type: 'string', - enum: enumValues - } + 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 convertedProperties: any = {} - for (const [key, value] of Object.entries(schema.properties)) { - convertedProperties[key] = convertEnumToOpenApi(value) - } + const properties: Record = {} + for (const [key, value] of Object.entries(schema.properties)) + properties[key] = enumToOpenApi(value) + return { ...schema, - properties: convertedProperties - } + properties + } as T } - if (schema.type === 'array' && schema.items) { + if (schema.type === 'array' && schema.items) return { ...schema, - items: convertEnumToOpenApi(schema.items) - } - } + items: enumToOpenApi(schema.items) + } as T - return schema + return schema as T } /** @@ -337,7 +348,6 @@ export function toOpenAPISchema( (hooks.response as TSchema).$ref || (hooks.response as any)['~standard'] ) - // @ts-ignore hooks.response = { 200: hooks.response as any } @@ -384,17 +394,9 @@ export function toOpenAPISchema( definitions ) - if (params && params.type === 'object' && params.properties) { - const convertedProperties: any = {} + if (params && params.type === 'object' && params.properties) for (const [paramName, paramSchema] of Object.entries( params.properties - )) { - convertedProperties[paramName] = - convertEnumToOpenApi(paramSchema) - } - - for (const [paramName, paramSchema] of Object.entries( - convertedProperties )) parameters.push({ name: paramName, @@ -402,7 +404,6 @@ export function toOpenAPISchema( required: true, // Path parameters are always required schema: paramSchema }) - } } // Handle query parameters @@ -413,17 +414,9 @@ export function toOpenAPISchema( ) if (query && query.type === 'object' && query.properties) { - const convertedProperties: any = {} - for (const [queryName, querySchema] of Object.entries( - query.properties - )) { - convertedProperties[queryName] = - convertEnumToOpenApi(querySchema) - } - const required = query.required || [] for (const [queryName, querySchema] of Object.entries( - convertedProperties + query.properties )) parameters.push({ name: queryName, @@ -442,17 +435,9 @@ export function toOpenAPISchema( ) if (headers && headers.type === 'object' && headers.properties) { - const convertedProperties: any = {} - for (const [headerName, headerSchema] of Object.entries( - headers.properties - )) { - convertedProperties[headerName] = - convertEnumToOpenApi(headerSchema) - } - const required = headers.required || [] for (const [headerName, headerSchema] of Object.entries( - convertedProperties + headers.properties )) parameters.push({ name: headerName, @@ -471,17 +456,9 @@ export function toOpenAPISchema( ) if (cookie && cookie.type === 'object' && cookie.properties) { - const convertedProperties: any = {} - for (const [cookieName, cookieSchema] of Object.entries( - cookie.properties - )) { - convertedProperties[cookieName] = - convertEnumToOpenApi(cookieSchema) - } - const required = cookie.required || [] for (const [cookieName, cookieSchema] of Object.entries( - convertedProperties + cookie.properties )) parameters.push({ name: cookieName, @@ -500,11 +477,11 @@ export function toOpenAPISchema( const body = unwrapSchema(hooks.body, vendors) if (body) { - const convertedBody = convertEnumToOpenApi(body) - // @ts-ignore - const { type: _type, description, $ref, ...options } = convertedBody - const type = _type as string | undefined + const { type, description, $ref, ...options } = unwrapReference( + body, + definitions + ) // @ts-ignore if (hooks.parse) { @@ -522,24 +499,26 @@ export function toOpenAPISchema( switch (parser.fn) { case 'text': case 'text/plain': - content['text/plain'] = { schema: convertedBody } + content['text/plain'] = { schema: body } continue case 'urlencoded': case 'application/x-www-form-urlencoded': content['application/x-www-form-urlencoded'] = { - schema: convertedBody + schema: body } continue case 'json': case 'application/json': - content['application/json'] = { schema: convertedBody } + content['application/json'] = { schema: body } continue case 'formdata': case 'multipart/form-data': - content['multipart/form-data'] = { schema: convertedBody } + content['multipart/form-data'] = { + schema: body + } continue } } @@ -558,17 +537,19 @@ export function toOpenAPISchema( type === 'integer' || type === 'boolean' ? { - 'text/plain': convertedBody + 'text/plain': { + schema: body + } } : { 'application/json': { - schema: convertedBody + schema: body }, 'application/x-www-form-urlencoded': { - schema: convertedBody + schema: body }, 'multipart/form-data': { - schema: convertedBody + schema: body } }, required: true @@ -593,10 +574,9 @@ export function toOpenAPISchema( if (!response) continue - const convertedResponse = convertEnumToOpenApi(response) // @ts-ignore Must exclude $ref from root options - const { type: _type, description, $ref, ...options } = convertedResponse - const type = _type as string | undefined + const { type, description, $ref, ...options } = + unwrapReference(response, definitions) operation.responses[status] = { description: @@ -605,19 +585,19 @@ export function toOpenAPISchema( type === 'void' || type === 'null' || type === 'undefined' - ? (convertedResponse as any) + ? ({ type, description } as any) : type === 'string' || type === 'number' || type === 'integer' || type === 'boolean' ? { 'text/plain': { - schema: convertedResponse + schema: response } } : { 'application/json': { - schema: convertedResponse + schema: response } } } @@ -626,14 +606,12 @@ export function toOpenAPISchema( const response = unwrapSchema(hooks.response as any, vendors) if (response) { - const convertedResponse = convertEnumToOpenApi(response) - // @ts-ignore const { type: _type, description, ...options - } = convertedResponse + } = unwrapReference(response, definitions) const type = _type as string | undefined // It's a single schema, default to 200 @@ -643,19 +621,19 @@ export function toOpenAPISchema( type === 'void' || type === 'null' || type === 'undefined' - ? (convertedResponse as any) + ? ({ type, description } as any) : type === 'string' || type === 'number' || type === 'integer' || type === 'boolean' ? { 'text/plain': { - schema: convertedResponse + schema: response } } : { 'application/json': { - schema: convertedResponse + schema: response } } } @@ -664,7 +642,8 @@ export function toOpenAPISchema( } for (let path of getPossiblePath(route.path)) { - const operationId = toOperationId(route.method, path) + const operationId = + hooks.detail?.operationId ?? toOperationId(route.method, path) path = path.replace(/:([^/]+)/g, '{$1}') diff --git a/test/openapi.test.ts b/test/openapi.test.ts index dfc0571..abf0e63 100644 --- a/test/openapi.test.ts +++ b/test/openapi.test.ts @@ -1,74 +1,69 @@ -import { describe, it, expect } from 'bun:test' -import { Kind } from '@sinclair/typebox' -import { convertEnumToOpenApi, getPossiblePath } from '../src/openapi' - -describe('OpenAPI utilities', () => { - it('getPossiblePath', () => { - expect(getPossiblePath('/user/:user?/name/:name?')).toEqual([ - '/user/:user/name/:name', - '/user/name/:name', - '/user/name', - '/user/:user/name', - '/user/name' - ]) - }) -}) - -describe('convertEnumToOpenApi', () => { - it('should convert enum schema to OpenAPI enum format', () => { - const expectedSchema = { - [Kind]: 'Union', - anyOf: [ - { const: 'male' }, - { const: 'female' } - ] - } - - const result = convertEnumToOpenApi(expectedSchema) - - 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 = convertEnumToOpenApi(expectedSchema) - - 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 = convertEnumToOpenApi(expectedSchema) - - expect(result).toEqual(expectedSchema) - }) -}) +import { describe, it, expect } from 'bun:test' +import { Kind, TAnySchema } from '@sinclair/typebox' +import { enumToOpenApi, getPossiblePath } from '../src/openapi' +import { t } from 'elysia' + +describe('OpenAPI utilities', () => { + it('getPossiblePath', () => { + expect(getPossiblePath('/user/:user?/name/:name?')).toEqual([ + '/user/:user/name/:name', + '/user/name/:name', + '/user/name', + '/user/:user/name', + '/user/name' + ]) + }) +}) + +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) + }) +}) From 9d4138fb0dc8f5227c12d84f06143f8c26d3018e Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 00:15:14 +0700 Subject: [PATCH 49/58] :broom: chore: refactor enumToOpenAPI --- test/openapi/enum-to-openapi.test.ts | 57 ++++++++++++++++++++++++++ test/openapi/get-possible-path.test.ts | 21 ++++++++++ 2 files changed, 78 insertions(+) create mode 100644 test/openapi/enum-to-openapi.test.ts create mode 100644 test/openapi/get-possible-path.test.ts 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' + ]) + }) +}) From 6d4efbbe808ca15ddb41feb5ea9440baf9c74fda Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 01:39:36 +0700 Subject: [PATCH 50/58] :broom: chore: test case --- CHANGELOG.md | 6 + example/c.ts | 21 + example/gen.ts | 1 + example/index.ts | 163 +--- src/gen/index.ts | 102 +- src/openapi.ts | 57 +- test/gen/index.test.ts | 681 +++++++++++++ test/gen/sample.ts | 61 ++ test/openapi.test.ts | 16 +- test/openapi/to-openapi-schema.test.ts | 1222 ++++++++++++++++++++++++ 10 files changed, 2088 insertions(+), 242 deletions(-) create mode 100644 example/c.ts create mode 100644 test/gen/index.test.ts create mode 100644 test/gen/sample.ts create mode 100644 test/openapi/to-openapi-schema.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8366a71..e82eccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # 1.4.10 - 22 Sep 2025 +Improvement: +- populate params from path when no schema is provided +- accept operationId from detail +- type gen: accept number as path segment +- add test case for type gen, and OpenAPI schema + Bug fix: - [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId diff --git a/example/c.ts b/example/c.ts new file mode 100644 index 0000000..0884401 --- /dev/null +++ b/example/c.ts @@ -0,0 +1,21 @@ +import { declarationToJSONSchema } from '../src/gen' + +console.log( + declarationToJSONSchema(`{ + "hello-world": { + 2: { + get: { + params: { } + query: { } + headers: { } + body: { } + response: { + 200: { + name: string + } + } + } + } + } + }`) +) diff --git a/example/gen.ts b/example/gen.ts index 7146693..a1f64b7 100644 --- a/example/gen.ts +++ b/example/gen.ts @@ -48,6 +48,7 @@ export const app = new Elysia() } } ) + .get('/hello/2', () => 'hello') .post( '/json', ({ body, status }) => (Math.random() > 0.5 ? status(418) : body), diff --git a/example/index.ts b/example/index.ts index 0c62643..3e309e8 100644 --- a/example/index.ts +++ b/example/index.ts @@ -18,158 +18,17 @@ const user = t.Object({ }) }) -export const app = new Elysia() - .use( - openapi({ - provider: 'scalar', - mapJsonSchema: { - zod: z.toJSONSchema, - effect: JSONSchema.make - }, - documentation: { - info: { - title: 'Elysia Scalar', - version: '1.3.1a' - }, - tags: [ - { - name: 'Test', - description: 'Hello' - } - ], - components: { - securitySchemes: { - bearer: { - type: 'http', - scheme: 'bearer' - }, - cookie: { - type: 'apiKey', - in: 'cookie', - name: 'session_id' - } - } - } - } - }) - ) - .model({ schema, schema2, user }) - .model({ - idParam: t.Object({ - id: t.Union([ - t.String({ format: 'uuid' }), - t.Number({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }) - ]), - id2: t.String() - }), - response200: t.Object({ - message: t.String(), - content: t.Array(t.Object({ - id: t.Union([t.String(), t.Number()]) - })) - }) +const model = new Elysia().model( + 'body', + t.Object({ + name: t.Literal('Lilith') }) - .get('/test/:id/:id2', ({ params: { id } }) => ({ - message: 'ok', - content: [{ id }] - }), { - params: 'idParam', - response: 'response200' +) + +const app = new Elysia() + .use(openapi()) + .use(model) + .post('/user', () => 'hello', { + body: 'body' }) - .get( - '/', - { test: 'hello' as const }, - { - response: { - 200: t.Object({ - test: t.Literal('hello') - }), - 204: withHeaders( - t.Void({ - title: 'Thing', - description: 'Void response' - }), - { - 'X-Custom-Header': t.Literal('Elysia') - } - ) - } - } - ) - .post( - '/json', - ({ body }) => ({ - test: 'hello' - }), - { - parse: ['json', 'formdata'], - body: 'schema', - response: { - 200: t.Object({ - test: t.Literal('hello') - }), - 400: z.object({ - a: z.string(), - b: z.literal('a') - }), - 401: Schema.standardSchemaV1( - Schema.Struct({ - a: Schema.Literal('hi') - }) - ) - } - } - ) - .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' } - ) - } - ) - .get('/id/:id/name/:name', () => {}) .listen(3000) diff --git a/src/gen/index.ts b/src/gen/index.ts index 7252957..d449cb9 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -12,13 +12,10 @@ import { TypeBox } from '@sinclair/typemap' import { tmpdir } from 'os' import { join } from 'path' import { spawnSync } from 'child_process' -import { AdditionalReference, AdditionalReferences } from '../types' -import { Kind, TObject } from '@sinclair/typebox/type' -import { readdir } from 'fs/promises' +import { AdditionalReference } from '../types' const matchRoute = /: Elysia<(.*)>/gs -const matchStatus = /(\d{3}):/g -const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":') +const numberKey = /(\d+):/g interface OpenAPIGeneratorOptions { /** @@ -90,7 +87,6 @@ function extractRootObjects(code: string) { const colonIdx = code.indexOf(':', i) if (colonIdx === -1) break - // --- find key --- // walk backwards from colon to find start of key let keyEnd = colonIdx - 1 while (keyEnd >= 0 && /\s/.test(code[keyEnd])) keyEnd-- @@ -127,6 +123,52 @@ function extractRootObjects(code: string) { 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 * @@ -155,6 +197,10 @@ export const fromTypes = ) => () => { try { + // targetFilePath is an actual dts reference + if (targetFilePath.trim().startsWith('{')) + return declarationToJSONSchema(targetFilePath) + if ( !targetFilePath.endsWith('.ts') && !targetFilePath.endsWith('.tsx') @@ -326,49 +372,7 @@ export const fromTypes = ) ) - const routes: AdditionalReference = {} - - // Treaty is a collection of { ... } & { ... } & { ... } - for (const route of extractRootObjects( - instance.slice(2).replace(matchStatus, '"$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 + return declarationToJSONSchema(instance.slice(2)) } catch (error) { console.warn( '[@elysiajs/openapi/gen] Failed to generate OpenAPI schema' diff --git a/src/openapi.ts b/src/openapi.ts index 9309aa5..886ce51 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -395,15 +395,24 @@ export function toOpenAPISchema( ) if (params && params.type === 'object' && params.properties) - for (const [paramName, paramSchema] of Object.entries( - params.properties - )) + for (const [name, schema] of Object.entries(params.properties)) parameters.push({ - name: paramName, + name, in: 'path', required: true, // Path parameters are always required - schema: paramSchema + 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 @@ -415,14 +424,12 @@ export function toOpenAPISchema( if (query && query.type === 'object' && query.properties) { const required = query.required || [] - for (const [queryName, querySchema] of Object.entries( - query.properties - )) + for (const [name, schema] of Object.entries(query.properties)) parameters.push({ - name: queryName, + name, in: 'query', - required: required.includes(queryName), - schema: querySchema + required: required.includes(name), + schema }) } } @@ -430,20 +437,18 @@ export function toOpenAPISchema( // Handle header parameters if (hooks.headers) { const headers = unwrapReference( - unwrapSchema(hooks.query, vendors), + unwrapSchema(hooks.headers, vendors), definitions ) if (headers && headers.type === 'object' && headers.properties) { const required = headers.required || [] - for (const [headerName, headerSchema] of Object.entries( - headers.properties - )) + for (const [name, schema] of Object.entries(headers.properties)) parameters.push({ - name: headerName, + name, in: 'header', - required: required.includes(headerName), - schema: headerSchema + required: required.includes(name), + schema }) } } @@ -457,14 +462,12 @@ export function toOpenAPISchema( if (cookie && cookie.type === 'object' && cookie.properties) { const required = cookie.required || [] - for (const [cookieName, cookieSchema] of Object.entries( - cookie.properties - )) + for (const [name, schema] of Object.entries(cookie.properties)) parameters.push({ - name: cookieName, + name, in: 'cookie', - required: required.includes(cookieName), - schema: cookieSchema + required: required.includes(name), + schema }) } } @@ -531,6 +534,7 @@ export function toOpenAPISchema( } else { operation.requestBody = { description, + required: true, content: type === 'string' || type === 'number' || @@ -551,8 +555,7 @@ export function toOpenAPISchema( 'multipart/form-data': { schema: body } - }, - required: true + } } } } @@ -575,7 +578,7 @@ export function toOpenAPISchema( if (!response) continue // @ts-ignore Must exclude $ref from root options - const { type, description, $ref, ...options } = + const { type, description, $ref, ..._options } = unwrapReference(response, definitions) operation.responses[status] = { diff --git a/test/gen/index.test.ts b/test/gen/index.test.ts new file mode 100644 index 0000000..499634b --- /dev/null +++ b/test/gen/index.test.ts @@ -0,0 +1,681 @@ +import { describe, it, expect } from 'bun:test' +import { Kind } from '@sinclair/typebox' + +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/openapi.test.ts b/test/openapi.test.ts index abf0e63..7929f8d 100644 --- a/test/openapi.test.ts +++ b/test/openapi.test.ts @@ -1,19 +1,7 @@ import { describe, it, expect } from 'bun:test' -import { Kind, TAnySchema } from '@sinclair/typebox' -import { enumToOpenApi, getPossiblePath } from '../src/openapi' -import { t } from 'elysia' +import { Kind } from '@sinclair/typebox' -describe('OpenAPI utilities', () => { - it('getPossiblePath', () => { - expect(getPossiblePath('/user/:user?/name/:name?')).toEqual([ - '/user/:user/name/:name', - '/user/name/:name', - '/user/name', - '/user/:user/name', - '/user/name' - ]) - }) -}) +import { enumToOpenApi } from '../src/openapi' describe('convertEnumToOpenApi', () => { it('should convert enum schema to OpenAPI enum format', () => { 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 + } + } + } + } + }) + }) +}) From 8a775ca3d2454b187377a889094ca57e2259fd00 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 01:44:27 +0700 Subject: [PATCH 51/58] :wrench: fix: #262 when failed to convert type to OpenAPI, log the error and continue --- CHANGELOG.md | 1 + example/index.ts | 36 ++++++---------- src/openapi.ts | 109 +++++++++++++++++++++++++---------------------- 3 files changed, 72 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82eccb..0e7ba3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Improvement: - accept operationId from detail - type gen: accept number as path segment - add test case for type gen, and OpenAPI schema +- when failed to convert type to OpenAPI, log the error and continue Bug fix: - [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId diff --git a/example/index.ts b/example/index.ts index 3e309e8..f87c171 100644 --- a/example/index.ts +++ b/example/index.ts @@ -4,31 +4,19 @@ import { JSONSchema, Schema } from 'effect' import { openapi, withHeaders } from '../src/index' -const schema = t.Object({ - test: t.Literal('hello') -}) - -const schema2 = t.Object({ - test: t.Literal('world') -}) - -const user = t.Object({ - name: t.String({ - example: 'saltyaom' - }) -}) - -const model = new Elysia().model( - 'body', - t.Object({ - name: t.Literal('Lilith') - }) -) +// const a = z.toJSONSchema(z.void()) const app = new Elysia() - .use(openapi()) - .use(model) - .post('/user', () => 'hello', { - body: 'body' + .use( + openapi({ + mapJsonSchema: { + zod: z.toJSONSchema + } + }) + ) + .get('/test', ({ status }) => status(204, undefined), { + response: { + 204: z.void() + } }) .listen(3000) diff --git a/src/openapi.ts b/src/openapi.ts index 886ce51..d99e9c7 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -139,69 +139,78 @@ export const unwrapSchema = ( // @ts-ignore const vendor = schema['~standard'].vendor - if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === 'function') - return enumToOpenApi(mapJsonSchema[vendor](schema)) + try { + if ( + mapJsonSchema?.[vendor] && + typeof mapJsonSchema[vendor] === 'function' + ) + return enumToOpenApi(mapJsonSchema[vendor](schema)) - switch (vendor) { - case 'zod': - if (warned.zod4 || warned.zod3) break + switch (vendor) { + case 'zod': + if (warned.zod4 || warned.zod3) break - console.warn( - "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema" - ) + console.warn( + "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema" + ) + + if ('_zod' in schema) { + warned.zod4 = true - 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( - 'For Zod v4, please provide z.toJSONSchema as follows:\n' + '[@elysiajs/openapi] Valibot require a separate package for JSON Schema conversion' ) - 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' + 'Please install @valibot/to-json-schema package and use it like this:\n' ) - console.warn(warnings.zod3) - } - break + console.warn(warnings.valibot) + break - case 'valibot': - if (warned.valibot) break - warned.valibot = true + 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] 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 + 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 + } - 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 + if (vendor === 'arktype') + // @ts-ignore + return enumToOpenApi(schema?.toJsonSchema?.()) - 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 + return enumToOpenApi( + // @ts-ignore + schema.toJSONSchema?.() ?? schema?.toJsonSchema?.() + ) + } catch (error) { + console.warn(error) } - - if (vendor === 'arktype') - // @ts-ignore - return enumToOpenApi(schema?.toJsonSchema?.()) - - // @ts-ignore - return enumToOpenApi(schema.toJSONSchema?.() ?? schema?.toJsonSchema?.()) } export const enumToOpenApi = < From ebdf2f504db4322022967b334f24c13f9e2844f0 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 02:11:31 +0700 Subject: [PATCH 52/58] :wrench: fix: #256 export fromTypes from index --- CHANGELOG.md | 3 ++ example/index.ts | 2 - src/gen/index.ts | 109 ++++++++++++++++++++++++++++++----------------- src/index.ts | 1 + 4 files changed, 73 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7ba3d..373c966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Improvement: - type gen: accept number as path segment - add test case for type gen, and OpenAPI schema - 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 Bug fix: - [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId diff --git a/example/index.ts b/example/index.ts index f87c171..6ef806b 100644 --- a/example/index.ts +++ b/example/index.ts @@ -4,8 +4,6 @@ import { JSONSchema, Schema } from 'effect' import { openapi, withHeaders } from '../src/index' -// const a = z.toJSONSchema(z.void()) - const app = new Elysia() .use( openapi({ diff --git a/src/gen/index.ts b/src/gen/index.ts index d449cb9..336049b 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -1,23 +1,10 @@ -import type { InputSchema, InternalRoute, TSchema } from 'elysia' -import { - readFileSync, - mkdirSync, - writeFileSync, - rmSync, - existsSync, - readdirSync -} from 'fs' import { TypeBox } from '@sinclair/typemap' - -import { tmpdir } from 'os' -import { join } from 'path' -import { spawnSync } from 'child_process' -import { AdditionalReference } from '../types' +import type { AdditionalReference } from '../types' const matchRoute = /: Elysia<(.*)>/gs const numberKey = /(\d+):/g -interface OpenAPIGeneratorOptions { +export interface OpenAPIGeneratorOptions { /** * Path to tsconfig.json * @default tsconfig.json @@ -78,7 +65,12 @@ interface OpenAPIGeneratorOptions { silent?: boolean } -function extractRootObjects(code: string) { +/** + * 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 @@ -183,7 +175,7 @@ export const fromTypes = * * The path must export an Elysia instance */ - targetFilePath: string, + targetFilePath = 'src/index.ts', { tsconfigPath = 'tsconfig.json', instanceName, @@ -191,16 +183,30 @@ export const fromTypes = overrideOutputPath, debug = false, compilerOptions, - tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI'), + tmpRoot, silent = false }: OpenAPIGeneratorOptions = {} ) => () => { - try { - // targetFilePath is an actual dts reference - if (targetFilePath.trim().startsWith('{')) - return declarationToJSONSchema(targetFilePath) + // targetFilePath is an actual dts reference + if (targetFilePath.trim().startsWith('{')) + 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') @@ -214,27 +220,38 @@ export const fromTypes = ? targetFilePath : join(projectRoot, targetFilePath) - if (!existsSync(src)) + 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 (existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) + if (fs.existsSync(tmpRoot)) + fs.rmSync(tmpRoot, { recursive: true, force: true }) - mkdirSync(tmpRoot, { recursive: true }) + fs.mkdirSync(tmpRoot, { recursive: true }) const tsconfig = tsconfigPath.startsWith('/') ? tsconfigPath : join(projectRoot, tsconfigPath) - let extendsRef = existsSync(tsconfig) + let extendsRef = fs.existsSync(tsconfig) ? `"extends": "${join(projectRoot, 'tsconfig.json')}",` : '' @@ -250,7 +267,7 @@ export const fromTypes = distDir = distDir.replace(/\\/g, '/') } - writeFileSync( + fs.writeFileSync( join(tmpRoot, 'tsconfig.json'), `{ ${extendsRef} @@ -273,6 +290,17 @@ export const fromTypes = }` ) + 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, @@ -298,7 +326,7 @@ export const fromTypes = fileName.slice(fileName.indexOf('/') + 1) ) - let existed = existsSync(targetFile) + let existed = fs.existsSync(targetFile) if (!existed && !overrideOutputPath) { targetFile = join( @@ -308,21 +336,22 @@ export const fromTypes = fileName ) - existed = existsSync(targetFile) + existed = fs.existsSync(targetFile) } if (!existed) { - rmSync(join(tmpRoot, 'tsconfig.json')) + 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 (existsSync(join(tmpRoot, 'dist'))) { - const tempFiles = readdirSync(join(tmpRoot, 'dist'), { - recursive: true - }) + 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') @@ -344,11 +373,11 @@ export const fromTypes = } } - const declaration = readFileSync(targetFile, 'utf8') + const declaration = fs.readFileSync(targetFile, 'utf8') // Check just in case of race-condition - if (!debug && existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) + if (!debug && fs.existsSync(tmpRoot)) + fs.rmSync(tmpRoot, { recursive: true, force: true }) let instance = declaration.match( instanceName @@ -381,7 +410,7 @@ export const fromTypes = return } finally { - if (!debug && existsSync(tmpRoot)) - rmSync(tmpRoot, { recursive: true, force: true }) + if (!debug && tmpRoot && fs.existsSync(tmpRoot)) + fs.rmSync(tmpRoot, { recursive: true, force: true }) } } diff --git a/src/index.ts b/src/index.ts index 3b293c8..57c52b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -131,6 +131,7 @@ export const openapi = < return app } +export { fromTypes } from './gen' export { toOpenAPISchema, withHeaders } from './openapi' export type { ElysiaOpenAPIConfig } From 672646571daa8483852a4acf7f2749bd4c3a2735 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 02:17:54 +0700 Subject: [PATCH 53/58] :wrench: fix: #230, #265 do not inline Response if is Cloudflare Worker --- CHANGELOG.md | 1 + src/index.ts | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 373c966..031247f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Improvement: - type gen: use `process.getBuiltinModule` to import native node module conditionally - type gen: `fromTypes` now accept direct declaration - export `fromTypes` from index +- [#230](https://github.com/elysiajs/elysia-openapi/issues/230) do not inline Response if is Cloudflare Worker Bug fix: - [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId diff --git a/src/index.ts b/src/index.ts index 57c52b8..ecb42d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,28 @@ import type { OpenAPIV3 } from 'openapi-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 ( + 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 OpenAPI documentation page. * @@ -47,8 +69,7 @@ export const openapi = < .use((app) => { if (provider === null) return app - return app.get( - path, + const page = () => new Response( provider === 'swagger-ui' ? SwaggerUIRender(info, { @@ -70,13 +91,13 @@ export const openapi = < 'content-type': 'text/html; charset=utf8' } } - ), - { - detail: { - hide: true - } + ) + + return app.get(path, isCloudflareWorker() ? page : page(), { + detail: { + hide: true } - ) + }) }) .get( specPath, From 4d2d66525a0e4899fba3e608cf5d99c87831163a Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 02:20:01 +0700 Subject: [PATCH 54/58] :wrench: fix: type error --- src/gen/index.ts | 8 ++++++-- src/index.ts | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gen/index.ts b/src/gen/index.ts index 336049b..6847f63 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -174,6 +174,7 @@ 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', { @@ -188,8 +189,11 @@ export const fromTypes = }: OpenAPIGeneratorOptions = {} ) => () => { - // targetFilePath is an actual dts reference - if (targetFilePath.trim().startsWith('{')) + // targetFilePath is an actual TypeScript declaration + if ( + targetFilePath.trimStart().startsWith('{') && + targetFilePath.trimEnd().endsWith('}') + ) return declarationToJSONSchema(targetFilePath) if ( diff --git a/src/index.ts b/src/index.ts index ecb42d8..43448c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ 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' From 460d159d36ed8666eb8877297a37e1c9a973c4b9 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 02:21:49 +0700 Subject: [PATCH 55/58] :blue_book: doc: update release note for 1.4.10 --- CHANGELOG.md | 5 ++--- package.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 031247f..c52e9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,16 @@ # 1.4.10 - 22 Sep 2025 Improvement: - populate params from path when no schema is provided -- accept operationId from detail - type gen: accept number as path segment -- add test case for type gen, and OpenAPI schema - 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 -- [#230](https://github.com/elysiajs/elysia-openapi/issues/230) do not inline Response if is Cloudflare Worker +- 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: diff --git a/package.json b/package.json index 9296bd1..3e8374a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.9", + "version": "1.4.10", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", From ae8f8b1f0d531a7b6c1e6842844458613c274e4e Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 02:22:58 +0700 Subject: [PATCH 56/58] :blue_book: doc: update release note for 1.4.10 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52e9e3..023f185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 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 From 8e7f97967884c9846e6cfba8050a1493aa87c5b3 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 22 Sep 2025 02:58:28 +0700 Subject: [PATCH 57/58] :broom: chore: add test case for reference --- test/gen/index.test.ts | 1 - test/openapi/references.test.ts | 208 ++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 test/openapi/references.test.ts diff --git a/test/gen/index.test.ts b/test/gen/index.test.ts index 499634b..850b654 100644 --- a/test/gen/index.test.ts +++ b/test/gen/index.test.ts @@ -1,5 +1,4 @@ import { describe, it, expect } from 'bun:test' -import { Kind } from '@sinclair/typebox' import { declarationToJSONSchema, fromTypes } from '../../src/gen' 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' + } + } + } + } + } + }) + }) +}) From 070919ebec4ab984af8caadec75b6cbabe527c8e Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 25 Sep 2025 07:43:03 +0700 Subject: [PATCH 58/58] :tada: feat: add `data-configuration` for adding custom Scalar configuration --- CHANGELOG.md | 5 + example/c.ts | 21 --- example/index.ts | 1 + package.json | 2 +- src/index.ts | 117 ++++++++++------ src/scalar/index.ts | 332 +++++++++++++++++++++++--------------------- src/types.ts | 13 +- 7 files changed, 260 insertions(+), 231 deletions(-) delete mode 100644 example/c.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 023f185..901326a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 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 diff --git a/example/c.ts b/example/c.ts deleted file mode 100644 index 0884401..0000000 --- a/example/c.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { declarationToJSONSchema } from '../src/gen' - -console.log( - declarationToJSONSchema(`{ - "hello-world": { - 2: { - get: { - params: { } - query: { } - headers: { } - body: { } - response: { - 200: { - name: string - } - } - } - } - } - }`) -) diff --git a/example/index.ts b/example/index.ts index 6ef806b..4b5350c 100644 --- a/example/index.ts +++ b/example/index.ts @@ -7,6 +7,7 @@ import { openapi, withHeaders } from '../src/index' const app = new Elysia() .use( openapi({ + embedSchema: true, mapJsonSchema: { zod: z.toJSONSchema } diff --git a/package.json b/package.json index 3e8374a..ee4473a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/openapi", - "version": "1.4.10", + "version": "1.4.11", "description": "Plugin for Elysia to auto-generate API documentation", "author": { "name": "saltyAom", diff --git a/src/index.ts b/src/index.ts index 43448c6..b6b1b89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,7 +50,8 @@ export const openapi = < swagger, scalar, references, - mapJsonSchema + mapJsonSchema, + embedSpec }: ElysiaOpenAPIConfig = {}) => { if (!enabled) return new Elysia({ name: '@elysiajs/openapi' }) @@ -66,6 +67,38 @@ export const openapi = < 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) + ), + 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) + } + } + }) + } + const app = new Elysia({ name: '@elysiajs/openapi' }) .use((app) => { if (provider === null) return app @@ -80,13 +113,30 @@ export const openapi = < autoDarkMode: true, ...swagger }) - : 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' - }), + : 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' @@ -94,50 +144,27 @@ export const openapi = < } ) - return app.get(path, isCloudflareWorker() ? page : page(), { - detail: { - hide: true + return app.get( + path, + embedSpec || isCloudflareWorker() ? page : page(), + { + detail: { + hide: true + } } - }) + ) }) .get( specPath, - function openAPISchema() { - if (totalRoutes === app.routes.length) return cachedSchema + function openAPISchema(): OpenAPIV3.Document { + if (totalRoutes === app.routes.length && cachedSchema) + return cachedSchema totalRoutes = app.routes.length - const { - paths, - components: { schemas } - } = toOpenAPISchema(app, exclude, references, mapJsonSchema) - - return (cachedSchema = { - openapi: '3.0.3', - ...documentation, - tags: !exclude?.tags - ? documentation.tags - : documentation.tags?.filter( - (tag) => !exclude.tags?.includes(tag.name) - ), - 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) - } - } - } satisfies OpenAPIV3.Document) + return toFullSchema( + toOpenAPISchema(app, exclude, references, mapJsonSchema) + ) }, { error({ error }) { diff --git a/src/scalar/index.ts b/src/scalar/index.ts index f01c715..f32101d 100644 --- a/src/scalar/index.ts +++ b/src/scalar/index.ts @@ -1,162 +1,170 @@ -import type { OpenAPIV3 } from 'openapi-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, - config: NonNullable -) => ` - - - ${info.title} - - - - - - - - - - - -` +import type { OpenAPIV3 } from 'openapi-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, + config: NonNullable, + embedSpec?: string +) => ` + + + ${info.title} + + + + + + + + + + + +` diff --git a/src/types.ts b/src/types.ts index b52fcd2..23f19ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,8 +8,8 @@ export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null type MaybeArray = T | T[] export type MapJsonSchema = { [vendor: string]: Function } & { - [vendor in // schema['~standard'].vendor - | 'zod' + [vendor in // schema['~standard'].vendor + | 'zod' | 'effect' | 'valibot' | 'arktype' @@ -101,6 +101,15 @@ export interface ElysiaOpenAPIConfig< */ references?: AdditionalReferences + /** + * Embed OpenAPI schema to provider body if possible + * + * This is highly discouraged, unless you really have to inline OpenAPI schema + * + * @default false + */ + embedSpec?: boolean + /** * Mapping function from Standard schema to OpenAPI schema *