Compare commits

..

5 Commits

Author SHA1 Message Date
Kamil Myśliwiec
b3f85e9c04 test: update module compiler test 2024-03-19 10:52:31 +01:00
Kamil Myśliwiec
c4a905b996 chore: update comment to suggest switching to a different algorithm 2024-03-19 09:37:58 +01:00
Kamil Myśliwiec
094c35cf79 refactor: update types to better reflect dynamic metadata shape 2024-03-19 09:35:43 +01:00
Kamil Mysliwiec
30110d49e0 Update packages/core/injector/compiler.ts
Co-authored-by: Vinicius Lourenço <12551007+H4ad@users.noreply.github.com>
2024-03-19 09:33:04 +01:00
Kamil Myśliwiec
fb72a8ec9f feat(core): introduce different module opaque key factories 2024-03-18 10:12:56 +01:00
198 changed files with 19270 additions and 24378 deletions

View File

@@ -129,6 +129,9 @@ jobs:
- run:
name: Lint
command: npm run lint
- run:
name: Lint commit
command: ./node_modules/.bin/commitlint-circle -c .commitlintrc.json
integration_tests:
working_directory: ~/nest

View File

@@ -38,12 +38,10 @@ body:
required: true
attributes:
label: "Minimum reproduction code"
placeholder: "https://github.com/..."
description: |
An URL to some Git repository/[StackBlitz](https://stackblitz.com/fork/github/nestjs/typescript-starter)/[CodeSandbox](https://codesandbox.io/s/github/nestjs/typescript-starter/tree/master) project that reproduces your issue. [What is a minimum reproduction?](https://jmcdo29.github.io/wtf-is-a-minimum-reproduction)
> [!WARNING]
> We may close this Issue if we don't manage to reproduce the potential bug. [Read this](https://antfu.me/posts/why-reproductions-are-required) to understand why.
An URL to some Git repository/[StackBlitz](https://stackblitz.com/fork/github/nestjs/typescript-starter)/[CodeSandbox](https://codesandbox.io/s/github/nestjs/typescript-starter/tree/master) project that reproduces your issue. [What is a minimum reproduction?](https://jmcdo29.github.io/wtf-is-a-minimum-reproduction)
:warning: **NOTE:** We can close this issue if we don't manage to reproduce your potential bug. [Here](https://antfu.me/posts/why-reproductions-are-required) is why.
placeholder: "https://github.com/..."
- type: textarea
attributes:

4
.gitignore vendored
View File

@@ -1,5 +1,3 @@
packages/*/package-lock.json
# dependencies
node_modules/
@@ -49,4 +47,4 @@ yarn-error.log
build/config\.gypi
.npmrc
pnpm-lock.yaml
pnpm-lock.yaml

121
Readme.md
View File

@@ -57,84 +57,71 @@ 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://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
<td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
<td><a href="https://marblism.com?utm_source=nest" target="_blank"><img src="https://nestjs.com/img/logos/marblism-logo.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="170" valign="middle" /></a></td>
<td><a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/logos/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" 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="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
<td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
</tr>
<tr>
<td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
<td><a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/logos/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/logos/ridi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/logos/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
<a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/ridi-logo.svg" width="105" valign="middle" /></a></td><td>
<a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/movavi-logo.svg" width="105" valign="middle" /></a></td>
</tr><tr><td>
<a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr></table>
#### Silver Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
<td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://bloodycase.com/?promocode=NEST" target="_blank"><img src="https://nestjs.com/img/logos/bloodycase-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
</tr>
</table>
#### Sponsors
<table>
<tr>
<td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" 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/logos/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/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/logos/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/logos/julienferand-logo.jpeg" width="55" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.slotsup.com/" target="_blank"><img src="https://nestjs.com/img/logos/slotsup-logo.png" width="60" valign="middle" /></a></td>
</tr>
</table>
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<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></tr><tr>
<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><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://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://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -45,11 +45,6 @@ class TestController {
overviewById() {
return RETURN_VALUE;
}
@Get('multiple/exclude')
multipleExclude() {
return RETURN_VALUE;
}
}
@Module({
@@ -64,7 +59,6 @@ class TestModule {
path: 'middleware',
method: RequestMethod.POST,
})
.exclude('multiple/exclude')
.forRoutes('*');
}
}
@@ -116,12 +110,6 @@ describe('Exclude middleware', () => {
.expect(200, RETURN_VALUE);
});
it(`should exclude "/multiple/exclude" endpoint`, () => {
return request(app.getHttpServer())
.get('/multiple/exclude')
.expect(200, RETURN_VALUE);
});
afterEach(async () => {
await app.close();
});

View File

@@ -45,10 +45,6 @@ describe('Hello world (express instance)', () => {
});
});
it('/HEAD should respond to with a 200', () => {
return request(server).head('/hello').expect(200);
});
afterEach(async () => {
await app.close();
});

View File

@@ -87,15 +87,6 @@ describe('Hello world (fastify adapter)', () => {
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it('/HEAD should respond to with a 200', () => {
return app
.inject({
method: 'HEAD',
url: '/hello',
})
.then(({ statusCode }) => expect(statusCode).to.be.eq(200));
});
afterEach(async () => {
await app.close();
});

View File

@@ -4,15 +4,11 @@ import { INestApplication } from '@nestjs/common';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import { fail } from 'assert';
import { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import { expect } from 'chai';
import { join } from 'path';
import * as sinon from 'sinon';
import * as request from 'supertest';
import { GrpcController } from '../src/grpc/grpc.controller';
use(chaiAsPromised);
describe('GRPC transport', () => {
let server;
let app: INestApplication;
@@ -36,7 +32,6 @@ describe('GRPC transport', () => {
],
},
});
// Start gRPC microservice
await app.startAllMicroservices();
await app.init();
@@ -154,50 +149,6 @@ describe('GRPC transport', () => {
expect(receivedIds).to.deep.equal(expectedIds);
});
describe('streaming calls that error', () => {
// We want to assert that the application does not crash when an error is encountered with an unhandledRejection
// the best way to do that is to listen for the unhandledRejection event and fail the test if it is called
let processSpy: sinon.SinonSpy;
beforeEach(() => {
processSpy = sinon.spy();
process.on('unhandledRejection', processSpy);
});
afterEach(() => {
process.off('unhandledRejection', processSpy);
});
it('should not crash when replying with an error', async () => {
const call = new Promise<void>((resolve, reject) => {
const stream = client.streamDivide({
data: [{ dividend: 1, divisor: 0 }],
});
stream.on('data', () => {
fail('Stream should not have emitted any data');
});
stream.on('error', err => {
if (err.code !== GRPC.status.CANCELLED) {
reject(err);
}
});
stream.on('end', () => {
resolve();
});
});
await expect(call).to.eventually.be.rejectedWith(
'3 INVALID_ARGUMENT: dividing by 0 is not possible',
);
// if this fails the application has crashed
expect(processSpy.called).to.be.false;
});
});
after(async () => {
await app.close();
});

View File

@@ -10,7 +10,7 @@ import {
RpcException,
} from '@nestjs/microservices';
import { join } from 'path';
import { Observable, of, catchError, from, mergeMap } from 'rxjs';
import { Observable, of, catchError } from 'rxjs';
class ErrorHandlingProxy extends ClientGrpcProxy {
serializeError(err) {
@@ -107,17 +107,6 @@ export class GrpcController {
};
}
// contrived example meant to show when an error is encountered, like dividing by zero, the
// application does not crash and the error is returned appropriately to the client
@GrpcMethod('Math', 'StreamDivide')
streamDivide({
data,
}: {
data: { dividend: number; divisor: number }[];
}): Observable<any> {
return from(data).pipe(mergeMap(request => this.divide(request)));
}
@GrpcMethod('Math2')
async sum2({ data }: { data: number[] }): Promise<any> {
return of({

View File

@@ -7,9 +7,7 @@ service Math {
rpc SumStream(stream RequestSum) returns(stream SumResult);
rpc SumStreamPass(stream RequestSum) returns(stream SumResult);
rpc Divide (RequestDivide) returns (DivideResult);
rpc StreamLargeMessages(Empty) returns (stream BackpressureData);
/* Given a series of dividend and divisor, stream back the division results for each */
rpc StreamDivide (StreamDivideRequest) returns (stream StreamDivideResponse);
rpc StreamLargeMessages(Empty) returns (stream BackpressureData) {}
}
message BackpressureData {
@@ -35,11 +33,3 @@ message RequestDivide {
message DivideResult {
int32 result = 1;
}
message StreamDivideRequest {
repeated RequestDivide data = 1;
}
message StreamDivideResponse {
DivideResult data = 1;
}

View File

@@ -13,7 +13,7 @@ import { Observable } from 'rxjs';
import { SumDto } from './dto/sum.dto';
/**
* The following function explicitly sends messages to the key representing the partition.
* The following function explicity sends messages to the key representing the partition.
*/
const explicitPartitioner = () => {
return ({ message }: PartitionerArgs) => {

View File

@@ -573,7 +573,7 @@ allow_anonymous true
# not given then the access is read/write. <topic> can contain the + or #
# wildcards as in subscriptions.
#
# The "deny" option can used to explicitly deny access to a topic that would
# The "deny" option can used to explicity deny access to a topic that would
# otherwise be granted by a broader read/write/readwrite statement. Any "deny"
# topics are handled before topics that grant read/write access.
#

View File

@@ -130,18 +130,6 @@ describe('Global prefix', () => {
.expect(200, { '0': 'params', tenantId: 'test' });
});
it(`should execute middleware only once`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/'] });
server = app.getHttpServer();
await app.init();
await request(server)
.get('/')
.expect(200, 'Extras: Data attached in middleware, Count: 1');
await request(server).get('/api/count').expect(200, '2');
});
afterEach(async () => {
await app.close();
});

View File

@@ -26,14 +26,4 @@ export class AppController {
postTest(): string {
return 'test';
}
@Get()
getHome(@Req() req) {
return 'Extras: ' + req.extras?.data + ', Count: ' + req.count;
}
@Get('count')
getCount(@Req() req) {
return req.count;
}
}

View File

@@ -8,7 +8,6 @@ export const MIDDLEWARE_PARAM_VALUE = 'middleware_param';
controllers: [AppController],
})
export class AppModule {
private count = 0;
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.end(MIDDLEWARE_VALUE))
@@ -28,12 +27,6 @@ export class AppModule {
req.middlewareParams = req.params;
next();
})
.forRoutes({ path: '*', method: RequestMethod.GET })
.apply((req, res, next) => {
this.count += 1;
req.count = this.count;
next();
})
.forRoutes('*');
.forRoutes({ path: '*', method: RequestMethod.GET });
}
}

View File

@@ -1,98 +0,0 @@
import { INestApplication, VersioningType } from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
/**
* `.enableVersioning()` uses `VersioningType.URI` type by default
* Regression test for #13496
* @see [Versioning](https://docs.nestjs.com/techniques/versioning)
*/
describe('Default Versioning behavior', () => {
// ======================================================================== //
describe('Express', () => {
let app: INestApplication;
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
app.enableVersioning();
await app.init();
});
describe('GET /', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1')
.expect(200)
.expect('Hello World V1!');
});
it('No Version', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /neutral', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/neutral')
.expect(200)
.expect('Neutral');
});
});
after(async () => {
await app.close();
});
});
// ======================================================================== //
describe('Fastify', () => {
let app: INestApplication;
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
app.enableVersioning();
await app.init();
await app.getHttpAdapter().getInstance().ready();
});
describe('GET /', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1')
.expect(200)
.expect('Hello World V1!');
});
it('No Version', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /neutral', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/neutral')
.expect(200)
.expect('Neutral');
});
});
after(async () => {
await app.close();
});
});
});

View File

@@ -3,5 +3,5 @@
"packages": [
"packages/*"
],
"version": "10.4.7"
"version": "10.3.3"
}

18366
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "10.4.5",
"version": "10.2.7",
"description": "Modern, fast, powerful node.js web framework",
"homepage": "https://nestjs.com",
"repository": {
@@ -63,75 +63,76 @@
"class-validator": "0.14.1",
"cli-color": "2.0.4",
"cors": "2.8.5",
"express": "4.21.1",
"fast-json-stringify": "6.0.0",
"express": "4.18.3",
"fast-json-stringify": "5.13.0",
"fast-safe-stringify": "2.1.1",
"graphql-subscriptions": "2.0.0",
"iterare": "1.2.1",
"object-hash": "3.0.0",
"path-to-regexp": "3.2.0",
"reflect-metadata": "0.2.2",
"reflect-metadata": "0.2.1",
"rxjs": "7.8.1",
"socket.io": "4.8.0",
"tslib": "2.7.0",
"socket.io": "4.7.5",
"tslib": "2.6.2",
"uid": "2.0.2",
"uuid": "10.0.0"
"uuid": "9.0.1"
},
"devDependencies": {
"@apollo/server": "4.11.0",
"@apollo/server": "4.10.1",
"@codechecks/client": "0.1.12",
"@commitlint/cli": "19.5.0",
"@commitlint/config-angular": "19.5.0",
"@commitlint/cli": "19.1.0",
"@commitlint/config-angular": "19.1.0",
"@fastify/cors": "9.0.1",
"@fastify/formbody": "7.4.0",
"@fastify/middie": "8.3.1",
"@fastify/multipart": "8.3.0",
"@fastify/static": "7.0.4",
"@fastify/view": "9.1.0",
"@grpc/grpc-js": "1.11.1",
"@grpc/proto-loader": "0.7.13",
"@nestjs/apollo": "12.2.0",
"@nestjs/graphql": "12.2.0",
"@nestjs/mongoose": "10.0.10",
"@fastify/middie": "8.3.0",
"@fastify/multipart": "8.1.0",
"@fastify/static": "7.0.1",
"@fastify/view": "9.0.0",
"@grpc/grpc-js": "1.10.2",
"@grpc/proto-loader": "0.7.10",
"@nestjs/apollo": "12.1.0",
"@nestjs/graphql": "12.1.1",
"@nestjs/mongoose": "10.0.4",
"@nestjs/typeorm": "10.0.2",
"@types/amqplib": "0.10.5",
"@types/bytes": "3.1.4",
"@types/chai": "4.3.16",
"@types/chai": "4.3.11",
"@types/chai-as-promised": "7.1.8",
"@types/cors": "2.8.17",
"@types/express": "4.17.21",
"@types/gulp": "4.0.17",
"@types/http-errors": "2.0.4",
"@types/mocha": "10.0.8",
"@types/node": "22.5.5",
"@types/mocha": "10.0.6",
"@types/node": "20.11.27",
"@types/sinon": "17.0.3",
"@types/supertest": "2.0.16",
"@types/ws": "8.5.12",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "7.2.0",
"@typescript-eslint/parser": "7.2.0",
"amqp-connection-manager": "4.1.14",
"amqplib": "0.10.4",
"artillery": "2.0.20",
"body-parser": "1.20.3",
"amqplib": "0.10.3",
"artillery": "2.0.7",
"body-parser": "1.20.2",
"bytes": "3.1.2",
"cache-manager": "5.7.6",
"cache-manager": "5.4.0",
"cache-manager-redis-store": "3.0.1",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"chai": "4.4.1",
"chai-as-promised": "7.1.1",
"clang-format": "1.8.0",
"concurrently": "9.0.1",
"conventional-changelog": "6.0.0",
"core-js": "3.38.1",
"commitlint-circle": "1.0.0",
"concurrently": "8.2.2",
"conventional-changelog": "5.1.0",
"core-js": "3.36.0",
"coveralls": "3.1.1",
"delete-empty": "3.0.0",
"engine.io-client": "6.6.1",
"eslint": "8.57.1",
"engine.io-client": "6.5.3",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1",
"eventsource": "2.0.2",
"fancy-log": "2.0.0",
"fastify": "4.28.1",
"graphql": "16.9.0",
"fastify": "4.26.2",
"graphql": "16.8.1",
"graphql-tools": "9.0.1",
"gulp": "4.0.2",
"gulp-clang-format": "1.0.27",
@@ -140,38 +141,39 @@
"gulp-typescript": "5.0.1",
"gulp-watch": "5.0.1",
"http-errors": "2.0.0",
"husky": "9.1.5",
"husky": "9.0.11",
"imports-loader": "5.0.0",
"ioredis": "5.4.1",
"ioredis": "5.3.2",
"json-loader": "0.5.7",
"kafkajs": "2.2.4",
"lerna": "2.11.0",
"lerna-changelog": "2.2.0",
"light-my-request": "6.1.0",
"lint-staged": "15.2.10",
"light-my-request": "5.12.0",
"lint-staged": "15.2.2",
"markdown-table": "2.0.0",
"mocha": "10.7.3",
"mongoose": "8.6.2",
"mqtt": "5.6.0",
"merge-graphql-schemas": "1.7.8",
"mocha": "10.3.0",
"mongoose": "8.2.1",
"mqtt": "5.4.0",
"multer": "1.4.4",
"mysql2": "3.11.3",
"nats": "2.28.2",
"nodemon": "3.1.5",
"mysql2": "3.9.2",
"nats": "2.19.0",
"nodemon": "3.1.0",
"nyc": "14.1.1",
"prettier": "3.3.3",
"redis": "4.7.0",
"prettier": "3.2.5",
"redis": "4.6.13",
"rxjs-compat": "6.6.7",
"sinon": "19.0.2",
"sinon": "17.0.1",
"sinon-chai": "3.7.0",
"socket.io-client": "4.8.0",
"socket.io-client": "4.7.5",
"subscriptions-transport-ws": "0.11.0",
"supertest": "7.0.0",
"ts-morph": "23.0.0",
"supertest": "6.3.3",
"ts-morph": "22.0.0",
"ts-node": "10.9.2",
"typeorm": "0.3.20",
"typescript": "5.6.2",
"typescript": "5.4.2",
"wrk": "1.2.1",
"ws": "8.18.0"
"ws": "8.16.0"
},
"engines": {
"node": ">= 16"

View File

@@ -25,12 +25,12 @@
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also provides compatibility with a wide range of other libraries, like <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad of third-party plugins which are available.</p>
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
## Philosophy
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a>, and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a>, which improve developer productivity and enable the construction of fast, testable, and extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers, and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, and loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a> which improve developer productivity and enable the construction of fast, testable, extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
## Getting started
@@ -57,84 +57,71 @@ 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://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
<td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
<td><a href="https://marblism.com?utm_source=nest" target="_blank"><img src="https://nestjs.com/img/logos/marblism-logo.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="170" valign="middle" /></a></td>
<td><a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/logos/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" 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="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
<td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
</tr>
<tr>
<td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
<td><a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/logos/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/logos/ridi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/logos/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
<a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/ridi-logo.svg" width="105" valign="middle" /></a></td><td>
<a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/movavi-logo.svg" width="105" valign="middle" /></a></td>
</tr><tr><td>
<a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr></table>
#### Silver Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
<td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://bloodycase.com/?promocode=NEST" target="_blank"><img src="https://nestjs.com/img/logos/bloodycase-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-logo.svg" width="150" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
</tr>
</table>
#### Sponsors
<table>
<tr>
<td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" 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/logos/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/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/logos/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/logos/julienferand-logo.jpeg" width="55" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.slotsup.com/" target="_blank"><img src="https://nestjs.com/img/logos/slotsup-logo.png" width="60" valign="middle" /></a></td>
</tr>
</table>
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<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></tr><tr>
<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><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://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://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -1,5 +1,4 @@
import {
PARAMTYPES_METADATA,
PROPERTY_DEPS_METADATA,
SELF_DECLARED_DEPS_METADATA,
} from '../../constants';
@@ -37,13 +36,13 @@ import { isUndefined } from '../../utils/shared.utils';
export function Inject<T = any>(
token?: T,
): PropertyDecorator & ParameterDecorator {
const injectCallHasArguments = arguments.length > 0;
return (target: object, key: string | symbol | undefined, index?: number) => {
let type = token || Reflect.getMetadata('design:type', target, key);
// Try to infer the token in a constructor-based injection
if (!type && !injectCallHasArguments) {
type = Reflect.getMetadata(PARAMTYPES_METADATA, target, key)?.[index];
const type = token || Reflect.getMetadata('design:type', target, key);
if (!type) {
throw new Error(`Token is undefined at index: ${index}. This often occurs due to circular dependencies.
Ensure there are no circular dependencies in your files or barrel files.
For more details, refer to https://trilon.io/blog/avoiding-circular-dependencies-in-nestjs.`);
}
if (!isUndefined(index)) {

View File

@@ -15,7 +15,7 @@ export type CustomDecorator<TKey = string> = MethodDecorator &
*
* Example: `@SetMetadata('roles', ['admin'])`
*
* @see [Reflection](https://docs.nestjs.com/fundamentals/execution-context#reflection-and-metadata)
* @see [Reflection](https://docs.nestjs.com/guards#reflection)
*
* @publicApi
*/

View File

@@ -508,53 +508,6 @@ export function Body(
);
}
/**
* Route handler parameter decorator. Extracts the `rawBody` Buffer
* property from the `req` object and populates the decorated parameter with that value.
*
* For example:
* ```typescript
* async create(@RawBody() rawBody: Buffer | undefined)
* ```
*
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
* @see [Raw body](https://docs.nestjs.com/faq/raw-body)
*
* @publicApi
*/
export function RawBody(): ParameterDecorator;
/**
* Route handler parameter decorator. Extracts the `rawBody` Buffer
* property from the `req` object and populates the decorated parameter with that value.
* Also applies pipes to the bound rawBody parameter.
*
* For example:
* ```typescript
* async create(@RawBody(new ValidationPipe()) rawBody: Buffer)
* ```
*
* @param pipes one or more pipes - either instances or classes - to apply to
* the bound body parameter.
*
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
* @see [Raw body](https://docs.nestjs.com/faq/raw-body)
* @see [Working with pipes](https://docs.nestjs.com/custom-decorators#working-with-pipes)
*
* @publicApi
*/
export function RawBody(
...pipes: (
| Type<PipeTransform<Buffer | undefined>>
| PipeTransform<Buffer | undefined>
)[]
): ParameterDecorator {
return createPipesRouteParamDecorator(RouteParamtypes.RAW_BODY)(
undefined,
...pipes,
);
}
/**
* Route handler parameter decorator. Extracts the `params`
* property from the `req` object and populates the decorated

View File

@@ -1,15 +1,14 @@
export enum RouteParamtypes {
REQUEST = 0,
RESPONSE = 1,
NEXT = 2,
BODY = 3,
QUERY = 4,
PARAM = 5,
HEADERS = 6,
SESSION = 7,
FILE = 8,
FILES = 9,
HOST = 10,
IP = 11,
RAW_BODY = 12,
REQUEST,
RESPONSE,
NEXT,
BODY,
QUERY,
PARAM,
HEADERS,
SESSION,
FILE,
FILES,
HOST,
IP,
}

View File

@@ -1,22 +1,10 @@
/**
* Options for `StreamableFile`
*
* @see [Streaming files](https://docs.nestjs.com/techniques/streaming-files)
*
* @publicApi
*/
export interface StreamableFileOptions {
/**
* The value that will be used for the `Content-Type` response header.
* @default `"application/octet-stream"`
*/
type?: string;
/**
* The value that will be used for the `Content-Disposition` response header.
*/
disposition?: string;
/**
* The value that will be used for the `Content-Length` response header.
*/
length?: number;
}

View File

@@ -7,7 +7,7 @@ import { MiddlewareConsumer } from './middleware-consumer.interface';
*/
export interface MiddlewareConfigProxy {
/**
* Routes to exclude from the current middleware.
* Excludes routes from the currently processed middleware.
*
* @param {(string | RouteInfo)[]} routes
* @returns {MiddlewareConfigProxy}
@@ -15,9 +15,8 @@ export interface MiddlewareConfigProxy {
exclude(...routes: (string | RouteInfo)[]): MiddlewareConfigProxy;
/**
* Attaches either routes or controllers to the current middleware.
* If you pass a controller class, Nest will attach the current middleware to every path
* defined within it.
* Attaches passed either routes or controllers to the currently configured middleware.
* If you pass a class, Nest would attach middleware to every path defined within this controller.
*
* @param {(string | Type | RouteInfo)[]} routes
* @returns {MiddlewareConsumer}

View File

@@ -44,4 +44,13 @@ export class NestApplicationContextOptions {
* @default false
*/
snapshot?: boolean;
/**
* Determines what algorithm use to generate module ids.
* When set to `deep-hash`, the module id is generated based on the serialized module definition.
* When set to `reference`, each module obtains a unique id based on its reference.
*
* @default 'reference'
*/
moduleIdGeneratorAlgorithm?: 'deep-hash' | 'reference';
}

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "10.4.7",
"version": "10.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"homepage": "https://nestjs.com",
@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"iterare": "1.2.1",
"tslib": "2.7.0",
"tslib": "2.6.2",
"uid": "2.0.2"
},
"peerDependencies": {

View File

@@ -1,10 +1,9 @@
export * from './default-value.pipe';
export * from './file';
export * from './parse-array.pipe';
export * from './parse-bool.pipe';
export * from './parse-date.pipe';
export * from './parse-enum.pipe';
export * from './parse-float.pipe';
export * from './parse-int.pipe';
export * from './parse-float.pipe';
export * from './parse-enum.pipe';
export * from './parse-uuid.pipe';
export * from './validation.pipe';
export * from './file';

View File

@@ -7,7 +7,7 @@ import {
PipeTransform,
} from '../interfaces/features/pipe-transform.interface';
import { HttpErrorByCode } from '../utils/http-error-by-code.util';
import { isNil, isString, isUndefined } from '../utils/shared.utils';
import { isNil, isUndefined, isString } from '../utils/shared.utils';
import { ValidationPipe, ValidationPipeOptions } from './validation.pipe';
const VALIDATION_ERROR_MESSAGE = 'Validation failed (parsable array expected)';
@@ -21,26 +21,9 @@ export interface ParseArrayOptions
ValidationPipeOptions,
'transform' | 'validateCustomDecorators' | 'exceptionFactory'
> {
/**
* Type for items to be converted into
*/
items?: Type<unknown>;
/**
* Items separator to split string by
* @default ','
*/
separator?: string;
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message or object
* @returns The exception object
*/
exceptionFactory?: (error: any) => any;
}

View File

@@ -15,21 +15,8 @@ import { isNil } from '../utils/shared.utils';
* @publicApi
*/
export interface ParseBoolPipeOptions {
/**
* The HTTP status code to be used in the response when the validation fails.
*/
errorHttpStatusCode?: ErrorHttpStatusCode;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message
* @returns The exception object
*/
exceptionFactory?: (error: string) => any;
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
}

View File

@@ -1,74 +0,0 @@
import { Injectable } from '../decorators/core/injectable.decorator';
import { HttpStatus } from '../enums/http-status.enum';
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
import {
ErrorHttpStatusCode,
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { isNil } from '../utils/shared.utils';
export interface ParseDatePipeOptions {
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
/**
* Default value for the date
*/
default?: () => Date;
/**
* The HTTP status code to be used in the response when the validation fails.
*/
errorHttpStatusCode?: ErrorHttpStatusCode;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message
* @returns The exception object
*/
exceptionFactory?: (error: string) => any;
}
@Injectable()
export class ParseDatePipe
implements PipeTransform<string | number | undefined | null>
{
protected exceptionFactory: (error: string) => any;
constructor(private readonly options: ParseDatePipeOptions = {}) {
const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } =
options;
this.exceptionFactory =
exceptionFactory ||
(error => new HttpErrorByCode[errorHttpStatusCode](error));
}
/**
* Method that accesses and performs optional transformation on argument for
* in-flight requests.
*
* @param value currently processed route argument
* @param metadata contains metadata about the currently processed route argument
*/
transform(value: string | number | undefined | null): Date {
if (this.options.optional && isNil(value)) {
return this.options.default
? this.options.default()
: (value as undefined | null);
}
if (!value) {
throw this.exceptionFactory('Validation failed (no Date provided)');
}
const transformedValue = new Date(value);
if (isNaN(transformedValue.getTime())) {
throw this.exceptionFactory('Validation failed (invalid date format)');
}
return transformedValue;
}
}

View File

@@ -11,21 +11,8 @@ import { isNil } from '../utils/shared.utils';
* @publicApi
*/
export interface ParseEnumPipeOptions {
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
/**
* The HTTP status code to be used in the response when the validation fails.
*/
errorHttpStatusCode?: ErrorHttpStatusCode;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message
* @returns The exception object
*/
exceptionFactory?: (error: string) => any;
}

View File

@@ -11,21 +11,8 @@ import { isNil } from '../utils/shared.utils';
* @publicApi
*/
export interface ParseFloatPipeOptions {
/**
* The HTTP status code to be used in the response when the validation fails.
*/
errorHttpStatusCode?: ErrorHttpStatusCode;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message
* @returns The exception object
*/
exceptionFactory?: (error: string) => any;
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
}

View File

@@ -15,21 +15,8 @@ import { isNil } from '../utils/shared.utils';
* @publicApi
*/
export interface ParseIntPipeOptions {
/**
* The HTTP status code to be used in the response when the validation fails.
*/
errorHttpStatusCode?: ErrorHttpStatusCode;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message
* @returns The exception object
*/
exceptionFactory?: (error: string) => any;
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
}

View File

@@ -15,25 +15,9 @@ import { isNil, isString } from '../utils/shared.utils';
* @publicApi
*/
export interface ParseUUIDPipeOptions {
/**
* UUID version to validate
*/
version?: '3' | '4' | '5' | '7';
/**
* The HTTP status code to be used in the response when the validation fails.
*/
version?: '3' | '4' | '5';
errorHttpStatusCode?: ErrorHttpStatusCode;
/**
* A factory function that returns an exception object to be thrown
* if validation fails.
* @param error Error message
* @returns The exception object
*/
exceptionFactory?: (errors: string) => any;
/**
* If true, the pipe will return null or undefined if the value is not provided
* @default false
*/
optional?: boolean;
}
@@ -50,10 +34,9 @@ export class ParseUUIDPipe implements PipeTransform<string> {
3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
7: /^[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
};
private readonly version: '3' | '4' | '5' | '7';
private readonly version: '3' | '4' | '5';
protected exceptionFactory: (errors: string) => any;
constructor(@Optional() protected readonly options?: ParseUUIDPipeOptions) {

View File

@@ -22,4 +22,14 @@ describe('@Inject', () => {
];
expect(metadata).to.be.eql(expectedMetadata);
});
it('should throw an error when token is undefined', () => {
const defineInvalidClass = () => {
class Test {
constructor(@Inject(undefined) invalidParam) {}
}
};
expect(defineInvalidClass).to.throw(/^Token is undefined/);
});
});

View File

@@ -1,71 +0,0 @@
import { expect } from 'chai';
import { BadRequestException } from '../../exceptions';
import { ParseDatePipe } from '../../pipes/parse-date.pipe';
describe('ParseDatePipe', () => {
let target: ParseDatePipe;
beforeEach(() => {
target = new ParseDatePipe();
});
describe('transform', () => {
describe('when validation passes', () => {
it('should return a valid date object', () => {
const date = new Date().toISOString();
const transformedDate = target.transform(date);
expect(transformedDate).to.be.instanceOf(Date);
expect(transformedDate.toISOString()).to.equal(date);
const asNumber = transformedDate.getTime();
const transformedNumber = target.transform(asNumber);
expect(transformedNumber).to.be.instanceOf(Date);
expect(transformedNumber.getTime()).to.equal(asNumber);
});
it('should not throw an error if the value is undefined/null and optional is true', () => {
const target = new ParseDatePipe({ optional: true });
const value = target.transform(undefined);
expect(value).to.equal(undefined);
});
});
describe('when default value is provided', () => {
it('should return the default value if the value is undefined/null', () => {
const defaultValue = new Date();
const target = new ParseDatePipe({
optional: true,
default: () => defaultValue,
});
const value = target.transform(undefined);
expect(value).to.equal(defaultValue);
});
});
describe('when validation fails', () => {
it('should throw an error', () => {
try {
target.transform('123abc');
expect.fail();
} catch (error) {
expect(error).to.be.instanceOf(BadRequestException);
expect(error.message).to.equal(
'Validation failed (invalid date format)',
);
}
});
});
describe('when empty value', () => {
it('should throw an error', () => {
try {
target.transform('');
expect.fail();
} catch (error) {
expect(error).to.be.instanceOf(BadRequestException);
expect(error.message).to.equal(
'Validation failed (no Date provided)',
);
}
});
});
});
});

View File

@@ -478,56 +478,6 @@ describe('ValidationPipe', () => {
});
});
describe('option: "validateCustomDecorators" when metadata.type is not `body`', () => {
describe('when is set to `true`', () => {
it('should transform and validate', async () => {
const target = new ValidationPipe({
validateCustomDecorators: true,
});
const metadata: ArgumentMetadata = {
type: 'custom',
metatype: TestModel,
data: '',
};
const testObj = { prop1: 'value1', prop2: 'value2' };
expect(await target.transform(testObj, metadata)).to.not.be.undefined;
});
});
describe('when is set to `false`', () => {
it('should throw an error', async () => {
const target = new ValidationPipe({
validateCustomDecorators: false,
});
const metadata: ArgumentMetadata = {
type: 'custom',
metatype: TestModel,
data: '',
};
const objNotFollowingTestModel = { prop1: undefined, prop2: 'value2' };
expect(await target.transform(objNotFollowingTestModel, metadata)).to
.not.be.undefined;
});
});
describe('when is not supplied', () => {
it('should transform and validate', async () => {
const target = new ValidationPipe({});
const metadata: ArgumentMetadata = {
type: 'custom',
metatype: TestModel,
data: '',
};
const testObj = { prop1: 'value1', prop2: 'value2' };
expect(await target.transform(testObj, metadata)).to.not.be.undefined;
});
});
});
describe('option: "errorHttpStatusCode"', () => {
describe('when validation fails', () => {
beforeEach(() => {

View File

@@ -1,7 +1,7 @@
import { Logger } from '../services/logger.service';
const MISSING_REQUIRED_DEPENDENCY = (name: string, reason: string) =>
`The "${name}" package is missing. Please, make sure to install it to take advantage of ${reason}.`;
`The "${name}" package is missing. Please, make sure to install this library ($ npm install ${name}) to take advantage of ${reason}.`;
const logger = new Logger('PackageLoader');

View File

@@ -25,12 +25,12 @@
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also provides compatibility with a wide range of other libraries, like <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad of third-party plugins which are available.</p>
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
## Philosophy
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a>, and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a>, which improve developer productivity and enable the construction of fast, testable, and extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers, and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, and loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a> which improve developer productivity and enable the construction of fast, testable, extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
## Getting started
@@ -57,84 +57,71 @@ 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://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
<td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
<td><a href="https://marblism.com?utm_source=nest" target="_blank"><img src="https://nestjs.com/img/logos/marblism-logo.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="170" valign="middle" /></a></td>
<td><a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/logos/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" 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="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
<td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
</tr>
<tr>
<td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
<td><a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/logos/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/logos/ridi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/logos/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
<a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/ridi-logo.svg" width="105" valign="middle" /></a></td><td>
<a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/movavi-logo.svg" width="105" valign="middle" /></a></td>
</tr><tr><td>
<a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr></table>
#### Silver Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
<td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://bloodycase.com/?promocode=NEST" target="_blank"><img src="https://nestjs.com/img/logos/bloodycase-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-logo.svg" width="150" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
</tr>
</table>
#### Sponsors
<table>
<tr>
<td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" 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/logos/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/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/logos/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/logos/julienferand-logo.jpeg" width="55" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.slotsup.com/" target="_blank"><img src="https://nestjs.com/img/logos/slotsup-logo.png" width="60" valign="middle" /></a></td>
</tr>
</table>
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<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></tr><tr>
<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><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://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://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -146,7 +146,7 @@ export class DiscoverableMetaHostCollection {
// But since calling `wrapper.instance` could degrade overall performance
// we must defer it as much we can.
instanceWrapper.metatype || instanceWrapper.inject
? (instanceWrapper.instance?.constructor ?? instanceWrapper.metatype)
? instanceWrapper.instance?.constructor ?? instanceWrapper.metatype
: instanceWrapper.metatype,
);
}

View File

@@ -9,7 +9,7 @@ export class ExceptionsZone {
public static run(
callback: () => void,
teardown: (err: any) => void = DEFAULT_TEARDOWN,
autoFlushLogs: boolean,
autoFlushLogs?: boolean,
) {
try {
callback();
@@ -25,7 +25,7 @@ export class ExceptionsZone {
public static async asyncRun(
callback: () => Promise<void>,
teardown: (err: any) => void = DEFAULT_TEARDOWN,
autoFlushLogs: boolean,
autoFlushLogs?: boolean,
) {
try {
await callback();

View File

@@ -89,11 +89,15 @@ export class ContextUtils {
instance?: object,
callback?: Function,
): (args: unknown[]) => ExecutionContextHost {
const type = instance && (instance.constructor as Type<unknown>);
return (args: unknown[]) => {
const ctx = new ExecutionContextHost(args, type, callback);
const contextFactory = (args: unknown[]) => {
const ctx = new ExecutionContextHost(
args,
instance && (instance.constructor as Type<unknown>),
callback,
);
ctx.setType(contextType);
return ctx;
};
return contextFactory;
}
}

View File

@@ -3,7 +3,7 @@ import {
ForwardReference,
Type,
} from '@nestjs/common/interfaces';
import { ModuleTokenFactory } from './module-token-factory';
import { ModuleOpaqueKeyFactory } from './opaque-key-factory/interfaces/module-opaque-key-factory.interface';
export interface ModuleFactory {
type: Type<any>;
@@ -12,36 +12,59 @@ export interface ModuleFactory {
}
export class ModuleCompiler {
constructor(private readonly moduleTokenFactory = new ModuleTokenFactory()) {}
constructor(
private readonly _moduleOpaqueKeyFactory: ModuleOpaqueKeyFactory,
) {}
get moduleOpaqueKeyFactory(): ModuleOpaqueKeyFactory {
return this._moduleOpaqueKeyFactory;
}
public async compile(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
moduleClsOrDynamic:
| Type
| DynamicModule
| ForwardReference
| Promise<DynamicModule>,
): Promise<ModuleFactory> {
const { type, dynamicMetadata } = this.extractMetadata(await metatype);
const token = this.moduleTokenFactory.create(type, dynamicMetadata);
moduleClsOrDynamic = await moduleClsOrDynamic;
const { type, dynamicMetadata } = this.extractMetadata(moduleClsOrDynamic);
const token = dynamicMetadata
? this._moduleOpaqueKeyFactory.createForDynamic(
type,
dynamicMetadata,
moduleClsOrDynamic as DynamicModule | ForwardReference,
)
: this._moduleOpaqueKeyFactory.createForStatic(
type,
moduleClsOrDynamic as Type,
);
return { type, dynamicMetadata, token };
}
public extractMetadata(
metatype: Type<any> | ForwardReference | DynamicModule,
moduleClsOrDynamic: Type | ForwardReference | DynamicModule,
): {
type: Type<any>;
dynamicMetadata?: Partial<DynamicModule> | undefined;
type: Type;
dynamicMetadata: Omit<DynamicModule, 'module'> | undefined;
} {
if (!this.isDynamicModule(metatype)) {
if (!this.isDynamicModule(moduleClsOrDynamic)) {
return {
type: (metatype as ForwardReference)?.forwardRef
? (metatype as ForwardReference).forwardRef()
: metatype,
type: (moduleClsOrDynamic as ForwardReference)?.forwardRef
? (moduleClsOrDynamic as ForwardReference).forwardRef()
: moduleClsOrDynamic,
dynamicMetadata: undefined,
};
}
const { module: type, ...dynamicMetadata } = metatype;
const { module: type, ...dynamicMetadata } = moduleClsOrDynamic;
return { type, dynamicMetadata };
}
public isDynamicModule(
module: Type<any> | DynamicModule | ForwardReference,
): module is DynamicModule {
return !!(module as DynamicModule).module;
moduleClsOrDynamic: Type | DynamicModule | ForwardReference,
): moduleClsOrDynamic is DynamicModule {
return !!(moduleClsOrDynamic as DynamicModule).module;
}
}

View File

@@ -4,6 +4,7 @@ import {
GLOBAL_MODULE_METADATA,
} from '@nestjs/common/constants';
import { Injectable, Type } from '@nestjs/common/interfaces';
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
import { ApplicationConfig } from '../application-config';
import { DiscoverableMetaHostCollection } from '../discovery/discoverable-meta-host-collection';
import {
@@ -19,16 +20,16 @@ import { ContextId } from './instance-wrapper';
import { InternalCoreModule } from './internal-core-module/internal-core-module';
import { InternalProvidersStorage } from './internal-providers-storage';
import { Module } from './module';
import { ModuleTokenFactory } from './module-token-factory';
import { ModulesContainer } from './modules-container';
import { ByReferenceModuleOpaqueKeyFactory } from './opaque-key-factory/by-reference-module-opaque-key-factory';
import { DeepHashedModuleOpaqueKeyFactory } from './opaque-key-factory/deep-hashed-module-opaque-key-factory';
import { ModuleOpaqueKeyFactory } from './opaque-key-factory/interfaces/module-opaque-key-factory.interface';
type ModuleMetatype = Type<any> | DynamicModule | Promise<DynamicModule>;
type ModuleScope = Type<any>[];
export class NestContainer {
private readonly globalModules = new Set<Module>();
private readonly moduleTokenFactory = new ModuleTokenFactory();
private readonly moduleCompiler = new ModuleCompiler(this.moduleTokenFactory);
private readonly modules = new ModulesContainer();
private readonly dynamicModulesMetadata = new Map<
string,
@@ -36,11 +37,27 @@ export class NestContainer {
>();
private readonly internalProvidersStorage = new InternalProvidersStorage();
private readonly _serializedGraph = new SerializedGraph();
private moduleCompiler: ModuleCompiler;
private internalCoreModule: Module;
constructor(
private readonly _applicationConfig: ApplicationConfig = undefined,
) {}
private readonly _applicationConfig:
| ApplicationConfig
| undefined = undefined,
private readonly _contextOptions:
| NestApplicationContextOptions
| undefined = undefined,
) {
const moduleOpaqueKeyFactory =
this._contextOptions?.moduleIdGeneratorAlgorithm === 'deep-hash'
? new DeepHashedModuleOpaqueKeyFactory()
: new ByReferenceModuleOpaqueKeyFactory({
keyGenerationStrategy: this._contextOptions?.snapshot
? 'shallow'
: 'random',
});
this.moduleCompiler = new ModuleCompiler(moduleOpaqueKeyFactory);
}
get serializedGraph(): SerializedGraph {
return this._serializedGraph;
@@ -313,7 +330,7 @@ export class NestContainer {
metadataKey?: Exclude<keyof DynamicModule, 'global' | 'module'>,
) {
const metadata = this.dynamicModulesMetadata.get(token);
return metadataKey ? (metadata?.[metadataKey] ?? []) : metadata;
return metadataKey ? metadata?.[metadataKey] ?? [] : metadata;
}
public registerCoreModuleRef(moduleRef: Module) {
@@ -321,8 +338,8 @@ export class NestContainer {
this.modules[InternalCoreModule.name] = moduleRef;
}
public getModuleTokenFactory(): ModuleTokenFactory {
return this.moduleTokenFactory;
public getModuleTokenFactory(): ModuleOpaqueKeyFactory {
return this.moduleCompiler.moduleOpaqueKeyFactory;
}
public registerRequestProvider<T = any>(request: T, contextId: ContextId) {

View File

@@ -2,7 +2,6 @@ import { Logger } from '@nestjs/common';
import { ExternalContextCreator } from '../../helpers/external-context-creator';
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
import { GraphInspector } from '../../inspector/graph-inspector';
import { InitializeOnPreviewAllowlist } from '../../inspector/initialize-on-preview.allowlist';
import { SerializedGraph } from '../../inspector/serialized-graph';
import { ModuleOverride } from '../../interfaces/module-override.interface';
import { DependenciesScanner } from '../../scanner';
@@ -43,8 +42,6 @@ export class InternalCoreModuleFactory {
);
};
InitializeOnPreviewAllowlist.add(InternalCoreModule);
return InternalCoreModule.register([
{
provide: ExternalContextCreator,

View File

@@ -653,7 +653,12 @@ export class Module {
private generateUuid(): string {
const prefix = 'M_';
const key = this.name?.toString() ?? this.token?.toString();
const key = this.token
? this.token.includes(':')
? this.token.split(':')[1]
: this.token
: this.name;
return key ? UuidFactory.get(`${prefix}_${key}`) : randomStringGenerator();
}
}

View File

@@ -0,0 +1,63 @@
import { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface';
import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { createHash } from 'crypto';
import { ModuleOpaqueKeyFactory } from './interfaces/module-opaque-key-factory.interface';
const K_MODULE_ID = Symbol('K_MODULE_ID');
export class ByReferenceModuleOpaqueKeyFactory
implements ModuleOpaqueKeyFactory
{
private readonly keyGenerationStrategy: 'random' | 'shallow';
constructor(options?: { keyGenerationStrategy: 'random' | 'shallow' }) {
this.keyGenerationStrategy = options?.keyGenerationStrategy ?? 'random';
}
public createForStatic(
moduleCls: Type,
originalRef: Type | ForwardReference = moduleCls,
): string {
return this.getOrCreateModuleId(moduleCls, undefined, originalRef);
}
public createForDynamic(
moduleCls: Type<unknown>,
dynamicMetadata: Omit<DynamicModule, 'module'>,
originalRef: DynamicModule | ForwardReference,
): string {
return this.getOrCreateModuleId(moduleCls, dynamicMetadata, originalRef);
}
private getOrCreateModuleId(
moduleCls: Type<unknown>,
dynamicMetadata: Partial<DynamicModule> | undefined,
originalRef: Type | DynamicModule | ForwardReference,
): string {
if (originalRef[K_MODULE_ID]) {
return originalRef[K_MODULE_ID];
}
let moduleId: string;
if (this.keyGenerationStrategy === 'random') {
moduleId = this.generateRandomString();
} else {
moduleId = dynamicMetadata
? `${this.generateRandomString()}:${this.hashString(moduleCls.name + JSON.stringify(dynamicMetadata))}`
: `${this.generateRandomString()}:${this.hashString(moduleCls.toString())}`;
}
originalRef[K_MODULE_ID] = moduleId;
return moduleId;
}
private hashString(value: string): string {
return createHash('sha256').update(value).digest('hex');
}
private generateRandomString(): string {
return randomStringGenerator();
}
}

View File

@@ -1,50 +1,28 @@
import { DynamicModule, Logger } from '@nestjs/common';
import { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { Logger } from '@nestjs/common/services/logger.service';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { isFunction, isSymbol } from '@nestjs/common/utils/shared.utils';
import { createHash } from 'crypto';
import stringify from 'fast-safe-stringify';
import { performance } from 'perf_hooks';
import { ModuleOpaqueKeyFactory } from './interfaces/module-opaque-key-factory.interface';
const CLASS_STR = 'class ';
const CLASS_STR_LEN = CLASS_STR.length;
export class ModuleTokenFactory {
private readonly moduleTokenCache = new Map<string, string>();
export class DeepHashedModuleOpaqueKeyFactory
implements ModuleOpaqueKeyFactory
{
private readonly moduleIdsCache = new WeakMap<Type<unknown>, string>();
private readonly logger = new Logger(ModuleTokenFactory.name, {
private readonly moduleTokenCache = new Map<string, string>();
private readonly logger = new Logger(DeepHashedModuleOpaqueKeyFactory.name, {
timestamp: true,
});
public create(
metatype: Type<unknown>,
dynamicModuleMetadata?: Partial<DynamicModule> | undefined,
): string {
const moduleId = this.getModuleId(metatype);
public createForStatic(moduleCls: Type): string {
const moduleId = this.getModuleId(moduleCls);
const moduleName = this.getModuleName(moduleCls);
if (!dynamicModuleMetadata) {
return this.getStaticModuleToken(moduleId, this.getModuleName(metatype));
}
const opaqueToken = {
id: moduleId,
module: this.getModuleName(metatype),
dynamic: dynamicModuleMetadata,
};
const start = performance.now();
const opaqueTokenString = this.getStringifiedOpaqueToken(opaqueToken);
const timeSpentInMs = performance.now() - start;
if (timeSpentInMs > 10) {
const formattedTimeSpent = timeSpentInMs.toFixed(2);
this.logger.warn(
`The module "${opaqueToken.module}" is taking ${formattedTimeSpent}ms to serialize, this may be caused by larger objects statically assigned to the module. More details: https://github.com/nestjs/nest/issues/12738`,
);
}
return this.hashString(opaqueTokenString);
}
public getStaticModuleToken(moduleId: string, moduleName: string): string {
const key = `${moduleId}_${moduleName}`;
if (this.moduleTokenCache.has(key)) {
return this.moduleTokenCache.get(key);
@@ -55,6 +33,31 @@ export class ModuleTokenFactory {
return hash;
}
public createForDynamic(
moduleCls: Type<unknown>,
dynamicMetadata: Omit<DynamicModule, 'module'>,
): string {
const moduleId = this.getModuleId(moduleCls);
const moduleName = this.getModuleName(moduleCls);
const opaqueToken = {
id: moduleId,
module: moduleName,
dynamic: dynamicMetadata,
};
const start = performance.now();
const opaqueTokenString = this.getStringifiedOpaqueToken(opaqueToken);
const timeSpentInMs = performance.now() - start;
if (timeSpentInMs > 10) {
const formattedTimeSpent = timeSpentInMs.toFixed(2);
this.logger.warn(
`The module "${opaqueToken.module}" is taking ${formattedTimeSpent}ms to serialize, this may be caused by larger objects statically assigned to the module. Consider changing the "moduleIdGeneratorAlgorithm" option to "reference" to improve the performance.`,
);
}
return this.hashString(opaqueTokenString);
}
public getStringifiedOpaqueToken(opaqueToken: object | undefined): string {
// Uses safeStringify instead of JSON.stringify to support circular dynamic modules
// The replacer function is also required in order to obtain real class names

View File

@@ -0,0 +1,26 @@
import { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface';
import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
export interface ModuleOpaqueKeyFactory {
/**
* Creates a unique opaque key for the given static module.
* @param moduleCls A static module class.
* @param originalRef Original object reference. In most cases, it's the same as `moduleCls`.
*/
createForStatic(
moduleCls: Type,
originalRef: Type | ForwardReference,
): string;
/**
* Creates a unique opaque key for the given dynamic module.
* @param moduleCls A dynamic module class reference.
* @param dynamicMetadata Dynamic module metadata.
* @param originalRef Original object reference.
*/
createForDynamic(
moduleCls: Type<unknown>,
dynamicMetadata: Omit<DynamicModule, 'module'>,
originalRef: DynamicModule | ForwardReference,
): string;
}

View File

@@ -144,7 +144,7 @@ export class SerializedGraph {
if (typeof value === 'symbol') {
return value.toString();
}
return typeof value === 'function' ? (value.name ?? 'Function') : value;
return typeof value === 'function' ? value.name ?? 'Function' : value;
};
return JSON.stringify(this.toJSON(), replacer, 2);
}

View File

@@ -30,8 +30,7 @@ export class InterceptorsConsumer {
return defer(AsyncResource.bind(() => this.transformDeferred(next)));
}
const handler: CallHandler = {
handle: () =>
defer(AsyncResource.bind(() => nextFn(i + 1))).pipe(mergeAll()),
handle: () => fromPromise(nextFn(i + 1)).pipe(mergeAll()),
};
return interceptors[i].intercept(context, handler);
};

View File

@@ -58,9 +58,8 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
public exclude(
...routes: Array<string | RouteInfo>
): MiddlewareConfigProxy {
this.excludedRoutes = [
...this.excludedRoutes,
...this.getRoutesFlatList(routes).reduce((excludedRoutes, route) => {
this.excludedRoutes = this.getRoutesFlatList(routes).reduce(
(excludedRoutes, route) => {
for (const routePath of this.routeInfoPathExtractor.extractPathFrom(
route,
)) {
@@ -71,8 +70,9 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
}
return excludedRoutes;
}, [] as RouteInfo[]),
];
},
[] as RouteInfo[],
);
return this;
}

View File

@@ -190,14 +190,12 @@ export class MiddlewareModule<
for (const metatype of middlewareCollection) {
const collection = middlewareContainer.getMiddlewareCollection(moduleKey);
const instanceWrapper = collection.get(metatype);
if (isUndefined(instanceWrapper)) {
throw new RuntimeException();
}
if (instanceWrapper.isTransient) {
return;
}
this.graphInspector.insertClassNode(
moduleRef,
instanceWrapper,
@@ -329,11 +327,7 @@ export class MiddlewareModule<
}
return next();
};
const pathsToApplyMiddleware = [];
paths.some(path => path.match(/^\/?$/))
? pathsToApplyMiddleware.push('/')
: pathsToApplyMiddleware.push(...paths);
pathsToApplyMiddleware.forEach(path => router(path, middlewareFunction));
paths.forEach(path => router(path, middlewareFunction));
}
private getContextId(request: unknown, isTreeDurable: boolean): ContextId {

View File

@@ -35,15 +35,11 @@ export class RouteInfoPathExtractor {
if (this.isAWildcard(path)) {
const entries =
versionPaths.length > 0
? versionPaths
.map(versionPath => [
this.prefixPath + versionPath + '$',
? versionPaths.map(
versionPath =>
this.prefixPath + versionPath + addLeadingSlash(path),
])
.flat()
: this.prefixPath
? [this.prefixPath + '$', this.prefixPath + addLeadingSlash(path)]
: [addLeadingSlash(path)];
)
: [this.prefixPath + addLeadingSlash(path)];
return Array.isArray(this.excludedGlobalPrefixRoutes)
? [

View File

@@ -50,7 +50,7 @@ export class NestApplicationContext<
private shouldFlushLogsOnOverride = false;
private readonly activeShutdownSignals = new Array<string>();
private readonly moduleCompiler = new ModuleCompiler();
private readonly moduleCompiler: ModuleCompiler;
private shutdownCleanupRef?: (...args: unknown[]) => unknown;
private _instanceLinksHost: InstanceLinksHost;
private _moduleRefsForHooksByDistance?: Array<Module>;
@@ -70,6 +70,7 @@ export class NestApplicationContext<
) {
super();
this.injector = new Injector();
this.moduleCompiler = container.getModuleCompiler();
if (this.appOptions.preview) {
this.printInPreviewModeWarning();
@@ -95,7 +96,13 @@ export class NestApplicationContext<
const moduleTokenFactory = this.container.getModuleTokenFactory();
const { type, dynamicMetadata } =
this.moduleCompiler.extractMetadata(moduleType);
const token = moduleTokenFactory.create(type, dynamicMetadata);
const token = dynamicMetadata
? moduleTokenFactory.createForDynamic(
type,
dynamicMetadata,
moduleType as DynamicModule,
)
: moduleTokenFactory.createForStatic(type, moduleType as Type);
const selectedModule = modulesContainer.get(token);
if (!selectedModule) {

View File

@@ -269,13 +269,9 @@ export class NestFactoryStatic {
return (...args: unknown[]) => {
let result: unknown;
ExceptionsZone.run(
() => {
result = receiver[prop](...args);
},
teardown,
this.autoFlushLogs,
);
ExceptionsZone.run(() => {
result = receiver[prop](...args);
}, teardown);
return result;
};

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "10.4.7",
"version": "10.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -31,12 +31,12 @@
"@nuxtjs/opencollective": "0.3.2",
"fast-safe-stringify": "2.1.1",
"iterare": "1.2.1",
"path-to-regexp": "3.3.0",
"tslib": "2.7.0",
"path-to-regexp": "3.2.0",
"tslib": "2.6.2",
"uid": "2.0.2"
},
"devDependencies": {
"@nestjs/common": "10.4.7"
"@nestjs/common": "10.3.3"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0",

View File

@@ -20,8 +20,6 @@ export class RouteParamsFactory implements IRouteParamsFactory {
return res as any;
case RouteParamtypes.BODY:
return data && req.body ? req.body[data] : req.body;
case RouteParamtypes.RAW_BODY:
return req.rawBody;
case RouteParamtypes.PARAM:
return data ? req.params[data] : req.params;
case RouteParamtypes.HOST:

View File

@@ -345,7 +345,6 @@ export class RouterExecutionContext {
public isPipeable(type: number | string): boolean {
return (
type === RouteParamtypes.BODY ||
type === RouteParamtypes.RAW_BODY ||
type === RouteParamtypes.QUERY ||
type === RouteParamtypes.PARAM ||
type === RouteParamtypes.FILE ||

View File

@@ -8,7 +8,7 @@ import {
import { isObject } from '@nestjs/common/utils/shared.utils';
import { IncomingMessage } from 'http';
import { EMPTY, lastValueFrom, Observable, isObservable } from 'rxjs';
import { catchError, concatMap, map } from 'rxjs/operators';
import { catchError, debounce, map } from 'rxjs/operators';
import {
AdditionalHeaders,
WritableHeaderStream,
@@ -128,7 +128,7 @@ export class RouterResponseController {
return { data: message as object | string };
}),
concatMap(
debounce(
message =>
new Promise<void>(resolve =>
stream.writeMessage(message, () => resolve()),
@@ -153,9 +153,6 @@ export class RouterResponseController {
request.on('close', () => {
subscription.unsubscribe();
if (!stream.writableEnded) {
stream.end();
}
});
}

View File

@@ -116,7 +116,7 @@ export class SseStream extends Transform {
message.id = this.lastEventId.toString();
}
if (!this.write(message, 'utf-8')) {
if (!this.write(message, 'utf-8', cb)) {
this.once('drain', cb);
} else {
process.nextTick(cb);

View File

@@ -522,14 +522,10 @@ export class DependenciesScanner {
}
public insertExportedProvider(
// TODO: improve the type definition below because it doesn't reflects the real usage of this method
exportedProvider: Type<Injectable> | ForwardReference,
exportedProvider: Type<Injectable>,
token: string,
) {
const fulfilledProvider = this.isForwardReference(exportedProvider)
? exportedProvider.forwardRef()
: exportedProvider;
this.container.addExportedProvider(fulfilledProvider, token);
this.container.addExportedProvider(exportedProvider, token);
}
public insertController(controller: Type<Controller>, token: string) {

View File

@@ -1,6 +1,5 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import { Logger } from '@nestjs/common';
import { ExceptionsZone } from '../../../errors/exceptions-zone';
describe('ExceptionsZone', () => {
@@ -14,7 +13,7 @@ describe('ExceptionsZone', () => {
callback = sinon.spy();
});
it('should call callback', () => {
ExceptionsZone.run(callback as any, rethrow, false);
ExceptionsZone.run(callback as any, rethrow);
expect(callback.called).to.be.true;
});
describe('when callback throws exception', () => {
@@ -22,54 +21,26 @@ describe('ExceptionsZone', () => {
handle: () => {},
};
let handleSpy: sinon.SinonSpy;
let LoggerFlushSpy: sinon.SinonSpy;
before(() => {
(ExceptionsZone as any).exceptionHandler = exceptionHandler;
handleSpy = sinon.spy(exceptionHandler, 'handle');
LoggerFlushSpy = sinon.spy(Logger, 'flush');
});
after(() => {
LoggerFlushSpy.restore();
});
describe('when callback throws exception and autoFlushLogs is false', () => {
it('should call "handle" method of exceptionHandler and rethrows and not flush logs', () => {
const throwsCallback = () => {
throw new Error('');
};
expect(() =>
ExceptionsZone.run(throwsCallback, rethrow, false),
).to.throws();
expect(handleSpy.called).to.be.true;
expect(LoggerFlushSpy.called).to.be.false;
});
});
describe('when callback throws exception and autoFlushLogs is true', () => {
it('should call "handle" method of exceptionHandler and rethrows and flush logs', () => {
const throwsCallback = () => {
throw new Error('');
};
expect(() =>
ExceptionsZone.run(throwsCallback, rethrow, true),
).to.throws();
expect(handleSpy.called).to.be.true;
expect(LoggerFlushSpy.called).to.be.true;
});
it('should call "handle" method of exceptionHandler and rethrows', () => {
const throwsCallback = () => {
throw new Error('');
};
expect(() => ExceptionsZone.run(throwsCallback, rethrow)).to.throws();
expect(handleSpy.called).to.be.true;
});
});
});
describe('asyncRun', () => {
let callback: sinon.SinonSpy;
beforeEach(() => {
callback = sinon.spy();
});
it('should call callback', async () => {
await ExceptionsZone.asyncRun(callback as any, rethrow, false);
await ExceptionsZone.asyncRun(callback as any, rethrow);
expect(callback.called).to.be.true;
});
describe('when callback throws exception', () => {
@@ -77,40 +48,16 @@ describe('ExceptionsZone', () => {
handle: () => {},
};
let handleSpy: sinon.SinonSpy;
let LoggerFlushSpy: sinon.SinonSpy;
before(() => {
(ExceptionsZone as any).exceptionHandler = exceptionHandler;
handleSpy = sinon.spy(exceptionHandler, 'handle');
LoggerFlushSpy = sinon.spy(Logger, 'flush');
});
after(() => {
LoggerFlushSpy.restore();
});
describe('when callback throws exception and autoFlushLogs is false', () => {
it('should call "handle" method of exceptionHandler and rethrows error and not flush logs', async () => {
const throwsCallback = () => {
throw new Error('');
};
expect(ExceptionsZone.asyncRun(throwsCallback, rethrow, false)).to
.eventually.be.rejected;
expect(handleSpy.called).to.be.true;
expect(LoggerFlushSpy.called).to.be.false;
});
});
describe('when callback throws exception and autoFlushLogs is true', () => {
it('should call "handle" method of exceptionHandler and rethrows error and flush logs', async () => {
const throwsCallback = () => {
throw new Error('');
};
expect(ExceptionsZone.asyncRun(throwsCallback, rethrow, true)).to
.eventually.be.rejected;
expect(handleSpy.called).to.be.true;
expect(LoggerFlushSpy.called).to.be.true;
});
it('should call "handle" method of exceptionHandler and rethrows error', async () => {
const throwsCallback = () => {
throw new Error('');
};
expect(ExceptionsZone.asyncRun(throwsCallback, rethrow)).to.eventually
.be.rejected;
});
});
});

View File

@@ -1,28 +1,30 @@
import { expect } from 'chai';
import { ModuleCompiler } from '../../injector/compiler';
import { ByReferenceModuleOpaqueKeyFactory } from '../../injector/opaque-key-factory/by-reference-module-opaque-key-factory';
describe('ModuleCompiler', () => {
let compiler: ModuleCompiler;
beforeEach(() => {
compiler = new ModuleCompiler();
compiler = new ModuleCompiler(new ByReferenceModuleOpaqueKeyFactory());
});
describe('extractMetadata', () => {
describe('when module is a dynamic module', () => {
it('should return object with "type" and "dynamicMetadata" property', async () => {
it('should return object with "type" and "dynamicMetadata" property', () => {
const obj = { module: 'test', providers: [] };
const { module, ...dynamicMetadata } = obj;
expect(await compiler.extractMetadata(obj as any)).to.be.deep.equal({
expect(compiler.extractMetadata(obj as any)).to.be.deep.equal({
type: module,
dynamicMetadata,
});
});
});
describe('when module is a not dynamic module', () => {
it('should return object with "type" property', async () => {
it('should return object with "type" property', () => {
const type = 'test';
expect(await compiler.extractMetadata(type as any)).to.be.deep.equal({
expect(compiler.extractMetadata(type as any)).to.be.deep.equal({
type,
dynamicMetadata: undefined,
});
});
});

View File

@@ -34,7 +34,7 @@ describe('Injector', () => {
constructor(
public one: DependencyOne,
@Inject() public two: DependencyTwo,
public two: DependencyTwo,
) {}
}

View File

@@ -0,0 +1,111 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import { ByReferenceModuleOpaqueKeyFactory } from '../../../injector/opaque-key-factory/by-reference-module-opaque-key-factory';
describe('ByReferenceModuleOpaqueKeyFactory', () => {
const moduleId = 'constId';
let factory: ByReferenceModuleOpaqueKeyFactory;
describe('when generating algorithm is random', () => {
beforeEach(() => {
factory = new ByReferenceModuleOpaqueKeyFactory();
sinon.stub(factory as any, 'generateRandomString').returns(moduleId);
});
describe('createForStatic', () => {
class Module {}
it('should return expected token', () => {
const type = Module;
const token1 = factory.createForStatic(type);
const token2 = factory.createForStatic(type);
expect(token1).to.be.deep.eq(token2);
});
});
describe('createForDynamic', () => {
class Module {}
it('should include dynamic metadata', () => {
const dynamicModule = {
module: Module,
providers: [
{
provide: 'test',
useValue: 'test',
},
],
};
const token1 = factory.createForDynamic(
dynamicModule.module,
{
providers: dynamicModule.providers,
},
dynamicModule,
);
const token2 = factory.createForDynamic(
dynamicModule.module,
{
providers: dynamicModule.providers,
},
dynamicModule,
);
expect(token1).to.be.deep.eq(token2);
});
});
});
describe('when generating algorithm is shallow', () => {
beforeEach(() => {
factory = new ByReferenceModuleOpaqueKeyFactory({
keyGenerationStrategy: 'shallow',
});
sinon.stub(factory as any, 'generateRandomString').returns(moduleId);
});
describe('createForStatic', () => {
class Module {}
it('should return expected token', () => {
const type = Module;
const token1 = factory.createForStatic(type);
const token2 = factory.createForStatic(type);
expect(token1).to.be.deep.eq(token2);
});
});
describe('createForDynamic', () => {
class Module {}
it('should include dynamic metadata', () => {
const dynamicModule = {
module: Module,
providers: [
{
provide: 'test',
useValue: 'test',
},
],
};
const token1 = factory.createForDynamic(
dynamicModule.module,
{
providers: dynamicModule.providers,
},
dynamicModule,
);
const token2 = factory.createForDynamic(
dynamicModule.module,
{
providers: dynamicModule.providers,
},
dynamicModule,
);
expect(token1).to.be.deep.eq(token2);
});
});
});
});

View File

@@ -1,41 +1,48 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import { ModuleTokenFactory } from '../../injector/module-token-factory';
import { DeepHashedModuleOpaqueKeyFactory } from '../../../injector/opaque-key-factory/deep-hashed-module-opaque-key-factory';
describe('ModuleTokenFactory', () => {
describe('DeepHashedModuleOpaqueKeyFactory', () => {
const moduleId = 'constId';
let factory: ModuleTokenFactory;
let factory: DeepHashedModuleOpaqueKeyFactory;
beforeEach(() => {
factory = new ModuleTokenFactory();
factory = new DeepHashedModuleOpaqueKeyFactory();
sinon.stub(factory, 'getModuleId').returns(moduleId);
});
describe('create', () => {
describe('createForStatic', () => {
class Module {}
it('should return expected token', () => {
const type = Module;
const token1 = factory.create(type, undefined);
const token2 = factory.create(type, undefined);
const token1 = factory.createForStatic(type);
const token2 = factory.createForStatic(type);
expect(token1).to.be.deep.eq(token2);
});
});
describe('createForDynamic', () => {
class Module {}
it('should include dynamic metadata', () => {
const type = Module;
const token1 = factory.create(type, {
const token1 = factory.createForDynamic(type, {
providers: [{}],
} as any);
const token2 = factory.create(type, {
const token2 = factory.createForDynamic(type, {
providers: [{}],
} as any);
expect(token1).to.be.deep.eq(token2);
});
});
describe('getModuleName', () => {
it('should map module metatype to name', () => {
const metatype = () => {};
expect(factory.getModuleName(metatype as any)).to.be.eql(metatype.name);
});
});
describe('getStringifiedOpaqueToken', () => {
describe('when metadata exists', () => {
it('should return hash', () => {
@@ -80,6 +87,7 @@ describe('ModuleTokenFactory', () => {
);
});
});
describe('when metadata does not exist', () => {
it('should return empty string', () => {
expect(factory.getStringifiedOpaqueToken(undefined)).to.be.eql('');

View File

@@ -1,7 +1,7 @@
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';
import { expect } from 'chai';
import { Observable, defer, lastValueFrom, merge, of, retry } from 'rxjs';
import { Observable, lastValueFrom, of, retry } from 'rxjs';
import * as sinon from 'sinon';
import { InterceptorsConsumer } from '../../interceptors/interceptors-consumer';
@@ -86,7 +86,7 @@ describe('InterceptorsConsumer', () => {
});
describe('when AsyncLocalStorage is used', () => {
it('should allow an interceptor to set values in AsyncLocalStorage that are accessible from the controller', async () => {
it('should allow an interceptor to set values in AsyncLocalStorage that are accesible from the controller', async () => {
const storage = new AsyncLocalStorage<Record<string, any>>();
class StorageInterceptor implements NestInterceptor {
intercept(
@@ -179,51 +179,6 @@ describe('InterceptorsConsumer', () => {
});
});
});
describe('deferred promise conversion', () => {
it('should convert promise to observable deferred', async () => {
class TestError extends Error {}
const testInterceptors = [
{
intercept: sinon.stub().callsFake(async (ctx, handler) => {
return merge(
handler.handle(),
defer(() => {
throw new TestError();
}),
);
}),
},
{
intercept: sinon
.stub()
.callsFake(async (ctx, handler) => handler.handle()),
},
{
intercept: sinon
.stub()
.callsFake(async (ctx, handler) => handler.handle()),
},
,
];
const observable = await consumer.intercept(
testInterceptors,
null,
{ constructor: null },
null,
async () => 1,
);
try {
await transformToResult(observable);
} catch (error) {
if (!(error instanceof TestError)) {
throw error;
}
}
expect(testInterceptors[2].intercept.called).to.be.false;
});
});
});
async function transformToResult(resultOrDeferred: any) {

View File

@@ -193,7 +193,7 @@ describe('MiddlewareBuilder', () => {
expect(proxy.getExcludedRoutes()).to.be.eql([
{
path,
method: -1,
method: -1 as any,
},
]);
});

View File

@@ -31,7 +31,7 @@ describe('RouteInfoPathExtractor', () => {
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/v1$', '/v1/*']);
).to.eql(['/v1/*']);
});
it(`should return correct paths when set global prefix`, () => {
@@ -42,7 +42,7 @@ describe('RouteInfoPathExtractor', () => {
path: '*',
method: RequestMethod.ALL,
}),
).to.eql(['/api$', '/api/*']);
).to.eql(['/api/*']);
expect(
routeInfoPathExtractor.extractPathsFrom({
@@ -50,7 +50,7 @@ describe('RouteInfoPathExtractor', () => {
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/api/v1$', '/api/v1/*']);
).to.eql(['/api/v1/*']);
});
it(`should return correct paths when set global prefix and global prefix options`, () => {
@@ -66,7 +66,7 @@ describe('RouteInfoPathExtractor', () => {
path: '*',
method: RequestMethod.ALL,
}),
).to.eql(['/api$', '/api/*', '/foo']);
).to.eql(['/api/*', '/foo']);
expect(
routeInfoPathExtractor.extractPathsFrom({
@@ -74,7 +74,7 @@ describe('RouteInfoPathExtractor', () => {
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/api/v1$', '/api/v1/*', '/v1/foo']);
).to.eql(['/api/v1/*', '/v1/foo']);
expect(
routeInfoPathExtractor.extractPathsFrom({

View File

@@ -13,7 +13,6 @@ describe('RouteParamsFactory', () => {
const req = {
ip: 'ip',
session: null,
rawBody: Buffer.from('{"foo":"bar"}'),
body: {
foo: 'bar',
},
@@ -68,16 +67,6 @@ describe('RouteParamsFactory', () => {
).to.be.eql(req.body);
});
});
describe(`RouteParamtypes.RAW_BODY`, () => {
it('should return rawBody buffer', () => {
expect(
(factory as any).exchangeKeyForValue(
RouteParamtypes.RAW_BODY,
...args,
),
).to.be.eql(req.rawBody);
});
});
describe(`RouteParamtypes.HEADERS`, () => {
it('should return headers object', () => {
expect(

View File

@@ -210,7 +210,7 @@ describe('RouterExecutionContext', () => {
beforeEach(() => {
consumerApplySpy = sinon.spy(consumer, 'apply');
});
describe('when paramtype is query, body, rawBody or param', () => {
describe('when paramtype is query, body or param', () => {
it('should call "consumer.apply" with expected arguments', () => {
contextCreator.getParamValue(
value,
@@ -238,19 +238,6 @@ describe('RouterExecutionContext', () => {
),
).to.be.true;
contextCreator.getParamValue(
value,
{ metatype, type: RouteParamtypes.RAW_BODY, data: null },
transforms,
);
expect(
consumerApplySpy.calledWith(
value,
{ metatype, type: RouteParamtypes.RAW_BODY, data: null },
transforms,
),
).to.be.true;
contextCreator.getParamValue(
value,
{ metatype, type: RouteParamtypes.PARAM, data: null },
@@ -274,7 +261,6 @@ describe('RouterExecutionContext', () => {
});
it('otherwise', () => {
expect(contextCreator.isPipeable(RouteParamtypes.BODY)).to.be.true;
expect(contextCreator.isPipeable(RouteParamtypes.RAW_BODY)).to.be.true;
expect(contextCreator.isPipeable(RouteParamtypes.QUERY)).to.be.true;
expect(contextCreator.isPipeable(RouteParamtypes.PARAM)).to.be.true;
expect(contextCreator.isPipeable(RouteParamtypes.FILE)).to.be.true;

View File

@@ -7,7 +7,6 @@ import { PassThrough, Writable } from 'stream';
import { HttpStatus, RequestMethod } from '../../../common';
import { RouterResponseController } from '../../router/router-response-controller';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
import { SseStream } from '../../router/sse-stream';
describe('RouterResponseController', () => {
let adapter: NoopHttpAdapter;
@@ -375,71 +374,6 @@ data: test
done();
});
describe('when writing data too densely', () => {
const DEFAULT_MAX_LISTENERS = SseStream.defaultMaxListeners;
const MAX_LISTENERS = 1;
const sandbox = sinon.createSandbox();
beforeEach(() => {
// Can't access to the internal sseStream,
// as a workaround, set `defaultMaxListeners` of `SseStream` and reset the max listeners of `process`
const PROCESS_MAX_LISTENERS = process.getMaxListeners();
SseStream.defaultMaxListeners = MAX_LISTENERS;
process.setMaxListeners(PROCESS_MAX_LISTENERS);
const sseStream = sinon.createStubInstance(SseStream);
const originalWrite = SseStream.prototype.write;
// Make `.write()` always return false, so as to listen `drain` event
sseStream.write.callsFake(function (...args: any[]) {
originalWrite.apply(this, args);
return false;
});
sandbox.replace(SseStream.prototype, 'write', sseStream.write);
});
afterEach(() => {
sandbox.restore();
SseStream.defaultMaxListeners = DEFAULT_MAX_LISTENERS;
});
it('should not cause memory leak', async () => {
let maxDrainListenersExceededWarning = null;
process.on('warning', (warning: any) => {
if (
warning.name === 'MaxListenersExceededWarning' &&
warning.emitter instanceof SseStream &&
warning.type === 'drain' &&
warning.count === MAX_LISTENERS + 1
) {
maxDrainListenersExceededWarning = warning;
}
});
const result = new Subject();
const response = new Writable();
response._write = () => {};
const request = new Writable();
request._write = () => {};
routerResponseController.sse(
result,
response as unknown as ServerResponse,
request as unknown as IncomingMessage,
);
// Send multiple messages simultaneously
Array.from({ length: MAX_LISTENERS + 1 }).forEach((_, i) =>
result.next(String(i)),
);
await new Promise(resolve => process.nextTick(resolve));
expect(maxDrainListenersExceededWarning).to.equal(null);
});
});
describe('when there is an error', () => {
it('should close the request', done => {
const result = new Subject();

View File

@@ -25,12 +25,12 @@
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also provides compatibility with a wide range of other libraries, like <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad of third-party plugins which are available.</p>
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
## Philosophy
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a>, and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a>, which improve developer productivity and enable the construction of fast, testable, and extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers, and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, and loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a> which improve developer productivity and enable the construction of fast, testable, extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
## Getting started
@@ -57,84 +57,71 @@ 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://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
<td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
<td><a href="https://marblism.com?utm_source=nest" target="_blank"><img src="https://nestjs.com/img/logos/marblism-logo.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="170" valign="middle" /></a></td>
<td><a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/logos/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" 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="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
<td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
</tr>
<tr>
<td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
<td><a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/logos/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/logos/ridi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
<td><a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/logos/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<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://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
<a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/ridi-logo.svg" width="105" valign="middle" /></a></td><td>
<a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/movavi-logo.svg" width="105" valign="middle" /></a></td>
</tr><tr><td>
<a href="https://skunk.team" target="_blank"><img src="https://nestjs.com/img/skunk-logo.png" height="60" valign="middle" /></a></td>
</tr></table>
#### Silver Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
<td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://bloodycase.com/?promocode=NEST" target="_blank"><img src="https://nestjs.com/img/logos/bloodycase-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-logo.svg" width="150" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
</tr>
</table>
#### Sponsors
<table>
<tr>
<td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" 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/logos/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/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/logos/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/logos/julienferand-logo.jpeg" width="55" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.slotsup.com/" target="_blank"><img src="https://nestjs.com/img/logos/slotsup-logo.png" width="60" valign="middle" /></a></td>
</tr>
</table>
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<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></tr><tr>
<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><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://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://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers

View File

@@ -18,7 +18,6 @@ let natsPackage = {} as any;
export class ClientNats extends ClientProxy {
protected readonly logger = new Logger(ClientNats.name);
protected natsClient: Client;
protected clientConnectionPromise: Promise<Client>;
constructor(protected readonly options: NatsOptions['options']) {
super();
@@ -31,15 +30,13 @@ export class ClientNats extends ClientProxy {
public async close() {
await this.natsClient?.close();
this.natsClient = null;
this.clientConnectionPromise = null;
}
public async connect(): Promise<any> {
if (this.clientConnectionPromise) {
return this.clientConnectionPromise;
if (this.natsClient) {
return this.natsClient;
}
this.clientConnectionPromise = this.createClient();
this.natsClient = await this.clientConnectionPromise;
this.natsClient = await this.createClient();
this.handleStatusUpdates(this.natsClient);
return this.natsClient;
}

View File

@@ -136,7 +136,7 @@ export class ClientRMQ extends ClientProxy {
public createClient(): AmqpConnectionManager {
const socketOptions = this.getOptionsProp(this.options, 'socketOptions');
return rmqPackage.connect(this.urls, {
connectionOptions: socketOptions?.connectionOptions,
connectionOptions: socketOptions,
});
}

View File

@@ -1,6 +1,3 @@
/**
* @publicApi
*/
export class BaseRpcContext<T = unknown[]> {
constructor(protected readonly args: T) {}

View File

@@ -10,9 +10,6 @@ type KafkaContextArgs = [
producer: Producer,
];
/**
* @publicApi
*/
export class KafkaContext extends BaseRpcContext<KafkaContextArgs> {
constructor(args: KafkaContextArgs) {
super(args);

View File

@@ -2,9 +2,6 @@ import { BaseRpcContext } from './base-rpc.context';
type MqttContextArgs = [string, Record<string, any>];
/**
* @publicApi
*/
export class MqttContext extends BaseRpcContext<MqttContextArgs> {
constructor(args: MqttContextArgs) {
super(args);

View File

@@ -2,9 +2,6 @@ import { BaseRpcContext } from './base-rpc.context';
type NatsContextArgs = [string, any];
/**
* @publicApi
*/
export class NatsContext extends BaseRpcContext<NatsContextArgs> {
constructor(args: NatsContextArgs) {
super(args);

View File

@@ -2,9 +2,6 @@ import { BaseRpcContext } from './base-rpc.context';
type RedisContextArgs = [string];
/**
* @publicApi
*/
export class RedisContext extends BaseRpcContext<RedisContextArgs> {
constructor(args: RedisContextArgs) {
super(args);

View File

@@ -2,9 +2,6 @@ import { BaseRpcContext } from './base-rpc.context';
type RmqContextArgs = [Record<string, any>, any, string];
/**
* @publicApi
*/
export class RmqContext extends BaseRpcContext<RmqContextArgs> {
constructor(args: RmqContextArgs) {
super(args);

View File

@@ -3,9 +3,6 @@ import { BaseRpcContext } from './base-rpc.context';
type TcpContextArgs = [TcpSocket, string];
/**
* @publicApi
*/
export class TcpContext extends BaseRpcContext<TcpContextArgs> {
constructor(args: TcpContextArgs) {
super(args);

View File

@@ -1,7 +1,5 @@
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum';
export enum RpcParamtype {
PAYLOAD = RouteParamtypes.BODY,
CONTEXT = RouteParamtypes.HEADERS,
GRPC_CALL = RouteParamtypes.FILES,
PAYLOAD = 3,
CONTEXT = 6,
GRPC_CALL = 9,
}

View File

@@ -66,7 +66,6 @@ export interface GrpcOptions {
protoLoader?: string;
packageDefinition?: any;
gracefulShutdown?: boolean;
onLoadPackageDefinition?: (pkg: any, server: any) => void;
loader?: {
keepCase?: boolean;
alternateCommentMode?: boolean;

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/microservices",
"version": "10.4.7",
"version": "10.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@microservices)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -19,11 +19,11 @@
},
"dependencies": {
"iterare": "1.2.1",
"tslib": "2.7.0"
"tslib": "2.6.2"
},
"devDependencies": {
"@nestjs/common": "10.4.7",
"@nestjs/core": "10.4.7"
"@nestjs/common": "10.3.3",
"@nestjs/core": "10.3.3"
},
"peerDependencies": {
"@grpc/grpc-js": "*",

View File

@@ -1,6 +1,3 @@
/**
* @publicApi
*/
export interface MqttRecordOptions {
/**
* The QoS
@@ -29,9 +26,6 @@ export interface MqttRecordOptions {
};
}
/**
* @publicApi
*/
export class MqttRecord<TData = any> {
constructor(
public readonly data: TData,
@@ -39,9 +33,6 @@ export class MqttRecord<TData = any> {
) {}
}
/**
* @publicApi
*/
export class MqttRecordBuilder<TData> {
private options?: MqttRecordOptions;

View File

@@ -1,6 +1,3 @@
/**
* @publicApi
*/
export class NatsRecord<TData = any, THeaders = any> {
constructor(
public readonly data: TData,
@@ -8,9 +5,6 @@ export class NatsRecord<TData = any, THeaders = any> {
) {}
}
/**
* @publicApi
*/
export class NatsRecordBuilder<TData> {
private headers?: any;

View File

@@ -1,6 +1,3 @@
/**
* @publicApi
*/
export interface RmqRecordOptions {
expiration?: string | number;
userId?: string;
@@ -19,9 +16,6 @@ export interface RmqRecordOptions {
appId?: string;
}
/**
* @publicApi
*/
export class RmqRecord<TData = any> {
constructor(
public readonly data: TData,
@@ -29,9 +23,6 @@ export class RmqRecord<TData = any> {
) {}
}
/**
* @publicApi
*/
export class RmqRecordBuilder<TData> {
private options?: RmqRecordOptions;

View File

@@ -42,9 +42,6 @@ interface GrpcCall<TRequest = any, TMetadata = any> {
emit: Function;
}
/**
* @publicApi
*/
export class ServerGrpc extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.GRPC;
@@ -84,7 +81,6 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
public async start(callback?: () => void) {
await this.bindEvents();
this.grpcClient.start();
callback();
}
@@ -125,6 +121,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
const service = {};
for (const methodName in grpcService.prototype) {
let pattern = '';
let methodHandler = null;
let streamingType = GrpcMethodStreamingType.NO_STREAMING;
@@ -134,32 +131,32 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
if (!isUndefined(methodReqStreaming) && methodReqStreaming) {
// Try first pattern to be presented, RX streaming pattern would be
// a preferable pattern to select among a few defined
methodHandler = this.getMessageHandler(
pattern = this.createPattern(
name,
methodName,
GrpcMethodStreamingType.RX_STREAMING,
methodFunction,
);
methodHandler = this.messageHandlers.get(pattern);
streamingType = GrpcMethodStreamingType.RX_STREAMING;
// If first pattern didn't match to any of handlers then try
// pass-through handler to be presented
if (!methodHandler) {
methodHandler = this.getMessageHandler(
pattern = this.createPattern(
name,
methodName,
GrpcMethodStreamingType.PT_STREAMING,
methodFunction,
);
methodHandler = this.messageHandlers.get(pattern);
streamingType = GrpcMethodStreamingType.PT_STREAMING;
}
} else {
// Select handler if any presented for No-Streaming pattern
methodHandler = this.getMessageHandler(
pattern = this.createPattern(
name,
methodName,
GrpcMethodStreamingType.NO_STREAMING,
methodFunction,
);
// Select handler if any presented for No-Streaming pattern
methodHandler = this.messageHandlers.get(pattern);
streamingType = GrpcMethodStreamingType.NO_STREAMING;
}
if (!methodHandler) {
@@ -174,22 +171,6 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
return service;
}
getMessageHandler(
serviceName: string,
methodName: string,
streaming: GrpcMethodStreamingType,
grpcMethod: { path?: string },
) {
let pattern = this.createPattern(serviceName, methodName, streaming);
let methodHandler = this.messageHandlers.get(pattern);
if (!methodHandler) {
const packageServiceName = grpcMethod.path?.split?.('/')[1];
pattern = this.createPattern(packageServiceName, methodName, streaming);
methodHandler = this.messageHandlers.get(pattern);
}
return methodHandler;
}
/**
* Will create a string of a JSON serialized format
*
@@ -260,6 +241,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
return async (call: GrpcCall, callback: Function) => {
const handler = methodHandler(call.request, call.metadata, call);
const result$ = this.transformToObservable(await handler);
await this.writeObservableToGrpc(result$, call);
};
}
@@ -274,13 +256,11 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
* @param call The GRPC call we want to write to.
* @returns A promise that resolves when we're done writing to the call.
*/
private writeObservableToGrpc<T>(
public writeObservableToGrpc<T>(
source: Observable<T>,
call: GrpcCall<T>,
): Promise<void> {
// this promise should **not** reject, as we're handling errors in the observable for the Call
// the promise is only needed to signal when writing/draining has been completed
return new Promise((resolve, _doNotUse) => {
return new Promise((resolve, reject) => {
const valuesWaitingToBeDrained: T[] = [];
let shouldErrorAfterDraining = false;
let error: any;
@@ -293,14 +273,17 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
// If the call is cancelled, unsubscribe from the source
const cancelHandler = () => {
subscription.unsubscribe();
// Calls that are cancelled by the client should be successfully resolved here
// The call has been cancelled, so we need to either resolve
// or reject the promise. We're resolving in this case because
// rejection is noisy. If at any point in the future, we need to
// know that cancellation happened, we can either reject or
// start resolving with some sort of outcome value.
resolve();
};
call.on(CANCEL_EVENT, cancelHandler);
subscription.add(() => call.off(CANCEL_EVENT, cancelHandler));
// In all cases, when we finalize, end the writable stream
// being careful that errors and writes must be emitted _before_ this call is ended
subscription.add(() => call.end());
const drain = () => {
@@ -324,7 +307,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
} else if (shouldErrorAfterDraining) {
call.emit('error', error);
subscription.unsubscribe();
resolve();
reject(error);
}
};
@@ -349,7 +332,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
// reject and teardown.
call.emit('error', err);
subscription.unsubscribe();
resolve();
reject(err);
} else {
// We're waiting for a drain event, record the
// error so it can be handled after everything is drained.
@@ -515,14 +498,6 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
this.options,
grpcProtoLoaderPackage,
);
if (this.options.onLoadPackageDefinition) {
this.options.onLoadPackageDefinition(
packageDefinition,
this.grpcClient,
);
}
return grpcPackage.loadPackageDefinition(packageDefinition);
} catch (err) {
const invalidProtoError = new InvalidProtoDefinitionException(err.path);
@@ -567,19 +542,14 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
? deepDefinition.service !== false
: false;
// grpc namespace object does not have 'format' or 'service' properties defined
const isFormatDefined =
deepDefinition && !isUndefined(deepDefinition.format);
if (isServiceDefined && isServiceBoolean) {
accumulator.push({
name: nameExtended,
service: deepDefinition,
});
} else if (isFormatDefined) {
// Do nothing
} else {
// Continue recursion for namespace object until objects end or service definition found
}
// Continue recursion until objects end or service definition found
else {
this.collectDeepServices(nameExtended, deepDefinition, accumulator);
}
}

View File

@@ -36,9 +36,6 @@ import { Server } from './server';
let kafkaPackage: any = {};
/**
* @publicApi
*/
export class ServerKafka extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.KAFKA;

View File

@@ -26,9 +26,6 @@ import { Server } from './server';
let mqttPackage: any = {};
/**
* @publicApi
*/
export class ServerMqtt extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.MQTT;

View File

@@ -13,9 +13,6 @@ import { Server } from './server';
let natsPackage = {} as any;
/**
* @publicApi
*/
export class ServerNats extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.NATS;

View File

@@ -19,9 +19,6 @@ type Redis = any;
let redisPackage = {} as any;
/**
* @publicApi
*/
export class ServerRedis extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.REDIS;

View File

@@ -36,9 +36,6 @@ let rmqPackage: any = {};
const INFINITE_CONNECTION_ATTEMPTS = -1;
/**
* @publicApi
*/
export class ServerRMQ extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.RMQ;
@@ -143,7 +140,7 @@ export class ServerRMQ extends Server implements CustomTransportStrategy {
public createClient<T = any>(): T {
const socketOptions = this.getOptionsProp(this.options, 'socketOptions');
return rmqPackage.connect(this.urls, {
connectionOptions: socketOptions?.connectionOptions,
connectionOptions: socketOptions,
heartbeatIntervalInSeconds: socketOptions?.heartbeatIntervalInSeconds,
reconnectTimeInSeconds: socketOptions?.reconnectTimeInSeconds,
});

View File

@@ -26,9 +26,6 @@ import {
import { TcpOptions } from '../interfaces/microservice-configuration.interface';
import { Server } from './server';
/**
* @publicApi
*/
export class ServerTCP extends Server implements CustomTransportStrategy {
public readonly transportId = Transport.TCP;

View File

@@ -34,9 +34,6 @@ import { ConsumerSerializer } from '../interfaces/serializer.interface';
import { IdentitySerializer } from '../serializers/identity.serializer';
import { transformPatternToRoute } from '../utils';
/**
* @publicApi
*/
export abstract class Server {
protected readonly messageHandlers = new Map<string, MessageHandler>();
protected readonly logger: LoggerService = new Logger(Server.name);

View File

@@ -253,7 +253,6 @@ describe('ClientNats', () => {
describe('when is not connected', () => {
beforeEach(async () => {
client['natsClient'] = null;
client['clientConnectionPromise'] = null;
await client.connect();
});
it('should call "handleStatusUpdatesSpy" once', async () => {

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