Compare commits

..

48 Commits

Author SHA1 Message Date
Kamil Myśliwiec
4804f32472 refactor(core): tweaks to host filtering, extract error message 2021-02-02 10:35:26 +01:00
Kamil Myśliwiec
40a0678fbf Merge branch 'host-array' of https://github.com/andrewda/nest into andrewda-host-array 2021-02-02 10:28:56 +01:00
Kamil Mysliwiec
6b119c3579 Merge pull request #6289 from nestjs/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v4.14.2
2021-02-02 08:41:55 +01:00
Renovate Bot
f22d3370f3 chore(deps): update typescript-eslint monorepo to v4.14.2 2021-02-01 20:22:34 +00:00
Kamil Mysliwiec
c12100c3c6 Merge pull request #6288 from nestjs/renovate/nest-monorepo
fix(deps): update nest monorepo to v7.6.8
2021-02-01 16:12:27 +01:00
Renovate Bot
8cf29c9172 fix(deps): update nest monorepo to v7.6.8 2021-02-01 14:42:54 +00:00
Kamil Myśliwiec
12ac1108a7 chore(): bump package json version 2021-02-01 15:26:40 +01:00
Kamil Myśliwiec
c150deefbd chore(@nestjs) publish v7.6.8 release 2021-02-01 15:25:38 +01:00
Kamil Myśliwiec
68b5d2a8e2 chore(): update README 2021-02-01 15:23:44 +01:00
Kamil Mysliwiec
4d87dd6b1e Merge pull request #6287 from nestjs/renovate/imports-loader-2.x
chore(deps): update dependency imports-loader to v2
2021-02-01 14:27:23 +01:00
Kamil Myśliwiec
e0b825121b Merge branch 'jmcdo29-feat/microservice-deserialize-error' 2021-02-01 14:26:37 +01:00
Kamil Myśliwiec
dc8b1d5c72 refactor(microservices): rename is custom client options method 2021-02-01 14:23:37 +01:00
Renovate Bot
832b3bc90c chore(deps): update dependency imports-loader to v2 2021-02-01 13:20:14 +00:00
Kamil Myśliwiec
8180e5905c Merge branch 'feat/microservice-deserialize-error' of https://github.com/jmcdo29/nest into jmcdo29-feat/microservice-deserialize-error 2021-02-01 13:46:55 +01:00
Kamil Myśliwiec
2307373bfe Merge branch 'gabeodess-graphql-schema-first-resolve-field' 2021-02-01 13:44:50 +01:00
Kamil Myśliwiec
71255392c9 sample(12-graphql-schema-first): fix sample 2021-02-01 13:43:45 +01:00
Kamil Myśliwiec
07c8cdc80b Merge branch 'graphql-schema-first-resolve-field' of https://github.com/gabeodess/nest into gabeodess-graphql-schema-first-resolve-field 2021-02-01 13:18:56 +01:00
Kamil Mysliwiec
f9e5272e2b Merge pull request #6286 from nestjs/renovate/point-of-view-4.x
fix(deps): update dependency point-of-view to v4.11.0
2021-02-01 13:14:06 +01:00
Renovate Bot
b842a7e817 fix(deps): update dependency point-of-view to v4.11.0 2021-02-01 12:12:15 +00:00
Kamil Mysliwiec
8df890a8ac Merge pull request #6245 from bonkboykz/fix-websockets-metadata-typings
fix(websockets): fix typings for gatewaymetadata origins
2021-02-01 13:10:34 +01:00
Kamil Mysliwiec
060befe22e Merge pull request #6257 from Tony133/chore/update-transform-options
chore(): update interface for transform options
2021-02-01 13:10:23 +01:00
Kamil Mysliwiec
06bddccb5c Merge pull request #6249 from nestjs/renovate/point-of-view-4.x
fix(deps): update dependency point-of-view to v4.10.0
2021-02-01 13:08:39 +01:00
Kamil Mysliwiec
4f88c3d147 Merge pull request #6282 from nestjs/renovate/eslint-7.x
chore(deps): update dependency eslint to v7.19.0
2021-02-01 10:00:30 +01:00
Renovate Bot
dd7c176df3 fix(deps): update dependency point-of-view to v4.10.0 2021-02-01 07:56:45 +00:00
Renovate Bot
c43fb62322 chore(deps): update dependency eslint to v7.19.0 2021-02-01 07:53:50 +00:00
Kamil Mysliwiec
8c4fb929b7 Merge pull request #6276 from nestjs/renovate/webpack-5.x
chore(deps): update dependency webpack to v5.19.0
2021-02-01 08:38:52 +01:00
Kamil Mysliwiec
1130edc150 Merge pull request #6285 from nestjs/dependabot/npm_and_yarn/point-of-view-4.10.0
chore(deps-dev): bump point-of-view from 4.9.0 to 4.10.0
2021-02-01 08:38:34 +01:00
dependabot[bot]
6417c5477a chore(deps-dev): bump point-of-view from 4.9.0 to 4.10.0
Bumps [point-of-view](https://github.com/fastify/point-of-view) from 4.9.0 to 4.10.0.
- [Release notes](https://github.com/fastify/point-of-view/releases)
- [Commits](https://github.com/fastify/point-of-view/compare/v4.9.0...v4.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 05:49:52 +00:00
Kamil Mysliwiec
149483a852 Update Readme.md 2021-01-30 12:41:17 +01:00
Kamil Mysliwiec
27d9202108 Update readme_jp.md 2021-01-30 12:33:13 +01:00
Kamil Mysliwiec
950e3697c7 Merge pull request #6275 from pochi-sato/master
docs(readme): Create Readme for "Japanese"!
2021-01-30 11:57:09 +01:00
Renovate Bot
df5a7c3a48 chore(deps): update dependency webpack to v5.19.0 2021-01-30 10:25:03 +00:00
Kamil Mysliwiec
25b7ad5805 Merge pull request #6273 from nestjs/renovate/ts-jest-26.x
chore(deps): update dependency ts-jest to v26.5.0
2021-01-30 11:23:19 +01:00
Takuto Sato
0f668075fb Create Readme for "Japanese"! 2021-01-30 00:45:31 +09:00
Renovate Bot
b74f80fb1c chore(deps): update dependency ts-jest to v26.5.0 2021-01-29 14:36:13 +00:00
Kamil Mysliwiec
a0d08f049c Merge pull request #6268 from nestjs/renovate/mongoose-5.x
fix(deps): update dependency mongoose to v5.11.14
2021-01-29 13:47:55 +01:00
Renovate Bot
c88db600a0 fix(deps): update dependency mongoose to v5.11.14 2021-01-29 10:44:03 +00:00
Kamil Mysliwiec
e196f51608 Update Readme.md 2021-01-28 11:56:17 +01:00
Kamil Mysliwiec
f4c45f8d88 Update Readme.md 2021-01-28 11:54:50 +01:00
Tony133
4baea48cfa chore(): update interface for trasform options ref:6256 2021-01-27 15:36:18 +01:00
Yesset Zhussupov
0fb8e1a025 fix(websockets): fix typings for gatewaymetadata origins 2021-01-26 14:04:12 +06:00
Andrew Dassonville
dd51cf8f51 feat(): accept array of host filters
Allows an array of host filters to be passed to the @Controller decorator.
2020-12-17 16:36:45 -08:00
Jay McDoniel
525ef91307 feat(microservice): adds tests for custom client proxy 2020-11-17 08:59:45 -08:00
Jay McDoniel
7dad2e479b feat(microservice): allow for custom serialization of data nad error 2020-11-17 07:01:43 -08:00
Jay McDoniel
530274e2c8 feat(microservice): allow for custom ClientProxy implementation 2020-11-17 07:01:42 -08:00
gabeodessru
d24d0381a2 sample(12): removed unused module method 2020-11-02 08:45:30 -06:00
gabeodessru
85dcf72508 sample(12): move owner tooling to its own service 2020-06-28 08:59:35 -05:00
gabeodessru
f4df4d9a9e sample(12): Example of how to use @ResolveField 2020-05-23 16:12:38 -05:00
281 changed files with 50505 additions and 8196 deletions

View File

@@ -56,16 +56,21 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
#### Principal Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td><td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="180" valign="middle" /></a></td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td></tr></table>
#### Silver Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="170" valign="middle" /></a> </td><td>
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></td><td>
<a href="http://www.meetdandy.com" target="_blank"><img src="https://nestjs.com/img/dandy-wide-logo.png" width="150" valign="middle" /></td></tr></table>
@@ -90,6 +95,11 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://streamat.se/" target="_blank"><img src="https://nestjs.com/img/streamat-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://filmen.nu/" target="_blank"><img src="https://nestjs.com/img/filmen-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -33,7 +33,6 @@ describe('GraphQL Pipes', () => {
code: 'INTERNAL_SERVER_ERROR',
exception: {
message: 'Bad Request Exception',
name: 'BadRequestException',
response: {
message: [
'description must be longer than or equal to 30 characters',

View File

@@ -63,6 +63,22 @@ describe('Hello world (fastify adapter)', () => {
});
});
it(`/GET { host: [":tenant.example1.com", ":tenant.example2.com"] } not matched`, () => {
return app
.inject({
method: 'GET',
url: '/host-array',
})
.then(({ payload }) => {
expect(JSON.parse(payload)).to.be.eql({
error: 'Internal Server Error',
message:
'HTTP adapter does not support filtering on hosts: [":tenant.example1.com", ":tenant.example2.com"]',
statusCode: 500,
});
});
});
it(`/GET inject with LightMyRequest chaining API`, () => {
return app
.inject()

View File

@@ -28,6 +28,16 @@ describe('Hello world (default adapter)', () => {
path: '/host',
greeting: 'Host Greeting! tenant=acme',
},
{
host: 'acme.example1.com',
path: '/host-array',
greeting: 'Host Greeting! tenant=acme',
},
{
host: 'acme.example2.com',
path: '/host-array',
greeting: 'Host Greeting! tenant=acme',
},
].forEach(({ host, path, greeting }) => {
describe(`host=${host}`, () => {
describe('/GET', () => {

View File

@@ -1,77 +0,0 @@
import {
Controller,
Get,
INestApplication,
MiddlewareConsumer,
Module,
} from '@nestjs/common';
import { RouterModule } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { ApplicationModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const SCOPED_VALUE = 'test_scoped';
@Controller()
class TestController {
@Get('test')
test() {
return RETURN_VALUE;
}
@Get('test2')
test2() {
return RETURN_VALUE;
}
}
@Module({
imports: [ApplicationModule],
controllers: [TestController],
})
class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(SCOPED_VALUE))
.forRoutes(TestController);
}
}
describe('RouterModule with Middleware functions', () => {
let app: INestApplication;
beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [
TestModule,
RouterModule.register([
{
path: '/module-path/',
module: TestModule,
},
]),
],
}).compile()
).createNestApplication();
await app.init();
});
it(`forRoutes(TestController) - /test`, () => {
return request(app.getHttpServer())
.get('/module-path/test')
.expect(200, SCOPED_VALUE);
});
it(`forRoutes(TestController) - /test2`, () => {
return request(app.getHttpServer())
.get('/module-path/test2')
.expect(200, SCOPED_VALUE);
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -1,99 +0,0 @@
import { Controller, Get, INestApplication, Module } from '@nestjs/common';
import { RouterModule, Routes } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
describe('RouterModule', () => {
let app: INestApplication;
abstract class BaseController {
@Get()
getName() {
return this.constructor.name;
}
}
@Controller('/parent-controller')
class ParentController extends BaseController {}
@Controller('/child-controller')
class ChildController extends BaseController {}
@Controller('no-slash-controller')
class NoSlashController extends BaseController {}
class UnknownController {}
@Module({ controllers: [ParentController] })
class ParentModule {}
@Module({ controllers: [ChildController] })
class ChildModule {}
@Module({})
class AuthModule {}
@Module({})
class PaymentsModule {}
@Module({ controllers: [NoSlashController] })
class NoSlashModule {}
const routes1: Routes = [
{
path: 'parent',
module: ParentModule,
children: [
{
path: 'child',
module: ChildModule,
},
],
},
];
const routes2: Routes = [
{ path: 'v1', children: [AuthModule, PaymentsModule, NoSlashModule] },
];
@Module({
imports: [ParentModule, ChildModule, RouterModule.register(routes1)],
})
class MainModule {}
@Module({
imports: [
AuthModule,
PaymentsModule,
NoSlashModule,
RouterModule.register(routes2),
],
})
class AppModule {}
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [MainModule, AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('should hit the "ParentController"', async () => {
return request(app.getHttpServer())
.get('/parent/parent-controller')
.expect(200, 'ParentController');
});
it('should hit the "ChildController"', async () => {
return request(app.getHttpServer())
.get('/parent/child/child-controller')
.expect(200, 'ChildController');
});
it('should hit the "NoSlashController"', async () => {
return request(app.getHttpServer())
.get('/v1/no-slash-controller')
.expect(200, 'NoSlashController');
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -1,8 +1,9 @@
import { Module } from '@nestjs/common';
import { HelloModule } from './hello/hello.module';
import { HostArrayModule } from './host-array/host-array.module';
import { HostModule } from './host/host.module';
@Module({
imports: [HelloModule, HostModule],
imports: [HelloModule, HostModule, HostArrayModule],
})
export class ApplicationModule {}

View File

@@ -0,0 +1,10 @@
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class TestDto {
@IsString()
@IsNotEmpty()
string: string;
@IsNumber()
number: number;
}

View File

@@ -0,0 +1,37 @@
import { Controller, Get, Header, HostParam, Param } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { HostArrayService } from './host-array.service';
import { UserByIdPipe } from './users/user-by-id.pipe';
@Controller({
path: 'host-array',
host: [':tenant.example1.com', ':tenant.example2.com'],
})
export class HostArrayController {
constructor(private readonly hostService: HostArrayService) {}
@Get()
@Header('Authorization', 'Bearer')
greeting(@HostParam('tenant') tenant: string): string {
return `${this.hostService.greeting()} tenant=${tenant}`;
}
@Get('async')
async asyncGreeting(@HostParam('tenant') tenant: string): Promise<string> {
return `${await this.hostService.greeting()} tenant=${tenant}`;
}
@Get('stream')
streamGreeting(@HostParam('tenant') tenant: string): Observable<string> {
return of(`${this.hostService.greeting()} tenant=${tenant}`);
}
@Get('local-pipe/:id')
localPipe(
@Param('id', UserByIdPipe)
user: any,
@HostParam('tenant') tenant: string,
): any {
return { ...user, tenant };
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { HostArrayController } from './host-array.controller';
import { HostArrayService } from './host-array.service';
import { UsersService } from './users/users.service';
@Module({
controllers: [HostArrayController],
providers: [HostArrayService, UsersService],
})
export class HostArrayModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class HostArrayService {
greeting(): string {
return 'Host Greeting!';
}
}

View File

@@ -0,0 +1,11 @@
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
import { UsersService } from './users.service';
@Injectable()
export class UserByIdPipe implements PipeTransform<string> {
constructor(private readonly usersService: UsersService) {}
transform(value: string, metadata: ArgumentMetadata) {
return this.usersService.findById(value);
}
}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
findById(id: string) {
return { id, host: true };
}
}

View File

@@ -1,4 +1,4 @@
import { BeforeApplicationShutdown, Injectable, Module } from '@nestjs/common';
import { BeforeApplicationShutdown, Injectable } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as Sinon from 'sinon';
@@ -19,46 +19,35 @@ describe('BeforeApplicationShutdown', () => {
const instance = module.get(TestInjectable);
expect(instance.beforeApplicationShutdown.called).to.be.true;
});
it('should sort modules by distance (topological sort) - DESC order', async () => {
@Injectable()
class BB implements BeforeApplicationShutdown {
public field: string;
async beforeApplicationShutdown() {
this.field = 'b-field';
}
}
@Module({
providers: [BB],
exports: [BB],
})
class B {}
@Injectable()
class AA implements BeforeApplicationShutdown {
public field: string;
constructor(private bb: BB) {}
async beforeApplicationShutdown() {
this.field = this.bb.field + '_a-field';
}
}
@Module({
imports: [B],
providers: [AA],
})
class A {}
/*
it('should not stop the server once beforeApplicationShutdown has been called', async () => {
let resolve;
const promise = new Promise(r => (resolve = r));
const module = await Test.createTestingModule({
imports: [A],
providers: [
{
provide: 'Test',
useValue: {
beforeApplicationShutdown: () => promise,
},
},
],
}).compile();
Sinon.stub(module, 'dispose' as any);
const app = module.createNestApplication();
await app.init();
await app.close();
const instance = module.get(AA);
expect(instance.field).to.equal('b-field_a-field');
});
app.close();
expect(((module as any).dispose as Sinon.SinonSpy).called, 'dispose').to.be
.false;
resolve();
setTimeout(
() =>
expect(((module as any).dispose as Sinon.SinonSpy).called, 'dispose').to
.be.true,
0,
);
});*/
});

View File

@@ -1,7 +1,7 @@
import { Injectable, Module, OnApplicationBootstrap } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as Sinon from 'sinon';
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
@Injectable()
class TestInjectable implements OnApplicationBootstrap {
@@ -41,45 +41,4 @@ describe('OnApplicationBootstrap', () => {
const app = module.createNestApplication();
await app.init().then(obj => expect(obj).to.not.be.undefined);
});
it('should sort modules by distance (topological sort) - DESC order', async () => {
@Injectable()
class BB implements OnApplicationBootstrap {
public field: string;
async onApplicationBootstrap() {
this.field = 'b-field';
}
}
@Module({
providers: [BB],
exports: [BB],
})
class B {}
@Injectable()
class AA implements OnApplicationBootstrap {
public field: string;
constructor(private bb: BB) {}
async onApplicationBootstrap() {
this.field = this.bb.field + '_a-field';
}
}
@Module({
imports: [B],
providers: [AA],
})
class A {}
const module = await Test.createTestingModule({
imports: [A],
}).compile();
const app = module.createNestApplication();
await app.init();
const instance = module.get(AA);
expect(instance.field).to.equal('b-field_a-field');
});
});

View File

@@ -1,4 +1,4 @@
import { Injectable, Module, OnApplicationShutdown } from '@nestjs/common';
import { Injectable, OnApplicationShutdown } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as Sinon from 'sinon';
@@ -19,46 +19,4 @@ describe('OnApplicationShutdown', () => {
const instance = module.get(TestInjectable);
expect(instance.onApplicationShutdown.called).to.be.true;
});
it('should sort modules by distance (topological sort) - DESC order', async () => {
@Injectable()
class BB implements OnApplicationShutdown {
public field: string;
async onApplicationShutdown() {
this.field = 'b-field';
}
}
@Module({
providers: [BB],
exports: [BB],
})
class B {}
@Injectable()
class AA implements OnApplicationShutdown {
public field: string;
constructor(private bb: BB) {}
async onApplicationShutdown() {
this.field = this.bb.field + '_a-field';
}
}
@Module({
imports: [B],
providers: [AA],
})
class A {}
const module = await Test.createTestingModule({
imports: [A],
}).compile();
const app = module.createNestApplication();
await app.init();
await app.close();
const instance = module.get(AA);
expect(instance.field).to.equal('b-field_a-field');
});
});

View File

@@ -1,4 +1,4 @@
import { Injectable, Module, OnModuleDestroy } from '@nestjs/common';
import { Injectable, OnModuleDestroy } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as Sinon from 'sinon';
@@ -39,46 +39,4 @@ describe('OnModuleDestroy', () => {
const app = module.createNestApplication();
await app.init().then(obj => expect(obj).to.not.be.undefined);
});
it('should sort modules by distance (topological sort) - DESC order', async () => {
@Injectable()
class BB implements OnModuleDestroy {
public field: string;
async onModuleDestroy() {
this.field = 'b-field';
}
}
@Module({
providers: [BB],
exports: [BB],
})
class B {}
@Injectable()
class AA implements OnModuleDestroy {
public field: string;
constructor(private bb: BB) {}
async onModuleDestroy() {
this.field = this.bb.field + '_a-field';
}
}
@Module({
imports: [B],
providers: [AA],
})
class A {}
const module = await Test.createTestingModule({
imports: [A],
}).compile();
const app = module.createNestApplication();
await app.init();
await app.close();
const instance = module.get(AA);
expect(instance.field).to.equal('b-field_a-field');
});
});

View File

@@ -1,7 +1,7 @@
import { Injectable, Module, OnModuleInit } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as Sinon from 'sinon';
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
class TestInjectable implements OnModuleInit {
@@ -37,45 +37,4 @@ describe('OnModuleInit', () => {
const app = module.createNestApplication();
await app.init().then(obj => expect(obj).to.not.be.undefined);
});
it('should sort modules by distance (topological sort) - DESC order', async () => {
@Injectable()
class BB implements OnModuleInit {
public field: string;
async onModuleInit() {
this.field = 'b-field';
}
}
@Module({
providers: [BB],
exports: [BB],
})
class B {}
@Injectable()
class AA implements OnModuleInit {
public field: string;
constructor(private bb: BB) {}
async onModuleInit() {
this.field = this.bb.field + '_a-field';
}
}
@Module({
imports: [B],
providers: [AA],
})
class A {}
const module = await Test.createTestingModule({
imports: [A],
}).compile();
const app = module.createNestApplication();
await app.init();
const instance = module.get(AA);
expect(instance.field).to.equal('b-field_a-field');
});
});

View File

@@ -28,7 +28,7 @@ describe('MQTT transport', () => {
host: '0.0.0.0',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -28,7 +28,7 @@ describe('NATS transport', () => {
url: 'nats://0.0.0.0:4222',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -28,7 +28,7 @@ describe('REDIS transport', () => {
url: 'redis://0.0.0.0:6379',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -57,7 +57,7 @@ describe('Kafka concurrent', function () {
apps.push(app);
// await the start
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
};

View File

@@ -1,4 +1,3 @@
import * as GRPC from '@grpc/grpc-js';
import * as ProtoLoader from '@grpc/proto-loader';
import { INestApplication } from '@nestjs/common';
import { Transport } from '@nestjs/microservices';
@@ -7,6 +6,7 @@ import { Test } from '@nestjs/testing';
import { fail } from 'assert';
import { expect } from 'chai';
import * as express from 'express';
import * as GRPC from 'grpc';
import { join } from 'path';
import * as request from 'supertest';
import { AdvancedGrpcController } from '../src/grpc-advanced/advanced.grpc.controller';
@@ -39,7 +39,7 @@ describe('Advanced GRPC transport', () => {
},
});
// Start gRPC microservice
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
// Load proto-buffers for test gRPC dispatch
const proto = ProtoLoader.loadSync('root.proto', {

View File

@@ -1,10 +1,10 @@
import * as GRPC from '@grpc/grpc-js';
import * as ProtoLoader from '@grpc/proto-loader';
import { INestApplication } from '@nestjs/common';
import { Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import { fail } from 'assert';
import { expect } from 'chai';
import * as GRPC from 'grpc';
import { join } from 'path';
import * as request from 'supertest';
import { GrpcController } from '../src/grpc/grpc.controller';
@@ -33,7 +33,7 @@ describe('GRPC transport', () => {
},
});
// Start gRPC microservice
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
// Load proto-buffers for test gRPC dispatch
const proto = ProtoLoader.loadSync(

View File

@@ -14,7 +14,7 @@ describe('Kafka transport', function () {
let app: INestApplication;
// set timeout to be longer (especially for the after hook)
this.timeout(50000);
this.timeout(30000);
it(`Start Kafka app`, async () => {
const module = await Test.createTestingModule({
@@ -33,7 +33,7 @@ describe('Kafka transport', function () {
},
});
app.enableShutdownHooks();
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
}).timeout(30000);
@@ -130,4 +130,4 @@ describe('Kafka transport', function () {
after(`Stopping Kafka app`, async () => {
await app.close();
});
}).timeout(50000);
}).timeout(30000);

View File

@@ -23,7 +23,7 @@ describe('MQTT transport', () => {
url: 'mqtt://0.0.0.0:1883',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -23,7 +23,7 @@ describe('NATS transport', () => {
url: 'nats://0.0.0.0:4222',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -23,7 +23,7 @@ describe('REDIS transport', () => {
url: 'redis://0.0.0.0:6379',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -26,7 +26,7 @@ describe('RabbitMQ transport', () => {
socketOptions: { noDelay: true },
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});

View File

@@ -24,7 +24,7 @@ describe('RPC transport', () => {
host: '0.0.0.0',
},
});
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
await app.init();
});
@@ -105,6 +105,22 @@ describe('RPC transport', () => {
});
});
it('/POST (custom client)', () => {
return request(server)
.post('/error?client=custom')
.send({})
.expect(200)
.expect('true');
});
it('/POST (standard client)', () => {
return request(server)
.post('/error?client=standard')
.send({})
.expect(200)
.expect('false');
});
afterEach(async () => {
await app.close();
});

View File

@@ -12,15 +12,17 @@ import {
EventPattern,
MessagePattern,
Transport,
RpcException,
} from '@nestjs/microservices';
import { from, Observable, of } from 'rxjs';
import { scan } from 'rxjs/operators';
import { from, Observable, of, throwError } from 'rxjs';
import { catchError, scan } from 'rxjs/operators';
@Controller()
export class AppController {
constructor(
@Inject('USE_CLASS_CLIENT') private useClassClient: ClientProxy,
@Inject('USE_FACTORY_CLIENT') private useFactoryClient: ClientProxy,
@Inject('CUSTOM_PROXY_CLIENT') private customClient: ClientProxy,
) {}
static IS_NOTIFIED = false;
@@ -75,6 +77,17 @@ export class AppController {
.reduce(async (a, b) => (await a) && b);
}
@Post('error')
@HttpCode(200)
serializeError(@Query('client') query: 'custom' | 'standard' = 'standard', @Body() body: Record<string, any>): Observable<boolean> {
const client = query === 'custom' ? this.customClient : this.client;
return client.send({ cmd: 'err' }, {}).pipe(
catchError((err) => {
return of(err instanceof RpcException);
})
)
}
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
@@ -95,6 +108,11 @@ export class AppController {
return from(data);
}
@MessagePattern({ cmd: 'err' })
throwAnError() {
return throwError(new Error('err'));
}
@Post('notify')
async sendNotification(): Promise<any> {
return this.client.emit<number>('notification', true);

View File

@@ -5,8 +5,16 @@ import {
Transport,
ClientsModuleOptionsFactory,
ClientOptions,
ClientTCP,
RpcException,
} from '@nestjs/microservices';
class ErrorHandlingProxy extends ClientTCP {
serializeError(err) {
return new RpcException(err);
}
}
@Injectable()
class ConfigService {
private readonly config = {
@@ -51,7 +59,14 @@ class ClientOptionService implements ClientsModuleOptionsFactory {
name: 'USE_CLASS_CLIENT',
useClass: ClientOptionService,
inject: [ConfigService],
},
}, {
imports: [ConfigModule],
inject: [ConfigService],
name: 'CUSTOM_PROXY_CLIENT',
useFactory: (config: ConfigService) => ({
customClass: ErrorHandlingProxy
})
}
]),
],
controllers: [AppController],

View File

@@ -1,4 +1,3 @@
import { Metadata } from '@grpc/grpc-js';
import { Body, Controller, HttpCode, Post } from '@nestjs/common';
import {
Client,
@@ -10,6 +9,7 @@ import {
} from '@nestjs/microservices';
import { join } from 'path';
import { Observable, of, ReplaySubject, Subject } from 'rxjs';
import { Metadata } from 'grpc';
@Controller()
export class AdvancedGrpcController {

View File

@@ -1,47 +0,0 @@
import { ExpressAdapter } from '@nestjs/platform-express';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
import * as express from 'express';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
describe('Listen (Express Application)', () => {
let testModule: TestingModule;
let app: INestApplication;
beforeEach(async () => {
testModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = testModule.createNestApplication(new ExpressAdapter(express()));
});
afterEach(async () => {
app.close();
});
it('should resolve with httpServer on success', async () => {
const response = await app.listen(3000);
expect(response).to.eql(app.getHttpServer());
});
it('should reject if the port is not available', async () => {
await app.listen(3000);
const secondApp = testModule.createNestApplication(
new ExpressAdapter(express()),
);
try {
await secondApp.listen(3000);
} catch (error) {
expect(error.code).to.equal('EADDRINUSE');
}
});
it('should reject if there is an invalid host', async () => {
try {
await app.listen(3000, '1');
} catch (error) {
expect(error.code).to.equal('EADDRNOTAVAIL');
}
});
});

View File

@@ -1,46 +0,0 @@
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
describe('Listen (Fastify Application)', () => {
let testModule: TestingModule;
let app: INestApplication;
beforeEach(async () => {
testModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = testModule.createNestApplication(new FastifyAdapter());
});
afterEach(async () => {
app.close();
});
it('should resolve with httpServer on success', async () => {
const response = await app.listen(3000);
expect(response).to.eql(app.getHttpServer());
});
it('should reject if the port is not available', async () => {
await app.listen(3000);
const secondApp = testModule.createNestApplication(new FastifyAdapter());
try {
await secondApp.listen(3000);
} catch (error) {
expect(error.code).to.equal('EADDRINUSE');
}
await secondApp.close();
});
it('should reject if there is an invalid host', async () => {
try {
await app.listen(3000, '1');
} catch (error) {
expect(error.code).to.equal('EADDRNOTAVAIL');
}
});
});

View File

@@ -1,12 +0,0 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.sayHello();
}
}

View File

@@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -1,8 +0,0 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
sayHello(): string {
return 'Hello World!';
}
}

View File

@@ -35,7 +35,7 @@ describe('Request scope (microservices)', () => {
server = app.getHttpServer();
await app.init();
await app.startAllMicroservices();
await app.startAllMicroservicesAsync();
});
describe('when one service is request scoped', () => {

View File

@@ -1,53 +0,0 @@
import { ExpressAdapter, NestExpressApplication } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { readFileSync } from 'fs';
import { join } from 'path';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const readmeString = readFileSync(join(process.cwd(), 'Readme.md')).toString();
describe('Express FileSend', () => {
let app: NestExpressApplication;
beforeEach(async () => {
const modRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = modRef.createNestApplication(new ExpressAdapter());
await app.init();
});
it('should return a file from a stream', async () => {
return request(app.getHttpServer())
.get('/file/stream/')
.expect(200)
.expect((res) => {
expect(res.body.toString()).to.be.eq(readmeString);
});
});
it('should return a file from a buffer', async () => {
return request(app.getHttpServer())
.get('/file/buffer')
.expect(200)
.expect((res) => {
expect(res.body.toString()).to.be.eq(readmeString);
});
});
it('should not stream a non-file', async () => {
return request(app.getHttpServer())
.get('/non-file/pipe-method')
.expect(200)
.expect({ value: 'Hello world' });
});
it('should return a file from an RxJS stream', async () => {
return request(app.getHttpServer())
.get('/file/rxjs/stream/')
.expect(200)
.expect((res) => {
expect(res.body.toString()).to.be.eq(readmeString);
});
});
});

View File

@@ -1,59 +0,0 @@
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { readFileSync } from 'fs';
import { join } from 'path';
import { AppModule } from '../src/app.module';
const readmeString = readFileSync(join(process.cwd(), 'Readme.md')).toString();
describe('Fastify FileSend', () => {
let app: NestFastifyApplication;
beforeEach(async () => {
const modRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = modRef.createNestApplication(new FastifyAdapter());
await app.init();
});
it('should return a file from a stream', async () => {
return app.inject({
method: 'GET',
url: '/file/stream'
}).then(({ payload }) => {
expect(payload.toString()).to.be.eq(readmeString);
});
});
it('should return a file from a buffer', async () => {
return app.inject({
method: 'GET',
url: '/file/buffer',
}).then(({ payload }) => {
expect(payload.toString()).to.be.eq(readmeString);
});
});
/**
* It seems that Fastify has a similar issue as Kamil initially pointed out
* If a class has a `pipe` method, it will be treated as a stream. This means
* that the `NonFile` test is a failed case for fastify, hence the skip.
*/
it.skip('should not stream a non-file', async () => {
return app.inject({
url: '/non-file/pipe-method',
method: 'get'
}).then(({ payload }) => {
expect(payload).to.be.eq({ value: 'Hello world' });
});
});
it('should return a file from an RxJS stream', async () => {
return app.inject({
method: 'GET',
url: '/file/rxjs/stream'
}).then(({ payload }) => {
expect(payload.toString()).to.be.eq(readmeString);
});
});
});

View File

@@ -1,29 +0,0 @@
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { Observable } from 'rxjs';
import { AppService } from './app.service';
import { NonFile } from './non-file';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('file/stream')
getFile(): StreamableFile {
return this.appService.getReadStream();
}
@Get('file/buffer')
getBuffer(): StreamableFile {
return this.appService.getBuffer();
}
@Get('non-file/pipe-method')
getNonFile(): NonFile {
return this.appService.getNonFile();
}
@Get('file/rxjs/stream')
getRxJSFile(): Observable<StreamableFile> {
return this.appService.getRxJSFile();
}
}

View File

@@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -1,26 +0,0 @@
import { Injectable, StreamableFile } from '@nestjs/common';
import { createReadStream, readFileSync } from 'fs';
import { join } from 'path';
import { Observable, of } from 'rxjs';
import { NonFile } from './non-file';
@Injectable()
export class AppService {
getReadStream(): StreamableFile {
return new StreamableFile(
createReadStream(join(process.cwd(), 'Readme.md')),
);
}
getBuffer(): StreamableFile {
return new StreamableFile(readFileSync(join(process.cwd(), 'Readme.md')));
}
getNonFile(): NonFile {
return new NonFile('Hello world');
}
getRxJSFile(): Observable<StreamableFile> {
return of(this.getReadStream());
}
}

View File

@@ -1,7 +0,0 @@
export class NonFile {
constructor(private readonly value: string) {}
pipe() {
return this.value;
}
}

View File

@@ -1,22 +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"
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -12,7 +12,7 @@ describe('ErrorGateway', () => {
providers: [ErrorGateway],
}).compile();
app = await testingModule.createNestApplication();
await app.listen(3000);
await app.listenAsync(3000);
});
it(`should handle error`, async () => {

View File

@@ -17,7 +17,7 @@ describe('WebSocketGateway (ack)', () => {
it(`should handle message with ack (http)`, async () => {
app = await createNestApp(AckGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = io.connect('http://localhost:8080');
await new Promise<void>(resolve =>
@@ -30,7 +30,7 @@ describe('WebSocketGateway (ack)', () => {
it(`should handle message with ack & without data (http)`, async () => {
app = await createNestApp(AckGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = io.connect('http://localhost:8080');
await new Promise<void>(resolve =>

View File

@@ -19,7 +19,7 @@ describe('WebSocketGateway', () => {
it(`should handle message (2nd port)`, async () => {
app = await createNestApp(ApplicationGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = io.connect('http://localhost:8080');
ws.emit('push', {
@@ -35,7 +35,7 @@ describe('WebSocketGateway', () => {
it(`should handle message (http)`, async () => {
app = await createNestApp(ServerGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = io.connect('http://localhost:3000');
ws.emit('push', {
@@ -51,7 +51,7 @@ describe('WebSocketGateway', () => {
it(`should handle message (2 gateways)`, async () => {
app = await createNestApp(ApplicationGateway, NamespaceGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = io.connect('http://localhost:8080');
io.connect('http://localhost:8080/test').emit('push', {});

View File

@@ -21,7 +21,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
it(`should handle message (2nd port)`, async () => {
app = await createNestApp(ApplicationGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = new WebSocket('ws://localhost:8080');
await new Promise(resolve => ws.on('open', resolve));
@@ -44,7 +44,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
it(`should handle message (http)`, async () => {
app = await createNestApp(ServerGateway);
await app.listen(3000);
await app.listenAsync(3000);
ws = new WebSocket('ws://localhost:3000');
await new Promise(resolve => ws.on('open', resolve));
@@ -69,7 +69,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
this.retries(10);
app = await createNestApp(ApplicationGateway, CoreGateway);
await app.listen(3000);
await app.listenAsync(3000);
// open websockets delay
await new Promise(resolve => setTimeout(resolve, 1000));

View File

@@ -3,5 +3,5 @@
"packages": [
"packages/*"
],
"version": "7.6.7"
"version": "7.6.8"
}

35230
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "7.5.3",
"version": "7.6.8",
"description": "Modern, fast, powerful node.js web framework",
"homepage": "https://nestjs.com",
"repository": {
@@ -24,7 +24,7 @@
"coverage": "nyc report --reporter=text-lcov | coveralls",
"format": "prettier \"**/*.ts\" --ignore-path ./.prettierignore --write && git status",
"postinstall": "opencollective",
"test": "nyc --require ts-node/register mocha packages/**/*.spec.ts --reporter spec --require 'node_modules/reflect-metadata/Reflect.js' --exit",
"test": "nyc --require ts-node/register mocha packages/**/*.spec.ts --reporter spec --retries 3 --require 'node_modules/reflect-metadata/Reflect.js' --exit",
"test:integration": "mocha \"integration/*/{,!(node_modules)/**/}/*.spec.ts\" --reporter spec --require ts-node/register --require 'node_modules/reflect-metadata/Reflect.js' --exit",
"test:docker:up": "docker-compose -f integration/docker-compose.yml up -d",
"test:docker:down": "docker-compose -f integration/docker-compose.yml down",
@@ -74,7 +74,6 @@
"@codechecks/client": "0.1.10",
"@commitlint/cli": "11.0.0",
"@commitlint/config-angular": "11.0.0",
"@grpc/grpc-js": "1.1.1",
"@grpc/proto-loader": "0.5.6",
"@nestjs/graphql": "7.9.8",
"@nestjs/mongoose": "7.2.2",
@@ -87,7 +86,6 @@
"@types/cors": "2.8.9",
"@types/express": "4.17.11",
"@types/gulp": "4.0.8",
"@types/http-errors": "1.8.0",
"@types/mocha": "8.2.0",
"@types/mongoose": "5.10.3",
"@types/node": "14.14.22",
@@ -96,8 +94,8 @@
"@types/sinon": "9.0.10",
"@types/socket.io": "2.1.13",
"@types/ws": "7.4.0",
"@typescript-eslint/eslint-plugin": "4.14.1",
"@typescript-eslint/parser": "4.14.1",
"@typescript-eslint/eslint-plugin": "4.14.2",
"@typescript-eslint/parser": "4.14.2",
"amqp-connection-manager": "3.2.1",
"amqplib": "0.6.0",
"apollo-server-express": "2.19.2",
@@ -116,7 +114,7 @@
"coveralls": "3.1.0",
"delete-empty": "3.0.0",
"engine.io-client": "4.1.0",
"eslint": "7.18.0",
"eslint": "7.19.0",
"eslint-config-prettier": "7.2.0",
"eslint-plugin-import": "2.22.1",
"eventsource": "1.0.7",
@@ -128,15 +126,15 @@
"fastify-static": "3.4.0",
"graphql": "15.5.0",
"graphql-tools": "7.0.2",
"grpc": "1.24.4",
"gulp": "4.0.2",
"gulp-clang-format": "1.0.27",
"gulp-clean": "0.4.0",
"gulp-sourcemaps": "3.0.0",
"gulp-typescript": "5.0.1",
"gulp-watch": "5.0.1",
"http-errors": "1.8.0",
"husky": "4.3.8",
"imports-loader": "1.2.0",
"imports-loader": "2.0.0",
"json-loader": "0.5.7",
"kafkajs": "1.15.0",
"lerna": "2.11.0",
@@ -146,14 +144,14 @@
"merge-graphql-schemas": "1.7.8",
"middie": "5.2.0",
"mocha": "8.2.1",
"mongoose": "5.11.13",
"mongoose": "5.11.14",
"mqtt": "4.2.6",
"multer": "1.4.2",
"mysql": "2.18.1",
"nats": "1.4.12",
"nodemon": "2.0.7",
"nyc": "15.1.0",
"point-of-view": "4.9.0",
"point-of-view": "4.11.0",
"prettier": "2.2.1",
"redis": "3.0.2",
"rxjs-compat": "6.6.3",
@@ -184,6 +182,7 @@
"packages/**/*.ts"
],
"exclude": [
"node_modules/",
"packages/**/test/**",
"packages/**/*.spec.ts",
"packages/**/adapters/*.ts",
@@ -200,7 +199,7 @@
"packages/core/injector/instance-links-host.ts",
"packages/common/cache/**/*",
"packages/common/serializer/**/*",
"packages/common/services/*.ts"
"packages/common/services/logger.service.ts"
],
"extension": [
".ts"

View File

@@ -56,16 +56,21 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
#### Principal Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td><td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="180" valign="middle" /></a></td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td></tr></table>
#### Silver Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="170" valign="middle" /></a> </td><td>
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></td><td>
<a href="http://www.meetdandy.com" target="_blank"><img src="https://nestjs.com/img/dandy-wide-logo.png" width="150" valign="middle" /></td></tr></table>
@@ -90,6 +95,11 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://streamat.se/" target="_blank"><img src="https://nestjs.com/img/streamat-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://filmen.nu/" target="_blank"><img src="https://nestjs.com/img/filmen-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -27,7 +27,7 @@ export interface ControllerOptions extends ScopeOptions {
*
* @see [Routing](https://docs.nestjs.com/controllers#routing)
*/
host?: string;
host?: string | string[];
}
/**

View File

@@ -2,7 +2,7 @@ import {
PROPERTY_DEPS_METADATA,
SELF_DECLARED_DEPS_METADATA,
} from '../../constants';
import { isUndefined } from '../../utils/shared.utils';
import { isFunction, isUndefined } from '../../utils/shared.utils';
/**
* Decorator that marks a constructor parameter as a target for
@@ -35,7 +35,9 @@ import { isUndefined } from '../../utils/shared.utils';
*/
export function Inject<T = any>(token?: T) {
return (target: object, key: string | symbol, index?: number) => {
const type = token || Reflect.getMetadata('design:type', target, key);
token = token || Reflect.getMetadata('design:type', target, key);
const type =
token && isFunction(token) ? ((token as any) as Function).name : token;
if (!isUndefined(index)) {
let dependencies =

View File

@@ -46,7 +46,7 @@ export function Injectable(options?: InjectableOptions): ClassDecorator {
};
}
export function mixin<T>(mixinClass: Type<T>) {
export function mixin(mixinClass: Type<any>) {
Object.defineProperty(mixinClass, 'name', {
value: uuid(),
});

View File

@@ -40,7 +40,6 @@ export class HttpException extends Error {
) {
super();
this.initMessage();
this.initName();
}
public initMessage() {
@@ -58,10 +57,6 @@ export class HttpException extends Error {
}
}
public initName(): void {
this.name = this.constructor.name;
}
public getResponse(): string | object {
return this.response;
}

View File

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

View File

@@ -1,24 +0,0 @@
import { Readable } from 'stream';
export class StreamableFile {
private readonly stream: Readable;
constructor(buffer: Buffer);
constructor(readble: Readable);
constructor(bufferOrReadStream: Buffer | Readable) {
if (Buffer.isBuffer(bufferOrReadStream)) {
this.stream = new Readable();
this.stream.push(bufferOrReadStream);
this.stream.push(null);
} else if (
bufferOrReadStream.pipe &&
typeof bufferOrReadStream.pipe === 'function'
) {
this.stream = bufferOrReadStream;
}
}
getStream(): Readable {
return this.stream;
}
}

View File

@@ -10,7 +10,6 @@ export * from './cache';
export * from './decorators';
export * from './enums';
export * from './exceptions';
export * from './file-stream';
export * from './http';
export {
Abstract,

View File

@@ -50,4 +50,9 @@ export interface ClassTransformOptions {
* and exposing all class properties (with undefined, if nothing else is given)
*/
excludeExtraneousValues?: boolean;
/**
* If set to true then class transformer will take default values for unprovided fields.
* This is useful when you convert a plain object to a class and have an optional field with a default value.
*/
exposeDefaultValues?: boolean;
}

View File

@@ -41,8 +41,6 @@ export interface HttpServer<TRequest = any, TResponse = any> {
put(path: string, handler: RequestHandler<TRequest, TResponse>): any;
patch(handler: RequestHandler<TRequest, TResponse>): any;
patch(path: string, handler: RequestHandler<TRequest, TResponse>): any;
all(path: string, handler: RequestHandler<TRequest, TResponse>): any;
all(handler: RequestHandler<TRequest, TResponse>): any;
options(handler: RequestHandler<TRequest, TResponse>): any;
options(path: string, handler: RequestHandler<TRequest, TResponse>): any;
listen(port: number | string, callback?: () => void): any;

View File

@@ -13,20 +13,6 @@ export class NestApplicationContextOptions {
* Whether to abort the process on Error. By default, the process is exited.
* Pass `false` to override the default behavior. If `false` is passed, Nest will not exit
* the application and instead will rethrow the exception.
* @default true
*/
abortOnError?: boolean | undefined;
/**
* If enabled, logs will be buffered until the "Logger#flush" method is called.
* @default false
*/
bufferLogs?: boolean;
/**
* If enabled, logs will be automatically flushed and buffer detached when
* application initialisation process either completes or fails.
* @default true
*/
autoFlushLogs?: boolean;
}

View File

@@ -56,12 +56,6 @@ export interface INestApplicationContext {
*/
useLogger(logger: LoggerService | LogLevel[] | false): void;
/**
* Prints buffered logs and detaches buffer.
* @returns {void}
*/
flushLogs(): void;
/**
* Enables the usage of shutdown hooks. Will call the
* `onApplicationShutdown` function of a provider if the

View File

@@ -50,9 +50,15 @@ export interface INestApplication extends INestApplicationContext {
callback?: () => void,
): Promise<any>;
/**
* Returns the url the application is listening at, based on OS and IP version. Returns as an IP value either in IPv6 or IPv4
*
* @returns {Promise<string>} The IP where the server is listening
*/
getUrl(): Promise<string>;
/**
* Starts the application (can be awaited).
* @deprecated use "listen" instead.
*
* @param {number|string} port
* @param {string} [hostname]
@@ -60,13 +66,6 @@ export interface INestApplication extends INestApplicationContext {
*/
listenAsync(port: number | string, hostname?: string): Promise<any>;
/**
* Returns the url the application is listening at, based on OS and IP version. Returns as an IP value either in IPv6 or IPv4
*
* @returns {Promise<string>} The IP where the server is listening
*/
getUrl(): Promise<string>;
/**
* Registers a prefix for every HTTP route path.
*
@@ -122,17 +121,17 @@ export interface INestApplication extends INestApplicationContext {
/**
* Starts all connected microservices asynchronously.
*
* @returns {Promise}
* @param {Function} [callback] Optional callback function
* @returns {this}
*/
startAllMicroservices(): Promise<this>;
startAllMicroservices(callback?: () => void): this;
/**
* Starts all connected microservices and can be awaited.
* @deprecated use "startAllMicroservices" instead.
*
* @returns {Promise}
*/
startAllMicroservicesAsync(): Promise<this>;
startAllMicroservicesAsync(): Promise<void>;
/**
* Registers exception filters as global filters (will be used within

View File

@@ -14,13 +14,13 @@ export interface INestMicroservice extends INestApplicationContext {
/**
* Starts the microservice.
*
* @param {Function} callback
* @returns {void}
*/
listen(): Promise<any>;
listen(callback: () => void): void;
/**
* Starts the microservice (can be awaited).
* @deprecated use "listen" instead.
*
* @returns {Promise}
*/

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "7.6.7",
"version": "7.6.8",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"homepage": "https://nestjs.com",

View File

@@ -1,254 +0,0 @@
import { Injectable } from '../decorators/core/injectable.decorator';
import { Optional } from '../decorators/core/optional.decorator';
import { clc, yellow } from '../utils/cli-colors.util';
import { isPlainObject } from '../utils/shared.utils';
import { LoggerService, LogLevel } from './logger.service';
import { isLogLevelEnabled } from './utils';
export interface ConsoleLoggerOptions {
/**
* Enabled log levels.
*/
logLevels?: LogLevel[];
/**
* If enabled, will print timestamp (time difference) between current and previous log message.
*/
timestamp?: boolean;
}
const DEFAULT_LOG_LEVELS: LogLevel[] = [
'log',
'error',
'warn',
'debug',
'verbose',
];
@Injectable()
export class ConsoleLogger implements LoggerService {
private static lastTimestampAt?: number;
constructor();
constructor(context: string);
constructor(context: string, options: ConsoleLoggerOptions);
constructor(
@Optional()
protected context?: string,
@Optional()
protected options: ConsoleLoggerOptions = {},
) {
if (!options.logLevels) {
options.logLevels = DEFAULT_LOG_LEVELS;
}
}
/**
* Write a 'log' level log, if the configured level allows for it.
* Prints to `stdout` with newline.
*/
log(message: any, context?: string): void;
log(message: any, ...optionalParams: [...any, string]): void;
log(message: any, ...optionalParams: [...any, string]) {
if (!this.isLevelEnabled('log')) {
return;
}
const { messages, context } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'log');
}
/**
* Write an 'error' level log, if the configured level allows for it.
* Prints to `stderr` with newline.
*/
error(message: any, stack?: string, context?: string): void;
error(message: any, ...optionalParams: [...any, string, string]): void;
error(message: any, ...optionalParams: [...any, string, string]) {
if (!this.isLevelEnabled('error')) {
return;
}
const {
messages,
context,
stack,
} = this.getContextAndStackAndMessagesToPrint([message, ...optionalParams]);
this.printMessages(messages, context, 'error', 'stderr');
this.printStackTrace(stack);
}
/**
* Write a 'warn' level log, if the configured level allows for it.
* Prints to `stdout` with newline.
*/
warn(message: any, context?: string): void;
warn(message: any, ...optionalParams: [...any, string]): void;
warn(message: any, ...optionalParams: [...any, string]) {
if (!this.isLevelEnabled('warn')) {
return;
}
const { messages, context } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'warn');
}
/**
* Write a 'debug' level log, if the configured level allows for it.
* Prints to `stdout` with newline.
*/
debug(message: any, context?: string): void;
debug(message: any, ...optionalParams: [...any, string]): void;
debug(message: any, ...optionalParams: [...any, string]) {
if (!this.isLevelEnabled('debug')) {
return;
}
const { messages, context } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'debug');
}
/**
* Write a 'verbose' level log, if the configured level allows for it.
* Prints to `stdout` with newline.
*/
verbose(message: any, context?: string): void;
verbose(message: any, ...optionalParams: [...any, string]): void;
verbose(message: any, ...optionalParams: [...any, string]) {
if (!this.isLevelEnabled('verbose')) {
return;
}
const { messages, context } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'verbose');
}
/**
* Set log levels
* @param levels log levels
*/
setLogLevels(levels: LogLevel[]) {
if (!this.options) {
this.options = {};
}
this.options.logLevels = levels;
}
/**
* Set logger context
* @param context context
*/
setContext(context: string) {
this.context = context;
}
isLevelEnabled(level: LogLevel): boolean {
const logLevels = this.options?.logLevels;
return isLogLevelEnabled(level, logLevels);
}
protected getTimestamp(): string {
const localeStringOptions = {
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
day: '2-digit',
month: '2-digit',
};
return new Date(Date.now()).toLocaleString(undefined, localeStringOptions);
}
protected printMessages(
messages: unknown[],
context = '',
logLevel: LogLevel = 'log',
writeStreamType?: 'stdout' | 'stderr',
) {
const color = this.getColorByLogLevel(logLevel);
messages.forEach(message => {
const output = isPlainObject(message)
? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n`
: color(message as string);
const pidMessage = color(`[Nest] ${process.pid} - `);
const contextMessage = context ? yellow(`[${context}] `) : '';
const timestampDiff = this.updateAndGetTimestampDiff();
const formattedLogLevel = color(logLevel.toUpperCase().padStart(7, ' '));
const computedMessage = `${pidMessage}${this.getTimestamp()} ${formattedLogLevel} ${contextMessage}${output}${timestampDiff}\n`;
process[writeStreamType ?? 'stdout'].write(computedMessage);
});
}
protected printStackTrace(stack: string) {
if (!stack) {
return;
}
process.stderr.write(`${stack}\n`);
}
private updateAndGetTimestampDiff(): string {
const includeTimestamp =
ConsoleLogger.lastTimestampAt && this.options?.timestamp;
const result = includeTimestamp
? yellow(` +${Date.now() - ConsoleLogger.lastTimestampAt}ms`)
: '';
ConsoleLogger.lastTimestampAt = Date.now();
return result;
}
private getContextAndMessagesToPrint(args: unknown[]) {
if (args?.length <= 1) {
return { messages: args, context: this.context };
}
const lastElement = args[args.length - 1];
const isContext = typeof lastElement === 'string';
if (!isContext) {
return { messages: args, context: this.context };
}
return {
context: lastElement as string,
messages: args.slice(0, args.length - 1),
};
}
private getContextAndStackAndMessagesToPrint(args: unknown[]) {
const { messages, context } = this.getContextAndMessagesToPrint(args);
if (messages?.length <= 1) {
return { messages, context };
}
const lastElement = messages[messages.length - 1];
const isStack = typeof lastElement === 'string';
if (!isStack) {
return { messages, context };
}
return {
stack: lastElement as string,
messages: messages.slice(0, messages.length - 1),
context,
};
}
private getColorByLogLevel(level: LogLevel) {
switch (level) {
case 'debug':
return clc.magentaBright;
case 'warn':
return clc.yellow;
case 'error':
return clc.red;
case 'verbose':
return clc.cyanBright;
default:
return clc.green;
}
}
}

View File

@@ -1,2 +1 @@
export * from './console-logger.service';
export * from './logger.service';

View File

@@ -1,264 +1,182 @@
import { Injectable } from '../decorators/core/injectable.decorator';
import { Optional } from '../decorators/core/optional.decorator';
import { isObject } from '../utils/shared.utils';
import { ConsoleLogger } from './console-logger.service';
import { isLogLevelEnabled } from './utils';
import { clc, yellow } from '../utils/cli-colors.util';
import { isObject, isPlainObject } from '../utils/shared.utils';
declare const process: any;
export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose';
export interface LoggerService {
/**
* Write a 'log' level log.
*/
log(message: any, ...optionalParams: any[]): any;
/**
* Write an 'error' level log.
*/
error(message: any, ...optionalParams: any[]): any;
/**
* Write a 'warn' level log.
*/
warn(message: any, ...optionalParams: any[]): any;
/**
* Write a 'debug' level log.
*/
debug?(message: any, ...optionalParams: any[]): any;
/**
* Write a 'verbose' level log.
*/
verbose?(message: any, ...optionalParams: any[]): any;
/**
* Set log levels.
* @param levels log levels
*/
setLogLevels?(levels: LogLevel[]): any;
log(message: any, context?: string);
error(message: any, trace?: string, context?: string);
warn(message: any, context?: string);
debug?(message: any, context?: string);
verbose?(message: any, context?: string);
}
interface LogBufferRecord {
/**
* Method to execute.
*/
methodRef: Function;
/**
* Arguments to pass to the method.
*/
arguments: unknown[];
}
const DEFAULT_LOGGER = new ConsoleLogger();
@Injectable()
export class Logger implements LoggerService {
protected static logBuffer = new Array<LogBufferRecord>();
protected static staticInstanceRef?: LoggerService = DEFAULT_LOGGER;
protected static logLevels?: LogLevel[];
private static isBufferAttached: boolean;
private static logLevels: LogLevel[] = [
'log',
'error',
'warn',
'debug',
'verbose',
];
private static lastTimestamp?: number;
protected static instance?: typeof Logger | LoggerService = Logger;
protected localInstanceRef?: LoggerService;
private static WrapBuffer: MethodDecorator = (
target: object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
const originalFn = descriptor.value;
descriptor.value = function (...args: unknown[]) {
if (Logger.isBufferAttached) {
Logger.logBuffer.push({
methodRef: originalFn.bind(this),
arguments: args,
});
return;
}
return originalFn.call(this, ...args);
};
};
constructor();
constructor(context: string);
constructor(context: string, options?: { timestamp?: boolean });
constructor(
@Optional() protected context?: string,
@Optional() protected options: { timestamp?: boolean } = {},
@Optional() private readonly isTimestampEnabled = false,
) {}
get localInstance(): LoggerService {
if (Logger.staticInstanceRef === DEFAULT_LOGGER) {
if (this.localInstanceRef) {
return this.localInstanceRef;
}
this.localInstanceRef = new ConsoleLogger(this.context, {
timestamp: this.options?.timestamp,
logLevels: Logger.logLevels,
});
return this.localInstanceRef;
error(message: any, trace = '', context?: string) {
const instance = this.getInstance();
if (!this.isLogLevelEnabled('error')) {
return;
}
return Logger.staticInstanceRef;
instance &&
instance.error.call(instance, message, trace, context || this.context);
}
/**
* Write an 'error' level log.
*/
error(message: any, stack?: string, context?: string): void;
error(message: any, ...optionalParams: [...any, string, string]): void;
@Logger.WrapBuffer
error(message: any, ...optionalParams: [...any, string, string]) {
optionalParams = this.context
? optionalParams.concat(this.context)
: optionalParams;
this.localInstance?.error(message, ...optionalParams);
log(message: any, context?: string) {
this.callFunction('log', message, context);
}
/**
* Write a 'log' level log.
*/
log(message: any, context?: string): void;
log(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
log(message: any, ...optionalParams: [...any, string]) {
optionalParams = this.context
? optionalParams.concat(this.context)
: optionalParams;
this.localInstance?.log(message, ...optionalParams);
warn(message: any, context?: string) {
this.callFunction('warn', message, context);
}
/**
* Write a 'warn' level log.
*/
warn(message: any, context?: string): void;
warn(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
warn(message: any, ...optionalParams: [...any, string]) {
optionalParams = this.context
? optionalParams.concat(this.context)
: optionalParams;
this.localInstance?.warn(message, ...optionalParams);
debug(message: any, context?: string) {
this.callFunction('debug', message, context);
}
/**
* Write a 'debug' level log.
*/
debug(message: any, context?: string): void;
debug(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
debug(message: any, ...optionalParams: [...any, string]) {
optionalParams = this.context
? optionalParams.concat(this.context)
: optionalParams;
this.localInstance?.debug(message, ...optionalParams);
verbose(message: any, context?: string) {
this.callFunction('verbose', message, context);
}
/**
* Write a 'verbose' level log.
*/
verbose(message: any, context?: string): void;
verbose(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
verbose(message: any, ...optionalParams: [...any, string]) {
optionalParams = this.context
? optionalParams.concat(this.context)
: optionalParams;
this.localInstance?.verbose(message, ...optionalParams);
setContext(context: string) {
this.context = context;
}
/**
* Write an 'error' level log.
*/
static error(message: any, context?: string): void;
static error(message: any, stack?: string, context?: string): void;
static error(message: any, ...optionalParams: [...any, string, string]): void;
@Logger.WrapBuffer
static error(message: any, ...optionalParams: [...any, string, string]) {
this.staticInstanceRef?.error(message, ...optionalParams);
}
/**
* Write a 'log' level log.
*/
static log(message: any, context?: string): void;
static log(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
static log(message: any, ...optionalParams: [...any, string]) {
this.staticInstanceRef?.log(message, ...optionalParams);
}
/**
* Write a 'warn' level log.
*/
static warn(message: any, context?: string): void;
static warn(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
static warn(message: any, ...optionalParams: [...any, string]) {
this.staticInstanceRef?.warn(message, ...optionalParams);
}
/**
* Write a 'debug' level log, if the configured level allows for it.
* Prints to `stdout` with newline.
*/
static debug(message: any, context?: string): void;
static debug(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
static debug(message: any, ...optionalParams: [...any, string]) {
this.staticInstanceRef?.debug(message, ...optionalParams);
}
/**
* Write a 'verbose' level log.
*/
static verbose(message: any, context?: string): void;
static verbose(message: any, ...optionalParams: [...any, string]): void;
@Logger.WrapBuffer
static verbose(message: any, ...optionalParams: [...any, string]) {
this.staticInstanceRef?.verbose(message, ...optionalParams);
}
/**
* Print buffered logs and detach buffer.
*/
static flush() {
this.isBufferAttached = false;
this.logBuffer.forEach(item =>
item.methodRef(...(item.arguments as [string])),
);
this.logBuffer = [];
}
/**
* Attach buffer.
* Turns on initialisation logs buffering.
*/
static attachBuffer() {
this.isBufferAttached = true;
}
/**
* Detach buffer.
* Turns off initialisation logs buffering.
*/
static detachBuffer() {
this.isBufferAttached = false;
getTimestamp() {
return Logger.getTimestamp();
}
static overrideLogger(logger: LoggerService | LogLevel[] | boolean) {
if (Array.isArray(logger)) {
Logger.logLevels = logger;
return this.staticInstanceRef?.setLogLevels(logger);
this.logLevels = logger;
return;
}
this.staticInstanceRef = isObject(logger)
? (logger as LoggerService)
: undefined;
this.instance = isObject(logger) ? (logger as LoggerService) : undefined;
}
static isLevelEnabled(level: LogLevel): boolean {
const logLevels = Logger.logLevels;
return isLogLevelEnabled(level, logLevels);
static log(message: any, context = '', isTimeDiffEnabled = true) {
this.printMessage(message, clc.green, context, isTimeDiffEnabled);
}
static error(
message: any,
trace = '',
context = '',
isTimeDiffEnabled = true,
) {
this.printMessage(message, clc.red, context, isTimeDiffEnabled, 'stderr');
this.printStackTrace(trace);
}
static warn(message: any, context = '', isTimeDiffEnabled = true) {
this.printMessage(message, clc.yellow, context, isTimeDiffEnabled);
}
static debug(message: any, context = '', isTimeDiffEnabled = true) {
this.printMessage(message, clc.magentaBright, context, isTimeDiffEnabled);
}
static verbose(message: any, context = '', isTimeDiffEnabled = true) {
this.printMessage(message, clc.cyanBright, context, isTimeDiffEnabled);
}
static getTimestamp() {
const localeStringOptions = {
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
day: '2-digit',
month: '2-digit',
};
return new Date(Date.now()).toLocaleString(undefined, localeStringOptions);
}
private callFunction(
name: 'log' | 'warn' | 'debug' | 'verbose',
message: any,
context?: string,
) {
if (!this.isLogLevelEnabled(name)) {
return;
}
const instance = this.getInstance();
const func = instance && (instance as typeof Logger)[name];
func &&
func.call(
instance,
message,
context || this.context,
this.isTimestampEnabled,
);
}
protected getInstance(): typeof Logger | LoggerService {
const { instance } = Logger;
return instance === this ? Logger : instance;
}
private isLogLevelEnabled(level: LogLevel): boolean {
return Logger.logLevels.includes(level);
}
private static printMessage(
message: any,
color: (message: string) => string,
context = '',
isTimeDiffEnabled?: boolean,
writeStreamType?: 'stdout' | 'stderr',
) {
const output = isPlainObject(message)
? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n`
: color(message);
const pidMessage = color(`[Nest] ${process.pid} - `);
const contextMessage = context ? yellow(`[${context}] `) : '';
const timestampDiff = this.updateAndGetTimestampDiff(isTimeDiffEnabled);
const instance = (this.instance as typeof Logger) ?? Logger;
const timestamp = instance.getTimestamp
? instance.getTimestamp()
: Logger.getTimestamp?.();
const computedMessage = `${pidMessage}${timestamp} ${contextMessage}${output}${timestampDiff}\n`;
process[writeStreamType ?? 'stdout'].write(computedMessage);
}
private static updateAndGetTimestampDiff(
isTimeDiffEnabled?: boolean,
): string {
const includeTimestamp = Logger.lastTimestamp && isTimeDiffEnabled;
const result = includeTimestamp
? yellow(` +${Date.now() - Logger.lastTimestamp}ms`)
: '';
Logger.lastTimestamp = Date.now();
return result;
}
private static printStackTrace(trace: string) {
if (!trace) {
return;
}
process.stderr.write(`${trace}\n`);
}
}

View File

@@ -1 +0,0 @@
export * from './is-log-level-enabled.util';

View File

@@ -1,32 +0,0 @@
import { LogLevel } from '../logger.service';
const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
debug: 0,
verbose: 1,
log: 2,
warn: 3,
error: 4,
};
/**
* Checks if target level is enabled.
* @param targetLevel target level
* @param logLevels array of enabled log levels
*/
export function isLogLevelEnabled(
targetLevel: LogLevel,
logLevels: LogLevel[] | undefined,
): boolean {
if (!logLevels || (Array.isArray(logLevels) && logLevels?.length === 0)) {
return false;
}
if (logLevels.includes(targetLevel)) {
return true;
}
const highestLogLevelValue = logLevels
.map(level => LOG_LEVEL_VALUES[level])
.sort((a, b) => b - a)?.[0];
const targetLevelValue = LOG_LEVEL_VALUES[targetLevel];
return targetLevelValue >= highestLogLevelValue;
}

View File

@@ -4,6 +4,7 @@ import { Controller } from '../../decorators/core/controller.decorator';
describe('@Controller', () => {
const reflectedPath = 'test';
const reflectedHost = 'api.example.com';
const reflectedHostArray = ['api1.example.com', 'api2.example.com'];
@Controller(reflectedPath)
class Test {}
@@ -14,6 +15,9 @@ describe('@Controller', () => {
@Controller({ path: reflectedPath, host: reflectedHost })
class PathAndHostDecorator {}
@Controller({ path: reflectedPath, host: reflectedHostArray })
class PathAndHostArrayDecorator {}
@Controller({ host: reflectedHost })
class HostOnlyDecorator {}
@@ -29,6 +33,8 @@ describe('@Controller', () => {
expect(host).to.be.eql(reflectedHost);
const host2 = Reflect.getMetadata('host', HostOnlyDecorator);
expect(host2).to.be.eql(reflectedHost);
const host3 = Reflect.getMetadata('host', PathAndHostArrayDecorator);
expect(host3).to.be.eql(reflectedHostArray);
});
it('should set default path when no object passed as param', () => {

View File

@@ -16,7 +16,7 @@ describe('@Inject', () => {
const metadata = Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, Test);
const expectedMetadata = [
{ index: 2, param: opaqueToken },
{ index: 2, param: opaqueToken.name },
{ index: 1, param: 'test2' },
{ index: 0, param: 'test' },
];

View File

@@ -62,7 +62,7 @@ describe('HttpException', () => {
it('should be serializable', () => {
const message = 'Some Error';
const error = new HttpException(message, 400);
expect(`${error}`).to.be.eql(`HttpException: ${message}`);
expect(`${error}`).to.be.eql(`Error: ${message}`);
});
describe('when "response" is an object', () => {
@@ -71,10 +71,8 @@ describe('HttpException', () => {
const error = new HttpException(obj, 400);
const badRequestError = new BadRequestException(obj);
expect(`${error}`).to.be.eql(`HttpException: Http Exception`);
expect(`${badRequestError}`).to.be.eql(
`BadRequestException: Bad Request Exception`,
);
expect(`${error}`).to.be.eql(`Error: Http Exception`);
expect(`${badRequestError}`).to.be.eql(`Error: Bad Request Exception`);
expect(`${error}`.includes('[object Object]')).to.not.be.true;
expect(`${badRequestError}`.includes('[object Object]')).to.not.be.true;
});
@@ -82,7 +80,7 @@ describe('HttpException', () => {
it('should concat strings', () => {
const test = 'test message';
const error = new HttpException(test, 400);
expect(`${error}`).to.be.eql(`HttpException: ${test}`);
expect(`${error}`).to.be.eql(`Error: ${test}`);
expect(`${error}`.includes('[object Object]')).to.not.be.true;
});
});

View File

@@ -1,502 +0,0 @@
import { expect } from 'chai';
import 'reflect-metadata';
import * as sinon from 'sinon';
import { Logger, LoggerService } from '../../services';
describe('Logger', () => {
describe('[static methods]', () => {
describe('when the default logger is used', () => {
let processStdoutWriteSpy: sinon.SinonSpy;
let processStderrWriteSpy: sinon.SinonSpy;
beforeEach(() => {
processStdoutWriteSpy = sinon.spy(process.stdout, 'write');
processStderrWriteSpy = sinon.spy(process.stderr, 'write');
});
afterEach(() => {
processStdoutWriteSpy.restore();
processStderrWriteSpy.restore();
});
it('should print one message to the console', () => {
const message = 'random message';
const context = 'RandomContext';
Logger.log(message, context);
expect(processStdoutWriteSpy.calledOnce).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print one message without context to the console', () => {
const message = 'random message without context';
Logger.log(message);
expect(processStdoutWriteSpy.calledOnce).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print multiple messages to the console', () => {
const messages = ['message 1', 'message 2', 'message 3'];
const context = 'RandomContext';
Logger.log(messages[0], messages[1], messages[2], context);
expect(processStdoutWriteSpy.calledThrice).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
messages[0],
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(
messages[1],
);
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include(
messages[2],
);
});
it('should print one error to the console', () => {
const message = 'random error';
const context = 'RandomContext';
Logger.error(message, context);
expect(processStderrWriteSpy.calledOnce).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print one error without context to the console', () => {
const message = 'random error without context';
Logger.error(message);
expect(processStderrWriteSpy.calledOnce).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print error object without context to the console', () => {
const error = new Error('Random text here');
Logger.error(error);
expect(processStderrWriteSpy.calledOnce).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`Error: Random text here`,
);
});
it('should serialise a plain JS object (as a message) without context to the console', () => {
const error = {
randomError: true,
};
Logger.error(error);
expect(processStderrWriteSpy.calledOnce).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(`Object:`);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`{\n "randomError": true\n}`,
);
});
it('should print one error with stacktrace and context to the console', () => {
const message = 'random error with context';
const stacktrace = 'stacktrace';
const context = 'ErrorContext';
Logger.error(message, stacktrace, context);
expect(processStderrWriteSpy.calledTwice).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(message);
expect(processStderrWriteSpy.secondCall.firstArg).to.equal(
stacktrace + '\n',
);
});
it('should print multiple 2 errors and one stacktrace to the console', () => {
const messages = ['message 1', 'message 2'];
const stack = 'stacktrace';
const context = 'RandomContext';
Logger.error(messages[0], messages[1], stack, context);
expect(processStderrWriteSpy.calledThrice).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
messages[0],
);
expect(processStderrWriteSpy.secondCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.secondCall.firstArg).to.include(
messages[1],
);
expect(processStderrWriteSpy.thirdCall.firstArg).to.not.include(
`[${context}]`,
);
expect(processStderrWriteSpy.thirdCall.firstArg).to.equal(stack + '\n');
});
});
describe('when logging is disabled', () => {
let processStdoutWriteSpy: sinon.SinonSpy;
let previousLoggerRef: LoggerService;
beforeEach(() => {
processStdoutWriteSpy = sinon.spy(process.stdout, 'write');
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(false);
});
afterEach(() => {
processStdoutWriteSpy.restore();
Logger.overrideLogger(previousLoggerRef);
});
it('should not print any message to the console', () => {
const message = 'random message';
const context = 'RandomContext';
Logger.log(message, context);
expect(processStdoutWriteSpy.called).to.be.false;
});
});
describe('when custom logger is being used', () => {
class CustomLogger implements LoggerService {
log(message: any, context?: string) {}
error(message: any, trace?: string, context?: string) {}
warn(message: any, context?: string) {}
}
const customLogger = new CustomLogger();
let previousLoggerRef: LoggerService;
beforeEach(() => {
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(customLogger);
});
afterEach(() => {
Logger.overrideLogger(previousLoggerRef);
});
it('should call custom logger "#log()" method', () => {
const message = 'random message';
const context = 'RandomContext';
const customLoggerLogSpy = sinon.spy(customLogger, 'log');
Logger.log(message, context);
expect(customLoggerLogSpy.called).to.be.true;
expect(customLoggerLogSpy.calledWith(message, context)).to.be.true;
});
it('should call custom logger "#error()" method', () => {
const message = 'random message';
const context = 'RandomContext';
const customLoggerErrorSpy = sinon.spy(customLogger, 'error');
Logger.error(message, context);
expect(customLoggerErrorSpy.called).to.be.true;
expect(customLoggerErrorSpy.calledWith(message, context)).to.be.true;
});
});
});
describe('[instance methods]', () => {
describe('when the default logger is used', () => {
const logger = new Logger();
let processStdoutWriteSpy: sinon.SinonSpy;
let processStderrWriteSpy: sinon.SinonSpy;
beforeEach(() => {
processStdoutWriteSpy = sinon.spy(process.stdout, 'write');
processStderrWriteSpy = sinon.spy(process.stderr, 'write');
});
afterEach(() => {
processStdoutWriteSpy.restore();
processStderrWriteSpy.restore();
});
it('should print one message to the console', () => {
const message = 'random message';
const context = 'RandomContext';
logger.log(message, context);
expect(processStdoutWriteSpy.calledOnce).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print one message without context to the console', () => {
const message = 'random message without context';
logger.log(message);
expect(processStdoutWriteSpy.calledOnce).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print multiple messages to the console', () => {
const messages = ['message 1', 'message 2', 'message 3'];
const context = 'RandomContext';
logger.log(messages[0], messages[1], messages[2], context);
expect(processStdoutWriteSpy.calledThrice).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
messages[0],
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(
messages[1],
);
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include(
`[${context}]`,
);
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include(
messages[2],
);
});
it('should print one error to the console', () => {
const message = 'random error';
const context = 'RandomContext';
logger.error(message, context);
expect(processStderrWriteSpy.calledOnce).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print one error without context to the console', () => {
const message = 'random error without context';
logger.error(message);
expect(processStderrWriteSpy.calledOnce).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(message);
});
it('should print one error with stacktrace and context to the console', () => {
const message = 'random error with context';
const stacktrace = 'stacktrace';
const context = 'ErrorContext';
logger.error(message, stacktrace, context);
expect(processStderrWriteSpy.calledTwice).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(message);
expect(processStderrWriteSpy.secondCall.firstArg).to.equal(
stacktrace + '\n',
);
});
it('should print 2 errors and one stacktrace to the console', () => {
const messages = ['message 1', 'message 2'];
const stack = 'stacktrace';
const context = 'RandomContext';
logger.error(messages[0], messages[1], stack, context);
expect(processStderrWriteSpy.calledThrice).to.be.true;
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.firstCall.firstArg).to.include(
messages[0],
);
expect(processStderrWriteSpy.secondCall.firstArg).to.include(
`[${context}]`,
);
expect(processStderrWriteSpy.secondCall.firstArg).to.include(
messages[1],
);
expect(processStderrWriteSpy.thirdCall.firstArg).to.not.include(
`[${context}]`,
);
expect(processStderrWriteSpy.thirdCall.firstArg).to.equal(stack + '\n');
});
});
describe('when the default logger is used and global context is set and timestamp enabled', () => {
const globalContext = 'GlobalContext';
const logger = new Logger(globalContext, { timestamp: true });
let processStdoutWriteSpy: sinon.SinonSpy;
let processStderrWriteSpy: sinon.SinonSpy;
beforeEach(() => {
processStdoutWriteSpy = sinon.spy(process.stdout, 'write');
processStderrWriteSpy = sinon.spy(process.stderr, 'write');
});
afterEach(() => {
processStdoutWriteSpy.restore();
processStderrWriteSpy.restore();
});
it('should print multiple messages to the console and append global context', () => {
const messages = ['message 1', 'message 2', 'message 3'];
logger.log(messages[0], messages[1], messages[2]);
expect(processStdoutWriteSpy.calledThrice).to.be.true;
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
`[${globalContext}]`,
);
expect(processStdoutWriteSpy.firstCall.firstArg).to.include(
messages[0],
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(
`[${globalContext}]`,
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(
messages[1],
);
expect(processStdoutWriteSpy.secondCall.firstArg).to.include('ms');
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include(
`[${globalContext}]`,
);
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include(
messages[2],
);
expect(processStdoutWriteSpy.thirdCall.firstArg).to.include('ms');
});
});
describe('when logging is disabled', () => {
const logger = new Logger();
let processStdoutWriteSpy: sinon.SinonSpy;
let previousLoggerRef: LoggerService;
beforeEach(() => {
processStdoutWriteSpy = sinon.spy(process.stdout, 'write');
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(false);
});
afterEach(() => {
processStdoutWriteSpy.restore();
Logger.overrideLogger(previousLoggerRef);
});
it('should not print any message to the console', () => {
const message = 'random message';
const context = 'RandomContext';
logger.log(message, context);
expect(processStdoutWriteSpy.called).to.be.false;
});
});
describe('when custom logger is being used', () => {
class CustomLogger implements LoggerService {
log(message: any, context?: string) {}
error(message: any, trace?: string, context?: string) {}
warn(message: any, context?: string) {}
}
const customLogger = new CustomLogger();
const originalLogger = new Logger();
let previousLoggerRef: LoggerService;
beforeEach(() => {
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(customLogger);
});
afterEach(() => {
Logger.overrideLogger(previousLoggerRef);
});
it('should call custom logger "#log()" method', () => {
const message = 'random message';
const context = 'RandomContext';
const customLoggerLogSpy = sinon.spy(customLogger, 'log');
originalLogger.log(message, context);
expect(customLoggerLogSpy.called).to.be.true;
expect(customLoggerLogSpy.calledWith(message, context)).to.be.true;
});
it('should call custom logger "#error()" method', () => {
const message = 'random message';
const context = 'RandomContext';
const customLoggerErrorSpy = sinon.spy(customLogger, 'error');
originalLogger.error(message, context);
expect(customLoggerErrorSpy.called).to.be.true;
expect(customLoggerErrorSpy.calledWith(message, context)).to.be.true;
});
});
});
});

View File

@@ -1,41 +0,0 @@
import { expect } from 'chai';
import { LogLevel } from '../../../services/logger.service';
import { isLogLevelEnabled } from '../../../services/utils';
describe('isLogLevelEnabled', () => {
const tests = [
{ inputArgs: ['log', ['log']], expectedReturnValue: true },
{ inputArgs: ['debug', ['debug']], expectedReturnValue: true },
{ inputArgs: ['verbose', ['verbose']], expectedReturnValue: true },
{ inputArgs: ['error', ['error']], expectedReturnValue: true },
{ inputArgs: ['warn', ['warn']], expectedReturnValue: true },
/** explicitly included + log level is higher than target */
{ inputArgs: ['log', ['error', 'log']], expectedReturnValue: true },
{ inputArgs: ['warn', ['warn', 'error']], expectedReturnValue: true },
{ inputArgs: ['debug', ['warn', 'debug']], expectedReturnValue: true },
{ inputArgs: ['verbose', ['error', 'verbose']], expectedReturnValue: true },
/** not explicitly included + log level is higher than target */
{ inputArgs: ['log', ['error', 'warn']], expectedReturnValue: false },
{ inputArgs: ['verbose', ['warn']], expectedReturnValue: false },
{ inputArgs: ['debug', ['warn', 'error']], expectedReturnValue: false },
{ inputArgs: ['warn', ['error']], expectedReturnValue: false },
];
for (const { inputArgs, expectedReturnValue } of tests) {
describe(`when log levels = [${inputArgs[1]}]`, () => {
describe(`and target level is "${inputArgs[0]}"`, () => {
it('should return true', () => {
expect(
isLogLevelEnabled(...(inputArgs as [LogLevel, LogLevel[]])),
).to.equal(expectedReturnValue);
});
});
});
}
describe(`when log levels = undefined`, () => {
it('should return false', () => {
expect(isLogLevelEnabled('warn', undefined)).to.be.false;
});
});
});

View File

@@ -9,7 +9,6 @@ import {
isPlainObject,
isString,
isUndefined,
normalizePath,
} from '../../utils/shared.utils';
function Foo(a) {
@@ -18,34 +17,34 @@ function Foo(a) {
describe('Shared utils', () => {
describe('isUndefined', () => {
it('should return true when obj is undefined', () => {
it('should returns true when obj is undefined', () => {
expect(isUndefined(undefined)).to.be.true;
});
it('should return false when object is not undefined', () => {
it('should returns false when object is not undefined', () => {
expect(isUndefined({})).to.be.false;
});
});
describe('isFunction', () => {
it('should return true when obj is function', () => {
it('should returns true when obj is function', () => {
expect(isFunction(() => ({}))).to.be.true;
});
it('should return false when object is not function', () => {
it('should returns false when object is not function', () => {
expect(isFunction(null)).to.be.false;
expect(isFunction(undefined)).to.be.false;
});
});
describe('isObject', () => {
it('should return true when obj is object', () => {
it('should returns true when obj is object', () => {
expect(isObject({})).to.be.true;
});
it('should return false when object is not object', () => {
it('should returns false when object is not object', () => {
expect(isObject(3)).to.be.false;
expect(isObject(null)).to.be.false;
expect(isObject(undefined)).to.be.false;
});
});
describe('isPlainObject', () => {
it('should return true when obj is plain object', () => {
it('should returns true when obj is plain object', () => {
expect(isPlainObject({})).to.be.true;
expect(isPlainObject({ prop: true })).to.be.true;
expect(
@@ -55,7 +54,7 @@ describe('Shared utils', () => {
).to.be.true;
expect(isPlainObject(Object.create(null))).to.be.true;
});
it('should return false when object is not object', () => {
it('should returns false when object is not object', () => {
expect(isPlainObject(3)).to.be.false;
expect(isPlainObject(null)).to.be.false;
expect(isPlainObject(undefined)).to.be.false;
@@ -65,69 +64,52 @@ describe('Shared utils', () => {
});
});
describe('isString', () => {
it('should return true when obj is a string', () => {
it('should returns true when obj is string', () => {
expect(isString('true')).to.be.true;
});
it('should return false when object is not a string', () => {
it('should returns false when object is not string', () => {
expect(isString(false)).to.be.false;
expect(isString(null)).to.be.false;
expect(isString(undefined)).to.be.false;
});
});
describe('isConstructor', () => {
it('should return true when string is equal to constructor', () => {
it('should returns true when string is equal constructor', () => {
expect(isConstructor('constructor')).to.be.true;
});
it('should return false when string is not equal to constructor', () => {
it('should returns false when string is not equal constructor', () => {
expect(isConstructor('nope')).to.be.false;
});
});
describe('addLeadingSlash', () => {
it('should return the validated path ("add / if not exists")', () => {
it('should returns validated path ("add / if not exists")', () => {
expect(addLeadingSlash('nope')).to.be.eql('/nope');
});
it('should return the same path', () => {
it('should returns same path', () => {
expect(addLeadingSlash('/nope')).to.be.eql('/nope');
});
it('should return empty path', () => {
it('should returns empty path', () => {
expect(addLeadingSlash('')).to.be.eql('');
expect(addLeadingSlash(null)).to.be.eql('');
expect(addLeadingSlash(undefined)).to.be.eql('');
});
});
describe('normalizePath', () => {
it('should remove all trailing slashes at the end of the path', () => {
expect(normalizePath('path/')).to.be.eql('/path');
expect(normalizePath('path///')).to.be.eql('/path');
expect(normalizePath('/path/path///')).to.be.eql('/path/path');
});
it('should replace all slashes with only one slash', () => {
expect(normalizePath('////path/')).to.be.eql('/path');
expect(normalizePath('///')).to.be.eql('/');
expect(normalizePath('/path////path///')).to.be.eql('/path/path');
});
it('should return / for empty path', () => {
expect(normalizePath('')).to.be.eql('/');
expect(normalizePath(null)).to.be.eql('/');
expect(normalizePath(undefined)).to.be.eql('/');
});
});
describe('isNil', () => {
it('should return true when obj is undefined or null', () => {
it('should returns true when obj is undefined or null', () => {
expect(isNil(undefined)).to.be.true;
expect(isNil(null)).to.be.true;
});
it('should return false when object is not undefined and null', () => {
it('should returns false when object is not undefined and null', () => {
expect(isNil('3')).to.be.false;
});
});
describe('isEmpty', () => {
it('should return true when array is empty or not exists', () => {
it('should returns true when array is empty or not exists', () => {
expect(isEmpty([])).to.be.true;
expect(isEmpty(null)).to.be.true;
expect(isEmpty(undefined)).to.be.true;
});
it('should return false when array is not empty', () => {
it('should returns false when array is not empty', () => {
expect(isEmpty([1, 2])).to.be.false;
});
});

View File

@@ -33,13 +33,6 @@ export const addLeadingSlash = (path?: string): string =>
*/
export const validatePath = addLeadingSlash;
export const normalizePath = (path?: string): string =>
path
? path.startsWith('/')
? ('/' + path.replace(/\/+$/, '')).replace(/\/+/g, '/')
: '/' + path.replace(/\/+$/, '')
: '/';
export const isFunction = (fn: any): boolean => typeof fn === 'function';
export const isString = (fn: any): fn is string => typeof fn === 'string';
export const isConstructor = (fn: any): boolean => fn === 'constructor';

View File

@@ -56,16 +56,21 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
#### Principal Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td><td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="180" valign="middle" /></a></td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td></tr></table>
#### Silver Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="170" valign="middle" /></a> </td><td>
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></td><td>
<a href="http://www.meetdandy.com" target="_blank"><img src="https://nestjs.com/img/dandy-wide-logo.png" width="150" valign="middle" /></td></tr></table>
@@ -90,6 +95,11 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://streamat.se/" target="_blank"><img src="https://nestjs.com/img/streamat-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://filmen.nu/" target="_blank"><img src="https://nestjs.com/img/filmen-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -17,6 +17,14 @@ export abstract class AbstractHttpAdapter<
protected httpServer: TServer;
constructor(protected readonly instance: any) {}
all(path: string, handler: RequestHandler<TRequest, TResponse>);
all(handler: RequestHandler<TRequest, TResponse>);
all(path: any, handler?: any) {
throw new Error('Method not implemented.');
}
setBaseViewsDir?(path: string | string[]): this {
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
public async init() {}
@@ -61,12 +69,6 @@ export abstract class AbstractHttpAdapter<
return this.instance.patch(...args);
}
public all(handler: RequestHandler);
public all(path: any, handler: RequestHandler);
public all(...args: any[]) {
return this.instance.all(...args);
}
public options(handler: RequestHandler);
public options(path: any, handler: RequestHandler);
public options(...args: any[]) {

View File

@@ -1,4 +1,3 @@
import { Logger } from '@nestjs/common';
import { ExceptionHandler } from './exception-handler';
const DEFAULT_TEARDOWN = () => process.exit(1);
@@ -9,15 +8,11 @@ export class ExceptionsZone {
public static run(
callback: () => void,
teardown: (err: any) => void = DEFAULT_TEARDOWN,
autoFlushLogs?: boolean,
) {
try {
callback();
} catch (e) {
this.exceptionHandler.handle(e);
if (autoFlushLogs) {
Logger.flush();
}
teardown(e);
}
}
@@ -25,15 +20,11 @@ export class ExceptionsZone {
public static async asyncRun(
callback: () => Promise<void>,
teardown: (err: any) => void = DEFAULT_TEARDOWN,
autoFlushLogs?: boolean,
) {
try {
await callback();
} catch (e) {
this.exceptionHandler.handle(e);
if (autoFlushLogs) {
Logger.flush();
}
teardown(e);
}
}

View File

@@ -46,15 +46,10 @@ export class BaseExceptionFilter<T = any> implements ExceptionFilter<T> {
host: ArgumentsHost,
applicationRef: AbstractHttpAdapter | HttpServer,
) {
const body = this.isHttpError(exception)
? {
statusCode: exception.statusCode,
message: exception.message,
}
: {
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
message: MESSAGES.UNKNOWN_EXCEPTION_MESSAGE,
};
const body = {
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
message: MESSAGES.UNKNOWN_EXCEPTION_MESSAGE,
};
applicationRef.reply(host.getArgByIndex(1), body, body.statusCode);
if (this.isExceptionObject(exception)) {
return BaseExceptionFilter.logger.error(
@@ -68,12 +63,4 @@ export class BaseExceptionFilter<T = any> implements ExceptionFilter<T> {
public isExceptionObject(err: any): err is Error {
return isObject(err) && !!(err as Error).message;
}
/**
* Checks if the thrown error comes from the "http-errors" library.
* @param err error object
*/
public isHttpError(err: any): err is { statusCode: number; message: string } {
return err?.statusCode && err?.message;
}
}

View File

@@ -1,6 +1,6 @@
import { CanActivate } from '@nestjs/common';
import { GUARDS_METADATA } from '@nestjs/common/constants';
import { Controller, Type } from '@nestjs/common/interfaces';
import { Controller } from '@nestjs/common/interfaces';
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { ApplicationConfig } from '../application-config';
@@ -54,17 +54,15 @@ export class GuardsContextCreator extends ContextCreator {
}
public getGuardInstance(
metatype: Function | CanActivate,
guard: Function | CanActivate,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): CanActivate | null {
const isObject = (metatype as CanActivate).canActivate;
const isObject = (guard as CanActivate).canActivate;
if (isObject) {
return metatype as CanActivate;
return guard as CanActivate;
}
const instanceWrapper = this.getInstanceByMetatype(
metatype as Type<unknown>,
);
const instanceWrapper = this.getInstanceByMetatype(guard);
if (!instanceWrapper) {
return null;
}
@@ -75,8 +73,8 @@ export class GuardsContextCreator extends ContextCreator {
return instanceHost && instanceHost.instance;
}
public getInstanceByMetatype(
metatype: Type<unknown>,
public getInstanceByMetatype<T extends Record<string, any> | Function>(
guard: T,
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return;
@@ -87,7 +85,7 @@ export class GuardsContextCreator extends ContextCreator {
return;
}
const injectables = moduleRef.injectables;
return injectables.get(metatype);
return injectables.get(guard.name as string);
}
public getGlobalMetadata<T extends unknown[]>(

View File

@@ -13,6 +13,7 @@ import { GuardsContextCreator } from '../guards/guards-context-creator';
import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { ContextId } from '../injector/instance-wrapper';
import { Module } from '../injector/module';
import { ModulesContainer } from '../injector/modules-container';
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
@@ -104,7 +105,7 @@ export class ExternalContextCreator {
},
contextType: TContext = 'http' as TContext,
) {
const module = this.getContextModuleKey(instance.constructor);
const module = this.getContextModuleName(instance.constructor);
const { argsLength, paramtypes, getParamsMetadata } = this.getMetadata<
TParamsMetadata,
TContext
@@ -237,18 +238,26 @@ export class ExternalContextCreator {
return handlerMetadata;
}
public getContextModuleKey(moduleCtor: Function | undefined): string {
const emptyModuleKey = '';
if (!moduleCtor) {
return emptyModuleKey;
public getContextModuleName(constructor: Function): string {
const defaultModuleName = '';
const className = constructor.name;
if (!className) {
return defaultModuleName;
}
const moduleContainerEntries = this.modulesContainer.entries();
for (const [key, moduleRef] of moduleContainerEntries) {
if (moduleRef.hasProvider(moduleCtor)) {
for (const [key, module] of [...this.modulesContainer.entries()]) {
if (this.getProviderByClassName(module, className)) {
return key;
}
}
return emptyModuleKey;
return defaultModuleName;
}
public getProviderByClassName(module: Module, className: string): boolean {
const { providers } = module;
const hasProvider = [...providers.keys()].some(
provider => provider === className,
);
return hasProvider;
}
public exchangeKeysForValues<TMetadata = any>(

View File

@@ -7,7 +7,7 @@ export class RouterMethodFactory {
case RequestMethod.POST:
return target.post;
case RequestMethod.ALL:
return target.all;
return target.use;
case RequestMethod.DELETE:
return target.delete;
case RequestMethod.PUT:

View File

@@ -56,7 +56,7 @@ export class NestContainer {
scope: Type<any>[],
): Promise<Module> {
// In DependenciesScanner#scanForModules we already check for undefined or invalid modules
// We still need to catch the edge-case of `forwardRef(() => undefined)`
// We sill need to catch the edge-case of `forwardRef(() => undefined)`
if (!metatype) {
throw new UndefinedForwardRefException(scope);
}
@@ -143,10 +143,7 @@ export class NestContainer {
moduleRef.addRelatedModule(related);
}
public addProvider(
provider: Provider,
token: string,
): string | symbol | Function {
public addProvider(provider: Provider, token: string): string {
if (!provider) {
throw new CircularDependencyException();
}

View File

@@ -1,13 +1,12 @@
import { iterate } from 'iterare';
import { InstanceWrapper } from '../instance-wrapper';
import { InstanceToken } from '../module';
/**
* Returns the instances which are transient
* @param instances The instances which should be checked whether they are transcient
*/
export function getTransientInstances(
instances: [InstanceToken, InstanceWrapper][],
instances: [string, InstanceWrapper][],
): InstanceWrapper[] {
return iterate(instances)
.filter(([_, wrapper]) => wrapper.isDependencyTreeStatic())
@@ -23,7 +22,7 @@ export function getTransientInstances(
* @param instances The instances which should be checked whether they are transcient
*/
export function getNonTransientInstances(
instances: [InstanceToken, InstanceWrapper][],
instances: [string, InstanceWrapper][],
): InstanceWrapper[] {
return iterate(instances)
.filter(

View File

@@ -9,6 +9,7 @@ import { Controller } from '@nestjs/common/interfaces/controllers/controller.int
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
import {
isFunction,
isNil,
isObject,
isString,
@@ -26,7 +27,7 @@ import {
InstanceWrapper,
PropertyMetadata,
} from './instance-wrapper';
import { InstanceToken, Module } from './module';
import { Module } from './module';
/**
* The type of an injectable dependency
@@ -53,9 +54,9 @@ export interface InjectorDependencyContext {
*/
key?: string | symbol;
/**
* The function itself, the name of the function, or injection token.
* The name of the function or injection token
*/
name?: Function | string | symbol;
name?: string | symbol;
/**
* The index of the dependency which gets injected
* from the dependencies array
@@ -69,27 +70,27 @@ export interface InjectorDependencyContext {
export class Injector {
public loadPrototype<T>(
{ token }: InstanceWrapper<T>,
collection: Map<InstanceToken, InstanceWrapper<T>>,
{ name }: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper<T>>,
contextId = STATIC_CONTEXT,
) {
if (!collection) {
return;
}
const target = collection.get(token);
const target = collection.get(name);
const instance = target.createPrototype(contextId);
if (instance) {
const wrapper = new InstanceWrapper({
...target,
instance,
});
collection.set(token, wrapper);
collection.set(name, wrapper);
}
}
public async loadInstance<T>(
wrapper: InstanceWrapper<T>,
collection: Map<InstanceToken, InstanceWrapper>,
collection: Map<string, InstanceWrapper>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
@@ -100,10 +101,9 @@ export class Injector {
return instanceHost.donePromise;
}
const done = this.applyDoneHook(instanceHost);
const token = wrapper.token || wrapper.name;
const { name, inject } = wrapper;
const { inject } = wrapper;
const targetWrapper = collection.get(token);
const targetWrapper = collection.get(name);
if (isUndefined(targetWrapper)) {
throw new RuntimeException();
}
@@ -142,13 +142,13 @@ export class Injector {
public async loadMiddleware(
wrapper: InstanceWrapper,
collection: Map<InstanceToken, InstanceWrapper>,
collection: Map<string, InstanceWrapper>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const { metatype, token } = wrapper;
const targetWrapper = collection.get(token);
const { metatype } = wrapper;
const targetWrapper = collection.get(metatype.name);
if (!isUndefined(targetWrapper.instance)) {
return;
}
@@ -317,7 +317,7 @@ export class Injector {
const token = this.resolveParamToken(wrapper, param);
return this.resolveComponentInstance<T>(
moduleRef,
token,
isFunction(token) ? (token as Type<any>).name : token,
dependencyContext,
wrapper,
contextId,
@@ -339,7 +339,7 @@ export class Injector {
public async resolveComponentInstance<T>(
moduleRef: Module,
token: InstanceToken,
name: any,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
@@ -350,7 +350,7 @@ export class Injector {
const instanceWrapper = await this.lookupComponent(
providers,
moduleRef,
{ ...dependencyContext, name: token },
{ ...dependencyContext, name },
wrapper,
contextId,
inquirer,
@@ -407,7 +407,7 @@ export class Injector {
}
public async lookupComponent<T = any>(
providers: Map<Function | string | symbol, InstanceWrapper>,
providers: Map<string | symbol, InstanceWrapper>,
moduleRef: Module,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
@@ -467,7 +467,7 @@ export class Injector {
public async lookupComponentInImports(
moduleRef: Module,
name: InstanceToken,
name: any,
wrapper: InstanceWrapper,
moduleRegistry: any[] = [],
contextId = STATIC_CONTEXT,
@@ -484,7 +484,7 @@ export class Injector {
if (isTraversing) {
const contextModuleExports = moduleRef.exports;
children = children.filter(child =>
contextModuleExports.has(child.metatype),
contextModuleExports.has(child.metatype && child.metatype.name),
);
}
for (const relatedModule of children) {
@@ -552,7 +552,7 @@ export class Injector {
try {
const dependencyContext = {
key: item.key,
name: item.name as Function | string | symbol,
name: item.name as string,
};
if (this.isInquirer(item.name, parentInquirer)) {
return parentInquirer && parentInquirer.instance;
@@ -656,12 +656,15 @@ export class Injector {
public async loadPerContext<T = any>(
instance: T,
moduleRef: Module,
collection: Map<InstanceToken, InstanceWrapper>,
collection: Map<string, InstanceWrapper>,
ctx: ContextId,
wrapper?: InstanceWrapper,
): Promise<T> {
if (!wrapper) {
const injectionToken = instance.constructor;
const providerCtor = instance.constructor;
const injectionToken =
(providerCtor && providerCtor.name) ||
((providerCtor as unknown) as string);
wrapper = collection.get(injectionToken);
}
await this.loadInstance(wrapper, collection, moduleRef, ctx, wrapper);

View File

@@ -1,9 +1,11 @@
import { Abstract, Type } from '@nestjs/common';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import { UnknownElementException } from '../errors/exceptions/unknown-element.exception';
import { NestContainer } from './container';
import { InstanceWrapper } from './instance-wrapper';
import { InstanceToken, Module } from './module';
import { Module } from './module';
type InstanceToken = string | symbol | Type<any> | Abstract<any> | Function;
type HostCollection = 'providers' | 'controllers' | 'injectables';
export interface InstanceLink<T = any> {
@@ -21,17 +23,20 @@ export class InstanceLinksHost {
}
get<T = any>(token: InstanceToken, moduleId?: string): InstanceLink<T> {
const modulesMap = this.instanceLinks.get(token);
const name = isFunction(token)
? (token as Function).name
: (token as string | symbol);
const modulesMap = this.instanceLinks.get(name);
if (!modulesMap) {
throw new UnknownElementException(this.getInstanceNameByToken(token));
throw new UnknownElementException(name);
}
const instanceLink = moduleId
? modulesMap.find(item => item.moduleId === moduleId)
: modulesMap[modulesMap.length - 1];
if (!instanceLink) {
throw new UnknownElementException(this.getInstanceNameByToken(token));
throw new UnknownElementException(name);
}
return instanceLink;
}
@@ -71,8 +76,4 @@ export class InstanceLinksHost {
existingLinks.push(instanceLink);
}
}
private getInstanceNameByToken(token: InstanceToken): string {
return isFunction(token) ? (token as Function)?.name : (token as string);
}
}

View File

@@ -9,9 +9,7 @@ import { Module } from './module';
export class InstanceLoader {
private readonly injector = new Injector();
private readonly logger = new Logger(InstanceLoader.name, {
timestamp: true,
});
private readonly logger = new Logger(InstanceLoader.name, true);
constructor(private readonly container: NestContainer) {}
@@ -23,69 +21,69 @@ export class InstanceLoader {
}
private createPrototypes(modules: Map<string, Module>) {
modules.forEach(moduleRef => {
this.createPrototypesOfProviders(moduleRef);
this.createPrototypesOfInjectables(moduleRef);
this.createPrototypesOfControllers(moduleRef);
modules.forEach(module => {
this.createPrototypesOfProviders(module);
this.createPrototypesOfInjectables(module);
this.createPrototypesOfControllers(module);
});
}
private async createInstances(modules: Map<string, Module>) {
await Promise.all(
[...modules.values()].map(async moduleRef => {
await this.createInstancesOfProviders(moduleRef);
await this.createInstancesOfInjectables(moduleRef);
await this.createInstancesOfControllers(moduleRef);
[...modules.values()].map(async module => {
await this.createInstancesOfProviders(module);
await this.createInstancesOfInjectables(module);
await this.createInstancesOfControllers(module);
const { name } = moduleRef.metatype;
const { name } = module.metatype;
this.isModuleWhitelisted(name) &&
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
}),
);
}
private createPrototypesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
private createPrototypesOfProviders(module: Module) {
const { providers } = module;
providers.forEach(wrapper =>
this.injector.loadPrototype<Injectable>(wrapper, providers),
);
}
private async createInstancesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
private async createInstancesOfProviders(module: Module) {
const { providers } = module;
const wrappers = [...providers.values()];
await Promise.all(
wrappers.map(item => this.injector.loadProvider(item, moduleRef)),
wrappers.map(item => this.injector.loadProvider(item, module)),
);
}
private createPrototypesOfControllers(moduleRef: Module) {
const { controllers } = moduleRef;
private createPrototypesOfControllers(module: Module) {
const { controllers } = module;
controllers.forEach(wrapper =>
this.injector.loadPrototype<Controller>(wrapper, controllers),
);
}
private async createInstancesOfControllers(moduleRef: Module) {
const { controllers } = moduleRef;
private async createInstancesOfControllers(module: Module) {
const { controllers } = module;
const wrappers = [...controllers.values()];
await Promise.all(
wrappers.map(item => this.injector.loadController(item, moduleRef)),
wrappers.map(item => this.injector.loadController(item, module)),
);
}
private createPrototypesOfInjectables(moduleRef: Module) {
const { injectables } = moduleRef;
private createPrototypesOfInjectables(module: Module) {
const { injectables } = module;
injectables.forEach(wrapper =>
this.injector.loadPrototype(wrapper, injectables),
);
}
private async createInstancesOfInjectables(moduleRef: Module) {
const { injectables } = moduleRef;
private async createInstancesOfInjectables(module: Module) {
const { injectables } = module;
const wrappers = [...injectables.values()];
await Promise.all(
wrappers.map(item => this.injector.loadInjectable(item, moduleRef)),
wrappers.map(item => this.injector.loadInjectable(item, module)),
);
}

View File

@@ -8,7 +8,7 @@ import { randomStringGenerator } from '@nestjs/common/utils/random-string-genera
import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { STATIC_CONTEXT } from './constants';
import { InstanceToken, Module } from './module';
import { Module } from './module';
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
@@ -36,7 +36,6 @@ interface InstanceMetadataStore {
export class InstanceWrapper<T = any> {
public readonly name: any;
public readonly token: InstanceToken;
public readonly async?: boolean;
public readonly host?: Module;
public readonly isAlias: boolean = false;

View File

@@ -4,25 +4,10 @@ import { requestProvider } from '../router/request/request-providers';
import { Reflector } from '../services';
import { inquirerProvider } from './inquirer/inquirer-providers';
const ReflectorAliasProvider = {
provide: Reflector.name,
useExisting: Reflector,
};
@Global()
@Module({
providers: [
Reflector,
ReflectorAliasProvider,
requestProvider,
inquirerProvider,
],
exports: [
Reflector,
ReflectorAliasProvider,
requestProvider,
inquirerProvider,
],
providers: [Reflector, requestProvider, inquirerProvider],
exports: [Reflector, requestProvider, inquirerProvider],
})
export class InternalCoreModule {
static register(providers: ValueProvider[]): DynamicModule {

View File

@@ -31,33 +31,21 @@ import { NestContainer } from './container';
import { InstanceWrapper } from './instance-wrapper';
import { ModuleRef } from './module-ref';
export type InstanceToken =
| string
| symbol
| Type<any>
| Abstract<any>
| Function;
interface ProviderName {
name?: string | symbol;
}
export class Module {
private readonly _id: string;
private readonly _imports = new Set<Module>();
private readonly _providers = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _injectables = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _middlewares = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _providers = new Map<any, InstanceWrapper<Injectable>>();
private readonly _injectables = new Map<any, InstanceWrapper<Injectable>>();
private readonly _middlewares = new Map<any, InstanceWrapper<Injectable>>();
private readonly _controllers = new Map<
InstanceToken,
string,
InstanceWrapper<Controller>
>();
private readonly _exports = new Set<InstanceToken>();
private readonly _exports = new Set<string | symbol>();
private _distance = 0;
constructor(
@@ -72,11 +60,11 @@ export class Module {
return this._id;
}
get providers(): Map<InstanceToken, InstanceWrapper<Injectable>> {
get providers(): Map<any, InstanceWrapper<Injectable>> {
return this._providers;
}
get middlewares(): Map<InstanceToken, InstanceWrapper<Injectable>> {
get middlewares(): Map<any, InstanceWrapper<Injectable>> {
return this._middlewares;
}
@@ -94,34 +82,34 @@ export class Module {
/**
* Left for backward-compatibility reasons
*/
get components(): Map<InstanceToken, InstanceWrapper<Injectable>> {
get components(): Map<string, InstanceWrapper<Injectable>> {
return this._providers;
}
/**
* Left for backward-compatibility reasons
*/
get routes(): Map<InstanceToken, InstanceWrapper<Controller>> {
get routes(): Map<string, InstanceWrapper<Controller>> {
return this._controllers;
}
get injectables(): Map<InstanceToken, InstanceWrapper<Injectable>> {
get injectables(): Map<string, InstanceWrapper<Injectable>> {
return this._injectables;
}
get controllers(): Map<InstanceToken, InstanceWrapper<Controller>> {
get controllers(): Map<string, InstanceWrapper<Controller>> {
return this._controllers;
}
get exports(): Set<InstanceToken> {
get exports(): Set<string | symbol> {
return this._exports;
}
get instance(): NestModule {
if (!this._providers.has(this._metatype)) {
if (!this._providers.has(this._metatype.name)) {
throw new RuntimeException();
}
const module = this._providers.get(this._metatype);
const module = this._providers.get(this._metatype.name);
return module.instance as NestModule;
}
@@ -146,9 +134,8 @@ export class Module {
public addModuleRef() {
const moduleRef = this.createModuleReferenceType();
this._providers.set(
ModuleRef,
ModuleRef.name,
new InstanceWrapper({
token: ModuleRef,
name: ModuleRef.name,
metatype: ModuleRef as any,
isResolved: true,
@@ -160,9 +147,8 @@ export class Module {
public addModuleAsProvider() {
this._providers.set(
this._metatype,
this._metatype.name,
new InstanceWrapper({
token: this._metatype,
name: this._metatype.name,
metatype: this._metatype,
isResolved: false,
@@ -174,9 +160,8 @@ export class Module {
public addApplicationConfig() {
this._providers.set(
ApplicationConfig,
ApplicationConfig.name,
new InstanceWrapper({
token: ApplicationConfig,
name: ApplicationConfig.name,
isResolved: true,
instance: this.container.applicationConfig,
@@ -192,10 +177,9 @@ export class Module {
if (this.isCustomProvider(injectable)) {
return this.addCustomProvider(injectable, this._injectables);
}
let instanceWrapper = this.injectables.get(injectable);
let instanceWrapper = this.injectables.get(injectable.name);
if (!instanceWrapper) {
instanceWrapper = new InstanceWrapper({
token: injectable,
name: injectable.name,
metatype: injectable,
instance: null,
@@ -203,23 +187,23 @@ export class Module {
scope: getClassScope(injectable),
host: this,
});
this._injectables.set(injectable, instanceWrapper);
this._injectables.set(injectable.name, instanceWrapper);
}
if (host) {
const token = host && host.name;
const hostWrapper =
this._controllers.get(host) || this._providers.get(host);
this._controllers.get(host && host.name) || this._providers.get(token);
hostWrapper && hostWrapper.addEnhancerMetadata(instanceWrapper);
}
}
public addProvider(provider: Provider) {
public addProvider(provider: Provider): string {
if (this.isCustomProvider(provider)) {
return this.addCustomProvider(provider, this._providers);
}
this._providers.set(
provider,
(provider as Type<Injectable>).name,
new InstanceWrapper({
token: provider,
name: (provider as Type<Injectable>).name,
metatype: provider as Type<Injectable>,
instance: null,
@@ -228,7 +212,7 @@ export class Module {
host: this,
}),
);
return provider as Type<Injectable>;
return (provider as Type<Injectable>).name;
}
public isCustomProvider(
@@ -248,13 +232,20 @@ export class Module {
}
public addCustomProvider(
provider:
provider: (
| ClassProvider
| FactoryProvider
| ValueProvider
| ExistingProvider,
collection: Map<Function | string | symbol, any>,
) {
| ExistingProvider
) &
ProviderName,
collection: Map<string, any>,
): string {
const name = this.getProviderStaticToken(provider.provide) as string;
provider = {
...provider,
name,
};
if (this.isCustomClass(provider)) {
this.addCustomClass(provider, collection);
} else if (this.isCustomValue(provider)) {
@@ -264,7 +255,7 @@ export class Module {
} else if (this.isCustomUseExisting(provider)) {
this.addCustomUseExisting(provider, collection);
}
return provider.provide;
return name;
}
public isCustomClass(provider: any): provider is ClassProvider {
@@ -288,20 +279,19 @@ export class Module {
}
public addCustomClass(
provider: ClassProvider,
collection: Map<InstanceToken, InstanceWrapper>,
provider: ClassProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
) {
let { scope } = provider;
const { name, useClass } = provider;
const { useClass } = provider;
let { scope } = provider;
if (isUndefined(scope)) {
scope = getClassScope(useClass);
}
collection.set(
provider.provide,
name as string,
new InstanceWrapper({
token: provider.provide,
name: useClass?.name || useClass,
name,
metatype: useClass,
instance: null,
isResolved: false,
@@ -312,15 +302,14 @@ export class Module {
}
public addCustomValue(
provider: ValueProvider,
collection: Map<Function | string | symbol, InstanceWrapper>,
provider: ValueProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
) {
const { useValue: value, provide: providerToken } = provider;
const { name, useValue: value } = provider;
collection.set(
providerToken,
name as string,
new InstanceWrapper({
token: providerToken,
name: (providerToken as Function)?.name || providerToken,
name,
metatype: null,
instance: value,
isResolved: true,
@@ -331,21 +320,14 @@ export class Module {
}
public addCustomFactory(
provider: FactoryProvider,
collection: Map<Function | string | symbol, InstanceWrapper>,
provider: FactoryProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
) {
const {
useFactory: factory,
inject,
scope,
provide: providerToken,
} = provider;
const { name, useFactory: factory, inject, scope } = provider;
collection.set(
providerToken,
name as string,
new InstanceWrapper({
token: providerToken,
name: (providerToken as Function)?.name || providerToken,
name,
metatype: factory as any,
instance: null,
isResolved: false,
@@ -357,15 +339,14 @@ export class Module {
}
public addCustomUseExisting(
provider: ExistingProvider,
collection: Map<Function | string | symbol, InstanceWrapper>,
provider: ExistingProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
) {
const { useExisting, provide: providerToken } = provider;
const { name, useExisting } = provider;
collection.set(
providerToken,
name as string,
new InstanceWrapper({
token: providerToken,
name: (providerToken as Function)?.name || providerToken,
name,
metatype: (instance => instance) as any,
instance: null,
isResolved: false,
@@ -377,9 +358,9 @@ export class Module {
}
public addExportedProvider(
provider: Provider | string | symbol | DynamicModule,
provider: (Provider & ProviderName) | string | symbol | DynamicModule,
) {
const addExportedUnit = (token: InstanceToken) =>
const addExportedUnit = (token: string | symbol) =>
this._exports.add(this.validateExportedProvider(token));
if (this.isCustomProvider(provider as any)) {
@@ -390,7 +371,7 @@ export class Module {
const { module } = provider;
return addExportedUnit(module.name);
}
addExportedUnit(provider as Type<any>);
addExportedUnit(provider.name);
}
public addCustomExportedProvider(
@@ -404,10 +385,10 @@ export class Module {
if (isString(provide) || isSymbol(provide)) {
return this._exports.add(this.validateExportedProvider(provide));
}
this._exports.add(this.validateExportedProvider(provide));
this._exports.add(this.validateExportedProvider(provide.name));
}
public validateExportedProvider(token: InstanceToken) {
public validateExportedProvider(token: string | symbol) {
if (this._providers.has(token)) {
return token;
}
@@ -421,17 +402,15 @@ export class Module {
if (!importsNames.includes(token as string)) {
const { name } = this.metatype;
const providerName = isFunction(token) ? (token as Function).name : token;
throw new UnknownExportException(providerName as string, name);
throw new UnknownExportException(token, name);
}
return token;
}
public addController(controller: Type<Controller>) {
this._controllers.set(
controller,
controller.name,
new InstanceWrapper({
token: controller,
name: controller.name,
metatype: controller,
instance: null,
@@ -457,13 +436,15 @@ export class Module {
this._imports.add(module);
}
public replace(toReplace: InstanceToken, options: any) {
public replace(toReplace: string | symbol | Type<any>, options: any) {
if (options.isProvider && this.hasProvider(toReplace)) {
const originalProvider = this._providers.get(toReplace);
const name = this.getProviderStaticToken(toReplace);
const originalProvider = this._providers.get(name);
return originalProvider.mergeWith({ provide: toReplace, ...options });
} else if (!options.isProvider && this.hasInjectable(toReplace)) {
const originalInjectable = this._injectables.get(toReplace);
const name = this.getProviderStaticToken(toReplace);
const originalInjectable = this._injectables.get(name);
return originalInjectable.mergeWith({
provide: toReplace,
@@ -472,21 +453,29 @@ export class Module {
}
}
public hasProvider(token: InstanceToken): boolean {
return this._providers.has(token);
public hasProvider(token: string | symbol | Type<any>): boolean {
const name = this.getProviderStaticToken(token);
return this._providers.has(name);
}
public hasInjectable(token: InstanceToken): boolean {
return this._injectables.has(token);
public hasInjectable(token: string | symbol | Type<any>): boolean {
const name = this.getProviderStaticToken(token);
return this._injectables.has(name);
}
public getProviderByKey<T = any>(name: InstanceToken): InstanceWrapper<T> {
public getProviderStaticToken(
provider: string | symbol | Type<any> | Abstract<any>,
): string | symbol {
return isFunction(provider)
? (provider as Function).name
: (provider as string | symbol);
}
public getProviderByKey<T = any>(name: string | symbol): InstanceWrapper<T> {
return this._providers.get(name) as InstanceWrapper<T>;
}
public getNonAliasProviders(): Array<
[InstanceToken, InstanceWrapper<Injectable>]
> {
public getNonAliasProviders(): Array<[string, InstanceWrapper<Injectable>]> {
return [...this._providers].filter(([_, wrapper]) => !wrapper.isAlias);
}

View File

@@ -1,10 +1,3 @@
import { v4 as uuid } from 'uuid';
import { Module } from './module';
export class ModulesContainer extends Map<string, Module> {
private readonly _applicationId = uuid();
get applicationId(): string {
return this._applicationId;
}
}
export class ModulesContainer extends Map<string, Module> {}

View File

@@ -1,5 +1,5 @@
import { INTERCEPTORS_METADATA } from '@nestjs/common/constants';
import { Controller, NestInterceptor, Type } from '@nestjs/common/interfaces';
import { Controller, NestInterceptor } from '@nestjs/common/interfaces';
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { ApplicationConfig } from '../application-config';
@@ -59,17 +59,15 @@ export class InterceptorsContextCreator extends ContextCreator {
}
public getInterceptorInstance(
metatype: Function | NestInterceptor,
interceptor: Function | NestInterceptor,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): NestInterceptor | null {
const isObject = (metatype as NestInterceptor).intercept;
const isObject = (interceptor as NestInterceptor).intercept;
if (isObject) {
return metatype as NestInterceptor;
return interceptor as NestInterceptor;
}
const instanceWrapper = this.getInstanceByMetatype(
metatype as Type<unknown>,
);
const instanceWrapper = this.getInstanceByMetatype(interceptor);
if (!instanceWrapper) {
return null;
}
@@ -80,8 +78,8 @@ export class InterceptorsContextCreator extends ContextCreator {
return instanceHost && instanceHost.instance;
}
public getInstanceByMetatype(
metatype: Type<unknown>,
public getInstanceByMetatype<T extends Record<string, any> = any>(
metatype: T,
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return;
@@ -91,7 +89,7 @@ export class InterceptorsContextCreator extends ContextCreator {
if (!moduleRef) {
return;
}
return moduleRef.injectables.get(metatype);
return moduleRef.injectables.get(metatype.name as string);
}
public getGlobalMetadata<T extends unknown[]>(

View File

@@ -9,10 +9,9 @@ import {
RouteInfo,
} from '@nestjs/common/interfaces/middleware';
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { iterate } from 'iterare';
import { NestContainer } from '../injector';
import { RoutesMapper } from './routes-mapper';
import { filterMiddleware } from './utils';
import { iterate } from 'iterare';
export class MiddlewareBuilder implements MiddlewareConsumer {
private readonly middlewareCollection = new Set<MiddlewareConfiguration>();
@@ -20,7 +19,6 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
constructor(
private readonly routesMapper: RoutesMapper,
private readonly httpAdapter: HttpServer,
private readonly container: NestContainer,
) {}
public apply(

View File

@@ -3,13 +3,9 @@ import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants';
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { NestContainer } from '../injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { InstanceToken } from '../injector/module';
export class MiddlewareContainer {
private readonly middleware = new Map<
string,
Map<InstanceToken, InstanceWrapper>
>();
private readonly middleware = new Map<string, Map<string, InstanceWrapper>>();
private readonly configurationSets = new Map<
string,
Set<MiddlewareConfiguration>
@@ -19,7 +15,7 @@ export class MiddlewareContainer {
public getMiddlewareCollection(
moduleKey: string,
): Map<InstanceToken, InstanceWrapper> {
): Map<string, InstanceWrapper> {
if (!this.middleware.has(moduleKey)) {
const moduleRef = this.container.getModuleByKey(moduleKey);
this.middleware.set(moduleKey, moduleRef.middlewares);
@@ -40,14 +36,13 @@ export class MiddlewareContainer {
const configurations = configList || [];
const insertMiddleware = <T extends Type<unknown>>(metatype: T) => {
const token = metatype;
const token = metatype.name;
middleware.set(
token,
new InstanceWrapper({
scope: this.getClassScope(metatype),
name: token,
metatype,
token,
name: token,
}),
);
};

View File

@@ -5,6 +5,7 @@ import {
RouteInfo,
} from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { NestMiddleware } from '@nestjs/common/interfaces/middleware/nest-middleware.interface';
import { NestModule } from '@nestjs/common/interfaces/modules/nest-module.interface';
import {
addLeadingSlash,
isUndefined,
@@ -18,7 +19,7 @@ import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { Injector } from '../injector/injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { InstanceToken, Module } from '../injector/module';
import { Module } from '../injector/module';
import { REQUEST_CONTEXT_ID } from '../router/request/request-constants';
import { RouterExceptionFilters } from '../router/router-exception-filters';
import { RouterProxy } from '../router/router-proxy';
@@ -69,29 +70,28 @@ export class MiddlewareModule {
modules: Map<string, Module>,
) {
const moduleEntries = [...modules.entries()];
const loadMiddlewareConfiguration = async ([moduleName, moduleRef]: [
const loadMiddlewareConfiguration = async ([name, module]: [
string,
Module,
]) => {
await this.loadConfiguration(middlewareContainer, moduleRef, moduleName);
await this.resolver.resolveInstances(moduleRef, moduleName);
const instance = module.instance;
await this.loadConfiguration(middlewareContainer, instance, name);
await this.resolver.resolveInstances(module, name);
};
await Promise.all(moduleEntries.map(loadMiddlewareConfiguration));
}
public async loadConfiguration(
middlewareContainer: MiddlewareContainer,
moduleRef: Module,
instance: NestModule,
moduleKey: string,
) {
const { instance } = moduleRef;
if (!instance.configure) {
return;
}
const middlewareBuilder = new MiddlewareBuilder(
this.routesMapper,
this.httpAdapter,
this.container,
);
await instance.configure(middlewareBuilder);
@@ -164,7 +164,7 @@ export class MiddlewareModule {
for (const metatype of middlewareCollection) {
const collection = middlewareContainer.getMiddlewareCollection(moduleKey);
const instanceWrapper = collection.get(metatype);
const instanceWrapper = collection.get(metatype.name);
if (isUndefined(instanceWrapper)) {
throw new RuntimeException();
}
@@ -188,10 +188,10 @@ export class MiddlewareModule {
method: RequestMethod,
path: string,
moduleRef: Module,
collection: Map<InstanceToken, InstanceWrapper>,
collection: Map<string, InstanceWrapper>,
) {
const { instance, metatype } = wrapper;
if (isUndefined(instance?.use)) {
if (isUndefined(instance.use)) {
throw new InvalidMiddlewareException(metatype.name);
}
const router = await applicationRef.createMiddlewareFactory(method);

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