├── .all-contributorsrc ├── .changes ├── 0.3.0.md ├── header.tpl.md └── unreleased │ ├── .gitkeep │ └── Changed-20240303-012115.yaml ├── .changie.yaml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── sweep-bugfix.yml │ ├── sweep-feature.yml │ └── sweep-refactor.yml └── workflows │ └── release.yml ├── .gitignore ├── .nvmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ ├── hof.test.ts.snap │ └── validation.test.ts.snap ├── hof.test.ts └── validation.test.ts ├── biome.json ├── cspell-tool.txt ├── cspell.json ├── example ├── .gitignore ├── .vercelignore ├── README.md ├── cypress.config.ts ├── cypress │ ├── e2e │ │ ├── contact.cy.ts │ │ ├── greeter.cy.ts │ │ ├── hello.cy.ts │ │ ├── register.cy.ts │ │ ├── support.cy.ts │ │ └── user.cy.ts │ ├── fixtures │ │ └── example.json │ ├── plugins │ │ └── index.ts │ └── support │ │ ├── commands.ts │ │ └── e2e.ts ├── link.config.json ├── next-env.d.ts ├── package.json ├── pages │ ├── _app.tsx │ ├── api │ │ ├── contact.ts │ │ ├── greeter.ts │ │ ├── hello.ts │ │ ├── register.ts │ │ ├── support.ts │ │ └── user.ts │ ├── index.tsx │ └── playground.tsx ├── pnpm-lock.yaml ├── public │ ├── favicon.ico │ ├── swagger.json │ └── vercel.svg ├── styles │ ├── Home.module.css │ └── globals.css └── tsconfig.json ├── next-validations.txt ├── package.json ├── pnpm-lock.yaml ├── renovate.json ├── src ├── index.ts ├── resolver.ts └── withValidation.ts ├── sweep.yaml ├── tsconfig.json ├── version.txt └── vitest.config.ts /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "jellydn", 12 | "name": "Dung Duc Huynh (Kaka)", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/870029?v=4", 14 | "profile": "https://productsway.com/", 15 | "contributions": [ 16 | "code", 17 | "doc" 18 | ] 19 | }, 20 | { 21 | "login": "SferaDev", 22 | "name": "Alexis Rico", 23 | "avatar_url": "https://avatars.githubusercontent.com/u/2181866?v=4", 24 | "profile": "http://sferadev.com", 25 | "contributions": [ 26 | "code" 27 | ] 28 | }, 29 | { 30 | "login": "decs", 31 | "name": "André Costa", 32 | "avatar_url": "https://avatars.githubusercontent.com/u/601381?v=4", 33 | "profile": "https://github.com/decs", 34 | "contributions": [ 35 | "code" 36 | ] 37 | } 38 | ], 39 | "contributorsPerLine": 7, 40 | "skipCi": true, 41 | "repoType": "github", 42 | "repoHost": "https://github.com", 43 | "projectName": "next-validations", 44 | "projectOwner": "jellydn" 45 | } 46 | -------------------------------------------------------------------------------- /.changes/0.3.0.md: -------------------------------------------------------------------------------- 1 | ## 0.3.0 - 2022-11-26 2 | ### Added 3 | * Validation of multiple modes 4 | -------------------------------------------------------------------------------- /.changes/header.tpl.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), 6 | and is generated by [Changie](https://github.com/miniscruff/changie). 7 | -------------------------------------------------------------------------------- /.changes/unreleased/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/next-validations/150b83f229c6757a92203acae5124fedac5f4c96/.changes/unreleased/.gitkeep -------------------------------------------------------------------------------- /.changes/unreleased/Changed-20240303-012115.yaml: -------------------------------------------------------------------------------- 1 | kind: Changed 2 | body: 'feat(resolvers): replace resolvers with typeschema' 3 | time: 2024-03-03T01:21:15.163345-08:00 4 | -------------------------------------------------------------------------------- /.changie.yaml: -------------------------------------------------------------------------------- 1 | changesDir: .changes 2 | unreleasedDir: unreleased 3 | headerPath: header.tpl.md 4 | changelogPath: CHANGELOG.md 5 | versionExt: md 6 | versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}' 7 | kindFormat: '### {{.Kind}}' 8 | changeFormat: '* {{.Body}}' 9 | kinds: 10 | - label: Added 11 | - label: Changed 12 | - label: Deprecated 13 | - label: Removed 14 | - label: Fixed 15 | - label: Security 16 | newlines: 17 | afterChangelogHeader: 1 18 | beforeChangelogVersion: 1 19 | endOfVersion: 1 20 | envPrefix: CHANGIE_ 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jellydn] 2 | ko_fi: dunghd 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/sweep-bugfix.yml: -------------------------------------------------------------------------------- 1 | name: Bugfix 2 | title: 'Sweep: ' 3 | description: Write something like "We notice ... behavior when ... happens instead of ..."" 4 | labels: sweep 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Details 10 | description: More details about the bug 11 | placeholder: The bug might be in ... file -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/sweep-feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | title: 'Sweep: ' 3 | description: Write something like "Write an api endpoint that does "..." in the "..." file" 4 | labels: sweep 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Details 10 | description: More details for Sweep 11 | placeholder: The new endpoint should use the ... class from ... file because it contains ... logic -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/sweep-refactor.yml: -------------------------------------------------------------------------------- 1 | name: Refactor 2 | title: 'Sweep: ' 3 | description: Write something like "Modify the ... api endpoint to use ... version and ... framework" 4 | labels: sweep 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Details 10 | description: More details for Sweep 11 | placeholder: We are migrating this function to ... version because ... -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - release 6 | pull_request: 7 | branches: 8 | - main 9 | - release 10 | 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | 15 | jobs: 16 | release: 17 | name: release 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: google-github-actions/release-please-action@v4 21 | id: release 22 | with: 23 | release-type: node 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | - uses: actions/checkout@v4 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | - name: tag stable versions 29 | if: ${{ steps.release.outputs.release_created }} 30 | run: | 31 | git config user.name github-actions[bot] 32 | git config user.email github-actions[bot]@users.noreply.github.com 33 | git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git" 34 | git tag -d stable || true 35 | git push origin :stable || true 36 | git tag -a stable -m "Last Stable Release" 37 | git push origin stable 38 | - uses: actions/setup-node@v4 39 | with: 40 | node-version: 18 41 | registry-url: "https://registry.npmjs.org" 42 | if: ${{ steps.release.outputs.release_created }} 43 | - run: corepack enable && corepack use pnpm@latest && pnpm install 44 | if: ${{ steps.release.outputs.release_created }} 45 | - run: npm publish 46 | env: 47 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 48 | if: ${{ steps.release.outputs.release_created }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | docs 7 | .vercel 8 | coverage 9 | example/cypress/screenshots 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), 6 | and is generated by [Changie](https://github.com/miniscruff/changie). 7 | 8 | 9 | ## [1.1.0](https://github.com/jellydn/next-validations/compare/v1.0.3...v1.1.0) (2025-05-23) 10 | 11 | 12 | ### Features 13 | 14 | * improve request validation with headers support ([96e7d1b](https://github.com/jellydn/next-validations/commit/96e7d1b4c0645992891edf5dac576b42a30061f5)) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * **deps:** update all non-major dependencies ([#1006](https://github.com/jellydn/next-validations/issues/1006)) ([3841dfb](https://github.com/jellydn/next-validations/commit/3841dfb56eedc34ac34d8d36d0f98c15e7f196f2)) 20 | * **deps:** update all non-major dependencies ([#1015](https://github.com/jellydn/next-validations/issues/1015)) ([1e2ace1](https://github.com/jellydn/next-validations/commit/1e2ace180595934a850b09b06b3a0b0d95a9bf2a)) 21 | * **deps:** update all non-major dependencies ([#1031](https://github.com/jellydn/next-validations/issues/1031)) ([26bab72](https://github.com/jellydn/next-validations/commit/26bab723e2d2e208343cac25a2ccdb40c66c80d6)) 22 | * **deps:** update all non-major dependencies ([#1033](https://github.com/jellydn/next-validations/issues/1033)) ([d11c4d5](https://github.com/jellydn/next-validations/commit/d11c4d5a8d7b1db3f619725504b9a558b3f10596)) 23 | * **deps:** update all non-major dependencies ([#1040](https://github.com/jellydn/next-validations/issues/1040)) ([3ae1425](https://github.com/jellydn/next-validations/commit/3ae1425c757d8b0206f583ea6c6fe40ca39a9cf4)) 24 | * **deps:** update all non-major dependencies ([#1046](https://github.com/jellydn/next-validations/issues/1046)) ([2756a08](https://github.com/jellydn/next-validations/commit/2756a0898d7b7e7c64541cae3826db234eb160ff)) 25 | * **deps:** update all non-major dependencies ([#1052](https://github.com/jellydn/next-validations/issues/1052)) ([033493b](https://github.com/jellydn/next-validations/commit/033493bbfa3a717d2588d2af8ef4b5e9f455b6f9)) 26 | * **deps:** update all non-major dependencies ([#1053](https://github.com/jellydn/next-validations/issues/1053)) ([dd04d80](https://github.com/jellydn/next-validations/commit/dd04d80a568d235086689f1f76219d3910e36756)) 27 | * **deps:** update all non-major dependencies ([#1065](https://github.com/jellydn/next-validations/issues/1065)) ([c8d04dd](https://github.com/jellydn/next-validations/commit/c8d04dd74b532a0d26537603ae1610ffc9fe8dd8)) 28 | * **deps:** update all non-major dependencies ([#1066](https://github.com/jellydn/next-validations/issues/1066)) ([31ca273](https://github.com/jellydn/next-validations/commit/31ca2737d6a4462bee4a23522dcb86fcf152f4fd)) 29 | * **deps:** update all non-major dependencies ([#1073](https://github.com/jellydn/next-validations/issues/1073)) ([4daf7ee](https://github.com/jellydn/next-validations/commit/4daf7eef4a42c8c8abe579d2910f3be14b89cca9)) 30 | * **deps:** update all non-major dependencies ([#1081](https://github.com/jellydn/next-validations/issues/1081)) ([62a5586](https://github.com/jellydn/next-validations/commit/62a55867f963c264fddbf82e44968cd7adc0f254)) 31 | * **deps:** update all non-major dependencies ([#1082](https://github.com/jellydn/next-validations/issues/1082)) ([bb40d1e](https://github.com/jellydn/next-validations/commit/bb40d1e331744b4d15f8607ea837bc8adf1d17d2)) 32 | * **deps:** update all non-major dependencies ([#1083](https://github.com/jellydn/next-validations/issues/1083)) ([42c815b](https://github.com/jellydn/next-validations/commit/42c815b37e7c07535c4fe4d46ee0011c5fa3b2f6)) 33 | * **deps:** update all non-major dependencies ([#999](https://github.com/jellydn/next-validations/issues/999)) ([fccf5eb](https://github.com/jellydn/next-validations/commit/fccf5eb9885638774ed549a27f54e4a2403413e6)) 34 | * **deps:** update all non-major dependencies to v19.1.0 ([#1026](https://github.com/jellydn/next-validations/issues/1026)) ([b78d5bb](https://github.com/jellydn/next-validations/commit/b78d5bba9d30ec9cb9447e72ccfa7250c3c11b93)) 35 | * **deps:** update dependency caniuse-lite to v1.0.30001676 ([0194fe1](https://github.com/jellydn/next-validations/commit/0194fe1e9c3cc5a648eb77ffd4e3205c7f2c9f6d)) 36 | * **deps:** update dependency caniuse-lite to v1.0.30001677 ([98dd6f8](https://github.com/jellydn/next-validations/commit/98dd6f8ea3198a1ac98ac56b7f0e916f5afe6cbc)) 37 | * **deps:** update dependency caniuse-lite to v1.0.30001678 ([377c90e](https://github.com/jellydn/next-validations/commit/377c90e9be1d21ef542959f80c0d146b5f5a9120)) 38 | * **deps:** update dependency caniuse-lite to v1.0.30001679 ([#969](https://github.com/jellydn/next-validations/issues/969)) ([ad17a6c](https://github.com/jellydn/next-validations/commit/ad17a6c903b4f08b0c8f475747fc21da87fbef4e)) 39 | * **deps:** update dependency caniuse-lite to v1.0.30001680 ([#970](https://github.com/jellydn/next-validations/issues/970)) ([b7a816d](https://github.com/jellydn/next-validations/commit/b7a816d33541061328f173d0aad92a18360bc86f)) 40 | * **deps:** update dependency caniuse-lite to v1.0.30001683 ([#980](https://github.com/jellydn/next-validations/issues/980)) ([fe5ea1d](https://github.com/jellydn/next-validations/commit/fe5ea1db8d01ef330a2891f2882ede8c61cfca6b)) 41 | * **deps:** update dependency caniuse-lite to v1.0.30001684 ([#982](https://github.com/jellydn/next-validations/issues/982)) ([3890d17](https://github.com/jellydn/next-validations/commit/3890d1740bd4fcfb201657c69a74f531a5514152)) 42 | * **deps:** update dependency caniuse-lite to v1.0.30001686 ([#990](https://github.com/jellydn/next-validations/issues/990)) ([5b454ad](https://github.com/jellydn/next-validations/commit/5b454adb9092bb760d213d0609b39227880a7df6)) 43 | * **deps:** update dependency caniuse-lite to v1.0.30001687 ([#996](https://github.com/jellydn/next-validations/issues/996)) ([483b808](https://github.com/jellydn/next-validations/commit/483b808bc73ef7d6854e842323067f8c692d0bb2)) 44 | * **deps:** update dependency caniuse-lite to v1.0.30001703 ([#1003](https://github.com/jellydn/next-validations/issues/1003)) ([693e9e8](https://github.com/jellydn/next-validations/commit/693e9e80d690a7f9293c3aaf3f6a8fb5bf93c752)) 45 | * **deps:** update dependency caniuse-lite to v1.0.30001704 ([#1008](https://github.com/jellydn/next-validations/issues/1008)) ([6eecb43](https://github.com/jellydn/next-validations/commit/6eecb43fa01d73be6eb5d24d82a7d08f1d3e41fa)) 46 | * **deps:** update dependency caniuse-lite to v1.0.30001705 ([#1012](https://github.com/jellydn/next-validations/issues/1012)) ([501fa64](https://github.com/jellydn/next-validations/commit/501fa64191a1d518939ada7387235ce65c207586)) 47 | * **deps:** update dependency caniuse-lite to v1.0.30001706 ([#1016](https://github.com/jellydn/next-validations/issues/1016)) ([16a8ab1](https://github.com/jellydn/next-validations/commit/16a8ab1e48b40861d428228557db9bdf08cd80db)) 48 | * **deps:** update dependency caniuse-lite to v1.0.30001707 ([#1020](https://github.com/jellydn/next-validations/issues/1020)) ([a9f55f6](https://github.com/jellydn/next-validations/commit/a9f55f6adab0740bfb6464234c35767890f7a8bb)) 49 | * **deps:** update dependency caniuse-lite to v1.0.30001709 ([#1032](https://github.com/jellydn/next-validations/issues/1032)) ([d24d928](https://github.com/jellydn/next-validations/commit/d24d9286b2a9abc0b71a13ca143a7fcf9d2564d8)) 50 | * **deps:** update dependency caniuse-lite to v1.0.30001710 ([#1034](https://github.com/jellydn/next-validations/issues/1034)) ([14fa2d8](https://github.com/jellydn/next-validations/commit/14fa2d8d9d4be83672b738115ede4054428744ba)) 51 | * **deps:** update dependency caniuse-lite to v1.0.30001711 ([#1037](https://github.com/jellydn/next-validations/issues/1037)) ([bb3ff12](https://github.com/jellydn/next-validations/commit/bb3ff12b7a06a49bd89df81075be25933d824738)) 52 | * **deps:** update dependency caniuse-lite to v1.0.30001712 ([#1038](https://github.com/jellydn/next-validations/issues/1038)) ([093be99](https://github.com/jellydn/next-validations/commit/093be99bb42222567ca9bc4d5512369344fd5441)) 53 | * **deps:** update dependency caniuse-lite to v1.0.30001715 ([#1057](https://github.com/jellydn/next-validations/issues/1057)) ([2004b41](https://github.com/jellydn/next-validations/commit/2004b419dafa3e4d40d6d43d22d10e6f0fa26276)) 54 | * **deps:** update dependency caniuse-lite to v1.0.30001717 ([#1070](https://github.com/jellydn/next-validations/issues/1070)) ([faa38f7](https://github.com/jellydn/next-validations/commit/faa38f7b8b840cb63cee0de9212de8d13490e5ea)) 55 | * **deps:** update dependency caniuse-lite to v1.0.30001718 ([#1077](https://github.com/jellydn/next-validations/issues/1077)) ([c4ad40a](https://github.com/jellydn/next-validations/commit/c4ad40a40d854355ad64c655ed83f379368adf2d)) 56 | * **deps:** update dependency next to v15.2.4 ([#1023](https://github.com/jellydn/next-validations/issues/1023)) ([dc603ed](https://github.com/jellydn/next-validations/commit/dc603ed3ef82aef8b8e6534f517855a3197d09e0)) 57 | * **deps:** update dependency next to v15.2.5 ([#1043](https://github.com/jellydn/next-validations/issues/1043)) ([30c510e](https://github.com/jellydn/next-validations/commit/30c510eedd5b3df59a3bdc9b70a5ba5044a6777c)) 58 | * **deps:** update dependency next to v15.3.0 ([#1045](https://github.com/jellydn/next-validations/issues/1045)) ([e284af0](https://github.com/jellydn/next-validations/commit/e284af0605370f7147abd17523ed2c8346fd78af)) 59 | * **deps:** update dependency next to v15.3.1 ([#1054](https://github.com/jellydn/next-validations/issues/1054)) ([8a126cb](https://github.com/jellydn/next-validations/commit/8a126cb0f768b0b9d5afdd7eb59d80d44fc04a90)) 60 | * **deps:** update dependency qs to v6.13.1 ([#975](https://github.com/jellydn/next-validations/issues/975)) ([b2cf99b](https://github.com/jellydn/next-validations/commit/b2cf99b21b664db8fbf23038c5688fcd6fae73d6)) 61 | * **deps:** update dependency swagger-ui-react to v5.18.0 ([#965](https://github.com/jellydn/next-validations/issues/965)) ([2d279b6](https://github.com/jellydn/next-validations/commit/2d279b66dd696a9cf0e7fdcaab14092cb829accf)) 62 | * **deps:** update dependency swagger-ui-react to v5.20.1 ([#1005](https://github.com/jellydn/next-validations/issues/1005)) ([ee2a3a6](https://github.com/jellydn/next-validations/commit/ee2a3a6e69469aee04aeb70bfb0133bd4096d944)) 63 | * **deps:** update dependency swagger-ui-react to v5.20.2 ([#1025](https://github.com/jellydn/next-validations/issues/1025)) ([018a4d9](https://github.com/jellydn/next-validations/commit/018a4d9d8d419ed6d4ce049b8b283ef72407c668)) 64 | * **deps:** update dependency swagger-ui-react to v5.20.6 ([#1035](https://github.com/jellydn/next-validations/issues/1035)) ([a121fe3](https://github.com/jellydn/next-validations/commit/a121fe3f3e07a6dc8918b3e8db748bf65ee1794a)) 65 | * **deps:** update dependency swagger-ui-react to v5.21.0 ([#1048](https://github.com/jellydn/next-validations/issues/1048)) ([f82394d](https://github.com/jellydn/next-validations/commit/f82394deb4ddba47af8db76fd86a99fc465f0866)) 66 | * **deps:** update dependency zod to v3.24.4 ([#1068](https://github.com/jellydn/next-validations/issues/1068)) ([aebf50c](https://github.com/jellydn/next-validations/commit/aebf50ce250b9c20ffa948a559c7bea4d61e83b6)) 67 | * **deps:** update dependency zod to v3.25.23 ([#1084](https://github.com/jellydn/next-validations/issues/1084)) ([6491963](https://github.com/jellydn/next-validations/commit/64919639e16f55349c8170edb965e86a3baaa27a)) 68 | * **deps:** update react monorepo to v19 ([#994](https://github.com/jellydn/next-validations/issues/994)) ([7bc2de2](https://github.com/jellydn/next-validations/commit/7bc2de2ad50e3a136904a298420ae35d2b3f45ab)) 69 | 70 | ## [1.0.2](https://github.com/jellydn/next-validations/compare/v1.0.1...v1.0.2) (2024-09-15) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * **deps:** update all non-major dependencies ([655c778](https://github.com/jellydn/next-validations/commit/655c778a0f3c73b212b60b0adc81a86fd9288a39)) 76 | * **deps:** update all non-major dependencies ([1dae42c](https://github.com/jellydn/next-validations/commit/1dae42ca58f9a82b33cb190ba9b6246ee3db8692)) 77 | * **deps:** update all non-major dependencies ([7fec067](https://github.com/jellydn/next-validations/commit/7fec0672317137b9c9261731d64a02c800e45236)) 78 | * **deps:** update all non-major dependencies ([e503aa1](https://github.com/jellydn/next-validations/commit/e503aa1d6f690e9f5ab8de5fd8e01f9460628bef)) 79 | * **deps:** update all non-major dependencies ([190dd9f](https://github.com/jellydn/next-validations/commit/190dd9f32393d3ee9d30d0c2e904294dc53fb2a0)) 80 | * **deps:** update all non-major dependencies ([c975f5a](https://github.com/jellydn/next-validations/commit/c975f5ad87474cb6f5bde41956626bb645303a9e)) 81 | * **deps:** update all non-major dependencies ([#885](https://github.com/jellydn/next-validations/issues/885)) ([3eb9d0a](https://github.com/jellydn/next-validations/commit/3eb9d0a799ef9cf114fd9c575a8f43f23f0812f0)) 82 | * **deps:** update dependency caniuse-lite to v1.0.30001645 ([fd076f6](https://github.com/jellydn/next-validations/commit/fd076f6e33fd6d3ee94c8bf32096d4f27e4a51b5)) 83 | * **deps:** update dependency caniuse-lite to v1.0.30001649 ([2645ea4](https://github.com/jellydn/next-validations/commit/2645ea4321a1533aaaad1ab3453cb470105291c6)) 84 | * **deps:** update dependency caniuse-lite to v1.0.30001650 ([b597f39](https://github.com/jellydn/next-validations/commit/b597f39b8fb880c712f64e6ef8526584543415c8)) 85 | * **deps:** update dependency caniuse-lite to v1.0.30001653 ([c0cce1c](https://github.com/jellydn/next-validations/commit/c0cce1cccd7570fb4d49441fe00682f95e28a580)) 86 | * **deps:** update dependency caniuse-lite to v1.0.30001654 ([bc11b0b](https://github.com/jellydn/next-validations/commit/bc11b0be9a0af95eef2b221ef2c7082e90d02be3)) 87 | * **deps:** update dependency caniuse-lite to v1.0.30001657 ([9281321](https://github.com/jellydn/next-validations/commit/92813212d226fa3c30b5425b326494cd614dfc13)) 88 | * **deps:** update dependency caniuse-lite to v1.0.30001658 ([020fadf](https://github.com/jellydn/next-validations/commit/020fadffbdfb3b31b1f4868e78efbce92627f6ab)) 89 | * **deps:** update dependency caniuse-lite to v1.0.30001659 ([54240c0](https://github.com/jellydn/next-validations/commit/54240c01e183fbc19410c00215d7ea8ef08a86ea)) 90 | * **deps:** update dependency qs to v6.13.0 ([6dc69ed](https://github.com/jellydn/next-validations/commit/6dc69ed3ad880e575e3f0b632b81884604c97982)) 91 | 92 | ## [1.0.0](https://github.com/jellydn/next-validations/compare/v0.4.2...v1.0.0) (2024-03-13) 93 | 94 | 95 | ### ⚠ BREAKING CHANGES 96 | 97 | * **package.json:** Update Node.js engine version to 18 98 | * **resolvers:** replace resolvers with typeschema 99 | 100 | ### Bug Fixes 101 | 102 | * **deps:** update dependency next-validations to v0.4.2 ([#792](https://github.com/jellydn/next-validations/issues/792)) ([0010d87](https://github.com/jellydn/next-validations/commit/0010d87eca8bd7728f47cadde4652ed3b58205cf)) 103 | 104 | 105 | ### Code Refactoring 106 | 107 | * **package.json:** Update Node.js engine version to 18 ([8ffb3c0](https://github.com/jellydn/next-validations/commit/8ffb3c08a0169448bc44031d03e4e62a13321363)) 108 | * **resolvers:** replace resolvers with typeschema ([cc97da4](https://github.com/jellydn/next-validations/commit/cc97da47a5b8e1d29631d139237b72b37c76f03e)) 109 | 110 | ## 0.3.0 - 2022-11-26 111 | ### Added 112 | * Validation of multiple modes 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Huynh Duc Dung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to next-validations 👋 2 | 3 | 4 | 5 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) 6 | 7 | 8 | 9 | [![Version](https://img.shields.io/npm/v/next-validations.svg)](https://npmjs.org/package/next-validations) 10 | [![Downloads/week](https://img.shields.io/npm/dw/next-validations.svg)](https://npmjs.org/package/next-validations) 11 | ![Prerequisite](https://img.shields.io/badge/node-%3E%3D10-blue.svg) 12 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](#) 13 | [![Twitter: jellydn](https://img.shields.io/twitter/follow/jellydn.svg?style=social)](https://twitter.com/jellydn) 14 | 15 | > NextJS API Validations 16 | 17 | ## 🏠 [Homepage](https://github.com/jellydn/next-validations) 18 | 19 | ### ✨ [Demo](https://next-validations-demo.productsway.com/) 20 | 21 | ![https://gyazo.com/bf4582f7b7aa0f0ae67c4cc337c4e974.gif](https://gyazo.com/bf4582f7b7aa0f0ae67c4cc337c4e974.gif) 22 | 23 | ## Prerequisites 24 | 25 | - node >=18 26 | - nextjs >= 9 27 | 28 | ## Install 29 | 30 | ```sh 31 | yarn add next-validations 32 | ``` 33 | 34 | ## Features 35 | 36 | - **Support for Multiple Validation Libraries**: This package is designed to work seamlessly with a variety of popular validation libraries. These include [Yup](https://github.com/jquense/yup), [Fastest-Validator](https://github.com/icebob/fastest-validator), [Joi](https://github.com/sideway/joi), [Zod](https://github.com/colinhacks/zod), and [Valibot](https://github.com/fabian-hiller/valibot). This means you can choose the library that best suits your project's needs. 37 | 38 | - **Integration with TypeSchema**: `next-validations` integrates with [TypeSchema - Universal adapter for TypeScript schema validation](https://typeschema.com/). This allows for even more flexibility and compatibility with additional validation libraries. 39 | 40 | ## Usage 41 | 42 | ### Validation of multiple modes 43 | 44 | ```sh 45 | yarn add yup joi next-validations @typeschema/yup @typeschema/yoi 46 | ``` 47 | 48 | ```typescript 49 | import Joi from 'joi'; 50 | import { NextApiRequest, NextApiResponse } from 'next'; 51 | import { createRouter } from 'next-connect'; 52 | import { withValidations } from 'next-validations'; 53 | import * as yup from 'yup'; 54 | 55 | const querySchema = yup.object().shape({ 56 | type: yup.string().oneOf(['email', 'sms']).required(), 57 | }); 58 | 59 | const validateQuery = { 60 | schema: querySchema, 61 | mode: 'query', 62 | } as const; 63 | 64 | const bodySchema = Joi.object({ 65 | phone: Joi.string().required(), 66 | email: Joi.string().email().required(), 67 | name: Joi.string().required(), 68 | }); 69 | 70 | const validateBody = { 71 | schema: bodySchema, 72 | mode: 'body', 73 | } as const; 74 | 75 | const validate = withValidations([validateQuery, validateBody]); 76 | 77 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 78 | res.status(200).json({ ...req.body, ...req.query }); 79 | }; 80 | 81 | export default connect().post(validate(), handler); 82 | ``` 83 | 84 | ### Validate custom API endpoint with Yup 85 | 86 | ```sh 87 | yarn add yup next-validations @typeschema/yup 88 | ``` 89 | 90 | ```typescript 91 | import { NextApiRequest, NextApiResponse } from 'next'; 92 | import { withValidation } from 'next-validations'; 93 | import * as yup from 'yup'; 94 | 95 | const schema = yup.object().shape({ 96 | name: yup.string().required(), 97 | }); 98 | 99 | const validate = withValidation({ 100 | schema, 101 | mode: 'query', 102 | }); 103 | 104 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 105 | res.status(200).json(req.query); 106 | }; 107 | 108 | const router = createRouter(); 109 | 110 | router.post(validate(), handler); 111 | 112 | export default router.handler({ 113 | onError: (err, _req, _event) => { 114 | return new NextResponse('Something broke!', { 115 | status: (err as any)?.statusCode ?? 500, 116 | }); 117 | }, 118 | }); 119 | ``` 120 | 121 | ### Validate custom API endpoint with Zod 122 | 123 | ```sh 124 | yarn add zod next-validations @typeschema/zod 125 | ``` 126 | 127 | ```typescript 128 | import { NextApiRequest, NextApiResponse } from 'next'; 129 | import { withValidation } from 'next-validations'; 130 | import { z } from 'zod'; 131 | 132 | const schema = z.object({ 133 | username: z.string().min(6), 134 | }); 135 | 136 | const validate = withValidation({ 137 | schema, 138 | mode: 'body', 139 | }); 140 | 141 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 142 | res.status(200).json(req.body); 143 | }; 144 | 145 | export default validate(handler); 146 | ``` 147 | 148 | ### Validate custom API endpoint with Valibot 149 | 150 | ```sh 151 | yarn add valibot next-validations @typeschema/valibot 152 | ``` 153 | 154 | ```typescript 155 | import { NextApiRequest, NextApiResponse } from 'next'; 156 | import { withValidation } from 'next-validations'; 157 | import * as valibot from 'valibot'; 158 | 159 | const schema = valibot.object({ 160 | name: valibot.string([valibot.minLength(4)]), 161 | }); 162 | 163 | const validate = withValidation({ 164 | schema, 165 | mode: 'query', 166 | }); 167 | 168 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 169 | res.status(200).json(req.query); 170 | }; 171 | 172 | export default validate(handler); 173 | ``` 174 | 175 | ### Validate custom API endpoint with fastest-validator 176 | 177 | ```sh 178 | yarn add fastest-validator next-validations @typeschema/fastest-validator 179 | ``` 180 | 181 | ```typescript 182 | import { NextApiRequest, NextApiResponse } from 'next'; 183 | import { withValidation } from 'next-validations'; 184 | 185 | const schema = { 186 | name: { type: 'string', min: 3, max: 255 }, 187 | email: { type: 'email' }, 188 | age: 'number', 189 | }; 190 | 191 | const validate = withValidation({ 192 | // This is fastest-validator schema, the type is not working nicely with TypeScript 193 | schema: schema as any, 194 | mode: 'body', 195 | }); 196 | 197 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 198 | res.status(200).json(req.body); 199 | }; 200 | 201 | export default validate(handler); 202 | ``` 203 | 204 | ### Validate custom API endpoint with joi 205 | 206 | ```sh 207 | yarn add joi next-connect next-validations @typeschema/joi 208 | ``` 209 | 210 | ```typescript 211 | import Joi from 'joi'; 212 | import { NextApiRequest, NextApiResponse } from 'next'; 213 | import { createRouter } from 'next-connect'; 214 | import { withValidation } from 'next-validations'; 215 | 216 | const schema = Joi.object({ 217 | dob: Joi.date().iso(), 218 | email: Joi.string().email().required(), 219 | name: Joi.string().required(), 220 | }); 221 | 222 | const validate = withValidation({ 223 | schema, 224 | mode: 'body', 225 | }); 226 | 227 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 228 | res.status(200).json(req.body); 229 | }; 230 | 231 | const router = createRouter(); 232 | 233 | router.post(validate(), handler); 234 | 235 | export default router.handler({ 236 | onError: (err, _req, _event) => { 237 | return new NextResponse('Something broke!', { 238 | status: (err as any)?.statusCode ?? 500, 239 | }); 240 | }, 241 | }); 242 | ``` 243 | 244 | ## Run tests 245 | 246 | ```sh 247 | yarn test 248 | ``` 249 | 250 | ## Author 251 | 252 | 👤 **Huynh Duc Dung** 253 | 254 | - Website: https://productsway.com/ 255 | - Twitter: [@jellydn](https://twitter.com/jellydn) 256 | - Github: [@jellydn](https://github.com/jellydn) 257 | 258 | ## Show your support 259 | 260 | Give a ⭐️ if this project helped you! 261 | 262 | [![kofi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/dunghd) 263 | [![paypal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/dunghd) 264 | [![buymeacoffee](https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/dunghd) 265 | 266 | ## Star History 267 | 268 | [![Star History Chart](https://api.star-history.com/svg?repos=jellydn/next-validations&type=Date)](https://star-history.com/#jellydn/next-validations&Date) 269 | 270 | ## Contributors ✨ 271 | 272 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 |
Dung Duc Huynh (Kaka)
Dung Duc Huynh (Kaka)

💻 📖
Alexis Rico
Alexis Rico

💻
André Costa
André Costa

💻
286 | 287 | 288 | 289 | 290 | 291 | 292 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 293 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/hof.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`withValidation > should return function 1`] = `[Function]`; 4 | 5 | exports[`withValidations > should return function 1`] = `[Function]`; 6 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/validation.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Valibot Validation > should return false 1`] = `[AggregateError: Assertion failed]`; 4 | 5 | exports[`Yoi Validation > should return false 1`] = `[AggregateError: Assertion failed]`; 6 | 7 | exports[`Yup Validation > should return false 1`] = `[AggregateError: Assertion failed]`; 8 | 9 | exports[`Zod Validation > should return false 1`] = `[AggregateError: Assertion failed]`; 10 | -------------------------------------------------------------------------------- /__tests__/hof.test.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | import { withValidation, withValidations } from '../src'; 3 | 4 | const schema = yup.object().shape({ 5 | name: yup.string().required(), 6 | }); 7 | 8 | describe('withValidation', () => { 9 | it('should return function', () => { 10 | expect(withValidation({ schema, mode: 'query' })).toMatchSnapshot(); 11 | }); 12 | }); 13 | 14 | describe('withValidations', () => { 15 | it('should return function', () => { 16 | expect(withValidations([{ schema, mode: 'body' }])).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /__tests__/validation.test.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import * as valibot from 'valibot'; 3 | import * as yup from 'yup'; 4 | import { z } from 'zod'; 5 | import { typeschemaResolver } from '../src/resolver'; 6 | 7 | describe('Yup Validation', () => { 8 | it('should return true', async () => { 9 | const schema = yup.object().shape({ 10 | name: yup.string().required(), 11 | age: yup.number().required().positive().integer(), 12 | email: yup.string().email(), 13 | website: yup.string().url(), 14 | createdOn: yup.date().default(function () { 15 | return new Date(); 16 | }), 17 | }); 18 | const resolver = typeschemaResolver(schema); 19 | const isValid = await resolver.validate({ 20 | name: 'jimmy', 21 | age: 24, 22 | }); 23 | expect(isValid).toBeTruthy(); 24 | }); 25 | 26 | it('should return false', async () => { 27 | const schema = yup.object().shape({ 28 | name: yup.string().required(), 29 | age: yup.number().required().positive().integer(), 30 | email: yup.string().email(), 31 | website: yup.string().url(), 32 | createdOn: yup.date().default(function () { 33 | return new Date(); 34 | }), 35 | }); 36 | const resolver = typeschemaResolver(schema); 37 | try { 38 | await resolver.validate({ 39 | name: 'jimmy', 40 | }); 41 | } catch (error) { 42 | expect(error).toMatchSnapshot(); 43 | } 44 | }); 45 | }); 46 | 47 | describe('Yoi Validation', () => { 48 | it('should return true', async () => { 49 | const schema = Joi.object({ 50 | dob: Joi.date().iso(), 51 | email: Joi.string().email().required(), 52 | name: Joi.string().required(), 53 | }); 54 | 55 | const resolver = typeschemaResolver(schema); 56 | const isValid = await resolver.validate({ 57 | name: 'Huynh Duc Dung', 58 | email: 'dung@productsway.com', 59 | dob: 1988, 60 | }); 61 | expect(isValid).toBeTruthy(); 62 | }); 63 | 64 | it('should return false', async () => { 65 | const schema = Joi.object({ 66 | dob: Joi.date().iso(), 67 | email: Joi.string().email().required(), 68 | name: Joi.string().required(), 69 | }); 70 | const resolver = typeschemaResolver(schema); 71 | try { 72 | await resolver.validate({}); 73 | } catch (error) { 74 | expect(error).toMatchSnapshot(); 75 | } 76 | }); 77 | }); 78 | 79 | describe('Zod Validation', () => { 80 | it('should return true', async () => { 81 | const schema = z.object({ 82 | username: z.string(), 83 | }); 84 | 85 | const resolver = typeschemaResolver(schema); 86 | const isValid = await resolver.validate({ 87 | username: 'jellydn', 88 | }); 89 | expect(isValid).toBeTruthy(); 90 | }); 91 | 92 | it('should return false', async () => { 93 | const schema = z.object({ 94 | username: z.string().min(8), 95 | }); 96 | const resolver = typeschemaResolver(schema); 97 | try { 98 | await resolver.validate({ 99 | username: 'jellydn', 100 | }); 101 | } catch (error) { 102 | expect(error).toMatchSnapshot(); 103 | } 104 | }); 105 | }); 106 | 107 | describe('Valibot Validation', () => { 108 | it('should return true', async () => { 109 | const schema = valibot.object({ 110 | username: valibot.string(), 111 | }); 112 | 113 | const resolver = typeschemaResolver(schema); 114 | const isValid = await resolver.validate({ 115 | username: 'jellydn', 116 | }); 117 | expect(isValid).toBeTruthy(); 118 | }); 119 | 120 | it('should return false', async () => { 121 | const schema = valibot.object({ 122 | age: valibot.number(), 123 | }); 124 | const resolver = typeschemaResolver(schema); 125 | try { 126 | await resolver.validate({ 127 | age: '35', 128 | }); 129 | } catch (error) { 130 | expect(error).toMatchSnapshot(); 131 | } 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.7.2/schema.json", 3 | "vcs": { 4 | "clientKind": "git", 5 | "enabled": true, 6 | "useIgnoreFile": true, 7 | "defaultBranch": "main" 8 | }, 9 | "formatter": { 10 | "enabled": true, 11 | "formatWithErrors": false, 12 | "indentStyle": "space", 13 | "indentWidth": 2, 14 | "lineEnding": "lf", 15 | "lineWidth": 80, 16 | "attributePosition": "auto" 17 | }, 18 | "organizeImports": { "enabled": true }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": false, 23 | "a11y": { "noBlankTarget": "error", "useButtonType": "error" }, 24 | "complexity": { 25 | "noBannedTypes": "error", 26 | "noExtraBooleanCast": "error", 27 | "noMultipleSpacesInRegularExpressionLiterals": "error", 28 | "noStaticOnlyClass": "error", 29 | "noUselessCatch": "error", 30 | "noUselessConstructor": "error", 31 | "noUselessEmptyExport": "error", 32 | "noUselessFragments": "error", 33 | "noUselessLabel": "error", 34 | "noUselessLoneBlockStatements": "error", 35 | "noUselessRename": "error", 36 | "noUselessTernary": "error", 37 | "noUselessThisAlias": "error", 38 | "noUselessTypeConstraint": "error", 39 | "noVoid": "error", 40 | "noWith": "error", 41 | "useArrowFunction": "error", 42 | "useLiteralKeys": "error", 43 | "useOptionalChain": "error", 44 | "useRegexLiterals": "error" 45 | }, 46 | "correctness": { 47 | "noChildrenProp": "error", 48 | "noConstAssign": "error", 49 | "noConstantCondition": "error", 50 | "noConstructorReturn": "error", 51 | "noEmptyCharacterClassInRegex": "error", 52 | "noEmptyPattern": "error", 53 | "noGlobalObjectCalls": "error", 54 | "noInnerDeclarations": "error", 55 | "noInvalidConstructorSuper": "error", 56 | "noInvalidNewBuiltin": "error", 57 | "noNonoctalDecimalEscape": "error", 58 | "noPrecisionLoss": "error", 59 | "noSelfAssign": "error", 60 | "noSetterReturn": "error", 61 | "noSwitchDeclarations": "error", 62 | "noUndeclaredVariables": "error", 63 | "noUnreachable": "error", 64 | "noUnreachableSuper": "error", 65 | "noUnsafeFinally": "error", 66 | "noUnsafeOptionalChaining": "error", 67 | "noUnusedLabels": "error", 68 | "noUnusedVariables": "error", 69 | "noVoidElementsWithChildren": "error", 70 | "useExhaustiveDependencies": "warn", 71 | "useHookAtTopLevel": "error", 72 | "useIsNan": "error", 73 | "useJsxKeyInIterable": "error", 74 | "useValidForDirection": "error", 75 | "useYield": "error" 76 | }, 77 | "security": { 78 | "noDangerouslySetInnerHtml": "error", 79 | "noDangerouslySetInnerHtmlWithChildren": "error", 80 | "noGlobalEval": "error" 81 | }, 82 | "style": { 83 | "noArguments": "error", 84 | "noCommaOperator": "error", 85 | "noImplicitBoolean": "error", 86 | "noInferrableTypes": "error", 87 | "noNamespace": "error", 88 | "noNegationElse": "error", 89 | "noParameterProperties": "error", 90 | "noRestrictedGlobals": { 91 | "level": "error", 92 | "options": { "deniedGlobals": ["event", "atob", "btoa"] } 93 | }, 94 | "noUselessElse": "error", 95 | "noVar": "error", 96 | "useAsConstAssertion": "error", 97 | "useBlockStatements": "error", 98 | "useCollapsedElseIf": "error", 99 | "useConsistentArrayType": { 100 | "level": "error", 101 | "options": { "syntax": "shorthand" } 102 | }, 103 | "useConst": "error", 104 | "useDefaultParameterLast": "error", 105 | "useExponentiationOperator": "error", 106 | "useExportType": "error", 107 | "useForOf": "error", 108 | "useFragmentSyntax": "error", 109 | "useImportType": "error", 110 | "useLiteralEnumMembers": "error", 111 | "useNamingConvention": { 112 | "level": "warn", 113 | "options": { "strictCase": false } 114 | }, 115 | "useNumericLiterals": "error", 116 | "useShorthandAssign": "error", 117 | "useShorthandFunctionType": "error", 118 | "useSingleVarDeclarator": "error" 119 | }, 120 | "suspicious": { 121 | "noArrayIndexKey": "error", 122 | "noAssignInExpressions": "error", 123 | "noAsyncPromiseExecutor": "error", 124 | "noCatchAssign": "error", 125 | "noClassAssign": "error", 126 | "noCommentText": "error", 127 | "noCompareNegZero": "error", 128 | "noConfusingLabels": "error", 129 | "noControlCharactersInRegex": "error", 130 | "noDebugger": "error", 131 | "noDoubleEquals": "error", 132 | "noDuplicateCase": "error", 133 | "noDuplicateClassMembers": "error", 134 | "noDuplicateJsxProps": "error", 135 | "noDuplicateObjectKeys": "error", 136 | "noDuplicateParameters": "error", 137 | "noEmptyBlockStatements": "error", 138 | "noEmptyInterface": "error", 139 | "noExtraNonNullAssertion": "error", 140 | "noFallthroughSwitchClause": "error", 141 | "noFunctionAssign": "error", 142 | "noGlobalAssign": "error", 143 | "noImportAssign": "error", 144 | "noLabelVar": "error", 145 | "noMisleadingCharacterClass": "error", 146 | "noMisleadingInstantiator": "error", 147 | "noPrototypeBuiltins": "error", 148 | "noRedeclare": "error", 149 | "noSelfCompare": "error", 150 | "noShadowRestrictedNames": "error", 151 | "noUnsafeDeclarationMerging": "error", 152 | "noUnsafeNegation": "error", 153 | "useDefaultSwitchClauseLast": "error", 154 | "useGetterReturn": "error", 155 | "useNamespaceKeyword": "error", 156 | "useValidTypeof": "error" 157 | } 158 | }, 159 | "ignore": [ 160 | "dist", 161 | ".eslintrc.cjs", 162 | "vite.config.ts", 163 | "vitest.config.ts", 164 | "__tests__" 165 | ] 166 | }, 167 | "javascript": { 168 | "formatter": { 169 | "jsxQuoteStyle": "double", 170 | "quoteProperties": "asNeeded", 171 | "trailingComma": "es5", 172 | "semicolons": "always", 173 | "arrowParentheses": "always", 174 | "bracketSpacing": true, 175 | "bracketSameLine": false, 176 | "quoteStyle": "single", 177 | "attributePosition": "auto" 178 | } 179 | }, 180 | "overrides": [ 181 | { 182 | "include": ["**/*.d.ts"], 183 | "linter": { "rules": { "correctness": { "noUnusedVariables": "off" } } } 184 | }, 185 | { "include": ["**/*.test-d.ts"], "linter": { "rules": {} } }, 186 | { 187 | "include": ["**/*.tsx"], 188 | "linter": { 189 | "rules": { 190 | "style": { 191 | "useNamingConvention": { 192 | "level": "error", 193 | "options": { "strictCase": false } 194 | } 195 | } 196 | } 197 | } 198 | } 199 | ] 200 | } 201 | -------------------------------------------------------------------------------- /cspell-tool.txt: -------------------------------------------------------------------------------- 1 | valibot 2 | typeschema 3 | Huynh 4 | jellydn 5 | Valibot 6 | Changie 7 | vfgts 8 | isarray 9 | aashutoshrathi 10 | braintree 11 | caseless 12 | typedarray 13 | isstream 14 | espree 15 | minimatch 16 | hapi 17 | hoek 18 | humanwhocodes 19 | isaacs 20 | nodelib 21 | scandir 22 | fastq 23 | pkgjs 24 | rushstack 25 | corejs 26 | apidom 27 | ramda 28 | unraw 29 | stampit 30 | asyncapi 31 | axios 32 | undici 33 | csstype 34 | arktype 35 | deepkit 36 | runtypes 37 | superstruct 38 | suretype 39 | typebox 40 | valita 41 | gcornut 42 | sodaru 43 | estree 44 | globby 45 | ungap 46 | yarnpkg 47 | sprintf 48 | dequal 49 | unscopables 50 | tweetnacl 51 | concat 52 | streamsearch 53 | xvfb 54 | sinonjs 55 | cachedir 56 | eventemitter 57 | execa 58 | getos 59 | listr 60 | minimist 61 | ospath 62 | untildify 63 | yauzl 64 | gopd 65 | esutils 66 | jsbn 67 | tapable 68 | arraybuffer 69 | tostringtag 70 | globalthis 71 | proto 72 | hasown 73 | weakref 74 | trimend 75 | trimstart 76 | asynciterator 77 | findlastindex 78 | flatmap 79 | fromentries 80 | groupby 81 | axobject 82 | damerau 83 | findlast 84 | toreversed 85 | tosorted 86 | estraverse 87 | matchall 88 | esrecurse 89 | regexpp 90 | esquery 91 | graphemer 92 | imurmurhash 93 | jsonify 94 | levn 95 | optionator 96 | pify 97 | reusify 98 | keyv 99 | rimraf 100 | asynckit 101 | jsonfile 102 | universalify 103 | jackspeak 104 | minipass 105 | realpath 106 | jsprim 107 | sshpk 108 | wrappy 109 | envify 110 | bigints 111 | extglob 112 | getprototypeof 113 | cliui 114 | parseargs 115 | topo 116 | argparse 117 | extsprintf 118 | verror 119 | subtag 120 | colorette 121 | rfdc 122 | yallist 123 | picomatch 124 | regexparam 125 | opentelemetry 126 | postcss 127 | msvc 128 | domexception 129 | memorystream 130 | pidtree 131 | yocto 132 | callsites 133 | klaw 134 | picocolors 135 | libc 136 | mkdirp 137 | napi 138 | xtend 139 | drange 140 | lowlight 141 | prismjs 142 | hastscript 143 | autolinker 144 | throttleit 145 | iojs 146 | microtask 147 | fullwidth 148 | pbkdf 149 | dashdash 150 | getpass 151 | eastasianwidth 152 | deepmerge 153 | classnames 154 | dompurify 155 | randexp 156 | randombytes 157 | proptypes 158 | zenscroll 159 | chownr 160 | tmpdir 161 | punycode 162 | prebuild 163 | toolbelt 164 | querystringify 165 | finalizationregistry 166 | weakmap 167 | weakset 168 | isexe 169 | toposort 170 | Petstore 171 | freenode 172 | petstore 173 | Bitstream 174 | tsup 175 | skypack 176 | trivago 177 | productsway 178 | metafile 179 | vitest 180 | typedoc 181 | vite 182 | jsesc 183 | jridgewell 184 | bcoe 185 | esbuild 186 | loong 187 | riscv 188 | sunos 189 | hutson 190 | istanbuljs 191 | yargs 192 | tsdoc 193 | rollup 194 | sindresorhus 195 | kleur 196 | pathe 197 | tinyspy 198 | fflate 199 | sirv 200 | jsonparse 201 | camelcase 202 | pathval 203 | anymatch 204 | readdirp 205 | fsevents 206 | dateformat 207 | codemirror 208 | conventionalcommits 209 | jquery 210 | ismatch 211 | decamelize 212 | arrayish 213 | dargs 214 | gitconfiglocal 215 | wordwrap 216 | mlly 217 | redent 218 | arrify 219 | thenify 220 | lilconfig 221 | nextick 222 | eabi 223 | gnueabihf 224 | mrmime 225 | totalist 226 | chokidar 227 | jiti 228 | nanospinner 229 | whatwg 230 | dotgitignore 231 | npmcli 232 | joycon 233 | shiki 234 | lightningcss 235 | sugarss 236 | jsdom 237 | tinybench 238 | tinypool 239 | sortby 240 | webidl 241 | siginfo 242 | stackback 243 | kofi 244 | buymeacoffee 245 | André 246 | automerge 247 | sweepai 248 | tsconfigs 249 | duplicative 250 | tsdx -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "language": "en", 5 | "globRoot": ".", 6 | "dictionaryDefinitions": [ 7 | { 8 | "name": "cspell-tool", 9 | "path": "./cspell-tool.txt", 10 | "addWords": true 11 | } 12 | ], 13 | "dictionaries": [ 14 | "cspell-tool" 15 | ], 16 | "ignorePaths": [ 17 | "node_modules", 18 | "dist", 19 | "build", 20 | "/cspell-tool.txt" 21 | ] 22 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /example/.vercelignore: -------------------------------------------------------------------------------- 1 | cypress -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Link package 4 | ```sh 5 | npx link 6 | 7 | ``` 8 | 9 | ## Getting Started 10 | 11 | First, run the development server: 12 | 13 | ```bash 14 | npm run dev 15 | # or 16 | yarn dev 17 | ``` 18 | 19 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 20 | 21 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 22 | 23 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 24 | 25 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 26 | 27 | ## Learn More 28 | 29 | To learn more about Next.js, take a look at the following resources: 30 | 31 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 32 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 33 | 34 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 35 | 36 | ## Deploy on Vercel 37 | 38 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 39 | 40 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 41 | -------------------------------------------------------------------------------- /example/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | projectId: '7vfgts', 5 | e2e: { 6 | // We've imported your old cypress plugins here. 7 | // You may want to clean this up later by importing these. 8 | setupNodeEvents(on, config) { 9 | return require('./cypress/plugins/index.ts')(on, config) 10 | }, 11 | baseUrl: 'http://localhost:3000', 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /example/cypress/e2e/contact.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export {}; 4 | 5 | context("/api/contact", () => { 6 | beforeEach(() => { 7 | cy.visit("/"); 8 | }); 9 | 10 | it("should get error when send empty data", () => { 11 | return cy.request({ 12 | method: "POST", 13 | url: "/api/contact", 14 | failOnStatusCode: false, 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | body: JSON.stringify({ 19 | name: "Dung", 20 | }), 21 | }).then((response) => { 22 | expect(response.status).equal(400); 23 | }); 24 | }); 25 | 26 | it("should say contact a new user when send valid data", () => { 27 | return cy.request({ 28 | method: "POST", 29 | url: "/api/contact", 30 | headers: { 31 | "Content-Type": "application/json", 32 | }, 33 | body: JSON.stringify({ 34 | name: "Dung", 35 | email: "dung@productsway.com", 36 | dob: 1988, 37 | }), 38 | }).then((response) => { 39 | expect(response.status).equal(200); 40 | expect(response.body.name).equal("Dung"); 41 | expect(response.body.email).equal("dung@productsway.com"); 42 | expect(response.body.dob).equal(1988); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /example/cypress/e2e/greeter.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export { }; 4 | 5 | context("/api/greeter", () => { 6 | beforeEach(() => { 7 | cy.visit("/"); 8 | }); 9 | 10 | it("should get error when navigate to /api/greeter", () => { 11 | return cy.request({ 12 | method: "GET", 13 | url: "/api/greeter", 14 | failOnStatusCode: false, 15 | }).then((response) => { 16 | expect(response.status).equal(400); 17 | }); 18 | }); 19 | 20 | it("should say greeter name when navigate to /api/greeter?name=Dung", () => { 21 | return cy.request({ 22 | method: "GET", 23 | url: "/api/greeter?name=Dung", 24 | }).then((response) => { 25 | expect(response.status).equal(200); 26 | expect(response.body.name).equal("Dung"); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /example/cypress/e2e/hello.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export {}; 4 | 5 | context("/api/hello", () => { 6 | beforeEach(() => { 7 | cy.visit("/"); 8 | }); 9 | 10 | it("should get error when navigate to /api/hello", () => { 11 | return cy.request({ 12 | method: "GET", 13 | url: "/api/hello", 14 | failOnStatusCode: false, 15 | }).then((response) => { 16 | expect(response.status).equal(400); 17 | }); 18 | }); 19 | 20 | it("should say hello name when navigate to /api/hello?name=Dung", () => { 21 | return cy.request({ 22 | method: "GET", 23 | url: "/api/hello?name=Dung", 24 | }).then((response) => { 25 | expect(response.status).equal(200); 26 | expect(response.body.name).equal("Dung"); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /example/cypress/e2e/register.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export {}; 4 | 5 | context("/api/register", () => { 6 | beforeEach(() => { 7 | cy.visit("/"); 8 | }); 9 | 10 | it("should get error when send empty data", async () => { 11 | cy.request({ 12 | method: "POST", 13 | url: "/api/register", 14 | failOnStatusCode: false, 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | body: JSON.stringify({}), 19 | }).then((response) => { 20 | expect(response.status).equal(400); 21 | }); 22 | }); 23 | 24 | it("should say register a new user when send valid data", async () => { 25 | cy.request({ 26 | method: "POST", 27 | url: "/api/register", 28 | headers: { 29 | "Content-Type": "application/json", 30 | }, 31 | body: JSON.stringify({ 32 | name: "Dung", 33 | email: "dung@productsway.com", 34 | age: 34, 35 | }), 36 | }).then((response) => { 37 | expect(response.status).equal(200); 38 | expect(response.body.name).equal("Dung"); 39 | expect(response.body.email).equal("dung@productsway.com"); 40 | expect(response.body.age).equal(34); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /example/cypress/e2e/support.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export {}; 4 | 5 | context("/api/support", () => { 6 | beforeEach(() => { 7 | cy.visit("/"); 8 | }); 9 | 10 | it("should get error if missing type on query", () => { 11 | return cy.request({ 12 | method: "POST", 13 | url: "/api/support", 14 | failOnStatusCode: false, 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | body: JSON.stringify({ 19 | name: "Dung", 20 | }), 21 | }).then((response) => { 22 | expect(response.status).equal(400); 23 | }); 24 | }); 25 | 26 | it("should get error when send invalid form data", () => { 27 | return cy.request({ 28 | method: "POST", 29 | url: "/api/support?type=sms", 30 | failOnStatusCode: false, 31 | headers: { 32 | "Content-Type": "application/json", 33 | }, 34 | body: JSON.stringify({ 35 | name: "Dung", 36 | phone: "12345679", 37 | }), 38 | }).then((response) => { 39 | expect(response.status).equal(400); 40 | }); 41 | }); 42 | 43 | it("should return support information on valid data", () => { 44 | return cy.request({ 45 | method: "POST", 46 | url: "/api/support?type=email", 47 | headers: { 48 | "Content-Type": "application/json", 49 | }, 50 | body: JSON.stringify({ 51 | name: "Dung", 52 | email: "dung@productsway.com", 53 | phone: "11223344", 54 | }), 55 | }).then((response) => { 56 | expect(response.status).equal(200); 57 | expect(response.body.name).equal("Dung"); 58 | expect(response.body.email).equal("dung@productsway.com"); 59 | expect(response.body.phone).equal("11223344"); 60 | expect(response.body.type).equal("email"); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /example/cypress/e2e/user.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export {}; 4 | 5 | context("/api/user", () => { 6 | beforeEach(() => { 7 | cy.visit("/"); 8 | }); 9 | 10 | it("should get error when navigate to /api/user", () => { 11 | return cy.request({ 12 | method: "POST", 13 | url: "/api/user", 14 | body: { 15 | username: "jelly", 16 | }, 17 | failOnStatusCode: false, 18 | }).then((response) => { 19 | expect(response.status).equal(400); 20 | }); 21 | }); 22 | 23 | it("should say user name when navigate to /api/user", () => { 24 | return cy.request({ 25 | method: "POST", 26 | url: "/api/user", 27 | body: { 28 | username: "jellydn", 29 | }, 30 | }).then((response) => { 31 | expect(response.status).equal(200); 32 | expect(response.body.username).equal("jellydn"); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /example/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /example/cypress/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | }; 23 | 24 | export {}; 25 | -------------------------------------------------------------------------------- /example/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | 27 | export {}; 28 | -------------------------------------------------------------------------------- /example/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /example/link.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "../" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "cypress:install": "cypress install", 8 | "cypress:open": "cypress open", 9 | "dev": "next dev", 10 | "start": "next start", 11 | "test": "run-p -l -r test:e2e dev", 12 | "test:e2e": "wait-on http://localhost:3000 && cypress run" 13 | }, 14 | "dependencies": { 15 | "@typeschema/fastest-validator": "0.2.0", 16 | "@typeschema/joi": "0.14.0", 17 | "@typeschema/valibot": "0.14.0", 18 | "@typeschema/yup": "0.14.0", 19 | "@typeschema/zod": "0.14.0", 20 | "caniuse-lite": "1.0.30001721", 21 | "fastest-validator": "1.19.1", 22 | "isarray": "2.0.5", 23 | "joi": "17.13.3", 24 | "next": "15.3.3", 25 | "next-connect": "1.0.0", 26 | "next-validations": "1.1.0", 27 | "qs": "6.14.0", 28 | "react": "19.1.0", 29 | "react-dom": "19.1.0", 30 | "swagger-ui-react": "5.22.0", 31 | "valibot": "1.1.0", 32 | "yup": "1.6.1", 33 | "zod": "3.25.51" 34 | }, 35 | "devDependencies": { 36 | "@types/react": "19.1.6", 37 | "cypress": "14.4.1", 38 | "npm-run-all2": "8.0.4", 39 | "typescript": "5.8.3", 40 | "wait-on": "8.0.3" 41 | }, 42 | "packageManager": "pnpm@10.11.1" 43 | } 44 | -------------------------------------------------------------------------------- /example/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return ; 5 | } 6 | 7 | export default MyApp; 8 | -------------------------------------------------------------------------------- /example/pages/api/contact.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import type { NextApiRequest, NextApiResponse } from 'next'; 3 | import { createRouter } from 'next-connect'; 4 | import { withValidation } from 'next-validations'; 5 | import { NextResponse } from 'next/server'; 6 | 7 | const schema = Joi.object({ 8 | dob: Joi.date().iso(), 9 | email: Joi.string().email().required(), 10 | name: Joi.string().required(), 11 | }); 12 | 13 | const validate = withValidation({ 14 | schema, 15 | mode: 'body', 16 | }); 17 | 18 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 19 | res.status(200).json(req.body); 20 | }; 21 | 22 | const router = createRouter(); 23 | 24 | router.post(validate(), handler); 25 | 26 | export default router.handler({ 27 | // @ts-expect-error │  Type '(err: unknown) => NextResponse' is not assignable to type '(err: unknown, req: IncomingMessage, res: ServerResponse) => ValueOrPromise'. Type 'NextR typescript (2322) [41, 3] 28 | onError: (err) => { 29 | return new NextResponse('Something broke!', { 30 | status: (err as any)?.statusCode ?? 500, 31 | }); 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /example/pages/api/greeter.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import { withValidation } from 'next-validations'; 3 | import * as valibot from 'valibot'; 4 | 5 | const schema = valibot.object({ 6 | name: valibot.pipe(valibot.string(), valibot.minLength(4)), 7 | }); 8 | 9 | const validate = withValidation({ 10 | schema, 11 | mode: 'query', 12 | }); 13 | 14 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 15 | res.status(200).json(req.query); 16 | }; 17 | 18 | export default validate(handler); 19 | -------------------------------------------------------------------------------- /example/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import { withValidation } from 'next-validations'; 3 | import * as yup from 'yup'; 4 | 5 | const schema = yup.object().shape({ 6 | name: yup.string().required(), 7 | }); 8 | 9 | const validate = withValidation({ 10 | schema, 11 | mode: 'query', 12 | }); 13 | 14 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 15 | res.status(200).json(req.query); 16 | }; 17 | 18 | export default validate(handler); 19 | -------------------------------------------------------------------------------- /example/pages/api/register.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import { withValidation } from 'next-validations'; 3 | 4 | // This is fastest-validator schema 5 | const schema = { 6 | name: { type: 'string', min: 3, max: 255 }, 7 | email: { type: 'email' }, 8 | age: 'number', 9 | }; 10 | 11 | const validate = withValidation({ 12 | // This is fastest-validator schema, the type is not working nicely with TypeScript 13 | schema: schema as any, 14 | mode: 'body', 15 | }); 16 | 17 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 18 | res.status(200).json(req.body); 19 | }; 20 | 21 | export default validate(handler); 22 | -------------------------------------------------------------------------------- /example/pages/api/support.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import type { NextApiRequest, NextApiResponse } from 'next'; 3 | import { createRouter } from 'next-connect'; 4 | import { withValidations } from 'next-validations'; 5 | import { NextResponse } from 'next/server'; 6 | import * as yup from 'yup'; 7 | 8 | const querySchema = yup.object().shape({ 9 | type: yup.string().oneOf(['email', 'sms']).required(), 10 | }); 11 | 12 | const validateQuery = { 13 | schema: querySchema, 14 | type: 'Yup', 15 | mode: 'query', 16 | } as const; 17 | 18 | const bodySchema = Joi.object({ 19 | phone: Joi.string().required(), 20 | email: Joi.string().email().required(), 21 | name: Joi.string().required(), 22 | }); 23 | 24 | const validateBody = { 25 | schema: bodySchema, 26 | type: 'Joi', 27 | mode: 'body', 28 | } as const; 29 | 30 | const validate = withValidations([validateQuery, validateBody]); 31 | 32 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 33 | res.status(200).json({ ...req.body, ...req.query }); 34 | }; 35 | 36 | const router = createRouter(); 37 | 38 | router.post(validate(), handler); 39 | 40 | export default router.handler({ 41 | // @ts-expect-error │  Type '(err: unknown) => NextResponse' is not assignable to type '(err: unknown, req: IncomingMessage, res: ServerResponse) => ValueOrPromise'. Type 'NextR typescript (2322) [41, 3] 42 | onError: (err) => { 43 | return new NextResponse('Something broke!', { 44 | status: (err as any)?.statusCode ?? 500, 45 | }); 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /example/pages/api/user.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | 3 | import { z } from 'zod'; 4 | import { withValidation } from 'next-validations'; 5 | 6 | const schema = z.object({ 7 | username: z.string().min(6), 8 | }); 9 | 10 | const validate = withValidation({ 11 | schema, 12 | mode: 'body', 13 | }); 14 | 15 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 16 | res.status(200).json(req.body); 17 | }; 18 | 19 | export default validate(handler); 20 | -------------------------------------------------------------------------------- /example/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Image from 'next/image'; 3 | import Link from 'next/link'; 4 | 5 | import styles from '../styles/Home.module.css'; 6 | 7 | export default function Home() { 8 | return ( 9 |
10 | 11 | Next Validations Demo App 12 | 13 | 14 | 15 | 16 |
17 |

18 | Welcome to{' '} 19 | 20 | Next-Validations 21 | 22 |

23 | 24 | 49 |
50 | 51 | 63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /example/pages/playground.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import 'swagger-ui-react/swagger-ui.css'; 3 | 4 | const SwaggerUI = dynamic<{ url: string }>(import('swagger-ui-react'), { 5 | ssr: false, 6 | }); 7 | 8 | const Playground = () => { 9 | return ; 10 | }; 11 | 12 | export default Playground; 13 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/next-validations/150b83f229c6757a92203acae5124fedac5f4c96/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", 5 | "version": "1.0.5", 6 | "title": "Swagger Petstore", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "email": "apiteam@swagger.io" 10 | }, 11 | "license": { 12 | "name": "Apache 2.0", 13 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 14 | } 15 | }, 16 | "host": "petstore.swagger.io", 17 | "basePath": "/v2", 18 | "tags": [ 19 | { 20 | "name": "pet", 21 | "description": "Everything about your Pets", 22 | "externalDocs": { 23 | "description": "Find out more", 24 | "url": "http://swagger.io" 25 | } 26 | }, 27 | { 28 | "name": "store", 29 | "description": "Access to Petstore orders" 30 | }, 31 | { 32 | "name": "user", 33 | "description": "Operations about user", 34 | "externalDocs": { 35 | "description": "Find out more about our store", 36 | "url": "http://swagger.io" 37 | } 38 | } 39 | ], 40 | "schemes": [ 41 | "https", 42 | "http" 43 | ], 44 | "paths": { 45 | "/pet/{petId}/uploadImage": { 46 | "post": { 47 | "tags": [ 48 | "pet" 49 | ], 50 | "summary": "uploads an image", 51 | "description": "", 52 | "operationId": "uploadFile", 53 | "consumes": [ 54 | "multipart/form-data" 55 | ], 56 | "produces": [ 57 | "application/json" 58 | ], 59 | "parameters": [ 60 | { 61 | "name": "petId", 62 | "in": "path", 63 | "description": "ID of pet to update", 64 | "required": true, 65 | "type": "integer", 66 | "format": "int64" 67 | }, 68 | { 69 | "name": "additionalMetadata", 70 | "in": "formData", 71 | "description": "Additional data to pass to server", 72 | "required": false, 73 | "type": "string" 74 | }, 75 | { 76 | "name": "file", 77 | "in": "formData", 78 | "description": "file to upload", 79 | "required": false, 80 | "type": "file" 81 | } 82 | ], 83 | "responses": { 84 | "200": { 85 | "description": "successful operation", 86 | "schema": { 87 | "$ref": "#/definitions/ApiResponse" 88 | } 89 | } 90 | }, 91 | "security": [ 92 | { 93 | "petstore_auth": [ 94 | "write:pets", 95 | "read:pets" 96 | ] 97 | } 98 | ] 99 | } 100 | }, 101 | "/pet": { 102 | "post": { 103 | "tags": [ 104 | "pet" 105 | ], 106 | "summary": "Add a new pet to the store", 107 | "description": "", 108 | "operationId": "addPet", 109 | "consumes": [ 110 | "application/json", 111 | "application/xml" 112 | ], 113 | "produces": [ 114 | "application/json", 115 | "application/xml" 116 | ], 117 | "parameters": [ 118 | { 119 | "in": "body", 120 | "name": "body", 121 | "description": "Pet object that needs to be added to the store", 122 | "required": true, 123 | "schema": { 124 | "$ref": "#/definitions/Pet" 125 | } 126 | } 127 | ], 128 | "responses": { 129 | "405": { 130 | "description": "Invalid input" 131 | } 132 | }, 133 | "security": [ 134 | { 135 | "petstore_auth": [ 136 | "write:pets", 137 | "read:pets" 138 | ] 139 | } 140 | ] 141 | }, 142 | "put": { 143 | "tags": [ 144 | "pet" 145 | ], 146 | "summary": "Update an existing pet", 147 | "description": "", 148 | "operationId": "updatePet", 149 | "consumes": [ 150 | "application/json", 151 | "application/xml" 152 | ], 153 | "produces": [ 154 | "application/json", 155 | "application/xml" 156 | ], 157 | "parameters": [ 158 | { 159 | "in": "body", 160 | "name": "body", 161 | "description": "Pet object that needs to be added to the store", 162 | "required": true, 163 | "schema": { 164 | "$ref": "#/definitions/Pet" 165 | } 166 | } 167 | ], 168 | "responses": { 169 | "400": { 170 | "description": "Invalid ID supplied" 171 | }, 172 | "404": { 173 | "description": "Pet not found" 174 | }, 175 | "405": { 176 | "description": "Validation exception" 177 | } 178 | }, 179 | "security": [ 180 | { 181 | "petstore_auth": [ 182 | "write:pets", 183 | "read:pets" 184 | ] 185 | } 186 | ] 187 | } 188 | }, 189 | "/pet/findByStatus": { 190 | "get": { 191 | "tags": [ 192 | "pet" 193 | ], 194 | "summary": "Finds Pets by status", 195 | "description": "Multiple status values can be provided with comma separated strings", 196 | "operationId": "findPetsByStatus", 197 | "produces": [ 198 | "application/json", 199 | "application/xml" 200 | ], 201 | "parameters": [ 202 | { 203 | "name": "status", 204 | "in": "query", 205 | "description": "Status values that need to be considered for filter", 206 | "required": true, 207 | "type": "array", 208 | "items": { 209 | "type": "string", 210 | "enum": [ 211 | "available", 212 | "pending", 213 | "sold" 214 | ], 215 | "default": "available" 216 | }, 217 | "collectionFormat": "multi" 218 | } 219 | ], 220 | "responses": { 221 | "200": { 222 | "description": "successful operation", 223 | "schema": { 224 | "type": "array", 225 | "items": { 226 | "$ref": "#/definitions/Pet" 227 | } 228 | } 229 | }, 230 | "400": { 231 | "description": "Invalid status value" 232 | } 233 | }, 234 | "security": [ 235 | { 236 | "petstore_auth": [ 237 | "write:pets", 238 | "read:pets" 239 | ] 240 | } 241 | ] 242 | } 243 | }, 244 | "/pet/findByTags": { 245 | "get": { 246 | "tags": [ 247 | "pet" 248 | ], 249 | "summary": "Finds Pets by tags", 250 | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 251 | "operationId": "findPetsByTags", 252 | "produces": [ 253 | "application/json", 254 | "application/xml" 255 | ], 256 | "parameters": [ 257 | { 258 | "name": "tags", 259 | "in": "query", 260 | "description": "Tags to filter by", 261 | "required": true, 262 | "type": "array", 263 | "items": { 264 | "type": "string" 265 | }, 266 | "collectionFormat": "multi" 267 | } 268 | ], 269 | "responses": { 270 | "200": { 271 | "description": "successful operation", 272 | "schema": { 273 | "type": "array", 274 | "items": { 275 | "$ref": "#/definitions/Pet" 276 | } 277 | } 278 | }, 279 | "400": { 280 | "description": "Invalid tag value" 281 | } 282 | }, 283 | "security": [ 284 | { 285 | "petstore_auth": [ 286 | "write:pets", 287 | "read:pets" 288 | ] 289 | } 290 | ], 291 | "deprecated": true 292 | } 293 | }, 294 | "/pet/{petId}": { 295 | "get": { 296 | "tags": [ 297 | "pet" 298 | ], 299 | "summary": "Find pet by ID", 300 | "description": "Returns a single pet", 301 | "operationId": "getPetById", 302 | "produces": [ 303 | "application/json", 304 | "application/xml" 305 | ], 306 | "parameters": [ 307 | { 308 | "name": "petId", 309 | "in": "path", 310 | "description": "ID of pet to return", 311 | "required": true, 312 | "type": "integer", 313 | "format": "int64" 314 | } 315 | ], 316 | "responses": { 317 | "200": { 318 | "description": "successful operation", 319 | "schema": { 320 | "$ref": "#/definitions/Pet" 321 | } 322 | }, 323 | "400": { 324 | "description": "Invalid ID supplied" 325 | }, 326 | "404": { 327 | "description": "Pet not found" 328 | } 329 | }, 330 | "security": [ 331 | { 332 | "api_key": [] 333 | } 334 | ] 335 | }, 336 | "post": { 337 | "tags": [ 338 | "pet" 339 | ], 340 | "summary": "Updates a pet in the store with form data", 341 | "description": "", 342 | "operationId": "updatePetWithForm", 343 | "consumes": [ 344 | "application/x-www-form-urlencoded" 345 | ], 346 | "produces": [ 347 | "application/json", 348 | "application/xml" 349 | ], 350 | "parameters": [ 351 | { 352 | "name": "petId", 353 | "in": "path", 354 | "description": "ID of pet that needs to be updated", 355 | "required": true, 356 | "type": "integer", 357 | "format": "int64" 358 | }, 359 | { 360 | "name": "name", 361 | "in": "formData", 362 | "description": "Updated name of the pet", 363 | "required": false, 364 | "type": "string" 365 | }, 366 | { 367 | "name": "status", 368 | "in": "formData", 369 | "description": "Updated status of the pet", 370 | "required": false, 371 | "type": "string" 372 | } 373 | ], 374 | "responses": { 375 | "405": { 376 | "description": "Invalid input" 377 | } 378 | }, 379 | "security": [ 380 | { 381 | "petstore_auth": [ 382 | "write:pets", 383 | "read:pets" 384 | ] 385 | } 386 | ] 387 | }, 388 | "delete": { 389 | "tags": [ 390 | "pet" 391 | ], 392 | "summary": "Deletes a pet", 393 | "description": "", 394 | "operationId": "deletePet", 395 | "produces": [ 396 | "application/json", 397 | "application/xml" 398 | ], 399 | "parameters": [ 400 | { 401 | "name": "api_key", 402 | "in": "header", 403 | "required": false, 404 | "type": "string" 405 | }, 406 | { 407 | "name": "petId", 408 | "in": "path", 409 | "description": "Pet id to delete", 410 | "required": true, 411 | "type": "integer", 412 | "format": "int64" 413 | } 414 | ], 415 | "responses": { 416 | "400": { 417 | "description": "Invalid ID supplied" 418 | }, 419 | "404": { 420 | "description": "Pet not found" 421 | } 422 | }, 423 | "security": [ 424 | { 425 | "petstore_auth": [ 426 | "write:pets", 427 | "read:pets" 428 | ] 429 | } 430 | ] 431 | } 432 | }, 433 | "/store/inventory": { 434 | "get": { 435 | "tags": [ 436 | "store" 437 | ], 438 | "summary": "Returns pet inventories by status", 439 | "description": "Returns a map of status codes to quantities", 440 | "operationId": "getInventory", 441 | "produces": [ 442 | "application/json" 443 | ], 444 | "parameters": [], 445 | "responses": { 446 | "200": { 447 | "description": "successful operation", 448 | "schema": { 449 | "type": "object", 450 | "additionalProperties": { 451 | "type": "integer", 452 | "format": "int32" 453 | } 454 | } 455 | } 456 | }, 457 | "security": [ 458 | { 459 | "api_key": [] 460 | } 461 | ] 462 | } 463 | }, 464 | "/store/order": { 465 | "post": { 466 | "tags": [ 467 | "store" 468 | ], 469 | "summary": "Place an order for a pet", 470 | "description": "", 471 | "operationId": "placeOrder", 472 | "consumes": [ 473 | "application/json" 474 | ], 475 | "produces": [ 476 | "application/json", 477 | "application/xml" 478 | ], 479 | "parameters": [ 480 | { 481 | "in": "body", 482 | "name": "body", 483 | "description": "order placed for purchasing the pet", 484 | "required": true, 485 | "schema": { 486 | "$ref": "#/definitions/Order" 487 | } 488 | } 489 | ], 490 | "responses": { 491 | "200": { 492 | "description": "successful operation", 493 | "schema": { 494 | "$ref": "#/definitions/Order" 495 | } 496 | }, 497 | "400": { 498 | "description": "Invalid Order" 499 | } 500 | } 501 | } 502 | }, 503 | "/store/order/{orderId}": { 504 | "get": { 505 | "tags": [ 506 | "store" 507 | ], 508 | "summary": "Find purchase order by ID", 509 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 510 | "operationId": "getOrderById", 511 | "produces": [ 512 | "application/json", 513 | "application/xml" 514 | ], 515 | "parameters": [ 516 | { 517 | "name": "orderId", 518 | "in": "path", 519 | "description": "ID of pet that needs to be fetched", 520 | "required": true, 521 | "type": "integer", 522 | "maximum": 10, 523 | "minimum": 1, 524 | "format": "int64" 525 | } 526 | ], 527 | "responses": { 528 | "200": { 529 | "description": "successful operation", 530 | "schema": { 531 | "$ref": "#/definitions/Order" 532 | } 533 | }, 534 | "400": { 535 | "description": "Invalid ID supplied" 536 | }, 537 | "404": { 538 | "description": "Order not found" 539 | } 540 | } 541 | }, 542 | "delete": { 543 | "tags": [ 544 | "store" 545 | ], 546 | "summary": "Delete purchase order by ID", 547 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 548 | "operationId": "deleteOrder", 549 | "produces": [ 550 | "application/json", 551 | "application/xml" 552 | ], 553 | "parameters": [ 554 | { 555 | "name": "orderId", 556 | "in": "path", 557 | "description": "ID of the order that needs to be deleted", 558 | "required": true, 559 | "type": "integer", 560 | "minimum": 1, 561 | "format": "int64" 562 | } 563 | ], 564 | "responses": { 565 | "400": { 566 | "description": "Invalid ID supplied" 567 | }, 568 | "404": { 569 | "description": "Order not found" 570 | } 571 | } 572 | } 573 | }, 574 | "/user/createWithList": { 575 | "post": { 576 | "tags": [ 577 | "user" 578 | ], 579 | "summary": "Creates list of users with given input array", 580 | "description": "", 581 | "operationId": "createUsersWithListInput", 582 | "consumes": [ 583 | "application/json" 584 | ], 585 | "produces": [ 586 | "application/json", 587 | "application/xml" 588 | ], 589 | "parameters": [ 590 | { 591 | "in": "body", 592 | "name": "body", 593 | "description": "List of user object", 594 | "required": true, 595 | "schema": { 596 | "type": "array", 597 | "items": { 598 | "$ref": "#/definitions/User" 599 | } 600 | } 601 | } 602 | ], 603 | "responses": { 604 | "default": { 605 | "description": "successful operation" 606 | } 607 | } 608 | } 609 | }, 610 | "/user/{username}": { 611 | "get": { 612 | "tags": [ 613 | "user" 614 | ], 615 | "summary": "Get user by user name", 616 | "description": "", 617 | "operationId": "getUserByName", 618 | "produces": [ 619 | "application/json", 620 | "application/xml" 621 | ], 622 | "parameters": [ 623 | { 624 | "name": "username", 625 | "in": "path", 626 | "description": "The name that needs to be fetched. Use user1 for testing. ", 627 | "required": true, 628 | "type": "string" 629 | } 630 | ], 631 | "responses": { 632 | "200": { 633 | "description": "successful operation", 634 | "schema": { 635 | "$ref": "#/definitions/User" 636 | } 637 | }, 638 | "400": { 639 | "description": "Invalid username supplied" 640 | }, 641 | "404": { 642 | "description": "User not found" 643 | } 644 | } 645 | }, 646 | "put": { 647 | "tags": [ 648 | "user" 649 | ], 650 | "summary": "Updated user", 651 | "description": "This can only be done by the logged in user.", 652 | "operationId": "updateUser", 653 | "consumes": [ 654 | "application/json" 655 | ], 656 | "produces": [ 657 | "application/json", 658 | "application/xml" 659 | ], 660 | "parameters": [ 661 | { 662 | "name": "username", 663 | "in": "path", 664 | "description": "name that need to be updated", 665 | "required": true, 666 | "type": "string" 667 | }, 668 | { 669 | "in": "body", 670 | "name": "body", 671 | "description": "Updated user object", 672 | "required": true, 673 | "schema": { 674 | "$ref": "#/definitions/User" 675 | } 676 | } 677 | ], 678 | "responses": { 679 | "400": { 680 | "description": "Invalid user supplied" 681 | }, 682 | "404": { 683 | "description": "User not found" 684 | } 685 | } 686 | }, 687 | "delete": { 688 | "tags": [ 689 | "user" 690 | ], 691 | "summary": "Delete user", 692 | "description": "This can only be done by the logged in user.", 693 | "operationId": "deleteUser", 694 | "produces": [ 695 | "application/json", 696 | "application/xml" 697 | ], 698 | "parameters": [ 699 | { 700 | "name": "username", 701 | "in": "path", 702 | "description": "The name that needs to be deleted", 703 | "required": true, 704 | "type": "string" 705 | } 706 | ], 707 | "responses": { 708 | "400": { 709 | "description": "Invalid username supplied" 710 | }, 711 | "404": { 712 | "description": "User not found" 713 | } 714 | } 715 | } 716 | }, 717 | "/user/login": { 718 | "get": { 719 | "tags": [ 720 | "user" 721 | ], 722 | "summary": "Logs user into the system", 723 | "description": "", 724 | "operationId": "loginUser", 725 | "produces": [ 726 | "application/json", 727 | "application/xml" 728 | ], 729 | "parameters": [ 730 | { 731 | "name": "username", 732 | "in": "query", 733 | "description": "The user name for login", 734 | "required": true, 735 | "type": "string" 736 | }, 737 | { 738 | "name": "password", 739 | "in": "query", 740 | "description": "The password for login in clear text", 741 | "required": true, 742 | "type": "string" 743 | } 744 | ], 745 | "responses": { 746 | "200": { 747 | "description": "successful operation", 748 | "headers": { 749 | "X-Expires-After": { 750 | "type": "string", 751 | "format": "date-time", 752 | "description": "date in UTC when token expires" 753 | }, 754 | "X-Rate-Limit": { 755 | "type": "integer", 756 | "format": "int32", 757 | "description": "calls per hour allowed by the user" 758 | } 759 | }, 760 | "schema": { 761 | "type": "string" 762 | } 763 | }, 764 | "400": { 765 | "description": "Invalid username/password supplied" 766 | } 767 | } 768 | } 769 | }, 770 | "/user/logout": { 771 | "get": { 772 | "tags": [ 773 | "user" 774 | ], 775 | "summary": "Logs out current logged in user session", 776 | "description": "", 777 | "operationId": "logoutUser", 778 | "produces": [ 779 | "application/json", 780 | "application/xml" 781 | ], 782 | "parameters": [], 783 | "responses": { 784 | "default": { 785 | "description": "successful operation" 786 | } 787 | } 788 | } 789 | }, 790 | "/user/createWithArray": { 791 | "post": { 792 | "tags": [ 793 | "user" 794 | ], 795 | "summary": "Creates list of users with given input array", 796 | "description": "", 797 | "operationId": "createUsersWithArrayInput", 798 | "consumes": [ 799 | "application/json" 800 | ], 801 | "produces": [ 802 | "application/json", 803 | "application/xml" 804 | ], 805 | "parameters": [ 806 | { 807 | "in": "body", 808 | "name": "body", 809 | "description": "List of user object", 810 | "required": true, 811 | "schema": { 812 | "type": "array", 813 | "items": { 814 | "$ref": "#/definitions/User" 815 | } 816 | } 817 | } 818 | ], 819 | "responses": { 820 | "default": { 821 | "description": "successful operation" 822 | } 823 | } 824 | } 825 | }, 826 | "/user": { 827 | "post": { 828 | "tags": [ 829 | "user" 830 | ], 831 | "summary": "Create user", 832 | "description": "This can only be done by the logged in user.", 833 | "operationId": "createUser", 834 | "consumes": [ 835 | "application/json" 836 | ], 837 | "produces": [ 838 | "application/json", 839 | "application/xml" 840 | ], 841 | "parameters": [ 842 | { 843 | "in": "body", 844 | "name": "body", 845 | "description": "Created user object", 846 | "required": true, 847 | "schema": { 848 | "$ref": "#/definitions/User" 849 | } 850 | } 851 | ], 852 | "responses": { 853 | "default": { 854 | "description": "successful operation" 855 | } 856 | } 857 | } 858 | } 859 | }, 860 | "securityDefinitions": { 861 | "api_key": { 862 | "type": "apiKey", 863 | "name": "api_key", 864 | "in": "header" 865 | }, 866 | "petstore_auth": { 867 | "type": "oauth2", 868 | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", 869 | "flow": "implicit", 870 | "scopes": { 871 | "read:pets": "read your pets", 872 | "write:pets": "modify pets in your account" 873 | } 874 | } 875 | }, 876 | "definitions": { 877 | "ApiResponse": { 878 | "type": "object", 879 | "properties": { 880 | "code": { 881 | "type": "integer", 882 | "format": "int32" 883 | }, 884 | "type": { 885 | "type": "string" 886 | }, 887 | "message": { 888 | "type": "string" 889 | } 890 | } 891 | }, 892 | "Category": { 893 | "type": "object", 894 | "properties": { 895 | "id": { 896 | "type": "integer", 897 | "format": "int64" 898 | }, 899 | "name": { 900 | "type": "string" 901 | } 902 | }, 903 | "xml": { 904 | "name": "Category" 905 | } 906 | }, 907 | "Pet": { 908 | "type": "object", 909 | "required": [ 910 | "name", 911 | "photoUrls" 912 | ], 913 | "properties": { 914 | "id": { 915 | "type": "integer", 916 | "format": "int64" 917 | }, 918 | "category": { 919 | "$ref": "#/definitions/Category" 920 | }, 921 | "name": { 922 | "type": "string", 923 | "example": "doggie" 924 | }, 925 | "photoUrls": { 926 | "type": "array", 927 | "xml": { 928 | "wrapped": true 929 | }, 930 | "items": { 931 | "type": "string", 932 | "xml": { 933 | "name": "photoUrl" 934 | } 935 | } 936 | }, 937 | "tags": { 938 | "type": "array", 939 | "xml": { 940 | "wrapped": true 941 | }, 942 | "items": { 943 | "xml": { 944 | "name": "tag" 945 | }, 946 | "$ref": "#/definitions/Tag" 947 | } 948 | }, 949 | "status": { 950 | "type": "string", 951 | "description": "pet status in the store", 952 | "enum": [ 953 | "available", 954 | "pending", 955 | "sold" 956 | ] 957 | } 958 | }, 959 | "xml": { 960 | "name": "Pet" 961 | } 962 | }, 963 | "Tag": { 964 | "type": "object", 965 | "properties": { 966 | "id": { 967 | "type": "integer", 968 | "format": "int64" 969 | }, 970 | "name": { 971 | "type": "string" 972 | } 973 | }, 974 | "xml": { 975 | "name": "Tag" 976 | } 977 | }, 978 | "Order": { 979 | "type": "object", 980 | "properties": { 981 | "id": { 982 | "type": "integer", 983 | "format": "int64" 984 | }, 985 | "petId": { 986 | "type": "integer", 987 | "format": "int64" 988 | }, 989 | "quantity": { 990 | "type": "integer", 991 | "format": "int32" 992 | }, 993 | "shipDate": { 994 | "type": "string", 995 | "format": "date-time" 996 | }, 997 | "status": { 998 | "type": "string", 999 | "description": "Order Status", 1000 | "enum": [ 1001 | "placed", 1002 | "approved", 1003 | "delivered" 1004 | ] 1005 | }, 1006 | "complete": { 1007 | "type": "boolean" 1008 | } 1009 | }, 1010 | "xml": { 1011 | "name": "Order" 1012 | } 1013 | }, 1014 | "User": { 1015 | "type": "object", 1016 | "properties": { 1017 | "id": { 1018 | "type": "integer", 1019 | "format": "int64" 1020 | }, 1021 | "username": { 1022 | "type": "string" 1023 | }, 1024 | "firstName": { 1025 | "type": "string" 1026 | }, 1027 | "lastName": { 1028 | "type": "string" 1029 | }, 1030 | "email": { 1031 | "type": "string" 1032 | }, 1033 | "password": { 1034 | "type": "string" 1035 | }, 1036 | "phone": { 1037 | "type": "string" 1038 | }, 1039 | "userStatus": { 1040 | "type": "integer", 1041 | "format": "int32", 1042 | "description": "User Status" 1043 | } 1044 | }, 1045 | "xml": { 1046 | "name": "User" 1047 | } 1048 | } 1049 | }, 1050 | "externalDocs": { 1051 | "description": "Find out more about Swagger", 1052 | "url": "http://swagger.io" 1053 | } 1054 | } -------------------------------------------------------------------------------- /example/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /example/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /next-validations.txt: -------------------------------------------------------------------------------- 1 | valibot 2 | Huynh 3 | jellydn 4 | Valibot 5 | Changie 6 | vfgts 7 | isarray 8 | aashutoshrathi 9 | braintree 10 | caseless 11 | typedarray 12 | isstream 13 | espree 14 | minimatch 15 | hapi 16 | hoek 17 | humanwhocodes 18 | nodelib 19 | scandir 20 | fastq 21 | rushstack 22 | corejs 23 | apidom 24 | ramda 25 | stampit 26 | unraw 27 | asyncapi 28 | axios 29 | csstype 30 | estree 31 | globby 32 | ungap 33 | yarnpkg 34 | sprintf 35 | dequal 36 | unscopables 37 | tweetnacl 38 | concat 39 | streamsearch 40 | xvfb 41 | sinonjs 42 | cachedir 43 | eventemitter 44 | execa 45 | getos 46 | listr 47 | minimist 48 | ospath 49 | untildify 50 | yauzl 51 | gopd 52 | esutils 53 | jsbn 54 | tapable 55 | arrayish 56 | arraybuffer 57 | tostringtag 58 | globalthis 59 | proto 60 | weakref 61 | trimend 62 | trimstart 63 | asynciterator 64 | findlastindex 65 | flatmap 66 | fromentries 67 | groupby 68 | axobject 69 | damerau 70 | tosorted 71 | estraverse 72 | hasown 73 | matchall 74 | esrecurse 75 | regexpp 76 | esquery 77 | graphemer 78 | imurmurhash 79 | jsonify 80 | levn 81 | optionator 82 | pify 83 | reusify 84 | keyv 85 | rimraf 86 | asynckit 87 | jsonfile 88 | universalify 89 | realpath 90 | jsprim 91 | sshpk 92 | wrappy 93 | envify 94 | bigints 95 | extglob 96 | getprototypeof 97 | topo 98 | argparse 99 | extsprintf 100 | verror 101 | subtag 102 | colorette 103 | rfdc 104 | yallist 105 | picomatch 106 | regexparam 107 | opentelemetry 108 | postcss 109 | watchpack 110 | msvc 111 | domexception 112 | memorystream 113 | pidtree 114 | padend 115 | yocto 116 | callsites 117 | klaw 118 | picocolors 119 | libc 120 | mkdirp 121 | napi 122 | xtend 123 | drange 124 | lowlight 125 | prismjs 126 | hastscript 127 | autolinker 128 | throttleit 129 | iojs 130 | microtask 131 | fullwidth 132 | pbkdf 133 | dashdash 134 | getpass 135 | deepmerge 136 | undici 137 | classnames 138 | dompurify 139 | randexp 140 | randombytes 141 | proptypes 142 | zenscroll 143 | chownr 144 | tmpdir 145 | punycode 146 | prebuild 147 | toolbelt 148 | querystringify 149 | finalizationregistry 150 | weakmap 151 | weakset 152 | isexe 153 | toposort 154 | Petstore 155 | freenode 156 | petstore 157 | Bitstream 158 | tsup 159 | skypack 160 | trivago 161 | productsway 162 | metafile 163 | vitest 164 | typedoc 165 | vite 166 | jsesc 167 | jridgewell 168 | bcoe 169 | esbuild 170 | loong 171 | riscv 172 | sunos 173 | hutson 174 | istanbuljs 175 | typebox 176 | yargs 177 | tsdoc 178 | kleur 179 | pathe 180 | tinyspy 181 | fflate 182 | sirv 183 | jsonparse 184 | camelcase 185 | pathval 186 | anymatch 187 | readdirp 188 | fsevents 189 | dateformat 190 | codemirror 191 | conventionalcommits 192 | jquery 193 | ismatch 194 | decamelize 195 | dargs 196 | gitconfiglocal 197 | wordwrap 198 | redent 199 | arrify 200 | thenify 201 | mlly 202 | lilconfig 203 | nextick 204 | mrmime 205 | totalist 206 | chokidar 207 | nanospinner 208 | whatwg 209 | dotgitignore 210 | npmcli 211 | joycon 212 | rollup 213 | shiki 214 | lightningcss 215 | sugarss 216 | jsdom 217 | safaridriver 218 | webdriverio 219 | tinybench 220 | tinypool 221 | sortby 222 | webidl 223 | siginfo 224 | stackback 225 | cliui 226 | kofi 227 | buymeacoffee 228 | automerge 229 | sweepai 230 | tsconfigs 231 | duplicative 232 | tsdx -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-validations", 3 | "version": "1.1.0", 4 | "keywords": [ 5 | "nextjs", 6 | "validation", 7 | "next-validation", 8 | "yup", 9 | "joi", 10 | "fastest-validator", 11 | "valibot" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/jellydn/next-validations.git" 16 | }, 17 | "license": "MIT", 18 | "author": "Huynh Duc Dung", 19 | "exports": { 20 | ".": { 21 | "require": "./dist/index.js", 22 | "import": "./dist/esm/index.js", 23 | "types": "./dist/index.d.ts" 24 | } 25 | }, 26 | "main": "dist/index.js", 27 | "module": "dist/esm/index.js", 28 | "typings": "dist/index.d.ts", 29 | "files": [ 30 | "dist", 31 | "src" 32 | ], 33 | "scripts": { 34 | "analyze": "size-limit --why", 35 | "build": "tsup", 36 | "coverage": "vitest --coverage", 37 | "lint": "biome lint --apply-unsafe src", 38 | "format": "biome format --write src", 39 | "check": "biome check --apply src", 40 | "release": "standard-version", 41 | "size": "size-limit", 42 | "start": "tsup --watch", 43 | "test": "vitest run --update", 44 | "test:ui": "vitest --ui", 45 | "vercel-build": "npx typedoc src/index.ts" 46 | }, 47 | "husky": { 48 | "hooks": { 49 | "pre-commit": "yarn lint" 50 | } 51 | }, 52 | "dependencies": { 53 | "@typeschema/main": "0.14.1" 54 | }, 55 | "devDependencies": { 56 | "@biomejs/biome": "1.9.4", 57 | "@size-limit/preset-small-lib": "11.2.0", 58 | "@skypack/package-check": "0.2.2", 59 | "@testing-library/react": "16.3.0", 60 | "@types/react": "19.1.6", 61 | "@types/react-dom": "19.1.6", 62 | "@typeschema/joi": "0.14.0", 63 | "@typeschema/valibot": "0.14.0", 64 | "@typeschema/yup": "0.14.0", 65 | "@typeschema/zod": "0.14.0", 66 | "@vitest/ui": "3.2.1", 67 | "c8": "10.1.3", 68 | "fastest-validator": "1.19.1", 69 | "husky": "9.1.7", 70 | "joi": "17.13.3", 71 | "next": "15.3.3", 72 | "react": "19.1.0", 73 | "react-dom": "19.1.0", 74 | "size-limit": "11.2.0", 75 | "standard-version": "9.5.0", 76 | "tslib": "2.8.1", 77 | "tsup": "8.5.0", 78 | "typedoc": "0.28.5", 79 | "typescript": "5.8.3", 80 | "valibot": "1.1.0", 81 | "vite": "6.3.5", 82 | "vitest": "3.2.1", 83 | "yup": "1.6.1", 84 | "zod": "3.25.51" 85 | }, 86 | "peerDependencies": { 87 | "next": "*" 88 | }, 89 | "packageManager": "pnpm@10.11.1", 90 | "engines": { 91 | "node": ">=18" 92 | }, 93 | "size-limit": [ 94 | { 95 | "path": "dist/index.js", 96 | "limit": "10 KB" 97 | }, 98 | { 99 | "path": "dist/esm/index.js", 100 | "limit": "10 KB" 101 | } 102 | ], 103 | "tsup": { 104 | "entry": [ 105 | "src/index.ts" 106 | ], 107 | "splitting": false, 108 | "sourcemap": true, 109 | "minify": true, 110 | "clean": true, 111 | "dts": true, 112 | "metafile": true, 113 | "format": [ 114 | "esm", 115 | "cjs" 116 | ], 117 | "legacyOutput": true 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | "group:allNonMajor", 6 | ":pinAllExceptPeerDependencies" 7 | ], 8 | "lockFileMaintenance": { 9 | "enabled": true 10 | }, 11 | "automerge": true, 12 | "major": { 13 | "automerge": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './withValidation'; 2 | -------------------------------------------------------------------------------- /src/resolver.ts: -------------------------------------------------------------------------------- 1 | import { assert, type Infer, type Schema } from '@typeschema/main'; 2 | 3 | export function typeschemaResolver(schema: T) { 4 | return { 5 | async validate(data: unknown): Promise> { 6 | return assert(schema, data); 7 | }, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/withValidation.ts: -------------------------------------------------------------------------------- 1 | import type { Schema } from '@typeschema/main'; 2 | import type { NextApiRequest, NextApiResponse } from 'next'; 3 | import { typeschemaResolver } from './resolver'; 4 | 5 | type NextHandler = (err?: Error) => void; 6 | 7 | interface NextApiRequestWithHeaders extends NextApiRequest { 8 | headers: { 9 | [key: string]: string | string[] | undefined; 10 | }; 11 | } 12 | 13 | type ValidationHoF = { 14 | mode?: 'body' | 'query' | 'headers'; 15 | schema: Schema; 16 | }; 17 | 18 | export function withValidation({ schema, mode = 'query' }: ValidationHoF) { 19 | return withValidations([{ schema, mode }]); 20 | } 21 | 22 | export function withValidations(validations: ValidationHoF[]) { 23 | return (handler?: (req: NextApiRequest, res: NextApiResponse) => any) => 24 | async (req: NextApiRequest, res: NextApiResponse, next?: NextHandler) => { 25 | try { 26 | await Promise.all( 27 | validations.map(async (validation) => { 28 | const resolver = typeschemaResolver(validation.schema); 29 | const mode = validation.mode ?? 'query'; 30 | 31 | if (mode === 'query') { 32 | await resolver.validate(req.query); 33 | } else if (mode === 'body') { 34 | await resolver.validate(req.body); 35 | } else if (mode === 'headers') { 36 | await resolver.validate((req as NextApiRequestWithHeaders).headers); 37 | } 38 | }) 39 | ); 40 | 41 | if (next) { 42 | next(); 43 | return; 44 | } 45 | 46 | if (handler) { 47 | return handler(req, res); 48 | } 49 | 50 | res.status(404).end(); 51 | } catch (error) { 52 | res.status(400).send(error); 53 | } 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /sweep.yaml: -------------------------------------------------------------------------------- 1 | # Sweep AI turns bug fixes & feature requests into code changes (https://sweep.dev) 2 | # For details on our config file, check out our docs at https://docs.sweep.dev 3 | 4 | # If you use this be sure to frequently sync your default branch(main, master) to dev. 5 | branch: 'main' 6 | # By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false. 7 | gha_enabled: True 8 | # This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want. 9 | # Here's an example: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8. 10 | description: '' 11 | 12 | # Default Values: https://github.com/sweepai/sweep/blob/main/sweep.yaml 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from 'vite'; 6 | 7 | export default defineConfig({ 8 | test: { 9 | globals: true, 10 | }, 11 | }); 12 | --------------------------------------------------------------------------------