Compare commits

...

90 Commits

Author SHA1 Message Date
Kamil Myśliwiec
e5612e0197 chore: merge 10.0.0 2023-06-12 10:58:30 +02:00
Kamil Myśliwiec
6c54448bf7 fix(fastify): validate middleware paths 2023-06-12 10:51:02 +02:00
Kamil Mysliwiec
8053f11451 Merge pull request #9809 from rychardvale/fix-middleware-multiple-runs
feat(core): middleware runs once for matching route
2023-06-12 09:47:02 +02:00
Kamil Mysliwiec
b1be7a3d21 Merge pull request #11650 from nestjs/fix/global-prefix-exclude-with-versioning
fix(core): global prefix exclude when versioning is turned on
2023-05-24 11:34:14 +02:00
Kamil Myśliwiec
0faddb4c37 fix(core): global prefix exclude when versioning is turned on 2023-05-22 13:17:22 +02:00
Kamil Mysliwiec
974c946845 Update integration/testing-module-override/tsconfig.json 2023-05-19 14:55:33 +02:00
Kamil Myśliwiec
c4828c473a ci: add --legacy-peer-deps flag to samples install 2023-05-19 13:01:19 +02:00
Kamil Myśliwiec
0a674557bd ci: add legacy peer deps flag to npm ci 2023-05-19 12:52:12 +02:00
Kamil Myśliwiec
b9d8a0769f chore: recreate package lock file 2023-05-19 12:44:15 +02:00
Kamil Myśliwiec
1e467c17f6 test: update async local storage signature 2023-05-19 12:35:57 +02:00
Kamil Myśliwiec
a665efcfba chore: update package lock file 2023-05-19 12:35:41 +02:00
Kamil Myśliwiec
aea0a0470a Merge branch 'parzhitsky-feat/improve-typings-for-createbody' into 10.0.0 2023-05-19 12:33:59 +02:00
Kamil Myśliwiec
5820bed145 chore: resolve conflicts 2023-05-19 12:33:43 +02:00
Kamil Mysliwiec
f6f577a4df Merge pull request #11555 from MaurerKrisztian/InjectionToken-generic
refactor(common): Add generic to InjectionToken type
2023-04-25 08:28:10 +02:00
krisztian.maurer
3bf74d8fb6 refactor(common): Add generic to InjectionToken type 2023-04-24 13:49:24 +02:00
Kamil Myśliwiec
278654247b ci: dont run test node 19 for now 2023-04-17 14:18:06 +02:00
Kamil Myśliwiec
6a8b649840 Merge branch 'leonardovillela-testing-module-builder-add-override-module-method' into 10.0.0 2023-04-17 14:06:14 +02:00
Kamil Myśliwiec
f98c779e35 test: update outdated tests 2023-04-17 14:05:50 +02:00
Kamil Myśliwiec
11b7418e3f Merge branch '10.0.0' into leonardovillela-testing-module-builder-add-override-module-method 2023-04-17 13:49:11 +02:00
Kamil Myśliwiec
fdfabf978c chore: resolve conflicts 2023-04-17 13:49:01 +02:00
Kamil Mysliwiec
7b05cd0cbf Merge pull request #10533 from Gustrb/add-search-method
feat(core/common): Add @Search decorator to the available HTTP route handlers
2023-04-17 13:14:09 +02:00
Kamil Mysliwiec
69fba03c7d Merge pull request #10359 from tijsmoree/redis-microservice-psubscribe
feat(microservices): enable wildcards in redis microservice patterns
2023-04-17 13:12:28 +02:00
Kamil Mysliwiec
b908f1a012 Merge pull request #11118 from elplancton/host-regex
feat(core): add support to named groups
2023-04-17 13:09:35 +02:00
Kamil Myśliwiec
bc4a571ef4 fix: update outdated imports 2023-04-17 13:06:25 +02:00
Kamil Myśliwiec
8b74665bdf chore: remove deprecated types, utils, logic 2023-04-11 13:34:10 +02:00
Kamil Mysliwiec
648f2d587f Merge pull request #11450 from micalevisk/refactor-v10/drop-deprecations
refactor(common)!: drop deprecated misspelled constant `CUSTOM_ROUTE_AGRS_METADATA`
2023-04-11 11:53:22 +02:00
Micael Levi L. Cavalcante
1a2ac3eeae refactor(common): drop deprecated misspelled constant 2023-04-10 20:33:11 -04:00
Kamil Myśliwiec
880e08b718 ci: update circleci setup to use newer versions of node 2023-04-06 09:07:54 +02:00
Kamil Mysliwiec
4765b46e6d Merge pull request #10630 from micalevisk/feat/narrowing-http-adapter-instance-type
feat(common)!: narrow `getInstance` return type for http adapters
2023-04-06 09:04:37 +02:00
Micael Levi L. Cavalcante
3b6b66b7cc feat(common): narrow getInstance return type for http platforms 2023-04-05 09:05:43 -04:00
Dima Parzhitsky
48e25d4080 chore: improve typings of .createBody(…)
Add overload signatures to `HttpException.createBody(…)` to reflect different ways of creating it:

```ts
createBody(<message>, <error>, <statusCode>);
createBody(<messages>, <error>, <statusCode>);
createBody(null, <message>, <statusCode>);
createBody(null, <messages>, <statusCode>);
createBody(<custom>);
```

Signed-off-by: Dima Parzhitsky <parzhitsky@gmail.com>
2023-04-05 14:43:45 +03:00
Kamil Myśliwiec
353b974ffe Merge branch '10.0.0' of https://github.com/nestjs/nest into 10.0.0 2023-04-05 13:31:18 +02:00
Kamil Myśliwiec
0f904d2cab chore: update minimum engine version to 16 2023-04-05 13:31:07 +02:00
Kamil Mysliwiec
77442c806f Merge pull request #10133 from TheKhanj/refactor/error
refactor(): refactor exception handlers
2023-04-05 13:14:53 +02:00
Kamil Mysliwiec
10c1bd8405 Merge pull request #11142 from nordfjord/support-async-local-storage-in-interceptors
feat: Support async local storage in interceptors
2023-04-05 13:11:37 +02:00
Kamil Mysliwiec
252e864df7 Merge pull request #10953 from micalevisk/fix/partial-issue-10246
fix(common)!: when transforming optional boolean parameters on `ValidationPipe`
2023-04-05 13:11:31 +02:00
Kamil Myśliwiec
90a3416288 Merge branch 'CodyTseng-feat-enhance-http-server-type' into 10.0.0 2023-04-05 13:10:43 +02:00
Kamil Myśliwiec
702eebf9b5 chore: resolve conflicts 2023-04-05 13:10:23 +02:00
Kamil Mysliwiec
2eb932cc58 Merge pull request #11040 from mohamadrezamomeni/refactor-define-type
feat(common): define type for file input
2023-04-05 13:07:20 +02:00
Kamil Mysliwiec
5a2be89eab Merge pull request #10549 from Papooch/fix/microservices-client-falsy-options
fix(microservices): allow falsy values in options
2023-04-05 13:06:50 +02:00
Kamil Mysliwiec
5aab28f260 Merge pull request #11318 from skrtheboss/fix/inject-and-optional-decorator-typings
fix(common): fix @Inject and @Optional return types
2023-04-05 13:06:42 +02:00
Kamil Mysliwiec
03aa13e980 Merge pull request #11413 from Tony133/chore/update-interface-deprecated-kafka
chore(): update interface deprecated for kafka
2023-04-05 13:06:10 +02:00
Kamil Myśliwiec
a432407efc chore: remove cache-manager from peer deps 2023-04-05 13:04:02 +02:00
Kamil Mysliwiec
9e06786820 Merge pull request #11428 from nestjs/chore/remove-cache-module
chore(common): remove cache module
2023-04-05 13:03:35 +02:00
Kamil Myśliwiec
31deefe3de test(): update internal core module test 2023-04-05 12:24:28 +02:00
Kamil Myśliwiec
2599395b3c Merge branch 'master' into chore/remove-cache-module 2023-04-05 12:23:56 +02:00
Kamil Myśliwiec
f9550c3393 chore: remove cache decorator test 2023-04-05 12:07:07 +02:00
Kamil Myśliwiec
570e52132c chore(common): remove cache module 2023-04-05 11:01:17 +02:00
Tony133
0eec329070 chore(): update interface deprecated for kafka 2023-03-31 16:59:45 +02:00
skrtheboss
ffc09ee82c fix(common): adjust @inject and @optional return types
Use PropertyDecorator and ParameterDecorator as return type to ensure typings are correct
2023-03-22 09:08:41 +01:00
Einar Norðfjörð
25d9ef940c feat(core): Support async local storage in interceptors
This is necessary to allow use cases such as allowing interceptors to
change the context in OpenTelemetry. You might for example want to add
Baggage to the context in an interceptor but because of the way it was
structured before the baggage would not make its way into the controller
2023-02-20 14:13:16 -05:00
Rafael
df74cde2a0 feat(core): add support to named groups 2023-02-15 15:18:49 -03:00
codytseng
ea79cefc28 fix(testing): correct the type of server 2023-02-15 15:46:18 +08:00
codytseng
cf4097cf24 feat(common,express,fastify): enhance the type of httpServer 2023-02-15 14:42:03 +08:00
Mohammad Reza Momeni
0b847e75bc chore: remove | {} 2023-02-09 16:14:57 +03:30
Mohammad Reza Momeni
a33e377d00 fix: generix with default type 2023-02-07 00:52:28 +03:30
Mohammad Reza Momeni
2ccf67b8e4 fix: make filetype small and rename to ifile 2023-02-07 00:22:20 +03:30
mohamadrezamomeni
f3e118e9c0 Update packages/common/pipes/file/file-validator.interface.ts
Co-authored-by: Thiago Valentim <51245643+thiagomini@users.noreply.github.com>
2023-02-06 22:32:03 +03:30
Mohammad Reza Momeni
a0f7ee8b1e fix: fix file type checking 2023-02-04 00:29:09 +03:30
Mohammad Reza Momeni
809dadbf82 refactor: use synthetic sugar in file-validator for validate mimetype 2023-02-03 23:33:49 +03:30
Mohammad Reza Momeni
dba518e89c refactor: define types for input file in file-type.validator
and file-validator.interface and max-file-size.validator
2023-02-03 23:31:19 +03:30
Micael Levi L. Cavalcante
14a0f069ac fix(common): when transforming invalid boolean values
on `ValidationPipe`, it must not return a valid boolean
2023-01-25 21:37:54 -04:00
Micael Levi L. Cavalcante
bcf4bf9b81 test(common): add tests for broken cases on ValidationPipe
when transforming booleans with non-valid boolean values
2023-01-25 21:23:00 -04:00
Gustrb
a7655a051d ci: don't install greater than npm 8 2022-11-12 22:06:38 -03:00
Gustrb
2f69e96852 feat(core): Add the search method to the adapter
This update was really simple, because we dont need any kind of aliasing
due to fastify and express already supporting the search method for Application
2022-11-09 19:49:35 -03:00
Gustrb
2dd3526d08 feat(common): Add the @Search decorator
This decorator uses the SEARCH method to construct a decorator
for a handler to a Search http request
2022-11-09 19:25:55 -03:00
Gustrb
93694eea56 feat(common): Add the search method to the list
The search method is going to be anexed with the @Search decorator and
going to be used to reference a SEARCH request
2022-11-09 19:25:52 -03:00
Ondřej Švanda
e45e04aa69 fix(microservices): allow falsy values in options 2022-11-09 16:08:47 +01:00
Tijs Moree
39d6e8ecf8 feat(microservices): add wildcard option in redis microservice patterns
The redis microservice now makes use of `psubscribe` and `pmessage`
when the `wildcards` option is enabled in the options of the microservice,
which makes it possible to use wildcards as specified by the Redis
documentation.

Closes #10344
2022-10-11 17:40:27 +02:00
Pooyan Khanjankhani
92bc195eb2 chore(common): add .util postfix to test filename 2022-08-19 00:34:31 +04:30
Pooyan Khanjankhani
1f81c86d84 test(common): add test for exception handler selector 2022-08-19 00:30:37 +04:30
Pooyan Khanjankhani
dcc2fa7836 chore(): append .util postfix to filename 2022-08-19 00:03:08 +04:30
Pooyan Khanjankhani
98acbe6430 fix(common): check for empty length in exception handler selector 2022-08-18 23:55:05 +04:30
Pooyan Khanjankhani
82555bed36 chore(common): fix import issue 2022-08-18 23:11:34 +04:30
Pooyan Khanjankhani
273f1aa3ed refactor(core): use selectExceptionFilterMetadata in
ExternalExceptionHandler
2022-08-18 23:09:06 +04:30
Pooyan Khanjankhani
469580da63 refactor(microservices): use selectExceptionFilterMetadata 2022-08-18 23:07:10 +04:30
Pooyan Khanjankhani
fe069f6ebe refactor(core): use 2022-08-18 23:06:50 +04:30
Pooyan Khanjankhani
c067f85d37 refactor(websockets): use selectExceptionFilterMetadata 2022-08-18 23:00:24 +04:30
Pooyan Khanjankhani
f98d0e9956 refactor(common): add selectExceptionFilterMetadata function 2022-08-18 22:58:30 +04:30
Rychard Vale
b0837af94f fix: convention/code style.
Co-authored-by: Antonio T. as Tony <Tony133@users.noreply.github.com>
2022-06-26 14:57:47 -03:00
Rychard
5bf90ac6d8 fix: increase readability 2022-06-24 23:46:49 -03:00
Rychard Vale
328169628b feat(core): middleware runs once for matching route 2022-06-20 07:26:58 -03:00
Leonardo Villela
91d4870323 refactor(testing): transform module override sample to integration test 2021-12-21 00:18:03 +01:00
Leonardo Villela
d725aae31c docs(testing): add modules override sample 2021-12-19 19:33:21 +01:00
Leonardo Villela
09f6c94d85 chore(testing): remove debugger instructions 2021-12-19 16:24:09 +01:00
Leonardo Villela
59bae05cb2 fix(testing): lazy module override 2021-12-19 01:38:15 +01:00
Leonardo Villela
d7351b7a49 fix(testing): circular dependency module override 2021-12-19 00:02:50 +01:00
Leonardo Villela
6ea534e2f4 feat(testing): add missing validation for replace container module 2021-12-13 20:54:04 +01:00
Leonardo Villela
3114a54d25 refactor(testing): remove duplication to add/replace container module 2021-12-13 20:38:00 +01:00
Leonardo Villela
2071461d5e feat(testing): support override modules in test module builder 2021-12-12 23:32:31 +01:00
188 changed files with 33662 additions and 1799 deletions

View File

@@ -3,19 +3,19 @@ version: 2
aliases:
- &restore-cache
restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-{{ checksum "package.json" }}
- &install-deps
run:
name: Install dependencies
command: npm ci
name: Install dependencies
command: npm ci --legacy-peer-deps
- &build-packages
run:
name: Build
command: npm run build
name: Build
command: npm run build
- &run-unit-tests
run:
name: Test
command: npm run test
name: Test
command: npm run test
unit-tests-template: &unit-tests-template
working_directory: ~/nest
@@ -30,7 +30,7 @@ jobs:
build:
working_directory: ~/nest
docker:
- image: circleci/node:12
- image: cimg/node:16.19
steps:
- checkout
- run:
@@ -40,7 +40,7 @@ jobs:
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: Install dependencies
command: npm ci
command: npm ci --legacy-peer-deps
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
@@ -48,21 +48,11 @@ jobs:
- run:
name: Build
command: npm run build
test_node_12:
<<: *unit-tests-template
docker:
- image: circleci/node:12
test_node_14:
<<: *unit-tests-template
docker:
- image: circleci/node:14
test_node_16:
working_directory: ~/nest
docker:
- image: circleci/node:16
- image: cimg/node:16.19
steps:
- checkout
- *restore-cache
@@ -77,6 +67,16 @@ jobs:
- store_artifacts:
path: coverage
test_node_18:
<<: *unit-tests-template
docker:
- image: cimg/node:18.14
test_node_19:
<<: *unit-tests-template
docker:
- image: cimg/node:19.8
lint:
working_directory: ~/nest
docker:
@@ -97,17 +97,17 @@ jobs:
machine: true
steps:
- checkout
- run:
- run:
name: Prepare nvm
command: |
echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
- run:
- run:
name: Upgrade Node.js
command: |
nvm install v12
nvm install v16
node -v
nvm alias default v12
nvm alias default v16
- run:
name: Install Docker Compose
command: |
@@ -126,11 +126,11 @@ jobs:
- run:
name: Integration tests
command: npm run test:integration
codechecks_benchmarks:
working_directory: ~/nest
docker:
- image: circleci/node:16
- image: cimg/node:16.19
steps:
- checkout
- *restore-cache
@@ -146,7 +146,7 @@ jobs:
samples:
working_directory: ~/nest
docker:
- image: circleci/node:16
- image: cimg/node:16.19
environment:
- DISABLE_OPENCOLLECTIVE: true
steps:
@@ -165,10 +165,7 @@ workflows:
- test_node_16:
requires:
- build
- test_node_14:
requires:
- build
- test_node_12:
- test_node_18:
requires:
- build
- lint:

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -1,39 +0,0 @@
import { CACHE_MANAGER, INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AsyncRegisterExtraModule } from '../src/async-register-extra-providers/async-register-extra.module';
import { Cache } from 'cache-manager';
import { assert } from 'chai';
describe('Async Register Extra Providers', () => {
let server;
let app: INestApplication;
let cacheManager: Cache;
before(async () => {
const module = await Test.createTestingModule({
imports: [AsyncRegisterExtraModule],
}).compile();
cacheManager = module.get<Cache>(CACHE_MANAGER);
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
it('should be defined', async () => {
assert.isDefined(cacheManager);
});
it(`should return empty`, async () => {
return request(server).get('/').expect(200, 'Not found');
});
it(`should return data`, async () => {
return request(server).get('/').expect(200, 'value');
});
after(async () => {
await app.close();
});
});

View File

@@ -1,39 +0,0 @@
import { CACHE_MANAGER, INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AsyncRegisterModule } from '../src/async-register/async-register.module';
import { Cache } from 'cache-manager';
import { assert } from 'chai';
describe('Async Register', () => {
let server;
let app: INestApplication;
let cacheManager: Cache;
before(async () => {
const module = await Test.createTestingModule({
imports: [AsyncRegisterModule],
}).compile();
cacheManager = module.get<Cache>(CACHE_MANAGER);
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
it('should be defined', async () => {
assert.isDefined(cacheManager);
});
it(`should return empty`, async () => {
return request(server).get('/').expect(200, 'Not found');
});
it(`should return data`, async () => {
return request(server).get('/').expect(200, 'value');
});
after(async () => {
await app.close();
});
});

View File

@@ -1,35 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { CustomTtlModule } from '../src/custom-ttl/custom-ttl.module';
describe('Caching Custom TTL', () => {
let server;
let app: INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [CustomTtlModule],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
it('should return a differnt value after the TTL is elapsed', async () => {
await request(server).get('/').expect(200, '0');
await new Promise(resolve => setTimeout(resolve, 500));
await request(server).get('/').expect(200, '1');
});
it('should return the cached value within the TTL', async () => {
await request(server).get('/').expect(200, '0');
await new Promise(resolve => setTimeout(resolve, 200));
await request(server).get('/').expect(200, '0');
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -1,31 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { MultiStoreModule } from '../src/multi-store/multi-store.module';
describe('Caching Multi Store', () => {
let server;
let app: INestApplication;
before(async () => {
const module = await Test.createTestingModule({
imports: [MultiStoreModule],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
it(`should return empty`, async () => {
return request(server).get('/').expect(200, '');
});
it(`should return data`, async () => {
return request(server).get('/').expect(200, 'multi-store-value');
});
after(async () => {
await app.close();
});
});

View File

@@ -1,16 +0,0 @@
import { Controller, Get, Inject, CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Controller()
export class AsyncRegisterExtraController {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
@Get()
async getFromStore(): Promise<string> {
const value: string | undefined = await this.cacheManager.get('key');
if (!value) {
await this.cacheManager.set('key', 'value');
}
return value ?? 'Not found';
}
}

View File

@@ -1,16 +0,0 @@
import { CacheModule, Module } from '@nestjs/common';
import { AsyncRegisterExtraController } from './async-register-extra.controller';
import { CacheConfig } from './config/cache.config';
import { ConfigModule } from './config/config.module';
@Module({
imports: [
CacheModule.registerAsync({
extraProviders: [ConfigModule],
isGlobal: true,
useClass: CacheConfig,
}),
],
controllers: [AsyncRegisterExtraController],
})
export class AsyncRegisterExtraModule {}

View File

@@ -1,17 +0,0 @@
import {
CacheModuleOptions,
CacheOptionsFactory,
Injectable,
} from '@nestjs/common';
import { ConfigService } from './config.service';
@Injectable()
export class CacheConfig implements CacheOptionsFactory {
constructor(private readonly configService: ConfigService) {}
createCacheOptions(): CacheModuleOptions {
const ttl = this.configService.getTtl();
return { ttl };
}
}

View File

@@ -1,8 +0,0 @@
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}

View File

@@ -1,8 +0,0 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class ConfigService {
public getTtl(): number {
return 10;
}
}

View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
"@nestjs/common/*": ["../../packages/common/*"],
"@nestjs/core": ["../../packages/core"],
"@nestjs/core/*": ["../../packages/core/*"],
"@nestjs/microservices": ["../../packages/microservices"],
"@nestjs/microservices/*": ["../../packages/microservices/*"],
"@nestjs/websockets": ["../../packages/websockets"],
"@nestjs/websockets/*": ["../../packages/websockets/*"],
"@nestjs/testing": ["../../packages/testing"],
"@nestjs/testing/*": ["../../packages/testing/*"],
"@nestjs/platform-express": ["../../packages/platform-express"],
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
"@nestjs/platform-ws": ["../../packages/platform-ws"],
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
}
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -1,16 +0,0 @@
import { Controller, Get, Inject, CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Controller()
export class AsyncRegisterController {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
@Get()
async getFromStore(): Promise<string> {
const value: string | undefined = await this.cacheManager.get('key');
if (!value) {
await this.cacheManager.set('key', 'value');
}
return value ?? 'Not found';
}
}

View File

@@ -1,14 +0,0 @@
import { CacheModule, Module } from '@nestjs/common';
import { AsyncRegisterController } from './async-register.controller';
import { CacheConfig } from './config/cache.config';
@Module({
imports: [
CacheModule.registerAsync({
isGlobal: true,
useClass: CacheConfig,
}),
],
controllers: [AsyncRegisterController],
})
export class AsyncRegisterModule {}

View File

@@ -1,14 +0,0 @@
import {
CacheModuleOptions,
CacheOptionsFactory,
Injectable,
} from '@nestjs/common';
@Injectable()
export class CacheConfig implements CacheOptionsFactory {
createCacheOptions(): CacheModuleOptions {
const ttl = 100;
return { ttl };
}
}

View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
"@nestjs/common/*": ["../../packages/common/*"],
"@nestjs/core": ["../../packages/core"],
"@nestjs/core/*": ["../../packages/core/*"],
"@nestjs/microservices": ["../../packages/microservices"],
"@nestjs/microservices/*": ["../../packages/microservices/*"],
"@nestjs/websockets": ["../../packages/websockets"],
"@nestjs/websockets/*": ["../../packages/websockets/*"],
"@nestjs/testing": ["../../packages/testing"],
"@nestjs/testing/*": ["../../packages/testing/*"],
"@nestjs/platform-express": ["../../packages/platform-express"],
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
"@nestjs/platform-ws": ["../../packages/platform-ws"],
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
}
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -1,20 +0,0 @@
import {
CacheInterceptor,
CacheTTL,
Controller,
Get,
UseInterceptors,
} from '@nestjs/common';
@Controller()
export class CustomTtlController {
counter = 0;
constructor() {}
@Get()
@CacheTTL(500)
@UseInterceptors(CacheInterceptor)
getNumber() {
return this.counter++;
}
}

View File

@@ -1,8 +0,0 @@
import { CacheModule, Module } from '@nestjs/common';
import { CustomTtlController } from './custom-ttl.controller';
@Module({
imports: [CacheModule.register()],
controllers: [CustomTtlController],
})
export class CustomTtlModule {}

View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
"@nestjs/common/*": ["../../packages/common/*"],
"@nestjs/core": ["../../packages/core"],
"@nestjs/core/*": ["../../packages/core/*"],
"@nestjs/microservices": ["../../packages/microservices"],
"@nestjs/microservices/*": ["../../packages/microservices/*"],
"@nestjs/websockets": ["../../packages/websockets"],
"@nestjs/websockets/*": ["../../packages/websockets/*"],
"@nestjs/testing": ["../../packages/testing"],
"@nestjs/testing/*": ["../../packages/testing/*"],
"@nestjs/platform-express": ["../../packages/platform-express"],
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
"@nestjs/platform-ws": ["../../packages/platform-ws"],
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
}
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -1,16 +0,0 @@
import { Controller, Get, Inject, CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Controller()
export class MultiStoreController {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
@Get()
async getFromMultiStore(): Promise<unknown> {
const value = await this.cacheManager.get('multi-store-key');
if (!value) {
await this.cacheManager.set('multi-store-key', 'multi-store-value');
}
return value;
}
}

View File

@@ -1,24 +0,0 @@
import { CacheModule, Module } from '@nestjs/common';
import { redisStore } from 'cache-manager-redis-store';
import { MultiStoreController } from './multi-store.controller';
@Module({
imports: [
CacheModule.register([
{
store: 'memory',
max: 100,
ttl: 10,
},
{
store: redisStore,
host: 'localhost',
port: 6379,
db: 0,
ttl: 600,
},
]),
],
controllers: [MultiStoreController],
})
export class MultiStoreModule {}

View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
"@nestjs/common/*": ["../../packages/common/*"],
"@nestjs/core": ["../../packages/core"],
"@nestjs/core/*": ["../../packages/core/*"],
"@nestjs/microservices": ["../../packages/microservices"],
"@nestjs/microservices/*": ["../../packages/microservices/*"],
"@nestjs/websockets": ["../../packages/websockets"],
"@nestjs/websockets/*": ["../../packages/websockets/*"],
"@nestjs/testing": ["../../packages/testing"],
"@nestjs/testing/*": ["../../packages/testing/*"],
"@nestjs/platform-express": ["../../packages/platform-express"],
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
"@nestjs/platform-ws": ["../../packages/platform-ws"],
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
}
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -1,15 +1,16 @@
import { HttpServer, HttpStatus, INestApplication } from '@nestjs/common';
import { HttpStatus, INestApplication } from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { RawServerDefault } from 'fastify';
import * as request from 'supertest';
import { ErrorsController } from '../src/errors/errors.controller';
describe('Error messages', () => {
let server: HttpServer;
let server: RawServerDefault;
describe('Express', () => {
let app: INestApplication;

View File

@@ -3,7 +3,10 @@ import {
Get,
MiddlewareConsumer,
Module,
NestMiddleware,
NestModule,
Query,
Req,
RequestMethod,
} from '@nestjs/common';
import {
@@ -14,178 +17,385 @@ import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
const INCLUDED_VALUE = 'test_included';
const QUERY_VALUE = 'test_query';
const REQ_URL_VALUE = 'test_req_url';
const RETURN_VALUE = 'test';
const SCOPED_VALUE = 'test_scoped';
const WILDCARD_VALUE = 'test_wildcard';
@Controller()
class TestController {
@Get('express_style_wildcard/wildcard_nested')
express_style_wildcard() {
return RETURN_VALUE;
}
@Get('test')
test() {
return RETURN_VALUE;
}
@Get('query')
query() {
return RETURN_VALUE;
}
@Get('tests/wildcard_nested')
wildcard_nested() {
return RETURN_VALUE;
}
@Get('tests/included')
included() {
return RETURN_VALUE;
}
}
@Controller(QUERY_VALUE)
class TestQueryController {
@Get()
[QUERY_VALUE](@Query('test') test: string) {
return test;
}
}
@Module({
imports: [AppModule],
controllers: [TestController, TestQueryController],
})
class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.end(INCLUDED_VALUE))
.forRoutes({ path: 'tests/included', method: RequestMethod.POST })
.apply((req, res, next) => res.end(`${REQ_URL_VALUE}${req.url}`))
.forRoutes('req/url/')
.apply((req, res, next) => res.end(WILDCARD_VALUE))
.forRoutes('express_style_wildcard/*', 'tests/(.*)')
.apply((req, res, next) => res.end(QUERY_VALUE))
.forRoutes('query')
.apply((req, res, next) => next())
.forRoutes(TestQueryController)
.apply((req, res, next) => res.end(SCOPED_VALUE))
.forRoutes(TestController)
.apply((req, res, next) => res.end(RETURN_VALUE))
.exclude({ path: QUERY_VALUE, method: -1 as any })
.forRoutes('(.*)');
}
}
describe('Middleware (FastifyAdapter)', () => {
let app: NestFastifyApplication;
beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [TestModule],
}).compile()
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
describe('should return expected values depending on the route', () => {
const INCLUDED_VALUE = 'test_included';
const QUERY_VALUE = 'test_query';
const REQ_URL_VALUE = 'test_req_url';
const RETURN_VALUE = 'test';
const SCOPED_VALUE = 'test_scoped';
const WILDCARD_VALUE = 'test_wildcard';
await app.init();
@Controller()
class TestController {
@Get('express_style_wildcard/wildcard_nested')
express_style_wildcard() {
return RETURN_VALUE;
}
@Get('test')
test() {
return RETURN_VALUE;
}
@Get('query')
query() {
return RETURN_VALUE;
}
@Get('tests/wildcard_nested')
wildcard_nested() {
return RETURN_VALUE;
}
@Get('tests/included')
included() {
return RETURN_VALUE;
}
}
@Controller(QUERY_VALUE)
class TestQueryController {
@Get()
[QUERY_VALUE](@Query('test') test: string) {
return test;
}
}
@Module({
imports: [AppModule],
controllers: [TestController, TestQueryController],
})
class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.end(INCLUDED_VALUE))
.forRoutes({ path: 'tests/included', method: RequestMethod.POST })
.apply((req, res, next) => res.end(REQ_URL_VALUE))
.forRoutes('req/url/(.*)')
.apply((req, res, next) => res.end(WILDCARD_VALUE))
.forRoutes('express_style_wildcard/*', 'tests/(.*)')
.apply((req, res, next) => res.end(QUERY_VALUE))
.forRoutes('query')
.apply((req, res, next) => next())
.forRoutes(TestQueryController)
.apply((req, res, next) => res.end(SCOPED_VALUE))
.forRoutes(TestController)
.apply((req, res, next) => res.end(RETURN_VALUE))
.exclude({ path: QUERY_VALUE, method: -1 as any })
.forRoutes('(.*)');
}
}
beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [TestModule],
}).compile()
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
await app.init();
});
it(`forRoutes((.*))`, () => {
return app
.inject({
method: 'GET',
url: '/hello',
})
.then(({ payload }) => expect(payload).to.be.eql(RETURN_VALUE));
});
it(`forRoutes(TestController)`, () => {
return app
.inject({
method: 'GET',
url: '/test',
})
.then(({ payload }) => expect(payload).to.be.eql(SCOPED_VALUE));
});
it(`query?test=${QUERY_VALUE} forRoutes(query)`, () => {
return app
.inject({
method: 'GET',
url: '/query',
query: {
test: QUERY_VALUE,
},
})
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
});
it(`${QUERY_VALUE}?test=${QUERY_VALUE} forRoutes(${QUERY_VALUE})`, () => {
return app
.inject({
method: 'GET',
url: QUERY_VALUE,
query: {
test: QUERY_VALUE,
},
})
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
});
it(`forRoutes(tests/(.*))`, () => {
return app
.inject({
method: 'GET',
url: '/tests/wildcard_nested',
})
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
it(`forRoutes(express_style_wildcard/*)`, () => {
return app
.inject({
method: 'GET',
url: '/express_style_wildcard/wildcard_nested',
})
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
it(`forRoutes(req/url/)`, () => {
const reqUrl = '/test';
return app
.inject({
method: 'GET',
url: `/req/url${reqUrl}`,
})
.then(({ payload }) => expect(payload).to.be.eql(REQ_URL_VALUE));
});
it(`GET forRoutes(POST tests/included)`, () => {
return app
.inject({
method: 'GET',
url: '/tests/included',
})
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
it(`POST forRoutes(POST tests/included)`, () => {
return app
.inject({
method: 'POST',
url: '/tests/included',
})
.then(({ payload }) => expect(payload).to.be.eql(INCLUDED_VALUE));
});
afterEach(async () => {
await app.close();
});
});
it(`forRoutes((.*))`, () => {
return app
.inject({
method: 'GET',
url: '/hello',
})
.then(({ payload }) => expect(payload).to.be.eql(RETURN_VALUE));
});
describe.only('should execute middleware only once for given routes', () => {
class Middleware implements NestMiddleware {
use(request: any, reply: any, next: () => void) {
if (request.middlewareExecutionCount === undefined) {
request.middlewareExecutionCount = 1;
} else {
request.middlewareExecutionCount++;
}
next();
}
}
it(`forRoutes(TestController)`, () => {
return app
.inject({
method: 'GET',
url: '/test',
})
.then(({ payload }) => expect(payload).to.be.eql(SCOPED_VALUE));
});
@Controller()
class AbcController {
@Get('/a')
async a(@Req() request: any) {
return this.validateExecutionCount({
request,
expected: 1,
});
}
it(`query?test=${QUERY_VALUE} forRoutes(query)`, () => {
return app
.inject({
method: 'GET',
url: '/query',
query: {
test: QUERY_VALUE,
},
})
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
});
@Get('/a/b')
async ab(@Req() request: any) {
return this.validateExecutionCount({
request,
expected: 1,
});
}
it(`${QUERY_VALUE}?test=${QUERY_VALUE} forRoutes(${QUERY_VALUE})`, () => {
return app
.inject({
method: 'GET',
url: QUERY_VALUE,
query: {
test: QUERY_VALUE,
},
})
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
});
@Get('/a/b/c')
async abc(@Req() request: any) {
return this.validateExecutionCount({
request,
expected: 1,
});
}
it(`forRoutes(tests/(.*))`, () => {
return app
.inject({
method: 'GET',
url: '/tests/wildcard_nested',
})
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
@Get('/similar')
async withSimilar(@Req() request: any) {
return this.validateExecutionCount({
request,
expected: 1,
});
}
it(`forRoutes(express_style_wildcard/*)`, () => {
return app
.inject({
method: 'GET',
url: '/express_style_wildcard/wildcard_nested',
})
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
@Get('/similar/test')
async withSimilarTest(@Req() request: any) {
return this.validateExecutionCount({
request,
expected: 1,
});
}
it(`forRoutes(req/url/)`, () => {
const reqUrl = '/test';
return app
.inject({
method: 'GET',
url: `/req/url${reqUrl}`,
})
.then(({ payload }) =>
expect(payload).to.be.eql(`${REQ_URL_VALUE}${reqUrl}`),
);
});
@Get('/similar/:id')
async withSimilarId(@Req() request: any) {
return this.validateExecutionCount({
request,
expected: 1,
});
}
it(`GET forRoutes(POST tests/included)`, () => {
return app
.inject({
method: 'GET',
url: '/tests/included',
})
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
private validateExecutionCount({
request,
expected,
}: {
request: any;
expected: number;
}) {
let actual: number | undefined;
actual = request.raw.middlewareExecutionCount;
actual ??= 0;
it(`POST forRoutes(POST tests/included)`, () => {
return app
.inject({
method: 'POST',
url: '/tests/included',
})
.then(({ payload }) => expect(payload).to.be.eql(INCLUDED_VALUE));
});
return {
success: actual === expected,
actual,
expected,
};
}
}
afterEach(async () => {
await app.close();
@Module({
controllers: [AbcController],
})
class TestModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(Middleware).forRoutes(AbcController);
}
}
beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [TestModule],
}).compile()
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
await app.init();
});
it(`GET forRoutes(/a/b/c)`, () => {
return app
.inject({
method: 'GET',
url: '/a/b/c',
})
.then(({ payload }) => {
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
expected: 1,
}),
);
});
});
it(`GET forRoutes(/a/b)`, () => {
return app
.inject({
method: 'GET',
url: '/a/b',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
expected: 1,
}),
),
);
});
it(`GET forRoutes(/a)`, () => {
return app
.inject({
method: 'GET',
url: '/a',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
expected: 1,
}),
),
);
});
it(`GET forRoutes(/similar)`, () => {
return app
.inject({
method: 'GET',
url: '/similar',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
expected: 1,
}),
),
);
});
it(`GET forRoutes(/similar/test)`, () => {
return app
.inject({
method: 'GET',
url: '/similar/test',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
expected: 1,
}),
),
);
});
it(`GET forRoutes(/similar/arbitrary)`, () => {
return app
.inject({
method: 'GET',
url: '/similar/arbitrary',
})
.then(({ payload }) =>
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
expected: 1,
}),
),
);
});
afterEach(async () => {
await app.close();
});
});
});

View File

@@ -0,0 +1,103 @@
import {
Controller,
Get,
INestApplication,
Injectable,
MiddlewareConsumer,
NestMiddleware,
Module,
} from '@nestjs/common';
import { Test } from '../../../packages/testing';
import * as request from 'supertest';
import { expect } from 'chai';
/**
* Number of times that the middleware was executed.
*/
let triggerCounter = 0;
@Injectable()
class Middleware implements NestMiddleware {
use(req, res, next) {
triggerCounter++;
next();
}
}
@Controller()
class TestController {
@Get('/test')
testA() {}
@Get('/:id')
testB() {}
@Get('/static/route')
testC() {}
@Get('/:id/:nested')
testD() {}
}
@Module({
controllers: [TestController],
})
class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(Middleware).forRoutes(TestController);
}
}
describe('Middleware (run on route match)', () => {
let app: INestApplication;
beforeEach(async () => {
triggerCounter = 0;
app = (
await Test.createTestingModule({
imports: [TestModule],
}).compile()
).createNestApplication();
await app.init();
});
it(`forRoutes(TestController) should execute middleware once when request url is equal match`, () => {
return request(app.getHttpServer())
.get('/test')
.expect(200)
.then(() => {
expect(triggerCounter).to.be.eq(1);
});
});
it(`forRoutes(TestController) should execute middleware once when request url is not equal match`, () => {
return request(app.getHttpServer())
.get('/1')
.expect(200)
.then(() => {
expect(triggerCounter).to.be.eq(1);
});
});
it(`forRoutes(TestController) should execute middleware once when request url is not of nested params`, () => {
return request(app.getHttpServer())
.get('/static/route')
.expect(200)
.then(() => {
expect(triggerCounter).to.be.eq(1);
});
});
it(`forRoutes(TestController) should execute middleware once when request url is of nested params`, () => {
return request(app.getHttpServer())
.get('/1/abc')
.expect(200)
.then(() => {
expect(triggerCounter).to.be.eq(1);
});
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -822,23 +822,6 @@
"token": "HttpAdapterHost"
}
},
"-553129559": {
"id": "-553129559",
"label": "HttpAdapterHost",
"parent": "555176277",
"metadata": {
"type": "provider",
"internal": true,
"sourceModuleName": "InternalCoreModule",
"durable": false,
"static": true,
"scope": 0,
"transient": false,
"exported": true,
"token": "HttpAdapterHost",
"initTime": 0
}
},
"-702581189": {
"id": "-702581189",
"label": "LazyModuleLoader",
@@ -2214,23 +2197,6 @@
},
"id": "1976848738"
},
"-1759625292": {
"source": "-553129559",
"target": "-326832201",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "InternalCoreModule",
"sourceClassName": "HttpAdapterHost",
"targetClassName": "HttpAdapterHost",
"sourceClassToken": "HttpAdapterHost",
"targetClassToken": "HttpAdapterHost",
"targetModuleName": "InternalCoreModule",
"keyOrIndex": 0,
"injectionType": "constructor",
"internal": true
},
"id": "-1759625292"
},
"-2105726668": {
"source": "-1803759743",
"target": "1010833816",
@@ -2756,7 +2722,8 @@
"patterns": [
"createExternalSvc"
],
"isEventHandler": false
"isEventHandler": false,
"extras": {}
},
"id": "-349899408_create"
},
@@ -2771,7 +2738,8 @@
"patterns": [
"findAllExternalSvc"
],
"isEventHandler": false
"isEventHandler": false,
"extras": {}
},
"id": "-349899408_findAll"
},
@@ -2786,7 +2754,8 @@
"patterns": [
"findOneExternalSvc"
],
"isEventHandler": false
"isEventHandler": false,
"extras": {}
},
"id": "-349899408_findOne"
},
@@ -2801,7 +2770,8 @@
"patterns": [
"updateExternalSvc"
],
"isEventHandler": false
"isEventHandler": false,
"extras": {}
},
"id": "-349899408_update"
},
@@ -2816,7 +2786,8 @@
"patterns": [
"removeExternalSvc"
],
"isEventHandler": false
"isEventHandler": false,
"extras": {}
},
"id": "-349899408_remove"
}

View File

@@ -806,23 +806,6 @@
"token": "HttpAdapterHost"
}
},
"-553129559": {
"id": "-553129559",
"label": "HttpAdapterHost",
"parent": "555176277",
"metadata": {
"type": "provider",
"internal": true,
"sourceModuleName": "InternalCoreModule",
"durable": false,
"static": true,
"scope": 0,
"transient": false,
"exported": true,
"token": "HttpAdapterHost",
"initTime": 0
}
},
"-702581189": {
"id": "-702581189",
"label": "LazyModuleLoader",
@@ -2198,23 +2181,6 @@
},
"id": "1976848738"
},
"-1759625292": {
"source": "-553129559",
"target": "-326832201",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "InternalCoreModule",
"sourceClassName": "HttpAdapterHost",
"targetClassName": "HttpAdapterHost",
"sourceClassToken": "HttpAdapterHost",
"targetClassToken": "HttpAdapterHost",
"targetModuleName": "InternalCoreModule",
"keyOrIndex": 0,
"injectionType": "constructor",
"internal": true
},
"id": "-1759625292"
},
"-2105726668": {
"source": "-1803759743",
"target": "1010833816",

View File

@@ -4,7 +4,7 @@ import { SerializedGraph } from '@nestjs/core/inspector/serialized-graph';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
import { readFileSync, writeFileSync } from 'fs';
import { readFileSync } from 'fs';
import { join } from 'path';
import * as sinon from 'sinon';
import { AppModule } from '../src/app.module';
@@ -26,10 +26,10 @@ describe('Graph inspector', () => {
const graph = testingModule.get(SerializedGraph);
// Update snapshot:
writeFileSync(
join(__dirname, 'fixtures', 'pre-init-graph.json'),
graph.toString(),
);
// writeFileSync(
// join(__dirname, 'fixtures', 'pre-init-graph.json'),
// graph.toString(),
// );
const snapshot = readFileSync(
join(__dirname, 'fixtures', 'pre-init-graph.json'),
@@ -54,10 +54,10 @@ describe('Graph inspector', () => {
const graph = testingModule.get(SerializedGraph);
// Update snapshot:
writeFileSync(
join(__dirname, 'fixtures', 'post-init-graph.json'),
graph.toString(),
);
// writeFileSync(
// join(__dirname, 'fixtures', 'post-init-graph.json'),
// graph.toString(),
// );
const snapshot = readFileSync(
join(__dirname, 'fixtures', 'post-init-graph.json'),

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"lib": ["dom"],

View File

@@ -8,7 +8,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist"

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -0,0 +1,12 @@
import { Injectable, Module, forwardRef } from '@nestjs/common';
import { BModule, BProvider } from './b.module';
@Injectable()
export class AProvider {}
@Module({
imports: [forwardRef(() => BModule)],
providers: [AProvider],
exports: [AProvider],
})
export class AModule {}

View File

@@ -0,0 +1,12 @@
import { Injectable, Module, forwardRef } from '@nestjs/common';
import { AModule, AProvider } from './a.module';
@Injectable()
export class BProvider {}
@Module({
imports: [forwardRef(() => AModule)],
providers: [BProvider],
exports: [BProvider],
})
export class BModule {}

View File

@@ -0,0 +1,309 @@
import {
Controller,
DynamicModule,
forwardRef,
Global,
Injectable,
Module,
} from '@nestjs/common';
import { LazyModuleLoader } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
import { AModule, AProvider } from './circular-dependency/a.module';
import { BModule, BProvider } from './circular-dependency/b.module';
describe('Modules overriding', () => {
describe('Top-level module', () => {
@Controller()
class ControllerOverwritten {}
@Module({
controllers: [ControllerOverwritten],
})
class ModuleToBeOverwritten {}
@Controller()
class ControllerOverride {}
@Module({
controllers: [ControllerOverride],
})
class ModuleOverride {}
let testingModule: TestingModule;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [ModuleToBeOverwritten],
})
.overrideModule(ModuleToBeOverwritten)
.useModule(ModuleOverride)
.compile();
});
it('should override top-level modules using testing module builder', () => {
expect(() =>
testingModule.get<ControllerOverwritten>(ControllerOverwritten),
).to.throw();
expect(
testingModule.get<ControllerOverride>(ControllerOverride),
).to.be.an.instanceof(ControllerOverride);
});
});
describe('Dynamic module', () => {
@Controller()
class ControllerOverwritten {}
@Module({})
class DynamicModuleToBeOverwritten {}
const dynamicModuleOverwritten: DynamicModule = {
module: DynamicModuleToBeOverwritten,
controllers: [ControllerOverwritten],
};
@Controller()
class ControllerOverride {}
@Module({})
class DynamicModuleOverride {}
const dynamicModuleOverride: DynamicModule = {
module: DynamicModuleOverride,
controllers: [ControllerOverride],
};
let testingModule: TestingModule;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [dynamicModuleOverwritten],
})
.overrideModule(dynamicModuleOverwritten)
.useModule(dynamicModuleOverride)
.compile();
});
it('should override dynamic modules using testing module builder', () => {
expect(() =>
testingModule.get<ControllerOverwritten>(ControllerOverwritten),
).to.throw();
expect(
testingModule.get<ControllerOverride>(ControllerOverride),
).to.be.an.instanceof(ControllerOverride);
});
});
describe('Circular dependency module', () => {
let testingModule: TestingModule;
@Injectable()
class CProvider {}
@Module({
providers: [CProvider],
})
class CModule {}
@Injectable()
class BProviderOverride {}
@Module({
imports: [forwardRef(() => AModule), forwardRef(() => CModule)],
providers: [BProviderOverride],
exports: [BProviderOverride],
})
class BModuleOverride {}
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [AModule],
})
.overrideModule(BModule)
.useModule(BModuleOverride)
.compile();
});
it('should override top-level modules using testing module builder', () => {
expect(testingModule.get<AProvider>(AProvider)).to.be.an.instanceof(
AProvider,
);
expect(() => testingModule.get<BProvider>(BProvider)).to.throw();
expect(testingModule.get<CProvider>(CProvider)).to.be.an.instanceof(
CProvider,
);
expect(
testingModule.get<BProviderOverride>(BProviderOverride),
).to.be.an.instanceof(BProviderOverride);
});
});
describe('Nested module', () => {
let testingModule: TestingModule;
@Controller()
class OverwrittenNestedModuleController {}
@Module({
controllers: [OverwrittenNestedModuleController],
})
class OverwrittenNestedModule {}
@Controller()
class OverrideNestedModuleController {}
@Module({
controllers: [OverrideNestedModuleController],
})
class OverrideNestedModule {}
@Module({
imports: [OverwrittenNestedModule],
})
class AppModule {}
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideModule(OverwrittenNestedModule)
.useModule(OverrideNestedModule)
.compile();
});
it('should override nested modules using testing module builder', () => {
expect(
testingModule.get<OverrideNestedModuleController>(
OverrideNestedModuleController,
),
).to.be.an.instanceof(OverrideNestedModuleController);
expect(() =>
testingModule.get<OverwrittenNestedModuleController>(
OverwrittenNestedModuleController,
),
).to.throw();
});
});
describe('Lazy-loaded module', () => {
let testingModule: TestingModule;
@Injectable()
class OverwrittenLazyProvider {
value() {
return 'overwritten lazy';
}
}
@Module({
providers: [
{
provide: 'LAZY_PROVIDER',
useClass: OverwrittenLazyProvider,
},
],
})
class OverwrittenLazyModule {}
@Injectable()
class OverrideLazyProvider {
value() {
return 'override lazy';
}
}
@Module({
providers: [
{
provide: 'LAZY_PROVIDER',
useClass: OverrideLazyProvider,
},
],
})
class OverrideLazyModule {}
@Injectable()
class AppService {
constructor(private lazyModuleLoader: LazyModuleLoader) {}
async value() {
const moduleRef = await this.lazyModuleLoader.load(
() => OverwrittenLazyModule,
);
return moduleRef.get('LAZY_PROVIDER').value();
}
}
@Module({
imports: [],
providers: [AppService],
})
class AppModule {}
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideModule(OverwrittenLazyModule)
.useModule(OverrideLazyModule)
.compile();
});
it('should override lazy loaded modules using testing module builder', async () => {
const result = await testingModule.get<AppService>(AppService).value();
expect(result).to.be.equal('override lazy');
});
});
describe('Global module', () => {
let testingModule: TestingModule;
@Injectable()
class OverwrittenProvider {
value() {
return 'overwritten lazy';
}
}
@Global()
@Module({
providers: [OverwrittenProvider],
exports: [OverwrittenProvider],
})
class OverwrittenModule {}
@Injectable()
class OverrideProvider {
value() {
return 'override lazy';
}
}
@Global()
@Module({
providers: [OverrideProvider],
exports: [OverrideProvider],
})
class OverrideModule {}
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [OverwrittenModule],
})
.overrideModule(OverwrittenModule)
.useModule(OverrideModule)
.compile();
});
it('should override global modules using testing module builder', () => {
expect(
testingModule.get<OverrideProvider>(OverrideProvider),
).to.be.an.instanceof(OverrideProvider);
expect(() =>
testingModule.get<OverwrittenProvider>(OverwrittenProvider),
).to.throw();
});
});
});

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -352,4 +352,70 @@ describe('URI Versioning', () => {
await app.close();
});
});
// ======================================================================== //
describe('with the global prefix enabled and an excluded route', () => {
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
app.setGlobalPrefix('api', { exclude: ['/foo/bar'] });
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
await app.init();
});
describe('GET /', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/api/v1')
.expect(200)
.expect('Hello World V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/api/v2')
.expect(200)
.expect('Hello World V2!');
});
it('V3', () => {
return request(app.getHttpServer()).get('/api/v3').expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/api').expect(404);
});
});
describe('GET /foo/bar (excluded from the API prefix)', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1/foo/bar')
.expect(200)
.expect('Hello FooBar!');
});
it('V2', () => {
return request(app.getHttpServer()).get('/v2/foo/bar').expect(404);
});
it('V3', () => {
return request(app.getHttpServer()).get('/v3/foo/bar').expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/foo/bar').expect(404);
});
});
after(async () => {
await app.close();
});
});
});

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

View File

@@ -145,7 +145,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
}),
);
});
});
}).timeout(5000);
it(`should support 2 different gateways running on the same path (but different ports)`, async function () {
this.retries(10);

View File

@@ -7,7 +7,7 @@
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",

31667
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
import { MODULE_OPTIONS_TOKEN } from './cache.module-definition';
/**
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
* @publicApi
*/
export const CACHE_MANAGER = 'CACHE_MANAGER';
export const CACHE_KEY_METADATA = 'cache_module:cache_key';
export const CACHE_TTL_METADATA = 'cache_module:cache_ttl';
/**
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
* @publicApi
*/
export const CACHE_MODULE_OPTIONS = MODULE_OPTIONS_TOKEN;

View File

@@ -1,12 +0,0 @@
import { ConfigurableModuleBuilder } from '../module-utils';
import {
CacheModuleOptions,
CacheOptionsFactory,
} from './interfaces/cache-module.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<CacheModuleOptions>({
moduleName: 'Cache',
})
.setFactoryMethodName('createCacheOptions' as keyof CacheOptionsFactory)
.build();

View File

@@ -1,60 +0,0 @@
import { Module } from '../decorators';
import { DynamicModule } from '../interfaces';
import { CACHE_MANAGER } from './cache.constants';
import { ConfigurableModuleClass } from './cache.module-definition';
import { createCacheManager } from './cache.providers';
import {
CacheModuleAsyncOptions,
CacheModuleOptions,
} from './interfaces/cache-module.interface';
/**
* Module that provides Nest cache-manager.
*
* @see [Caching](https://docs.nestjs.com/techniques/caching)
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
* @publicApi
*/
@Module({
providers: [createCacheManager()],
exports: [CACHE_MANAGER],
})
export class CacheModule extends ConfigurableModuleClass {
/**
* Configure the cache manager statically.
*
* @param options options to configure the cache manager
*
* @see [Customize caching](https://docs.nestjs.com/techniques/caching#customize-caching)
*/
static register<StoreConfig extends Record<any, any> = Record<string, any>>(
options: CacheModuleOptions<StoreConfig> = {} as any,
): DynamicModule {
return {
global: options.isGlobal,
...super.register(options),
};
}
/**
* Configure the cache manager dynamically.
*
* @param options method for dynamically supplying cache manager configuration
* options
*
* @see [Async configuration](https://docs.nestjs.com/techniques/caching#async-configuration)
*/
static registerAsync<
StoreConfig extends Record<any, any> = Record<string, any>,
>(options: CacheModuleAsyncOptions<StoreConfig>): DynamicModule {
const moduleDefinition = super.registerAsync(options);
return {
global: options.isGlobal,
...moduleDefinition,
providers: options.extraProviders
? moduleDefinition.providers.concat(options.extraProviders)
: moduleDefinition.providers,
};
}
}

View File

@@ -1,61 +0,0 @@
import { Provider } from '../interfaces';
import { loadPackage } from '../utils/load-package.util';
import { CACHE_MANAGER } from './cache.constants';
import { MODULE_OPTIONS_TOKEN } from './cache.module-definition';
import { defaultCacheOptions } from './default-options';
import {
CacheManagerOptions,
CacheStore,
} from './interfaces/cache-manager.interface';
/**
* Creates a CacheManager Provider.
*
* @publicApi
*/
export function createCacheManager(): Provider {
return {
provide: CACHE_MANAGER,
useFactory: async (options: CacheManagerOptions) => {
const cacheManager = loadPackage('cache-manager', 'CacheModule', () =>
require('cache-manager'),
);
const cacheManagerIsv5OrGreater = 'memoryStore' in cacheManager;
const cachingFactory = async (
store: CacheManagerOptions['store'],
options: Omit<CacheManagerOptions, 'store'>,
): Promise<Record<string, any>> => {
if (!cacheManagerIsv5OrGreater) {
return cacheManager.caching({
...defaultCacheOptions,
...{ ...options, store },
});
}
let cache: string | Function | CacheStore = 'memory';
defaultCacheOptions.ttl *= 1000;
if (typeof store === 'object') {
if ('create' in store) {
cache = store.create;
} else {
cache = store;
}
} else if (typeof store === 'function') {
cache = store;
}
return cacheManager.caching(cache, {
...defaultCacheOptions,
...options,
});
};
return Array.isArray(options)
? cacheManager.multiCaching(
await Promise.all(
options.map(option => cachingFactory(option.store, option)),
),
)
: cachingFactory(options.store, options);
},
inject: [MODULE_OPTIONS_TOKEN],
};
}

View File

@@ -1,18 +0,0 @@
import { SetMetadata } from '../../decorators';
import { CACHE_KEY_METADATA } from '../cache.constants';
/**
* Decorator that sets the caching key used to store/retrieve cached items for
* Web sockets or Microservice based apps.
*
* For example:
* `@CacheKey('events')`
*
* @param key string naming the field to be used as a cache key
*
* @see [Caching](https://docs.nestjs.com/techniques/caching)
*
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
* @publicApi
*/
export const CacheKey = (key: string) => SetMetadata(CACHE_KEY_METADATA, key);

View File

@@ -1,19 +0,0 @@
import { SetMetadata } from '../../decorators';
import { ExecutionContext } from '../../interfaces/features/execution-context.interface';
import { CACHE_TTL_METADATA } from '../cache.constants';
/**
* Decorator that sets the cache ttl setting the duration for cache expiration.
*
* For example: `@CacheTTL(5)`
*
* @param ttl number set the cache expiration time
*
* @see [Caching](https://docs.nestjs.com/techniques/caching)
*
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
* @publicApi
*/
type CacheTTLFactory = (ctx: ExecutionContext) => Promise<number> | number;
export const CacheTTL = (ttl: number | CacheTTLFactory) =>
SetMetadata(CACHE_TTL_METADATA, ttl);

View File

@@ -1,2 +0,0 @@
export * from './cache-key.decorator';
export * from './cache-ttl.decorator';

View File

@@ -1,5 +0,0 @@
export const defaultCacheOptions = {
ttl: 5,
max: 100,
store: 'memory',
};

View File

@@ -1,5 +0,0 @@
export * from './cache.constants';
export * from './cache.module';
export * from './decorators';
export * from './interceptors';
export * from './interfaces';

View File

@@ -1,134 +0,0 @@
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Inject, Injectable, Optional } from '../../decorators';
import { StreamableFile } from '../../file-stream';
import {
CallHandler,
ExecutionContext,
HttpServer,
NestInterceptor,
} from '../../interfaces';
import { Logger } from '../../services/logger.service';
import { loadPackage } from '../../utils/load-package.util';
import { isFunction, isNil } from '../../utils/shared.utils';
import {
CACHE_KEY_METADATA,
CACHE_MANAGER,
CACHE_TTL_METADATA,
} from '../cache.constants';
/** @deprecated */
const HTTP_ADAPTER_HOST = 'HttpAdapterHost';
/** @deprecated */
const REFLECTOR = 'Reflector';
/** @deprecated Import from the "@nestjs/core" instead. */
export interface HttpAdapterHost<T extends HttpServer = any> {
httpAdapter: T;
}
/**
* @see [Caching](https://docs.nestjs.com/techniques/caching)
*
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
* @publicApi
*/
@Injectable()
export class CacheInterceptor implements NestInterceptor {
@Optional()
@Inject(HTTP_ADAPTER_HOST)
protected readonly httpAdapterHost: HttpAdapterHost;
protected allowedMethods = ['GET'];
private cacheManagerIsv5OrGreater: boolean;
constructor(
@Inject(CACHE_MANAGER) protected readonly cacheManager: any,
@Inject(REFLECTOR) protected readonly reflector: any,
) {
// We need to check if the cache-manager package is v5 or greater
// because the set method signature changed in v5
const cacheManagerPackage = loadPackage(
'cache-manager',
'CacheModule',
() => require('cache-manager'),
);
this.cacheManagerIsv5OrGreater = 'memoryStore' in cacheManagerPackage;
Logger.warn(
'DEPRECATED! "CacheModule" (from the "@nestjs/common" package) is deprecated and will be removed in the next major release. Please, use the "@nestjs/cache-manager" package instead.',
);
}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const key = this.trackBy(context);
const ttlValueOrFactory =
this.reflector.get(CACHE_TTL_METADATA, context.getHandler()) ?? null;
if (!key) {
return next.handle();
}
try {
const value = await this.cacheManager.get(key);
if (!isNil(value)) {
return of(value);
}
const ttl = isFunction(ttlValueOrFactory)
? await ttlValueOrFactory(context)
: ttlValueOrFactory;
return next.handle().pipe(
tap(async response => {
if (response instanceof StreamableFile) {
return;
}
const args = [key, response];
if (!isNil(ttl)) {
args.push(this.cacheManagerIsv5OrGreater ? ttl : { ttl });
}
try {
await this.cacheManager.set(...args);
} catch (err) {
Logger.error(
`An error has occurred when inserting "key: ${key}", "value: ${response}"`,
'CacheInterceptor',
);
}
}),
);
} catch {
return next.handle();
}
}
protected trackBy(context: ExecutionContext): string | undefined {
const httpAdapter = this.httpAdapterHost.httpAdapter;
const isHttpApp = httpAdapter && !!httpAdapter.getRequestMethod;
const cacheMetadata = this.reflector.get(
CACHE_KEY_METADATA,
context.getHandler(),
);
if (!isHttpApp || cacheMetadata) {
return cacheMetadata;
}
const request = context.getArgByIndex(0);
if (!this.isRequestCacheable(context)) {
return undefined;
}
return httpAdapter.getRequestUrl(request);
}
protected isRequestCacheable(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
return this.allowedMethods.includes(req.method);
}
}

View File

@@ -1 +0,0 @@
export * from './cache.interceptor';

View File

@@ -1,86 +0,0 @@
export interface LiteralObject {
[key: string]: any;
}
/**
* Interface defining a cache store. Implement this interface to create a custom
* cache store.
*
* @publicApi
*/
export interface CacheStore {
/**
* Create a key/value pair in the cache.
*
* @param key cache key
* @param value cache value
*/
set<T>(
key: string,
value: T,
options?: CacheStoreSetOptions<T> | number,
): Promise<void> | void;
/**
* Retrieve a key/value pair from the cache.
*
* @param key cache key
*/
get<T>(key: string): Promise<T | undefined> | T | undefined;
/**
* Destroy a key/value pair from the cache.
*
* @param key cache key
*/
del?(key: string): void | Promise<void>;
}
export interface CacheStoreSetOptions<T> {
/**
* Time to live - amount of time in seconds that a response is cached before it
* is deleted. Defaults based on your cache manager settings.
*/
ttl?: ((value: T) => number) | number;
}
/**
* Interface defining a factory to create a cache store.
*
* @publicApi
*/
export type CacheStoreFactory =
| {
/**
* Return a configured cache store.
*
* @param args Cache manager options received from `CacheModule.register()`
* or `CacheModule.registerAsync()`
*/
create(args: LiteralObject): CacheStore;
}
| ((args: LiteralObject) => CacheStore | Promise<CacheStore>);
/**
* Interface defining Cache Manager configuration options.
*
* @publicApi
*/
export interface CacheManagerOptions {
/**
* Cache storage manager. Default is `'memory'` (in-memory store). See
* [Different stores](https://docs.nestjs.com/techniques/caching#different-stores)
* for more info.
*/
store?: string | CacheStoreFactory | CacheStore;
/**
* Time to live - amount of time that a response is cached before it
* is deleted. Subsequent request will call through the route handler and refresh
* the cache. Defaults to 5 seconds. In `cache-manager@^4` this value is in seconds.
* In `cache-manager@^5` this value is in milliseconds.
*/
ttl?: number;
/**
* Maximum number of responses to store in the cache. Defaults to 100.
*/
max?: number;
isCacheableValue?: (value: any) => boolean;
}

View File

@@ -1,78 +0,0 @@
import { Provider, Type } from '../../interfaces';
import { ConfigurableModuleAsyncOptions } from '../../module-utils';
import { CacheManagerOptions } from './cache-manager.interface';
export type CacheModuleOptions<
StoreConfig extends Record<any, any> = Record<string, any>,
> =
// Store-specific configuration takes precedence over cache module options due
// to how `createCacheManager` is implemented.
CacheManagerOptions &
StoreConfig & {
/**
* If "true', register `CacheModule` as a global module.
*/
isGlobal?: boolean;
};
/**
* Interface describing a `CacheOptionsFactory`. Providers supplying configuration
* options for the Cache module must implement this interface.
*
* @see [Async configuration](https://docs.nestjs.com/techniques/caching#async-configuration)
*
* @publicApi
*/
export interface CacheOptionsFactory<
StoreConfig extends Record<any, any> = Record<string, any>,
> {
createCacheOptions():
| Promise<CacheModuleOptions<StoreConfig>>
| CacheModuleOptions<StoreConfig>;
}
/**
* Options for dynamically configuring the Cache module.
*
* @see [Async configuration](https://docs.nestjs.com/techniques/caching#async-configuration)
*
* @publicApi
*/
export interface CacheModuleAsyncOptions<
StoreConfig extends Record<any, any> = Record<string, any>,
> extends ConfigurableModuleAsyncOptions<
CacheModuleOptions<StoreConfig>,
keyof CacheOptionsFactory
> {
/**
* Injection token resolving to an existing provider. The provider must implement
* the `CacheOptionsFactory` interface.
*/
useExisting?: Type<CacheOptionsFactory<StoreConfig>>;
/**
* Injection token resolving to a class that will be instantiated as a provider.
* The class must implement the `CacheOptionsFactory` interface.
*/
useClass?: Type<CacheOptionsFactory<StoreConfig>>;
/**
* Function returning options (or a Promise resolving to options) to configure the
* cache module.
*/
useFactory?: (
...args: any[]
) =>
| Promise<CacheModuleOptions<StoreConfig>>
| CacheModuleOptions<StoreConfig>;
/**
* Dependencies that a Factory may inject.
*/
inject?: any[];
/**
* Extra providers to be registered within a scope of this module.
*/
extraProviders?: Provider[];
/**
* If "true', register `CacheModule` as a global module.
*/
isGlobal?: boolean;
}

View File

@@ -1,2 +0,0 @@
export * from './cache-manager.interface';
export * from './cache-module.interface';

View File

@@ -17,10 +17,6 @@ export const SCOPE_OPTIONS_METADATA = 'scope:options';
export const METHOD_METADATA = 'method';
export const ROUTE_ARGS_METADATA = '__routeArguments__';
export const CUSTOM_ROUTE_ARGS_METADATA = '__customRouteArgs__';
/**
* @deprecated Use `CUSTOM_ROUTE_ARGS_METADATA` instead
*/
export const CUSTOM_ROUTE_AGRS_METADATA = CUSTOM_ROUTE_ARGS_METADATA;
export const FILTER_CATCH_EXCEPTIONS = '__filterCatchExceptions__';
export const PIPES_METADATA = '__pipes__';

View File

@@ -33,7 +33,9 @@ import { isUndefined } from '../../utils/shared.utils';
*
* @publicApi
*/
export function Inject<T = any>(token?: T) {
export function Inject<T = any>(
token?: T,
): PropertyDecorator & ParameterDecorator {
return (target: object, key: string | symbol | undefined, index?: number) => {
const type = token || Reflect.getMetadata('design:type', target, key);

View File

@@ -17,7 +17,7 @@ import { isUndefined } from '../../utils/shared.utils';
*
* @publicApi
*/
export function Optional() {
export function Optional(): PropertyDecorator & ParameterDecorator {
return (target: object, key: string | symbol | undefined, index?: number) => {
if (!isUndefined(index)) {
const args = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];

View File

@@ -109,3 +109,12 @@ export const Head = createMappingDecorator(RequestMethod.HEAD);
* @publicApi
*/
export const All = createMappingDecorator(RequestMethod.ALL);
/**
* Route handler (method) Decorator. Routes all HTTP requests to the specified path.
*
* @see [Routing](https://docs.nestjs.com/controllers#routing)
*
* @publicApi
*/
export const Search = createMappingDecorator(RequestMethod.SEARCH);

View File

@@ -7,4 +7,5 @@ export enum RequestMethod {
ALL,
OPTIONS,
HEAD,
SEARCH,
}

View File

@@ -1,4 +1,7 @@
import { Logger } from '../services';
import {
HttpExceptionBody,
HttpExceptionBodyMessage,
} from '../interfaces/http/http-exception-body.interface';
import { isObject, isString } from '../utils/shared.utils';
export interface HttpExceptionOptions {
@@ -26,8 +29,6 @@ export class HttpException extends Error {
* @example
* throw new HttpException()
* throw new HttpException('message', HttpStatus.BAD_REQUEST)
* throw new HttpException({ reason: 'this can be a human readable reason' }, HttpStatus.BAD_REQUEST)
* throw new HttpException(new Error('Cause Error'), HttpStatus.BAD_REQUEST)
* throw new HttpException('custom message', HttpStatus.BAD_REQUEST, {
* cause: new Error('Cause Error'),
* })
@@ -81,13 +82,6 @@ export class HttpException extends Error {
this.cause = this.options.cause;
return;
}
if (this.response instanceof Error) {
Logger.warn(
'DEPRECATED! Passing the error cause as the first argument to HttpException constructor is deprecated. You should use the "options" parameter instead: new HttpException("message", 400, { cause: new Error("Some Error") }) ',
);
this.cause = this.response;
}
}
public initMessage() {
@@ -118,17 +112,42 @@ export class HttpException extends Error {
}
public static createBody(
objectOrErrorMessage: object | string,
description?: string,
nil: null | '',
message: HttpExceptionBodyMessage,
statusCode: number,
): HttpExceptionBody;
public static createBody(
message: HttpExceptionBodyMessage,
error: string,
statusCode: number,
): HttpExceptionBody;
public static createBody<Body extends Record<string, unknown>>(
custom: Body,
): Body;
public static createBody<Body extends Record<string, unknown>>(
arg0: null | HttpExceptionBodyMessage | Body,
arg1?: HttpExceptionBodyMessage | string,
statusCode?: number,
) {
if (!objectOrErrorMessage) {
return { statusCode, message: description };
): HttpExceptionBody | Body {
if (!arg0) {
return {
message: arg1,
statusCode: statusCode,
};
}
return isObject(objectOrErrorMessage) &&
!Array.isArray(objectOrErrorMessage)
? objectOrErrorMessage
: { statusCode, message: objectOrErrorMessage, error: description };
if (isString(arg0) || Array.isArray(arg0)) {
return {
message: arg0,
error: arg1 as string,
statusCode: statusCode,
};
}
return arg0;
}
public static getDescriptionFrom(

View File

@@ -6,7 +6,6 @@
*/
import 'reflect-metadata';
export * from './cache';
export * from './decorators';
export * from './enums';
export * from './exceptions';
@@ -27,6 +26,8 @@ export {
FactoryProvider,
ForwardReference,
HttpServer,
HttpExceptionBody,
HttpExceptionBodyMessage,
INestApplication,
INestApplicationContext,
INestMicroservice,

View File

@@ -0,0 +1,7 @@
export type HttpExceptionBodyMessage = string | string[];
export interface HttpExceptionBody {
message: HttpExceptionBodyMessage;
error?: string;
statusCode: number;
}

View File

@@ -18,7 +18,11 @@ export type RequestHandler<TRequest = any, TResponse = any> = (
next?: Function,
) => any;
export interface HttpServer<TRequest = any, TResponse = any> {
export interface HttpServer<
TRequest = any,
TResponse = any,
ServerInstance = any,
> {
use(
handler:
| RequestHandler<TRequest, TResponse>
@@ -69,7 +73,7 @@ export interface HttpServer<TRequest = any, TResponse = any> {
getRequestHostname?(request: TRequest): string;
getRequestMethod?(request: TRequest): string;
getRequestUrl?(request: TRequest): string;
getInstance(): any;
getInstance(): ServerInstance;
registerParserMiddleware(...args: any[]): any;
enableCors(options: CorsOptions | CorsOptionsDelegate<TRequest>): any;
getHttpServer(): any;

View File

@@ -1,3 +1,4 @@
export * from './http-exception-body.interface';
export * from './http-server.interface';
export * from './message-event.interface';
export * from './raw-body-request.interface';

View File

@@ -4,9 +4,9 @@ import { Type } from '../type.interface';
/**
* @publicApi
*/
export type InjectionToken =
export type InjectionToken<T = any> =
| string
| symbol
| Type<any>
| Abstract<any>
| Type<T>
| Abstract<T>
| Function;

View File

@@ -21,7 +21,8 @@ import { WebSocketAdapter } from './websockets/web-socket-adapter.interface';
*
* @publicApi
*/
export interface INestApplication extends INestApplicationContext {
export interface INestApplication<TServer = any>
extends INestApplicationContext {
/**
* A wrapper function around HTTP adapter method: `adapter.use()`.
* Example `app.use(cors())`
@@ -110,9 +111,9 @@ export interface INestApplication extends INestApplicationContext {
/**
* Returns the underlying native HTTP server.
*
* @returns {*}
* @returns {TServer}
*/
getHttpServer(): any;
getHttpServer(): TServer;
/**
* Returns the underlying HTTP adapter.

View File

@@ -23,16 +23,12 @@
"uid": "2.0.2"
},
"peerDependencies": {
"cache-manager": "<=5",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12",
"rxjs": "^7.1.0"
},
"peerDependenciesMeta": {
"cache-manager": {
"optional": true
},
"class-validator": {
"optional": true
},

View File

@@ -1,4 +1,5 @@
import { FileValidator } from './file-validator.interface';
import { IFile } from './interfaces';
export type FileTypeValidatorOptions = {
fileType: string | RegExp;
@@ -20,17 +21,14 @@ export class FileTypeValidator extends FileValidator<FileTypeValidatorOptions> {
return `Validation failed (expected type is ${this.validationOptions.fileType})`;
}
isValid(file: any): boolean {
isValid<TFile extends IFile = any>(file: TFile): boolean {
if (!this.validationOptions) {
return true;
}
if (!file.mimetype) {
return false;
}
return Boolean(
(file.mimetype as string).match(this.validationOptions.fileType),
return (
'mimetype' in file &&
!!file.mimetype.match(this.validationOptions.fileType)
);
}
}

View File

@@ -1,3 +1,5 @@
import { IFile } from './interfaces';
/**
* Interface describing FileValidators, which can be added to a ParseFilePipe
*
@@ -11,7 +13,9 @@ export abstract class FileValidator<TValidationOptions = Record<string, any>> {
* Indicates if this file should be considered valid, according to the options passed in the constructor.
* @param file the file from the request object
*/
abstract isValid(file?: any): boolean | Promise<boolean>;
abstract isValid<TFile extends IFile = any>(
file?: TFile,
): boolean | Promise<boolean>;
/**
* Builds an error message in case the validation fails.

View File

@@ -0,0 +1,4 @@
export interface IFile {
mimetype: string;
size: number;
}

View File

@@ -0,0 +1 @@
export * from './file.interface';

View File

@@ -1,4 +1,5 @@
import { FileValidator } from './file-validator.interface';
import { IFile } from './interfaces';
export type MaxFileSizeValidatorOptions = {
maxSize: number;
@@ -25,11 +26,11 @@ export class MaxFileSizeValidator extends FileValidator<MaxFileSizeValidatorOpti
return `Validation failed (expected size is less than ${this.validationOptions.maxSize})`;
}
public isValid(file: any): boolean {
public isValid<TFile extends IFile = any>(file: TFile): boolean {
if (!this.validationOptions) {
return true;
}
return file.size < this.validationOptions.maxSize;
return 'size' in file && file.size < this.validationOptions.maxSize;
}
}

View File

@@ -18,7 +18,7 @@ import {
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { loadPackage } from '../utils/load-package.util';
import { isNil } from '../utils/shared.utils';
import { isNil, isUndefined } from '../utils/shared.utils';
/**
* @publicApi
@@ -193,6 +193,13 @@ export class ValidationPipe implements PipeTransform<any> {
return value;
}
if (metatype === Boolean) {
if (isUndefined(value)) {
// This is an workaround to deal with optional boolean values since
// optional booleans shouldn't be parsed to a valid boolean when
// they were not defined
return undefined;
}
// Any fasly value but `undefined` will be parsed to `false`
return value === true || value === 'true';
}
if (metatype === Number) {

View File

@@ -1,47 +0,0 @@
import { expect } from 'chai';
import { CacheKey, CacheInterceptor, CacheTTL } from '../../cache';
import {
CACHE_KEY_METADATA,
CACHE_TTL_METADATA,
} from '../../cache/cache.constants';
import { UseInterceptors } from '../../decorators';
import { Controller } from '../../decorators/core/controller.decorator';
describe('@Cache', () => {
@Controller('test')
@CacheKey('/a_different_cache_key')
@CacheTTL(99999)
@UseInterceptors(CacheInterceptor)
class TestAll {}
@Controller()
@UseInterceptors(CacheInterceptor)
@CacheKey('/a_different_cache_key')
class TestKey {}
@Controller()
@UseInterceptors(CacheInterceptor)
@CacheTTL(99999)
class TestTTL {}
it('should override global defaults for CacheKey and CacheTTL', () => {
expect(Reflect.getMetadata(CACHE_KEY_METADATA, TestAll)).to.be.eql(
'/a_different_cache_key',
);
expect(Reflect.getMetadata(CACHE_TTL_METADATA, TestAll)).to.be.greaterThan(
9999,
);
});
it('should override only the TTL', () => {
expect(Reflect.getMetadata(CACHE_TTL_METADATA, TestTTL)).to.be.greaterThan(
9999,
);
});
it('should override only the Key', () => {
expect(Reflect.getMetadata(CACHE_KEY_METADATA, TestKey)).to.be.eql(
'/a_different_cache_key',
);
});
});

View File

@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { Body, HostParam, Param, Query } from '../../decorators';
import { Body, HostParam, Param, Query, Search } from '../../decorators';
import { RequestMethod } from '../../enums/request-method.enum';
import { All, Delete, Get, Patch, Post, Put } from '../../index';
@@ -311,6 +311,63 @@ describe('@Patch', () => {
});
});
describe('@Search', () => {
const requestPath = 'test';
const requestProps = {
path: requestPath,
method: RequestMethod.SEARCH,
};
const requestPathUsingArray = ['foo', 'bar'];
const requestPropsUsingArray = {
path: requestPathUsingArray,
method: RequestMethod.SEARCH,
};
it('should enhance class with expected request metadata', () => {
class Test {
@Search(requestPath)
public static test() {}
@Search(requestPathUsingArray)
public static testUsingArray() {}
}
const path = Reflect.getMetadata('path', Test.test);
const method = Reflect.getMetadata('method', Test.test);
const pathUsingArray = Reflect.getMetadata('path', Test.testUsingArray);
const methodUsingArray = Reflect.getMetadata('method', Test.testUsingArray);
expect(path).to.be.eql(requestPath);
expect(method).to.be.eql(requestProps.method);
expect(pathUsingArray).to.be.eql(requestPathUsingArray);
expect(methodUsingArray).to.be.eql(requestPropsUsingArray.method);
});
it('should set path on "/" by default', () => {
class Test {
@Search()
public static test(
@Query() query,
@Param() params,
@HostParam() hostParams,
) {}
@Search([])
public static testUsingArray(
@Query() query,
@Param() params,
@HostParam() hostParams,
) {}
}
const path = Reflect.getMetadata('path', Test.test);
const pathUsingArray = Reflect.getMetadata('path', Test.testUsingArray);
expect(path).to.be.eql('/');
expect(pathUsingArray).to.be.eql('/');
});
});
describe('Inheritance', () => {
const requestPath = 'test';
const requestProps = {

View File

@@ -1,5 +1,5 @@
import { Type } from '../../../common';
import { expect } from 'chai';
import { Type } from '../../../common';
import {
BadGatewayException,
BadRequestException,
@@ -220,15 +220,6 @@ describe('HttpException', () => {
const errorCause = new Error('An internal error cause');
const customDescription = 'custom description';
it('configures a cause when message is an instance of error', () => {
const message = new Error('Some Error');
const error = new HttpException(message, 400);
expect(`${error}`).to.be.eql(`HttpException: ${message.message}`);
const { cause } = error;
expect(cause).to.be.eql(message);
});
it('configures a cause when message is a string and the options object is passed', () => {
const error = new HttpException(customDescription, 400, {
cause: errorCause,

View File

@@ -1,5 +1,5 @@
import { FileTypeValidator } from '../../../pipes';
import { expect } from 'chai';
import { FileTypeValidator } from '../../../pipes';
describe('FileTypeValidator', () => {
describe('isValid', () => {
@@ -10,7 +10,7 @@ describe('FileTypeValidator', () => {
const requestFile = {
mimetype: 'image/jpeg',
};
} as any;
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
});
@@ -22,7 +22,7 @@ describe('FileTypeValidator', () => {
const requestFile = {
mimetype: 'image/jpeg',
};
} as any;
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
});
@@ -34,7 +34,7 @@ describe('FileTypeValidator', () => {
const requestFile = {
mimetype: 'application/msword',
};
} as any;
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
});
@@ -46,7 +46,7 @@ describe('FileTypeValidator', () => {
const requestFile = {
mimetype: 'image/png',
};
} as any;
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
});
@@ -58,7 +58,7 @@ describe('FileTypeValidator', () => {
const requestFile = {
mimetype: 'image/png',
};
} as any;
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
});
@@ -68,7 +68,7 @@ describe('FileTypeValidator', () => {
fileType: 'image/jpeg',
});
const requestFile = {};
const requestFile = {} as any;
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
});

View File

@@ -12,7 +12,7 @@ describe('MaxFileSizeValidator', () => {
const requestFile = {
size: 100,
};
} as any;
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(true);
});
@@ -24,7 +24,7 @@ describe('MaxFileSizeValidator', () => {
const requestFile = {
size: oneKb + 1,
};
} as any;
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(false);
});
@@ -36,7 +36,7 @@ describe('MaxFileSizeValidator', () => {
const requestFile = {
size: oneKb,
};
} as any;
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(false);
});

View File

@@ -1,7 +1,6 @@
import { expect } from 'chai';
import {
FileTypeValidator,
FileTypeValidatorOptions,
FileValidator,
MaxFileSizeValidator,
ParseFilePipeBuilder,

View File

@@ -221,7 +221,7 @@ describe('ValidationPipe', () => {
});
});
describe('when input is a query parameter (boolean)', () => {
it('should parse to boolean', async () => {
it('should parse the string "true" to the boolean true', async () => {
target = new ValidationPipe({ transform: true });
const value = 'true';
@@ -233,9 +233,45 @@ describe('ValidationPipe', () => {
}),
).to.be.true;
});
it('should parse the string "false" to the boolean false', async () => {
target = new ValidationPipe({ transform: true });
const value = 'false';
expect(
await target.transform(value, {
metatype: Boolean,
data: 'test',
type: 'query',
}),
).to.be.false;
});
it('should parse an empty string to false', async () => {
target = new ValidationPipe({ transform: true });
const value = '';
expect(
await target.transform(value, {
metatype: Boolean,
data: 'test',
type: 'query',
}),
).to.be.false;
});
it('should parse undefined to undefined', async () => {
target = new ValidationPipe({ transform: true });
const value = undefined;
expect(
await target.transform(value, {
metatype: Boolean,
data: 'test',
type: 'query',
}),
).to.be.undefined;
});
});
describe('when input is a path parameter (boolean)', () => {
it('should parse to boolean', async () => {
it('should parse the string "true" to boolean true', async () => {
target = new ValidationPipe({ transform: true });
const value = 'true';
@@ -247,6 +283,42 @@ describe('ValidationPipe', () => {
}),
).to.be.true;
});
it('should parse the string "false" to boolean false', async () => {
target = new ValidationPipe({ transform: true });
const value = 'false';
expect(
await target.transform(value, {
metatype: Boolean,
data: 'test',
type: 'param',
}),
).to.be.false;
});
it('should parse an empty string to false', async () => {
target = new ValidationPipe({ transform: true });
const value = '';
expect(
await target.transform(value, {
metatype: Boolean,
data: 'test',
type: 'param',
}),
).to.be.false;
});
it('should parse undefined to undefined', async () => {
target = new ValidationPipe({ transform: true });
const value = undefined;
expect(
await target.transform(value, {
metatype: Boolean,
data: 'test',
type: 'param',
}),
).to.be.undefined;
});
});
describe('when validation strips', () => {
it('should return a TestModel without extra properties', async () => {

View File

@@ -0,0 +1,85 @@
import { expect } from 'chai';
import { selectExceptionFilterMetadata } from '../../utils/select-exception-filter-metadata.util';
class FirstError {}
class SecondError {}
class ThirdError {}
class FourthError {}
describe('selectExceptionFilterMetadata', () => {
it('should pass error handling to first suitable error handler', () => {
const metadataList = [
{
exceptionMetatypes: [FirstError, SecondError],
func: () => {},
},
{
exceptionMetatypes: [ThirdError, FourthError],
func: () => {},
},
];
expect(
selectExceptionFilterMetadata(metadataList, new FourthError()),
).to.be.equal(metadataList[1]);
});
describe('when multiple exception handlers are accepting error handling', () => {
it('should pass exception handling to the first one', () => {
const metadataList = [
{
exceptionMetatypes: [FirstError, SecondError],
func: () => {},
},
{
exceptionMetatypes: [FirstError, FourthError],
func: () => {},
},
];
expect(
selectExceptionFilterMetadata(metadataList, new FirstError()),
).to.be.equal(metadataList[0]);
});
});
describe('when no exception handler is accepting error handling', () => {
it('should return undefined', () => {
const metadataList = [
{
exceptionMetatypes: [FirstError, SecondError],
func: () => {},
},
{
exceptionMetatypes: [FirstError, FourthError],
func: () => {},
},
];
expect(selectExceptionFilterMetadata(metadataList, new ThirdError())).to
.be.undefined;
});
});
describe('when exception handler has empty list of meta types', () => {
it('should pass any remaining error handling to it', () => {
const metadataList = [
{
exceptionMetatypes: [FirstError, SecondError],
func: () => {},
},
{
exceptionMetatypes: [],
func: () => {},
},
];
expect(
selectExceptionFilterMetadata(metadataList, new ThirdError()),
).to.be.equal(metadataList[1]);
});
});
});

View File

@@ -0,0 +1,13 @@
import { ExceptionFilterMetadata } from '../interfaces/exceptions';
export const selectExceptionFilterMetadata = <T = any>(
filters: ExceptionFilterMetadata[],
exception: T,
): ExceptionFilterMetadata | undefined =>
filters.find(
({ exceptionMetatypes }) =>
!exceptionMetatypes.length ||
exceptionMetatypes.some(
ExceptionMetaType => exception instanceof ExceptionMetaType,
),
);

View File

@@ -68,6 +68,12 @@ export abstract class AbstractHttpAdapter<
return this.instance.all(...args);
}
public search(port: string | number, callback?: () => void);
public search(port: string | number, hostname: string, callback?: () => void);
public search(port: any, hostname?: any, callback?: any) {
return this.instance.search(port, hostname, callback);
}
public options(handler: RequestHandler);
public options(path: any, handler: RequestHandler);
public options(...args: any[]) {

Some files were not shown because too many files have changed in this diff Show More