diff --git a/.github/workflows/async-api-validation.yml b/.github/workflows/async-api-validation.yml index 33c31dec..ead79c9a 100644 --- a/.github/workflows/async-api-validation.yml +++ b/.github/workflows/async-api-validation.yml @@ -2,9 +2,9 @@ name: AsyncAPI Validation on: push: - branches: [ main, make-v4 ] + branches: [ main, make-v6 ] pull_request: - branches: [ main, make-v4 ] + branches: [ main, make-v6 ] jobs: validate: diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 293c10c3..381f5034 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,16 +5,16 @@ name: Node.js CI on: push: - branches: [ main, make-v5 ] + branches: [ main, make-v6 ] pull_request: - branches: [ main, make-v5 ] + branches: [ main, make-v6 ] jobs: build: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.19.0, 20.x, 22.12.0, 22.x, 24.0.0, 24.x] + node-version: [22.19.0, 22.x, 24.0.0, 24.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - name: Checkout diff --git a/CHANGELOG.md b/CHANGELOG.md index 947efa1c..e4ee2e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Version 6 + +### v6.0.0 + +- Supported Node.js versions: `^22.19.0 || ^24.0.0`; +- Supported Zod version: `^4.3.4`; +- The `typescript` dependency is now optional and only required for making `Integration`: + - Either import and assign the `typescript` property to its constructor argument; + - Or use the new static async method `create()` to delegate the import; + - This change addresses the potential memory consumption issue. + ## Version 5 ### v5.2.0 diff --git a/README.md b/README.md index 6dc6ea4f..ed1ddd1d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ schemas, it can be exported to frontend side, thus ensuring that the established Install the package and its peer dependencies. ```shell -pnpm add zod-sockets zod socket.io typescript +pnpm add zod-sockets zod socket.io ``` ## Set up config @@ -577,8 +577,9 @@ In order to establish constraints for events on the client side you can generate ```typescript import { Integration } from "zod-sockets"; +import typescript from "typescript"; -const integration = new Integration({ config, actions }); +const integration = new Integration({ typescript, config, actions }); const typescriptCode = integration.print(); // write this to a file ``` diff --git a/compat-test/package.json b/compat-test/package.json index 131009f1..44fa9855 100644 --- a/compat-test/package.json +++ b/compat-test/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "express": "^5.1.0", - "express-zod-api": "^27.2.0", + "express-zod-api": "^28.0.0-beta.3", "http-errors": "^2.0.0", "socket.io": "catalog:dev", "typescript": "catalog:dev", diff --git a/example/generate-client.ts b/example/generate-client.ts index 034a7083..3cc6ee7a 100644 --- a/example/generate-client.ts +++ b/example/generate-client.ts @@ -2,9 +2,10 @@ import { writeFile } from "node:fs/promises"; import { Integration } from "zod-sockets"; import { actions } from "./actions"; import { config } from "./config"; +import typescript from "typescript"; await writeFile( "example-client.ts", - new Integration({ config, actions }).print(), + new Integration({ typescript, config, actions }).print(), "utf-8", ); diff --git a/package.json b/package.json index 5b114d34..7a2cc37c 100644 --- a/package.json +++ b/package.json @@ -13,19 +13,21 @@ }, "devDependencies": { "@arethetypeswrong/core": "^0.18.1", - "@tsconfig/node20": "^20.1.5", + "@eslint/js": "^10.0.1", + "@tsconfig/node22": "^22.0.5", "@types/node": "^25.0.2", "@vitest/coverage-v8": "^4.1.2", - "eslint": "^9.39.4", + "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-allowed-dependencies": "^2.1.0", "eslint-plugin-prettier": "^5.5.5", + "globals": "^17.4.0", "husky": "^9.0.10", "prettier": "3.8.3", "tsdown": "^0.21.4", - "unrun": "^0.2.32", "typescript": "catalog:dev", - "typescript-eslint": "~8.55.0", + "typescript-eslint": "^8.58.0", + "unrun": "^0.2.32", "vitest": "^4.1.2" }, "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index deb2caa6..67f00050 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,11 +16,11 @@ catalogs: specifier: ^6.0.2 version: 6.0.3 zod: - specifier: ^4.1.0 + specifier: ^4.3.4 version: 4.3.6 prod: ansis: - specifier: ^4.0.0 + specifier: ^4.2.0 version: 4.2.0 ramda: specifier: ^0.32.0 @@ -40,9 +40,12 @@ importers: '@arethetypeswrong/core': specifier: ^0.18.1 version: 0.18.2 - '@tsconfig/node20': - specifier: ^20.1.5 - version: 20.1.9 + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.0.3) + '@tsconfig/node22': + specifier: ^22.0.5 + version: 22.0.5 '@types/node': specifier: ^25.0.2 version: 25.6.0 @@ -50,17 +53,20 @@ importers: specifier: ^4.1.2 version: 4.1.5(vitest@4.1.5) eslint: - specifier: ^9.39.4 - version: 9.39.4 + specifier: ^10.0.3 + version: 10.0.3 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.4) + version: 10.1.8(eslint@10.0.3) eslint-plugin-allowed-dependencies: specifier: ^2.1.0 - version: 2.1.0(eslint@9.39.4)(typescript-eslint@8.55.0(eslint@9.39.4)(typescript@6.0.3)) + version: 2.1.0(eslint@10.0.3)(typescript-eslint@8.58.0(eslint@10.0.3)(typescript@6.0.3)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.3))(eslint@10.0.3)(prettier@3.8.3) + globals: + specifier: ^17.4.0 + version: 17.4.0 husky: specifier: ^9.0.10 version: 9.1.7 @@ -74,8 +80,8 @@ importers: specifier: catalog:dev version: 6.0.3 typescript-eslint: - specifier: ~8.55.0 - version: 8.55.0(eslint@9.39.4)(typescript@6.0.3) + specifier: ^8.58.0 + version: 8.58.0(eslint@10.0.3)(typescript@6.0.3) unrun: specifier: ^0.2.32 version: 0.2.36(synckit@0.11.12) @@ -104,8 +110,8 @@ importers: specifier: ^5.1.0 version: 5.2.1 express-zod-api: - specifier: ^27.2.0 - version: 27.2.2(@types/node@25.6.0)(express@5.2.1)(http-errors@2.0.1)(typescript@6.0.3)(zod@4.3.6) + specifier: ^28.0.0-beta.3 + version: 28.0.0-beta.3(@types/node@25.6.0)(express@5.2.1)(http-errors@2.0.1)(typescript@6.0.3)(zod@4.3.6) http-errors: specifier: ^2.0.0 version: 2.0.1 @@ -271,39 +277,34 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.3': + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.3': + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.1.1': + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.3': + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@express-zod-api/zod-plugin@4.1.0': - resolution: {integrity: sha512-nHUlO2Hd71lDvi2LN9OBQ81VwXmSpOaMtZIcqeVyuMidrdu2rrURz+4nlD5b7p8dzNkzJZ7EogHgREUwAPXPPw==} - engines: {node: ^20.19.0 || ^22.12.0 || ^24.0.0} - peerDependencies: - zod: ^4.3.4 + '@eslint/plugin-kit@0.6.1': + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -457,8 +458,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tsconfig/node20@20.1.9': - resolution: {integrity: sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==} + '@tsconfig/node22@22.0.5': + resolution: {integrity: sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==} '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -472,6 +473,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -490,63 +494,63 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.55.0': - resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} + '@typescript-eslint/eslint-plugin@8.58.0': + resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.55.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser': ^8.58.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.55.0': - resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} + '@typescript-eslint/parser@8.58.0': + resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.55.0': - resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} + '@typescript-eslint/project-service@8.58.0': + resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.55.0': - resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} + '@typescript-eslint/scope-manager@8.58.0': + resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.55.0': - resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} + '@typescript-eslint/tsconfig-utils@8.58.0': + resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.55.0': - resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} + '@typescript-eslint/type-utils@8.58.0': + resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.55.0': - resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} + '@typescript-eslint/types@8.58.0': + resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.55.0': - resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} + '@typescript-eslint/typescript-estree@8.58.0': + resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.55.0': - resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} + '@typescript-eslint/utils@8.58.0': + resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.55.0': - resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} + '@typescript-eslint/visitor-keys@8.58.0': + resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@4.1.5': @@ -608,17 +612,10 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} - 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'} @@ -630,8 +627,9 @@ packages: ast-v8-to-istanbul@1.0.0: resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} base64id@2.0.0: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} @@ -644,11 +642,9 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} - - brace-expansion@2.0.3: - resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -666,31 +662,13 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -832,21 +810,21 @@ packages: eslint-config-prettier: optional: true - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.0.3: + resolution: {integrity: sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -854,9 +832,9 @@ packages: jiti: optional: true - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} @@ -885,10 +863,11 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - express-zod-api@27.2.2: - resolution: {integrity: sha512-92CcamxNzTguCtzqZzu41LWSQtT0hyPpmQ9JRI/6WEjxct9KlKfiL9Ub5M9akFGCeSHDcxO7r41MdEERerrbAw==} - engines: {node: ^20.19.0 || ^22.12.0 || ^24.0.0} + express-zod-api@28.0.0-beta.3: + resolution: {integrity: sha512-rRtPTVPutLBSmkpqARPNOuX8w+J6byolmQb95Pc4ugrXL97En3ICi3ZgNXUoZQOb29UawZv7IPifsEZgmrSK4Q==} + engines: {node: ^22.19.0 || ^24.0.0} peerDependencies: + '@express-zod-api/zod-plugin': ^5.0.0-beta.3 '@types/compression': ^1.7.5 '@types/express': ^5.0.0 '@types/express-fileupload': ^1.5.0 @@ -900,6 +879,8 @@ packages: typescript: ^5.1.3 || ^6.0.2 zod: ^4.3.4 peerDependenciesMeta: + '@express-zod-api/zod-plugin': + optional: true '@types/compression': optional: true '@types/express': @@ -997,8 +978,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} gopd@1.2.0: @@ -1044,10 +1025,6 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - import-without-cache@0.3.3: resolution: {integrity: sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==} engines: {node: '>=20.19.0'} @@ -1092,10 +1069,6 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - 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'} @@ -1121,9 +1094,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lru-cache@11.2.7: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} @@ -1182,12 +1152,9 @@ packages: engines: {node: '>=4'} hasBin: true - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1253,10 +1220,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1325,10 +1288,6 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - 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==} @@ -1438,10 +1397,6 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1473,8 +1428,8 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -1528,12 +1483,12 @@ packages: types-ramda@0.31.0: resolution: {integrity: sha512-vaoC35CRC3xvL8Z6HkshDbi6KWM1ezK0LHN0YyxXWUn9HKzBNg/T3xSGlJZjCYspnOD3jE7bcizsp0bUXZDxnQ==} - typescript-eslint@8.55.0: - resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} + typescript-eslint@8.58.0: + resolution: {integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' typescript@5.6.1-rc: resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} @@ -1774,57 +1729,40 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.3)': dependencies: - eslint: 9.39.4 + eslint: 10.0.3 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.23.3': dependencies: - '@eslint/object-schema': 2.1.7 + '@eslint/object-schema': 3.0.3 debug: 4.4.3 - minimatch: 3.1.5 + minimatch: 10.2.4 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + '@eslint/config-helpers@0.5.3': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.1.1 - '@eslint/core@0.17.0': + '@eslint/core@1.1.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': - dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.4': {} + '@eslint/js@10.0.1(eslint@10.0.3)': + optionalDependencies: + eslint: 10.0.3 - '@eslint/object-schema@2.1.7': {} + '@eslint/object-schema@3.0.3': {} - '@eslint/plugin-kit@0.4.1': + '@eslint/plugin-kit@0.6.1': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.1.1 levn: 0.4.1 - '@express-zod-api/zod-plugin@4.1.0(zod@4.3.6)': - dependencies: - ramda: 0.32.0 - zod: 4.3.6 - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -1924,7 +1862,7 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@tsconfig/node20@20.1.9': {} + '@tsconfig/node22@22.0.5': {} '@tybys/wasm-util@0.10.1': dependencies: @@ -1942,6 +1880,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} '@types/jsesc@2.5.1': {} @@ -1960,96 +1900,96 @@ snapshots: dependencies: '@types/node': 25.6.0 - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.0.3)(typescript@6.0.3))(eslint@10.0.3)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.55.0 - eslint: 9.39.4 + '@typescript-eslint/parser': 8.58.0(eslint@10.0.3)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/type-utils': 8.58.0(eslint@10.0.3)(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.0(eslint@10.0.3)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.58.0 + eslint: 10.0.3 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@6.0.3) + ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/parser@8.58.0(eslint@10.0.3)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - eslint: 9.39.4 + eslint: 10.0.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.55.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.58.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@6.0.3) - '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.3) + '@typescript-eslint/types': 8.58.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.55.0': + '@typescript-eslint/scope-manager@8.58.0': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 - '@typescript-eslint/tsconfig-utils@8.55.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.58.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.58.0(eslint@10.0.3)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.0(eslint@10.0.3)(typescript@6.0.3) debug: 4.4.3 - eslint: 9.39.4 - ts-api-utils: 2.4.0(typescript@6.0.3) + eslint: 10.0.3 + ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.55.0': {} + '@typescript-eslint/types@8.58.0': {} - '@typescript-eslint/typescript-estree@8.55.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.55.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@6.0.3) - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/project-service': 8.58.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.3) + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - minimatch: 9.0.9 + minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.16 - ts-api-utils: 2.4.0(typescript@6.0.3) + ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/utils@8.58.0(eslint@10.0.3)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - eslint: 9.39.4 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.3) + eslint: 10.0.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.55.0': + '@typescript-eslint/visitor-keys@8.58.0': dependencies: - '@typescript-eslint/types': 8.55.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.58.0 + eslint-visitor-keys: 5.0.1 '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': dependencies: @@ -2129,14 +2069,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - ansis@4.2.0: {} - argparse@2.0.1: {} - assertion-error@2.0.1: {} ast-kit@3.0.0-beta.1: @@ -2151,7 +2085,7 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} base64id@2.0.0: {} @@ -2171,14 +2105,9 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.13: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.3: + brace-expansion@5.0.4: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 bytes@3.1.2: {} @@ -2194,25 +2123,10 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - callsites@3.1.0: {} - chai@6.2.2: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - cjs-module-lexer@1.4.3: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - concat-map@0.0.1: {} - content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -2311,56 +2225,55 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.4): + eslint-config-prettier@10.1.8(eslint@10.0.3): dependencies: - eslint: 9.39.4 + eslint: 10.0.3 - eslint-plugin-allowed-dependencies@2.1.0(eslint@9.39.4)(typescript-eslint@8.55.0(eslint@9.39.4)(typescript@6.0.3)): + eslint-plugin-allowed-dependencies@2.1.0(eslint@10.0.3)(typescript-eslint@8.58.0(eslint@10.0.3)(typescript@6.0.3)): dependencies: - eslint: 9.39.4 + eslint: 10.0.3 ramda: 0.32.0 - typescript-eslint: 8.55.0(eslint@9.39.4)(typescript@6.0.3) + typescript-eslint: 8.58.0(eslint@10.0.3)(typescript@6.0.3) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.3))(eslint@10.0.3)(prettier@3.8.3): dependencies: - eslint: 9.39.4 + eslint: 10.0.3 prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.39.4) + eslint-config-prettier: 10.1.8(eslint@10.0.3) - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} - eslint@9.39.4: + eslint@10.0.3: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 + '@eslint/config-array': 0.23.3 + '@eslint/config-helpers': 0.5.3 + '@eslint/core': 1.1.1 + '@eslint/plugin-kit': 0.6.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 ajv: 6.14.0 - chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2371,18 +2284,17 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.4 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: - supports-color - espree@10.4.0: + espree@11.2.0: dependencies: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 + eslint-visitor-keys: 5.0.1 esquery@1.7.0: dependencies: @@ -2404,9 +2316,8 @@ snapshots: expect-type@1.3.0: {} - express-zod-api@27.2.2(@types/node@25.6.0)(express@5.2.1)(http-errors@2.0.1)(typescript@6.0.3)(zod@4.3.6): + express-zod-api@28.0.0-beta.3(@types/node@25.6.0)(express@5.2.1)(http-errors@2.0.1)(typescript@6.0.3)(zod@4.3.6): dependencies: - '@express-zod-api/zod-plugin': 4.1.0(zod@4.3.6) ansis: 4.2.0 express: 5.2.1 http-errors: 2.0.1 @@ -2530,7 +2441,7 @@ snapshots: dependencies: is-glob: 4.0.3 - globals@14.0.0: {} + globals@17.4.0: {} gopd@1.2.0: {} @@ -2564,11 +2475,6 @@ snapshots: ignore@7.0.5: {} - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - import-without-cache@0.3.3: {} imurmurhash@0.1.4: {} @@ -2602,10 +2508,6 @@ snapshots: js-tokens@10.0.0: {} - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -2627,8 +2529,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.merge@4.6.2: {} - lru-cache@11.2.7: {} magic-string@0.30.21: @@ -2671,13 +2571,9 @@ snapshots: mime@1.6.0: {} - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.13 - - minimatch@9.0.9: + minimatch@10.2.4: dependencies: - brace-expansion: 2.0.3 + brace-expansion: 5.0.4 ms@2.1.3: {} @@ -2739,10 +2635,6 @@ snapshots: dependencies: p-limit: 3.1.0 - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -2795,8 +2687,6 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.16)(typescript@6.0.3): @@ -2966,8 +2856,6 @@ snapshots: std-env@4.1.0: {} - strip-json-comments@3.1.1: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -2991,7 +2879,7 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.4.0(typescript@6.0.3): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -3047,13 +2935,13 @@ snapshots: dependencies: ts-toolbelt: 9.6.0 - typescript-eslint@8.55.0(eslint@9.39.4)(typescript@6.0.3): + typescript-eslint@8.58.0(eslint@10.0.3)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/parser': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - eslint: 9.39.4 + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.0.3)(typescript@6.0.3))(eslint@10.0.3)(typescript@6.0.3) + '@typescript-eslint/parser': 8.58.0(eslint@10.0.3)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.0(eslint@10.0.3)(typescript@6.0.3) + eslint: 10.0.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f8f40210..c1eb12f0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,22 +10,20 @@ engineStrict: true autoInstallPeers: false publicHoistPattern: - "@vitest/*" - - "@eslint/*" - - "globals" catalogs: prod: - "ansis": "^4.0.0" + "ansis": "^4.2.0" "ramda": "^0.32.0" "yaml": "^2.8.3" peer: "socket.io": "^4.7.4" "typescript": "^5.3.3 || ^6.0.2" - "zod": "^4.1.0" + "zod": "^4.3.4" dev: "socket.io": "^4.7.4" "socket.io-client": "^4.7.4" "typescript": "^6.0.2" - "zod": "^4.1.0" + "zod": "^4.3.4" overrides: "@scarf/scarf": "npm:empty-npm-package@1.0.0" "lightningcss": "npm:empty-npm-package@1.0.0" diff --git a/tsconfig.json b/tsconfig.json index 26195e28..9abd669c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node20/tsconfig.json", + "extends": "@tsconfig/node22/tsconfig.json", "compilerOptions": { "module": "preserve", "moduleResolution": "bundler", diff --git a/zod-sockets/package.json b/zod-sockets/package.json index 94a6a442..4d1ff1e4 100644 --- a/zod-sockets/package.json +++ b/zod-sockets/package.json @@ -34,7 +34,7 @@ "*.md" ], "engines": { - "node": "^20.19.0 || ^22.12.0 || ^24.0.0" + "node": "^22.19.0 || ^24.0.0" }, "dependencies": { "ansis": "catalog:prod", @@ -46,6 +46,11 @@ "typescript": "catalog:peer", "zod": "catalog:peer" }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, "devDependencies": { "@types/ramda": "^0.31.0", "socket.io": "catalog:dev", diff --git a/zod-sockets/src/__snapshots__/zts.spec.ts.snap b/zod-sockets/src/__snapshots__/zts.spec.ts.snap index fcaeed14..080403a6 100644 --- a/zod-sockets/src/__snapshots__/zts.spec.ts.snap +++ b/zod-sockets/src/__snapshots__/zts.spec.ts.snap @@ -52,7 +52,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = ` ] | bigint)[]; }; }>; - looseRecord: Record<"one" | "two", boolean>; + looseRecord: Record<"one" | "two", boolean> & Record; map: any; set: any; intersection: (string & number) | bigint; @@ -77,6 +77,13 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = ` catch: number; pipeline: string; readonly: string; + extended: { + hex: string; + hash: string; + }; + codec: string; + slug: string; + xor: string | number; }" `; @@ -277,6 +284,7 @@ exports[`zod-to-ts > z.optional() > Zod 4: should add question mark only to opti required: string; } ] | undefined; + exact?: string; }" `; diff --git a/zod-sockets/src/integration.spec.ts b/zod-sockets/src/integration.spec.ts index 28cec54c..bc129fa2 100644 --- a/zod-sockets/src/integration.spec.ts +++ b/zod-sockets/src/integration.spec.ts @@ -1,3 +1,4 @@ +import ts from "typescript"; import { z } from "zod"; import { ActionsFactory } from "./actions-factory"; import { Config, createSimpleConfig } from "./config"; @@ -16,6 +17,7 @@ describe("Integration", () => { }); const input = z.tuple([feature.array()]); const instance = new Integration({ + typescript: ts, config: sampleConfig, actions: [ new ActionsFactory(sampleConfig).build({ @@ -28,7 +30,7 @@ describe("Integration", () => { expect(instance.print()).toMatchSnapshot(); }); - test("should handle namespaces with emission", () => { + test("should handle namespaces with emission", async () => { const configWithEmission = new Config().addNamespace({ path: "/test", emission: { @@ -38,7 +40,7 @@ describe("Integration", () => { }, }, }); - const instance = new Integration({ + const instance = await Integration.create({ config: configWithEmission, actions: [], }); diff --git a/zod-sockets/src/integration.ts b/zod-sockets/src/integration.ts index f4211ac7..3fad9c26 100644 --- a/zod-sockets/src/integration.ts +++ b/zod-sockets/src/integration.ts @@ -1,4 +1,4 @@ -import ts from "typescript"; +import type ts from "typescript"; import { z } from "zod"; import { AbstractAction } from "./action"; import { makeCleanId } from "./common-helpers"; @@ -6,15 +6,10 @@ import { Config } from "./config"; import { makeEventFnSchema } from "./integration-helpers"; import { Namespaces, normalizeNS } from "./namespace"; import { zodToTs } from "./zts"; -import { - addJsDoc, - makeType, - printNode, - f, - exportModifier, -} from "./typescript-api"; +import { TypescriptAPI } from "./typescript-api"; -interface IntegrationProps { +interface IntegrationParams { + typescript: typeof ts; config: Config; actions: AbstractAction[]; /** @@ -30,18 +25,20 @@ const fallbackNs = "root"; const registryScopes = ["emission", "actions"]; export class Integration { + /** @internal */ + protected readonly api: TypescriptAPI; #program: ts.Node[] = []; #aliases: Record< string, // namespace Map > = {}; #ids = { - path: f.createIdentifier("path"), - socket: f.createIdentifier("Socket"), - socketBase: f.createIdentifier("SocketBase"), - ioClient: f.createStringLiteral("socket.io-client"), - emission: f.createIdentifier(makeCleanId(registryScopes[0])), - actions: f.createIdentifier(makeCleanId(registryScopes[1])), + path: "path", + socket: "Socket", + socketBase: "SocketBase", + ioClient: "socket.io-client", + emission: makeCleanId(registryScopes[0]), + actions: makeCleanId(registryScopes[1]), }; protected registry: Record< string, // namespace @@ -51,48 +48,49 @@ export class Integration { > > = {}; - #makeAlias( - ns: string, - key: object, - produce: () => ts.TypeNode, - ): ts.TypeReferenceNode { + #makeAlias(ns: string, key: object, produce: () => ts.TypeNode): ts.TypeNode { let name = this.#aliases[ns].get(key)?.name?.text; if (!name) { name = `Type${this.#aliases[ns].size + 1}`; - const temp = f.createLiteralTypeNode(f.createNull()); - this.#aliases[ns].set(key, makeType(name, temp)); - this.#aliases[ns].set(key, makeType(name, produce())); + const temp = this.api.makeLiteralType(null); + this.#aliases[ns].set(key, this.api.makeType(name, temp)); + this.#aliases[ns].set(key, this.api.makeType(name, produce())); } - return f.createTypeReferenceNode(name); + return this.api.ensureTypeNode(name); } constructor({ + typescript, config: { namespaces }, actions, maxOverloads = 3, - }: IntegrationProps) { + }: IntegrationParams) { + this.api = new TypescriptAPI(typescript); this.#program.push( - f.createImportDeclaration( + this.api.f.createImportDeclaration( undefined, - f.createImportClause( - true, + this.api.f.createImportClause( + this.api.ts.SyntaxKind.TypeKeyword, undefined, - f.createNamedImports([ - f.createImportSpecifier( + this.api.f.createNamedImports([ + this.api.f.createImportSpecifier( false, - this.#ids.socket, - this.#ids.socketBase, + this.api.f.createIdentifier(this.#ids.socket), + this.api.f.createIdentifier(this.#ids.socketBase), ), ]), ), - this.#ids.ioClient, + this.api.f.createStringLiteral(this.#ids.ioClient), ), ); for (const [ns, { emission }] of Object.entries(namespaces)) { this.#aliases[ns] = new Map(); this.registry[ns] = { emission: [], actions: [] }; - const commons = { makeAlias: this.#makeAlias.bind(this, ns) }; + const commons = { + makeAlias: this.#makeAlias.bind(this, ns), + api: this.api, + }; for (const [event, { schema, ack }] of Object.entries(emission)) { const node = zodToTs(makeEventFnSchema(schema, ack, maxOverloads), { isResponse: true, @@ -115,69 +113,64 @@ export class Integration { for (const ns in this.registry) { const publicName = makeCleanId(ns) || makeCleanId(fallbackNs); - const nsNameNode = f.createVariableStatement( - exportModifier, - f.createVariableDeclarationList( - [ - f.createVariableDeclaration( - this.#ids.path, - undefined, - undefined, - f.createStringLiteral(normalizeNS(ns)), - ), - ], - ts.NodeFlags.Const, - ), + const nsNameNode = this.api.makeConst( + this.#ids.path, + this.api.f.createStringLiteral(normalizeNS(ns)), + { expose: true }, ); - addJsDoc( + this.api.addJsDoc( nsNameNode, `@desc The actual path of the ${publicName} namespace`, ); const interfaces = Object.entries(this.registry[ns]).map( ([scope, events]) => - f.createInterfaceDeclaration( - exportModifier, + this.api.makeInterface( makeCleanId(scope), - undefined, - undefined, events.map(({ event, node }) => - f.createPropertySignature(undefined, event, undefined, node), + this.api.makeInterfaceProp(event, node), ), + { expose: true }, ), ); - const socketNode = f.createTypeAliasDeclaration( - exportModifier, + const socketNode = this.api.makeType( this.#ids.socket, - undefined, - f.createTypeReferenceNode(this.#ids.socketBase, [ - f.createTypeReferenceNode(this.#ids.emission), - f.createTypeReferenceNode(this.#ids.actions), + this.api.ensureTypeNode(this.#ids.socketBase, [ + this.#ids.emission, + this.#ids.actions, ]), + { expose: true }, ); - addJsDoc( + this.api.addJsDoc( socketNode, - `@example const socket: ${publicName}.${this.#ids.socket.text} = io(${publicName}.${this.#ids.path.text})`, + `@example const socket: ${publicName}.${this.#ids.socket} = io(${publicName}.${this.#ids.path})`, ); this.#program.push( - f.createModuleDeclaration( - exportModifier, - f.createIdentifier(publicName), - f.createModuleBlock([ + this.api.f.createModuleDeclaration( + this.api.exportModifier, + this.api.f.createIdentifier(publicName), + this.api.f.createModuleBlock([ nsNameNode, ...this.#aliases[ns].values(), ...interfaces, socketNode, ]), - ts.NodeFlags.Namespace, + this.api.ts.NodeFlags.Namespace, ), ); } } + public static async create(params: Omit) { + return new Integration({ + ...params, + typescript: (await import("typescript"))["default"], + }); + } + public print(printerOptions?: ts.PrinterOptions) { return this.#program - .map((node) => printNode(node, printerOptions)) + .map((node) => this.api.printNode(node, printerOptions)) .join("\n\n"); } } diff --git a/zod-sockets/src/typescript-api.ts b/zod-sockets/src/typescript-api.ts index fc16b819..d42b5ac8 100644 --- a/zod-sockets/src/typescript-api.ts +++ b/zod-sockets/src/typescript-api.ts @@ -1,5 +1,5 @@ import * as R from "ramda"; -import ts from "typescript"; +import type ts from "typescript"; export type Typeable = | ts.TypeNode @@ -7,122 +7,215 @@ export type Typeable = | string | ts.KeywordTypeSyntaxKind; -export const f = ts.factory; - -export const exportModifier = [f.createModifier(ts.SyntaxKind.ExportKeyword)]; - -export const addJsDoc = (node: T, text: string) => - ts.addSyntheticLeadingComment( - node, - ts.SyntaxKind.MultiLineCommentTrivia, - `* ${text} `, - true, - ); - -export const printNode = ( - node: ts.Node, - printerOptions?: ts.PrinterOptions, -) => { - const sourceFile = ts.createSourceFile( - "print.ts", - "", - ts.ScriptTarget.Latest, - false, - ts.ScriptKind.TS, - ); - const printer = ts.createPrinter(printerOptions); - return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); -}; - -const safePropRegex = /^[A-Za-z_$][A-Za-z0-9_$]*$/; -export const makePropertyIdentifier = (name: string | number) => - typeof name === "string" && safePropRegex.test(name) - ? f.createIdentifier(name) - : literally(name); - -export const ensureTypeNode = ( - subject: Typeable, - args?: Typeable[], // only for string and id -): ts.TypeNode => - typeof subject === "number" - ? f.createKeywordTypeNode(subject) - : typeof subject === "string" || ts.isIdentifier(subject) - ? f.createTypeReferenceNode(subject, args && R.map(ensureTypeNode, args)) - : subject; - -/** ensures distinct union (unique primitives) */ -export const makeUnion = (entries: ts.TypeNode[]) => { - const nodes = new Map(); - for (const entry of entries) - nodes.set(isPrimitive(entry) ? entry.kind : entry, entry); - return f.createUnionTypeNode(Array.from(nodes.values())); -}; - -export const makeInterfaceProp = ( - name: string | number, - value: Typeable, - { - isOptional, - isDeprecated, - comment, - }: { isOptional?: boolean; isDeprecated?: boolean; comment?: string } = {}, -) => { - const propType = ensureTypeNode(value); - const node = f.createPropertySignature( - undefined, - makePropertyIdentifier(name), - isOptional ? f.createToken(ts.SyntaxKind.QuestionToken) : undefined, - isOptional - ? makeUnion([propType, ensureTypeNode(ts.SyntaxKind.UndefinedKeyword)]) - : propType, - ); - const jsdoc = R.reject(R.isNil, [ - isDeprecated ? "@deprecated" : undefined, - comment, - ]); - return jsdoc.length ? addJsDoc(node, jsdoc.join(" ")) : node; -}; - -export const makeType = ( - name: ts.Identifier | string, - value: ts.TypeNode, - { expose, comment }: { expose?: boolean; comment?: string } = {}, -) => { - const node = f.createTypeAliasDeclaration( - expose ? exportModifier : undefined, - name, - undefined, - value, - ); - return comment ? addJsDoc(node, comment) : node; -}; - -/* eslint-disable prettier/prettier -- shorter and works better this way than overrides */ -export const literally = (subj: T) => ( - typeof subj === "number" ? f.createNumericLiteral(subj) - : typeof subj === "bigint" ? f.createBigIntLiteral(subj.toString()) - : typeof subj === "boolean" ? subj ? f.createTrue() : f.createFalse() - : subj === null ? f.createNull() : f.createStringLiteral(subj) +type TypeParams = + | string[] + | Partial>; + +export class TypescriptAPI { + public ts: typeof ts; + public f: typeof ts.factory; + public exportModifier: ts.ModifierToken[]; + #primitives: ts.KeywordTypeSyntaxKind[]; + static #safePropRegex = /^[A-Za-z_$][A-Za-z0-9_$]*$/; + + constructor(typescript: typeof ts) { + this.ts = typescript; + this.f = this.ts.factory; + this.exportModifier = [ + this.f.createModifier(this.ts.SyntaxKind.ExportKeyword), + ]; + this.#primitives = [ + this.ts.SyntaxKind.AnyKeyword, + this.ts.SyntaxKind.BigIntKeyword, + this.ts.SyntaxKind.BooleanKeyword, + this.ts.SyntaxKind.NeverKeyword, + this.ts.SyntaxKind.NumberKeyword, + this.ts.SyntaxKind.ObjectKeyword, + this.ts.SyntaxKind.StringKeyword, + this.ts.SyntaxKind.SymbolKeyword, + this.ts.SyntaxKind.UndefinedKeyword, + this.ts.SyntaxKind.UnknownKeyword, + this.ts.SyntaxKind.VoidKeyword, + ]; + } + + public addJsDoc = (node: T, text: string) => + this.ts.addSyntheticLeadingComment( + node, + this.ts.SyntaxKind.MultiLineCommentTrivia, + `* ${text} `, + true, + ); + + public printNode = (node: ts.Node, printerOptions?: ts.PrinterOptions) => { + const sourceFile = this.ts.createSourceFile( + "print.ts", + "", + this.ts.ScriptTarget.Latest, + false, + this.ts.ScriptKind.TS, + ); + const printer = this.ts.createPrinter(printerOptions); + return printer.printNode(this.ts.EmitHint.Unspecified, node, sourceFile); + }; + + public makeId = (name: string) => this.f.createIdentifier(name); + + public makePropertyIdentifier = (name: string | number) => + typeof name === "string" && TypescriptAPI.#safePropRegex.test(name) + ? this.makeId(name) + : this.literally(name); + + public ensureTypeNode = ( + subject: Typeable, + args?: Typeable[], // only for string and id + ): ts.TypeNode => + typeof subject === "number" + ? this.f.createKeywordTypeNode(subject) + : typeof subject === "string" || this.ts.isIdentifier(subject) + ? this.f.createTypeReferenceNode( + subject, + args && R.map(this.ensureTypeNode, args), + ) + : subject; + + /** + * @internal + * ensures distinct union (unique primitives) + * */ + public makeUnion = (entries: ts.TypeNode[]) => { + const nodes = new Map< + ts.TypeNode | ts.KeywordTypeSyntaxKind, + ts.TypeNode + >(); + for (const entry of entries) + nodes.set(this.isPrimitive(entry) ? entry.kind : entry, entry); + return this.f.createUnionTypeNode(Array.from(nodes.values())); + }; + + public makeInterfaceProp = ( + name: string | number, + value: Typeable, + { + isOptional, + hasUndefined = isOptional, + isDeprecated, + comment, + }: { + isOptional?: boolean; + hasUndefined?: boolean; + isDeprecated?: boolean; + comment?: string; + } = {}, + ) => { + const propType = this.ensureTypeNode(value); + const node = this.f.createPropertySignature( + undefined, + this.makePropertyIdentifier(name), + isOptional + ? this.f.createToken(this.ts.SyntaxKind.QuestionToken) + : undefined, + hasUndefined + ? this.makeUnion([ + propType, + this.ensureTypeNode(this.ts.SyntaxKind.UndefinedKeyword), + ]) + : propType, + ); + const jsdoc = R.reject(R.isNil, [ + isDeprecated ? "@deprecated" : undefined, + comment, + ]); + return jsdoc.length ? this.addJsDoc(node, jsdoc.join(" ")) : node; + }; + + public makeConst = ( + name: string | ts.Identifier | ts.ArrayBindingPattern, + value: ts.Expression, + { type, expose }: { type?: Typeable; expose?: true } = {}, + ) => + this.f.createVariableStatement( + expose && this.exportModifier, + this.f.createVariableDeclarationList( + [ + this.f.createVariableDeclaration( + name, + undefined, + type ? this.ensureTypeNode(type) : undefined, + value, + ), + ], + this.ts.NodeFlags.Const, + ), + ); + + public makeType = ( + name: ts.Identifier | string, + value: ts.TypeNode, + { + expose, + comment, + params, + }: { expose?: boolean; comment?: string; params?: TypeParams } = {}, + ) => { + const node = this.f.createTypeAliasDeclaration( + expose ? this.exportModifier : undefined, + name, + params && this.makeTypeParams(params), + value, + ); + return comment ? this.addJsDoc(node, comment) : node; + }; + + public makeInterface = ( + name: ts.Identifier | string, + props: ts.PropertySignature[], + { expose, comment }: { expose?: boolean; comment?: string } = {}, + ) => { + const node = this.f.createInterfaceDeclaration( + expose ? this.exportModifier : undefined, + name, + undefined, + undefined, + props, + ); + return comment ? this.addJsDoc(node, comment) : node; + }; + + public makeTypeParams = ( + params: + | string[] + | Partial< + Record + >, + ) => + (Array.isArray(params) + ? params.map((name) => R.pair(name, undefined)) + : Object.entries(params) + ).map(([name, val]) => { + const { type, init } = + typeof val === "object" && "init" in val ? val : { type: val }; + return this.f.createTypeParameterDeclaration( + [], + name, + type ? this.ensureTypeNode(type) : undefined, + init ? this.ensureTypeNode(init) : undefined, + ); + }); + + /* eslint-disable prettier/prettier -- shorter and works better this way than overrides */ + public literally = (subj: T) => ( + typeof subj === "number" ? this.f.createNumericLiteral(subj) + : typeof subj === "bigint" ? this.f.createBigIntLiteral(subj.toString()) + : typeof subj === "boolean" ? subj ? this.f.createTrue() : this.f.createFalse() + : subj === null ? this.f.createNull() : this.f.createStringLiteral(subj) ) as T extends string ? ts.StringLiteral : T extends number ? ts.NumericLiteral - : T extends boolean ? ts.BooleanLiteral : ts.NullLiteral; -/* eslint-enable prettier/prettier */ - -export const makeLiteralType = (subj: Parameters[0]) => - f.createLiteralTypeNode(literally(subj)); - -const primitives: ts.KeywordTypeSyntaxKind[] = [ - ts.SyntaxKind.AnyKeyword, - ts.SyntaxKind.BigIntKeyword, - ts.SyntaxKind.BooleanKeyword, - ts.SyntaxKind.NeverKeyword, - ts.SyntaxKind.NumberKeyword, - ts.SyntaxKind.ObjectKeyword, - ts.SyntaxKind.StringKeyword, - ts.SyntaxKind.SymbolKeyword, - ts.SyntaxKind.UndefinedKeyword, - ts.SyntaxKind.UnknownKeyword, - ts.SyntaxKind.VoidKeyword, -]; - -const isPrimitive = (node: ts.TypeNode): node is ts.KeywordTypeNode => - (primitives as ts.SyntaxKind[]).includes(node.kind); + : T extends boolean ? ts.BooleanLiteral : ts.NullLiteral; + /* eslint-enable prettier/prettier */ + + public makeLiteralType = (subj: Parameters[0]) => + this.f.createLiteralTypeNode(this.literally(subj)); + + public isPrimitive = (node: ts.TypeNode): node is ts.KeywordTypeNode => + (this.#primitives as ts.SyntaxKind[]).includes(node.kind); +} diff --git a/zod-sockets/src/zts-helpers.ts b/zod-sockets/src/zts-helpers.ts index f442fbed..f5c2a4d0 100644 --- a/zod-sockets/src/zts-helpers.ts +++ b/zod-sockets/src/zts-helpers.ts @@ -1,10 +1,13 @@ -import ts from "typescript"; +import type ts from "typescript"; import { FlatObject } from "./common-helpers"; import { SchemaHandler } from "./schema-walker"; +import { TypescriptAPI } from "./typescript-api"; export interface ZTSContext extends FlatObject { isResponse: boolean; makeAlias: (key: object, produce: () => ts.TypeNode) => ts.TypeNode; + /** @internal */ + api: TypescriptAPI; } export type Producer = SchemaHandler; diff --git a/zod-sockets/src/zts.spec.ts b/zod-sockets/src/zts.spec.ts index 5b27c99a..09052b22 100644 --- a/zod-sockets/src/zts.spec.ts +++ b/zod-sockets/src/zts.spec.ts @@ -1,16 +1,18 @@ import assert from "node:assert/strict"; import ts from "typescript"; import { z } from "zod"; -import { f, printNode } from "./typescript-api"; +import { TypescriptAPI } from "./typescript-api"; import { zodToTs } from "./zts"; import { ZTSContext } from "./zts-helpers"; describe("zod-to-ts", () => { + const api = new TypescriptAPI(ts); const printNodeTest = (node: ts.Node) => - printNode(node, { newLine: ts.NewLineKind.LineFeed }); + api.printNode(node, { newLine: ts.NewLineKind.LineFeed }); const ctx: ZTSContext = { isResponse: false, - makeAlias: vi.fn(() => f.createTypeReferenceNode("SomeType")), + makeAlias: vi.fn(() => api.f.createTypeReferenceNode("SomeType")), + api, }; describe("z.array()", () => { @@ -163,11 +165,18 @@ describe("zod-to-ts", () => { catch: z.number().catch(123), pipeline: z.string().regex(/\d+/).transform(Number).pipe(z.number()), readonly: z.string().readonly(), + extended: z.object({}).extend({ hex: z.hex(), hash: z.hash("sha256") }), + codec: z.codec(z.string(), z.number(), { + encode: String, + decode: Number, + }), + slug: z.string().slugify(), + xor: z.xor([z.string(), z.number()]), }); test("should produce the expected results", () => { const node = zodToTs(example, ctx); - expect(printNode(node)).toMatchSnapshot(); + expect(api.printNode(node)).toMatchSnapshot(); }); }); @@ -211,6 +220,7 @@ describe("zod-to-ts", () => { }), ]) .optional(), + exact: z.string().exactOptional(), }); test("Zod 4: does not add undefined to it, unwrap as is", () => { diff --git a/zod-sockets/src/zts.ts b/zod-sockets/src/zts.ts index 8d0f944e..9bdbd304 100644 --- a/zod-sockets/src/zts.ts +++ b/zod-sockets/src/zts.ts @@ -1,4 +1,4 @@ -import ts from "typescript"; +import type ts from "typescript"; import { globalRegistry, z } from "zod"; import { lcFirst, @@ -10,24 +10,7 @@ import { hasCycle } from "./integration-helpers"; import { FirstPartyKind, HandlingRules, walkSchema } from "./schema-walker"; import * as R from "ramda"; import { Producer, ZTSContext } from "./zts-helpers"; -import { - ensureTypeNode, - makeInterfaceProp, - makeLiteralType, - makeUnion, -} from "./typescript-api"; - -const { factory: f } = ts; - -const samples = { - [ts.SyntaxKind.AnyKeyword]: "", - [ts.SyntaxKind.BigIntKeyword]: BigInt(0), - [ts.SyntaxKind.BooleanKeyword]: false, - [ts.SyntaxKind.NumberKeyword]: 0, - [ts.SyntaxKind.ObjectKeyword]: {}, - [ts.SyntaxKind.StringKeyword]: "", - [ts.SyntaxKind.UndefinedKeyword]: undefined, -} satisfies Partial>; +import { TypescriptAPI } from "./typescript-api"; const nodePath = { name: R.path([ @@ -41,18 +24,21 @@ const nodePath = { optional: R.path(["questionToken" satisfies keyof ts.TypeElement]), }; -const onLiteral: Producer = ({ _zod: { def } }: z.core.$ZodLiteral) => { +const onLiteral: Producer = ( + { _zod: { def } }: z.core.$ZodLiteral, + { api }, +) => { const values = def.values.map((entry) => entry === undefined - ? ensureTypeNode(ts.SyntaxKind.UndefinedKeyword) - : makeLiteralType(entry), + ? api.ensureTypeNode(api.ts.SyntaxKind.UndefinedKeyword) + : api.makeLiteralType(entry), ); - return values.length === 1 ? values[0] : makeUnion(values); + return values.length === 1 ? values[0] : api.makeUnion(values); }; const onTemplateLiteral: Producer = ( { _zod: { def } }: z.core.$ZodTemplateLiteral, - { next }, + { next, api }, ) => { const parts = [...def.parts]; const shiftText = () => { @@ -67,78 +53,95 @@ const onTemplateLiteral: Producer = ( } return text; }; - const head = f.createTemplateHead(shiftText()); + const head = api.f.createTemplateHead(shiftText()); const spans: ts.TemplateLiteralTypeSpan[] = []; while (parts.length) { const schema = next(parts.shift() as z.core.$ZodType); const text = shiftText(); const textWrapper = parts.length - ? f.createTemplateMiddle - : f.createTemplateTail; - spans.push(f.createTemplateLiteralTypeSpan(schema, textWrapper(text))); + ? api.f.createTemplateMiddle + : api.f.createTemplateTail; + spans.push(api.f.createTemplateLiteralTypeSpan(schema, textWrapper(text))); } - if (!spans.length) return makeLiteralType(head.text); - return f.createTemplateLiteralType(head, spans); + if (!spans.length) return api.makeLiteralType(head.text); + return api.f.createTemplateLiteralType(head, spans); }; const onObject: Producer = ( obj: z.core.$ZodObject, - { isResponse, next, makeAlias }, + { isResponse, next, makeAlias, api }, ) => { const produce = () => { const members = Object.entries(obj._zod.def.shape).map( ([key, value]) => { const { description: comment, deprecated: isDeprecated } = globalRegistry.get(value) || {}; - return makeInterfaceProp(key, next(value), { + const isOptional = + (isResponse ? value._zod.optout : value._zod.optin) === "optional"; + const hasUndefined = + isOptional && !(value instanceof z.core.$ZodExactOptional); + return api.makeInterfaceProp(key, next(value), { comment, isDeprecated, - isOptional: - (isResponse ? value._zod.optout : value._zod.optin) === "optional", + isOptional, + hasUndefined, }); }, ); - return f.createTypeLiteralNode(members); + return api.f.createTypeLiteralNode(members); }; return hasCycle(obj, { io: isResponse ? "output" : "input" }) ? makeAlias(obj, produce) : produce(); }; -const onArray: Producer = ({ _zod: { def } }: z.core.$ZodArray, { next }) => - f.createArrayTypeNode(next(def.element)); +const onArray: Producer = ( + { _zod: { def } }: z.core.$ZodArray, + { next, api }, +) => api.f.createArrayTypeNode(next(def.element)); -const onEnum: Producer = ({ _zod: { def } }: z.core.$ZodEnum) => - makeUnion(Object.values(def.entries).map(makeLiteralType)); +const onEnum: Producer = ({ _zod: { def } }: z.core.$ZodEnum, { api }) => + api.makeUnion(R.map(api.makeLiteralType, Object.values(def.entries))); const onSomeUnion: Producer = ( { _zod: { def } }: z.core.$ZodUnion | z.core.$ZodDiscriminatedUnion, - { next }, -) => { - return makeUnion(def.options.map(next)); -}; - -const makeSample = (produced: ts.TypeNode) => - samples?.[produced.kind as keyof typeof samples]; + { next, api }, +) => api.makeUnion(def.options.map(next)); const onNullable: Producer = ( { _zod: { def } }: z.core.$ZodNullable, - { next }, -) => makeUnion([next(def.innerType), makeLiteralType(null)]); + { next, api }, +) => api.makeUnion([next(def.innerType), api.makeLiteralType(null)]); -const onTuple: Producer = ({ _zod: { def } }: z.core.$ZodTuple, { next }) => - f.createTupleTypeNode( +const onTuple: Producer = ( + { _zod: { def } }: z.core.$ZodTuple, + { next, api }, +) => + api.f.createTupleTypeNode( def.items .map(next) - .concat(def.rest === null ? [] : f.createRestTypeNode(next(def.rest))), + .concat( + def.rest === null ? [] : api.f.createRestTypeNode(next(def.rest)), + ), ); -const onRecord: Producer = ({ _zod: { def } }: z.core.$ZodRecord, { next }) => - ensureTypeNode("Record", [def.keyType, def.valueType].map(next)); +const onRecord: Producer = ( + { _zod: { def } }: z.core.$ZodRecord, + { next, api }, +) => { + const [keyNode, valueNode] = [def.keyType, def.valueType].map(next); + const primary = api.ensureTypeNode("Record", [keyNode, valueNode]); + const isLoose = def.mode === "loose"; + if (!isLoose) return primary; + return api.f.createIntersectionTypeNode([ + primary, + api.ensureTypeNode("Record", ["PropertyKey", valueNode]), + ]); +}; const intersect = R.tryCatch( - (nodes: ts.TypeNode[]) => { - if (!nodes.every(ts.isTypeLiteralNode)) throw new Error("Not objects"); + (api: TypescriptAPI, nodes: ts.TypeNode[]) => { + if (!nodes.every(api.ts.isTypeLiteralNode)) throw new Error("Not objects"); const members = R.chain(R.prop("members"), nodes); const uniqs = R.uniqWith((...props) => { if (!R.eqBy(nodePath.name, ...props)) return false; @@ -146,20 +149,31 @@ const intersect = R.tryCatch( return true; throw new Error("Has conflicting prop"); }, members); - return f.createTypeLiteralNode(uniqs); + return api.f.createTypeLiteralNode(uniqs); }, - (_err, nodes) => f.createIntersectionTypeNode(nodes), + (_err, api, nodes) => api.f.createIntersectionTypeNode(nodes), ); const onIntersection: Producer = ( { _zod: { def } }: z.core.$ZodIntersection, - { next }, -) => intersect([def.left, def.right].map(next)); + { next, api }, +) => intersect(api, [def.left, def.right].map(next)); const onPrimitive = - (syntaxKind: ts.KeywordTypeSyntaxKind): Producer => - () => - ensureTypeNode(syntaxKind); + ( + syntaxKind: + | "AnyKeyword" + | "BigIntKeyword" + | "BooleanKeyword" + | "NeverKeyword" + | "NumberKeyword" + | "StringKeyword" + | "UndefinedKeyword" + | "UnknownKeyword" + | "VoidKeyword", + ): Producer => + ({}, { api }) => + api.ensureTypeNode(api.ts.SyntaxKind[syntaxKind]); const onWrapped: Producer = ( { @@ -169,61 +183,72 @@ const onWrapped: Producer = ( | z.core.$ZodCatch | z.core.$ZodDefault | z.core.$ZodOptional - | z.core.$ZodNonOptional, + | z.core.$ZodNonOptional + | z.core.$ZodExactOptional, { next }, ) => next(def.innerType); -const getFallback = (isResponse: boolean) => - ensureTypeNode( - isResponse ? ts.SyntaxKind.UnknownKeyword : ts.SyntaxKind.AnyKeyword, +const getFallback = (api: TypescriptAPI, isResponse: boolean) => + api.ensureTypeNode( + isResponse + ? api.ts.SyntaxKind.UnknownKeyword + : api.ts.SyntaxKind.AnyKeyword, ); const onPipeline: Producer = ( { _zod: { def } }: z.core.$ZodPipe, - { next, isResponse }, + { next, isResponse, api }, ) => { const target = def[isResponse ? "out" : "in"]; const opposite = def[isResponse ? "in" : "out"]; if (!isSchema(target, "transform")) return next(target); const opposingType = next(opposite); + const samples = { + [api.ts.SyntaxKind.AnyKeyword]: "", + [api.ts.SyntaxKind.BigIntKeyword]: BigInt(0), + [api.ts.SyntaxKind.BooleanKeyword]: false, + [api.ts.SyntaxKind.NumberKeyword]: 0, + [api.ts.SyntaxKind.ObjectKeyword]: {}, + [api.ts.SyntaxKind.StringKeyword]: "", + [api.ts.SyntaxKind.UndefinedKeyword]: undefined, + } satisfies Partial>; + const sample = samples[opposingType.kind as keyof typeof samples]; const targetType = getTransformedType( target, - isSchema(opposite, "date") - ? new Date() - : makeSample(opposingType), + isSchema(opposite, "date") ? new Date() : sample, ); const resolutions: Partial< Record, ts.KeywordTypeSyntaxKind> > = { - number: ts.SyntaxKind.NumberKeyword, - bigint: ts.SyntaxKind.BigIntKeyword, - boolean: ts.SyntaxKind.BooleanKeyword, - string: ts.SyntaxKind.StringKeyword, - undefined: ts.SyntaxKind.UndefinedKeyword, - object: ts.SyntaxKind.ObjectKeyword, + number: api.ts.SyntaxKind.NumberKeyword, + bigint: api.ts.SyntaxKind.BigIntKeyword, + boolean: api.ts.SyntaxKind.BooleanKeyword, + string: api.ts.SyntaxKind.StringKeyword, + undefined: api.ts.SyntaxKind.UndefinedKeyword, + object: api.ts.SyntaxKind.ObjectKeyword, }; - return ensureTypeNode( - (targetType && resolutions[targetType]) || getFallback(isResponse), + return api.ensureTypeNode( + (targetType && resolutions[targetType]) || getFallback(api, isResponse), ); }; -const onNull: Producer = () => makeLiteralType(null); +const onNull: Producer = ({}, { api }) => api.makeLiteralType(null); const onLazy: Producer = ( { _zod: { def } }: z.core.$ZodLazy, { makeAlias, next }, ) => makeAlias(def.getter, () => next(def.getter())); -const onFunction: Producer = (schema: z.core.$ZodFunction, { next }) => { +const onFunction: Producer = (schema: z.core.$ZodFunction, { next, api }) => { const { input, output } = schema._zod.def; if (!isSchema(input, "tuple")) throw new Error("z.function()::input must be a tuple"); const params = input._zod.def.items.map((subject, index) => { const { description } = globalRegistry.get(subject) || {}; - return f.createParameterDeclaration( + return api.f.createParameterDeclaration( undefined, undefined, - f.createIdentifier( + api.f.createIdentifier( description ? lcFirst(makeCleanId(description)) : `${isSchema(subject, "function") ? "cb" : "p"}${index + 1}`, @@ -236,10 +261,10 @@ const onFunction: Producer = (schema: z.core.$ZodFunction, { next }) => { if (rest) { const { description } = globalRegistry.get(rest) || {}; params.push( - f.createParameterDeclaration( + api.f.createParameterDeclaration( undefined, - f.createToken(ts.SyntaxKind.DotDotDotToken), - f.createIdentifier( + api.f.createToken(api.ts.SyntaxKind.DotDotDotToken), + api.f.createIdentifier( description ? lcFirst(makeCleanId(description)) : "rest", ), undefined, @@ -247,19 +272,19 @@ const onFunction: Producer = (schema: z.core.$ZodFunction, { next }) => { ), ); } - return f.createFunctionTypeNode(undefined, params, next(output)); + return api.f.createFunctionTypeNode(undefined, params, next(output)); }; const producers: HandlingRules = { - string: onPrimitive(ts.SyntaxKind.StringKeyword), - number: onPrimitive(ts.SyntaxKind.NumberKeyword), - bigint: onPrimitive(ts.SyntaxKind.BigIntKeyword), - boolean: onPrimitive(ts.SyntaxKind.BooleanKeyword), - any: onPrimitive(ts.SyntaxKind.AnyKeyword), - undefined: onPrimitive(ts.SyntaxKind.UndefinedKeyword), - never: onPrimitive(ts.SyntaxKind.NeverKeyword), - void: onPrimitive(ts.SyntaxKind.VoidKeyword), - unknown: onPrimitive(ts.SyntaxKind.UnknownKeyword), + string: onPrimitive("StringKeyword"), + number: onPrimitive("NumberKeyword"), + bigint: onPrimitive("BigIntKeyword"), + boolean: onPrimitive("BooleanKeyword"), + any: onPrimitive("AnyKeyword"), + undefined: onPrimitive("UndefinedKeyword"), + never: onPrimitive("NeverKeyword"), + void: onPrimitive("VoidKeyword"), + unknown: onPrimitive("UnknownKeyword"), null: onNull, array: onArray, tuple: onTuple, @@ -284,6 +309,6 @@ const producers: HandlingRules = { export const zodToTs = (schema: z.ZodType, ctx: ZTSContext) => walkSchema(schema, { rules: producers, - onMissing: ({}, { isResponse }) => getFallback(isResponse), + onMissing: ({}, { isResponse, api }) => getFallback(api, isResponse), ctx, });