mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
Compare commits
498 Commits
jbpionnier
...
v6.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c687b5bcda | ||
|
|
eca9a486e1 | ||
|
|
72edae181e | ||
|
|
e2f93ef226 | ||
|
|
4b73ca638e | ||
|
|
1425635190 | ||
|
|
ded6710d37 | ||
|
|
b3f51cc9c1 | ||
|
|
d7f2e4abbf | ||
|
|
3b06632a37 | ||
|
|
f3330223e8 | ||
|
|
1ce763b167 | ||
|
|
ab8e02e33e | ||
|
|
79056a0483 | ||
|
|
eda8065e02 | ||
|
|
fb72dcc0fe | ||
|
|
cc0bc31bd4 | ||
|
|
11a85cd254 | ||
|
|
fc40f218a4 | ||
|
|
d6b31c3490 | ||
|
|
97afee965c | ||
|
|
7f5fac66e9 | ||
|
|
b920eb6641 | ||
|
|
a20d06239c | ||
|
|
33c3a4ce9b | ||
|
|
b4759ad00f | ||
|
|
7fcb4d524d | ||
|
|
fd5d28a2de | ||
|
|
1e6600b482 | ||
|
|
f6176378f4 | ||
|
|
b8b7a5d3f9 | ||
|
|
2aa9f2fbeb | ||
|
|
6ed25e4102 | ||
|
|
b3f1dc0e8c | ||
|
|
8db96f3545 | ||
|
|
2d12dd7219 | ||
|
|
472fe76701 | ||
|
|
54bc519b06 | ||
|
|
628ec3279e | ||
|
|
8d21314273 | ||
|
|
d058c9fc4b | ||
|
|
8a45e55352 | ||
|
|
cfc1874d9b | ||
|
|
53203ef378 | ||
|
|
7058acf0ca | ||
|
|
caf2322a96 | ||
|
|
4214fa8103 | ||
|
|
9b6a9a0d2f | ||
|
|
ed1d3e1539 | ||
|
|
6263ff75b9 | ||
|
|
e0cc9fb6ac | ||
|
|
9741885f5d | ||
|
|
f2fd6d72df | ||
|
|
043ce9b04f | ||
|
|
8be5181e67 | ||
|
|
43ca7fb5b1 | ||
|
|
5134f6f6ba | ||
|
|
d64274ff96 | ||
|
|
0ec4becd24 | ||
|
|
1a4837fa04 | ||
|
|
56951d15de | ||
|
|
178d34c831 | ||
|
|
0137ebce00 | ||
|
|
039d32271e | ||
|
|
390b214c24 | ||
|
|
a3500f4047 | ||
|
|
c2c3dab5aa | ||
|
|
43e5bdd93b | ||
|
|
1e29999ba7 | ||
|
|
b201ac34ed | ||
|
|
e664830d5e | ||
|
|
c60b1ace78 | ||
|
|
8bbf9419a3 | ||
|
|
ad81b4e4d7 | ||
|
|
ae5e7ca728 | ||
|
|
c6e9b23e45 | ||
|
|
e5a7de868d | ||
|
|
aaed3d1c14 | ||
|
|
e5dd459a88 | ||
|
|
3403ae7c54 | ||
|
|
65de083b6a | ||
|
|
228d43f9d6 | ||
|
|
74d9ad9fb4 | ||
|
|
e50f65e866 | ||
|
|
cbbbc9d257 | ||
|
|
5e2727bf90 | ||
|
|
5fce020f08 | ||
|
|
7a6c9c5a9d | ||
|
|
335dee25cb | ||
|
|
6c9bd5a58c | ||
|
|
2a9183f4da | ||
|
|
d8188306a5 | ||
|
|
61d699ead2 | ||
|
|
fab531c991 | ||
|
|
6c397ba344 | ||
|
|
67572b7eb7 | ||
|
|
e910ef7751 | ||
|
|
99af074f9d | ||
|
|
111deb3191 | ||
|
|
662213b003 | ||
|
|
21f9486f3e | ||
|
|
9d5a777d80 | ||
|
|
932f5d5d02 | ||
|
|
b183f1c783 | ||
|
|
89c7a3e1aa | ||
|
|
4c8ed7f295 | ||
|
|
74b4a13442 | ||
|
|
297eb65823 | ||
|
|
451e2ca7ea | ||
|
|
6338782546 | ||
|
|
783467b25a | ||
|
|
0bc04142dd | ||
|
|
8ab031784e | ||
|
|
bcf2fc49b8 | ||
|
|
d86c57eeea | ||
|
|
91cfc62032 | ||
|
|
64ed7ebc55 | ||
|
|
bf1de49dcd | ||
|
|
24e34a62d5 | ||
|
|
4592401c7d | ||
|
|
0ddf5b0060 | ||
|
|
1e9c3139b5 | ||
|
|
223501cb94 | ||
|
|
3c92750465 | ||
|
|
4b2fd75cf3 | ||
|
|
0f72500966 | ||
|
|
06ac009bb4 | ||
|
|
b355af9914 | ||
|
|
405bc9bd1b | ||
|
|
9325059885 | ||
|
|
8649c33030 | ||
|
|
9c3e7033bb | ||
|
|
3b2c740bd7 | ||
|
|
035d7d4d05 | ||
|
|
a9dcb9b33e | ||
|
|
06e9720186 | ||
|
|
d1b42e29c5 | ||
|
|
8a1eb31255 | ||
|
|
555dadeb36 | ||
|
|
27315975d3 | ||
|
|
7b2b74dcc6 | ||
|
|
c944ef8816 | ||
|
|
efa5d5886c | ||
|
|
f440366f3d | ||
|
|
4f1888f9a0 | ||
|
|
a50ec037ee | ||
|
|
3735556603 | ||
|
|
6754b96039 | ||
|
|
a48257c968 | ||
|
|
f472852d44 | ||
|
|
49e028c4fe | ||
|
|
f5d286b556 | ||
|
|
6966fd7df2 | ||
|
|
ce6110cf32 | ||
|
|
b1ecb73da6 | ||
|
|
efaeb40e52 | ||
|
|
1ad68cc0bc | ||
|
|
73b27cafa9 | ||
|
|
db616f7cee | ||
|
|
83afa42ee0 | ||
|
|
a5086dcb27 | ||
|
|
526445ab00 | ||
|
|
8206917d0e | ||
|
|
a92c57e53b | ||
|
|
27a02a7745 | ||
|
|
78a58ac69c | ||
|
|
ae4216361e | ||
|
|
c472a7556a | ||
|
|
e48c90e69e | ||
|
|
2f96c8568d | ||
|
|
803b408f0f | ||
|
|
6cdc31abd2 | ||
|
|
5c06f1528f | ||
|
|
cbb7022dc2 | ||
|
|
4a69ddefac | ||
|
|
e33ed082f0 | ||
|
|
7f49452516 | ||
|
|
f58e87ef4f | ||
|
|
94b884a0fa | ||
|
|
5a2216e58b | ||
|
|
4c9eb5a4ac | ||
|
|
6e4b19d0f7 | ||
|
|
dcb0ca77e8 | ||
|
|
c7e1d8e293 | ||
|
|
a026e4b564 | ||
|
|
21a2bdaee9 | ||
|
|
e4f1a1dde2 | ||
|
|
54530247d4 | ||
|
|
cbc9b987fb | ||
|
|
eeb260acde | ||
|
|
85fb17b32b | ||
|
|
78732403b8 | ||
|
|
4608b6e8e3 | ||
|
|
d01497c0cb | ||
|
|
ef6591674a | ||
|
|
808b6d0627 | ||
|
|
b36b0f0f03 | ||
|
|
8b6dd09ca5 | ||
|
|
f9a42cf442 | ||
|
|
6cd93a9918 | ||
|
|
95e2f7e9ed | ||
|
|
4a4fd2e0e0 | ||
|
|
980f98d6b9 | ||
|
|
1283fd3201 | ||
|
|
e59eb97585 | ||
|
|
13697371a4 | ||
|
|
ec14c921ad | ||
|
|
b12328d0b8 | ||
|
|
9783ac2437 | ||
|
|
7d463c64a4 | ||
|
|
5e8fd1e690 | ||
|
|
71c4bf31cb | ||
|
|
7149f96e11 | ||
|
|
68f33be063 | ||
|
|
4259eb409e | ||
|
|
1d0a9fdc01 | ||
|
|
c7ef4c8778 | ||
|
|
fb02c35e57 | ||
|
|
d78cd72164 | ||
|
|
fb96560cba | ||
|
|
c6e84b6ce3 | ||
|
|
371554a00e | ||
|
|
a9658634e0 | ||
|
|
cdd38dc018 | ||
|
|
bab370cf6d | ||
|
|
981a64ebae | ||
|
|
7f3f022d1c | ||
|
|
685a1ff2c6 | ||
|
|
4a66e75d62 | ||
|
|
4c775b7e24 | ||
|
|
7f1dd41cc6 | ||
|
|
8a7f4df9cb | ||
|
|
960dafcd33 | ||
|
|
dbe952bb1b | ||
|
|
1847f14840 | ||
|
|
57a7b7325d | ||
|
|
09eb4c14e3 | ||
|
|
f6252a49ac | ||
|
|
9a3c8af62c | ||
|
|
1f790a6f4c | ||
|
|
2f4c842c01 | ||
|
|
dfd87b43f7 | ||
|
|
c6d52e5be6 | ||
|
|
8d7375fa1c | ||
|
|
c5c4bf314e | ||
|
|
e888b65106 | ||
|
|
37fa9d529e | ||
|
|
5922e7ed9c | ||
|
|
efb4421280 | ||
|
|
85c2483d60 | ||
|
|
313bc08cda | ||
|
|
ce5d3793f8 | ||
|
|
5cd03dc720 | ||
|
|
baa475a98e | ||
|
|
9775b2c74a | ||
|
|
7b1f2b92fa | ||
|
|
669c110d45 | ||
|
|
348c1caa3e | ||
|
|
026bb027a9 | ||
|
|
9a6f2a96ad | ||
|
|
03fa137dc6 | ||
|
|
5e935e8aae | ||
|
|
cf6ce1f31c | ||
|
|
0c4818281f | ||
|
|
7311b956a5 | ||
|
|
f045eb2b09 | ||
|
|
71b35c1b63 | ||
|
|
5eb65422da | ||
|
|
3fd5e90b84 | ||
|
|
e7804eb8d8 | ||
|
|
292b9fbb25 | ||
|
|
c70f42c5ac | ||
|
|
18288965ca | ||
|
|
4ba700af7e | ||
|
|
10ea259ba0 | ||
|
|
c434a80e75 | ||
|
|
536f4db3c0 | ||
|
|
3898d4f199 | ||
|
|
9d44042d0b | ||
|
|
44c54ed93e | ||
|
|
1b6e07c907 | ||
|
|
e41a025fc5 | ||
|
|
d9e96208fb | ||
|
|
0ef55c0b65 | ||
|
|
7a72c1d4ec | ||
|
|
22a54d714f | ||
|
|
1dcb7aefbe | ||
|
|
82e52fc3e8 | ||
|
|
ea2d32669e | ||
|
|
d2cadc12c1 | ||
|
|
0363108120 | ||
|
|
107b243c8b | ||
|
|
758201cf94 | ||
|
|
205d737214 | ||
|
|
b83357e2e9 | ||
|
|
0712346adc | ||
|
|
d465d1b8eb | ||
|
|
f5f0ff48bd | ||
|
|
1dfc81459c | ||
|
|
22ec505ff6 | ||
|
|
868aa2d07d | ||
|
|
d8c241e64f | ||
|
|
bed8e0a7f5 | ||
|
|
48137df07d | ||
|
|
c687421c03 | ||
|
|
547615afd7 | ||
|
|
b04d2bbc91 | ||
|
|
f8a335dc11 | ||
|
|
32aa7f1507 | ||
|
|
85c290dda5 | ||
|
|
0ba5424af1 | ||
|
|
9e76f416c1 | ||
|
|
098eb3b4f6 | ||
|
|
c29144c5c0 | ||
|
|
4d267f643c | ||
|
|
ee8b112504 | ||
|
|
2fa9989072 | ||
|
|
21c22d5af3 | ||
|
|
1a64c893c7 | ||
|
|
8ce7cfd677 | ||
|
|
7d31f7a7ab | ||
|
|
e444600f4d | ||
|
|
24f67c911a | ||
|
|
dba1bb8d86 | ||
|
|
347d51d77f | ||
|
|
59267796ec | ||
|
|
4152540051 | ||
|
|
13d8b3e9fe | ||
|
|
af0c2085bb | ||
|
|
283de2ec40 | ||
|
|
bff7901078 | ||
|
|
59304b0856 | ||
|
|
253c959e88 | ||
|
|
b056a80ead | ||
|
|
6618badc23 | ||
|
|
422880f84e | ||
|
|
33f7e6f37b | ||
|
|
417b8a72cd | ||
|
|
e7c2a4e7b1 | ||
|
|
08ea60e620 | ||
|
|
6be6f14d7c | ||
|
|
85b166fc6d | ||
|
|
8e6fb080f9 | ||
|
|
00e121a702 | ||
|
|
8ebd26d586 | ||
|
|
40769fcca1 | ||
|
|
9311abb33f | ||
|
|
8a7bc79297 | ||
|
|
9ff511d6cd | ||
|
|
cf01bd29ed | ||
|
|
74a5bb5d1d | ||
|
|
6168c34607 | ||
|
|
54012efa82 | ||
|
|
7050f78851 | ||
|
|
a76e17c0ff | ||
|
|
629224b8a3 | ||
|
|
152140085b | ||
|
|
6415633097 | ||
|
|
3d6c0a4925 | ||
|
|
922bbc3e57 | ||
|
|
d5fce7845e | ||
|
|
13f44328d1 | ||
|
|
45e315741b | ||
|
|
2e895033f7 | ||
|
|
00f7c5746b | ||
|
|
be093f3f30 | ||
|
|
62004c1762 | ||
|
|
89b2f69a7e | ||
|
|
5d3d2f449d | ||
|
|
8c354e76c9 | ||
|
|
b3f69ed624 | ||
|
|
b1eac49ffc | ||
|
|
df6c27c634 | ||
|
|
37fd2caa98 | ||
|
|
12b11c38f1 | ||
|
|
feeedb44ee | ||
|
|
3588f67bef | ||
|
|
9497beb5cd | ||
|
|
d0fca77744 | ||
|
|
efacf4ad0f | ||
|
|
5ff04e756e | ||
|
|
ad07a26975 | ||
|
|
4acf1b83af | ||
|
|
e17594983b | ||
|
|
da252ca23d | ||
|
|
8293bfa519 | ||
|
|
482432d477 | ||
|
|
041f0ca992 | ||
|
|
66b13f42b9 | ||
|
|
f51d331cc2 | ||
|
|
24b0f5a583 | ||
|
|
156184ead7 | ||
|
|
0b0afac93c | ||
|
|
d1828d9026 | ||
|
|
20145b06e8 | ||
|
|
fd2854d832 | ||
|
|
20bc124e3d | ||
|
|
fd86e7e4a8 | ||
|
|
ed0dd2188c | ||
|
|
497e5fe3c3 | ||
|
|
61df77daef | ||
|
|
029082b11d | ||
|
|
5e45858c9c | ||
|
|
1988dc6bba | ||
|
|
f0caa89f5d | ||
|
|
0c511a8a42 | ||
|
|
815b66121f | ||
|
|
540abe91ac | ||
|
|
2bf4577212 | ||
|
|
ca62dc951d | ||
|
|
1a3d16a7ea | ||
|
|
de446dacfa | ||
|
|
a6743f1078 | ||
|
|
0fbc8f2860 | ||
|
|
54d5592c09 | ||
|
|
cf5e7ed3b1 | ||
|
|
2eb2a037b8 | ||
|
|
a37acf66c6 | ||
|
|
ca110dfdb8 | ||
|
|
cf7ff71694 | ||
|
|
70fa9f038b | ||
|
|
802be5c8eb | ||
|
|
69ba249337 | ||
|
|
48be3ef669 | ||
|
|
0a28c16b67 | ||
|
|
a6c0d19b1d | ||
|
|
78777bfd03 | ||
|
|
1f394247bf | ||
|
|
2ce08651bb | ||
|
|
3886e23ac9 | ||
|
|
59827181cb | ||
|
|
d9a28f2253 | ||
|
|
cf8950fd94 | ||
|
|
146c3e1fbc | ||
|
|
dbcac33499 | ||
|
|
c378b25ce4 | ||
|
|
bf5eaed54c | ||
|
|
6d9f15a2a8 | ||
|
|
ab8ce64fe8 | ||
|
|
654b746d55 | ||
|
|
349b39fb01 | ||
|
|
4cd4904b54 | ||
|
|
bf9d7c3ee1 | ||
|
|
17fa9a4b9e | ||
|
|
6489611725 | ||
|
|
3edc181150 | ||
|
|
ee2dc91357 | ||
|
|
c1b8f54104 | ||
|
|
612a18bb0e | ||
|
|
c4b7b4c016 | ||
|
|
9e05feb8c2 | ||
|
|
ffd24db6d2 | ||
|
|
c0cfb16175 | ||
|
|
d8a39a74c3 | ||
|
|
845c5df66f | ||
|
|
cd1d338d2c | ||
|
|
a39821a624 | ||
|
|
4fa54ffcdd | ||
|
|
9c3e15a902 | ||
|
|
9f5f1159ae | ||
|
|
aefdee3983 | ||
|
|
5f2a395158 | ||
|
|
01b06f3848 | ||
|
|
6d09f36cb0 | ||
|
|
01dd813f5c | ||
|
|
704fe4c700 | ||
|
|
568d321cfa | ||
|
|
be92334bb5 | ||
|
|
179a65f1ab | ||
|
|
9111d12f8a | ||
|
|
acd1a439c0 | ||
|
|
a54a5b90af | ||
|
|
4d691ce42e | ||
|
|
b0088db764 | ||
|
|
22889735cf | ||
|
|
92068f302c | ||
|
|
a09e8260cd | ||
|
|
50e91cfb44 | ||
|
|
e3a2378b9a | ||
|
|
15bbd74cf8 | ||
|
|
5ba9a94957 | ||
|
|
82910abca0 | ||
|
|
94d7ab0000 | ||
|
|
55970a1dff | ||
|
|
839fcb0f9c | ||
|
|
b318da43c8 | ||
|
|
2d56988e86 | ||
|
|
67e0c89980 | ||
|
|
bab88e328f | ||
|
|
c5acf28c4b | ||
|
|
a833d08d1f | ||
|
|
5103278f0a | ||
|
|
3a757698ba | ||
|
|
8765b989c4 | ||
|
|
a301e95e82 | ||
|
|
a56896e7ba | ||
|
|
2bd7346bb8 | ||
|
|
672dd499c3 |
65
Readme.md
65
Readme.md
@@ -7,29 +7,29 @@
|
||||
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
|
||||
[linux-url]: https://travis-ci.org/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#8" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge" target="_blank"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
<p>Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).</p>
|
||||
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
|
||||
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
|
||||
## Philosophy
|
||||
|
||||
@@ -41,35 +41,44 @@
|
||||
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
|
||||
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
|
||||
|
||||
## Consulting
|
||||
|
||||
With official support, you can get expert help straight from Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://docs.nestjs.com/enterprise).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
#### Principal Sponsor
|
||||
|
||||
<a href="https://valor-software.com/"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Base Sponsor
|
||||
|
||||
<a href="https://blueanchor.io/"><img src="https://nestjs.com/img/blueanchor.png" width="300" /></a>
|
||||
<a href="https://www.novologic.com/"><img src="https://nestjs.com/img/novologic.png" width="200" /></a>
|
||||
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Silver Sponsors
|
||||
<a href="https://neoteric.eu/"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a> <a href="https://www.swingdev.io"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="150" valign="middle" /> </a>
|
||||
<a href="https://yakaz.com/"><img src="https://nestjs.com/img/yakaz.png" width="100" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/"><img src="https://nestjs.com/img/logo-xtremis.svg" width="150" valign="middle" /></a>
|
||||
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a>
|
||||
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="150" valign="middle" /></a>
|
||||
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></a>
|
||||
|
||||
#### Sponsors
|
||||
|
||||
<a href="https://scal.io"><img src="https://nestjs.com/img/scalio-logo.svg" width="110" valign="middle" /></a> <a href="http://angularity.io"><img src="http://angularity.io/media/logo.svg" height="30" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a> <a href="https://genuinebee.com/"><img src="https://nestjs.com/img/genuinebee.svg" height="38" valign="middle" /></a> <a href="http://architectnow.net/"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a>
|
||||
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="125" valign="middle" /> </a> <a href="https://blueanchor.io/" target="_blank"><img src="https://nestjs.com/img/blueanchor.png" width="180" valign="middle" /></a>
|
||||
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="130" valign="middle" /></a>
|
||||
<a href="https://scal.io" target="_blank"><img src="https://nestjs.com/img/scalio-logo.svg" width="100" valign="middle" /></a> <a href="http://angularity.io" target="_blank"><img src="http://angularity.io/media/logo.svg" height="26" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com" target="_blank"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a>
|
||||
|
||||
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" height="36" valign="middle" /></a> <a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a> <a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" height="20" valign="middle" /></a>
|
||||
<a href="https://buddy.works/" target="_blank"><img src="https://nestjs.com/img/buddy-logo.svg" height="35" valign="middle" /></a>
|
||||
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" height="31" valign="middle" /></a>
|
||||
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" height="28" valign="middle" /></a> <a href="https://yakaz.com/" target="_blank"><img src="https://nestjs.com/img/yakaz.png" width="80" valign="middle" /></a>
|
||||
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="120" valign="middle" /></a>
|
||||
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="90" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/" target="_blank"><img src="https://nestjs.com/img/logo-xtremis.svg" width="145" valign="middle" /></a>
|
||||
|
||||
<a href="https://triplebyte.com/"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
<a href="https://opencollective.com/nest"><img src="https://opencollective.com/nest/backers.svg?width=890"></a>
|
||||
<a href="https://opencollective.com/nest" target="_blank"><img src="https://opencollective.com/nest/backers.svg?width=1600"></a>
|
||||
|
||||
## Stay in touch
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ services:
|
||||
- "9001:9001"
|
||||
restart: always
|
||||
mysql:
|
||||
image: mysql:5.7.25
|
||||
restart: always
|
||||
image: mysql:5.7.26
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: test
|
||||
@@ -43,4 +42,4 @@ services:
|
||||
ports:
|
||||
- "15672:15672"
|
||||
- "5672:5672"
|
||||
tty: true
|
||||
tty: true
|
||||
|
||||
1515
integration/graphql/package-lock.json
generated
1515
integration/graphql/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,21 +9,21 @@
|
||||
"start:prod": "node dist/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "6.0.1",
|
||||
"@nestjs/core": "6.0.1",
|
||||
"@nestjs/graphql": "6.0.1",
|
||||
"apollo-server-express": "2.4.8",
|
||||
"graphql": "14.1.1",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/graphql": "6.2.1",
|
||||
"apollo-server-express": "2.5.0",
|
||||
"graphql": "14.3.0",
|
||||
"graphql-tools": "4.0.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs": "6.5.2",
|
||||
"subscriptions-transport-ws": "0.9.16",
|
||||
"typescript": "3.3.3333",
|
||||
"ws": "4.1.0"
|
||||
"typescript": "3.4.5",
|
||||
"ws": "7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "5.14.0"
|
||||
"@types/node": "7.10.6",
|
||||
"ts-node": "8.1.0",
|
||||
"tslint": "5.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,34 @@ export class TransformInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class StatusInterceptor {
|
||||
constructor(private statusCode: number) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler) {
|
||||
const ctx = context.switchToHttp();
|
||||
const res = ctx.getResponse();
|
||||
res.status(this.statusCode);
|
||||
return next.handle().pipe(map(data => ({ data })));
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class HeaderInterceptor {
|
||||
constructor(private headers: object) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler) {
|
||||
const ctx = context.switchToHttp();
|
||||
const res = ctx.getResponse();
|
||||
for (const key in this.headers) {
|
||||
if (this.headers.hasOwnProperty(key)) {
|
||||
res.header(key, this.headers[key]);
|
||||
}
|
||||
}
|
||||
return next.handle().pipe(map(data => ({ data })));
|
||||
}
|
||||
}
|
||||
|
||||
function createTestModule(interceptor) {
|
||||
return Test.createTestingModule({
|
||||
imports: [ApplicationModule],
|
||||
@@ -87,6 +115,33 @@ describe('Interceptors', () => {
|
||||
.expect(200, { data: 'Hello world!' });
|
||||
});
|
||||
|
||||
it(`should modify response status`, async () => {
|
||||
app = (await createTestModule(
|
||||
new StatusInterceptor(400),
|
||||
)).createNestApplication();
|
||||
|
||||
await app.init();
|
||||
return request(app.getHttpServer())
|
||||
.get('/hello')
|
||||
.expect(400, { data: 'Hello world!' });
|
||||
});
|
||||
|
||||
it(`should modify Authorization header`, async () => {
|
||||
const customHeaders = {
|
||||
Authorization: 'jwt',
|
||||
};
|
||||
|
||||
app = (await createTestModule(
|
||||
new HeaderInterceptor(customHeaders),
|
||||
)).createNestApplication();
|
||||
|
||||
await app.init();
|
||||
return request(app.getHttpServer())
|
||||
.get('/hello')
|
||||
.expect(200)
|
||||
.expect('Authorization', 'jwt');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
1168
integration/hello-world/package-lock.json
generated
1168
integration/hello-world/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,21 +7,21 @@
|
||||
"start": "ts-node src/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/microservices": "5.7.4",
|
||||
"@nestjs/testing": "5.7.4",
|
||||
"@nestjs/websockets": "5.7.4",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/microservices": "6.2.2",
|
||||
"@nestjs/testing": "6.2.2",
|
||||
"@nestjs/websockets": "6.2.2",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"fastify": "2.1.0",
|
||||
"fastify": "2.3.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"@types/node": "7.10.6",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3"
|
||||
"ts-node": "8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HelloService } from './hello.service';
|
||||
import { Controller, Get, Header, Param } from '@nestjs/common';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { HelloService } from './hello.service';
|
||||
import { UserByIdPipe } from './users/user-by-id.pipe';
|
||||
|
||||
@Controller('hello')
|
||||
|
||||
805
integration/hooks/package-lock.json
generated
805
integration/hooks/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,17 +7,17 @@
|
||||
"start": "ts-node src/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"@types/node": "7.10.6",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3"
|
||||
"ts-node": "8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
1134
integration/injector/package-lock.json
generated
1134
integration/injector/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,20 +7,20 @@
|
||||
"start": "ts-node src/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/microservices": "5.7.4",
|
||||
"@nestjs/testing": "5.7.4",
|
||||
"@nestjs/websockets": "5.7.4",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/microservices": "6.2.2",
|
||||
"@nestjs/testing": "6.2.2",
|
||||
"@nestjs/websockets": "6.2.2",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"@types/node": "7.10.6",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3"
|
||||
"ts-node": "8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('RabbitMQ transport', () => {
|
||||
urls: [`amqp://localhost:5672`],
|
||||
queue: 'test',
|
||||
queueOptions: { durable: false },
|
||||
socketOptions: { noDelay: true },
|
||||
},
|
||||
});
|
||||
await app.startAllMicroservicesAsync();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Transport } from '@nestjs/microservices';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import * as request from 'supertest';
|
||||
import { AppController } from '../src/app.controller';
|
||||
import { ApplicationModule } from '../src/app.module';
|
||||
|
||||
describe('RPC transport', () => {
|
||||
@@ -76,6 +78,18 @@ describe('RPC transport', () => {
|
||||
.expect(500);
|
||||
});
|
||||
|
||||
it(`/POST (event notification)`, done => {
|
||||
request(server)
|
||||
.post('/notify')
|
||||
.send([1, 2, 3, 4, 5])
|
||||
.end(() => {
|
||||
setTimeout(() => {
|
||||
expect(AppController.IS_NOTIFIED).to.be.true;
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
1140
integration/microservices/package-lock.json
generated
1140
integration/microservices/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,21 +7,21 @@
|
||||
"start": "ts-node src/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/microservices": "5.7.4",
|
||||
"@nestjs/testing": "5.7.4",
|
||||
"@nestjs/websockets": "5.7.4",
|
||||
"amqp-connection-manager": "2.3.0",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/microservices": "6.2.2",
|
||||
"@nestjs/testing": "6.2.2",
|
||||
"@nestjs/websockets": "6.2.2",
|
||||
"amqp-connection-manager": "2.3.1",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"@types/node": "7.10.6",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3"
|
||||
"ts-node": "8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Controller, Post, Body, Query, HttpCode } from '@nestjs/common';
|
||||
import { Body, Controller, HttpCode, Post, Query } from '@nestjs/common';
|
||||
import {
|
||||
Client,
|
||||
MessagePattern,
|
||||
ClientProxy,
|
||||
EventPattern,
|
||||
MessagePattern,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
import { Observable, of, from } from 'rxjs';
|
||||
import { from, Observable, of } from 'rxjs';
|
||||
import { scan } from 'rxjs/operators';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
static IS_NOTIFIED = false;
|
||||
|
||||
@Client({ transport: Transport.TCP })
|
||||
client: ClientProxy;
|
||||
|
||||
@@ -62,4 +65,14 @@ export class AppController {
|
||||
streaming(data: number[]): Observable<number> {
|
||||
return from(data);
|
||||
}
|
||||
|
||||
@Post('notify')
|
||||
async sendNotification(): Promise<any> {
|
||||
return this.client.emit<number>('notification', true);
|
||||
}
|
||||
|
||||
@EventPattern('notification')
|
||||
eventHandler(data: boolean) {
|
||||
AppController.IS_NOTIFIED = data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export class RMQBroadcastController {
|
||||
urls: [`amqp://localhost:5672`],
|
||||
queue: 'test_broadcast',
|
||||
queueOptions: { durable: false },
|
||||
socketOptions: { noDelay: true },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export class RMQController {
|
||||
urls: [`amqp://localhost:5672`],
|
||||
queue: 'test',
|
||||
queueOptions: { durable: false },
|
||||
socketOptions: { noDelay: true },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
997
integration/mongoose/package-lock.json
generated
997
integration/mongoose/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,18 +9,18 @@
|
||||
"start:prod": "node dist/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/mongoose": "6.0.0",
|
||||
"mongoose": "5.4.19",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/mongoose": "6.1.2",
|
||||
"mongoose": "5.5.9",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mongoose": "5.3.23",
|
||||
"@types/node": "7.10.5",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "5.14.0"
|
||||
"@types/mongoose": "5.5.1",
|
||||
"@types/node": "7.10.6",
|
||||
"ts-node": "8.1.0",
|
||||
"tslint": "5.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
1168
integration/scopes/package-lock.json
generated
1168
integration/scopes/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,21 +7,21 @@
|
||||
"start": "ts-node src/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/microservices": "5.7.4",
|
||||
"@nestjs/testing": "5.7.4",
|
||||
"@nestjs/websockets": "5.7.4",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/microservices": "6.2.2",
|
||||
"@nestjs/testing": "6.2.2",
|
||||
"@nestjs/websockets": "6.2.2",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"fastify": "2.1.0",
|
||||
"fastify": "2.3.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"@types/node": "7.10.6",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3"
|
||||
"ts-node": "8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { ApplicationModule } from '../src/app.module';
|
||||
|
||||
describe('GraphQL - Pipes', () => {
|
||||
describe('GraphQL Pipes', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
1454
integration/typegraphql/package-lock.json
generated
1454
integration/typegraphql/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,24 +9,24 @@
|
||||
"start:prod": "node dist/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "6.0.1",
|
||||
"@nestjs/core": "6.0.1",
|
||||
"@nestjs/graphql": "6.0.1",
|
||||
"apollo-server-express": "2.4.8",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/graphql": "6.2.1",
|
||||
"apollo-server-express": "2.5.0",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"graphql": "14.1.1",
|
||||
"graphql": "14.3.0",
|
||||
"graphql-tools": "4.0.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs": "6.5.2",
|
||||
"subscriptions-transport-ws": "0.9.16",
|
||||
"type-graphql": "0.17.0",
|
||||
"typescript": "3.3.3333",
|
||||
"ws": "4.1.0"
|
||||
"type-graphql": "0.17.4",
|
||||
"typescript": "3.4.5",
|
||||
"ws": "7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "5.14.0"
|
||||
"@types/node": "7.10.6",
|
||||
"ts-node": "8.1.0",
|
||||
"tslint": "5.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
1023
integration/typeorm/package-lock.json
generated
1023
integration/typeorm/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,18 +9,18 @@
|
||||
"start:prod": "node dist/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/typeorm": "6.0.0",
|
||||
"mysql": "2.16.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/typeorm": "6.1.1",
|
||||
"mysql": "2.17.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typeorm": "0.2.15",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typeorm": "0.2.17",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "5.14.0"
|
||||
"@types/node": "7.10.6",
|
||||
"ts-node": "8.1.0",
|
||||
"tslint": "5.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
1134
integration/websockets/package-lock.json
generated
1134
integration/websockets/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,20 +7,20 @@
|
||||
"start": "ts-node src/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "5.7.4",
|
||||
"@nestjs/core": "5.7.4",
|
||||
"@nestjs/microservices": "5.7.4",
|
||||
"@nestjs/testing": "5.7.4",
|
||||
"@nestjs/websockets": "5.7.4",
|
||||
"class-transformer": "0.2.0",
|
||||
"@nestjs/common": "6.2.2",
|
||||
"@nestjs/core": "6.2.2",
|
||||
"@nestjs/microservices": "6.2.2",
|
||||
"@nestjs/testing": "6.2.2",
|
||||
"@nestjs/websockets": "6.2.2",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"typescript": "3.3.3333"
|
||||
"rxjs": "6.5.2",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "7.10.5",
|
||||
"@types/node": "7.10.6",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3"
|
||||
"ts-node": "8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "6.0.1"
|
||||
"version": "6.2.4"
|
||||
}
|
||||
|
||||
4104
package-lock.json
generated
4104
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
86
package.json
86
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "nestjs",
|
||||
"version": "6.0.0",
|
||||
"version": "6.2.4",
|
||||
"description": "Modern, fast, powerful node.js web framework",
|
||||
"scripts": {
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"test": "nyc --require ts-node/register mocha packages/**/*.spec.ts --reporter spec --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"test": "nyc --require ts-node/register mocha packages/**/*.spec.ts --reporter spec --retries 3 --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"integration-test": "mocha integration/**/*.spec.ts --reporter spec --require ts-node/register --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json \"packages/**/*.ts\" -e \"*.spec.ts\"",
|
||||
"format": "prettier **/**/*.ts --ignore-path ./.prettierignore --write && git status",
|
||||
@@ -31,49 +31,48 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "0.3.0",
|
||||
"@nestjs/common": "6.0.1",
|
||||
"@nestjs/core": "6.0.1",
|
||||
"@nestjs/microservices": "6.0.1",
|
||||
"@nestjs/testing": "6.0.1",
|
||||
"@nestjs/websockets": "6.0.1",
|
||||
"@nestjs/common": "6.1.1",
|
||||
"@nestjs/core": "6.1.1",
|
||||
"@nestjs/microservices": "6.1.1",
|
||||
"@nestjs/testing": "6.1.1",
|
||||
"@nestjs/websockets": "6.1.1",
|
||||
"@nuxtjs/opencollective": "0.2.1",
|
||||
"amqp-connection-manager": "2.3.0",
|
||||
"amqp-connection-manager": "2.3.1",
|
||||
"amqplib": "0.5.3",
|
||||
"apollo-server-express": "2.4.8",
|
||||
"apollo-server-express": "2.5.0",
|
||||
"axios": "0.18.0",
|
||||
"cache-manager": "2.9.0",
|
||||
"class-transformer": "0.2.0",
|
||||
"class-transformer": "0.2.2",
|
||||
"class-validator": "0.9.1",
|
||||
"cli-color": "1.4.0",
|
||||
"connect": "3.6.6",
|
||||
"connect": "3.7.0",
|
||||
"cors": "2.8.5",
|
||||
"engine.io-client": "3.3.2",
|
||||
"express": "4.16.4",
|
||||
"fast-json-stringify": "1.11.3",
|
||||
"express": "4.17.0",
|
||||
"fast-json-stringify": "1.15.2",
|
||||
"fast-safe-stringify": "2.0.6",
|
||||
"fastify": "2.1.0",
|
||||
"fastify-cors": "2.1.2",
|
||||
"fastify": "2.3.0",
|
||||
"fastify-cors": "2.1.3",
|
||||
"fastify-formbody": "3.1.0",
|
||||
"fastify-multipart": "0.7.0",
|
||||
"graphql": "14.1.1",
|
||||
"grpc": "1.19.0",
|
||||
"fastify-multipart": "0.8.2",
|
||||
"graphql": "14.3.0",
|
||||
"grpc": "1.20.0",
|
||||
"http2": "3.3.7",
|
||||
"iterare": "1.1.2",
|
||||
"json-socket": "0.3.0",
|
||||
"merge-graphql-schemas": "1.5.8",
|
||||
"mqtt": "2.18.8",
|
||||
"multer": "1.4.1",
|
||||
"nats": "1.2.2",
|
||||
"nats": "1.2.10",
|
||||
"object-hash": "1.3.1",
|
||||
"optional": "0.1.4",
|
||||
"path-to-regexp": "3.0.0",
|
||||
"pump": "3.0.0",
|
||||
"redis": "2.8.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs-compat": "6.4.0",
|
||||
"rxjs": "6.5.2",
|
||||
"rxjs-compat": "6.5.2",
|
||||
"socket.io": "2.2.0",
|
||||
"ts-morph": "1.3.1",
|
||||
"ts-morph": "2.0.1",
|
||||
"uuid": "3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -81,31 +80,31 @@
|
||||
"@types/cache-manager": "1.2.7",
|
||||
"@types/chai": "4.1.7",
|
||||
"@types/chai-as-promised": "7.1.0",
|
||||
"@types/cors": "2.8.4",
|
||||
"@types/cors": "2.8.5",
|
||||
"@types/express": "4.16.1",
|
||||
"@types/fastify-cors": "2.1.0",
|
||||
"@types/kafka-node": "2.0.8",
|
||||
"@types/mocha": "5.2.6",
|
||||
"@types/node": "10.14.1",
|
||||
"@types/redis": "2.8.11",
|
||||
"@types/node": "10.14.7",
|
||||
"@types/redis": "2.8.12",
|
||||
"@types/reflect-metadata": "0.0.5",
|
||||
"@types/sinon": "7.0.10",
|
||||
"@types/sinon": "7.0.11",
|
||||
"@types/socket.io": "2.1.2",
|
||||
"@types/ws": "4.0.2",
|
||||
"artillery": "1.6.0-27",
|
||||
"@types/ws": "6.0.1",
|
||||
"artillery": "1.6.0-28",
|
||||
"awesome-typescript-loader": "5.2.1",
|
||||
"body-parser": "1.18.3",
|
||||
"body-parser": "1.19.0",
|
||||
"chai": "4.2.0",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"clang-format": "1.2.4",
|
||||
"concurrently": "4.1.0",
|
||||
"conventional-changelog": "3.0.6",
|
||||
"core-js": "2.6.5",
|
||||
"conventional-changelog": "3.1.8",
|
||||
"core-js": "3.0.1",
|
||||
"coveralls": "3.0.3",
|
||||
"csv-write-stream": "2.0.0",
|
||||
"delete-empty": "2.0.0",
|
||||
"fastify-static": "2.3.4",
|
||||
"gulp": "4.0.0",
|
||||
"fastify-static": "2.4.0",
|
||||
"gulp": "4.0.1",
|
||||
"gulp-clang-format": "1.0.27",
|
||||
"gulp-clean": "0.4.0",
|
||||
"gulp-sourcemaps": "2.6.5",
|
||||
@@ -114,20 +113,20 @@
|
||||
"husky": "1.3.1",
|
||||
"imports-loader": "0.8.0",
|
||||
"json-loader": "0.5.7",
|
||||
"lerna": "3.13.1",
|
||||
"lint-staged": "8.1.5",
|
||||
"lerna": "3.14.1",
|
||||
"lint-staged": "8.1.7",
|
||||
"memory-usage": "1.2.1",
|
||||
"mocha": "3.5.3",
|
||||
"nodemon": "1.18.10",
|
||||
"nyc": "13.3.0",
|
||||
"prettier": "1.16.4",
|
||||
"sinon": "7.2.7",
|
||||
"nodemon": "1.19.0",
|
||||
"nyc": "14.1.1",
|
||||
"prettier": "1.17.1",
|
||||
"sinon": "7.3.2",
|
||||
"sinon-chai": "3.3.0",
|
||||
"socket.io-client": "2.2.0",
|
||||
"supertest": "4.0.2",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "5.14.0",
|
||||
"typescript": "3.3.3333"
|
||||
"ts-node": "8.1.0",
|
||||
"tslint": "5.16.0",
|
||||
"typescript": "3.4.5"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
@@ -145,6 +144,7 @@
|
||||
"packages/**/*.spec.ts",
|
||||
"packages/**/adapters/*.ts",
|
||||
"packages/**/nest-*.ts",
|
||||
"packages/**/test/**/*.ts",
|
||||
"packages/core/errors/**/*",
|
||||
"packages/common/exceptions/*.ts",
|
||||
"packages/common/http/*.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017 Kamil Myśliwiec <http://kamilmysliwiec.com>
|
||||
Copyright (c) 2017-2019 Kamil Myśliwiec <http://kamilmysliwiec.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -7,29 +7,29 @@
|
||||
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
|
||||
[linux-url]: https://travis-ci.org/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#8" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge" target="_blank"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
<p>Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).</p>
|
||||
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
|
||||
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
|
||||
## Philosophy
|
||||
|
||||
@@ -41,35 +41,44 @@
|
||||
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
|
||||
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
|
||||
|
||||
## Consulting
|
||||
|
||||
With official support, you can get expert help straight from Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://docs.nestjs.com/enterprise).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
#### Principal Sponsor
|
||||
|
||||
<a href="https://valor-software.com/"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Base Sponsor
|
||||
|
||||
<a href="https://blueanchor.io/"><img src="https://nestjs.com/img/blueanchor.png" width="300" /></a>
|
||||
<a href="https://www.novologic.com/"><img src="https://nestjs.com/img/novologic.png" width="200" /></a>
|
||||
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Silver Sponsors
|
||||
<a href="https://neoteric.eu/"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a> <a href="https://www.swingdev.io"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="150" valign="middle" /> </a>
|
||||
<a href="https://yakaz.com/"><img src="https://nestjs.com/img/yakaz.png" width="100" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/"><img src="https://nestjs.com/img/logo-xtremis.svg" width="150" valign="middle" /></a>
|
||||
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a>
|
||||
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="150" valign="middle" /></a>
|
||||
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></a>
|
||||
|
||||
#### Sponsors
|
||||
|
||||
<a href="https://scal.io"><img src="https://nestjs.com/img/scalio-logo.svg" width="110" valign="middle" /></a> <a href="http://angularity.io"><img src="http://angularity.io/media/logo.svg" height="30" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a> <a href="https://genuinebee.com/"><img src="https://nestjs.com/img/genuinebee.svg" height="38" valign="middle" /></a> <a href="http://architectnow.net/"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a>
|
||||
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="125" valign="middle" /> </a> <a href="https://blueanchor.io/" target="_blank"><img src="https://nestjs.com/img/blueanchor.png" width="180" valign="middle" /></a>
|
||||
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="130" valign="middle" /></a>
|
||||
<a href="https://scal.io" target="_blank"><img src="https://nestjs.com/img/scalio-logo.svg" width="100" valign="middle" /></a> <a href="http://angularity.io" target="_blank"><img src="http://angularity.io/media/logo.svg" height="26" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com" target="_blank"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a>
|
||||
|
||||
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" height="36" valign="middle" /></a> <a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a> <a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" height="20" valign="middle" /></a>
|
||||
<a href="https://buddy.works/" target="_blank"><img src="https://nestjs.com/img/buddy-logo.svg" height="35" valign="middle" /></a>
|
||||
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" height="31" valign="middle" /></a>
|
||||
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" height="28" valign="middle" /></a> <a href="https://yakaz.com/" target="_blank"><img src="https://nestjs.com/img/yakaz.png" width="80" valign="middle" /></a>
|
||||
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="120" valign="middle" /></a>
|
||||
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="90" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/" target="_blank"><img src="https://nestjs.com/img/logo-xtremis.svg" width="145" valign="middle" /></a>
|
||||
|
||||
<a href="https://triplebyte.com/"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
<a href="https://opencollective.com/nest"><img src="https://opencollective.com/nest/backers.svg?width=890"></a>
|
||||
<a href="https://opencollective.com/nest" target="_blank"><img src="https://opencollective.com/nest/backers.svg?width=1600"></a>
|
||||
|
||||
## Stay in touch
|
||||
|
||||
|
||||
@@ -9,18 +9,18 @@ import {
|
||||
} from '../../interfaces';
|
||||
import { CACHE_KEY_METADATA, CACHE_MANAGER } from '../cache.constants';
|
||||
|
||||
const APPLICATION_REFERENCE_HOST = 'ApplicationReferenceHost';
|
||||
const HTTP_ADAPTER_HOST = 'HttpAdapterHost';
|
||||
const REFLECTOR = 'Reflector';
|
||||
|
||||
export interface ApplicationHost<T extends HttpServer = any> {
|
||||
applicationRef: T;
|
||||
export interface HttpAdapterHost<T extends HttpServer = any> {
|
||||
httpAdapter: T;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CacheInterceptor implements NestInterceptor {
|
||||
@Optional()
|
||||
@Inject(APPLICATION_REFERENCE_HOST)
|
||||
protected readonly applicationHost: ApplicationHost;
|
||||
@Inject(HTTP_ADAPTER_HOST)
|
||||
protected readonly httpAdapterHost: HttpAdapterHost;
|
||||
|
||||
constructor(
|
||||
@Inject(CACHE_MANAGER) protected readonly cacheManager: any,
|
||||
@@ -49,16 +49,16 @@ export class CacheInterceptor implements NestInterceptor {
|
||||
}
|
||||
|
||||
trackBy(context: ExecutionContext): string | undefined {
|
||||
const httpServer = this.applicationHost.applicationRef;
|
||||
const isHttpApp = httpServer && !!httpServer.getRequestMethod;
|
||||
const httpAdapter = this.httpAdapterHost.httpAdapter;
|
||||
const isHttpApp = httpAdapter && !!httpAdapter.getRequestMethod;
|
||||
|
||||
if (!isHttpApp) {
|
||||
return this.reflector.get(CACHE_KEY_METADATA, context.getHandler());
|
||||
}
|
||||
const request = context.getArgByIndex(0);
|
||||
if (httpServer.getRequestMethod(request) !== 'GET') {
|
||||
if (httpAdapter.getRequestMethod(request) !== 'GET') {
|
||||
return undefined;
|
||||
}
|
||||
return httpServer.getRequestUrl(request);
|
||||
return httpAdapter.getRequestUrl(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ export class HttpException extends Error {
|
||||
public readonly message: any;
|
||||
|
||||
/**
|
||||
* The base Nest Application exception, which is handled by the default Exceptions Handler.
|
||||
* Base Nest application exception, which is handled by the default Exceptions Handler.
|
||||
* If you throw an exception from your HTTP route handlers, Nest will map them to the appropriate HTTP response and send to the client.
|
||||
*
|
||||
* When `response` is an object:
|
||||
|
||||
@@ -56,11 +56,6 @@ export class HttpModule {
|
||||
provide: HTTP_MODULE_ID,
|
||||
useValue: randomStringGenerator(),
|
||||
},
|
||||
{
|
||||
provide: AXIOS_INSTANCE_TOKEN,
|
||||
useValue: (config: HttpModuleOptions) => Axios.create(config),
|
||||
inject: [HTTP_MODULE_OPTIONS],
|
||||
},
|
||||
...(options.extraProviders || []),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -42,7 +42,8 @@ export interface HttpServer<TRequest = any, TResponse = any> {
|
||||
options(path: string, handler: RequestHandler<TRequest, TResponse>): any;
|
||||
listen(port: number | string, callback?: () => void): any;
|
||||
listen(port: number | string, hostname: string, callback?: () => void): any;
|
||||
reply(response: any, body: any, statusCode: number): any;
|
||||
reply(response: any, body: any, statusCode?: number): any;
|
||||
status(response: any, statusCode: number): any;
|
||||
render(response: any, view: string, options: any): any;
|
||||
setHeader(response: any, name: string, value: string): any;
|
||||
setErrorHandler?(handler: Function): any;
|
||||
|
||||
@@ -89,5 +89,6 @@ export interface RmqOptions {
|
||||
prefetchCount?: number;
|
||||
isGlobalPrefetchCount?: boolean;
|
||||
queueOptions?: any;
|
||||
socketOptions?: any;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface WsMessageHandler<T = string> {
|
||||
message: T;
|
||||
callback: (...args: any[]) => Observable<any> | Promise<any>;
|
||||
}
|
||||
|
||||
export interface WebSocketAdapter<
|
||||
TServer = any,
|
||||
TClient = any,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/common",
|
||||
"version": "6.0.1",
|
||||
"version": "6.2.4",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"repository": {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './validation.pipe';
|
||||
export * from './parse-int.pipe';
|
||||
export * from './validation.pipe';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Optional } from '../decorators';
|
||||
import { Injectable } from '../decorators/core';
|
||||
import { ArgumentMetadata, BadRequestException, ValidationError } from '../index';
|
||||
import {
|
||||
ArgumentMetadata,
|
||||
BadRequestException,
|
||||
ValidationError,
|
||||
} from '../index';
|
||||
import { ClassTransformOptions } from '../interfaces/external/class-transform-options.interface';
|
||||
import { ValidatorOptions } from '../interfaces/external/validator-options.interface';
|
||||
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
|
||||
@@ -57,10 +61,13 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
if (!metatype || !this.toValidate(metadata)) {
|
||||
return value;
|
||||
}
|
||||
value = this.toEmptyIfNil(value);
|
||||
|
||||
this.stripProtoKeys(value);
|
||||
const entity = classTransformer.plainToClass(
|
||||
metatype,
|
||||
this.toEmptyIfNil(value),
|
||||
this.transformOptions
|
||||
value,
|
||||
this.transformOptions,
|
||||
);
|
||||
const errors = await classValidator.validate(entity, this.validatorOptions);
|
||||
if (errors.length > 0) {
|
||||
@@ -69,8 +76,8 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
return this.isTransformEnabled
|
||||
? entity
|
||||
: Object.keys(this.validatorOptions).length > 0
|
||||
? classTransformer.classToPlain(entity, this.transformOptions)
|
||||
: value;
|
||||
? classTransformer.classToPlain(entity, this.transformOptions)
|
||||
: value;
|
||||
}
|
||||
|
||||
private toValidate(metadata: ArgumentMetadata): boolean {
|
||||
@@ -82,7 +89,15 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
return !types.some(t => metatype === t) && !isNil(metatype);
|
||||
}
|
||||
|
||||
toEmptyIfNil<T = any, R = any>(value: T): R | {} {
|
||||
private toEmptyIfNil<T = any, R = any>(value: T): R | {} {
|
||||
return isNil(value) ? {} : value;
|
||||
}
|
||||
|
||||
private stripProtoKeys(value: Record<string, any>) {
|
||||
delete value.__proto__;
|
||||
const keys = Object.keys(value);
|
||||
keys
|
||||
.filter(key => typeof value[key] === 'object' && value[key])
|
||||
.forEach(key => this.stripProtoKeys(value[key]));
|
||||
}
|
||||
}
|
||||
|
||||
47
packages/common/test/exceptions/http.exception.spec.ts
Normal file
47
packages/common/test/exceptions/http.exception.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { expect } from 'chai';
|
||||
import { BadRequestException, HttpException, NotFoundException } from '../../exceptions';
|
||||
|
||||
describe('HttpException', () => {
|
||||
it('should return a message as a string when input is a string', () => {
|
||||
const message: string = 'My error message';
|
||||
expect(new HttpException(message, 404).message).to.be.eql('My error message');
|
||||
});
|
||||
|
||||
it('should return a message as an object when input is an object', () => {
|
||||
const message: object = {
|
||||
msg: 'My error message',
|
||||
reason: 'this can be a human readable reason',
|
||||
anything: 'else',
|
||||
};
|
||||
expect(new HttpException(message, 404).message).to.be.eql(message);
|
||||
});
|
||||
|
||||
it('should return a message from a built-in exception as an object', () => {
|
||||
const message: string = 'My error message';
|
||||
expect(new BadRequestException(message).message).to.be.eql({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'My error message',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an object even when the message is undefined', () => {
|
||||
expect(new BadRequestException().message).to.be.eql({statusCode: 400, error: 'Bad Request'});
|
||||
});
|
||||
|
||||
it('should return a status code', () => {
|
||||
expect(new BadRequestException().getStatus()).to.be.eql(400);
|
||||
expect(new NotFoundException().getStatus()).to.be.eql(404);
|
||||
});
|
||||
|
||||
it('should return a response', () => {
|
||||
expect(new BadRequestException().getResponse()).to.be.eql({
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
});
|
||||
expect(new NotFoundException().getResponse()).to.be.eql({
|
||||
error: 'Not Found',
|
||||
statusCode: 404,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,10 @@ class TestModel {
|
||||
@IsString() public prop1: string;
|
||||
|
||||
@IsString() public prop2: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
public optionalProp: string;
|
||||
}
|
||||
|
||||
describe('ValidationPipe', () => {
|
||||
@@ -46,13 +50,34 @@ describe('ValidationPipe', () => {
|
||||
beforeEach(() => {
|
||||
target = new ValidationPipe();
|
||||
});
|
||||
it('should return the value unchanged', async () => {
|
||||
it('should return the value unchanged if optional value is not defined', async () => {
|
||||
const testObj = { prop1: 'value1', prop2: 'value2' };
|
||||
expect(await target.transform(testObj, {} as any)).to.equal(testObj);
|
||||
expect(
|
||||
await target.transform(testObj, metadata as any),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
});
|
||||
it('should return the value unchanged if optional value is set undefined', async () => {
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', optionalProp: undefined };
|
||||
expect(await target.transform(testObj, {} as any)).to.equal(testObj);
|
||||
expect(
|
||||
await target.transform(testObj, metadata as any),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
});
|
||||
it('should return the value unchanged if optional value is null', async () => {
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', optionalProp: null };
|
||||
expect(await target.transform(testObj, {} as any)).to.equal(testObj);
|
||||
expect(
|
||||
await target.transform(testObj, metadata as any),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
});
|
||||
it('should return the value unchanged if optional value is set', async () => {
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', optionalProp: 'optional value' };
|
||||
expect(await target.transform(testObj, {} as any)).to.equal(testObj);
|
||||
expect(
|
||||
await target.transform(testObj, metadata as any),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
});
|
||||
});
|
||||
describe('when validation fails', () => {
|
||||
beforeEach(() => {
|
||||
@@ -134,6 +159,48 @@ describe('ValidationPipe', () => {
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.have.property('prop3');
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.have.property('optionalProp');
|
||||
});
|
||||
it('should return a plain object without extra properties if optional prop is defined', async () => {
|
||||
target = new ValidationPipe({ transform: false, whitelist: true });
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', prop3: 'value3', optionalProp: 'optional value' };
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.have.property('prop3');
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.have.property('optionalProp');
|
||||
});
|
||||
it('should return a plain object without extra properties if optional prop is undefined', async () => {
|
||||
target = new ValidationPipe({ transform: false, whitelist: true });
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', prop3: 'value3', optionalProp: undefined };
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.have.property('prop3');
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.have.property('optionalProp');
|
||||
});
|
||||
it('should return a plain object without extra properties if optional prop is null', async () => {
|
||||
target = new ValidationPipe({ transform: false, whitelist: true });
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', prop3: 'value3', optionalProp: null };
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.be.instanceOf(TestModel);
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.not.have.property('prop3');
|
||||
expect(
|
||||
await target.transform(testObj, metadata),
|
||||
).to.have.property('optionalProp');
|
||||
});
|
||||
});
|
||||
describe('when validation rejects', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017 Kamil Myśliwiec <http://kamilmysliwiec.com>
|
||||
Copyright (c) 2017-2019 Kamil Myśliwiec <http://kamilmysliwiec.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -7,29 +7,29 @@
|
||||
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
|
||||
[linux-url]: https://travis-ci.org/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#8" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge" target="_blank"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
<p>Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).</p>
|
||||
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
|
||||
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
|
||||
## Philosophy
|
||||
|
||||
@@ -41,35 +41,44 @@
|
||||
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
|
||||
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
|
||||
|
||||
## Consulting
|
||||
|
||||
With official support, you can get expert help straight from Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://docs.nestjs.com/enterprise).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
#### Principal Sponsor
|
||||
|
||||
<a href="https://valor-software.com/"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Base Sponsor
|
||||
|
||||
<a href="https://blueanchor.io/"><img src="https://nestjs.com/img/blueanchor.png" width="300" /></a>
|
||||
<a href="https://www.novologic.com/"><img src="https://nestjs.com/img/novologic.png" width="200" /></a>
|
||||
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Silver Sponsors
|
||||
<a href="https://neoteric.eu/"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a> <a href="https://www.swingdev.io"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="150" valign="middle" /> </a>
|
||||
<a href="https://yakaz.com/"><img src="https://nestjs.com/img/yakaz.png" width="100" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/"><img src="https://nestjs.com/img/logo-xtremis.svg" width="150" valign="middle" /></a>
|
||||
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a>
|
||||
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="150" valign="middle" /></a>
|
||||
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></a>
|
||||
|
||||
#### Sponsors
|
||||
|
||||
<a href="https://scal.io"><img src="https://nestjs.com/img/scalio-logo.svg" width="110" valign="middle" /></a> <a href="http://angularity.io"><img src="http://angularity.io/media/logo.svg" height="30" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a> <a href="https://genuinebee.com/"><img src="https://nestjs.com/img/genuinebee.svg" height="38" valign="middle" /></a> <a href="http://architectnow.net/"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a>
|
||||
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="125" valign="middle" /> </a> <a href="https://blueanchor.io/" target="_blank"><img src="https://nestjs.com/img/blueanchor.png" width="180" valign="middle" /></a>
|
||||
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="130" valign="middle" /></a>
|
||||
<a href="https://scal.io" target="_blank"><img src="https://nestjs.com/img/scalio-logo.svg" width="100" valign="middle" /></a> <a href="http://angularity.io" target="_blank"><img src="http://angularity.io/media/logo.svg" height="26" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com" target="_blank"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a>
|
||||
|
||||
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" height="36" valign="middle" /></a> <a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a> <a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" height="20" valign="middle" /></a>
|
||||
<a href="https://buddy.works/" target="_blank"><img src="https://nestjs.com/img/buddy-logo.svg" height="35" valign="middle" /></a>
|
||||
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" height="31" valign="middle" /></a>
|
||||
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" height="28" valign="middle" /></a> <a href="https://yakaz.com/" target="_blank"><img src="https://nestjs.com/img/yakaz.png" width="80" valign="middle" /></a>
|
||||
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="120" valign="middle" /></a>
|
||||
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="90" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/" target="_blank"><img src="https://nestjs.com/img/logo-xtremis.svg" width="145" valign="middle" /></a>
|
||||
|
||||
<a href="https://triplebyte.com/"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
<a href="https://opencollective.com/nest"><img src="https://opencollective.com/nest/backers.svg?width=890"></a>
|
||||
<a href="https://opencollective.com/nest" target="_blank"><img src="https://opencollective.com/nest/backers.svg?width=1600"></a>
|
||||
|
||||
## Stay in touch
|
||||
|
||||
|
||||
@@ -82,7 +82,8 @@ export abstract class AbstractHttpAdapter<
|
||||
abstract setViewEngine(engine: string);
|
||||
abstract getRequestMethod(request);
|
||||
abstract getRequestUrl(request);
|
||||
abstract reply(response, body: any, statusCode: number);
|
||||
abstract status(response, statusCode: number);
|
||||
abstract reply(response, body: any, statusCode?: number);
|
||||
abstract render(response, view: string, options: any);
|
||||
abstract setErrorHandler(handler: Function);
|
||||
abstract setNotFoundHandler(handler: Function);
|
||||
|
||||
@@ -64,7 +64,7 @@ export const UNKNOWN_DEPENDENCIES_MESSAGE = (
|
||||
export const INVALID_MIDDLEWARE_MESSAGE = (
|
||||
text: TemplateStringsArray,
|
||||
name: string,
|
||||
) => `The middleware doesn't provide the 'resolve' method (${name})`;
|
||||
) => `The middleware doesn't provide the 'use' method (${name})`;
|
||||
|
||||
export const INVALID_MODULE_MESSAGE = (
|
||||
text: TemplateStringsArray,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ExternalExceptionsHandler } from './external-exceptions-handler';
|
||||
export class ExternalExceptionFilterContext extends BaseExceptionFilterContext {
|
||||
constructor(
|
||||
container: NestContainer,
|
||||
private readonly config: ApplicationConfig,
|
||||
private readonly config?: ApplicationConfig,
|
||||
) {
|
||||
super(container);
|
||||
}
|
||||
@@ -41,6 +41,9 @@ export class ExternalExceptionFilterContext extends BaseExceptionFilterContext {
|
||||
}
|
||||
|
||||
public getGlobalMetadata<T extends any[]>(): T {
|
||||
if (!this.config) {
|
||||
return [] as T;
|
||||
}
|
||||
return this.config.getGlobalFilters() as T;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ export interface ExternalHandlerMetadata {
|
||||
) => (ParamProperties & { metatype?: any })[];
|
||||
}
|
||||
|
||||
export interface ExternalContextOptions {
|
||||
guards?: boolean;
|
||||
interceptors?: boolean;
|
||||
filters?: boolean;
|
||||
}
|
||||
|
||||
export class ExternalContextCreator {
|
||||
private readonly contextUtils = new ContextUtils();
|
||||
private readonly externalErrorProxy = new ExternalErrorProxy();
|
||||
@@ -98,6 +104,11 @@ export class ExternalContextCreator {
|
||||
paramsFactory?: ParamsFactory,
|
||||
contextId = STATIC_CONTEXT,
|
||||
inquirerId?: string,
|
||||
options: ExternalContextOptions = {
|
||||
interceptors: true,
|
||||
guards: true,
|
||||
filters: true,
|
||||
},
|
||||
) {
|
||||
const module = this.findContextModuleName(instance.constructor);
|
||||
const { argsLength, paramtypes, getParamsMetadata } = this.getMetadata<T>(
|
||||
@@ -106,7 +117,6 @@ export class ExternalContextCreator {
|
||||
metadataKey,
|
||||
paramsFactory,
|
||||
);
|
||||
|
||||
const pipes = this.pipesContextCreator.create(
|
||||
instance,
|
||||
callback,
|
||||
@@ -114,7 +124,6 @@ export class ExternalContextCreator {
|
||||
contextId,
|
||||
inquirerId,
|
||||
);
|
||||
|
||||
const guards = this.guardsContextCreator.create(
|
||||
instance,
|
||||
callback,
|
||||
@@ -122,13 +131,6 @@ export class ExternalContextCreator {
|
||||
contextId,
|
||||
inquirerId,
|
||||
);
|
||||
const interceptors = this.interceptorsContextCreator.create(
|
||||
instance,
|
||||
callback,
|
||||
module,
|
||||
contextId,
|
||||
inquirerId,
|
||||
);
|
||||
const exceptionFilter = this.filtersContextCreator.create(
|
||||
instance,
|
||||
callback,
|
||||
@@ -136,12 +138,24 @@ export class ExternalContextCreator {
|
||||
contextId,
|
||||
inquirerId,
|
||||
);
|
||||
const interceptors = options.interceptors
|
||||
? this.interceptorsContextCreator.create(
|
||||
instance,
|
||||
callback,
|
||||
module,
|
||||
contextId,
|
||||
inquirerId,
|
||||
)
|
||||
: [];
|
||||
|
||||
const paramsMetadata = getParamsMetadata(module, contextId, inquirerId);
|
||||
const paramsOptions = paramsMetadata
|
||||
? this.contextUtils.mergeParamsMetatypes(paramsMetadata, paramtypes)
|
||||
: [];
|
||||
|
||||
const fnCanActivate = options.guards
|
||||
? this.createGuardsFn(guards, instance, callback)
|
||||
: null;
|
||||
const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
|
||||
const handler = (initialArgs: any[], ...args: any[]) => async () => {
|
||||
if (fnApplyPipes) {
|
||||
@@ -153,15 +167,8 @@ export class ExternalContextCreator {
|
||||
|
||||
const target = async (...args: any[]) => {
|
||||
const initialArgs = this.contextUtils.createNullArray(argsLength);
|
||||
const canActivate = await this.guardsConsumer.tryActivate(
|
||||
guards,
|
||||
args,
|
||||
instance,
|
||||
callback,
|
||||
);
|
||||
if (!canActivate) {
|
||||
throw new ForbiddenException(FORBIDDEN_MESSAGE);
|
||||
}
|
||||
fnCanActivate && (await fnCanActivate(args));
|
||||
|
||||
const result = await this.interceptorsConsumer.intercept(
|
||||
interceptors,
|
||||
args,
|
||||
@@ -171,7 +178,9 @@ export class ExternalContextCreator {
|
||||
);
|
||||
return this.transformToResult(result);
|
||||
};
|
||||
return this.externalErrorProxy.createProxy(target, exceptionFilter);
|
||||
return options.filters
|
||||
? this.externalErrorProxy.createProxy(target, exceptionFilter)
|
||||
: target;
|
||||
}
|
||||
|
||||
public getMetadata<T>(
|
||||
@@ -328,4 +337,23 @@ export class ExternalContextCreator {
|
||||
}
|
||||
return resultOrDeffered;
|
||||
}
|
||||
|
||||
public createGuardsFn(
|
||||
guards: any[],
|
||||
instance: Controller,
|
||||
callback: (...args: any[]) => any,
|
||||
): Function | null {
|
||||
const canActivateFn = async (args: any[]) => {
|
||||
const canActivate = await this.guardsConsumer.tryActivate(
|
||||
guards,
|
||||
args,
|
||||
instance,
|
||||
callback,
|
||||
);
|
||||
if (!canActivate) {
|
||||
throw new ForbiddenException(FORBIDDEN_MESSAGE);
|
||||
}
|
||||
};
|
||||
return guards.length ? canActivateFn : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ export const HANDLER_METADATA_SYMBOL = Symbol.for('handler_metadata:cache');
|
||||
export interface HandlerMetadata {
|
||||
argsLength: number;
|
||||
paramtypes: any[];
|
||||
httpStatusCode: number;
|
||||
responseHeaders: any[];
|
||||
hasCustomHeaders: boolean;
|
||||
getParamsMetadata: (
|
||||
moduleKey: string,
|
||||
contextId?: ContextId,
|
||||
|
||||
@@ -8,9 +8,13 @@ const MISSING_REQUIRED_DEPENDENCY = (
|
||||
|
||||
const logger = new Logger('PackageLoader');
|
||||
|
||||
export function loadAdapter(defaultPlatform: string, transport: string) {
|
||||
export function loadAdapter(
|
||||
defaultPlatform: string,
|
||||
transport: string,
|
||||
loaderFn?: Function,
|
||||
) {
|
||||
try {
|
||||
return require(defaultPlatform);
|
||||
return loaderFn ? loaderFn() : require(defaultPlatform);
|
||||
} catch (e) {
|
||||
logger.error(MISSING_REQUIRED_DEPENDENCY(defaultPlatform, transport));
|
||||
process.exit(1);
|
||||
|
||||
@@ -80,8 +80,8 @@ export class Injector {
|
||||
}
|
||||
const loadInstance = (instances: any[]) => {
|
||||
targetWrapper.instance = targetWrapper.isDependencyTreeStatic()
|
||||
? new metatype(...instances)
|
||||
: Object.create(metatype);
|
||||
? new (metatype as Type<any>)(...instances)
|
||||
: Object.create(metatype.prototype);
|
||||
};
|
||||
await this.resolveConstructorParams(
|
||||
wrapper,
|
||||
@@ -190,7 +190,7 @@ export class Injector {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (instanceHost.isResolved) {
|
||||
return;
|
||||
return done();
|
||||
}
|
||||
const callback = async (instances: any[]) => {
|
||||
const properties = await this.resolveProperties(
|
||||
@@ -235,10 +235,10 @@ export class Injector {
|
||||
return callback(deps);
|
||||
}
|
||||
const dependencies = isNil(inject)
|
||||
? this.reflectConstructorParams(wrapper.metatype)
|
||||
? this.reflectConstructorParams(wrapper.metatype as Type<any>)
|
||||
: inject;
|
||||
const optionalDependenciesIds = isNil(inject)
|
||||
? this.reflectOptionalParams(wrapper.metatype)
|
||||
? this.reflectOptionalParams(wrapper.metatype as Type<any>)
|
||||
: [];
|
||||
|
||||
let isResolved = true;
|
||||
@@ -504,7 +504,7 @@ export class Injector {
|
||||
if (metadata && contextId !== STATIC_CONTEXT) {
|
||||
return this.loadPropertiesMetadata(metadata, contextId, inquirer);
|
||||
}
|
||||
const properties = this.reflectProperties(wrapper.metatype);
|
||||
const properties = this.reflectProperties(wrapper.metatype as Type<any>);
|
||||
const instances = await Promise.all(
|
||||
properties.map(async (item: PropertyDependency) => {
|
||||
try {
|
||||
@@ -588,14 +588,12 @@ export class Injector {
|
||||
const isInContext = isStatic || isInRequestScope || isLazyTransient;
|
||||
|
||||
if (isNil(inject) && isInContext) {
|
||||
const targetInstance = wrapper.getInstanceByContextId(
|
||||
contextId,
|
||||
inquirerId,
|
||||
);
|
||||
|
||||
targetInstance.instance = wrapper.forwardRef
|
||||
? Object.assign(targetInstance.instance, new metatype(...instances))
|
||||
: new metatype(...instances);
|
||||
instanceHost.instance = wrapper.forwardRef
|
||||
? Object.assign(
|
||||
instanceHost.instance,
|
||||
new (metatype as Type<any>)(...instances),
|
||||
)
|
||||
: new (metatype as Type<any>)(...instances);
|
||||
} else if (isInContext) {
|
||||
const factoryReturnValue = ((targetMetatype.metatype as any) as Function)(
|
||||
...instances,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Scope, Type } from '@nestjs/common';
|
||||
import { Provider, Scope, Type } from '@nestjs/common';
|
||||
import {
|
||||
ClassProvider,
|
||||
FactoryProvider,
|
||||
ValueProvider,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
|
||||
import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import { STATIC_CONTEXT } from './constants';
|
||||
@@ -30,11 +35,11 @@ interface InstanceMetadataStore {
|
||||
|
||||
export class InstanceWrapper<T = any> {
|
||||
public readonly name: any;
|
||||
public readonly metatype: Type<T>;
|
||||
public readonly inject?: (string | symbol | Function | Type<any>)[];
|
||||
public readonly async?: boolean;
|
||||
public readonly host?: Module;
|
||||
public readonly scope?: Scope = Scope.DEFAULT;
|
||||
public metatype: Type<T> | Function;
|
||||
public inject?: (string | symbol | Function | Type<any>)[];
|
||||
public forwardRef?: boolean;
|
||||
|
||||
private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();
|
||||
@@ -66,7 +71,8 @@ export class InstanceWrapper<T = any> {
|
||||
}
|
||||
|
||||
get isNotMetatype(): boolean {
|
||||
return !this.metatype;
|
||||
const isFactory = this.metatype && !isNil(this.inject);
|
||||
return !this.metatype || isFactory;
|
||||
}
|
||||
|
||||
get isTransient(): boolean {
|
||||
@@ -292,6 +298,24 @@ export class InstanceWrapper<T = any> {
|
||||
.filter(item => !!item);
|
||||
}
|
||||
|
||||
public mergeWith(provider: Provider) {
|
||||
if ((provider as ValueProvider).useValue) {
|
||||
this.metatype = null;
|
||||
this.inject = null;
|
||||
this.setInstanceByContextId(STATIC_CONTEXT, {
|
||||
instance: (provider as ValueProvider).useValue,
|
||||
isResolved: true,
|
||||
isPending: false,
|
||||
});
|
||||
} else if ((provider as ClassProvider).useClass) {
|
||||
this.inject = null;
|
||||
this.metatype = (provider as ClassProvider).useClass;
|
||||
} else if ((provider as FactoryProvider).useFactory) {
|
||||
this.metatype = (provider as FactoryProvider).useFactory;
|
||||
this.inject = (provider as FactoryProvider).inject || [];
|
||||
}
|
||||
}
|
||||
|
||||
private isNewable(): boolean {
|
||||
return isNil(this.inject) && this.metatype && this.metatype.prototype;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants';
|
||||
import {
|
||||
Abstract,
|
||||
Controller,
|
||||
DynamicModule,
|
||||
Injectable,
|
||||
@@ -26,7 +27,13 @@ export interface CustomProvider {
|
||||
provide: any;
|
||||
name: string;
|
||||
}
|
||||
export type OpaqueToken = string | symbol | Type<any>;
|
||||
export type OpaqueToken =
|
||||
| string
|
||||
| symbol
|
||||
| Type<any>
|
||||
| Function
|
||||
| Abstract<any>;
|
||||
|
||||
export type CustomClass = CustomProvider & {
|
||||
useClass: Type<any>;
|
||||
scope?: Scope;
|
||||
@@ -358,9 +365,13 @@ export class Module {
|
||||
|
||||
public replace(toReplace: string | symbol | Type<any>, options: any) {
|
||||
if (options.isProvider && this.hasProvider(toReplace)) {
|
||||
return this.addProvider({ provide: toReplace, ...options });
|
||||
const name = this.getProviderStaticToken(toReplace);
|
||||
const originalProvider = this._providers.get(name);
|
||||
return originalProvider.mergeWith({ provide: toReplace, ...options });
|
||||
} else if (!options.isProvider && this.hasInjectable(toReplace)) {
|
||||
this.addInjectable({
|
||||
const name = this.getProviderStaticToken(toReplace);
|
||||
const originalInjectable = this._injectables.get(name);
|
||||
return originalInjectable.mergeWith({
|
||||
provide: toReplace,
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ export class MiddlewareContainer {
|
||||
new InstanceWrapper({
|
||||
scope: this.getClassScope(metatype),
|
||||
metatype,
|
||||
name: token,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -185,7 +185,11 @@ export class NestFactoryStatic {
|
||||
}
|
||||
|
||||
private createHttpAdapter<T = any>(httpServer?: T): AbstractHttpAdapter {
|
||||
const { ExpressAdapter } = loadAdapter('@nestjs/platform-express', 'HTTP');
|
||||
const { ExpressAdapter } = loadAdapter(
|
||||
'@nestjs/platform-express',
|
||||
'HTTP',
|
||||
() => require('@nestjs/platform-express'),
|
||||
);
|
||||
return new ExpressAdapter(httpServer);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/core",
|
||||
"version": "6.0.1",
|
||||
"version": "6.2.4",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -83,7 +83,11 @@ export class RouterExecutionContext {
|
||||
fnHandleResponse,
|
||||
paramtypes,
|
||||
getParamsMetadata,
|
||||
httpStatusCode,
|
||||
responseHeaders,
|
||||
hasCustomHeaders,
|
||||
} = this.getMetadata(instance, callback, methodName, module, requestMethod);
|
||||
|
||||
const paramsOptions = this.contextUtils.mergeParamsMetatypes(
|
||||
getParamsMetadata(module, contextId, inquirerId),
|
||||
paramtypes,
|
||||
@@ -131,6 +135,10 @@ export class RouterExecutionContext {
|
||||
const args = this.contextUtils.createNullArray(argsLength);
|
||||
fnCanActivate && (await fnCanActivate([req, res]));
|
||||
|
||||
this.responseController.setStatus(res, httpStatusCode);
|
||||
hasCustomHeaders &&
|
||||
this.responseController.setHeaders(res, responseHeaders);
|
||||
|
||||
const result = await this.interceptorsConsumer.intercept(
|
||||
interceptors,
|
||||
[req, res],
|
||||
@@ -165,7 +173,6 @@ export class RouterExecutionContext {
|
||||
instance,
|
||||
methodName,
|
||||
);
|
||||
const httpCode = this.reflectHttpStatusCode(callback);
|
||||
const getParamsMetadata = (
|
||||
moduleKey: string,
|
||||
contextId = STATIC_CONTEXT,
|
||||
@@ -184,20 +191,28 @@ export class RouterExecutionContext {
|
||||
({ type }) =>
|
||||
type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
|
||||
);
|
||||
const httpStatusCode = httpCode
|
||||
? httpCode
|
||||
: this.responseController.getStatusByMethod(requestMethod);
|
||||
|
||||
const fnHandleResponse = this.createHandleResponseFn(
|
||||
callback,
|
||||
isResponseHandled,
|
||||
httpStatusCode,
|
||||
);
|
||||
|
||||
const httpCode = this.reflectHttpStatusCode(callback);
|
||||
const httpStatusCode = httpCode
|
||||
? httpCode
|
||||
: this.responseController.getStatusByMethod(requestMethod);
|
||||
|
||||
const responseHeaders = this.reflectResponseHeaders(callback);
|
||||
const hasCustomHeaders = !isEmpty(responseHeaders);
|
||||
|
||||
const handlerMetadata: HandlerMetadata = {
|
||||
argsLength,
|
||||
fnHandleResponse,
|
||||
paramtypes,
|
||||
getParamsMetadata,
|
||||
httpStatusCode,
|
||||
hasCustomHeaders,
|
||||
responseHeaders,
|
||||
};
|
||||
this.handlerMetadataStorage.set(instance, methodName, handlerMetadata);
|
||||
return handlerMetadata;
|
||||
@@ -342,23 +357,17 @@ export class RouterExecutionContext {
|
||||
public createHandleResponseFn(
|
||||
callback: (...args: any[]) => any,
|
||||
isResponseHandled: boolean,
|
||||
httpStatusCode: number,
|
||||
httpStatusCode?: number,
|
||||
) {
|
||||
const renderTemplate = this.reflectRenderTemplate(callback);
|
||||
const responseHeaders = this.reflectResponseHeaders(callback);
|
||||
const hasCustomHeaders = !isEmpty(responseHeaders);
|
||||
|
||||
if (renderTemplate) {
|
||||
return async <TResult, TResponse>(result: TResult, res: TResponse) => {
|
||||
hasCustomHeaders &&
|
||||
this.responseController.setHeaders(res, responseHeaders);
|
||||
await this.responseController.render(result, res, renderTemplate);
|
||||
};
|
||||
}
|
||||
return async <TResult, TResponse>(result: TResult, res: TResponse) => {
|
||||
hasCustomHeaders &&
|
||||
this.responseController.setHeaders(res, responseHeaders);
|
||||
|
||||
result = await this.responseController.transformToResult(result);
|
||||
!isResponseHandled &&
|
||||
(await this.responseController.apply(result, res, httpStatusCode));
|
||||
};
|
||||
|
||||
@@ -10,11 +10,10 @@ export class RouterResponseController {
|
||||
constructor(private readonly applicationRef: HttpServer) {}
|
||||
|
||||
public async apply<TInput = any, TResponse = any>(
|
||||
resultOrDeffered: TInput,
|
||||
result: TInput,
|
||||
response: TResponse,
|
||||
httpStatusCode: number,
|
||||
httpStatusCode?: number,
|
||||
) {
|
||||
const result = await this.transformToResult(resultOrDeffered);
|
||||
return this.applicationRef.reply(response, result, httpStatusCode);
|
||||
}
|
||||
|
||||
@@ -51,4 +50,8 @@ export class RouterResponseController {
|
||||
this.applicationRef.setHeader(response, name, value),
|
||||
);
|
||||
}
|
||||
|
||||
public setStatus<TResponse = any>(response: TResponse, statusCode: number) {
|
||||
this.applicationRef.status(response, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { MODULE_PATH } from '@nestjs/common/constants';
|
||||
import { HttpServer } from '@nestjs/common/interfaces';
|
||||
import { HttpServer, Type } from '@nestjs/common/interfaces';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
@@ -60,7 +60,10 @@ export class RoutesResolver implements Resolver {
|
||||
) {
|
||||
routes.forEach(instanceWrapper => {
|
||||
const { metatype } = instanceWrapper;
|
||||
const path = this.routerBuilder.extractRouterPath(metatype, basePath);
|
||||
const path = this.routerBuilder.extractRouterPath(
|
||||
metatype as Type<any>,
|
||||
basePath,
|
||||
);
|
||||
const controllerName = metatype.name;
|
||||
|
||||
this.logger.log(CONTROLLER_MAPPING_MESSAGE(controllerName, path));
|
||||
|
||||
@@ -33,12 +33,14 @@ describe('ExceptionsHandler', () => {
|
||||
beforeEach(() => {
|
||||
sinon
|
||||
.stub(adapter, 'reply')
|
||||
.callsFake((responseRef: any, body: any, statusCode: number) => {
|
||||
const res = responseRef.status(statusCode);
|
||||
if (isNil(body)) {
|
||||
return res.send();
|
||||
.callsFake((responseRef: any, body: any, statusCode?: number) => {
|
||||
if (statusCode) {
|
||||
responseRef.status(statusCode);
|
||||
}
|
||||
return isObject(body) ? res.json(body) : res.send(String(body));
|
||||
if (isNil(body)) {
|
||||
return responseRef.send();
|
||||
}
|
||||
return isObject(body) ? responseRef.json(body) : responseRef.send(String(body));
|
||||
});
|
||||
});
|
||||
it('should method send expected response status code and message when exception is unknown', () => {
|
||||
|
||||
8
packages/core/test/helpers/context-id-factory.spec.ts
Normal file
8
packages/core/test/helpers/context-id-factory.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { expect } from 'chai';
|
||||
import { createContextId } from '../../helpers/context-id-factory';
|
||||
|
||||
describe('createContextId', () => {
|
||||
it('should return an object with random "id" property', () => {
|
||||
expect(createContextId()).to.have.property('id');
|
||||
});
|
||||
});
|
||||
@@ -43,8 +43,7 @@ describe('ExternalContextCreator', () => {
|
||||
new PipesContextCreator(new NestContainer()),
|
||||
consumer,
|
||||
new ExternalExceptionFilterContext(
|
||||
new NestContainer(),
|
||||
new ApplicationConfig(),
|
||||
new NestContainer()
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('Module', () => {
|
||||
const setSpy = sinon.spy(collection, 'set');
|
||||
(module as any)._injectables = collection;
|
||||
|
||||
module.addInjectable(TestProvider);
|
||||
module.addInjectable(TestProvider, TestModule);
|
||||
expect(
|
||||
setSpy.calledWith(
|
||||
'TestProvider',
|
||||
@@ -272,21 +272,28 @@ describe('Module', () => {
|
||||
|
||||
describe('replace', () => {
|
||||
describe('when provider', () => {
|
||||
it('should call `addProvider`', () => {
|
||||
const addProviderSpy = sinon.spy(module, 'addProvider');
|
||||
it('should call `mergeWith`', () => {
|
||||
const wrapper = {
|
||||
mergeWith: sinon.spy(),
|
||||
};
|
||||
sinon.stub(module, 'hasProvider').callsFake(() => true);
|
||||
sinon.stub(module.providers, 'get').callsFake(() => wrapper as any);
|
||||
|
||||
module.replace(null, { isProvider: true });
|
||||
expect(addProviderSpy.called).to.be.true;
|
||||
expect(wrapper.mergeWith.called).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('when guard', () => {
|
||||
it('should call `addInjectable`', () => {
|
||||
const addInjectableSpy = sinon.spy(module, 'addInjectable');
|
||||
it('should call `mergeWith`', () => {
|
||||
const wrapper = {
|
||||
mergeWith: sinon.spy(),
|
||||
isProvider: true,
|
||||
};
|
||||
sinon.stub(module, 'hasInjectable').callsFake(() => true);
|
||||
sinon.stub(module.injectables, 'get').callsFake(() => wrapper as any);
|
||||
|
||||
module.replace(null, {});
|
||||
expect(addInjectableSpy.called).to.be.true;
|
||||
expect(wrapper.mergeWith.called).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -413,4 +420,25 @@ describe('Module', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getter "id"', () => {
|
||||
it('should return module id', () => {
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
expect(module.id).to.be.equal(module['_id']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderByKey', () => {
|
||||
describe('when does not exist', () => {
|
||||
it('should return undefined', () => {
|
||||
expect(module.getProviderByKey('test')).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('otherwise', () => {
|
||||
it('should return instance wrapper', () => {
|
||||
module.addProvider(TestProvider);
|
||||
expect(module.getProviderByKey('TestProvider')).to.not.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -281,7 +281,7 @@ describe('RouterExecutionContext', () => {
|
||||
sinon.stub(contextCreator, 'reflectResponseHeaders').returns([]);
|
||||
sinon.stub(contextCreator, 'reflectRenderTemplate').returns(template);
|
||||
|
||||
const handler = contextCreator.createHandleResponseFn(null, true, 100);
|
||||
const handler = contextCreator.createHandleResponseFn(null, true, 200);
|
||||
await handler(value, response);
|
||||
|
||||
expect(response.render.calledWith(template, value)).to.be.true;
|
||||
@@ -295,7 +295,7 @@ describe('RouterExecutionContext', () => {
|
||||
sinon.stub(contextCreator, 'reflectResponseHeaders').returns([]);
|
||||
sinon.stub(contextCreator, 'reflectRenderTemplate').returns(undefined);
|
||||
|
||||
const handler = contextCreator.createHandleResponseFn(null, true, 100);
|
||||
const handler = contextCreator.createHandleResponseFn(null, true, 200);
|
||||
handler(result, response);
|
||||
|
||||
expect(response.render.called).to.be.false;
|
||||
|
||||
@@ -22,19 +22,22 @@ describe('RouterResponseController', () => {
|
||||
json: sinon.SinonSpy;
|
||||
};
|
||||
beforeEach(() => {
|
||||
response = { send: sinon.spy(), json: sinon.spy() };
|
||||
response.status = sinon.stub().returns(response);
|
||||
response = { send: sinon.spy(), json: sinon.spy(), status: sinon.spy() };
|
||||
});
|
||||
describe('when result is', () => {
|
||||
beforeEach(() => {
|
||||
sinon
|
||||
.stub(adapter, 'reply')
|
||||
.callsFake((responseRef: any, body: any, statusCode: number) => {
|
||||
const res = responseRef.status(statusCode);
|
||||
if (isNil(body)) {
|
||||
return res.send();
|
||||
.callsFake((responseRef: any, body: any, statusCode?: number) => {
|
||||
if (statusCode) {
|
||||
responseRef.status(statusCode);
|
||||
}
|
||||
return isObject(body) ? res.json(body) : res.send(String(body));
|
||||
if (isNil(body)) {
|
||||
return responseRef.send();
|
||||
}
|
||||
return isObject(body)
|
||||
? responseRef.json(body)
|
||||
: responseRef.send(String(body));
|
||||
});
|
||||
});
|
||||
describe('nil', () => {
|
||||
@@ -149,4 +152,20 @@ describe('RouterResponseController', () => {
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('status', () => {
|
||||
let statusStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
statusStub = sinon.stub(adapter, 'status').callsFake(() => ({}));
|
||||
});
|
||||
|
||||
it('should set status', () => {
|
||||
const response = {};
|
||||
const statusCode = 400;
|
||||
|
||||
routerResponseController.setStatus(response, statusCode);
|
||||
expect(statusStub.calledWith(response, statusCode)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,8 @@ export class NoopHttpAdapter extends AbstractHttpAdapter {
|
||||
setViewEngine(engine: string): any {}
|
||||
getRequestMethod(request: any): any {}
|
||||
getRequestUrl(request: any): any {}
|
||||
reply(response: any, body: any, statusCode: number): any {}
|
||||
reply(response: any, body: any): any {}
|
||||
status(response: any, statusCode: number): any {}
|
||||
render(response: any, view: string, options: any): any {}
|
||||
setErrorHandler(handler: Function): any {}
|
||||
setNotFoundHandler(handler: Function): any {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017 Kamil Myśliwiec <http://kamilmysliwiec.com>
|
||||
Copyright (c) 2017-2019 Kamil Myśliwiec <http://kamilmysliwiec.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -7,29 +7,29 @@
|
||||
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
|
||||
[linux-url]: https://travis-ci.org/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#8" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
|
||||
<a href="https://travis-ci.org/nestjs/nest" target="_blank"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge" target="_blank"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
<p>Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).</p>
|
||||
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
|
||||
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
|
||||
|
||||
## Philosophy
|
||||
|
||||
@@ -41,35 +41,44 @@
|
||||
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
|
||||
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
|
||||
|
||||
## Consulting
|
||||
|
||||
With official support, you can get expert help straight from Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://docs.nestjs.com/enterprise).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
#### Principal Sponsor
|
||||
|
||||
<a href="https://valor-software.com/"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Base Sponsor
|
||||
|
||||
<a href="https://blueanchor.io/"><img src="https://nestjs.com/img/blueanchor.png" width="300" /></a>
|
||||
<a href="https://www.novologic.com/"><img src="https://nestjs.com/img/novologic.png" width="200" /></a>
|
||||
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
|
||||
|
||||
#### Silver Sponsors
|
||||
<a href="https://neoteric.eu/"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a> <a href="https://www.swingdev.io"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="150" valign="middle" /> </a>
|
||||
<a href="https://yakaz.com/"><img src="https://nestjs.com/img/yakaz.png" width="100" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/"><img src="https://nestjs.com/img/logo-xtremis.svg" width="150" valign="middle" /></a>
|
||||
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a>
|
||||
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" height="95" /></a>
|
||||
<a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="150" valign="middle" /></a>
|
||||
<a href="http://www.leogistics.com" target="_blank"><img src="https://nestjs.com/img/leogistics-logo.jpeg" width="150" valign="middle" /></a>
|
||||
|
||||
#### Sponsors
|
||||
|
||||
<a href="https://scal.io"><img src="https://nestjs.com/img/scalio-logo.svg" width="110" valign="middle" /></a> <a href="http://angularity.io"><img src="http://angularity.io/media/logo.svg" height="30" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a> <a href="https://genuinebee.com/"><img src="https://nestjs.com/img/genuinebee.svg" height="38" valign="middle" /></a> <a href="http://architectnow.net/"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a>
|
||||
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="125" valign="middle" /> </a> <a href="https://blueanchor.io/" target="_blank"><img src="https://nestjs.com/img/blueanchor.png" width="180" valign="middle" /></a>
|
||||
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="130" valign="middle" /></a>
|
||||
<a href="https://scal.io" target="_blank"><img src="https://nestjs.com/img/scalio-logo.svg" width="100" valign="middle" /></a> <a href="http://angularity.io" target="_blank"><img src="http://angularity.io/media/logo.svg" height="26" valign="middle" /></a> <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> --> <a href="https://hostpresto.com" target="_blank"><img src="https://nestjs.com/img/hostpresto.png" height="30" valign="middle" /></a>
|
||||
|
||||
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" height="36" valign="middle" /></a> <a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" height="24" valign="middle" /></a> <a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" height="28" valign="middle" /></a> <a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" height="20" valign="middle" /></a> <a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" height="20" valign="middle" /></a>
|
||||
<a href="https://buddy.works/" target="_blank"><img src="https://nestjs.com/img/buddy-logo.svg" height="35" valign="middle" /></a>
|
||||
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" height="31" valign="middle" /></a>
|
||||
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" height="28" valign="middle" /></a> <a href="https://yakaz.com/" target="_blank"><img src="https://nestjs.com/img/yakaz.png" width="80" valign="middle" /></a>
|
||||
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="120" valign="middle" /></a>
|
||||
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="90" valign="middle" /></a>
|
||||
<a href="http://xtremis.com/" target="_blank"><img src="https://nestjs.com/img/logo-xtremis.svg" width="145" valign="middle" /></a>
|
||||
|
||||
<a href="https://triplebyte.com/"><img src="https://nestjs.com/img/triplebyte.png" height="30" valign="middle" /></a>
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
<a href="https://opencollective.com/nest"><img src="https://opencollective.com/nest/backers.svg?width=890"></a>
|
||||
<a href="https://opencollective.com/nest" target="_blank"><img src="https://opencollective.com/nest/backers.svg?width=1600"></a>
|
||||
|
||||
## Stay in touch
|
||||
|
||||
|
||||
@@ -92,7 +92,8 @@ export class ClientRMQ extends ClientProxy {
|
||||
}
|
||||
|
||||
public createClient<T = any>(): T {
|
||||
return rqmPackage.connect(this.urls) as T;
|
||||
const socketOptions = this.getOptionsProp<RmqOptions>(this.options, 'socketOptions');
|
||||
return rqmPackage.connect(this.urls, socketOptions) as T;
|
||||
}
|
||||
|
||||
public mergeDisconnectEvent<T = any>(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import * as JsonSocket from 'json-socket';
|
||||
import * as net from 'net';
|
||||
import { share, tap } from 'rxjs/operators';
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
ClientOptions,
|
||||
TcpClientOptions,
|
||||
} from '../interfaces/client-metadata.interface';
|
||||
import { JsonSocket } from '../helpers/json-socket';
|
||||
import { ClientProxy } from './client-proxy';
|
||||
import { ECONNREFUSED } from './constants';
|
||||
|
||||
@@ -42,7 +42,7 @@ export class ClientTCP extends ClientProxy {
|
||||
this.socket = this.createSocket();
|
||||
this.bindEvents(this.socket);
|
||||
|
||||
const source$ = this.connect$(this.socket._socket).pipe(
|
||||
const source$ = this.connect$(this.socket.netSocket).pipe(
|
||||
tap(() => {
|
||||
this.isConnected = true;
|
||||
this.socket.on(MESSAGE_EVENT, (buffer: WritePacket & PacketId) =>
|
||||
@@ -52,10 +52,7 @@ export class ClientTCP extends ClientProxy {
|
||||
share(),
|
||||
);
|
||||
|
||||
this.socket.connect(
|
||||
this.port,
|
||||
this.host,
|
||||
);
|
||||
this.socket.connect(this.port, this.host);
|
||||
this.connection = source$.toPromise();
|
||||
return this.connection;
|
||||
}
|
||||
@@ -122,7 +119,9 @@ export class ClientTCP extends ClientProxy {
|
||||
}
|
||||
|
||||
protected async dispatchEvent(packet: ReadPacket): Promise<any> {
|
||||
const pattern = this.normalizePattern(packet.pattern);
|
||||
return this.socket.sendMessage(pattern);
|
||||
return this.socket.sendMessage({
|
||||
...packet,
|
||||
pattern: this.normalizePattern(packet.pattern),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ export * from './client-nats';
|
||||
export * from './client-proxy';
|
||||
export { ClientProxyFactory } from './client-proxy-factory';
|
||||
export * from './client-redis';
|
||||
export * from './client-rmq';
|
||||
export * from './client-tcp';
|
||||
|
||||
@@ -9,6 +9,7 @@ export const RQM_DEFAULT_URL = 'amqp://localhost';
|
||||
export const CONNECT_EVENT = 'connect';
|
||||
export const DISCONNECT_EVENT = 'disconnect';
|
||||
export const MESSAGE_EVENT = 'message';
|
||||
export const DATA_EVENT = 'data';
|
||||
export const ERROR_EVENT = 'error';
|
||||
export const CLOSE_EVENT = 'close';
|
||||
export const SUBSCRIBE = 'subscribe';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export class CorruptedPacketLengthException extends Error {
|
||||
constructor(rawContentLength: string) {
|
||||
super(`Corrupted length value "${rawContentLength}" supplied in a packet`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class InvalidJSONFormatException extends Error {
|
||||
constructor(err: Error, data: string) {
|
||||
super(`Could not parse JSON: ${err.message}\nRequest data: ${data}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class NetSocketClosedException extends Error {
|
||||
constructor() {
|
||||
super(`The net socket is closed.`);
|
||||
}
|
||||
}
|
||||
130
packages/microservices/helpers/json-socket.ts
Normal file
130
packages/microservices/helpers/json-socket.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Socket } from 'net';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
import {
|
||||
CLOSE_EVENT,
|
||||
CONNECT_EVENT,
|
||||
DATA_EVENT,
|
||||
ERROR_EVENT,
|
||||
MESSAGE_EVENT,
|
||||
} from '../constants';
|
||||
import { CorruptedPacketLengthException } from '../errors/corrupted-packet-length.exception';
|
||||
import { InvalidJSONFormatException } from '../errors/invalid-json-format.exception';
|
||||
import { NetSocketClosedException } from '../errors/net-socket-closed.exception';
|
||||
|
||||
export class JsonSocket {
|
||||
private contentLength: number | null = null;
|
||||
private isClosed = false;
|
||||
private buffer = '';
|
||||
|
||||
private readonly stringDecoder = new StringDecoder();
|
||||
private readonly delimeter = '#';
|
||||
|
||||
public get netSocket() {
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
constructor(public readonly socket: Socket) {
|
||||
this.socket.on(DATA_EVENT, this.onData.bind(this));
|
||||
this.socket.on(CONNECT_EVENT, () => (this.isClosed = false));
|
||||
this.socket.on(CLOSE_EVENT, () => (this.isClosed = true));
|
||||
this.socket.on(ERROR_EVENT, () => (this.isClosed = true));
|
||||
}
|
||||
|
||||
public connect(port: number, host: string) {
|
||||
this.socket.connect(port, host);
|
||||
return this;
|
||||
}
|
||||
|
||||
public on(event: string, callback: (err?: any) => void) {
|
||||
this.socket.on(event, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public once(event: string, callback: (err?: any) => void) {
|
||||
this.socket.once(event, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public end() {
|
||||
this.socket.end();
|
||||
return this;
|
||||
}
|
||||
|
||||
public sendMessage(message: any, callback?: (err?: any) => void) {
|
||||
if (this.isClosed) {
|
||||
callback && callback(new NetSocketClosedException());
|
||||
return;
|
||||
}
|
||||
this.socket.write(this.formatMessageData(message), 'utf-8', callback);
|
||||
}
|
||||
|
||||
private onData(dataRaw: Buffer | string) {
|
||||
const data = Buffer.isBuffer(dataRaw)
|
||||
? this.stringDecoder.write(dataRaw)
|
||||
: dataRaw;
|
||||
|
||||
try {
|
||||
this.handleData(data);
|
||||
} catch (e) {
|
||||
this.socket.emit(ERROR_EVENT, e.message);
|
||||
this.socket.end();
|
||||
}
|
||||
}
|
||||
|
||||
private handleData(data: string) {
|
||||
this.buffer += data;
|
||||
|
||||
if (this.contentLength == null) {
|
||||
const i = this.buffer.indexOf(this.delimeter);
|
||||
/**
|
||||
* Check if the buffer has the delimeter (#),
|
||||
* if not, the end of the buffer string might be in the middle of a content length string
|
||||
*/
|
||||
if (i !== -1) {
|
||||
const rawContentLength = this.buffer.substring(0, i);
|
||||
this.contentLength = parseInt(rawContentLength, 10);
|
||||
|
||||
if (isNaN(this.contentLength)) {
|
||||
this.contentLength = null;
|
||||
this.buffer = '';
|
||||
throw new CorruptedPacketLengthException(rawContentLength);
|
||||
}
|
||||
this.buffer = this.buffer.substring(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.contentLength !== null) {
|
||||
const length = this.buffer.length;
|
||||
|
||||
if (length === this.contentLength) {
|
||||
this.handleMessage(this.buffer);
|
||||
} else if (length > this.contentLength) {
|
||||
const message = this.buffer.substring(0, this.contentLength);
|
||||
const rest = this.buffer.substring(this.contentLength);
|
||||
this.handleMessage(message);
|
||||
this.onData(rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleMessage(data: string) {
|
||||
this.contentLength = null;
|
||||
this.buffer = '';
|
||||
|
||||
let message: Record<string, unknown>;
|
||||
try {
|
||||
message = JSON.parse(data);
|
||||
} catch (e) {
|
||||
throw new InvalidJSONFormatException(e, data);
|
||||
}
|
||||
message = message || {};
|
||||
this.socket.emit(MESSAGE_EVENT, message);
|
||||
}
|
||||
|
||||
private formatMessageData(message: any) {
|
||||
const messageData = JSON.stringify(message);
|
||||
const length = messageData.length;
|
||||
const data = length + this.delimeter + messageData;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -93,5 +93,6 @@ export interface RmqOptions {
|
||||
prefetchCount?: number;
|
||||
isGlobalPrefetchCount?: boolean;
|
||||
queueOptions?: any;
|
||||
socketOptions?: any;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@ export class ListenersController {
|
||||
}
|
||||
server.addHandler(
|
||||
pattern,
|
||||
data => {
|
||||
async data => {
|
||||
const contextId = createContextId();
|
||||
const contextInstance = this.injector.loadPerContext(
|
||||
const contextInstance = await this.injector.loadPerContext(
|
||||
instance,
|
||||
module,
|
||||
collection,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/microservices",
|
||||
"version": "6.0.1",
|
||||
"version": "6.2.4",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@microservices)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -3,4 +3,5 @@ export * from './server-grpc';
|
||||
export * from './server-mqtt';
|
||||
export * from './server-nats';
|
||||
export * from './server-redis';
|
||||
export * from './server-rmq';
|
||||
export * from './server-tcp';
|
||||
|
||||
@@ -30,14 +30,12 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
private readonly url: string;
|
||||
private grpcClient: any;
|
||||
|
||||
constructor(private readonly options: MicroserviceOptions['options']) {
|
||||
constructor(private readonly options: GrpcOptions['options']) {
|
||||
super();
|
||||
this.url =
|
||||
this.getOptionsProp<GrpcOptions>(options, 'url') || GRPC_DEFAULT_URL;
|
||||
this.url = this.getOptionsProp(options, 'url') || GRPC_DEFAULT_URL;
|
||||
|
||||
const protoLoader =
|
||||
this.getOptionsProp<GrpcOptions>(options, 'protoLoader') ||
|
||||
GRPC_DEFAULT_PROTO_LOADER;
|
||||
this.getOptionsProp(options, 'protoLoader') || GRPC_DEFAULT_PROTO_LOADER;
|
||||
|
||||
grpcPackage = this.loadPackage('grpc', ServerGrpc.name, () =>
|
||||
require('grpc'),
|
||||
@@ -58,10 +56,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
|
||||
public async bindEvents() {
|
||||
const grpcContext = this.loadProto();
|
||||
const packageName = this.getOptionsProp<GrpcOptions>(
|
||||
this.options,
|
||||
'package',
|
||||
);
|
||||
const packageName = this.getOptionsProp(this.options, 'package');
|
||||
const grpcPkg = this.lookupPackage(grpcContext, packageName);
|
||||
if (!grpcPkg) {
|
||||
const invalidPackageError = new InvalidGrpcPackageException();
|
||||
@@ -165,21 +160,18 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
|
||||
public createClient(): any {
|
||||
const server = new grpcPackage.Server({
|
||||
'grpc.max_send_message_length': this.getOptionsProp<GrpcOptions>(
|
||||
'grpc.max_send_message_length': this.getOptionsProp(
|
||||
this.options,
|
||||
'maxSendMessageLength',
|
||||
GRPC_DEFAULT_MAX_SEND_MESSAGE_LENGTH,
|
||||
),
|
||||
'grpc.max_receive_message_length': this.getOptionsProp<GrpcOptions>(
|
||||
'grpc.max_receive_message_length': this.getOptionsProp(
|
||||
this.options,
|
||||
'maxReceiveMessageLength',
|
||||
GRPC_DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
const credentials = this.getOptionsProp<GrpcOptions>(
|
||||
this.options,
|
||||
'credentials',
|
||||
);
|
||||
const credentials = this.getOptionsProp(this.options, 'credentials');
|
||||
server.bind(
|
||||
this.url,
|
||||
credentials || grpcPackage.ServerCredentials.createInsecure(),
|
||||
@@ -198,8 +190,8 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
|
||||
public loadProto(): any {
|
||||
try {
|
||||
const file = this.getOptionsProp<GrpcOptions>(this.options, 'protoPath');
|
||||
const loader = this.getOptionsProp<GrpcOptions>(this.options, 'loader');
|
||||
const file = this.getOptionsProp(this.options, 'protoPath');
|
||||
const loader = this.getOptionsProp(this.options, 'loader');
|
||||
|
||||
const packageDefinition = grpcProtoLoaderPackage.loadSync(file, loader);
|
||||
const packageObject = grpcPackage.loadPackageDefinition(
|
||||
|
||||
@@ -21,10 +21,9 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
|
||||
private readonly url: string;
|
||||
private mqttClient: MqttClient;
|
||||
|
||||
constructor(private readonly options: MicroserviceOptions['options']) {
|
||||
constructor(private readonly options: MqttOptions['options']) {
|
||||
super();
|
||||
this.url =
|
||||
this.getOptionsProp<MqttOptions>(options, 'url') || MQTT_DEFAULT_URL;
|
||||
this.url = this.getOptionsProp(options, 'url') || MQTT_DEFAULT_URL;
|
||||
|
||||
mqttPackage = this.loadPackage('mqtt', ServerMqtt.name, () =>
|
||||
require('mqtt'),
|
||||
@@ -59,10 +58,7 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
|
||||
}
|
||||
|
||||
public createMqttClient(): MqttClient {
|
||||
return mqttPackage.connect(
|
||||
this.url,
|
||||
this.options as MqttOptions,
|
||||
);
|
||||
return mqttPackage.connect(this.url, this.options as MqttOptions);
|
||||
}
|
||||
|
||||
public getMessageHandler(pub: MqttClient): Function {
|
||||
|
||||
@@ -21,10 +21,9 @@ export class ServerNats extends Server implements CustomTransportStrategy {
|
||||
private readonly url: string;
|
||||
private natsClient: Client;
|
||||
|
||||
constructor(private readonly options: MicroserviceOptions['options']) {
|
||||
constructor(private readonly options: NatsOptions['options']) {
|
||||
super();
|
||||
this.url =
|
||||
this.getOptionsProp<NatsOptions>(this.options, 'url') || NATS_DEFAULT_URL;
|
||||
this.url = this.getOptionsProp(this.options, 'url') || NATS_DEFAULT_URL;
|
||||
|
||||
natsPackage = this.loadPackage('nats', ServerNats.name, () =>
|
||||
require('nats'),
|
||||
@@ -43,7 +42,7 @@ export class ServerNats extends Server implements CustomTransportStrategy {
|
||||
}
|
||||
|
||||
public bindEvents(client: Client) {
|
||||
const queue = this.getOptionsProp<NatsOptions>(this.options, 'queue');
|
||||
const queue = this.getOptionsProp(this.options, 'queue');
|
||||
const subscribe = queue
|
||||
? (channel: string) =>
|
||||
client.subscribe(
|
||||
|
||||
@@ -27,11 +27,9 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
private pubClient: RedisClient;
|
||||
private isExplicitlyTerminated = false;
|
||||
|
||||
constructor(private readonly options: MicroserviceOptions['options']) {
|
||||
constructor(private readonly options: RedisOptions['options']) {
|
||||
super();
|
||||
this.url =
|
||||
this.getOptionsProp<RedisOptions>(this.options, 'url') ||
|
||||
REDIS_DEFAULT_URL;
|
||||
this.url = this.getOptionsProp(this.options, 'url') || REDIS_DEFAULT_URL;
|
||||
|
||||
redisPackage = this.loadPackage('redis', ServerRedis.name, () =>
|
||||
require('redis'),
|
||||
@@ -148,12 +146,11 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
}
|
||||
if (
|
||||
this.isExplicitlyTerminated ||
|
||||
!this.getOptionsProp<RedisOptions>(this.options, 'retryAttempts') ||
|
||||
options.attempt >
|
||||
this.getOptionsProp<RedisOptions>(this.options, 'retryAttempts')
|
||||
!this.getOptionsProp(this.options, 'retryAttempts') ||
|
||||
options.attempt > this.getOptionsProp(this.options, 'retryAttempts')
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getOptionsProp<RedisOptions>(this.options, 'retryDelay') || 0;
|
||||
return this.getOptionsProp(this.options, 'retryDelay') || 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
RQM_DEFAULT_URL,
|
||||
} from '../constants';
|
||||
import { CustomTransportStrategy, RmqOptions } from '../interfaces';
|
||||
import { MicroserviceOptions } from '../interfaces/microservice-configuration.interface';
|
||||
import { Server } from './server';
|
||||
|
||||
let rqmPackage: any = {};
|
||||
@@ -26,22 +25,19 @@ export class ServerRMQ extends Server implements CustomTransportStrategy {
|
||||
private readonly queueOptions: any;
|
||||
private readonly isGlobalPrefetchCount: boolean;
|
||||
|
||||
constructor(private readonly options: MicroserviceOptions) {
|
||||
constructor(private readonly options: RmqOptions['options']) {
|
||||
super();
|
||||
this.urls = this.getOptionsProp<RmqOptions>(this.options, 'urls') || [
|
||||
RQM_DEFAULT_URL,
|
||||
];
|
||||
this.urls = this.getOptionsProp(this.options, 'urls') || [RQM_DEFAULT_URL];
|
||||
this.queue =
|
||||
this.getOptionsProp<RmqOptions>(this.options, 'queue') ||
|
||||
RQM_DEFAULT_QUEUE;
|
||||
this.getOptionsProp(this.options, 'queue') || RQM_DEFAULT_QUEUE;
|
||||
this.prefetchCount =
|
||||
this.getOptionsProp<RmqOptions>(this.options, 'prefetchCount') ||
|
||||
this.getOptionsProp(this.options, 'prefetchCount') ||
|
||||
RQM_DEFAULT_PREFETCH_COUNT;
|
||||
this.isGlobalPrefetchCount =
|
||||
this.getOptionsProp<RmqOptions>(this.options, 'isGlobalPrefetchCount') ||
|
||||
this.getOptionsProp(this.options, 'isGlobalPrefetchCount') ||
|
||||
RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT;
|
||||
this.queueOptions =
|
||||
this.getOptionsProp<RmqOptions>(this.options, 'queueOptions') ||
|
||||
this.getOptionsProp(this.options, 'queueOptions') ||
|
||||
RQM_DEFAULT_QUEUE_OPTIONS;
|
||||
|
||||
this.loadPackage('amqplib', ServerRMQ.name, () => require('amqplib'));
|
||||
@@ -75,7 +71,8 @@ export class ServerRMQ extends Server implements CustomTransportStrategy {
|
||||
}
|
||||
|
||||
public createClient<T = any>(): T {
|
||||
return rqmPackage.connect(this.urls);
|
||||
const socketOptions = this.getOptionsProp(this.options, 'socketOptions');
|
||||
return rqmPackage.connect(this.urls, socketOptions);
|
||||
}
|
||||
|
||||
public async setupChannel(channel: any, callback: Function) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { isString, isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import * as JsonSocket from 'json-socket';
|
||||
import * as net from 'net';
|
||||
import { Server as NetSocket } from 'net';
|
||||
import { Server as NetSocket, Socket } from 'net';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
CLOSE_EVENT,
|
||||
ERROR_EVENT,
|
||||
MESSAGE_EVENT,
|
||||
NO_MESSAGE_HANDLER,
|
||||
TCP_DEFAULT_HOST,
|
||||
TCP_DEFAULT_PORT,
|
||||
} from '../constants';
|
||||
import { JsonSocket } from '../helpers/json-socket';
|
||||
import { CustomTransportStrategy, PacketId, ReadPacket } from '../interfaces';
|
||||
import {
|
||||
MicroserviceOptions,
|
||||
@@ -19,19 +20,23 @@ import { Server } from './server';
|
||||
|
||||
export class ServerTCP extends Server implements CustomTransportStrategy {
|
||||
private readonly port: number;
|
||||
private readonly host: string;
|
||||
|
||||
private server: NetSocket;
|
||||
private isExplicitlyTerminated = false;
|
||||
private retryAttemptsCount = 0;
|
||||
|
||||
constructor(private readonly options: MicroserviceOptions['options']) {
|
||||
constructor(private readonly options: TcpOptions['options']) {
|
||||
super();
|
||||
this.port =
|
||||
this.getOptionsProp<TcpOptions>(options, 'port') || TCP_DEFAULT_PORT;
|
||||
this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT;
|
||||
this.host =
|
||||
this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
public listen(callback: () => void) {
|
||||
this.server.listen(this.port, callback);
|
||||
this.server.listen(this.port, this.host, callback);
|
||||
}
|
||||
|
||||
public close() {
|
||||
@@ -39,15 +44,16 @@ export class ServerTCP extends Server implements CustomTransportStrategy {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
public bindHandler<T extends Record<string, any>>(socket: T) {
|
||||
public bindHandler(socket: Socket) {
|
||||
const readSocket = this.getSocketInstance(socket);
|
||||
readSocket.on(MESSAGE_EVENT, async (msg: ReadPacket & PacketId) =>
|
||||
this.handleMessage(readSocket, msg),
|
||||
);
|
||||
readSocket.on(ERROR_EVENT, this.handleError.bind(this));
|
||||
}
|
||||
|
||||
public async handleMessage<T extends Record<string, any>>(
|
||||
socket: T,
|
||||
public async handleMessage(
|
||||
socket: JsonSocket,
|
||||
packet: ReadPacket & PacketId,
|
||||
) {
|
||||
const pattern = !isString(packet.pattern)
|
||||
@@ -79,16 +85,16 @@ export class ServerTCP extends Server implements CustomTransportStrategy {
|
||||
public handleClose(): undefined | number | NodeJS.Timer {
|
||||
if (
|
||||
this.isExplicitlyTerminated ||
|
||||
!this.getOptionsProp<TcpOptions>(this.options, 'retryAttempts') ||
|
||||
!this.getOptionsProp(this.options, 'retryAttempts') ||
|
||||
this.retryAttemptsCount >=
|
||||
this.getOptionsProp<TcpOptions>(this.options, 'retryAttempts')
|
||||
this.getOptionsProp(this.options, 'retryAttempts')
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
++this.retryAttemptsCount;
|
||||
return setTimeout(
|
||||
() => this.server.listen(this.port),
|
||||
this.getOptionsProp<TcpOptions>(this.options, 'retryDelay') || 0,
|
||||
this.getOptionsProp(this.options, 'retryDelay') || 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +104,7 @@ export class ServerTCP extends Server implements CustomTransportStrategy {
|
||||
this.server.on(CLOSE_EVENT, this.handleClose.bind(this));
|
||||
}
|
||||
|
||||
private getSocketInstance<T>(socket: T): JsonSocket {
|
||||
private getSocketInstance(socket: Socket): JsonSocket {
|
||||
return new JsonSocket(socket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { loadPackage } from '@nestjs/common/utils/load-package.util';
|
||||
import { isFunction, isString } from '@nestjs/common/utils/shared.utils';
|
||||
import {
|
||||
ConnectableObservable,
|
||||
EMPTY as empty,
|
||||
from as fromPromise,
|
||||
Observable,
|
||||
of,
|
||||
Subscription,
|
||||
} from 'rxjs';
|
||||
import { catchError, finalize } from 'rxjs/operators';
|
||||
import { catchError, finalize, publish } from 'rxjs/operators';
|
||||
import {
|
||||
MessageHandler,
|
||||
MicroserviceOptions,
|
||||
@@ -61,24 +62,26 @@ export abstract class Server {
|
||||
if (!handler) {
|
||||
return this.logger.error(NO_EVENT_HANDLER);
|
||||
}
|
||||
await handler(packet.data);
|
||||
const resultOrStream = await handler(packet.data);
|
||||
if (this.isObservable(resultOrStream)) {
|
||||
(resultOrStream.pipe(publish()) as ConnectableObservable<any>).connect();
|
||||
}
|
||||
}
|
||||
|
||||
public transformToObservable<T = any>(resultOrDeffered: any): Observable<T> {
|
||||
if (resultOrDeffered instanceof Promise) {
|
||||
return fromPromise(resultOrDeffered);
|
||||
} else if (!(resultOrDeffered && isFunction(resultOrDeffered.subscribe))) {
|
||||
} else if (!this.isObservable(resultOrDeffered)) {
|
||||
return of(resultOrDeffered);
|
||||
}
|
||||
return resultOrDeffered;
|
||||
}
|
||||
|
||||
public getOptionsProp<T extends { options?: any }>(
|
||||
obj: MicroserviceOptions['options'],
|
||||
prop: keyof T['options'],
|
||||
defaultValue: any = undefined,
|
||||
) {
|
||||
return (obj && obj[prop as string]) || defaultValue;
|
||||
public getOptionsProp<
|
||||
T extends MicroserviceOptions['options'],
|
||||
K extends keyof T
|
||||
>(obj: T, prop: K, defaultValue: T[K] = undefined) {
|
||||
return (obj && obj[prop]) || defaultValue;
|
||||
}
|
||||
|
||||
protected handleError(error: string) {
|
||||
@@ -92,4 +95,8 @@ export abstract class Server {
|
||||
): T {
|
||||
return loadPackage(name, ctx, loader);
|
||||
}
|
||||
|
||||
private isObservable(input: unknown): input is Observable<any> {
|
||||
return input && isFunction((input as Observable<any>).subscribe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,61 @@ describe('ClientProxy', () => {
|
||||
client = new TestClientProxy();
|
||||
});
|
||||
|
||||
describe('createObserver', () => {
|
||||
describe('returned function calls', () => {
|
||||
it(`"error" when first parameter is not null or undefined`, () => {
|
||||
const testClient = new TestClientProxy();
|
||||
const err = 'test';
|
||||
const error = sinon.spy();
|
||||
const next = sinon.spy();
|
||||
const complete = sinon.spy();
|
||||
const observer = {
|
||||
error,
|
||||
next,
|
||||
complete,
|
||||
};
|
||||
const fn = testClient['createObserver'](observer);
|
||||
|
||||
fn({ err });
|
||||
expect(error.calledWith(err)).to.be.true;
|
||||
});
|
||||
|
||||
it(`"next" when first parameter is null or undefined`, () => {
|
||||
const testClient = new TestClientProxy();
|
||||
const data = 'test';
|
||||
const error = sinon.spy();
|
||||
const next = sinon.spy();
|
||||
const complete = sinon.spy();
|
||||
const observer = {
|
||||
error,
|
||||
next,
|
||||
complete,
|
||||
};
|
||||
const fn = testClient['createObserver'](observer);
|
||||
|
||||
fn({ response: data });
|
||||
expect(next.calledWith(data)).to.be.true;
|
||||
});
|
||||
|
||||
it(`"complete" when third parameter is true`, () => {
|
||||
const testClient = new TestClientProxy();
|
||||
const data = 'test';
|
||||
const error = sinon.spy();
|
||||
const next = sinon.spy();
|
||||
const complete = sinon.spy();
|
||||
const observer = {
|
||||
error,
|
||||
next,
|
||||
complete,
|
||||
};
|
||||
const fn = testClient['createObserver'](observer);
|
||||
|
||||
fn({ data, isDisposed: true } as any);
|
||||
expect(complete.called).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('send', () => {
|
||||
it(`should return an observable stream`, () => {
|
||||
const stream$ = client.send({}, '');
|
||||
@@ -117,60 +172,4 @@ describe('ClientProxy', () => {
|
||||
expect(err$).to.be.instanceOf(Observable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObserver', () => {
|
||||
it(`should return function`, () => {
|
||||
expect(typeof client['createObserver'](null)).to.be.eql('function');
|
||||
});
|
||||
|
||||
describe('returned function calls', () => {
|
||||
it(`"error" when first parameter is not null or undefined`, () => {
|
||||
const err = 'test';
|
||||
const error = sinon.spy();
|
||||
const next = sinon.spy();
|
||||
const complete = sinon.spy();
|
||||
const observer = {
|
||||
error,
|
||||
next,
|
||||
complete,
|
||||
};
|
||||
const fn = client['createObserver'](observer);
|
||||
|
||||
fn({ err });
|
||||
expect(error.calledWith(err)).to.be.true;
|
||||
});
|
||||
|
||||
it(`"next" when first parameter is null or undefined`, () => {
|
||||
const data = 'test';
|
||||
const error = sinon.spy();
|
||||
const next = sinon.spy();
|
||||
const complete = sinon.spy();
|
||||
const observer = {
|
||||
error,
|
||||
next,
|
||||
complete,
|
||||
};
|
||||
const fn = client['createObserver'](observer);
|
||||
|
||||
fn({ response: data });
|
||||
expect(next.calledWith(data)).to.be.true;
|
||||
});
|
||||
|
||||
it(`"complete" when third parameter is true`, () => {
|
||||
const data = 'test';
|
||||
const error = sinon.spy();
|
||||
const next = sinon.spy();
|
||||
const complete = sinon.spy();
|
||||
const observer = {
|
||||
error,
|
||||
next,
|
||||
complete,
|
||||
};
|
||||
const fn = client['createObserver'](observer);
|
||||
|
||||
fn({ data, isDisposed: true } as any);
|
||||
expect(complete.called).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ describe('ClientRedis', () => {
|
||||
};
|
||||
|
||||
describe('not completed', () => {
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
callback = sinon.spy();
|
||||
|
||||
subscription = client.createResponseCallback();
|
||||
@@ -172,7 +172,7 @@ describe('ClientRedis', () => {
|
||||
});
|
||||
});
|
||||
describe('disposed and "id" is incorrect', () => {
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
callback = sinon.spy();
|
||||
subscription = client.createResponseCallback();
|
||||
subscription('channel', new Buffer(JSON.stringify(responseMessage)));
|
||||
@@ -218,7 +218,7 @@ describe('ClientRedis', () => {
|
||||
let createClientSpy: sinon.SinonSpy;
|
||||
let handleErrorsSpy: sinon.SinonSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
createClientSpy = sinon.stub(client, 'createClient').callsFake(
|
||||
() =>
|
||||
({
|
||||
|
||||
@@ -6,18 +6,7 @@ import { ERROR_EVENT } from '../../constants';
|
||||
|
||||
describe('ClientTCP', () => {
|
||||
let client: ClientTCP;
|
||||
let socket: {
|
||||
connect: sinon.SinonStub;
|
||||
publish: sinon.SinonSpy;
|
||||
_socket: {
|
||||
addListener: sinon.SinonStub;
|
||||
removeListener: sinon.SinonSpy;
|
||||
once: sinon.SinonStub;
|
||||
};
|
||||
on: sinon.SinonStub;
|
||||
end: sinon.SinonSpy;
|
||||
sendMessage: sinon.SinonSpy;
|
||||
};
|
||||
let socket;
|
||||
let createSocketStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -27,9 +16,8 @@ describe('ClientTCP', () => {
|
||||
|
||||
socket = {
|
||||
connect: sinon.stub(),
|
||||
publish: sinon.spy(),
|
||||
on: sinon.stub().callsFake(onFakeCallback),
|
||||
_socket: {
|
||||
netSocket: {
|
||||
addListener: sinon.stub().callsFake(onFakeCallback),
|
||||
removeListener: sinon.spy(),
|
||||
once: sinon.stub().callsFake(onFakeCallback),
|
||||
@@ -134,7 +122,9 @@ describe('ClientTCP', () => {
|
||||
toPromise: () => source,
|
||||
pipe: () => source,
|
||||
};
|
||||
connect$Stub = sinon.stub(client, 'connect$' as any).callsFake(() => source);
|
||||
connect$Stub = sinon
|
||||
.stub(client, 'connect$' as any)
|
||||
.callsFake(() => source);
|
||||
await client.connect();
|
||||
});
|
||||
afterEach(() => {
|
||||
|
||||
215
packages/microservices/test/json-socket/connection.spec.ts
Normal file
215
packages/microservices/test/json-socket/connection.spec.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { expect } from 'chai';
|
||||
import { AddressInfo, createServer, Socket } from 'net';
|
||||
import { CONNECT_EVENT, MESSAGE_EVENT } from '../../constants';
|
||||
import { JsonSocket } from '../../helpers/json-socket';
|
||||
import { longPayload } from './data/long-payload-with-special-chars';
|
||||
import * as helpers from './helpers';
|
||||
import { ip } from './helpers';
|
||||
// tslint:disable:no-string-literal
|
||||
|
||||
describe('JsonSocket connection', () => {
|
||||
it('should connect, send and receive message', done => {
|
||||
helpers.createServerAndClient(
|
||||
(error, server, clientSocket, serverSocket) => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
expect(clientSocket['isClosed']).to.be.false;
|
||||
expect(serverSocket['isClosed']).to.be.false;
|
||||
|
||||
Promise.all([
|
||||
new Promise(callback => {
|
||||
clientSocket.sendMessage({ type: 'ping' }, callback);
|
||||
}),
|
||||
new Promise(callback => {
|
||||
clientSocket.on(MESSAGE_EVENT, (message: string) => {
|
||||
expect(message).to.deep.equal({ type: 'pong' });
|
||||
callback();
|
||||
});
|
||||
}),
|
||||
new Promise(callback => {
|
||||
serverSocket.on(MESSAGE_EVENT, (message: string) => {
|
||||
expect(message).to.deep.equal({ type: 'ping' });
|
||||
serverSocket.sendMessage({ type: 'pong' }, callback);
|
||||
});
|
||||
}),
|
||||
])
|
||||
.then(() => {
|
||||
expect(clientSocket['isClosed']).to.equal(false);
|
||||
expect(serverSocket['isClosed']).to.equal(false);
|
||||
clientSocket.end();
|
||||
server.close(done);
|
||||
})
|
||||
.catch(e => done(e));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should send long messages with special characters without issues', done => {
|
||||
helpers.createServerAndClient((err, server, clientSocket, serverSocket) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
expect(clientSocket['isClosed']).to.equal(false);
|
||||
expect(serverSocket['isClosed']).to.equal(false);
|
||||
Promise.all([
|
||||
new Promise(callback => {
|
||||
clientSocket.sendMessage(longPayload, callback);
|
||||
}),
|
||||
new Promise(callback => {
|
||||
clientSocket.on(MESSAGE_EVENT, (message: { type: 'pong' }) => {
|
||||
expect(message).to.deep.equal({ type: 'pong' });
|
||||
callback();
|
||||
});
|
||||
}),
|
||||
new Promise(callback => {
|
||||
serverSocket.on(MESSAGE_EVENT, (message: { type: 'pong' }) => {
|
||||
expect(message).to.deep.equal(longPayload);
|
||||
serverSocket.sendMessage({ type: 'pong' }, callback);
|
||||
});
|
||||
}),
|
||||
])
|
||||
.then(() => {
|
||||
expect(clientSocket['isClosed']).to.equal(false);
|
||||
expect(serverSocket['isClosed']).to.equal(false);
|
||||
clientSocket.end();
|
||||
server.close(done);
|
||||
})
|
||||
.catch(e => done(e));
|
||||
});
|
||||
});
|
||||
|
||||
it('should send multiple messages', done => {
|
||||
helpers.createServerAndClient((err, server, clientSocket, serverSocket) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
Promise.all([
|
||||
new Promise(callback =>
|
||||
Promise.all(
|
||||
helpers
|
||||
.range(1, 100)
|
||||
.map(
|
||||
i =>
|
||||
new Promise(resolve =>
|
||||
clientSocket.sendMessage({ number: i }, resolve),
|
||||
),
|
||||
),
|
||||
).then(_ => callback()),
|
||||
),
|
||||
new Promise(callback => {
|
||||
let lastNumber = 0;
|
||||
serverSocket.on(MESSAGE_EVENT, (message: { number: number }) => {
|
||||
expect(message.number).to.deep.equal(lastNumber + 1);
|
||||
lastNumber = message.number;
|
||||
if (lastNumber === 100) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}),
|
||||
])
|
||||
.then(() => {
|
||||
clientSocket.end();
|
||||
server.close(done);
|
||||
})
|
||||
.catch(e => done(e));
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true for "closed" when server disconnects', done => {
|
||||
helpers.createServerAndClient((err, server, clientSocket, serverSocket) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
new Promise(callback => {
|
||||
serverSocket.end();
|
||||
setTimeout(callback, 10);
|
||||
})
|
||||
.then(
|
||||
() =>
|
||||
new Promise(callback => {
|
||||
expect(clientSocket['isClosed']).to.equal(true);
|
||||
expect(serverSocket['isClosed']).to.equal(true);
|
||||
callback();
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
clientSocket.end();
|
||||
server.close(done);
|
||||
})
|
||||
.catch(e => done(e));
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true for "closed" when client disconnects', done => {
|
||||
helpers.createServerAndClient((err, server, clientSocket, serverSocket) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
new Promise(callback => {
|
||||
clientSocket.end();
|
||||
setTimeout(callback, 10);
|
||||
})
|
||||
.then(
|
||||
() =>
|
||||
new Promise(callback => {
|
||||
expect(clientSocket['isClosed']).to.equal(true);
|
||||
expect(serverSocket['isClosed']).to.equal(true);
|
||||
callback();
|
||||
}),
|
||||
)
|
||||
.then(() => server.close(done))
|
||||
.catch(e => done(e));
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true for "closed" when client (re)connects', done => {
|
||||
const server = createServer();
|
||||
|
||||
server.on('listening', () => {
|
||||
const clientSocket = new JsonSocket(new Socket());
|
||||
|
||||
server.once('connection', socket => {
|
||||
const serverSocket = new JsonSocket(socket);
|
||||
|
||||
serverSocket.once('end', () => {
|
||||
setTimeout(() => {
|
||||
expect(serverSocket['isClosed']).to.equal(true);
|
||||
expect(clientSocket['isClosed']).to.equal(true);
|
||||
|
||||
clientSocket.on(CONNECT_EVENT, () => {
|
||||
setTimeout(() => {
|
||||
expect(clientSocket['isClosed']).to.equal(false);
|
||||
|
||||
clientSocket.end();
|
||||
server.close(done);
|
||||
}, 10);
|
||||
});
|
||||
|
||||
const address2 = server.address();
|
||||
if (!address2) {
|
||||
throw new Error('server.address() returned null');
|
||||
}
|
||||
const port2 = (address2 as AddressInfo).port;
|
||||
|
||||
clientSocket.connect(port2, ip);
|
||||
}, 10);
|
||||
});
|
||||
|
||||
clientSocket.end();
|
||||
});
|
||||
|
||||
const address1 = server.address();
|
||||
if (!address1) {
|
||||
throw new Error('server.address() returned null');
|
||||
}
|
||||
const port1 = (address1 as AddressInfo).port;
|
||||
|
||||
clientSocket.connect(port1, ip);
|
||||
});
|
||||
server.listen();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,263 @@
|
||||
export const longPayload = [
|
||||
{
|
||||
_id: '584f17147fce7ca0a8bacfd2',
|
||||
index: 0,
|
||||
guid: '1d127572-0369-45fb-aa2f-e3bb083ac2b2',
|
||||
isActive: true,
|
||||
balance: '$2,926.06',
|
||||
picture: 'http://placehold.it/32x32',
|
||||
age: 26,
|
||||
eyeColor: 'green',
|
||||
name:
|
||||
'Wçêtson Aguilar [special characters in name that used to fail on long payloads]',
|
||||
gender: 'male',
|
||||
company: 'PROWASTE',
|
||||
email: 'watsonaguilar@prowaste.com',
|
||||
phone: '+1 (821) 517-2430',
|
||||
address: '910 Robert Street, Bangor, Delaware, 4159',
|
||||
about:
|
||||
'Aliqua et irure id do id id non dolore ipsum sit in proident ipsum. Id elit incididunt occaecat do laboris sunt officia fugiat aliquip. Incididunt aute ad minim Lorem cupidatat aute labore enim elit nostrud amet. Tempor sint irure incididunt aliquip amet sunt mollit aliqua Lorem officia pariatur.\r\n',
|
||||
registered: '2014-02-11T08:45:28 +05:00',
|
||||
latitude: 73.891198,
|
||||
longitude: 90.23414,
|
||||
tags: ['veniam', 'nulla', 'cillum', 'tempor', 'sint', 'magna', 'nostrud'],
|
||||
friends: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Cecelia James'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Hilary Young'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Sharron Goodwin'
|
||||
}
|
||||
],
|
||||
greeting: 'Hello, Watson Aguilar! You have 3 unread messages.',
|
||||
favoriteFruit: 'banana'
|
||||
},
|
||||
{
|
||||
_id: '584f1714b2e945fb30f73892',
|
||||
index: 1,
|
||||
guid: '3ffce1ee-a442-4dae-804f-40c59f19e7ee',
|
||||
isActive: false,
|
||||
balance: '$2,507.49',
|
||||
picture: 'http://placehold.it/32x32',
|
||||
age: 34,
|
||||
eyeColor: 'brown',
|
||||
name: 'Aguirre Salazar',
|
||||
gender: 'male',
|
||||
company: 'EZENTIA',
|
||||
email: 'aguirresalazar@ezentia.com',
|
||||
phone: '+1 (910) 443-3647',
|
||||
address: '629 Burnett Street, Tyhee, West Virginia, 2905',
|
||||
about:
|
||||
'Labore laboris et deserunt aliquip. Occaecat esse officia est eiusmod. Officia tempor cupidatat commodo minim deserunt mollit qui ut culpa. Est occaecat laborum occaecat non mollit ad reprehenderit magna ad. Consequat culpa excepteur qui aliqua dolore occaecat aliqua sunt elit ea nisi. Officia consectetur dolor labore voluptate. Esse ad esse qui id incididunt.\r\n',
|
||||
registered: '2015-01-28T06:47:34 +05:00',
|
||||
latitude: -64.632254,
|
||||
longitude: -116.659127,
|
||||
tags: [
|
||||
'sit',
|
||||
'anim',
|
||||
'quis',
|
||||
'officia',
|
||||
'minim',
|
||||
'cupidatat',
|
||||
'adipisicing'
|
||||
],
|
||||
friends: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Olson Mccall'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Carolina Conway'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Carlson Pacheco'
|
||||
}
|
||||
],
|
||||
greeting: 'Hello, Aguirre Salazar! You have 9 unread messages.',
|
||||
favoriteFruit: 'apple'
|
||||
},
|
||||
{
|
||||
_id: '584f17148282bb876fc4e9a2',
|
||||
index: 2,
|
||||
guid: '892ba80c-7149-4904-bd36-22f619d4df0a',
|
||||
isActive: true,
|
||||
balance: '$2,132.56',
|
||||
picture: 'http://placehold.it/32x32',
|
||||
age: 26,
|
||||
eyeColor: 'green',
|
||||
name: 'Hardin Grant',
|
||||
gender: 'male',
|
||||
company: 'CINASTER',
|
||||
email: 'hardingrant@cinaster.com',
|
||||
phone: '+1 (900) 437-2390',
|
||||
address: '180 Ide Court, Gibsonia, Washington, 3027',
|
||||
about:
|
||||
'Ut aliquip officia adipisicing voluptate aliquip aute fugiat ad quis ad eu non consectetur. Laboris labore veniam officia qui eiusmod. Duis aliqua est quis do dolor excepteur ea dolore non. Nisi mollit laboris nostrud nostrud pariatur culpa laboris anim est irure id aute.\r\n',
|
||||
registered: '2016-09-13T10:54:27 +04:00',
|
||||
latitude: 8.651031,
|
||||
longitude: -136.777747,
|
||||
tags: ['consequat', 'deserunt', 'magna', 'enim', 'esse', 'minim', 'ipsum'],
|
||||
friends: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Lesley Velasquez'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Natasha Simmons'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Isabel Avery'
|
||||
}
|
||||
],
|
||||
greeting: 'Hello, Hardin Grant! You have 7 unread messages.',
|
||||
favoriteFruit: 'strawberry'
|
||||
},
|
||||
{
|
||||
_id: '584f1714d90ff4b8914a69e7',
|
||||
index: 3,
|
||||
guid: '76f37726-1f73-4cf7-aabe-8dadf37d3ddd',
|
||||
isActive: true,
|
||||
balance: '$2,493.04',
|
||||
picture: 'http://placehold.it/32x32',
|
||||
age: 32,
|
||||
eyeColor: 'blue',
|
||||
name: 'Randall Roy',
|
||||
gender: 'male',
|
||||
company: 'ZAJ',
|
||||
email: 'randallroy@zaj.com',
|
||||
phone: '+1 (938) 562-2214',
|
||||
address: '872 Rugby Road, Hoehne, Indiana, 9792',
|
||||
about:
|
||||
'Non laboris id et cupidatat velit ea ipsum ea mollit quis qui dolore nisi laboris. Enim sit irure enim dolor velit proident sunt pariatur proident consequat mollit enim minim. Laboris deserunt cupidatat nisi enim adipisicing officia dolore ex cupidatat anim. Cupidatat labore voluptate non magna est dolor. Occaecat occaecat magna anim laborum adipisicing esse excepteur cillum aute qui eu do excepteur eu. Nostrud consectetur consectetur aliquip deserunt velit culpa sint excepteur mollit nostrud sit ex. Est ex ut laboris pariatur.\r\n',
|
||||
registered: '2016-05-05T05:24:56 +04:00',
|
||||
latitude: 18.943281,
|
||||
longitude: -110.942673,
|
||||
tags: [
|
||||
'eu',
|
||||
'aliqua',
|
||||
'reprehenderit',
|
||||
'amet',
|
||||
'nulla',
|
||||
'consequat',
|
||||
'nisi'
|
||||
],
|
||||
friends: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Barron Maynard'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Lynn Shepard'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Robin Whitehead'
|
||||
}
|
||||
],
|
||||
greeting: 'Hello, Randall Roy! You have 3 unread messages.',
|
||||
favoriteFruit: 'strawberry'
|
||||
},
|
||||
{
|
||||
_id: '584f17142a8f47cef0f5401a',
|
||||
index: 4,
|
||||
guid: '9b50ec22-3fbe-40ce-a5b8-b956f1340a77',
|
||||
isActive: false,
|
||||
balance: '$3,234.48',
|
||||
picture: 'http://placehold.it/32x32',
|
||||
age: 33,
|
||||
eyeColor: 'green',
|
||||
name: 'Chandler Vasquez',
|
||||
gender: 'male',
|
||||
company: 'ZILLACTIC',
|
||||
email: 'chandlervasquez@zillactic.com',
|
||||
phone: '+1 (830) 550-3428',
|
||||
address: '610 Hunts Lane, Cazadero, Michigan, 3584',
|
||||
about:
|
||||
'Fugiat in anim adipisicing sint aliquip ea velit do proident eu ad amet. Nulla velit duis ullamco labore ea Lorem velit elit Lorem. Id laboris do mollit exercitation veniam do amet culpa est excepteur reprehenderit consectetur laborum.\r\n',
|
||||
registered: '2014-04-20T05:23:32 +04:00',
|
||||
latitude: -88.088841,
|
||||
longitude: -163.602482,
|
||||
tags: [
|
||||
'sunt',
|
||||
'excepteur',
|
||||
'enim',
|
||||
'incididunt',
|
||||
'officia',
|
||||
'amet',
|
||||
'irure'
|
||||
],
|
||||
friends: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Mckee Norton'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Durham Parrish'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Stewart Kramer'
|
||||
}
|
||||
],
|
||||
greeting: 'Hello, Chandler Vasquez! You have 3 unread messages.',
|
||||
favoriteFruit: 'strawberry'
|
||||
},
|
||||
{
|
||||
_id: '584f171450a4e9dda687adc5',
|
||||
index: 5,
|
||||
guid: '68eeea45-ba6e-4740-b89b-10d690c37a02',
|
||||
isActive: false,
|
||||
balance: '$3,771.46',
|
||||
picture: 'http://placehold.it/32x32',
|
||||
age: 25,
|
||||
eyeColor: 'blue',
|
||||
name: 'Fernandez Caldwell',
|
||||
gender: 'male',
|
||||
company: 'SNIPS',
|
||||
email: 'fernandezcaldwell@snips.com',
|
||||
phone: '+1 (911) 544-3684',
|
||||
address: '786 Newel Street, Elliston, Massachusetts, 6683',
|
||||
about:
|
||||
'Voluptate commodo labore aliqua excepteur irure aliquip officia. Incididunt excepteur elit quis reprehenderit voluptate aliqua ad voluptate duis nisi dolor dolor id dolor. Irure sit consequat amet ea magna laborum velit eu in. Sunt occaecat quis consectetur laboris. Duis est do eu consectetur dolore id incididunt incididunt ut esse magna est. Nostrud irure magna nulla fugiat deserunt deserunt enim mollit proident qui sint dolore incididunt. Incididunt incididunt do quis culpa sint ut aliqua id.\r\n',
|
||||
registered: '2015-08-09T09:02:36 +04:00',
|
||||
latitude: -46.941347,
|
||||
longitude: -171.796168,
|
||||
tags: [
|
||||
'sit',
|
||||
'irure',
|
||||
'reprehenderit',
|
||||
'ut',
|
||||
'proident',
|
||||
'aliquip',
|
||||
'labore'
|
||||
],
|
||||
friends: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Adela Preston'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Phillips Moses'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Neva Wise'
|
||||
}
|
||||
],
|
||||
greeting: 'Hello, Fernandez Caldwell! You have 10 unread messages.',
|
||||
favoriteFruit: 'apple'
|
||||
}
|
||||
];
|
||||
82
packages/microservices/test/json-socket/helpers.ts
Normal file
82
packages/microservices/test/json-socket/helpers.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
AddressInfo,
|
||||
createServer as netCreateServer,
|
||||
Server,
|
||||
Socket,
|
||||
} from 'net';
|
||||
import { ERROR_EVENT } from '../../constants';
|
||||
import { JsonSocket } from '../../helpers/json-socket';
|
||||
|
||||
export const ip = '127.0.0.1';
|
||||
|
||||
export function createServer(callback: (err?: any, server?: Server) => void) {
|
||||
const server = netCreateServer();
|
||||
server.listen();
|
||||
|
||||
server.on('listening', () => {
|
||||
callback(null, server);
|
||||
});
|
||||
|
||||
server.on(ERROR_EVENT, (err: any) => {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function createClient(
|
||||
server: Server,
|
||||
callback: (
|
||||
err?: any,
|
||||
clientSocket?: JsonSocket,
|
||||
serverSocket?: JsonSocket,
|
||||
) => void,
|
||||
) {
|
||||
const clientSocket = new JsonSocket(new Socket());
|
||||
|
||||
const address = server.address();
|
||||
if (!address) {
|
||||
throw new Error('server.address() returned null');
|
||||
}
|
||||
const port = (address as AddressInfo).port;
|
||||
|
||||
clientSocket.connect(port, ip);
|
||||
|
||||
clientSocket.on(ERROR_EVENT, (err: any) => {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
server.once('connection', socket => {
|
||||
const serverSocket = new JsonSocket(socket);
|
||||
callback(null, clientSocket, serverSocket);
|
||||
});
|
||||
}
|
||||
|
||||
export function createServerAndClient(
|
||||
callback: (
|
||||
err?: any,
|
||||
server?: Server,
|
||||
clientSocket?: JsonSocket,
|
||||
serverSocket?: JsonSocket,
|
||||
) => void,
|
||||
) {
|
||||
createServer((serverErr, server) => {
|
||||
if (serverErr) {
|
||||
return callback(serverErr);
|
||||
}
|
||||
|
||||
createClient(server, (clientErr, clientSocket, serverSocket) => {
|
||||
if (clientErr) {
|
||||
return callback(clientErr);
|
||||
}
|
||||
|
||||
callback(null, server, clientSocket, serverSocket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function range(start: number, end: number) {
|
||||
const r = [];
|
||||
for (let i = start; i <= end; i++) {
|
||||
r.push(i);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { CONNECT_EVENT, MESSAGE_EVENT } from '../../constants';
|
||||
import { JsonSocket } from '../../helpers/json-socket';
|
||||
import * as helpers from './helpers';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('JsonSocket chaining', () => {
|
||||
it('should return the instance when subscribing to event', done => {
|
||||
helpers.createServerAndClient((err, server, clientSocket, serverSocket) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
expect(clientSocket.on(MESSAGE_EVENT, () => {})).to.be.instanceof(
|
||||
JsonSocket,
|
||||
);
|
||||
expect(clientSocket.on(CONNECT_EVENT, () => {})).to.deep.equal(
|
||||
clientSocket,
|
||||
);
|
||||
expect(
|
||||
clientSocket.on(MESSAGE_EVENT, () => {}).on('end', () => {}),
|
||||
).to.deep.equal(clientSocket);
|
||||
|
||||
clientSocket.end();
|
||||
server.close(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
186
packages/microservices/test/json-socket/message-parsing.spec.ts
Normal file
186
packages/microservices/test/json-socket/message-parsing.spec.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { Socket } from 'net';
|
||||
import * as sinon from 'sinon';
|
||||
import { ERROR_EVENT, MESSAGE_EVENT } from '../../constants';
|
||||
import { JsonSocket } from '../../helpers/json-socket';
|
||||
import { expect } from 'chai';
|
||||
// tslint:disable:no-string-literal
|
||||
|
||||
describe('JsonSocket message parsing', () => {
|
||||
const socket = new JsonSocket(new Socket());
|
||||
let messages: string[] = [];
|
||||
|
||||
socket.on(MESSAGE_EVENT, message => {
|
||||
messages.push(message);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
messages = [];
|
||||
socket['contentLength'] = null;
|
||||
socket['buffer'] = '';
|
||||
});
|
||||
|
||||
it('should parse JSON strings', () => {
|
||||
socket['handleData']('13#"Hello there"');
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal('Hello there');
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse JSON numbers', () => {
|
||||
socket['handleData']('5#12.34');
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal(12.34);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse JSON bools', () => {
|
||||
socket['handleData']('4#true');
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal(true);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse JSON objects', () => {
|
||||
socket['handleData']('17#{"a":"yes","b":9}');
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal({ a: 'yes', b: 9 });
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse JSON arrays', () => {
|
||||
socket['handleData']('9#["yes",9]');
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal(['yes', 9]);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse multiple messages in one packet', () => {
|
||||
socket['handleData']('5#"hey"4#true');
|
||||
expect(messages.length).to.deep.equal(2);
|
||||
expect(messages[0]).to.deep.equal('hey');
|
||||
expect(messages[1]).to.deep.equal(true);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse chunked messages', () => {
|
||||
socket['handleData']('13#"Hel');
|
||||
socket['handleData']('lo there"');
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal('Hello there');
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse chunked and multiple messages', () => {
|
||||
socket['handleData']('13#"Hel');
|
||||
socket['handleData']('lo there"4#true');
|
||||
expect(messages.length).to.deep.equal(2);
|
||||
expect(messages[0]).to.deep.equal('Hello there');
|
||||
expect(messages[1]).to.deep.equal(true);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse chunked messages with multi-byte characters', () => {
|
||||
// 0x33 0x23 0xd8 0x22 0xa9 0x22 = 3#"ة" (U+00629)
|
||||
socket['onData'](Buffer.from([0x33, 0x23, 0x22, 0xd8]));
|
||||
socket['onData'](Buffer.from([0xa9, 0x22]));
|
||||
expect(messages.length).to.deep.equal(1);
|
||||
expect(messages[0]).to.deep.equal('ة');
|
||||
});
|
||||
|
||||
it('should parse multiple messages with unicode correctly', () => {
|
||||
socket['handleData']('41#"Diese Zeile enthält das Unicode-Zeichen"4#true');
|
||||
expect(messages[0]).to.deep.equal(
|
||||
'Diese Zeile enthält das Unicode-Zeichen',
|
||||
);
|
||||
expect(messages[1]).to.deep.equal(true);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it('should parse multiple and chunked messages with unicode correctly', () => {
|
||||
socket['handleData']('41#"Diese Zeile enthält ');
|
||||
socket['handleData']('das Unicode-Zeichen"4#true');
|
||||
expect(messages[0]).to.deep.equal(
|
||||
'Diese Zeile enthält das Unicode-Zeichen',
|
||||
);
|
||||
expect(messages[1]).to.deep.equal(true);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
describe('Error handling', () => {
|
||||
describe('JSON Error', () => {
|
||||
const errorMsg = `Could not parse JSON: Unexpected end of JSON input\nRequest data: "Hel`;
|
||||
const packetStrin = '4#"Hel';
|
||||
const packet = Buffer.from(packetStrin);
|
||||
|
||||
it('should fail to parse invalid JSON', () => {
|
||||
try {
|
||||
socket['handleData']('4#"Hel');
|
||||
} catch (err) {
|
||||
expect(err.message).to.deep.equal(errorMsg);
|
||||
}
|
||||
expect(messages.length).to.deep.equal(0);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it(`should emit ${ERROR_EVENT} event on socket`, () => {
|
||||
const socketEmitSpy: sinon.SinonSpy<any, any> = sinon.spy(
|
||||
socket['socket'],
|
||||
'emit',
|
||||
);
|
||||
|
||||
socket['onData'](packet);
|
||||
|
||||
expect(socketEmitSpy.calledOnceWithExactly(ERROR_EVENT, errorMsg)).to.be
|
||||
.true;
|
||||
socketEmitSpy.restore();
|
||||
});
|
||||
|
||||
it(`should send a FIN packet`, () => {
|
||||
const socketEndSpy = sinon.spy(socket['socket'], 'end');
|
||||
|
||||
socket['onData'](packet);
|
||||
|
||||
expect(socketEndSpy.calledOnce).to.be.true;
|
||||
socketEndSpy.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Corrupted length value', () => {
|
||||
const errorMsg = `Corrupted length value "wtf" supplied in a packet`;
|
||||
const packetStrin = 'wtf#"Hello"';
|
||||
const packet = Buffer.from(packetStrin);
|
||||
|
||||
it('should not accept invalid content length', () => {
|
||||
try {
|
||||
socket['handleData'](packetStrin);
|
||||
} catch (err) {
|
||||
expect(err.message).to.deep.equal(errorMsg);
|
||||
}
|
||||
expect(messages.length).to.deep.equal(0);
|
||||
expect(socket['buffer']).to.deep.equal('');
|
||||
});
|
||||
|
||||
it(`should emit ${ERROR_EVENT} event on socket`, () => {
|
||||
const socketEmitSpy: sinon.SinonSpy<any, any> = sinon.spy(
|
||||
socket['socket'],
|
||||
'emit',
|
||||
);
|
||||
|
||||
socket['onData'](packet);
|
||||
|
||||
expect(socketEmitSpy.calledOnceWithExactly(ERROR_EVENT, errorMsg)).to.be
|
||||
.true;
|
||||
socketEmitSpy.restore();
|
||||
});
|
||||
|
||||
it(`should send a FIN packet`, () => {
|
||||
const socketEndSpy = sinon.spy(socket['socket'], 'end');
|
||||
|
||||
socket['onData'](packet);
|
||||
|
||||
expect(socketEndSpy.calledOnce).to.be.true;
|
||||
socketEndSpy.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,9 +21,9 @@ describe('ServerTCP', () => {
|
||||
.stub(server, 'getSocketInstance' as any)
|
||||
.callsFake(() => socket);
|
||||
});
|
||||
it('should bind message event to handler', () => {
|
||||
it('should bind message and error events to handler', () => {
|
||||
server.bindHandler(null);
|
||||
expect(socket.on.called).to.be.true;
|
||||
expect(socket.on.calledTwice).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('close', () => {
|
||||
@@ -44,8 +44,13 @@ describe('ServerTCP', () => {
|
||||
it('should call native listen method with expected arguments', () => {
|
||||
const callback = () => {};
|
||||
server.listen(callback);
|
||||
expect(serverMock.listen.calledWith((server as any).port, callback)).to.be
|
||||
.true;
|
||||
expect(
|
||||
serverMock.listen.calledWith(
|
||||
(server as any).port,
|
||||
(server as any).host,
|
||||
callback,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('handleMessage', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user