diff --git a/.gitattributes b/.gitattributes index 6eb545c0..c1d8dc9c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,3 @@ * text=auto * text eol=lf *.zip binary -# GitHub Linguist Override -.yarn/* linguist-vendored -.pnp.js linguist-vendored diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ec8bd189..4d4fd708 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,13 +14,16 @@ jobs: steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - run: corepack enable - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: ".nvmrc" - cache: "pnpm" - - run: pnpm install --frozen-lockfile + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.1 + + - uses: dtolnay/rust-toolchain@stable + + - run: bun install --frozen-lockfile - - run: pnpm check + - run: bun run check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7f6ee6fd..b97d44d1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,50 +12,55 @@ permissions: contents: read jobs: - build: + native: + strategy: + matrix: + os: [ubuntu-latest, macos-14, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.1 + - uses: dtolnay/rust-toolchain@stable + - run: bun install --frozen-lockfile + - run: bun run build:native + - uses: actions/upload-artifact@v4 + with: + name: native-${{ runner.os }}-${{ runner.arch }} + path: dist/*.node + + publish: + needs: native runs-on: ubuntu-latest environment: v4 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - - run: corepack enable - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: ".nvmrc" - cache: "pnpm" - - run: pnpm install --frozen-lockfile + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.1 + - run: bun install --frozen-lockfile - name: Set package version from tag + shell: bash run: | VERSION="${GITHUB_REF_NAME#v}" export VERSION - - node <<'NODE' - const fs = require("fs"); - - const { VERSION: version } = process.env; - - if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) { - throw new Error(`Invalid release version: ${version}`); - } - - const packageJsonPath = "package.json"; - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); - - packageJson.version = version; - fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`); - NODE - - - run: pnpm build - + bun -e 'const packageJson = await Bun.file("package.json").json(); packageJson.version = process.env.VERSION; await Bun.write("package.json", `${JSON.stringify(packageJson, null, 2)}\n`);' + - run: bun run build:ts + - uses: actions/download-artifact@v4 + with: + pattern: native-* + path: dist + merge-multiple: true - run: npm pack - - name: Publish release if: "!contains(github.ref, '-rc.')" run: npm publish --access=public - - name: Publish release candidate if: contains(github.ref, '-rc.') run: npm publish --access=public --tag next diff --git a/.gitignore b/.gitignore index f0878248..49847f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,3 @@ -# yarn -.yarn/* -#!.yarn/cache -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks - /output /dist diff --git a/.oxfmtrc.json b/.oxfmtrc.json index c99d415f..23f63213 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,4 +1,4 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", - "ignorePatterns": ["**/*.hbs", "node_modules", ".yarn", "test/generated/**"] + "ignorePatterns": ["**/*.hbs", "node_modules", "test/generated/**"] } diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..1f2ac5e9 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +bun 1.4.1 diff --git a/README.md b/README.md index 9499d9b2..32cbfdb0 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ The tool partially leverages code from [openapi-zod-client](https://github.com/a ## Setup ```bash -yarn add @povio/openapi-codegen-cli +bun add @povio/openapi-codegen-cli ``` ## Example ```bash -yarn openapi-codegen generate --input http://localhost:3001/docs-json +bunx openapi-codegen generate --input http://localhost:3001/docs-json ``` ## Configuration Files @@ -46,7 +46,7 @@ export default config; Then run without arguments: ```bash -yarn openapi-codegen generate +bunx openapi-codegen generate ``` ### Configuration File Discovery @@ -58,7 +58,7 @@ The CLI automatically searches for the TypeScript configuration file: You can also specify a custom configuration file: ```bash -yarn openapi-codegen generate --config my-config.ts +bunx openapi-codegen generate --config my-config.ts ``` ## Options @@ -89,7 +89,8 @@ yarn openapi-codegen generate --config my-config.ts --modelsInCommon Keep all schema declarations in defaultTag models and emit per-module proxy exports (default: false) --replaceOptionalWithNullish Replace `.optional()` chains with `.nullish()` in generated Zod schemas (default: false) - --axiosRequestConfig Include Axios request config parameters in query hooks (default: false) + --restClient REST transport to generate: 'axios' or 'native' (default: 'axios') + --axiosRequestConfig Include transport request config parameters in query hooks (default: false) --infiniteQueries Generate infinite queries for paginated API endpoints (default: false) --mutationEffects Add mutation effects options to mutation hooks (default: true) --mutationScope Serialize mutations for the same path-param resource via TanStack scope.id (default: false). @@ -132,29 +133,76 @@ yarn openapi-codegen generate --config my-config.ts #### Test locally ```bash -# prerequisites -corepack install -yarn +# install dependencies +bun install # run tests -yarn test +bun run test -# run sources with tsx -yarn start --help -yarn start generate --input ./test/petstore.yaml --verbose +# run TypeScript sources directly with Bun +bun run start --help +bun run start generate --input ./test/petstore.yaml --verbose # build new version -yarn build +bun run build # test build -yarn start --help -yarn start:dist generate --input ./test/petstore.yaml --verbose +bun run start --help +bun run start:dist generate --input ./test/petstore.yaml --verbose ``` +### Native Bun code generation + +The Bun and Node.js CLIs and the Vite plugin automatically use the bundled Rust code-generation core when a compatible native binary is available. Generated files remain byte-for-byte compatible with the TypeScript implementation, which is retained as the fallback when the addon cannot be loaded. + +Set `OPENAPI_CODEGEN_NATIVE=0` to force the TypeScript path, or `OPENAPI_CODEGEN_NATIVE=1` to require the native path and fail when its binary is unavailable. + +Release packages include native binaries for Linux x64, macOS arm64, and Windows x64. Build a binary for the current platform with `bun run build:native`. + ## Common Issues ### App REST Client Interceptors +Select the fetch-based client without changing endpoint and query APIs: + +```ts +import type { OpenAPICodegenConfig } from "@povio/openapi-codegen-cli"; + +export default { + restClient: "native", +} satisfies OpenAPICodegenConfig; +``` + +Native mode imports common request/response contracts from `@povio/openapi-codegen-cli/rest` and the concrete +`NativeRestClient` from `@povio/openapi-codegen-cli/native`. It uses `fetch` for normal requests and uploads, switching +to `XMLHttpRequest` in browsers only when an upload progress callback is provided. + +Native interceptors use the common transport interface: + +```ts +import { NativeRestClient } from "@povio/openapi-codegen-cli/native"; +import type { RestTransportInterceptor } from "@povio/openapi-codegen-cli/rest"; + +const authorizationInterceptor: RestTransportInterceptor = { + onRequest(request) { + request.headers.set("Authorization", `Bearer ${localStorage.getItem("accessToken")}`); + return request; + }, +}; + +export const AppRestClient = new NativeRestClient({ + config: { baseURL: "https://api.example.com" }, + interceptors: [authorizationInterceptor], +}); +``` + +Axios remains the default for backward compatibility. The existing Axios interceptor API remains available in Axios +mode. + +Native mode does not run the library `ErrorHandler` or create `ApplicationException` values. It throws `HttpError` for +non-success HTTP responses and preserves Zod, network, cancellation, and timeout errors so applications can handle them +directly in query callbacks, error boundaries, or their own normalization layer. + In order to add interceptors to the used REST client, you must create your own instance of a RestClient and pass your implemented interceptors into the constructor. Make sure to set `restClientImportPath` in your openapi generation configuration too. ```ts diff --git a/bun.lock b/bun.lock new file mode 100644 index 00000000..906e8737 --- /dev/null +++ b/bun.lock @@ -0,0 +1,962 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "@povio/openapi-codegen-cli", + "dependencies": { + "@orpc/contract": "^1.14.3", + "@orpc/openapi": "^1.14.3", + "@orpc/zod": "^1.14.3", + "openapi-types": "^12.1.3", + "prompt-sync": "^4.2.0", + "reflect-metadata": "^0.2.2", + "yaml": "^2.9.0", + "yargs": "^18.0.0", + }, + "devDependencies": { + "@casl/ability": "6.8.0", + "@casl/react": "5.0.1", + "@tanstack/react-query": "~5.90.21", + "@types/bun": "1.4.1", + "@types/node": "^25.3.0", + "@types/prompt-sync": "^4.2.3", + "@types/react": "^19.2.14", + "@types/yargs": "^17.0.35", + "@vitejs/plugin-react": "^5.1.4", + "axios": "^1.13.1", + "i18next": "^25.8.11", + "oxfmt": "^0.34.0", + "oxlint": "1.50.0", + "oxlint-tsgolint": "^0.15.0", + "react": "^19.2.4", + "tsdown": "^0.22.14", + "tsx": "^4.21.0", + "type-fest": "^5.4.4", + "typescript": "^7.0.2", + "vite": "npm:rolldown-vite@^7.3.1", + "vite-plugin-dts": "^4.5.4", + "vitest": "4.0.18", + "zod": "^4.5.0", + }, + "peerDependencies": { + "@casl/ability": "^6.7.3", + "@casl/react": "^5.0.0", + "@tanstack/react-query": "^5.90.21", + "axios": "^1.13.1", + "i18next": "^25.8.11", + "react": "^19.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "zod": "^4.5.0", + }, + "optionalPeers": [ + "@casl/ability", + "@casl/react", + "@tanstack/react-query", + "axios", + "i18next", + "react", + "vite", + ], + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + + "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@casl/ability": ["@casl/ability@6.8.0", "", { "dependencies": { "@ucast/mongo2js": "^1.3.0" } }, "sha512-Ipt4mzI4gSgnomFdaPjaLgY2MWuXqAEZLrU6qqWBB7khGiBBuuEp6ytYDnq09bRXqcjaeeHiaCvCGFbBA2SpvA=="], + + "@casl/react": ["@casl/react@5.0.1", "", { "peerDependencies": { "@casl/ability": "^4.0.0 || ^5.1.0 || ^6.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8E3GkvwlxEW+bkxWTdvjn6SALNJZa9khNk591xTpU27nPK/nzgfqsVfEvrFjYIQ2LODrxXsWLmw7iHPEI0/TaQ=="], + + "@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" } }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], + + "@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/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + + "@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=="], + + "@microsoft/api-extractor": ["@microsoft/api-extractor@7.59.0", "", { "dependencies": { "@microsoft/api-extractor-model": "7.33.11", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.24.0", "@rushstack/rig-package": "0.7.3", "@rushstack/terminal": "0.24.3", "@rushstack/ts-command-line": "5.3.13", "diff": "~8.0.2", "minimatch": "10.2.3", "resolve": "~1.22.1", "semver": "~7.7.4", "source-map": "~0.6.1", "typescript": "5.9.3" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-KDYV8kSVjmG8JJWVg1f1aKxteNG1IQ44D07Rjhlcci8wlqrSFNhUfgv7dJhwT0W2h3NDIL5Vz2f+/DfO4OpDHw=="], + + "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.33.11", "", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.24.0" } }, "sha512-iDu1AtuRC2Z8XVs2SieZieVavF1FXPBX1C9pMexJh8Dx/Dd/3ZZ4nRQp/PU2Vk2rUHWuBz1wOyL4DcuhVnBXwg=="], + + "@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="], + + "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.1", "", { "dependencies": { "@microsoft/tsdoc": "0.16.0", "ajv": "~8.18.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="], + + "@orpc/client": ["@orpc/client@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-fetch": "1.15.0", "@orpc/standard-server-peer": "1.15.0" } }, "sha512-Qt0FdPSGySdQwy83iUWOq+Iqhw2gM9qHtyxfBbcw1mwOz1s4O2BwUFA3ymVTLIRRNYRgPrQLaBZhp8CacqeePg=="], + + "@orpc/contract": ["@orpc/contract@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/shared": "1.15.0", "@standard-schema/spec": "^1.1.0", "openapi-types": "^12.1.3" } }, "sha512-zo8+x+5iqnIMrFdcj+ibVgn6dJneWzLNcD3kZQxrJFGFNAFoxvZbwryR5wOdcHkaSCeS3N7Ib2NydU6pihSYmg=="], + + "@orpc/interop": ["@orpc/interop@1.15.0", "", {}, "sha512-Ah48o/rc00rN1yP5HcaoVsDCjdUhoh3JP1VibBbvwijhGvdOwRwiw5RWiGu442GSKcrPt9etAGhWXH88q8C3ww=="], + + "@orpc/json-schema": ["@orpc/json-schema@1.15.0", "", { "dependencies": { "@orpc/contract": "1.15.0", "@orpc/interop": "1.15.0", "@orpc/openapi": "1.15.0", "@orpc/server": "1.15.0", "@orpc/shared": "1.15.0", "json-schema-typed": "^8.0.2" } }, "sha512-MH8Qh6yPVaz76BdwcetKMUoL+Fpho1/e30Tb3WawU+EDZGD5jzA7W/KdRGvEYTHGod7qduS/kPd191NCVIEtDw=="], + + "@orpc/openapi": ["@orpc/openapi@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/contract": "1.15.0", "@orpc/interop": "1.15.0", "@orpc/openapi-client": "1.15.0", "@orpc/server": "1.15.0", "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-nwdsVkIRTVhW+2kjtEwHQqfkYnmbJN6HTHvBfnt0KYrPftJ7qvwDo4WnoxfS7Q1jdUlVu8griT5cKZ1gAIYcpQ=="], + + "@orpc/openapi-client": ["@orpc/openapi-client@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/contract": "1.15.0", "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-MGyMAC6dvQ/YoUARpTN4L1MAnK4L2T3+Z7LA8PVva2rZ8XpKABZRcfqufyPfAC0nu/0uPeebnxHaXZ/OeUIOFg=="], + + "@orpc/server": ["@orpc/server@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/contract": "1.15.0", "@orpc/interop": "1.15.0", "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-aws-lambda": "1.15.0", "@orpc/standard-server-fastify": "1.15.0", "@orpc/standard-server-fetch": "1.15.0", "@orpc/standard-server-node": "1.15.0", "@orpc/standard-server-peer": "1.15.0", "cookie": "^1.1.1" }, "peerDependencies": { "crossws": ">=0.3.4", "ws": ">=8.18.1" }, "optionalPeers": ["crossws", "ws"] }, "sha512-l9FnL1MjQ8g77mB10/8D61okuKHLJBWhuwmd6OF65POHzBF177kMCcbHvRFEqUuxSbzP2eNiZGf3kXPMHmIKiQ=="], + + "@orpc/shared": ["@orpc/shared@1.15.0", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-A3/JE7pQYSrrRm6/WYJxV3GBhpMdJRPM3h47slQtWBUZe9Sao5En5WBc4tISGQNP2emcez6qIvDziSgD2T+img=="], + + "@orpc/standard-server": ["@orpc/standard-server@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0" } }, "sha512-bje/xn6thDqJY/JQ7xoOjmD1KWE4FsyZOPgnPxwqTZLx/r00stRhLuFvk1hDEGj9UlG7NLZEijDVx0wqvcyDzA=="], + + "@orpc/standard-server-aws-lambda": ["@orpc/standard-server-aws-lambda@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-fetch": "1.15.0", "@orpc/standard-server-node": "1.15.0" } }, "sha512-laFW7C4t1gEV0NIt4EjCVCBU3kFPnj2FObJLQkTH+DkEOzxVy6zyJ1coIncDSeOPxzbf7rG8cs0B2vQQyuROag=="], + + "@orpc/standard-server-fastify": ["@orpc/standard-server-fastify@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-node": "1.15.0" }, "peerDependencies": { "fastify": ">=5.6.1" }, "optionalPeers": ["fastify"] }, "sha512-fcOl2004o3RnZxW7rhLuDCrCKSi/H2eWnyljeeoY+xykcygRf0ONCs2K2bSdxPm6cmvQGAXa9m8VBglG9oERVQ=="], + + "@orpc/standard-server-fetch": ["@orpc/standard-server-fetch@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-XYVfgmIt71YrPJSI7RKRiNWjcyktPYHpevkjeohGNBP5aSMAUL95quZArAWr+XOPp4U56XRao54I6wFMZa4How=="], + + "@orpc/standard-server-node": ["@orpc/standard-server-node@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-fetch": "1.15.0" } }, "sha512-3ye9SIIhYfJckQwyyGQQRHAEF5vW6QDbax7oi1mOQ9UBHaRDH5kcL28mT0JnETrUvgBoDRx+rfCsQaAOB4n3zw=="], + + "@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-fsTN+FrdPkseVx99yRenGJYSp0xOrEk9fODk8oh3zzricNYzlZ8GGgCYtDNgt9vFIR/J05dc+Nk76KhcPBQpLw=="], + + "@orpc/zod": ["@orpc/zod@1.15.0", "", { "dependencies": { "@orpc/json-schema": "1.15.0", "@orpc/openapi": "1.15.0", "@orpc/shared": "1.15.0", "escape-string-regexp": "^5.0.0", "wildcard-match": "^5.1.4" }, "peerDependencies": { "@orpc/contract": "1.15.0", "@orpc/server": "1.15.0", "zod": ">=3.25.0" } }, "sha512-QaxvUaxemoz57ljE6GNoRHy6tUrLz9PZH+NjPtbL2SilTwrtTDPgWzS+D8TYW6VZqhdBJM6jY93j81ygVf88dQ=="], + + "@oxc-project/runtime": ["@oxc-project/runtime@0.101.0", "", {}, "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ=="], + + "@oxc-project/types": ["@oxc-project/types@0.148.0", "", {}, "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A=="], + + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.34.0", "", { "os": "android", "cpu": "arm" }, "sha512-sqkqjh/Z38l+duOb1HtVqJTAj1grt2ttkobCopC/72+a4Xxz4xUgZPFyQ4HxrYMvyqO/YA0tvM1QbfOu70Gk1Q=="], + + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.34.0", "", { "os": "android", "cpu": "arm64" }, "sha512-1KRCtasHcVcGOMwfOP9d5Bus2NFsN8yAYM5cBwi8LBg5UtXC3C49WHKrlEa8iF1BjOS6CR2qIqiFbGoA0DJQNQ=="], + + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.34.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b+Rmw9Bva6e/7PBES2wLO8sEU7Mi0+/Kv+pXSe/Y8i4fWNftZZlGwp8P01eECaUqpXATfSgNxdEKy7+ssVNz7g=="], + + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.34.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-QGjpevWzf1T9COEokZEWt80kPOtthW1zhRbo7x4Qoz646eTTfi6XsHG2uHeDWJmTbgBoJZPMgj2TAEV/ppEZaA=="], + + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.34.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VMSaC02cG75qL59M9M/szEaqq/RsLfgpzQ4nqUu8BUnX1zkiZIW2gTpUv3ZJ6qpWnHxIlAXiRZjQwmcwpvtbcg=="], + + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.34.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Klm367PFJhH6vYK3vdIOxFepSJZHPaBfIuqwxdkOcfSQ4qqc/M8sgK0UTFnJWWTA/IkhMIh1kW6uEqiZ/xtQqg=="], + + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.34.0", "", { "os": "linux", "cpu": "arm" }, "sha512-nqn0QueVXRfbN9m58/E9Zij0Ap8lzayx591eWBYn0sZrGzY1IRv9RYS7J/1YUXbb0Ugedo0a8qIWzUHU9bWQuA=="], + + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.34.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DDn+dcqW+sMTCEjvLoQvC/VWJjG7h8wcdN/J+g7ZTdf/3/Dx730pQElxPPGsCXPhprb11OsPyMp5FwXjMY3qvA=="], + + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.34.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-H+F8+71gHQoGTFPPJ6z4dD0Fzfzi0UP8Zx94h5kUmIFThLvMq5K1Y/bUUubiXwwHfwb5C3MPjUpYijiy0rj51Q=="], + + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.34.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-dIGnzTNhCXqQD5pzBwduLg8pClm+t8R53qaE9i5h8iua1iaFAJyLffh4847CNZSlASb7gn1Ofuv7KoG/EpoGZg=="], + + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.34.0", "", { "os": "linux", "cpu": "none" }, "sha512-FGQ2GTTooilDte/ogwWwkHuuL3lGtcE3uKM2EcC7kOXNWdUfMY6Jx3JCodNVVbFoybv4A+HuCj8WJji2uu1Ceg=="], + + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.34.0", "", { "os": "linux", "cpu": "none" }, "sha512-2dGbGneJ7ptOIVKMwEIHdCkdZEomh74X3ggo4hCzEXL/rl9HwfsZDR15MkqfQqAs6nVXMvtGIOMxjDYa5lwKaA=="], + + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.34.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-cCtGgmrTrxq3OeSG0UAO+w6yLZTMeOF4XM9SAkNrRUxYhRQELSDQ/iNPCLyHhYNi38uHJQbS5RQweLUDpI4ajA=="], + + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.34.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7AvMzmeX+k7GdgitXp99GQoIV/QZIpAS7rwxQvC/T541yWC45nwvk4mpnU8N+V6dE5SPEObnqfhCjO80s7qIsg=="], + + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.34.0", "", { "os": "linux", "cpu": "x64" }, "sha512-uNiglhcmivJo1oDMh3hoN/Z0WsbEXOpRXZdQ3W/IkOpyV8WF308jFjSC1ZxajdcNRXWej0zgge9QXba58Owt+g=="], + + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.34.0", "", { "os": "none", "cpu": "arm64" }, "sha512-5eFsTjCyji25j6zznzlMc+wQAZJoL9oWy576xhqd2efv+N4g1swIzuSDcb1dz4gpcVC6veWe9pAwD7HnrGjLwg=="], + + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.34.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-6id8kK0t5hKfbV6LHDzRO21wRTA6ctTlKGTZIsG/mcoir0rssvaYsedUymF4HDj7tbCUlnxCX/qOajKlEuqbIw=="], + + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.34.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHaz+w673mlYqn9v/+fuiKZpjkmagleXQ+NygShDv8tdHpRYX2oYhTJwwt9j1ZfVhRgza1EIUW3JmzCXmtPdhQ=="], + + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.34.0", "", { "os": "win32", "cpu": "x64" }, "sha512-CXKQM/VaF+yuvGru8ktleHLJoBdjBtTFmAsLGePiESiTN0NjCI/PiaiOCfHMJ1HdP1LykvARUwMvgaN3tDhcrg=="], + + "@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.15.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d7Ch+A6hic+RYrm32+Gh1o4lOrQqnFsHi721ORdHUDBiQPea+dssKUEMwIbA6MKmCy6TVJ02sQyi24OEfCiGzw=="], + + "@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.15.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Aoai2wAkaUJqp/uEs1gml6TbaPW4YmyO5Ai/vOSkiizgHqVctjhjKqmRiWTX2xuPY94VkwOLqp+Qr3y/0qSpWQ=="], + + "@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.15.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og13a7ec4Vku5t2Y7s3zx6YJP6IKadb1uA9fOoRH6lm/wHWoCnxjcfJmKHXRZJII81WmbdJMSPxaBfwN/S68Q=="], + + "@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.15.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9b9xzh/1Harn3a+XiKTK/8LrWw3VcqLfYp/vhV5/zAVR2Mt0d63WSp4FL+wG7DKnI2T/CbMFUFHwc7kCQjDMzQ=="], + + "@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.15.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-nNac5hewHdkk5mowOwTqB1ZD76zB/FsUiyUvdCyupq5cG54XyKqSLEp9QGbx7wFJkWCkeWmuwRed4sfpAlKaeA=="], + + "@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.15.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ioAY2XLpy83E2EqOLH9p1cEgj0G2qB1lmAn0a3yFV1jHQB29LIPIKGNsu/tYCClpwmHN79pT5KZAHZOgWxxqNg=="], + + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.50.0", "", { "os": "android", "cpu": "arm" }, "sha512-G7MRGk/6NCe+L8ntonRdZP7IkBfEpiZ/he3buLK6JkLgMHgJShXZ+BeOwADmspXez7U7F7L1Anf4xLSkLHiGTg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.50.0", "", { "os": "android", "cpu": "arm64" }, "sha512-GeSuMoJWCVpovJi/e3xDSNgjeR8WEZ6MCXL6EtPiCIM2NTzv7LbflARINTXTJy2oFBYyvdf/l2PwHzYo6EdXvg=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.50.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w3SY5YtxGnxCHPJ8Twl3KmS9oja1gERYk3AMoZ7Hv8P43ZtB6HVfs02TxvarxfL214Tm3uzvc2vn+DhtUNeKnw=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.50.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-hNfogDqy7tvmllXKBSlHo6k5x7dhTUVOHbMSE15CCAcXzmqf5883aPvBYPOq9AE7DpDUQUZ1kVE22YbiGW+tuw=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.50.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ykZevOWEyu0nsxolA911ucxpEv0ahw8jfEeGWOwwb/VPoE4xoexuTOAiPNlWZNJqANlJl7yp8OyzCtXTUAxotw=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-hif3iDk7vo5GGJ4OLCCZAf2vjnU9FztGw4L0MbQL0M2iY9LKFtDMMiQAHmkF0PQGQMVbTYtPdXCLKVgdkiqWXQ=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-dVp9iSssiGAnTNey2Ruf6xUaQhdnvcFOJyRWd/mu5o2jVbFK15E5fbWGeFRfmuobu5QXuROtFga44+7DOS3PLg=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1cT7yz2HA910CKA9NkH1ZJo50vTtmND2fkoW1oyiSb0j6WvNtJ0Wx2zoySfXWc/c+7HFoqRK5AbEoL41LOn9oA=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-++B3k/HEPFVlj89cOz8kWfQccMZB/aWL9AhsW7jPIkG++63Mpwb2cE9XOEsd0PATbIan78k2Gky+09uWM1d/gQ=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.50.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Z9b/KpFMkx66w3gVBqjIC1AJBTZAGoI9+U+K5L4QM0CB/G0JSNC1es9b3Y0Vcrlvtdn8A+IQTkYjd/Q0uCSaZw=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-jvmuIw8wRSohsQlFNIST5uUwkEtEJmOQYr33bf/K2FrFPXHhM4KqGekI3ShYJemFS/gARVacQFgBzzJKCAyJjg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-x+UrN47oYNh90nmAAyql8eQaaRpHbDPu5guasDg10+OpszUQ3/1+1J6zFMmV4xfIEgTcUXG/oI5fxJhF4eWCNA=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.50.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-i/JLi2ljLUIVfekMj4ISmdt+Hn11wzYUdRRrkVUYsCWw7zAy5xV7X9iA+KMyM156LTFympa7s3oKBjuCLoTAUQ=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/C7brhn6c6UUPccgSPCcpLQXcp+xKIW/3sji/5VZ8/OItL3tQ2U7KalHz887UxxSQeEOmd1kY6lrpuwFnmNqOA=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-oDR1f+bGOYU8LfgtEW8XtotWGB63ghtcxk5Jm6IDTCk++rTA/IRMsjOid2iMd+1bW+nP9Mdsmcdc7VbPD3+iyQ=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.50.0", "", { "os": "none", "cpu": "arm64" }, "sha512-4CmRGPp5UpvXyu4jjP9Tey/SrXDQLRvZXm4pb4vdZBxAzbFZkCyh0KyRy4txld/kZKTJlW4TO8N1JKrNEk+mWw=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.50.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Fq0M6vsGcFsSfeuWAACDhd5KJrO85ckbEfe1EGuBj+KPyJz7KeWte2fSFrFGmNKNXyhEMyx4tbgxiWRujBM2KQ=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.50.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-qTdWR9KwY/vxJGhHVIZG2eBOhidOQvOwzDxnX+jhW/zIVacal1nAhR8GLkiywW8BIFDkQKXo/zOfT+/DY+ns/w=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.50.0", "", { "os": "win32", "cpu": "x64" }, "sha512-682t7npLC4G2Ca+iNlI9fhAKTcFPYYXJjwoa88H4q+u5HHHlsnL/gHULapX3iqp+A8FIJbgdylL5KMYo2LaluQ=="], + + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], + + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.7", "", { "os": "android", "cpu": "arm64" }, "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.7", "", { "os": "linux", "cpu": "arm" }, "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.7", "", { "os": "none", "cpu": "arm64" }, "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.53", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.0" }, "cpu": "none" }, "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], + + "@rushstack/node-core-library": ["@rushstack/node-core-library@5.24.0", "", { "dependencies": { "ajv": "~8.20.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.7.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-g/Z47ZwARn/VkTnHcyJslrR26erRf1G5JbqPlfGZGBCrOcrEt8fTQXXDVhUDDNl/g/v3TXI1dNiAAT5FEks8BA=="], + + "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.2.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog=="], + + "@rushstack/rig-package": ["@rushstack/rig-package@0.7.3", "", { "dependencies": { "jju": "~1.4.0", "resolve": "~1.22.1" } }, "sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA=="], + + "@rushstack/terminal": ["@rushstack/terminal@0.24.3", "", { "dependencies": { "@rushstack/node-core-library": "5.24.0", "@rushstack/problem-matcher": "0.2.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-KxphDhPGC4xDrKg8O4yrWCEpPMi87aJc1iycXqMVh1dLgV0M34hiH9ZTgWjBRmcrT/OIHoOeWf48npcQFzgp+g=="], + + "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.3.13", "", { "dependencies": { "@rushstack/terminal": "0.24.3", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-+jNbxhh8CkrZYxMk9X3GRYM2+Myr1xCCj+eHbJ9Vp+PCdNHS0TUl5QQFT6UbM/aTFRCzs5jiBTKYzBRpe5uYbQ=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.90.21", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/bun": ["@types/bun@1.4.1", "", { "dependencies": { "bun-types": "1.4.1" } }, "sha512-0AVGiTXGajf1rgKom3N+c5L7CBxuoyyv1i44M0nX4UDK0G/fnRAMiri93nHuVPIb429KKtAgj7HatVmmOjeQLA=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@types/prompt-sync": ["@types/prompt-sync@4.2.3", "", {}, "sha512-Ox77gCSx0YyeakGt/qfOZUSFNSSi+sh3ABoGOiCwiO2KODx492BJnUm9oIXS+AHJtqp12iM4RduY6viTJ9bYwA=="], + + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], + + "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], + + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "@ucast/core": ["@ucast/core@1.10.2", "", {}, "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="], + + "@ucast/js": ["@ucast/js@3.1.0", "", { "dependencies": { "@ucast/core": "1.10.2" } }, "sha512-eJ7yQeYtMK85UZjxoxBEbTWx6UMxEXKbjVyp+NlzrT5oMKV5Gpo/9bjTl3r7msaXTVC8iD9NJacqJ8yp7joX+Q=="], + + "@ucast/mongo": ["@ucast/mongo@2.4.3", "", { "dependencies": { "@ucast/core": "^1.4.1" } }, "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA=="], + + "@ucast/mongo2js": ["@ucast/mongo2js@1.4.1", "", { "dependencies": { "@ucast/core": "1.10.2", "@ucast/js": "3.1.0", "@ucast/mongo": "2.4.3" } }, "sha512-9aeg5cmqwRQnKCXHN6I17wk83Rcm487bHelaG8T4vfpWneAI469wSI3Srnbu+PuZ5znWRbnwtVq9RgPL+bN6CA=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], + + "@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="], + + "@vitest/runner": ["@vitest/runner@4.0.18", "", { "dependencies": { "@vitest/utils": "4.0.18", "pathe": "^2.0.3" } }, "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="], + + "@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="], + + "@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="], + + "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], + + "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], + + "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.42", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.42", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.42", "", { "dependencies": { "@vue/compiler-core": "3.5.42", "@vue/shared": "3.5.42" } }, "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA=="], + + "@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="], + + "@vue/language-core": ["@vue/language-core@2.2.0", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw=="], + + "@vue/shared": ["@vue/shared@3.5.42", "", {}, "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g=="], + + "@yuku-codegen/binding-android-arm64": ["@yuku-codegen/binding-android-arm64@0.8.7", "", { "os": "android", "cpu": "arm64" }, "sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw=="], + + "@yuku-codegen/binding-darwin-arm64": ["@yuku-codegen/binding-darwin-arm64@0.8.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw=="], + + "@yuku-codegen/binding-darwin-x64": ["@yuku-codegen/binding-darwin-x64@0.8.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw=="], + + "@yuku-codegen/binding-freebsd-x64": ["@yuku-codegen/binding-freebsd-x64@0.8.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw=="], + + "@yuku-codegen/binding-linux-arm-gnu": ["@yuku-codegen/binding-linux-arm-gnu@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg=="], + + "@yuku-codegen/binding-linux-arm-musl": ["@yuku-codegen/binding-linux-arm-musl@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q=="], + + "@yuku-codegen/binding-linux-arm64-gnu": ["@yuku-codegen/binding-linux-arm64-gnu@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A=="], + + "@yuku-codegen/binding-linux-arm64-musl": ["@yuku-codegen/binding-linux-arm64-musl@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA=="], + + "@yuku-codegen/binding-linux-x64-gnu": ["@yuku-codegen/binding-linux-x64-gnu@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw=="], + + "@yuku-codegen/binding-linux-x64-musl": ["@yuku-codegen/binding-linux-x64-musl@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA=="], + + "@yuku-codegen/binding-win32-arm64": ["@yuku-codegen/binding-win32-arm64@0.8.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA=="], + + "@yuku-codegen/binding-win32-x64": ["@yuku-codegen/binding-win32-x64@0.8.7", "", { "os": "win32", "cpu": "x64" }, "sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA=="], + + "@yuku-parser/binding-android-arm64": ["@yuku-parser/binding-android-arm64@0.8.7", "", { "os": "android", "cpu": "arm64" }, "sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ=="], + + "@yuku-parser/binding-darwin-arm64": ["@yuku-parser/binding-darwin-arm64@0.8.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ=="], + + "@yuku-parser/binding-darwin-x64": ["@yuku-parser/binding-darwin-x64@0.8.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA=="], + + "@yuku-parser/binding-freebsd-x64": ["@yuku-parser/binding-freebsd-x64@0.8.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA=="], + + "@yuku-parser/binding-linux-arm-gnu": ["@yuku-parser/binding-linux-arm-gnu@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg=="], + + "@yuku-parser/binding-linux-arm-musl": ["@yuku-parser/binding-linux-arm-musl@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ=="], + + "@yuku-parser/binding-linux-arm64-gnu": ["@yuku-parser/binding-linux-arm64-gnu@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA=="], + + "@yuku-parser/binding-linux-arm64-musl": ["@yuku-parser/binding-linux-arm64-musl@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg=="], + + "@yuku-parser/binding-linux-x64-gnu": ["@yuku-parser/binding-linux-x64-gnu@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg=="], + + "@yuku-parser/binding-linux-x64-musl": ["@yuku-parser/binding-linux-x64-musl@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ=="], + + "@yuku-parser/binding-win32-arm64": ["@yuku-parser/binding-win32-arm64@0.8.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw=="], + + "@yuku-parser/binding-win32-x64": ["@yuku-parser/binding-win32-x64@0.8.7", "", { "os": "win32", "cpu": "x64" }, "sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w=="], + + "@yuku-toolchain/types": ["@yuku-toolchain/types@0.8.7", "", {}, "sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ajv": ["ajv@8.18.0", "", { "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-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "alien-signals": ["alien-signals@0.4.14", "", {}, "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q=="], + + "ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], + + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.20.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ=="], + + "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], + + "bun-types": ["bun-types@1.4.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-loKuVrAFZKfEv+JvWkHRS9GW5IqLuLRjVXN9p+vZvBN86O5hf/pBZQ5hSoyipsrMmWObZBDvWnlmKvjKTM0PdA=="], + + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + + "confbox": ["confbox@0.3.1", "", {}, "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "dts-resolver": ["dts-resolver@3.0.0", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.421", "", {}, "sha512-cUhfpHQy+PGbt+X90DMcAVazDCziIZr73hpxD4LRs4BGQoJCifPzTfQWa7S6c+uhokTOBe8tot09GBSEO6c9LA=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "empathic": ["empathic@2.0.1", "", {}, "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], + + "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.7", "", {}, "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + + "fs-extra": ["fs-extra@11.3.6", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-tsconfig": ["get-tsconfig@5.0.0-beta.5", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + + "hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "i18next": ["i18next@25.10.10", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ=="], + + "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], + + "import-without-cache": ["import-without-cache@0.4.0", "", {}, "sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@10.2.3", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + + "node-releases": ["node-releases@2.0.54", "", {}, "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "oxfmt": ["oxfmt@0.34.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.34.0", "@oxfmt/binding-android-arm64": "0.34.0", "@oxfmt/binding-darwin-arm64": "0.34.0", "@oxfmt/binding-darwin-x64": "0.34.0", "@oxfmt/binding-freebsd-x64": "0.34.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.34.0", "@oxfmt/binding-linux-arm-musleabihf": "0.34.0", "@oxfmt/binding-linux-arm64-gnu": "0.34.0", "@oxfmt/binding-linux-arm64-musl": "0.34.0", "@oxfmt/binding-linux-ppc64-gnu": "0.34.0", "@oxfmt/binding-linux-riscv64-gnu": "0.34.0", "@oxfmt/binding-linux-riscv64-musl": "0.34.0", "@oxfmt/binding-linux-s390x-gnu": "0.34.0", "@oxfmt/binding-linux-x64-gnu": "0.34.0", "@oxfmt/binding-linux-x64-musl": "0.34.0", "@oxfmt/binding-openharmony-arm64": "0.34.0", "@oxfmt/binding-win32-arm64-msvc": "0.34.0", "@oxfmt/binding-win32-ia32-msvc": "0.34.0", "@oxfmt/binding-win32-x64-msvc": "0.34.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-t+zTE4XGpzPTK+Zk9gSwcJcFi4pqjl6PwO/ZxPBJiJQ2XCKMucwjPlHxvPHyVKJtkMSyrDGfQ7Ntg/hUr4OgHQ=="], + + "oxlint": ["oxlint@1.50.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.50.0", "@oxlint/binding-android-arm64": "1.50.0", "@oxlint/binding-darwin-arm64": "1.50.0", "@oxlint/binding-darwin-x64": "1.50.0", "@oxlint/binding-freebsd-x64": "1.50.0", "@oxlint/binding-linux-arm-gnueabihf": "1.50.0", "@oxlint/binding-linux-arm-musleabihf": "1.50.0", "@oxlint/binding-linux-arm64-gnu": "1.50.0", "@oxlint/binding-linux-arm64-musl": "1.50.0", "@oxlint/binding-linux-ppc64-gnu": "1.50.0", "@oxlint/binding-linux-riscv64-gnu": "1.50.0", "@oxlint/binding-linux-riscv64-musl": "1.50.0", "@oxlint/binding-linux-s390x-gnu": "1.50.0", "@oxlint/binding-linux-x64-gnu": "1.50.0", "@oxlint/binding-linux-x64-musl": "1.50.0", "@oxlint/binding-openharmony-arm64": "1.50.0", "@oxlint/binding-win32-arm64-msvc": "1.50.0", "@oxlint/binding-win32-ia32-msvc": "1.50.0", "@oxlint/binding-win32-x64-msvc": "1.50.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.14.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-iSJ4IZEICBma8cZX7kxIIz9PzsYLF2FaLAYN6RKu7VwRVKdu7RIgpP99bTZaGl//Yao7fsaGZLSEo5xBrI5ReQ=="], + + "oxlint-tsgolint": ["oxlint-tsgolint@0.15.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.15.0", "@oxlint-tsgolint/darwin-x64": "0.15.0", "@oxlint-tsgolint/linux-arm64": "0.15.0", "@oxlint-tsgolint/linux-x64": "0.15.0", "@oxlint-tsgolint/win32-arm64": "0.15.0", "@oxlint-tsgolint/win32-x64": "0.15.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-iwvFmhKQVZzVTFygUVI4t2S/VKEm+Mqkw3jQRJwfDuTcUYI5LCIYzdO5Dbuv4mFOkXZCcXaRRh0m+uydB5xdqw=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + + "pkg-types": ["pkg-types@2.3.2", "", { "dependencies": { "confbox": "^0.3.0", "exsolve": "^1.1.1", "pathe": "^2.0.3" } }, "sha512-v0sVXzj7oPGysr543YYZLYbcJNJsKikSsp/fFzoxQ12ewY3ZZr7oCPC8y7OlmxfYB3QPvriXmuPD8KZggE1vqg=="], + + "postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="], + + "prompt-sync": ["prompt-sync@4.2.0", "", { "dependencies": { "strip-ansi": "^5.0.0" } }, "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + + "radash": ["radash@12.1.1", "", {}, "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA=="], + + "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "rolldown": ["rolldown@1.2.7", "", { "dependencies": { "@oxc-project/types": "=0.148.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.7", "@rolldown/binding-android-arm64": "1.2.7", "@rolldown/binding-darwin-arm64": "1.2.7", "@rolldown/binding-darwin-x64": "1.2.7", "@rolldown/binding-freebsd-x64": "1.2.7", "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", "@rolldown/binding-linux-arm64-gnu": "1.2.7", "@rolldown/binding-linux-arm64-musl": "1.2.7", "@rolldown/binding-linux-ppc64-gnu": "1.2.7", "@rolldown/binding-linux-s390x-gnu": "1.2.7", "@rolldown/binding-linux-x64-gnu": "1.2.7", "@rolldown/binding-linux-x64-musl": "1.2.7", "@rolldown/binding-openharmony-arm64": "1.2.7", "@rolldown/binding-win32-arm64-msvc": "1.2.7", "@rolldown/binding-win32-x64-msvc": "1.2.7" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.27.14", "", { "dependencies": { "dts-resolver": "^3.0.0", "get-tsconfig": "5.0.0-beta.5", "obug": "^2.1.4", "yuku-ast": "^0.8.0", "yuku-codegen": "^0.8.0", "yuku-parser": "^0.8.0" }, "peerDependencies": { "@typescript/native-preview": "*", "@volar/typescript": "~2.4.0", "rolldown": "^1.0.0", "typescript": "^5.0.0 || ^6.0.0 || ~7.0.0", "vue-tsc": "~3.2.0 || ~3.3.0" }, "optionalPeers": ["@typescript/native-preview", "@volar/typescript", "typescript", "vue-tsc"] }, "sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw=="], + + "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], + + "strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], + + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + + "tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "tsdown": ["tsdown@0.22.14", "", { "dependencies": { "ansis": "^4.3.1", "cac": "^7.0.0", "defu": "^6.1.7", "empathic": "^2.0.1", "hookable": "^6.1.1", "import-without-cache": "^0.4.0", "obug": "^2.1.4", "picomatch": "^4.0.5", "rolldown": "~1.2.0", "rolldown-plugin-dts": "^0.27.13", "tinyexec": "^1.2.4", "tinyglobby": "^0.2.17", "tree-kill": "^1.2.2", "unconfig-core": "^7.5.0", "verkit": "^0.3.0" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.22.14", "@tsdown/exe": "0.22.14", "@vitejs/devtools": "*", "publint": "^0.3.8", "tsx": "*", "typescript": "^5.0.0 || ^6.0.0 || ^7.0.0", "unplugin-unused": "^0.5.0", "unrun": "*" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@vitejs/devtools", "publint", "tsx", "typescript", "unplugin-unused", "unrun"], "bin": { "tsdown": "./dist/run.mjs" } }, "sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsx": ["tsx@4.23.13", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw=="], + + "type-fest": ["type-fest@5.9.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw=="], + + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.3.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="], + + "verkit": ["verkit@0.3.2", "", {}, "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg=="], + + "vite": ["rolldown-vite@7.3.1", "", { "dependencies": { "@oxc-project/runtime": "0.101.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.53", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w=="], + + "vite-plugin-dts": ["vite-plugin-dts@4.5.4", "", { "dependencies": { "@microsoft/api-extractor": "^7.50.1", "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.11", "@vue/language-core": "2.2.0", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "magic-string": "^0.30.17" }, "peerDependencies": { "typescript": "*", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg=="], + + "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], + + "vscode-uri": ["vscode-uri@3.2.0", "", {}, "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wildcard-match": ["wildcard-match@5.1.4", "", {}, "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + + "yuku-ast": ["yuku-ast@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7" } }, "sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ=="], + + "yuku-codegen": ["yuku-codegen@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7" }, "optionalDependencies": { "@yuku-codegen/binding-android-arm64": "0.8.7", "@yuku-codegen/binding-darwin-arm64": "0.8.7", "@yuku-codegen/binding-darwin-x64": "0.8.7", "@yuku-codegen/binding-freebsd-x64": "0.8.7", "@yuku-codegen/binding-linux-arm-gnu": "0.8.7", "@yuku-codegen/binding-linux-arm-musl": "0.8.7", "@yuku-codegen/binding-linux-arm64-gnu": "0.8.7", "@yuku-codegen/binding-linux-arm64-musl": "0.8.7", "@yuku-codegen/binding-linux-x64-gnu": "0.8.7", "@yuku-codegen/binding-linux-x64-musl": "0.8.7", "@yuku-codegen/binding-win32-arm64": "0.8.7", "@yuku-codegen/binding-win32-x64": "0.8.7" } }, "sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw=="], + + "yuku-parser": ["yuku-parser@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7", "yuku-ast": "^0.8.7" }, "optionalDependencies": { "@yuku-parser/binding-android-arm64": "0.8.7", "@yuku-parser/binding-darwin-arm64": "0.8.7", "@yuku-parser/binding-darwin-x64": "0.8.7", "@yuku-parser/binding-freebsd-x64": "0.8.7", "@yuku-parser/binding-linux-arm-gnu": "0.8.7", "@yuku-parser/binding-linux-arm-musl": "0.8.7", "@yuku-parser/binding-linux-arm64-gnu": "0.8.7", "@yuku-parser/binding-linux-arm64-musl": "0.8.7", "@yuku-parser/binding-linux-x64-gnu": "0.8.7", "@yuku-parser/binding-linux-x64-musl": "0.8.7", "@yuku-parser/binding-win32-arm64": "0.8.7", "@yuku-parser/binding-win32-x64": "0.8.7" } }, "sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ=="], + + "zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], + + "@microsoft/api-extractor/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "@microsoft/api-extractor/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "@rushstack/node-core-library/ajv": ["ajv@8.20.0", "", { "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-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "@rushstack/node-core-library/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "@vue/language-core/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "ajv-formats/ajv": ["ajv@8.20.0", "", { "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-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "bun-types/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], + + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "local-pkg/quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "mlly/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=="], + + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "vite/rolldown": ["rolldown@1.0.0-beta.53", "", { "dependencies": { "@oxc-project/types": "=0.101.0", "@rolldown/pluginutils": "1.0.0-beta.53" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-x64": "1.0.0-beta.53", "@rolldown/binding-freebsd-x64": "1.0.0-beta.53", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], + + "bun-types/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], + + "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.101.0", "", {}, "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ=="], + + "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.53", "", { "os": "android", "cpu": "arm64" }, "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ=="], + + "vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.53", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg=="], + + "vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.53", "", { "os": "darwin", "cpu": "x64" }, "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ=="], + + "vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.53", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w=="], + + "vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53", "", { "os": "linux", "cpu": "arm" }, "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ=="], + + "vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53", "", { "os": "linux", "cpu": "arm64" }, "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg=="], + + "vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.53", "", { "os": "linux", "cpu": "arm64" }, "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA=="], + + "vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.53", "", { "os": "linux", "cpu": "x64" }, "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA=="], + + "vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.53", "", { "os": "linux", "cpu": "x64" }, "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw=="], + + "vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.53", "", { "os": "none", "cpu": "arm64" }, "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A=="], + + "vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53", "", { "os": "win32", "cpu": "arm64" }, "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw=="], + + "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.53", "", { "os": "win32", "cpu": "x64" }, "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA=="], + + "vite/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], + + "@vue/language-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + } +} diff --git a/native/.gitignore b/native/.gitignore new file mode 100644 index 00000000..388c77b5 --- /dev/null +++ b/native/.gitignore @@ -0,0 +1,2 @@ +/target +/*.node diff --git a/native/Cargo.lock b/native/Cargo.lock new file mode 100644 index 00000000..9971e2f2 --- /dev/null +++ b/native/Cargo.lock @@ -0,0 +1,480 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "ctor" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d83cb7e7a873830708d6b02a78cd36a592c6fa14bf267b68725103b85c0d77f" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "napi" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f71d6bc097c4a6eb853c3f24991ab8c9f50f57d1f719e305175541482217e36" +dependencies = [ + "bitflags", + "ctor", + "futures", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", + "serde", + "serde_json", +] + +[[package]] +name = "napi-build" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5282704fbe8d49b0cf8b08e3f33233416a528658f205c7e5ace63b582de0b11c" + +[[package]] +name = "napi-derive" +version = "3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9002b2940f0184444754546e0fcd15182f56948e6f381968b019d549387c42" +dependencies = [ + "convert_case", + "ctor", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "napi-derive-backend" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60b5d773ad46c698c8cc2cd9fde0b283d39cbb7f71c04bee633c7bdba4423bd" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "semver", + "syn 2.0.119", +] + +[[package]] +name = "napi-sys" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" +dependencies = [ + "libloading", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "openapi-codegen-native" +version = "0.1.0" +dependencies = [ + "indexmap", + "mimalloc", + "napi", + "napi-build", + "napi-derive", + "regex", + "rustc-hash", + "serde", + "serde_json", + "serde_yaml", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/native/Cargo.toml b/native/Cargo.toml new file mode 100644 index 00000000..3a0c1375 --- /dev/null +++ b/native/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "openapi-codegen-native" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +lto = "fat" +opt-level = 3 +panic = "abort" +strip = "symbols" + +[profile.dev] +opt-level = 2 + +[dependencies] +napi = { version = "3", default-features = false, features = ["napi8", "serde-json"] } +napi-derive = "3" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } +serde_yaml = "0.9" +regex = "1" +indexmap = { version = "2", features = ["serde"] } +mimalloc = { version = "0.1", default-features = false } +rustc-hash = "2" + +[build-dependencies] +napi-build = "2" diff --git a/native/build.rs b/native/build.rs new file mode 100644 index 00000000..0f1b0100 --- /dev/null +++ b/native/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/native/src/config.rs b/native/src/config.rs new file mode 100644 index 00000000..751f90ed --- /dev/null +++ b/native/src/config.rs @@ -0,0 +1,167 @@ +use std::collections::HashMap; + +use serde::Deserialize; + +#[derive(Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GenerateConfig { + #[serde(default)] + pub output_file_name_suffix: String, + #[serde(default)] + pub namespace_suffix: String, +} + +#[derive(Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InfiniteQueryResponseParamNames { + #[serde(default = "default_page")] + pub page: String, + #[serde(default = "default_total_items")] + pub total_items: String, + #[serde(default = "default_limit")] + pub limit: String, +} + +#[derive(Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InfiniteQueryParamNames { + #[serde(default = "default_page")] + pub page: String, +} + +#[derive(Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GenerateOptions { + #[serde(default = "default_true")] + pub split_by_tags: bool, + #[serde(default = "default_tag")] + pub default_tag: String, + #[serde(default)] + pub include_tags: Vec, + #[serde(default)] + pub exclude_tags: Vec, + #[serde(default)] + pub exclude_path_regex: String, + #[serde(default)] + pub with_deprecated_endpoints: bool, + #[serde(default = "default_true")] + pub ts_namespaces: bool, + #[serde(default)] + pub tree_shakeable_namespaces: bool, + #[serde(default = "default_remove_prefix")] + pub remove_operation_prefix_ending_with: String, + #[serde(default = "default_schema_suffix")] + pub schema_suffix: String, + #[serde(default)] + pub models_in_common: bool, + #[serde(default)] + pub models_in_modules: bool, + #[serde(default)] + pub with_implicit_required_props: bool, + #[serde(default = "default_true")] + pub with_default_values: bool, + #[serde(default)] + pub with_description: bool, + #[serde(default)] + pub all_readonly: bool, + #[serde(default = "default_true")] + pub extract_enums: bool, + #[serde(default)] + pub replace_optional_with_nullish: bool, + #[serde(default = "default_true")] + pub exclude_redundant_zod_schemas: bool, + #[serde(default = "default_enum_suffix")] + pub enum_suffix: String, + #[serde(default)] + pub base_url: String, + #[serde(default)] + pub output: String, + #[serde(default)] + pub ts_path: String, + #[serde(default)] + pub import_path: String, + #[serde(default)] + pub configs: HashMap, + #[serde(default)] + pub rest_client_import_path: String, + #[serde(default = "default_rest_client")] + pub rest_client: String, + #[serde(default = "default_zod_import_path")] + pub zod_import_path: String, + #[serde(default)] + pub axios_request_config: bool, + #[serde(default = "default_true")] + pub parse_request_params: bool, + #[serde(default)] + pub inline_endpoints: bool, + #[serde(default)] + pub builder_configs: bool, + #[serde(default = "default_true")] + pub acl: bool, + #[serde(default)] + pub native_compact: bool, + #[serde(default)] + pub inline_endpoints_exclude_modules: Vec, + #[serde(default)] + pub infinite_queries: bool, + #[serde(default)] + pub infinite_query_param_names: InfiniteQueryParamNames, + #[serde(default)] + pub infinite_query_response_param_names: InfiniteQueryResponseParamNames, + #[serde(default = "default_true")] + pub prefetch_queries: bool, + #[serde(default = "default_true")] + pub mutation_effects: bool, + #[serde(default)] + pub mutation_default_on_error: bool, + #[serde(default)] + pub query_types_import_path: String, + #[serde(default = "default_package_import_path")] + pub mutation_effects_import_path: String, + #[serde(default = "default_true")] + pub check_acl: bool, + #[serde(default = "default_acl_import_path")] + pub acl_check_import_path: String, + #[serde(default)] + pub workspace_context: Vec, + #[serde(default)] + pub mutation_scope: serde_json::Value, +} + +fn default_rest_client() -> String { + "axios".into() +} + +fn default_true() -> bool { + true +} +fn default_tag() -> String { + "Common".into() +} +fn default_remove_prefix() -> String { + "Controller_".into() +} +fn default_schema_suffix() -> String { + "Schema".into() +} +fn default_enum_suffix() -> String { + "Enum".into() +} +fn default_page() -> String { + "page".into() +} +fn default_total_items() -> String { + "totalItems".into() +} +fn default_limit() -> String { + "limit".into() +} +fn default_package_import_path() -> String { + "@povio/openapi-codegen-cli".into() +} +fn default_acl_import_path() -> String { + "@povio/openapi-codegen-cli/acl".into() +} +fn default_zod_import_path() -> String { + "@povio/openapi-codegen-cli/zod".into() +} diff --git a/native/src/endpoints.rs b/native/src/endpoints.rs new file mode 100644 index 00000000..8021ec67 --- /dev/null +++ b/native/src/endpoints.rs @@ -0,0 +1,1151 @@ +use std::{cell::RefCell, rc::Rc}; + +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; +use serde_json::{Map, Number, Value}; + +use crate::{ + config::GenerateOptions, + resolver::{IndexedOperation, Resolver, format_tag}, + zod::ZodCompiler, +}; + +pub struct EndpointExtractor<'a> { + document: &'a Value, + options: &'a GenerateOptions, + resolver: &'a Resolver<'a>, + compiler: ZodCompiler<'a>, + generated_schemas: RefCell>, + generated_tags: RefCell>>, + generated_objects: RefCell>, + generated_dependencies: RefCell>, + canonical_enum_refs: HashMap, + runtime_tags: Rc>>>, + first_tags: RefCell>, + registering: RefCell>, +} + +pub struct GeneratedEndpointData { + pub schemas: Map, + pub tags: HashMap>, + pub objects: Map, + pub dependencies: Map, + pub first_tags: HashMap, +} + +impl<'a> EndpointExtractor<'a> { + pub fn new( + document: &'a Value, + options: &'a GenerateOptions, + resolver: &'a Resolver<'a>, + ) -> Self { + Self::new_with_runtime_tags( + document, + options, + resolver, + Rc::new(RefCell::new(HashMap::default())), + ) + } + + pub fn new_with_runtime_tags( + document: &'a Value, + options: &'a GenerateOptions, + resolver: &'a Resolver<'a>, + runtime_tags: Rc>>>, + ) -> Self { + let canonical_enum_refs = canonical_enum_refs(document); + Self { + document, + options, + resolver, + compiler: ZodCompiler::new_with_runtime_tags( + document, + options, + resolver, + runtime_tags.clone(), + ), + generated_schemas: RefCell::new(Map::new()), + generated_tags: RefCell::new(HashMap::default()), + generated_objects: RefCell::new(Map::new()), + generated_dependencies: RefCell::new(Map::new()), + canonical_enum_refs, + runtime_tags, + first_tags: RefCell::new(HashMap::default()), + registering: RefCell::new(HashSet::default()), + } + } + + pub fn extract(&self) -> Result, String> { + let mut name_counts = HashMap::default(); + for operation in &self.resolver.operations { + *name_counts.entry(operation.name.as_str()).or_insert(0usize) += 1; + } + self.resolver + .operations + .iter() + .map(|operation| { + self.extract_operation(operation, name_counts[operation.name.as_str()] == 1) + }) + .collect() + } + + pub fn into_generated_data(self) -> (GeneratedEndpointData, ZodCompiler<'a>) { + ( + GeneratedEndpointData { + schemas: self.generated_schemas.into_inner(), + tags: self.generated_tags.into_inner(), + objects: self.generated_objects.into_inner(), + dependencies: self.generated_dependencies.into_inner(), + first_tags: self.first_tags.into_inner(), + }, + self.compiler, + ) + } + + fn generated_dependency_names_from_references(&self, references: &[String]) -> Vec { + let registered = self.first_tags.borrow(); + let mut names = Vec::new(); + let mut expanded = HashSet::default(); + for reference in references { + self.add_generated_dependency(reference, ®istered, &mut expanded, &mut names); + } + names + } + + fn register_inline_dependencies( + &self, + schema: &Value, + tag: &str, + ) -> Result, String> { + let mut references = Vec::new(); + collect_compiled_reference_order(schema, &mut references, &mut HashSet::default()); + let dependencies = self.generated_dependency_names_from_references(&references); + if schema.get("$ref").is_none() { + for reference in references { + self.register_reference(&reference, tag)?; + } + } + Ok(dependencies) + } + + fn add_generated_dependency( + &self, + reference: &str, + registered: &HashMap, + expanded: &mut HashSet, + names: &mut Vec, + ) { + let name = schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + ); + if !names.contains(&name) { + names.push(name); + } + if registered.contains_key(reference) || !expanded.insert(reference.to_string()) { + return; + } + if let Some(canonical) = self.canonical_enum_refs.get(reference) { + let canonical_name = schema_name( + canonical.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + ); + if !names.contains(&canonical_name) { + names.push(canonical_name); + } + } + if let Some(children) = self.resolver.ordered_dependencies.get(reference) { + for child in children { + self.add_generated_dependency(child, registered, expanded, names); + } + } + } + + fn set_generated_dependencies(&self, name: &str, dependencies: Vec) { + self.generated_dependencies.borrow_mut().insert( + name.to_string(), + Value::Array(dependencies.into_iter().map(Value::String).collect()), + ); + } + + fn set_generated_object(&self, name: &str, schema: &Value) { + self.generated_objects + .borrow_mut() + .entry(name.to_string()) + .or_insert_with(|| schema.clone()); + } + + fn set_generated_schema(&self, name: String, code: String, tag: &str) { + self.generated_schemas + .borrow_mut() + .insert(name.clone(), Value::String(code)); + self.generated_tags + .borrow_mut() + .entry(name) + .or_default() + .insert(tag.to_string()); + } + + fn extract_operation( + &self, + indexed: &IndexedOperation<'a>, + unique_name: bool, + ) -> Result { + let operation = indexed.operation; + let mut endpoint = Map::new(); + endpoint.insert("method".into(), Value::String(indexed.method.into())); + endpoint.insert("path".into(), Value::String(replace_path(indexed.path))); + endpoint.insert("operationName".into(), Value::String(indexed.name.clone())); + copy_optional_string(operation, &mut endpoint, "description"); + copy_optional_string(operation, &mut endpoint, "summary"); + if let Some(tags) = operation.get("tags").and_then(Value::as_array) { + endpoint.insert( + "tags".into(), + Value::Array( + tags.iter() + .filter_map(Value::as_str) + .map(|tag| Value::String(format_tag(tag))) + .collect(), + ), + ); + } + endpoint.insert( + "requestFormat".into(), + Value::String("application/json".into()), + ); + let mut parameters = Vec::new(); + + if let Some(request_body) = operation.get("requestBody") { + if let Some((parameter, format)) = + self.body_parameter(request_body, indexed, unique_name)? + { + parameters.push(parameter); + endpoint.insert("requestFormat".into(), Value::String(format)); + } + } + for parameter in self.merged_parameters(indexed) { + if let Some(parameter) = self.parameter(parameter, indexed, unique_name)? { + parameters.push(parameter); + } + } + add_missing_path_parameters( + endpoint + .get("path") + .and_then(Value::as_str) + .unwrap_or_default(), + &mut parameters, + ); + endpoint.insert("parameters".into(), Value::Array(parameters)); + endpoint.insert("response".into(), Value::String(String::new())); + endpoint.insert("errors".into(), Value::Array(Vec::new())); + endpoint.insert("responseStatusCodes".into(), Value::Array(Vec::new())); + endpoint.insert( + "mediaUpload".into(), + Value::Bool( + operation + .get("x-media-upload") + .and_then(Value::as_bool) + .unwrap_or(false), + ), + ); + endpoint.insert( + "mediaDownload".into(), + Value::Bool( + operation + .get("x-media-download") + .and_then(Value::as_bool) + .unwrap_or(false), + ), + ); + + if let Some(responses) = operation.get("responses").and_then(Value::as_object) { + for (status_code, raw_response) in responses { + let response = self.resolve(raw_response).unwrap_or(raw_response); + let Some(response_obj) = response.as_object() else { + continue; + }; + endpoint + .get_mut("responseStatusCodes") + .and_then(Value::as_array_mut) + .unwrap() + .push(Value::String(status_code.clone())); + let content = response_obj.get("content").and_then(Value::as_object); + let matching = content.and_then(|content| { + content + .iter() + .find(|(_, media)| media.pointer("/schema").is_some()) + .or_else(|| { + content + .iter() + .find(|(name, _)| name.starts_with("application/")) + }) + }); + if let Some((media_type, _)) = matching { + let status = status_code.parse::().ok(); + if status.is_some_and(is_success) + || (status_code == "default" && !endpoint.contains_key("responseFormat")) + { + endpoint.insert("responseFormat".into(), Value::String(media_type.clone())); + } + } + let schema = matching.and_then(|(_, media)| media.get("schema")); + if let Some(schema) = schema { + self.register_reference_schema(schema, &indexed.tag)?; + let schema_object = self.resolve(schema).unwrap_or(schema); + let dependencies = self.register_inline_dependencies(schema, &indexed.tag)?; + let (code, chain) = + self.compiler + .compile_endpoint_schema(schema, true, &indexed.tag)?; + let fallback = + response_schema_name(status_code, &indexed.name, unique_name, &indexed.tag); + let resolved = resolve_schema_name( + schema, + schema_object, + code.clone(), + Some(&fallback), + self.options, + ); + if schema.get("$ref").is_none() && complexity(schema_object) >= 2 { + self.set_generated_schema(resolved.clone(), code, &indexed.tag); + self.set_generated_object(&resolved, schema_object); + self.set_generated_dependencies(&resolved, dependencies); + } + let response_zod = format!("{resolved}{chain}"); + let status = status_code.parse::().ok(); + let current_empty = endpoint + .get("response") + .and_then(Value::as_str) + .is_some_and(str::is_empty); + if (status.is_some_and(is_success) || status_code == "default") && current_empty + { + endpoint.insert("response".into(), Value::String(response_zod)); + endpoint.insert("responseObject".into(), response.clone()); + if let Some(description) = response_obj.get("description") { + endpoint.insert("responseDescription".into(), description.clone()); + } + } else if status_code != "default" + && status.is_some_and(|status| !is_success(status)) + { + let mut error = Map::new(); + error.insert("zodSchema".into(), Value::String(response_zod)); + error.insert( + "status".into(), + Value::Number(Number::from(status.unwrap())), + ); + if let Some(description) = response_obj.get("description") { + error.insert("description".into(), description.clone()); + } + if let (Some(domain), Some(code)) = ( + schema_object + .get("x-domain-error-domain") + .and_then(Value::as_str), + schema_object + .pointer("/properties/code/enum/0") + .filter(|value| value.is_number() || value.is_string()), + ) { + if schema_object + .pointer("/properties/code/enum") + .and_then(Value::as_array) + .is_some_and(|values| values.len() == 1) + { + let mut domain_error = Map::new(); + domain_error.insert("domain".into(), Value::String(domain.into())); + domain_error.insert("code".into(), code.clone()); + if let Some(name) = schema_object + .get("x-domain-error-name") + .and_then(Value::as_str) + { + domain_error.insert("name".into(), Value::String(name.into())); + } + error.insert("domainError".into(), Value::Object(domain_error)); + } + } + endpoint + .get_mut("errors") + .and_then(Value::as_array_mut) + .unwrap() + .push(Value::Object(error)); + } + } else if status_code != "default" + && status_code + .parse::() + .ok() + .is_some_and(|status| !is_success(status)) + { + let mut error = Map::new(); + error.insert("zodSchema".into(), Value::String("z.void()".into())); + error.insert( + "status".into(), + Value::Number(Number::from(status_code.parse::().unwrap())), + ); + if let Some(description) = response_obj.get("description") { + error.insert("description".into(), description.clone()); + } + endpoint + .get_mut("errors") + .and_then(Value::as_array_mut) + .unwrap() + .push(Value::Object(error)); + } + } + } + if endpoint + .get("response") + .and_then(Value::as_str) + .is_some_and(str::is_empty) + { + endpoint.insert("response".into(), Value::String("z.void()".into())); + } + if let Some(acl) = self.endpoint_acl(operation, &endpoint) { + endpoint.insert("acl".into(), Value::Array(acl)); + } + Ok(Value::Object(endpoint)) + } + + fn endpoint_acl( + &self, + operation: &Map, + endpoint: &Map, + ) -> Option> { + let acl = operation.get("x-acl")?.as_array()?; + if acl.is_empty() { + return None; + } + Some( + acl.iter() + .map(|item| { + let mut output = item.as_object().cloned().unwrap_or_default(); + let mut condition_types = Vec::new(); + if let Some(conditions) = item.get("conditions").and_then(Value::as_object) { + for name in conditions.keys() { + if let Some(info) = self.acl_condition(name, acl, endpoint) { + condition_types.push(info); + } + } + } + output.insert("conditionsTypes".into(), Value::Array(condition_types)); + Value::Object(output) + }) + .collect(), + ) + } + + fn acl_condition( + &self, + name: &str, + acl: &[Value], + endpoint: &Map, + ) -> Option { + let condition_path = acl + .first()? + .pointer(&format!("/conditions/{name}"))? + .as_str()?; + let stripped = condition_path + .strip_prefix('$') + .and_then(|path| path.split_once('.').map(|(_, rest)| rest)) + .unwrap_or(condition_path); + let segments: Vec<&str> = stripped.split('.').collect(); + let parameters = endpoint.get("parameters")?.as_array()?; + let mut schema: Option<&Value> = None; + let mut required = None; + let mut info = None; + let mut index = 0; + if let Some(parameter) = parameters.iter().find(|parameter| { + parameter.get("name").and_then(Value::as_str) == segments.first().copied() + }) { + let parameter_object = parameter.get("parameterObject"); + required = parameter_object + .and_then(|object| object.get("required")) + .and_then(Value::as_bool); + schema = parameter_object.and_then(|object| object.get("schema")); + let kind = parameter + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + let decapitalized = kind + .chars() + .next() + .map(|first| { + first + .to_lowercase() + .chain(kind.chars().skip(1)) + .collect::() + }) + .unwrap_or_default(); + info = Some(format!( + "{} {decapitalized} parameter", + parameter + .get("name") + .and_then(Value::as_str) + .unwrap_or_default() + )); + index += 1; + } else if let Some(body) = parameters + .iter() + .find(|parameter| parameter.get("bodyObject").is_some()) + { + let body_object = body.get("bodyObject")?; + let content = body_object.get("content")?.as_object()?; + let (_, media) = content.iter().find(|(name, _)| is_parameter_media(name))?; + schema = media.get("schema"); + required = body_object.get("required").and_then(Value::as_bool); + info = Some(format!( + "{} data", + if endpoint.get("method").and_then(Value::as_str) == Some("get") { + "query" + } else { + "mutation" + } + )); + if segments + .first() + .is_some_and(|segment| segment.starts_with('$')) + { + index += 1; + } + } + while let Some(current) = schema { + if index >= segments.len() { + break; + } + let resolved = self.resolve(current).unwrap_or(current); + let object = resolved.as_object()?; + let property = object.get("properties")?.get(segments[index])?; + required = Some( + object + .get("required") + .and_then(Value::as_array) + .is_some_and(|items| { + items + .iter() + .any(|item| item.as_str() == Some(segments[index])) + }), + ); + schema = Some(property); + index += 1; + } + let schema = schema?; + let mut result = Map::new(); + result.insert("name".into(), Value::String(name.into())); + if let Some(reference) = schema.get("$ref").and_then(Value::as_str) { + result.insert( + "zodSchemaName".into(), + Value::String(schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + )), + ); + } else if let Some(schema_type) = schema.get("type") { + result.insert("type".into(), schema_type.clone()); + } + if let Some(required) = required { + result.insert("required".into(), Value::Bool(required)); + } + if let Some(info) = info { + result.insert("info".into(), Value::String(info)); + } + Some(Value::Object(result)) + } + + fn body_parameter( + &self, + raw_body: &Value, + indexed: &IndexedOperation<'_>, + unique_name: bool, + ) -> Result, String> { + let body = self.resolve(raw_body).unwrap_or(raw_body); + let Some(body_obj) = body.as_object() else { + return Ok(None); + }; + let Some(content) = body_obj.get("content").and_then(Value::as_object) else { + return Ok(None); + }; + let Some((media_type, media)) = content.iter().find(|(name, _)| is_parameter_media(name)) + else { + return Ok(None); + }; + let Some(schema) = media.get("schema") else { + return Ok(None); + }; + self.register_reference_schema(schema, &indexed.tag)?; + let required = body_obj + .get("required") + .and_then(Value::as_bool) + .unwrap_or(true); + let dependencies = self.register_inline_dependencies(schema, &indexed.tag)?; + let (code, chain) = + self.compiler + .compile_endpoint_schema(schema, required, &indexed.tag)?; + let operation_name = if unique_name { + indexed.name.clone() + } else { + format!("{}_{}", indexed.tag, indexed.name) + }; + let fallback = snake_to_camel(&format!("{operation_name}_Body")); + let actual = self.resolve(schema).unwrap_or(schema); + let resolved = + resolve_schema_name(schema, actual, code.clone(), Some(&fallback), self.options); + if schema.get("$ref").is_none() && complexity(actual) >= 2 { + self.set_generated_schema(resolved.clone(), code, &indexed.tag); + self.set_generated_object(&resolved, actual); + self.set_generated_dependencies(&resolved, dependencies); + } + let zod = if is_named(&resolved) { + resolved + } else { + format!("{resolved}{chain}") + }; + let mut parameter = Map::new(); + parameter.insert("name".into(), Value::String("data".into())); + parameter.insert("type".into(), Value::String("Body".into())); + if let Some(description) = body_obj.get("description") { + parameter.insert("description".into(), description.clone()); + } + parameter.insert("zodSchema".into(), Value::String(zod)); + parameter.insert("bodyObject".into(), body.clone()); + Ok(Some((Value::Object(parameter), media_type.clone()))) + } + + fn merged_parameters(&self, indexed: &IndexedOperation<'a>) -> Vec<&'a Value> { + let mut values: Vec<(String, &Value)> = Vec::new(); + for source in [indexed.path_parameters, indexed.operation.get("parameters")] + .into_iter() + .flatten() + { + if let Some(params) = source.as_array() { + for param in params { + let key = param + .get("$ref") + .and_then(Value::as_str) + .or_else(|| param.get("name").and_then(Value::as_str)) + .unwrap_or_default() + .to_string(); + if let Some(existing) = values.iter_mut().find(|(name, _)| name == &key) { + existing.1 = param; + } else { + values.push((key, param)); + } + } + } + } + values.into_iter().map(|(_, value)| value).collect() + } + + fn parameter( + &self, + raw: &Value, + indexed: &IndexedOperation<'_>, + unique_name: bool, + ) -> Result, String> { + let param = self.resolve(raw).unwrap_or(raw); + let Some(obj) = param.as_object() else { + return Ok(None); + }; + let location = obj.get("in").and_then(Value::as_str).unwrap_or_default(); + if !matches!(location, "query" | "header" | "path") { + return Ok(None); + }; + let name = obj.get("name").and_then(Value::as_str).unwrap_or_default(); + let schema = if let Some(content) = obj.get("content").and_then(Value::as_object) { + let (_, media) = content + .iter() + .find(|(key, _)| is_parameter_media(key)) + .ok_or_else(|| format!("Unsupported media type for param {name}"))?; + media.get("schema").unwrap_or(media) + } else { + obj.get("schema").unwrap_or(&Value::Null) + }; + let required = location == "path" + || obj + .get("required") + .and_then(Value::as_bool) + .unwrap_or(false); + self.register_reference_schema(schema, &indexed.tag)?; + let dependencies = self.register_inline_dependencies(schema, &indexed.tag)?; + let (code, chain) = + self.compiler + .compile_endpoint_schema(schema, required, &indexed.tag)?; + let op = if unique_name { + indexed.name.clone() + } else { + format!("{}_{}", indexed.tag, indexed.name) + }; + let fallback = snake_to_camel(&format!("{op}_{name}Param")); + let actual = self.resolve(schema).unwrap_or(schema); + let root_optional_object = matches!(location, "query" | "header") + && !required + && (actual.get("type").and_then(Value::as_str) == Some("object") + || actual + .get("properties") + .and_then(Value::as_object) + .is_some_and(|p| !p.is_empty())); + let effective_chain = if root_optional_object { + self.compiler.endpoint_chain(schema, true) + } else { + chain + }; + let composed_code = format!("{code}{effective_chain}"); + let zod = resolve_schema_name( + schema, + actual, + composed_code.clone(), + Some(&fallback), + self.options, + ); + if schema.get("$ref").is_none() && complexity(actual) >= 2 { + self.set_generated_schema(zod.clone(), composed_code, &indexed.tag); + self.set_generated_object(&zod, actual); + self.set_generated_dependencies(&zod, dependencies); + } + let mut result = Map::new(); + result.insert( + "name".into(), + Value::String(if location == "path" { + path_param_name(name) + } else { + name.into() + }), + ); + result.insert( + "type".into(), + Value::String( + match location { + "header" => "Header", + "query" => "Query", + _ => "Path", + } + .into(), + ), + ); + result.insert("zodSchema".into(), Value::String(zod)); + result.insert("parameterObject".into(), param.clone()); + if let Some(enum_names) = obj + .get("x-enumNames") + .or_else(|| schema.get("x-enumNames")) + .and_then(Value::as_array) + .filter(|_| is_string_like(actual)) + { + let enum_name = schema_name( + &fallback, + &format!("{}{}", self.options.enum_suffix, self.options.schema_suffix), + ); + result.insert( + "parameterSortingEnumSchemaName".into(), + Value::String(enum_name.clone()), + ); + let enum_code = format!( + "z.enum([{}])", + enum_names + .iter() + .filter_map(Value::as_str) + .map(|value| format!("\"{value}\"")) + .collect::>() + .join(", ") + ); + self.set_generated_schema(enum_name, enum_code, &indexed.tag); + } + Ok(Some(Value::Object(result))) + } + + fn resolve<'b>(&'b self, value: &'b Value) -> Option<&'b Value> { + let reference = value.get("$ref")?.as_str()?; + self.document.pointer(reference.strip_prefix('#')?) + } + + fn register_reference_schema(&self, schema: &Value, tag: &str) -> Result<(), String> { + let Some(reference) = schema.get("$ref").and_then(Value::as_str) else { + return Ok(()); + }; + self.register_reference(reference, tag) + } + + fn register_reference(&self, reference: &str, tag: &str) -> Result<(), String> { + let reference = if reference.starts_with("#/") { + reference.to_string() + } else { + format!( + "#/{}", + reference.trim_start_matches('#').trim_start_matches('/') + ) + }; + let effective_tag = tag; + self.runtime_tags + .borrow_mut() + .entry(reference.clone()) + .or_default() + .insert(effective_tag.to_string()); + let first_use = { + let mut first_tags = self.first_tags.borrow_mut(); + if first_tags.contains_key(&reference) { + false + } else { + first_tags.insert(reference.clone(), effective_tag.to_string()); + true + } + }; + if !first_use || !self.registering.borrow_mut().insert(reference.clone()) { + return Ok(()); + } + if let Some(actual) = self + .document + .pointer(reference.strip_prefix('#').unwrap_or_default()) + { + let mut refs = Vec::new(); + let mut seen = HashSet::default(); + collect_compiled_reference_order(actual, &mut refs, &mut seen); + for child in refs { + self.register_reference(&child, effective_tag)?; + } + if actual.get("enum").is_some() { + let name = schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + ); + let (_, code) = self + .compiler + .compile_reference_for_tag(&reference, effective_tag)?; + self.set_generated_schema(name, code, effective_tag); + } + } + if self + .resolver + .deep_dependencies + .get(&reference) + .is_some_and(|dependencies| dependencies.contains(&reference)) + { + let name = schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + ); + let (_, code) = self + .compiler + .compile_reference_for_tag(&reference, effective_tag)?; + self.set_generated_schema(name, code, effective_tag); + } + self.registering.borrow_mut().remove(&reference); + Ok(()) + } +} + +fn collect_compiled_reference_order( + value: &Value, + refs: &mut Vec, + seen: &mut HashSet, +) { + match value { + Value::Object(object) => { + if let Some(reference) = object.get("$ref").and_then(Value::as_str) { + if seen.insert(reference.to_string()) { + refs.push(reference.to_string()); + } + return; + } + for keyword in ["allOf", "anyOf", "oneOf"] { + if let Some(items) = object.get(keyword).and_then(Value::as_array) { + for child in items { + collect_compiled_reference_order(child, refs, seen); + } + return; + } + } + if let Some(properties) = object.get("properties").and_then(Value::as_object) { + for child in properties.values() { + collect_compiled_reference_order(child, refs, seen); + } + } + if let Some(child) = object + .get("additionalProperties") + .filter(|value| value.is_object()) + { + collect_compiled_reference_order(child, refs, seen); + } + if object.get("type").and_then(Value::as_str) == Some("array") { + if let Some(child) = object.get("items") { + collect_compiled_reference_order(child, refs, seen); + } + } + } + Value::Array(array) => { + for child in array { + collect_compiled_reference_order(child, refs, seen); + } + } + _ => {} + } +} + +fn canonical_enum_refs(document: &Value) -> HashMap { + let mut canonical_by_code = HashMap::default(); + let mut result = HashMap::default(); + let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + else { + return result; + }; + for (name, schema) in schemas { + let Some(values) = schema.get("enum").and_then(Value::as_array) else { + continue; + }; + let Ok(code) = serde_json::to_string(values) else { + continue; + }; + let reference = format!("#/components/schemas/{name}"); + let canonical = canonical_by_code + .entry(code) + .or_insert_with(|| reference.clone()); + if canonical != &reference { + result.insert(reference, canonical.clone()); + } + } + result +} + +fn resolve_schema_name( + original: &Value, + actual: &Value, + code: String, + fallback: Option<&str>, + options: &GenerateOptions, +) -> String { + if let Some(reference) = original.get("$ref").and_then(Value::as_str) { + let name = reference.rsplit('/').next().unwrap_or_default(); + return schema_name(name, &options.schema_suffix); + } + if complexity(actual) < 2 { + return code; + } + fallback + .map(|name| schema_name(name, &options.schema_suffix)) + .unwrap_or(code) +} +fn complexity(schema: &Value) -> u32 { + if schema.get("$ref").is_some() { + return 2; + } + if let Some(types) = schema.get("type").and_then(Value::as_array) { + return 3 + types.iter().map(complexity).sum::(); + } + for key in ["oneOf", "anyOf", "allOf"] { + if let Some(items) = schema.get(key).and_then(Value::as_array) { + let composite = if key == "anyOf" { 3 } else { 2 }; + return composite + + if items.len() == 1 { + complexity(&items[0]) + } else { + items.iter().map(complexity).sum() + }; + } + } + match schema.get("type").and_then(Value::as_str) { + Some("string" | "number" | "integer" | "boolean") => { + 1 + u32::from(schema.get("enum").is_some()) + } + Some("array") => 1 + schema.get("items").map(complexity).unwrap_or(0), + Some("object") => { + 2 + schema + .get("properties") + .and_then(Value::as_object) + .map(|properties| properties.values().map(complexity).sum()) + .unwrap_or(0) + } + _ if schema.get("properties").is_some() => { + 2 + schema + .get("properties") + .and_then(Value::as_object) + .map(|properties| properties.values().map(complexity).sum()) + .unwrap_or(0) + } + _ => 0, + } +} +fn response_schema_name(status: &str, operation: &str, unique: bool, tag: &str) -> String { + let op = if unique { + operation.into() + } else { + format!("{tag}_{operation}") + }; + if status != "default" && status.parse::().ok().is_some_and(|s| !is_success(s)) { + snake_to_camel(&format!("{op}_{status}_ErrorResponse")) + } else { + snake_to_camel(&format!("{op}Response")) + } +} +fn schema_name(name: &str, suffix: &str) -> String { + let name = capitalize(&normalize_name(name)); + if name.ends_with(suffix) { + name + } else { + format!("{name}{suffix}") + } +} +fn normalize_name(value: &str) -> String { + let mut sanitized = String::new(); + let mut separator = false; + for ch in value.trim().chars() { + if ch.is_ascii_alphanumeric() || ch == '_' { + sanitized.push(ch); + separator = false; + } else if !separator { + sanitized.push('_'); + separator = true; + } + } + if sanitized + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_digit()) + { + sanitized.insert(0, '_'); + } + let chars: Vec = sanitized.chars().collect(); + let mut output = String::new(); + let mut index = 0; + while index < chars.len() { + if chars[index] == '_' + && index + 1 < chars.len() + && (chars[index + 1].is_ascii_alphanumeric() || chars[index + 1] == '_') + { + output.extend(chars[index + 1].to_uppercase()); + index += 2; + } else { + output.push(chars[index]); + index += 1; + } + } + output +} +fn snake_to_camel(value: &str) -> String { + let mut out = String::new(); + let mut upper = false; + for ch in value.chars() { + if ch == '_' { + upper = true + } else if upper { + out.extend(ch.to_uppercase()); + upper = false + } else { + out.push(ch) + } + } + out +} +fn capitalize(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_uppercase().chain(chars).collect()) + .unwrap_or_default() +} +fn is_success(status: u16) -> bool { + (200..300).contains(&status) +} +fn is_parameter_media(name: &str) -> bool { + (name.contains("application/") && name.contains("json")) + || name.contains("text/") + || matches!( + name, + "application/octet-stream" + | "multipart/form-data" + | "application/x-www-form-urlencoded" + | "*/*" + ) +} +fn is_named(value: &str) -> bool { + !value.starts_with("z.") && !value.starts_with("ZodExtended.") +} +fn is_string_like(schema: &Value) -> bool { + if schema.get("$ref").is_some() { + return true; + } + if schema.get("type").and_then(Value::as_str) == Some("string") { + return true; + } + if schema + .get("type") + .and_then(Value::as_array) + .is_some_and(|types| types.iter().any(|t| t.as_str() == Some("string"))) + { + return true; + } + ["allOf", "oneOf", "anyOf"].iter().any(|key| { + schema + .get(*key) + .and_then(Value::as_array) + .is_some_and(|items| items.iter().any(is_string_like)) + }) +} +fn copy_optional_string(from: &Map, to: &mut Map, key: &str) { + if let Some(value) = from.get(key) { + to.insert(key.into(), value.clone()); + } +} +fn replace_path(path: &str) -> String { + let mut output = String::with_capacity(path.len()); + let mut remaining = path; + while let Some(open) = remaining.find('{') { + output.push_str(&remaining[..open]); + let after_open = &remaining[open + 1..]; + let Some(close) = after_open.find('}') else { + output.push_str(&remaining[open..]); + return output; + }; + let name = &after_open[..close]; + if !name.is_empty() + && name + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')) + { + output.push(':'); + output.push_str(&path_param_name(name)); + } else { + output.push('{'); + output.push_str(name); + output.push('}'); + } + remaining = &after_open[close + 1..]; + } + output.push_str(remaining); + output +} +fn path_param_name(name: &str) -> String { + name.replace('_', "#") + .split('-') + .enumerate() + .map(|(index, part)| { + if index == 0 { + part.into() + } else { + capitalize(part) + } + }) + .collect::() + .replace('#', "_") +} +fn add_missing_path_parameters(path: &str, parameters: &mut Vec) { + let names: HashSet = parameters + .iter() + .filter_map(|p| p.get("name").and_then(Value::as_str).map(str::to_string)) + .collect(); + let bytes = path.as_bytes(); + let mut index = 0; + while index < bytes.len() { + if bytes[index] != b':' { + index += 1; + continue; + } + let start = index + 1; + let mut end = start; + while end < bytes.len() && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_') { + end += 1; + } + if end == start { + index += 1; + continue; + } + let name = &path[start..end]; + if !names.contains(name) { + parameters.push(serde_json::json!({"name":name,"type":"Path","zodSchema":"z.string()","parameterObject":{"name":name,"required":true,"in":"path","schema":{"type":"string"}}})); + } + index = end; + } +} diff --git a/native/src/engine.rs b/native/src/engine.rs new file mode 100644 index 00000000..07f80c26 --- /dev/null +++ b/native/src/engine.rs @@ -0,0 +1,75 @@ +use serde::Serialize; +use serde_json::Value; + +const METHODS: [&str; 8] = [ + "get", "put", "post", "delete", "options", "head", "patch", "trace", +]; + +#[derive(Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DocumentStats { + pub operations: u32, + pub schemas: u32, + pub parameters: u32, + pub responses: u32, + pub request_bodies: u32, + pub schema_refs: u32, +} + +pub fn analyze(document: &Value) -> DocumentStats { + let mut stats = DocumentStats::default(); + if let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + { + stats.schemas = schemas.len() as u32; + for schema in schemas.values() { + walk_schema(schema, &mut stats); + } + } + + let Some(paths) = document.get("paths").and_then(Value::as_object) else { + return stats; + }; + for path_item in paths.values().filter_map(Value::as_object) { + let path_parameters = path_item.get("parameters").and_then(Value::as_array); + for method in METHODS { + let Some(operation) = path_item.get(method).and_then(Value::as_object) else { + continue; + }; + stats.operations += 1; + stats.parameters += path_parameters.map_or(0, |params| params.len() as u32); + stats.parameters += operation + .get("parameters") + .and_then(Value::as_array) + .map_or(0, |params| params.len() as u32); + if operation.get("requestBody").is_some() { + stats.request_bodies += 1; + } + stats.responses += operation + .get("responses") + .and_then(Value::as_object) + .map_or(0, |responses| responses.len() as u32); + } + } + stats +} + +fn walk_schema(value: &Value, stats: &mut DocumentStats) { + match value { + Value::Object(object) => { + if object.contains_key("$ref") { + stats.schema_refs += 1; + } + for child in object.values() { + walk_schema(child, stats); + } + } + Value::Array(array) => { + for child in array { + walk_schema(child, stats); + } + } + _ => {} + } +} diff --git a/native/src/lib.rs b/native/src/lib.rs new file mode 100644 index 00000000..35706b5e --- /dev/null +++ b/native/src/lib.rs @@ -0,0 +1,495 @@ +mod config; +mod endpoints; +mod engine; +mod openapi; +mod render; +mod resolver; +mod zod; + +use napi::bindgen_prelude::*; +use napi_derive::napi; +use serde_json::Value; + +#[global_allocator] +static ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc; + +#[napi(object)] +pub struct NativeParseResult { + pub document_json: String, + pub parse_micros: i64, +} + +#[napi(object)] +pub struct NativeAnalyzeResult { + pub stats_json: String, + pub elapsed_micros: i64, +} + +#[napi(object)] +pub struct NativeResolverResult { + pub stats_json: String, + pub operation_names_json: String, + pub schema_tags_json: String, + pub elapsed_micros: i64, +} + +#[napi(object)] +pub struct NativeSchemasResult { + pub schemas_json: String, + pub elapsed_micros: i64, +} + +#[napi(object)] +pub struct NativeEndpointsResult { + pub endpoints_json: String, + pub elapsed_micros: i64, +} + +#[napi(object)] +pub struct NativeDataResult { + pub data: Value, + pub elapsed_micros: i64, +} + +#[napi] +pub fn parse_openapi(source: String, yaml: bool) -> Result { + let started = std::time::Instant::now(); + let document = openapi::parse_document(&source, yaml) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let document_json = serde_json::to_string(&document) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + + Ok(NativeParseResult { + document_json, + parse_micros: started.elapsed().as_micros() as i64, + }) +} + +#[napi] +pub fn analyze_openapi(source: String, yaml: bool) -> Result { + let started = std::time::Instant::now(); + let document = openapi::parse_document(&source, yaml) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let stats = engine::analyze(&document); + let stats_json = serde_json::to_string(&stats) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + + Ok(NativeAnalyzeResult { + stats_json, + elapsed_micros: started.elapsed().as_micros() as i64, + }) +} + +#[napi] +pub fn analyze_generation( + source: String, + yaml: bool, + options_json: String, +) -> Result { + let started = std::time::Instant::now(); + let document = openapi::parse_document(&source, yaml) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let options: config::GenerateOptions = serde_json::from_str(&options_json) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let resolver = resolver::Resolver::new(&document, &options) + .map_err(|error| Error::new(Status::InvalidArg, error))?; + let stats_json = serde_json::to_string(&resolver.stats(&document)) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + let names: Vec<&str> = resolver + .operations + .iter() + .map(|operation| operation.name.as_str()) + .collect(); + let operation_names_json = serde_json::to_string(&names) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + let schema_tags_json = serde_json::to_string(&resolver.schema_tags) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + Ok(NativeResolverResult { + stats_json, + operation_names_json, + schema_tags_json, + elapsed_micros: started.elapsed().as_micros() as i64, + }) +} + +#[napi] +pub fn compile_schemas( + source: String, + yaml: bool, + options_json: String, +) -> Result { + let started = std::time::Instant::now(); + let document = openapi::parse_document(&source, yaml) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let options: config::GenerateOptions = serde_json::from_str(&options_json) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let resolver = resolver::Resolver::new(&document, &options) + .map_err(|error| Error::new(Status::InvalidArg, error))?; + let schemas = zod::ZodCompiler::new(&document, &options, &resolver) + .compile_components() + .map_err(|error| Error::new(Status::GenericFailure, error))?; + let schemas_json = serde_json::to_string(&schemas) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + Ok(NativeSchemasResult { + schemas_json, + elapsed_micros: started.elapsed().as_micros() as i64, + }) +} + +#[napi] +pub fn compile_endpoints( + source: String, + yaml: bool, + options_json: String, +) -> Result { + let started = std::time::Instant::now(); + let document = openapi::parse_document(&source, yaml) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let options: config::GenerateOptions = serde_json::from_str(&options_json) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let resolver = resolver::Resolver::new(&document, &options) + .map_err(|error| Error::new(Status::InvalidArg, error))?; + let endpoints = endpoints::EndpointExtractor::new(&document, &options, &resolver) + .extract() + .map_err(|error| Error::new(Status::GenericFailure, error))?; + let endpoints_json = serde_json::to_string(&endpoints) + .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?; + Ok(NativeEndpointsResult { + endpoints_json, + elapsed_micros: started.elapsed().as_micros() as i64, + }) +} + +#[napi] +pub fn compile_data(source: String, yaml: bool, options_json: String) -> Result { + let started = std::time::Instant::now(); + let document = openapi::parse_document(&source, yaml) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let options: config::GenerateOptions = serde_json::from_str(&options_json) + .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?; + let resolver = resolver::Resolver::new(&document, &options) + .map_err(|error| Error::new(Status::InvalidArg, error))?; + let after_resolver = started.elapsed(); + let extractor = endpoints::EndpointExtractor::new(&document, &options, &resolver); + let endpoints = extractor + .extract() + .map_err(|error| Error::new(Status::GenericFailure, error))?; + let after_endpoints = started.elapsed(); + let (generated, compiler) = extractor.into_generated_data(); + let mut schema_owners = serde_json::Map::new(); + let mut schema_refs = serde_json::Map::new(); + if let Some(document_schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + { + for name in document_schemas.keys() { + let reference = format!("#/components/schemas/{name}"); + let zod_name = zod::schema_name(name, &options.schema_suffix); + schema_owners.insert( + zod_name.clone(), + Value::String(compiler.schema_owner(&reference)), + ); + schema_refs.insert(zod_name, Value::String(reference)); + } + } + for (name, tag) in compiler.extracted_schema_tags() { + schema_owners.insert(name, Value::String(tag)); + } + for (name, tags) in generated.tags { + let tag = if tags.len() == 1 { + tags.into_iter() + .next() + .unwrap_or_else(|| options.default_tag.clone()) + } else { + options.default_tag.clone() + }; + schema_owners.insert(name, Value::String(tag)); + } + let circular_schemas = compiler.circular_schema_names(); + let generated_objects = generated.objects; + let generated_dependencies = generated.dependencies; + let topology_order: Vec = resolver + .topology_order + .iter() + .map(|reference| { + zod::schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &options.schema_suffix, + ) + }) + .collect(); + let base_url = if options.base_url.is_empty() { + document + .pointer("/servers/0/url") + .and_then(Value::as_str) + .unwrap_or_default() + } else { + &options.base_url + }; + let extracted_schemas = compiler.extracted_schema_codes(); + let components = compiler + .compile_components_with_tags(&generated.first_tags) + .map_err(|error| Error::new(Status::GenericFailure, error))?; + let after_components = started.elapsed(); + let root_enums: Vec<(String, Value)> = components + .iter() + .filter(|(_, code)| code.as_str().is_some_and(|code| code.starts_with("z.enum"))) + .map(|(name, code)| (name.clone(), code.clone())) + .collect(); + let mut sortable_schemas = serde_json::Map::new(); + for (name, code) in components { + if !code.as_str().is_some_and(|code| code.starts_with("z.enum")) { + sortable_schemas.insert(name, code); + } + } + sortable_schemas.extend(generated.schemas); + sortable_schemas.extend(root_enums); + let mut schemas = serde_json::Map::new(); + for reference in &resolver.topology_order { + let name = zod::schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &options.schema_suffix, + ); + if let Some(code) = sortable_schemas.shift_remove(&name) { + schemas.insert(name, code); + } + } + schemas.extend(sortable_schemas); + let mut ordered_schemas = extracted_schemas; + ordered_schemas.extend(schemas); + let mut usage_tags: rustc_hash::FxHashMap> = + rustc_hash::FxHashMap::default(); + for endpoint in &endpoints { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + let mut direct = Vec::new(); + if let Some(name) = endpoint.get("response").and_then(Value::as_str) { + direct.push(name); + } + direct.extend( + endpoint + .get("errors") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|error| error.get("zodSchema").and_then(Value::as_str)), + ); + direct.extend( + endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .flat_map(|parameter| { + ["zodSchema", "parameterSortingEnumSchemaName"] + .into_iter() + .filter_map(|key| parameter.get(key).and_then(Value::as_str)) + }), + ); + for name in direct { + if ordered_schemas.contains_key(name) { + usage_tags + .entry(name.to_string()) + .or_default() + .insert(tag.to_string()); + } + } + } + let mut changed = true; + while changed { + changed = false; + for (name, code) in &ordered_schemas { + let Some(tags) = usage_tags.get(name).cloned() else { + continue; + }; + for dependency in code + .as_str() + .into_iter() + .flat_map(|code| { + code.split(|c: char| !c.is_ascii_alphanumeric() && c != '_' && c != '$') + }) + .filter(|dependency| { + *dependency != name && ordered_schemas.contains_key(*dependency) + }) + { + let target = usage_tags.entry(dependency.to_string()).or_default(); + let previous = target.len(); + target.extend(tags.iter().cloned()); + changed |= target.len() != previous; + } + } + } + for (name, tags) in &usage_tags { + let owner = if tags.len() == 1 { + tags.iter() + .next() + .cloned() + .unwrap_or_else(|| options.default_tag.clone()) + } else { + options.default_tag.clone() + }; + schema_owners.insert(name.clone(), Value::String(owner)); + } + let render_started = std::time::Instant::now(); + let (rendered_models, rendered_endpoints, rendered_queries, rendered_acl, rendered_shared) = + std::thread::scope(|scope| { + let models = scope.spawn(|| { + render::render_model_proxies( + &document, + &endpoints, + &ordered_schemas, + &schema_owners, + &usage_tags, + &schema_refs, + &resolver.ordered_dependencies, + &generated_objects, + &generated_dependencies, + &circular_schemas, + &options, + ) + }); + let rendered_endpoints = scope.spawn(|| { + render::render_endpoints(&endpoints, &ordered_schemas, &schema_owners, &options) + }); + let queries = + scope.spawn(|| render::render_queries(&endpoints, &schema_owners, &options)); + let acl = scope.spawn(|| render::render_acl(&endpoints, &schema_owners, &options)); + let shared = scope.spawn(|| render::render_shared(&endpoints, &options)); + ( + models.join().unwrap(), + rendered_endpoints.join().unwrap(), + queries.join().unwrap(), + acl.join().unwrap(), + shared.join().unwrap(), + ) + }); + let render_elapsed = render_started.elapsed(); + let after_render = started.elapsed(); + let acl_tag_count = endpoints + .iter() + .filter(|endpoint| { + endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()) + }) + .filter_map(|endpoint| { + endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + }) + .collect::>() + .len(); + let rendered_complete = options.native_compact + && options.split_by_tags + && ((options.models_in_common && options.ts_namespaces) + || (!options.models_in_common && !options.ts_namespaces)) + && !options.inline_endpoints + && !options.builder_configs + && options.workspace_context.is_empty() + && (!options.acl + || (rendered_acl.len() == acl_tag_count && rendered_shared.contains_key("appAcl"))); + let mut rendered_tags = indexmap::IndexSet::new(); + for endpoint in &endpoints { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + rendered_tags.insert(tag.to_string()); + } + rendered_tags.insert(options.default_tag.clone()); + let rendered_tags = rendered_tags.into_iter().collect::>(); + let compact = || Value::Object(serde_json::Map::new()); + let data = serde_json::json!({ + "endpoints": if rendered_complete { Value::Array(Vec::new()) } else { Value::Array(endpoints) }, + "schemas": if rendered_complete { compact() } else { Value::Object(ordered_schemas) }, + "schemaOwners": if rendered_complete { compact() } else { Value::Object(schema_owners) }, + "schemaRefs": if rendered_complete { compact() } else { Value::Object(schema_refs) }, + "circularSchemas": if rendered_complete { Value::Array(Vec::new()) } else { serde_json::to_value(circular_schemas).unwrap() }, + "topologyOrder": if rendered_complete { Value::Array(Vec::new()) } else { serde_json::to_value(topology_order).unwrap() }, + "baseUrl": base_url, + "generatedObjects": if rendered_complete { compact() } else { Value::Object(generated_objects) }, + "generatedDependencies": if rendered_complete { compact() } else { Value::Object(generated_dependencies) }, + "dependencies": if rendered_complete { compact() } else { serde_json::to_value(resolver.ordered_dependencies).unwrap() }, + "renderedModels": rendered_models, + "renderedEndpoints": rendered_endpoints, + "renderedQueries": rendered_queries, + "renderedAcl": rendered_acl, + "renderedShared": rendered_shared, + "renderedComplete": rendered_complete, + "renderedTags": rendered_tags, + "document": if rendered_complete { Value::Null } else { document.clone() }, + }); + if std::env::var_os("OPENAPI_NATIVE_PROFILE").is_some() { + let finished = started.elapsed(); + eprintln!( + "native profile resolver={:.3}ms endpoints={:.3}ms components={:.3}ms finalize={:.3}ms total={:.3}ms", + after_resolver.as_secs_f64() * 1000.0, + (after_endpoints - after_resolver).as_secs_f64() * 1000.0, + (after_components - after_endpoints).as_secs_f64() * 1000.0, + (finished - after_components).as_secs_f64() * 1000.0, + finished.as_secs_f64() * 1000.0, + ); + eprintln!( + "native render parallel={:.3}ms assemble+json={:.3}ms", + render_elapsed.as_secs_f64() * 1000.0, + (finished - after_render).as_secs_f64() * 1000.0, + ); + } + Ok(NativeDataResult { + data, + elapsed_micros: started.elapsed().as_micros() as i64, + }) +} + +#[napi] +pub fn native_version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openapi_codegen_compile_data( + source_ptr: *const u8, + source_len: usize, + yaml: u8, + options_ptr: *const u8, + options_len: usize, + output_len: *mut usize, +) -> *mut u8 { + if source_ptr.is_null() || options_ptr.is_null() || output_len.is_null() { + return std::ptr::null_mut(); + } + let source = unsafe { std::slice::from_raw_parts(source_ptr, source_len) }; + let options = unsafe { std::slice::from_raw_parts(options_ptr, options_len) }; + let Ok(source) = std::str::from_utf8(source) else { + return std::ptr::null_mut(); + }; + let Ok(options) = std::str::from_utf8(options) else { + return std::ptr::null_mut(); + }; + let Ok(result) = compile_data(source.to_string(), yaml != 0, options.to_string()) else { + return std::ptr::null_mut(); + }; + let Ok(mut bytes) = serde_json::to_vec(&result.data) else { + return std::ptr::null_mut(); + }; + let pointer = bytes.as_mut_ptr(); + unsafe { *output_len = bytes.len() }; + std::mem::forget(bytes); + pointer +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openapi_codegen_free(pointer: *mut u8, length: usize) { + if !pointer.is_null() && length > 0 { + unsafe { drop(Vec::from_raw_parts(pointer, length, length)) }; + } +} diff --git a/native/src/openapi.rs b/native/src/openapi.rs new file mode 100644 index 00000000..e16c656e --- /dev/null +++ b/native/src/openapi.rs @@ -0,0 +1,11 @@ +use serde_json::Value; + +pub fn parse_document(source: &str, yaml: bool) -> Result> { + let source = source.strip_prefix('\u{feff}').unwrap_or(source); + if yaml { + let value: serde_yaml::Value = serde_yaml::from_str(source)?; + return Ok(serde_json::to_value(value)?); + } + + Ok(serde_json::from_str(source)?) +} diff --git a/native/src/render.rs b/native/src/render.rs new file mode 100644 index 00000000..39e122e5 --- /dev/null +++ b/native/src/render.rs @@ -0,0 +1,3814 @@ +use std::collections::VecDeque; + +use indexmap::{IndexMap as BaseIndexMap, IndexSet as BaseIndexSet}; +use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet}; +use serde_json::{Map, Value}; + +use crate::config::GenerateOptions; + +type IndexMap = BaseIndexMap; +type IndexSet = BaseIndexSet; + +pub fn render_model_proxies( + document: &Value, + endpoints: &[Value], + schemas: &Map, + schema_owners: &Map, + schema_usage_tags: &HashMap>, + schema_refs: &Map, + dependencies: &IndexMap>, + generated_objects: &Map, + generated_dependencies: &Map, + circular_schemas: &[String], + options: &GenerateOptions, +) -> Map { + let started = std::time::Instant::now(); + let mut rendered = Map::new(); + if !options.split_by_tags { + return rendered; + } + if !options.models_in_common { + return render_local_models( + document, + schemas, + schema_owners, + schema_usage_tags, + schema_refs, + dependencies, + generated_objects, + generated_dependencies, + circular_schemas, + options, + ); + } + let render_dependencies = augment_render_dependencies(schemas, schema_refs, dependencies); + let after_dependencies = started.elapsed(); + let composite_dependencies = compiled_component_dependencies(schemas, schema_refs); + let after_composites = started.elapsed(); + let (common, proxies, common_elapsed, proxies_elapsed) = std::thread::scope(|scope| { + let common_started = std::time::Instant::now(); + let common = scope.spawn(move || { + let content = render_common_models( + document, + schemas, + schema_refs, + generated_objects, + circular_schemas, + &options.default_tag, + options, + ); + (content, common_started.elapsed()) + }); + let proxies_started = std::time::Instant::now(); + let proxies = scope.spawn(move || { + let content = render_proxy_modules( + endpoints, + schemas, + schema_refs, + &render_dependencies, + &composite_dependencies, + generated_dependencies, + options, + ); + (content, proxies_started.elapsed()) + }); + let (common, common_elapsed) = common.join().unwrap(); + let (proxies, proxies_elapsed) = proxies.join().unwrap(); + (common, proxies, common_elapsed, proxies_elapsed) + }); + rendered.insert(options.default_tag.clone(), Value::String(common)); + rendered.extend(proxies); + if std::env::var_os("OPENAPI_NATIVE_PROFILE").is_some() { + eprintln!( + "native models dependencies={:.3}ms composites={:.3}ms common={:.3}ms proxies={:.3}ms total={:.3}ms", + after_dependencies.as_secs_f64() * 1000.0, + (after_composites - after_dependencies).as_secs_f64() * 1000.0, + common_elapsed.as_secs_f64() * 1000.0, + proxies_elapsed.as_secs_f64() * 1000.0, + started.elapsed().as_secs_f64() * 1000.0, + ); + } + rendered +} + +fn render_local_models( + document: &Value, + schemas: &Map, + schema_owners: &Map, + schema_usage_tags: &HashMap>, + schema_refs: &Map, + dependencies: &IndexMap>, + generated_objects: &Map, + generated_dependencies: &Map, + circular_schemas: &[String], + options: &GenerateOptions, +) -> Map { + let mut by_tag: IndexMap> = IndexMap::default(); + for (name, code) in schemas { + if options.models_in_modules + && let Some(tags) = schema_usage_tags.get(name) + { + for tag in tags { + by_tag + .entry(tag.clone()) + .or_default() + .insert(name.clone(), code.clone()); + } + } else if let Some(tag) = schema_owners.get(name).and_then(Value::as_str) { + by_tag + .entry(tag.to_string()) + .or_default() + .insert(name.clone(), code.clone()); + } + } + + let model_suffix = options + .configs + .get("models") + .map(|config| config.output_file_name_suffix.as_str()) + .unwrap_or("models"); + let import_root = match options.import_path.as_str() { + "relative" => "../".to_string(), + "absolute" => format!("{}/", options.output.trim_end_matches('/')), + _ => format!("{}/", options.ts_path.trim_end_matches('/')), + }; + let mut rendered = Map::new(); + let name_by_ref: HashMap<&str, &str> = schema_refs + .iter() + .filter_map(|(name, reference)| { + reference + .as_str() + .map(|reference| (reference, name.as_str())) + }) + .collect(); + for (tag, mut tag_schemas) in by_tag { + let mut imports: IndexMap> = IndexMap::default(); + for schema_name in tag_schemas.keys() { + if let Some(reference) = schema_refs.get(schema_name).and_then(Value::as_str) { + for name in dependencies + .get(reference) + .into_iter() + .flatten() + .filter_map(|dependency| name_by_ref.get(dependency.as_str()).copied()) + { + let Some(owner) = schema_owners.get(name).and_then(Value::as_str) else { + continue; + }; + if owner != tag { + let names = imports.entry(owner.to_string()).or_default(); + if !names.iter().any(|existing| existing == name) { + names.push(name.to_string()); + } + } + } + if let Some(schema) = resolve_document_ref(document, reference) { + let mut enum_codes = Vec::new(); + collect_inline_enum_codes(schema, &mut enum_codes); + for code in enum_codes { + let Some(name) = schemas.iter().find_map(|(name, value)| { + (value.as_str() == Some(code.as_str()) + && !schema_refs.contains_key(name)) + .then_some(name.as_str()) + }) else { + continue; + }; + let Some(owner) = schema_owners.get(name).and_then(Value::as_str) else { + continue; + }; + if owner != tag { + let names = imports.entry(owner.to_string()).or_default(); + if !names.iter().any(|existing| existing == name) { + names.push(name.to_string()); + } + } + } + } + } + let mut visited = HashSet::default(); + for name in generated_dependencies + .get(schema_name) + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + { + append_local_model_dependency( + name, + &tag, + schemas, + schema_owners, + schema_refs, + dependencies, + &name_by_ref, + &mut imports, + &mut visited, + ); + } + } + if options.models_in_modules { + for names in imports.values() { + for name in names { + if let Some(code) = schemas.get(name) { + tag_schemas.insert(name.clone(), code.clone()); + } + } + } + imports.clear(); + let namespace_prefixes = schema_owners + .values() + .filter_map(Value::as_str) + .map(|owner| format!("{}Models.", capitalize(owner))) + .collect::>(); + for value in tag_schemas.values_mut() { + let Value::String(code) = value else { + continue; + }; + for prefix in &namespace_prefixes { + *code = code.replace(prefix, ""); + } + } + } + let mut content = render_common_models( + document, + &tag_schemas, + schema_refs, + generated_objects, + circular_schemas, + &tag, + options, + ); + if !imports.is_empty() { + let mut lines = vec!["import { z } from \"zod\";".to_string()]; + for (owner, names) in imports { + let owner_tag = decapitalize(&owner); + lines.push(format!( + "import {{ {} }} from \"{import_root}{owner_tag}/{owner_tag}.{model_suffix}\";", + names.join(", ") + )); + } + content = content.replacen("import { z } from \"zod\";", &lines.join("\n"), 1); + } + rendered.insert(tag, Value::String(content)); + } + rendered +} + +fn collect_inline_enum_codes(value: &Value, codes: &mut Vec) { + match value { + Value::Array(values) => values + .iter() + .for_each(|value| collect_inline_enum_codes(value, codes)), + Value::Object(object) => { + if !object.contains_key("$ref") { + if let Some(values) = object.get("enum").and_then(Value::as_array) { + let code = format!( + "z.enum({})", + Value::Array(values.clone()).to_string().replace(',', ", ") + ); + if !codes.contains(&code) { + codes.push(code); + } + } + object + .values() + .for_each(|value| collect_inline_enum_codes(value, codes)); + } + } + _ => {} + } +} + +#[allow(clippy::too_many_arguments)] +fn append_local_model_dependency<'a>( + name: &'a str, + tag: &str, + schemas: &Map, + schema_owners: &Map, + schema_refs: &Map, + dependencies: &IndexMap>, + name_by_ref: &HashMap<&'a str, &'a str>, + imports: &mut IndexMap>, + visited: &mut HashSet, +) { + if !visited.insert(name.to_string()) { + return; + } + if let Some(owner) = schema_owners.get(name).and_then(Value::as_str) { + if owner != tag && schemas.contains_key(name) { + let names = imports.entry(owner.to_string()).or_default(); + if !names.iter().any(|existing| existing == name) { + names.push(name.to_string()); + } + } + } + let Some(reference) = schema_refs.get(name).and_then(Value::as_str) else { + return; + }; + for child in dependencies + .get(reference) + .into_iter() + .flatten() + .filter_map(|dependency| name_by_ref.get(dependency.as_str()).copied()) + { + append_local_model_dependency( + child, + tag, + schemas, + schema_owners, + schema_refs, + dependencies, + name_by_ref, + imports, + visited, + ); + } +} + +#[allow(clippy::too_many_arguments)] +fn render_proxy_modules( + endpoints: &[Value], + schemas: &Map, + schema_refs: &Map, + dependencies: &IndexMap>, + composite_dependencies: &IndexMap>, + generated_dependencies: &Map, + options: &GenerateOptions, +) -> Map { + let mut rendered = Map::new(); + let name_by_ref: HashMap<&str, &str> = schema_refs + .iter() + .filter_map(|(name, reference)| { + reference + .as_str() + .map(|reference| (reference, name.as_str())) + }) + .collect(); + let ref_by_name: HashMap<&str, &str> = schema_refs + .iter() + .filter_map(|(name, reference)| { + reference + .as_str() + .map(|reference| (name.as_str(), reference)) + }) + .collect(); + let mut by_tag: IndexMap> = IndexMap::default(); + for endpoint in endpoints { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + by_tag.entry(tag.to_string()).or_default().push(endpoint); + } + for (tag, tag_endpoints) in by_tag { + if tag == options.default_tag { + continue; + } + let mut used = Vec::new(); + let mut seen = HashSet::default(); + let mut queue = VecDeque::new(); + for endpoint in tag_endpoints { + enqueue_endpoint_names(endpoint, &mut used, &mut seen, &mut queue); + } + while let Some(name) = queue.pop_front() { + let mut direct_names = HashSet::default(); + if let Some(reference) = ref_by_name.get(name.as_str()) { + if let Some(children) = dependencies.get(*reference) { + for child in children { + if let Some(child_name) = name_by_ref.get(child.as_str()) { + direct_names.insert((*child_name).to_string()); + enqueue(child_name, &mut used, &mut seen, &mut queue); + } + } + } + } + if let Some(children) = generated_dependencies.get(&name).and_then(Value::as_array) { + let mut expanded = HashSet::default(); + for child in children.iter().filter_map(Value::as_str) { + enqueue_deep_dependency( + child, + &ref_by_name, + &name_by_ref, + composite_dependencies, + &mut direct_names, + &mut expanded, + &mut used, + &mut seen, + &mut queue, + ); + } + } + if let Some(code) = schemas.get(&name).and_then(Value::as_str) { + for candidate in schema_identifiers(code) { + if candidate != name + && schemas.contains_key(candidate) + && !direct_names.contains(candidate) + { + enqueue(candidate, &mut used, &mut seen, &mut queue); + } + } + } + } + used.retain(|name| schemas.contains_key(name)); + if used.is_empty() { + continue; + } + let content = render_proxy(&tag, &used, schemas, options); + rendered.insert(tag, Value::String(content)); + } + rendered +} + +fn render_common_models( + document: &Value, + schemas: &Map, + schema_refs: &Map, + generated_objects: &Map, + circular_schemas: &[String], + namespace_tag: &str, + options: &GenerateOptions, +) -> String { + let suffix = &options.schema_suffix; + let namespace_suffix = options + .configs + .get("models") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Models"); + let namespace = format!("{}{}", capitalize(namespace_tag), namespace_suffix); + let mut enum_objects = HashMap::default(); + if let Some(component_schemas) = document.pointer("/components/schemas") { + collect_enum_objects(component_schemas, &mut enum_objects); + } + let mut lines = vec!["import { z } from \"zod\";".to_string(), String::new()]; + if options.ts_namespaces { + lines.push(format!("export namespace {namespace} {{")); + } + let schemas = schemas.iter().collect::>(); + let chunk_size = schemas.len().div_ceil(4).max(1); + let schema_lines = std::thread::scope(|scope| { + schemas + .chunks(chunk_size) + .map(|chunk| { + scope.spawn(|| { + chunk + .iter() + .filter_map(|(name, code)| { + render_common_schema_lines( + document, + name, + code, + schema_refs, + generated_objects, + &enum_objects, + circular_schemas, + suffix, + options, + ) + }) + .flatten() + .collect::>() + }) + }) + .collect::>() + .into_iter() + .flat_map(|handle| handle.join().unwrap()) + .collect::>() + }); + lines.extend(schema_lines); + if options.ts_namespaces { + lines.push("}".into()); + } + format!("{}\n", lines.join("\n").trim_end()) +} + +fn render_common_schema_lines( + document: &Value, + name: &str, + code: &Value, + schema_refs: &Map, + generated_objects: &Map, + enum_objects: &HashMap, + circular_schemas: &[String], + suffix: &str, + options: &GenerateOptions, +) -> Option> { + let code = code.as_str()?; + let schema_object = generated_objects + .get(name) + .or_else(|| { + schema_refs + .get(name) + .and_then(Value::as_str) + .and_then(|reference| resolve_document_ref(document, reference)) + }) + .or_else(|| enum_objects.get(code)); + let mut lines = vec!["/** ".into(), format!(" * {name} ")]; + let schema_type = if code.starts_with("z.enum(") { + "enum" + } else { + schema_object + .and_then(|schema| schema.get("type")) + .and_then(Value::as_str) + .unwrap_or("object") + }; + lines.push(format!(" * @type {{ {schema_type} }}")); + if let Some(schema) = schema_object { + let description = schema_description(schema); + if !description.is_empty() { + lines.push(format!( + " * @description {}", + description.replace('\n', "\n *") + )); + } + let mut properties = IndexMap::default(); + collect_property_docs(document, schema, "", &mut properties, suffix); + for (property, (ty, description)) in properties { + lines.push(format!( + " * @property {{ {ty} }} {property} {} ", + description.replace('\n', "\n *") + )); + } + } + lines.push(" */".into()); + let type_name = remove_suffix(name, suffix); + let is_circular = circular_schemas.iter().any(|schema| schema == name); + if is_circular { + if let Some(schema) = schema_object { + lines.push(format!( + "export type {type_name} = {};", + render_schema_type(schema, suffix, options) + )); + } + lines.push(format!( + "export const {name}: z.ZodObject & z.ZodType<{type_name}> = {code};" + )); + } else { + lines.push(format!("export const {name} = z.compile({code});")); + lines.push(format!("export type {type_name} = z.infer;")); + } + if code.starts_with("z.enum(") { + lines.push(format!("export const {type_name} = {name}.enum;")); + } + lines.push(String::new()); + Some(lines) +} + +fn render_schema_type(schema: &Value, suffix: &str, options: &GenerateOptions) -> String { + if let Some(reference) = schema.get("$ref").and_then(Value::as_str) { + let name = reference.rsplit('/').next().unwrap_or("unknown"); + return remove_suffix(&crate::zod::schema_name(name, suffix), suffix); + } + + for (key, separator) in [("allOf", " & "), ("oneOf", " | "), ("anyOf", " | ")] { + if let Some(parts) = schema + .get(key) + .and_then(Value::as_array) + .filter(|parts| !parts.is_empty()) + { + let rendered = parts + .iter() + .map(|part| render_schema_type(part, suffix, options)) + .collect::>() + .join(separator); + return add_nullable_type(schema, rendered); + } + } + + if let Some(values) = schema + .get("enum") + .and_then(Value::as_array) + .filter(|values| !values.is_empty()) + { + let rendered = values + .iter() + .map(Value::to_string) + .collect::>() + .join(" | "); + return add_nullable_type(schema, rendered); + } + + if schema.get("type").and_then(Value::as_str) == Some("array") { + let item_type = schema + .get("items") + .map(|items| render_schema_type(items, suffix, options)) + .unwrap_or_else(|| "unknown".into()); + return add_nullable_type(schema, format!("Array<{item_type}>")); + } + + if schema.get("type").and_then(Value::as_str) == Some("object") + || schema.get("properties").is_some() + || schema.get("additionalProperties").is_some() + { + let required = schema.get("required").and_then(Value::as_array); + let mut properties = schema + .get("properties") + .and_then(Value::as_object) + .into_iter() + .flatten() + .map(|(name, property)| { + let is_required = options.with_implicit_required_props + || required.is_some_and(|required| { + required.iter().any(|value| value.as_str() == Some(name)) + }) + || property.get("default").is_some(); + let optional = if is_required { "" } else { "?" }; + let mut property_type = render_schema_type(property, suffix, options); + if options.replace_optional_with_nullish + && !is_required + && property.get("nullable").and_then(Value::as_bool) != Some(true) + { + property_type.push_str(" | null"); + } + let property_name = if name + .chars() + .next() + .is_some_and(|first| first.is_ascii_alphabetic()) + && name + .chars() + .all(|character| character.is_ascii_alphanumeric() || character == '_') + { + name.clone() + } else { + Value::String(name.clone()).to_string() + }; + format!("{property_name}{optional}: {property_type}") + }) + .collect::>(); + if let Some(additional) = schema.get("additionalProperties") { + let value_type = if additional.is_object() { + render_schema_type(additional, suffix, options) + } else { + "unknown".into() + }; + properties.push(format!("[key: string]: {value_type}")); + } + return add_nullable_type(schema, format!("{{ {} }}", properties.join("; "))); + } + + let primitive = match schema.get("type").and_then(Value::as_str) { + Some("string") => "string", + Some("number" | "integer") => "number", + Some("boolean") => "boolean", + Some("null") => "null", + _ => "unknown", + }; + add_nullable_type(schema, primitive.into()) +} + +fn add_nullable_type(schema: &Value, rendered: String) -> String { + if schema.get("nullable").and_then(Value::as_bool) == Some(true) && rendered != "null" { + format!("{rendered} | null") + } else { + rendered + } +} + +fn collect_enum_objects(value: &Value, enums: &mut HashMap) { + match value { + Value::Array(values) => values + .iter() + .for_each(|value| collect_enum_objects(value, enums)), + Value::Object(object) => { + if let Some(values) = object.get("enum").and_then(Value::as_array) { + let code = format!( + "z.enum({})", + Value::Array(values.clone()).to_string().replace(',', ", ") + ); + let entry = enums + .entry(code) + .or_insert_with(|| Value::Object(Map::new())); + if let Some(target) = entry.as_object_mut() { + for (key, value) in object { + target.insert(key.clone(), value.clone()); + } + } + } + object + .values() + .for_each(|value| collect_enum_objects(value, enums)); + } + _ => {} + } +} + +fn resolve_document_ref<'a>(document: &'a Value, reference: &str) -> Option<&'a Value> { + let pointer = reference.strip_prefix('#')?; + document.pointer(pointer) +} + +const DESCRIPTION_KEYS: &[(&str, &str)] = &[ + ("minimum", "Minimum"), + ("exclusiveMinimum", "Exclusive Minimum"), + ("maximum", "Maximum"), + ("exclusiveMaximum", "Exclusive Maximum"), + ("minItems", "Min Items"), + ("minLength", "Min Length"), + ("minProperties", "Min Properties"), + ("maxItems", "Max Items"), + ("maxLength", "Max Length"), + ("maxProperties", "Max Properties"), + ("default", "Default"), + ("example", "Example"), +]; + +fn schema_description(schema: &Value) -> String { + let mut parts = Vec::new(); + if let Some(description) = schema.get("description").and_then(Value::as_str) { + parts.push(description.to_string()); + } + for (key, label) in DESCRIPTION_KEYS { + if let Some(value) = schema.get(*key) { + parts.push(format!("{label}: `{}`", display_js_value(value))); + } + } + parts.join(". ") +} + +fn display_js_value(value: &Value) -> String { + match value { + Value::String(value) => value.clone(), + Value::Array(values) => values + .iter() + .map(display_js_value) + .collect::>() + .join(","), + Value::Object(_) => "[object Object]".into(), + Value::Null => "null".into(), + _ => value.to_string(), + } +} + +fn collect_property_docs( + document: &Value, + schema: &Value, + prefix: &str, + properties: &mut IndexMap, + schema_suffix: &str, +) { + if let Some(all_of) = schema.get("allOf").and_then(Value::as_array) { + for member in all_of.iter().filter(|member| member.get("$ref").is_some()) { + if let Some(reference) = member.get("$ref").and_then(Value::as_str) { + if let Some(resolved) = resolve_document_ref(document, reference) { + collect_property_docs(document, resolved, prefix, properties, schema_suffix); + } + } + } + for member in all_of.iter().filter(|member| member.get("$ref").is_none()) { + collect_property_docs(document, member, prefix, properties, schema_suffix); + } + } + if let Some(object) = schema.get("properties").and_then(Value::as_object) { + for (name, property_schema) in object { + let key = if prefix.is_empty() { + name.clone() + } else { + format!("{prefix}.{name}") + }; + let resolved = property_schema + .get("$ref") + .and_then(Value::as_str) + .and_then(|reference| resolve_document_ref(document, reference)) + .unwrap_or(property_schema); + let ty = property_doc_type(document, property_schema, schema_suffix); + let preserve_existing_object = properties.contains_key(&key) + && property_schema.get("type").and_then(Value::as_str) == Some("object"); + if !preserve_existing_object { + properties.shift_remove(&key); + properties.insert(key.clone(), (ty, schema_description(resolved))); + } + let composite = ["allOf", "anyOf", "oneOf"].iter().any(|key| { + property_schema + .get(*key) + .is_some_and(|value| !value.is_null()) + }); + if property_schema.get("$ref").is_none() && !composite { + if property_schema.get("type").and_then(Value::as_str) == Some("array") { + if let Some(items) = property_schema.get("items") { + if items.get("$ref").is_none() { + collect_property_docs( + document, + items, + &format!("{key}.[0]"), + properties, + schema_suffix, + ); + } + } + } else { + collect_property_docs( + document, + property_schema, + &key, + properties, + schema_suffix, + ); + } + } else if composite { + for keyword in ["allOf", "anyOf", "oneOf"] { + for member in property_schema + .get(keyword) + .and_then(Value::as_array) + .into_iter() + .flatten() + { + if member.get("$ref").is_none() { + collect_property_docs( + document, + member, + &key, + properties, + schema_suffix, + ); + } + } + } + } + } + } + if let Some(additional) = schema + .get("additionalProperties") + .filter(|value| value.is_object()) + { + let key = if prefix.is_empty() { + "[key]".into() + } else { + format!("{prefix}.[key]") + }; + properties.insert( + key.clone(), + ( + property_doc_type(document, additional, schema_suffix), + schema_description(additional), + ), + ); + collect_property_docs(document, additional, &key, properties, schema_suffix); + } +} + +fn property_doc_type(document: &Value, schema: &Value, schema_suffix: &str) -> String { + if let Some(reference) = schema.get("$ref").and_then(Value::as_str) { + return property_ref_type(reference); + } + if schema.get("type").and_then(Value::as_str) == Some("array") { + let item_type = schema + .get("items") + .and_then(|items| { + if let Some(reference) = items.get("$ref").and_then(Value::as_str) { + Some(property_ref_type(reference)) + } else { + Some( + items + .get("type") + .and_then(Value::as_str) + .unwrap_or("unknown") + .to_string(), + ) + } + }) + .unwrap_or_else(|| "unknown".into()); + return format!("{item_type}[]"); + } + for keyword in ["allOf", "anyOf", "oneOf"] { + if let Some(first) = schema + .get(keyword) + .and_then(Value::as_array) + .and_then(|values| values.first()) + { + return property_doc_type(document, first, schema_suffix); + } + } + schema + .get("type") + .and_then(Value::as_str) + .unwrap_or("unknown") + .to_string() +} + +fn property_ref_type(reference: &str) -> String { + let mut name = reference + .rsplit('/') + .next() + .unwrap_or("unknown") + .to_string(); + while name.contains("__schema") { + name = name.replace("__schema", "_schema"); + } + name +} + +fn compiled_component_dependencies( + schemas: &Map, + schema_refs: &Map, +) -> IndexMap> { + let mut result = IndexMap::default(); + for (name, code) in schemas { + let Some(reference) = schema_refs.get(name).and_then(Value::as_str) else { + continue; + }; + let Some(code) = code.as_str() else { + continue; + }; + let mut values = Vec::new(); + for candidate in schema_identifiers(code) { + if candidate == name { + continue; + } + if let Some(candidate_ref) = schema_refs.get(candidate).and_then(Value::as_str) { + if !values.iter().any(|value| value == candidate_ref) { + values.push(candidate_ref.to_string()); + } + } + } + if !values.is_empty() { + result.insert(reference.to_string(), values); + } + } + result +} + +fn augment_render_dependencies( + schemas: &Map, + schema_refs: &Map, + dependencies: &IndexMap>, +) -> IndexMap> { + let mut result = dependencies.clone(); + let name_by_ref: HashMap<&str, &str> = schema_refs + .iter() + .filter_map(|(name, reference)| { + reference + .as_str() + .map(|reference| (reference, name.as_str())) + }) + .collect(); + let ref_by_name: HashMap<&str, (&str, usize)> = schema_refs + .iter() + .enumerate() + .filter_map(|(index, (name, reference))| { + reference + .as_str() + .map(|reference| (name.as_str(), (reference, index))) + }) + .collect(); + for (name, code) in schemas { + let Some(reference) = schema_refs.get(name).and_then(Value::as_str) else { + continue; + }; + let identifiers: HashSet<&str> = code + .as_str() + .into_iter() + .flat_map(schema_identifiers) + .collect(); + if identifiers.is_empty() { + continue; + } + let values = result.entry(reference.to_string()).or_default(); + let plain_names: HashSet<&str> = values + .iter() + .filter_map(|child| name_by_ref.get(child.as_str()).copied()) + .collect(); + let mut additions: Vec<(&str, usize)> = identifiers + .into_iter() + .filter(|candidate| *candidate != name && !plain_names.contains(candidate)) + .filter_map(|candidate| ref_by_name.get(candidate).copied()) + .collect(); + additions.sort_unstable_by_key(|(_, index)| *index); + let additions = additions + .into_iter() + .map(|(candidate_ref, _)| candidate_ref.to_string()); + values.extend(additions); + } + result +} + +#[allow(clippy::too_many_arguments)] +fn enqueue_deep_dependency<'a>( + name: &'a str, + ref_by_name: &HashMap<&'a str, &'a str>, + name_by_ref: &HashMap<&'a str, &'a str>, + dependencies: &IndexMap>, + direct_names: &mut HashSet, + expanded: &mut HashSet<&'a str>, + used: &mut Vec, + seen: &mut HashSet, + queue: &mut VecDeque, +) { + direct_names.insert(name.to_string()); + enqueue(name, used, seen, queue); + if !expanded.insert(name) { + return; + } + if let Some(reference) = ref_by_name.get(name) { + if let Some(children) = dependencies.get(*reference) { + for child in children { + if let Some(child_name) = name_by_ref.get(child.as_str()) { + enqueue_deep_dependency( + child_name, + ref_by_name, + name_by_ref, + dependencies, + direct_names, + expanded, + used, + seen, + queue, + ); + } + } + } + } +} + +fn schema_identifiers(code: &str) -> Vec<&str> { + let mut result = Vec::new(); + for candidate in code.split(|character: char| { + !(character.is_ascii_alphanumeric() || character == '_' || character == '$') + }) { + if candidate.ends_with("Schema") && !candidate.is_empty() && !result.contains(&candidate) { + result.push(candidate); + } + } + result +} + +fn enqueue_endpoint_names( + endpoint: &Value, + used: &mut Vec, + seen: &mut HashSet, + queue: &mut VecDeque, +) { + if let Some(response) = endpoint.get("response").and_then(Value::as_str) { + enqueue(response, used, seen, queue); + } + if let Some(errors) = endpoint.get("errors").and_then(Value::as_array) { + for error in errors { + if let Some(name) = error.get("zodSchema").and_then(Value::as_str) { + enqueue(name, used, seen, queue); + } + } + } + if let Some(parameters) = endpoint.get("parameters").and_then(Value::as_array) { + for parameter in parameters { + for key in ["zodSchema", "parameterSortingEnumSchemaName"] { + if let Some(name) = parameter.get(key).and_then(Value::as_str) { + enqueue(name, used, seen, queue); + } + } + } + } +} + +fn enqueue( + name: &str, + used: &mut Vec, + seen: &mut HashSet, + queue: &mut VecDeque, +) { + if name.starts_with("z.") || name.starts_with("ZodExtended.") || !seen.insert(name.to_string()) + { + return; + } + used.push(name.to_string()); + queue.push_back(name.to_string()); +} + +fn render_proxy( + tag: &str, + names: &[String], + schemas: &Map, + options: &GenerateOptions, +) -> String { + let models_config = options.configs.get("models"); + let namespace_suffix = models_config + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Models"); + let file_suffix = models_config + .map(|config| config.output_file_name_suffix.as_str()) + .unwrap_or("models"); + let namespace = format!("{}{}", capitalize(tag), namespace_suffix); + let common_namespace = format!("{}{}", capitalize(&options.default_tag), namespace_suffix); + let common_tag = decapitalize(&options.default_tag); + let import_root = match options.import_path.as_str() { + "relative" => "../".to_string(), + "absolute" => format!("{}/", options.output.trim_end_matches('/')), + _ => format!("{}/", options.ts_path.trim_end_matches('/')), + }; + let common_path = format!("{import_root}{common_tag}/{common_tag}.{file_suffix}"); + let inferred: Vec = names + .iter() + .map(|name| remove_suffix(name, &options.schema_suffix)) + .collect(); + let enum_names: Vec = names + .iter() + .filter(|name| { + schemas + .get(*name) + .and_then(Value::as_str) + .is_some_and(|code| code.starts_with("z.enum")) + }) + .map(|name| remove_suffix(name, &options.schema_suffix)) + .collect(); + if options.ts_namespaces { + let mut lines = vec![ + format!("import {{ {common_namespace} }} from \"{common_path}\";"), + String::new(), + format!("export namespace {namespace} {{"), + ]; + lines.extend( + names + .iter() + .map(|name| format!(" export const {name} = {common_namespace}.{name};")), + ); + lines.extend( + inferred + .iter() + .map(|name| format!(" export type {name} = {common_namespace}.{name};")), + ); + lines.extend( + enum_names + .iter() + .map(|name| format!(" export const {name} = {common_namespace}.{name};")), + ); + lines.push("}".into()); + lines.push(String::new()); + return lines.join("\n"); + } + let mut lines = Vec::new(); + let mut values = names.to_vec(); + for name in enum_names { + if !values.contains(&name) { + values.push(name); + } + } + if !values.is_empty() { + lines.push(format!( + "export {{ {} }} from \"{common_path}\";", + values.join(", ") + )); + } + if !inferred.is_empty() { + lines.push(format!( + "export type {{ {} }} from \"{common_path}\";", + inferred.join(", ") + )); + } + format!("{}\n", lines.join("\n")) +} + +fn capitalize(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_uppercase().chain(chars).collect()) + .unwrap_or_default() +} + +fn decapitalize(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_lowercase().chain(chars).collect()) + .unwrap_or_default() +} + +fn remove_suffix(value: &str, suffix: &str) -> String { + value.strip_suffix(suffix).unwrap_or(value).to_string() +} + +pub fn render_endpoints( + endpoints: &[Value], + schemas: &Map, + schema_owners: &Map, + options: &GenerateOptions, +) -> Map { + let mut rendered = Map::new(); + if !options.split_by_tags { + return rendered; + } + let mut by_tag: IndexMap> = IndexMap::default(); + for endpoint in endpoints { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + by_tag.entry(tag.to_string()).or_default().push(endpoint); + } + for (tag, tag_endpoints) in by_tag { + if should_inline_endpoints(&tag, options) || tag_endpoints.is_empty() { + continue; + } + rendered.insert( + tag.clone(), + Value::String(render_endpoint_module( + &tag, + &tag_endpoints, + schemas, + schema_owners, + options, + )), + ); + } + rendered +} + +fn should_inline_endpoints(tag: &str, options: &GenerateOptions) -> bool { + options.inline_endpoints + && !options + .inline_endpoints_exclude_modules + .iter() + .any(|name| name.eq_ignore_ascii_case(tag)) +} + +fn uses_native_rest_client(options: &GenerateOptions) -> bool { + options.rest_client == "native" +} + +fn request_config_type(options: &GenerateOptions) -> &'static str { + if uses_native_rest_client(options) { + "TransportRequestConfig" + } else { + "AxiosRequestConfig" + } +} + +fn response_type(options: &GenerateOptions) -> &'static str { + if uses_native_rest_client(options) { + "TransportResponse" + } else { + "AxiosResponse" + } +} + +fn render_endpoint_module( + tag: &str, + endpoints: &[&Value], + schemas: &Map, + schema_owners: &Map, + options: &GenerateOptions, +) -> String { + let mut lines = vec![format!( + "import {{ AppRestClient }} from \"{}\";", + options.rest_client_import_path + )]; + let has_get = endpoints + .iter() + .any(|endpoint| endpoint.get("method").and_then(Value::as_str) == Some("get")); + if options.axios_request_config || has_get { + lines.push(if uses_native_rest_client(options) { + "import { type TransportRequestConfig } from \"@povio/openapi-codegen-cli/rest\";" + .into() + } else { + "import { type AxiosRequestConfig } from \"axios\";".into() + }); + } + let parse_schemas: Vec<&str> = endpoints + .iter() + .flat_map(|endpoint| { + endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter(|parameter| { + !matches!( + parameter.get("type").and_then(Value::as_str), + Some("Path" | "Header") + ) + }) + .filter_map(|parameter| { + parameter + .get("parameterSortingEnumSchemaName") + .or_else(|| parameter.get("zodSchema")) + .and_then(Value::as_str) + }) + }) + .collect(); + let response_schemas = endpoints + .iter() + .filter_map(|endpoint| endpoint.get("response").and_then(Value::as_str)); + let used_schemas: Vec<&str> = response_schemas + .chain( + options + .parse_request_params + .then_some(parse_schemas.iter().copied()) + .into_iter() + .flatten(), + ) + .collect(); + if used_schemas.iter().any(|schema| !is_named_schema(schema)) { + lines.push("import { z } from \"zod\";".into()); + } + if options.parse_request_params && !parse_schemas.is_empty() { + lines.push(format!( + "import {{ ZodExtended }} from \"{}\";", + options.zod_import_path + )); + } + let has_named_models = used_schemas.iter().any(|schema| is_named_schema(schema)) + || endpoints + .iter() + .flat_map(|endpoint| { + endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + }) + .filter_map(|parameter| parameter.get("zodSchema").and_then(Value::as_str)) + .any(is_named_schema); + let model_config = options.configs.get("models"); + let model_namespace = format!( + "{}{}", + capitalize(tag), + model_config + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Models") + ); + if has_named_models { + let suffix = model_config + .map(|config| config.output_file_name_suffix.as_str()) + .unwrap_or("models"); + let local_tag = decapitalize(tag); + if options.ts_namespaces { + lines.push(format!( + "import {{ {model_namespace} }} from \"./{local_tag}.{suffix}\";" + )); + } else { + let mut imports: IndexMap> = IndexMap::default(); + for schema in &used_schemas { + if is_named_schema(schema) { + let owner = if options.models_in_modules { + tag + } else { + schema_owners + .get(*schema) + .and_then(Value::as_str) + .unwrap_or(tag) + }; + let bindings = imports.entry(owner.to_string()).or_default(); + if !bindings.iter().any(|binding| binding == *schema) { + bindings.push((*schema).to_string()); + } + } + } + for endpoint in endpoints { + for schema in endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|parameter| parameter.get("zodSchema").and_then(Value::as_str)) + .filter(|schema| is_named_schema(schema)) + { + let name = remove_suffix(schema, &options.schema_suffix); + let owner = if options.models_in_modules { + tag + } else { + schema_owners + .get(schema) + .and_then(Value::as_str) + .unwrap_or(tag) + }; + let binding = format!("type {name}"); + let bindings = imports.entry(owner.to_string()).or_default(); + if !bindings.contains(&binding) { + bindings.push(binding); + } + } + } + for (owner, bindings) in imports { + let owner_tag = decapitalize(&owner); + let path = if owner == tag { + format!("./{owner_tag}.{suffix}") + } else { + format!("{}{owner_tag}/{owner_tag}.{suffix}", import_root(options)) + }; + if bindings.iter().all(|binding| binding.starts_with("type ")) { + lines.push(format!( + "import type {{ {} }} from \"{path}\";", + bindings + .iter() + .map(|binding| binding.trim_start_matches("type ")) + .collect::>() + .join(", ") + )); + } else { + lines.push(format!( + "import {{ {} }} from \"{path}\";", + bindings.join(", ") + )); + } + } + } + } + lines.push(String::new()); + if options.ts_namespaces { + let namespace_suffix = options + .configs + .get("endpoints") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Api"); + lines.push(format!( + "export namespace {}{} {{", + capitalize(tag), + namespace_suffix + )); + } + for endpoint in endpoints { + render_endpoint( + &mut lines, + endpoint, + tag, + schemas, + options, + if options.ts_namespaces { + &model_namespace + } else { + "" + }, + ); + } + if options.ts_namespaces { + lines.push("}".into()); + } + format!("{}\n", lines.join("\n").trim_end()) +} + +#[derive(Clone)] +struct FunctionParam<'a> { + source: &'a Value, + name: String, + kind: &'a str, + required: bool, + ty: String, +} + +fn render_endpoint( + lines: &mut Vec, + endpoint: &Value, + tag: &str, + schemas: &Map, + options: &GenerateOptions, + model_namespace: &str, +) { + let method = endpoint + .get("method") + .and_then(Value::as_str) + .unwrap_or_default(); + let params = function_params(endpoint, options, model_namespace); + let rendered_params = params + .iter() + .map(|param| { + format!( + "{}{}: {}, ", + param.name, + if param.required { "" } else { "?" }, + param.ty + ) + }) + .collect::(); + let request_config = options.axios_request_config || method == "get"; + let config_param = if request_config { + format!( + "config?: {} & {{ allowInvalidResponseData?: boolean }}", + request_config_type(options) + ) + } else { + String::new() + }; + let name = endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + lines.push(format!( + "export const {name} = ({rendered_params}{config_param}) => {{" + )); + lines.push(format!(" return AppRestClient.{method}(")); + let response = endpoint + .get("response") + .and_then(Value::as_str) + .unwrap_or("z.void()"); + lines.push(format!( + " {{ resSchema: {} }},", + imported_schema(response, model_namespace) + )); + let path = endpoint + .get("path") + .and_then(Value::as_str) + .unwrap_or_default(); + lines.push(format!(" `{}`,", render_path(path))); + let body = params.iter().find(|param| param.kind == "Body"); + let config = endpoint_config( + endpoint, + tag, + schemas, + options, + model_namespace, + request_config, + ); + if let Some(body) = body { + let value = if options.parse_request_params { + render_param_parse(body.source, &body.name, schemas, options, model_namespace) + } else { + body.name.clone() + }; + lines.push(format!(" {value},")); + } else if method != "get" && !config.is_empty() { + lines.push(" undefined,".into()); + } + lines.push(format!(" {config}")); + lines.push(" )".into()); + lines.push("};".into()); +} + +fn function_params<'a>( + endpoint: &'a Value, + options: &GenerateOptions, + model_namespace: &str, +) -> Vec> { + let mut result: Vec> = endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .map(|parameter| { + let kind = parameter + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + let schema = parameter + .get("zodSchema") + .and_then(Value::as_str) + .unwrap_or_default(); + let required = parameter + .get("parameterObject") + .or_else(|| parameter.get("bodyObject")) + .and_then(|object| object.get("required")) + .and_then(Value::as_bool) + .unwrap_or(true); + let ty = if is_named_schema(schema) { + let name = remove_suffix(schema, &options.schema_suffix); + if model_namespace.is_empty() { + name + } else { + format!("{model_namespace}.{name}") + } + } else { + primitive_parameter_type(parameter) + }; + FunctionParam { + source: parameter, + name: variable_name( + parameter + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(), + ), + kind, + required, + ty, + } + }) + .collect(); + let kind_rank = |kind: &str| match kind { + "Path" => 0, + "Body" => 1, + "Query" => 2, + "Header" => 3, + _ => 4, + }; + result.sort_by_key(|param| (!param.required, kind_rank(param.kind))); + result +} + +fn primitive_parameter_type(parameter: &Value) -> String { + match parameter + .pointer("/parameterObject/schema/type") + .and_then(Value::as_str) + { + Some("number" | "integer") => "number".into(), + Some("boolean") => "boolean".into(), + Some("array") => "unknown[]".into(), + Some("object") => "Record".into(), + _ => "string".into(), + } +} + +fn endpoint_config( + endpoint: &Value, + _tag: &str, + schemas: &Map, + options: &GenerateOptions, + model_namespace: &str, + request_config: bool, +) -> String { + let parameters = endpoint.get("parameters").and_then(Value::as_array); + let query: Vec<&Value> = parameters + .into_iter() + .flatten() + .filter(|param| param.get("type").and_then(Value::as_str) == Some("Query")) + .collect(); + let headers: Vec<&Value> = parameters + .into_iter() + .flatten() + .filter(|param| param.get("type").and_then(Value::as_str) == Some("Header")) + .collect(); + let request_format = endpoint + .get("requestFormat") + .and_then(Value::as_str) + .unwrap_or("application/json"); + let response_format = endpoint.get("responseFormat").and_then(Value::as_str); + let blob = endpoint.get("response").and_then(Value::as_str) == Some("z.instanceof(Blob)"); + let download = endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false); + let has_headers = !headers.is_empty() + || request_format != "application/json" + || response_format.is_some_and(|format| format != "application/json"); + if query.is_empty() && !has_headers && !blob && !download { + return if request_config { + "config".into() + } else { + String::new() + }; + } + let mut lines = vec!["{".to_string()]; + if request_config { + lines.push(" ...config,".into()); + } + if !query.is_empty() { + lines.push(" params: {".into()); + for param in query { + let raw_name = param + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(); + let property = if valid_property_name(raw_name) { + raw_name.to_string() + } else { + format!("\"{raw_name}\"") + }; + let variable = variable_name(raw_name); + let value = if options.parse_request_params { + render_param_parse(param, &variable, schemas, options, model_namespace) + } else { + variable + }; + lines.push(format!(" {property}: {value},")); + } + lines.push(" },".into()); + } + if has_headers { + lines.push(" headers: {".into()); + if request_format != "application/json" { + lines.push(format!(" 'Content-Type': '{request_format}',")); + } + if let Some(format) = response_format.filter(|format| *format != "application/json") { + lines.push(format!(" 'Accept': '{format}',")); + } + for param in headers { + let name = param + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(); + lines.push(format!(" '{name}': {},", variable_name(name))); + } + lines.push(" },".into()); + } + if blob { + lines.push(" responseType: \"blob\",".into()); + } + if download { + lines.push(" rawResponse: true,".into()); + } + lines.push("}".into()); + lines.join("\n ") +} + +fn render_param_parse( + parameter: &Value, + variable: &str, + schemas: &Map, + _options: &GenerateOptions, + model_namespace: &str, +) -> String { + let schema = parameter + .get("zodSchema") + .and_then(Value::as_str) + .unwrap_or_default(); + let required = parameter + .get("parameterObject") + .or_else(|| parameter.get("bodyObject")) + .and_then(|object| object.get("required")) + .and_then(Value::as_bool) + .unwrap_or(false); + let schema_value = if let Some(sort_schema) = parameter + .get("parameterSortingEnumSchemaName") + .and_then(Value::as_str) + { + let code = schemas + .get(schema) + .and_then(Value::as_str) + .unwrap_or(schema); + let presence = if code.contains(".nullish()") { + ".nullish()" + } else if code.contains(".nullable()") { + ".nullable()" + } else if !required { + ".optional()" + } else { + "" + }; + format!( + "ZodExtended.sortExp({}){presence}", + imported_schema(sort_schema, model_namespace) + ) + } else { + let optional = !required && is_named_schema(schema); + format!( + "{}{}", + imported_schema(schema, model_namespace), + if optional { ".optional()" } else { "" } + ) + }; + let query_arg = if parameter.get("type").and_then(Value::as_str) == Some("Query") { + format!( + ", {{ type: \"query\", name: \"{}\" }}", + parameter + .get("name") + .and_then(Value::as_str) + .unwrap_or_default() + ) + } else { + String::new() + }; + format!("ZodExtended.parse({schema_value}, {variable}{query_arg})") +} + +fn imported_schema(schema: &str, model_namespace: &str) -> String { + if is_named_schema(schema) { + if model_namespace.is_empty() { + schema.to_string() + } else { + format!("{model_namespace}.{schema}") + } + } else { + schema.to_string() + } +} + +fn is_named_schema(schema: &str) -> bool { + !schema.starts_with("z.") && !schema.starts_with("ZodExtended.") +} + +fn endpoint_name(value: &str) -> String { + decapitalize(&snake_to_camel(value)) +} + +fn snake_to_camel(value: &str) -> String { + let mut output = String::new(); + let mut uppercase = false; + for character in value.chars() { + if character == '_' || character == '-' || character == ' ' { + uppercase = true; + } else if uppercase { + output.extend(character.to_uppercase()); + uppercase = false; + } else { + output.push(character); + } + } + output +} + +fn variable_name(value: &str) -> String { + let mut output = String::new(); + let mut uppercase = false; + for character in value.chars() { + if character.is_ascii_alphanumeric() || character == '_' || character == '$' { + if uppercase { + output.extend(character.to_uppercase()); + uppercase = false; + } else { + output.push(character); + } + } else { + uppercase = true; + } + } + if output + .chars() + .next() + .is_some_and(|character| character.is_ascii_digit()) + { + output.insert(0, '_'); + } + output +} + +fn valid_property_name(value: &str) -> bool { + let mut chars = value.chars(); + chars + .next() + .is_some_and(|first| first.is_ascii_alphabetic() || first == '_' || first == '$') + && chars.all(|character| { + character.is_ascii_alphanumeric() || character == '_' || character == '$' + }) +} + +fn render_path(path: &str) -> String { + let mut output = String::with_capacity(path.len()); + let bytes = path.as_bytes(); + let mut index = 0; + while index < bytes.len() { + if bytes[index] == b':' { + let start = index + 1; + let mut end = start; + while end < bytes.len() && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_') { + end += 1; + } + if end > start { + output.push_str("${"); + output.push_str(&path[start..end]); + output.push('}'); + index = end; + continue; + } + } + output.push(bytes[index] as char); + index += 1; + } + output +} + +pub fn render_queries( + endpoints: &[Value], + schema_owners: &Map, + options: &GenerateOptions, +) -> Map { + let mut rendered = Map::new(); + if !options.split_by_tags || options.inline_endpoints || !options.workspace_context.is_empty() { + return rendered; + } + let mut by_tag: IndexMap> = IndexMap::default(); + for endpoint in endpoints { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + by_tag.entry(tag.to_string()).or_default().push(endpoint); + } + let tags = by_tag.into_iter().collect::>(); + let chunk_size = tags.len().div_ceil(4).max(1); + let modules = std::thread::scope(|scope| { + tags.chunks(chunk_size) + .map(|chunk| { + scope.spawn(|| { + chunk + .iter() + .map(|(tag, tag_endpoints)| { + ( + tag.clone(), + Value::String(render_query_module( + tag, + tag_endpoints, + schema_owners, + options, + )), + ) + }) + .collect::>() + }) + }) + .collect::>() + .into_iter() + .flat_map(|handle| handle.join().unwrap()) + .collect::>() + }); + rendered.extend(modules); + rendered +} + +pub fn render_acl( + endpoints: &[Value], + schema_owners: &Map, + options: &GenerateOptions, +) -> Map { + let mut rendered = Map::new(); + if !options.split_by_tags || !options.workspace_context.is_empty() { + return rendered; + } + let mut by_tag: IndexMap> = IndexMap::default(); + for endpoint in endpoints { + if !endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()) + { + continue; + } + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + by_tag.entry(tag.to_string()).or_default().push(endpoint); + } + for (tag, tag_endpoints) in by_tag { + rendered.insert( + tag.clone(), + Value::String(render_acl_module( + &tag, + &tag_endpoints, + schema_owners, + options, + )), + ); + } + rendered +} + +pub fn render_shared(endpoints: &[Value], options: &GenerateOptions) -> Map { + let mut rendered = Map::new(); + if !options.split_by_tags || !options.workspace_context.is_empty() { + return rendered; + } + rendered.insert( + "queryModules".into(), + Value::String(render_query_modules(endpoints, options)), + ); + if let Some(content) = render_domain_errors(endpoints) { + rendered.insert("domainErrors".into(), Value::String(content)); + } + if let Some(content) = render_app_acl(endpoints) { + rendered.insert("appAcl".into(), Value::String(content)); + } + rendered +} + +fn render_query_modules(endpoints: &[Value], options: &GenerateOptions) -> String { + let suffix = options + .configs + .get("queries") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Queries"); + let mut tags = IndexSet::default(); + for endpoint in endpoints { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + tags.insert(tag); + } + let mut lines = vec!["export const enum QueryModule {".to_string()]; + for tag in tags { + lines.push(format!(" {tag} = \"{}{suffix}\",", capitalize(tag))); + } + lines.push("}".into()); + lines.join("\n") +} + +fn render_app_acl(endpoints: &[Value]) -> Option { + let mut by_tag: IndexMap<&str, Vec<&Value>> = IndexMap::default(); + for endpoint in endpoints.iter().filter(|endpoint| { + endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()) + }) { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or_default(); + by_tag.entry(tag).or_default().push(endpoint); + } + let acl_endpoints: Vec<&Value> = by_tag.into_values().flatten().collect(); + if acl_endpoints.is_empty() { + return None; + } + // Named schema conditions need the full TypeScript import merger for uncommon configurations. + if acl_endpoints + .iter() + .flat_map(|endpoint| acl_conditions(endpoint)) + .any(|condition| condition.get("zodSchemaName").is_some()) + { + return None; + } + let additional = acl_endpoints.iter().any(|endpoint| { + endpoint + .pointer("/acl/0/conditions") + .and_then(Value::as_object) + .is_some_and(|conditions| !conditions.is_empty()) + }); + let mut actions: IndexMap<&str, IndexSet> = IndexMap::default(); + for endpoint in acl_endpoints { + let Some(action) = endpoint.pointer("/acl/0/action").and_then(Value::as_str) else { + continue; + }; + let subject = endpoint + .pointer("/acl/0/subject") + .and_then(Value::as_str) + .unwrap_or_default(); + let subjects = actions.entry(action).or_default(); + subjects.insert(format!("\"{subject}\"")); + let mut conditions: Vec<&Value> = acl_conditions(endpoint).collect(); + conditions.sort_by_key(|condition| { + condition + .get("name") + .and_then(Value::as_str) + .unwrap_or_default() + }); + if !conditions.is_empty() { + let fields = conditions + .iter() + .map(|condition| { + let name = condition + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(); + let optional = if condition + .get("required") + .and_then(Value::as_bool) + .unwrap_or(false) + { + "" + } else { + "?" + }; + let ty = condition + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + format!("{name}{optional}: {ty},") + }) + .collect::>() + .join(" "); + subjects.insert(format!("ForcedSubject<\"{subject}\"> & {{ {fields} }}")); + } + } + let forced = if additional { ", ForcedSubject" } else { "" }; + let mut lines = vec![ + format!("import type {{ PureAbility, AbilityTuple{forced} }} from \"@casl/ability\";"), + String::new(), + "export type AppAbilities = ".into(), + ]; + for (action, subjects) in actions { + lines.push(format!( + "| AbilityTuple<\"{action}\", {}>", + subjects.into_iter().collect::>().join(" | ") + )); + } + lines.push(String::new()); + lines.push("export type AppAbility = PureAbility;".into()); + Some(lines.join("\n")) +} + +fn render_domain_errors(endpoints: &[Value]) -> Option { + let mut domains: HashMap)>> = HashMap::default(); + for endpoint in endpoints { + for error in endpoint + .get("errors") + .and_then(Value::as_array) + .into_iter() + .flatten() + { + let Some(domain_error) = error.get("domainError") else { + continue; + }; + let Some(domain) = domain_error.get("domain").and_then(Value::as_str) else { + continue; + }; + let Some(code) = domain_error.get("code") else { + continue; + }; + let key = domain_error + .get("name") + .and_then(Value::as_str) + .map(str::to_string) + .unwrap_or_else(|| { + code.as_str() + .map(str::to_string) + .unwrap_or_else(|| format!("ERROR_{}", code)) + }); + if !valid_property_name(&key) { + return None; + } + let entries = domains.entry(domain.to_string()).or_default(); + if !entries.iter().any(|(existing, _, _)| existing == code) { + entries.push(( + code.clone(), + key, + error + .get("description") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + .map(str::to_string), + )); + } + } + } + if domains.is_empty() { + return None; + } + let mut domains: Vec<_> = domains.into_iter().collect(); + domains.sort_by(|left, right| left.0.cmp(&right.0)); + let mut blocks = Vec::new(); + for (domain, mut entries) in domains { + entries.sort_by(|left, right| match (left.0.as_f64(), right.0.as_f64()) { + (Some(left), Some(right)) => left.total_cmp(&right), + _ => value_text(&left.0).cmp(&value_text(&right.0)), + }); + let name = domain.split(['-', '_']).map(capitalize).collect::(); + let body = entries + .into_iter() + .map(|(code, key, description)| { + let prefix = description + .map(|description| format!(" /** {description} */\n ")) + .unwrap_or_else(|| " ".into()); + format!("{prefix}{key}: {}", value_text(&code)) + }) + .collect::>() + .join(",\n"); + blocks.push(format!( + "export const {name}DomainErrors = {{\n{body},\n}} as const;" + )); + blocks.push(format!("export type {name}DomainErrorCode = (typeof {name}DomainErrors)[keyof typeof {name}DomainErrors];")); + } + Some(format!("{}\n", blocks.join("\n\n"))) +} + +fn value_text(value: &Value) -> String { + value + .as_str() + .map(|value| serde_json::to_string(value).unwrap()) + .unwrap_or_else(|| value.to_string()) +} + +fn render_acl_module( + tag: &str, + endpoints: &[&Value], + schema_owners: &Map, + options: &GenerateOptions, +) -> String { + let additional = endpoints.iter().any(|endpoint| { + endpoint + .pointer("/acl/0/conditions") + .and_then(Value::as_object) + .is_some_and(|conditions| !conditions.is_empty()) + }); + let model_suffix = options + .configs + .get("models") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Models"); + let acl_suffix = options + .configs + .get("acl") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Acl"); + let models_namespace = format!("{}{}", capitalize(tag), model_suffix); + let has_models = endpoints + .iter() + .flat_map(|endpoint| acl_conditions(endpoint)) + .any(|condition| { + condition + .get("zodSchemaName") + .and_then(Value::as_str) + .is_some_and(is_named_schema) + }); + let mut lines = Vec::new(); + lines.push(if additional { + "import { ForcedSubject, subject, type AbilityTuple } from \"@casl/ability\";".into() + } else { + "import { type AbilityTuple } from \"@casl/ability\";".into() + }); + if has_models { + if options.ts_namespaces { + lines.push(format!( + "import type {{ {models_namespace} }} from \"./{}.models\";", + decapitalize(tag) + )); + } else { + let mut imports: IndexMap> = IndexMap::default(); + for condition in endpoints + .iter() + .flat_map(|endpoint| acl_conditions(endpoint)) + { + if let Some(schema) = condition.get("zodSchemaName").and_then(Value::as_str) { + let name = remove_suffix(schema, &options.schema_suffix); + let owner = if options.models_in_modules { + tag + } else { + schema_owners + .get(schema) + .and_then(Value::as_str) + .unwrap_or(tag) + }; + let names = imports.entry(owner.to_string()).or_default(); + if !names.contains(&name) { + names.push(name); + } + } + } + for (owner, names) in imports { + let owner_tag = decapitalize(&owner); + let path = if owner == tag { + format!("./{owner_tag}.models") + } else { + format!("{}{owner_tag}/{owner_tag}.models", import_root(options)) + }; + lines.push(format!( + "import type {{ {} }} from \"{path}\";", + names.join(", ") + )); + } + } + } + lines.push(String::new()); + if options.ts_namespaces { + lines.push(format!( + "export namespace {}{} {{", + capitalize(tag), + acl_suffix + )); + } + for endpoint in endpoints { + render_ability( + &mut lines, + endpoint, + if options.ts_namespaces { + &models_namespace + } else { + "" + }, + options, + ); + lines.push(String::new()); + } + if options.ts_namespaces { + lines.push("}".into()); + } + format!("{}\n", lines.join("\n").trim_end()) +} + +fn acl_conditions(endpoint: &Value) -> impl Iterator { + endpoint + .pointer("/acl/0/conditionsTypes") + .and_then(Value::as_array) + .into_iter() + .flatten() +} + +fn condition_type(condition: &Value, models_namespace: &str, options: &GenerateOptions) -> String { + if let Some(schema) = condition.get("zodSchemaName").and_then(Value::as_str) { + let name = remove_suffix(schema, &options.schema_suffix); + if models_namespace.is_empty() { + name + } else { + format!("{models_namespace}.{name}") + } + } else { + condition + .get("type") + .and_then(Value::as_str) + .unwrap_or_default() + .to_string() + } +} + +fn render_ability( + lines: &mut Vec, + endpoint: &Value, + models_namespace: &str, + options: &GenerateOptions, +) { + let (_, cap) = query_names(endpoint); + let ability_name = format!("canUse{cap}"); + let mut conditions: Vec<&Value> = acl_conditions(endpoint).collect(); + conditions.sort_by(|left, right| { + left.get("name") + .and_then(Value::as_str) + .unwrap_or_default() + .cmp( + right + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(), + ) + }); + let has_conditions = !conditions.is_empty(); + let method = endpoint + .get("method") + .and_then(Value::as_str) + .unwrap_or_default(); + let query_name = format!("use{cap}"); + let ability_query = if method == "get" { + if endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) + { + format!("`{query_name}` query or `{query_name}Mutation` mutation") + } else { + format!("`{query_name}` query") + } + } else { + format!("`{query_name}` mutation") + }; + lines.push("/**".into()); + lines.push(format!( + " * Use for {ability_query} ability. {}", + if has_conditions { + "For global ability, omit the object parameter." + } else { + "" + } + )); + if let Some(description) = endpoint + .pointer("/acl/0/description") + .and_then(Value::as_str) + { + lines.push(format!(" * @description {description}")); + } + for condition in &conditions { + let name = condition + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(); + let info = condition + .get("info") + .and_then(Value::as_str) + .unwrap_or_default(); + lines.push(format!( + " * @param {{ {} }} object.{name} {name} from {info}", + condition_type(condition, models_namespace, options) + )); + } + lines.push(format!(" * @returns {{ AbilityTuple }} An ability tuple indicating the user's ability to use {ability_query}")); + lines.push(" */".into()); + lines.push(format!("export const {ability_name} = (")); + if has_conditions { + let object = conditions + .iter() + .map(|condition| { + let name = condition + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(); + let optional = if condition + .get("required") + .and_then(Value::as_bool) + .unwrap_or(false) + { + "" + } else { + "?" + }; + format!( + "{name}{optional}: {}, ", + condition_type(condition, models_namespace, options) + ) + }) + .collect::(); + lines.push(format!(" object?: {{ {object} }} ")); + } + lines.push(") => [".into()); + let action = endpoint + .pointer("/acl/0/action") + .and_then(Value::as_str) + .unwrap_or_default(); + let subject = endpoint + .pointer("/acl/0/subject") + .and_then(Value::as_str) + .unwrap_or_default(); + lines.push(format!(" \"{action}\",")); + lines.push(if has_conditions { + format!(" object ? subject(\"{subject}\", object) : \"{subject}\"") + } else { + format!(" \"{subject}\"") + }); + let subject_types = if has_conditions { + let object = conditions + .iter() + .map(|condition| { + let name = condition + .get("name") + .and_then(Value::as_str) + .unwrap_or_default(); + let optional = if condition + .get("required") + .and_then(Value::as_bool) + .unwrap_or(false) + { + "" + } else { + "?" + }; + format!( + "{name}{optional}: {},", + condition_type(condition, models_namespace, options) + ) + }) + .collect::>() + .join(" "); + format!("\"{subject}\" | ForcedSubject<\"{subject}\"> & {{ {object} }}") + } else { + format!("\"{subject}\"") + }; + lines.push(format!("] as AbilityTuple<\"{action}\", {subject_types}>;")); +} + +fn render_query_module( + tag: &str, + endpoints: &[&Value], + schema_owners: &Map, + options: &GenerateOptions, +) -> String { + let model_suffix = options + .configs + .get("models") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Models"); + let api_suffix = options + .configs + .get("endpoints") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Api"); + let query_suffix = options + .configs + .get("queries") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Queries"); + let acl_suffix = options + .configs + .get("acl") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Acl"); + let models_namespace = format!("{}{}", capitalize(tag), model_suffix); + let api_namespace = format!("{}{}", capitalize(tag), api_suffix); + let query_namespace = format!("{}{}", capitalize(tag), query_suffix); + let acl_namespace = format!("{}{}", capitalize(tag), acl_suffix); + let infinite: Vec<&Value> = endpoints + .iter() + .copied() + .filter(|endpoint| is_infinite(endpoint, options)) + .collect(); + let queries: Vec<&Value> = endpoints + .iter() + .copied() + .filter(|endpoint| endpoint.get("method").and_then(Value::as_str) == Some("get")) + .collect(); + let mutations: Vec<&Value> = endpoints + .iter() + .copied() + .filter(|endpoint| is_mutation_endpoint(endpoint)) + .collect(); + let has_media_upload = endpoints.iter().any(|endpoint| { + endpoint + .get("mediaUpload") + .and_then(Value::as_bool) + .unwrap_or(false) + }); + let has_media_download = endpoints.iter().any(|endpoint| { + endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) + }); + let acl_endpoints: Vec<&Value> = endpoints + .iter() + .copied() + .filter(|endpoint| { + endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()) + }) + .collect(); + let mut lines = Vec::new(); + if uses_native_rest_client(options) && has_media_upload { + lines.push(format!( + "import {{ AppRestClient }} from \"{}\";", + options.rest_client_import_path + )); + } + if uses_native_rest_client(options) { + let mut transport_types = Vec::new(); + if !queries.is_empty() || options.axios_request_config { + transport_types.push("type TransportRequestConfig"); + } + if has_media_download { + transport_types.push("type TransportResponse"); + } + if !transport_types.is_empty() { + lines.push(format!( + "import {{ {} }} from \"@povio/openapi-codegen-cli/rest\";", + transport_types.join(", ") + )); + } + } else if !queries.is_empty() || options.axios_request_config || has_media_upload { + lines.push(if has_media_upload { + "import axios, { type AxiosRequestConfig } from \"axios\";".to_string() + } else { + "import { type AxiosRequestConfig } from \"axios\";".to_string() + }); + } + let mut query_imports = Vec::new(); + if options.prefetch_queries && !queries.is_empty() { + query_imports.push("QueryClient"); + } + if !queries.is_empty() { + query_imports.push("useQuery"); + } + if options.infinite_queries && !infinite.is_empty() { + query_imports.push("useInfiniteQuery"); + } + if !mutations.is_empty() { + query_imports.push("useMutation"); + } + lines.push(format!( + "import {{ {} }} from \"@tanstack/react-query\";", + query_imports.join(", ") + )); + if options.mutation_effects { + lines.push(format!( + "import {{ QueryModule }} from \"{}queryModules\";", + import_root(options) + )); + } + if options.mutation_effects && !mutations.is_empty() { + lines.push(format!( + "import {{ useMutationEffects, type MutationEffectsOptions }} from \"{}\";", + options.mutation_effects_import_path + )); + } + if options.check_acl && !acl_endpoints.is_empty() { + lines.push(format!( + "import {{ useAclCheck }} from \"{}\";", + options.acl_check_import_path + )); + if options.ts_namespaces { + lines.push(format!( + "import {{ {acl_namespace} }} from \"./{}.acl\";", + decapitalize(tag) + )); + } else { + let abilities = acl_endpoints + .iter() + .map(|endpoint| format!("canUse{}", query_names(endpoint).1)) + .collect::>(); + lines.push(format!( + "import {{ {} }} from \"./{}.acl\";", + abilities.join(", "), + decapitalize(tag) + )); + } + } + let mut query_types = Vec::new(); + if !queries.is_empty() || (options.mutation_default_on_error && !mutations.is_empty()) { + query_types.push("OpenApiQueryConfig".to_string()); + } + if !queries.is_empty() { + query_types.push("type AppQueryOptions".to_string()); + } + if options.infinite_queries && !infinite.is_empty() { + query_types.push("type AppInfiniteQueryOptions".into()); + } + if !mutations.is_empty() { + query_types.push("type AppMutationOptions".into()); + } + lines.push(format!( + "import {{ {} }} from \"{}\";", + query_types.join(", "), + options.query_types_import_path + )); + if endpoints + .iter() + .flat_map(|endpoint| { + endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + }) + .filter_map(|param| param.get("zodSchema").and_then(Value::as_str)) + .any(is_named_schema) + { + if options.ts_namespaces { + lines.push(format!( + "import {{ {models_namespace} }} from \"./{}.models\";", + decapitalize(tag) + )); + } else { + let mut imports: IndexMap> = IndexMap::default(); + for endpoint in endpoints { + for schema in endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|parameter| parameter.get("zodSchema").and_then(Value::as_str)) + .filter(|schema| is_named_schema(schema)) + { + let name = remove_suffix(&schema, &options.schema_suffix); + let owner = if options.models_in_modules { + tag + } else { + schema_owners + .get(schema) + .and_then(Value::as_str) + .unwrap_or(tag) + }; + let names = imports.entry(owner.to_string()).or_default(); + if !names.contains(&name) { + names.push(name); + } + } + } + for (owner, names) in imports { + let owner_tag = decapitalize(&owner); + let path = if owner == tag { + format!("./{owner_tag}.models") + } else { + format!("{}{owner_tag}/{owner_tag}.models", import_root(options)) + }; + lines.push(format!( + "import type {{ {} }} from \"{path}\";", + names.join(", ") + )); + } + } + } + if options.ts_namespaces { + lines.push(format!( + "import {{ {api_namespace} }} from \"./{}.api\";", + decapitalize(tag) + )); + } else { + let operations = endpoints + .iter() + .map(|endpoint| { + endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ) + }) + .collect::>(); + lines.push(format!( + "import {{ {} }} from \"./{}.api\";", + operations.join(", "), + decapitalize(tag) + )); + } + lines.push(String::new()); + if options.ts_namespaces { + lines.push(format!("export namespace {query_namespace} {{")); + } + lines.push(format!( + "export const moduleName = {};", + if options.mutation_effects { + format!("QueryModule.{tag}") + } else { + format!("\"{query_namespace}\"") + } + )); + lines.push(String::new()); + if !queries.is_empty() { + render_query_keys(&mut lines, &queries, options, &models_namespace); + lines.push(String::new()); + } else { + // The TypeScript renderer appends both the empty renderQueryKeys result and + // its separator. Preserve those raw blank lines for byte parity. + lines.push(String::new()); + lines.push(String::new()); + } + for endpoint in endpoints { + if endpoint.get("method").and_then(Value::as_str) == Some("get") { + render_query_options_native( + &mut lines, + endpoint, + options, + &models_namespace, + &api_namespace, + false, + ); + lines.push(String::new()); + render_query_hook_native( + &mut lines, + endpoint, + options, + &models_namespace, + &api_namespace, + &acl_namespace, + false, + ); + lines.push(String::new()); + if options.prefetch_queries { + render_prefetch_native(&mut lines, endpoint, options, &models_namespace, false); + lines.push(String::new()); + } + } + if is_mutation_endpoint(endpoint) { + render_mutation_native( + &mut lines, + endpoint, + endpoints, + options, + &models_namespace, + &api_namespace, + &acl_namespace, + ); + lines.push(String::new()); + } + if endpoint.get("method").and_then(Value::as_str) == Some("get") { + if options.infinite_queries && is_infinite(endpoint, options) { + render_query_options_native( + &mut lines, + endpoint, + options, + &models_namespace, + &api_namespace, + true, + ); + lines.push(String::new()); + render_query_hook_native( + &mut lines, + endpoint, + options, + &models_namespace, + &api_namespace, + &acl_namespace, + true, + ); + lines.push(String::new()); + if options.prefetch_queries { + render_prefetch_native(&mut lines, endpoint, options, &models_namespace, true); + lines.push(String::new()); + } + } + } + } + if options.ts_namespaces { + lines.push("}".into()); + } + let mut content = format!("{}\n", lines.join("\n").trim_end()); + if !options.ts_namespaces { + content = content.replace(&format!("{models_namespace}."), ""); + content = content.replace(&format!("{}Models.", capitalize(&options.default_tag)), ""); + content = content.replace(&format!("{api_namespace}."), ""); + content = content.replace(&format!("{acl_namespace}."), ""); + } + content +} + +fn import_root(options: &GenerateOptions) -> String { + match options.import_path.as_str() { + "relative" => "../".into(), + "absolute" => format!("{}/", options.output.trim_end_matches('/')), + _ => format!("{}/", options.ts_path.trim_end_matches('/')), + } +} + +fn is_infinite(endpoint: &Value, options: &GenerateOptions) -> bool { + let page = &options.infinite_query_param_names.page; + endpoint + .get("parameters") + .and_then(Value::as_array) + .is_some_and(|parameters| { + parameters.iter().any(|param| { + param.get("name").and_then(Value::as_str) == Some(page) + && param.get("type").and_then(Value::as_str) == Some("Query") + }) + }) +} + +fn render_query_keys( + lines: &mut Vec, + endpoints: &[&Value], + options: &GenerateOptions, + models_namespace: &str, +) { + lines.push("export const keys = {".into()); + lines.push(" all: [moduleName] as const,".into()); + for endpoint in endpoints { + let name = endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let params = query_function_params(endpoint, options, models_namespace, false, true); + lines.push(format!( + " {name}: ({}) => [...keys.all, \"{}\", {}] as const,", + render_param_list(¶ms), + endpoint + .get("path") + .and_then(Value::as_str) + .unwrap_or_default(), + render_arg_list(¶ms) + )); + if options.infinite_queries && is_infinite(endpoint, options) { + let params = query_function_params(endpoint, options, models_namespace, true, true); + lines.push(format!( + " {name}Infinite: ({}) => [...keys.all, \"{}\", \"infinite\", {}] as const,", + render_param_list(¶ms), + endpoint + .get("path") + .and_then(Value::as_str) + .unwrap_or_default(), + render_arg_list(¶ms) + )); + } + } + lines.push("};".into()); +} + +fn query_function_params<'a>( + endpoint: &'a Value, + options: &GenerateOptions, + models_namespace: &str, + exclude_page: bool, + path_required_only: bool, +) -> Vec> { + function_params(endpoint, options, models_namespace) + .into_iter() + .filter_map(|mut param| { + if exclude_page && param.name == options.infinite_query_param_names.page { + return None; + } + if path_required_only && param.kind != "Path" { + param.required = false; + } + Some(param) + }) + .collect() +} + +fn render_param_list(params: &[FunctionParam<'_>]) -> String { + params + .iter() + .map(|param| { + format!( + "{}{}: {}", + param.name, + if param.required { "" } else { "?" }, + param.ty + ) + }) + .collect::>() + .join(", ") +} +fn render_arg_list(params: &[FunctionParam<'_>]) -> String { + params + .iter() + .map(|param| param.name.as_str()) + .collect::>() + .join(", ") +} + +fn query_names(endpoint: &Value) -> (String, String) { + let operation = snake_to_camel( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let cap = capitalize(&operation); + (operation, cap) +} + +fn render_query_options_native( + lines: &mut Vec, + endpoint: &Value, + options: &GenerateOptions, + models_namespace: &str, + api_namespace: &str, + infinite: bool, +) { + let (operation, _) = query_names(endpoint); + let api_operation = endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let params = query_function_params(endpoint, options, models_namespace, infinite, false); + let args = render_arg_list(¶ms); + let object = if params.is_empty() { + String::new() + } else { + format!("{{ {args} }}: {{ {} }}", render_param_list(¶ms)) + }; + let options_name = format!( + "{operation}{}QueryOptions", + if infinite { "Infinite" } else { "" } + ); + lines.push(format!("const {options_name} = ({object}{}config?: {} & {{ allowInvalidResponseData?: boolean }}) => ({{", if params.is_empty() { "" } else { ", " }, request_config_type(options))); + lines.push(format!( + " queryKey: keys.{api_operation}{}({args}),", + if infinite { "Infinite" } else { "" } + )); + let endpoint_args = if infinite { + let full = query_function_params(endpoint, options, models_namespace, false, false); + full.iter() + .map(|param| { + if param.name == options.infinite_query_param_names.page { + "pageParam".to_string() + } else { + param.name.clone() + } + }) + .collect::>() + .join(", ") + } else { + args.clone() + }; + if infinite { + lines.push(format!(" queryFn: ({{ pageParam, signal }}: {{ pageParam: number; signal: AbortSignal }}) => {api_namespace}.{api_operation}({endpoint_args}, {{ ...config, signal }}),")); + lines.push(" initialPageParam: 1,".into()); + let names = &options.infinite_query_response_param_names; + lines.push(format!(" getNextPageParam: ({{ {}, {}, {}: limitParam }}: Awaited> & Partial>) => {{", names.page, names.total_items, names.limit, names.page, names.total_items, names.limit)); + lines.push(format!(" const pageParam = {} ?? 1;", names.page)); + lines.push(" const pageSize = limitParam ?? 0;".into()); + lines.push(format!( + " return pageParam * pageSize < ({} ?? 0) ? pageParam + 1 : null;", + names.total_items + )); + lines.push(" },".into()); + } else { + lines.push(format!(" queryFn: ({{ signal }}: {{ signal: AbortSignal }}) => {api_namespace}.{api_operation}({args}{}{{ ...config, signal }}),", if args.is_empty() { "" } else { ", " })); + } + lines.push("});".into()); +} + +fn render_query_hook_native( + lines: &mut Vec, + endpoint: &Value, + options: &GenerateOptions, + models_namespace: &str, + api_namespace: &str, + acl_namespace: &str, + infinite: bool, +) { + let (operation, cap) = query_names(endpoint); + let api_operation = endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let params = query_function_params(endpoint, options, models_namespace, infinite, false); + let args = render_arg_list(¶ms); + render_query_docs(lines, endpoint, options, models_namespace, infinite); + let hook = format!("use{cap}{}", if infinite { "Infinite" } else { "" }); + let option_type = if infinite { + "AppInfiniteQueryOptions" + } else { + "AppQueryOptions" + }; + lines.push(format!("export const {hook} = ({}options?: {option_type}) => {{", if params.is_empty() { "".into() } else { format!("{{ {args} }}: {{ {} }}, ", render_param_list(¶ms)) })); + lines.push(" const queryConfig = OpenApiQueryConfig.useConfig();".into()); + let has_acl = options.check_acl + && endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()); + if has_acl { + lines.push(" const { checkAcl } = useAclCheck();".into()); + } + lines.push(if infinite { String::new() } else { " ".into() }); + lines.push(format!( + " return {}({{", + if infinite { + "useInfiniteQuery" + } else { + "useQuery" + } + )); + let options_name = format!( + "{operation}{}QueryOptions", + if infinite { "Infinite" } else { "" } + ); + let call_args = format!( + "{}{{ allowInvalidResponseData: queryConfig.allowInvalidResponseData }}", + if params.is_empty() { + "".into() + } else { + format!("{{ {args} }}, ") + } + ); + lines.push(format!(" ...{options_name}({call_args}),")); + if has_acl { + lines.push(format!( + " queryFn: async ({{ {}signal }}: {{ {}signal: AbortSignal }}) => {{", + if infinite { "pageParam, " } else { "" }, + if infinite { "pageParam: number; " } else { "" } + )); + lines.push(format!( + "{}checkAcl({acl_namespace}.canUse{cap}({}));", + if infinite { " " } else { " " }, + acl_args(endpoint) + )); + lines.push(format!( + " return {options_name}({call_args}).queryFn({{ {}signal }});", + if infinite { "pageParam, " } else { "" } + )); + lines.push(" },".into()); + } + lines.push(" ...options,".into()); + lines.push(" });".into()); + lines.push("};".into()); +} + +fn acl_args(endpoint: &Value) -> String { + let Some(conditions) = endpoint + .pointer("/acl/0/conditionsTypes") + .and_then(Value::as_array) + else { + return String::new(); + }; + if conditions.is_empty() { + return String::new(); + } + let mut names: Vec = conditions + .iter() + .filter_map(|condition| condition.get("name").and_then(Value::as_str)) + .map(variable_name) + .collect(); + names.sort(); + let params: HashSet = endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|param| param.get("name").and_then(Value::as_str)) + .map(variable_name) + .collect(); + if names.iter().all(|name| params.contains(name)) { + format!("{{ {} }} ", names.join(", ")) + } else { + String::new() + } +} + +fn render_prefetch_native( + lines: &mut Vec, + endpoint: &Value, + options: &GenerateOptions, + models_namespace: &str, + infinite: bool, +) { + let (operation, cap) = query_names(endpoint); + let params = query_function_params(endpoint, options, models_namespace, infinite, false); + let args = render_arg_list(¶ms); + let function = format!("prefetch{cap}{}", if infinite { "Infinite" } else { "" }); + let factory = format!( + "{operation}{}QueryOptions", + if infinite { "Infinite" } else { "" } + ); + let method = if infinite { + "prefetchInfiniteQuery" + } else { + "prefetchQuery" + }; + let omitted = if infinite { + "\"queryKey\" | \"queryFn\" | \"initialPageParam\" | \"getNextPageParam\"" + } else { + "\"queryKey\" | \"queryFn\"" + }; + let has_request_config = options.axios_request_config + || endpoint.get("method").and_then(Value::as_str) == Some("get"); + let request_config_param = if has_request_config { + format!("config?: {}, ", request_config_type(options)) + } else { + String::new() + }; + lines.push(format!("export const {function} = (queryClient: QueryClient, {}options?: Omit[0], {omitted}>, {request_config_param}throwOnError = false) => {{", if params.is_empty() { "".into() } else { format!("{{ {args} }}: {{ {} }}, ", render_param_list(¶ms)) })); + let request_config_arg = if has_request_config { + if params.is_empty() { + "config" + } else { + ", config" + } + } else { + "" + }; + lines.push(format!( + " const queryOptions = {{ ...{factory}({}{}), ...{} }};", + if params.is_empty() { + "".into() + } else { + format!("{{ {args} }}") + }, + request_config_arg, + if infinite { + "(options as {})" + } else { + "options" + } + )); + lines.push(format!( + " return throwOnError ? queryClient.{}(queryOptions) : queryClient.{method}(queryOptions);", + if infinite { "fetchInfiniteQuery" } else { "fetchQuery" } + )); + lines.push("};".into()); +} + +fn render_query_docs( + lines: &mut Vec, + endpoint: &Value, + options: &GenerateOptions, + models_namespace: &str, + infinite: bool, +) { + let (_, cap) = query_names(endpoint); + lines.push("/** ".into()); + lines.push(if infinite { + format!(" * Infinite query `use{cap}Infinite") + } else { + format!( + " * Query `use{cap}`{}", + if endpoint + .get("summary") + .and_then(Value::as_str) + .is_some_and(|value| !value.is_empty()) + && endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) + { + " - recommended when file should be cached" + } else { + "" + } + ) + }); + if let Some(summary) = endpoint + .get("summary") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + { + lines.push(format!(" * @summary {}", summary.replace('\n', "\n *"))); + } + if let Some(description) = endpoint + .get("description") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + { + lines.push(format!( + " * @description {}", + description.replace('\n', "\n *") + )); + } + if endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()) + { + lines.push(format!(" * @permission Requires `canUse{cap}` ability ")); + } + for param in function_params(endpoint, options, models_namespace) { + lines.push(format!( + " * @param {{ {} }} {} {} parameter{}", + param.ty, + param.name, + param.kind, + parameter_description(param.source) + )); + } + lines.push(if infinite { + " * @param { AppInfiniteQueryOptions } options Infinite query options".into() + } else { + " * @param { AppQueryOptions } options Query options".into() + }); + let response = endpoint + .get("response") + .and_then(Value::as_str) + .unwrap_or("z.void()"); + let result = schema_type(response, models_namespace, options); + let result = if !infinite + && endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) + { + format!("{}<{result}>", response_type(options)) + } else { + result + }; + lines.push(format!( + " * @returns {{ {}<{}> }} {}", + if infinite { + "UseInfiniteQueryResult" + } else { + "UseQueryResult" + }, + result, + endpoint + .get("responseDescription") + .and_then(Value::as_str) + .unwrap_or_default() + )); + let statuses = endpoint + .get("responseStatusCodes") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .collect::>() + .join(", "); + lines.push(format!(" * @statusCodes [{statuses}]")); + lines.push(" */".into()); +} + +fn schema_type(schema: &str, models_namespace: &str, options: &GenerateOptions) -> String { + if schema == "z.void()" { + "void".into() + } else if is_named_schema(schema) { + format!( + "{models_namespace}.{}", + remove_suffix(schema, &options.schema_suffix) + ) + } else { + schema.to_string() + } +} + +fn parameter_description(parameter: &Value) -> String { + let mut parts = Vec::new(); + if let Some(description) = parameter + .get("parameterObject") + .or_else(|| parameter.get("bodyObject")) + .and_then(|object| object.get("description")) + .and_then(Value::as_str) + { + parts.push(description.to_string()); + } + let schema = parameter + .pointer("/parameterObject/schema") + .filter(|schema| schema.get("$ref").is_none()); + if let Some(schema) = schema { + if let Some(minimum) = schema.get("minimum") { + parts.push(format!("Minimum: `{minimum}`")); + } + if let Some(maximum) = schema.get("maximum") { + parts.push(format!("Maximum: `{maximum}`")); + } + if let Some(default) = schema.get("default") { + parts.push(format!("Default: `{}`", display_json(default))); + } + if let Some(example) = schema.get("example") { + parts.push(format!("Example: `{}`", display_json(example))); + } + } + if parts.is_empty() { + String::new() + } else { + format!(". {}", parts.join(". ")) + } +} + +fn display_json(value: &Value) -> String { + value + .as_str() + .map(str::to_string) + .unwrap_or_else(|| value.to_string()) +} + +fn is_mutation_endpoint(endpoint: &Value) -> bool { + endpoint.get("method").and_then(Value::as_str) != Some("get") + || endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) +} + +fn mutation_scope_enabled(endpoint: &Value, tag: &str, options: &GenerateOptions) -> bool { + if endpoint.get("method").and_then(Value::as_str) == Some("post") + || !is_mutation_endpoint(endpoint) + { + return false; + } + let operation = endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let key = format!("{tag}/{operation}"); + match &options.mutation_scope { + Value::Bool(value) => *value, + Value::Object(object) => { + if let Some(include) = object.get("include").and_then(Value::as_array) { + include + .iter() + .filter_map(Value::as_str) + .any(|value| value == operation || value == key) + } else if let Some(exclude) = object.get("exclude").and_then(Value::as_array) { + !exclude + .iter() + .filter_map(Value::as_str) + .any(|value| value == operation || value == key) + } else { + false + } + } + _ => false, + } +} + +fn render_mutation_native( + lines: &mut Vec, + endpoint: &Value, + all_endpoints: &[&Value], + options: &GenerateOptions, + models_namespace: &str, + api_namespace: &str, + acl_namespace: &str, +) { + let (_, cap) = query_names(endpoint); + let api_operation = endpoint_name( + endpoint + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let mutation_cap = if endpoint.get("method").and_then(Value::as_str) == Some("get") { + format!("{cap}Mutation") + } else { + cap.clone() + }; + let params = query_function_params(endpoint, options, models_namespace, false, false); + let args = render_arg_list(¶ms); + let media_upload = endpoint + .get("mediaUpload") + .and_then(Value::as_bool) + .unwrap_or(false); + let scope_params: Vec<&FunctionParam<'_>> = if mutation_scope_enabled( + endpoint, + endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag), + options, + ) { + params.iter().filter(|param| param.kind == "Path").collect() + } else { + Vec::new() + }; + let is_scoped = !scope_params.is_empty(); + let effective_params: Vec> = if is_scoped { + params + .iter() + .filter(|param| param.kind != "Path") + .cloned() + .collect() + } else { + params.clone() + }; + let effective_args = render_arg_list(&effective_params); + render_mutation_docs(lines, endpoint, options, models_namespace); + let mut variables = render_param_list(&effective_params); + if media_upload { + if !variables.is_empty() { + variables.push_str(", "); + } + variables.push_str("file?: File; abortController?: AbortController; onUploadProgress?: (progress: { loaded: number; total: number }) => void"); + } + let path_arg = if is_scoped { + let names = scope_params + .iter() + .map(|param| param.name.as_str()) + .collect::>() + .join(", "); + let common_namespace = if options.models_in_modules { + models_namespace.to_string() + } else { + format!( + "{}{}", + capitalize(&options.default_tag), + options + .configs + .get("models") + .map(|config| config.namespace_suffix.as_str()) + .unwrap_or("Models") + ) + }; + let types = scope_params + .iter() + .map(|param| { + let schema = param + .source + .get("zodSchema") + .and_then(Value::as_str) + .unwrap_or_default(); + let ty = if is_named_schema(schema) { + format!( + "{common_namespace}.{}", + remove_suffix(schema, &options.schema_suffix) + ) + } else { + param.ty.clone() + }; + format!("{}: {ty}", param.name) + }) + .collect::>() + .join("; "); + format!("{{ {names} }}: {{ {types} }}, ") + } else { + String::new() + }; + let variables_arg = if is_scoped && effective_params.is_empty() { + String::new() + } else { + format!(", {{ {variables} }}") + }; + lines.push(format!( + "export const use{mutation_cap} = ({path_arg}options?: AppMutationOptions{}) => {{", + if options.mutation_effects { " & MutationEffectsOptions" } else { "" } + )); + if options.mutation_default_on_error { + lines.push(" const queryConfig = OpenApiQueryConfig.useConfig();".into()); + } + let has_acl = options.check_acl + && endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()); + if has_acl { + lines.push(" const { checkAcl } = useAclCheck();".into()); + } + if options.mutation_effects { + let tag = endpoint + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + lines.push(format!(" const {{ runMutationEffects }} = useMutationEffects({{ currentModule: moduleName }});")); + } + lines.push(String::new()); + lines.push(" return useMutation({".into()); + let has_body = has_acl || media_upload; + let mut mutation_args = effective_args.clone(); + if media_upload { + if !mutation_args.is_empty() { + mutation_args.push_str(", "); + } + mutation_args.push_str("file, abortController, onUploadProgress"); + } + lines.push(format!( + " mutationFn: {}({}) => {}", + if media_upload { "async " } else { "" }, + if mutation_args.is_empty() { + "".into() + } else { + format!("{{ {mutation_args} }}") + }, + if has_body { "{ " } else { "" } + )); + if has_acl { + lines.push(format!( + " checkAcl({acl_namespace}.canUse{cap}({}));", + acl_args(endpoint) + )); + } + if media_upload { + render_media_upload_body( + lines, + api_namespace, + &api_operation, + &args, + options.axios_request_config, + options, + ); + lines.push(" },".into()); + } else if has_acl { + lines.push(format!( + " return {api_namespace}.{api_operation}({args})" + )); + lines.push(" },".into()); + } else { + lines.push(format!(" {api_namespace}.{api_operation}({args})")); + lines.push(",".into()); + } + if is_scoped { + let interpolation = scope_params + .iter() + .map(|param| format!(":${{{}}}", param.name)) + .collect::>() + .join(""); + lines.push(format!( + " scope: {{ id: `{api_operation}{interpolation}` }}," + )); + } + lines.push(" ...options,".into()); + if options.mutation_default_on_error { + lines.push(" onError: options?.onError ?? queryConfig.onError,".into()); + } + if options.mutation_effects { + let updates = update_query_endpoints(endpoint, all_endpoints); + lines + .push(" onSuccess: async (resData, variables, onMutateResult, context) => {".into()); + if updates.is_empty() { + lines.push(" await runMutationEffects(resData, variables, options);".into()); + } else { + let destructured = + destructured_update_variables(endpoint, &updates, options, models_namespace); + let scoped_names: HashSet<&str> = scope_params + .iter() + .map(|param| param.name.as_str()) + .collect(); + let destructured: Vec = destructured + .into_iter() + .filter(|name| !scoped_names.contains(name.as_str())) + .collect(); + if !destructured.is_empty() { + lines.push(format!( + " const {{ {} }} = variables;", + destructured.join(", ") + )); + } + let keys = updates + .iter() + .map(|query| { + let name = endpoint_name( + query + .get("operationName") + .and_then(Value::as_str) + .unwrap_or_default(), + ); + let required: Vec> = + query_function_params(query, options, models_namespace, false, false) + .into_iter() + .filter(|param| param.required) + .collect(); + format!("keys.{name}({})", render_arg_list(&required)) + }) + .collect::>() + .join(", "); + lines.push(format!(" const updateKeys = [{keys}];")); + lines.push( + " await runMutationEffects(resData, variables, options, updateKeys);".into(), + ); + } + lines.push( + " options?.onSuccess?.(resData, variables, onMutateResult, context);".into(), + ); + lines.push(" },".into()); + } + lines.push(" });".into()); + lines.push("};".into()); +} + +fn render_media_upload_body( + lines: &mut Vec, + api_namespace: &str, + operation: &str, + args: &str, + has_config: bool, + options: &GenerateOptions, +) { + let config_arg = if has_config { + if args.is_empty() { + "config" + } else { + ", config" + } + } else { + "" + }; + lines.push(format!( + " const uploadInstructions = await {api_namespace}.{operation}({args}{config_arg});" + )); + lines.push(" ".into()); + lines.push(" if (file && uploadInstructions.url) {".into()); + lines.push( + " const method = (uploadInstructions.method?.toLowerCase() ?? \"put\") as \"put\" | \"post\";" + .into(), + ); + lines.push(" let dataToSend: File | FormData = file;".into()); + lines.push(" if (method === \"post\") {".into()); + lines.push(" dataToSend = new FormData();".into()); + lines.push( + " if (\"fields\" in uploadInstructions && uploadInstructions.fields) {".into(), + ); + lines.push( + " for (const [key, value] of Object.entries(uploadInstructions.fields)) {" + .into(), + ); + lines.push(" dataToSend.append(key, String(value));".into()); + lines.push(" }".into()); + lines.push(" }".into()); + lines.push(" dataToSend.append(\"file\", file);".into()); + lines.push(" }".into()); + lines.push(if uses_native_rest_client(options) { + " await AppRestClient.upload(uploadInstructions.url, dataToSend, {".into() + } else { + " await axios[method](uploadInstructions.url, dataToSend, {".into() + }); + if uses_native_rest_client(options) { + lines.push( + " headers: method === \"put\" ? { \"Content-Type\": file.type } : undefined," + .into(), + ); + } else { + lines.push(" headers: {".into()); + lines.push(" \"Content-Type\": file.type,".into()); + lines.push(" },".into()); + } + lines.push(" signal: abortController?.signal,".into()); + lines.push(" onUploadProgress: onUploadProgress".into()); + lines.push(" ? (progressEvent) => onUploadProgress({ loaded: progressEvent.loaded, total: progressEvent.total ?? 0 })".into()); + lines.push(" : undefined,".into()); + lines.push(if uses_native_rest_client(options) { + " }, method);".into() + } else { + " });".into() + }); + lines.push(" }".into()); + lines.push(" ".into()); + lines.push(" return uploadInstructions;".into()); +} + +fn update_query_endpoints<'a>(mutation: &Value, endpoints: &'a [&Value]) -> Vec<&'a Value> { + let response = mutation.get("response").and_then(Value::as_str); + let mutation_names: HashSet<&str> = mutation + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|param| param.get("name").and_then(Value::as_str)) + .collect(); + endpoints + .iter() + .copied() + .filter(|endpoint| { + endpoint.get("method").and_then(Value::as_str) == Some("get") + && endpoint.get("response").and_then(Value::as_str) == response + && endpoint + .get("parameters") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter(|param| { + param + .get("parameterObject") + .and_then(|object| object.get("required")) + .and_then(Value::as_bool) + .unwrap_or(false) + }) + .filter_map(|param| param.get("name").and_then(Value::as_str)) + .all(|name| mutation_names.contains(name)) + }) + .collect() +} + +fn destructured_update_variables( + mutation: &Value, + updates: &[&Value], + options: &GenerateOptions, + models_namespace: &str, +) -> Vec { + let required_names: HashSet = updates + .iter() + .flat_map(|endpoint| { + query_function_params(endpoint, options, models_namespace, false, false) + }) + .filter(|param| param.required) + .map(|param| param.name) + .collect(); + query_function_params(mutation, options, models_namespace, false, false) + .into_iter() + .filter(|param| { + param.required && param.kind != "Body" && required_names.contains(¶m.name) + }) + .map(|param| param.name) + .collect() +} + +fn render_mutation_docs( + lines: &mut Vec, + endpoint: &Value, + options: &GenerateOptions, + models_namespace: &str, +) { + let (_, cap) = query_names(endpoint); + let mutation_cap = if endpoint.get("method").and_then(Value::as_str) == Some("get") { + format!("{cap}Mutation") + } else { + cap.clone() + }; + lines.push("/** ".into()); + lines.push(format!( + " * Mutation `use{mutation_cap}`{}", + if endpoint + .get("summary") + .and_then(Value::as_str) + .is_some_and(|value| !value.is_empty()) + && endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) + { + " - recommended when file should not be cached" + } else { + "" + } + )); + if let Some(summary) = endpoint + .get("summary") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + { + lines.push(format!(" * @summary {}", summary.replace('\n', "\n *"))); + } + if let Some(description) = endpoint + .get("description") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + { + lines.push(format!( + " * @description {}", + description.replace('\n', "\n *") + )); + } + if endpoint + .get("acl") + .and_then(Value::as_array) + .is_some_and(|acl| !acl.is_empty()) + { + lines.push(format!(" * @permission Requires `canUse{cap}` ability ")); + } + for param in function_params(endpoint, options, models_namespace) { + lines.push(format!( + " * @param {{ {} }} {} {} parameter{}", + param.ty, + param.name, + param.kind, + parameter_description(param.source) + )); + } + if endpoint + .get("mediaUpload") + .and_then(Value::as_bool) + .unwrap_or(false) + { + lines.push(" * @param { File } file Body parameter".into()); + } + lines.push(format!( + " * @param {{ AppMutationOptions{} }} options Mutation options", + if options.mutation_effects { + " & MutationEffectsOptions" + } else { + "" + } + )); + let response = endpoint + .get("response") + .and_then(Value::as_str) + .unwrap_or("z.void()"); + let result = schema_type(response, models_namespace, options); + lines.push(format!( + " * @returns {{ UseMutationResult<{}> }} {}", + if endpoint + .get("mediaDownload") + .and_then(Value::as_bool) + .unwrap_or(false) + { + format!("{}<{result}>", response_type(options)) + } else { + result + }, + endpoint + .get("responseDescription") + .and_then(Value::as_str) + .unwrap_or_default() + )); + let statuses = endpoint + .get("responseStatusCodes") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .collect::>() + .join(", "); + lines.push(format!(" * @statusCodes [{statuses}]")); + lines.push(" */".into()); +} diff --git a/native/src/resolver.rs b/native/src/resolver.rs new file mode 100644 index 00000000..65106a04 --- /dev/null +++ b/native/src/resolver.rs @@ -0,0 +1,752 @@ +use indexmap::IndexMap as BaseIndexMap; +use regex::Regex; +use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet}; +use serde::Serialize; +use serde_json::{Map, Value}; + +use crate::config::GenerateOptions; + +type IndexMap = BaseIndexMap; + +const METHODS: [&str; 8] = [ + "get", "put", "post", "delete", "options", "head", "patch", "trace", +]; +const RESERVED: [&str; 35] = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", +]; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResolverStats { + pub operations: usize, + pub operation_tags: usize, + pub schemas: usize, + pub direct_schema_edges: usize, + pub deep_schema_edges: usize, + pub schema_tags: usize, +} + +pub struct IndexedOperation<'a> { + pub path: &'a str, + pub method: &'static str, + pub operation: &'a Map, + pub tag: String, + pub name: String, + pub path_parameters: Option<&'a Value>, +} + +pub struct Resolver<'a> { + pub operations: Vec>, + pub direct_dependencies: HashMap>, + pub deep_dependencies: HashMap>, + pub schema_tags: HashMap>, + pub topology_order: Vec, + pub ordered_dependencies: IndexMap>, +} + +impl<'a> Resolver<'a> { + pub fn new(document: &'a Value, options: &GenerateOptions) -> Result { + let started = std::time::Instant::now(); + let ordered_dependencies = collect_ordered_dependencies(document, options.extract_enums); + let after_direct = started.elapsed(); + let ordered_deep_dependencies = ordered_transitive_graph(&ordered_dependencies); + let after_deep = started.elapsed(); + let topology_order = ordered_topology(&ordered_deep_dependencies); + let direct_dependencies = ordered_dependencies + .iter() + .map(|(root, dependencies)| (root.clone(), dependencies.iter().cloned().collect())) + .collect(); + let deep_dependencies = ordered_deep_dependencies + .iter() + .map(|(root, dependencies)| (root.clone(), dependencies.iter().cloned().collect())) + .collect(); + let mut operations = index_operations(document, options)?; + assign_unique_names(&mut operations, options); + let after_operations = started.elapsed(); + let mut schema_tags = collect_schema_tags(document, &operations, &deep_dependencies); + let after_tags = started.elapsed(); + if options.extract_enums { + merge_duplicate_enum_tags(document, &mut schema_tags); + } + if std::env::var_os("OPENAPI_NATIVE_PROFILE").is_some() { + let finished = started.elapsed(); + eprintln!( + "native resolver direct={:.3}ms deep={:.3}ms topology+ops={:.3}ms tags={:.3}ms enums={:.3}ms", + after_direct.as_secs_f64() * 1000.0, + (after_deep - after_direct).as_secs_f64() * 1000.0, + (after_operations - after_deep).as_secs_f64() * 1000.0, + (after_tags - after_operations).as_secs_f64() * 1000.0, + (finished - after_tags).as_secs_f64() * 1000.0, + ); + } + Ok(Self { + operations, + direct_dependencies, + deep_dependencies, + schema_tags, + topology_order, + ordered_dependencies, + }) + } + + pub fn stats(&self, document: &Value) -> ResolverStats { + ResolverStats { + operations: self.operations.len(), + operation_tags: self + .operations + .iter() + .map(|operation| operation.tag.as_str()) + .collect::>() + .len(), + schemas: document + .pointer("/components/schemas") + .and_then(Value::as_object) + .map_or(0, Map::len), + direct_schema_edges: self.direct_dependencies.values().map(HashSet::len).sum(), + deep_schema_edges: self.deep_dependencies.values().map(HashSet::len).sum(), + schema_tags: self.schema_tags.values().map(HashSet::len).sum(), + } + } +} + +fn collect_ordered_dependencies( + document: &Value, + extract_enums: bool, +) -> IndexMap> { + let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + else { + return IndexMap::default(); + }; + let mut enum_ref_by_values: IndexMap = IndexMap::default(); + for (name, schema) in schemas { + if let Some(values) = schema.get("enum").and_then(Value::as_array) { + enum_ref_by_values + .entry(Value::Array(values.clone()).to_string()) + .or_insert_with(|| format!("#/components/schemas/{name}")); + } + } + let mut direct: IndexMap> = IndexMap::default(); + let mut inline_enum_values_by_parent: IndexMap> = IndexMap::default(); + for (name, schema) in schemas { + let root = format!("#/components/schemas/{name}"); + let mut dependencies = Vec::new(); + let mut seen = HashSet::default(); + collect_ordered_refs(schema, &mut dependencies, &mut seen); + if !dependencies.is_empty() { + direct.insert(root.clone(), dependencies); + } + if extract_enums { + let mut inline_values = HashSet::default(); + if let Some(object) = schema.as_object() { + for value in object.values() { + collect_inline_enum_values(value, &mut inline_values); + } + } + if !inline_values.is_empty() { + inline_enum_values_by_parent.insert(root, inline_values); + } + } + } + // The TypeScript resolver appends duplicate-inline-enum edges after building + // the ordinary reference graph. Preserve that insertion order: named enums + // first, then their parent schemas in document order. + if extract_enums { + for (enum_values, enum_reference) in &enum_ref_by_values { + for (parent_reference, inline_values) in &inline_enum_values_by_parent { + if parent_reference == enum_reference { + continue; + } + if inline_values.contains(enum_values) { + let dependencies = direct.entry(parent_reference.clone()).or_default(); + if !dependencies.contains(enum_reference) { + dependencies.push(enum_reference.clone()); + } + } + } + } + } + direct +} + +fn collect_inline_enum_values(value: &Value, values: &mut HashSet) { + match value { + Value::Array(items) => { + for value in items { + collect_inline_enum_values(value, values); + } + } + Value::Object(object) => { + if let Some(enum_values) = object.get("enum").and_then(Value::as_array) { + values.insert(Value::Array(enum_values.clone()).to_string()); + } + for value in object.values() { + collect_inline_enum_values(value, values); + } + } + _ => {} + } +} + +fn ordered_transitive_graph( + direct: &IndexMap>, +) -> IndexMap> { + let mut deep: IndexMap> = IndexMap::default(); + for root in direct.keys() { + let mut dependencies = Vec::new(); + let mut visited = HashSet::default(); + visit_ordered_dependencies(root, root, &direct, &mut dependencies, &mut visited); + deep.insert(root.clone(), dependencies); + } + deep +} + +fn ordered_topology(deep: &IndexMap>) -> Vec { + let mut sorted = Vec::new(); + let mut visited = HashSet::default(); + for root in deep.keys() { + visit_topology( + root, + &deep, + &mut sorted, + &mut visited, + &mut HashSet::default(), + ); + } + sorted +} + +fn visit_ordered_dependencies( + current: &str, + root: &str, + direct: &IndexMap>, + output: &mut Vec, + visited: &mut HashSet, +) { + let Some(children) = direct.get(current) else { + return; + }; + for child in children { + if visited.insert(child.clone()) { + output.push(child.clone()); + if child != root { + visit_ordered_dependencies(child, root, direct, output, visited); + } + } + } +} + +fn visit_topology( + name: &str, + graph: &IndexMap>, + sorted: &mut Vec, + visited: &mut HashSet, + ancestors: &mut HashSet, +) { + ancestors.insert(name.to_string()); + visited.insert(name.to_string()); + if let Some(children) = graph.get(name) { + for child in children { + if !ancestors.contains(child) && !visited.contains(child) { + visit_topology(child, graph, sorted, visited, ancestors); + } + } + } + ancestors.remove(name); + if !sorted.iter().any(|value| value == name) { + sorted.push(name.to_string()); + } +} + +fn collect_ordered_refs(value: &Value, refs: &mut Vec, seen: &mut HashSet) { + let Some(object) = value.as_object() else { + return; + }; + if let Some(reference) = object.get("$ref").and_then(Value::as_str) { + let reference = autocorrect_ref(reference); + if seen.insert(reference.clone()) { + refs.push(reference); + } + } + if let Some(items) = ["allOf", "anyOf", "oneOf"] + .iter() + .find_map(|keyword| object.get(*keyword).and_then(Value::as_array)) + { + for item in items { + collect_ordered_refs(item, refs, seen); + } + } + if let Some(properties) = object.get("properties").and_then(Value::as_object) { + for property in properties.values() { + collect_ordered_refs(property, refs, seen); + } + } + if let Some(additional) = object + .get("additionalProperties") + .filter(|value| value.is_object()) + { + collect_ordered_refs(additional, refs, seen); + } + if object.get("type").and_then(Value::as_str) == Some("array") { + if let Some(items) = object.get("items") { + collect_ordered_refs(items, refs, seen); + } + } +} + +fn merge_duplicate_enum_tags(document: &Value, schema_tags: &mut HashMap>) { + let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + else { + return; + }; + let mut enum_ref_by_code = HashMap::default(); + for (name, schema) in schemas { + if let Some(values) = schema.get("enum").and_then(Value::as_array) { + if let Ok(code) = serde_json::to_string(values) { + enum_ref_by_code + .entry(code) + .or_insert_with(|| format!("#/components/schemas/{name}")); + } + } + } + let mut additions: Vec<(String, Vec)> = Vec::new(); + for (name, schema) in schemas { + // Component-level enums are canonical declarations, not extracted inline enums. + if schema.get("enum").is_some() { + continue; + } + let parent_ref = format!("#/components/schemas/{name}"); + let parent_tags: Vec = schema_tags + .get(&parent_ref) + .into_iter() + .flatten() + .cloned() + .collect(); + if parent_tags.is_empty() { + continue; + } + collect_duplicate_enum_tags(schema, &enum_ref_by_code, &parent_tags, &mut additions); + } + for (reference, tags) in additions { + schema_tags.entry(reference).or_default().extend(tags); + } +} + +fn collect_duplicate_enum_tags( + value: &Value, + enum_ref_by_code: &HashMap, + tags: &[String], + additions: &mut Vec<(String, Vec)>, +) { + match value { + Value::Object(object) => { + if let Some(values) = object.get("enum").and_then(Value::as_array) { + if let Ok(code) = serde_json::to_string(values) { + if let Some(reference) = enum_ref_by_code.get(&code) { + additions.push((reference.clone(), tags.to_vec())); + } + } + } + for child in object.values() { + collect_duplicate_enum_tags(child, enum_ref_by_code, tags, additions); + } + } + Value::Array(array) => { + for child in array { + collect_duplicate_enum_tags(child, enum_ref_by_code, tags, additions); + } + } + _ => {} + } +} + +fn index_operations<'a>( + document: &'a Value, + options: &GenerateOptions, +) -> Result>, String> { + let exclude_path = if options.exclude_path_regex.is_empty() { + None + } else { + Some(Regex::new(&options.exclude_path_regex).map_err(|error| error.to_string())?) + }; + let include: HashSet = options + .include_tags + .iter() + .map(|tag| format_tag(tag).to_lowercase()) + .collect(); + let exclude: HashSet = options + .exclude_tags + .iter() + .map(|tag| format_tag(tag).to_lowercase()) + .collect(); + let mut result = Vec::new(); + let Some(paths) = document.get("paths").and_then(Value::as_object) else { + return Ok(result); + }; + for (path, item) in paths { + if exclude_path + .as_ref() + .is_some_and(|regex| regex.is_match(path)) + { + continue; + } + let Some(item) = item.as_object() else { + continue; + }; + for (method_name, value) in item { + let Some(method) = METHODS + .iter() + .copied() + .find(|candidate| candidate == method_name) + else { + continue; + }; + let Some(operation) = value.as_object() else { + continue; + }; + if operation + .get("deprecated") + .and_then(Value::as_bool) + .unwrap_or(false) + && !options.with_deprecated_endpoints + { + continue; + } + let source_tag = operation + .get("tags") + .and_then(Value::as_array) + .and_then(|tags| tags.first()) + .and_then(Value::as_str) + .unwrap_or(&options.default_tag); + let formatted = format_tag(source_tag); + let normalized = formatted.to_lowercase(); + if (!include.is_empty() && !include.contains(&normalized)) + || exclude.contains(&normalized) + { + continue; + } + let tag = if options.split_by_tags { + formatted + } else { + options.default_tag.clone() + }; + result.push(IndexedOperation { + path, + method, + operation, + tag, + name: String::new(), + path_parameters: item.get("parameters"), + }); + } + } + Ok(result) +} + +fn assign_unique_names(operations: &mut [IndexedOperation<'_>], options: &GenerateOptions) { + let mut counts: HashMap<(String, String), usize> = HashMap::default(); + for operation in operations.iter() { + let name = operation_name(operation, options, false, false); + *counts.entry((operation.tag.clone(), name)).or_default() += 1; + } + let mut tag_counts: HashMap<(String, String), usize> = HashMap::default(); + for operation in operations.iter() { + let name = operation_name(operation, options, true, false); + *tag_counts.entry((operation.tag.clone(), name)).or_default() += 1; + } + for operation in operations.iter_mut() { + let short = operation_name(operation, options, false, false); + operation.name = if counts.get(&(operation.tag.clone(), short.clone())) == Some(&1) { + short + } else { + let tagged = operation_name(operation, options, true, false); + if tag_counts.get(&(operation.tag.clone(), tagged.clone())) == Some(&1) { + tagged + } else { + operation_name(operation, options, true, true) + } + }; + } +} + +fn operation_name( + operation: &IndexedOperation<'_>, + options: &GenerateOptions, + keep_tag: bool, + keep_prefix: bool, +) -> String { + let mut name = operation + .operation + .get("operationId") + .and_then(Value::as_str) + .map(invalid_identifier) + .unwrap_or_else(|| { + format!( + "{}{}", + operation.method, + path_to_variable_name(operation.path) + ) + }); + if !options.remove_operation_prefix_ending_with.is_empty() { + if keep_prefix { + name = name + .split(&options.remove_operation_prefix_ending_with) + .enumerate() + .map(|(index, part)| { + if index == 0 { + part.to_string() + } else { + capitalize(part) + } + }) + .collect(); + } else if let Some(index) = name.rfind(&options.remove_operation_prefix_ending_with) { + name = name[index + options.remove_operation_prefix_ending_with.len()..].to_string(); + } + } + if (options.ts_namespaces || options.tree_shakeable_namespaces) && !keep_tag { + let without_tag = remove_word(&name, &operation.tag); + if without_tag.is_empty() { + name = operation.method.to_string(); + } else if !RESERVED.contains(&without_tag.as_str()) { + name = without_tag; + } + } + if RESERVED.contains(&name.as_str()) { + format!( + "{}{}", + operation.method, + path_to_variable_name(operation.path) + ) + } else { + name + } +} + +fn collect_schema_tags( + document: &Value, + operations: &[IndexedOperation<'_>], + deep: &HashMap>, +) -> HashMap> { + let mut result: HashMap> = HashMap::default(); + for operation in operations { + let mut refs = HashSet::default(); + if let Some(params) = operation.operation.get("parameters") { + collect_operation_schema_refs(document, params, &mut refs, &mut HashSet::default()); + } + if let Some(body) = operation.operation.get("requestBody") { + collect_operation_schema_refs(document, body, &mut refs, &mut HashSet::default()); + } + if let Some(responses) = operation.operation.get("responses") { + collect_operation_schema_refs(document, responses, &mut refs, &mut HashSet::default()); + } + let roots: Vec = refs.iter().cloned().collect(); + for reference in roots { + result + .entry(reference.clone()) + .or_default() + .insert(operation.tag.clone()); + if let Some(children) = deep.get(&reference) { + for child in children { + result + .entry(child.clone()) + .or_default() + .insert(operation.tag.clone()); + } + } + } + } + result +} + +fn collect_operation_schema_refs( + document: &Value, + value: &Value, + refs: &mut HashSet, + visited_components: &mut HashSet, +) { + match value { + Value::Object(object) => { + if let Some(reference) = object.get("$ref").and_then(Value::as_str) { + let reference = autocorrect_ref(reference); + if reference.starts_with("#/components/schemas/") { + refs.insert(reference); + return; + } + if visited_components.insert(reference.clone()) { + if let Some(resolved) = resolve_pointer(document, &reference) { + collect_operation_schema_refs(document, resolved, refs, visited_components); + } + } + return; + } + for child in object.values() { + collect_operation_schema_refs(document, child, refs, visited_components); + } + } + Value::Array(array) => { + for child in array { + collect_operation_schema_refs(document, child, refs, visited_components); + } + } + _ => {} + } +} + +fn resolve_pointer<'a>(document: &'a Value, reference: &str) -> Option<&'a Value> { + reference + .strip_prefix('#') + .and_then(|pointer| document.pointer(pointer)) +} + +fn autocorrect_ref(reference: &str) -> String { + if reference.starts_with("#/") { + reference.to_string() + } else if let Some(rest) = reference.strip_prefix('#') { + format!("#/{rest}") + } else { + reference.to_string() + } +} + +pub(crate) fn format_tag(value: &str) -> String { + let mut result = String::with_capacity(value.len()); + let mut uppercase_next = false; + for character in value.chars() { + if character.is_alphanumeric() { + if uppercase_next { + result.extend(character.to_uppercase()); + uppercase_next = false; + } else { + result.push(character); + } + } else { + uppercase_next = true; + } + } + result +} + +fn invalid_identifier(value: &str) -> String { + let mut result = String::new(); + let mut uppercase_next = false; + for character in value.chars() { + if result.is_empty() + && !(character.is_ascii_alphabetic() || character == '_' || character == '$') + { + continue; + } + if character.is_ascii_alphanumeric() || character == '_' || character == '$' { + if uppercase_next { + result.extend(character.to_uppercase()); + uppercase_next = false; + } else { + result.push(character); + } + } else { + uppercase_next = true; + } + } + result +} + +fn path_to_variable_name(path: &str) -> String { + let mut base = String::new(); + let mut uppercase_next = true; + let mut last_param = None; + for segment in path.split('/') { + if segment.is_empty() { + continue; + } + if segment.starts_with('{') && segment.ends_with('}') { + last_param = Some(segment[1..segment.len() - 1].to_string()); + continue; + } + for character in segment.chars() { + if character == '-' { + uppercase_next = true; + } else if uppercase_next { + base.extend(character.to_uppercase()); + uppercase_next = false; + } else if character.is_alphanumeric() || character == '_' { + base.push(character); + } else { + base.push('_'); + } + } + uppercase_next = true; + } + if let Some(param) = last_param { + base.push_str("By"); + base.push_str(&capitalize(¶m)); + } + base +} + +fn capitalize(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_uppercase().chain(chars).collect()) + .unwrap_or_default() +} + +fn remove_word(source: &str, word: &str) -> String { + let singular = word + .strip_suffix("es") + .or_else(|| word.strip_suffix('s')) + .unwrap_or(word); + if singular.is_empty() { + return source.to_string(); + } + let needle = singular.to_ascii_lowercase(); + let mut output = source.to_string(); + while let Some(start) = output.to_ascii_lowercase().find(&needle) { + let mut end = start + needle.len(); + for (index, character) in output[end..].char_indices() { + if character.is_ascii_uppercase() { + break; + } + end = start + needle.len() + index + character.len_utf8(); + } + output.replace_range(start..end, ""); + } + output +} diff --git a/native/src/zod.rs b/native/src/zod.rs new file mode 100644 index 00000000..436d6059 --- /dev/null +++ b/native/src/zod.rs @@ -0,0 +1,1113 @@ +use std::{cell::RefCell, rc::Rc}; + +use indexmap::IndexMap as BaseIndexMap; +use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet}; +use serde_json::{Map, Value}; + +use crate::{config::GenerateOptions, resolver::Resolver}; + +type IndexMap = BaseIndexMap; + +#[derive(Clone, Copy, Default)] +struct Meta { + required: bool, + parent_partial: bool, +} + +pub struct ZodCompiler<'a> { + document: &'a Value, + options: &'a GenerateOptions, + resolver: &'a Resolver<'a>, + root_enums: HashMap, + extracted_enums: IndexMap, + circular_getters: HashSet, + runtime_tags: Rc>>>, +} + +impl<'a> ZodCompiler<'a> { + pub fn new( + document: &'a Value, + options: &'a GenerateOptions, + resolver: &'a Resolver<'a>, + ) -> Self { + Self::new_with_runtime_tags( + document, + options, + resolver, + Rc::new(RefCell::new(HashMap::default())), + ) + } + + pub fn new_with_runtime_tags( + document: &'a Value, + options: &'a GenerateOptions, + resolver: &'a Resolver<'a>, + runtime_tags: Rc>>>, + ) -> Self { + let mut root_enums = HashMap::default(); + if let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + { + for (name, schema) in schemas { + if let Some(code) = enum_code(schema) { + root_enums + .entry(code) + .or_insert_with(|| schema_name(name, &options.schema_suffix)); + } + } + } + let extracted_enums = collect_extracted_enums(document, resolver, options, &root_enums); + let circular_getters = collect_precompiled_circular_refs(document, resolver); + Self { + document, + options, + resolver, + root_enums, + extracted_enums, + circular_getters, + runtime_tags, + } + } + + pub fn compile_components(&self) -> Result, String> { + self.compile_components_with_tags(&HashMap::default()) + } + + pub fn compile_components_with_tags( + &self, + first_tags: &HashMap, + ) -> Result, String> { + let mut result = Map::new(); + let Some(schemas) = self + .document + .pointer("/components/schemas") + .and_then(Value::as_object) + else { + return Ok(result); + }; + for (name, schema) in schemas { + let zod_name = schema_name(name, &self.options.schema_suffix); + let reference = format!("#/components/schemas/{name}"); + if self.options.exclude_redundant_zod_schemas + && !self.resolver.schema_tags.contains_key(&reference) + { + continue; + } + let tag = first_tags + .get(&reference) + .cloned() + .unwrap_or_else(|| self.schema_tag(&reference)); + let code = if schema.get("enum").is_some() { + enum_code(schema).unwrap_or_else(|| "z.enum([])".into()) + } else { + self.compile( + schema, + Meta::default(), + &tag, + Some(&reference), + &mut Vec::new(), + )? + }; + result.insert(zod_name, Value::String(code)); + } + Ok(result) + } + + pub fn compile_endpoint_schema( + &self, + schema: &Value, + required: bool, + tag: &str, + ) -> Result<(String, String), String> { + let meta = Meta { + required, + parent_partial: false, + }; + let code = self.compile(schema, meta, tag, None, &mut Vec::new())?; + let actual = self.resolve_schema(schema).unwrap_or(schema); + Ok((code, chain(actual, meta, self.options))) + } + + pub fn endpoint_chain(&self, schema: &Value, required: bool) -> String { + let actual = self.resolve_schema(schema).unwrap_or(schema); + chain( + actual, + Meta { + required, + parent_partial: false, + }, + self.options, + ) + } + + pub fn extracted_schema_codes(&self) -> Map { + self.extracted_enums + .iter() + .map(|(code, (name, _))| (name.clone(), Value::String(code.clone()))) + .collect() + } + + pub fn extracted_schema_tags(&self) -> HashMap { + self.extracted_enums + .values() + .map(|(name, tag)| (name.clone(), tag.clone())) + .collect() + } + + pub fn schema_owner(&self, reference: &str) -> String { + self.schema_tag(reference) + } + + pub fn circular_schema_names(&self) -> Vec { + self.resolver + .deep_dependencies + .iter() + .filter(|(reference, dependencies)| dependencies.contains(*reference)) + .map(|(reference, _)| { + schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + ) + }) + .collect() + } + + pub fn compile_reference_for_tag( + &self, + reference: &str, + tag: &str, + ) -> Result<(String, String), String> { + let reference = autocorrect_ref(reference); + let schema = self + .document + .pointer( + reference + .strip_prefix('#') + .ok_or_else(|| format!("Invalid ref: {reference}"))?, + ) + .ok_or_else(|| format!("Schema not found: {reference}"))?; + let name = schema_name( + reference.rsplit('/').next().unwrap_or_default(), + &self.options.schema_suffix, + ); + let code = self.compile( + schema, + Meta::default(), + tag, + Some(&reference), + &mut vec![reference.clone()], + )?; + Ok((name, code)) + } + + fn compile( + &self, + schema: &Value, + meta: Meta, + tag: &str, + root_ref: Option<&str>, + stack: &mut Vec, + ) -> Result { + if let Some(reference) = schema.get("$ref").and_then(Value::as_str) { + let reference = autocorrect_ref(reference); + let name = reference + .rsplit('/') + .next() + .ok_or_else(|| format!("Invalid ref: {reference}"))?; + let zod_name = schema_name(name, &self.options.schema_suffix); + let owner = self.schema_tag(&reference); + let output = if self.options.ts_namespaces && owner != tag { + format!("{}Models.{zod_name}", capitalize(&owner)) + } else { + zod_name + }; + return Ok(output); + } + + let object = schema + .as_object() + .ok_or_else(|| "Schema must be an object".to_string())?; + if let Some(types) = object.get("type").and_then(Value::as_array) { + let mut compiled = Vec::with_capacity(types.len()); + for schema_type in types { + let mut variant = object.clone(); + variant.insert("type".into(), schema_type.clone()); + compiled.push(self.compile(&Value::Object(variant), meta, tag, root_ref, stack)?); + } + return Ok(if compiled.len() == 1 { + compiled.remove(0) + } else { + format!("z.union([{}])", compiled.join(", ")) + }); + } + for keyword in ["oneOf", "anyOf"] { + if let Some(items) = object.get(keyword).and_then(Value::as_array) { + let compiled = items + .iter() + .map(|item| self.compile(item, meta, tag, root_ref, stack)) + .collect::, _>>()?; + if compiled.len() == 1 { + return Ok(compiled[0].clone()); + } + if keyword == "oneOf" + && object.get("discriminator").is_some() + && !items.iter().any(has_multiple_all_of) + { + let property = object + .get("discriminator") + .and_then(Value::as_object) + .and_then(|discriminator| discriminator.get("propertyName")) + .and_then(Value::as_str) + .unwrap_or_default(); + return Ok(format!( + "\n z.discriminatedUnion(\"{property}\", [{}])\n ", + compiled.join(", ") + )); + } + return Ok(format!("z.union([{}])", compiled.join(", "))); + } + } + if let Some(items) = object.get("allOf").and_then(Value::as_array) { + return self.compile_all_of(items, meta, tag, root_ref, stack); + } + if let Some(primitive) = self.compile_primitive(object, tag, root_ref)? { + return Ok(primitive); + } + let schema_type = object + .get("type") + .and_then(Value::as_str) + .map(str::to_ascii_lowercase); + if schema_type.as_deref() == Some("array") { + let item = if let Some(items) = object.get("items") { + let code = self.compile( + items, + Meta { + required: true, + parent_partial: false, + }, + tag, + root_ref, + stack, + )?; + let actual = self.resolve_schema(items).unwrap_or(items); + format!( + "{code}{}", + chain( + actual, + Meta { + required: true, + parent_partial: false + }, + self.options + ) + ) + } else { + "z.any()".into() + }; + return Ok(format!( + "z.array({item}){}", + if self.options.all_readonly { + ".readonly()" + } else { + "" + } + )); + } + if schema_type.as_deref() == Some("object") + || object.contains_key("properties") + || object.contains_key("additionalProperties") + { + return self.compile_object(object, meta, tag, root_ref, stack); + } + if schema_type.as_deref() == Some("any") { + return Ok("z.any()".into()); + } + if schema_type.as_deref() == Some("null") { + return Ok("z.null()".into()); + } + if schema_type.is_none() { + return Ok("z.unknown()".into()); + } + Err(format!( + "Unsupported schema type: {}", + schema_type.unwrap_or_default() + )) + } + + fn compile_primitive( + &self, + schema: &Map, + tag: &str, + root_ref: Option<&str>, + ) -> Result, String> { + let Some(schema_type) = schema + .get("type") + .and_then(Value::as_str) + .map(str::to_ascii_lowercase) + else { + return Ok(None); + }; + if !matches!( + schema_type.as_str(), + "string" | "number" | "integer" | "boolean" + ) { + return Ok(None); + } + if let Some(values) = schema.get("enum").and_then(Value::as_array) { + if schema_type == "string" { + let code = enum_code(&Value::Object(schema.clone())) + .unwrap_or_else(|| "z.enum([])".into()); + if self.options.extract_enums { + if let Some(name) = self.root_enums.get(&code) { + let enum_ref = format!( + "#/components/schemas/{}", + name.strip_suffix(&self.options.schema_suffix) + .unwrap_or(name) + ); + if root_ref != Some(enum_ref.as_str()) { + let owner = self.schema_tag(&enum_ref); + return Ok(Some(if self.options.ts_namespaces && owner != tag { + format!("{}Models.{name}", capitalize(&owner)) + } else { + name.clone() + })); + } + } + if let Some((name, owner)) = self.extracted_enums.get(&code) { + return Ok(Some(if self.options.ts_namespaces && owner != tag { + format!("{}Models.{name}", capitalize(owner)) + } else { + name.clone() + })); + } + } + return Ok(Some(code)); + } + if values.iter().any(Value::is_string) { + return Ok(Some("z.never()".into())); + } + let literals: Vec = values.iter().map(js_scalar).collect(); + return Ok(Some(if literals.len() == 1 { + format!("z.literal({})", literals[0]) + } else { + format!( + "z.union([{}])", + literals + .iter() + .map(|value| format!("z.literal({value})")) + .collect::>() + .join(", ") + ) + })); + } + Ok(Some(match schema_type.as_str() { + "integer" => "z.int()".into(), + "number" => "z.number()".into(), + "boolean" => "z.boolean()".into(), + "string" => match schema.get("format").and_then(Value::as_str) { + Some("binary") => "z.instanceof(Blob)".into(), + Some("email") => "z.email()".into(), + Some("hostname" | "uri") => "z.url()".into(), + Some("uuid") => "z.uuid()".into(), + Some("date-time") => "z.iso.datetime({ offset: true })".into(), + _ => "z.string()".into(), + }, + _ => unreachable!(), + })) + } + + fn compile_object( + &self, + schema: &Map, + _meta: Meta, + tag: &str, + root_ref: Option<&str>, + stack: &mut Vec, + ) -> Result { + let required: HashSet<&str> = schema + .get("required") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .collect(); + let properties = schema.get("properties").and_then(Value::as_object); + let partial = !self.options.with_implicit_required_props + && properties.is_some() + && required.is_empty(); + let mut rendered = Vec::new(); + if let Some(properties) = properties { + for (name, property) in properties { + let property_meta = Meta { + required: if partial { + true + } else if !required.is_empty() { + required.contains(name.as_str()) + } else { + self.options.with_implicit_required_props + }, + parent_partial: partial, + }; + let mut code = self.compile(property, property_meta, tag, root_ref, stack)?; + let actual = self.resolve_schema(property).unwrap_or(property); + code.push_str(&chain(actual, property_meta, self.options)); + let property_name = quote_property(name); + let circular = root_ref.is_some_and(|root| { + (!stack.is_empty() || self.circular_getters.contains(root)) + && schema_reaches(property, root, self.resolver) + }); + rendered.push(if circular { + format!("get {property_name}() {{ return {code} }}") + } else { + format!("{property_name}: {code}") + }); + } + } + let properties_code = if rendered.is_empty() { + if properties.is_some() { + "{ }".to_string() + } else { + "{}".to_string() + } + } else { + format!("{{ {} }}", rendered.join(", ")) + }; + let partial_code = if partial { ".partial()" } else { "" }; + let additional = match schema.get("additionalProperties") { + Some(Value::Object(object)) if !object.is_empty() => { + let value = Value::Object(object.clone()); + let mut code = self.compile( + &value, + Meta { + required: true, + parent_partial: false, + }, + tag, + root_ref, + stack, + )?; + code.push_str(&chain( + &value, + Meta { + required: true, + parent_partial: false, + }, + self.options, + )); + format!(".catchall({code})") + } + Some(value) if value.as_bool() == Some(true) || value.as_object().is_some() => { + ".catchall(z.any())".into() + } + _ => String::new(), + }; + Ok(format!( + "z.object({properties_code}){partial_code}{additional}{}", + if self.options.all_readonly { + ".readonly()" + } else { + "" + } + )) + } + + fn compile_all_of( + &self, + items: &[Value], + meta: Meta, + tag: &str, + root_ref: Option<&str>, + stack: &mut Vec, + ) -> Result { + if items.len() == 1 { + return self.compile(&items[0], meta, tag, root_ref, stack); + } + let mut standalone_required = Vec::new(); + let mut regular = Vec::new(); + for item in items { + let broken = item.as_object().is_some_and(|object| { + object.contains_key("required") + && !["type", "properties", "allOf", "anyOf", "oneOf"] + .iter() + .any(|key| object.contains_key(*key)) + }); + if broken { + standalone_required.extend( + item.get("required") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .map(str::to_string), + ); + } else { + regular.push(item); + } + } + let mut compiled = regular + .iter() + .map(|item| self.compile(item, meta, tag, root_ref, stack)) + .collect::, _>>()?; + if !standalone_required.is_empty() { + let mut properties = Map::new(); + for required in &standalone_required { + let found = regular + .iter() + .find_map(|item| { + self.resolve_schema(item) + .and_then(|schema| schema.pointer(&format!("/properties/{required}"))) + }) + .cloned() + .unwrap_or_else(|| Value::Object(Map::new())); + properties.insert(required.clone(), found); + } + let required_schema = serde_json::json!({"type":"object","properties":properties,"required":standalone_required}); + compiled.push(self.compile(&required_schema, meta, tag, root_ref, stack)?); + } + let Some(first) = compiled.first() else { + return Ok("z.object({})".into()); + }; + let rest = compiled + .iter() + .skip(1) + .map(|code| format!("...{code}.shape")) + .collect::>() + .join(", "); + Ok(format!("z.object({{ ...{first}.shape, {rest} }})")) + } + + fn resolve_schema<'b>(&'b self, schema: &'b Value) -> Option<&'b Value> { + let reference = schema.get("$ref")?.as_str()?; + self.document.pointer(reference.strip_prefix('#')?) + } + + fn schema_tag(&self, reference: &str) -> String { + if !self.options.split_by_tags || self.options.models_in_common { + return self.options.default_tag.clone(); + } + let mut tags = self + .resolver + .schema_tags + .get(reference) + .cloned() + .unwrap_or_default(); + if let Some(runtime) = self.runtime_tags.borrow().get(reference) { + tags.extend(runtime.iter().cloned()); + } + if tags.len() == 1 { + tags.into_iter() + .next() + .unwrap_or_else(|| self.options.default_tag.clone()) + } else { + self.options.default_tag.clone() + } + } +} + +fn chain(schema: &Value, meta: Meta, options: &GenerateOptions) -> String { + let mut chains = Vec::new(); + match schema.get("type").and_then(Value::as_str) { + Some("string") if schema.get("enum").is_none() => { + if let Some(value) = schema.get("minLength") { + chains.push(format!("min({value})")); + } + if let Some(value) = schema.get("maxLength") { + chains.push(format!("max({value})")); + } + if let Some(pattern) = schema.get("pattern").and_then(Value::as_str) { + chains.push(format!( + "regex(/{}/)", + escape_pattern(pattern.trim_matches('/')) + )); + } + } + Some("number" | "integer") if schema.get("enum").is_none() => { + if let Some(value) = schema.get("minimum") { + chains.push(format!( + "{}({value})", + if schema.get("exclusiveMinimum").and_then(Value::as_bool) == Some(true) { + "gt" + } else { + "gte" + } + )); + } else if let Some(value) = schema + .get("exclusiveMinimum") + .filter(|value| value.is_number()) + { + chains.push(format!("gt({value})")); + } + if let Some(value) = schema.get("maximum") { + chains.push(format!( + "{}({value})", + if schema.get("exclusiveMaximum").and_then(Value::as_bool) == Some(true) { + "lt" + } else { + "lte" + } + )); + } else if let Some(value) = schema + .get("exclusiveMaximum") + .filter(|value| value.is_number()) + { + chains.push(format!("lt({value})")); + } + if let Some(value) = schema + .get("multipleOf") + .filter(|value| value.as_f64() != Some(0.0)) + { + chains.push(format!("multipleOf({value})")); + } + } + Some("array") => { + if let Some(value) = schema + .get("minItems") + .and_then(Value::as_u64) + .filter(|value| *value > 0) + { + chains.push(format!("min({value})")); + } + if let Some(value) = schema + .get("maxItems") + .and_then(Value::as_u64) + .filter(|value| *value > 0) + { + chains.push(format!("max({value})")); + } + } + _ => {} + } + if options.with_description { + if let Some(description) = schema + .get("description") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + { + chains.push(format!( + "describe({})", + serde_json::to_string(description).unwrap_or_default() + )); + } + } + let nullable = schema + .get("nullable") + .and_then(Value::as_bool) + .unwrap_or(false); + if nullable && !meta.required { + chains.push("nullish()".into()); + } else if nullable || (options.replace_optional_with_nullish && meta.parent_partial) { + chains.push("nullable()".into()); + } else if !meta.required { + chains.push(if options.replace_optional_with_nullish { + "nullish()".into() + } else { + "optional()".into() + }); + } + if options.with_default_values { + if let Some(value) = schema.get("default") { + chains.push(format!( + "default({})", + if matches!( + schema.get("type").and_then(Value::as_str), + Some("number" | "integer") + ) { + js_scalar(value) + } else { + serde_json::to_string(value).unwrap_or_default() + } + )); + } + } + if chains.is_empty() { + String::new() + } else { + format!(".{}", chains.join(".")) + } +} + +fn enum_code(schema: &Value) -> Option { + let values = schema.get("enum")?.as_array()?; + let values = values + .iter() + .filter(|value| !value.is_null()) + .map(|value| { + format!( + "\"{}\"", + value + .as_str() + .map(str::to_string) + .unwrap_or_else(|| value.to_string()) + ) + }) + .collect::>() + .join(", "); + Some(format!("z.enum([{values}])")) +} +pub(crate) fn schema_name(name: &str, suffix: &str) -> String { + let normalized = normalize_name(name); + let capitalized = capitalize(&normalized); + if capitalized.ends_with(suffix) { + capitalized + } else { + format!("{capitalized}{suffix}") + } +} +fn normalize_name(value: &str) -> String { + let mut sanitized = String::new(); + let mut separator = false; + for ch in value.trim().chars() { + if ch.is_ascii_alphanumeric() || ch == '_' { + sanitized.push(ch); + separator = false; + } else if !separator { + sanitized.push('_'); + separator = true; + } + } + if sanitized + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_digit()) + { + sanitized.insert(0, '_'); + } + let chars: Vec = sanitized.chars().collect(); + let mut result = String::new(); + let mut index = 0; + while index < chars.len() { + if chars[index] == '_' + && index + 1 < chars.len() + && (chars[index + 1].is_ascii_alphanumeric() || chars[index + 1] == '_') + { + result.extend(chars[index + 1].to_uppercase()); + index += 2; + } else { + result.push(chars[index]); + index += 1; + } + } + result +} +fn capitalize(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_uppercase().chain(chars).collect()) + .unwrap_or_default() +} +fn autocorrect_ref(reference: &str) -> String { + if reference.starts_with("#/") { + reference.into() + } else if let Some(rest) = reference.strip_prefix('#') { + format!("#/{rest}") + } else { + reference.into() + } +} +fn has_multiple_all_of(value: &Value) -> bool { + value + .get("allOf") + .and_then(Value::as_array) + .is_some_and(|items| items.len() > 1) +} +fn js_scalar(value: &Value) -> String { + if let Some(value) = value.as_str() { + value.trim_matches('"').to_string() + } else { + value.to_string() + } +} +fn quote_property(value: &str) -> String { + if value + .chars() + .next() + .is_some_and(|first| first.is_ascii_alphabetic()) + && value + .chars() + .all(|character| character.is_ascii_alphanumeric() || character == '_') + { + value.into() + } else { + format!("\"{value}\"") + } +} +fn escape_pattern(value: &str) -> String { + value + .replace('\t', "\\t") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('/', "\\/") +} + +#[derive(Default)] +struct ExtractedEnumCandidate { + last_segments: Vec, + component_names: Vec, + tags: HashSet, +} + +fn collect_extracted_enums( + document: &Value, + resolver: &Resolver<'_>, + options: &GenerateOptions, + root_enums: &HashMap, +) -> IndexMap { + if !options.extract_enums { + return IndexMap::default(); + } + let mut candidates: IndexMap = IndexMap::default(); + if let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + { + for (component_name, schema) in schemas { + if schema.get("enum").is_some() { + continue; + } + let reference = format!("#/components/schemas/{component_name}"); + let tags = resolver + .schema_tags + .get(&reference) + .cloned() + .unwrap_or_default(); + collect_inline_enum_candidates(schema, component_name, &tags, &mut candidates); + } + } + // Codes backed by a canonical component enum never become separately extracted schemas. + candidates.retain(|code, _| !root_enums.contains_key(code)); + + let mut preliminary: IndexMap)> = IndexMap::default(); + for (code, candidate) in candidates { + let Some(common) = most_common_adjacent(&candidate.last_segments) else { + continue; + }; + let name = enum_schema_name(&common, &options.enum_suffix, &options.schema_suffix); + let tag = if candidate.tags.len() == 1 { + candidate + .tags + .iter() + .next() + .cloned() + .unwrap_or_else(|| options.default_tag.clone()) + } else { + options.default_tag.clone() + }; + preliminary.insert(code, (name, tag, candidate.component_names)); + } + let duplicate_names = preliminary + .values() + .filter_map(|(name, _, _)| { + (preliminary + .values() + .filter(|(other, _, _)| other == name) + .count() + > 1) + .then_some(name.clone()) + }) + .collect::>(); + for (name, _, component_names) in preliminary.values_mut() { + if !duplicate_names.contains(name) { + continue; + } + if let Some(prefix) = most_common_adjacent(component_names) { + *name = format!("{}{}", sanitize_enum_name(&capitalize(&prefix)), name); + } + } + preliminary + .into_iter() + .map(|(code, (name, tag, _))| (code, (name, tag))) + .collect() +} + +fn collect_inline_enum_candidates( + schema: &Value, + component_name: &str, + tags: &HashSet, + candidates: &mut IndexMap, +) { + let Some(object) = schema.as_object() else { + return; + }; + if object.contains_key("$ref") { + return; + } + if let Some(properties) = object.get("properties").and_then(Value::as_object) { + for (property_name, property) in properties { + if let Some(code) = enum_code(property) { + let candidate = candidates.entry(code).or_default(); + candidate.last_segments.push(property_name.clone()); + candidate.component_names.push(component_name.to_string()); + candidate.tags.extend(tags.iter().cloned()); + } + collect_inline_enum_candidates(property, component_name, tags, candidates); + } + } + for keyword in ["allOf", "anyOf", "oneOf"] { + if let Some(items) = object.get(keyword).and_then(Value::as_array) { + for item in items { + if let Some(code) = enum_code(item) { + let candidate = candidates.entry(code).or_default(); + candidate.last_segments.push(component_name.to_string()); + candidate.component_names.push(component_name.to_string()); + candidate.tags.extend(tags.iter().cloned()); + } + collect_inline_enum_candidates(item, component_name, tags, candidates); + } + } + } + if object.get("type").and_then(Value::as_str) == Some("array") { + if let Some(item) = object.get("items") { + if let Some(code) = enum_code(item) { + let candidate = candidates.entry(code).or_default(); + candidate.last_segments.push(component_name.to_string()); + candidate.component_names.push(component_name.to_string()); + candidate.tags.extend(tags.iter().cloned()); + } + collect_inline_enum_candidates(item, component_name, tags, candidates); + } + } + if let Some(additional) = object + .get("additionalProperties") + .filter(|value| value.is_object()) + { + collect_inline_enum_candidates(additional, component_name, tags, candidates); + } +} + +fn sanitize_enum_name(name: &str) -> String { + ["Dto", "DTO", "Response", "Request"] + .into_iter() + .fold(name.to_string(), |value, fragment| { + value.replace(fragment, "") + }) +} + +fn enum_schema_name(name: &str, enum_suffix: &str, schema_suffix: &str) -> String { + let normalized = capitalize(&normalize_name(name)); + let suffix = format!("{enum_suffix}{schema_suffix}"); + if normalized.ends_with(&suffix) { + normalized + } else { + format!("{normalized}{suffix}") + } +} + +fn most_common_adjacent(values: &[String]) -> Option { + const IGNORED: [&str; 8] = ["dto", "by", "for", "of", "in", "to", "and", "with"]; + let mut counts: HashMap = HashMap::default(); + let mut order = 0usize; + for value in values { + let words = split_by_uppercase(&capitalize(value)); + for start in 0..words.len() { + if IGNORED.contains(&words[start].to_ascii_lowercase().as_str()) { + continue; + } + for end in start + 1..=words.len() { + if IGNORED.contains(&words[end - 1].to_ascii_lowercase().as_str()) { + continue; + } + let combination = words[start..end].join(""); + let entry = counts.entry(combination).or_insert_with(|| { + order += 1; + (0, order) + }); + entry.0 += 1; + } + } + } + counts + .into_iter() + .max_by( + |(left, (left_count, left_order)), (right, (right_count, right_order))| { + left_count + .cmp(right_count) + .then_with(|| left.len().cmp(&right.len())) + .then_with(|| right_order.cmp(left_order)) + }, + ) + .map(|(value, _)| value) +} + +fn split_by_uppercase(value: &str) -> Vec { + let chars: Vec = value.chars().collect(); + let mut words = Vec::new(); + let mut start = 0; + for index in 1..chars.len() { + if chars[index].is_ascii_uppercase() && !chars[index - 1].is_ascii_uppercase() { + words.push(chars[start..index].iter().collect()); + start = index; + } + } + if start < chars.len() { + words.push(chars[start..].iter().collect()); + } + words + .into_iter() + .filter(|word: &String| !word.is_empty()) + .collect() +} + +fn collect_precompiled_circular_refs(document: &Value, resolver: &Resolver<'_>) -> HashSet { + let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + else { + return HashSet::default(); + }; + let indexes: HashMap = schemas + .keys() + .enumerate() + .map(|(index, name)| (format!("#/components/schemas/{name}"), index)) + .collect(); + let mut result = HashSet::default(); + for (source_index, schema) in schemas.values().enumerate() { + let mut refs = HashSet::default(); + collect_schema_refs(schema, &mut refs); + for reference in refs { + if indexes + .get(&reference) + .is_some_and(|target_index| *target_index > source_index) + && resolver + .deep_dependencies + .get(&reference) + .is_some_and(|dependencies| dependencies.contains(&reference)) + { + result.insert(reference); + } + } + } + result +} + +fn collect_schema_refs(value: &Value, refs: &mut HashSet) { + match value { + Value::Object(object) => { + if let Some(reference) = object.get("$ref").and_then(Value::as_str) { + refs.insert(autocorrect_ref(reference)); + } + for child in object.values() { + collect_schema_refs(child, refs); + } + } + Value::Array(array) => { + for child in array { + collect_schema_refs(child, refs); + } + } + _ => {} + } +} + +fn schema_reaches(schema: &Value, root: &str, resolver: &Resolver<'_>) -> bool { + let mut refs = HashSet::default(); + collect_schema_refs(schema, &mut refs); + refs.iter().any(|reference| { + reference == root + || resolver + .deep_dependencies + .get(reference) + .is_some_and(|dependencies| dependencies.contains(root)) + }) +} diff --git a/package.json b/package.json index a1e7817a..8269d3de 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "src/assets/**" ], "type": "module", + "sideEffects": false, "main": "./dist/index.mjs", + "module": "./dist/index.mjs", "types": "./dist/index.d.mts", "exports": { ".": { @@ -54,64 +56,97 @@ "./zod": { "types": "./dist/zod.d.mts", "import": "./dist/zod.mjs" + }, + "./native": { + "types": "./dist/native.d.mts", + "import": "./dist/native.mjs" + }, + "./rest": { + "types": "./dist/rest.d.mts", + "import": "./dist/rest.mjs" + }, + "./axios": { + "types": "./dist/axios.d.mts", + "import": "./dist/axios.mjs" + }, + "./errors": { + "types": "./dist/errors.d.mts", + "import": "./dist/errors.mjs" + }, + "./query": { + "types": "./dist/query.d.mts", + "import": "./dist/query.mjs" + }, + "./config": { + "types": "./dist/config.d.mts", + "import": "./dist/config.mjs" + }, + "./auth": { + "types": "./dist/auth.d.mts", + "import": "./dist/auth.mjs" } }, "scripts": { - "start": "node --import tsx ./src/sh.ts", - "test": "vitest run", + "start": "bun ./src/sh.ts", + "test": "bun run build:native && bun run test:ts && bun run test:native", + "test:ts": "OPENAPI_CODEGEN_NATIVE=0 vitest run", + "test:native": "OPENAPI_CODEGEN_NATIVE=1 OPENAPI_CODEGEN_REQUIRE_FULL_NATIVE=1 vitest run", "test:watch": "vitest", - "build": "tsdown", - "start:dist": "node ./dist/sh.mjs", - "gen:base": "rm -rf ./test/generated/base && mkdir -p ./test/generated/base && pnpm start generate --config ./test/config.mjs --output ./test/generated/base --no-prettier", - "gen:next": "rm -rf ./test/generated/next && mkdir -p ./test/generated/next && pnpm start generate --config ./test/config.mjs --output ./test/generated/next --no-prettier", + "build": "tsdown && bun run build:native", + "build:ts": "tsdown", + "build:native": "bun ./scripts/build-native.mjs", + "start:dist": "bun ./dist/sh.mjs", + "gen:base": "rm -rf ./test/generated/base && mkdir -p ./test/generated/base && bun run start generate --config ./test/config.mjs --output ./test/generated/base --no-prettier", + "gen:next": "rm -rf ./test/generated/next && mkdir -p ./test/generated/next && bun run start generate --config ./test/config.mjs --output ./test/generated/next --no-prettier", "test:generated-eq": "vitest run ./src/generators/generated-output-eq.test.ts", - "gen:verify": "pnpm gen:base && pnpm gen:next && pnpm test:generated-eq", + "gen:verify": "bun run gen:base && bun run gen:next && bun run test:generated-eq", "typecheck": "tsc --noEmit", "lint": "oxlint --type-aware --fix", "lint:check": "oxlint --type-aware", "format": "oxfmt", "format:check": "oxfmt --check", - "check": "pnpm typecheck && pnpm lint && pnpm test", - "push": "node ./scripts/publish.mjs", - "dev:generate": "rm -rf ./output && pnpm start generate --config ./test/config.mjs", - "dev:check": "pnpm start check --config ./test/config.mjs", - "snapshot:openapi-localhost": "node ./scripts/snapshot-openapi-localhost.mjs", - "bench:vite-codegen": "node ./scripts/benchmark-vite-codegen.mjs" + "check": "bun run typecheck && bun run lint && bun run test", + "push": "bun ./scripts/publish.mjs", + "dev:generate": "rm -rf ./output && bun run start generate --config ./test/config.mjs", + "dev:check": "bun run start check --config ./test/config.mjs", + "snapshot:openapi-localhost": "bun ./scripts/snapshot-openapi-localhost.mjs", + "bench:vite-codegen": "bun ./scripts/benchmark-vite-codegen.mjs", + "verify:tree-shaking": "bun run build:ts && bun ./scripts/verify-tree-shaking.mjs" }, "dependencies": { - "@apidevtools/swagger-parser": "^10.1.0", "@orpc/contract": "^1.14.3", "@orpc/openapi": "^1.14.3", "@orpc/zod": "^1.14.3", - "import-fresh": "^3.3.1", "openapi-types": "^12.1.3", "prompt-sync": "^4.2.0", "reflect-metadata": "^0.2.2", - "ts-pattern": "^5.9.0", - "typescript": "^5.9.3", + "yaml": "^2.9.0", "yargs": "^18.0.0" }, "devDependencies": { - "@casl/ability": "^6.8.0", - "@casl/react": "^5.0.1", + "@casl/ability": "6.8.0", + "@casl/react": "5.0.1", "@tanstack/react-query": "~5.90.21", + "@types/bun": "1.4.1", "@types/node": "^25.3.0", "@types/prompt-sync": "^4.2.3", "@types/react": "^19.2.14", "@types/yargs": "^17.0.35", "@vitejs/plugin-react": "^5.1.4", + "axios": "^1.13.1", + "i18next": "^25.8.11", "oxfmt": "^0.34.0", - "oxlint": "^1.50.0", + "oxlint": "1.50.0", "oxlint-tsgolint": "^0.15.0", "react": "^19.2.4", - "tsdown": "^0.21.0-beta.1", + "tsdown": "^0.22.14", "tsx": "^4.21.0", "type-fest": "^5.4.4", + "typescript": "^7.0.2", "vite": "npm:rolldown-vite@^7.3.1", "vite-plugin-dts": "^4.5.4", "vitest": "4.0.18", - "yargs": "^17.7.2", - "zod": "^4.3.6" + "zod": "^4.5.0" }, "peerDependencies": { "@casl/ability": "^6.7.3", @@ -121,7 +156,7 @@ "i18next": "^25.8.11", "react": "^19.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "zod": "^4.1.12" + "zod": "^4.5.0" }, "peerDependenciesMeta": { "@casl/ability": { @@ -130,13 +165,25 @@ "@casl/react": { "optional": true }, + "@tanstack/react-query": { + "optional": true + }, + "axios": { + "optional": true + }, + "i18next": { + "optional": true + }, + "react": { + "optional": true + }, "vite": { "optional": true } }, "engines": { - "node": ">= 14", - "pnpm": ">= 9" + "bun": ">= 1.4", + "node": ">= 18" }, - "packageManager": "pnpm@10.4.0" + "packageManager": "bun@1.4.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 9494d7cd..00000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,4082 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@apidevtools/swagger-parser': - specifier: ^10.1.0 - version: 10.1.1(openapi-types@12.1.3) - '@orpc/contract': - specifier: ^1.14.3 - version: 1.14.6 - '@orpc/openapi': - specifier: ^1.14.3 - version: 1.14.6 - '@orpc/zod': - specifier: ^1.14.3 - version: 1.14.6(@orpc/contract@1.14.6)(@orpc/server@1.14.6)(zod@4.3.6) - axios: - specifier: ^1.13.1 - version: 1.13.5 - i18next: - specifier: ^25.8.11 - version: 25.8.13(typescript@5.9.3) - import-fresh: - specifier: ^3.3.1 - version: 3.3.1 - openapi-types: - specifier: ^12.1.3 - version: 12.1.3 - prompt-sync: - specifier: ^4.2.0 - version: 4.2.0 - reflect-metadata: - specifier: ^0.2.2 - version: 0.2.2 - ts-pattern: - specifier: ^5.9.0 - version: 5.9.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - yargs: - specifier: ^18.0.0 - version: 18.0.0 - devDependencies: - '@casl/ability': - specifier: ^6.8.0 - version: 6.8.0 - '@casl/react': - specifier: ^5.0.1 - version: 5.0.1(@casl/ability@6.8.0)(react@19.2.4) - '@tanstack/react-query': - specifier: ~5.90.21 - version: 5.90.21(react@19.2.4) - '@types/node': - specifier: ^25.3.0 - version: 25.3.1 - '@types/prompt-sync': - specifier: ^4.2.3 - version: 4.2.3 - '@types/react': - specifier: ^19.2.14 - version: 19.2.14 - '@types/yargs': - specifier: ^17.0.35 - version: 17.0.35 - '@vitejs/plugin-react': - specifier: ^5.1.4 - version: 5.1.4(rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0)) - oxfmt: - specifier: ^0.34.0 - version: 0.34.0 - oxlint: - specifier: ^1.50.0 - version: 1.50.0(oxlint-tsgolint@0.15.0) - oxlint-tsgolint: - specifier: ^0.15.0 - version: 0.15.0 - react: - specifier: ^19.2.4 - version: 19.2.4 - tsdown: - specifier: ^0.21.0-beta.1 - version: 0.21.0-beta.2(typescript@5.9.3) - tsx: - specifier: ^4.21.0 - version: 4.21.0 - type-fest: - specifier: ^5.4.4 - version: 5.4.4 - vite: - specifier: npm:rolldown-vite@^7.3.1 - version: rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0) - vite-plugin-dts: - specifier: ^4.5.4 - version: 4.5.4(@types/node@25.3.1)(rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0))(rollup@4.59.0)(typescript@5.9.3) - vitest: - specifier: 4.0.18 - version: 4.0.18(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0) - zod: - specifier: ^4.3.6 - version: 4.3.6 - -packages: - - '@apidevtools/json-schema-ref-parser@11.7.2': - resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} - engines: {node: '>= 16'} - - '@apidevtools/openapi-schemas@2.1.0': - resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} - engines: {node: '>=10'} - - '@apidevtools/swagger-methods@3.0.2': - resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} - - '@apidevtools/swagger-parser@10.1.1': - resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==} - peerDependencies: - openapi-types: '>=7' - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/generator@8.0.0-rc.1': - resolution: {integrity: sha512-3ypWOOiC4AYHKr8vYRVtWtWmyvcoItHtVqF8paFax+ydpmUdPsJpLBkBBs5ItmhdrwC3a0ZSqqFAdzls4ODP3w==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@8.0.0-rc.2': - resolution: {integrity: sha512-noLx87RwlBEMrTzncWd/FvTxoJ9+ycHNg0n8yyYydIoDsLZuxknKgWRJUqcrVkNrJ74uGyhWQzQaS3q8xfGAhQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@8.0.0-rc.1': - resolution: {integrity: sha512-I4YnARytXC2RzkLNVnf5qFNFMzp679qZpmtw/V3Jt2uGnWiIxyJtaukjG7R8pSx8nG2NamICpGfljQsogj+FbQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.0': - resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/parser@8.0.0-rc.1': - resolution: {integrity: sha512-6HyyU5l1yK/7h9Ki52i5h6mDAx4qJdiLQO4FdCyJNoB/gy3T3GGJdhQzzbZgvgZCugYBvwtQiWRt94QKedHnkA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@babel/types@8.0.0-rc.1': - resolution: {integrity: sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@casl/ability@6.8.0': - resolution: {integrity: sha512-Ipt4mzI4gSgnomFdaPjaLgY2MWuXqAEZLrU6qqWBB7khGiBBuuEp6ytYDnq09bRXqcjaeeHiaCvCGFbBA2SpvA==} - - '@casl/react@5.0.1': - resolution: {integrity: sha512-8E3GkvwlxEW+bkxWTdvjn6SALNJZa9khNk591xTpU27nPK/nzgfqsVfEvrFjYIQ2LODrxXsWLmw7iHPEI0/TaQ==} - peerDependencies: - '@casl/ability': ^4.0.0 || ^5.1.0 || ^6.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@emnapi/core@1.8.1': - resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@jsdevtools/ono@7.1.3': - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - - '@microsoft/api-extractor-model@7.33.4': - resolution: {integrity: sha512-u1LTaNTikZAQ9uK6KG1Ms7nvNedsnODnspq/gH2dcyETWvH4hVNGNDvRAEutH66kAmxA4/necElqGNs1FggC8w==} - - '@microsoft/api-extractor@7.57.6': - resolution: {integrity: sha512-0rFv/D8Grzw1Mjs2+8NGUR+o4h9LVm5zKRtMeWnpdB5IMJF4TeHCL1zR5LMCIudkOvyvjbhMG5Wjs0B5nqsrRQ==} - hasBin: true - - '@microsoft/tsdoc-config@0.18.1': - resolution: {integrity: sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==} - - '@microsoft/tsdoc@0.16.0': - resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} - - '@napi-rs/wasm-runtime@1.1.1': - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - - '@orpc/client@1.14.6': - resolution: {integrity: sha512-Y03NcTtmEJdxcqkKBkdGxqe1IHVpD9IorshG4PaTnz9dQIW+RYI8anRo7o0IlbBlzBICN+Ubo1rnw6bpkhagCQ==} - - '@orpc/contract@1.14.6': - resolution: {integrity: sha512-o4i2ciYWtALidF969S8yj/VFk7ZUmHHKaYGRVitX5K2uMaSB8lJM6TMyBKzRC5Clo99VPjCb9mcl6jMLEAgmKw==} - - '@orpc/interop@1.14.6': - resolution: {integrity: sha512-ZjNaHH9754uUkC98DHfm9HaGnniFnFRLdXXBWL2u+o1TVzEbiNX8dA5+oChxoSTUD3GiwpjLjAuTWXvrbhm4fA==} - - '@orpc/json-schema@1.14.6': - resolution: {integrity: sha512-p79GOJ88DpOTsOaAQ8Ef/2J5455Yh2qp3KjY9q6858RozHuPSXHu7MSgE/bilkzbiuGNoen3frw7GSiKfYpKSA==} - - '@orpc/openapi-client@1.14.6': - resolution: {integrity: sha512-Z0rdO+oVJTASat3fIQmLEjKnqRsjg4QsjRFwFamZ5W+Y076I3Lbp8XhVcMoHbKyfgm/TKnZu3y41b4NFfPBYiA==} - - '@orpc/openapi@1.14.6': - resolution: {integrity: sha512-MdQ0KKLdvqZlmpUgw/6W/6dOfwo1GdtW2kE0VL/AJxqaeM6i3sygtIGAzEr6/CfhIJnfGHqo1jMzdlELQjCqYg==} - - '@orpc/server@1.14.6': - resolution: {integrity: sha512-aHn8dW2clr/fRZzMLbRsZ1Pe4AYxg7YmM3tSZSAkPjn5/3UaHnpuwWeY7LYPok23CGYUmjidTmJQjm0zm+vgoA==} - peerDependencies: - crossws: '>=0.3.4' - ws: '>=8.18.1' - peerDependenciesMeta: - crossws: - optional: true - ws: - optional: true - - '@orpc/shared@1.14.6': - resolution: {integrity: sha512-P2W+DdrUq18kUiF7nIw5wDOA0SR41mM/NsVKDVRsdyhdHk9V9KDuW1JRymyMl+7Wo5SDeSr1Rm/VjA5v08+PHw==} - peerDependencies: - '@opentelemetry/api': '>=1.9.0' - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - - '@orpc/standard-server-aws-lambda@1.14.6': - resolution: {integrity: sha512-8FQX0uBQ2OKkIrF0u5qpd9EH2uqaM1n37NJSw9bvQqLnqoSfzk0xzVkP5s+c8N0Gec6M9mnrLvB7aqQSlpPukw==} - - '@orpc/standard-server-fastify@1.14.6': - resolution: {integrity: sha512-cwS7jRfc+yM8bFyEFVzVcXfgYLfy96KpdPBPUlIQLpInwYvMxePpENM3rEAJ3MHOfMHx1PT5LidrlbR7Npb1Cg==} - peerDependencies: - fastify: '>=5.6.1' - peerDependenciesMeta: - fastify: - optional: true - - '@orpc/standard-server-fetch@1.14.6': - resolution: {integrity: sha512-XnwEnHaKMMBoilK0QVwgMsUvDbCXyz6CDoLgMN51v+p3VSnwjIkrMdOwbc/sj43z6T4irQDu9Zm8T1pxxh1L8w==} - - '@orpc/standard-server-node@1.14.6': - resolution: {integrity: sha512-CmW/EPu1dxoppYMRmsK+F3Z22wMx74RLUtjRokXyRKu4XEX/Ja6nwD+xauQcAMiAQYkp39aCMofAW7BvA46Qcg==} - - '@orpc/standard-server-peer@1.14.6': - resolution: {integrity: sha512-jwVGc5yRA5hk1F/W4yw45P2H4fbu4Tfsk62XijJBXRvtlSNKvvPAuwAd6jFv/fxnq2SH/uskgi91Ja9wgfnYDQ==} - - '@orpc/standard-server@1.14.6': - resolution: {integrity: sha512-75Oh4rAZb8K7P46d6v7R2JhjqjLLEo7Qs4+ABdF+f0m0uKM1oaykJWy7leqTJ+WaYm+uDbsZl/3nyWie9aeJTg==} - - '@orpc/zod@1.14.6': - resolution: {integrity: sha512-skGc5Qj6BLy3ir+VgYZZBJFlxDVAYoJPVymVPEOLawEVSf2mje0/oMPiaQJE4+CyVRSIXUHG8MoQnW0X1OoQ5w==} - peerDependencies: - '@orpc/contract': 1.14.6 - '@orpc/server': 1.14.6 - zod: '>=3.25.0' - - '@oxc-project/runtime@0.101.0': - resolution: {integrity: sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@oxc-project/types@0.101.0': - resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==} - - '@oxc-project/types@0.114.0': - resolution: {integrity: sha512-//nBfbzHQHvJs8oFIjv6coZ6uxQ4alLfiPe6D5vit6c4pmxATHHlVwgB1k+Hv4yoAMyncdxgRBF5K4BYWUCzvA==} - - '@oxfmt/binding-android-arm-eabi@0.34.0': - resolution: {integrity: sha512-sqkqjh/Z38l+duOb1HtVqJTAj1grt2ttkobCopC/72+a4Xxz4xUgZPFyQ4HxrYMvyqO/YA0tvM1QbfOu70Gk1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxfmt/binding-android-arm64@0.34.0': - resolution: {integrity: sha512-1KRCtasHcVcGOMwfOP9d5Bus2NFsN8yAYM5cBwi8LBg5UtXC3C49WHKrlEa8iF1BjOS6CR2qIqiFbGoA0DJQNQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxfmt/binding-darwin-arm64@0.34.0': - resolution: {integrity: sha512-b+Rmw9Bva6e/7PBES2wLO8sEU7Mi0+/Kv+pXSe/Y8i4fWNftZZlGwp8P01eECaUqpXATfSgNxdEKy7+ssVNz7g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxfmt/binding-darwin-x64@0.34.0': - resolution: {integrity: sha512-QGjpevWzf1T9COEokZEWt80kPOtthW1zhRbo7x4Qoz646eTTfi6XsHG2uHeDWJmTbgBoJZPMgj2TAEV/ppEZaA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxfmt/binding-freebsd-x64@0.34.0': - resolution: {integrity: sha512-VMSaC02cG75qL59M9M/szEaqq/RsLfgpzQ4nqUu8BUnX1zkiZIW2gTpUv3ZJ6qpWnHxIlAXiRZjQwmcwpvtbcg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxfmt/binding-linux-arm-gnueabihf@0.34.0': - resolution: {integrity: sha512-Klm367PFJhH6vYK3vdIOxFepSJZHPaBfIuqwxdkOcfSQ4qqc/M8sgK0UTFnJWWTA/IkhMIh1kW6uEqiZ/xtQqg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm-musleabihf@0.34.0': - resolution: {integrity: sha512-nqn0QueVXRfbN9m58/E9Zij0Ap8lzayx591eWBYn0sZrGzY1IRv9RYS7J/1YUXbb0Ugedo0a8qIWzUHU9bWQuA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm64-gnu@0.34.0': - resolution: {integrity: sha512-DDn+dcqW+sMTCEjvLoQvC/VWJjG7h8wcdN/J+g7ZTdf/3/Dx730pQElxPPGsCXPhprb11OsPyMp5FwXjMY3qvA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxfmt/binding-linux-arm64-musl@0.34.0': - resolution: {integrity: sha512-H+F8+71gHQoGTFPPJ6z4dD0Fzfzi0UP8Zx94h5kUmIFThLvMq5K1Y/bUUubiXwwHfwb5C3MPjUpYijiy0rj51Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxfmt/binding-linux-ppc64-gnu@0.34.0': - resolution: {integrity: sha512-dIGnzTNhCXqQD5pzBwduLg8pClm+t8R53qaE9i5h8iua1iaFAJyLffh4847CNZSlASb7gn1Ofuv7KoG/EpoGZg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@oxfmt/binding-linux-riscv64-gnu@0.34.0': - resolution: {integrity: sha512-FGQ2GTTooilDte/ogwWwkHuuL3lGtcE3uKM2EcC7kOXNWdUfMY6Jx3JCodNVVbFoybv4A+HuCj8WJji2uu1Ceg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxfmt/binding-linux-riscv64-musl@0.34.0': - resolution: {integrity: sha512-2dGbGneJ7ptOIVKMwEIHdCkdZEomh74X3ggo4hCzEXL/rl9HwfsZDR15MkqfQqAs6nVXMvtGIOMxjDYa5lwKaA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxfmt/binding-linux-s390x-gnu@0.34.0': - resolution: {integrity: sha512-cCtGgmrTrxq3OeSG0UAO+w6yLZTMeOF4XM9SAkNrRUxYhRQELSDQ/iNPCLyHhYNi38uHJQbS5RQweLUDpI4ajA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@oxfmt/binding-linux-x64-gnu@0.34.0': - resolution: {integrity: sha512-7AvMzmeX+k7GdgitXp99GQoIV/QZIpAS7rwxQvC/T541yWC45nwvk4mpnU8N+V6dE5SPEObnqfhCjO80s7qIsg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxfmt/binding-linux-x64-musl@0.34.0': - resolution: {integrity: sha512-uNiglhcmivJo1oDMh3hoN/Z0WsbEXOpRXZdQ3W/IkOpyV8WF308jFjSC1ZxajdcNRXWej0zgge9QXba58Owt+g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxfmt/binding-openharmony-arm64@0.34.0': - resolution: {integrity: sha512-5eFsTjCyji25j6zznzlMc+wQAZJoL9oWy576xhqd2efv+N4g1swIzuSDcb1dz4gpcVC6veWe9pAwD7HnrGjLwg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxfmt/binding-win32-arm64-msvc@0.34.0': - resolution: {integrity: sha512-6id8kK0t5hKfbV6LHDzRO21wRTA6ctTlKGTZIsG/mcoir0rssvaYsedUymF4HDj7tbCUlnxCX/qOajKlEuqbIw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxfmt/binding-win32-ia32-msvc@0.34.0': - resolution: {integrity: sha512-QHaz+w673mlYqn9v/+fuiKZpjkmagleXQ+NygShDv8tdHpRYX2oYhTJwwt9j1ZfVhRgza1EIUW3JmzCXmtPdhQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxfmt/binding-win32-x64-msvc@0.34.0': - resolution: {integrity: sha512-CXKQM/VaF+yuvGru8ktleHLJoBdjBtTFmAsLGePiESiTN0NjCI/PiaiOCfHMJ1HdP1LykvARUwMvgaN3tDhcrg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@oxlint-tsgolint/darwin-arm64@0.15.0': - resolution: {integrity: sha512-d7Ch+A6hic+RYrm32+Gh1o4lOrQqnFsHi721ORdHUDBiQPea+dssKUEMwIbA6MKmCy6TVJ02sQyi24OEfCiGzw==} - cpu: [arm64] - os: [darwin] - - '@oxlint-tsgolint/darwin-x64@0.15.0': - resolution: {integrity: sha512-Aoai2wAkaUJqp/uEs1gml6TbaPW4YmyO5Ai/vOSkiizgHqVctjhjKqmRiWTX2xuPY94VkwOLqp+Qr3y/0qSpWQ==} - cpu: [x64] - os: [darwin] - - '@oxlint-tsgolint/linux-arm64@0.15.0': - resolution: {integrity: sha512-4og13a7ec4Vku5t2Y7s3zx6YJP6IKadb1uA9fOoRH6lm/wHWoCnxjcfJmKHXRZJII81WmbdJMSPxaBfwN/S68Q==} - cpu: [arm64] - os: [linux] - - '@oxlint-tsgolint/linux-x64@0.15.0': - resolution: {integrity: sha512-9b9xzh/1Harn3a+XiKTK/8LrWw3VcqLfYp/vhV5/zAVR2Mt0d63WSp4FL+wG7DKnI2T/CbMFUFHwc7kCQjDMzQ==} - cpu: [x64] - os: [linux] - - '@oxlint-tsgolint/win32-arm64@0.15.0': - resolution: {integrity: sha512-nNac5hewHdkk5mowOwTqB1ZD76zB/FsUiyUvdCyupq5cG54XyKqSLEp9QGbx7wFJkWCkeWmuwRed4sfpAlKaeA==} - cpu: [arm64] - os: [win32] - - '@oxlint-tsgolint/win32-x64@0.15.0': - resolution: {integrity: sha512-ioAY2XLpy83E2EqOLH9p1cEgj0G2qB1lmAn0a3yFV1jHQB29LIPIKGNsu/tYCClpwmHN79pT5KZAHZOgWxxqNg==} - cpu: [x64] - os: [win32] - - '@oxlint/binding-android-arm-eabi@1.50.0': - resolution: {integrity: sha512-G7MRGk/6NCe+L8ntonRdZP7IkBfEpiZ/he3buLK6JkLgMHgJShXZ+BeOwADmspXez7U7F7L1Anf4xLSkLHiGTg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxlint/binding-android-arm64@1.50.0': - resolution: {integrity: sha512-GeSuMoJWCVpovJi/e3xDSNgjeR8WEZ6MCXL6EtPiCIM2NTzv7LbflARINTXTJy2oFBYyvdf/l2PwHzYo6EdXvg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxlint/binding-darwin-arm64@1.50.0': - resolution: {integrity: sha512-w3SY5YtxGnxCHPJ8Twl3KmS9oja1gERYk3AMoZ7Hv8P43ZtB6HVfs02TxvarxfL214Tm3uzvc2vn+DhtUNeKnw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxlint/binding-darwin-x64@1.50.0': - resolution: {integrity: sha512-hNfogDqy7tvmllXKBSlHo6k5x7dhTUVOHbMSE15CCAcXzmqf5883aPvBYPOq9AE7DpDUQUZ1kVE22YbiGW+tuw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxlint/binding-freebsd-x64@1.50.0': - resolution: {integrity: sha512-ykZevOWEyu0nsxolA911ucxpEv0ahw8jfEeGWOwwb/VPoE4xoexuTOAiPNlWZNJqANlJl7yp8OyzCtXTUAxotw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxlint/binding-linux-arm-gnueabihf@1.50.0': - resolution: {integrity: sha512-hif3iDk7vo5GGJ4OLCCZAf2vjnU9FztGw4L0MbQL0M2iY9LKFtDMMiQAHmkF0PQGQMVbTYtPdXCLKVgdkiqWXQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxlint/binding-linux-arm-musleabihf@1.50.0': - resolution: {integrity: sha512-dVp9iSssiGAnTNey2Ruf6xUaQhdnvcFOJyRWd/mu5o2jVbFK15E5fbWGeFRfmuobu5QXuROtFga44+7DOS3PLg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxlint/binding-linux-arm64-gnu@1.50.0': - resolution: {integrity: sha512-1cT7yz2HA910CKA9NkH1ZJo50vTtmND2fkoW1oyiSb0j6WvNtJ0Wx2zoySfXWc/c+7HFoqRK5AbEoL41LOn9oA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxlint/binding-linux-arm64-musl@1.50.0': - resolution: {integrity: sha512-++B3k/HEPFVlj89cOz8kWfQccMZB/aWL9AhsW7jPIkG++63Mpwb2cE9XOEsd0PATbIan78k2Gky+09uWM1d/gQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxlint/binding-linux-ppc64-gnu@1.50.0': - resolution: {integrity: sha512-Z9b/KpFMkx66w3gVBqjIC1AJBTZAGoI9+U+K5L4QM0CB/G0JSNC1es9b3Y0Vcrlvtdn8A+IQTkYjd/Q0uCSaZw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@oxlint/binding-linux-riscv64-gnu@1.50.0': - resolution: {integrity: sha512-jvmuIw8wRSohsQlFNIST5uUwkEtEJmOQYr33bf/K2FrFPXHhM4KqGekI3ShYJemFS/gARVacQFgBzzJKCAyJjg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxlint/binding-linux-riscv64-musl@1.50.0': - resolution: {integrity: sha512-x+UrN47oYNh90nmAAyql8eQaaRpHbDPu5guasDg10+OpszUQ3/1+1J6zFMmV4xfIEgTcUXG/oI5fxJhF4eWCNA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxlint/binding-linux-s390x-gnu@1.50.0': - resolution: {integrity: sha512-i/JLi2ljLUIVfekMj4ISmdt+Hn11wzYUdRRrkVUYsCWw7zAy5xV7X9iA+KMyM156LTFympa7s3oKBjuCLoTAUQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@oxlint/binding-linux-x64-gnu@1.50.0': - resolution: {integrity: sha512-/C7brhn6c6UUPccgSPCcpLQXcp+xKIW/3sji/5VZ8/OItL3tQ2U7KalHz887UxxSQeEOmd1kY6lrpuwFnmNqOA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxlint/binding-linux-x64-musl@1.50.0': - resolution: {integrity: sha512-oDR1f+bGOYU8LfgtEW8XtotWGB63ghtcxk5Jm6IDTCk++rTA/IRMsjOid2iMd+1bW+nP9Mdsmcdc7VbPD3+iyQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxlint/binding-openharmony-arm64@1.50.0': - resolution: {integrity: sha512-4CmRGPp5UpvXyu4jjP9Tey/SrXDQLRvZXm4pb4vdZBxAzbFZkCyh0KyRy4txld/kZKTJlW4TO8N1JKrNEk+mWw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxlint/binding-win32-arm64-msvc@1.50.0': - resolution: {integrity: sha512-Fq0M6vsGcFsSfeuWAACDhd5KJrO85ckbEfe1EGuBj+KPyJz7KeWte2fSFrFGmNKNXyhEMyx4tbgxiWRujBM2KQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxlint/binding-win32-ia32-msvc@1.50.0': - resolution: {integrity: sha512-qTdWR9KwY/vxJGhHVIZG2eBOhidOQvOwzDxnX+jhW/zIVacal1nAhR8GLkiywW8BIFDkQKXo/zOfT+/DY+ns/w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxlint/binding-win32-x64-msvc@1.50.0': - resolution: {integrity: sha512-682t7npLC4G2Ca+iNlI9fhAKTcFPYYXJjwoa88H4q+u5HHHlsnL/gHULapX3iqp+A8FIJbgdylL5KMYo2LaluQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@quansync/fs@1.0.0': - resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - - '@rolldown/binding-android-arm64@1.0.0-beta.53': - resolution: {integrity: sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-android-arm64@1.0.0-rc.5': - resolution: {integrity: sha512-zCEmUrt1bggwgBgeKLxNj217J1OrChrp3jJt24VK9jAharSTeVaHODNL+LpcQVhRz+FktYWfT9cjo5oZ99ZLpg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-beta.53': - resolution: {integrity: sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.5': - resolution: {integrity: sha512-ZP9xb9lPAex36pvkNWCjSEJW/Gfdm9I3ssiqOFLmpZ/vosPXgpoGxCmh+dX1Qs+/bWQE6toNFXWWL8vYoKoK9Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-beta.53': - resolution: {integrity: sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.5': - resolution: {integrity: sha512-7IdrPunf6dp9mywMgTOKMMGDnMHQ6+h5gRl6LW8rhD8WK2kXX0IwzcM5Zc0B5J7xQs8QWOlKjv8BJsU/1CD3pg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-beta.53': - resolution: {integrity: sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.5': - resolution: {integrity: sha512-o/JCk+dL0IN68EBhZ4DqfsfvxPfMeoM6cJtxORC1YYoxGHZyth2Kb2maXDb4oddw2wu8iIbnYXYPEzBtAF5CAg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53': - resolution: {integrity: sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5': - resolution: {integrity: sha512-IIBwTtA6VwxQLcEgq2mfrUgam7VvPZjhd/jxmeS1npM+edWsrrpRLHUdze+sk4rhb8/xpP3flemgcZXXUW6ukw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53': - resolution: {integrity: sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5': - resolution: {integrity: sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': - resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.5': - resolution: {integrity: sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': - resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.5': - resolution: {integrity: sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': - resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.5': - resolution: {integrity: sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': - resolution: {integrity: sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.5': - resolution: {integrity: sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.53': - resolution: {integrity: sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.5': - resolution: {integrity: sha512-LztXnGzv6t2u830mnZrFLRVqT/DPJ9DL4ZTz/y93rqUVkeHjMMYIYaFj+BUthiYxbVH9dH0SZYufETspKY/NhA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53': - resolution: {integrity: sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5': - resolution: {integrity: sha512-jUct1XVeGtyjqJXEAfvdFa8xoigYZ2rge7nYEm70ppQxpfH9ze2fbIrpHmP2tNM2vL/F6Dd0CpXhpjPbC6bSxQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53': - resolution: {integrity: sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.5': - resolution: {integrity: sha512-VQ8F9ld5gw29epjnVGdrx8ugiLTe8BMqmhDYy7nGbdeDo4HAt4bgdZvLbViEhg7DZyHLpiEUlO5/jPSUrIuxRQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-beta.53': - resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} - - '@rolldown/pluginutils@1.0.0-rc.3': - resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - - '@rolldown/pluginutils@1.0.0-rc.5': - resolution: {integrity: sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==} - - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} - cpu: [x64] - os: [win32] - - '@rushstack/node-core-library@5.20.3': - resolution: {integrity: sha512-95JgEPq2k7tHxhF9/OJnnyHDXfC9cLhhta0An/6MlkDsX2A6dTzDrTUG18vx4vjc280V0fi0xDH9iQczpSuWsw==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/problem-matcher@0.2.1': - resolution: {integrity: sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/rig-package@0.7.2': - resolution: {integrity: sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A==} - - '@rushstack/terminal@0.22.3': - resolution: {integrity: sha512-gHC9pIMrUPzAbBiI4VZMU7Q+rsCzb8hJl36lFIulIzoceKotyKL3Rd76AZ2CryCTKEg+0bnTj406HE5YY5OQvw==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/ts-command-line@5.3.3': - resolution: {integrity: sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@tanstack/query-core@5.90.20': - resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} - - '@tanstack/react-query@5.90.21': - resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} - peerDependencies: - react: ^18 || ^19 - - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - - '@types/argparse@1.0.38': - resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/jsesc@2.5.1': - resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/node@25.3.1': - resolution: {integrity: sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==} - - '@types/prompt-sync@4.2.3': - resolution: {integrity: sha512-Ox77gCSx0YyeakGt/qfOZUSFNSSi+sh3ABoGOiCwiO2KODx492BJnUm9oIXS+AHJtqp12iM4RduY6viTJ9bYwA==} - - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - - '@ucast/core@1.10.2': - resolution: {integrity: sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g==} - - '@ucast/js@3.1.0': - resolution: {integrity: sha512-eJ7yQeYtMK85UZjxoxBEbTWx6UMxEXKbjVyp+NlzrT5oMKV5Gpo/9bjTl3r7msaXTVC8iD9NJacqJ8yp7joX+Q==} - - '@ucast/mongo2js@1.4.1': - resolution: {integrity: sha512-9aeg5cmqwRQnKCXHN6I17wk83Rcm487bHelaG8T4vfpWneAI469wSI3Srnbu+PuZ5znWRbnwtVq9RgPL+bN6CA==} - - '@ucast/mongo@2.4.3': - resolution: {integrity: sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==} - - '@vitejs/plugin-react@5.1.4': - resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} - - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} - - '@volar/language-core@2.4.28': - resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} - - '@volar/source-map@2.4.28': - resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} - - '@volar/typescript@2.4.28': - resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} - - '@vue/compiler-core@3.5.29': - resolution: {integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==} - - '@vue/compiler-dom@3.5.29': - resolution: {integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==} - - '@vue/compiler-vue2@2.7.16': - resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - - '@vue/language-core@2.2.0': - resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@vue/shared@3.5.29': - resolution: {integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv-draft-04@1.0.0: - resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} - peerDependencies: - ajv: ^8.5.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - - alien-signals@0.4.14: - resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} - - ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - ansis@4.2.0: - resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} - engines: {node: '>=14'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-kit@3.0.0-beta.1: - resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} - engines: {node: '>=20.19.0'} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} - engines: {node: '>=6.0.0'} - hasBin: true - - birpc@4.0.0: - resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} - - brace-expansion@5.0.3: - resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} - engines: {node: 18 || 20 || >=22} - - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - caniuse-lite@1.0.30001774: - resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - compare-versions@6.1.1: - resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - de-indent@1.0.2: - resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} - engines: {node: '>=0.3.1'} - - dts-resolver@2.1.3: - resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} - engines: {node: '>=20.19.0'} - peerDependencies: - oxc-resolver: '>=11.0.0' - peerDependenciesMeta: - oxc-resolver: - optional: true - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - electron-to-chromium@1.5.302: - resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - empathic@2.0.0: - resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} - engines: {node: '>=14'} - - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} - engines: {node: '>= 6'} - - fs-extra@11.3.3: - resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} - engines: {node: '>=14.14'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} - engines: {node: '>=18'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - - hookable@6.0.1: - resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} - - i18next@25.8.13: - resolution: {integrity: sha512-E0vzjBY1yM+nsFrtgkjLhST2NBkirkvOVoQa0MSldhsuZ3jUge7ZNpuwG0Cfc74zwo5ZwRzg3uOgT+McBn32iA==} - peerDependencies: - typescript: ^5 - peerDependenciesMeta: - typescript: - optional: true - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - import-lazy@4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - - import-without-cache@0.2.5: - resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} - engines: {node: '>=20.19.0'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} - engines: {node: '>= 12.0.0'} - - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - minimatch@10.2.1: - resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==} - engines: {node: 20 || >=22} - - minimatch@9.0.8: - resolution: {integrity: sha512-reYkDYtj/b19TeqbNZCV4q9t+Yxylf/rYBsLb42SXJatTv4/ylq5lEiAmhA/IToxO7NI2UzNMghHoHuaqDkAjw==} - engines: {node: '>=16 || 14 >=14.17'} - - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - openapi-types@12.1.3: - resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - - oxfmt@0.34.0: - resolution: {integrity: sha512-t+zTE4XGpzPTK+Zk9gSwcJcFi4pqjl6PwO/ZxPBJiJQ2XCKMucwjPlHxvPHyVKJtkMSyrDGfQ7Ntg/hUr4OgHQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - oxlint-tsgolint@0.15.0: - resolution: {integrity: sha512-iwvFmhKQVZzVTFygUVI4t2S/VKEm+Mqkw3jQRJwfDuTcUYI5LCIYzdO5Dbuv4mFOkXZCcXaRRh0m+uydB5xdqw==} - hasBin: true - - oxlint@1.50.0: - resolution: {integrity: sha512-iSJ4IZEICBma8cZX7kxIIz9PzsYLF2FaLAYN6RKu7VwRVKdu7RIgpP99bTZaGl//Yao7fsaGZLSEo5xBrI5ReQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - oxlint-tsgolint: '>=0.14.1' - peerDependenciesMeta: - oxlint-tsgolint: - optional: true - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - prompt-sync@4.2.0: - resolution: {integrity: sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - quansync@1.0.0: - resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} - - radash@12.1.1: - resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==} - engines: {node: '>=14.18.0'} - - react-refresh@0.18.0: - resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} - engines: {node: '>=0.10.0'} - - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} - engines: {node: '>=0.10.0'} - - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - rolldown-plugin-dts@0.22.2: - resolution: {integrity: sha512-Ge+XF962Kobjr0hRPx1neVnLU2jpKkD2zevZTfPKf/0el4eYo9SyGPm0stiHDG2JQuL0Q3HLD0Kn+ST8esvVdA==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@ts-macro/tsc': ^0.3.6 - '@typescript/native-preview': '>=7.0.0-dev.20250601.1' - rolldown: ^1.0.0-rc.3 - typescript: ^5.0.0 || ^6.0.0-beta - vue-tsc: ~3.2.0 - peerDependenciesMeta: - '@ts-macro/tsc': - optional: true - '@typescript/native-preview': - optional: true - typescript: - optional: true - vue-tsc: - optional: true - - rolldown-vite@7.3.1: - resolution: {integrity: sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - esbuild: ^0.27.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - rolldown@1.0.0-beta.53: - resolution: {integrity: sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rolldown@1.0.0-rc.5: - resolution: {integrity: sha512-0AdalTs6hNTioaCYIkAa7+xsmHBfU5hCNclZnM/lp7lGGDuUOb6N4BVNtwiomybbencDjq/waKjTImqiGCs5sw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.59.0: - resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rou3@0.7.12: - resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - strip-ansi@5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} - engines: {node: '>=6'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} - engines: {node: '>=18'} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - tinypool@2.1.0: - resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} - engines: {node: ^20.0.0 || >=22.0.0} - - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-pattern@5.9.0: - resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} - - tsdown@0.21.0-beta.2: - resolution: {integrity: sha512-OKj8mKf0ws1ucxuEi3mO/OGyfRQxO9MY2D6SoIE/7RZcbojsZSBhJr4xC4MNivMqrQvi3Ke2e+aRZDemPBWPCw==} - engines: {node: '>=20.19.0'} - hasBin: true - peerDependencies: - '@arethetypeswrong/core': ^0.18.1 - '@vitejs/devtools': '*' - publint: ^0.3.0 - typescript: ^5.0.0 - unplugin-lightningcss: ^0.4.0 - unplugin-unused: ^0.5.0 - peerDependenciesMeta: - '@arethetypeswrong/core': - optional: true - '@vitejs/devtools': - optional: true - publint: - optional: true - typescript: - optional: true - unplugin-lightningcss: - optional: true - unplugin-unused: - optional: true - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-fest@5.4.4: - resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} - engines: {node: '>=20'} - - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - - unconfig-core@7.5.0: - resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unrun@0.2.28: - resolution: {integrity: sha512-LqMrI3ZEUMZ2476aCsbUTfy95CHByqez05nju4AQv4XFPkxh5yai7Di1/Qb0FoELHEEPDWhQi23EJeFyrBV0Og==} - engines: {node: '>=20.19.0'} - hasBin: true - peerDependencies: - synckit: ^0.11.11 - peerDependenciesMeta: - synckit: - optional: true - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - vite-plugin-dts@4.5.4: - resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} - peerDependencies: - typescript: '*' - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wildcard-match@5.1.4: - resolution: {integrity: sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==} - - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yargs@18.0.0: - resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - -snapshots: - - '@apidevtools/json-schema-ref-parser@11.7.2': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - js-yaml: 4.1.1 - - '@apidevtools/openapi-schemas@2.1.0': {} - - '@apidevtools/swagger-methods@3.0.2': {} - - '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)': - 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.18.0 - ajv-draft-04: 1.0.0(ajv@8.18.0) - call-me-maybe: 1.0.2 - openapi-types: 12.1.3 - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/generator@8.0.0-rc.1': - dependencies: - '@babel/parser': 8.0.0-rc.1 - '@babel/types': 8.0.0-rc.1 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@types/jsesc': 2.5.1 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-string-parser@8.0.0-rc.2': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-identifier@8.0.0-rc.1': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.0': - dependencies: - '@babel/types': 7.29.0 - - '@babel/parser@8.0.0-rc.1': - dependencies: - '@babel/types': 8.0.0-rc.1 - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/runtime@7.28.6': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@babel/types@8.0.0-rc.1': - dependencies: - '@babel/helper-string-parser': 8.0.0-rc.2 - '@babel/helper-validator-identifier': 8.0.0-rc.1 - - '@casl/ability@6.8.0': - dependencies: - '@ucast/mongo2js': 1.4.1 - - '@casl/react@5.0.1(@casl/ability@6.8.0)(react@19.2.4)': - dependencies: - '@casl/ability': 6.8.0 - react: 19.2.4 - - '@emnapi/core@1.8.1': - dependencies: - '@emnapi/wasi-threads': 1.1.0 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.8.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.1.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.27.3': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@esbuild/win32-x64@0.27.3': - optional: true - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@jsdevtools/ono@7.1.3': {} - - '@microsoft/api-extractor-model@7.33.4(@types/node@25.3.1)': - dependencies: - '@microsoft/tsdoc': 0.16.0 - '@microsoft/tsdoc-config': 0.18.1 - '@rushstack/node-core-library': 5.20.3(@types/node@25.3.1) - transitivePeerDependencies: - - '@types/node' - - '@microsoft/api-extractor@7.57.6(@types/node@25.3.1)': - dependencies: - '@microsoft/api-extractor-model': 7.33.4(@types/node@25.3.1) - '@microsoft/tsdoc': 0.16.0 - '@microsoft/tsdoc-config': 0.18.1 - '@rushstack/node-core-library': 5.20.3(@types/node@25.3.1) - '@rushstack/rig-package': 0.7.2 - '@rushstack/terminal': 0.22.3(@types/node@25.3.1) - '@rushstack/ts-command-line': 5.3.3(@types/node@25.3.1) - diff: 8.0.3 - lodash: 4.17.23 - minimatch: 10.2.1 - resolve: 1.22.11 - semver: 7.5.4 - source-map: 0.6.1 - typescript: 5.8.2 - transitivePeerDependencies: - - '@types/node' - - '@microsoft/tsdoc-config@0.18.1': - dependencies: - '@microsoft/tsdoc': 0.16.0 - ajv: 8.18.0 - jju: 1.4.0 - resolve: 1.22.11 - - '@microsoft/tsdoc@0.16.0': {} - - '@napi-rs/wasm-runtime@1.1.1': - dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 - '@tybys/wasm-util': 0.10.1 - optional: true - - '@orpc/client@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - '@orpc/standard-server-fetch': 1.14.6 - '@orpc/standard-server-peer': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/contract@1.14.6': - dependencies: - '@orpc/client': 1.14.6 - '@orpc/shared': 1.14.6 - '@standard-schema/spec': 1.1.0 - openapi-types: 12.1.3 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/interop@1.14.6': {} - - '@orpc/json-schema@1.14.6': - dependencies: - '@orpc/contract': 1.14.6 - '@orpc/interop': 1.14.6 - '@orpc/openapi': 1.14.6 - '@orpc/server': 1.14.6 - '@orpc/shared': 1.14.6 - json-schema-typed: 8.0.2 - transitivePeerDependencies: - - '@opentelemetry/api' - - crossws - - fastify - - ws - - '@orpc/openapi-client@1.14.6': - dependencies: - '@orpc/client': 1.14.6 - '@orpc/contract': 1.14.6 - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/openapi@1.14.6': - dependencies: - '@orpc/client': 1.14.6 - '@orpc/contract': 1.14.6 - '@orpc/interop': 1.14.6 - '@orpc/openapi-client': 1.14.6 - '@orpc/server': 1.14.6 - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - json-schema-typed: 8.0.2 - rou3: 0.7.12 - transitivePeerDependencies: - - '@opentelemetry/api' - - crossws - - fastify - - ws - - '@orpc/server@1.14.6': - dependencies: - '@orpc/client': 1.14.6 - '@orpc/contract': 1.14.6 - '@orpc/interop': 1.14.6 - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - '@orpc/standard-server-aws-lambda': 1.14.6 - '@orpc/standard-server-fastify': 1.14.6 - '@orpc/standard-server-fetch': 1.14.6 - '@orpc/standard-server-node': 1.14.6 - '@orpc/standard-server-peer': 1.14.6 - cookie: 1.1.1 - transitivePeerDependencies: - - '@opentelemetry/api' - - fastify - - '@orpc/shared@1.14.6': - dependencies: - radash: 12.1.1 - type-fest: 5.4.4 - - '@orpc/standard-server-aws-lambda@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - '@orpc/standard-server-fetch': 1.14.6 - '@orpc/standard-server-node': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/standard-server-fastify@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - '@orpc/standard-server-node': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/standard-server-fetch@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/standard-server-node@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - '@orpc/standard-server-fetch': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/standard-server-peer@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - '@orpc/standard-server': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/standard-server@1.14.6': - dependencies: - '@orpc/shared': 1.14.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@orpc/zod@1.14.6(@orpc/contract@1.14.6)(@orpc/server@1.14.6)(zod@4.3.6)': - dependencies: - '@orpc/contract': 1.14.6 - '@orpc/json-schema': 1.14.6 - '@orpc/openapi': 1.14.6 - '@orpc/server': 1.14.6 - '@orpc/shared': 1.14.6 - escape-string-regexp: 5.0.0 - wildcard-match: 5.1.4 - zod: 4.3.6 - transitivePeerDependencies: - - '@opentelemetry/api' - - crossws - - fastify - - ws - - '@oxc-project/runtime@0.101.0': {} - - '@oxc-project/types@0.101.0': {} - - '@oxc-project/types@0.114.0': {} - - '@oxfmt/binding-android-arm-eabi@0.34.0': - optional: true - - '@oxfmt/binding-android-arm64@0.34.0': - optional: true - - '@oxfmt/binding-darwin-arm64@0.34.0': - optional: true - - '@oxfmt/binding-darwin-x64@0.34.0': - optional: true - - '@oxfmt/binding-freebsd-x64@0.34.0': - optional: true - - '@oxfmt/binding-linux-arm-gnueabihf@0.34.0': - optional: true - - '@oxfmt/binding-linux-arm-musleabihf@0.34.0': - optional: true - - '@oxfmt/binding-linux-arm64-gnu@0.34.0': - optional: true - - '@oxfmt/binding-linux-arm64-musl@0.34.0': - optional: true - - '@oxfmt/binding-linux-ppc64-gnu@0.34.0': - optional: true - - '@oxfmt/binding-linux-riscv64-gnu@0.34.0': - optional: true - - '@oxfmt/binding-linux-riscv64-musl@0.34.0': - optional: true - - '@oxfmt/binding-linux-s390x-gnu@0.34.0': - optional: true - - '@oxfmt/binding-linux-x64-gnu@0.34.0': - optional: true - - '@oxfmt/binding-linux-x64-musl@0.34.0': - optional: true - - '@oxfmt/binding-openharmony-arm64@0.34.0': - optional: true - - '@oxfmt/binding-win32-arm64-msvc@0.34.0': - optional: true - - '@oxfmt/binding-win32-ia32-msvc@0.34.0': - optional: true - - '@oxfmt/binding-win32-x64-msvc@0.34.0': - optional: true - - '@oxlint-tsgolint/darwin-arm64@0.15.0': - optional: true - - '@oxlint-tsgolint/darwin-x64@0.15.0': - optional: true - - '@oxlint-tsgolint/linux-arm64@0.15.0': - optional: true - - '@oxlint-tsgolint/linux-x64@0.15.0': - optional: true - - '@oxlint-tsgolint/win32-arm64@0.15.0': - optional: true - - '@oxlint-tsgolint/win32-x64@0.15.0': - optional: true - - '@oxlint/binding-android-arm-eabi@1.50.0': - optional: true - - '@oxlint/binding-android-arm64@1.50.0': - optional: true - - '@oxlint/binding-darwin-arm64@1.50.0': - optional: true - - '@oxlint/binding-darwin-x64@1.50.0': - optional: true - - '@oxlint/binding-freebsd-x64@1.50.0': - optional: true - - '@oxlint/binding-linux-arm-gnueabihf@1.50.0': - optional: true - - '@oxlint/binding-linux-arm-musleabihf@1.50.0': - optional: true - - '@oxlint/binding-linux-arm64-gnu@1.50.0': - optional: true - - '@oxlint/binding-linux-arm64-musl@1.50.0': - optional: true - - '@oxlint/binding-linux-ppc64-gnu@1.50.0': - optional: true - - '@oxlint/binding-linux-riscv64-gnu@1.50.0': - optional: true - - '@oxlint/binding-linux-riscv64-musl@1.50.0': - optional: true - - '@oxlint/binding-linux-s390x-gnu@1.50.0': - optional: true - - '@oxlint/binding-linux-x64-gnu@1.50.0': - optional: true - - '@oxlint/binding-linux-x64-musl@1.50.0': - optional: true - - '@oxlint/binding-openharmony-arm64@1.50.0': - optional: true - - '@oxlint/binding-win32-arm64-msvc@1.50.0': - optional: true - - '@oxlint/binding-win32-ia32-msvc@1.50.0': - optional: true - - '@oxlint/binding-win32-x64-msvc@1.50.0': - optional: true - - '@quansync/fs@1.0.0': - dependencies: - quansync: 1.0.0 - - '@rolldown/binding-android-arm64@1.0.0-beta.53': - optional: true - - '@rolldown/binding-android-arm64@1.0.0-rc.5': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-beta.53': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.5': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-beta.53': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.5': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-beta.53': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.5': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.5': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.5': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.5': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.5': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.53': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.5': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53': - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.5': - optional: true - - '@rolldown/pluginutils@1.0.0-beta.53': {} - - '@rolldown/pluginutils@1.0.0-rc.3': {} - - '@rolldown/pluginutils@1.0.0-rc.5': {} - - '@rollup/pluginutils@5.3.0(rollup@4.59.0)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.59.0 - - '@rollup/rollup-android-arm-eabi@4.59.0': - optional: true - - '@rollup/rollup-android-arm64@4.59.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.59.0': - optional: true - - '@rollup/rollup-darwin-x64@4.59.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.59.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.59.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.59.0': - optional: true - - '@rollup/rollup-openbsd-x64@4.59.0': - optional: true - - '@rollup/rollup-openharmony-arm64@4.59.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.59.0': - optional: true - - '@rushstack/node-core-library@5.20.3(@types/node@25.3.1)': - dependencies: - ajv: 8.18.0 - ajv-draft-04: 1.0.0(ajv@8.18.0) - ajv-formats: 3.0.1(ajv@8.18.0) - fs-extra: 11.3.3 - import-lazy: 4.0.0 - jju: 1.4.0 - resolve: 1.22.11 - semver: 7.5.4 - optionalDependencies: - '@types/node': 25.3.1 - - '@rushstack/problem-matcher@0.2.1(@types/node@25.3.1)': - optionalDependencies: - '@types/node': 25.3.1 - - '@rushstack/rig-package@0.7.2': - dependencies: - resolve: 1.22.11 - strip-json-comments: 3.1.1 - - '@rushstack/terminal@0.22.3(@types/node@25.3.1)': - dependencies: - '@rushstack/node-core-library': 5.20.3(@types/node@25.3.1) - '@rushstack/problem-matcher': 0.2.1(@types/node@25.3.1) - supports-color: 8.1.1 - optionalDependencies: - '@types/node': 25.3.1 - - '@rushstack/ts-command-line@5.3.3(@types/node@25.3.1)': - dependencies: - '@rushstack/terminal': 0.22.3(@types/node@25.3.1) - '@types/argparse': 1.0.38 - argparse: 1.0.10 - string-argv: 0.3.2 - transitivePeerDependencies: - - '@types/node' - - '@standard-schema/spec@1.1.0': {} - - '@tanstack/query-core@5.90.20': {} - - '@tanstack/react-query@5.90.21(react@19.2.4)': - dependencies: - '@tanstack/query-core': 5.90.20 - react: 19.2.4 - - '@tybys/wasm-util@0.10.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/argparse@1.0.38': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/jsesc@2.5.1': {} - - '@types/json-schema@7.0.15': {} - - '@types/node@25.3.1': - dependencies: - undici-types: 7.18.2 - - '@types/prompt-sync@4.2.3': {} - - '@types/react@19.2.14': - dependencies: - csstype: 3.2.3 - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@17.0.35': - dependencies: - '@types/yargs-parser': 21.0.3 - - '@ucast/core@1.10.2': {} - - '@ucast/js@3.1.0': - dependencies: - '@ucast/core': 1.10.2 - - '@ucast/mongo2js@1.4.1': - dependencies: - '@ucast/core': 1.10.2 - '@ucast/js': 3.1.0 - '@ucast/mongo': 2.4.3 - - '@ucast/mongo@2.4.3': - dependencies: - '@ucast/core': 1.10.2 - - '@vitejs/plugin-react@5.1.4(rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-rc.3 - '@types/babel__core': 7.20.5 - react-refresh: 0.18.0 - vite: rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@4.0.18': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - chai: 6.2.2 - tinyrainbow: 3.0.3 - - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0) - - '@vitest/pretty-format@4.0.18': - dependencies: - tinyrainbow: 3.0.3 - - '@vitest/runner@4.0.18': - dependencies: - '@vitest/utils': 4.0.18 - pathe: 2.0.3 - - '@vitest/snapshot@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.0.18': {} - - '@vitest/utils@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 - - '@volar/language-core@2.4.28': - dependencies: - '@volar/source-map': 2.4.28 - - '@volar/source-map@2.4.28': {} - - '@volar/typescript@2.4.28': - dependencies: - '@volar/language-core': 2.4.28 - path-browserify: 1.0.1 - vscode-uri: 3.1.0 - - '@vue/compiler-core@3.5.29': - dependencies: - '@babel/parser': 7.29.0 - '@vue/shared': 3.5.29 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - - '@vue/compiler-dom@3.5.29': - dependencies: - '@vue/compiler-core': 3.5.29 - '@vue/shared': 3.5.29 - - '@vue/compiler-vue2@2.7.16': - dependencies: - de-indent: 1.0.2 - he: 1.2.0 - - '@vue/language-core@2.2.0(typescript@5.9.3)': - dependencies: - '@volar/language-core': 2.4.28 - '@vue/compiler-dom': 3.5.29 - '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.29 - alien-signals: 0.4.14 - minimatch: 9.0.8 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - optionalDependencies: - typescript: 5.9.3 - - '@vue/shared@3.5.29': {} - - acorn@8.16.0: {} - - ajv-draft-04@1.0.0(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - - ajv-formats@3.0.1(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - alien-signals@0.4.14: {} - - ansi-regex@4.1.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@6.2.3: {} - - ansis@4.2.0: {} - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - argparse@2.0.1: {} - - assertion-error@2.0.1: {} - - ast-kit@3.0.0-beta.1: - dependencies: - '@babel/parser': 8.0.0-rc.1 - estree-walker: 3.0.3 - pathe: 2.0.3 - - asynckit@0.4.0: {} - - axios@1.13.5: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.5 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - balanced-match@4.0.4: {} - - baseline-browser-mapping@2.10.0: {} - - birpc@4.0.0: {} - - brace-expansion@5.0.3: - dependencies: - balanced-match: 4.0.4 - - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001774 - electron-to-chromium: 1.5.302 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - - cac@6.7.14: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-me-maybe@1.0.2: {} - - callsites@3.1.0: {} - - caniuse-lite@1.0.30001774: {} - - chai@6.2.2: {} - - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.1.2 - wrap-ansi: 9.0.2 - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - compare-versions@6.1.1: {} - - confbox@0.1.8: {} - - confbox@0.2.4: {} - - convert-source-map@2.0.0: {} - - cookie@1.1.1: {} - - csstype@3.2.3: {} - - de-indent@1.0.2: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - defu@6.1.4: {} - - delayed-stream@1.0.0: {} - - detect-libc@2.1.2: {} - - diff@8.0.3: {} - - dts-resolver@2.1.3: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - electron-to-chromium@1.5.302: {} - - emoji-regex@10.6.0: {} - - empathic@2.0.0: {} - - entities@7.0.1: {} - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-module-lexer@1.7.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - - escalade@3.2.0: {} - - escape-string-regexp@5.0.0: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - expect-type@1.3.0: {} - - exsolve@1.0.8: {} - - fast-deep-equal@3.1.3: {} - - fast-uri@3.1.0: {} - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - follow-redirects@1.15.11: {} - - form-data@4.0.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - fs-extra@11.3.3: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - gensync@1.0.0-beta.2: {} - - get-caller-file@2.0.5: {} - - get-east-asian-width@1.5.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-tsconfig@4.13.6: - dependencies: - resolve-pkg-maps: 1.0.0 - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - has-flag@4.0.0: {} - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - he@1.2.0: {} - - hookable@6.0.1: {} - - i18next@25.8.13(typescript@5.9.3): - dependencies: - '@babel/runtime': 7.28.6 - optionalDependencies: - typescript: 5.9.3 - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - import-lazy@4.0.0: {} - - import-without-cache@0.2.5: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - jju@1.4.0: {} - - js-tokens@4.0.0: {} - - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - - jsesc@3.1.0: {} - - json-schema-traverse@1.0.0: {} - - json-schema-typed@8.0.2: {} - - json5@2.2.3: {} - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - kolorist@1.8.0: {} - - lightningcss-android-arm64@1.31.1: - optional: true - - lightningcss-darwin-arm64@1.31.1: - optional: true - - lightningcss-darwin-x64@1.31.1: - optional: true - - lightningcss-freebsd-x64@1.31.1: - optional: true - - lightningcss-linux-arm-gnueabihf@1.31.1: - optional: true - - lightningcss-linux-arm64-gnu@1.31.1: - optional: true - - lightningcss-linux-arm64-musl@1.31.1: - optional: true - - lightningcss-linux-x64-gnu@1.31.1: - optional: true - - lightningcss-linux-x64-musl@1.31.1: - optional: true - - lightningcss-win32-arm64-msvc@1.31.1: - optional: true - - lightningcss-win32-x64-msvc@1.31.1: - optional: true - - lightningcss@1.31.1: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.31.1 - lightningcss-darwin-arm64: 1.31.1 - lightningcss-darwin-x64: 1.31.1 - lightningcss-freebsd-x64: 1.31.1 - lightningcss-linux-arm-gnueabihf: 1.31.1 - lightningcss-linux-arm64-gnu: 1.31.1 - lightningcss-linux-arm64-musl: 1.31.1 - lightningcss-linux-x64-gnu: 1.31.1 - lightningcss-linux-x64-musl: 1.31.1 - lightningcss-win32-arm64-msvc: 1.31.1 - lightningcss-win32-x64-msvc: 1.31.1 - - local-pkg@1.1.2: - dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 - quansync: 0.2.11 - - lodash@4.17.23: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - math-intrinsics@1.1.0: {} - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - minimatch@10.2.1: - dependencies: - brace-expansion: 5.0.3 - - minimatch@9.0.8: - dependencies: - brace-expansion: 5.0.3 - - mlly@1.8.0: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 - - ms@2.1.3: {} - - muggle-string@0.4.1: {} - - nanoid@3.3.11: {} - - node-releases@2.0.27: {} - - obug@2.1.1: {} - - openapi-types@12.1.3: {} - - oxfmt@0.34.0: - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.34.0 - '@oxfmt/binding-android-arm64': 0.34.0 - '@oxfmt/binding-darwin-arm64': 0.34.0 - '@oxfmt/binding-darwin-x64': 0.34.0 - '@oxfmt/binding-freebsd-x64': 0.34.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.34.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.34.0 - '@oxfmt/binding-linux-arm64-gnu': 0.34.0 - '@oxfmt/binding-linux-arm64-musl': 0.34.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.34.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.34.0 - '@oxfmt/binding-linux-riscv64-musl': 0.34.0 - '@oxfmt/binding-linux-s390x-gnu': 0.34.0 - '@oxfmt/binding-linux-x64-gnu': 0.34.0 - '@oxfmt/binding-linux-x64-musl': 0.34.0 - '@oxfmt/binding-openharmony-arm64': 0.34.0 - '@oxfmt/binding-win32-arm64-msvc': 0.34.0 - '@oxfmt/binding-win32-ia32-msvc': 0.34.0 - '@oxfmt/binding-win32-x64-msvc': 0.34.0 - - oxlint-tsgolint@0.15.0: - optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.15.0 - '@oxlint-tsgolint/darwin-x64': 0.15.0 - '@oxlint-tsgolint/linux-arm64': 0.15.0 - '@oxlint-tsgolint/linux-x64': 0.15.0 - '@oxlint-tsgolint/win32-arm64': 0.15.0 - '@oxlint-tsgolint/win32-x64': 0.15.0 - - oxlint@1.50.0(oxlint-tsgolint@0.15.0): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.50.0 - '@oxlint/binding-android-arm64': 1.50.0 - '@oxlint/binding-darwin-arm64': 1.50.0 - '@oxlint/binding-darwin-x64': 1.50.0 - '@oxlint/binding-freebsd-x64': 1.50.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.50.0 - '@oxlint/binding-linux-arm-musleabihf': 1.50.0 - '@oxlint/binding-linux-arm64-gnu': 1.50.0 - '@oxlint/binding-linux-arm64-musl': 1.50.0 - '@oxlint/binding-linux-ppc64-gnu': 1.50.0 - '@oxlint/binding-linux-riscv64-gnu': 1.50.0 - '@oxlint/binding-linux-riscv64-musl': 1.50.0 - '@oxlint/binding-linux-s390x-gnu': 1.50.0 - '@oxlint/binding-linux-x64-gnu': 1.50.0 - '@oxlint/binding-linux-x64-musl': 1.50.0 - '@oxlint/binding-openharmony-arm64': 1.50.0 - '@oxlint/binding-win32-arm64-msvc': 1.50.0 - '@oxlint/binding-win32-ia32-msvc': 1.50.0 - '@oxlint/binding-win32-x64-msvc': 1.50.0 - oxlint-tsgolint: 0.15.0 - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - path-browserify@1.0.1: {} - - path-parse@1.0.7: {} - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@4.0.3: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.0 - pathe: 2.0.3 - - pkg-types@2.3.0: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prompt-sync@4.2.0: - dependencies: - strip-ansi: 5.2.0 - - proxy-from-env@1.1.0: {} - - quansync@0.2.11: {} - - quansync@1.0.0: {} - - radash@12.1.1: {} - - react-refresh@0.18.0: {} - - react@19.2.4: {} - - reflect-metadata@0.2.2: {} - - require-from-string@2.0.2: {} - - resolve-from@4.0.0: {} - - resolve-pkg-maps@1.0.0: {} - - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rolldown-plugin-dts@0.22.2(rolldown@1.0.0-rc.5)(typescript@5.9.3): - dependencies: - '@babel/generator': 8.0.0-rc.1 - '@babel/helper-validator-identifier': 8.0.0-rc.1 - '@babel/parser': 8.0.0-rc.1 - '@babel/types': 8.0.0-rc.1 - ast-kit: 3.0.0-beta.1 - birpc: 4.0.0 - dts-resolver: 2.1.3 - get-tsconfig: 4.13.6 - obug: 2.1.1 - rolldown: 1.0.0-rc.5 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - oxc-resolver - - rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0): - dependencies: - '@oxc-project/runtime': 0.101.0 - fdir: 6.5.0(picomatch@4.0.3) - lightningcss: 1.31.1 - picomatch: 4.0.3 - postcss: 8.5.6 - rolldown: 1.0.0-beta.53 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.3.1 - esbuild: 0.27.3 - fsevents: 2.3.3 - tsx: 4.21.0 - - rolldown@1.0.0-beta.53: - dependencies: - '@oxc-project/types': 0.101.0 - '@rolldown/pluginutils': 1.0.0-beta.53 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.53 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.53 - '@rolldown/binding-darwin-x64': 1.0.0-beta.53 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.53 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.53 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.53 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.53 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.53 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.53 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.53 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.53 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53 - - rolldown@1.0.0-rc.5: - dependencies: - '@oxc-project/types': 0.114.0 - '@rolldown/pluginutils': 1.0.0-rc.5 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.5 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.5 - '@rolldown/binding-darwin-x64': 1.0.0-rc.5 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.5 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.5 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.5 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.5 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.5 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.5 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.5 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.5 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.5 - - rollup@4.59.0: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 - fsevents: 2.3.3 - - rou3@0.7.12: {} - - semver@6.3.1: {} - - semver@7.5.4: - dependencies: - lru-cache: 6.0.0 - - semver@7.7.4: {} - - siginfo@2.0.0: {} - - source-map-js@1.2.1: {} - - source-map@0.6.1: {} - - sprintf-js@1.0.3: {} - - stackback@0.0.2: {} - - std-env@3.10.0: {} - - string-argv@0.3.2: {} - - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 - strip-ansi: 7.1.2 - - strip-ansi@5.2.0: - dependencies: - ansi-regex: 4.1.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - - strip-json-comments@3.1.1: {} - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - tagged-tag@1.0.0: {} - - tinybench@2.9.0: {} - - tinyexec@1.0.2: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinypool@2.1.0: {} - - tinyrainbow@3.0.3: {} - - tree-kill@1.2.2: {} - - ts-pattern@5.9.0: {} - - tsdown@0.21.0-beta.2(typescript@5.9.3): - dependencies: - ansis: 4.2.0 - cac: 6.7.14 - defu: 6.1.4 - empathic: 2.0.0 - hookable: 6.0.1 - import-without-cache: 0.2.5 - obug: 2.1.1 - picomatch: 4.0.3 - rolldown: 1.0.0-rc.5 - rolldown-plugin-dts: 0.22.2(rolldown@1.0.0-rc.5)(typescript@5.9.3) - semver: 7.7.4 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tree-kill: 1.2.2 - unconfig-core: 7.5.0 - unrun: 0.2.28 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@ts-macro/tsc' - - '@typescript/native-preview' - - oxc-resolver - - synckit - - vue-tsc - - tslib@2.8.1: - optional: true - - tsx@4.21.0: - dependencies: - esbuild: 0.27.3 - get-tsconfig: 4.13.6 - optionalDependencies: - fsevents: 2.3.3 - - type-fest@5.4.4: - dependencies: - tagged-tag: 1.0.0 - - typescript@5.8.2: {} - - typescript@5.9.3: {} - - ufo@1.6.3: {} - - unconfig-core@7.5.0: - dependencies: - '@quansync/fs': 1.0.0 - quansync: 1.0.0 - - undici-types@7.18.2: {} - - universalify@2.0.1: {} - - unrun@0.2.28: - dependencies: - rolldown: 1.0.0-rc.5 - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - - vite-plugin-dts@4.5.4(@types/node@25.3.1)(rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0))(rollup@4.59.0)(typescript@5.9.3): - dependencies: - '@microsoft/api-extractor': 7.57.6(@types/node@25.3.1) - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - '@volar/typescript': 2.4.28 - '@vue/language-core': 2.2.0(typescript@5.9.3) - compare-versions: 6.1.1 - debug: 4.4.3 - kolorist: 1.8.0 - local-pkg: 1.1.2 - magic-string: 0.30.21 - typescript: 5.9.3 - optionalDependencies: - vite: rolldown-vite@7.3.1(@types/node@25.3.1)(esbuild@0.27.3)(tsx@4.21.0) - transitivePeerDependencies: - - '@types/node' - - rollup - - supports-color - - vite@7.3.1(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0): - dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.59.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.3.1 - fsevents: 2.3.3 - lightningcss: 1.31.1 - tsx: 4.21.0 - - vitest@4.0.18(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0): - dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.1)(lightningcss@1.31.1)(tsx@4.21.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.3.1 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - vscode-uri@3.1.0: {} - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wildcard-match@5.1.4: {} - - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.1.2 - - y18n@5.0.8: {} - - yallist@3.1.1: {} - - yallist@4.0.0: {} - - yargs-parser@22.0.0: {} - - yargs@18.0.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 7.2.0 - y18n: 5.0.8 - yargs-parser: 22.0.0 - - zod@4.3.6: {} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..2fc3eef2 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +profile = "minimal" diff --git a/scripts/benchmark-vite-codegen.mjs b/scripts/benchmark-vite-codegen.mjs index ec673afc..caf8226d 100644 --- a/scripts/benchmark-vite-codegen.mjs +++ b/scripts/benchmark-vite-codegen.mjs @@ -8,6 +8,8 @@ const DATA_DIR = path.join(EXAMPLE_ROOT, "src/data"); const DIST_INDEX = path.join(ROOT, "dist/index.mjs"); const DIST_VITE = path.join(ROOT, "dist/vite.mjs"); const SNAPSHOT_OPENAPI = path.join(ROOT, "test/benchmarks/openapi.localhost4000.json"); +const OPENAPI_INPUT = fs.existsSync(SNAPSHOT_OPENAPI) ? SNAPSHOT_OPENAPI : path.join(ROOT, "test/petstore.yaml"); +const ITERATIONS = Number.parseInt(process.env.OPENAPI_CODEGEN_BENCH_ITERATIONS ?? "5", 10); function run(command, args, env = {}) { const start = process.hrtime.bigint(); @@ -32,17 +34,18 @@ function run(command, args, env = {}) { function ensureBuiltPackage() { if (!fs.existsSync(DIST_INDEX) || !fs.existsSync(DIST_VITE)) { console.log("Building package (dist)..."); - run("yarn", ["build"]); + run("bun", ["run", "build"]); } } function ensureBaselineGeneratedData() { fs.rmSync(DATA_DIR, { recursive: true, force: true }); - run("yarn", [ + run("bun", [ + "run", "start", "generate", "--input", - "./test/petstore.yaml", + OPENAPI_INPUT, "--output", "./test/vite-example/src/data", "--importPath", @@ -52,17 +55,14 @@ function ensureBaselineGeneratedData() { } function buildBaseline() { - return run("yarn", ["vite", "build", "--config", "test/vite-example/vite.base.config.ts"]); + return run("bun", ["x", "vite", "build", "--config", "test/vite-example/vite.base.config.ts"]); } -function buildWithCodegen(incremental) { - if (!incremental) { - fs.rmSync(DATA_DIR, { recursive: true, force: true }); - } - const openApiInput = fs.existsSync(SNAPSHOT_OPENAPI) ? SNAPSHOT_OPENAPI : path.join(ROOT, "test/petstore.yaml"); - return run("yarn", ["vite", "build", "--config", "test/vite-example/vite.codegen.config.ts"], { - OPENAPI_CODEGEN_INCREMENTAL: incremental ? "true" : "false", - OPENAPI_CODEGEN_INPUT: openApiInput, +function buildWithCodegen() { + fs.rmSync(DATA_DIR, { recursive: true, force: true }); + return run("bun", ["x", "vite", "build", "--config", "test/vite-example/vite.codegen.config.ts"], { + OPENAPI_CODEGEN_INCREMENTAL: "false", + OPENAPI_CODEGEN_INPUT: OPENAPI_INPUT, }); } @@ -70,32 +70,40 @@ function pctDiff(value, baseline) { return ((value - baseline) / baseline) * 100; } -function printMs(label, ms) { - console.log(`${label}: ${ms.toFixed(1)}ms`); +function median(values) { + const sorted = [...values].sort((a, b) => a - b); + const middle = Math.floor(sorted.length / 2); + return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle]; +} + +function printMeasurements(label, values) { + console.log( + `${label}: ${median(values).toFixed(1)}ms median (${values.map((value) => value.toFixed(1)).join(", ")})`, + ); } function main() { ensureBuiltPackage(); ensureBaselineGeneratedData(); - const baselineMs = buildBaseline(); - const codegenColdMs = buildWithCodegen(false); + const baselineMeasurements = []; + const codegenColdMeasurements = []; + for (let iteration = 0; iteration < ITERATIONS; iteration += 1) { + ensureBaselineGeneratedData(); + baselineMeasurements.push(buildBaseline()); + codegenColdMeasurements.push(buildWithCodegen()); + } - // Prime cache once; measure second incremental run. - buildWithCodegen(true); - const codegenWarmMs = buildWithCodegen(true); + const baselineMs = median(baselineMeasurements); + const codegenColdMs = median(codegenColdMeasurements); console.log("\nVite build benchmark (test/vite-example):"); + console.log(`OpenAPI input: ${path.relative(ROOT, OPENAPI_INPUT)}`); + console.log(`Iterations: ${ITERATIONS}`); + printMeasurements("baseline (same generated sources, no plugin)", baselineMeasurements); + printMeasurements("with openApiCodegen (cold output)", codegenColdMeasurements); console.log( - `OpenAPI input: ${fs.existsSync(SNAPSHOT_OPENAPI) ? "test/benchmarks/openapi.localhost4000.json" : "test/petstore.yaml (fallback)"}`, - ); - printMs("baseline (no plugin)", baselineMs); - printMs("with openApiCodegen (incremental=false)", codegenColdMs); - printMs("with openApiCodegen (incremental=true, warm)", codegenWarmMs); - console.log( - `\nOverhead vs baseline:\n` + - `cold: ${pctDiff(codegenColdMs, baselineMs).toFixed(1)}%\n` + - `warm: ${pctDiff(codegenWarmMs, baselineMs).toFixed(1)}%`, + `\nCold codegen overhead: ${(codegenColdMs - baselineMs).toFixed(1)}ms (${pctDiff(codegenColdMs, baselineMs).toFixed(1)}%)`, ); } diff --git a/scripts/build-native.mjs b/scripts/build-native.mjs new file mode 100644 index 00000000..09e09834 --- /dev/null +++ b/scripts/build-native.mjs @@ -0,0 +1,42 @@ +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import path from "node:path"; + +const root = process.cwd(); +const manifest = path.join(root, "native", "Cargo.toml"); +const userCargo = process.env.HOME ? path.join(process.env.HOME, ".cargo", "bin", "cargo") : undefined; +const brewRustup = "/opt/homebrew/opt/rustup/bin/rustup"; +let command = userCargo && existsSync(userCargo) ? userCargo : "cargo"; +let toolchainBin; +if (command === "cargo" && existsSync(brewRustup)) { + const resolved = spawnSync(brewRustup, ["which", "cargo"], { encoding: "utf8" }); + if (resolved.status === 0) { + command = resolved.stdout.trim(); + toolchainBin = path.dirname(command); + } +} +const release = process.env.OPENAPI_NATIVE_FAST_BUILD !== "1"; +const args = ["build", ...(release ? ["--release"] : []), "--manifest-path", manifest]; +const result = spawnSync(command, args, { + cwd: root, + env: toolchainBin ? { ...process.env, PATH: `${toolchainBin}:${process.env.PATH ?? ""}` } : process.env, + stdio: "inherit", +}); +if (result.status !== 0) { + process.exit(result.status ?? 1); +} + +const libraryName = + process.platform === "win32" + ? "openapi_codegen_native.dll" + : process.platform === "darwin" + ? "libopenapi_codegen_native.dylib" + : "libopenapi_codegen_native.so"; +const source = path.join(root, "native", "target", release ? "release" : "debug", libraryName); +const outputDir = path.join(root, "dist"); +mkdirSync(outputDir, { recursive: true }); +const output = path.join(outputDir, `openapi-codegen-native-${process.platform}-${process.arch}.node`); +copyFileSync(source, output); +if (process.platform === "darwin") { + spawnSync("xattr", ["-d", "com.apple.provenance", output], { stdio: "ignore" }); +} diff --git a/scripts/publish.mjs b/scripts/publish.mjs index ce9a5306..a7e848cb 100644 --- a/scripts/publish.mjs +++ b/scripts/publish.mjs @@ -21,7 +21,7 @@ function main() { // build, just in case console.log("Building..."); - execSync("pnpm build", { stdio: "inherit" }); + execSync("bun run build", { stdio: "inherit" }); // read version from package.json const pkg = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), "package.json"), "utf8")); diff --git a/scripts/verify-tree-shaking.mjs b/scripts/verify-tree-shaking.mjs new file mode 100644 index 00000000..656a4a52 --- /dev/null +++ b/scripts/verify-tree-shaking.mjs @@ -0,0 +1,47 @@ +import { rolldown } from "rolldown"; +import { gzipSync } from "node:zlib"; + +const cases = [ + { + name: "native subpath", + source: 'import { NativeRestClient } from "@povio/openapi-codegen-cli/native"; export { NativeRestClient };', + forbidden: ["axios", "react", "ErrorHandler"], + }, + { + name: "native root export", + source: 'import { NativeRestClient } from "@povio/openapi-codegen-cli"; export { NativeRestClient };', + forbidden: ["axios", "react", "ErrorHandler"], + }, + { + name: "transport types runtime", + source: 'import { HttpError } from "@povio/openapi-codegen-cli/rest"; export { HttpError };', + forbidden: ["axios", "react", "zod", "NativeRestClient"], + }, +]; + +for (const testCase of cases) { + const build = await rolldown({ + input: "virtual:entry", + cwd: process.cwd(), + platform: "browser", + external: ["zod"], + plugins: [ + { + name: "tree-shaking-entry", + resolveId(id) { + if (id === "virtual:entry") return id; + }, + load(id) { + if (id === "virtual:entry") return testCase.source; + }, + }, + ], + }); + const { output } = await build.generate({ format: "esm", minify: true }); + const code = output.find(({ type }) => type === "chunk")?.code ?? ""; + for (const forbidden of testCase.forbidden) { + if (code.includes(forbidden)) throw new Error(`${testCase.name} retained ${forbidden}`); + } + console.log(`${testCase.name}: ${code.length} B (${gzipSync(code).length} B gzip)`); + await build.close(); +} diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 00000000..447f89ab --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,3 @@ +export { AuthContext } from "./lib/auth/auth.context"; +export { AuthGuard } from "./lib/auth/AuthGuard"; +export type { AuthGuardProps } from "./lib/auth/AuthGuard"; diff --git a/src/axios.ts b/src/axios.ts new file mode 100644 index 00000000..00724bd4 --- /dev/null +++ b/src/axios.ts @@ -0,0 +1,3 @@ +export { RestClient } from "./lib/rest/rest-client"; +export { RestInterceptor } from "./lib/rest/rest-interceptor"; +export type { RequestInfo, RequestConfig, Response, RestClient as IRestClient } from "./lib/rest/rest-client.types"; diff --git a/src/commands/check.command.ts b/src/commands/check.command.ts index 22a25880..7be74954 100644 --- a/src/commands/check.command.ts +++ b/src/commands/check.command.ts @@ -1,4 +1,4 @@ -import yargs from "yargs"; +import type { CommandModule } from "yargs"; import { logBanner } from "@/helpers/cli.helper"; import { getVersion } from "@/helpers/version.helper"; @@ -35,7 +35,7 @@ class CheckOptions implements CheckParams { verbose?: boolean; } -export const command: yargs.CommandModule = { +export const command: CommandModule = { command: "check", describe: "Check OpenAPI spec", builder: getBuilder(CheckOptions), diff --git a/src/commands/check.ts b/src/commands/check.ts index 74ac6f90..ff797f34 100644 --- a/src/commands/check.ts +++ b/src/commands/check.ts @@ -1,11 +1,9 @@ -import { OpenAPIV3 } from "openapi-types"; - import { checkOpenAPIDoc } from "@/generators/checkOpenAPIDoc"; import { resolveConfig } from "@/generators/core/resolveConfig"; +import { getOpenApiDoc } from "@/generators/run/generate.runner"; import { GenerateOptions } from "@/generators/types/options"; import { logInfo, logSuccess } from "@/helpers/cli.helper"; import { loadConfig } from "@/helpers/config.helper"; -import SwaggerParser from "@apidevtools/swagger-parser"; export type CheckParams = { config?: string; @@ -32,7 +30,7 @@ export async function check({ if (verbose) { logInfo("Parsing OpenAPI spec..."); } - const openApiDoc = (await SwaggerParser.bundle(config.input)) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc(config.input); if (verbose) { logInfo("Running check..."); diff --git a/src/commands/generate.command.ts b/src/commands/generate.command.ts index 70d09936..9248c106 100644 --- a/src/commands/generate.command.ts +++ b/src/commands/generate.command.ts @@ -1,4 +1,4 @@ -import yargs from "yargs"; +import type { CommandModule } from "yargs"; import { logBanner } from "@/helpers/cli.helper"; import { getVersion } from "@/helpers/version.helper"; @@ -25,6 +25,9 @@ class GenerateOptions implements GenerateParams { @YargOption({ envAlias: "tsNamespaces", type: "boolean" }) tsNamespaces?: boolean; + @YargOption({ envAlias: "treeShakeableNamespaces", type: "boolean" }) + treeShakeableNamespaces?: boolean; + @YargOption({ envAlias: "tsPath" }) tsPath?: string; @@ -55,6 +58,21 @@ class GenerateOptions implements GenerateParams { @YargOption({ envAlias: "modelsInCommon", type: "boolean" }) modelsInCommon?: boolean; + @YargOption({ envAlias: "modelsInModules", type: "boolean" }) + modelsInModules?: boolean; + + @YargOption({ envAlias: "zodImportPath" }) + zodImportPath?: string; + + @YargOption({ envAlias: "restClient", choices: ["axios", "native"] }) + restClient?: "axios" | "native"; + + @YargOption({ envAlias: "mutationEffectsImportPath" }) + mutationEffectsImportPath?: string; + + @YargOption({ envAlias: "aclCheckImportPath" }) + aclCheckImportPath?: string; + @YargOption({ envAlias: "removeOperationPrefixEndingWith" }) removeOperationPrefixEndingWith?: string; @@ -110,7 +128,7 @@ class GenerateOptions implements GenerateParams { verbose?: boolean; } -export const command: yargs.CommandModule = { +export const command: CommandModule = { command: "generate", describe: "Generate code from OpenAPI spec", builder: getBuilder(GenerateOptions), diff --git a/src/commands/generate.ts b/src/commands/generate.ts index 14d61eb8..1cf395b7 100644 --- a/src/commands/generate.ts +++ b/src/commands/generate.ts @@ -28,6 +28,7 @@ export type GenerateParams = { | "importPath" | "extractEnums" | "modelsInCommon" + | "restClient" | "acl" | "checkAcl" | "standalone" diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..5b249652 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,6 @@ +export { OpenApiRouter } from "./lib/config/router.context"; +export { OpenApiQueryConfig } from "./lib/config/queryConfig.context"; +export type { InvalidationMap, InvalidationMapFunc, QueryModule } from "./lib/config/queryConfig.context"; +export { OpenApiWorkspaceContext, useWorkspaceContext } from "./lib/config/workspace.context"; +export { configureTranslations, ns, resources } from "./lib/config/i18n"; +export type { TranslateFunction } from "./lib/config/i18n"; diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 00000000..1181212c --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,3 @@ +export { ApplicationException, ErrorHandler, SharedErrorHandler, DomainErrorRegistry } from "./lib/rest/error-handling"; +export type { GeneralErrorCodes, ErrorHandlerOptions, ErrorEntry, DomainErrorEntry } from "./lib/rest/error-handling"; +export { RestUtils } from "./lib/rest/rest.utils"; diff --git a/src/generator.ts b/src/generator.ts index bf4bcd73..a46b1911 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -1,17 +1,14 @@ -import { OpenAPIV3 } from "openapi-types"; - -import SwaggerParser from "@apidevtools/swagger-parser"; - import { DEFAULT_GENERATE_OPTIONS } from "./generators/const/options.const"; import { getMetadataFromOpenAPIDoc } from "./generators/core/getMetadataFromOpenAPIDoc"; import { generateCodeFromOpenAPIDoc } from "./generators/generateCodeFromOpenAPIDoc"; +import { getOpenApiDoc } from "./generators/run/generate.runner"; import { GenerateParams } from "./generators/types/metadata"; import { GenerateOptions } from "./generators/types/options"; export async function getGenerateMetadata({ input, options: genOptions }: GenerateParams) { const options = { ...DEFAULT_GENERATE_OPTIONS, ...genOptions } as GenerateOptions; - const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc(input); return await getMetadataFromOpenAPIDoc(openApiDoc, options); } @@ -19,7 +16,7 @@ export async function getGenerateMetadata({ input, options: genOptions }: Genera export async function getGenerateFilesData({ input, options: genOptions }: GenerateParams) { const options = { ...DEFAULT_GENERATE_OPTIONS, ...genOptions } as GenerateOptions; - const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc(input); return generateCodeFromOpenAPIDoc(openApiDoc, options); } diff --git a/src/generators/const/deps.const.ts b/src/generators/const/deps.const.ts index 8b41d5b9..25482f88 100644 --- a/src/generators/const/deps.const.ts +++ b/src/generators/const/deps.const.ts @@ -1,6 +1,6 @@ import { GenerateFile, Import } from "@/generators/types/generate"; -import { ACL_PACKAGE_IMPORT_PATH, PACKAGE_IMPORT_PATH } from "./package.const"; +import { ACL_PACKAGE_IMPORT_PATH, ERRORS_PACKAGE_IMPORT_PATH } from "./package.const"; export const APP_REST_CLIENT_NAME = "AppRestClient"; export const APP_REST_CLIENT_FILE: GenerateFile = { fileName: "app-rest-client", extension: "ts" }; @@ -20,7 +20,7 @@ export const ERROR_HANDLERS = { }; export const ERROR_HANDLING_IMPORT: Import = { bindings: [ERROR_HANDLERS.ErrorHandler, ERROR_HANDLERS.SharedErrorHandler], - from: PACKAGE_IMPORT_PATH, + from: ERRORS_PACKAGE_IMPORT_PATH, }; export const ABILITY_CONTEXT = "AbilityContext"; export const ABILITY_CONTEXT_IMPORT: Import = { diff --git a/src/generators/const/endpoints.const.ts b/src/generators/const/endpoints.const.ts index 7f6be769..6e374867 100644 --- a/src/generators/const/endpoints.const.ts +++ b/src/generators/const/endpoints.const.ts @@ -11,9 +11,15 @@ export const BODY_PARAMETER_NAME = "data"; export const AXIOS_DEFAULT_IMPORT_NAME = "axios"; export const AXIOS_REQUEST_CONFIG_NAME = "config"; export const AXIOS_REQUEST_CONFIG_TYPE = "AxiosRequestConfig"; +export const NATIVE_REQUEST_CONFIG_TYPE = "TransportRequestConfig"; +export const NATIVE_RESPONSE_TYPE = "TransportResponse"; export const AXIOS_IMPORT: Import = { defaultImport: AXIOS_DEFAULT_IMPORT_NAME, bindings: [], typeBindings: [AXIOS_REQUEST_CONFIG_TYPE], from: "axios", }; + +export function getRequestConfigTypeName(restClient: "axios" | "native") { + return restClient === "native" ? NATIVE_REQUEST_CONFIG_TYPE : AXIOS_REQUEST_CONFIG_TYPE; +} diff --git a/src/generators/const/options.const.ts b/src/generators/const/options.const.ts index a822ad41..0cc80f9a 100644 --- a/src/generators/const/options.const.ts +++ b/src/generators/const/options.const.ts @@ -1,7 +1,7 @@ import { GenerateType } from "@/generators/types/generate"; import { GenerateOptions } from "@/generators/types/options"; -import { PACKAGE_IMPORT_PATH } from "./package.const"; +import { QUERY_PACKAGE_IMPORT_PATH } from "./package.const"; import { ENUM_SUFFIX, SCHEMA_SUFFIX } from "./zod.const"; export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = { @@ -17,6 +17,7 @@ export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = { excludePathRegex: "", excludeRedundantZodSchemas: true, tsNamespaces: true, + treeShakeableNamespaces: false, tsPath: "@/data", importPath: "ts", configs: { @@ -48,18 +49,22 @@ export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = { schemaSuffix: SCHEMA_SUFFIX, enumSuffix: ENUM_SUFFIX, modelsInCommon: false, + modelsInModules: false, withDefaultValues: true, extractEnums: true, replaceOptionalWithNullish: false, // Endpoints options + restClient: "axios", restClientImportPath: "", + zodImportPath: "@povio/openapi-codegen-cli/zod", errorHandlingImportPath: "", removeOperationPrefixEndingWith: "Controller_", parseRequestParams: true, inlineEndpoints: false, inlineEndpointsExcludeModules: [], // Queries options - queryTypesImportPath: PACKAGE_IMPORT_PATH, + queryTypesImportPath: QUERY_PACKAGE_IMPORT_PATH, + mutationEffectsImportPath: QUERY_PACKAGE_IMPORT_PATH, axiosRequestConfig: false, mutationEffects: true, mutationDefaultOnError: false, @@ -81,6 +86,7 @@ export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = { checkAcl: true, abilityContextGenericAppAbilities: false, abilityContextImportPath: "", + aclCheckImportPath: "@povio/openapi-codegen-cli/acl", // Builder Configs options builderConfigs: false, filterParamName: "filter", diff --git a/src/generators/const/package.const.ts b/src/generators/const/package.const.ts index ff32f4c8..fe8ea70b 100644 --- a/src/generators/const/package.const.ts +++ b/src/generators/const/package.const.ts @@ -1,3 +1,8 @@ export const PACKAGE_IMPORT_PATH = "@povio/openapi-codegen-cli"; +export const NATIVE_PACKAGE_IMPORT_PATH = `${PACKAGE_IMPORT_PATH}/native`; +export const REST_PACKAGE_IMPORT_PATH = `${PACKAGE_IMPORT_PATH}/rest`; +export const ERRORS_PACKAGE_IMPORT_PATH = `${PACKAGE_IMPORT_PATH}/errors`; +export const QUERY_PACKAGE_IMPORT_PATH = `${PACKAGE_IMPORT_PATH}/query`; +export const CONFIG_PACKAGE_IMPORT_PATH = `${PACKAGE_IMPORT_PATH}/config`; export const ACL_PACKAGE_IMPORT_PATH = "@povio/openapi-codegen-cli/acl"; export const ZOD_PACKAGE_IMPORT_PATH = "@povio/openapi-codegen-cli/zod"; diff --git a/src/generators/core/SchemaResolver.class.ts b/src/generators/core/SchemaResolver.class.ts index 5c66829e..d924eb53 100644 --- a/src/generators/core/SchemaResolver.class.ts +++ b/src/generators/core/SchemaResolver.class.ts @@ -1,12 +1,10 @@ import { OpenAPIV3 } from "openapi-types"; -import { ALLOWED_METHODS } from "@/generators/const/openapi.const"; import { Profiler } from "@/helpers/profile.helper"; import { OperationObject } from "@/generators/types/openapi"; import { GenerateOptions } from "@/generators/types/options"; import { ValidationError } from "@/generators/types/validation"; import { getUniqueArray } from "@/generators/utils/array.utils"; -import { pick } from "@/generators/utils/object.utils"; import { isReferenceObject } from "@/generators/utils/openapi-schema.utils"; import { autocorrectRef, @@ -14,14 +12,8 @@ import { getSchemaRef, isMediaTypeAllowed, isParamMediaTypeAllowed, - isPathExcluded, } from "@/generators/utils/openapi.utils"; -import { - getOperationsByTag, - getUniqueOperationName, - getUniqueOperationNamesWithoutSplitByTags, - isOperationExcluded, -} from "@/generators/utils/operation.utils"; +import { getOperationIndex, IndexedOperation } from "@/generators/utils/operation.utils"; import { snakeToCamel } from "@/generators/utils/string.utils"; import { formatTag, getOperationTag } from "@/generators/utils/tag.utils"; import { @@ -119,7 +111,10 @@ export class SchemaResolver { readonly operationsByTag: Record = {}; readonly operationNames: string[] = []; private readonly operationNameCounts = new Map(); + private readonly operationNameByOperation: WeakMap; + private readonly indexedOperations: IndexedOperation[]; private readonly operationContexts: OperationContext[] = []; + private zodSchemaTagCache?: Map; readonly validationErrors: ValidationError[] = []; @@ -132,14 +127,22 @@ export class SchemaResolver { public readonly options: GenerateOptions, private readonly profiler?: Profiler, ) { - this.schemaRefs = Object.keys(this.docSchemas).map(getSchemaRef); - this.dependencyGraph = getOpenAPISchemaDependencyGraph(this.schemaRefs, this.getSchemaByRef.bind(this)); - this.enumZodSchemas = getEnumZodSchemasFromOpenAPIDoc(this); - this.operationsByTag = getOperationsByTag(openApiDoc, options); - this.operationNames = getUniqueOperationNamesWithoutSplitByTags(openApiDoc, this.operationsByTag, options); - for (const name of this.operationNames) { - this.operationNameCounts.set(name, (this.operationNameCounts.get(name) ?? 0) + 1); - } + const p = profiler ?? new Profiler(false); + this.schemaRefs = p.runSync("resolver.construct.schemaRefs", () => Object.keys(this.docSchemas).map(getSchemaRef)); + this.dependencyGraph = p.runSync("resolver.construct.dependencyGraph", () => + getOpenAPISchemaDependencyGraph(this.schemaRefs, this.getSchemaByRef.bind(this)), + ); + this.enumZodSchemas = p.runSync("resolver.construct.enums", () => getEnumZodSchemasFromOpenAPIDoc(this)); + const operationIndex = p.runSync("resolver.construct.operationIndex", () => getOperationIndex(openApiDoc, options)); + this.operationsByTag = operationIndex.operationsByTag; + this.operationNames = operationIndex.operationNames; + this.operationNameByOperation = operationIndex.operationNameByOperation; + this.indexedOperations = operationIndex.indexedOperations; + p.runSync("resolver.construct.operationNameCounts", () => { + for (const name of this.operationNames) { + this.operationNameCounts.set(name, (this.operationNameCounts.get(name) ?? 0) + 1); + } + }); this.initialize(); } @@ -175,6 +178,36 @@ export class SchemaResolver { } getTagByZodSchemaName(zodSchemaName: string) { + const cachedTag = this.zodSchemaTagCache?.get(zodSchemaName); + if (cachedTag) { + return cachedTag; + } + + const tag = this.resolveTagByZodSchemaName(zodSchemaName); + this.zodSchemaTagCache?.set(zodSchemaName, tag); + return tag; + } + + getTagsByZodSchemaName(zodSchemaName: string) { + if (!this.options.splitByTags) return [formatTag(this.options.defaultTag)]; + const extractedEnum = this.extractedEnumZodSchemaData.find((data) => data.zodSchemaName === zodSchemaName); + if (extractedEnum) { + const tags = getUniqueArray(extractedEnum.meta.tags); + return (tags.length > 0 ? tags : [extractedEnum.tag ?? this.options.defaultTag]).map(formatTag); + } + const schemaRef = this.getRefByZodSchemaName(zodSchemaName); + const tags = getUniqueArray( + schemaRef ? (this.getSchemaDataByRef(schemaRef)?.tags ?? []) : [], + this.zodSchemaDataByName.get(zodSchemaName)?.tags ?? [], + ); + return (tags.length > 0 ? tags : [this.options.defaultTag]).map(formatTag); + } + + enableZodSchemaTagCache() { + this.zodSchemaTagCache = new Map(); + } + + private resolveTagByZodSchemaName(zodSchemaName: string) { if (!this.options.splitByTags) { return this.options.defaultTag; } @@ -362,146 +395,134 @@ export class SchemaResolver { }); p.runSync("resolver.init.operationsLoop", () => { - for (const path in this.openApiDoc.paths) { - if (isPathExcluded(path, this.options)) { - continue; + let currentPath = ""; + let pathParameters: ReturnType = {}; + for (const { path, method, operation, pathParameters: rawPathParameters } of this.indexedOperations) { + if (path !== currentPath) { + currentPath = path; + pathParameters = this.getParameters(rawPathParameters ?? []); } - const pathItemObj = this.openApiDoc.paths[path] as OpenAPIV3.PathItemObject; - const pathParameters = this.getParameters(pathItemObj.parameters ?? []); - - const pathItem = pick(pathItemObj, ALLOWED_METHODS); - for (const method in pathItem) { - const operation = pathItem[method as keyof typeof pathItem] as OperationObject | undefined; - if (!operation || isOperationExcluded(operation, this.options)) { - continue; - } - - const tag = getOperationTag(operation, this.options); - const operationName = getUniqueOperationName({ - path, - method, - operation, - operationsByTag: this.operationsByTag, - options: this.options, - }); - const isUniqueOperationName = this.isOperationNameUnique(operationName); - const parameters = p.runSync("resolver.init.parameters.mergeResolve", () => - Object.entries({ - ...pathParameters, - ...this.getParameters(operation.parameters ?? []), - }).map(([, param]) => this.resolveObject(param as OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)), - ); - const responses = p.runSync("resolver.init.responses.prepare", () => - Object.entries(operation.responses).map(([statusCode, response]) => { - const responseObj = this.resolveObject(response) as OpenAPIV3.ResponseObject; - const mediaTypes = Object.keys(responseObj?.content ?? {}); - const matchingMediaType = mediaTypes.find(isMediaTypeAllowed); - const schema = matchingMediaType ? responseObj.content?.[matchingMediaType]?.schema : undefined; - return { statusCode, responseObj, matchingMediaType, schema }; - }), - ); - const zodSchemaOperationName = snakeToCamel( - getZodSchemaOperationName(operationName, isUniqueOperationName, tag), - ); - const schemaRefObjs = [] as OpenAPIV3.ReferenceObject[]; - - p.runSync("resolver.init.parameters.refs", () => { - parameters.forEach((parameter) => { - const parameterObject = this.resolveObject( - parameter as OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject, - ); - const parameterSchema = parameterObject.schema; - const schemaInfo = `Operation ${operation.operationId ?? path} parameter ${parameterObject.name}`; - - schemaRefObjs.push(...getSchemaRefObjs(this, parameterSchema, schemaInfo)); - - if (this.options.extractEnums) { - updateExtractedEnumZodSchemaData({ - resolver: this, - schema: parameterSchema, - schemaInfo, - tags: [tag], - nameSegments: [zodSchemaOperationName, parameterObject.name], - includeSelf: true, - }); - } - }); - }); - - p.runSync("resolver.init.requestBody.refs", () => { - if (operation.requestBody) { - const requestBodyObj = this.resolveObject(operation.requestBody); - const mediaTypes = Object.keys(requestBodyObj.content ?? {}); - const matchingMediaType = mediaTypes.find(isParamMediaTypeAllowed); - if (matchingMediaType) { - const matchingMediaSchema = requestBodyObj.content?.[matchingMediaType]?.schema; - const schemaInfo = `Operation ${operation.operationId} request body`; - - schemaRefObjs.push(...getSchemaRefObjs(this, matchingMediaSchema, schemaInfo)); - - if (this.options.extractEnums) { - updateExtractedEnumZodSchemaData({ - resolver: this, - schema: matchingMediaSchema, - schemaInfo, - tags: [tag], - nameSegments: [getBodyZodSchemaName(zodSchemaOperationName)], - }); - } - } + const tag = getOperationTag(operation, this.options); + const operationName = this.operationNameByOperation.get(operation)!; + const isUniqueOperationName = this.isOperationNameUnique(operationName); + const parameters = p.runSync("resolver.init.parameters.mergeResolve", () => + Object.entries({ + ...pathParameters, + ...this.getParameters(operation.parameters ?? []), + }).map(([, param]) => this.resolveObject(param as OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)), + ); + const responses = p.runSync("resolver.init.responses.prepare", () => + Object.entries(operation.responses).map(([statusCode, response]) => { + const responseObj = this.resolveObject(response) as OpenAPIV3.ResponseObject; + const mediaTypes = Object.keys(responseObj?.content ?? {}); + const matchingMediaType = + mediaTypes.find((mediaType) => responseObj.content?.[mediaType]?.schema) ?? + mediaTypes.find(isMediaTypeAllowed); + const schema = matchingMediaType ? responseObj.content?.[matchingMediaType]?.schema : undefined; + return { statusCode, responseObj, matchingMediaType, schema }; + }), + ); + const zodSchemaOperationName = snakeToCamel( + getZodSchemaOperationName(operationName, isUniqueOperationName, tag), + ); + const schemaRefObjs = [] as OpenAPIV3.ReferenceObject[]; + + p.runSync("resolver.init.parameters.refs", () => { + parameters.forEach((parameter) => { + const parameterObject = this.resolveObject( + parameter as OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject, + ); + const parameterSchema = parameterObject.schema; + const schemaInfo = `Operation ${operation.operationId ?? path} parameter ${parameterObject.name}`; + + schemaRefObjs.push(...getSchemaRefObjs(this, parameterSchema, schemaInfo)); + + if (this.options.extractEnums) { + updateExtractedEnumZodSchemaData({ + resolver: this, + schema: parameterSchema, + schemaInfo, + tags: [tag], + nameSegments: [zodSchemaOperationName, parameterObject.name], + includeSelf: true, + }); } }); + }); - p.runSync("resolver.init.responses.refs", () => { - for (const responseData of responses) { - if (!responseData.matchingMediaType) { - continue; - } + p.runSync("resolver.init.requestBody.refs", () => { + if (operation.requestBody) { + const requestBodyObj = this.resolveObject(operation.requestBody); + const mediaTypes = Object.keys(requestBodyObj.content ?? {}); + const matchingMediaType = mediaTypes.find(isParamMediaTypeAllowed); + if (matchingMediaType) { + const matchingMediaSchema = requestBodyObj.content?.[matchingMediaType]?.schema; + const schemaInfo = `Operation ${operation.operationId} request body`; - const schemaInfo = `Operation ${operation.operationId} response body`; - schemaRefObjs.push(...getSchemaRefObjs(this, responseData.schema, schemaInfo)); + schemaRefObjs.push(...getSchemaRefObjs(this, matchingMediaSchema, schemaInfo)); if (this.options.extractEnums) { updateExtractedEnumZodSchemaData({ resolver: this, - schema: responseData.schema, + schema: matchingMediaSchema, schemaInfo, tags: [tag], - nameSegments: [ - getResponseZodSchemaName({ - statusCode: responseData.statusCode, - operationName, - isUniqueOperationName, - tag, - }), - ], + nameSegments: [getBodyZodSchemaName(zodSchemaOperationName)], }); } } - }); + } + }); - const deepRefs = p.runSync("resolver.init.deepRefs", () => getDeepSchemaRefObjs(this, schemaRefObjs)); - const operationContext: OperationContext = { - path, - method: method as OpenAPIV3.HttpMethods, - operation, - tag, - operationName, - isUniqueOperationName, - parameters, - deepRefs, - responses, - }; - this.operationContexts.push(operationContext); - deepRefs.forEach((schemaRef) => { - const schemaData = this.getSchemaDataByRef(schemaRef); - if (schemaData) { - schemaData.tags.push(tag); - schemaData.deepRefOperations.push(operation); + p.runSync("resolver.init.responses.refs", () => { + for (const responseData of responses) { + if (!responseData.matchingMediaType) { + continue; } - }); - } + + const schemaInfo = `Operation ${operation.operationId} response body`; + schemaRefObjs.push(...getSchemaRefObjs(this, responseData.schema, schemaInfo)); + + if (this.options.extractEnums) { + updateExtractedEnumZodSchemaData({ + resolver: this, + schema: responseData.schema, + schemaInfo, + tags: [tag], + nameSegments: [ + getResponseZodSchemaName({ + statusCode: responseData.statusCode, + operationName, + isUniqueOperationName, + tag, + }), + ], + }); + } + } + }); + + const deepRefs = p.runSync("resolver.init.deepRefs", () => getDeepSchemaRefObjs(this, schemaRefObjs)); + const operationContext: OperationContext = { + path, + method: method as OpenAPIV3.HttpMethods, + operation, + tag, + operationName, + isUniqueOperationName, + parameters, + deepRefs, + responses, + }; + this.operationContexts.push(operationContext); + deepRefs.forEach((schemaRef) => { + const schemaData = this.getSchemaDataByRef(schemaRef); + if (schemaData) { + schemaData.tags.push(tag); + schemaData.deepRefOperations.push(operation); + } + }); } }); diff --git a/src/generators/core/endpoints/getEndpointParameter.ts b/src/generators/core/endpoints/getEndpointParameter.ts index af6721d7..f31e322a 100644 --- a/src/generators/core/endpoints/getEndpointParameter.ts +++ b/src/generators/core/endpoints/getEndpointParameter.ts @@ -1,5 +1,4 @@ import { OpenAPIV3 } from "openapi-types"; -import { match } from "ts-pattern"; import { ALLOWED_PATH_IN } from "@/generators/const/openapi.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; @@ -121,14 +120,8 @@ export function getEndpointParameter({ }); return { - name: match(paramObj.in) - .with("path", () => pathParamToVariableName(paramObj.name)) - .otherwise(() => paramObj.name), - type: match(paramObj.in) - .with("header", () => "Header") - .with("query", () => "Query") - .with("path", () => "Path") - .run() as "Header" | "Query" | "Path", + name: paramObj.in === "path" ? pathParamToVariableName(paramObj.name) : paramObj.name, + type: paramObj.in === "header" ? "Header" : paramObj.in === "query" ? "Query" : "Path", zodSchema: zodSchemaName, parameterObject: paramObj, ...(parameterSortingEnumSchemaName !== undefined ? { parameterSortingEnumSchemaName } : {}), diff --git a/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.test.ts b/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.test.ts index e88ae957..c32c2650 100644 --- a/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.test.ts +++ b/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.test.ts @@ -4,8 +4,8 @@ import { describe, expect, test } from "vitest"; import { JSON_APPLICATION_FORMAT } from "@/generators/const/endpoints.const"; import { DEFAULT_GENERATE_OPTIONS } from "@/generators/const/options.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; +import { getOpenApiDoc } from "@/generators/run/generate.runner"; import { GenerateOptions } from "@/generators/types/options"; -import SwaggerParser from "@apidevtools/swagger-parser"; import { getEndpointsFromOpenAPIDoc } from "./getEndpointsFromOpenAPIDoc"; @@ -894,7 +894,7 @@ describe("getEndpointsFromOpenAPIDoc", () => { }); test("petstore.yaml", async () => { - const openApiDoc = (await SwaggerParser.parse("./test/petstore.yaml")) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc("./test/petstore.yaml"); const resolver = new SchemaResolver(openApiDoc, generateOptions); const endpoints = getEndpointsFromOpenAPIDoc(resolver); expect(endpoints).toEqual([ diff --git a/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts b/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts index 9fbc57ee..6b2ea4d4 100644 --- a/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts +++ b/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts @@ -1,7 +1,6 @@ import { OpenAPIV3 } from "openapi-types"; import { JSON_APPLICATION_FORMAT } from "@/generators/const/endpoints.const"; -import { ALLOWED_METHODS } from "@/generators/const/openapi.const"; import { HttpStatusCode } from "@/generators/const/validation.const"; import { STRING_SCHEMA, VOID_SCHEMA } from "@/generators/const/zod.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; @@ -9,19 +8,10 @@ import { getZodChain } from "@/generators/core/zod/getZodChain"; import { getZodSchema } from "@/generators/core/zod/getZodSchema"; import { resolveZodSchemaName } from "@/generators/core/zod/resolveZodSchemaName"; import { Endpoint, EndpointParameter } from "@/generators/types/endpoint"; -import { OperationObject } from "@/generators/types/openapi"; import { invalidVariableNameCharactersToCamel } from "@/generators/utils/js.utils"; -import { pick } from "@/generators/utils/object.utils"; import { isReferenceObject } from "@/generators/utils/openapi-schema.utils"; -import { - isErrorStatus, - isMainResponseStatus, - isMediaTypeAllowed, - isPathExcluded, - replaceHyphenatedPath, -} from "@/generators/utils/openapi.utils"; -import { getUniqueOperationName, isOperationExcluded } from "@/generators/utils/operation.utils"; -import { formatTag, getOperationTag } from "@/generators/utils/tag.utils"; +import { isErrorStatus, isMainResponseStatus, replaceHyphenatedPath } from "@/generators/utils/openapi.utils"; +import { formatTag } from "@/generators/utils/tag.utils"; import { getInvalidOperationIdError, getInvalidStatusCodeError, @@ -30,134 +20,106 @@ import { getMultipleSuccessStatusCodesError, } from "@/generators/utils/validation.utils"; import { getResponseZodSchemaName } from "@/generators/utils/zod-schema.utils"; +import { Profiler } from "@/helpers/profile.helper"; import { getEndpointAcl } from "./getEndpointAcl"; import { getEndpointBody } from "./getEndpointBody"; import { getEndpointParameter } from "./getEndpointParameter"; -export function getEndpointsFromOpenAPIDoc(resolver: SchemaResolver) { +export function getEndpointsFromOpenAPIDoc(resolver: SchemaResolver, profiler = new Profiler(false)) { const endpoints = []; - - for (const path in resolver.openApiDoc.paths) { - if (isPathExcluded(path, resolver.options)) { - continue; + const responseSchemaByRefAndTag = new Map< + string, + { schemaObject: OpenAPIV3.SchemaObject; responseZodSchema: string } + >(); + + for (const context of resolver.getOperationContexts()) { + const { path, method, operation, operationName, isUniqueOperationName, tag, parameters, responses } = context; + + const invalidOperationId = + operation.operationId && operation.operationId !== invalidVariableNameCharactersToCamel(operation.operationId); + if (operation.operationId && invalidOperationId) { + resolver.validationErrors.push(getInvalidOperationIdError(operation.operationId)); } - const pathItemObj = resolver.openApiDoc.paths[path] as OpenAPIV3.PathItemObject; - const pathItem = pick(pathItemObj, ALLOWED_METHODS); - const pathParameters = getParameters(pathItemObj.parameters ?? []); - - for (const method in pathItem) { - const operation = pathItem[method as keyof typeof pathItem] as OperationObject | undefined; - if (!operation || isOperationExcluded(operation, resolver.options)) { - continue; - } - - const invalidOperationId = - operation.operationId && operation.operationId !== invalidVariableNameCharactersToCamel(operation.operationId); - if (operation.operationId && invalidOperationId) { - resolver.validationErrors.push(getInvalidOperationIdError(operation.operationId)); - } - - const parameters = Object.entries({ - ...pathParameters, - ...getParameters(operation.parameters ?? []), - }).map(([, param]) => param); - const operationName = getUniqueOperationName({ - path, - method, - operation, - operationsByTag: resolver.operationsByTag, - options: resolver.options, - }); - const isUniqueOperationName = resolver.operationNames.filter((name) => name === operationName).length <= 1; - const tag = getOperationTag(operation, resolver.options); - const endpoint: Endpoint = { - method: method as OpenAPIV3.HttpMethods, - path: replaceHyphenatedPath(path), - operationName, - description: operation.description, - summary: operation.summary, - tags: operation.tags?.map(formatTag), - requestFormat: JSON_APPLICATION_FORMAT, - parameters: [], - response: "", - errors: [], - responseStatusCodes: [], - mediaUpload: !!operation["x-media-upload"], - mediaDownload: !!operation["x-media-download"], - }; - - if (operation.requestBody) { - const body = getEndpointBody({ resolver, operation, operationName, isUniqueOperationName, tag }); - if (body) { - endpoint.parameters.push(body.endpointParameter); - endpoint.requestFormat = body.requestFormat; - } + const endpoint: Endpoint = { + method, + path: replaceHyphenatedPath(path), + operationName, + description: operation.description, + summary: operation.summary, + tags: operation.tags?.map(formatTag), + requestFormat: JSON_APPLICATION_FORMAT, + parameters: [], + response: "", + errors: [], + responseStatusCodes: [], + mediaUpload: !!operation["x-media-upload"], + mediaDownload: !!operation["x-media-download"], + }; + + if (operation.requestBody) { + const body = profiler.runSync("endpoints.body", () => + getEndpointBody({ resolver, operation, operationName, isUniqueOperationName, tag }), + ); + if (body) { + endpoint.parameters.push(body.endpointParameter); + endpoint.requestFormat = body.requestFormat; } + } - for (const param of parameters) { - const endpointParameter = getEndpointParameter({ + for (const param of parameters) { + const endpointParameter = profiler.runSync("endpoints.parameter", () => + getEndpointParameter({ resolver, param, operationName, isUniqueOperationName, tag, - }); - if (endpointParameter) { - endpoint.parameters.push(endpointParameter); - } + }), + ); + if (endpointParameter) { + endpoint.parameters.push(endpointParameter); } + } - const missingPathParameters = getMissingPathParameters(endpoint); - missingPathParameters.forEach((pathParam) => { - endpoint.parameters.push(pathParam); - }); - if (missingPathParameters.length > 0) { - resolver.validationErrors.push(getMissingPathParameterError(missingPathParameters, path)); - } - - for (const statusCode in operation.responses) { - endpoint.responseStatusCodes.push(statusCode); - - const responseObj = resolver.resolveObject(operation.responses[statusCode]); - const mediaTypes = Object.keys(responseObj?.content ?? {}); - // Prefer any content entry that declares a body schema. Some specs use - // non-application media types (e.g. text/json) which would otherwise skip - // schema resolution and fall back to z.void() for the whole operation. - const matchingMediaType = - mediaTypes.find((mt) => { - const entry = responseObj.content?.[mt]; - return !!entry?.schema; - }) ?? mediaTypes.find(isMediaTypeAllowed); + const missingPathParameters = getMissingPathParameters(endpoint.path, endpoint.parameters); + endpoint.parameters.push(...missingPathParameters); + if (missingPathParameters.length > 0) { + resolver.validationErrors.push(getMissingPathParameterError(missingPathParameters, path)); + } - let schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | undefined; - if (matchingMediaType) { - const statusNum = Number(statusCode); - // Only let success responses (2xx) or "default" (when nothing is set yet) determine the - // response format used for the Accept header. Error responses (4xx/5xx) must not - // overwrite the success content-type — doing so would strip rawResponse/blob config - // from blob-download endpoints that also declare domain-error responses. - if (isMainResponseStatus(statusNum) || (statusCode === "default" && !endpoint.responseFormat)) { - endpoint.responseFormat = matchingMediaType; - } - schema = responseObj.content?.[matchingMediaType]?.schema; - } else if (statusCode === "200") { - resolver.validationErrors.push( - getInvalidStatusCodeError({ received: "200", expected: "204" }, operation, endpoint), - ); + for (const { statusCode, responseObj, matchingMediaType, schema } of responses) { + endpoint.responseStatusCodes.push(statusCode); + if (matchingMediaType) { + const statusNum = Number(statusCode); + // Only let success responses (2xx) or "default" (when nothing is set yet) determine the + // response format used for the Accept header. Error responses (4xx/5xx) must not + // overwrite the success content-type — doing so would strip rawResponse/blob config + // from blob-download endpoints that also declare domain-error responses. + if (isMainResponseStatus(statusNum) || (statusCode === "default" && !endpoint.responseFormat)) { + endpoint.responseFormat = matchingMediaType; } + } else if (statusCode === "200") { + resolver.validationErrors.push( + getInvalidStatusCodeError({ received: "200", expected: "204" }, operation, endpoint), + ); + } - if (schema) { - const zodSchema = getZodSchema({ - schema, - resolver, - meta: { isRequired: true }, - tag, - }); + if (schema) { + const responseCacheKey = isReferenceObject(schema) ? `${schema.$ref}|${tag}` : undefined; + let resolvedResponse = responseCacheKey ? responseSchemaByRefAndTag.get(responseCacheKey) : undefined; + if (!resolvedResponse) { + const zodSchema = profiler.runSync("endpoints.response.zod", () => + getZodSchema({ + schema, + resolver, + meta: { isRequired: true }, + tag, + }), + ); const schemaObject = resolver.resolveObject(schema); - const zodSchemaName = resolveZodSchemaName({ schema: schemaObject, zodSchema, @@ -167,105 +129,120 @@ export function getEndpointsFromOpenAPIDoc(resolver: SchemaResolver) { resolver, tag, }); - - const responseZodSchema = - zodSchemaName + getZodChain({ schema: schemaObject, meta: zodSchema.meta, options: resolver.options }); - - const status = Number(statusCode); - - if (isMainResponseStatus(status) && !endpoint.response) { - endpoint.response = responseZodSchema; - endpoint.responseObject = responseObj; - endpoint.responseDescription = responseObj?.description; - } else if (statusCode === "default" && !endpoint.response) { - // Nest/Swagger often puts the JSON body only under `default` while `200` has no content. - endpoint.response = responseZodSchema; - endpoint.responseObject = responseObj; - endpoint.responseDescription = responseObj?.description; - } else if (statusCode !== "default" && !Number.isNaN(status) && isErrorStatus(status)) { - const rawSchema = schemaObject as Record; - const domainStr = rawSchema["x-domain-error-domain"]; - const domainName = rawSchema["x-domain-error-name"]; - const codeEnum = (rawSchema?.properties as Record | undefined)?.code; - const codeEnumArr = (codeEnum as Record | undefined)?.enum; - const domainCode = - Array.isArray(codeEnumArr) && - codeEnumArr.length === 1 && - (typeof codeEnumArr[0] === "number" || typeof codeEnumArr[0] === "string") - ? (codeEnumArr[0] as number | string) - : undefined; - - endpoint.errors.push({ - zodSchema: responseZodSchema, - status, - description: responseObj?.description, - ...(typeof domainStr === "string" && domainCode !== undefined - ? { - domainError: { - domain: domainStr, - code: domainCode, - ...(typeof domainName === "string" ? { name: domainName } : {}), - }, - } - : {}), - }); - } - } else { - const status = Number(statusCode); - const responseZodSchema = VOID_SCHEMA; - - if (statusCode !== "default" && !Number.isNaN(status) && isErrorStatus(status)) { - endpoint.errors.push({ - zodSchema: responseZodSchema, - status, - description: responseObj?.description, - }); + resolvedResponse = { + schemaObject, + responseZodSchema: + zodSchemaName + getZodChain({ schema: schemaObject, meta: zodSchema.meta, options: resolver.options }), + }; + if (responseCacheKey) { + responseSchemaByRefAndTag.set(responseCacheKey, resolvedResponse); } } + const { schemaObject, responseZodSchema } = resolvedResponse; + + const status = Number(statusCode); + + if (isMainResponseStatus(status) && !endpoint.response) { + endpoint.response = responseZodSchema; + endpoint.responseObject = responseObj; + endpoint.responseDescription = responseObj?.description; + } else if (statusCode === "default" && !endpoint.response) { + // Nest/Swagger often puts the JSON body only under `default` while `200` has no content. + endpoint.response = responseZodSchema; + endpoint.responseObject = responseObj; + endpoint.responseDescription = responseObj?.description; + } else if (statusCode !== "default" && !Number.isNaN(status) && isErrorStatus(status)) { + const rawSchema = schemaObject as Record; + const domainStr = rawSchema["x-domain-error-domain"]; + const domainName = rawSchema["x-domain-error-name"]; + const codeEnum = (rawSchema?.properties as Record | undefined)?.code; + const codeEnumArr = (codeEnum as Record | undefined)?.enum; + const domainCode = + Array.isArray(codeEnumArr) && + codeEnumArr.length === 1 && + (typeof codeEnumArr[0] === "number" || typeof codeEnumArr[0] === "string") + ? (codeEnumArr[0] as number | string) + : undefined; + + endpoint.errors.push({ + zodSchema: responseZodSchema, + status, + description: responseObj?.description, + ...(typeof domainStr === "string" && domainCode !== undefined + ? { + domainError: { + domain: domainStr, + code: domainCode, + ...(typeof domainName === "string" ? { name: domainName } : {}), + }, + } + : {}), + }); + } + } else { + const status = Number(statusCode); + const responseZodSchema = VOID_SCHEMA; + + if (statusCode !== "default" && !Number.isNaN(status) && isErrorStatus(status)) { + endpoint.errors.push({ + zodSchema: responseZodSchema, + status, + description: responseObj?.description, + }); + } } + } - if (!endpoint.response) { - endpoint.response = VOID_SCHEMA; - } - - const mainStatusCodes = Object.keys(operation.responses).map(Number).filter(isMainResponseStatus); - if (mainStatusCodes.length > 1) { - resolver.validationErrors.push( - getMultipleSuccessStatusCodesError(mainStatusCodes.map(String) as HttpStatusCode[], operation, endpoint), - ); - } + if (!endpoint.response) { + endpoint.response = VOID_SCHEMA; + } - const resolvedAcl = getEndpointAcl({ resolver, endpoint, operation }); - if (resolvedAcl?.length) { - endpoint.acl = resolvedAcl; + const mainStatusCodes: number[] = []; + for (const { statusCode } of responses) { + const status = Number(statusCode); + if (isMainResponseStatus(status)) { + mainStatusCodes.push(status); } + } + if (mainStatusCodes.length > 1) { + resolver.validationErrors.push( + getMultipleSuccessStatusCodesError(mainStatusCodes.map(String) as HttpStatusCode[], operation, endpoint), + ); + } - if (operation.security?.[0].Authorization && !endpoint.responseStatusCodes.includes("401")) { - resolver.validationErrors.push(getMissingStatusCodeError("401", operation, endpoint)); - } + const resolvedAcl = getEndpointAcl({ resolver, endpoint, operation }); + if (resolvedAcl?.length) { + endpoint.acl = resolvedAcl; + } - if (endpoint.acl?.[0] && !endpoint.responseStatusCodes.includes("403")) { - resolver.validationErrors.push(getMissingStatusCodeError("403", operation, endpoint)); - } + if (operation.security?.[0].Authorization && !endpoint.responseStatusCodes.includes("401")) { + resolver.validationErrors.push(getMissingStatusCodeError("401", operation, endpoint)); + } - endpoints.push(endpoint); + if (endpoint.acl?.[0] && !endpoint.responseStatusCodes.includes("403")) { + resolver.validationErrors.push(getMissingStatusCodeError("403", operation, endpoint)); } + + endpoints.push(endpoint); } return endpoints; } -function getParameters(parameters: NonNullable) { - return Object.fromEntries( - (parameters ?? []).map((param) => [isReferenceObject(param) ? param.$ref : param.name, param] as const), - ); -} +function getMissingPathParameters(path: string, parameters: EndpointParameter[]): EndpointParameter[] { + if (!path.includes(":")) { + return []; + } -function getMissingPathParameters(endpoint: Endpoint): EndpointParameter[] { - const pathParams = [...endpoint.path.matchAll(/:([a-zA-Z0-9_]+)/g)].map((param) => param[1]); - return pathParams - .filter((pathParam) => endpoint.parameters.findIndex(({ name }) => name === pathParam) === -1) - .map((name) => getPathParameterFromName(name)); + const parameterNames = new Set(parameters.map(({ name }) => name)); + const missingParameters: EndpointParameter[] = []; + for (const match of path.matchAll(/:([a-zA-Z0-9_]+)/g)) { + const name = match[1]; + if (name && !parameterNames.has(name)) { + missingParameters.push(getPathParameterFromName(name)); + } + } + return missingParameters; } function getPathParameterFromName(name: string): EndpointParameter { diff --git a/src/generators/core/getDataFromOpenAPIDoc.ts b/src/generators/core/getDataFromOpenAPIDoc.ts index 5832d4f7..78bb2b94 100644 --- a/src/generators/core/getDataFromOpenAPIDoc.ts +++ b/src/generators/core/getDataFromOpenAPIDoc.ts @@ -7,14 +7,16 @@ import { getEndpointTag } from "../utils/tag.utils"; import { getEndpointsFromOpenAPIDoc } from "./endpoints/getEndpointsFromOpenAPIDoc"; import { SchemaResolver } from "./SchemaResolver.class"; import { getZodSchemasFromOpenAPIDoc } from "./zod/getZodSchemasFromOpenAPIDoc"; +import { getZodSchemaRefs } from "./zod/getZodSchemaRefs"; import { sortZodSchemasByTopology } from "./zod/sortZodSchemasByTopology"; export function getDataFromOpenAPIDoc(openApiDoc: OpenAPIV3.Document, options: GenerateOptions, profiler?: Profiler) { const p = profiler ?? new Profiler(false); const resolver = p.runSync("data.resolver.init", () => new SchemaResolver(openApiDoc, options, p)); - const endpoints = p.runSync("data.endpoints.extract", () => getEndpointsFromOpenAPIDoc(resolver)); + const endpoints = p.runSync("data.endpoints.extract", () => getEndpointsFromOpenAPIDoc(resolver, p)); const zodSchemasFromDocSchemas = p.runSync("data.zod.extract", () => getZodSchemasFromOpenAPIDoc(resolver, p)); + resolver.enableZodSchemaTagCache(); let zodSchemas = { ...zodSchemasFromDocSchemas.zodSchemas, @@ -54,12 +56,31 @@ function splitDataByTags({ }); Object.entries(zodSchemas).forEach(([zodSchemaName, zodSchemaCode]) => { + if (options.modelsInModules) { + for (const tag of resolver.getTagsByZodSchemaName(zodSchemaName)) { + getTagElement(tag, data).zodSchemas[zodSchemaName] = zodSchemaCode; + } + return; + } const tag = options.modelsInCommon ? options.defaultTag : resolver.getTagByZodSchemaName(zodSchemaName); if (tag) { getTagElement(tag, data).zodSchemas[zodSchemaName] = zodSchemaCode; } }); + if (options.modelsInModules) { + for (const tagData of data.values()) { + const pending = Object.keys(tagData.zodSchemas); + for (let index = 0; index < pending.length; index++) { + for (const dependencyName of getZodSchemaRefs(resolver, pending[index])) { + if (tagData.zodSchemas[dependencyName] || !zodSchemas[dependencyName]) continue; + tagData.zodSchemas[dependencyName] = zodSchemas[dependencyName]; + pending.push(dependencyName); + } + } + } + } + return data; } diff --git a/src/generators/core/getMetadataFromOpenAPIDoc.test.ts b/src/generators/core/getMetadataFromOpenAPIDoc.test.ts index 1640f291..171363a8 100644 --- a/src/generators/core/getMetadataFromOpenAPIDoc.test.ts +++ b/src/generators/core/getMetadataFromOpenAPIDoc.test.ts @@ -2,8 +2,8 @@ import { OpenAPIV3 } from "openapi-types"; import { describe, expect, test } from "vitest"; import { DEFAULT_GENERATE_OPTIONS } from "@/generators/const/options.const"; +import { getOpenApiDoc } from "@/generators/run/generate.runner"; import { ModelMetadata, QueryMetadata, TsType } from "@/generators/types/metadata"; -import SwaggerParser from "@apidevtools/swagger-parser"; import { getMetadataFromOpenAPIDoc } from "./getMetadataFromOpenAPIDoc"; @@ -404,7 +404,7 @@ describe("getMetadataFromOpenAPIDoc", () => { ]; test("getMetadataFromOpenAPIDoc", async () => { - const openApiDoc = (await SwaggerParser.bundle("./test/petstore.yaml")) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc("./test/petstore.yaml"); const metadata = await getMetadataFromOpenAPIDoc(openApiDoc, { ...DEFAULT_GENERATE_OPTIONS, @@ -416,7 +416,7 @@ describe("getMetadataFromOpenAPIDoc", () => { }); test("getMetadataFromOpenAPIDoc", async () => { - const openApiDoc = (await SwaggerParser.bundle("./test/petstore.yaml")) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc("./test/petstore.yaml"); const extractEnums = false; const metadata = await getMetadataFromOpenAPIDoc(openApiDoc, { @@ -430,7 +430,7 @@ describe("getMetadataFromOpenAPIDoc", () => { }); test("uses common model namespace and import path when modelsInCommon is enabled with includeTags", async () => { - const openApiDoc = (await SwaggerParser.bundle("./test/petstore.yaml")) as OpenAPIV3.Document; + const openApiDoc = await getOpenApiDoc("./test/petstore.yaml"); const metadata = await getMetadataFromOpenAPIDoc(openApiDoc, { ...DEFAULT_GENERATE_OPTIONS, diff --git a/src/generators/core/openapi/getOpenAPISchemaComplexity.ts b/src/generators/core/openapi/getOpenAPISchemaComplexity.ts index 3be08a35..ed596a28 100644 --- a/src/generators/core/openapi/getOpenAPISchemaComplexity.ts +++ b/src/generators/core/openapi/getOpenAPISchemaComplexity.ts @@ -1,5 +1,4 @@ import { OpenAPIV3 } from "openapi-types"; -import { match } from "ts-pattern"; import { CompositeType, PrimitiveType } from "@/generators/types/openapi"; import { sum } from "@/generators/utils/math.utils"; @@ -107,17 +106,24 @@ export function getOpenAPISchemaComplexity( } function complexityByType(schema: OpenAPIV3.SchemaObject) { - return match(schema.type) - .with("string", "number", "integer", "boolean", () => 1) - .otherwise(() => 0); + return schema.type === "string" || schema.type === "number" || schema.type === "integer" || schema.type === "boolean" + ? 1 + : 0; } function complexityByComposite(type?: CompositeType) { - return match(type) - .with("oneOf", () => 2) - .with("anyOf", () => 3) - .with("allOf", () => 2) - .with("enum", "array", "empty-object", () => 1) - .with("object", () => 2) - .otherwise(() => 0); + switch (type) { + case "anyOf": + return 3; + case "oneOf": + case "allOf": + case "object": + return 2; + case "enum": + case "array": + case "empty-object": + return 1; + default: + return 0; + } } diff --git a/src/generators/core/zod/getZodChain.ts b/src/generators/core/zod/getZodChain.ts index a6df93d4..8ee50ea9 100644 --- a/src/generators/core/zod/getZodChain.ts +++ b/src/generators/core/zod/getZodChain.ts @@ -1,11 +1,12 @@ import { OpenAPIV3 } from "openapi-types"; -import { match } from "ts-pattern"; import { GenerateOptions } from "@/generators/types/options"; import { escapeControlCharacters, unwrapQuotesIfNeeded } from "@/generators/utils/openapi.utils"; import { ZodSchemaMetaData } from "./ZodSchema.class"; +const zodChainCache = new WeakMap>>(); + export function getZodChain({ schema, meta, @@ -15,13 +16,31 @@ export function getZodChain({ meta?: ZodSchemaMetaData; options: GenerateOptions; }) { + let optionsCache = zodChainCache.get(options); + if (!optionsCache) { + optionsCache = new WeakMap(); + zodChainCache.set(options, optionsCache); + } + let schemaCache = optionsCache.get(schema); + if (!schemaCache) { + schemaCache = new Map(); + optionsCache.set(schema, schemaCache); + } + const cacheKey = `${Boolean(meta?.isRequired)}|${Boolean(meta?.isParentPartial)}`; + const cached = schemaCache.get(cacheKey); + if (cached !== undefined) { + return cached; + } + const chains: string[] = []; - match(schema.type) - .with("string", () => chains.push(getZodChainableStringValidations(schema))) - .with("number", "integer", () => chains.push(getZodChainableNumberValidations(schema))) - .with("array", () => chains.push(getZodChainableArrayValidations(schema))) - .otherwise(() => void 0); + if (schema.type === "string") { + chains.push(getZodChainableStringValidations(schema)); + } else if (schema.type === "number" || schema.type === "integer") { + chains.push(getZodChainableNumberValidations(schema)); + } else if (schema.type === "array") { + chains.push(getZodChainableArrayValidations(schema)); + } if (typeof schema.description === "string" && schema.description !== "" && options.withDescription) { chains.push(`describe(${JSON.stringify(schema.description)})`); @@ -35,7 +54,9 @@ export function getZodChain({ .filter(Boolean) .join("."); - return output ? `.${output}` : ""; + const chain = output ? `.${output}` : ""; + schemaCache.set(cacheKey, chain); + return chain; } function getZodChainablePresence({ @@ -64,9 +85,10 @@ function getZodChainablePresence({ function getZodChainableDefault(schema: OpenAPIV3.SchemaObject) { if (schema.default !== undefined) { - const value = match(schema.type) - .with("number", "integer", () => unwrapQuotesIfNeeded(schema.default)) - .otherwise(() => JSON.stringify(schema.default)); + const value = + schema.type === "number" || schema.type === "integer" + ? unwrapQuotesIfNeeded(schema.default) + : JSON.stringify(schema.default); return `default(${value})`; } diff --git a/src/generators/core/zod/getZodSchema.ts b/src/generators/core/zod/getZodSchema.ts index 3849a5b1..2dd126c4 100644 --- a/src/generators/core/zod/getZodSchema.ts +++ b/src/generators/core/zod/getZodSchema.ts @@ -1,5 +1,4 @@ import { OpenAPIV3 } from "openapi-types"; -import { match } from "ts-pattern"; import { ANY_SCHEMA, @@ -197,13 +196,17 @@ function getReferenceZodSchema({ schema, zodSchema, resolver, meta, tag }: GetPa return; } - const refsPath = zodSchema.meta.referencedBy - .slice(0, -1) - .map((prev) => (prev.ref ? (resolver.getZodSchemaNameByRef(prev.ref) ?? prev.ref) : undefined)) - .filter(Boolean); const zodSchemaName = resolver.getZodSchemaNameByRef(schema.$ref); - if (refsPath.length > 1 && refsPath.includes(zodSchemaName)) { - return zodSchema.assign(resolver.getCodeByZodSchemaName(zodSchema.ref!)!); + // A cycle can only exist when there are at least two ancestors. Most endpoint + // references are roots, so avoid allocating and resolving an empty ancestry path. + if (zodSchema.meta.referencedBy.length > 2) { + const refsPath = zodSchema.meta.referencedBy + .slice(0, -1) + .map((prev) => (prev.ref ? (resolver.getZodSchemaNameByRef(prev.ref) ?? prev.ref) : undefined)) + .filter(Boolean); + if (refsPath.length > 1 && refsPath.includes(zodSchemaName)) { + return zodSchema.assign(resolver.getCodeByZodSchemaName(zodSchema.ref!)!); + } } let result = resolver.getCodeByZodSchemaName(schema.$ref); @@ -345,24 +348,32 @@ function getPrimitiveZodSchema({ schema, zodSchema, resolver, meta, tag }: GetPa ); } - return zodSchema.assign( - match(schemaType) - .with("integer", () => - match(schema.type) - .with("integer", () => INT_SCHEMA) - .otherwise(() => NUMBER_SCHEMA), - ) - .with("string", () => - match(schema.format) - .with("binary", () => BLOB_SCHEMA) - .with("email", () => EMAIL_SCHEMA) - .with("hostname", "uri", () => URL_SCHEMA) - .with("uuid", () => UUID_SCHEMA) - .with("date-time", () => DATETIME_SCHEMA) - .otherwise(() => STRING_SCHEMA), - ) - .otherwise((type) => `z.${type}()`), - ); + if (schemaType === "integer") { + return zodSchema.assign(schema.type === "integer" ? INT_SCHEMA : NUMBER_SCHEMA); + } + if (schemaType === "string") { + let stringSchema = STRING_SCHEMA; + switch (schema.format) { + case "binary": + stringSchema = BLOB_SCHEMA; + break; + case "email": + stringSchema = EMAIL_SCHEMA; + break; + case "hostname": + case "uri": + stringSchema = URL_SCHEMA; + break; + case "uuid": + stringSchema = UUID_SCHEMA; + break; + case "date-time": + stringSchema = DATETIME_SCHEMA; + break; + } + return zodSchema.assign(stringSchema); + } + return zodSchema.assign(`z.${schemaType}()`); } } @@ -409,7 +420,8 @@ function getEnumZodSchema({ resolver, schema, zodSchema, meta, tag }: GetPartial } export function getEnumZodSchemaCode(schema: OpenAPIV3.SchemaObject) { - return `${ENUM_SCHEMA}([${schema.enum?.map((value) => (value === null ? "null" : `"${value}"`)).join(", ")}])`; + const values = schema.enum?.filter((value) => value !== null) ?? []; + return `${ENUM_SCHEMA}([${values.map((value) => `"${value}"`).join(", ")}])`; } export function getEnumZodSchemaCodeFromEnumNames(enumNames: string[]) { diff --git a/src/generators/core/zod/getZodSchemasFromOpenAPIDoc.ts b/src/generators/core/zod/getZodSchemasFromOpenAPIDoc.ts index b8d40177..90dc1e43 100644 --- a/src/generators/core/zod/getZodSchemasFromOpenAPIDoc.ts +++ b/src/generators/core/zod/getZodSchemasFromOpenAPIDoc.ts @@ -23,12 +23,16 @@ export function getZodSchemasFromOpenAPIDoc(resolver: SchemaResolver, profiler?: if (zodSchemas[zodSchemaName]) { return; } - const tag = resolver.getTagByZodSchemaName(zodSchemaName); const schemaObject = resolver.resolveObject(schema); if (schemaObject.enum) { enumZodSchemas[zodSchemaName] = getEnumZodSchemaCode(schemaObject); } else { + const existingCode = resolver.getCodeByZodSchemaName(zodSchemaName); + if (existingCode !== undefined) { + zodSchemas[zodSchemaName] = existingCode; + return; + } zodSchemas[zodSchemaName] = p.runSync("zod.extract.schemaToCode", () => getZodSchema({ schema, @@ -49,7 +53,7 @@ export function getEnumZodSchemasFromOpenAPIDoc(resolver: SchemaResolver) { Object.entries(resolver.openApiDoc.components?.schemas ?? {}).forEach(([name, schema]) => { const zodSchemaName = getZodSchemaName(name, resolver.options.schemaSuffix); const schemaObject = resolver.resolveObject(schema); - if (!enumZodSchemas.find((enumZodSchema) => enumZodSchema.zodSchemaName === zodSchemaName) && schemaObject.enum) { + if (schemaObject.enum) { enumZodSchemas.push({ zodSchemaName, code: getEnumZodSchemaCode(schemaObject) }); } }); diff --git a/src/generators/generate/generateAcl.ts b/src/generators/generate/generateAcl.ts index 754320d0..f0bf321f 100644 --- a/src/generators/generate/generateAcl.ts +++ b/src/generators/generate/generateAcl.ts @@ -1,5 +1,5 @@ import { ACL_APP_ABILITIES, CASL_ABILITY_BINDING, CASL_ABILITY_IMPORT } from "@/generators/const/acl.const"; -import { PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; +import { CONFIG_PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; import { Endpoint } from "@/generators/types/endpoint"; import { GenerateType, GenerateTypeParams, Import } from "@/generators/types/generate"; import { @@ -20,6 +20,11 @@ import { capitalize } from "@/generators/utils/string.utils"; import { getEndpointTag } from "@/generators/utils/tag.utils"; export function generateAcl({ resolver, data, tag }: GenerateTypeParams) { + const nativeContent = ( + resolver as GenerateTypeParams["resolver"] & { getNativeRenderedAcl?: (tag: string) => string | undefined } + ).getNativeRenderedAcl?.(tag); + if (nativeContent) return nativeContent; + const aclData = getAclData({ resolver, data, tag }); if (!aclData) { return; @@ -37,7 +42,7 @@ export function generateAcl({ resolver, data, tag }: GenerateTypeParams) { }; const workspaceContextImport: Import = { bindings: ["useWorkspaceContext"], - from: PACKAGE_IMPORT_PATH, + from: CONFIG_PACKAGE_IMPORT_PATH, }; const lines: string[] = []; @@ -67,6 +72,11 @@ export function generateAcl({ resolver, data, tag }: GenerateTypeParams) { } export function generateAppAcl({ resolver, data }: Omit) { + const nativeContent = ( + resolver as GenerateTypeParams["resolver"] & { getNativeRenderedShared?: (name: string) => string | undefined } + ).getNativeRenderedShared?.("appAcl"); + if (nativeContent) return nativeContent; + const { appAbilitiesType, hasAdditionalAbilityImports, modelsImports } = getAppAbilitiesType({ resolver, data }); const caslAbilityTupleImport: Import = { diff --git a/src/generators/generate/generateAppRestClient.ts b/src/generators/generate/generateAppRestClient.ts index f9b31343..21dbb58d 100644 --- a/src/generators/generate/generateAppRestClient.ts +++ b/src/generators/generate/generateAppRestClient.ts @@ -1,11 +1,13 @@ import { APP_REST_CLIENT_NAME } from "@/generators/const/deps.const"; -import { PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; +import { NATIVE_PACKAGE_IMPORT_PATH, PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; export function generateAppRestClient(resolver: SchemaResolver) { - return `import { RestClient } from "${PACKAGE_IMPORT_PATH}"; + const clientName = resolver.options.restClient === "native" ? "NativeRestClient" : "RestClient"; + const importPath = resolver.options.restClient === "native" ? NATIVE_PACKAGE_IMPORT_PATH : PACKAGE_IMPORT_PATH; + return `import { ${clientName} } from "${importPath}"; -export const ${APP_REST_CLIENT_NAME} = new RestClient({ +export const ${APP_REST_CLIENT_NAME} = new ${clientName}({ config: { baseURL: "${resolver.getBaseUrl()}" }, diff --git a/src/generators/generate/generateConfigs.test.ts b/src/generators/generate/generateConfigs.test.ts index 5acf310e..a2a6256b 100644 --- a/src/generators/generate/generateConfigs.test.ts +++ b/src/generators/generate/generateConfigs.test.ts @@ -311,6 +311,10 @@ describe("generateConfigs builderConfigs media upload", () => { expect(configsFile?.content).toContain("const uploadInstructions = await MediaApi.upload(data);"); expect(configsFile?.content).not.toContain("MediaApi.upload(data, file)"); + expect(configsFile?.content).toContain( + 'const method = (uploadInstructions.method?.toLowerCase() ?? "put") as "put" | "post";', + ); + expect(configsFile?.content).not.toContain("data?.method?.toLowerCase()"); expect(configsFile?.content).toContain("await axios[method](uploadInstructions.url, dataToSend,"); expect(configsFile?.content).toContain("return uploadInstructions;"); }); @@ -328,4 +332,24 @@ describe("generateConfigs builderConfigs media upload", () => { expect(configsFile?.content).toContain('import axios, { type AxiosRequestConfig } from "axios";'); }); + + it("imports the application client for native upload calls", () => { + const files = generateCodeFromOpenAPIDoc(mediaUploadDoc, { + ...DEFAULT_GENERATE_OPTIONS, + output: "test-output", + restClient: "native", + builderConfigs: true, + acl: false, + checkAcl: false, + }); + + const configsFile = files.find((file) => file.fileName.endsWith("/media/media.configs.ts")); + const queriesFile = files.find((file) => file.fileName.endsWith("/media/media.queries.ts")); + + for (const file of [configsFile, queriesFile]) { + expect(file?.content).toContain('import { AppRestClient } from "@/data/app-rest-client";'); + expect(file?.content).toContain("await AppRestClient.upload("); + expect(file?.content).not.toContain('from "axios"'); + } + }); }); diff --git a/src/generators/generate/generateConfigs.ts b/src/generators/generate/generateConfigs.ts index e06e6360..e3f92e14 100644 --- a/src/generators/generate/generateConfigs.ts +++ b/src/generators/generate/generateConfigs.ts @@ -1,6 +1,11 @@ import { ACL_CHECK_HOOK } from "@/generators/const/acl.const"; -import { AXIOS_DEFAULT_IMPORT_NAME, AXIOS_REQUEST_CONFIG_TYPE } from "@/generators/const/endpoints.const"; -import { PACKAGE_IMPORT_PATH, ACL_PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; +import { + AXIOS_DEFAULT_IMPORT_NAME, + AXIOS_REQUEST_CONFIG_TYPE, + getRequestConfigTypeName, +} from "@/generators/const/endpoints.const"; +import { REST_PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; +import { APP_REST_CLIENT_NAME } from "@/generators/const/deps.const"; import { MUTATION_EFFECTS, QUERY_MODULE_ENUM, @@ -20,7 +25,11 @@ import { getEndpointsImports } from "@/generators/utils/generate/generate.import import { getBuilderConfigs } from "@/generators/utils/generate/generate.configs.utils"; import { renderAclCheckCall } from "@/generators/utils/generate/generate.acl.utils"; import { getNamespaceName } from "@/generators/utils/namespace.utils"; -import { getQueryModulesImportPath, getQueryTypesImportPath } from "@/generators/utils/generate/generate.utils"; +import { + getAppRestClientImportPath, + getQueryModulesImportPath, + getQueryTypesImportPath, +} from "@/generators/utils/generate/generate.utils"; import { getEndpointTag } from "@/generators/utils/tag.utils"; import { QUERY_HOOKS } from "@/generators/const/queries.const"; @@ -46,14 +55,24 @@ export function generateConfigs(generateTypeParams: GenerateTypeParams) { const hasMutationEffects = resolver.options.mutationEffects && hasMutation; const hasMutationDefaultOnError = resolver.options.mutationDefaultOnError && hasMutation; const hasAxiosRequestConfig = resolver.options.axiosRequestConfig; - const hasAxiosDefaultImport = endpoints.some((e) => e.mediaUpload); - const hasAxiosImport = hasAxiosRequestConfig || hasAxiosDefaultImport; + const nativeClient = resolver.options.restClient === "native"; + const hasAxiosDefaultImport = !nativeClient && endpoints.some((e) => e.mediaUpload); + const hasAxiosImport = !nativeClient && (hasAxiosRequestConfig || hasAxiosDefaultImport); const axiosImport: Import = { defaultImport: hasAxiosDefaultImport ? AXIOS_DEFAULT_IMPORT_NAME : undefined, bindings: [], typeBindings: hasAxiosImport ? [AXIOS_REQUEST_CONFIG_TYPE] : [], from: "axios", }; + const nativeImport: Import = { + bindings: [], + typeBindings: nativeClient && hasAxiosRequestConfig ? [getRequestConfigTypeName("native")] : [], + from: REST_PACKAGE_IMPORT_PATH, + }; + const appRestClientImport: Import = { + bindings: [APP_REST_CLIENT_NAME], + from: getAppRestClientImportPath(resolver.options), + }; const endpointsImports = getEndpointsImports({ tag, @@ -75,7 +94,7 @@ export function generateConfigs(generateTypeParams: GenerateTypeParams) { const mutationEffectsImport: Import = { bindings: [MUTATION_EFFECTS.hookName], typeBindings: [MUTATION_EFFECTS.optionsType], - from: PACKAGE_IMPORT_PATH, + from: resolver.options.mutationEffectsImportPath, }; const queryModulesImport: Import = { @@ -85,7 +104,7 @@ export function generateConfigs(generateTypeParams: GenerateTypeParams) { const aclCheckImport: Import = { bindings: [ACL_CHECK_HOOK], - from: ACL_PACKAGE_IMPORT_PATH, + from: resolver.options.aclCheckImportPath, }; const hasDynamicInputsImport = configs.some( @@ -106,6 +125,10 @@ export function generateConfigs(generateTypeParams: GenerateTypeParams) { if (hasAxiosImport) { lines.push(renderImport(axiosImport)); } + if (nativeImport.typeBindings?.length) lines.push(renderImport(nativeImport)); + if (nativeClient && endpoints.some((endpoint) => endpoint.mediaUpload)) { + lines.push(renderImport(appRestClientImport)); + } if (hasZodImport) { lines.push(renderImport(ZOD_IMPORT)); } @@ -234,7 +257,7 @@ function renderMutationContent(resolver: any, endpoint: Endpoint, tag: string) { const lines: string[] = []; lines.push( - `(options?: AppMutationOptions${hasMutationEffects ? ` & ${MUTATION_EFFECTS.optionsType}` : ""}${hasAxiosRequestConfig ? `, config?: ${AXIOS_REQUEST_CONFIG_TYPE}` : ""}) => {`, + `(options?: AppMutationOptions${hasMutationEffects ? ` & ${MUTATION_EFFECTS.optionsType}` : ""}${hasAxiosRequestConfig ? `, config?: ${getRequestConfigTypeName(resolver.options.restClient)}` : ""}) => {`, ); if (hasMutationDefaultOnError) { lines.push(" const queryConfig = OpenApiQueryConfig.useConfig();"); diff --git a/src/generators/generate/generateDomainErrors.ts b/src/generators/generate/generateDomainErrors.ts index ebd3e5a4..9a9ba2cc 100644 --- a/src/generators/generate/generateDomainErrors.ts +++ b/src/generators/generate/generateDomainErrors.ts @@ -14,7 +14,18 @@ interface DomainErrorDef { description?: string; } -export function generateDomainErrors({ data }: { resolver: SchemaResolver; data: GenerateData }): string | undefined { +export function generateDomainErrors({ + resolver, + data, +}: { + resolver: SchemaResolver; + data: GenerateData; +}): string | undefined { + const nativeContent = ( + resolver as SchemaResolver & { getNativeRenderedShared?: (name: string) => string | undefined } + ).getNativeRenderedShared?.("domainErrors"); + if (nativeContent) return nativeContent; + const byDomain = new Map>(); for (const { endpoints } of data.values()) { diff --git a/src/generators/generate/generateEndpoints.ts b/src/generators/generate/generateEndpoints.ts index 35c65a25..cc3c36c5 100644 --- a/src/generators/generate/generateEndpoints.ts +++ b/src/generators/generate/generateEndpoints.ts @@ -1,6 +1,11 @@ import { APP_REST_CLIENT_NAME, ZOD_EXTENDED } from "@/generators/const/deps.const"; -import { AXIOS_IMPORT, AXIOS_REQUEST_CONFIG_NAME, AXIOS_REQUEST_CONFIG_TYPE } from "@/generators/const/endpoints.const"; -import { ZOD_PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; +import { + AXIOS_IMPORT, + AXIOS_REQUEST_CONFIG_NAME, + AXIOS_REQUEST_CONFIG_TYPE, + getRequestConfigTypeName, +} from "@/generators/const/endpoints.const"; +import { REST_PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; import { ZOD_IMPORT } from "@/generators/const/zod.const"; import { Endpoint, EndpointParameter } from "@/generators/types/endpoint"; import { GenerateType, GenerateTypeParams, Import } from "@/generators/types/generate"; @@ -22,6 +27,10 @@ import { getNamespaceName } from "@/generators/utils/namespace.utils"; import { isNamedZodSchema } from "@/generators/utils/zod-schema.utils"; export function generateEndpoints({ resolver, data, tag }: GenerateTypeParams) { + const nativeContent = ( + resolver as GenerateTypeParams["resolver"] & { getNativeRenderedEndpoints?: (tag: string) => string | undefined } + ).getNativeRenderedEndpoints?.(tag); + if (nativeContent) return nativeContent; if (shouldInlineEndpointsForTag(tag, resolver.options)) { return; } @@ -38,12 +47,19 @@ export function generateEndpoints({ resolver, data, tag }: GenerateTypeParams) { const hasAxiosRequestConfig = resolver.options.axiosRequestConfig; const hasGetEndpoints = endpoints.some((endpoint) => endpoint.method === "get"); - const hasAxiosImport = hasAxiosRequestConfig || hasGetEndpoints; + const nativeClient = resolver.options.restClient === "native"; + const hasAxiosImport = !nativeClient && (hasAxiosRequestConfig || hasGetEndpoints); const axiosImport: Import = { bindings: [], typeBindings: hasAxiosImport ? [AXIOS_REQUEST_CONFIG_TYPE] : [], from: AXIOS_IMPORT.from, }; + const nativeImport: Import = { + bindings: [], + typeBindings: + nativeClient && (hasAxiosRequestConfig || hasGetEndpoints) ? [getRequestConfigTypeName("native")] : [], + from: REST_PACKAGE_IMPORT_PATH, + }; const generateParse = resolver.options.parseRequestParams; @@ -59,7 +75,7 @@ export function generateEndpoints({ resolver, data, tag }: GenerateTypeParams) { const hasZodExtendedImport = resolver.options.parseRequestParams && endpointParamsParseSchemas.length > 0; const zodExtendedImport: Import = { bindings: [ZOD_EXTENDED.namespace], - from: ZOD_PACKAGE_IMPORT_PATH, + from: resolver.options.zodImportPath, }; const modelsImports = getModelsImports({ @@ -74,6 +90,7 @@ export function generateEndpoints({ resolver, data, tag }: GenerateTypeParams) { if (hasAxiosImport) { lines.push(renderImport(axiosImport)); } + if (nativeImport.typeBindings?.length) lines.push(renderImport(nativeImport)); if (hasZodImport) { lines.push(renderImport(ZOD_IMPORT)); } @@ -101,7 +118,7 @@ export function generateEndpoints({ resolver, data, tag }: GenerateTypeParams) { const hasRequestConfigParam = hasAxiosRequestConfig || endpoint.method === "get"; lines.push( - `export const ${getEndpointName(endpoint)} = (${endpointParams}${hasRequestConfigParam ? `${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType()}` : ""}) => {`, + `export const ${getEndpointName(endpoint)} = (${endpointParams}${hasRequestConfigParam ? `${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType(resolver)}` : ""}) => {`, ); lines.push(` return ${APP_REST_CLIENT_NAME}.${endpoint.method}(`); lines.push(` ${renderRequestInfo(resolver, endpoint, tag)},`); @@ -131,13 +148,15 @@ function renderRequestInfo(resolver: GenerateTypeParams["resolver"], endpoint: E const schemaName = getImportedZodSchemaName( resolver, endpoint.response, - resolver.options.modelsInCommon && resolver.options.splitByTags ? tag : undefined, + (resolver.options.modelsInCommon || resolver.options.modelsInModules) && resolver.options.splitByTags + ? tag + : undefined, ); return `{ resSchema: ${schemaName} }`; } -function getRequestConfigType() { - return `${AXIOS_REQUEST_CONFIG_TYPE} & { allowInvalidResponseData?: boolean }`; +function getRequestConfigType(resolver: GenerateTypeParams["resolver"]) { + return `${getRequestConfigTypeName(resolver.options.restClient)} & { allowInvalidResponseData?: boolean }`; } function renderImport(importData: Import) { diff --git a/src/generators/generate/generateModels.test.ts b/src/generators/generate/generateModels.test.ts index b75a2f65..b3d318a4 100644 --- a/src/generators/generate/generateModels.test.ts +++ b/src/generators/generate/generateModels.test.ts @@ -1,3 +1,7 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + import { OpenAPIV3 } from "openapi-types"; import { describe, expect, test } from "vitest"; @@ -45,8 +49,102 @@ describe("generateModels", () => { expect(models).toContain("export type AccessTag = { player?: Player };"); expect(models).toContain("export const PlayerSchema: z.ZodObject & z.ZodType ="); expect(models).toContain("export const AccessTagSchema: z.ZodObject & z.ZodType ="); + expect(models).not.toContain( + "export const PlayerSchema: z.ZodObject & z.ZodType = z.compile(", + ); + expect(models).not.toContain( + "export const AccessTagSchema: z.ZodObject & z.ZodType = z.compile(", + ); expect(models).not.toContain("ZodObject"); - expect(models).toContain("export const UnrelatedSchema ="); + expect(models).toContain("export const UnrelatedSchema = z.compile(z.object("); + }); + + test("compiles every non-circular generated schema", () => { + const doc = { + openapi: "3.0.3", + info: { title: "Compiled schemas", version: "1.0.0" }, + paths: {}, + components: { + schemas: { + Status: { type: "string", enum: ["active", "inactive"] }, + User: { + type: "object", + required: ["status"], + properties: { status: { $ref: "#/components/schemas/Status" } }, + }, + }, + }, + } as OpenAPIV3.Document; + const options = { + ...DEFAULT_GENERATE_OPTIONS, + excludeRedundantZodSchemas: false, + splitByTags: false, + tsNamespaces: false, + }; + const { resolver, data } = getDataFromOpenAPIDoc(doc, options); + + const models = generateModels({ resolver, data, tag: options.defaultTag }); + + expect(models).toContain('export const StatusSchema = z.compile(z.enum(["active", "inactive"]));'); + expect(models).toContain("export const UserSchema = z.compile(z.object({ status: StatusSchema }));"); + expect(models).toContain("export const Status = StatusSchema.enum;"); + }); + + test("generated compiled schemas retain runtime behavior", async () => { + const doc = { + openapi: "3.0.3", + info: { title: "Compiled schema runtime", version: "1.0.0" }, + paths: {}, + components: { + schemas: { + Status: { type: "string", enum: ["active", "inactive"] }, + User: { + type: "object", + required: ["status"], + properties: { status: { $ref: "#/components/schemas/Status" } }, + }, + Category: { + type: "object", + required: ["name", "children"], + properties: { + name: { type: "string" }, + children: { type: "array", items: { $ref: "#/components/schemas/Category" } }, + }, + }, + }, + }, + } as OpenAPIV3.Document; + const options = { + ...DEFAULT_GENERATE_OPTIONS, + excludeRedundantZodSchemas: false, + splitByTags: false, + tsNamespaces: false, + }; + const { resolver, data } = getDataFromOpenAPIDoc(doc, options); + const models = generateModels({ resolver, data, tag: options.defaultTag }); + expect(models).toBeDefined(); + if (!models) { + throw new Error("Expected generated models"); + } + const tempDir = await fs.mkdtemp(path.join(process.cwd(), ".generated-models-")); + const modulePath = path.join(tempDir, "models.ts"); + + try { + await fs.writeFile(modulePath, models); + const generated = await import(`${pathToFileURL(modulePath).href}?cacheBust=${Date.now()}`); + + expect(generated.Status).toEqual({ active: "active", inactive: "inactive" }); + expect(generated.UserSchema.safeParse({ status: "active" }).success).toBe(true); + expect(generated.UserSchema.safeParse({ status: "unknown" }).success).toBe(false); + + const category: { name: string; children: unknown[] } = { name: "root", children: [] }; + category.children.push(category); + const parsedCategory = generated.CategorySchema.parse(category); + expect(parsedCategory.children[0]).toBe(parsedCategory); + expect(generated.CategorySchema.safeParse({ name: 1, children: [] }).success).toBe(false); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } }); test("uses lazy properties for circular refs inside allOf object composition", () => { diff --git a/src/generators/generate/generateModels.ts b/src/generators/generate/generateModels.ts index 10dfe37b..8c4a4b03 100644 --- a/src/generators/generate/generateModels.ts +++ b/src/generators/generate/generateModels.ts @@ -19,6 +19,11 @@ import { isReferenceObject } from "@/generators/utils/openapi-schema.utils"; import { wrapWithQuotesIfNeeded } from "@/generators/utils/openapi.utils"; export function generateModels({ resolver, data, tag }: GenerateTypeParams) { + const nativeContent = ( + resolver as GenerateTypeParams["resolver"] & { getNativeRenderedModels?: (tag: string) => string | undefined } + ).getNativeRenderedModels?.(tag); + if (nativeContent) return nativeContent; + if (resolver.options.modelsInCommon && resolver.options.splitByTags && tag !== resolver.options.defaultTag) { return renderModelsProxy({ resolver, data, tag }); } @@ -48,7 +53,7 @@ export function generateModels({ resolver, data, tag }: GenerateTypeParams) { for (const [key, code] of Object.entries(zodSchemas)) { const schemaRef = resolver.getRefByZodSchemaName(key); zodSchemasData[key] = { - code, + code: resolver.options.modelsInModules ? code.replace(/\b[A-Z][A-Za-z0-9]*Models\./g, "") : code, isCircular: schemaRef ? resolver.isSchemaCircular(schemaRef) : false, isEnum: isEnumZodSchema(code), schemaObj: resolver.getZodSchemaObj(key), @@ -73,7 +78,8 @@ export function generateModels({ resolver, data, tag }: GenerateTypeParams) { lines.push(`export type ${inferredTypeName} = ${renderSchemaType(zodSchema.schemaObj, resolver)};`); } const typeAnnotation = zodSchema.isCircular ? `: z.ZodObject & z.ZodType<${inferredTypeName}>` : ""; - lines.push(`export const ${name}${typeAnnotation} = ${zodSchema.code};`); + const schemaCode = zodSchema.isCircular ? zodSchema.code : `z.compile(${zodSchema.code})`; + lines.push(`export const ${name}${typeAnnotation} = ${schemaCode};`); if (!zodSchema.isCircular) { lines.push(`export type ${inferredTypeName} = z.infer;`); } @@ -242,8 +248,9 @@ function getUsedSchemaNames({ } } - while (queue.length > 0) { - const schemaName = queue.shift(); + let queueIndex = 0; + while (queueIndex < queue.length) { + const schemaName = queue[queueIndex++]; if (!schemaName) { continue; } diff --git a/src/generators/generate/generateQueries.test.ts b/src/generators/generate/generateQueries.test.ts index 87ced3b2..578d2892 100644 --- a/src/generators/generate/generateQueries.test.ts +++ b/src/generators/generate/generateQueries.test.ts @@ -295,7 +295,7 @@ describe("generateQueries workspaceContext", () => { const aclFile = files.find((file) => file.fileName.endsWith("/workspace/workspace.acl.ts")); - expect(aclFile?.content).toContain('import { useWorkspaceContext } from "@povio/openapi-codegen-cli";'); + expect(aclFile?.content).toContain('import { useWorkspaceContext } from "@povio/openapi-codegen-cli/config";'); expect(aclFile?.content).toContain("object?: { officeId: string, positionId: string, }"); expect(aclFile?.content).toContain("export const useCanUseGetPosition = ("); expect(aclFile?.content).toContain("object: { officeId?: string, positionId: string, }"); @@ -453,8 +453,10 @@ describe("generateQueries mutationEffects + infiniteQuery", () => { expect(queriesFile?.content).not.toContain("MutationEffectsOptions>)"); - expect(queriesFile?.content).toContain("pageParam * limitParam < (totalItems ?? 0)"); + expect(queriesFile?.content).toContain( + '}: Awaited> & Partial>)', + ); + expect(queriesFile?.content).toContain("pageParam * pageSize < (totalItems ?? 0)"); // queryFn retains precise pageParam: number typing and forwards TanStack's abort signal to Axios expect(queriesFile?.content).toContain( "queryFn: ({ pageParam, signal }: { pageParam: number; signal: AbortSignal })", @@ -483,7 +485,50 @@ describe("generateQueries mutationEffects + infiniteQuery", () => { // the options type defaults prefetchInfiniteQuery generics to unknown, which conflicts with the // generated queryFn expecting pageParam: number. expect(queriesFile?.content).toContain("...(options as {})"); + expect(queriesFile?.content).toContain( + "return throwOnError ? queryClient.fetchInfiniteQuery(queryOptions) : queryClient.prefetchInfiniteQuery(queryOptions)", + ); + expect(queriesFile?.content).toContain("config?: AxiosRequestConfig, throwOnError = false"); + expect(queriesFile?.content).toContain("listInfiniteQueryOptions({ limit }, config)"); + expect(queriesFile?.content).not.toContain("): void =>"); // no pages emitted anywhere expect(queriesFile?.content).not.toContain("pages:"); }); + + it("uses configurable application hook import paths", () => { + const files = generateCodeFromOpenAPIDoc(openApiDoc, { + ...DEFAULT_GENERATE_OPTIONS, + output: "test-output", + mutationEffectsImportPath: "@/data/useMutationEffects", + aclCheckImportPath: "@/data/acl/useAclCheck", + zodImportPath: "@/data/zod.extended", + builderConfigs: false, + }); + + const queriesFile = files.find((file) => file.fileName.endsWith("/workspace/workspace.queries.ts")); + const apiFile = files.find((file) => file.fileName.endsWith("/workspace/workspace.api.ts")); + + expect(queriesFile?.content).toContain('from "@/data/useMutationEffects";'); + expect(queriesFile?.content).toContain('from "@/data/acl/useAclCheck";'); + expect(apiFile?.content).toContain('from "@/data/zod.extended";'); + }); + + it("generates an Axios-free native REST client mode", () => { + const files = generateCodeFromOpenAPIDoc(openApiDoc, { + ...DEFAULT_GENERATE_OPTIONS, + output: "test-output", + restClient: "native", + axiosRequestConfig: true, + builderConfigs: false, + }); + + const appClient = files.find((file) => file.fileName.endsWith("/app-rest-client.ts")); + const apiFile = files.find((file) => file.fileName.endsWith("/workspace/workspace.api.ts")); + const queriesFile = files.find((file) => file.fileName.endsWith("/workspace/workspace.queries.ts")); + + expect(appClient?.content).toContain('import { NativeRestClient } from "@povio/openapi-codegen-cli/native";'); + expect(apiFile?.content).toContain("config?: TransportRequestConfig"); + expect(queriesFile?.content).toContain("config?: TransportRequestConfig"); + expect([appClient, apiFile, queriesFile].map((file) => file?.content).join("\n")).not.toContain('from "axios"'); + }); }); diff --git a/src/generators/generate/generateQueries.ts b/src/generators/generate/generateQueries.ts index adde7667..7d5cb3f7 100644 --- a/src/generators/generate/generateQueries.ts +++ b/src/generators/generate/generateQueries.ts @@ -11,12 +11,10 @@ import { AXIOS_IMPORT, AXIOS_REQUEST_CONFIG_NAME, AXIOS_REQUEST_CONFIG_TYPE, + getRequestConfigTypeName, + NATIVE_RESPONSE_TYPE, } from "@/generators/const/endpoints.const"; -import { - ACL_PACKAGE_IMPORT_PATH, - PACKAGE_IMPORT_PATH, - ZOD_PACKAGE_IMPORT_PATH, -} from "@/generators/const/package.const"; +import { CONFIG_PACKAGE_IMPORT_PATH, REST_PACKAGE_IMPORT_PATH } from "@/generators/const/package.const"; import { QUERIES_MODULE_NAME, QUERY_HOOKS, QUERY_IMPORT } from "@/generators/const/queries.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; import { Endpoint, EndpointParameter } from "@/generators/types/endpoint"; @@ -75,9 +73,15 @@ const endpointParamMappingCache = new WeakMap< SchemaResolver, WeakMap>> >(); +const workspaceParamNamesCache = new WeakMap>(); +const endpointParamDescriptionCache = new WeakMap(); export function generateQueries(params: GenerateTypeParams) { const { resolver, data, tag } = params; + const nativeContent = ( + resolver as GenerateTypeParams["resolver"] & { getNativeRenderedQueries?: (tag: string) => string | undefined } + ).getNativeRenderedQueries?.(tag); + if (nativeContent) return nativeContent; const mutationScopeOption = resolver.options.mutationScope; if (mutationScopeOption && typeof mutationScopeOption === "object") { @@ -94,15 +98,28 @@ export function generateQueries(params: GenerateTypeParams) { const endpointGroups = groupEndpoints(endpoints, resolver); const hasAxiosRequestConfig = resolver.options.axiosRequestConfig; - const hasAxiosDefaultImport = endpoints.some(({ mediaUpload }) => mediaUpload); + const nativeClient = resolver.options.restClient === "native"; + const hasNativeMediaUpload = nativeClient && endpoints.some(({ mediaUpload }) => mediaUpload); + const requestConfigType = getRequestConfigTypeName(resolver.options.restClient); + const hasAxiosDefaultImport = !nativeClient && endpoints.some(({ mediaUpload }) => mediaUpload); const hasGetEndpoints = endpoints.some((endpoint) => endpoint.method === "get"); - const hasAxiosImport = hasAxiosRequestConfig || hasAxiosDefaultImport || hasGetEndpoints; + const hasAxiosImport = !nativeClient && (hasAxiosRequestConfig || hasAxiosDefaultImport || hasGetEndpoints); const axiosImport: Import = { defaultImport: hasAxiosDefaultImport ? AXIOS_DEFAULT_IMPORT_NAME : undefined, bindings: [], typeBindings: hasAxiosImport ? [AXIOS_REQUEST_CONFIG_TYPE] : [], from: AXIOS_IMPORT.from, }; + const nativeTransportImport: Import = { + bindings: [], + typeBindings: nativeClient + ? [ + ...(hasAxiosRequestConfig || hasGetEndpoints ? [requestConfigType] : []), + ...(endpoints.some(({ mediaDownload }) => mediaDownload) ? [NATIVE_RESPONSE_TYPE] : []), + ] + : [], + from: REST_PACKAGE_IMPORT_PATH, + }; const { queryEndpoints, infiniteQueryEndpoints, mutationEndpoints, aclEndpoints } = endpointGroups; const hasMutationDefaultOnError = resolver.options.mutationDefaultOnError && mutationEndpoints.length > 0; @@ -127,13 +144,13 @@ export function generateQueries(params: GenerateTypeParams) { const mutationEffectsImport: Import = { bindings: [...(mutationEndpoints.length > 0 ? [MUTATION_EFFECTS.hookName] : [])], typeBindings: [...(mutationEndpoints.length > 0 ? [MUTATION_EFFECTS.optionsType] : [])], - from: PACKAGE_IMPORT_PATH, + from: resolver.options.mutationEffectsImportPath, }; const hasAclCheck = resolver.options.checkAcl && aclEndpoints.length > 0; const aclCheckImport: Import = { bindings: [ACL_CHECK_HOOK], - from: ACL_PACKAGE_IMPORT_PATH, + from: resolver.options.aclCheckImportPath, }; const queryTypesImport: Import = { @@ -157,7 +174,7 @@ export function generateQueries(params: GenerateTypeParams) { endpoints.some((endpoint) => getWorkspaceParamNames(resolver, endpoint).length > 0); const workspaceContextImport: Import = { bindings: ["useWorkspaceContext"], - from: PACKAGE_IMPORT_PATH, + from: CONFIG_PACKAGE_IMPORT_PATH, }; const endpointParams = endpoints.flatMap((endpoint) => endpoint.parameters) as EndpointParameter[]; @@ -178,7 +195,7 @@ export function generateQueries(params: GenerateTypeParams) { }; const zodExtendedImport: Import = { bindings: [ZOD_EXTENDED.namespace], - from: ZOD_PACKAGE_IMPORT_PATH, + from: resolver.options.zodImportPath, }; const modelsImports = getModelsImports({ @@ -208,8 +225,11 @@ export function generateQueries(params: GenerateTypeParams) { if (hasAxiosImport) { lines.push(renderImport(axiosImport)); } - if (inlineEndpoints) { + if (nativeTransportImport.typeBindings?.length) lines.push(renderImport(nativeTransportImport)); + if (inlineEndpoints || hasNativeMediaUpload) { lines.push(renderImport(appRestClientImport)); + } + if (inlineEndpoints) { if (hasZodImport) { lines.push(renderImport(ZOD_IMPORT)); } @@ -318,11 +338,17 @@ function getEndpointParamMapping( resolverCache.set(endpoint, endpointCache); } - const key = JSON.stringify( - Object.entries(options ?? {}) - .sort(([left], [right]) => left.localeCompare(right)) - .map(([optionName, optionValue]) => [optionName, optionValue]), - ); + const key = JSON.stringify([ + options?.excludeBodyParam, + options?.excludePageParam, + options?.replacePageParam, + options?.includeFileParam, + options?.includeOnlyRequiredParams, + options?.pathParamsRequiredOnly, + options?.optionalPathParams, + options?.modelNamespaceTag, + options?.excludePathParams, + ]); const cached = endpointCache.get(key); if (cached) { return cached; @@ -385,6 +411,10 @@ function renderEndpointObjectArgs( } function renderEndpointParamDescription(endpointParam: ReturnType[0]) { + const cached = endpointParamDescriptionCache.get(endpointParam); + if (cached !== undefined) { + return cached; + } const strs = [`${endpointParam.paramType} parameter`]; const description = endpointParam.parameterObject?.description || endpointParam.bodyObject?.description; if (description) { @@ -413,10 +443,22 @@ function renderEndpointParamDescription(endpointParam: ReturnType param.name)); @@ -426,7 +468,9 @@ function getWorkspaceParamNames(resolver: SchemaResolver, endpoint: Endpoint) { .map((condition) => invalidVariableNameCharactersToCamel(condition.name)) .filter((name) => endpointParamNames.has(name)); - return getUniqueArray([...workspaceParamNames, ...aclParamNames]).filter((name) => allowList.has(name)); + const names = getUniqueArray([...workspaceParamNames, ...aclParamNames]).filter((name) => allowList.has(name)); + resolverCache.set(endpoint, names); + return names; } function getWorkspaceParamReplacements(resolver: SchemaResolver, endpoint: Endpoint) { @@ -559,13 +603,14 @@ function renderQueryJsDocs({ lines.push(" * @param { AppInfiniteQueryOptions } options Infinite query options"); } - const withAxiosResponse = endpoint.mediaDownload && mode !== "infiniteQuery"; - const resultType = `${withAxiosResponse ? "AxiosResponse<" : ""}${getImportedZodSchemaInferedTypeName( + const withRawResponse = endpoint.mediaDownload && mode !== "infiniteQuery"; + const responseType = resolver.options.restClient === "native" ? NATIVE_RESPONSE_TYPE : "AxiosResponse"; + const resultType = `${withRawResponse ? `${responseType}<` : ""}${getImportedZodSchemaInferedTypeName( resolver, endpoint.response, undefined, tag, - )}${withAxiosResponse ? ">" : ""}`; + )}${withRawResponse ? ">" : ""}`; if (mode === "query") { lines.push(` * @returns { UseQueryResult<${resultType}> } ${endpoint.responseDescription ?? ""}`); @@ -637,7 +682,7 @@ function renderInlineEndpoints({ const hasRequestConfigParam = resolver.options.axiosRequestConfig || endpoint.method === "get"; lines.push( - `const ${getEndpointName(endpoint)} = (${endpointParams}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType()}` : ""}) => {`, + `const ${getEndpointName(endpoint)} = (${endpointParams}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType(resolver)}` : ""}) => {`, ); lines.push(` return ${APP_REST_CLIENT_NAME}.${endpoint.method}(`); lines.push(` ${renderInlineRequestInfo(resolver, endpoint, tag)},`); @@ -663,8 +708,8 @@ function renderInlineRequestInfo(resolver: SchemaResolver, endpoint: Endpoint, t return `{ resSchema: ${getImportedZodSchemaName(resolver, endpoint.response, tag)} }`; } -function getRequestConfigType() { - return `${AXIOS_REQUEST_CONFIG_TYPE} & { allowInvalidResponseData?: boolean }`; +function getRequestConfigType(resolver: SchemaResolver) { + return `${getRequestConfigTypeName(resolver.options.restClient)} & { allowInvalidResponseData?: boolean }`; } function renderRequestConfigWithSignal(hasRequestConfigParam: boolean) { @@ -768,7 +813,7 @@ function renderQueryOptions({ const lines: string[] = []; lines.push( - `const ${getQueryOptionsName(endpoint)} = (${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }` : ""}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType()}` : ""}) => ({`, + `const ${getQueryOptionsName(endpoint)} = (${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }` : ""}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType(resolver)}` : ""}) => ({`, ); lines.push(` queryKey: keys.${getEndpointName(endpoint)}(${endpointArgs}),`); const requestConfigWithSignal = renderRequestConfigWithSignal(hasRequestConfigParam); @@ -803,7 +848,7 @@ function renderInfiniteQueryOptions({ const lines: string[] = []; lines.push( - `const ${getInfiniteQueryOptionsName(endpoint)} = (${endpointParams ? `{ ${endpointArgsWithoutPage} }: { ${endpointParams} }` : ""}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType()}` : ""}) => ({`, + `const ${getInfiniteQueryOptionsName(endpoint)} = (${endpointParams ? `{ ${endpointArgsWithoutPage} }: { ${endpointParams} }` : ""}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigType(resolver)}` : ""}) => ({`, ); lines.push(` queryKey: keys.${getEndpointName(endpoint)}Infinite(${endpointArgsWithoutPage}),`); const requestConfigWithSignal = renderRequestConfigWithSignal(hasRequestConfigParam); @@ -812,11 +857,12 @@ function renderInfiniteQueryOptions({ ); lines.push(" initialPageParam: 1,"); lines.push( - ` getNextPageParam: ({ ${resolver.options.infiniteQueryResponseParamNames.page}, ${resolver.options.infiniteQueryResponseParamNames.totalItems}, ${resolver.options.infiniteQueryResponseParamNames.limit}: limitParam }: Awaited>) => {`, + ` getNextPageParam: ({ ${resolver.options.infiniteQueryResponseParamNames.page}, ${resolver.options.infiniteQueryResponseParamNames.totalItems}, ${resolver.options.infiniteQueryResponseParamNames.limit}: limitParam }: Awaited> & Partial>) => {`, ); lines.push(` const pageParam = ${resolver.options.infiniteQueryResponseParamNames.page} ?? 1;`); + lines.push(" const pageSize = limitParam ?? 0;"); lines.push( - ` return pageParam * limitParam < (${resolver.options.infiniteQueryResponseParamNames.totalItems} ?? 0) ? pageParam + 1 : null;`, + ` return pageParam * pageSize < (${resolver.options.infiniteQueryResponseParamNames.totalItems} ?? 0) ? pageParam + 1 : null;`, ); lines.push(" },"); lines.push("});"); @@ -824,7 +870,7 @@ function renderInfiniteQueryOptions({ } function renderPrefetchQuery({ resolver, endpoint }: { resolver: SchemaResolver; endpoint: Endpoint }) { - const hasAxiosRequestConfig = resolver.options.axiosRequestConfig; + const hasRequestConfigParam = resolver.options.axiosRequestConfig || endpoint.method === "get"; const tag = getEndpointTag(endpoint, resolver.options); const endpointParams = renderEndpointParams(resolver, endpoint, { modelNamespaceTag: tag, @@ -833,35 +879,39 @@ function renderPrefetchQuery({ resolver, endpoint }: { resolver: SchemaResolver; const lines: string[] = []; lines.push( - `export const ${getPrefetchQueryName(endpoint)} = (queryClient: QueryClient, ${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }, ` : ""}${hasAxiosRequestConfig ? `${AXIOS_REQUEST_CONFIG_NAME}: ${AXIOS_REQUEST_CONFIG_TYPE}, ` : ""}options?: Omit[0], "queryKey" | "queryFn">): void => {`, + `export const ${getPrefetchQueryName(endpoint)} = (queryClient: QueryClient, ${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }, ` : ""}options?: Omit[0], "queryKey" | "queryFn">, ${hasRequestConfigParam ? `${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigTypeName(resolver.options.restClient)}, ` : ""}throwOnError = false) => {`, ); lines.push( - ` void queryClient.prefetchQuery({ ...${getQueryOptionsName(endpoint)}(${endpointParams ? `{ ${endpointArgs} }` : ""}${hasAxiosRequestConfig ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}` : ""}), ...options });`, + ` const queryOptions = { ...${getQueryOptionsName(endpoint)}(${endpointParams ? `{ ${endpointArgs} }` : ""}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}` : ""}), ...options };`, ); + lines.push(` return throwOnError ? queryClient.fetchQuery(queryOptions) : queryClient.prefetchQuery(queryOptions);`); lines.push("};"); return lines.join("\n"); } function renderPrefetchInfiniteQuery({ resolver, endpoint }: { resolver: SchemaResolver; endpoint: Endpoint }) { - const hasAxiosRequestConfig = resolver.options.axiosRequestConfig; + const hasRequestConfigParam = resolver.options.axiosRequestConfig || endpoint.method === "get"; const tag = getEndpointTag(endpoint, resolver.options); const endpointParams = renderEndpointParams(resolver, endpoint, { excludePageParam: true, modelNamespaceTag: tag, }); const endpointArgs = renderEndpointArgs(resolver, endpoint, { excludePageParam: true }); - const optionsArgs = `${endpointParams ? `{ ${endpointArgs} }` : ""}${hasAxiosRequestConfig ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}` : ""}`; + const optionsArgs = `${endpointParams ? `{ ${endpointArgs} }` : ""}${hasRequestConfigParam ? `${endpointParams ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}` : ""}`; const lines: string[] = []; lines.push( - `export const ${getPrefetchInfiniteQueryName(endpoint)} = (queryClient: QueryClient, ${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }, ` : ""}${hasAxiosRequestConfig ? `${AXIOS_REQUEST_CONFIG_NAME}: ${AXIOS_REQUEST_CONFIG_TYPE}, ` : ""}options?: Omit[0], "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">): void => {`, + `export const ${getPrefetchInfiniteQueryName(endpoint)} = (queryClient: QueryClient, ${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }, ` : ""}options?: Omit[0], "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">, ${hasRequestConfigParam ? `${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigTypeName(resolver.options.restClient)}, ` : ""}throwOnError = false) => {`, ); // options is cast to {} so it contributes no typed properties to the spread, letting TypeScript // infer TPageParam and TQueryFnData solely from the options factory (via initialPageParam and // queryFn). Without the cast, the options type defaults prefetchInfiniteQuery generics to unknown, // which conflicts with the generated queryFn expecting pageParam: number. lines.push( - ` void queryClient.prefetchInfiniteQuery({ ...${getInfiniteQueryOptionsName(endpoint)}(${optionsArgs}), ...(options as {}) });`, + ` const queryOptions = { ...${getInfiniteQueryOptionsName(endpoint)}(${optionsArgs}), ...(options as {}) };`, + ); + lines.push( + ` return throwOnError ? queryClient.fetchInfiniteQuery(queryOptions) : queryClient.prefetchInfiniteQuery(queryOptions);`, ); lines.push("};"); return lines.join("\n"); @@ -897,7 +947,7 @@ function renderQuery({ const lines: string[] = []; lines.push(renderQueryJsDocs({ resolver, endpoint, mode: "query", tag })); lines.push( - `export const ${getQueryName(endpoint)} = (${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }, ` : ""}options?: AppQueryOptions${hasAxiosRequestConfig ? `, ${AXIOS_REQUEST_CONFIG_NAME}?: ${AXIOS_REQUEST_CONFIG_TYPE}` : ""}) => {`, + `export const ${getQueryName(endpoint)} = (${endpointParams ? `{ ${endpointArgs} }: { ${endpointParams} }, ` : ""}options?: AppQueryOptions${hasAxiosRequestConfig ? `, ${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigTypeName(resolver.options.restClient)}` : ""}) => {`, ); lines.push(" const queryConfig = OpenApiQueryConfig.useConfig();"); if (hasAclCheck) { @@ -982,7 +1032,7 @@ function renderMutation({ } } const scopePathParams = scopeEnabled - ? mapEndpointParamsToFunctionParams(resolver, endpoint, {}).filter((p) => p.paramType === "Path") + ? getEndpointParamMapping(resolver, endpoint, {}).filter((p) => p.paramType === "Path") : []; const isScoped = scopePathParams.length > 0; @@ -1012,7 +1062,7 @@ function renderMutation({ const lines: string[] = []; lines.push(renderQueryJsDocs({ resolver, endpoint, mode: "mutation", tag })); lines.push( - `export const ${getQueryName(endpoint, true)} = (${pathParamFirstArg}options?: AppMutationOptions${hasMutationEffects ? ` & ${MUTATION_EFFECTS.optionsType}` : ""}${hasAxiosRequestConfig ? `, ${AXIOS_REQUEST_CONFIG_NAME}?: ${AXIOS_REQUEST_CONFIG_TYPE}` : ""}) => {`, + `export const ${getQueryName(endpoint, true)} = (${pathParamFirstArg}options?: AppMutationOptions${hasMutationEffects ? ` & ${MUTATION_EFFECTS.optionsType}` : ""}${hasAxiosRequestConfig ? `, ${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigTypeName(resolver.options.restClient)}` : ""}) => {`, ); if (hasMutationDefaultOnError) { lines.push(" const queryConfig = OpenApiQueryConfig.useConfig();"); @@ -1208,7 +1258,7 @@ function renderInfiniteQuery({ const lines: string[] = []; lines.push(renderQueryJsDocs({ resolver, endpoint, mode: "infiniteQuery", tag })); lines.push( - `export const ${getInfiniteQueryName(endpoint)} = (${endpointParams ? `{ ${endpointArgsWithoutPage} }: { ${endpointParams} }, ` : ""}options?: AppInfiniteQueryOptions${hasAxiosRequestConfig ? `, ${AXIOS_REQUEST_CONFIG_NAME}?: ${AXIOS_REQUEST_CONFIG_TYPE}` : ""}) => {`, + `export const ${getInfiniteQueryName(endpoint)} = (${endpointParams ? `{ ${endpointArgsWithoutPage} }: { ${endpointParams} }, ` : ""}options?: AppInfiniteQueryOptions${hasAxiosRequestConfig ? `, ${AXIOS_REQUEST_CONFIG_NAME}?: ${getRequestConfigTypeName(resolver.options.restClient)}` : ""}) => {`, ); lines.push(" const queryConfig = OpenApiQueryConfig.useConfig();"); if (hasAclCheck) { diff --git a/src/generators/generate/generateQueryModules.ts b/src/generators/generate/generateQueryModules.ts index 46d9cae7..371b685d 100644 --- a/src/generators/generate/generateQueryModules.ts +++ b/src/generators/generate/generateQueryModules.ts @@ -2,6 +2,11 @@ import { GenerateType, GenerateTypeParams } from "@/generators/types/generate"; import { getNamespaceName } from "@/generators/utils/namespace.utils"; export function generateQueryModules({ resolver, data }: Omit) { + const nativeContent = ( + resolver as GenerateTypeParams["resolver"] & { getNativeRenderedShared?: (name: string) => string | undefined } + ).getNativeRenderedShared?.("queryModules"); + if (nativeContent) return nativeContent; + const modules: { tag: string; namespace: string }[] = []; data.forEach((_, tag) => { diff --git a/src/generators/generateCodeFromOpenAPIDoc.ts b/src/generators/generateCodeFromOpenAPIDoc.ts index 073d3c21..9ee31f45 100644 --- a/src/generators/generateCodeFromOpenAPIDoc.ts +++ b/src/generators/generateCodeFromOpenAPIDoc.ts @@ -18,16 +18,22 @@ import { import { getTagFileName } from "./utils/generate/generate.utils"; import { shouldInlineEndpointsForTag } from "./utils/tag.utils"; import { Profiler } from "../helpers/profile.helper"; +import { getDataFromNativeOpenAPIDoc } from "../native/getDataFromNativeOpenAPIDoc"; +import { shouldUseNativeCodegen } from "../native/native-bindings"; export function generateCodeFromOpenAPIDoc( openApiDoc: OpenAPIV3.Document, options: GenerateOptions, profiler?: Profiler, + nativeSource?: { source: string; yaml: boolean }, ) { const p = profiler ?? new Profiler(false); const importPath = options.standalone && options.importPath === "ts" ? "relative" : options.importPath; + const resolvedOptions = { ...options, importPath }; const { resolver, data } = p.runSync("data.extract", () => - getDataFromOpenAPIDoc(openApiDoc, { ...options, importPath }, p), + nativeSource && shouldUseNativeCodegen() + ? getDataFromNativeOpenAPIDoc(openApiDoc, resolvedOptions, nativeSource) + : getDataFromOpenAPIDoc(openApiDoc, resolvedOptions, p), ); const generateFilesData: GenerateFileData[] = []; diff --git a/src/generators/generated-output-eq.test.ts b/src/generators/generated-output-eq.test.ts index 8af23095..40ad7a63 100644 --- a/src/generators/generated-output-eq.test.ts +++ b/src/generators/generated-output-eq.test.ts @@ -41,7 +41,7 @@ async function normalizeContent(filePath: string, content: string) { const hasGeneratedFolders = fs.existsSync(BASE_DIR) && fs.existsSync(NEXT_DIR); -// Opt-in via `pnpm gen:verify` (or `pnpm gen:base && pnpm gen:next` first). +// Opt-in via `bun run gen:verify` (or `bun run gen:base && bun run gen:next` first). // Skipped in the default suite when the gitignored output folders are absent. describe.skipIf(!hasGeneratedFolders)("Generated output parity", () => { it("matches between base and next output folders", async () => { diff --git a/src/generators/run/generate.runner.ts b/src/generators/run/generate.runner.ts index 6d038221..1915f72e 100644 --- a/src/generators/run/generate.runner.ts +++ b/src/generators/run/generate.runner.ts @@ -1,13 +1,14 @@ +import fs from "fs"; import path from "path"; -import SwaggerParser from "@apidevtools/swagger-parser"; import { OpenAPIV3 } from "openapi-types"; import { resolveConfig } from "@/generators/core/resolveConfig"; -import { generateCodeFromOpenAPIDoc } from "@/generators/generateCodeFromOpenAPIDoc"; import { GenerateFileFormatter } from "@/generators/types/generate"; import { GenerateOptions } from "@/generators/types/options"; import { removeStaleGeneratedFiles, writeGenerateFileData } from "@/generators/utils/file.utils"; import { Profiler } from "@/helpers/profile.helper"; +import { shouldUseNativeCodegen } from "@/native/native-bindings"; +import { generateFilesFromNativeOpenAPI } from "@/native/generateFilesFromNativeOpenAPI"; type GenerateStats = { generatedFilesCount: number; @@ -33,69 +34,90 @@ export async function runGenerate({ profiler?: Profiler; }) { const config = profiler.runSync("config.resolve", () => resolveConfig({ fileConfig, params: params ?? {} })); - const openApiDoc = await getOpenApiDoc(config.input, profiler); - - const filesData = profiler.runSync("generate.total", () => generateCodeFromOpenAPIDoc(openApiDoc, config, profiler)); + const useNative = shouldUseNativeCodegen(); + const isJson = path.extname(new URL(config.input, "file://").pathname).toLowerCase() === ".json"; + const useRawNativeInput = useNative && (isJson || typeof Bun === "undefined"); + let nativeInput = useRawNativeInput + ? await getRawOpenApiSource(config.input, profiler) + : await getOpenApiSource(config.input, profiler); + if (useNative && !useRawNativeInput) { + const document = "document" in nativeInput ? nativeInput.document : undefined; + nativeInput = { + ...nativeInput, + source: profiler.runSync("openapi.serialize", () => JSON.stringify(document)), + yaml: false, + }; + } + const openApiDoc = + "document" in nativeInput ? (nativeInput.document as OpenAPIV3.Document) : ({} as OpenAPIV3.Document); + const outputExists = fs.existsSync(config.output); + + const filesData = await profiler.runAsync("generate.total", async () => { + if (useNative) { + const nativeFiles = generateFilesFromNativeOpenAPI(nativeInput.source, nativeInput.yaml, config); + if (nativeFiles) return nativeFiles; + } + const { generateCodeFromOpenAPIDoc } = await import("@/generators/generateCodeFromOpenAPIDoc"); + return generateCodeFromOpenAPIDoc(openApiDoc, config, profiler, { + source: nativeInput.source, + yaml: nativeInput.yaml, + }); + }); if (config.clearOutput) { profiler.runSync("files.removeStaleGenerated", () => { removeStaleGeneratedFiles({ output: config.output, filesData, options: config }); }); } await profiler.runAsync("files.write", async () => { - await writeGenerateFileData(filesData, { formatGeneratedFile }); + await writeGenerateFileData(filesData, { formatGeneratedFile, skipExistingCheck: !outputExists }); }); const stats = getGenerateStats(filesData, config); return { skipped: false, config, stats }; } -async function getOpenApiDoc(input: string, profiler: Profiler): Promise { - const parsedDoc = (await profiler.runAsync( - "openapi.parse", - async () => await SwaggerParser.parse(input), - )) as OpenAPIV3.Document; - const hasExternalRefs = profiler.runSync("openapi.detectExternalRefs", () => hasExternalRef(parsedDoc)); - if (!hasExternalRefs) { - return parsedDoc; - } +export async function getOpenApiDoc(input: string, profiler = new Profiler(false)): Promise { + return (await getOpenApiSource(input, profiler)).document; +} - return (await profiler.runAsync( - "openapi.bundle", - async () => await SwaggerParser.bundle(input), - )) as OpenAPIV3.Document; +async function getOpenApiSource(input: string, profiler = new Profiler(false)) { + const raw = await getRawOpenApiSource(input, profiler); + const { source } = raw; + const document = await profiler.runAsync("openapi.parse", () => parseOpenApiSource(input, source)); + return { ...raw, document }; } -function hasExternalRef(value: unknown): boolean { - const stack = [value]; - const visited = new Set(); +async function getRawOpenApiSource(input: string, profiler = new Profiler(false)) { + const source = await profiler.runAsync("openapi.read", () => readOpenApiSource(input)); + const extension = path.extname(new URL(input, "file://").pathname).toLowerCase(); + return { source, yaml: extension !== ".json" }; +} - while (stack.length > 0) { - const current = stack.pop(); - if (!current || typeof current !== "object") { - continue; +async function readOpenApiSource(input: string) { + if (/^https?:\/\//i.test(input)) { + const response = await fetch(input); + if (!response.ok) { + throw new Error(`Unable to load OpenAPI document: ${response.status} ${response.statusText}`); } + return response.text(); + } - if (visited.has(current)) { - continue; - } - visited.add(current); - - if ( - "$ref" in current && - typeof (current as { $ref?: unknown }).$ref === "string" && - !(current as { $ref: string }).$ref.startsWith("#/") - ) { - return true; - } + return fs.promises.readFile(input, "utf-8"); +} - for (const nested of Object.values(current)) { - if (nested && typeof nested === "object") { - stack.push(nested); - } +async function parseOpenApiSource(input: string, source: string): Promise { + try { + return JSON.parse(source.charCodeAt(0) === 0xfeff ? source.slice(1) : source) as OpenAPIV3.Document; + } catch (error) { + if (path.extname(new URL(input, "file://").pathname).toLowerCase() === ".json") { + throw error; } + if (typeof Bun !== "undefined") { + return Bun.YAML.parse(source) as OpenAPIV3.Document; + } + const { parse } = await import("yaml"); + return parse(source) as OpenAPIV3.Document; } - - return false; } function getGenerateStats(filesData: { fileName: string }[], config: GenerateOptions): GenerateStats { diff --git a/src/generators/types/options.ts b/src/generators/types/options.ts index b9344b92..c5705909 100644 --- a/src/generators/types/options.ts +++ b/src/generators/types/options.ts @@ -4,6 +4,7 @@ interface ZodGenerateOptions { schemaSuffix: string; enumSuffix: string; modelsInCommon?: boolean; + modelsInModules?: boolean; withImplicitRequiredProps?: boolean; withDefaultValues?: boolean; withDescription?: boolean; @@ -13,7 +14,9 @@ interface ZodGenerateOptions { } interface EndpointsGenerateOptions { + restClient: "axios" | "native"; restClientImportPath: string; + zodImportPath: string; errorHandlingImportPath?: string; withDeprecatedEndpoints?: boolean; removeOperationPrefixEndingWith?: string; @@ -24,6 +27,7 @@ interface EndpointsGenerateOptions { interface QueriesGenerateOptions { queryTypesImportPath: string; + mutationEffectsImportPath: string; axiosRequestConfig?: boolean; mutationEffects?: boolean; mutationDefaultOnError?: boolean; @@ -49,6 +53,7 @@ interface ACLGenerateOptions { checkAcl?: boolean; abilityContextGenericAppAbilities: boolean; abilityContextImportPath?: string; + aclCheckImportPath: string; } interface BuilderConfigsGenerateOptions { @@ -76,6 +81,7 @@ interface BaseGenerateOptions { excludePathRegex: string; excludeRedundantZodSchemas: boolean; tsNamespaces: boolean; + treeShakeableNamespaces?: boolean; tsPath: string; importPath: "ts" | "relative" | "absolute"; configs: Record; diff --git a/src/generators/utils/file.utils.ts b/src/generators/utils/file.utils.ts index cbb16b51..ddc19a48 100644 --- a/src/generators/utils/file.utils.ts +++ b/src/generators/utils/file.utils.ts @@ -33,34 +33,27 @@ export function getOutputFileName({ output, fileName }: { output: string; fileNa type WriteGenerateFileDataOptions = { formatGeneratedFile?: GenerateFileFormatter; + skipExistingCheck?: boolean; }; -function hashString(input: string) { - let hash = 2166136261; - for (let i = 0; i < input.length; i += 1) { - hash ^= input.charCodeAt(i); - hash = Math.imul(hash, 16777619); - } - return (hash >>> 0).toString(16); -} - -function writeFileWithDirSync(file: string, data: string) { - const dir = path.dirname(file); - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); +async function writeFileIfChanged(file: string, data: string, skipExistingCheck = false) { + if (skipExistingCheck) { + await fs.promises.writeFile(file, data, "utf-8"); + return; } - if (fs.existsSync(file)) { - const existingData = fs.readFileSync(file, "utf-8"); - const existingHash = hashString(existingData); - const nextHash = hashString(data); - if (existingHash === nextHash && existingData === data) { + try { + const existingData = await fs.promises.readFile(file, "utf-8"); + if (existingData === data) { return; } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + throw error; + } } - fs.writeFileSync(file, data, "utf-8"); + await fs.promises.writeFile(file, data, "utf-8"); } async function writeFile({ fileName, content }: GenerateFileData, options?: WriteGenerateFileDataOptions) { @@ -68,10 +61,22 @@ async function writeFile({ fileName, content }: GenerateFileData, options?: Writ ? await options.formatGeneratedFile({ fileName, content }) : content; - writeFileWithDirSync(fileName, formattedContent); + await fs.promises.mkdir(path.dirname(fileName), { recursive: true }); + await writeFileIfChanged(fileName, formattedContent, options?.skipExistingCheck); } export async function writeGenerateFileData(filesData: GenerateFileData[], options?: WriteGenerateFileDataOptions) { + if (!options?.formatGeneratedFile) { + const directories = new Set(filesData.map(({ fileName }) => path.dirname(fileName))); + for (const directory of directories) { + fs.mkdirSync(directory, { recursive: true }); + } + await Promise.all( + filesData.map(({ fileName, content }) => writeFileIfChanged(fileName, content, options?.skipExistingCheck)), + ); + return; + } + for (const file of filesData) { await writeFile(file, options); } diff --git a/src/generators/utils/generate/generate.endpoints.utils.ts b/src/generators/utils/generate/generate.endpoints.utils.ts index 0cfe3f07..14d8f664 100644 --- a/src/generators/utils/generate/generate.endpoints.utils.ts +++ b/src/generators/utils/generate/generate.endpoints.utils.ts @@ -17,7 +17,19 @@ import { isNamedZodSchema } from "@/generators/utils/zod-schema.utils"; import { getImportedZodSchemaInferedTypeName } from "./generate.zod.utils"; -export const getEndpointName = (endpoint: Endpoint) => decapitalize(snakeToCamel(endpoint.operationName)); +const endpointNameCache = new WeakMap(); +const endpointPathCache = new WeakMap(); +const endpointBodyCache = new WeakMap>(); +const endpointConfigCache = new WeakMap>(); + +export const getEndpointName = (endpoint: Endpoint) => { + let name = endpointNameCache.get(endpoint); + if (name === undefined) { + name = decapitalize(snakeToCamel(endpoint.operationName)); + endpointNameCache.set(endpoint, name); + } + return name; +}; export function getImportedEndpointName(endpoint: Endpoint, options: GenerateOptions) { const namespacePrefix = options.tsNamespaces @@ -28,7 +40,14 @@ export function getImportedEndpointName(endpoint: Endpoint, options: GenerateOpt export const requiresBody = (endpoint: Endpoint) => endpoint.method !== OpenAPIV3.HttpMethods.GET; -export const getEndpointBody = (endpoint: Endpoint) => endpoint.parameters.find((param) => param.type === "Body"); +const findEndpointBody = (endpoint: Endpoint) => endpoint.parameters.find((param) => param.type === "Body"); + +export const getEndpointBody = (endpoint: Endpoint) => { + if (!endpointBodyCache.has(endpoint)) { + endpointBodyCache.set(endpoint, findEndpointBody(endpoint)); + } + return endpointBodyCache.get(endpoint); +}; export const hasEndpointConfig = (endpoint: Endpoint, resolver: SchemaResolver) => { const endpointConfig = getEndpointConfig(endpoint); @@ -37,7 +56,14 @@ export const hasEndpointConfig = (endpoint: Endpoint, resolver: SchemaResolver) return Object.keys(endpointConfig).length > 0 || hasAxiosRequestConfig || needsBlobConfig; }; -export const getEndpointPath = (endpoint: Endpoint) => endpoint.path.replace(/:([a-zA-Z0-9_]+)/g, "${$1}"); +export const getEndpointPath = (endpoint: Endpoint) => { + let endpointPath = endpointPathCache.get(endpoint); + if (endpointPath === undefined) { + endpointPath = endpoint.path.replace(/:([a-zA-Z0-9_]+)/g, "${$1}"); + endpointPathCache.set(endpoint, endpointPath); + } + return endpointPath; +}; export function mapEndpointParamsToFunctionParams( resolver: SchemaResolver, @@ -130,6 +156,15 @@ export function endpointParamsAllOptional( } export function getEndpointConfig(endpoint: Endpoint) { + let config = endpointConfigCache.get(endpoint); + if (!config) { + config = createEndpointConfig(endpoint); + endpointConfigCache.set(endpoint, config); + } + return config; +} + +function createEndpointConfig(endpoint: Endpoint) { const params = endpoint.parameters .filter((param) => param.type === "Query") .map((param) => { @@ -155,11 +190,10 @@ export function getEndpointConfig(endpoint: Endpoint) { headers[param.name] = invalidVariableNameCharactersToCamel(param.name); }); - const endpointConfig = { + return { ...(params.length > 0 ? { params } : {}), ...(Object.keys(headers).length ? { headers } : {}), }; - return endpointConfig; } /** Renders the body of a media-upload mutationFn: call the endpoint (without the file arg) to @@ -180,26 +214,29 @@ export function renderMediaUploadMutationBody({ `const uploadInstructions = await ${endpointFunction}(${resolvedEndpointArgs}${hasAxiosRequestConfig ? `${resolvedEndpointArgs ? ", " : ""}${AXIOS_REQUEST_CONFIG_NAME}` : ""});`, "", "if (file && uploadInstructions.url) {", - ` const method = (${BODY_PARAMETER_NAME}?.method?.toLowerCase() ?? "put") as "put" | "post";`, + ` const method = (uploadInstructions.method?.toLowerCase() ?? "put") as "put" | "post";`, " let dataToSend: File | FormData = file;", ' if (method === "post") {', " dataToSend = new FormData();", - " if (uploadInstructions.fields) {", - " for (const [key, value] of uploadInstructions.fields) {", - " dataToSend.append(key, value);", + ' if ("fields" in uploadInstructions && uploadInstructions.fields) {', + " for (const [key, value] of Object.entries(uploadInstructions.fields)) {", + " dataToSend.append(key, String(value));", " }", " }", ' dataToSend.append("file", file);', " }", - " await axios[method](uploadInstructions.url, dataToSend, {", - " headers: {", - ' "Content-Type": file.type,', - " },", + resolver.options.restClient === "native" + ? " await AppRestClient.upload(uploadInstructions.url, dataToSend, {" + : " await axios[method](uploadInstructions.url, dataToSend, {", + resolver.options.restClient === "native" + ? ' headers: method === "put" ? { "Content-Type": file.type } : undefined,' + : " headers: {", + ...(resolver.options.restClient === "native" ? [] : [' "Content-Type": file.type,', " },"]), " signal: abortController?.signal,", " onUploadProgress: onUploadProgress", " ? (progressEvent) => onUploadProgress({ loaded: progressEvent.loaded, total: progressEvent.total ?? 0 })", " : undefined,", - " });", + resolver.options.restClient === "native" ? " }, method);" : " });", "}", "", "return uploadInstructions;", diff --git a/src/generators/utils/generate/generate.imports.utils.ts b/src/generators/utils/generate/generate.imports.utils.ts index a8b1005f..8612ee61 100644 --- a/src/generators/utils/generate/generate.imports.utils.ts +++ b/src/generators/utils/generate/generate.imports.utils.ts @@ -185,7 +185,9 @@ function getImports({ const imports = new Map(); entities.forEach((entity) => { const tag = - type === GenerateType.Models && options.modelsInCommon && options.splitByTags ? currentTag : getTag(entity); + type === GenerateType.Models && (options.modelsInCommon || options.modelsInModules) && options.splitByTags + ? currentTag + : getTag(entity); if (!imports.has(tag)) { const sameTagDir = currentTag === tag; imports.set(tag, { diff --git a/src/generators/utils/generate/generate.query.utils.ts b/src/generators/utils/generate/generate.query.utils.ts index 3b53f260..dc69adad 100644 --- a/src/generators/utils/generate/generate.query.utils.ts +++ b/src/generators/utils/generate/generate.query.utils.ts @@ -6,24 +6,42 @@ import { isMutation, isQuery } from "@/generators/utils/query.utils"; import { capitalize, snakeToCamel } from "@/generators/utils/string.utils"; import { getEndpointTag } from "@/generators/utils/tag.utils"; +const operationNameByEndpoint = new WeakMap(); +const capitalizedOperationNameByEndpoint = new WeakMap(); + +function getOperationName(endpoint: Endpoint) { + let name = operationNameByEndpoint.get(endpoint); + if (name === undefined) { + name = snakeToCamel(endpoint.operationName); + operationNameByEndpoint.set(endpoint, name); + } + return name; +} + +function getCapitalizedOperationName(endpoint: Endpoint) { + let name = capitalizedOperationNameByEndpoint.get(endpoint); + if (name === undefined) { + name = capitalize(getOperationName(endpoint)); + capitalizedOperationNameByEndpoint.set(endpoint, name); + } + return name; +} + export const getQueryName = (endpoint: Endpoint, mutation?: boolean) => { const addMutationSuffix = isQuery(endpoint) && isMutation(endpoint) && mutation; - return `use${capitalize(snakeToCamel(endpoint.operationName))}${addMutationSuffix ? "Mutation" : ""}`; + return `use${getCapitalizedOperationName(endpoint)}${addMutationSuffix ? "Mutation" : ""}`; }; -export const getInfiniteQueryName = (endpoint: Endpoint) => - `use${capitalize(snakeToCamel(endpoint.operationName))}Infinite`; +export const getInfiniteQueryName = (endpoint: Endpoint) => `use${getCapitalizedOperationName(endpoint)}Infinite`; -export const getQueryOptionsName = (endpoint: Endpoint) => `${snakeToCamel(endpoint.operationName)}QueryOptions`; +export const getQueryOptionsName = (endpoint: Endpoint) => `${getOperationName(endpoint)}QueryOptions`; -export const getInfiniteQueryOptionsName = (endpoint: Endpoint) => - `${snakeToCamel(endpoint.operationName)}InfiniteQueryOptions`; +export const getInfiniteQueryOptionsName = (endpoint: Endpoint) => `${getOperationName(endpoint)}InfiniteQueryOptions`; -export const getPrefetchQueryName = (endpoint: Endpoint) => - `prefetch${capitalize(snakeToCamel(endpoint.operationName))}`; +export const getPrefetchQueryName = (endpoint: Endpoint) => `prefetch${getCapitalizedOperationName(endpoint)}`; export const getPrefetchInfiniteQueryName = (endpoint: Endpoint) => - `prefetch${capitalize(snakeToCamel(endpoint.operationName))}Infinite`; + `prefetch${getCapitalizedOperationName(endpoint)}Infinite`; export const getImportedQueryName = (endpoint: Endpoint, options: GenerateOptions) => { const namespacePrefix = options.tsNamespaces diff --git a/src/generators/utils/generate/generate.zod.utils.ts b/src/generators/utils/generate/generate.zod.utils.ts index 402b67e7..96ce36b1 100644 --- a/src/generators/utils/generate/generate.zod.utils.ts +++ b/src/generators/utils/generate/generate.zod.utils.ts @@ -35,7 +35,11 @@ export const getImportedZodSchemaName = (resolver: SchemaResolver, zodSchemaName // (e.g. a schema shared by 2+ tags is promoted to the common tag) - namespaceTag must not // override that, or shared schemas would incorrectly resolve to the endpoint's own tag. function getOwningOrLocalProxyTag(resolver: SchemaResolver, zodSchemaName: string, namespaceTag?: string) { - if (namespaceTag && resolver.options.modelsInCommon && resolver.options.splitByTags) { + if ( + namespaceTag && + (resolver.options.modelsInCommon || resolver.options.modelsInModules) && + resolver.options.splitByTags + ) { return namespaceTag; } return resolver.getTagByZodSchemaName(zodSchemaName) ?? namespaceTag; diff --git a/src/generators/utils/openapi.utils.ts b/src/generators/utils/openapi.utils.ts index 2ece2d5a..9bd829a4 100644 --- a/src/generators/utils/openapi.utils.ts +++ b/src/generators/utils/openapi.utils.ts @@ -1,6 +1,5 @@ /* eslint-disable no-control-regex, no-useless-escape */ import { OpenAPIV3 } from "openapi-types"; -import { match, P } from "ts-pattern"; import { ALLOWED_PARAM_MEDIA_TYPES, PRIMITIVE_TYPE_LIST } from "@/generators/const/openapi.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; @@ -81,11 +80,15 @@ export function escapeControlCharacters(str: string): string { .replace(/\//g, "\\/"); } -export const toBoolean = (value: undefined | string | boolean, defaultValue: boolean) => - match(value) - .with(P.string.regex(/^false$/i), false, () => false) - .with(P.string.regex(/^true$/i), true, () => true) - .otherwise(() => defaultValue); +export const toBoolean = (value: undefined | string | boolean, defaultValue: boolean) => { + if (value === false || (typeof value === "string" && /^false$/i.test(value))) { + return false; + } + if (value === true || (typeof value === "string" && /^true$/i.test(value))) { + return true; + } + return defaultValue; +}; export function isParamMediaTypeAllowed( mediaType: string, diff --git a/src/generators/utils/operation.utils.ts b/src/generators/utils/operation.utils.ts index 97479d5a..55e5de82 100644 --- a/src/generators/utils/operation.utils.ts +++ b/src/generators/utils/operation.utils.ts @@ -35,10 +35,35 @@ export function getOperationName({ keepOperationTag?: boolean; keepOperationPrefix?: boolean; }) { - const pathOperationName = `${method}${pathToVariableName(path)}`; - let operationName = operation.operationId - ? invalidVariableNameCharactersToCamel(operation.operationId) - : pathOperationName; + return resolveOperationName({ path, method, operation, options, tag, keepOperationTag, keepOperationPrefix }).name; +} + +function resolveOperationName({ + path, + method, + operation, + options, + tag, + keepOperationTag, + keepOperationPrefix, +}: { + path: string; + method: string; + operation: OperationObject; + options: GenerateOptions; + tag: string; + keepOperationTag?: boolean; + keepOperationPrefix?: boolean; +}) { + const usesPathAsSource = !operation.operationId; + let pathOperationName: string | undefined; + let operationName: string; + if (operation.operationId) { + operationName = invalidVariableNameCharactersToCamel(operation.operationId); + } else { + pathOperationName = `${method}${pathToVariableName(path)}`; + operationName = pathOperationName; + } if (options.removeOperationPrefixEndingWith && keepOperationPrefix) { const splits = operationName.split(options.removeOperationPrefixEndingWith); @@ -48,7 +73,7 @@ export function getOperationName({ operationName = operationName.replace(regex, ""); } - if (options.tsNamespaces && !keepOperationTag) { + if ((options.tsNamespaces || options.treeShakeableNamespaces) && !keepOperationTag) { const operationNameWithoutTag = removeWord(operationName, tag); if (operationNameWithoutTag === "") { operationName = method.toLowerCase(); @@ -57,11 +82,69 @@ export function getOperationName({ } } - return RESERVED_WORDS.includes(operationName) ? pathOperationName : operationName; + const usesReservedWordFallback = RESERVED_WORDS.includes(operationName); + if (usesReservedWordFallback && pathOperationName === undefined) { + pathOperationName = `${method}${pathToVariableName(path)}`; + } + return { + name: usesReservedWordFallback ? pathOperationName! : operationName, + usesPathAsSource, + usesReservedWordFallback, + }; +} + +type OperationNameIndex = { + stableCounts: Map; + pathSourceCount: number; + reservedFallbackCount: number; +}; + +export type IndexedOperation = { + path: string; + method: string; + operation: OperationObject; + tag: string; + pathParameters: OpenAPIV3.PathItemObject["parameters"]; +}; + +function createOperationNameIndex({ + operations, + options, + tag, + keepOperationTag, +}: { + operations: OperationObject[]; + options: GenerateOptions; + tag: string; + keepOperationTag?: boolean; +}): OperationNameIndex { + const index: OperationNameIndex = { stableCounts: new Map(), pathSourceCount: 0, reservedFallbackCount: 0 }; + for (const operation of operations) { + if (!operation.operationId) { + index.pathSourceCount++; + continue; + } + + const result = resolveOperationName({ + path: "", + method: "", + operation, + options, + tag, + keepOperationTag, + }); + if (result.usesReservedWordFallback) { + index.reservedFallbackCount++; + } else { + index.stableCounts.set(result.name, (index.stableCounts.get(result.name) ?? 0) + 1); + } + } + return index; } export function getUniqueOperationName({ operationsByTag, + nameIndexes, ...params }: { path: string; @@ -69,23 +152,48 @@ export function getUniqueOperationName({ operation: OperationObject; operationsByTag: Record; options: GenerateOptions; + nameIndexes?: [OperationNameIndex, OperationNameIndex]; }) { const { operation, options } = params; const tag = options.splitByTags ? getOperationTag(operation, options) : options.defaultTag; - const operationName = (keepOperationTag?: boolean) => { + const operationName = (keepOperationTag?: boolean, nameIndex?: OperationNameIndex) => { const name = getOperationName({ ...params, tag, keepOperationTag }); - const operationsWithName = operationsByTag[tag].filter( - (operation) => getOperationName({ ...params, operation, tag, keepOperationTag }) === name, - ); - if (operationsWithName.length === 1) { + if (nameIndex) { + const pathSourceMatches = + nameIndex.pathSourceCount > 0 && + getOperationName({ + ...params, + operation: {} as OperationObject, + tag, + keepOperationTag, + }) === name; + const pathFallbackMatches = + nameIndex.reservedFallbackCount > 0 && `${params.method}${pathToVariableName(params.path)}` === name; + const matchingOperations = + (nameIndex.stableCounts.get(name) ?? 0) + + (pathSourceMatches ? nameIndex.pathSourceCount : 0) + + (pathFallbackMatches ? nameIndex.reservedFallbackCount : 0); + return matchingOperations === 1 ? name : undefined; + } + + let matchingOperations = 0; + for (const candidate of operationsByTag[tag]) { + if (getOperationName({ ...params, operation: candidate, tag, keepOperationTag }) === name) { + matchingOperations++; + if (matchingOperations > 1) { + return; + } + } + } + if (matchingOperations === 1) { return name; } }; return ( - operationName() ?? - operationName(true) ?? + operationName(undefined, nameIndexes?.[0]) ?? + operationName(true, nameIndexes?.[1]) ?? getOperationName({ ...params, tag, keepOperationTag: true, keepOperationPrefix: true }) ); } @@ -94,8 +202,9 @@ export function getUniqueOperationNamesWithoutSplitByTags( openApiDoc: OpenAPIV3.Document, operationsByTag: Record, options: GenerateOptions, + operationNameByOperation?: WeakMap, ) { - const operationNames: string[] = []; + const indexedOperations: IndexedOperation[] = []; for (const path in openApiDoc.paths) { if (isPathExcluded(path, options)) { continue; @@ -103,20 +212,86 @@ export function getUniqueOperationNamesWithoutSplitByTags( const pathItemObj = openApiDoc.paths[path] as OpenAPIV3.PathItemObject; const pathItem = pick(pathItemObj, ALLOWED_METHODS); - for (const method in pathItem) { const operation = pathItem[method as keyof typeof pathItem] as OperationObject | undefined; if (!operation || isOperationExcluded(operation, options)) { continue; } + indexedOperations.push({ + path, + method, + operation, + tag: options.splitByTags ? getOperationTag(operation, options) : options.defaultTag, + pathParameters: pathItemObj.parameters, + }); + } + } + return getUniqueOperationNamesFromIndex(indexedOperations, operationsByTag, options, operationNameByOperation); +} - const operationName = getUniqueOperationName({ path, method, operation, operationsByTag, options }); - operationNames.push(operationName); +function getUniqueOperationNamesFromIndex( + indexedOperations: IndexedOperation[], + operationsByTag: Record, + options: GenerateOptions, + operationNameByOperation?: WeakMap, +) { + const operationNames: string[] = []; + const nameIndexesByTag = new Map(); + for (const { path, method, operation, tag } of indexedOperations) { + let nameIndexes = nameIndexesByTag.get(tag); + if (!nameIndexes) { + const operations = operationsByTag[tag]; + nameIndexes = [ + createOperationNameIndex({ operations, options, tag }), + createOperationNameIndex({ operations, options, tag, keepOperationTag: true }), + ]; + nameIndexesByTag.set(tag, nameIndexes); } + const operationName = getUniqueOperationName({ + path, + method, + operation, + operationsByTag, + options, + nameIndexes, + }); + operationNameByOperation?.set(operation, operationName); + operationNames.push(operationName); } return operationNames; } +export function getOperationIndex(openApiDoc: OpenAPIV3.Document, options: GenerateOptions) { + const operationsByTag: Record = {}; + const indexedOperations: IndexedOperation[] = []; + for (const path in openApiDoc.paths) { + if (isPathExcluded(path, options)) { + continue; + } + + const pathItemObj = openApiDoc.paths[path] as OpenAPIV3.PathItemObject; + const pathItem = pick(pathItemObj, ALLOWED_METHODS); + for (const method in pathItem) { + const operation = pathItem[method as keyof typeof pathItem] as OperationObject | undefined; + if (!operation || isOperationExcluded(operation, options)) { + continue; + } + const tag = options.splitByTags ? getOperationTag(operation, options) : options.defaultTag; + (operationsByTag[tag] ??= []).push(operation); + indexedOperations.push({ path, method, operation, tag, pathParameters: pathItemObj.parameters }); + } + } + + const operationNameByOperation = new WeakMap(); + const operationNames = getUniqueOperationNamesFromIndex( + indexedOperations, + operationsByTag, + options, + operationNameByOperation, + ); + return { operationsByTag, operationNames, operationNameByOperation, indexedOperations }; +} + export function getOperationsByTag(openApiDoc: OpenAPIV3.Document, options: GenerateOptions) { const operationsByTag: Record = {}; for (const path in openApiDoc.paths) { diff --git a/src/generators/utils/sort.utils.ts b/src/generators/utils/sort.utils.ts index 3262f45a..be98e98d 100644 --- a/src/generators/utils/sort.utils.ts +++ b/src/generators/utils/sort.utils.ts @@ -2,36 +2,36 @@ import { ObjectLiteral } from "@/generators/types/common"; /** @see https://gist.github.com/RubyTuesdayDONO/5006455 */ export function topologicalSort(graph: Record>) { - const sorted: string[] = []; // sorted list of IDs ( returned value ) - const visited: Record = {}; // hash: id of already visited node => true + const sorted: string[] = []; + const sortedNames = new Set(); + const visited = new Set(); - function visit(name: string, ancestors: string[]) { - if (!Array.isArray(ancestors)) { - ancestors = []; - } - ancestors.push(name); - visited[name] = true; + function visit(name: string, ancestors: Set) { + ancestors.add(name); + visited.add(name); if (graph[name]) { graph[name].forEach((dep) => { // if already in ancestors, a closed chain (recursive relation) exists - if (ancestors.includes(dep)) { + if (ancestors.has(dep)) { return; } // if already exists, do nothing - if (visited[dep]) { + if (visited.has(dep)) { return; } - visit(dep, ancestors.slice(0)); // recursive call + visit(dep, ancestors); }); } + ancestors.delete(name); - if (!sorted.includes(name)) { + if (!sortedNames.has(name)) { + sortedNames.add(name); sorted.push(name); } } - Object.keys(graph).forEach((name) => visit(name, [])); + Object.keys(graph).forEach((name) => visit(name, new Set())); return sorted; } @@ -39,10 +39,13 @@ export function topologicalSort(graph: Record>) { /** Sort object keys using a reference order array, sort keys not in reference order in lasts positions */ export function sortObjKeysFromArray(obj: T, orderedKeys: Array) { const entries = Object.entries(obj) as Array<[keyof T, T[keyof T]]>; + const keyRanks = new Map(orderedKeys.map((key, index) => [key, index])); + const orderedEntries: Array<[keyof T, T[keyof T]]> = []; + const remainingEntries: Array<[keyof T, T[keyof T]]> = []; + for (const entry of entries) { + (keyRanks.has(entry[0]) ? orderedEntries : remainingEntries).push(entry); + } - const sortedEntries = entries - .filter(([key]) => orderedKeys.includes(key)) - .toSorted(([a], [b]) => orderedKeys.indexOf(a) - orderedKeys.indexOf(b)) - .concat(entries.filter(([key]) => !orderedKeys.includes(key))); - return Object.fromEntries(sortedEntries); + orderedEntries.sort(([a], [b]) => keyRanks.get(a)! - keyRanks.get(b)!); + return Object.fromEntries(orderedEntries.concat(remainingEntries)); } diff --git a/src/generators/utils/tag.utils.ts b/src/generators/utils/tag.utils.ts index 1e670116..6ca001d8 100644 --- a/src/generators/utils/tag.utils.ts +++ b/src/generators/utils/tag.utils.ts @@ -4,8 +4,16 @@ import { GenerateOptions } from "@/generators/types/options"; import { nonWordCharactersToCamel } from "./string.utils"; +const formattedTagCache = new Map(); +const tagFilterCache = new WeakMap; exclude: Set }>(); + export function formatTag(tag: string) { - return nonWordCharactersToCamel(tag); + let formattedTag = formattedTagCache.get(tag); + if (formattedTag === undefined) { + formattedTag = nonWordCharactersToCamel(tag); + formattedTagCache.set(tag, formattedTag); + } + return formattedTag; } export function getOperationTag(operation: OperationObject, options: GenerateOptions) { @@ -20,10 +28,18 @@ export function getEndpointTag(endpoint: Endpoint, options: GenerateOptions) { export function isTagIncluded(tag: string, options: GenerateOptions) { const normalizedTag = formatTag(tag).toLowerCase(); - if (options.includeTags.some((includeTag) => formatTag(includeTag).toLowerCase() === normalizedTag)) { + let filters = tagFilterCache.get(options); + if (!filters) { + filters = { + include: new Set(options.includeTags.map((includeTag) => formatTag(includeTag).toLowerCase())), + exclude: new Set(options.excludeTags.map((excludeTag) => formatTag(excludeTag).toLowerCase())), + }; + tagFilterCache.set(options, filters); + } + if (filters.include.has(normalizedTag)) { return true; } - if (options.excludeTags.some((excludeTag) => formatTag(excludeTag).toLowerCase() === normalizedTag)) { + if (filters.exclude.has(normalizedTag)) { return false; } return options.includeTags.length === 0; diff --git a/src/generators/utils/ts.utils.ts b/src/generators/utils/ts.utils.ts index 2cd3366d..83a8916b 100644 --- a/src/generators/utils/ts.utils.ts +++ b/src/generators/utils/ts.utils.ts @@ -1,5 +1,4 @@ import { OpenAPIV3 } from "openapi-types"; -import { match } from "ts-pattern"; import { COMPOSITE_KEYWORDS } from "@/generators/const/openapi.const"; import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; @@ -14,12 +13,15 @@ import { isPrimitiveType } from "./openapi.utils"; import { isNamedZodSchema } from "./zod-schema.utils"; export function primitiveTypeToTsType(type: PrimitiveType): string { - return match(type) - .with("string", () => "string") - .with("number", () => "number") - .with("integer", () => "number") - .with("boolean", () => "boolean") - .exhaustive(); + switch (type) { + case "string": + return "string"; + case "number": + case "integer": + return "number"; + case "boolean": + return "boolean"; + } } export function getTsTypeBase({ diff --git a/src/helpers/config.helper.ts b/src/helpers/config.helper.ts index 08913227..1c51156d 100644 --- a/src/helpers/config.helper.ts +++ b/src/helpers/config.helper.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-require-imports */ -import fs, { existsSync, rmSync, writeFileSync } from "fs"; +import fs from "fs"; import path from "path"; import { OpenAPICodegenConfig } from "@/generators/types/config"; @@ -43,77 +42,23 @@ async function loadConfigFromPath(filePath: string): Promise { + return loadModuleConfig(filePath, "ESM"); +} + +async function loadModuleConfig(filePath: string, format: string): Promise { const imported = (await import(`${pathToFileURL(filePath).href}?t=${Date.now()}`)) as { default?: OpenAPICodegenConfig; }; if (!imported.default) { - throw new Error(`ESM config must have a default export: ${filePath}`); + throw new Error(`${format} config must have a default export: ${filePath}`); } return imported.default; } -let importFresh: typeof import("import-fresh"); -export const loadJsSync = function loadJsSync(filepath: string) { - if (importFresh === undefined) { - importFresh = require("import-fresh"); - } - return importFresh(filepath); -}; - -let typescript: typeof import("typescript"); -async function loadTsConfig(filePath: string): Promise { - if (typescript === undefined) { - typescript = require("typescript"); - } - - const transpiledFilepath = `${filePath.slice(0, -2)}cjs`; - try { - const tsConfig = resolveTsConfig(path.dirname(filePath)) ?? {}; - tsConfig.compilerOptions = Object.assign({}, tsConfig.compilerOptions, { - module: typescript.ModuleKind.NodeNext, - moduleResolution: typescript.ModuleResolutionKind.NodeNext, - target: typescript.ScriptTarget.ES2022, - noEmit: false, - }); - - const fileContent = fs.readFileSync(filePath, "utf-8"); - - const transpiledFileContent = typescript.transpileModule(fileContent, tsConfig).outputText; - - writeFileSync(transpiledFilepath, transpiledFileContent); - - return (loadJsSync(transpiledFilepath) as any).default; - } catch (error: any) { - error.message = `TypeScript Error in ${filePath}:\n${error.message}`; - throw error; - } finally { - if (existsSync(transpiledFilepath)) { - rmSync(transpiledFilepath); - } - } -} - -function resolveTsConfig(directory: string) { - const filePath = typescript.findConfigFile(directory, (fileName) => { - return typescript.sys.fileExists(fileName); - }); - - if (filePath === undefined) { - return; - } - - const { config, error } = typescript.readConfigFile(filePath, (path) => typescript.sys.readFile(path)); - if (error) { - throw new Error(`Error in ${filePath}: ${error.messageText.toString()}`); - } - - return config; -} - function pathToFileURL(filePath: string) { const resolvedPath = path.resolve(filePath); const normalizedPath = resolvedPath.replace(/\\/g, "/"); diff --git a/src/index.ts b/src/index.ts index 73c0fc2e..e6684fe7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,16 @@ export * from "./generators/types/config"; // REST client export { RestClient } from "./lib/rest/rest-client"; +export { NativeRestClient } from "./lib/rest/native-rest-client"; +export { NativeRestInterceptor } from "./lib/rest/native-rest-interceptor"; +export type { NativeInterceptor } from "./lib/rest/native-rest-interceptor"; +export type { + NativeRequest, + NativeRequestConfig, + NativeResponse, + NativeUploadProgress, +} from "./lib/rest/native-rest-client.types"; +export { NativeHttpError } from "./lib/rest/native-rest-client.types"; export type { RequestInfo, RequestConfig, Response, RestClient as IRestClient } from "./lib/rest/rest-client.types"; export { RestInterceptor } from "./lib/rest/rest-interceptor"; export { RestUtils } from "./lib/rest/rest.utils"; diff --git a/src/lib/acl/ability.context.tsx b/src/lib/acl/ability.context.tsx index 21799ca2..9e4607ba 100644 --- a/src/lib/acl/ability.context.tsx +++ b/src/lib/acl/ability.context.tsx @@ -1,4 +1,4 @@ -import { type PropsWithChildren, createContext, useEffect, useState } from "react"; +import { type PropsWithChildren, createContext, useMemo } from "react"; import { AbilityBuilder, type PureAbility, type RawRuleOf, createMongoAbility } from "@casl/ability"; import { type PackRule, unpackRules } from "@casl/ability/extra"; @@ -21,12 +21,10 @@ export namespace AbilityContext { } export const Provider = ({ children }: PropsWithChildren) => { - const [ability, setAbility] = useState(initialAppAbility); - const { user } = AuthContext.useAuth<{ aclRules: PackRule>[] }>(); - useEffect(() => { + const ability = useMemo(() => { if (!user || !("aclRules" in user)) { - return; + return initialAppAbility; } const { can, build } = createAppAbilityBuilder(); @@ -35,7 +33,7 @@ export namespace AbilityContext { rules.forEach(({ action, subject, conditions }) => { can(action, subject, conditions); }); - setAbility(build()); + return build(); }, [user]); return {children}; diff --git a/src/lib/rest/error-handling.ts b/src/lib/rest/error-handling.ts index 0f1cad3e..1a14fa02 100644 --- a/src/lib/rest/error-handling.ts +++ b/src/lib/rest/error-handling.ts @@ -1,7 +1,8 @@ -import { isAxiosError, isCancel } from "axios"; import { z } from "zod"; import { ns, resolveT, type TranslateFunction } from "../config/i18n"; +import { isAxiosErrorLike, isCanceledRequest } from "./http-error.utils"; +import { NativeHttpError } from "./native-rest-client.types"; import { RestUtils } from "./rest.utils"; export type GeneralErrorCodes = @@ -87,10 +88,12 @@ export class ErrorHandler { const internalError: ErrorEntry = { code: "INTERNAL_ERROR", condition: (e) => { - if (isAxiosError(e)) { + if (isAxiosErrorLike(e)) { return e.response?.status != null && e.response.status >= 500 && e.response.status < 600; } + if (e instanceof NativeHttpError) return e.response.status >= 500 && e.response.status < 600; + return false; }, getMessage: () => resolveT()("openapi.sharedErrors.internalError", { ns }), @@ -99,10 +102,12 @@ export class ErrorHandler { const networkError: ErrorEntry = { code: "NETWORK_ERROR", condition: (e) => { - if (isAxiosError(e)) { + if (isAxiosErrorLike(e)) { return e.code === "ERR_NETWORK"; } + if (e instanceof TypeError) return true; + return false; }, getMessage: () => resolveT()("openapi.sharedErrors.networkError", { ns }), @@ -111,15 +116,7 @@ export class ErrorHandler { const canceledError: ErrorEntry = { code: "CANCELED_ERROR", condition: (e) => { - if (isCancel(e)) { - return true; - } - - if (isAxiosError(e) && e.code === "ECONNABORTED") { - return true; - } - - return false; + return isCanceledRequest(e); }, getMessage: () => resolveT()("openapi.sharedErrors.canceledError", { ns }), }; diff --git a/src/lib/rest/http-error.utils.ts b/src/lib/rest/http-error.utils.ts new file mode 100644 index 00000000..6931fc3c --- /dev/null +++ b/src/lib/rest/http-error.utils.ts @@ -0,0 +1,14 @@ +export interface HttpErrorLike { + code?: string; + response?: { status?: number; data?: unknown }; +} + +export function isAxiosErrorLike(error: unknown): error is HttpErrorLike { + return typeof error === "object" && error !== null && "isAxiosError" in error; +} + +export function isCanceledRequest(error: unknown) { + if (error instanceof DOMException && ["AbortError", "TimeoutError"].includes(error.name)) return true; + if (isAxiosErrorLike(error)) return error.code === "ERR_CANCELED" || error.code === "ECONNABORTED"; + return false; +} diff --git a/src/lib/rest/native-rest-client.test.ts b/src/lib/rest/native-rest-client.test.ts new file mode 100644 index 00000000..dc397892 --- /dev/null +++ b/src/lib/rest/native-rest-client.test.ts @@ -0,0 +1,169 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { z } from "zod"; + +import { NativeRestClient } from "./native-rest-client"; +import { NativeRestInterceptor } from "./native-rest-interceptor"; +import { NativeHttpError } from "./native-rest-client.types"; + +describe("NativeRestClient", () => { + afterEach(() => vi.unstubAllGlobals()); + + it("uses fetch, serializes params, and validates JSON", async () => { + const fetchMock = vi + .fn() + .mockResolvedValue( + new Response(JSON.stringify({ id: "1" }), { status: 200, headers: { "content-type": "application/json" } }), + ); + vi.stubGlobal("fetch", fetchMock); + + const client = new NativeRestClient({ config: { baseURL: "https://api.example.com/v1/" } }); + const result = await client.get({ resSchema: z.object({ id: z.string() }) }, "items", { + params: { tag: ["a", "b"], page: 2 }, + }); + + expect(result).toEqual({ id: "1" }); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://api.example.com/v1/items?tag=a&tag=b&page=2"); + }); + + it("runs request interceptors and can return response metadata", async () => { + const fetchMock = vi + .fn() + .mockResolvedValue( + new Response(JSON.stringify({ ok: true }), { status: 201, headers: { "content-type": "application/json" } }), + ); + vi.stubGlobal("fetch", fetchMock); + const auth = new NativeRestInterceptor({ + onRequest(request) { + request.headers.set("authorization", "Bearer token"); + return request; + }, + }); + const client = new NativeRestClient({ + config: { baseURL: "https://api.example.com" }, + interceptors: [auth], + }); + + const response = await client.post( + { resSchema: z.object({ ok: z.boolean() }) }, + "/items", + { name: "item" }, + { rawResponse: true }, + ); + + expect(response.status).toBe(201); + expect(response.data).toEqual({ ok: true }); + const init = fetchMock.mock.calls[0]?.[1] as RequestInit; + expect((init.headers as Headers).get("authorization")).toBe("Bearer token"); + expect(init.body).toBe(JSON.stringify({ name: "item" })); + }); + + it("supports an injected fetch implementation", async () => { + const customFetch = vi + .fn() + .mockResolvedValue( + new Response(JSON.stringify({ ok: true }), { headers: { "content-type": "application/json" } }), + ); + const client = new NativeRestClient({ + config: { baseURL: "https://api.example.com", fetch: customFetch }, + }); + + await client.get({ resSchema: z.object({ ok: z.boolean() }) }, "/items"); + + expect(customFetch).toHaveBeenCalledOnce(); + }); + + it("lets an error interceptor refresh credentials and retry a request", async () => { + const customFetch = vi + .fn() + .mockResolvedValueOnce( + new Response(JSON.stringify({ code: "UNAUTHORIZED" }), { + status: 401, + headers: { "content-type": "application/json" }, + }), + ) + .mockResolvedValueOnce( + new Response(JSON.stringify({ id: "1" }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + const client = new NativeRestClient({ + config: { baseURL: "https://api.example.com", fetch: customFetch }, + interceptors: [ + { + async onError(error, context) { + if (error instanceof NativeHttpError && error.response.status === 401) { + context.request.headers.set("authorization", "Bearer refreshed"); + return context.retry(); + } + throw error; + }, + }, + ], + }); + + await expect(client.get({ resSchema: z.object({ id: z.string() }) }, "/items")).resolves.toEqual({ id: "1" }); + expect((customFetch.mock.calls[1]?.[1]?.headers as Headers).get("authorization")).toBe("Bearer refreshed"); + }); + + it("uses XHR only when browser upload progress is requested", async () => { + const fetchMock = vi.fn(); + const progress = vi.fn(); + const instances: MockXmlHttpRequest[] = []; + class MockXmlHttpRequest { + upload: { onprogress?: (event: { loaded: number; total: number }) => void } = {}; + status = 200; + timeout = 0; + onload?: () => void; + onerror?: () => void; + ontimeout?: () => void; + onabort?: () => void; + method?: string; + url?: string; + constructor() { + instances.push(this); + } + open(method: string, url: string) { + this.method = method; + this.url = url; + } + setRequestHeader() {} + abort() { + this.onabort?.(); + } + send() { + this.upload.onprogress?.({ loaded: 5, total: 10 }); + this.onload?.(); + } + } + vi.stubGlobal("fetch", fetchMock); + vi.stubGlobal("XMLHttpRequest", MockXmlHttpRequest); + + const client = new NativeRestClient(); + await client.upload("https://uploads.example.com/file", new Blob(["data"]), { onUploadProgress: progress }); + + expect(fetchMock).not.toHaveBeenCalled(); + expect(instances[0]).toMatchObject({ method: "PUT", url: "https://uploads.example.com/file" }); + expect(progress).toHaveBeenCalledWith({ loaded: 5, total: 10 }); + }); + + it("throws transport errors directly for the application to handle", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue( + new Response(JSON.stringify({ code: "NOT_FOUND", message: "Missing" }), { + status: 404, + headers: { "content-type": "application/json" }, + }), + ), + ); + const client = new NativeRestClient({ config: { baseURL: "https://api.example.com" } }); + + const request = client.get({ resSchema: z.object({ id: z.string() }) }, "/missing"); + + await expect(request).rejects.toBeInstanceOf(NativeHttpError); + await expect(request).rejects.toMatchObject({ + response: { status: 404, data: { code: "NOT_FOUND", message: "Missing" } }, + }); + }); +}); diff --git a/src/lib/rest/native-rest-client.ts b/src/lib/rest/native-rest-client.ts new file mode 100644 index 00000000..3acc93f2 --- /dev/null +++ b/src/lib/rest/native-rest-client.ts @@ -0,0 +1,288 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { z } from "zod"; +import { ErrorHandler } from "./error-handling"; + +import { + NativeHttpError, + NativeRequest, + NativeRequestConfig, + NativeRequestInfo, + NativeResponse, +} from "./native-rest-client.types"; +import type { + GeneralTransportErrorCode, + RestTransport, + RestTransportInterceptor, + TransportErrorContext, + TransportFetch, + TransportResponse, +} from "./rest-transport.types"; + +type NativeResult = IsRawRes extends true ? NativeResponse : T; + +export class NativeRestClient implements RestTransport { + private readonly interceptors: RestTransportInterceptor[]; + private readonly errorHandler?: ErrorHandler; + + constructor({ + config, + interceptors = [], + errorHandler, + }: { + config?: { + baseURL?: string; + headers?: HeadersInit; + credentials?: RequestCredentials; + fetch?: TransportFetch; + }; + interceptors?: RestTransportInterceptor[]; + errorHandler?: ErrorHandler; + } = {}) { + this.baseURL = config?.baseURL ?? ""; + this.headers = config?.headers; + this.credentials = config?.credentials; + this.fetch = config?.fetch ?? globalThis.fetch.bind(globalThis); + this.interceptors = [...interceptors]; + this.errorHandler = errorHandler; + } + + private readonly baseURL: string; + private readonly headers?: HeadersInit; + private readonly credentials?: RequestCredentials; + private readonly fetch: TransportFetch; + + attachInterceptor(interceptor: RestTransportInterceptor) { + this.interceptors.push(interceptor); + } + + ejectInterceptor(interceptor: RestTransportInterceptor) { + const index = this.interceptors.indexOf(interceptor); + if (index >= 0) this.interceptors.splice(index, 1); + } + + get( + info: NativeRequestInfo, + url: string, + config?: NativeRequestConfig, + ): Promise> { + return this.request(info, "GET", url, undefined, config); + } + + post( + info: NativeRequestInfo, + url: string, + data?: unknown, + config?: NativeRequestConfig, + ): Promise> { + return this.request(info, "POST", url, data, config); + } + + patch( + info: NativeRequestInfo, + url: string, + data?: unknown, + config?: NativeRequestConfig, + ): Promise> { + return this.request(info, "PATCH", url, data, config); + } + + put( + info: NativeRequestInfo, + url: string, + data?: unknown, + config?: NativeRequestConfig, + ): Promise> { + return this.request(info, "PUT", url, data, config); + } + + delete( + info: NativeRequestInfo, + url: string, + data?: unknown, + config?: NativeRequestConfig, + ): Promise> { + return this.request(info, "DELETE", url, data, config); + } + + async upload( + url: string, + data: BodyInit, + config: NativeRequestConfig = {}, + method: "put" | "post" = "put", + ): Promise { + if (config.onUploadProgress && typeof XMLHttpRequest !== "undefined") { + await this.uploadWithXhr(url, data, config, method); + return; + } + await this.execute( + { url, method: method.toUpperCase(), body: data, headers: new Headers(config.headers), signal: config.signal }, + config, + false, + ); + } + + private async request( + info: NativeRequestInfo, + method: string, + path: string, + data?: unknown, + config: NativeRequestConfig = {}, + ): Promise> { + const errorStack = new Error().stack; + try { + const headers = new Headers(this.headers); + new Headers(config.headers).forEach((value, key) => headers.set(key, value)); + let body: BodyInit | undefined; + if (data != null) { + if (data instanceof FormData || data instanceof Blob || typeof data === "string") body = data; + else { + body = JSON.stringify(data); + if (!headers.has("content-type")) headers.set("content-type", "application/json"); + } + } + const request: NativeRequest = { + url: this.resolveUrl(path, config.params), + method, + headers, + body, + signal: config.signal, + credentials: config.credentials ?? this.credentials, + }; + const response = await this.execute(request, config); + const parseResult = info.resSchema.safeParse(response.data); + let dataResult: T; + if (parseResult.success) dataResult = parseResult.data; + else if (config.allowInvalidResponseData && method === "GET") { + parseResult.error.name = "BE Response schema mismatch - ZodError"; + parseResult.error.stack = [parseResult.error.stack, ...(errorStack?.split("\n").slice(2) ?? [])].join("\n"); + console.error(parseResult.error); + dataResult = response.data as T; + } else throw parseResult.error; + return (config.rawResponse ? { ...response, data: dataResult } : dataResult) as NativeResult; + } catch (error) { + if (error instanceof z.ZodError) error.name = "BE Response schema mismatch - ZodError"; + const transformed = await this.runErrorInterceptors(error); + throw this.errorHandler ? this.errorHandler.rethrowError(transformed) : transformed; + } + } + + private async execute( + request: NativeRequest, + config: NativeRequestConfig = {}, + applyInterceptors = true, + ): Promise> { + let next = request; + if (applyInterceptors) { + for (const interceptor of this.interceptors) next = (await interceptor.onRequest?.(next)) ?? next; + } + const timeoutController = config.timeout ? new AbortController() : undefined; + const timer = timeoutController ? setTimeout(() => timeoutController.abort(), config.timeout) : undefined; + if (timeoutController && next.signal) { + if (next.signal.aborted) timeoutController.abort(next.signal.reason); + else next.signal.addEventListener("abort", () => timeoutController.abort(next.signal?.reason), { once: true }); + } + const signal = timeoutController?.signal ?? next.signal; + try { + const res = await this.fetch(next.url, { + method: next.method, + headers: next.headers, + body: next.body, + signal, + credentials: next.credentials, + }); + const data = await this.readResponse(res, config.responseType); + let response: NativeResponse = { + data, + status: res.status, + statusText: res.statusText, + headers: res.headers, + url: res.url, + }; + if (!res.ok) throw new NativeHttpError(`Request failed with status ${res.status}`, response); + if (applyInterceptors) { + for (const interceptor of this.interceptors) response = (await interceptor.onResponse?.(response)) ?? response; + } + return response; + } catch (error) { + if (!applyInterceptors) throw error; + const context: TransportErrorContext = { + request: next, + response: error instanceof NativeHttpError ? error.response : undefined, + retry: (request = next) => this.execute({ ...request, retryCount: (next.retryCount ?? 0) + 1 }, config, true), + }; + const transformed = await this.runErrorInterceptors(error, context); + if (this.isTransportResponse(transformed)) return transformed; + throw transformed; + } finally { + if (timer) clearTimeout(timer); + } + } + + private async readResponse(response: globalThis.Response, type?: NativeRequestConfig["responseType"]) { + if (response.status === 204) return undefined; + if (type === "blob") return response.blob(); + if (type === "arrayBuffer") return response.arrayBuffer(); + if (type === "text") return response.text(); + const text = await response.text(); + if (text === "") return undefined; + if (!response.headers.get("content-type")?.includes("json")) { + try { + return JSON.parse(text); + } catch { + return text; + } + } + return JSON.parse(text); + } + + private resolveUrl(path: string, params?: Record) { + const baseURL = this.baseURL && !this.baseURL.endsWith("/") ? `${this.baseURL}/` : this.baseURL; + const url = /^https?:\/\//.test(path) + ? new URL(path) + : new URL(path.replace(/^\//, ""), baseURL || globalThis.location?.origin); + for (const [key, value] of Object.entries(params ?? {})) { + if (value == null) continue; + if (Array.isArray(value)) value.forEach((item) => url.searchParams.append(key, String(item))); + else url.searchParams.append(key, String(value)); + } + return url.toString(); + } + + private async runErrorInterceptors(error: unknown, context?: TransportErrorContext) { + let next = error; + if (!context) return next; + for (const interceptor of [...this.interceptors].reverse()) { + if (interceptor.onError) { + try { + next = await interceptor.onError(next, context); + } catch (transformed) { + next = transformed; + } + } + } + return next; + } + + private isTransportResponse(value: unknown): value is TransportResponse { + return !!value && typeof value === "object" && "status" in value && "headers" in value && "data" in value; + } + + private uploadWithXhr(url: string, data: BodyInit, config: NativeRequestConfig, method: "put" | "post") { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open(method.toUpperCase(), url); + new Headers(config.headers).forEach((value, key) => xhr.setRequestHeader(key, value)); + if (config.timeout) xhr.timeout = config.timeout; + xhr.upload.onprogress = ({ loaded, total }) => config.onUploadProgress?.({ loaded, total }); + xhr.onload = () => + xhr.status >= 200 && xhr.status < 300 + ? resolve() + : reject(new Error(`Upload failed with status ${xhr.status}`)); + xhr.onerror = () => reject(new TypeError("Network request failed")); + xhr.ontimeout = () => reject(new DOMException("Request timed out", "TimeoutError")); + config.signal?.addEventListener("abort", () => xhr.abort(), { once: true }); + xhr.onabort = () => reject(config.signal?.reason ?? new DOMException("Request aborted", "AbortError")); + xhr.send(data as XMLHttpRequestBodyInit); + }); + } +} diff --git a/src/lib/rest/native-rest-client.types.ts b/src/lib/rest/native-rest-client.types.ts new file mode 100644 index 00000000..efc3f266 --- /dev/null +++ b/src/lib/rest/native-rest-client.types.ts @@ -0,0 +1,9 @@ +export type { + RestRequestInfo as NativeRequestInfo, + RestResponseType as NativeResponseType, + TransportRequest as NativeRequest, + TransportRequestConfig as NativeRequestConfig, + TransportResponse as NativeResponse, + UploadProgress as NativeUploadProgress, +} from "./rest-transport.types"; +export { HttpError as NativeHttpError } from "./rest-transport.types"; diff --git a/src/lib/rest/native-rest-interceptor.ts b/src/lib/rest/native-rest-interceptor.ts new file mode 100644 index 00000000..50b2f3e1 --- /dev/null +++ b/src/lib/rest/native-rest-interceptor.ts @@ -0,0 +1,25 @@ +import type { + RestTransportInterceptor, + TransportErrorContext, + TransportRequest, + TransportResponse, +} from "./rest-transport.types"; + +export type NativeInterceptor = RestTransportInterceptor; + +export class NativeRestInterceptor implements RestTransportInterceptor { + constructor(private readonly interceptor: RestTransportInterceptor) {} + + onRequest(request: TransportRequest) { + return this.interceptor.onRequest?.(request) ?? request; + } + + onResponse(response: TransportResponse) { + return this.interceptor.onResponse?.(response) ?? response; + } + + onError(error: unknown, context: TransportErrorContext) { + if (!this.interceptor.onError) throw error; + return this.interceptor.onError(error, context); + } +} diff --git a/src/lib/rest/rest-transport.types.ts b/src/lib/rest/rest-transport.types.ts new file mode 100644 index 00000000..950fd0e3 --- /dev/null +++ b/src/lib/rest/rest-transport.types.ts @@ -0,0 +1,109 @@ +import { z } from "zod"; + +export type GeneralTransportErrorCode = + | "DATA_VALIDATION_ERROR" + | "NETWORK_ERROR" + | "CANCELED_ERROR" + | "INTERNAL_ERROR" + | "UNKNOWN_ERROR"; + +export type RestResponseType = "json" | "text" | "blob" | "arrayBuffer"; +export type TransportFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise; + +export interface UploadProgress { + loaded: number; + total: number; +} + +export interface TransportRequestConfig { + headers?: HeadersInit; + params?: Record; + signal?: AbortSignal; + timeout?: number; + credentials?: RequestCredentials; + responseType?: RestResponseType; + rawResponse?: IsRawRes; + allowInvalidResponseData?: boolean; + onUploadProgress?: (progress: UploadProgress) => void; +} + +export interface TransportResponse { + data: T; + status: number; + statusText: string; + headers: Headers; + url: string; +} + +export interface RestRequestInfo { + resSchema: z.ZodType; +} + +export interface TransportRequest { + url: string; + method: string; + headers: Headers; + body?: BodyInit | null; + signal?: AbortSignal; + credentials?: RequestCredentials; + retryCount?: number; +} + +export interface TransportErrorContext { + request: TransportRequest; + response?: TransportResponse; + retry(request?: TransportRequest): Promise>; +} + +export interface RestTransportInterceptor { + onRequest?(request: TransportRequest): TransportRequest | Promise; + onResponse?(response: TransportResponse): TransportResponse | Promise>; + onError?(error: unknown, context: TransportErrorContext): unknown | Promise; +} + +export type TransportResult = IsRawRes extends true ? TransportResponse : T; + +export interface RestTransport { + attachInterceptor(interceptor: RestTransportInterceptor): void; + ejectInterceptor(interceptor: RestTransportInterceptor): void; + get( + info: RestRequestInfo, + url: string, + config?: TransportRequestConfig, + ): Promise>; + post( + info: RestRequestInfo, + url: string, + data?: unknown, + config?: TransportRequestConfig, + ): Promise>; + patch( + info: RestRequestInfo, + url: string, + data?: unknown, + config?: TransportRequestConfig, + ): Promise>; + put( + info: RestRequestInfo, + url: string, + data?: unknown, + config?: TransportRequestConfig, + ): Promise>; + delete( + info: RestRequestInfo, + url: string, + data?: unknown, + config?: TransportRequestConfig, + ): Promise>; + upload(url: string, data: BodyInit, config?: TransportRequestConfig, method?: "put" | "post"): Promise; +} + +export class HttpError extends Error { + constructor( + message: string, + public readonly response: TransportResponse, + ) { + super(message); + this.name = "HttpError"; + } +} diff --git a/src/lib/rest/rest.utils.ts b/src/lib/rest/rest.utils.ts index f7369b82..6fee9dfb 100644 --- a/src/lib/rest/rest.utils.ts +++ b/src/lib/rest/rest.utils.ts @@ -1,22 +1,21 @@ -import { AxiosError, AxiosResponseHeaders } from "axios"; -import { isAxiosError } from "axios"; +import type { AxiosError, AxiosResponseHeaders } from "axios"; import { z } from "zod"; +import { isAxiosErrorLike } from "./http-error.utils"; +import { NativeHttpError } from "./native-rest-client.types"; + +function getResponseData(error: unknown) { + if (error instanceof NativeHttpError) return error.response.data; + return isAxiosErrorLike(error) ? error.response?.data : undefined; +} + export namespace RestUtils { export const extractServerResponseCode = (e: unknown): string | number | null => { if (e instanceof z.ZodError) { return "validation-exception"; } - if (!isAxiosError(e)) { - return null; - } - - if (!e.response) { - return null; - } - - const data = e.response.data as { code: unknown } | undefined; + const data = getResponseData(e) as { code: unknown } | undefined; if (typeof data?.code === "string") { return data.code; @@ -43,15 +42,7 @@ export namespace RestUtils { return e.message; } - if (!isAxiosError(e)) { - return null; - } - - if (!e.response) { - return null; - } - - const data = e.response.data as { message: unknown } | undefined; + const data = getResponseData(e) as { message: unknown } | undefined; if (typeof data?.message === "string") { return data.message; diff --git a/src/native.ts b/src/native.ts new file mode 100644 index 00000000..36df2128 --- /dev/null +++ b/src/native.ts @@ -0,0 +1,23 @@ +export { NativeRestClient } from "./lib/rest/native-rest-client"; +export { NativeRestInterceptor } from "./lib/rest/native-rest-interceptor"; +export type { NativeInterceptor } from "./lib/rest/native-rest-interceptor"; +export type { + NativeRequest, + NativeRequestConfig, + NativeResponse, + NativeUploadProgress, +} from "./lib/rest/native-rest-client.types"; +export { NativeHttpError } from "./lib/rest/native-rest-client.types"; +export type { + RestRequestInfo, + GeneralTransportErrorCode, + RestResponseType, + RestTransport, + RestTransportInterceptor, + TransportRequest, + TransportRequestConfig, + TransportResponse, + TransportResult, + UploadProgress, +} from "./lib/rest/rest-transport.types"; +export { HttpError } from "./lib/rest/rest-transport.types"; diff --git a/src/native/generateFilesFromNativeOpenAPI.test.ts b/src/native/generateFilesFromNativeOpenAPI.test.ts new file mode 100644 index 00000000..2c080133 --- /dev/null +++ b/src/native/generateFilesFromNativeOpenAPI.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; + +import { DEFAULT_GENERATE_OPTIONS } from "@/generators/const/options.const"; + +import { supportsCompleteNativeRender } from "./generateFilesFromNativeOpenAPI"; + +describe("supportsCompleteNativeRender", () => { + const supportedOptions = { + ...DEFAULT_GENERATE_OPTIONS, + modelsInCommon: true, + tsNamespaces: true, + }; + + it("supports the namespace-based common-model renderer", () => { + expect(supportsCompleteNativeRender(supportedOptions)).toBe(true); + }); + + it("supports namespace-free module-local models", () => { + expect(supportsCompleteNativeRender({ ...supportedOptions, modelsInCommon: false, tsNamespaces: false })).toBe( + true, + ); + }); + + it.each([ + ["module-local models with namespaces", { modelsInCommon: false }], + ["namespace-free common models", { tsNamespaces: false }], + ["unsplit output", { splitByTags: false }], + ["inline endpoints", { inlineEndpoints: true }], + ["builder configs", { builderConfigs: true }], + ["workspace context", { workspaceContext: ["officeId"] }], + ])("does not claim full native support for %s", (_name, override) => { + expect(supportsCompleteNativeRender({ ...supportedOptions, ...override })).toBe(false); + }); +}); diff --git a/src/native/generateFilesFromNativeOpenAPI.ts b/src/native/generateFilesFromNativeOpenAPI.ts new file mode 100644 index 00000000..50ae84c7 --- /dev/null +++ b/src/native/generateFilesFromNativeOpenAPI.ts @@ -0,0 +1,99 @@ +import { GenerateFileData, GenerateType } from "@/generators/types/generate"; +import { GenerateOptions } from "@/generators/types/options"; +import { getOutputFileName } from "@/generators/utils/file.utils"; +import { getTagFileName } from "@/generators/utils/generate/generate.utils"; + +import { compileNativeData } from "./native-bindings"; + +type CompleteNativeData = { + baseUrl: string; + renderedModels: Record; + renderedEndpoints: Record; + renderedQueries: Record; + renderedAcl: Record; + renderedShared: Record; + renderedComplete: boolean; + renderedTags: string[]; +}; + +export function generateFilesFromNativeOpenAPI( + source: string, + yaml: boolean, + options: GenerateOptions, +): GenerateFileData[] | undefined { + if (!supportsCompleteNativeRender(options)) { + if (process.env.OPENAPI_CODEGEN_REQUIRE_FULL_NATIVE === "1") { + throw new Error("The selected options are not supported by the full native renderer"); + } + return undefined; + } + + const nativeData = compileNativeData(source, yaml, JSON.stringify({ ...options, nativeCompact: true })) + .data as CompleteNativeData; + if (!nativeData.renderedComplete) { + if (process.env.OPENAPI_CODEGEN_REQUIRE_FULL_NATIVE === "1") { + throw new Error("The selected options are not supported by the full native renderer"); + } + return undefined; + } + + const files: GenerateFileData[] = []; + const taggedRenderers: [GenerateType, Record][] = options.modelsOnly + ? [[GenerateType.Models, nativeData.renderedModels]] + : [ + [GenerateType.Models, nativeData.renderedModels], + [GenerateType.Endpoints, nativeData.renderedEndpoints], + [GenerateType.Queries, nativeData.renderedQueries], + ...(options.acl + ? ([[GenerateType.Acl, nativeData.renderedAcl]] as [GenerateType, Record][]) + : []), + ]; + + for (const tag of nativeData.renderedTags) { + for (const [type, rendered] of taggedRenderers) { + const content = rendered[tag]; + if (content) files.push(taggedFile(options, tag, type, content)); + } + } + + if (!options.modelsOnly) { + if (options.acl && nativeData.renderedShared.appAcl) { + files.push(outputFile(options, "acl/app.ability.ts", nativeData.renderedShared.appAcl)); + } + if (options.mutationEffects && nativeData.renderedShared.queryModules) { + files.push(outputFile(options, "queryModules.ts", nativeData.renderedShared.queryModules)); + } + if (!options.restClientImportPath) { + files.push( + outputFile( + options, + "app-rest-client.ts", + `import { ${options.restClient === "native" ? "NativeRestClient" : "RestClient"} } from "@povio/openapi-codegen-cli${options.restClient === "native" ? "/native" : ""}";\n\nexport const AppRestClient = new ${options.restClient === "native" ? "NativeRestClient" : "RestClient"}({\n config: {\n baseURL: "${nativeData.baseUrl}"\n },\n});\n`, + ), + ); + } + if (nativeData.renderedShared.domainErrors) { + files.push(outputFile(options, "domain-errors.ts", nativeData.renderedShared.domainErrors)); + } + } + + return files; +} + +export function supportsCompleteNativeRender(options: GenerateOptions) { + return ( + options.splitByTags && + ((options.modelsInCommon && options.tsNamespaces) || (!options.modelsInCommon && !options.tsNamespaces)) && + !options.inlineEndpoints && + !options.builderConfigs && + (options.workspaceContext?.length ?? 0) === 0 + ); +} + +function taggedFile(options: GenerateOptions, tag: string, type: GenerateType, content: string) { + return outputFile(options, getTagFileName({ tag, type, options }), content); +} + +function outputFile(options: GenerateOptions, fileName: string, content: string) { + return { fileName: getOutputFileName({ output: options.output, fileName }), content }; +} diff --git a/src/native/getDataFromNativeOpenAPIDoc.ts b/src/native/getDataFromNativeOpenAPIDoc.ts new file mode 100644 index 00000000..fb3e5b78 --- /dev/null +++ b/src/native/getDataFromNativeOpenAPIDoc.ts @@ -0,0 +1,269 @@ +import { OpenAPIV3 } from "openapi-types"; + +import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; +import { Endpoint } from "@/generators/types/endpoint"; +import { GenerateData } from "@/generators/types/generate"; +import { GenerateOptions } from "@/generators/types/options"; +import { getEndpointTag } from "@/generators/utils/tag.utils"; + +import { compileNativeData } from "./native-bindings"; + +type NativeData = { + endpoints: Endpoint[]; + schemas: Record; + schemaOwners: Record; + schemaRefs: Record; + circularSchemas: string[]; + topologyOrder: string[]; + baseUrl: string; + generatedObjects: Record; + generatedDependencies: Record; + dependencies: Record; + renderedModels: Record; + renderedEndpoints: Record; + renderedQueries: Record; + renderedAcl: Record; + renderedShared: Record; + renderedComplete: boolean; + renderedTags: string[]; + document: OpenAPIV3.Document | null; +}; + +class NativeSchemaResolver { + readonly options: GenerateOptions; + readonly openApiDoc: OpenAPIV3.Document; + readonly dependencyGraph: SchemaResolver["dependencyGraph"]; + + private readonly refByName: Map; + private readonly nameByRef: Map; + private readonly circularSchemas: Set; + private readonly referencedNames = new Map(); + private enumObjectsByCode?: Map; + + constructor( + openApiDoc: OpenAPIV3.Document, + options: GenerateOptions, + private readonly nativeData: NativeData, + ) { + this.openApiDoc = openApiDoc; + this.options = options; + this.refByName = new Map(Object.entries(nativeData.schemaRefs)); + for (const name of Object.keys(nativeData.generatedObjects)) { + if (!this.refByName.has(name)) this.refByName.set(name, `#/x-native-schemas/${encodeURIComponent(name)}`); + } + this.nameByRef = new Map([...this.refByName].map(([name, ref]) => [ref, name])); + this.circularSchemas = new Set(nativeData.circularSchemas); + const refsDependencyGraph = Object.fromEntries( + Object.entries(nativeData.dependencies).map(([ref, dependencies]) => [ref, new Set(dependencies)]), + ); + this.dependencyGraph = { refsDependencyGraph, deepDependencyGraph: {} }; + for (const name of Object.keys(nativeData.generatedObjects)) { + const ref = this.refByName.get(name)!; + const dependencies = new Set(); + const addDependency = (dependency: string) => { + if (dependency === ref || dependencies.has(dependency)) return; + dependencies.add(dependency); + for (const child of this.dependencyGraph.refsDependencyGraph[dependency] ?? []) addDependency(child); + }; + for (const dependencyName of nativeData.generatedDependencies[name] ?? []) { + const dependency = this.refByName.get(dependencyName); + if (dependency) addDependency(dependency); + } + this.dependencyGraph.refsDependencyGraph[ref] = dependencies; + } + } + + getCodeByZodSchemaName(name: string) { + return this.nativeData.schemas[name]; + } + + getNativeRenderedModels(tag: string) { + return this.nativeData.renderedModels[tag]; + } + + getNativeRenderedEndpoints(tag: string) { + return this.nativeData.renderedEndpoints[tag]; + } + + getNativeRenderedQueries(tag: string) { + return this.nativeData.renderedQueries[tag]; + } + + getNativeRenderedAcl(tag: string) { + return this.nativeData.renderedAcl[tag]; + } + + getNativeRenderedShared(name: string) { + return this.nativeData.renderedShared[name]; + } + + getRefByZodSchemaName(name: string) { + return this.refByName.get(name); + } + + getZodSchemaNameByRef(ref: string) { + return this.nameByRef.get(normalizeRef(ref)); + } + + getTagByZodSchemaName(name: string) { + return this.options.modelsInCommon ? this.options.defaultTag : this.nativeData.schemaOwners[name]; + } + + isSchemaCircular(ref: string) { + const name = this.nameByRef.get(normalizeRef(ref)); + return Boolean(name && this.circularSchemas.has(name)); + } + + getZodSchemaObj(name: string) { + if (this.nativeData.generatedObjects[name]) return this.nativeData.generatedObjects[name]; + const ref = this.refByName.get(name); + if (ref) return this.resolveRef(ref); + const code = this.nativeData.schemas[name]; + if (!code?.startsWith("z.enum(")) return undefined; + this.enumObjectsByCode ??= collectEnumObjects(this.openApiDoc); + return this.enumObjectsByCode.get(code); + } + + getExtractedEnumZodSchemaNamesReferencedBySchemaRef(ref: string) { + const normalizedRef = normalizeRef(ref); + const cached = this.referencedNames.get(normalizedRef); + if (cached) return cached; + const name = this.nameByRef.get(normalizedRef); + const code = name ? this.nativeData.schemas[name] : undefined; + const directNames = new Set( + [...(this.dependencyGraph.refsDependencyGraph[normalizedRef] ?? [])] + .map((dependency) => this.nameByRef.get(dependency)) + .filter((value): value is string => Boolean(value)), + ); + const references = [ + ...(code + ? [...code.matchAll(/\b[A-Za-z_$][\w$]*Schema\b/g)] + .map(([match]) => match) + .filter( + (match, index, values) => + match !== name && + values.indexOf(match) === index && + match in this.nativeData.schemas && + !directNames.has(match), + ) + : []), + ].filter((value, index, values) => values.indexOf(value) === index); + this.referencedNames.set(normalizedRef, references); + return references; + } + + getCompositeZodSchemaByZodSchemaName() { + return undefined; + } + + resolveObject(value: OpenAPIV3.ReferenceObject | T): T { + if (value && typeof value === "object" && "$ref" in value) { + return this.resolveRef((value as OpenAPIV3.ReferenceObject).$ref) as T; + } + return value as T; + } + + getBaseUrl() { + return this.nativeData.baseUrl; + } + + private resolveRef(ref: string) { + const segments = normalizeRef(ref) + .slice(2) + .split("/") + .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")); + let value: unknown = this.openApiDoc; + for (const segment of segments) { + value = (value as Record | undefined)?.[segment]; + } + return value; + } +} + +function collectEnumObjects(document: OpenAPIV3.Document) { + const enums = new Map(); + const visit = (value: unknown) => { + if (!value || typeof value !== "object") return; + if (Array.isArray(value)) { + value.forEach(visit); + return; + } + const object = value as Record; + if (Array.isArray(object.enum)) { + const code = `z.enum(${JSON.stringify(object.enum)})`.replaceAll(",", ", "); + Object.assign(enums.get(code) ?? {}, object); + enums.set(code, { ...(enums.get(code) ?? {}), ...(object as OpenAPIV3.SchemaObject) }); + } + Object.values(object).forEach(visit); + }; + visit(document.components?.schemas ?? {}); + return enums; +} + +function normalizeRef(ref: string) { + if (ref.startsWith("#/")) return ref; + if (ref.startsWith("#")) return `#/${ref.slice(1).replace(/^\//, "")}`; + return ref; +} + +function getTagElement(tag: string, data: GenerateData) { + let value = data.get(tag); + if (!value) { + value = { endpoints: [], zodSchemas: {} }; + data.set(tag, value); + } + return value; +} + +export function getDataFromNativeOpenAPIDoc( + openApiDoc: OpenAPIV3.Document, + options: GenerateOptions, + nativeSource?: { source: string; yaml: boolean }, +) { + const started = performance.now(); + const source = nativeSource?.source ?? JSON.stringify(openApiDoc); + const afterLoad = performance.now(); + const nativeResult = compileNativeData( + source, + nativeSource?.yaml ?? false, + JSON.stringify({ ...options, nativeCompact: true }), + ); + const afterNative = performance.now(); + const nativeData = nativeResult.data as NativeData; + const afterParse = performance.now(); + const resolverDocument = nativeData.document ?? openApiDoc; + const resolver = new NativeSchemaResolver(resolverDocument, options, nativeData) as unknown as SchemaResolver; + const afterResolver = performance.now(); + const data: GenerateData = new Map(); + + if (nativeData.renderedComplete) { + for (const tag of nativeData.renderedTags) data.set(tag, { endpoints: [], zodSchemas: {} }); + if (process.env.OPENAPI_NATIVE_PROFILE === "1") { + console.error( + `native bridge load=${(afterLoad - started).toFixed(1)}ms call=${(afterNative - afterLoad).toFixed(1)}ms parse=${(afterParse - afterNative).toFixed(1)}ms facade=${(performance.now() - afterParse).toFixed(1)}ms compact=true`, + ); + } + return { resolver, data }; + } + + if (!options.splitByTags) { + data.set(options.defaultTag, { endpoints: nativeData.endpoints, zodSchemas: nativeData.schemas }); + return { resolver, data }; + } + + for (const endpoint of nativeData.endpoints) { + getTagElement(getEndpointTag(endpoint, options), data).endpoints.push(endpoint); + } + for (const [name, code] of Object.entries(nativeData.schemas)) { + const tag = options.modelsInCommon ? options.defaultTag : nativeData.schemaOwners[name]; + if (tag) getTagElement(tag, data).zodSchemas[name] = code; + } + + if (process.env.OPENAPI_NATIVE_PROFILE === "1") { + console.error( + `native bridge load=${(afterLoad - started).toFixed(1)}ms call=${(afterNative - afterLoad).toFixed(1)}ms parse=${(afterParse - afterNative).toFixed(1)}ms facade=${(afterResolver - afterParse).toFixed(1)}ms split=${(performance.now() - afterResolver).toFixed(1)}ms`, + ); + } + + return { resolver, data }; +} diff --git a/src/native/native-bindings.test.ts b/src/native/native-bindings.test.ts new file mode 100644 index 00000000..da1cc127 --- /dev/null +++ b/src/native/native-bindings.test.ts @@ -0,0 +1,353 @@ +import { describe, expect, test } from "vitest"; +import { existsSync } from "node:fs"; +import fs from "node:fs/promises"; + +import { SchemaResolver } from "@/generators/core/SchemaResolver.class"; +import { getDataFromOpenAPIDoc } from "@/generators/core/getDataFromOpenAPIDoc"; +import { resolveConfig } from "@/generators/core/resolveConfig"; +import { getZodSchemasFromOpenAPIDoc } from "@/generators/core/zod/getZodSchemasFromOpenAPIDoc"; +import { generateModels } from "@/generators/generate/generateModels"; +import { generateEndpoints } from "@/generators/generate/generateEndpoints"; +import { generateQueries } from "@/generators/generate/generateQueries"; +import { generateAcl, generateAppAcl } from "@/generators/generate/generateAcl"; +import { generateDomainErrors } from "@/generators/generate/generateDomainErrors"; +import { generateQueryModules } from "@/generators/generate/generateQueryModules"; + +import { getNativeBindings, shouldUseNativeCodegen } from "./native-bindings"; + +const BENCHMARK_FIXTURE = "test/benchmarks/openapi.localhost4000.json"; +const benchmarkTest = existsSync(BENCHMARK_FIXTURE) ? test : test.skip; + +describe("native OpenAPI bindings", () => { + test("loads in Bun/Node and parses JSON", () => { + const native = getNativeBindings(); + const result = native.parseOpenapi('{"openapi":"3.0.3","paths":{}}', false); + + expect(native.nativeVersion()).toBe("0.1.0"); + expect(JSON.parse(result.documentJson)).toEqual({ openapi: "3.0.3", paths: {} }); + expect(result.parseMicros).toBeGreaterThanOrEqual(0); + }); + + test("auto-selects a loadable native addon and supports an explicit TypeScript fallback", () => { + const previous = process.env.OPENAPI_CODEGEN_NATIVE; + delete process.env.OPENAPI_CODEGEN_NATIVE; + expect(shouldUseNativeCodegen()).toBe(true); + process.env.OPENAPI_CODEGEN_NATIVE = "0"; + expect(shouldUseNativeCodegen()).toBe(false); + if (previous === undefined) delete process.env.OPENAPI_CODEGEN_NATIVE; + else process.env.OPENAPI_CODEGEN_NATIVE = previous; + }); + + test("parses YAML", () => { + const result = getNativeBindings().parseOpenapi("openapi: 3.0.3\npaths: {}\n", true); + expect(JSON.parse(result.documentJson)).toEqual({ openapi: "3.0.3", paths: {} }); + }); + + test("renders explicit types for mutually recursive schemas", () => { + const source = JSON.stringify({ + openapi: "3.0.3", + info: { title: "Recursive schemas", version: "1.0.0" }, + paths: {}, + components: { + schemas: { + Player: { + type: "object", + properties: { access_tags: { type: "array", items: { $ref: "#/components/schemas/AccessTag" } } }, + }, + AccessTag: { + type: "object", + properties: { player: { $ref: "#/components/schemas/Player" } }, + }, + }, + }, + }); + const options = resolveConfig({ + fileConfig: { + input: "fixture", + output: "output", + excludeRedundantZodSchemas: false, + modelsInCommon: false, + tsNamespaces: false, + }, + params: {}, + }); + const actual = getNativeBindings().compileData(source, false, JSON.stringify({ ...options, nativeCompact: true })) + .data as { renderedModels: Record }; + const models = Object.values(actual.renderedModels).join("\n"); + + expect(models).toContain("export type Player = { access_tags?: Array };"); + expect(models).toContain("export type AccessTag = { player?: Player };"); + expect(models).toContain("export const PlayerSchema: z.ZodObject & z.ZodType ="); + expect(models).toContain("export const AccessTagSchema: z.ZodObject & z.ZodType ="); + expect(models).not.toContain("z.ZodType = z.compile("); + expect(models).not.toContain("z.ZodType = z.compile("); + }); + + test("uses the module tag for module-local model namespaces", () => { + const source = JSON.stringify({ + openapi: "3.0.3", + info: { title: "Module models", version: "1.0.0" }, + paths: { + "/messages": { + get: { + tags: ["Chat"], + operationId: "ChatController_list", + responses: { + 200: { + description: "ok", + content: { "application/json": { schema: { $ref: "#/components/schemas/Message" } } }, + }, + }, + }, + }, + }, + components: { schemas: { Message: { type: "object", properties: { id: { type: "string" } } } } }, + }); + const options = resolveConfig({ + fileConfig: { + input: "fixture", + output: "output", + modelsInCommon: false, + modelsInModules: true, + tsNamespaces: true, + }, + params: {}, + }); + const actual = getNativeBindings().compileData(source, false, JSON.stringify(options)).data as { + renderedModels: Record; + }; + + expect(actual.renderedModels.Chat).toContain("export namespace ChatModels {"); + expect(actual.renderedModels.Chat).toContain("export const MessageSchema = z.compile(z.object("); + expect(actual.renderedModels.Chat).not.toContain("export namespace CommonModels {"); + }); + + benchmarkTest("matches TypeScript operation names and component ownership", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ fileConfig: { input: "fixture", output: "output" }, params: {} }); + const resolver = new SchemaResolver(document, options); + const nativeResult = getNativeBindings().analyzeGeneration(source, false, JSON.stringify(options)); + const nativeNames = JSON.parse(nativeResult.operationNamesJson) as string[]; + const nativeTags = JSON.parse(nativeResult.schemaTagsJson) as Record; + + expect(nativeNames).toEqual(resolver.getOperationContexts().map(({ operationName }) => operationName)); + for (const name of Object.keys(document.components.schemas)) { + const tags = nativeTags[`#/components/schemas/${name}`] ?? []; + const nativeTag = tags.length === 1 ? tags[0] : options.defaultTag; + expect(nativeTag, name).toBe(resolver.getTagByZodSchemaName(`${name}${options.schemaSuffix}`)); + } + }); + + benchmarkTest.each([true, false])( + "matches all TypeScript component schemas with extractEnums=%s", + async (extractEnums) => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { input: "fixture", output: "output", excludeRedundantZodSchemas: false, extractEnums }, + params: {}, + }); + const schemas = getZodSchemasFromOpenAPIDoc(new SchemaResolver(document, options)); + const expected = { ...schemas.zodSchemas, ...schemas.enumZodSchemas }; + const result = getNativeBindings().compileSchemas(source, false, JSON.stringify(options)); + + expect(JSON.parse(result.schemasJson)).toEqual(expected); + }, + ); + + benchmarkTest("matches the complete TypeScript endpoint and schema data", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ fileConfig: { input: "fixture", output: "output" }, params: {} }); + const expected = getDataFromOpenAPIDoc(document, options); + const expectedEndpoints = [...expected.data.values()].flatMap(({ endpoints }) => endpoints); + const expectedSchemas = Object.assign({}, ...[...expected.data.values()].map(({ zodSchemas }) => zodSchemas)); + const result = getNativeBindings().compileData(source, false, JSON.stringify(options)); + const actual = result.data as { + endpoints: unknown[]; + schemas: Record; + }; + + expect(actual.endpoints).toEqual(expectedEndpoints); + expect(actual.schemas).toEqual(expectedSchemas); + }); + + benchmarkTest("matches TypeScript models-in-common proxy rendering", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { input: "fixture", output: "output", modelsInCommon: true }, + params: {}, + }); + const expected = getDataFromOpenAPIDoc(document, options); + const result = getNativeBindings().compileData(source, false, JSON.stringify(options)); + const actual = result.data as { + renderedModels: Record; + }; + + for (const tag of expected.data.keys()) { + const expectedContent = generateModels({ resolver: expected.resolver, data: expected.data, tag }); + expect(actual.renderedModels[tag], tag).toBe(expectedContent); + } + }); + + benchmarkTest("matches TypeScript models-in-common endpoint rendering", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { + input: "fixture", + output: "output", + modelsInCommon: true, + restClientImportPath: "@test/app-rest-client", + zodImportPath: "@test/zod-extended", + }, + params: {}, + }); + const expected = getDataFromOpenAPIDoc(document, options); + const result = getNativeBindings().compileData(source, false, JSON.stringify(options)); + const actual = result.data as { + renderedEndpoints: Record; + }; + + for (const tag of expected.data.keys()) { + expect(actual.renderedEndpoints[tag], tag).toBe( + generateEndpoints({ resolver: expected.resolver, data: expected.data, tag }), + ); + } + expect(Object.values(actual.renderedEndpoints).join("\n")).toContain('from "@test/zod-extended"'); + }); + + benchmarkTest.each([false, true, { exclude: ["Currencies/updateCurrency"] }])( + "matches TypeScript query module rendering with mutationScope=%s", + async (mutationScope) => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { + input: "fixture", + output: "output", + modelsInCommon: true, + restClientImportPath: "@test/app-rest-client", + zodImportPath: "@test/zod-extended", + mutationEffectsImportPath: "@test/mutation-effects", + aclCheckImportPath: "@test/acl-check", + workspaceContext: [], + mutationScope, + }, + params: {}, + }); + const expected = getDataFromOpenAPIDoc(document, options); + const result = getNativeBindings().compileData(source, false, JSON.stringify(options)); + const actual = result.data as { + renderedQueries: Record; + }; + + for (const [tag, content] of Object.entries(actual.renderedQueries)) { + expect(content, tag).toBe(generateQueries({ resolver: expected.resolver, data: expected.data, tag })); + } + expect(Object.keys(actual.renderedQueries).length).toBeGreaterThan(0); + const renderedQueries = Object.values(actual.renderedQueries).join("\n"); + expect(renderedQueries).toContain('from "@test/mutation-effects"'); + expect(renderedQueries).toContain('from "@test/acl-check"'); + expect(renderedQueries).toContain( + "return throwOnError ? queryClient.fetchQuery(queryOptions) : queryClient.prefetchQuery(queryOptions)", + ); + expect(renderedQueries).toContain("uploadInstructions.method?.toLowerCase()"); + expect(renderedQueries).not.toContain("data?.method?.toLowerCase()"); + }, + ); + + benchmarkTest("matches TypeScript ACL module rendering", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { input: "fixture", output: "output", modelsInCommon: true, workspaceContext: [] }, + params: {}, + }); + const expected = getDataFromOpenAPIDoc(document, options); + const result = getNativeBindings().compileData(source, false, JSON.stringify(options)); + const actual = result.data as { + renderedAcl: Record; + }; + for (const [tag, content] of Object.entries(actual.renderedAcl)) { + expect(content, tag).toBe(generateAcl({ resolver: expected.resolver, data: expected.data, tag })); + } + expect(Object.keys(actual.renderedAcl).length).toBeGreaterThan(0); + }); + + benchmarkTest("matches TypeScript shared-file rendering", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { input: "fixture", output: "output", modelsInCommon: true, workspaceContext: [] }, + params: {}, + }); + const expected = getDataFromOpenAPIDoc(document, options); + const result = getNativeBindings().compileData(source, false, JSON.stringify(options)); + const actual = result.data as { + renderedShared: Record; + }; + + expect(actual.renderedShared.queryModules).toBe( + generateQueryModules({ resolver: expected.resolver, data: expected.data }), + ); + expect(actual.renderedShared.domainErrors).toBe( + generateDomainErrors({ resolver: expected.resolver, data: expected.data }), + ); + expect(actual.renderedShared.appAcl).toBe(generateAppAcl({ resolver: expected.resolver, data: expected.data })); + }); + + benchmarkTest("fully renders namespace-free module-local output with TypeScript parity", async () => { + const source = await fs.readFile(BENCHMARK_FIXTURE, "utf8"); + const document = JSON.parse(source); + const options = resolveConfig({ + fileConfig: { + input: "fixture", + output: "output", + modelsInCommon: false, + tsNamespaces: false, + restClientImportPath: "@test/app-rest-client", + zodImportPath: "@test/zod-extended", + mutationEffectsImportPath: "@test/mutation-effects", + aclCheckImportPath: "@test/acl-check", + workspaceContext: [], + }, + params: {}, + }); + const expected = getDataFromOpenAPIDoc(document, options); + const actual = getNativeBindings().compileData(source, false, JSON.stringify({ ...options, nativeCompact: true })) + .data as { + renderedComplete: boolean; + renderedModels: Record; + renderedEndpoints: Record; + renderedQueries: Record; + renderedAcl: Record; + renderedShared: Record; + }; + + expect(actual.renderedComplete).toBe(true); + for (const tag of expected.data.keys()) { + expect(actual.renderedModels[tag], `${tag} models`).toBe( + generateModels({ resolver: expected.resolver, data: expected.data, tag }), + ); + expect(actual.renderedEndpoints[tag], `${tag} endpoints`).toBe( + generateEndpoints({ resolver: expected.resolver, data: expected.data, tag }), + ); + expect(actual.renderedQueries[tag], `${tag} queries`).toBe( + generateQueries({ resolver: expected.resolver, data: expected.data, tag }), + ); + expect(actual.renderedAcl[tag], `${tag} ACL`).toBe( + generateAcl({ resolver: expected.resolver, data: expected.data, tag }), + ); + } + expect(actual.renderedShared.queryModules).toBe( + generateQueryModules({ resolver: expected.resolver, data: expected.data }), + ); + expect(actual.renderedShared.domainErrors).toBe( + generateDomainErrors({ resolver: expected.resolver, data: expected.data }), + ); + expect(actual.renderedShared.appAcl).toBe(generateAppAcl({ resolver: expected.resolver, data: expected.data })); + }); +}); diff --git a/src/native/native-bindings.ts b/src/native/native-bindings.ts new file mode 100644 index 00000000..3919d258 --- /dev/null +++ b/src/native/native-bindings.ts @@ -0,0 +1,103 @@ +import { createRequire } from "node:module"; +import { existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +type NativeBindings = { + nativeVersion(): string; + analyzeOpenapi( + source: string, + yaml: boolean, + ): { + statsJson: string; + elapsedMicros: number; + }; + analyzeGeneration( + source: string, + yaml: boolean, + optionsJson: string, + ): { + statsJson: string; + operationNamesJson: string; + schemaTagsJson: string; + elapsedMicros: number; + }; + compileSchemas( + source: string, + yaml: boolean, + optionsJson: string, + ): { + schemasJson: string; + elapsedMicros: number; + }; + compileEndpoints( + source: string, + yaml: boolean, + optionsJson: string, + ): { + endpointsJson: string; + elapsedMicros: number; + }; + compileData( + source: string, + yaml: boolean, + optionsJson: string, + ): { + data: unknown; + elapsedMicros: number; + }; + parseOpenapi( + source: string, + yaml: boolean, + ): { + documentJson: string; + parseMicros: number; + }; +}; + +let bindings: NativeBindings | undefined; +let nativePath: string | undefined; + +function getNativePath() { + if (nativePath) return nativePath; + const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + const binaryName = `openapi-codegen-native-${process.platform}-${process.arch}.node`; + const candidates = [path.join(moduleDir, binaryName), path.resolve(moduleDir, "../../dist", binaryName)]; + nativePath = candidates.find(existsSync); + if (!nativePath) throw new Error("Native OpenAPI codegen module is not built. Run `bun run build:native`."); + return nativePath; +} + +export function hasNativeBindings() { + try { + getNativeBindings(); + return true; + } catch { + return false; + } +} + +export function shouldUseNativeCodegen() { + if (process.env.OPENAPI_CODEGEN_NATIVE === "0") return false; + if (process.env.OPENAPI_CODEGEN_NATIVE === "1") return true; + return hasNativeBindings(); +} + +export function getNativeBindings(): NativeBindings { + if (!bindings) { + const require = createRequire(import.meta.url); + const resolvedNativePath = getNativePath(); + if (typeof process.dlopen === "function") { + const nativeModule = { exports: {} as NativeBindings }; + process.dlopen(nativeModule as unknown as NodeModule, resolvedNativePath); + bindings = nativeModule.exports; + } else { + bindings = require(resolvedNativePath) as NativeBindings; + } + } + return bindings; +} + +export function compileNativeData(source: string, yaml: boolean, optionsJson: string) { + return getNativeBindings().compileData(source, yaml, optionsJson); +} diff --git a/src/plugins/openapi-codegen.runner.ts b/src/plugins/openapi-codegen.runner.ts index a9f4f239..1cea67d8 100644 --- a/src/plugins/openapi-codegen.runner.ts +++ b/src/plugins/openapi-codegen.runner.ts @@ -30,6 +30,9 @@ export function createOpenApiCodegenRunner( formatGeneratedFile, profiler, }); + for (const line of profiler.formatLines()) { + console.info(`[openapi-codegen] ${line}`); + } options.onGenerated?.(); }); diff --git a/src/query.ts b/src/query.ts new file mode 100644 index 00000000..aa7d11ea --- /dev/null +++ b/src/query.ts @@ -0,0 +1,5 @@ +export type { AppQueryOptions, AppMutationOptions, AppInfiniteQueryOptions } from "./lib/react-query.types"; +export { useMutationEffects } from "./lib/react-query/useMutationEffects"; +export type { MutationEffectsOptions } from "./lib/react-query/useMutationEffects"; +export { OpenApiQueryConfig } from "./lib/config/queryConfig.context"; +export type { InvalidationMap, InvalidationMapFunc, QueryModule } from "./lib/config/queryConfig.context"; diff --git a/src/rest.ts b/src/rest.ts new file mode 100644 index 00000000..4b924a1d --- /dev/null +++ b/src/rest.ts @@ -0,0 +1,13 @@ +export type { + RestRequestInfo, + GeneralTransportErrorCode, + RestResponseType, + RestTransport, + RestTransportInterceptor, + TransportRequest, + TransportRequestConfig, + TransportResponse, + TransportResult, + UploadProgress, +} from "./lib/rest/rest-transport.types"; +export { HttpError } from "./lib/rest/rest-transport.types"; diff --git a/src/sh.ts b/src/sh.ts index be170985..e238b724 100644 --- a/src/sh.ts +++ b/src/sh.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env bun import yargs from "yargs"; import { hideBin } from "yargs/helpers"; diff --git a/src/tiny/openapi.ts b/src/tiny/openapi.ts index 3f7d5d62..c825e506 100644 --- a/src/tiny/openapi.ts +++ b/src/tiny/openapi.ts @@ -1147,18 +1147,26 @@ export function toOpenAPI30Document(spec: JsonObject): JsonObject { } export async function generateORPCOpenAPISpec(options: GenerateORPCOpenAPISpecOptions): Promise { - const [{ OpenAPIGenerator }, { ZodToJsonSchemaConverter }] = await Promise.all([ + const profile = process.env.TINY_OPENAPI_PROFILE === "1"; + const started = performance.now(); + const runtimePromise = Promise.all([ importRuntimeModule("@orpc/openapi"), importRuntimeModule("@orpc/zod/zod4"), ]); + const modelSchemasPromise = collectModelSchemaExports({ + apiRoot: options.apiRoot, + dbTablesRoot: options.dbTablesRoot, + }); + const [[{ OpenAPIGenerator }, { ZodToJsonSchemaConverter }], modelSchemas] = await Promise.all([ + runtimePromise, + modelSchemasPromise, + ]); const generator = new OpenAPIGenerator({ schemaConverters: [new ZodToJsonSchemaConverter()], }); + const afterRuntime = performance.now(); const explicitSchemaName = options.getOpenApiSchemaName ?? getOpenApiSchemaName; - const modelSchemas = await collectModelSchemaExports({ - apiRoot: options.apiRoot, - dbTablesRoot: options.dbTablesRoot, - }); + const afterModels = performance.now(); const getSchemaName = createModelSchemaNameGetter(modelSchemas, explicitSchemaName); const operationMeta = collectOperationMeta(options.contract); const extraSchemas = collectExtraSchemas(options.apiModules); @@ -1174,6 +1182,7 @@ export async function generateORPCOpenAPISpec(options: GenerateORPCOpenAPISpecOp ...extraSchemas, ...reachableModelSchemas, }; + const afterCollection = performance.now(); const spec = await generator.generate(options.contract, { info: options.info ?? { @@ -1185,6 +1194,7 @@ export async function generateORPCOpenAPISpec(options: GenerateORPCOpenAPISpecOp ...(Object.keys(commonSchemas).length > 0 ? { commonSchemas } : {}), }); const jsonSpec = spec as JsonObject; + const afterGeneration = performance.now(); const sortableSchemaNames = options.realBackendSortableSchemaNames instanceof Map ? options.realBackendSortableSchemaNames @@ -1218,5 +1228,12 @@ export async function generateORPCOpenAPISpec(options: GenerateORPCOpenAPISpecOp applyRobodevModuleExtensions(jsonSpec, robodevModuleExtensions, options.apiModules); applyRobodevUserRolesExtension(jsonSpec, userRoles); - return toOpenAPI30Document(jsonSpec); + const output = toOpenAPI30Document(jsonSpec); + if (profile) { + const finished = performance.now(); + console.error( + `tiny openapi profile runtime=${(afterRuntime - started).toFixed(1)}ms models=${(afterModels - afterRuntime).toFixed(1)}ms collection=${(afterCollection - afterModels).toFixed(1)}ms orpc=${(afterGeneration - afterCollection).toFixed(1)}ms post=${(finished - afterGeneration).toFixed(1)}ms total=${(finished - started).toFixed(1)}ms`, + ); + } + return output; } diff --git a/tsconfig.json b/tsconfig.json index bce8d0c1..09ec322c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "outDir": "dist", "strict": true, "lib": ["esnext", "dom"], - "moduleResolution": "node", + "types": ["bun", "node"], + "moduleResolution": "bundler", "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "allowJs": true, diff --git a/tsdown.config.ts b/tsdown.config.ts index 739259f7..9a55c439 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -17,6 +17,13 @@ export default defineConfig([ "./src/tiny.ts", "./src/acl.ts", "./src/zod.ts", + "./src/native.ts", + "./src/rest.ts", + "./src/axios.ts", + "./src/errors.ts", + "./src/query.ts", + "./src/config.ts", + "./src/auth.ts", ], format: "esm", platform: "node",