@ts-rest/nest
Advanced tools
Comparing version 3.28.0 to 3.30.0
# @ts-rest/nest | ||
## 3.30.0 | ||
## 3.29.0 | ||
### Minor Changes | ||
- 19aeb0a: - feat: Add option to disable request validation | ||
- This is useful when a user wants to perform validation or handle validation errors themselves | ||
### Patch Changes | ||
- 5f7b236: - bump `@ts-rest/react-query` peer dependency `@tanstack/react-query` to `^4.0.0` (latest 4.33.0) | ||
- bump `@ts-rest/react-query` peer dependency `zod` to `^3.21.0` | ||
- upgrades NX to 16.7 for project root | ||
## 3.28.0 | ||
@@ -4,0 +19,0 @@ |
89
index.js
@@ -13,2 +13,5 @@ 'use strict'; | ||
const ValidateResponsesSymbol = Symbol('ts-rest-validate-responses'); | ||
const ValidateRequestHeadersSymbol = Symbol('ts-rest-validate-request-headers'); | ||
const ValidateRequestQuerySymbol = Symbol('ts-rest-validate-request-query'); | ||
const ValidateRequestBodySymbol = Symbol('ts-rest-validate-request-body'); | ||
@@ -99,2 +102,3 @@ const JsonQuery = (jsonQuery = true) => { | ||
const TsRest = (appRouteOrOptions, options = {}) => { | ||
var _a, _b, _c; | ||
const decorators = []; | ||
@@ -110,2 +114,5 @@ const isMethodDecorator = 'path' in appRouteOrOptions; | ||
} | ||
else { | ||
decorators.push(common.SetMetadata(ValidateRequestHeadersSymbol, (_a = optionsToUse.validateRequestHeaders) !== null && _a !== void 0 ? _a : true), common.SetMetadata(ValidateRequestQuerySymbol, (_b = optionsToUse.validateRequestQuery) !== null && _b !== void 0 ? _b : true), common.SetMetadata(ValidateRequestBodySymbol, (_c = optionsToUse.validateRequestBody) !== null && _c !== void 0 ? _c : true)); | ||
} | ||
if (optionsToUse.jsonQuery !== undefined) { | ||
@@ -117,2 +124,11 @@ decorators.push(JsonQuery(optionsToUse.jsonQuery)); | ||
} | ||
if (optionsToUse.validateRequestBody !== undefined) { | ||
decorators.push(common.SetMetadata(ValidateRequestBodySymbol, optionsToUse.validateRequestBody)); | ||
} | ||
if (optionsToUse.validateRequestQuery !== undefined) { | ||
decorators.push(common.SetMetadata(ValidateRequestQuerySymbol, optionsToUse.validateRequestQuery)); | ||
} | ||
if (optionsToUse.validateRequestHeaders !== undefined) { | ||
decorators.push(common.SetMetadata(ValidateRequestHeadersSymbol, optionsToUse.validateRequestHeaders)); | ||
} | ||
return common.applyDecorators(...decorators); | ||
@@ -151,6 +167,18 @@ }; | ||
} | ||
const getRequestValidationValue = (key) => { | ||
const handlerValue = Reflect.getMetadata(key, ctx.getHandler()); | ||
const classValue = Reflect.getMetadata(key, ctx.getClass()); | ||
if (handlerValue === undefined && classValue === undefined) { | ||
return true; | ||
} | ||
if (handlerValue !== undefined) { | ||
return handlerValue; | ||
} | ||
return classValue; | ||
}; | ||
const headersResult = core.checkZodSchema(req.headers, appRoute.headers, { | ||
passThroughExtraKeys: true, | ||
}); | ||
if (!headersResult.success) { | ||
const headerValidation = getRequestValidationValue(ValidateRequestHeadersSymbol); | ||
if (!headersResult.success && headerValidation) { | ||
throw new common.BadRequestException(core.zodErrorResponse(headersResult.error)); | ||
@@ -163,14 +191,18 @@ } | ||
const queryResult = core.checkZodSchema(query, appRoute.query); | ||
if (!queryResult.success) { | ||
const queryValidation = getRequestValidationValue(ValidateRequestQuerySymbol); | ||
if (!queryResult.success && queryValidation) { | ||
throw new common.BadRequestException(core.zodErrorResponse(queryResult.error)); | ||
} | ||
const bodyResult = core.checkZodSchema(req.body, appRoute.method === 'GET' ? null : appRoute.body); | ||
if (!bodyResult.success) { | ||
const bodyValidation = getRequestValidationValue(ValidateRequestBodySymbol); | ||
if (!bodyResult.success && bodyValidation) { | ||
throw new common.BadRequestException(core.zodErrorResponse(bodyResult.error)); | ||
} | ||
return { | ||
query: queryResult.data, | ||
query: queryResult.success ? queryResult.data : req.query, | ||
params: pathParamsResult.data, | ||
body: bodyResult.data, | ||
headers: headersResult.data, | ||
body: bodyResult.success ? bodyResult.data : req.body, | ||
headers: headersResult.success | ||
? headersResult.data | ||
: req.headers, | ||
}; | ||
@@ -202,2 +234,3 @@ }); | ||
const TsRestHandler = (appRouterOrRoute, options = {}) => { | ||
var _a, _b, _c; | ||
const decorators = []; | ||
@@ -210,2 +243,3 @@ if (options.jsonQuery !== undefined) { | ||
} | ||
decorators.push(common.SetMetadata(ValidateRequestHeadersSymbol, (_a = options.validateRequestHeaders) !== null && _a !== void 0 ? _a : true), common.SetMetadata(ValidateRequestQuerySymbol, (_b = options.validateRequestQuery) !== null && _b !== void 0 ? _b : true), common.SetMetadata(ValidateRequestBodySymbol, (_c = options.validateRequestBody) !== null && _c !== void 0 ? _c : true)); | ||
const isMultiHandler = !core.isAppRoute(appRouterOrRoute); | ||
@@ -310,3 +344,6 @@ if (isMultiHandler) { | ||
const isJsonQuery = !!((_a = Reflect.getMetadata(JsonQuerySymbol, ctx.getHandler())) !== null && _a !== void 0 ? _a : Reflect.getMetadata(JsonQuerySymbol, ctx.getClass())); | ||
const isValidationEnabled = Boolean(this.reflector.getAllAndOverride(ValidateResponsesSymbol, [ctx.getHandler(), ctx.getClass()])); | ||
const getMetadataValue = (key) => Boolean(this.reflector.getAllAndOverride(key, [ | ||
ctx.getHandler(), | ||
ctx.getClass(), | ||
])); | ||
const paramsResult = core.checkZodSchema(req.params, appRoute.pathParams, { | ||
@@ -323,7 +360,11 @@ passThroughExtraKeys: true, | ||
const bodyResult = core.checkZodSchema(req.body, 'body' in appRoute ? appRoute.body : null); | ||
const isValidationEnabled = getMetadataValue(ValidateResponsesSymbol); | ||
const isHeadersInvalid = !headersResult.success && getMetadataValue(ValidateRequestHeadersSymbol); | ||
const isQueryInvalid = !queryResult.success && getMetadataValue(ValidateRequestQuerySymbol); | ||
const isBodyInvalid = !bodyResult.success && getMetadataValue(ValidateRequestBodySymbol); | ||
if (!paramsResult.success || | ||
!headersResult.success || | ||
!queryResult.success || | ||
!bodyResult.success) { | ||
throw new RequestValidationError(!paramsResult.success ? paramsResult.error : null, !headersResult.success ? headersResult.error : null, !queryResult.success ? queryResult.error : null, !bodyResult.success ? bodyResult.error : null); | ||
isHeadersInvalid || | ||
isQueryInvalid || | ||
isBodyInvalid) { | ||
throw new RequestValidationError(!paramsResult.success ? paramsResult.error : null, isHeadersInvalid ? headersResult.error : null, isQueryInvalid ? queryResult.error : null, isBodyInvalid ? bodyResult.error : null); | ||
} | ||
@@ -333,18 +374,9 @@ return next.handle().pipe(rxjs.map(async (impl) => { | ||
try { | ||
if (routeKey) { | ||
result = await impl[routeKey]({ | ||
query: queryResult.data, | ||
params: paramsResult.data, | ||
body: bodyResult.data, | ||
headers: headersResult.data, | ||
}); | ||
} | ||
else { | ||
result = await impl({ | ||
query: queryResult.data, | ||
params: paramsResult.data, | ||
body: bodyResult.data, | ||
headers: headersResult.data, | ||
}); | ||
} | ||
const res = { | ||
params: paramsResult.data, | ||
query: queryResult.success ? queryResult.data : req.query, | ||
body: bodyResult.success ? bodyResult.data : req.body, | ||
headers: headersResult.success ? headersResult.data : req.headers, | ||
}; | ||
result = routeKey ? await impl[routeKey](res) : await impl(res); | ||
} | ||
@@ -415,2 +447,5 @@ catch (e) { | ||
exports.TsRestRequest = TsRestRequest; | ||
exports.ValidateRequestBodySymbol = ValidateRequestBodySymbol; | ||
exports.ValidateRequestHeadersSymbol = ValidateRequestHeadersSymbol; | ||
exports.ValidateRequestQuerySymbol = ValidateRequestQuerySymbol; | ||
exports.ValidateResponsesSymbol = ValidateResponsesSymbol; | ||
@@ -417,0 +452,0 @@ exports.doesUrlMatchContractPath = doesUrlMatchContractPath; |
{ | ||
"name": "@ts-rest/nest", | ||
"version": "3.28.0", | ||
"version": "3.30.0", | ||
"description": "Nest server integration for @ts-rest", | ||
@@ -27,3 +27,3 @@ "license": "MIT", | ||
"zod": "^3.21.0", | ||
"@ts-rest/core": "3.28.0" | ||
"@ts-rest/core": "3.30.0" | ||
}, | ||
@@ -40,6 +40,2 @@ "peerDependenciesMeta": { | ||
}, | ||
"typedoc": { | ||
"entryPoint": "./src/index.ts", | ||
"tsconfig": "./tsconfig.lib.json" | ||
}, | ||
"module": "./index.mjs", | ||
@@ -46,0 +42,0 @@ "main": "./index.js", |
@@ -7,13 +7,26 @@ # ts-rest | ||
<p align="center">RPC-like client and server helpers for a magical end to end typed experience</p> | ||
<p align="center">Incrementally adoptable RPC-like client and server helpers for a magical end to end typed experience ๐ช</p> | ||
<p align="center"> | ||
<a href="https://www.npmjs.com/package/@ts-rest/core"><img src="https://img.shields.io/npm/v/@ts-rest/core.svg" alt="langue typescript"/></a> | ||
<img alt="Github Workflow Status" src="https://img.shields.io/github/actions/workflow/status/ts-rest/ts-rest/release.yml?branch=main"/> | ||
<a href="https://www.npmjs.com/package/@ts-rest/core"><img alt="npm" src="https://img.shields.io/npm/dw/@ts-rest/core"/></a> | ||
<a href="https://github.com/ts-rest/ts-rest/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/ts-rest/ts-rest"/></a> | ||
<img alt="Bundle Size" src="https://img.shields.io/bundlephobia/minzip/@ts-rest/core?label=%40ts-rest%2Fcore"/> | ||
<a href="https://discord.com/invite/2Megk85k5a"><img alt="Discord" src="https://img.shields.io/discord/1055855205960392724"/></a> | ||
<a href="https://github.com/ts-rest/ts-rest"> | ||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ts-rest/ts-rest"/> | ||
</a> | ||
<a href="https://www.npmjs.com/package/@ts-rest/core"> | ||
<img src="https://img.shields.io/npm/dm/%40ts-rest/core"/> | ||
</a> | ||
<a href="https://github.com/ts-rest/ts-rest/blob/main/LICENSE"> | ||
<img alt="License" src="https://img.shields.io/github/license/ts-rest/ts-rest"/> | ||
</a> | ||
<a href="https://bundlephobia.com/package/@ts-rest/core"> | ||
<img alt="Bundle Size" src="https://img.shields.io/bundlephobia/minzip/@ts-rest/core?label=%40ts-rest%2Fcore"/> | ||
</a> | ||
</p> | ||
<div align="center"> | ||
<div>Join us on Discord for help, feedback, and discussions!</div><br></br> | ||
<a href="https://discord.gg/2Megk85k5a"> | ||
<img src="https://discordapp.com/api/guilds/1055855205960392724/widget.png?style=banner2" alt="Discord Shield"/> | ||
</a> | ||
</div> | ||
# Introduction | ||
@@ -25,10 +38,13 @@ | ||
- End to end type safety ๐ | ||
- RPC-like client side interface ๐ก | ||
- [Tiny bundle size ๐](https://bundlephobia.com/package/@ts-rest/core) (1kb!) | ||
- Well-tested and production ready โ | ||
- End-to-end type safety ๐ | ||
- RPC-like client side API โก๏ธ | ||
- Small Bundle Size ๐ | ||
- No Code Generation ๐โโ๏ธ | ||
- Zod support for runtime type checks ๐ฎโโ๏ธ | ||
- Zod support for runtime validation ๐ | ||
- Full optional OpenAPI integration ๐ | ||
<div align="center"> | ||
<h3>๐ Start reading the official <a href="https://ts-rest.com/docs/quickstart?utm_source=github&utm_medium=documentation&utm_campaign=readme">Quickstart Guide</a> ๐</h3> | ||
</div> | ||
### Super Simple Example | ||
@@ -57,7 +73,7 @@ | ||
Fulfil the contract on your server, with a type-safe router: | ||
Fulfill the contract on your server, with a type-safe router: | ||
```typescript | ||
const router = s.router(contract, { | ||
getPost: async ({ params: { id } }) => { | ||
getPosts: async ({ params: { id } }) => { | ||
return { | ||
@@ -83,27 +99,11 @@ status: 200, | ||
Install the core package | ||
```bash | ||
yarn add @ts-rest/core | ||
# Optional react-query integration | ||
yarn add @ts-rest/react-query | ||
# Pick your backend | ||
yarn add @ts-rest/nest @ts-rest/express | ||
# For automatic server OpenAPI gen | ||
yarn add @ts-rest/open-api | ||
``` | ||
Create a contract, implement it on your server then consume it in your client. Incrementally adopt, trial it with your team, then get shipping faster. | ||
<div align="center"> | ||
<h3>๐ Read more on the official <a href="https://ts-rest.com/docs/quickstart?utm_source=github&utm_medium=documentation&utm_campaign=readme">Quickstart Guide</a> ๐</h3> | ||
<h3>๐ Start reading the official <a href="https://ts-rest.com/docs/quickstart?utm_source=github&utm_medium=documentation&utm_campaign=readme">Quickstart Guide</a> ๐</h3> | ||
</div> | ||
## Star History | ||
[![Star History Chart](https://api.star-history.com/svg?repos=ts-rest/ts-rest&type=Timeline)](https://star-history.com/#ts-rest/ts-rest&Timeline) | ||
## Contributors โจ | ||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): | ||
MASSIVE Thanks to all of these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)), who have helped make ts-rest possible: | ||
@@ -129,2 +129,5 @@ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> | ||
<td align="center" valign="top" width="14.28%"><a href="https://nad.dev"><img src="https://avatars.githubusercontent.com/u/6670753?v=4?s=100" width="100px;" alt="Neil A. Dobson"/><br /><sub><b>Neil A. Dobson</b></sub></a><br /><a href="https://github.com/ts-rest/ts-rest/commits?author=neildobson-au" title="Code">๐ป</a></td> | ||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dohaicuong"><img src="https://avatars.githubusercontent.com/u/20808725?v=4?s=100" width="100px;" alt="Eric Do"/><br /><sub><b>Eric Do</b></sub></a><br /><a href="https://github.com/ts-rest/ts-rest/commits?author=dohaicuong" title="Documentation">๐</a></td> | ||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fruchtzwerg"><img src="https://avatars.githubusercontent.com/u/15377955?v=4?s=100" width="100px;" alt="Ben"/><br /><sub><b>Ben</b></sub></a><br /><a href="https://github.com/ts-rest/ts-rest/commits?author=fruchtzwerg" title="Code">๐ป</a> <a href="https://github.com/ts-rest/ts-rest/commits?author=fruchtzwerg" title="Documentation">๐</a> <a href="https://github.com/ts-rest/ts-rest/commits?author=fruchtzwerg" title="Tests">โ ๏ธ</a></td> | ||
<td align="center" valign="top" width="14.28%"><a href="https://llllvvuu.dev"><img src="https://avatars.githubusercontent.com/u/5601392?v=4?s=100" width="100px;" alt="LW"/><br /><sub><b>LW</b></sub></a><br /><a href="https://github.com/ts-rest/ts-rest/commits?author=llllvvuu" title="Code">๐ป</a> <a href="https://github.com/ts-rest/ts-rest/issues?q=author%3Allllvvuu" title="Bug reports">๐</a></td> | ||
</tr> | ||
@@ -139,5 +142,24 @@ </tbody> | ||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! | ||
## Star History | ||
<div style={{paddingTop: "25px"}}> | ||
Since our first commit in 2022 we've been growing steadily. We're proud of our progress and we're excited about the future. | ||
<div align="center"> | ||
<a href="https://star-history.com/#ts-rest/ts-rest&Timeline"> | ||
<picture> | ||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ts-rest/ts-rest&type=Timeline&theme=dark" /> | ||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ts-rest/ts-rest&type=Timeline" /> | ||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ts-rest/ts-rest&type=Timeline" /> | ||
</picture> | ||
</a> | ||
</div> | ||
<div align="center" > | ||
<div>Join us on Discord for help, feedback, and discussions!</div><br></br> | ||
<a href="https://discord.gg/2Megk85k5a"> | ||
<img src="https://discordapp.com/api/guilds/1055855205960392724/widget.png?style=banner2" alt="Discord Shield"/> | ||
</a> | ||
</div> | ||
<div align="center"> | ||
<a | ||
@@ -144,0 +166,0 @@ href="https://vercel.com/?utm_source=ts-rest&utm_campaign=oss" |
@@ -0,0 +0,0 @@ export * from './lib/constants'; |
export declare const TsRestAppRouteMetadataKey: unique symbol; | ||
export declare const JsonQuerySymbol: unique symbol; | ||
export declare const ValidateResponsesSymbol: unique symbol; | ||
export declare const ValidateRequestHeadersSymbol: unique symbol; | ||
export declare const ValidateRequestQuerySymbol: unique symbol; | ||
export declare const ValidateRequestBodySymbol: unique symbol; |
@@ -0,0 +0,0 @@ import { JsonQuerySymbol } from './constants'; |
import { Reflector } from '@nestjs/core'; | ||
import { Observable } from 'rxjs'; | ||
import { NestInterceptor, ExecutionContext, CallHandler, BadRequestException, InternalServerErrorException, HttpException } from '@nestjs/common'; | ||
import { AppRouter, AppRoute, ServerInferResponses } from '@ts-rest/core'; | ||
import { BadRequestException, CallHandler, ExecutionContext, HttpException, InternalServerErrorException, NestInterceptor } from '@nestjs/common'; | ||
import { AppRoute, AppRouter, ServerInferResponses } from '@ts-rest/core'; | ||
import { TsRestRequestShape } from './ts-rest-request.decorator'; | ||
@@ -6,0 +6,0 @@ import { z } from 'zod'; |
@@ -0,0 +0,0 @@ import { AppRoute, AppRouter, Without, ServerInferResponses } from '@ts-rest/core'; |
@@ -0,0 +0,0 @@ import { AppRoute, ServerInferRequest } from '@ts-rest/core'; |
@@ -5,2 +5,5 @@ import { AppRoute } from '@ts-rest/core'; | ||
validateResponses?: boolean; | ||
validateRequestHeaders?: boolean; | ||
validateRequestQuery?: boolean; | ||
validateRequestBody?: boolean; | ||
}; | ||
@@ -7,0 +10,0 @@ type TsRestType = { |
@@ -0,0 +0,0 @@ import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
62928
978
171