mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
Compare commits
298 Commits
feat/numbe
...
v10.4.15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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();
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "10.4.7"
|
||||
"version": "10.4.15"
|
||||
}
|
||||
|
||||
51239
package-lock.json
generated
51239
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
90
package.json
90
package.json
@@ -59,59 +59,58 @@
|
||||
},
|
||||
"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.21.2",
|
||||
"fast-json-stringify": "6.0.0",
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
"graphql-subscriptions": "2.0.0",
|
||||
"iterare": "1.2.1",
|
||||
"object-hash": "3.0.0",
|
||||
"path-to-regexp": "3.2.0",
|
||||
"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.0.3"
|
||||
},
|
||||
"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 +118,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-tools": "9.0.5",
|
||||
"graphql-subscriptions": "2.0.0",
|
||||
"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-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.15",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"homepage": "https://nestjs.com",
|
||||
@@ -19,7 +19,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iterare": "1.2.1",
|
||||
"tslib": "2.7.0",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -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]]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.15",
|
||||
"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.15"
|
||||
},
|
||||
"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.15",
|
||||
"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.15",
|
||||
"@nestjs/core": "10.4.15"
|
||||
},
|
||||
"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.15",
|
||||
"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",
|
||||
"express": "4.21.2",
|
||||
"multer": "1.4.4-lts.1",
|
||||
"tslib": "2.7.0"
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "10.4.7",
|
||||
"@nestjs/core": "10.4.7"
|
||||
"@nestjs/common": "10.4.15",
|
||||
"@nestjs/core": "10.4.15"
|
||||
},
|
||||
"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.15",
|
||||
"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.15",
|
||||
"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.15",
|
||||
"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.15",
|
||||
"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.15",
|
||||
"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.15",
|
||||
"@nestjs/core": "10.4.15"
|
||||
},
|
||||
"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
@@ -19,30 +19,30 @@
|
||||
"test:e2e": "echo 'No e2e tests implemented yet.'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/platform-fastify": "10.3.2",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-fastify": "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",
|
||||
|
||||
16434
sample/11-swagger/package-lock.json
generated
16434
sample/11-swagger/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/platform-express": "10.3.8",
|
||||
"@nestjs/swagger": "7.3.0",
|
||||
"@nestjs/common": "10.4.13",
|
||||
"@nestjs/core": "10.4.13",
|
||||
"@nestjs/platform-express": "10.4.13",
|
||||
"@nestjs/swagger": "8.0.7",
|
||||
"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",
|
||||
|
||||
67
sample/12-graphql-schema-first/e2e/cats/cats.e2e-spec.ts
Normal file
67
sample/12-graphql-schema-first/e2e/cats/cats.e2e-spec.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
|
||||
import { Cat } from '../../src/graphql.schema';
|
||||
import { AppModule } from '../../src/app.module';
|
||||
|
||||
describe('Cats Resolver (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
let cat: Cat;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should create a new cat', async () => {
|
||||
const query = `
|
||||
mutation {
|
||||
createCat(createCatInput: { name: "Cat", age: 5 }) {
|
||||
id
|
||||
name
|
||||
age
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post('/graphql')
|
||||
.send({ query })
|
||||
.expect(200)
|
||||
.expect(response => {
|
||||
cat = response.body.data.createCat;
|
||||
expect(cat.name).toEqual('Cat');
|
||||
expect(cat.age).toEqual(5);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get all cats', async () => {
|
||||
const query = `
|
||||
query {
|
||||
cats {
|
||||
id
|
||||
name
|
||||
age
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post('/graphql')
|
||||
.send({ query })
|
||||
.expect(200)
|
||||
.expect(response => {
|
||||
const cats = response.body.data.cats;
|
||||
expect(cats[0].name).toEqual('Cat');
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user