mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
Compare commits
374 Commits
feat/numbe
...
v10.4.21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd11464a6e | ||
|
|
cd2bd3e1b4 | ||
|
|
d96149c87b | ||
|
|
2e55ee1883 | ||
|
|
e7f9ade559 | ||
|
|
a74f89ac10 | ||
|
|
5a59810c9c | ||
|
|
3d4fa469c9 | ||
|
|
79cd7bc949 | ||
|
|
5ab165a60d | ||
|
|
eb0f29d3b7 | ||
|
|
f8021ab3e9 | ||
|
|
0402529e56 | ||
|
|
5ce44905c0 | ||
|
|
e3a69525ce | ||
|
|
82f9fed477 | ||
|
|
9e9c3c1c6e | ||
|
|
421a5802ff | ||
|
|
2e9f312e9d | ||
|
|
9da1249256 | ||
|
|
ae40d669e5 | ||
|
|
dc2a3c0d31 | ||
|
|
c4fb432637 | ||
|
|
6c8aec63b4 | ||
|
|
bce9a49214 | ||
|
|
2b9e132f6a | ||
|
|
cb0d6506b3 | ||
|
|
6196ab2150 | ||
|
|
0ac7959927 | ||
|
|
983216b10e | ||
|
|
312a54ae2e | ||
|
|
a28fc033f4 | ||
|
|
07b4b3829e | ||
|
|
0b7af8ab07 | ||
|
|
6953b7a79e | ||
|
|
1f20f52731 | ||
|
|
08840c57df | ||
|
|
cddbcd1897 | ||
|
|
890fde4ef0 | ||
|
|
5ae8168617 | ||
|
|
38a77e2336 | ||
|
|
d90cc8dcf2 | ||
|
|
2260e00bab | ||
|
|
cda46a219a | ||
|
|
23308c7d4a | ||
|
|
f37603a708 | ||
|
|
d57f934a3c | ||
|
|
059592944b | ||
|
|
5feec1f861 | ||
|
|
ca1bd82bf9 | ||
|
|
7f37b492c9 | ||
|
|
c6240a0840 | ||
|
|
a203e07f48 | ||
|
|
315fe55648 | ||
|
|
14c6cd0e02 | ||
|
|
d51f9b6c9d | ||
|
|
18750bd800 | ||
|
|
f6f4baf53d | ||
|
|
010607f172 | ||
|
|
69fd0e9ddf | ||
|
|
579d68a6ec | ||
|
|
dd19ac8f06 | ||
|
|
c571796093 | ||
|
|
100d3c0dc8 | ||
|
|
db2fe5b5aa | ||
|
|
30e4acae29 | ||
|
|
4a2c6e020e | ||
|
|
b03302cf26 | ||
|
|
fdebabd003 | ||
|
|
e1229b124d | ||
|
|
4d21f1e491 | ||
|
|
0ff055834c | ||
|
|
d7a6952f19 | ||
|
|
ac04c340f3 | ||
|
|
598ee4f265 | ||
|
|
3dc367bbb1 | ||
|
|
d0fb875e23 | ||
|
|
b3412d9ca9 | ||
|
|
1d457fe79f | ||
|
|
6d90dfadca | ||
|
|
b8ed8eb4fd | ||
|
|
e52f80af3c | ||
|
|
c7e5e08c97 | ||
|
|
ef67824272 | ||
|
|
3044aae2fe | ||
|
|
e22768083e | ||
|
|
27aaa560e1 | ||
|
|
d3f525135b | ||
|
|
fd7d10f69f | ||
|
|
c9a600c3f5 | ||
|
|
a9a7a9095c | ||
|
|
2c276ac8ec | ||
|
|
0d0b5f7098 | ||
|
|
0e5bf68e7a | ||
|
|
6029a251ba | ||
|
|
cf9bf7cdc2 | ||
|
|
03e0af6440 | ||
|
|
9e5e4f6db3 | ||
|
|
39cc540826 | ||
|
|
4971ef3a5a | ||
|
|
f4b242a6aa | ||
|
|
31a5a552ca | ||
|
|
b642364f6b | ||
|
|
fb72d01118 | ||
|
|
8c1a6878cd | ||
|
|
42c4316777 | ||
|
|
a03363f9ca | ||
|
|
563c088f2a | ||
|
|
31c6fadbc3 | ||
|
|
e24a6833af | ||
|
|
84ab3145a9 | ||
|
|
f0824383ba | ||
|
|
762406a78d | ||
|
|
e0e9002bb7 | ||
|
|
15c04cd0e5 | ||
|
|
f5f226c513 | ||
|
|
26f1341429 | ||
|
|
07886bf320 | ||
|
|
0e54f52d27 | ||
|
|
50796582df | ||
|
|
e3c95e4f60 | ||
|
|
6ea7dcebb6 | ||
|
|
34c5ba6191 | ||
|
|
edb8b1b0e8 | ||
|
|
ce764067a4 | ||
|
|
ef66275ae9 | ||
|
|
21143d757b | ||
|
|
06349b61cd | ||
|
|
1eba0121a0 | ||
|
|
c5bbd1e40f | ||
|
|
265f6d03c8 | ||
|
|
3f127e6a2f | ||
|
|
b1b262f4c3 | ||
|
|
59bf615d29 | ||
|
|
c53eee2065 | ||
|
|
25feb4540a | ||
|
|
9edc20d84c | ||
|
|
f6240160ae | ||
|
|
5a558b271f | ||
|
|
2c72d447c1 | ||
|
|
a7dd69328a | ||
|
|
57c5e6f118 | ||
|
|
bc50321c7a | ||
|
|
9b0cd523aa | ||
|
|
24e0106b46 | ||
|
|
439231ed61 | ||
|
|
425121d809 | ||
|
|
d0fe4833ad | ||
|
|
c95ed06498 | ||
|
|
d7b460c8b8 | ||
|
|
28b785991b | ||
|
|
72b32356dc | ||
|
|
b1a2582abc | ||
|
|
c8c9ce7e89 | ||
|
|
cf8d27c2f3 | ||
|
|
526f3d4b0a | ||
|
|
3eea086936 | ||
|
|
74c4376bbc | ||
|
|
6474afae9d | ||
|
|
f53d138b04 | ||
|
|
6563c18417 | ||
|
|
a071a73e6d | ||
|
|
fb9a534531 | ||
|
|
d220da6b6f | ||
|
|
c0a87b868f | ||
|
|
988d49e0d0 | ||
|
|
fc8c3e2167 | ||
|
|
19f9e4ed1b | ||
|
|
50e97b2abe | ||
|
|
18389c7261 | ||
|
|
4808efa0cd | ||
|
|
d51448cae6 | ||
|
|
da369005f2 | ||
|
|
b31f9e4825 | ||
|
|
34eb4c1be9 | ||
|
|
ccfd0f19b6 | ||
|
|
7db85ad33c | ||
|
|
f69bb660e0 | ||
|
|
ffaa6de5c4 | ||
|
|
315a430197 | ||
|
|
eca955d3d0 | ||
|
|
0e92425c0e | ||
|
|
a23e7df2a4 | ||
|
|
5b0824385f | ||
|
|
95d5dc1af3 | ||
|
|
f1226548f7 | ||
|
|
bedf1fa2a6 | ||
|
|
96e3e79acc | ||
|
|
c5e1c2e74a | ||
|
|
a9b198c08f | ||
|
|
043d4a095b | ||
|
|
09f1d002f3 | ||
|
|
91dffa5da4 | ||
|
|
13a22ef3d6 | ||
|
|
d7d784f926 | ||
|
|
e411d80d4d | ||
|
|
9fb542968b | ||
|
|
e6fe92ce79 | ||
|
|
2df0c754da | ||
|
|
a119a1a9c1 | ||
|
|
5ebe4b93b3 | ||
|
|
4a9a3d86c2 | ||
|
|
1cf339c5be | ||
|
|
2c2d766502 | ||
|
|
dbeaa0b316 | ||
|
|
5396c8c6f0 | ||
|
|
13092aeac0 | ||
|
|
6aa6d47c25 | ||
|
|
38bea6e544 | ||
|
|
42a443ee9a | ||
|
|
f39cffe8ea | ||
|
|
f97c8bc586 | ||
|
|
ca78cbe447 | ||
|
|
9e21f093d8 | ||
|
|
619db30f16 | ||
|
|
9174dd647e | ||
|
|
f95758d88d | ||
|
|
7854ceb1db | ||
|
|
d8343a22cc | ||
|
|
38ab78272f | ||
|
|
45f3322832 | ||
|
|
9bbc4dbab3 | ||
|
|
b46508793e | ||
|
|
feb75da3ce | ||
|
|
46db58c196 | ||
|
|
638d4666fb | ||
|
|
91190b4175 | ||
|
|
d1a76abe21 | ||
|
|
055709dd3a | ||
|
|
f80b00793c | ||
|
|
b876e6b60d | ||
|
|
a7392981cf | ||
|
|
afb889dd21 | ||
|
|
8da7ebcbfd | ||
|
|
c90553bb05 | ||
|
|
8102325bd2 | ||
|
|
bba5fde16e | ||
|
|
416830c392 | ||
|
|
9286733482 | ||
|
|
81597f7ed9 | ||
|
|
84b8744f90 | ||
|
|
95c8547b68 | ||
|
|
ae0517b364 | ||
|
|
8ffae006c8 | ||
|
|
f7248e2d8d | ||
|
|
25ad5b4b80 | ||
|
|
3324703b88 | ||
|
|
3b5cb562a1 | ||
|
|
67e30e2f1f | ||
|
|
f650c22af8 | ||
|
|
2b90f44621 | ||
|
|
3cf1130eaa | ||
|
|
646c663366 | ||
|
|
f39fdacf8a | ||
|
|
7fe2f43d28 | ||
|
|
180a3e6bd3 | ||
|
|
afbba2d318 | ||
|
|
e3e9636dcc | ||
|
|
a4ddd867e6 | ||
|
|
f99353f45e | ||
|
|
f3c0fcde5b | ||
|
|
29cc21c7b7 | ||
|
|
40dbb27519 | ||
|
|
2111a40e47 | ||
|
|
440e9cf6a3 | ||
|
|
f76a69341e | ||
|
|
a3acefffda | ||
|
|
73dea121d7 | ||
|
|
a1732f5b3d | ||
|
|
76e73c26d5 | ||
|
|
62e3bd5a16 | ||
|
|
45f4612ed5 | ||
|
|
aabde1aadb | ||
|
|
de6f7ca20e | ||
|
|
33be15afd5 | ||
|
|
48a74b338b | ||
|
|
5064d9b4a4 | ||
|
|
d98524ab9c | ||
|
|
ddf99c39d0 | ||
|
|
691e7d448e | ||
|
|
518879b3d6 | ||
|
|
b770d7d9b2 | ||
|
|
8b978e3028 | ||
|
|
0f25e83229 | ||
|
|
f3ec5f22fa | ||
|
|
467697599f | ||
|
|
1dafb5fdce | ||
|
|
71e8acec39 | ||
|
|
6094701679 | ||
|
|
99b5a5bbfe | ||
|
|
1531e6a767 | ||
|
|
3fa80a9dab | ||
|
|
439516d7dc | ||
|
|
22c4e9cae3 | ||
|
|
2dda578d9a | ||
|
|
b65c41c5a8 | ||
|
|
f0c3cef7d4 | ||
|
|
e2e70c1bc3 | ||
|
|
70c18e9e7b | ||
|
|
102718ad1b | ||
|
|
02d26a6fd7 | ||
|
|
8279fb5319 | ||
|
|
d4e5743c9b | ||
|
|
b1e33ece5d | ||
|
|
e15d73190d | ||
|
|
4564503936 | ||
|
|
30a6b52c50 | ||
|
|
bc19f81729 | ||
|
|
989075e6c1 | ||
|
|
c60428a6fd | ||
|
|
92e2db8d99 | ||
|
|
5911ed245e | ||
|
|
1b4decf75e | ||
|
|
36756dd194 | ||
|
|
27193cc99f | ||
|
|
194732f4aa | ||
|
|
3559e0c1f2 | ||
|
|
2e4ebe4870 | ||
|
|
7d62aad5fc | ||
|
|
ef8f64cf42 | ||
|
|
afa667509f | ||
|
|
dc09280634 | ||
|
|
8115930ec8 | ||
|
|
8fc1c9786a | ||
|
|
2b9f2fd493 | ||
|
|
2867dff697 | ||
|
|
ee7eb9d48d | ||
|
|
f38a07719a | ||
|
|
af8cd7e4c9 | ||
|
|
c800b06a22 | ||
|
|
f00192f4f1 | ||
|
|
b8ddf4fa59 | ||
|
|
a9954025dd | ||
|
|
0d9b7c85db | ||
|
|
d9a592d899 | ||
|
|
ef24801fcc | ||
|
|
bde210677f | ||
|
|
d35006a0d3 | ||
|
|
29a80ba8ab | ||
|
|
3c5180d2d5 | ||
|
|
a7b73e3107 | ||
|
|
491ed77f22 | ||
|
|
e64ab182ba | ||
|
|
dade6d5889 | ||
|
|
49dc36d9e5 | ||
|
|
831d29553d | ||
|
|
1a076fc4cb | ||
|
|
5c6986f0c7 | ||
|
|
229d97f018 | ||
|
|
1fe4dc2cad | ||
|
|
9cd43532ae | ||
|
|
aff30bf63d | ||
|
|
0dafaaa0b9 | ||
|
|
6dba4debb9 | ||
|
|
3e061e9b14 | ||
|
|
d0b2835293 | ||
|
|
2da82286d2 | ||
|
|
3c6a295037 | ||
|
|
3117b241c1 | ||
|
|
f0527d08fc | ||
|
|
daf0f88366 | ||
|
|
91beaf7bc2 | ||
|
|
0f00a347a6 | ||
|
|
14cffb328a | ||
|
|
cb8e04dd62 | ||
|
|
069676fd84 | ||
|
|
86826a651a | ||
|
|
bb520c073e | ||
|
|
992b18e12a | ||
|
|
d073083757 | ||
|
|
1a80a9c6cd | ||
|
|
c032e83e4d | ||
|
|
5c9bcc85ef | ||
|
|
7ec3eb235d |
@@ -121,7 +121,7 @@ jobs:
|
||||
lint:
|
||||
working_directory: ~/nest
|
||||
docker:
|
||||
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
|
||||
- image: cimg/node:<< pipeline.parameters.current-node-version >>
|
||||
steps:
|
||||
- checkout
|
||||
- *restore-cache
|
||||
|
||||
@@ -6,8 +6,8 @@ cd "$(dirname "$0")"
|
||||
|
||||
cd /tmp/
|
||||
sudo apt-get install build-essential libssl-dev git -y
|
||||
git clone https://github.com/wg/wrk.git wrk
|
||||
git clone --depth=1 https://github.com/wg/wrk.git wrk
|
||||
cd wrk
|
||||
sudo make
|
||||
# move the executable to somewhere in your PATH, ex:
|
||||
sudo cp wrk /usr/local/bin
|
||||
sudo cp wrk /usr/local/bin
|
||||
|
||||
20
.eslintrc.js
20
.eslintrc.js
@@ -17,13 +17,13 @@ module.exports = {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-array-constructor': 'off',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
'@typescript-eslint/no-wrapper-object-types': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -34,13 +34,13 @@ module.exports = {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
'@typescript-eslint/no-wrapper-object-types': 'off',
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
packages/*/package-lock.json
|
||||
sample/*/package-lock.json
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
@@ -10,7 +11,7 @@ node_modules/
|
||||
/.devcontainer
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
/.settings
|
||||
*.code-workspace
|
||||
|
||||
# Vim
|
||||
@@ -50,3 +51,4 @@ build/config\.gypi
|
||||
|
||||
.npmrc
|
||||
pnpm-lock.yaml
|
||||
/.history
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
- "9001:9001"
|
||||
restart: always
|
||||
mysql:
|
||||
image: mysql:8.3.0
|
||||
image: mysql:9.1.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: test
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
zookeeper:
|
||||
container_name: test-zookeeper
|
||||
hostname: zookeeper
|
||||
image: confluentinc/cp-zookeeper:7.5.3
|
||||
image: confluentinc/cp-zookeeper:7.8.0
|
||||
ports:
|
||||
- "2181:2181"
|
||||
environment:
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
kafka:
|
||||
container_name: test-kafka
|
||||
hostname: kafka
|
||||
image: confluentinc/cp-kafka:7.5.3
|
||||
image: confluentinc/cp-kafka:7.8.0
|
||||
depends_on:
|
||||
- zookeeper
|
||||
ports:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Controller } from '@nestjs/common';
|
||||
import { MessagePattern, Payload } from '@nestjs/microservices';
|
||||
import { ExternalSvcService } from './external-svc.service';
|
||||
import { CreateExternalSvcDto } from './dto/create-external-svc.dto';
|
||||
import { UpdateExternalSvcDto } from './dto/update-external-svc.dto';
|
||||
import { ExternalSvcService } from './external-svc.service';
|
||||
|
||||
@Controller()
|
||||
export class ExternalSvcController {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { TransientLazyModule } from '../src/transient.module';
|
||||
import { LazyController } from '../src/lazy.controller';
|
||||
import * as chai from 'chai';
|
||||
import { expect } from 'chai';
|
||||
import * as request from 'supertest';
|
||||
|
||||
describe('Lazy Transient providers', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
controllers: [LazyController],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should not recreate dependencies for default scope', async () => {
|
||||
const resultOne = await request(app.getHttpServer()).get('/lazy/transient');
|
||||
|
||||
const resultTwo = await request(app.getHttpServer()).get('/lazy/transient');
|
||||
|
||||
expect(resultOne.text).to.be.equal('Hi! Counter is 1');
|
||||
expect(resultOne.statusCode).to.be.equal(200);
|
||||
|
||||
expect(resultTwo.text).to.be.equal('Hi! Counter is 2');
|
||||
expect(resultTwo.statusCode).to.be.equal(200);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,13 @@ import { GlobalService } from './global.module';
|
||||
|
||||
@Injectable()
|
||||
export class EagerService {
|
||||
private counter = 0;
|
||||
constructor(public globalService: GlobalService) {}
|
||||
|
||||
sayHello() {
|
||||
this.counter++;
|
||||
return 'Hi! Counter is ' + this.counter;
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
|
||||
18
integration/lazy-modules/src/lazy.controller.ts
Normal file
18
integration/lazy-modules/src/lazy.controller.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { LazyModuleLoader } from '@nestjs/core';
|
||||
|
||||
@Controller('lazy')
|
||||
export class LazyController {
|
||||
constructor(private lazyLoadModule: LazyModuleLoader) {}
|
||||
|
||||
@Get('transient')
|
||||
async exec() {
|
||||
const { TransientLazyModule } = await import('./transient.module');
|
||||
const moduleRef = await this.lazyLoadModule.load(() => TransientLazyModule);
|
||||
|
||||
const { TransientService } = await import('./transient.service');
|
||||
const _service = await moduleRef.resolve(TransientService);
|
||||
|
||||
return _service.eager();
|
||||
}
|
||||
}
|
||||
11
integration/lazy-modules/src/transient.module.ts
Normal file
11
integration/lazy-modules/src/transient.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GlobalService } from './global.module';
|
||||
import { EagerService } from './eager.module';
|
||||
import { TransientService } from './transient.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [TransientService, GlobalService, EagerService],
|
||||
exports: [TransientService],
|
||||
})
|
||||
export class TransientLazyModule {}
|
||||
11
integration/lazy-modules/src/transient.service.ts
Normal file
11
integration/lazy-modules/src/transient.service.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Injectable, Scope } from '@nestjs/common';
|
||||
import { EagerService } from './eager.module';
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class TransientService {
|
||||
constructor(private eagerService: EagerService) {}
|
||||
|
||||
eager() {
|
||||
return this.eagerService.sayHello();
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,9 @@ export class DisconnectedClientController {
|
||||
return throwError(() =>
|
||||
code === 'ECONNREFUSED' ||
|
||||
code === 'CONN_ERR' ||
|
||||
code === 'ENOTFOUND' ||
|
||||
code === 'CONNECTION_REFUSED' ||
|
||||
error.message === 'Connection is closed.'
|
||||
error.message.includes('Connection is closed.')
|
||||
? new RequestTimeoutException('ECONNREFUSED')
|
||||
: new InternalServerErrorException(),
|
||||
);
|
||||
|
||||
@@ -27,10 +27,14 @@ describe('Durable providers', () => {
|
||||
tenantId: number,
|
||||
end: (err?: any) => void,
|
||||
endpoint = '/durable',
|
||||
opts: {
|
||||
forceError: boolean;
|
||||
} = { forceError: false },
|
||||
) =>
|
||||
request(server)
|
||||
.get(endpoint)
|
||||
.set({ ['x-tenant-id']: tenantId })
|
||||
.set({ ['x-tenant-id']: String(tenantId) })
|
||||
.set({ ['x-force-error']: opts.forceError ? 'true' : 'false' })
|
||||
.end((err, res) => {
|
||||
if (err) return end(err);
|
||||
end(res);
|
||||
@@ -84,6 +88,42 @@ describe('Durable providers', () => {
|
||||
);
|
||||
expect(result.body).deep.equal({ tenantId: '3' });
|
||||
});
|
||||
|
||||
it(`should return the same tenantId both from durable request scoped service and non-durable request scoped service`, async () => {
|
||||
let result: request.Response;
|
||||
result = await new Promise<request.Response>(resolve =>
|
||||
performHttpCall(1, resolve, '/durable/request-context'),
|
||||
);
|
||||
expect(result.body).deep.equal({
|
||||
durableService: '1',
|
||||
nonDurableService: '1',
|
||||
});
|
||||
|
||||
result = await new Promise<request.Response>(resolve =>
|
||||
performHttpCall(2, resolve, '/durable/request-context'),
|
||||
);
|
||||
expect(result.body).deep.equal({
|
||||
durableService: '2',
|
||||
nonDurableService: '2',
|
||||
});
|
||||
});
|
||||
|
||||
it(`should not cache durable providers that throw errors`, async () => {
|
||||
let result: request.Response;
|
||||
|
||||
result = await new Promise<request.Response>(resolve =>
|
||||
performHttpCall(10, resolve, '/durable/echo', { forceError: true }),
|
||||
);
|
||||
|
||||
expect(result.statusCode).equal(412);
|
||||
|
||||
// The second request should be successful
|
||||
result = await new Promise<request.Response>(resolve =>
|
||||
performHttpCall(10, resolve, '/durable/echo'),
|
||||
);
|
||||
|
||||
expect(result.body).deep.equal({ tenantId: '10' });
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { ContextId, ContextIdStrategy, HostComponentInfo } from '@nestjs/core';
|
||||
import { Request } from 'express';
|
||||
|
||||
export type TenantContext = {
|
||||
tenantId: string;
|
||||
forceError?: boolean;
|
||||
};
|
||||
|
||||
const tenants = new Map<string, ContextId>();
|
||||
|
||||
export class DurableContextIdStrategy implements ContextIdStrategy {
|
||||
attach(contextId: ContextId, request: Request) {
|
||||
const tenantId = request.headers['x-tenant-id'] as string;
|
||||
const forceError = request.headers['x-force-error'] === 'true';
|
||||
|
||||
let tenantSubTreeId: ContextId;
|
||||
|
||||
if (tenants.has(tenantId)) {
|
||||
@@ -14,10 +21,15 @@ export class DurableContextIdStrategy implements ContextIdStrategy {
|
||||
tenantSubTreeId = { id: +tenantId } as ContextId;
|
||||
tenants.set(tenantId, tenantSubTreeId);
|
||||
}
|
||||
|
||||
const payload: TenantContext = { tenantId };
|
||||
if (forceError) {
|
||||
payload.forceError = true;
|
||||
}
|
||||
return {
|
||||
resolve: (info: HostComponentInfo) =>
|
||||
info.isTreeDurable ? tenantSubTreeId : contextId,
|
||||
payload: { tenantId },
|
||||
payload,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { DurableService } from './durable.service';
|
||||
import { NonDurableService } from './non-durable.service';
|
||||
|
||||
@Controller('durable')
|
||||
export class DurableController {
|
||||
constructor(private readonly durableService: DurableService) {}
|
||||
constructor(
|
||||
private readonly durableService: DurableService,
|
||||
private readonly nonDurableService: NonDurableService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
greeting(): string {
|
||||
@@ -12,6 +16,16 @@ export class DurableController {
|
||||
|
||||
@Get('echo')
|
||||
echo() {
|
||||
return this.durableService.requestPayload;
|
||||
return {
|
||||
tenantId: this.durableService.getTenantId(),
|
||||
};
|
||||
}
|
||||
|
||||
@Get('request-context')
|
||||
getRequestContext() {
|
||||
return {
|
||||
durableService: this.durableService.getTenantId(),
|
||||
nonDurableService: this.nonDurableService.getTenantId(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import { APP_GUARD } from '@nestjs/core';
|
||||
import { DurableController } from './durable.controller';
|
||||
import { DurableGuard } from './durable.guard';
|
||||
import { DurableService } from './durable.service';
|
||||
import { NonDurableService } from './non-durable.service';
|
||||
|
||||
@Module({
|
||||
controllers: [DurableController],
|
||||
providers: [
|
||||
DurableService,
|
||||
NonDurableService,
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: DurableGuard,
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import {
|
||||
Inject,
|
||||
Injectable,
|
||||
PreconditionFailedException,
|
||||
Scope,
|
||||
} from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { TenantContext } from './durable-context-id.strategy';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST, durable: true })
|
||||
export class DurableService {
|
||||
public instanceCounter = 0;
|
||||
|
||||
constructor(@Inject(REQUEST) public readonly requestPayload: unknown) {}
|
||||
constructor(
|
||||
@Inject(REQUEST) private readonly requestPayload: TenantContext,
|
||||
) {
|
||||
if (requestPayload.forceError) {
|
||||
throw new PreconditionFailedException('Forced error');
|
||||
}
|
||||
}
|
||||
|
||||
greeting() {
|
||||
++this.instanceCounter;
|
||||
return `Hello world! Counter: ${this.instanceCounter}`;
|
||||
}
|
||||
|
||||
getTenantId() {
|
||||
return this.requestPayload.tenantId;
|
||||
}
|
||||
}
|
||||
|
||||
14
integration/scopes/src/durable/non-durable.service.ts
Normal file
14
integration/scopes/src/durable/non-durable.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { TenantContext } from './durable-context-id.strategy';
|
||||
|
||||
@Injectable()
|
||||
export class NonDurableService {
|
||||
constructor(
|
||||
@Inject(REQUEST) private readonly requestPayload: TenantContext,
|
||||
) {}
|
||||
|
||||
getTenantId() {
|
||||
return this.requestPayload.tenantId;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import * as EventSource from 'eventsource';
|
||||
import { io } from 'socket.io-client';
|
||||
import { AppController as LongConnectionController } from '../../nest-application/sse/src/app.controller';
|
||||
import { ApplicationGateway } from '../src/app.gateway';
|
||||
import { NamespaceGateway } from '../src/namespace.gateway';
|
||||
import { ServerGateway } from '../src/server.gateway';
|
||||
@@ -15,7 +17,7 @@ async function createNestApp(...gateways): Promise<INestApplication> {
|
||||
}
|
||||
|
||||
describe('WebSocketGateway', () => {
|
||||
let ws, app;
|
||||
let ws: ReturnType<typeof io>, app: INestApplication;
|
||||
|
||||
it(`should handle message (2nd port)`, async () => {
|
||||
app = await createNestApp(ApplicationGateway);
|
||||
@@ -98,5 +100,78 @@ describe('WebSocketGateway', () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('shared server for WS and Long-Running connections', () => {
|
||||
afterEach(() => {});
|
||||
it('should block application shutdown', function (done) {
|
||||
let eventSource;
|
||||
|
||||
(async () => {
|
||||
this.timeout(30000);
|
||||
|
||||
setTimeout(() => {
|
||||
const instance = testingModule.get(ServerGateway);
|
||||
expect(instance.onApplicationShutdown.called).to.be.false;
|
||||
eventSource.close();
|
||||
done();
|
||||
}, 25000);
|
||||
|
||||
const testingModule = await Test.createTestingModule({
|
||||
providers: [ServerGateway],
|
||||
controllers: [LongConnectionController],
|
||||
}).compile();
|
||||
app = testingModule.createNestApplication();
|
||||
|
||||
await app.listen(3000);
|
||||
|
||||
ws = io(`http://localhost:3000`);
|
||||
eventSource = new EventSource(`http://localhost:3000/sse`);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
ws.on('connect', resolve);
|
||||
ws.on('error', reject);
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
eventSource.onmessage = resolve;
|
||||
eventSource.onerror = reject;
|
||||
});
|
||||
|
||||
app.close();
|
||||
})();
|
||||
});
|
||||
|
||||
it('should shutdown application immediately when forceCloseConnections is true', async () => {
|
||||
const testingModule = await Test.createTestingModule({
|
||||
providers: [ServerGateway],
|
||||
controllers: [LongConnectionController],
|
||||
}).compile();
|
||||
|
||||
app = testingModule.createNestApplication({
|
||||
forceCloseConnections: true,
|
||||
});
|
||||
|
||||
await app.listen(3000);
|
||||
|
||||
ws = io(`http://localhost:3000`);
|
||||
const eventSource = new EventSource(`http://localhost:3000/sse`);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
ws.on('connect', resolve);
|
||||
ws.on('error', reject);
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
eventSource.onmessage = resolve;
|
||||
eventSource.onerror = reject;
|
||||
});
|
||||
|
||||
await app.close();
|
||||
|
||||
const instance = testingModule.get(ServerGateway);
|
||||
expect(instance.onApplicationShutdown.called).to.be.true;
|
||||
eventSource.close();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => app.close());
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { UseInterceptors } from '@nestjs/common';
|
||||
import { OnApplicationShutdown, UseInterceptors } from '@nestjs/common';
|
||||
import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets';
|
||||
import * as Sinon from 'sinon';
|
||||
import { RequestInterceptor } from './request.interceptor';
|
||||
|
||||
@WebSocketGateway()
|
||||
export class ServerGateway {
|
||||
export class ServerGateway implements OnApplicationShutdown {
|
||||
@SubscribeMessage('push')
|
||||
onPush(client, data) {
|
||||
return {
|
||||
@@ -20,4 +21,6 @@ export class ServerGateway {
|
||||
data: { ...data, path: client.pattern },
|
||||
};
|
||||
}
|
||||
|
||||
onApplicationShutdown = Sinon.spy();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"lerna": "2.4.0",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "10.4.7"
|
||||
"packages": ["packages/*"],
|
||||
"version": "10.4.21",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
|
||||
39849
package-lock.json
generated
39849
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
104
package.json
104
package.json
@@ -41,12 +41,13 @@
|
||||
"lint:packages": "eslint 'packages/**/**.ts' --ignore-pattern 'packages/**/*.spec.ts'",
|
||||
"lint:spec": "eslint 'packages/**/**.spec.ts'",
|
||||
"prerelease": "gulp copy-misc",
|
||||
"publish": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --force-publish --access public --exact -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --force-publish --exact -m \"chore(release): publish %s release\"",
|
||||
"prepublishOnly": "npm run changelog | pbcopy",
|
||||
"publish:beta": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=beta --access public -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:next": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=next --access public --skip-git -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:rc": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=rc --access public -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:test": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --force-publish --access public --npm-tag=test --skip-git -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:legacy": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=legacy --force-publish -m \"chore(release): publish %s release\"",
|
||||
"publish:beta": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=beta -m \"chore(release): publish %s release\"",
|
||||
"publish:next": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=next --skip-git -m \"chore(release): publish %s release\"",
|
||||
"publish:rc": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --npm-tag=rc -m \"chore(release): publish %s release\"",
|
||||
"publish:test": "npm run prerelease && npm run build:prod && ./node_modules/.bin/lerna publish --force-publish --npm-tag=test --skip-git -m \"chore(release): publish %s release\"",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -59,59 +60,59 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/opencollective": "0.3.2",
|
||||
"ansis": "3.3.2",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.1",
|
||||
"cli-color": "2.0.4",
|
||||
"cors": "2.8.5",
|
||||
"express": "4.21.1",
|
||||
"express": "4.22.1",
|
||||
"fast-json-stringify": "6.0.0",
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
"graphql-subscriptions": "2.0.0",
|
||||
"file-type": "20.4.1",
|
||||
"iterare": "1.2.1",
|
||||
"object-hash": "3.0.0",
|
||||
"path-to-regexp": "3.2.0",
|
||||
"path-to-regexp": "3.3.0",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rxjs": "7.8.1",
|
||||
"socket.io": "4.8.0",
|
||||
"tslib": "2.7.0",
|
||||
"socket.io": "4.8.1",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2",
|
||||
"uuid": "10.0.0"
|
||||
"uuid": "11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/server": "4.11.0",
|
||||
"@apollo/server": "4.11.2",
|
||||
"@codechecks/client": "0.1.12",
|
||||
"@commitlint/cli": "19.5.0",
|
||||
"@commitlint/config-angular": "19.5.0",
|
||||
"@commitlint/cli": "19.6.0",
|
||||
"@commitlint/config-angular": "19.6.0",
|
||||
"@fastify/cors": "9.0.1",
|
||||
"@fastify/formbody": "7.4.0",
|
||||
"@fastify/middie": "8.3.1",
|
||||
"@fastify/middie": "8.3.3",
|
||||
"@fastify/multipart": "8.3.0",
|
||||
"@fastify/static": "7.0.4",
|
||||
"@fastify/view": "9.1.0",
|
||||
"@grpc/grpc-js": "1.11.1",
|
||||
"@grpc/grpc-js": "1.12.4",
|
||||
"@grpc/proto-loader": "0.7.13",
|
||||
"@nestjs/apollo": "12.2.0",
|
||||
"@nestjs/graphql": "12.2.0",
|
||||
"@nestjs/mongoose": "10.0.10",
|
||||
"@nestjs/apollo": "12.2.2",
|
||||
"@nestjs/graphql": "12.2.2",
|
||||
"@nestjs/mongoose": "10.1.0",
|
||||
"@nestjs/typeorm": "10.0.2",
|
||||
"@types/amqplib": "0.10.5",
|
||||
"@types/bytes": "3.1.4",
|
||||
"@types/chai": "4.3.16",
|
||||
"@types/amqplib": "0.10.6",
|
||||
"@types/bytes": "3.1.5",
|
||||
"@types/chai": "4.3.20",
|
||||
"@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.10",
|
||||
"@types/node": "22.10.0",
|
||||
"@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/supertest": "6.0.2",
|
||||
"@types/ws": "8.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.17.0",
|
||||
"@typescript-eslint/parser": "8.17.0",
|
||||
"amqp-connection-manager": "4.1.14",
|
||||
"amqplib": "0.10.4",
|
||||
"artillery": "2.0.20",
|
||||
"amqplib": "0.10.5",
|
||||
"artillery": "2.0.21",
|
||||
"body-parser": "1.20.3",
|
||||
"bytes": "3.1.2",
|
||||
"cache-manager": "5.7.6",
|
||||
@@ -119,57 +120,58 @@
|
||||
"chai": "4.5.0",
|
||||
"chai-as-promised": "7.1.2",
|
||||
"clang-format": "1.8.0",
|
||||
"concurrently": "9.0.1",
|
||||
"concurrently": "9.1.0",
|
||||
"conventional-changelog": "6.0.0",
|
||||
"core-js": "3.38.1",
|
||||
"core-js": "3.39.0",
|
||||
"coveralls": "3.1.1",
|
||||
"delete-empty": "3.0.0",
|
||||
"engine.io-client": "6.6.1",
|
||||
"engine.io-client": "6.6.2",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eventsource": "2.0.2",
|
||||
"fancy-log": "2.0.0",
|
||||
"fastify": "4.28.1",
|
||||
"graphql": "16.9.0",
|
||||
"graphql-tools": "9.0.1",
|
||||
"gulp": "4.0.2",
|
||||
"graphql-subscriptions": "2.0.0",
|
||||
"graphql-tools": "9.0.5",
|
||||
"gulp": "5.0.0",
|
||||
"gulp-clang-format": "1.0.27",
|
||||
"gulp-clean": "0.4.0",
|
||||
"gulp-sourcemaps": "3.0.0",
|
||||
"gulp-typescript": "5.0.1",
|
||||
"gulp-watch": "5.0.1",
|
||||
"http-errors": "2.0.0",
|
||||
"husky": "9.1.5",
|
||||
"husky": "9.1.7",
|
||||
"imports-loader": "5.0.0",
|
||||
"ioredis": "5.4.1",
|
||||
"json-loader": "0.5.7",
|
||||
"kafkajs": "2.2.4",
|
||||
"lerna": "2.11.0",
|
||||
"lerna": "9.0.3",
|
||||
"lerna-changelog": "2.2.0",
|
||||
"light-my-request": "6.1.0",
|
||||
"light-my-request": "6.3.0",
|
||||
"lint-staged": "15.2.10",
|
||||
"markdown-table": "2.0.0",
|
||||
"mocha": "10.7.3",
|
||||
"mongoose": "8.6.2",
|
||||
"mqtt": "5.6.0",
|
||||
"mocha": "10.8.2",
|
||||
"mongoose": "8.8.4",
|
||||
"mqtt": "5.10.3",
|
||||
"multer": "1.4.4",
|
||||
"mysql2": "3.11.3",
|
||||
"mysql2": "3.11.4",
|
||||
"nats": "2.28.2",
|
||||
"nodemon": "3.1.5",
|
||||
"nyc": "14.1.1",
|
||||
"prettier": "3.3.3",
|
||||
"nodemon": "3.1.7",
|
||||
"nyc": "17.1.0",
|
||||
"prettier": "3.4.2",
|
||||
"redis": "4.7.0",
|
||||
"rxjs-compat": "6.6.7",
|
||||
"sinon": "19.0.2",
|
||||
"sinon-chai": "3.7.0",
|
||||
"socket.io-client": "4.8.0",
|
||||
"sinon-chai": "4.0.0",
|
||||
"socket.io-client": "4.8.1",
|
||||
"subscriptions-transport-ws": "0.11.0",
|
||||
"supertest": "7.0.0",
|
||||
"ts-morph": "23.0.0",
|
||||
"ts-morph": "24.0.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.6.2",
|
||||
"typescript": "5.6.3",
|
||||
"wrk": "1.2.1",
|
||||
"ws": "8.18.0"
|
||||
},
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -524,6 +524,32 @@ export function Body(
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `rawBody` Buffer
|
||||
* property from the `req` object and populates the decorated parameter with that value.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isUndefined } from '../../utils/shared.utils';
|
||||
import {
|
||||
InjectionToken,
|
||||
Provider,
|
||||
@@ -6,15 +7,17 @@ import {
|
||||
} from '../../interfaces';
|
||||
|
||||
/**
|
||||
* check if x is OptionalFactoryDependency, based on prototype presence
|
||||
* (to avoid classes with a static 'token' field)
|
||||
* @param x
|
||||
* @returns x is OptionalFactoryDependency
|
||||
* @param value
|
||||
* @returns `true` if value is `OptionalFactoryDependency`
|
||||
*/
|
||||
function isOptionalFactoryDependency(
|
||||
x: InjectionToken | OptionalFactoryDependency,
|
||||
): x is OptionalFactoryDependency {
|
||||
return !!((x as any)?.token && !(x as any)?.prototype);
|
||||
value: InjectionToken | OptionalFactoryDependency,
|
||||
): value is OptionalFactoryDependency {
|
||||
return (
|
||||
!isUndefined((value as OptionalFactoryDependency).token) &&
|
||||
!isUndefined((value as OptionalFactoryDependency).optional) &&
|
||||
!(value as any).prototype
|
||||
);
|
||||
}
|
||||
|
||||
const mapInjectToTokens = (t: InjectionToken | OptionalFactoryDependency) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/common",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"homepage": "https://nestjs.com",
|
||||
@@ -18,8 +18,9 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-type": "20.4.1",
|
||||
"iterare": "1.2.1",
|
||||
"tslib": "2.7.0",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -3,14 +3,19 @@ import { IFile } from './interfaces';
|
||||
|
||||
export type FileTypeValidatorOptions = {
|
||||
fileType: string | RegExp;
|
||||
|
||||
/**
|
||||
* If `true`, the validator will skip the magic numbers validation.
|
||||
* This can be useful when you can't identify some files as there are no common magic numbers available for some file types.
|
||||
* @default false
|
||||
*/
|
||||
skipMagicNumbersValidation?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines the built-in FileType File Validator. It validates incoming files mime-type
|
||||
* matching a string or a regular expression. Note that this validator uses a naive strategy
|
||||
* to check the mime-type and could be fooled if the client provided a file with renamed extension.
|
||||
* (for instance, renaming a 'malicious.bat' to 'malicious.jpeg'). To handle such security issues
|
||||
* with more reliability, consider checking against the file's [magic-numbers](https://en.wikipedia.org/wiki/Magic_number_%28programming%29)
|
||||
* Defines the built-in FileTypeValidator. It validates incoming files by examining
|
||||
* their magic numbers using the file-type package, providing more reliable file type validation
|
||||
* than just checking the mimetype string.
|
||||
*
|
||||
* @see [File Validators](https://docs.nestjs.com/techniques/file-upload#validators)
|
||||
*
|
||||
@@ -20,19 +25,42 @@ export class FileTypeValidator extends FileValidator<
|
||||
FileTypeValidatorOptions,
|
||||
IFile
|
||||
> {
|
||||
buildErrorMessage(): string {
|
||||
buildErrorMessage(file?: IFile): string {
|
||||
if (file?.mimetype) {
|
||||
return `Validation failed (current file type is ${file.mimetype}, expected type is ${this.validationOptions.fileType})`;
|
||||
}
|
||||
return `Validation failed (expected type is ${this.validationOptions.fileType})`;
|
||||
}
|
||||
|
||||
isValid(file?: IFile): boolean {
|
||||
async isValid(file?: IFile): Promise<boolean> {
|
||||
if (!this.validationOptions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
!!file &&
|
||||
'mimetype' in file &&
|
||||
!!file.mimetype.match(this.validationOptions.fileType)
|
||||
);
|
||||
const isFileValid = !!file && 'mimetype' in file;
|
||||
|
||||
if (this.validationOptions.skipMagicNumbersValidation) {
|
||||
return (
|
||||
isFileValid && !!file.mimetype.match(this.validationOptions.fileType)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFileValid || !file.buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const { fileTypeFromBuffer } = (await eval(
|
||||
'import ("file-type")',
|
||||
)) as typeof import('file-type');
|
||||
|
||||
const fileType = await fileTypeFromBuffer(file.buffer);
|
||||
|
||||
return (
|
||||
!!fileType && !!fileType.mime.match(this.validationOptions.fileType)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface IFile {
|
||||
mimetype: string;
|
||||
size: number;
|
||||
buffer?: Buffer;
|
||||
}
|
||||
|
||||
@@ -116,11 +116,12 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
: value;
|
||||
}
|
||||
const originalValue = value;
|
||||
value = this.toEmptyIfNil(value);
|
||||
value = this.toEmptyIfNil(value, metatype);
|
||||
|
||||
const isNil = value !== originalValue;
|
||||
const isPrimitive = this.isPrimitive(value);
|
||||
this.stripProtoKeys(value);
|
||||
|
||||
let entity = classTransformer.plainToClass(
|
||||
metatype,
|
||||
value,
|
||||
@@ -205,11 +206,30 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
if (metatype === Number) {
|
||||
return +value;
|
||||
}
|
||||
if (metatype === String && !isUndefined(value)) {
|
||||
return String(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected toEmptyIfNil<T = any, R = any>(value: T): R | {} {
|
||||
return isNil(value) ? {} : value;
|
||||
protected toEmptyIfNil<T = any, R = any>(
|
||||
value: T,
|
||||
metatype: Type<unknown> | object,
|
||||
): R | {} {
|
||||
if (!isNil(value)) {
|
||||
return value;
|
||||
}
|
||||
if (
|
||||
typeof metatype === 'function' ||
|
||||
(metatype && 'prototype' in metatype && metatype.prototype?.constructor)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
// Builder like SWC require empty string to be returned instead of an empty object
|
||||
// when the value is nil and the metatype is not a class instance, but a plain object (enum, for example).
|
||||
// Otherwise, the error will be thrown.
|
||||
// @see https://github.com/nestjs/nest/issues/12680
|
||||
return '';
|
||||
}
|
||||
|
||||
protected stripProtoKeys(value: any) {
|
||||
|
||||
@@ -5,19 +5,30 @@ import { getInjectionProviders } from '../../../module-utils/utils/get-injection
|
||||
describe('getInjectionProviders', () => {
|
||||
it('should take only required providers', () => {
|
||||
class C {
|
||||
static token = 'a';
|
||||
static token = 'anything';
|
||||
}
|
||||
const p: Provider[] = [
|
||||
class G {
|
||||
static token = 'anything';
|
||||
static optional = true;
|
||||
}
|
||||
class H {
|
||||
static token = 'anything';
|
||||
static optional = false;
|
||||
}
|
||||
const providers: Provider[] = [
|
||||
{
|
||||
//0
|
||||
provide: 'a',
|
||||
useValue: 'a',
|
||||
},
|
||||
{
|
||||
//1
|
||||
provide: 'b',
|
||||
useValue: 'b',
|
||||
},
|
||||
C,
|
||||
C, //2
|
||||
{
|
||||
//3
|
||||
provide: 'd',
|
||||
useFactory: (c, b) => [c, b],
|
||||
inject: [
|
||||
@@ -27,23 +38,36 @@ describe('getInjectionProviders', () => {
|
||||
optional: true,
|
||||
},
|
||||
'x',
|
||||
G,
|
||||
H,
|
||||
],
|
||||
},
|
||||
{
|
||||
//4
|
||||
provide: 'e',
|
||||
useFactory: (d, b) => [d, b],
|
||||
inject: ['d', 'b'],
|
||||
},
|
||||
{
|
||||
//5
|
||||
provide: 'f',
|
||||
useValue: 'f',
|
||||
},
|
||||
G, //6
|
||||
H, //7
|
||||
];
|
||||
// should not include 'a' and 'f'
|
||||
const expected = p.slice(1, -1);
|
||||
const result = getInjectionProviders(p, ['e']);
|
||||
expect(result).to.have.length(expected.length);
|
||||
|
||||
const expected = [
|
||||
providers[1],
|
||||
providers[2],
|
||||
providers[3],
|
||||
providers[4],
|
||||
providers[6],
|
||||
providers[7],
|
||||
];
|
||||
|
||||
const result = getInjectionProviders(providers, ['e']);
|
||||
|
||||
expect(result).to.have.members(expected);
|
||||
expect(result).not.to.have.members([p[0], p[5]]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,89 +1,195 @@
|
||||
import { expect } from 'chai';
|
||||
import { IFile } from '../../../../common/pipes/file/interfaces';
|
||||
import { FileTypeValidator } from '../../../pipes';
|
||||
|
||||
describe('FileTypeValidator', () => {
|
||||
describe('isValid', () => {
|
||||
it('should return true when the file mimetype is the same as the specified', () => {
|
||||
it('should return true when the file buffer matches the specified type', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
const jpegBuffer = Buffer.from([
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
} as any;
|
||||
buffer: jpegBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return true when the file mimetype ends with the specified option type', () => {
|
||||
it('should return true when the file buffer matches the specified file extension', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'jpeg',
|
||||
});
|
||||
|
||||
const jpegBuffer = Buffer.from([
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
buffer: jpegBuffer,
|
||||
} as IFile;
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return true when the file mimetype matches the specified regexp', () => {
|
||||
it('should return true when the file buffer matches the specified regexp', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: /word/,
|
||||
fileType: /^image\//,
|
||||
});
|
||||
|
||||
const jpegBuffer = Buffer.from([
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'application/msword',
|
||||
} as any;
|
||||
mimetype: 'image/jpeg',
|
||||
buffer: jpegBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return false when the file mimetype is different from the specified', () => {
|
||||
it('should return false when the file buffer does not match the specified type', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
const pngBuffer = Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg', // Spoofed mimetype
|
||||
buffer: pngBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return false when the file buffer does not match the specified file extension', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'jpeg',
|
||||
});
|
||||
|
||||
const pngBuffer = Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/png',
|
||||
} as any;
|
||||
buffer: pngBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return false when the file mimetype does not match the provided regexp', () => {
|
||||
it('should return false when no buffer is provided', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: /mp4/,
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'image/png',
|
||||
} as any;
|
||||
mimetype: 'image/jpeg',
|
||||
} as IFile;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return false when the file mimetype was not provided', () => {
|
||||
it('should return false when no file is provided', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
const requestFile = {} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
expect(await fileTypeValidator.isValid()).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return false when no file provided', () => {
|
||||
it('should return false when no buffer is provided', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
expect(fileTypeValidator.isValid()).to.equal(false);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return true when the file buffer matches the specified regexp', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: /^image\//,
|
||||
});
|
||||
|
||||
const jpegBuffer = Buffer.from([
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
buffer: jpegBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return true when no validation options are provided', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({} as any);
|
||||
const jpegBuffer = Buffer.from([
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
buffer: jpegBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should skip magic numbers validation when the skipMagicNumbersValidation is true', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'image/jpeg',
|
||||
skipMagicNumbersValidation: true,
|
||||
});
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return false when the file buffer does not match any known type', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'unknown/type',
|
||||
});
|
||||
|
||||
const unknownBuffer = Buffer.from([
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
]);
|
||||
const requestFile = {
|
||||
mimetype: 'unknown/type',
|
||||
buffer: unknownBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return false when the buffer is empty', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
const emptyBuffer = Buffer.from([]);
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
buffer: emptyBuffer,
|
||||
} as IFile;
|
||||
|
||||
expect(await fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildErrorMessage', () => {
|
||||
it('should return a string with the format "Validation failed (expected type is #fileType)"', () => {
|
||||
it('should return a string with the format "Validation failed (expected type is #fileType)"', async () => {
|
||||
const fileType = 'image/jpeg';
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType,
|
||||
@@ -93,5 +199,85 @@ describe('FileTypeValidator', () => {
|
||||
`Validation failed (expected type is ${fileType})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should include the file type in the error message when a file is provided', async () => {
|
||||
const currentFileType = 'image/png';
|
||||
const fileType = 'image/jpeg';
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType,
|
||||
});
|
||||
|
||||
const file = { mimetype: currentFileType } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
`Validation failed (current file type is ${currentFileType}, expected type is ${fileType})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle regexp file type in error message', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: /^image\//,
|
||||
});
|
||||
const file = { mimetype: 'application/pdf' } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
`Validation failed (current file type is application/pdf, expected type is /^image\\//)`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle file extension in error message', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'jpeg',
|
||||
});
|
||||
const file = { mimetype: 'image/png' } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
'Validation failed (current file type is image/png, expected type is jpeg)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle regexp file type in error message', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: /^image\//,
|
||||
});
|
||||
const file = { mimetype: 'application/pdf' } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
`Validation failed (current file type is application/pdf, expected type is /^image\\//)`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle file extension in error message', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'jpeg',
|
||||
});
|
||||
const file = { mimetype: 'image/png' } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
'Validation failed (current file type is image/png, expected type is jpeg)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle regexp file type in error message', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: /^image\//,
|
||||
});
|
||||
const file = { mimetype: 'application/pdf' } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
`Validation failed (current file type is application/pdf, expected type is /^image\\//)`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle file extension in error message', async () => {
|
||||
const fileTypeValidator = new FileTypeValidator({
|
||||
fileType: 'jpeg',
|
||||
});
|
||||
const file = { mimetype: 'image/png' } as IFile;
|
||||
|
||||
expect(fileTypeValidator.buildErrorMessage(file)).to.equal(
|
||||
'Validation failed (current file type is image/png, expected type is jpeg)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
FileTypeValidator,
|
||||
FileValidator,
|
||||
MaxFileSizeValidator,
|
||||
ParseFilePipeBuilder,
|
||||
FileTypeValidator,
|
||||
} from '../../../pipes';
|
||||
|
||||
describe('ParseFilePipeBuilder', () => {
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { AbstractHttpAdapter } from '../adapters/http-adapter';
|
||||
|
||||
/**
|
||||
@@ -16,6 +17,8 @@ export class HttpAdapterHost<
|
||||
T extends AbstractHttpAdapter = AbstractHttpAdapter,
|
||||
> {
|
||||
private _httpAdapter?: T;
|
||||
private _listen$ = new Subject<void>();
|
||||
private isListening = false;
|
||||
|
||||
/**
|
||||
* Accessor for the underlying `HttpAdapter`
|
||||
@@ -35,4 +38,31 @@ export class HttpAdapterHost<
|
||||
get httpAdapter(): T {
|
||||
return this._httpAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observable that allows to subscribe to the `listen` event.
|
||||
* This event is emitted when the HTTP application is listening for incoming requests.
|
||||
*/
|
||||
get listen$(): Observable<void> {
|
||||
return this._listen$.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listening state of the application.
|
||||
*/
|
||||
set listening(listening: boolean) {
|
||||
this.isListening = listening;
|
||||
|
||||
if (listening) {
|
||||
this._listen$.next();
|
||||
this._listen$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the application is listening for incoming requests.
|
||||
*/
|
||||
get listening(): boolean {
|
||||
return this.isListening;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,11 @@ export class Injector {
|
||||
inquirer,
|
||||
);
|
||||
} catch (err) {
|
||||
wrapper.removeInstanceByContextId(
|
||||
this.getContextId(contextId, wrapper),
|
||||
inquirerId,
|
||||
);
|
||||
|
||||
settlementSignal.error(err);
|
||||
throw err;
|
||||
}
|
||||
@@ -338,11 +343,20 @@ export class Injector {
|
||||
wrapper: InstanceWrapper<T>,
|
||||
): [InjectorDependency[], number[]] {
|
||||
const optionalDependenciesIds = [];
|
||||
const isOptionalFactoryDep = (
|
||||
item: InjectionToken | OptionalFactoryDependency,
|
||||
): item is OptionalFactoryDependency =>
|
||||
!isUndefined((item as OptionalFactoryDependency).token) &&
|
||||
!isUndefined((item as OptionalFactoryDependency).optional);
|
||||
|
||||
/**
|
||||
* Same as the internal utility function `isOptionalFactoryDependency` from `@nestjs/common`.
|
||||
* We are duplicating it here because that one is not supposed to be exported.
|
||||
*/
|
||||
function isOptionalFactoryDependency(
|
||||
value: InjectionToken | OptionalFactoryDependency,
|
||||
): value is OptionalFactoryDependency {
|
||||
return (
|
||||
!isUndefined((value as OptionalFactoryDependency).token) &&
|
||||
!isUndefined((value as OptionalFactoryDependency).optional) &&
|
||||
!(value as any).prototype
|
||||
);
|
||||
}
|
||||
|
||||
const mapFactoryProviderInjectArray = (
|
||||
item: InjectionToken | OptionalFactoryDependency,
|
||||
@@ -351,7 +365,7 @@ export class Injector {
|
||||
if (typeof item !== 'object') {
|
||||
return item;
|
||||
}
|
||||
if (isOptionalFactoryDep(item)) {
|
||||
if (isOptionalFactoryDependency(item)) {
|
||||
if (item.optional) {
|
||||
optionalDependenciesIds.push(index);
|
||||
}
|
||||
|
||||
@@ -168,6 +168,21 @@ export class InstanceWrapper<T = any> {
|
||||
collection.set(contextId, value);
|
||||
}
|
||||
|
||||
public removeInstanceByContextId(contextId: ContextId, inquirerId?: string) {
|
||||
if (this.scope === Scope.TRANSIENT && inquirerId) {
|
||||
return this.removeInstanceByInquirerId(contextId, inquirerId);
|
||||
}
|
||||
this.values.delete(contextId);
|
||||
}
|
||||
|
||||
public removeInstanceByInquirerId(contextId: ContextId, inquirerId: string) {
|
||||
const collection = this.transientMap.get(inquirerId);
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
collection.delete(contextId);
|
||||
}
|
||||
|
||||
public addCtorMetadata(index: number, wrapper: InstanceWrapper) {
|
||||
if (!this[INSTANCE_METADATA_SYMBOL].dependencies) {
|
||||
this[INSTANCE_METADATA_SYMBOL].dependencies = [];
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
InjectionToken,
|
||||
NestModule,
|
||||
Provider,
|
||||
Scope,
|
||||
Type,
|
||||
ValueProvider,
|
||||
} from '@nestjs/common/interfaces';
|
||||
@@ -253,6 +254,11 @@ export class Module {
|
||||
return this.addCustomProvider(provider, this._providers, enhancerSubtype);
|
||||
}
|
||||
|
||||
const isAlreadyDeclared = this._providers.has(provider);
|
||||
if (this.isTransientProvider(provider) && isAlreadyDeclared) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
this._providers.set(
|
||||
provider,
|
||||
new InstanceWrapper({
|
||||
@@ -656,4 +662,8 @@ export class Module {
|
||||
const key = this.name?.toString() ?? this.token?.toString();
|
||||
return key ? UuidFactory.get(`${prefix}_${key}`) : randomStringGenerator();
|
||||
}
|
||||
|
||||
private isTransientProvider(provider: Type<any>): boolean {
|
||||
return getClassScope(provider) === Scope.TRANSIENT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,9 @@ export class MiddlewareModule<
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
const requestProviderValue = isTreeDurable ? contextId.payload : request;
|
||||
const requestProviderValue = isTreeDurable
|
||||
? contextId.payload
|
||||
: Object.assign(request, contextId.payload);
|
||||
this.container.registerRequestProvider(requestProviderValue, contextId);
|
||||
}
|
||||
return contextId;
|
||||
|
||||
@@ -54,6 +54,7 @@ export class NestApplicationContext<
|
||||
private shutdownCleanupRef?: (...args: unknown[]) => unknown;
|
||||
private _instanceLinksHost: InstanceLinksHost;
|
||||
private _moduleRefsForHooksByDistance?: Array<Module>;
|
||||
private initializationPromise?: Promise<void>;
|
||||
|
||||
protected get instanceLinksHost() {
|
||||
if (!this._instanceLinksHost) {
|
||||
@@ -234,8 +235,16 @@ export class NestApplicationContext<
|
||||
if (this.isInitialized) {
|
||||
return this;
|
||||
}
|
||||
await this.callInitHook();
|
||||
await this.callBootstrapHook();
|
||||
this.initializationPromise = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await this.callInitHook();
|
||||
await this.callBootstrapHook();
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
await this.initializationPromise;
|
||||
|
||||
this.isInitialized = true;
|
||||
return this;
|
||||
@@ -246,6 +255,7 @@ export class NestApplicationContext<
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async close(signal?: string): Promise<void> {
|
||||
await this.initializationPromise;
|
||||
await this.callDestroyHook();
|
||||
await this.callBeforeShutdownHook(signal);
|
||||
await this.dispose();
|
||||
@@ -333,6 +343,7 @@ export class NestApplicationContext<
|
||||
return;
|
||||
}
|
||||
receivedSignal = true;
|
||||
await this.initializationPromise;
|
||||
await this.callDestroyHook();
|
||||
await this.callBeforeShutdownHook(signal);
|
||||
await this.dispose();
|
||||
|
||||
@@ -294,8 +294,12 @@ export class NestApplication
|
||||
public async listen(port: number | string, hostname: string): Promise<any>;
|
||||
public async listen(port: number | string, ...args: any[]): Promise<any> {
|
||||
this.assertNotInPreviewMode('listen');
|
||||
!this.isInitialized && (await this.init());
|
||||
|
||||
if (!this.isInitialized) {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
const httpAdapterHost = this.container.getHttpAdapterHostRef();
|
||||
return new Promise((resolve, reject) => {
|
||||
const errorHandler = (e: any) => {
|
||||
this.logger.error(e?.toString?.());
|
||||
@@ -323,6 +327,8 @@ export class NestApplication
|
||||
if (address) {
|
||||
this.httpServer.removeListener('error', errorHandler);
|
||||
this.isListening = true;
|
||||
|
||||
httpAdapterHost.listening = true;
|
||||
resolve(this.httpServer);
|
||||
}
|
||||
if (isCallbackInOriginalArgs) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/core",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -32,11 +32,11 @@
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
"iterare": "1.2.1",
|
||||
"path-to-regexp": "3.3.0",
|
||||
"tslib": "2.7.0",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "10.4.7"
|
||||
"@nestjs/common": "^10.4.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
|
||||
@@ -418,7 +418,9 @@ export class RouterExplorer {
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
const requestProviderValue = isTreeDurable ? contextId.payload : request;
|
||||
const requestProviderValue = isTreeDurable
|
||||
? contextId.payload
|
||||
: Object.assign(request, contextId.payload);
|
||||
this.container.registerRequestProvider(requestProviderValue, contextId);
|
||||
}
|
||||
return contextId;
|
||||
|
||||
@@ -2,11 +2,27 @@ import { expect } from 'chai';
|
||||
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
|
||||
|
||||
describe('HttpAdapterHost', () => {
|
||||
const applicationRefHost = new HttpAdapterHost();
|
||||
let applicationRefHost: HttpAdapterHost;
|
||||
beforeEach(() => {
|
||||
applicationRefHost = new HttpAdapterHost();
|
||||
});
|
||||
|
||||
it('should wrap application reference', () => {
|
||||
const ref = {};
|
||||
applicationRefHost.httpAdapter = ref as any;
|
||||
|
||||
expect(applicationRefHost.httpAdapter).to.be.eql(ref);
|
||||
});
|
||||
|
||||
it('should emit listen event when listening is set to true', done => {
|
||||
applicationRefHost.listen$.subscribe(() => {
|
||||
expect(applicationRefHost.listening).to.be.true;
|
||||
done();
|
||||
});
|
||||
applicationRefHost.listening = true;
|
||||
});
|
||||
|
||||
it('listening should return false if the application isnt listening yet', () => {
|
||||
expect(applicationRefHost.listening).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { createContextId } from '../../helpers';
|
||||
import { STATIC_CONTEXT } from '../../injector/constants';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
|
||||
@@ -737,6 +738,53 @@ describe('InstanceWrapper', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeInstanceByContextId', () => {
|
||||
describe('without inquirer', () => {
|
||||
it('should remove instance for given context', () => {
|
||||
const wrapper = new InstanceWrapper({
|
||||
scope: Scope.TRANSIENT,
|
||||
});
|
||||
|
||||
const contextId = createContextId();
|
||||
wrapper.setInstanceByContextId(contextId, { instance: {} });
|
||||
|
||||
const existingContext = wrapper.getInstanceByContextId(contextId);
|
||||
expect(existingContext.instance).to.be.not.undefined;
|
||||
wrapper.removeInstanceByContextId(contextId);
|
||||
|
||||
const removedContext = wrapper.getInstanceByContextId(contextId);
|
||||
expect(removedContext.instance).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when transient and inquirer has been passed', () => {
|
||||
it('should remove instance for given context', () => {
|
||||
const wrapper = new InstanceWrapper({
|
||||
scope: Scope.TRANSIENT,
|
||||
});
|
||||
|
||||
wrapper.setInstanceByContextId(
|
||||
STATIC_CONTEXT,
|
||||
{ instance: {} },
|
||||
'inquirerId',
|
||||
);
|
||||
|
||||
const existingContext = wrapper.getInstanceByContextId(
|
||||
STATIC_CONTEXT,
|
||||
'inquirerId',
|
||||
);
|
||||
expect(existingContext.instance).to.be.not.undefined;
|
||||
wrapper.removeInstanceByContextId(STATIC_CONTEXT, 'inquirerId');
|
||||
|
||||
const removedContext = wrapper.getInstanceByContextId(
|
||||
STATIC_CONTEXT,
|
||||
'inquirerId',
|
||||
);
|
||||
expect(removedContext.instance).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInRequestScope', () => {
|
||||
describe('when tree and context are not static and is not transient', () => {
|
||||
it('should return true', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectionToken, Scope } from '@nestjs/common';
|
||||
import { InjectionToken, Provider, Scope } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { ContextIdFactory } from '../helpers/context-id-factory';
|
||||
@@ -7,6 +7,7 @@ import { Injector } from '../injector/injector';
|
||||
import { InstanceLoader } from '../injector/instance-loader';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { NestApplicationContext } from '../nest-application-context';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
|
||||
describe('NestApplicationContext', () => {
|
||||
class A {}
|
||||
@@ -14,6 +15,7 @@ describe('NestApplicationContext', () => {
|
||||
async function testHelper(
|
||||
injectionKey: InjectionToken,
|
||||
scope: Scope,
|
||||
additionalProviders: Array<Provider> = [],
|
||||
): Promise<NestApplicationContext> {
|
||||
const nestContainer = new NestContainer();
|
||||
const injector = new Injector();
|
||||
@@ -33,6 +35,10 @@ describe('NestApplicationContext', () => {
|
||||
moduleRef.token,
|
||||
);
|
||||
|
||||
for (const provider of additionalProviders) {
|
||||
nestContainer.addProvider(provider, moduleRef.token);
|
||||
}
|
||||
|
||||
nestContainer.addInjectable(
|
||||
{
|
||||
provide: injectionKey,
|
||||
@@ -96,6 +102,58 @@ describe('NestApplicationContext', () => {
|
||||
expect(processUp).to.be.false;
|
||||
expect(promisesResolved).to.be.true;
|
||||
});
|
||||
|
||||
it('should defer shutdown until all init hooks are resolved', async () => {
|
||||
const clock = sinon.useFakeTimers({
|
||||
toFake: ['setTimeout'],
|
||||
});
|
||||
const signal = 'SIGTERM';
|
||||
|
||||
const onModuleInitStub = sinon.stub();
|
||||
const onApplicationShutdownStub = sinon.stub();
|
||||
|
||||
class B {
|
||||
async onModuleInit() {
|
||||
await setTimeout(5000);
|
||||
onModuleInitStub();
|
||||
}
|
||||
|
||||
async onApplicationShutdown() {
|
||||
await setTimeout(1000);
|
||||
onApplicationShutdownStub();
|
||||
}
|
||||
}
|
||||
|
||||
const applicationContext = await testHelper(A, Scope.DEFAULT, [
|
||||
{ provide: B, useClass: B, scope: Scope.DEFAULT },
|
||||
]);
|
||||
applicationContext.enableShutdownHooks([signal]);
|
||||
|
||||
const ignoreProcessSignal = () => {
|
||||
// noop to prevent process from exiting
|
||||
};
|
||||
process.on(signal, ignoreProcessSignal);
|
||||
|
||||
const deferredShutdown = async () => {
|
||||
setTimeout(1);
|
||||
process.kill(process.pid, signal);
|
||||
};
|
||||
Promise.all([applicationContext.init(), deferredShutdown()]);
|
||||
|
||||
await clock.nextAsync();
|
||||
expect(onModuleInitStub.called).to.be.false;
|
||||
expect(onApplicationShutdownStub.called).to.be.false;
|
||||
|
||||
await clock.nextAsync();
|
||||
expect(onModuleInitStub.called).to.be.true;
|
||||
expect(onApplicationShutdownStub.called).to.be.false;
|
||||
|
||||
await clock.nextAsync();
|
||||
expect(onModuleInitStub.called).to.be.true;
|
||||
expect(onApplicationShutdownStub.called).to.be.true;
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { loadPackage } from '@nestjs/common/utils/load-package.util';
|
||||
import { isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import {
|
||||
KAFKA_DEFAULT_BROKER,
|
||||
KAFKA_DEFAULT_CLIENT,
|
||||
@@ -37,6 +37,15 @@ import {
|
||||
KafkaRequestSerializer,
|
||||
} from '../serializers/kafka-request.serializer';
|
||||
import { ClientProxy } from './client-proxy';
|
||||
import {
|
||||
connectable,
|
||||
defer,
|
||||
Observable,
|
||||
Subject,
|
||||
throwError as _throw,
|
||||
} from 'rxjs';
|
||||
import { mergeMap } from 'rxjs/operators';
|
||||
import { InvalidMessageException } from '../errors/invalid-message.exception';
|
||||
|
||||
let kafkaPackage: any = {};
|
||||
|
||||
@@ -223,6 +232,58 @@ export class ClientKafka extends ClientProxy {
|
||||
return this.consumerAssignments;
|
||||
}
|
||||
|
||||
public emitBatch<TResult = any, TInput = any>(
|
||||
pattern: any,
|
||||
data: { messages: TInput[] },
|
||||
): Observable<TResult> {
|
||||
if (isNil(pattern) || isNil(data)) {
|
||||
return _throw(() => new InvalidMessageException());
|
||||
}
|
||||
const source = defer(async () => this.connect()).pipe(
|
||||
mergeMap(() => this.dispatchBatchEvent({ pattern, data })),
|
||||
);
|
||||
const connectableSource = connectable(source, {
|
||||
connector: () => new Subject(),
|
||||
resetOnDisconnect: false,
|
||||
});
|
||||
connectableSource.connect();
|
||||
return connectableSource;
|
||||
}
|
||||
|
||||
public commitOffsets(
|
||||
topicPartitions: TopicPartitionOffsetAndMetadata[],
|
||||
): Promise<void> {
|
||||
if (this.consumer) {
|
||||
return this.consumer.commitOffsets(topicPartitions);
|
||||
} else {
|
||||
throw new Error('No consumer initialized');
|
||||
}
|
||||
}
|
||||
|
||||
protected async dispatchBatchEvent<TInput = any>(
|
||||
packets: ReadPacket<{ messages: TInput[] }>,
|
||||
): Promise<any> {
|
||||
if (packets.data.messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
const pattern = this.normalizePattern(packets.pattern);
|
||||
const outgoingEvents = await Promise.all(
|
||||
packets.data.messages.map(message => {
|
||||
return this.serializer.serialize(message as any, { pattern });
|
||||
}),
|
||||
);
|
||||
|
||||
const message = Object.assign(
|
||||
{
|
||||
topic: pattern,
|
||||
messages: outgoingEvents,
|
||||
},
|
||||
this.options.send || {},
|
||||
);
|
||||
|
||||
return this.producer.send(message);
|
||||
}
|
||||
|
||||
protected async dispatchEvent(packet: OutgoingEvent): Promise<any> {
|
||||
const pattern = this.normalizePattern(packet.pattern);
|
||||
const outgoingEvent = await this.serializer.serialize(packet.data, {
|
||||
@@ -320,14 +381,4 @@ export class ClientKafka extends ClientProxy {
|
||||
this.deserializer =
|
||||
(options && options.deserializer) || new KafkaResponseDeserializer();
|
||||
}
|
||||
|
||||
public commitOffsets(
|
||||
topicPartitions: TopicPartitionOffsetAndMetadata[],
|
||||
): Promise<void> {
|
||||
if (this.consumer) {
|
||||
return this.consumer.commitOffsets(topicPartitions);
|
||||
} else {
|
||||
throw new Error('No consumer initialized');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,15 +210,22 @@ export class ClientMqtt extends ClientProxy {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...requestOptions,
|
||||
properties: {
|
||||
...requestOptions?.properties,
|
||||
// Cant just spread objects as MQTT won't deliver
|
||||
// any message with empty object as "userProperties" field
|
||||
// @url https://github.com/nestjs/nest/issues/14079
|
||||
let options: MqttRecordOptions = {};
|
||||
if (requestOptions) {
|
||||
options = { ...requestOptions };
|
||||
}
|
||||
if (this.options?.userProperties) {
|
||||
options.properties = {
|
||||
...options.properties,
|
||||
userProperties: {
|
||||
...this.options?.userProperties,
|
||||
...requestOptions?.properties?.userProperties,
|
||||
...options.properties?.userProperties,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,9 +135,7 @@ export class ClientRMQ extends ClientProxy {
|
||||
|
||||
public createClient(): AmqpConnectionManager {
|
||||
const socketOptions = this.getOptionsProp(this.options, 'socketOptions');
|
||||
return rmqPackage.connect(this.urls, {
|
||||
connectionOptions: socketOptions?.connectionOptions,
|
||||
});
|
||||
return rmqPackage.connect(this.urls, socketOptions);
|
||||
}
|
||||
|
||||
public mergeDisconnectEvent<T = any>(
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import {
|
||||
isObject,
|
||||
isNumber,
|
||||
isNil,
|
||||
isNumber,
|
||||
isObject,
|
||||
isSymbol,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
import {
|
||||
PATTERN_EXTRAS_METADATA,
|
||||
PATTERN_HANDLER_METADATA,
|
||||
PATTERN_METADATA,
|
||||
TRANSPORT_METADATA,
|
||||
PATTERN_EXTRAS_METADATA,
|
||||
} from '../constants';
|
||||
import { PatternHandler } from '../enums/pattern-handler.enum';
|
||||
import { PatternMetadata } from '../interfaces/pattern-metadata.interface';
|
||||
import { Transport } from '../enums';
|
||||
import { PatternHandler } from '../enums/pattern-handler.enum';
|
||||
import {
|
||||
InvalidGrpcDecoratorException,
|
||||
RpcDecoratorMetadata,
|
||||
} from '../errors/invalid-grpc-message-decorator.exception';
|
||||
import { PatternMetadata } from '../interfaces/pattern-metadata.interface';
|
||||
|
||||
export enum GrpcMethodStreamingType {
|
||||
NO_STREAMING = 'no_stream',
|
||||
@@ -141,7 +141,35 @@ export function GrpcStreamMethod(
|
||||
method,
|
||||
GrpcMethodStreamingType.RX_STREAMING,
|
||||
);
|
||||
return MessagePattern(metadata, Transport.GRPC)(target, key, descriptor);
|
||||
|
||||
MessagePattern(metadata, Transport.GRPC)(target, key, descriptor);
|
||||
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// Override original method to call the "drainBuffer" method on the first parameter
|
||||
// This is required to avoid premature message emission
|
||||
descriptor.value = async function (
|
||||
this: any,
|
||||
observable: any,
|
||||
...args: any[]
|
||||
) {
|
||||
const result = await Promise.resolve(
|
||||
originalMethod.apply(this, [observable, ...args]),
|
||||
);
|
||||
|
||||
// Drain buffer if "drainBuffer" method is available
|
||||
if (observable && observable.drainBuffer) {
|
||||
observable.drainBuffer();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Copy all metadata from the original method to the new one
|
||||
const metadataKeys = Reflect.getMetadataKeys(originalMethod);
|
||||
metadataKeys.forEach(metadataKey => {
|
||||
const metadataValue = Reflect.getMetadata(metadataKey, originalMethod);
|
||||
Reflect.defineMetadata(metadataKey, metadataValue, descriptor.value);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +308,9 @@ export class ListenersController {
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
const requestProviderValue = isTreeDurable ? contextId.payload : request;
|
||||
const requestProviderValue = isTreeDurable
|
||||
? contextId.payload
|
||||
: Object.assign(request, contextId.payload);
|
||||
this.container.registerRequestProvider(requestProviderValue, contextId);
|
||||
}
|
||||
return contextId;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/microservices",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"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.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "10.4.7",
|
||||
"@nestjs/core": "10.4.7"
|
||||
"@nestjs/common": "^10.4.21",
|
||||
"@nestjs/core": "^10.4.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grpc/grpc-js": "*",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import {
|
||||
EMPTY,
|
||||
Observable,
|
||||
ReplaySubject,
|
||||
Subject,
|
||||
Subscription,
|
||||
defaultIfEmpty,
|
||||
@@ -165,7 +166,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
if (!methodHandler) {
|
||||
continue;
|
||||
}
|
||||
service[methodName] = await this.createServiceMethod(
|
||||
service[methodName] = this.createServiceMethod(
|
||||
methodHandler,
|
||||
grpcService.prototype[methodName],
|
||||
streamingType,
|
||||
@@ -174,7 +175,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
return service;
|
||||
}
|
||||
|
||||
getMessageHandler(
|
||||
public getMessageHandler(
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
streaming: GrpcMethodStreamingType,
|
||||
@@ -278,7 +279,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
source: Observable<T>,
|
||||
call: GrpcCall<T>,
|
||||
): Promise<void> {
|
||||
// this promise should **not** reject, as we're handling errors in the observable for the Call
|
||||
// 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) => {
|
||||
const valuesWaitingToBeDrained: T[] = [];
|
||||
@@ -380,8 +381,11 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
call: GrpcCall,
|
||||
callback: (err: unknown, value: unknown) => void,
|
||||
) => {
|
||||
const req = new Subject<any>();
|
||||
call.on('data', (m: any) => req.next(m));
|
||||
// Needs to be a Proxy in order to buffer messages that come before handler is executed
|
||||
// This could happen if handler has any async guards or interceptors registered that would delay
|
||||
// the execution.
|
||||
const { subject, next, error, complete } = this.bufferUntilDrained();
|
||||
call.on('data', (m: any) => next(m));
|
||||
call.on('error', (e: any) => {
|
||||
// Check if error means that stream ended on other end
|
||||
const isCancelledError = String(e).toLowerCase().indexOf('cancelled');
|
||||
@@ -391,11 +395,15 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
return;
|
||||
}
|
||||
// If another error then just pass it along
|
||||
req.error(e);
|
||||
error(e);
|
||||
});
|
||||
call.on('end', () => req.complete());
|
||||
call.on('end', () => complete());
|
||||
|
||||
const handler = methodHandler(req.asObservable(), call.metadata, call);
|
||||
const handler = methodHandler(
|
||||
subject.asObservable(),
|
||||
call.metadata,
|
||||
call,
|
||||
);
|
||||
const res = this.transformToObservable(await handler);
|
||||
if (isResponseStream) {
|
||||
await this.writeObservableToGrpc(res, call);
|
||||
@@ -426,11 +434,15 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
call: GrpcCall,
|
||||
callback: (err: unknown, value: unknown) => void,
|
||||
) => {
|
||||
let handlerStream: Observable<any>;
|
||||
if (isResponseStream) {
|
||||
methodHandler(call);
|
||||
handlerStream = this.transformToObservable(await methodHandler(call));
|
||||
} else {
|
||||
methodHandler(call, callback);
|
||||
handlerStream = this.transformToObservable(
|
||||
await methodHandler(call, callback),
|
||||
);
|
||||
}
|
||||
await lastValueFrom(handlerStream);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -469,7 +481,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
this.messageHandlers.set(route, callback);
|
||||
}
|
||||
|
||||
public async createClient(): Promise<any> {
|
||||
public async createClient() {
|
||||
const channelOptions: ChannelOptions =
|
||||
this.options && this.options.channelOptions
|
||||
? this.options.channelOptions
|
||||
@@ -612,4 +624,71 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private bufferUntilDrained<T>() {
|
||||
type DrainableSubject<T> = Subject<T> & { drainBuffer: () => void };
|
||||
|
||||
const subject = new Subject<T>();
|
||||
let replayBuffer = new ReplaySubject<T>();
|
||||
let hasDrained = false;
|
||||
|
||||
function drainBuffer(this: DrainableSubject<T>) {
|
||||
if (hasDrained) {
|
||||
return;
|
||||
}
|
||||
hasDrained = true;
|
||||
|
||||
// Replay buffered values to the new subscriber
|
||||
setImmediate(() => {
|
||||
const subcription = replayBuffer.subscribe(subject);
|
||||
subcription.unsubscribe();
|
||||
replayBuffer = null;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
subject: new Proxy<DrainableSubject<T>>(subject as DrainableSubject<T>, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === 'asObservable') {
|
||||
return () => {
|
||||
const stream = subject.asObservable();
|
||||
|
||||
// "drainBuffer" will be called before the evaluation of the handler
|
||||
// but after any enhancers have been applied (e.g., `interceptors`)
|
||||
Object.defineProperty(stream, drainBuffer.name, {
|
||||
value: drainBuffer,
|
||||
});
|
||||
return stream;
|
||||
};
|
||||
}
|
||||
if (hasDrained) {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
return Reflect.get(replayBuffer, prop, receiver);
|
||||
},
|
||||
}),
|
||||
next: (value: T) => {
|
||||
if (!hasDrained) {
|
||||
replayBuffer.next(value);
|
||||
}
|
||||
subject.next(value);
|
||||
},
|
||||
error: (err: any) => {
|
||||
if (!hasDrained) {
|
||||
replayBuffer.error(err);
|
||||
}
|
||||
subject.error(err);
|
||||
},
|
||||
complete: () => {
|
||||
if (!hasDrained) {
|
||||
replayBuffer.complete();
|
||||
// Replay buffer is no longer needed
|
||||
// Return early to allow subject to complete later, after the replay buffer
|
||||
// has been drained
|
||||
return;
|
||||
}
|
||||
subject.complete();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
EachMessagePayload,
|
||||
KafkaMessage,
|
||||
} from '../../external/kafka.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Producer } from 'kafkajs';
|
||||
|
||||
describe('ClientKafka', () => {
|
||||
const topic = 'test.topic';
|
||||
@@ -549,6 +551,77 @@ describe('ClientKafka', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('emitBatch', () => {
|
||||
it(`should return an observable stream`, () => {
|
||||
const stream$ = client.emitBatch(
|
||||
{},
|
||||
{
|
||||
messages: [],
|
||||
},
|
||||
);
|
||||
expect(stream$ instanceof Observable).to.be.true;
|
||||
});
|
||||
|
||||
it(`should call "connect" immediately`, () => {
|
||||
const connectSpy = sinon.spy(client, 'connect');
|
||||
client.emitBatch(
|
||||
{},
|
||||
{
|
||||
messages: [],
|
||||
},
|
||||
);
|
||||
expect(connectSpy.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
describe('when "connect" throws', () => {
|
||||
it('should return Observable with error', () => {
|
||||
sinon.stub(client, 'connect').callsFake(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
const stream$ = client.emitBatch(
|
||||
{},
|
||||
{
|
||||
messages: [],
|
||||
},
|
||||
);
|
||||
|
||||
stream$.subscribe({
|
||||
next: () => {},
|
||||
error: err => {
|
||||
expect(err).to.be.instanceof(Error);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when is connected', () => {
|
||||
beforeEach(() => {
|
||||
sinon
|
||||
.stub(client, 'connect')
|
||||
.callsFake(() => Promise.resolve({} as Producer));
|
||||
});
|
||||
|
||||
it(`should call dispatchBatchEvent`, () => {
|
||||
const pattern = { test: 3 };
|
||||
const data = { messages: [] };
|
||||
const dispatchBatchEventSpy = sinon
|
||||
.stub()
|
||||
.callsFake(() => Promise.resolve(true));
|
||||
const stream$ = client.emitBatch(pattern, data);
|
||||
client['dispatchBatchEvent'] = dispatchBatchEventSpy;
|
||||
stream$.subscribe(() => {
|
||||
expect(dispatchBatchEventSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Observable with error', () => {
|
||||
const err$ = client.emitBatch(null, null);
|
||||
expect(err$).to.be.instanceOf(Observable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispatchEvent', () => {
|
||||
const eventMessage = {
|
||||
id: undefined,
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/platform-express",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -20,13 +20,13 @@
|
||||
"dependencies": {
|
||||
"body-parser": "1.20.3",
|
||||
"cors": "2.8.5",
|
||||
"express": "4.21.1",
|
||||
"multer": "1.4.4-lts.1",
|
||||
"tslib": "2.7.0"
|
||||
"express": "4.22.1",
|
||||
"multer": "2.0.2",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "10.4.7",
|
||||
"@nestjs/core": "10.4.7"
|
||||
"@nestjs/common": "^10.4.21",
|
||||
"@nestjs/core": "^10.4.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/platform-fastify",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@platform-fastify)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -22,9 +22,9 @@
|
||||
"@fastify/formbody": "7.4.0",
|
||||
"@fastify/middie": "8.3.3",
|
||||
"fastify": "4.28.1",
|
||||
"light-my-request": "6.1.0",
|
||||
"light-my-request": "6.3.0",
|
||||
"path-to-regexp": "3.3.0",
|
||||
"tslib": "2.7.0"
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fastify/static": "^6.0.0 || ^7.0.0",
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -82,4 +82,12 @@ export class IoAdapter extends AbstractWsAdapter {
|
||||
}
|
||||
return { data: payload };
|
||||
}
|
||||
|
||||
public close(server: Server) {
|
||||
if (this.forceCloseConnections && server.httpServer === this.httpServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
return super.close(server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/platform-socket.io",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@platform-socket.io)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -18,8 +18,8 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"socket.io": "4.8.0",
|
||||
"tslib": "2.7.0"
|
||||
"socket.io": "4.8.1",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/platform-ws",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@platform-ws)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -18,7 +18,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "2.7.0",
|
||||
"tslib": "2.8.1",
|
||||
"ws": "8.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/testing",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@testing)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -18,7 +18,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "2.7.0"
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
|
||||
@@ -94,7 +94,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
||||
<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><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>
|
||||
</tr>
|
||||
|
||||
@@ -17,6 +17,15 @@ export abstract class AbstractWsAdapter<
|
||||
> implements WebSocketAdapter<TServer, TClient, TOptions>
|
||||
{
|
||||
protected readonly httpServer: any;
|
||||
private _forceCloseConnections: boolean;
|
||||
|
||||
public set forceCloseConnections(value: boolean) {
|
||||
this._forceCloseConnections = value;
|
||||
}
|
||||
|
||||
public get forceCloseConnections(): boolean {
|
||||
return this._forceCloseConnections;
|
||||
}
|
||||
|
||||
constructor(appOrHttpServer?: INestApplicationContext | any) {
|
||||
if (appOrHttpServer && appOrHttpServer instanceof NestApplication) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/websockets",
|
||||
"version": "10.4.7",
|
||||
"version": "10.4.21",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@websockets)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
@@ -15,11 +15,11 @@
|
||||
"dependencies": {
|
||||
"iterare": "1.2.1",
|
||||
"object-hash": "3.0.0",
|
||||
"tslib": "2.7.0"
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "10.4.7",
|
||||
"@nestjs/core": "10.4.7"
|
||||
"@nestjs/common": "^10.4.21",
|
||||
"@nestjs/core": "^10.4.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NestApplicationOptions } from '@nestjs/common';
|
||||
import { InjectionToken } from '@nestjs/common/interfaces';
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
|
||||
@@ -113,8 +114,12 @@ export class SocketModule<
|
||||
}
|
||||
|
||||
private initializeAdapter() {
|
||||
const forceCloseConnections = (this.appOptions as NestApplicationOptions)
|
||||
.forceCloseConnections;
|
||||
const adapter = this.applicationConfig.getIoAdapter();
|
||||
if (adapter) {
|
||||
(adapter as AbstractWsAdapter).forceCloseConnections =
|
||||
forceCloseConnections;
|
||||
this.isAdapterInitialized = true;
|
||||
return;
|
||||
}
|
||||
@@ -124,6 +129,7 @@ export class SocketModule<
|
||||
() => require('@nestjs/platform-socket.io'),
|
||||
);
|
||||
const ioAdapter = new IoAdapter(this.httpServer);
|
||||
ioAdapter.forceCloseConnections = forceCloseConnections;
|
||||
this.applicationConfig.setIoAdapter(ioAdapter);
|
||||
|
||||
this.isAdapterInitialized = true;
|
||||
|
||||
16628
sample/01-cats-app/package-lock.json
generated
16628
sample/01-cats-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,35 +19,35 @@
|
||||
"test:e2e": "jest --config ./e2e/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.1",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rimraf": "5.0.5",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "22.10.0",
|
||||
"@types/supertest": "6.0.2",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -19,16 +19,36 @@ describe('CatsController', () => {
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return an array of cats', async () => {
|
||||
const result: Cat[] = [
|
||||
const cats: Cat[] = [
|
||||
{
|
||||
age: 2,
|
||||
breed: 'Bombay',
|
||||
name: 'Pixel',
|
||||
},
|
||||
];
|
||||
jest.spyOn(catsService, 'findAll').mockImplementation(() => result);
|
||||
// @ts-ignore
|
||||
catsService.cats = cats;
|
||||
|
||||
expect(await catsController.findAll()).toBe(result);
|
||||
expect(await catsController.findAll()).toBe(cats);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should add a new cat', async () => {
|
||||
const cat: Cat = {
|
||||
age: 2,
|
||||
breed: 'Bombay',
|
||||
name: 'Pixel',
|
||||
};
|
||||
const expectedCatArray = [cat];
|
||||
|
||||
// @ts-ignore
|
||||
expect(catsService.cats).toStrictEqual([]);
|
||||
|
||||
await catsController.create(cat);
|
||||
|
||||
// @ts-ignore
|
||||
expect(catsService.cats).toStrictEqual(expectedCatArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
48
sample/01-cats-app/src/cats/cats.service.spec.ts
Normal file
48
sample/01-cats-app/src/cats/cats.service.spec.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { CatsService } from './cats.service';
|
||||
import { Cat } from './interfaces/cat.interface';
|
||||
|
||||
describe('CatsService', () => {
|
||||
let catsService: CatsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
providers: [CatsService],
|
||||
}).compile();
|
||||
|
||||
catsService = moduleRef.get<CatsService>(CatsService);
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return an array of cats', async () => {
|
||||
const result = [
|
||||
{
|
||||
name: 'Frajola',
|
||||
age: 2,
|
||||
breed: 'Stray',
|
||||
},
|
||||
];
|
||||
//@ts-ignore
|
||||
catsService.cats = result;
|
||||
|
||||
await expect(catsService.findAll()).resolves.toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should add a new cat', async () => {
|
||||
const cat: Cat = {
|
||||
name: 'Frajola',
|
||||
age: 2,
|
||||
breed: 'Stray',
|
||||
};
|
||||
const expectedCatArray = [cat];
|
||||
//@ts-ignore
|
||||
expect(catsService.cats).toStrictEqual([]);
|
||||
|
||||
await catsService.create(cat);
|
||||
//@ts-ignore
|
||||
expect(catsService.cats).toStrictEqual(expectedCatArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@ export class CatsService {
|
||||
this.cats.push(cat);
|
||||
}
|
||||
|
||||
findAll(): Cat[] {
|
||||
return this.cats;
|
||||
findAll(): Promise<Cat[]> {
|
||||
return Promise.resolve(this.cats);
|
||||
}
|
||||
}
|
||||
|
||||
16764
sample/02-gateways/package-lock.json
generated
16764
sample/02-gateways/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,37 +19,37 @@
|
||||
"test:e2e": "jest --config ./e2e/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/platform-socket.io": "10.3.2",
|
||||
"@nestjs/websockets": "10.3.2",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"@nestjs/platform-socket.io": "10.4.13",
|
||||
"@nestjs/websockets": "10.4.13",
|
||||
"@socket.io/redis-adapter": "8.2.1",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.1",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rimraf": "5.0.5",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1",
|
||||
"socket.io": "4.7.4"
|
||||
"socket.io": "4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.4.5",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "22.10.0",
|
||||
"@types/supertest": "6.0.2",
|
||||
"@types/ws": "8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"prettier": "3.4.2",
|
||||
"redis": "4.6.13",
|
||||
"supertest": "6.3.3",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
16373
sample/03-microservices/package-lock.json
generated
16373
sample/03-microservices/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,36 +19,36 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/microservices": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/microservices": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.1",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rimraf": "5.0.5",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@types/amqplib": "0.10.4",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/amqplib": "0.10.6",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/node": "22.10.0",
|
||||
"@types/supertest": "6.0.2",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"typescript": "5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
16563
sample/04-grpc/package-lock.json
generated
16563
sample/04-grpc/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,32 +20,32 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "1.10.9",
|
||||
"@grpc/reflection": "1.0.3",
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/microservices": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@grpc/reflection": "1.0.4",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/microservices": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.1",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rimraf": "5.0.5",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/node": "22.10.0",
|
||||
"@types/supertest": "6.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ReflectionService } from '@grpc/reflection';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import { GrpcOptions, Transport } from '@nestjs/microservices';
|
||||
import { join } from 'path';
|
||||
|
||||
export const grpcClientOptions: ClientOptions = {
|
||||
export const grpcClientOptions: GrpcOptions = {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'hero', // ['hero', 'hero2']
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "3"
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8
|
||||
image: mysql:9
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
@@ -10,7 +10,7 @@ services:
|
||||
ports:
|
||||
- "3306:3306"
|
||||
db-test:
|
||||
image: mysql:8
|
||||
image: mysql:9
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
|
||||
17140
sample/05-sql-typeorm/package-lock.json
generated
17140
sample/05-sql-typeorm/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,32 +19,32 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/typeorm": "10.0.1",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"@nestjs/typeorm": "10.0.2",
|
||||
"mysql2": "3.9.8",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.5",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1",
|
||||
"typeorm": "0.3.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "20.17.9",
|
||||
"@types/supertest": "6.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
16620
sample/06-mongoose/package-lock.json
generated
16620
sample/06-mongoose/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,31 +19,31 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/mongoose": "10.0.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"mongoose": "8.0.1",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rimraf": "5.0.5",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/mongoose": "10.0.3",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"mongoose": "8.8.4",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "22.10.0",
|
||||
"@types/supertest": "6.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Types } from 'mongoose';
|
||||
import { CatsController } from './cats.controller';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
import { CatsService } from './cats.service';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
|
||||
describe('Cats Controller', () => {
|
||||
const catsServiceMock = {
|
||||
create: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
describe('CatsController', () => {
|
||||
let controller: CatsController;
|
||||
let service: CatsService;
|
||||
const createCatDto: CreateCatDto = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
|
||||
const mockCat = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
_id: 'a id',
|
||||
};
|
||||
let service: jest.Mocked<CatsService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -25,65 +22,124 @@ describe('Cats Controller', () => {
|
||||
providers: [
|
||||
{
|
||||
provide: CatsService,
|
||||
useValue: {
|
||||
findAll: jest.fn().mockResolvedValue([
|
||||
{
|
||||
name: 'Cat #1',
|
||||
breed: 'Bread #1',
|
||||
age: 4,
|
||||
},
|
||||
{
|
||||
name: 'Cat #2',
|
||||
breed: 'Breed #2',
|
||||
age: 3,
|
||||
},
|
||||
{
|
||||
name: 'Cat #3',
|
||||
breed: 'Breed #3',
|
||||
age: 2,
|
||||
},
|
||||
]),
|
||||
create: jest.fn().mockResolvedValue(createCatDto),
|
||||
},
|
||||
useValue: catsServiceMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<CatsController>(CatsController);
|
||||
service = module.get<CatsService>(CatsService);
|
||||
controller = module.get(CatsController);
|
||||
service = module.get(CatsService);
|
||||
});
|
||||
|
||||
describe('create()', () => {
|
||||
describe('create', () => {
|
||||
it('should create a new cat', async () => {
|
||||
const createSpy = jest
|
||||
.spyOn(service, 'create')
|
||||
.mockResolvedValueOnce(mockCat);
|
||||
const mockedCat = {
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
service.create.mockResolvedValueOnce(mockedCat);
|
||||
|
||||
await controller.create(createCatDto);
|
||||
expect(createSpy).toHaveBeenCalledWith(createCatDto);
|
||||
const createCatDto: CreateCatDto = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
const result = await controller.create(createCatDto);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(service.create).toHaveBeenCalledWith(createCatDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll()', () => {
|
||||
describe('findAll', () => {
|
||||
it('should return an array of cats', async () => {
|
||||
expect(controller.findAll()).resolves.toEqual([
|
||||
const mockedCats = [
|
||||
{
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #1',
|
||||
breed: 'Bread #1',
|
||||
age: 4,
|
||||
},
|
||||
{
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #2',
|
||||
breed: 'Breed #2',
|
||||
age: 3,
|
||||
},
|
||||
{
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #3',
|
||||
breed: 'Breed #3',
|
||||
age: 2,
|
||||
},
|
||||
]);
|
||||
];
|
||||
service.findAll.mockResolvedValueOnce(mockedCats);
|
||||
|
||||
const result = await controller.findAll();
|
||||
|
||||
expect(result).toEqual(mockedCats);
|
||||
expect(service.findAll).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOne', () => {
|
||||
it('should return a single cat', async () => {
|
||||
const mockedCat = {
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
service.findOne.mockResolvedValueOnce(mockedCat);
|
||||
|
||||
const id = new Types.ObjectId().toString();
|
||||
const result = await controller.findOne(id);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(service.findOne).toHaveBeenCalledWith(id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update a single cat', async () => {
|
||||
const mockedCat = {
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
service.update.mockResolvedValueOnce(mockedCat);
|
||||
|
||||
const id = new Types.ObjectId().toString();
|
||||
const updateCatDto: CreateCatDto = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
const result = await controller.update(id, updateCatDto);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(service.update).toHaveBeenCalledWith(id, updateCatDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete a single cat', async () => {
|
||||
const mockedCat = {
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
service.delete.mockResolvedValueOnce(mockedCat);
|
||||
|
||||
const id = new Types.ObjectId().toString();
|
||||
const result = await controller.delete(id);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(service.delete).toHaveBeenCalledWith(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
|
||||
import { CatsService } from './cats.service';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
import { UpdateCatDto } from './dto/update-cat.dto';
|
||||
import { Cat } from './schemas/cat.schema';
|
||||
|
||||
@Controller('cats')
|
||||
@@ -9,7 +10,7 @@ export class CatsController {
|
||||
|
||||
@Post()
|
||||
async create(@Body() createCatDto: CreateCatDto) {
|
||||
await this.catsService.create(createCatDto);
|
||||
return this.catsService.create(createCatDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@@ -22,6 +23,11 @@ export class CatsController {
|
||||
return this.catsService.findOne(id);
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
async update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
|
||||
return this.catsService.update(id, updateCatDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string) {
|
||||
return this.catsService.delete(id);
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
import { getModelToken } from '@nestjs/mongoose';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Model } from 'mongoose';
|
||||
import { Model, Types } from 'mongoose';
|
||||
import { CatsService } from './cats.service';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
import { Cat } from './schemas/cat.schema';
|
||||
|
||||
const mockCat = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
const catModelMock = {
|
||||
create: jest.fn(),
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
findByIdAndUpdate: jest.fn(),
|
||||
findByIdAndDelete: jest.fn(),
|
||||
};
|
||||
|
||||
describe('CatsService', () => {
|
||||
let service: CatsService;
|
||||
let model: Model<Cat>;
|
||||
|
||||
const catsArray = [
|
||||
{
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
},
|
||||
{
|
||||
name: 'Cat #2',
|
||||
breed: 'Breed #2',
|
||||
age: 2,
|
||||
},
|
||||
];
|
||||
let model: jest.Mocked<Model<Cat>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -33,46 +23,128 @@ describe('CatsService', () => {
|
||||
CatsService,
|
||||
{
|
||||
provide: getModelToken('Cat'),
|
||||
useValue: {
|
||||
new: jest.fn().mockResolvedValue(mockCat),
|
||||
constructor: jest.fn().mockResolvedValue(mockCat),
|
||||
find: jest.fn(),
|
||||
create: jest.fn(),
|
||||
exec: jest.fn(),
|
||||
},
|
||||
useValue: catModelMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CatsService>(CatsService);
|
||||
model = module.get<Model<Cat>>(getModelToken('Cat'));
|
||||
service = module.get(CatsService);
|
||||
model = module.get(getModelToken('Cat'));
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return all cats', async () => {
|
||||
jest.spyOn(model, 'find').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(catsArray),
|
||||
} as any);
|
||||
const cats = await service.findAll();
|
||||
expect(cats).toEqual(catsArray);
|
||||
});
|
||||
|
||||
it('should insert a new cat', async () => {
|
||||
jest.spyOn(model, 'create').mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
describe('create', () => {
|
||||
it('should insert a new cat', async () => {
|
||||
const mockedCat: CreateCatDto = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
} as any),
|
||||
);
|
||||
const newCat = await service.create({
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
model.create.mockResolvedValueOnce(mockedCat as any);
|
||||
|
||||
const createCatDto = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
const result = await service.create(createCatDto);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(model.create).toHaveBeenCalledWith(createCatDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all cats', async () => {
|
||||
const mockedCats = [
|
||||
{
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
},
|
||||
{
|
||||
name: 'Cat #2',
|
||||
breed: 'Breed #2',
|
||||
age: 2,
|
||||
},
|
||||
];
|
||||
model.find.mockReturnValueOnce({
|
||||
exec: jest.fn().mockResolvedValueOnce(mockedCats),
|
||||
} as any);
|
||||
|
||||
const result = await service.findAll();
|
||||
|
||||
expect(result).toEqual(mockedCats);
|
||||
expect(model.find).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOne', () => {
|
||||
it('should return one cat', async () => {
|
||||
const mockedCat = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
model.findOne.mockReturnValueOnce({
|
||||
exec: jest.fn().mockResolvedValueOnce(mockedCat),
|
||||
} as any);
|
||||
|
||||
const id = new Types.ObjectId().toString();
|
||||
const result = await service.findOne(id);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(model.findOne).toHaveBeenCalledWith({ _id: id });
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update a cat', async () => {
|
||||
const mockedCat = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
model.findByIdAndUpdate.mockReturnValueOnce({
|
||||
exec: jest.fn().mockResolvedValueOnce(mockedCat),
|
||||
} as any);
|
||||
|
||||
const id = new Types.ObjectId().toString();
|
||||
const updateCatDto = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
const result = await service.update(id, updateCatDto);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(model.findByIdAndUpdate).toHaveBeenCalledWith(
|
||||
{ _id: id },
|
||||
updateCatDto,
|
||||
{ new: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete a cat', async () => {
|
||||
const mockedCat = {
|
||||
name: 'Cat #1',
|
||||
breed: 'Breed #1',
|
||||
age: 4,
|
||||
};
|
||||
model.findByIdAndDelete.mockReturnValueOnce({
|
||||
exec: jest.fn().mockResolvedValueOnce(mockedCat),
|
||||
} as any);
|
||||
|
||||
const id = new Types.ObjectId().toString();
|
||||
const result = await service.delete(id);
|
||||
|
||||
expect(result).toEqual(mockedCat);
|
||||
expect(model.findByIdAndDelete).toHaveBeenCalledWith({ _id: id });
|
||||
});
|
||||
expect(newCat).toEqual(mockCat);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
import { UpdateCatDto } from './dto/update-cat.dto';
|
||||
import { Cat } from './schemas/cat.schema';
|
||||
|
||||
@Injectable()
|
||||
@@ -21,9 +22,15 @@ export class CatsService {
|
||||
return this.catModel.findOne({ _id: id }).exec();
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
async update(id: string, updateCatDto: UpdateCatDto): Promise<Cat> {
|
||||
return this.catModel
|
||||
.findByIdAndUpdate({ _id: id }, updateCatDto, { new: true })
|
||||
.exec();
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<Cat> {
|
||||
const deletedCat = await this.catModel
|
||||
.findByIdAndRemove({ _id: id })
|
||||
.findByIdAndDelete({ _id: id })
|
||||
.exec();
|
||||
return deletedCat;
|
||||
}
|
||||
|
||||
5
sample/06-mongoose/src/cats/dto/update-cat.dto.ts
Normal file
5
sample/06-mongoose/src/cats/dto/update-cat.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class UpdateCatDto {
|
||||
readonly name?: string;
|
||||
readonly age?: number;
|
||||
readonly breed?: string;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ version: "3"
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8
|
||||
image: mysql:9
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
|
||||
16698
sample/07-sequelize/package-lock.json
generated
16698
sample/07-sequelize/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,34 +19,34 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/sequelize": "10.0.0",
|
||||
"mysql2": "3.9.7",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rimraf": "5.0.5",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"@nestjs/sequelize": "10.0.1",
|
||||
"mysql2": "3.9.8",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rimraf": "5.0.10",
|
||||
"rxjs": "7.8.1",
|
||||
"sequelize": "6.36.0",
|
||||
"sequelize": "6.37.5",
|
||||
"sequelize-typescript": "2.1.6",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "20.8.7",
|
||||
"@types/supertest": "2.0.16",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "20.17.9",
|
||||
"@types/supertest": "6.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4",
|
||||
"ts-jest": "29.1.2",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
11807
sample/08-webpack/package-lock.json
generated
11807
sample/08-webpack/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,26 +12,26 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rxjs": "7.8.1",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "10.3.2",
|
||||
"@nestjs/schematics": "10.1.1",
|
||||
"@types/node": "20.8.7",
|
||||
"@nestjs/cli": "10.4.8",
|
||||
"@nestjs/schematics": "10.2.3",
|
||||
"@types/node": "22.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"start-server-webpack-plugin": "2.2.5",
|
||||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
"webpack": "5.90.3",
|
||||
"webpack": "5.96.1",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-node-externals": "3.0.0"
|
||||
}
|
||||
|
||||
15213
sample/09-babel-example/package-lock.json
generated
15213
sample/09-babel-example/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,28 +13,28 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/microservices": "10.3.2",
|
||||
"@nestjs/websockets": "10.3.2",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"@nestjs/microservices": "10.4.13",
|
||||
"@nestjs/websockets": "10.4.13",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.23.9",
|
||||
"@babel/core": "7.23.9",
|
||||
"@babel/node": "7.23.9",
|
||||
"@babel/plugin-proposal-decorators": "7.23.9",
|
||||
"@babel/plugin-transform-runtime": "7.23.9",
|
||||
"@babel/preset-env": "7.23.9",
|
||||
"@babel/register": "7.23.7",
|
||||
"@babel/runtime": "7.23.9",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@babel/cli": "7.26.4",
|
||||
"@babel/core": "7.26.0",
|
||||
"@babel/node": "7.26.0",
|
||||
"@babel/plugin-proposal-decorators": "7.25.9",
|
||||
"@babel/plugin-transform-runtime": "7.25.9",
|
||||
"@babel/preset-env": "7.26.0",
|
||||
"@babel/register": "7.25.9",
|
||||
"@babel/runtime": "7.26.0",
|
||||
"@nestjs/testing": "10.4.13",
|
||||
"jest": "29.7.0",
|
||||
"nodemon": "3.0.3",
|
||||
"prettier": "3.2.5",
|
||||
"supertest": "6.3.3"
|
||||
"nodemon": "3.1.7",
|
||||
"prettier": "3.4.2",
|
||||
"supertest": "6.3.4"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
||||
16049
sample/10-fastify/package-lock.json
generated
16049
sample/10-fastify/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user