mirror of
https://github.com/nestjs/nest.git
synced 2026-02-24 00:02:56 +00:00
Compare commits
65 Commits
v8.4.6
...
Tony133-ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fac9c9bf2 | ||
|
|
dd58e42501 | ||
|
|
2362ade438 | ||
|
|
f11181071e | ||
|
|
11ac94e23e | ||
|
|
cd62e31bcf | ||
|
|
34143d9e79 | ||
|
|
cf42ed5b8d | ||
|
|
72c046dec7 | ||
|
|
1e8966b603 | ||
|
|
40e2755c82 | ||
|
|
64e58b750c | ||
|
|
61bcb7fe39 | ||
|
|
ddd95b810f | ||
|
|
707a3e069f | ||
|
|
66da12aecf | ||
|
|
1f5a5b4f2e | ||
|
|
be1a8c33bd | ||
|
|
e3e3ef5cc5 | ||
|
|
fc95619e4d | ||
|
|
19b55e9a37 | ||
|
|
ca967b809e | ||
|
|
206579242a | ||
|
|
ddd24f970d | ||
|
|
28afaa27dc | ||
|
|
dde23b1a2b | ||
|
|
4ca738dddf | ||
|
|
5464d72738 | ||
|
|
cfe1191d22 | ||
|
|
90af9590f5 | ||
|
|
6547f922fb | ||
|
|
a439055646 | ||
|
|
e7087adcb7 | ||
|
|
a3cfb1717a | ||
|
|
4d6216695d | ||
|
|
0d0a6c7657 | ||
|
|
87969fba5f | ||
|
|
c53f7f2cde | ||
|
|
b11959c979 | ||
|
|
bc0a1dedf8 | ||
|
|
d51c7289a8 | ||
|
|
41850c0920 | ||
|
|
eb33fe5919 | ||
|
|
c4887a7f44 | ||
|
|
18be1e1158 | ||
|
|
17d48f3cb4 | ||
|
|
37d83eeb21 | ||
|
|
0b93404bef | ||
|
|
f5051727ea | ||
|
|
5e52a7e85e | ||
|
|
0fe67d5f90 | ||
|
|
ef70418627 | ||
|
|
b91803c671 | ||
|
|
48dca4ee22 | ||
|
|
c229a75c43 | ||
|
|
77b7bd6905 | ||
|
|
5b24f81cd8 | ||
|
|
91178c8a8a | ||
|
|
4ae2ef54d4 | ||
|
|
bac34473d0 | ||
|
|
ce96a0ed13 | ||
|
|
9c0cdd2950 | ||
|
|
7cc81067fe | ||
|
|
d32e8dd582 | ||
|
|
3827736d96 |
@@ -65,11 +65,6 @@ jobs:
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
|
||||
test_node_10:
|
||||
<<: *unit-tests-template
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
|
||||
test_node_14:
|
||||
<<: *unit-tests-template
|
||||
docker:
|
||||
@@ -174,9 +169,6 @@ workflows:
|
||||
- test_node_12:
|
||||
requires:
|
||||
- build
|
||||
- test_node_10:
|
||||
requires:
|
||||
- build
|
||||
- lint:
|
||||
requires:
|
||||
- build
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('Disconnected client', () => {
|
||||
.send({
|
||||
transport: Transport.REDIS,
|
||||
options: {
|
||||
url: 'redis://localhost:3333',
|
||||
port: '3333',
|
||||
},
|
||||
})
|
||||
.expect(408);
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
Controller,
|
||||
InternalServerErrorException,
|
||||
Post,
|
||||
RequestTimeoutException,
|
||||
RequestTimeoutException
|
||||
} from '@nestjs/common';
|
||||
import { ClientProxyFactory } from '@nestjs/microservices';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
@@ -24,7 +24,8 @@ export class DisconnectedClientController {
|
||||
return throwError(() =>
|
||||
code === 'ECONNREFUSED' ||
|
||||
code === 'CONN_ERR' ||
|
||||
code === 'CONNECTION_REFUSED'
|
||||
code === 'CONNECTION_REFUSED' ||
|
||||
error.message === 'Connection is closed.'
|
||||
? new RequestTimeoutException('ECONNREFUSED')
|
||||
: new InternalServerErrorException(),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ConfigurableModuleBuilder } from '@nestjs/common';
|
||||
import { IntegrationModuleOptions } from './interfaces/integration-module-options.interface';
|
||||
|
||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
|
||||
new ConfigurableModuleBuilder<IntegrationModuleOptions>()
|
||||
.setClassMethodName('forRoot')
|
||||
.setFactoryMethodName('construct')
|
||||
.setExtras(
|
||||
{
|
||||
isGlobal: true,
|
||||
},
|
||||
(definition, extras) => ({
|
||||
...definition,
|
||||
global: extras.isGlobal,
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
16
integration/module-utils/src/integration.module.ts
Normal file
16
integration/module-utils/src/integration.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Inject, Module } from '@nestjs/common';
|
||||
import {
|
||||
ConfigurableModuleClass,
|
||||
MODULE_OPTIONS_TOKEN,
|
||||
} from './integration.module-definition';
|
||||
import { IntegrationModuleOptions } from './interfaces/integration-module-options.interface';
|
||||
|
||||
@Module({})
|
||||
export class IntegrationModule extends ConfigurableModuleClass {
|
||||
constructor(
|
||||
@Inject(MODULE_OPTIONS_TOKEN)
|
||||
public readonly options: IntegrationModuleOptions,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IntegrationModuleOptions {
|
||||
url: string;
|
||||
secure?: boolean;
|
||||
}
|
||||
47
integration/module-utils/test/integration-module.spec.ts
Normal file
47
integration/module-utils/test/integration-module.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import { IntegrationModule } from '../src/integration.module';
|
||||
|
||||
describe('Module utils (ConfigurableModuleBuilder)', () => {
|
||||
it('should auto-generate "forRoot" method', async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [
|
||||
IntegrationModule.forRoot({
|
||||
isGlobal: true,
|
||||
url: 'test_url',
|
||||
secure: false,
|
||||
}),
|
||||
],
|
||||
}).compile();
|
||||
|
||||
const integrationModule = moduleRef.get(IntegrationModule);
|
||||
|
||||
expect(integrationModule.options).to.deep.equal({
|
||||
url: 'test_url',
|
||||
secure: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should auto-generate "forRootAsync" method', async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [
|
||||
IntegrationModule.forRootAsync({
|
||||
isGlobal: true,
|
||||
useFactory: () => {
|
||||
return {
|
||||
url: 'test_url',
|
||||
secure: false,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
}).compile();
|
||||
|
||||
const integrationModule = moduleRef.get(IntegrationModule);
|
||||
|
||||
expect(integrationModule.options).to.deep.equal({
|
||||
url: 'test_url',
|
||||
secure: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
22
integration/module-utils/tsconfig.json
Normal file
22
integration/module-utils/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"e2e/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
657
package-lock.json
generated
657
package-lock.json
generated
@@ -1221,12 +1221,225 @@
|
||||
"ajv": "^6.12.6"
|
||||
}
|
||||
},
|
||||
"@fastify/busboy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.0.0.tgz",
|
||||
"integrity": "sha512-tzTXX1TFEjWCseEsNdIlXXkD+48uJoN+zpqIojUX4pSoMscsbhO/UuVEB5SzJucexqDWOo2ma0ECwdD7hZdrzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"text-decoding": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@fastify/cors": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-7.0.0.tgz",
|
||||
"integrity": "sha512-nlo6ScwagBNJacAZD3KX90xjWLIoV0vN9QqoX1wUE9ZeZMdvkVkMZCGlxEtr00NshV0X5wDge4w5rwox7rRzSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"vary": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"@fastify/error": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/error/-/error-2.0.0.tgz",
|
||||
"integrity": "sha512-wI3fpfDT0t7p8E6dA2eTECzzOd+bZsZCJ2Hcv+Onn2b7ZwK3RwD27uW2QDaMtQhAfWQQP+WNK7nKf0twLsBf9w==",
|
||||
"dev": true
|
||||
},
|
||||
"@fastify/formbody": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-6.0.0.tgz",
|
||||
"integrity": "sha512-YzPTXJbB3CzDMqU5K9YGSBt/nc/RDFZSxSWZ4SoqA3T2VRJzCPd7sZFpggdmlBRWhEBlvl0EWW7EX33kfbbFlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-plugin": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@fastify/multipart": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-6.0.0.tgz",
|
||||
"integrity": "sha512-TwxPH9jE3bEaCdMD1Xqm2YS1aelgJxcNmA/uYAPCzqnVEylDiKCmxCstGulb1W5WdMoyqD5LBGm7AoqDwWTCWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@fastify/busboy": "^1.0.0",
|
||||
"@fastify/error": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"end-of-stream": "^1.4.4",
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"hexoid": "^1.0.0",
|
||||
"secure-json-parse": "^2.4.0",
|
||||
"stream-wormhole": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@fastify/static": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-5.0.0.tgz",
|
||||
"integrity": "sha512-GGltJkO0idXa7yCZ0PfdTZ6qokWDX/vigCvmRpjOU2A3jc93c9p+oHDvHmwHK60hwWoBEGqHjGofVyn3H1CjZg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"content-disposition": "^0.5.3",
|
||||
"encoding-negotiator": "^2.0.1",
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"p-limit": "^3.1.0",
|
||||
"readable-stream": "^3.4.0",
|
||||
"send": "^0.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
|
||||
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
||||
"integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "1.8.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
|
||||
"dev": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@gar/promisify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz",
|
||||
@@ -1494,6 +1707,12 @@
|
||||
"integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@ioredis/commands": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.1.1.tgz",
|
||||
"integrity": "sha512-fsR4P/ROllzf/7lXYyElUJCheWdTJVJvOTps8v9IWKFATxR61ANOlnoPqhH099xYLrJGpc2ZQ28B3rMeUt5VQg==",
|
||||
"dev": true
|
||||
},
|
||||
"@istanbuljs/load-nyc-config": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||
@@ -2780,15 +2999,6 @@
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/redis": {
|
||||
"version": "4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz",
|
||||
"integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"redis": "*"
|
||||
}
|
||||
},
|
||||
"@types/reflect-metadata": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/reflect-metadata/-/reflect-metadata-0.1.0.tgz",
|
||||
@@ -5849,6 +6059,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
|
||||
"dev": true
|
||||
},
|
||||
"code-block-writer": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.0.tgz",
|
||||
@@ -8449,6 +8665,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding-negotiator": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/encoding-negotiator/-/encoding-negotiator-2.0.1.tgz",
|
||||
"integrity": "sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
@@ -9930,297 +10152,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastify-cors": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.1.0.tgz",
|
||||
"integrity": "sha512-QBKz32IoY/iuT74CunRY1XOSpjSTIOh9E3FxulXIBhd0D2vdgG0kDvy0eG6HA/88sRfWHeba43LkGEXPz0Rh8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-cors-deprecated": "npm:fastify-cors@6.0.3",
|
||||
"process-warning": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify-cors-deprecated": {
|
||||
"version": "npm:fastify-cors@6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.3.tgz",
|
||||
"integrity": "sha512-fMbXubKKyBHHCfSBtsCi3+7VyVRdhJQmGes5gM+eGKkRErCdm0NaYO0ozd31BQBL1ycoTIjbqOZhJo4RTF/Vlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"vary": "^1.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastify-formbody": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-formbody/-/fastify-formbody-5.3.0.tgz",
|
||||
"integrity": "sha512-7cjFV2HE/doojyfTwCLToIFD6Hmbw2jVTbfqZ2lbUZznQWlSXu+MBQgqBU8T2nHcMfqSi9vx6PyX0LwTehuKkg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-formbody-deprecated": "npm:fastify-formbody@5.2.0",
|
||||
"process-warning": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify-formbody-deprecated": {
|
||||
"version": "npm:fastify-formbody@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-formbody/-/fastify-formbody-5.2.0.tgz",
|
||||
"integrity": "sha512-d8Y5hCL82akPyoFiXh2wYOm3es0pV9jqoPo3pO9OV2cNF0cQx39J5WAVXzCh4MSt9Z2qF4Fy5gHlvlyESwjtvg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-plugin": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastify-multipart": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-multipart/-/fastify-multipart-5.4.0.tgz",
|
||||
"integrity": "sha512-Pafy4mtcuFUnFM/t0kgCdL854KIEoDymNVdv4nD7uBfV7lBCQq/NVEuNnaNXAbuCTpeXzYRzi50lSDa9ZM838A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-multipart-deprecated": "npm:fastify-multipart@5.3.1",
|
||||
"process-warning": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"fastify-multipart-deprecated": {
|
||||
"version": "npm:fastify-multipart@5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fastify-multipart/-/fastify-multipart-5.3.1.tgz",
|
||||
"integrity": "sha512-c2pnGfkJmiNpYqzFYT2QfBg/06AxG531O+n1elqc8YUbWPRzufdqn3yfGAIV3RA7J4Vnf7Pfvgx0iaWqaRTOVA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@fastify/busboy": "^1.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"end-of-stream": "^1.4.4",
|
||||
"fastify-error": "^0.3.0",
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"hexoid": "^1.0.0",
|
||||
"secure-json-parse": "^2.4.0",
|
||||
"stream-wormhole": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/busboy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.0.0.tgz",
|
||||
"integrity": "sha512-tzTXX1TFEjWCseEsNdIlXXkD+48uJoN+zpqIojUX4pSoMscsbhO/UuVEB5SzJucexqDWOo2ma0ECwdD7hZdrzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"text-decoding": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fastify-error": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz",
|
||||
"integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"stream-wormhole": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz",
|
||||
"integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastify-plugin": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz",
|
||||
"integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==",
|
||||
"dev": true
|
||||
},
|
||||
"fastify-static": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.7.0.tgz",
|
||||
"integrity": "sha512-zZhCfJv/hkmud2qhWqpU3K9XVAuy3+IV8Tp9BC5J5U+GyA2XwoB6h8lh9GqpEIqdXOw01WyWQllV7dOWVyAlXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fastify-static-deprecated": "npm:fastify-static@4.6.1",
|
||||
"process-warning": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
|
||||
"dev": true
|
||||
},
|
||||
"fastify-static-deprecated": {
|
||||
"version": "npm:fastify-static@4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.6.1.tgz",
|
||||
"integrity": "sha512-vy7N28U4AMhuOim12ZZWHulEE6OQKtzZbHgiB8Zj4llUuUQXPka0WHAQI3njm1jTCx4W6fixUHfpITxweMtAIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"content-disposition": "^0.5.3",
|
||||
"encoding-negotiator": "^2.0.1",
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"p-limit": "^3.1.0",
|
||||
"readable-stream": "^3.4.0",
|
||||
"send": "^0.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"encoding-negotiator": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/encoding-negotiator/-/encoding-negotiator-2.0.1.tgz",
|
||||
"integrity": "sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
|
||||
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
||||
"integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "1.8.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
|
||||
"dev": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastq": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz",
|
||||
@@ -11962,12 +11899,6 @@
|
||||
"integrity": "sha512-hvyIS71vs4Tu/yUYHPvGXsTgo0t3arU820+lT5VjZS2go0ewp2LqyCgxEN56CzOG7Iys52eRhHBiD1gGRdiQtw==",
|
||||
"dev": true
|
||||
},
|
||||
"growl": {
|
||||
"version": "1.10.5",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
|
||||
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
|
||||
"dev": true
|
||||
},
|
||||
"gulp": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
|
||||
@@ -13216,6 +13147,46 @@
|
||||
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
|
||||
"dev": true
|
||||
},
|
||||
"ioredis": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.0.4.tgz",
|
||||
"integrity": "sha512-qFJw3MnPNsJF1lcIOP3vztbsasOXK3nDdNAgjQj7t7/Bn/w10PGchTOpqylQNxjzPbLoYDu34LjeJtSWiKBntQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.0.1",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"denque": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
|
||||
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
@@ -15187,6 +15158,12 @@
|
||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.escape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz",
|
||||
@@ -16282,32 +16259,30 @@
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz",
|
||||
"integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz",
|
||||
"integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ungap/promise-all-settled": "1.1.2",
|
||||
"ansi-colors": "4.1.1",
|
||||
"browser-stdout": "1.3.1",
|
||||
"chokidar": "3.5.3",
|
||||
"debug": "4.3.3",
|
||||
"debug": "4.3.4",
|
||||
"diff": "5.0.0",
|
||||
"escape-string-regexp": "4.0.0",
|
||||
"find-up": "5.0.0",
|
||||
"glob": "7.2.0",
|
||||
"growl": "1.10.5",
|
||||
"he": "1.2.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"log-symbols": "4.1.0",
|
||||
"minimatch": "4.2.1",
|
||||
"minimatch": "5.0.1",
|
||||
"ms": "2.1.3",
|
||||
"nanoid": "3.3.1",
|
||||
"nanoid": "3.3.3",
|
||||
"serialize-javascript": "6.0.0",
|
||||
"strip-json-comments": "3.1.1",
|
||||
"supports-color": "8.1.1",
|
||||
"which": "2.0.2",
|
||||
"workerpool": "6.2.0",
|
||||
"workerpool": "6.2.1",
|
||||
"yargs": "16.2.0",
|
||||
"yargs-parser": "20.2.4",
|
||||
"yargs-unparser": "2.0.0"
|
||||
@@ -16412,9 +16387,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
@@ -16550,12 +16525,23 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz",
|
||||
"integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
|
||||
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
@@ -16627,15 +16613,6 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "20.2.4",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
|
||||
@@ -17031,9 +17008,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
|
||||
"integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
|
||||
"integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
|
||||
"dev": true
|
||||
},
|
||||
"nanomatch": {
|
||||
@@ -20765,6 +20742,12 @@
|
||||
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
|
||||
"dev": true
|
||||
},
|
||||
"standard-as-callback": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"dev": true
|
||||
},
|
||||
"static-eval": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
|
||||
@@ -20894,6 +20877,12 @@
|
||||
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
|
||||
"dev": true
|
||||
},
|
||||
"stream-wormhole": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz",
|
||||
"integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==",
|
||||
"dev": true
|
||||
},
|
||||
"streamsearch": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
||||
@@ -21594,6 +21583,12 @@
|
||||
"through2": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"dev": true
|
||||
},
|
||||
"touch": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||
@@ -22806,9 +22801,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"workerpool": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz",
|
||||
"integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
|
||||
"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
|
||||
"dev": true
|
||||
},
|
||||
"wrap-ansi": {
|
||||
|
||||
15
package.json
15
package.json
@@ -72,6 +72,10 @@
|
||||
"@codechecks/client": "0.1.12",
|
||||
"@commitlint/cli": "17.0.0",
|
||||
"@commitlint/config-angular": "17.0.0",
|
||||
"@fastify/cors": "7.0.0",
|
||||
"@fastify/formbody": "6.0.0",
|
||||
"@fastify/multipart": "6.0.0",
|
||||
"@fastify/static": "5.0.0",
|
||||
"@grpc/grpc-js": "1.6.7",
|
||||
"@grpc/proto-loader": "0.6.12",
|
||||
"@nestjs/apollo": "10.0.11",
|
||||
@@ -89,7 +93,6 @@
|
||||
"@types/http-errors": "1.8.2",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "17.0.34",
|
||||
"@types/redis": "4.0.11",
|
||||
"@types/reflect-metadata": "0.1.0",
|
||||
"@types/sinon": "10.0.11",
|
||||
"@types/socket.io": "3.0.2",
|
||||
@@ -122,10 +125,6 @@
|
||||
"eventsource": "2.0.2",
|
||||
"fancy-log": "2.0.0",
|
||||
"fastify": "3.29.0",
|
||||
"fastify-cors": "6.1.0",
|
||||
"fastify-formbody": "5.3.0",
|
||||
"fastify-multipart": "5.4.0",
|
||||
"fastify-static": "4.7.0",
|
||||
"graphql": "15.8.0",
|
||||
"graphql-tools": "8.2.9",
|
||||
"gulp": "4.0.2",
|
||||
@@ -137,6 +136,7 @@
|
||||
"http-errors": "2.0.0",
|
||||
"husky": "8.0.1",
|
||||
"imports-loader": "3.1.1",
|
||||
"ioredis": "5.0.4",
|
||||
"json-loader": "0.5.7",
|
||||
"kafkajs": "2.0.0",
|
||||
"lerna": "3.0.0",
|
||||
@@ -146,7 +146,7 @@
|
||||
"markdown-table": "2.0.0",
|
||||
"merge-graphql-schemas": "1.7.8",
|
||||
"middie": "6.1.0",
|
||||
"mocha": "9.2.2",
|
||||
"mocha": "10.0.0",
|
||||
"mongoose": "6.3.3",
|
||||
"mqtt": "4.3.7",
|
||||
"multer": "1.4.4",
|
||||
@@ -156,7 +156,6 @@
|
||||
"nyc": "15.1.0",
|
||||
"point-of-view": "5.3.0",
|
||||
"prettier": "2.6.2",
|
||||
"redis": "3.1.2",
|
||||
"rxjs-compat": "6.6.7",
|
||||
"sinon": "14.0.0",
|
||||
"sinon-chai": "3.7.0",
|
||||
@@ -171,7 +170,7 @@
|
||||
"ws": "8.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
"node": ">= 12.9.0"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
|
||||
1
packages/common/cache/cache.constants.ts
vendored
1
packages/common/cache/cache.constants.ts
vendored
@@ -1,4 +1,3 @@
|
||||
export const CACHE_MANAGER = 'CACHE_MANAGER';
|
||||
export const CACHE_MODULE_OPTIONS = 'CACHE_MODULE_OPTIONS';
|
||||
export const CACHE_KEY_METADATA = 'cache_module:cache_key';
|
||||
export const CACHE_TTL_METADATA = 'cache_module:cache_ttl';
|
||||
|
||||
12
packages/common/cache/cache.module-definition.ts
vendored
Normal file
12
packages/common/cache/cache.module-definition.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ConfigurableModuleBuilder } from '../module-utils';
|
||||
import {
|
||||
CacheModuleOptions,
|
||||
CacheOptionsFactory,
|
||||
} from './interfaces/cache-module.interface';
|
||||
|
||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
|
||||
new ConfigurableModuleBuilder<CacheModuleOptions>({
|
||||
moduleName: 'Cache',
|
||||
})
|
||||
.setFactoryMethodName('createCacheOptions' as keyof CacheOptionsFactory)
|
||||
.build();
|
||||
53
packages/common/cache/cache.module.ts
vendored
53
packages/common/cache/cache.module.ts
vendored
@@ -1,11 +1,11 @@
|
||||
import { Module } from '../decorators';
|
||||
import { DynamicModule, Provider } from '../interfaces';
|
||||
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from './cache.constants';
|
||||
import { DynamicModule } from '../interfaces';
|
||||
import { CACHE_MANAGER } from './cache.constants';
|
||||
import { ConfigurableModuleClass } from './cache.module-definition';
|
||||
import { createCacheManager } from './cache.providers';
|
||||
import {
|
||||
CacheModuleAsyncOptions,
|
||||
CacheModuleOptions,
|
||||
CacheOptionsFactory,
|
||||
} from './interfaces/cache-module.interface';
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
providers: [createCacheManager()],
|
||||
exports: [CACHE_MANAGER],
|
||||
})
|
||||
export class CacheModule {
|
||||
export class CacheModule extends ConfigurableModuleClass {
|
||||
/**
|
||||
* Configure the cache manager statically.
|
||||
*
|
||||
@@ -31,9 +31,8 @@ export class CacheModule {
|
||||
options: CacheModuleOptions<StoreConfig> = {} as any,
|
||||
): DynamicModule {
|
||||
return {
|
||||
module: CacheModule,
|
||||
global: options.isGlobal,
|
||||
providers: [{ provide: CACHE_MODULE_OPTIONS, useValue: options }],
|
||||
...super.register(options),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,47 +47,11 @@ export class CacheModule {
|
||||
static registerAsync<
|
||||
StoreConfig extends Record<any, any> = Record<string, any>,
|
||||
>(options: CacheModuleAsyncOptions<StoreConfig>): DynamicModule {
|
||||
const moduleDefinition = super.registerAsync(options);
|
||||
return {
|
||||
module: CacheModule,
|
||||
global: options.isGlobal,
|
||||
imports: options.imports,
|
||||
providers: [
|
||||
...this.createAsyncProviders<StoreConfig>(options),
|
||||
...(options.extraProviders || []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private static createAsyncProviders<StoreConfig extends Record<any, any>>(
|
||||
options: CacheModuleAsyncOptions<StoreConfig>,
|
||||
): Provider[] {
|
||||
if (options.useExisting || options.useFactory) {
|
||||
return [this.createAsyncOptionsProvider(options)];
|
||||
}
|
||||
return [
|
||||
this.createAsyncOptionsProvider(options),
|
||||
{
|
||||
provide: options.useClass,
|
||||
useClass: options.useClass,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private static createAsyncOptionsProvider<
|
||||
StoreConfig extends Record<any, any>,
|
||||
>(options: CacheModuleAsyncOptions<StoreConfig>): Provider {
|
||||
if (options.useFactory) {
|
||||
return {
|
||||
provide: CACHE_MODULE_OPTIONS,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject || [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
provide: CACHE_MODULE_OPTIONS,
|
||||
useFactory: async (optionsFactory: CacheOptionsFactory<StoreConfig>) =>
|
||||
optionsFactory.createCacheOptions(),
|
||||
inject: [options.useExisting || options.useClass],
|
||||
...moduleDefinition,
|
||||
providers: moduleDefinition.providers.concat(options.extraProviders),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
5
packages/common/cache/cache.providers.ts
vendored
5
packages/common/cache/cache.providers.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { Provider } from '../interfaces';
|
||||
import { loadPackage } from '../utils/load-package.util';
|
||||
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from './cache.constants';
|
||||
import { CACHE_MANAGER } from './cache.constants';
|
||||
import { MODULE_OPTIONS_TOKEN } from './cache.module-definition';
|
||||
import { defaultCacheOptions } from './default-options';
|
||||
import { CacheManagerOptions } from './interfaces/cache-manager.interface';
|
||||
|
||||
@@ -31,6 +32,6 @@ export function createCacheManager(): Provider {
|
||||
...(options || {}),
|
||||
});
|
||||
},
|
||||
inject: [CACHE_MODULE_OPTIONS],
|
||||
inject: [MODULE_OPTIONS_TOKEN],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ModuleMetadata, Provider, Type } from '../../interfaces';
|
||||
import { Provider, Type } from '../../interfaces';
|
||||
import { ConfigurableModuleAsyncOptions } from '../../module-utils';
|
||||
import { CacheManagerOptions } from './cache-manager.interface';
|
||||
|
||||
export type CacheModuleOptions<
|
||||
@@ -39,7 +40,10 @@ export interface CacheOptionsFactory<
|
||||
*/
|
||||
export interface CacheModuleAsyncOptions<
|
||||
StoreConfig extends Record<any, any> = Record<string, any>,
|
||||
> extends Pick<ModuleMetadata, 'imports'> {
|
||||
> extends ConfigurableModuleAsyncOptions<
|
||||
CacheModuleOptions<StoreConfig>,
|
||||
keyof CacheOptionsFactory
|
||||
> {
|
||||
/**
|
||||
* Injection token resolving to an existing provider. The provider must implement
|
||||
* the `CacheOptionsFactory` interface.
|
||||
@@ -63,6 +67,9 @@ export interface CacheModuleAsyncOptions<
|
||||
* Dependencies that a Factory may inject.
|
||||
*/
|
||||
inject?: any[];
|
||||
/**
|
||||
* Extra providers to be registered within a scope of this module.
|
||||
*/
|
||||
extraProviders?: Provider[];
|
||||
/**
|
||||
* If "true', register `CacheModule` as a global module.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const AXIOS_INSTANCE_TOKEN = 'AXIOS_INSTANCE_TOKEN';
|
||||
export const HTTP_MODULE_ID = 'HTTP_MODULE_ID';
|
||||
export const HTTP_MODULE_OPTIONS = 'HTTP_MODULE_OPTIONS';
|
||||
@@ -1,99 +0,0 @@
|
||||
import Axios from 'axios';
|
||||
import { Module } from '../decorators/modules/module.decorator';
|
||||
import { DynamicModule, Provider } from '../interfaces';
|
||||
import { randomStringGenerator } from '../utils/random-string-generator.util';
|
||||
import {
|
||||
AXIOS_INSTANCE_TOKEN,
|
||||
HTTP_MODULE_ID,
|
||||
HTTP_MODULE_OPTIONS,
|
||||
} from './http.constants';
|
||||
import { HttpService } from './http.service';
|
||||
import {
|
||||
HttpModuleAsyncOptions,
|
||||
HttpModuleOptions,
|
||||
HttpModuleOptionsFactory,
|
||||
} from './interfaces';
|
||||
|
||||
/**
|
||||
* @deprecated "HttpModule" (from the "@nestjs/common" package) is deprecated and will be removed in the next major release. Please, use the "@nestjs/axios" package instead.
|
||||
*/
|
||||
@Module({
|
||||
providers: [
|
||||
HttpService,
|
||||
{
|
||||
provide: AXIOS_INSTANCE_TOKEN,
|
||||
useValue: Axios,
|
||||
},
|
||||
],
|
||||
exports: [HttpService],
|
||||
})
|
||||
export class HttpModule {
|
||||
static register(config: HttpModuleOptions): DynamicModule {
|
||||
return {
|
||||
module: HttpModule,
|
||||
providers: [
|
||||
{
|
||||
provide: AXIOS_INSTANCE_TOKEN,
|
||||
useValue: Axios.create(config),
|
||||
},
|
||||
{
|
||||
provide: HTTP_MODULE_ID,
|
||||
useValue: randomStringGenerator(),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static registerAsync(options: HttpModuleAsyncOptions): DynamicModule {
|
||||
return {
|
||||
module: HttpModule,
|
||||
imports: options.imports,
|
||||
providers: [
|
||||
...this.createAsyncProviders(options),
|
||||
{
|
||||
provide: AXIOS_INSTANCE_TOKEN,
|
||||
useFactory: (config: HttpModuleOptions) => Axios.create(config),
|
||||
inject: [HTTP_MODULE_OPTIONS],
|
||||
},
|
||||
{
|
||||
provide: HTTP_MODULE_ID,
|
||||
useValue: randomStringGenerator(),
|
||||
},
|
||||
...(options.extraProviders || []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private static createAsyncProviders(
|
||||
options: HttpModuleAsyncOptions,
|
||||
): Provider[] {
|
||||
if (options.useExisting || options.useFactory) {
|
||||
return [this.createAsyncOptionsProvider(options)];
|
||||
}
|
||||
return [
|
||||
this.createAsyncOptionsProvider(options),
|
||||
{
|
||||
provide: options.useClass,
|
||||
useClass: options.useClass,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private static createAsyncOptionsProvider(
|
||||
options: HttpModuleAsyncOptions,
|
||||
): Provider {
|
||||
if (options.useFactory) {
|
||||
return {
|
||||
provide: HTTP_MODULE_OPTIONS,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject || [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
provide: HTTP_MODULE_OPTIONS,
|
||||
useFactory: async (optionsFactory: HttpModuleOptionsFactory) =>
|
||||
optionsFactory.createHttpOptions(),
|
||||
inject: [options.useExisting || options.useClass],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import Axios, {
|
||||
AxiosInstance,
|
||||
AxiosPromise,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
CancelTokenSource,
|
||||
} from 'axios';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Inject } from '../decorators';
|
||||
import { Logger } from '../services';
|
||||
import { AXIOS_INSTANCE_TOKEN } from './http.constants';
|
||||
|
||||
/**
|
||||
* @deprecated "HttpModule" (from the "@nestjs/common" package) is deprecated and will be removed in the next major release. Please, use the "@nestjs/axios" package instead.
|
||||
*/
|
||||
export class HttpService {
|
||||
private readonly logger = new Logger(HttpService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(AXIOS_INSTANCE_TOKEN)
|
||||
private readonly instance: AxiosInstance = Axios,
|
||||
) {
|
||||
this.logger.warn(
|
||||
'DEPRECATED! "HttpModule" (from the "@nestjs/common" package) is deprecated and will be removed in the next major release. Please, use the "@nestjs/axios" package instead.',
|
||||
);
|
||||
}
|
||||
|
||||
request<T = any>(config: AxiosRequestConfig): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.request, config);
|
||||
}
|
||||
|
||||
get<T = any>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig,
|
||||
): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.get, url, config);
|
||||
}
|
||||
|
||||
delete<T = any>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig,
|
||||
): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.delete, url, config);
|
||||
}
|
||||
|
||||
head<T = any>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig,
|
||||
): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.head, url, config);
|
||||
}
|
||||
|
||||
post<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig,
|
||||
): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.post, url, data, config);
|
||||
}
|
||||
|
||||
put<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig,
|
||||
): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.put, url, data, config);
|
||||
}
|
||||
|
||||
patch<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig,
|
||||
): Observable<AxiosResponse<T>> {
|
||||
return this.makeObservable<T>(this.instance.patch, url, data, config);
|
||||
}
|
||||
|
||||
get axiosRef(): AxiosInstance {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private makeObservable<T>(
|
||||
axios: (...args: any[]) => AxiosPromise<T>,
|
||||
...args: any[]
|
||||
) {
|
||||
return new Observable<AxiosResponse<T>>(subscriber => {
|
||||
const config: AxiosRequestConfig = { ...(args[args.length - 1] || {}) };
|
||||
|
||||
let cancelSource: CancelTokenSource;
|
||||
if (!config.cancelToken) {
|
||||
cancelSource = Axios.CancelToken.source();
|
||||
config.cancelToken = cancelSource.token;
|
||||
}
|
||||
|
||||
axios(...args)
|
||||
.then(res => {
|
||||
subscriber.next(res);
|
||||
subscriber.complete();
|
||||
})
|
||||
.catch(err => {
|
||||
subscriber.error(err);
|
||||
});
|
||||
return () => {
|
||||
if (config.responseType === 'stream') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cancelSource) {
|
||||
cancelSource.cancel();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './http.module';
|
||||
export * from './http.service';
|
||||
export * from './interfaces';
|
||||
@@ -1,19 +0,0 @@
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { ModuleMetadata, Provider, Type } from '../../interfaces';
|
||||
|
||||
export type HttpModuleOptions = AxiosRequestConfig;
|
||||
|
||||
export interface HttpModuleOptionsFactory {
|
||||
createHttpOptions(): Promise<HttpModuleOptions> | HttpModuleOptions;
|
||||
}
|
||||
|
||||
export interface HttpModuleAsyncOptions
|
||||
extends Pick<ModuleMetadata, 'imports'> {
|
||||
useExisting?: Type<HttpModuleOptionsFactory>;
|
||||
useClass?: Type<HttpModuleOptionsFactory>;
|
||||
useFactory?: (
|
||||
...args: any[]
|
||||
) => Promise<HttpModuleOptions> | HttpModuleOptions;
|
||||
inject?: any[];
|
||||
extraProviders?: Provider[];
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './http-module.interface';
|
||||
export * from './raw-body-request.interface';
|
||||
@@ -11,7 +11,6 @@ export * from './decorators';
|
||||
export * from './enums';
|
||||
export * from './exceptions';
|
||||
export * from './file-stream';
|
||||
export * from './http';
|
||||
export {
|
||||
Abstract,
|
||||
ArgumentMetadata,
|
||||
@@ -49,6 +48,7 @@ export {
|
||||
Paramtype,
|
||||
PipeTransform,
|
||||
Provider,
|
||||
RawBodyRequest,
|
||||
RpcExceptionFilter,
|
||||
Scope,
|
||||
ScopeOptions,
|
||||
@@ -61,6 +61,7 @@ export {
|
||||
WsExceptionFilter,
|
||||
WsMessageHandler,
|
||||
} from './interfaces';
|
||||
export * from './module-utils';
|
||||
export * from './pipes';
|
||||
export * from './serializer';
|
||||
export * from './services';
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './http-server.interface';
|
||||
export * from './message-event.interface';
|
||||
export * from './raw-body-request.interface';
|
||||
|
||||
@@ -46,6 +46,12 @@ export interface ClassProvider<T = any> {
|
||||
* Optional enum defining lifetime of the provider that is injected.
|
||||
*/
|
||||
scope?: Scope;
|
||||
/**
|
||||
* This option is only available on factory providers!
|
||||
*
|
||||
* @see [Use factory](https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory)
|
||||
*/
|
||||
inject?: never;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +78,12 @@ export interface ValueProvider<T = any> {
|
||||
* Instance of a provider to be injected.
|
||||
*/
|
||||
useValue: T;
|
||||
/**
|
||||
* This option is only available on factory providers!
|
||||
*
|
||||
* @see [Use factory](https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory)
|
||||
*/
|
||||
inject?: never;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +114,7 @@ export interface FactoryProvider<T = any> {
|
||||
/**
|
||||
* Factory function that returns an instance of the provider to be injected.
|
||||
*/
|
||||
useFactory: (...args: any[]) => T;
|
||||
useFactory: (...args: any[]) => T | Promise<T>;
|
||||
/**
|
||||
* Optional list of providers to be injected into the context of the Factory function.
|
||||
*/
|
||||
|
||||
330
packages/common/module-utils/configurable-module.builder.ts
Normal file
330
packages/common/module-utils/configurable-module.builder.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
import { DynamicModule, Provider } from '../interfaces';
|
||||
import { Logger } from '../services/logger.service';
|
||||
import { randomStringGenerator } from '../utils/random-string-generator.util';
|
||||
import {
|
||||
ASYNC_METHOD_SUFFIX,
|
||||
CONFIGURABLE_MODULE_ID,
|
||||
DEFAULT_FACTORY_CLASS_METHOD_KEY,
|
||||
DEFAULT_METHOD_KEY,
|
||||
} from './constants';
|
||||
import {
|
||||
ConfigurableModuleAsyncOptions,
|
||||
ConfigurableModuleCls,
|
||||
ConfigurableModuleOptionsFactory,
|
||||
} from './interfaces';
|
||||
import { ConfigurableModuleHost } from './interfaces/configurable-module-host.interface';
|
||||
import { generateOptionsInjectionToken } from './utils/generate-options-injection-token.util';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ConfigurableModuleBuilderOptions {
|
||||
/**
|
||||
* Specified what injection token should be used for the module options provider.
|
||||
* By default, an auto-generated UUID will be used.
|
||||
*/
|
||||
optionsInjectionToken?: string | symbol;
|
||||
/**
|
||||
* By default, an UUID will be used as a module options provider token.
|
||||
* Explicitly specifying the "moduleName" will instruct the "ConfigurableModuleBuilder"
|
||||
* to use a more descriptive provider token.
|
||||
*
|
||||
* For example, if `moduleName: "Cache"` then auto-generated provider token will be "CACHE_MODULE_OPTIONS".
|
||||
*/
|
||||
moduleName?: string;
|
||||
/**
|
||||
* Indicates whether module should always be "transient", meaning,
|
||||
* every time you call the static method to construct a dynamic module,
|
||||
* regardless of what arguments you pass in, a new "unique" module will be created.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
alwaysTransient?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory that lets you create configurable modules and
|
||||
* provides a way to reduce the majority of dynamic module boilerplate.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class ConfigurableModuleBuilder<
|
||||
ModuleOptions,
|
||||
StaticMethodKey extends string = typeof DEFAULT_METHOD_KEY,
|
||||
FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
|
||||
ExtraModuleDefinitionOptions = {},
|
||||
> {
|
||||
protected staticMethodKey: StaticMethodKey;
|
||||
protected factoryClassMethodKey: FactoryClassMethodKey;
|
||||
protected extras: ExtraModuleDefinitionOptions;
|
||||
protected transformModuleDefinition: (
|
||||
definition: DynamicModule,
|
||||
extraOptions: ExtraModuleDefinitionOptions,
|
||||
) => DynamicModule;
|
||||
|
||||
protected readonly logger = new Logger(ConfigurableModuleBuilder.name);
|
||||
|
||||
constructor(
|
||||
protected readonly options: ConfigurableModuleBuilderOptions = {},
|
||||
parentBuilder?: ConfigurableModuleBuilder<ModuleOptions>,
|
||||
) {
|
||||
if (parentBuilder) {
|
||||
this.staticMethodKey = parentBuilder.staticMethodKey as StaticMethodKey;
|
||||
this.factoryClassMethodKey =
|
||||
parentBuilder.factoryClassMethodKey as FactoryClassMethodKey;
|
||||
this.transformModuleDefinition = parentBuilder.transformModuleDefinition;
|
||||
this.extras = parentBuilder.extras as ExtraModuleDefinitionOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the "extras" object (a set of extra options that can be used to modify the dynamic module definition).
|
||||
* Values you specify within the "extras" object will be used as default values (that can be overriden by module consumers).
|
||||
*
|
||||
* This method also applies the so-called "module definition transform function" that takes the auto-generated
|
||||
* dynamic module object ("DynamicModule") and the actual consumer "extras" object as input parameters.
|
||||
* The "extras" object consists of values explicitly specified by module consumers and default values.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* .setExtras<{ isGlobal?: boolean }>({ isGlobal: false }, (definition, extras) =>
|
||||
* ({ ...definition, global: extras.isGlobal })
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
setExtras<ExtraModuleDefinitionOptions>(
|
||||
extras: ExtraModuleDefinitionOptions,
|
||||
transformDefinition: (
|
||||
definition: DynamicModule,
|
||||
extras: ExtraModuleDefinitionOptions,
|
||||
) => DynamicModule,
|
||||
) {
|
||||
const builder = new ConfigurableModuleBuilder<
|
||||
ModuleOptions,
|
||||
StaticMethodKey,
|
||||
FactoryClassMethodKey,
|
||||
ExtraModuleDefinitionOptions
|
||||
>(this.options, this as any);
|
||||
builder.extras = extras;
|
||||
builder.transformModuleDefinition = transformDefinition;
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic modules must expose public static methods that let you pass in
|
||||
* configuration parameters (control the module's behavior from the outside).
|
||||
* Some frequently used names that you may have seen in other modules are:
|
||||
* "forRoot", "forFeature", "register", "configure".
|
||||
*
|
||||
* This method "setClassMethodName" lets you specify the name of the
|
||||
* method that will be auto-generated.
|
||||
*
|
||||
* @param key name of the method
|
||||
*/
|
||||
setClassMethodName<StaticMethodKey extends string>(key: StaticMethodKey) {
|
||||
const builder = new ConfigurableModuleBuilder<
|
||||
ModuleOptions,
|
||||
StaticMethodKey,
|
||||
FactoryClassMethodKey,
|
||||
ExtraModuleDefinitionOptions
|
||||
>(this.options, this as any);
|
||||
builder.staticMethodKey = key;
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously configured modules (that rely on other modules, i.e. "ConfigModule")
|
||||
* let you pass the configuration factory class that will be registered and instantiated as a provider.
|
||||
* This provider then will be used to retrieve the module's configuration. To provide the configuration,
|
||||
* the corresponding factory method must be implemented.
|
||||
*
|
||||
* This method ("setFactoryMethodName") lets you control what method name will have to be
|
||||
* implemented by the config factory (default is "create").
|
||||
*
|
||||
* @param key name of the method
|
||||
*/
|
||||
setFactoryMethodName<FactoryClassMethodKey extends string>(
|
||||
key: FactoryClassMethodKey,
|
||||
) {
|
||||
const builder = new ConfigurableModuleBuilder<
|
||||
ModuleOptions,
|
||||
StaticMethodKey,
|
||||
FactoryClassMethodKey,
|
||||
ExtraModuleDefinitionOptions
|
||||
>(this.options, this as any);
|
||||
builder.factoryClassMethodKey = key;
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object consisting of multiple properties that lets you
|
||||
* easily construct dynamic configurable modules. See "ConfigurableModuleHost" interface for more details.
|
||||
*/
|
||||
build(): ConfigurableModuleHost<
|
||||
ModuleOptions,
|
||||
StaticMethodKey,
|
||||
FactoryClassMethodKey,
|
||||
ExtraModuleDefinitionOptions
|
||||
> {
|
||||
this.staticMethodKey ??= DEFAULT_METHOD_KEY as StaticMethodKey;
|
||||
this.factoryClassMethodKey ??=
|
||||
DEFAULT_FACTORY_CLASS_METHOD_KEY as FactoryClassMethodKey;
|
||||
this.options.optionsInjectionToken ??= this.options.moduleName
|
||||
? this.constructInjectionTokenString()
|
||||
: generateOptionsInjectionToken();
|
||||
this.transformModuleDefinition ??= definition => definition;
|
||||
|
||||
return {
|
||||
ConfigurableModuleClass:
|
||||
this.createConfigurableModuleCls<ModuleOptions>(),
|
||||
MODULE_OPTIONS_TOKEN: this.options.optionsInjectionToken,
|
||||
ASYNC_OPTIONS_TYPE: this.createTypeProxy('ASYNC_OPTIONS_TYPE'),
|
||||
OPTIONS_TYPE: this.createTypeProxy('OPTIONS_TYPE'),
|
||||
};
|
||||
}
|
||||
|
||||
private constructInjectionTokenString(): string {
|
||||
const moduleNameInSnakeCase = this.options.moduleName
|
||||
.trim()
|
||||
.split(/(?=[A-Z])/)
|
||||
.join('_')
|
||||
.toUpperCase();
|
||||
return `${moduleNameInSnakeCase}_MODULE_OPTIONS`;
|
||||
}
|
||||
|
||||
private createConfigurableModuleCls<ModuleOptions>(): ConfigurableModuleCls<
|
||||
ModuleOptions,
|
||||
StaticMethodKey,
|
||||
FactoryClassMethodKey
|
||||
> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
const asyncMethodKey = this.staticMethodKey + ASYNC_METHOD_SUFFIX;
|
||||
|
||||
class InternalModuleClass {
|
||||
static [self.staticMethodKey](
|
||||
options: ModuleOptions & ExtraModuleDefinitionOptions,
|
||||
): DynamicModule {
|
||||
const providers = [
|
||||
{
|
||||
provide: self.options.optionsInjectionToken,
|
||||
useValue: this.omitExtras(options, self.extras),
|
||||
},
|
||||
];
|
||||
if (self.options.alwaysTransient) {
|
||||
providers.push({
|
||||
provide: CONFIGURABLE_MODULE_ID,
|
||||
useValue: randomStringGenerator(),
|
||||
});
|
||||
}
|
||||
return self.transformModuleDefinition(
|
||||
{
|
||||
module: this,
|
||||
providers,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
static [asyncMethodKey](
|
||||
options: ConfigurableModuleAsyncOptions<ModuleOptions> &
|
||||
ExtraModuleDefinitionOptions,
|
||||
): DynamicModule {
|
||||
const providers = this.createAsyncProviders(options);
|
||||
if (self.options.alwaysTransient) {
|
||||
providers.push({
|
||||
provide: CONFIGURABLE_MODULE_ID,
|
||||
useValue: randomStringGenerator(),
|
||||
});
|
||||
}
|
||||
return self.transformModuleDefinition(
|
||||
{
|
||||
module: this,
|
||||
imports: options.imports || [],
|
||||
providers,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
private static omitExtras(
|
||||
input: ModuleOptions & ExtraModuleDefinitionOptions,
|
||||
extras: ExtraModuleDefinitionOptions | undefined,
|
||||
): ModuleOptions {
|
||||
if (!extras) {
|
||||
return input;
|
||||
}
|
||||
const moduleOptions = {};
|
||||
const extrasKeys = Object.keys(extras);
|
||||
|
||||
Object.keys(input)
|
||||
.filter(key => !extrasKeys.includes(key))
|
||||
.forEach(key => {
|
||||
moduleOptions[key] = input[key];
|
||||
});
|
||||
return moduleOptions as ModuleOptions;
|
||||
}
|
||||
|
||||
private static createAsyncProviders(
|
||||
options: ConfigurableModuleAsyncOptions<ModuleOptions>,
|
||||
): Provider[] {
|
||||
if (options.useExisting || options.useFactory) {
|
||||
return [this.createAsyncOptionsProvider(options)];
|
||||
}
|
||||
return [
|
||||
this.createAsyncOptionsProvider(options),
|
||||
{
|
||||
provide: options.useClass,
|
||||
useClass: options.useClass,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private static createAsyncOptionsProvider(
|
||||
options: ConfigurableModuleAsyncOptions<ModuleOptions>,
|
||||
): Provider {
|
||||
if (options.useFactory) {
|
||||
return {
|
||||
provide: self.options.optionsInjectionToken,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject || [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
provide: self.options.optionsInjectionToken,
|
||||
useFactory: async (
|
||||
optionsFactory: ConfigurableModuleOptionsFactory<
|
||||
ModuleOptions,
|
||||
FactoryClassMethodKey
|
||||
>,
|
||||
) =>
|
||||
await optionsFactory[
|
||||
self.factoryClassMethodKey as keyof typeof optionsFactory
|
||||
](),
|
||||
inject: [options.useExisting || options.useClass],
|
||||
};
|
||||
}
|
||||
}
|
||||
return InternalModuleClass as unknown as ConfigurableModuleCls<
|
||||
ModuleOptions,
|
||||
StaticMethodKey,
|
||||
FactoryClassMethodKey
|
||||
>;
|
||||
}
|
||||
|
||||
private createTypeProxy(
|
||||
typeName: 'OPTIONS_TYPE' | 'ASYNC_OPTIONS_TYPE' | 'OptionsFactoryInterface',
|
||||
) {
|
||||
const proxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => {
|
||||
throw new Error(
|
||||
`"${typeName}" is not supposed to be used as a value.`,
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
return proxy as any;
|
||||
}
|
||||
}
|
||||
5
packages/common/module-utils/constants.ts
Normal file
5
packages/common/module-utils/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const DEFAULT_METHOD_KEY = 'register';
|
||||
export const DEFAULT_FACTORY_CLASS_METHOD_KEY = 'create';
|
||||
|
||||
export const ASYNC_METHOD_SUFFIX = 'Async';
|
||||
export const CONFIGURABLE_MODULE_ID = 'CONFIGURABLE_MODULE_ID';
|
||||
2
packages/common/module-utils/index.ts
Normal file
2
packages/common/module-utils/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './configurable-module.builder';
|
||||
export * from './interfaces';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { FactoryProvider, ModuleMetadata, Type } from '../../interfaces';
|
||||
import { DEFAULT_FACTORY_CLASS_METHOD_KEY } from '../constants';
|
||||
|
||||
/**
|
||||
* Interface that must be implemented by the module options factory class.
|
||||
* Method key varies depending on the "FactoryClassMethodKey" type argument.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type ConfigurableModuleOptionsFactory<
|
||||
ModuleOptions,
|
||||
FactoryClassMethodKey extends string,
|
||||
> = Record<
|
||||
`${FactoryClassMethodKey}`,
|
||||
() => Promise<ModuleOptions> | ModuleOptions
|
||||
>;
|
||||
|
||||
/**
|
||||
* Interface that represents the module async options object
|
||||
* Factory method name varies depending on the "FactoryClassMethodKey" type argument.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ConfigurableModuleAsyncOptions<
|
||||
ModuleOptions,
|
||||
FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
|
||||
> extends Pick<ModuleMetadata, 'imports'> {
|
||||
/**
|
||||
* Injection token resolving to an existing provider. The provider must implement
|
||||
* the corresponding interface.
|
||||
*/
|
||||
useExisting?: Type<
|
||||
ConfigurableModuleOptionsFactory<ModuleOptions, FactoryClassMethodKey>
|
||||
>;
|
||||
/**
|
||||
* Injection token resolving to a class that will be instantiated as a provider.
|
||||
* The class must implement the corresponding interface.
|
||||
*/
|
||||
useClass?: Type<
|
||||
ConfigurableModuleOptionsFactory<ModuleOptions, FactoryClassMethodKey>
|
||||
>;
|
||||
/**
|
||||
* Function returning options (or a Promise resolving to options) to configure the
|
||||
* cache module.
|
||||
*/
|
||||
useFactory?: (...args: unknown[]) => Promise<ModuleOptions> | ModuleOptions;
|
||||
/**
|
||||
* Dependencies that a Factory may inject.
|
||||
*/
|
||||
inject?: FactoryProvider['inject'];
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { DynamicModule } from '../../interfaces';
|
||||
import {
|
||||
DEFAULT_FACTORY_CLASS_METHOD_KEY,
|
||||
DEFAULT_METHOD_KEY,
|
||||
} from '../constants';
|
||||
import { ConfigurableModuleAsyncOptions } from './configurable-module-async-options.interface';
|
||||
|
||||
/**
|
||||
* Class that represents a blueprint/prototype for a configurable Nest module.
|
||||
* This class provides static methods for constructing dynamic modules. Their names
|
||||
* can be controlled through the "MethodKey" type argument.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type ConfigurableModuleCls<
|
||||
ModuleOptions,
|
||||
MethodKey extends string = typeof DEFAULT_METHOD_KEY,
|
||||
FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
|
||||
ExtraModuleDefinitionOptions = {},
|
||||
> = {
|
||||
new (): any;
|
||||
} & Record<
|
||||
`${MethodKey}`,
|
||||
(options: ModuleOptions & ExtraModuleDefinitionOptions) => DynamicModule
|
||||
> &
|
||||
Record<
|
||||
`${MethodKey}Async`,
|
||||
(
|
||||
options: ConfigurableModuleAsyncOptions<
|
||||
ModuleOptions,
|
||||
FactoryClassMethodKey
|
||||
> &
|
||||
ExtraModuleDefinitionOptions,
|
||||
) => DynamicModule
|
||||
>;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { ConfigurableModuleAsyncOptions } from './configurable-module-async-options.interface';
|
||||
import { ConfigurableModuleCls } from './configurable-module-cls.interface';
|
||||
|
||||
/**
|
||||
* Configurable module host. See properties for more details
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ConfigurableModuleHost<
|
||||
ModuleOptions = Record<string, unknown>,
|
||||
MethodKey extends string = string,
|
||||
FactoryClassMethodKey extends string = string,
|
||||
ExtraModuleDefinitionOptions = {},
|
||||
> {
|
||||
/**
|
||||
* Class that represents a blueprint/prototype for a configurable Nest module.
|
||||
* This class provides static methods for constructing dynamic modules. Their names
|
||||
* can be controlled through the "MethodKey" type argument.
|
||||
*
|
||||
* Your module class should inherit from this class to make the static methods available.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Module({})
|
||||
* class IntegrationModule extends ConfigurableModuleCls {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
ConfigurableModuleClass: ConfigurableModuleCls<
|
||||
ModuleOptions,
|
||||
MethodKey,
|
||||
FactoryClassMethodKey,
|
||||
ExtraModuleDefinitionOptions
|
||||
>;
|
||||
/**
|
||||
* Module options provider token. Can be used to inject the "options object" to
|
||||
* providers registered within the host module.
|
||||
*/
|
||||
MODULE_OPTIONS_TOKEN: string | symbol;
|
||||
/**
|
||||
* Can be used to auto-infer the compound "async module options" type.
|
||||
* Note: this property is not supposed to be used as a value.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Module({})
|
||||
* class IntegrationModule extends ConfigurableModuleCls {
|
||||
* static module = initializer(IntegrationModule);
|
||||
*
|
||||
* static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
|
||||
* return super.registerAsync(options);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
ASYNC_OPTIONS_TYPE: ConfigurableModuleAsyncOptions<
|
||||
ModuleOptions,
|
||||
FactoryClassMethodKey
|
||||
> &
|
||||
ExtraModuleDefinitionOptions;
|
||||
/**
|
||||
* Can be used to auto-infer the compound "module options" type (options interface + extra module definition options).
|
||||
* Note: this property is not supposed to be used as a value.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Module({})
|
||||
* class IntegrationModule extends ConfigurableModuleCls {
|
||||
* static module = initializer(IntegrationModule);
|
||||
*
|
||||
* static register(options: typeof OPTIONS_TYPE): DynamicModule {
|
||||
* return super.register(options);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
OPTIONS_TYPE: ModuleOptions & ExtraModuleDefinitionOptions;
|
||||
}
|
||||
3
packages/common/module-utils/interfaces/index.ts
Normal file
3
packages/common/module-utils/interfaces/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './configurable-module-async-options.interface';
|
||||
export * from './configurable-module-cls.interface';
|
||||
export * from './configurable-module-host.interface';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { randomStringGenerator } from '../../utils/random-string-generator.util';
|
||||
|
||||
export function generateOptionsInjectionToken() {
|
||||
const hash = randomStringGenerator();
|
||||
return `CONFIGURABLE_MODULE_OPTIONS[${hash}]`;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ErrorHttpStatusCode,
|
||||
HttpErrorByCode,
|
||||
} from '../utils/http-error-by-code.util';
|
||||
import { isUUID } from '../utils/is-uuid';
|
||||
import { isString } from '../utils/shared.utils';
|
||||
|
||||
export interface ParseUUIDPipeOptions {
|
||||
version?: '3' | '4' | '5';
|
||||
@@ -19,6 +19,12 @@ export interface ParseUUIDPipeOptions {
|
||||
|
||||
@Injectable()
|
||||
export class ParseUUIDPipe implements PipeTransform<string> {
|
||||
protected static uuidRegExps = {
|
||||
3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
||||
4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
|
||||
5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
|
||||
all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
||||
};
|
||||
private readonly version: '3' | '4' | '5';
|
||||
protected exceptionFactory: (errors: string) => any;
|
||||
|
||||
@@ -35,14 +41,23 @@ export class ParseUUIDPipe implements PipeTransform<string> {
|
||||
exceptionFactory ||
|
||||
(error => new HttpErrorByCode[errorHttpStatusCode](error));
|
||||
}
|
||||
|
||||
async transform(value: string, metadata: ArgumentMetadata): Promise<string> {
|
||||
if (!isUUID(value, this.version)) {
|
||||
if (!this.isUUID(value, this.version)) {
|
||||
throw this.exceptionFactory(
|
||||
`Validation failed (uuid ${
|
||||
this.version ? 'v' + this.version : ''
|
||||
`Validation failed (uuid${
|
||||
this.version ? ` v ${this.version}` : ''
|
||||
} is expected)`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected isUUID(str: unknown, version = 'all') {
|
||||
if (!isString(str)) {
|
||||
throw this.exceptionFactory('The value passed as UUID is not a string');
|
||||
}
|
||||
const pattern = ParseUUIDPipe.uuidRegExps[version];
|
||||
return pattern?.test(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { expect } from 'chai';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { HttpService } from '../../http/http.service';
|
||||
|
||||
describe('HttpService', () => {
|
||||
it('should not mutate user-given axios options object', done => {
|
||||
const http = new HttpService({ get: () => Promise.resolve() } as any);
|
||||
const options: AxiosRequestConfig = {};
|
||||
|
||||
lastValueFrom(http.get('/', options)).then(() => {
|
||||
expect(options.cancelToken).to.be.undefined;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { expect } from 'chai';
|
||||
import { Provider } from '../../interfaces';
|
||||
import { ConfigurableModuleBuilder } from '../../module-utils';
|
||||
|
||||
describe('ConfigurableModuleBuilder', () => {
|
||||
describe('setExtras', () => {
|
||||
it('should apply module definition transformer function and return typed builder', () => {
|
||||
const { ConfigurableModuleClass } = new ConfigurableModuleBuilder()
|
||||
.setExtras(
|
||||
{ isGlobal: false },
|
||||
(definition, extras: { isGlobal: boolean }) => ({
|
||||
...definition,
|
||||
global: extras.isGlobal,
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
expect(
|
||||
ConfigurableModuleClass.register({
|
||||
// No type error
|
||||
isGlobal: true,
|
||||
}),
|
||||
).to.deep.include({
|
||||
global: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('setClassMethodName', () => {
|
||||
it('should set static class method name and return typed builder', () => {
|
||||
const { ConfigurableModuleClass } = new ConfigurableModuleBuilder()
|
||||
.setClassMethodName('forRoot')
|
||||
.build();
|
||||
|
||||
expect(ConfigurableModuleClass.forRoot).to.not.be.undefined;
|
||||
expect(ConfigurableModuleClass.forRootAsync).to.not.be.undefined;
|
||||
expect((ConfigurableModuleClass as any).register).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('setFactoryMethodName', () => {
|
||||
it('should set configuration factory class method name and return typed builder', () => {
|
||||
const { ConfigurableModuleClass } = new ConfigurableModuleBuilder()
|
||||
.setFactoryMethodName('createOptions')
|
||||
.build();
|
||||
|
||||
expect(
|
||||
ConfigurableModuleClass.registerAsync({
|
||||
useClass: class {
|
||||
// No type error
|
||||
createOptions() {}
|
||||
},
|
||||
}),
|
||||
).to.not.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('build', () => {
|
||||
it('should return a fully typed "ConfigurableModuleClass"', () => {
|
||||
type ExtraConfig = { isGlobal?: boolean; extraProviders: Provider[] };
|
||||
|
||||
const {
|
||||
ConfigurableModuleClass,
|
||||
OPTIONS_TYPE,
|
||||
ASYNC_OPTIONS_TYPE,
|
||||
MODULE_OPTIONS_TOKEN,
|
||||
} = new ConfigurableModuleBuilder({
|
||||
moduleName: 'RandomTest',
|
||||
alwaysTransient: true,
|
||||
})
|
||||
.setFactoryMethodName('createOptions')
|
||||
.setClassMethodName('forFeature')
|
||||
.setExtras<ExtraConfig>(
|
||||
{ isGlobal: false, extraProviders: [] },
|
||||
(definition, extras) => ({
|
||||
...definition,
|
||||
global: extras.isGlobal,
|
||||
providers: definition.providers?.concat(extras.extraProviders),
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
const definition = ConfigurableModuleClass.forFeatureAsync({
|
||||
useFactory: () => {},
|
||||
isGlobal: true,
|
||||
extraProviders: ['test' as any],
|
||||
});
|
||||
|
||||
expect(definition.global).to.equal(true);
|
||||
expect(definition.providers).to.have.length(3);
|
||||
expect(definition.providers).to.deep.contain('test');
|
||||
expect(MODULE_OPTIONS_TOKEN).to.equal('RANDOM_TEST_MODULE_OPTIONS');
|
||||
expect((definition.providers[0] as any).provide).to.equal(
|
||||
'RANDOM_TEST_MODULE_OPTIONS',
|
||||
);
|
||||
|
||||
try {
|
||||
expect(ASYNC_OPTIONS_TYPE.imports).to.equal(undefined);
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(err.message).to.equal(
|
||||
'"ASYNC_OPTIONS_TYPE" is not supposed to be used as a value.',
|
||||
);
|
||||
}
|
||||
try {
|
||||
expect(OPTIONS_TYPE.isGlobal).to.equal(undefined);
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(err.message).to.equal(
|
||||
'"OPTIONS_TYPE" is not supposed to be used as a value.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { ArgumentMetadata } from '../../interfaces';
|
||||
import { ParseIntPipe } from '../../pipes/parse-int.pipe';
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { expect } from 'chai';
|
||||
import { HttpStatus } from '../../enums';
|
||||
import { HttpException } from '../../exceptions';
|
||||
import { ArgumentMetadata } from '../../interfaces';
|
||||
import { ParseUUIDPipe } from '../../pipes/parse-uuid.pipe';
|
||||
|
||||
class TestException extends HttpException {
|
||||
constructor() {
|
||||
super('This is a TestException', HttpStatus.I_AM_A_TEAPOT);
|
||||
}
|
||||
}
|
||||
|
||||
describe('ParseUUIDPipe', () => {
|
||||
let target: ParseUUIDPipe;
|
||||
const exceptionFactory = (error: any) => new TestException();
|
||||
|
||||
describe('transform', () => {
|
||||
const v3 = 'e8b5a51d-11c8-3310-a6ab-367563f20686';
|
||||
@@ -12,53 +21,80 @@ describe('ParseUUIDPipe', () => {
|
||||
|
||||
describe('when validation passes', () => {
|
||||
it('should return string if value is uuid v3, v4 or v5', async () => {
|
||||
target = new ParseUUIDPipe();
|
||||
target = new ParseUUIDPipe({ exceptionFactory });
|
||||
expect(await target.transform(v3, {} as ArgumentMetadata)).to.equal(v3);
|
||||
expect(await target.transform(v4, {} as ArgumentMetadata)).to.equal(v4);
|
||||
expect(await target.transform(v5, {} as ArgumentMetadata)).to.equal(v5);
|
||||
});
|
||||
|
||||
it('should return string if value is uuid v3', async () => {
|
||||
target = new ParseUUIDPipe({ version: '3' });
|
||||
target = new ParseUUIDPipe({ version: '3', exceptionFactory });
|
||||
expect(await target.transform(v3, {} as ArgumentMetadata)).to.equal(v3);
|
||||
});
|
||||
|
||||
it('should return string if value is uuid v4', async () => {
|
||||
target = new ParseUUIDPipe({ version: '4' });
|
||||
target = new ParseUUIDPipe({ version: '4', exceptionFactory });
|
||||
expect(await target.transform(v4, {} as ArgumentMetadata)).to.equal(v4);
|
||||
});
|
||||
|
||||
it('should return string if value is uuid v5', async () => {
|
||||
target = new ParseUUIDPipe({ version: '5' });
|
||||
target = new ParseUUIDPipe({ version: '5', exceptionFactory });
|
||||
expect(await target.transform(v5, {} as ArgumentMetadata)).to.equal(v5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when validation fails', () => {
|
||||
it('should throw an error', async () => {
|
||||
target = new ParseUUIDPipe();
|
||||
expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected;
|
||||
target = new ParseUUIDPipe({ exceptionFactory });
|
||||
await expect(
|
||||
target.transform('123a', {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
});
|
||||
|
||||
it('should throw an error - not a string', async () => {
|
||||
target = new ParseUUIDPipe({ exceptionFactory });
|
||||
await expect(
|
||||
target.transform(undefined, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
});
|
||||
|
||||
it('should throw an error - v3', async () => {
|
||||
target = new ParseUUIDPipe({ version: '3' });
|
||||
expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected;
|
||||
expect(target.transform(v4, {} as ArgumentMetadata)).to.be.rejected;
|
||||
expect(target.transform(v5, {} as ArgumentMetadata)).to.be.rejected;
|
||||
target = new ParseUUIDPipe({ version: '3', exceptionFactory });
|
||||
await expect(
|
||||
target.transform('123a', {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
await expect(
|
||||
target.transform(v4, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
await expect(
|
||||
target.transform(v5, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
});
|
||||
|
||||
it('should throw an error - v4', async () => {
|
||||
target = new ParseUUIDPipe({ version: '4' });
|
||||
expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected;
|
||||
expect(target.transform(v3, {} as ArgumentMetadata)).to.be.rejected;
|
||||
expect(target.transform(v5, {} as ArgumentMetadata)).to.be.rejected;
|
||||
target = new ParseUUIDPipe({ version: '4', exceptionFactory });
|
||||
await expect(
|
||||
target.transform('123a', {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
await expect(
|
||||
target.transform(v3, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
await expect(
|
||||
target.transform(v5, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
});
|
||||
|
||||
it('should throw an error - v5 ', async () => {
|
||||
target = new ParseUUIDPipe({ version: '5' });
|
||||
expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected;
|
||||
expect(target.transform(v3, {} as ArgumentMetadata)).to.be.rejected;
|
||||
expect(target.transform(v4, {} as ArgumentMetadata)).to.be.rejected;
|
||||
target = new ParseUUIDPipe({ version: '5', exceptionFactory });
|
||||
await expect(
|
||||
target.transform('123a', {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
await expect(
|
||||
target.transform(v3, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
await expect(
|
||||
target.transform(v4, {} as ArgumentMetadata),
|
||||
).to.be.rejectedWith(TestException);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { BadRequestException } from '../exceptions';
|
||||
import { isString } from './shared.utils';
|
||||
|
||||
const uuid = {
|
||||
3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
||||
4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
|
||||
5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
|
||||
all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
||||
};
|
||||
|
||||
export function isUUID(str: any, version = 'all') {
|
||||
if (!isString(str)) {
|
||||
throw new BadRequestException('The value passed as UUID is not a string');
|
||||
}
|
||||
const pattern = uuid[version];
|
||||
return pattern && pattern.test(str);
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
RequestMethod,
|
||||
MessageEvent,
|
||||
} from '@nestjs/common';
|
||||
import { isFunction, isObject } from '@nestjs/common/utils/shared.utils';
|
||||
import { isObject } from '@nestjs/common/utils/shared.utils';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { EMPTY, lastValueFrom, Observable } from 'rxjs';
|
||||
import { EMPTY, lastValueFrom, Observable, isObservable } from 'rxjs';
|
||||
import { catchError, debounce, map } from 'rxjs/operators';
|
||||
import {
|
||||
AdditionalHeaders,
|
||||
@@ -64,7 +64,7 @@ export class RouterResponseController {
|
||||
}
|
||||
|
||||
public async transformToResult(resultOrDeferred: any) {
|
||||
if (resultOrDeferred && isFunction(resultOrDeferred.subscribe)) {
|
||||
if (isObservable(resultOrDeferred)) {
|
||||
return lastValueFrom(resultOrDeferred);
|
||||
}
|
||||
return resultOrDeferred;
|
||||
@@ -152,8 +152,8 @@ export class RouterResponseController {
|
||||
});
|
||||
}
|
||||
|
||||
private assertObservable(result: any) {
|
||||
if (!isFunction(result.subscribe)) {
|
||||
private assertObservable(value: any) {
|
||||
if (!isObservable(value)) {
|
||||
throw new ReferenceError(
|
||||
'You must return an Observable stream to use Server-Sent Events (SSE).',
|
||||
);
|
||||
|
||||
@@ -102,13 +102,13 @@ export class DependenciesScanner {
|
||||
moduleDefinition as Type<any> | DynamicModule,
|
||||
)
|
||||
? this.reflectMetadata(
|
||||
moduleDefinition as Type<any>,
|
||||
MODULE_METADATA.IMPORTS,
|
||||
moduleDefinition as Type<any>,
|
||||
)
|
||||
: [
|
||||
...this.reflectMetadata(
|
||||
(moduleDefinition as DynamicModule).module,
|
||||
MODULE_METADATA.IMPORTS,
|
||||
(moduleDefinition as DynamicModule).module,
|
||||
),
|
||||
...((moduleDefinition as DynamicModule).imports || []),
|
||||
];
|
||||
@@ -177,7 +177,7 @@ export class DependenciesScanner {
|
||||
context: string,
|
||||
) {
|
||||
const modules = [
|
||||
...this.reflectMetadata(module, MODULE_METADATA.IMPORTS),
|
||||
...this.reflectMetadata(MODULE_METADATA.IMPORTS, module),
|
||||
...this.container.getDynamicMetadataByToken(
|
||||
token,
|
||||
MODULE_METADATA.IMPORTS as 'imports',
|
||||
@@ -190,7 +190,7 @@ export class DependenciesScanner {
|
||||
|
||||
public reflectProviders(module: Type<any>, token: string) {
|
||||
const providers = [
|
||||
...this.reflectMetadata(module, MODULE_METADATA.PROVIDERS),
|
||||
...this.reflectMetadata(MODULE_METADATA.PROVIDERS, module),
|
||||
...this.container.getDynamicMetadataByToken(
|
||||
token,
|
||||
MODULE_METADATA.PROVIDERS as 'providers',
|
||||
@@ -204,7 +204,7 @@ export class DependenciesScanner {
|
||||
|
||||
public reflectControllers(module: Type<any>, token: string) {
|
||||
const controllers = [
|
||||
...this.reflectMetadata(module, MODULE_METADATA.CONTROLLERS),
|
||||
...this.reflectMetadata(MODULE_METADATA.CONTROLLERS, module),
|
||||
...this.container.getDynamicMetadataByToken(
|
||||
token,
|
||||
MODULE_METADATA.CONTROLLERS as 'controllers',
|
||||
@@ -229,7 +229,7 @@ export class DependenciesScanner {
|
||||
|
||||
public reflectExports(module: Type<unknown>, token: string) {
|
||||
const exports = [
|
||||
...this.reflectMetadata(module, MODULE_METADATA.EXPORTS),
|
||||
...this.reflectMetadata(MODULE_METADATA.EXPORTS, module),
|
||||
...this.container.getDynamicMetadataByToken(
|
||||
token,
|
||||
MODULE_METADATA.EXPORTS as 'exports',
|
||||
@@ -245,7 +245,7 @@ export class DependenciesScanner {
|
||||
token: string,
|
||||
metadataKey: string,
|
||||
) {
|
||||
const controllerInjectables = this.reflectMetadata(component, metadataKey);
|
||||
const controllerInjectables = this.reflectMetadata(metadataKey, component);
|
||||
const methodsInjectables = this.metadataScanner.scanFromPrototype(
|
||||
null,
|
||||
component.prototype,
|
||||
@@ -416,7 +416,7 @@ export class DependenciesScanner {
|
||||
this.container.addController(controller, token);
|
||||
}
|
||||
|
||||
public reflectMetadata(metatype: Type<any>, metadataKey: string) {
|
||||
public reflectMetadata(metadataKey: string, metatype: Type<any>) {
|
||||
return Reflect.getMetadata(metadataKey, metatype) || [];
|
||||
}
|
||||
|
||||
|
||||
@@ -71,13 +71,13 @@ describe('RouterResponseController', () => {
|
||||
describe('transformToResult', () => {
|
||||
describe('when resultOrDeferred', () => {
|
||||
describe('is Promise', () => {
|
||||
it('should return Promise', async () => {
|
||||
it('should return Promise that resolves to the value resolved by the input Promise', async () => {
|
||||
const value = 100;
|
||||
expect(
|
||||
await routerResponseController.transformToResult(
|
||||
Promise.resolve(value),
|
||||
),
|
||||
).to.be.eq(100);
|
||||
).to.be.eq(value);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,16 +88,25 @@ describe('RouterResponseController', () => {
|
||||
await routerResponseController.transformToResult(
|
||||
of(1, 2, 3, lastValue),
|
||||
),
|
||||
).to.be.eq(100);
|
||||
).to.be.eq(lastValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is value', () => {
|
||||
it('should return Promise', async () => {
|
||||
describe('is an object that has the method `subscribe`', () => {
|
||||
it('should return a Promise that resolves to the input value', async () => {
|
||||
const value = { subscribe() {} };
|
||||
expect(
|
||||
await routerResponseController.transformToResult(value),
|
||||
).to.equal(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is an ordinary value', () => {
|
||||
it('should return a Promise that resolves to the input value', async () => {
|
||||
const value = 100;
|
||||
expect(
|
||||
await routerResponseController.transformToResult(value),
|
||||
).to.be.eq(100);
|
||||
).to.be.eq(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,48 +1,31 @@
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { loadPackage } from '@nestjs/common/utils/load-package.util';
|
||||
import {
|
||||
EmptyError,
|
||||
fromEvent,
|
||||
lastValueFrom,
|
||||
merge,
|
||||
Subject,
|
||||
zip,
|
||||
} from 'rxjs';
|
||||
import { share, take, tap } from 'rxjs/operators';
|
||||
import {
|
||||
CONNECT_EVENT,
|
||||
ECONNREFUSED,
|
||||
ERROR_EVENT,
|
||||
MESSAGE_EVENT,
|
||||
REDIS_DEFAULT_URL,
|
||||
REDIS_DEFAULT_HOST,
|
||||
REDIS_DEFAULT_PORT,
|
||||
} from '../constants';
|
||||
import {
|
||||
ClientOpts,
|
||||
RedisClient,
|
||||
RetryStrategyOptions,
|
||||
} from '../external/redis.interface';
|
||||
import { ReadPacket, RedisOptions, WritePacket } from '../interfaces';
|
||||
import { ClientProxy } from './client-proxy';
|
||||
|
||||
let redisPackage: any = {};
|
||||
type Redis = any;
|
||||
|
||||
let redisPackage = {} as any;
|
||||
|
||||
export class ClientRedis extends ClientProxy {
|
||||
protected readonly logger = new Logger(ClientProxy.name);
|
||||
protected readonly subscriptionsCount = new Map<string, number>();
|
||||
protected readonly url: string;
|
||||
protected pubClient: RedisClient;
|
||||
protected subClient: RedisClient;
|
||||
protected pubClient: Redis;
|
||||
protected subClient: Redis;
|
||||
protected connection: Promise<any>;
|
||||
protected isExplicitlyTerminated = false;
|
||||
|
||||
constructor(protected readonly options: RedisOptions['options']) {
|
||||
super();
|
||||
this.url =
|
||||
this.getOptionsProp(options, 'url') ||
|
||||
(!this.getOptionsProp(options, 'host') && REDIS_DEFAULT_URL);
|
||||
|
||||
redisPackage = loadPackage('redis', ClientRedis.name, () =>
|
||||
require('redis'),
|
||||
redisPackage = loadPackage('ioredis', ClientRedis.name, () =>
|
||||
require('ioredis'),
|
||||
);
|
||||
|
||||
this.initializeSerializer(options);
|
||||
@@ -64,73 +47,57 @@ export class ClientRedis extends ClientProxy {
|
||||
this.isExplicitlyTerminated = true;
|
||||
}
|
||||
|
||||
public connect(): Promise<any> {
|
||||
public async connect(): Promise<any> {
|
||||
if (this.pubClient && this.subClient) {
|
||||
return this.connection;
|
||||
}
|
||||
const error$ = new Subject<Error>();
|
||||
|
||||
this.pubClient = this.createClient(error$);
|
||||
this.subClient = this.createClient(error$);
|
||||
this.pubClient = this.createClient();
|
||||
this.subClient = this.createClient();
|
||||
this.handleError(this.pubClient);
|
||||
this.handleError(this.subClient);
|
||||
|
||||
const pubConnect$ = fromEvent(this.pubClient, CONNECT_EVENT);
|
||||
const subClient$ = fromEvent(this.subClient, CONNECT_EVENT);
|
||||
this.connection = Promise.all([
|
||||
this.subClient.connect(),
|
||||
this.pubClient.connect(),
|
||||
]);
|
||||
await this.connection;
|
||||
|
||||
this.connection = lastValueFrom(
|
||||
merge(error$, zip(pubConnect$, subClient$)).pipe(
|
||||
take(1),
|
||||
tap(() =>
|
||||
this.subClient.on(MESSAGE_EVENT, this.createResponseCallback()),
|
||||
),
|
||||
share(),
|
||||
),
|
||||
).catch(err => {
|
||||
if (err instanceof EmptyError) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
this.subClient.on(MESSAGE_EVENT, this.createResponseCallback());
|
||||
return this.connection;
|
||||
}
|
||||
|
||||
public createClient(error$: Subject<Error>): RedisClient {
|
||||
return redisPackage.createClient({
|
||||
...this.getClientOptions(error$),
|
||||
url: this.url,
|
||||
public createClient(): Redis {
|
||||
return new redisPackage({
|
||||
host: REDIS_DEFAULT_HOST,
|
||||
port: REDIS_DEFAULT_PORT,
|
||||
...this.getClientOptions(),
|
||||
lazyConnect: true,
|
||||
});
|
||||
}
|
||||
|
||||
public handleError(client: RedisClient) {
|
||||
public handleError(client: Redis) {
|
||||
client.addListener(ERROR_EVENT, (err: any) => this.logger.error(err));
|
||||
}
|
||||
|
||||
public getClientOptions(error$: Subject<Error>): Partial<ClientOpts> {
|
||||
const retry_strategy = (options: RetryStrategyOptions) =>
|
||||
this.createRetryStrategy(options, error$);
|
||||
public getClientOptions(): Partial<RedisOptions['options']> {
|
||||
const retryStrategy = (times: number) => this.createRetryStrategy(times);
|
||||
|
||||
return {
|
||||
...(this.options || {}),
|
||||
retry_strategy,
|
||||
retryStrategy,
|
||||
};
|
||||
}
|
||||
|
||||
public createRetryStrategy(
|
||||
options: RetryStrategyOptions,
|
||||
error$: Subject<Error>,
|
||||
): undefined | number | Error {
|
||||
if (options.error && (options.error as any).code === ECONNREFUSED) {
|
||||
error$.error(options.error);
|
||||
}
|
||||
public createRetryStrategy(times: number): undefined | number {
|
||||
if (this.isExplicitlyTerminated) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
!this.getOptionsProp(this.options, 'retryAttempts') ||
|
||||
options.attempt > this.getOptionsProp(this.options, 'retryAttempts')
|
||||
times > this.getOptionsProp(this.options, 'retryAttempts')
|
||||
) {
|
||||
return new Error('Retry time exhausted');
|
||||
this.logger.error('Retry time exhausted');
|
||||
return;
|
||||
}
|
||||
return this.getOptionsProp(this.options, 'retryDelay') || 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants';
|
||||
|
||||
export const TCP_DEFAULT_PORT = 3000;
|
||||
export const TCP_DEFAULT_HOST = 'localhost';
|
||||
export const REDIS_DEFAULT_URL = 'redis://localhost:6379';
|
||||
|
||||
export const REDIS_DEFAULT_PORT = 6379;
|
||||
export const REDIS_DEFAULT_HOST = 'localhost';
|
||||
|
||||
export const NATS_DEFAULT_URL = 'nats://localhost:4222';
|
||||
export const MQTT_DEFAULT_URL = 'mqtt://localhost:1883';
|
||||
export const GRPC_DEFAULT_URL = 'localhost:5000';
|
||||
@@ -38,8 +41,10 @@ export const RQM_DEFAULT_NOACK = true;
|
||||
export const RQM_DEFAULT_PERSISTENT = false;
|
||||
export const GRPC_DEFAULT_PROTO_LOADER = '@grpc/proto-loader';
|
||||
|
||||
export const NO_EVENT_HANDLER = (text: TemplateStringsArray, pattern: string) =>
|
||||
`There is no matching event handler defined in the remote service. Event pattern: ${pattern}`;
|
||||
export const NO_MESSAGE_HANDLER = `There is no matching message handler defined in the remote service.`;
|
||||
export const NO_EVENT_HANDLER = `There is no matching event handler defined in the remote service.`;
|
||||
|
||||
export const DISCONNECTED_RMQ_MESSAGE = `Disconnected from RMQ. Trying to reconnect.`;
|
||||
|
||||
export const KAFKA_DEFAULT_CLIENT = 'nestjs-consumer';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
|
||||
import { Observable, isObservable } from 'rxjs';
|
||||
import { isObservable, Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { RpcExceptionsHandler } from '../exceptions/rpc-exceptions-handler';
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { KafkaMessage } from '../external/kafka.interface';
|
||||
import { Consumer, KafkaMessage } from '../external/kafka.interface';
|
||||
import { BaseRpcContext } from './base-rpc.context';
|
||||
|
||||
type KafkaContextArgs = [KafkaMessage, number, string];
|
||||
type KafkaContextArgs = [
|
||||
message: KafkaMessage,
|
||||
partition: number,
|
||||
topic: string,
|
||||
consumer: Consumer,
|
||||
];
|
||||
|
||||
export class KafkaContext extends BaseRpcContext<KafkaContextArgs> {
|
||||
constructor(args: KafkaContextArgs) {
|
||||
@@ -28,4 +33,11 @@ export class KafkaContext extends BaseRpcContext<KafkaContextArgs> {
|
||||
getTopic() {
|
||||
return this.args[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kafka consumer reference.
|
||||
*/
|
||||
getConsumer() {
|
||||
return this.args[3];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,11 @@ export const EventPattern: {
|
||||
key: string | symbol,
|
||||
descriptor: PropertyDescriptor,
|
||||
) => {
|
||||
Reflect.defineMetadata(PATTERN_METADATA, metadata, descriptor.value);
|
||||
Reflect.defineMetadata(
|
||||
PATTERN_METADATA,
|
||||
[].concat(metadata),
|
||||
descriptor.value,
|
||||
);
|
||||
Reflect.defineMetadata(
|
||||
PATTERN_HANDLER_METADATA,
|
||||
PatternHandler.EVENT,
|
||||
|
||||
@@ -62,7 +62,11 @@ export const MessagePattern: {
|
||||
key: string | symbol,
|
||||
descriptor: PropertyDescriptor,
|
||||
) => {
|
||||
Reflect.defineMetadata(PATTERN_METADATA, metadata, descriptor.value);
|
||||
Reflect.defineMetadata(
|
||||
PATTERN_METADATA,
|
||||
[].concat(metadata),
|
||||
descriptor.value,
|
||||
);
|
||||
Reflect.defineMetadata(
|
||||
PATTERN_HANDLER_METADATA,
|
||||
PatternHandler.MESSAGE,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { IncomingEvent, IncomingRequest } from '../interfaces';
|
||||
import { KafkaRequest } from '../serializers/kafka-request.serializer';
|
||||
import { IncomingRequestDeserializer } from './incoming-request.deserializer';
|
||||
|
||||
export class KafkaRequestDeserializer extends IncomingRequestDeserializer {
|
||||
mapToSchema(
|
||||
data: KafkaRequest,
|
||||
options?: Record<string, any>,
|
||||
): IncomingRequest | IncomingEvent {
|
||||
if (!options) {
|
||||
return {
|
||||
pattern: undefined,
|
||||
data: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
pattern: options.channel,
|
||||
data: data?.value ?? data,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './base-rpc-exception-filter';
|
||||
export * from './kafka-retriable-exception';
|
||||
export * from './rpc-exception';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { RpcException } from './rpc-exception';
|
||||
|
||||
/**
|
||||
* Exception that instructs Kafka driver to instead of introspecting
|
||||
* error processing flow and sending serialized error message to the consumer,
|
||||
* force bubble it up to the "eachMessage" callback of the underlying "kafkajs" package
|
||||
* (even if interceptors are applied, or an observable stream is returned from the message handler).
|
||||
*
|
||||
* A transient exception that if retried may succeed.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class KafkaRetriableException extends RpcException {
|
||||
public getError(): string | object {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,5 @@ export interface ChannelOptions {
|
||||
'grpc.max_reconnect_backoff_ms'?: number;
|
||||
'grpc.use_local_subchannel_pool'?: number;
|
||||
'grpc-node.max_session_memory'?: number;
|
||||
[key: string]: string | number | undefined;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
552
packages/microservices/external/redis.interface.ts
vendored
552
packages/microservices/external/redis.interface.ts
vendored
@@ -1,383 +1,213 @@
|
||||
/* eslint-disable @typescript-eslint/adjacent-overload-signatures */
|
||||
import { ConnectionOptions } from 'tls';
|
||||
|
||||
export interface RetryStrategyOptions {
|
||||
error: Error;
|
||||
total_retry_time: number;
|
||||
times_connected: number;
|
||||
attempt: number;
|
||||
}
|
||||
/**
|
||||
* @see https://github.dev/luin/ioredis/blob/df04dd8d87a44d3b64b385c86581915248554508/lib/redis/RedisOptions.ts#L184
|
||||
*/
|
||||
export interface IORedisOptions {
|
||||
Connector?: any;
|
||||
retryStrategy?: (times: number) => number | void | null;
|
||||
|
||||
export interface ClientOpts {
|
||||
auth_pass?: string;
|
||||
command_queue_high_water?: number;
|
||||
command_queue_low_water?: number;
|
||||
connect_timeout?: number;
|
||||
db?: string;
|
||||
detect_buffers?: boolean;
|
||||
disable_resubscribing?: boolean;
|
||||
enable_offline_queue?: boolean;
|
||||
family?: string;
|
||||
host?: string;
|
||||
max_attempts?: number;
|
||||
no_ready_check?: boolean;
|
||||
parser?: string;
|
||||
/**
|
||||
* If a command does not return a reply within a set number of milliseconds,
|
||||
* a "Command timed out" error will be thrown.
|
||||
*/
|
||||
commandTimeout?: number;
|
||||
/**
|
||||
* Enable/disable keep-alive functionality.
|
||||
* @link https://nodejs.org/api/net.html#socketsetkeepaliveenable-initialdelay
|
||||
* @default 0
|
||||
*/
|
||||
keepAlive?: number;
|
||||
|
||||
/**
|
||||
* Enable/disable the use of Nagle's algorithm.
|
||||
* @link https://nodejs.org/api/net.html#socketsetnodelaynodelay
|
||||
* @default true
|
||||
*/
|
||||
noDelay?: boolean;
|
||||
|
||||
/**
|
||||
* Set the name of the connection to make it easier to identity the connection
|
||||
* in client list.
|
||||
* @link https://redis.io/commands/client-setname
|
||||
*/
|
||||
connectionName?: string;
|
||||
|
||||
/**
|
||||
* If set, client will send AUTH command with the value of this option as the first argument when connected.
|
||||
* This is supported since Redis 6.
|
||||
*/
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* If set, client will send AUTH command with the value of this option when connected.
|
||||
*/
|
||||
password?: string;
|
||||
path?: string;
|
||||
port?: number;
|
||||
prefix?: string;
|
||||
rename_commands?: any;
|
||||
retry_max_delay?: number;
|
||||
retry_strategy?: any;
|
||||
retry_unfulfilled_commands?: boolean;
|
||||
return_buffers?: boolean;
|
||||
socket_keepalive?: boolean;
|
||||
socket_nodelay?: boolean;
|
||||
string_numbers?: boolean;
|
||||
tls?: any;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface RedisClient {
|
||||
// event: connect
|
||||
// event: error
|
||||
// event: message
|
||||
// event: pmessage
|
||||
// event: subscribe
|
||||
// event: psubscribe
|
||||
// event: unsubscribe
|
||||
// event: punsubscribe
|
||||
|
||||
connected: boolean;
|
||||
retry_delay: number;
|
||||
retry_backoff: number;
|
||||
command_queue: any[];
|
||||
offline_queue: any[];
|
||||
server_info: any;
|
||||
|
||||
/**
|
||||
* Forcibly close the connection to the Redis server. Note that this does not wait until all replies have been parsed. If you want to exit cleanly, call client.quit()
|
||||
* Database index to use.
|
||||
*
|
||||
* @param {boolean} flush You should set flush to true, if you are not absolutely sure you do not care about any other commands. If you set flush to false all still running commands will silently fail.
|
||||
* @default 0
|
||||
*/
|
||||
end(flush: boolean): void;
|
||||
unref(): void;
|
||||
db?: number;
|
||||
|
||||
/**
|
||||
* Stop sending commands and queue the commands.
|
||||
* When the client reconnects, channels subscribed in the previous connection will be
|
||||
* resubscribed automatically if `autoResubscribe` is `true`.
|
||||
* @default true
|
||||
*/
|
||||
cork(): void;
|
||||
autoResubscribe?: boolean;
|
||||
|
||||
/**
|
||||
* Resume and send the queued commands at once.
|
||||
* Whether or not to resend unfulfilled commands on reconnect.
|
||||
* Unfulfilled commands are most likely to be blocking commands such as `brpop` or `blpop`.
|
||||
* @default true
|
||||
*/
|
||||
uncork(): void;
|
||||
autoResendUnfulfilledCommands?: boolean;
|
||||
/**
|
||||
* Whether or not to reconnect on certain Redis errors.
|
||||
* This options by default is `null`, which means it should never reconnect on Redis errors.
|
||||
* You can pass a function that accepts an Redis error, and returns:
|
||||
* - `true` or `1` to trigger a reconnection.
|
||||
* - `false` or `0` to not reconnect.
|
||||
* - `2` to reconnect and resend the failed command (who triggered the error) after reconnection.
|
||||
* @example
|
||||
* ```js
|
||||
* const redis = new Redis({
|
||||
* reconnectOnError(err) {
|
||||
* const targetError = "READONLY";
|
||||
* if (err.message.includes(targetError)) {
|
||||
* // Only reconnect when the error contains "READONLY"
|
||||
* return true; // or `return 1;`
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @default null
|
||||
*/
|
||||
reconnectOnError?: ((err: Error) => boolean | 1 | 2) | null;
|
||||
|
||||
// Low level command execution
|
||||
send_command(command: string, ...args: any[]): boolean;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
/**
|
||||
* When enabled, numbers returned by Redis will be converted to JavaScript strings instead of numbers.
|
||||
* This is necessary if you want to handle big numbers (above `Number.MAX_SAFE_INTEGER` === 2^53).
|
||||
* @default false
|
||||
*/
|
||||
stringNumbers?: boolean;
|
||||
|
||||
// Connection (http://redis.io/commands#connection)
|
||||
auth(password: string, callback?: any): boolean;
|
||||
ping(callback?: any): boolean;
|
||||
/**
|
||||
* How long the client will wait before killing a socket due to inactivity during initial connection.
|
||||
* @default 10000
|
||||
*/
|
||||
connectTimeout?: number;
|
||||
|
||||
// Strings (http://redis.io/commands#strings)
|
||||
append(key: string, value: string, callback?: any): boolean;
|
||||
bitcount(key: string, callback?: any): boolean;
|
||||
bitcount(key: string, start: number, end: number, callback?: any): boolean;
|
||||
set(key: string, value: string, callback?: any): boolean;
|
||||
get(key: string, callback?: any): boolean;
|
||||
exists(key: string, value: string, callback?: any): boolean;
|
||||
/**
|
||||
* This option is used internally when you call `redis.monitor()` to tell Redis
|
||||
* to enter the monitor mode when the connection is established.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
monitor?: boolean;
|
||||
|
||||
publish(channel: string, value: any): boolean;
|
||||
subscribe(channel: string): boolean;
|
||||
on(event: string, callback: Function): any;
|
||||
off(event: string, callback: Function): any;
|
||||
addListener(event: string, callback: Function): any;
|
||||
/**
|
||||
* The commands that don't get a reply due to the connection to the server is lost are
|
||||
* put into a queue and will be resent on reconnect (if allowed by the `retryStrategy` option).
|
||||
* This option is used to configure how many reconnection attempts should be allowed before
|
||||
* the queue is flushed with a `MaxRetriesPerRequestError` error.
|
||||
* Set this options to `null` instead of a number to let commands wait forever
|
||||
* until the connection is alive again.
|
||||
*
|
||||
* @default 20
|
||||
*/
|
||||
maxRetriesPerRequest?: number | null;
|
||||
|
||||
/*
|
||||
commands = set_union([
|
||||
"get", "set", "setnx", "setex", "append", "strlen", "del", "exists", "setbit", "getbit", "setrange", "getrange", "substr",
|
||||
"incr", "decr", "mget", "rpush", "lpush", "rpushx", "lpushx", "linsert", "rpop", "lpop", "brpop", "brpoplpush", "blpop", "llen", "lindex",
|
||||
"lset", "lrange", "ltrim", "lrem", "rpoplpush", "sadd", "srem", "smove", "sismember", "scard", "spop", "srandmember", "sinter", "sinterstore",
|
||||
"sunion", "sunionstore", "sdiff", "sdiffstore", "smembers", "zadd", "zincrby", "zrem", "zremrangebyscore", "zremrangebyrank", "zunionstore",
|
||||
"zinterstore", "zrange", "zrangebyscore", "zrevrangebyscore", "zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank", "hset", "hsetnx",
|
||||
"hget", "hmset", "hmget", "hincrby", "hincrbyfloat", "hdel", "hlen", "hkeys", "hvals", "hgetall", "hexists", "incrby", "decrby", "getset", "mset", "msetnx",
|
||||
"randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo", "save", "bgsave",
|
||||
"bgrewriteaof", "shutdown", "lastsave", "type", "any", "exec", "discard", "sync", "flushdb", "flushall", "sort", "info", "monitor", "ttl",
|
||||
"persist", "slaveof", "debug", "config", "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "publish", "watch", "unwatch", "cluster",
|
||||
"restore", "migrate", "dump", "object", "client", "eval", "evalsha"], require("./lib/commands"));
|
||||
/**
|
||||
* @default 10000
|
||||
*/
|
||||
maxLoadingRetryTime?: number;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
enableAutoPipelining?: boolean;
|
||||
/**
|
||||
* @default []
|
||||
*/
|
||||
autoPipeliningIgnoredCommands?: string[];
|
||||
offlineQueue?: boolean;
|
||||
commandQueue?: boolean;
|
||||
|
||||
/**
|
||||
*
|
||||
* By default, if the connection to Redis server has not been established, commands are added to a queue
|
||||
* and are executed once the connection is "ready" (when `enableReadyCheck` is true, "ready" means
|
||||
* the Redis server has loaded the database from disk, otherwise means the connection to the Redis
|
||||
* server has been established). If this option is false, when execute the command when the connection
|
||||
* isn't ready, an error will be returned.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableOfflineQueue?: boolean;
|
||||
|
||||
/**
|
||||
* The client will sent an INFO command to check whether the server is still loading data from the disk (
|
||||
* which happens when the server is just launched) when the connection is established, and only wait until
|
||||
* the loading process is finished before emitting the `ready` event.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableReadyCheck?: boolean;
|
||||
|
||||
/**
|
||||
* When a Redis instance is initialized, a connection to the server is immediately established. Set this to
|
||||
* true will delay the connection to the server until the first command is sent or `redis.connect()` is called
|
||||
* explicitly.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
|
||||
get(args: any[], callback?: any): boolean;
|
||||
get(...args: any[]): boolean;
|
||||
set(args: any[], callback?: any): boolean;
|
||||
set(...args: any[]): boolean;
|
||||
setnx(args: any[], callback?: any): boolean;
|
||||
setnx(...args: any[]): boolean;
|
||||
setex(args: any[], callback?: any): boolean;
|
||||
setex(...args: any[]): boolean;
|
||||
append(args: any[], callback?: any): boolean;
|
||||
append(...args: any[]): boolean;
|
||||
strlen(args: any[], callback?: any): boolean;
|
||||
strlen(...args: any[]): boolean;
|
||||
del(args: any[], callback?: any): boolean;
|
||||
del(...args: any[]): boolean;
|
||||
exists(args: any[], callback?: any): boolean;
|
||||
exists(...args: any[]): boolean;
|
||||
setbit(args: any[], callback?: any): boolean;
|
||||
setbit(...args: any[]): boolean;
|
||||
getbit(args: any[], callback?: any): boolean;
|
||||
getbit(...args: any[]): boolean;
|
||||
setrange(args: any[], callback?: any): boolean;
|
||||
setrange(...args: any[]): boolean;
|
||||
getrange(args: any[], callback?: any): boolean;
|
||||
getrange(...args: any[]): boolean;
|
||||
substr(args: any[], callback?: any): boolean;
|
||||
substr(...args: any[]): boolean;
|
||||
incr(args: any[], callback?: any): boolean;
|
||||
incr(...args: any[]): boolean;
|
||||
decr(args: any[], callback?: any): boolean;
|
||||
decr(...args: any[]): boolean;
|
||||
mget(args: any[], callback?: any): boolean;
|
||||
mget(...args: any[]): boolean;
|
||||
rpush(...args: any[]): boolean;
|
||||
lpush(args: any[], callback?: any): boolean;
|
||||
lpush(...args: any[]): boolean;
|
||||
rpushx(args: any[], callback?: any): boolean;
|
||||
rpushx(...args: any[]): boolean;
|
||||
lpushx(args: any[], callback?: any): boolean;
|
||||
lpushx(...args: any[]): boolean;
|
||||
linsert(args: any[], callback?: any): boolean;
|
||||
linsert(...args: any[]): boolean;
|
||||
rpop(args: any[], callback?: any): boolean;
|
||||
rpop(...args: any[]): boolean;
|
||||
lpop(args: any[], callback?: any): boolean;
|
||||
lpop(...args: any[]): boolean;
|
||||
brpop(args: any[], callback?: any): boolean;
|
||||
brpop(...args: any[]): boolean;
|
||||
brpoplpush(args: any[], callback?: any): boolean;
|
||||
brpoplpush(...args: any[]): boolean;
|
||||
blpop(args: any[], callback?: any): boolean;
|
||||
blpop(...args: any[]): boolean;
|
||||
llen(args: any[], callback?: any): boolean;
|
||||
llen(...args: any[]): boolean;
|
||||
lindex(args: any[], callback?: any): boolean;
|
||||
lindex(...args: any[]): boolean;
|
||||
lset(args: any[], callback?: any): boolean;
|
||||
lset(...args: any[]): boolean;
|
||||
lrange(args: any[], callback?: any): boolean;
|
||||
lrange(...args: any[]): boolean;
|
||||
ltrim(args: any[], callback?: any): boolean;
|
||||
ltrim(...args: any[]): boolean;
|
||||
lrem(args: any[], callback?: any): boolean;
|
||||
lrem(...args: any[]): boolean;
|
||||
rpoplpush(args: any[], callback?: any): boolean;
|
||||
rpoplpush(...args: any[]): boolean;
|
||||
sadd(args: any[], callback?: any): boolean;
|
||||
sadd(...args: any[]): boolean;
|
||||
srem(args: any[], callback?: any): boolean;
|
||||
srem(...args: any[]): boolean;
|
||||
smove(args: any[], callback?: any): boolean;
|
||||
smove(...args: any[]): boolean;
|
||||
sismember(args: any[], callback?: any): boolean;
|
||||
sismember(...args: any[]): boolean;
|
||||
scard(args: any[], callback?: any): boolean;
|
||||
scard(...args: any[]): boolean;
|
||||
spop(args: any[], callback?: any): boolean;
|
||||
spop(...args: any[]): boolean;
|
||||
srandmember(args: any[], callback?: any): boolean;
|
||||
srandmember(...args: any[]): boolean;
|
||||
sinter(args: any[], callback?: any): boolean;
|
||||
sinter(...args: any[]): boolean;
|
||||
sinterstore(args: any[], callback?: any): boolean;
|
||||
sinterstore(...args: any[]): boolean;
|
||||
sunion(args: any[], callback?: any): boolean;
|
||||
sunion(...args: any[]): boolean;
|
||||
sunionstore(args: any[], callback?: any): boolean;
|
||||
sunionstore(...args: any[]): boolean;
|
||||
sdiff(args: any[], callback?: any): boolean;
|
||||
sdiff(...args: any[]): boolean;
|
||||
sdiffstore(args: any[], callback?: any): boolean;
|
||||
sdiffstore(...args: any[]): boolean;
|
||||
smembers(args: any[], callback?: any): boolean;
|
||||
smembers(...args: any[]): boolean;
|
||||
zadd(args: any[], callback?: any): boolean;
|
||||
zadd(...args: any[]): boolean;
|
||||
zincrby(args: any[], callback?: any): boolean;
|
||||
zincrby(...args: any[]): boolean;
|
||||
zrem(args: any[], callback?: any): boolean;
|
||||
zrem(...args: any[]): boolean;
|
||||
zremrangebyscore(args: any[], callback?: any): boolean;
|
||||
zremrangebyscore(...args: any[]): boolean;
|
||||
zremrangebyrank(args: any[], callback?: any): boolean;
|
||||
zremrangebyrank(...args: any[]): boolean;
|
||||
zunionstore(args: any[], callback?: any): boolean;
|
||||
zunionstore(...args: any[]): boolean;
|
||||
zinterstore(args: any[], callback?: any): boolean;
|
||||
zinterstore(...args: any[]): boolean;
|
||||
zrange(args: any[], callback?: any): boolean;
|
||||
zrange(...args: any[]): boolean;
|
||||
zrangebyscore(args: any[], callback?: any): boolean;
|
||||
zrangebyscore(...args: any[]): boolean;
|
||||
zrevrangebyscore(args: any[], callback?: any): boolean;
|
||||
zrevrangebyscore(...args: any[]): boolean;
|
||||
zcount(args: any[], callback?: any): boolean;
|
||||
zcount(...args: any[]): boolean;
|
||||
zrevrange(args: any[], callback?: any): boolean;
|
||||
zrevrange(...args: any[]): boolean;
|
||||
zcard(args: any[], callback?: any): boolean;
|
||||
zcard(...args: any[]): boolean;
|
||||
zscore(args: any[], callback?: any): boolean;
|
||||
zscore(...args: any[]): boolean;
|
||||
zrank(args: any[], callback?: any): boolean;
|
||||
zrank(...args: any[]): boolean;
|
||||
zrevrank(args: any[], callback?: any): boolean;
|
||||
zrevrank(...args: any[]): boolean;
|
||||
hset(args: any[], callback?: any): boolean;
|
||||
hset(...args: any[]): boolean;
|
||||
hsetnx(args: any[], callback?: any): boolean;
|
||||
hsetnx(...args: any[]): boolean;
|
||||
hget(args: any[], callback?: any): boolean;
|
||||
hget(...args: any[]): boolean;
|
||||
hmset(args: any[], callback?: any): boolean;
|
||||
hmset(key: string, hash: any, callback?: any): boolean;
|
||||
hmset(...args: any[]): boolean;
|
||||
hmget(args: any[], callback?: any): boolean;
|
||||
hmget(...args: any[]): boolean;
|
||||
hincrby(args: any[], callback?: any): boolean;
|
||||
hincrby(...args: any[]): boolean;
|
||||
hincrbyfloat(args: any[], callback?: any): boolean;
|
||||
hincrbyfloat(...args: any[]): boolean;
|
||||
hdel(args: any[], callback?: any): boolean;
|
||||
hdel(...args: any[]): boolean;
|
||||
hlen(args: any[], callback?: any): boolean;
|
||||
hlen(...args: any[]): boolean;
|
||||
hkeys(args: any[], callback?: any): boolean;
|
||||
hkeys(...args: any[]): boolean;
|
||||
hvals(args: any[], callback?: any): boolean;
|
||||
hvals(...args: any[]): boolean;
|
||||
hgetall(args: any[], callback?: any): boolean;
|
||||
hgetall(...args: any[]): boolean;
|
||||
hgetall(key: string, callback?: any): boolean;
|
||||
hexists(args: any[], callback?: any): boolean;
|
||||
hexists(...args: any[]): boolean;
|
||||
incrby(args: any[], callback?: any): boolean;
|
||||
incrby(...args: any[]): boolean;
|
||||
decrby(args: any[], callback?: any): boolean;
|
||||
decrby(...args: any[]): boolean;
|
||||
getset(args: any[], callback?: any): boolean;
|
||||
getset(...args: any[]): boolean;
|
||||
mset(args: any[], callback?: any): boolean;
|
||||
mset(...args: any[]): boolean;
|
||||
msetnx(args: any[], callback?: any): boolean;
|
||||
msetnx(...args: any[]): boolean;
|
||||
randomkey(args: any[], callback?: any): boolean;
|
||||
randomkey(...args: any[]): boolean;
|
||||
select(args: any[], callback?: any): void;
|
||||
select(...args: any[]): void;
|
||||
move(args: any[], callback?: any): boolean;
|
||||
move(...args: any[]): boolean;
|
||||
rename(args: any[], callback?: any): boolean;
|
||||
rename(...args: any[]): boolean;
|
||||
renamenx(args: any[], callback?: any): boolean;
|
||||
renamenx(...args: any[]): boolean;
|
||||
expire(args: any[], callback?: any): boolean;
|
||||
expire(...args: any[]): boolean;
|
||||
expireat(args: any[], callback?: any): boolean;
|
||||
expireat(...args: any[]): boolean;
|
||||
keys(args: any[], callback?: any): boolean;
|
||||
keys(...args: any[]): boolean;
|
||||
dbsize(args: any[], callback?: any): boolean;
|
||||
dbsize(...args: any[]): boolean;
|
||||
auth(args: any[], callback?: any): void;
|
||||
auth(...args: any[]): void;
|
||||
ping(args: any[], callback?: any): boolean;
|
||||
ping(...args: any[]): boolean;
|
||||
echo(args: any[], callback?: any): boolean;
|
||||
echo(...args: any[]): boolean;
|
||||
save(args: any[], callback?: any): boolean;
|
||||
save(...args: any[]): boolean;
|
||||
bgsave(args: any[], callback?: any): boolean;
|
||||
bgsave(...args: any[]): boolean;
|
||||
bgrewriteaof(args: any[], callback?: any): boolean;
|
||||
bgrewriteaof(...args: any[]): boolean;
|
||||
shutdown(args: any[], callback?: any): boolean;
|
||||
shutdown(...args: any[]): boolean;
|
||||
lastsave(args: any[], callback?: any): boolean;
|
||||
lastsave(...args: any[]): boolean;
|
||||
type(args: any[], callback?: any): boolean;
|
||||
type(...args: any[]): boolean;
|
||||
any(args: any[], callback?: any): any;
|
||||
any(...args: any[]): any;
|
||||
exec(args: any[], callback?: any): boolean;
|
||||
exec(...args: any[]): boolean;
|
||||
discard(args: any[], callback?: any): boolean;
|
||||
discard(...args: any[]): boolean;
|
||||
sync(args: any[], callback?: any): boolean;
|
||||
sync(...args: any[]): boolean;
|
||||
flushdb(args: any[], callback?: any): boolean;
|
||||
flushdb(...args: any[]): boolean;
|
||||
flushall(args: any[], callback?: any): boolean;
|
||||
flushall(...args: any[]): boolean;
|
||||
sort(args: any[], callback?: any): boolean;
|
||||
sort(...args: any[]): boolean;
|
||||
info(args: any[], callback?: any): boolean;
|
||||
info(...args: any[]): boolean;
|
||||
monitor(args: any[], callback?: any): boolean;
|
||||
monitor(...args: any[]): boolean;
|
||||
ttl(args: any[], callback?: any): boolean;
|
||||
ttl(...args: any[]): boolean;
|
||||
persist(args: any[], callback?: any): boolean;
|
||||
persist(...args: any[]): boolean;
|
||||
slaveof(args: any[], callback?: any): boolean;
|
||||
slaveof(...args: any[]): boolean;
|
||||
debug(args: any[], callback?: any): boolean;
|
||||
debug(...args: any[]): boolean;
|
||||
config(args: any[], callback?: any): boolean;
|
||||
config(...args: any[]): boolean;
|
||||
subscribe(args: any[], callback?: any): boolean;
|
||||
subscribe(...args: any[]): boolean;
|
||||
unsubscribe(args: any[], callback?: any): boolean;
|
||||
unsubscribe(...args: any[]): boolean;
|
||||
psubscribe(args: any[], callback?: any): boolean;
|
||||
psubscribe(...args: any[]): boolean;
|
||||
punsubscribe(args: any[], callback?: any): boolean;
|
||||
punsubscribe(...args: any[]): boolean;
|
||||
publish(args: any[], callback?: any): boolean;
|
||||
publish(...args: any[]): boolean;
|
||||
watch(args: any[], callback?: any): boolean;
|
||||
watch(...args: any[]): boolean;
|
||||
unwatch(args: any[], callback?: any): boolean;
|
||||
unwatch(...args: any[]): boolean;
|
||||
cluster(args: any[], callback?: any): boolean;
|
||||
cluster(...args: any[]): boolean;
|
||||
restore(args: any[], callback?: any): boolean;
|
||||
restore(...args: any[]): boolean;
|
||||
migrate(args: any[], callback?: any): boolean;
|
||||
migrate(...args: any[]): boolean;
|
||||
dump(args: any[], callback?: any): boolean;
|
||||
dump(...args: any[]): boolean;
|
||||
object(args: any[], callback?: any): boolean;
|
||||
object(...args: any[]): boolean;
|
||||
client(args: any[], callback?: any): boolean;
|
||||
client(...args: any[]): boolean;
|
||||
eval(args: any[], callback?: any): boolean;
|
||||
eval(...args: any[]): boolean;
|
||||
evalsha(args: any[], callback?: any): boolean;
|
||||
evalsha(...args: any[]): boolean;
|
||||
script(args: any[], callback?: any): boolean;
|
||||
script(...args: any[]): boolean;
|
||||
script(key: string, callback?: any): boolean;
|
||||
quit(args: any[], callback?: any): boolean;
|
||||
quit(...args: any[]): boolean;
|
||||
sscan(...args: any[]): boolean;
|
||||
sscan(args: any[], callback?: any): boolean;
|
||||
scan(...args: any[]): boolean;
|
||||
scan(args: any[], callback?: any): boolean;
|
||||
hscan(...args: any[]): boolean;
|
||||
hscan(args: any[], callback?: any): boolean;
|
||||
zscan(...args: any[]): boolean;
|
||||
zscan(args: any[], callback?: any): boolean;
|
||||
lazyConnect?: boolean;
|
||||
|
||||
// Extras
|
||||
duplicate(options?: any[], callback?: any): RedisClient;
|
||||
/**
|
||||
* @default undefined
|
||||
*/
|
||||
scripts?: Record<
|
||||
string,
|
||||
{ lua: string; numberOfKeys?: number; readOnly?: boolean }
|
||||
>;
|
||||
|
||||
keyPrefix?: string;
|
||||
showFriendlyErrorStack?: boolean;
|
||||
|
||||
// StandaloneConnectionOptions
|
||||
disconnectTimeout?: number;
|
||||
tls?: ConnectionOptions;
|
||||
|
||||
// SentinelConnectionOptions
|
||||
/**
|
||||
* Master group name of the Sentinel
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @default "master"
|
||||
*/
|
||||
role?: 'master' | 'slave';
|
||||
sentinelUsername?: string;
|
||||
sentinelPassword?: string;
|
||||
sentinels?: Array<Partial<any>>;
|
||||
sentinelRetryStrategy?: (retryAttempts: number) => number | void | null;
|
||||
sentinelReconnectStrategy?: (retryAttempts: number) => number | void | null;
|
||||
preferredSlaves?: any;
|
||||
sentinelCommandTimeout?: number;
|
||||
enableTLSForSentinelMode?: boolean;
|
||||
sentinelTLS?: ConnectionOptions;
|
||||
natMap?: any;
|
||||
updateSentinels?: boolean;
|
||||
/**
|
||||
* @default 10
|
||||
*/
|
||||
sentinelMaxConnections?: number;
|
||||
failoverDetector?: boolean;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ProducerRecord,
|
||||
} from '../external/kafka.interface';
|
||||
import { MqttClientOptions, QoS } from '../external/mqtt-options.interface';
|
||||
import { ClientOpts } from '../external/redis.interface';
|
||||
import { IORedisOptions } from '../external/redis.interface';
|
||||
import { RmqUrl } from '../external/rmq-url.interface';
|
||||
import { TcpSocket } from '../helpers';
|
||||
import { CustomTransportStrategy } from './custom-transport-strategy.interface';
|
||||
@@ -35,9 +35,6 @@ export interface CustomStrategy {
|
||||
export interface GrpcOptions {
|
||||
transport?: Transport.GRPC;
|
||||
options: {
|
||||
interceptors?: Array<
|
||||
(options: any, nextCall: (options: any) => any) => any
|
||||
>;
|
||||
url?: string;
|
||||
maxSendMessageLength?: number;
|
||||
maxReceiveMessageLength?: number;
|
||||
@@ -94,7 +91,7 @@ export interface RedisOptions {
|
||||
retryDelay?: number;
|
||||
serializer?: Serializer;
|
||||
deserializer?: Deserializer;
|
||||
} & ClientOpts;
|
||||
} & IORedisOptions;
|
||||
}
|
||||
|
||||
export interface MqttOptions {
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface ClientProperties {
|
||||
}
|
||||
|
||||
export interface EventOrMessageListenerDefinition {
|
||||
pattern: PatternMetadata;
|
||||
patterns: PatternMetadata[];
|
||||
methodKey: string;
|
||||
isEventHandler: boolean;
|
||||
targetCallback: (...args: any[]) => any;
|
||||
@@ -58,13 +58,13 @@ export class ListenerMetadataExplorer {
|
||||
if (isUndefined(handlerType)) {
|
||||
return;
|
||||
}
|
||||
const pattern = Reflect.getMetadata(PATTERN_METADATA, targetCallback);
|
||||
const patterns = Reflect.getMetadata(PATTERN_METADATA, targetCallback);
|
||||
const transport = Reflect.getMetadata(TRANSPORT_METADATA, targetCallback);
|
||||
const extras = Reflect.getMetadata(PATTERN_EXTRAS_METADATA, targetCallback);
|
||||
return {
|
||||
methodKey,
|
||||
targetCallback,
|
||||
pattern,
|
||||
patterns,
|
||||
transport,
|
||||
extras,
|
||||
isEventHandler: handlerType === PatternHandler.EVENT,
|
||||
|
||||
@@ -71,8 +71,21 @@ export class ListenersController {
|
||||
isUndefined(server.transportId) ||
|
||||
transport === server.transportId,
|
||||
)
|
||||
.reduce((acc, handler) => {
|
||||
// Optional chaining for backward-compatibility
|
||||
handler.patterns?.forEach(pattern =>
|
||||
acc.push({ ...handler, patterns: [pattern] }),
|
||||
);
|
||||
return acc;
|
||||
}, [])
|
||||
.forEach(
|
||||
({ pattern, targetCallback, methodKey, extras, isEventHandler }) => {
|
||||
({
|
||||
patterns: [pattern],
|
||||
targetCallback,
|
||||
methodKey,
|
||||
extras,
|
||||
isEventHandler,
|
||||
}) => {
|
||||
if (isStatic) {
|
||||
const proxy = this.contextCreator.create(
|
||||
instance as object,
|
||||
|
||||
@@ -24,13 +24,12 @@ export class MicroservicesModule {
|
||||
private listenersController: ListenersController;
|
||||
|
||||
public register(container: NestContainer, config: ApplicationConfig) {
|
||||
const rpcProxy = new RpcProxy();
|
||||
const exceptionFiltersContext = new ExceptionFiltersContext(
|
||||
container,
|
||||
config,
|
||||
);
|
||||
const contextCreator = new RpcContextCreator(
|
||||
rpcProxy,
|
||||
new RpcProxy(),
|
||||
exceptionFiltersContext,
|
||||
new PipesContextCreator(container, config),
|
||||
new PipesConsumer(),
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { isNil } from '@nestjs/common/utils/shared.utils';
|
||||
import { Observable } from 'rxjs';
|
||||
import { isObservable, lastValueFrom, Observable, ReplaySubject } from 'rxjs';
|
||||
import {
|
||||
KAFKA_DEFAULT_BROKER,
|
||||
KAFKA_DEFAULT_CLIENT,
|
||||
KAFKA_DEFAULT_GROUP,
|
||||
NO_EVENT_HANDLER,
|
||||
NO_MESSAGE_HANDLER,
|
||||
} from '../constants';
|
||||
import { KafkaContext } from '../ctx-host';
|
||||
import { KafkaRequestDeserializer } from '../deserializers/kafka-request.deserializer';
|
||||
import { KafkaHeaders, Transport } from '../enums';
|
||||
import { KafkaRetriableException } from '../exceptions';
|
||||
import {
|
||||
BrokersFunction,
|
||||
Consumer,
|
||||
@@ -26,6 +29,7 @@ import {
|
||||
CustomTransportStrategy,
|
||||
KafkaOptions,
|
||||
OutgoingResponse,
|
||||
ReadPacket,
|
||||
} from '../interfaces';
|
||||
import { KafkaRequestSerializer } from '../serializers/kafka-request.serializer';
|
||||
import { Server } from './server';
|
||||
@@ -162,6 +166,7 @@ export class ServerKafka extends Server implements CustomTransportStrategy {
|
||||
rawMessage,
|
||||
payload.partition,
|
||||
payload.topic,
|
||||
this.consumer,
|
||||
]);
|
||||
const handler = this.getHandlerByPattern(packet.pattern);
|
||||
// if the correlation id or reply topic is not set
|
||||
@@ -186,7 +191,37 @@ export class ServerKafka extends Server implements CustomTransportStrategy {
|
||||
const response$ = this.transformToObservable(
|
||||
await handler(packet.data, kafkaContext),
|
||||
);
|
||||
response$ && this.send(response$, publish);
|
||||
|
||||
const replayStream$ = new ReplaySubject();
|
||||
await this.combineStreamsAndThrowIfRetriable(response$, replayStream$);
|
||||
|
||||
this.send(replayStream$, publish);
|
||||
}
|
||||
|
||||
private combineStreamsAndThrowIfRetriable(
|
||||
response$: Observable<any>,
|
||||
replayStream$: ReplaySubject<unknown>,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let isPromiseResolved = false;
|
||||
response$.subscribe({
|
||||
next: val => {
|
||||
replayStream$.next(val);
|
||||
if (!isPromiseResolved) {
|
||||
isPromiseResolved = true;
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
if (err instanceof KafkaRetriableException && !isPromiseResolved) {
|
||||
isPromiseResolved = true;
|
||||
reject(err);
|
||||
}
|
||||
replayStream$.error(err);
|
||||
},
|
||||
complete: () => replayStream$.complete(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async sendMessage(
|
||||
@@ -228,9 +263,12 @@ export class ServerKafka extends Server implements CustomTransportStrategy {
|
||||
if (!outgoingResponse.err) {
|
||||
return;
|
||||
}
|
||||
outgoingMessage.headers[KafkaHeaders.NEST_ERR] = Buffer.from(
|
||||
outgoingResponse.err,
|
||||
);
|
||||
const stringifiedError =
|
||||
typeof outgoingResponse.err === 'object'
|
||||
? JSON.stringify(outgoingResponse.err)
|
||||
: outgoingResponse.err;
|
||||
outgoingMessage.headers[KafkaHeaders.NEST_ERR] =
|
||||
Buffer.from(stringifiedError);
|
||||
}
|
||||
|
||||
public assignCorrelationIdHeader(
|
||||
@@ -251,8 +289,27 @@ export class ServerKafka extends Server implements CustomTransportStrategy {
|
||||
outgoingMessage.partition = parseFloat(replyPartition);
|
||||
}
|
||||
|
||||
public async handleEvent(
|
||||
pattern: string,
|
||||
packet: ReadPacket,
|
||||
context: KafkaContext,
|
||||
): Promise<any> {
|
||||
const handler = this.getHandlerByPattern(pattern);
|
||||
if (!handler) {
|
||||
return this.logger.error(NO_EVENT_HANDLER`${pattern}`);
|
||||
}
|
||||
const resultOrStream = await handler(packet.data, context);
|
||||
if (isObservable(resultOrStream)) {
|
||||
await lastValueFrom(resultOrStream);
|
||||
}
|
||||
}
|
||||
|
||||
protected initializeSerializer(options: KafkaOptions['options']) {
|
||||
this.serializer =
|
||||
(options && options.serializer) || new KafkaRequestSerializer();
|
||||
}
|
||||
|
||||
protected initializeDeserializer(options: KafkaOptions['options']) {
|
||||
this.deserializer = options?.deserializer ?? new KafkaRequestDeserializer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
import { isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
CONNECT_EVENT,
|
||||
ERROR_EVENT,
|
||||
MESSAGE_EVENT,
|
||||
NO_MESSAGE_HANDLER,
|
||||
REDIS_DEFAULT_URL,
|
||||
REDIS_DEFAULT_HOST,
|
||||
REDIS_DEFAULT_PORT,
|
||||
} from '../constants';
|
||||
import { RedisContext } from '../ctx-host';
|
||||
import { Transport } from '../enums';
|
||||
import {
|
||||
ClientOpts,
|
||||
RedisClient,
|
||||
RetryStrategyOptions,
|
||||
} from '../external/redis.interface';
|
||||
import { CustomTransportStrategy, IncomingRequest } from '../interfaces';
|
||||
import { RedisOptions } from '../interfaces/microservice-configuration.interface';
|
||||
CustomTransportStrategy,
|
||||
IncomingRequest,
|
||||
RedisOptions,
|
||||
} from '../interfaces';
|
||||
import { Server } from './server';
|
||||
|
||||
let redisPackage: any = {};
|
||||
type Redis = any;
|
||||
|
||||
let redisPackage = {} as any;
|
||||
|
||||
export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
public readonly transportId = Transport.REDIS;
|
||||
|
||||
private readonly url: string;
|
||||
private subClient: RedisClient;
|
||||
private pubClient: RedisClient;
|
||||
private subClient: Redis;
|
||||
private pubClient: Redis;
|
||||
private isExplicitlyTerminated = false;
|
||||
|
||||
constructor(private readonly options: RedisOptions['options']) {
|
||||
super();
|
||||
this.url =
|
||||
this.getOptionsProp(options, 'url') ||
|
||||
(!this.getOptionsProp(options, 'host') && REDIS_DEFAULT_URL);
|
||||
|
||||
redisPackage = this.loadPackage('redis', ServerRedis.name, () =>
|
||||
require('redis'),
|
||||
redisPackage = this.loadPackage('ioredis', ServerRedis.name, () =>
|
||||
require('ioredis'),
|
||||
);
|
||||
|
||||
this.initializeSerializer(options);
|
||||
@@ -51,6 +47,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
|
||||
this.handleError(this.pubClient);
|
||||
this.handleError(this.subClient);
|
||||
|
||||
this.start(callback);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
@@ -58,11 +55,15 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
}
|
||||
|
||||
public start(callback?: () => void) {
|
||||
this.bindEvents(this.subClient, this.pubClient);
|
||||
this.subClient.on(CONNECT_EVENT, callback);
|
||||
Promise.all([this.subClient.connect(), this.pubClient.connect()])
|
||||
.then(() => {
|
||||
this.bindEvents(this.subClient, this.pubClient);
|
||||
callback();
|
||||
})
|
||||
.catch(callback);
|
||||
}
|
||||
|
||||
public bindEvents(subClient: RedisClient, pubClient: RedisClient) {
|
||||
public bindEvents(subClient: Redis, pubClient: Redis) {
|
||||
subClient.on(MESSAGE_EVENT, this.getMessageHandler(pubClient).bind(this));
|
||||
const subscribePatterns = [...this.messageHandlers.keys()];
|
||||
subscribePatterns.forEach(pattern => {
|
||||
@@ -79,14 +80,16 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
this.subClient && this.subClient.quit();
|
||||
}
|
||||
|
||||
public createRedisClient(): RedisClient {
|
||||
return redisPackage.createClient({
|
||||
public createRedisClient(): Redis {
|
||||
return new redisPackage({
|
||||
port: REDIS_DEFAULT_PORT,
|
||||
host: REDIS_DEFAULT_HOST,
|
||||
...this.getClientOptions(),
|
||||
url: this.url,
|
||||
lazyConnect: true,
|
||||
});
|
||||
}
|
||||
|
||||
public getMessageHandler(pub: RedisClient) {
|
||||
public getMessageHandler(pub: Redis) {
|
||||
return async (channel: string, buffer: string | any) =>
|
||||
this.handleMessage(channel, buffer, pub);
|
||||
}
|
||||
@@ -94,7 +97,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
public async handleMessage(
|
||||
channel: string,
|
||||
buffer: string | any,
|
||||
pub: RedisClient,
|
||||
pub: Redis,
|
||||
) {
|
||||
const rawMessage = this.parseMessage(buffer);
|
||||
const packet = await this.deserializer.deserialize(rawMessage, { channel });
|
||||
@@ -125,7 +128,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
response$ && this.send(response$, publish);
|
||||
}
|
||||
|
||||
public getPublisher(pub: RedisClient, pattern: any, id: string) {
|
||||
public getPublisher(pub: Redis, pattern: any, id: string) {
|
||||
return (response: any) => {
|
||||
Object.assign(response, { id });
|
||||
const outgoingResponse = this.serializer.serialize(response);
|
||||
@@ -157,31 +160,25 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
stream.on(ERROR_EVENT, (err: any) => this.logger.error(err));
|
||||
}
|
||||
|
||||
public getClientOptions(): Partial<ClientOpts> {
|
||||
const retry_strategy = (options: RetryStrategyOptions) =>
|
||||
this.createRetryStrategy(options);
|
||||
public getClientOptions(): Partial<RedisOptions['options']> {
|
||||
const retryStrategy = (times: number) => this.createRetryStrategy(times);
|
||||
|
||||
return {
|
||||
...(this.options || {}),
|
||||
retry_strategy,
|
||||
retryStrategy,
|
||||
};
|
||||
}
|
||||
|
||||
public createRetryStrategy(
|
||||
options: RetryStrategyOptions,
|
||||
): undefined | number | void {
|
||||
if (options.error && (options.error as any).code === 'ECONNREFUSED') {
|
||||
this.logger.error(`Error ECONNREFUSED: ${this.url}`);
|
||||
}
|
||||
public createRetryStrategy(times: number): undefined | number | void {
|
||||
if (this.isExplicitlyTerminated) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
!this.getOptionsProp(this.options, 'retryAttempts') ||
|
||||
options.attempt > this.getOptionsProp(this.options, 'retryAttempts')
|
||||
times > this.getOptionsProp(this.options, 'retryAttempts')
|
||||
) {
|
||||
this.logger.error(`Retry time exhausted: ${this.url}`);
|
||||
throw new Error('Retry time exhausted');
|
||||
this.logger.error(`Retry time exhausted`);
|
||||
return;
|
||||
}
|
||||
return this.getOptionsProp(this.options, 'retryDelay') || 0;
|
||||
}
|
||||
|
||||
@@ -111,9 +111,7 @@ export abstract class Server {
|
||||
): Promise<any> {
|
||||
const handler = this.getHandlerByPattern(pattern);
|
||||
if (!handler) {
|
||||
return this.logger.error(
|
||||
`${NO_EVENT_HANDLER} Event pattern: ${JSON.stringify(pattern)}.`,
|
||||
);
|
||||
return this.logger.error(NO_EVENT_HANDLER`${pattern}`);
|
||||
}
|
||||
const resultOrStream = await handler(packet.data, context);
|
||||
if (isObservable(resultOrStream)) {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import { Subject } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { ClientRedis } from '../../client/client-redis';
|
||||
import { ERROR_EVENT } from '../../constants';
|
||||
import { Client } from '../../external/nats-client.interface';
|
||||
|
||||
describe('ClientRedis', () => {
|
||||
const test = 'test';
|
||||
@@ -252,53 +250,39 @@ describe('ClientRedis', () => {
|
||||
});
|
||||
});
|
||||
describe('getClientOptions', () => {
|
||||
it('should return options object with "retry_strategy" and call "createRetryStrategy"', () => {
|
||||
it('should return options object with "retryStrategy" and call "createRetryStrategy"', () => {
|
||||
const createSpy = sinon.spy(client, 'createRetryStrategy');
|
||||
const { retry_strategy } = client.getClientOptions(new Subject());
|
||||
const { retryStrategy } = client.getClientOptions();
|
||||
try {
|
||||
retry_strategy({} as any);
|
||||
retryStrategy({} as any);
|
||||
} catch {}
|
||||
expect(createSpy.called).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('createRetryStrategy', () => {
|
||||
const subject = new Subject<Error>();
|
||||
describe('when is terminated', () => {
|
||||
it('should return undefined', () => {
|
||||
(client as any).isExplicitlyTerminated = true;
|
||||
const result = client.createRetryStrategy({} as any, subject);
|
||||
const result = client.createRetryStrategy(0);
|
||||
expect(result).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('when "retryAttempts" does not exist', () => {
|
||||
it('should return an error', () => {
|
||||
it('should return undefined', () => {
|
||||
(client as any).isExplicitlyTerminated = false;
|
||||
(client as any).options.options = {};
|
||||
(client as any).options.options.retryAttempts = undefined;
|
||||
const result = client.createRetryStrategy({} as any, subject);
|
||||
expect(result).to.be.instanceOf(Error);
|
||||
const result = client.createRetryStrategy(1);
|
||||
expect(result).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('when "attempts" count is max', () => {
|
||||
it('should return an error', () => {
|
||||
it('should return undefined', () => {
|
||||
(client as any).isExplicitlyTerminated = false;
|
||||
(client as any).options.options = {};
|
||||
(client as any).options.options.retryAttempts = 3;
|
||||
const result = client.createRetryStrategy(
|
||||
{ attempt: 4 } as any,
|
||||
subject,
|
||||
);
|
||||
expect(result).to.be.instanceOf(Error);
|
||||
});
|
||||
});
|
||||
describe('when ECONNREFUSED', () => {
|
||||
it('should return error', () => {
|
||||
(client as any).options.options = {};
|
||||
(client as any).options.options.retryAttempts = 10;
|
||||
|
||||
const error = { code: 'ECONNREFUSED' };
|
||||
const result = client.createRetryStrategy({ error } as any, subject);
|
||||
expect(result).to.be.instanceOf(Error);
|
||||
const result = client.createRetryStrategy(4);
|
||||
expect(result).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('otherwise', () => {
|
||||
@@ -307,10 +291,7 @@ describe('ClientRedis', () => {
|
||||
(client as any).isExplicitlyTerminated = false;
|
||||
(client as any).options.retryAttempts = 3;
|
||||
(client as any).options.retryDelay = 3;
|
||||
const result = client.createRetryStrategy(
|
||||
{ attempt: 2 } as any,
|
||||
subject,
|
||||
);
|
||||
const result = client.createRetryStrategy(2);
|
||||
expect(result).to.be.eql((client as any).options.retryDelay);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { expect } from 'chai';
|
||||
import { KafkaContext } from '../../ctx-host';
|
||||
import { KafkaMessage } from '../../external/kafka.interface';
|
||||
import { Consumer, KafkaMessage } from '../../external/kafka.interface';
|
||||
|
||||
describe('KafkaContext', () => {
|
||||
const args = ['test', { test: true }];
|
||||
const args = ['test', { test: true }, undefined, { test: 'consumer' }];
|
||||
let context: KafkaContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new KafkaContext(args as [KafkaMessage, number, string]);
|
||||
context = new KafkaContext(
|
||||
args as [KafkaMessage, number, string, Consumer],
|
||||
);
|
||||
});
|
||||
describe('getTopic', () => {
|
||||
it('should return topic', () => {
|
||||
@@ -24,4 +26,9 @@ describe('KafkaContext', () => {
|
||||
expect(context.getMessage()).to.be.eql(args[0]);
|
||||
});
|
||||
});
|
||||
describe('getConsumer', () => {
|
||||
it('should return consumer instance', () => {
|
||||
expect(context.getConsumer()).to.deep.eq({ test: 'consumer' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,14 +9,24 @@ import { EventPattern } from '../../decorators/event-pattern.decorator';
|
||||
|
||||
describe('@EventPattern', () => {
|
||||
const pattern = { role: 'test' };
|
||||
const patternSecond = { role: 'test2' };
|
||||
const patternThird = { role: 'test3' };
|
||||
const extras = { param: 'value' };
|
||||
class TestComponent {
|
||||
@EventPattern(pattern, undefined, extras)
|
||||
public static test() {}
|
||||
|
||||
@EventPattern(patternSecond, undefined, extras)
|
||||
@EventPattern(patternThird, undefined, extras)
|
||||
public static testOnlyThird() {}
|
||||
|
||||
@EventPattern([patternSecond, patternThird], undefined, extras)
|
||||
public static testBoth() {}
|
||||
}
|
||||
it(`should enhance method with ${PATTERN_METADATA} metadata`, () => {
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, TestComponent.test);
|
||||
expect(metadata).to.be.eql(pattern);
|
||||
expect(metadata.length).to.equal(1);
|
||||
expect(metadata[0]).to.be.eql(pattern);
|
||||
});
|
||||
it(`should enhance method with ${PATTERN_EXTRAS_METADATA} metadata`, () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
@@ -25,6 +35,23 @@ describe('@EventPattern', () => {
|
||||
);
|
||||
expect(metadata).to.be.deep.equal(extras);
|
||||
});
|
||||
it(`should enhance method with last ${PATTERN_METADATA} metadata`, () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent.testOnlyThird,
|
||||
);
|
||||
expect(metadata.length).to.equal(1);
|
||||
expect(metadata[0]).to.be.eql(patternSecond);
|
||||
});
|
||||
it(`should enhance method with both ${PATTERN_METADATA} metadata`, () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent.testBoth,
|
||||
);
|
||||
expect(metadata.length).to.equal(2);
|
||||
expect(metadata[0]).to.be.eql(patternSecond);
|
||||
expect(metadata[1]).to.be.eql(patternThird);
|
||||
});
|
||||
|
||||
describe('decorator overloads', () => {
|
||||
class TestComponent1 {
|
||||
@@ -45,7 +72,7 @@ describe('@EventPattern', () => {
|
||||
}
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent1.test,
|
||||
);
|
||||
@@ -63,7 +90,7 @@ describe('@EventPattern', () => {
|
||||
});
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA}, ${TRANSPORT_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent2.test,
|
||||
);
|
||||
@@ -81,7 +108,7 @@ describe('@EventPattern', () => {
|
||||
});
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA}, ${PATTERN_EXTRAS_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent3.test,
|
||||
);
|
||||
@@ -100,7 +127,7 @@ describe('@EventPattern', () => {
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA}, ${TRANSPORT_METADATA} and \
|
||||
${PATTERN_EXTRAS_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent4.test,
|
||||
);
|
||||
|
||||
@@ -21,7 +21,10 @@ describe('@MessagePattern', () => {
|
||||
public static test() {}
|
||||
}
|
||||
it(`should enhance method with ${PATTERN_METADATA} metadata`, () => {
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, TestComponent.test);
|
||||
const [metadata] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent.test,
|
||||
);
|
||||
expect(metadata).to.be.eql(pattern);
|
||||
});
|
||||
it(`should enhance method with ${PATTERN_EXTRAS_METADATA} metadata`, () => {
|
||||
@@ -51,7 +54,7 @@ describe('@MessagePattern', () => {
|
||||
}
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent1.test,
|
||||
);
|
||||
@@ -69,7 +72,7 @@ describe('@MessagePattern', () => {
|
||||
});
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA}, ${TRANSPORT_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent2.test,
|
||||
);
|
||||
@@ -87,7 +90,7 @@ describe('@MessagePattern', () => {
|
||||
});
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA}, ${PATTERN_EXTRAS_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent3.test,
|
||||
);
|
||||
@@ -106,7 +109,7 @@ describe('@MessagePattern', () => {
|
||||
|
||||
it(`should enhance method with ${PATTERN_METADATA}, ${TRANSPORT_METADATA} and \
|
||||
${PATTERN_EXTRAS_METADATA} metadata`, () => {
|
||||
const metadataArg = Reflect.getMetadata(
|
||||
const [metadataArg] = Reflect.getMetadata(
|
||||
PATTERN_METADATA,
|
||||
TestComponent4.test,
|
||||
);
|
||||
@@ -139,7 +142,7 @@ describe('@GrpcMethod', () => {
|
||||
|
||||
it('should derive method and service name', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test);
|
||||
expect(metadata).to.be.eql({
|
||||
service: TestService.name,
|
||||
rpc: 'Test',
|
||||
@@ -149,7 +152,7 @@ describe('@GrpcMethod', () => {
|
||||
|
||||
it('should derive method', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test2);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test2);
|
||||
expect(metadata).to.be.eql({
|
||||
service: 'TestService2',
|
||||
rpc: 'Test2',
|
||||
@@ -159,7 +162,7 @@ describe('@GrpcMethod', () => {
|
||||
|
||||
it('should override both method and service', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test3);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test3);
|
||||
expect(metadata).to.be.eql({
|
||||
service: 'TestService2',
|
||||
rpc: 'Test2',
|
||||
@@ -182,7 +185,7 @@ describe('@GrpcStreamMethod', () => {
|
||||
|
||||
it('should derive method and service name', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test);
|
||||
expect(metadata).to.be.eql({
|
||||
service: TestService.name,
|
||||
rpc: 'Test',
|
||||
@@ -192,7 +195,7 @@ describe('@GrpcStreamMethod', () => {
|
||||
|
||||
it('should derive method', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test2);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test2);
|
||||
expect(metadata).to.be.eql({
|
||||
service: 'TestService2',
|
||||
rpc: 'Test2',
|
||||
@@ -202,7 +205,7 @@ describe('@GrpcStreamMethod', () => {
|
||||
|
||||
it('should override both method and service', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test3);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test3);
|
||||
expect(metadata).to.be.eql({
|
||||
service: 'TestService2',
|
||||
rpc: 'Test2',
|
||||
@@ -225,7 +228,7 @@ describe('@GrpcStreamCall', () => {
|
||||
|
||||
it('should derive method and service name', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test);
|
||||
expect(metadata).to.be.eql({
|
||||
service: TestService.name,
|
||||
rpc: 'Test',
|
||||
@@ -235,7 +238,7 @@ describe('@GrpcStreamCall', () => {
|
||||
|
||||
it('should derive method', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test2);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test2);
|
||||
expect(metadata).to.be.eql({
|
||||
service: 'TestService2',
|
||||
rpc: 'Test2',
|
||||
@@ -245,7 +248,7 @@ describe('@GrpcStreamCall', () => {
|
||||
|
||||
it('should override both method and service', () => {
|
||||
const svc = new TestService();
|
||||
const metadata = Reflect.getMetadata(PATTERN_METADATA, svc.test3);
|
||||
const [metadata] = Reflect.getMetadata(PATTERN_METADATA, svc.test3);
|
||||
expect(metadata).to.be.eql({
|
||||
service: 'TestService2',
|
||||
rpc: 'Test2',
|
||||
|
||||
@@ -74,8 +74,8 @@ describe('ListenersController', () => {
|
||||
|
||||
describe('registerPatternHandlers', () => {
|
||||
const handlers = [
|
||||
{ pattern: 'test', targetCallback: 'tt' },
|
||||
{ pattern: 'test2', targetCallback: '2', isEventHandler: true },
|
||||
{ patterns: ['test'], targetCallback: 'tt' },
|
||||
{ patterns: ['test2'], targetCallback: '2', isEventHandler: true },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -89,7 +89,7 @@ describe('ListenersController', () => {
|
||||
it(`should call "addHandler" method of server for each pattern handler with same transport`, () => {
|
||||
const serverHandlers = [
|
||||
{
|
||||
pattern: { cmd: 'test' },
|
||||
patterns: [{ cmd: 'test' }],
|
||||
targetCallback: 'tt',
|
||||
transport: Transport.TCP,
|
||||
},
|
||||
@@ -101,8 +101,12 @@ describe('ListenersController', () => {
|
||||
});
|
||||
it(`should call "addHandler" method of server without transportID for each pattern handler with any transport value`, () => {
|
||||
const serverHandlers = [
|
||||
{ pattern: { cmd: 'test' }, targetCallback: 'tt' },
|
||||
{ pattern: 'test2', targetCallback: '2', transport: Transport.KAFKA },
|
||||
{ patterns: [{ cmd: 'test' }], targetCallback: 'tt' },
|
||||
{
|
||||
patterns: ['test2'],
|
||||
targetCallback: '2',
|
||||
transport: Transport.KAFKA,
|
||||
},
|
||||
];
|
||||
explorer.expects('explore').returns(serverHandlers);
|
||||
instance.registerPatternHandlers(new InstanceWrapper(), server, '');
|
||||
@@ -110,10 +114,14 @@ describe('ListenersController', () => {
|
||||
});
|
||||
it(`should call "addHandler" method of server with transportID for each pattern handler with self transport and without transport`, () => {
|
||||
const serverHandlers = [
|
||||
{ pattern: 'test', targetCallback: 'tt' },
|
||||
{ pattern: 'test2', targetCallback: '2', transport: Transport.KAFKA },
|
||||
{ patterns: ['test'], targetCallback: 'tt' },
|
||||
{
|
||||
pattern: { cmd: 'test3' },
|
||||
patterns: ['test2'],
|
||||
targetCallback: '2',
|
||||
transport: Transport.KAFKA,
|
||||
},
|
||||
{
|
||||
patterns: [{ cmd: 'test3' }],
|
||||
targetCallback: '3',
|
||||
transport: Transport.TCP,
|
||||
},
|
||||
@@ -130,11 +138,15 @@ describe('ListenersController', () => {
|
||||
it(`should call "addHandler" method of server with custom transportID for pattern handler with the same custom token`, () => {
|
||||
const serverHandlers = [
|
||||
{
|
||||
pattern: { cmd: 'test' },
|
||||
patterns: [{ cmd: 'test' }],
|
||||
targetCallback: 'tt',
|
||||
transport: customTransport,
|
||||
},
|
||||
{ pattern: 'test2', targetCallback: '2', transport: Transport.KAFKA },
|
||||
{
|
||||
patterns: ['test2'],
|
||||
targetCallback: '2',
|
||||
transport: Transport.KAFKA,
|
||||
},
|
||||
];
|
||||
|
||||
explorer.expects('explore').returns(serverHandlers);
|
||||
@@ -143,7 +155,11 @@ describe('ListenersController', () => {
|
||||
});
|
||||
it(`should call "addHandler" method of server with extras data`, () => {
|
||||
const serverHandlers = [
|
||||
{ pattern: 'test', targetCallback: 'tt', extras: { param: 'value' } },
|
||||
{
|
||||
patterns: ['test'],
|
||||
targetCallback: 'tt',
|
||||
extras: { param: 'value' },
|
||||
},
|
||||
];
|
||||
explorer.expects('explore').returns(serverHandlers);
|
||||
instance.registerPatternHandlers(new InstanceWrapper(), serverTCP, '');
|
||||
@@ -193,7 +209,7 @@ describe('ListenersController', () => {
|
||||
const module = {
|
||||
controllers: new Map(),
|
||||
} as any;
|
||||
const pattern = {};
|
||||
const patterns = [{}];
|
||||
const wrapper = new InstanceWrapper({ instance: { [methodKey]: {} } });
|
||||
|
||||
it('should pass all arguments to the proxy chain', async () => {
|
||||
@@ -202,7 +218,7 @@ describe('ListenersController', () => {
|
||||
.callsFake(() => Promise.resolve({}));
|
||||
const handler = instance.createRequestScopedHandler(
|
||||
wrapper,
|
||||
pattern,
|
||||
patterns,
|
||||
module,
|
||||
moduleKey,
|
||||
methodKey,
|
||||
@@ -221,7 +237,7 @@ describe('ListenersController', () => {
|
||||
const module = {
|
||||
controllers: new Map(),
|
||||
} as any;
|
||||
const pattern = {};
|
||||
const patterns = [{}];
|
||||
const wrapper = new InstanceWrapper({ instance: { [methodKey]: {} } });
|
||||
|
||||
it('should delegete error to exception filters', async () => {
|
||||
@@ -230,7 +246,7 @@ describe('ListenersController', () => {
|
||||
});
|
||||
const handler = instance.createRequestScopedHandler(
|
||||
wrapper,
|
||||
pattern,
|
||||
patterns,
|
||||
module,
|
||||
moduleKey,
|
||||
methodKey,
|
||||
|
||||
@@ -2,15 +2,20 @@ import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { MetadataScanner } from '../../core/metadata-scanner';
|
||||
import { Client } from '../decorators/client.decorator';
|
||||
import { EventPattern } from '../decorators/event-pattern.decorator';
|
||||
import { MessagePattern } from '../decorators/message-pattern.decorator';
|
||||
import { Transport } from '../enums/transport.enum';
|
||||
import { ListenerMetadataExplorer } from '../listener-metadata-explorer';
|
||||
|
||||
describe('ListenerMetadataExplorer', () => {
|
||||
const pattern = { pattern: 'test' };
|
||||
const secPattern = { role: '2', cmd: 'm' };
|
||||
const msgPattern = { pattern: 'testMsg' };
|
||||
const firstMultipleMsgPattern = { pattern: 'testMultipleMsg1' };
|
||||
const secondMultipleMsgPattern = { pattern: 'testMultipleMsg2' };
|
||||
const clientMetadata = {};
|
||||
const clientSecMetadata = { transport: Transport.REDIS };
|
||||
const evtPattern = { role: 'testEvt' };
|
||||
const firstMultipleEvtPattern = { role: 'testMultipleEvt1' };
|
||||
const secondMultipleEvtPattern = { role: 'testMultipleEvt2' };
|
||||
|
||||
class Test {
|
||||
@Client(clientMetadata as any)
|
||||
@@ -25,11 +30,17 @@ describe('ListenerMetadataExplorer', () => {
|
||||
|
||||
constructor() {}
|
||||
|
||||
@MessagePattern(pattern)
|
||||
public test() {}
|
||||
@MessagePattern(msgPattern)
|
||||
public testMessage() {}
|
||||
|
||||
@MessagePattern(secPattern)
|
||||
public testSec() {}
|
||||
@MessagePattern([firstMultipleMsgPattern, secondMultipleMsgPattern])
|
||||
public testMultipleMessage() {}
|
||||
|
||||
@EventPattern(evtPattern)
|
||||
public testEvent() {}
|
||||
|
||||
@EventPattern([firstMultipleEvtPattern, secondMultipleEvtPattern])
|
||||
public testMultipleEvent() {}
|
||||
|
||||
public noPattern() {}
|
||||
}
|
||||
@@ -66,20 +77,77 @@ describe('ListenerMetadataExplorer', () => {
|
||||
);
|
||||
expect(metadata).to.eq(undefined);
|
||||
});
|
||||
it(`should return pattern properties when "handlerType" metadata is not undefined`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
Object.getPrototypeOf(test),
|
||||
'test',
|
||||
);
|
||||
expect(metadata).to.have.keys([
|
||||
'isEventHandler',
|
||||
'methodKey',
|
||||
'targetCallback',
|
||||
'pattern',
|
||||
'transport',
|
||||
'extras',
|
||||
]);
|
||||
expect(metadata.pattern).to.eql(pattern);
|
||||
|
||||
describe('@MessagePattern', () => {
|
||||
it(`should return pattern properties when "handlerType" metadata is not undefined`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
Object.getPrototypeOf(test),
|
||||
'testMessage',
|
||||
);
|
||||
expect(metadata).to.have.keys([
|
||||
'isEventHandler',
|
||||
'methodKey',
|
||||
'targetCallback',
|
||||
'patterns',
|
||||
'transport',
|
||||
'extras',
|
||||
]);
|
||||
expect(metadata.patterns.length).to.eql(1);
|
||||
expect(metadata.patterns[0]).to.eql(msgPattern);
|
||||
});
|
||||
it(`should return multiple patterns when more than one is declared`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
Object.getPrototypeOf(test),
|
||||
'testMultipleMessage',
|
||||
);
|
||||
expect(metadata).to.have.keys([
|
||||
'isEventHandler',
|
||||
'methodKey',
|
||||
'targetCallback',
|
||||
'patterns',
|
||||
'transport',
|
||||
'extras',
|
||||
]);
|
||||
expect(metadata.patterns.length).to.eql(2);
|
||||
expect(metadata.patterns[0]).to.eql(firstMultipleMsgPattern);
|
||||
expect(metadata.patterns[1]).to.eql(secondMultipleMsgPattern);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@EventPattern', () => {
|
||||
it(`should return pattern properties when "handlerType" metadata is not undefined`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
Object.getPrototypeOf(test),
|
||||
'testEvent',
|
||||
);
|
||||
expect(metadata).to.have.keys([
|
||||
'isEventHandler',
|
||||
'methodKey',
|
||||
'targetCallback',
|
||||
'patterns',
|
||||
'transport',
|
||||
'extras',
|
||||
]);
|
||||
expect(metadata.patterns.length).to.eql(1);
|
||||
expect(metadata.patterns[0]).to.eql(evtPattern);
|
||||
});
|
||||
it(`should return multiple patterns when more than one is declared`, () => {
|
||||
const metadata = instance.exploreMethodMetadata(
|
||||
Object.getPrototypeOf(test),
|
||||
'testMultipleEvent',
|
||||
);
|
||||
expect(metadata).to.have.keys([
|
||||
'isEventHandler',
|
||||
'methodKey',
|
||||
'targetCallback',
|
||||
'patterns',
|
||||
'transport',
|
||||
'extras',
|
||||
]);
|
||||
expect(metadata.patterns.length).to.eql(2);
|
||||
expect(metadata.patterns[0]).to.eql(firstMultipleEvtPattern);
|
||||
expect(metadata.patterns[1]).to.eql(secondMultipleEvtPattern);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('scanForClientHooks', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import { AssertionError, expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { NO_MESSAGE_HANDLER } from '../../constants';
|
||||
import { KafkaHeaders } from '../../enums';
|
||||
@@ -277,6 +277,7 @@ describe('ServerKafka', () => {
|
||||
|
||||
sinon.stub(server, 'getPublisher').callsFake(() => getPublisherSpy);
|
||||
});
|
||||
|
||||
it('should call "handleEvent" if correlation identifier is not present', async () => {
|
||||
const handleEventSpy = sinon.spy(server, 'handleEvent');
|
||||
await server.handleMessage(eventPayload);
|
||||
@@ -289,6 +290,42 @@ describe('ServerKafka', () => {
|
||||
expect(handleEventSpy.called).to.be.true;
|
||||
});
|
||||
|
||||
it('should call event handler when "handleEvent" is called', async () => {
|
||||
const messageHandler = sinon.mock();
|
||||
const context = { test: true } as any;
|
||||
const messageData = 'some data';
|
||||
sinon.stub(server, 'getHandlerByPattern').callsFake(() => messageHandler);
|
||||
|
||||
await server.handleEvent(
|
||||
topic,
|
||||
{ data: messageData, pattern: topic },
|
||||
context,
|
||||
);
|
||||
expect(messageHandler.calledWith(messageData, context)).to.be.true;
|
||||
});
|
||||
|
||||
it('should not catch error thrown by event handler as part of "handleEvent"', async () => {
|
||||
const error = new Error('handler error');
|
||||
const messageHandler = sinon.mock().throwsException(error);
|
||||
sinon.stub(server, 'getHandlerByPattern').callsFake(() => messageHandler);
|
||||
|
||||
try {
|
||||
await server.handleEvent(
|
||||
topic,
|
||||
{ data: 'some data', pattern: topic },
|
||||
{} as any,
|
||||
);
|
||||
|
||||
// code should not be executed
|
||||
expect(true).to.be.false;
|
||||
} catch (e) {
|
||||
if (e instanceof AssertionError) {
|
||||
throw e;
|
||||
}
|
||||
expect(e).to.be.eq(error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should call "handleEvent" if correlation identifier and reply topic are present but the handler is of type eventHandler', async () => {
|
||||
const handler = sinon.spy();
|
||||
(handler as any).isEventHandler = true;
|
||||
@@ -320,6 +357,7 @@ describe('ServerKafka', () => {
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
it(`should call handler with expected arguments`, async () => {
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
|
||||
@@ -15,13 +15,17 @@ describe('ServerRedis', () => {
|
||||
});
|
||||
describe('listen', () => {
|
||||
let onSpy: sinon.SinonSpy;
|
||||
let connectSpy: sinon.SinonSpy;
|
||||
let client: any;
|
||||
let callbackSpy: sinon.SinonSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
onSpy = sinon.spy();
|
||||
connectSpy = sinon.spy();
|
||||
|
||||
client = {
|
||||
on: onSpy,
|
||||
connect: connectSpy,
|
||||
};
|
||||
sinon.stub(server, 'createRedisClient').callsFake(() => client);
|
||||
|
||||
@@ -31,13 +35,9 @@ describe('ServerRedis', () => {
|
||||
server.listen(callbackSpy);
|
||||
expect(onSpy.getCall(0).args[0]).to.be.equal('error');
|
||||
});
|
||||
it('should bind "connect" event to handler', () => {
|
||||
it('should call "RedisClient#connect()"', () => {
|
||||
server.listen(callbackSpy);
|
||||
expect(onSpy.getCall(3).args[0]).to.be.equal('connect');
|
||||
});
|
||||
it('should bind "message" event to handler', () => {
|
||||
server.listen(callbackSpy);
|
||||
expect(onSpy.getCall(2).args[0]).to.be.equal('message');
|
||||
expect(connectSpy.called).to.be.true;
|
||||
});
|
||||
describe('when "start" throws an exception', () => {
|
||||
it('should call callback with a thrown error as an argument', () => {
|
||||
@@ -191,11 +191,11 @@ describe('ServerRedis', () => {
|
||||
});
|
||||
});
|
||||
describe('getClientOptions', () => {
|
||||
it('should return options object with "retry_strategy" and call "createRetryStrategy"', () => {
|
||||
it('should return options object with "retryStrategy" and call "createRetryStrategy"', () => {
|
||||
const createSpy = sinon.spy(server, 'createRetryStrategy');
|
||||
const { retry_strategy } = server.getClientOptions();
|
||||
const { retryStrategy } = server.getClientOptions();
|
||||
try {
|
||||
retry_strategy({} as any);
|
||||
retryStrategy(0);
|
||||
} catch {}
|
||||
expect(createSpy.called).to.be.true;
|
||||
});
|
||||
@@ -204,37 +204,24 @@ describe('ServerRedis', () => {
|
||||
describe('when is terminated', () => {
|
||||
it('should return undefined', () => {
|
||||
(server as any).isExplicitlyTerminated = true;
|
||||
const result = server.createRetryStrategy({} as any);
|
||||
const result = server.createRetryStrategy(0);
|
||||
expect(result).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('when "retryAttempts" does not exist', () => {
|
||||
it('should throw an exception', () => {
|
||||
it('should return undefined', () => {
|
||||
(server as any).options.options = {};
|
||||
(server as any).options.options.retryAttempts = undefined;
|
||||
|
||||
expect(() => server.createRetryStrategy({} as any)).to.throw(Error);
|
||||
expect(server.createRetryStrategy(4)).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('when "attempts" count is max', () => {
|
||||
it('should throw an exception', () => {
|
||||
it('should return undefined', () => {
|
||||
(server as any).options.options = {};
|
||||
(server as any).options.options.retryAttempts = 3;
|
||||
|
||||
expect(() =>
|
||||
server.createRetryStrategy({ attempt: 4 } as any),
|
||||
).to.throw(Error);
|
||||
});
|
||||
});
|
||||
describe('when ECONNREFUSED', () => {
|
||||
it('should call logger', () => {
|
||||
const loggerErrorSpy = sinon.spy((server as any).logger, 'error');
|
||||
try {
|
||||
server.createRetryStrategy({
|
||||
error: { code: 'ECONNREFUSED' },
|
||||
} as any);
|
||||
} catch {}
|
||||
expect(loggerErrorSpy.called).to.be.true;
|
||||
expect(server.createRetryStrategy(4)).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('otherwise', () => {
|
||||
@@ -243,7 +230,7 @@ describe('ServerRedis', () => {
|
||||
(server as any).isExplicitlyTerminated = false;
|
||||
(server as any).options.retryAttempts = 3;
|
||||
(server as any).options.retryDelay = 3;
|
||||
const result = server.createRetryStrategy({ attempt: 2 } as any);
|
||||
const result = server.createRetryStrategy(2);
|
||||
expect(result).to.be.eql((server as any).options.retryDelay);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -382,8 +382,8 @@ export class FastifyAdapter<
|
||||
|
||||
public useStaticAssets(options: FastifyStaticOptions) {
|
||||
return this.register(
|
||||
loadPackage('fastify-static', 'FastifyAdapter.useStaticAssets()', () =>
|
||||
require('fastify-static'),
|
||||
loadPackage('@fastify/static', 'FastifyAdapter.useStaticAssets()', () =>
|
||||
require('@fastify/static'),
|
||||
),
|
||||
options,
|
||||
);
|
||||
@@ -423,14 +423,14 @@ export class FastifyAdapter<
|
||||
}
|
||||
|
||||
public enableCors(options: CorsOptions | CorsOptionsDelegate<TRequest>) {
|
||||
this.register(import('fastify-cors'), options);
|
||||
this.register(import('@fastify/cors'), options);
|
||||
}
|
||||
|
||||
public registerParserMiddleware(prefix?: string, rawBody?: boolean) {
|
||||
if (this._isParserRegistered) {
|
||||
return;
|
||||
}
|
||||
this.register(import('fastify-formbody'));
|
||||
this.register(import('@fastify/formbody'));
|
||||
|
||||
if (rawBody) {
|
||||
this.registerContentParserWithRawBody();
|
||||
|
||||
@@ -2,15 +2,28 @@
|
||||
* "fastify-static" interfaces
|
||||
* @see https://github.com/fastify/fastify-static/blob/master/index.d.ts
|
||||
*/
|
||||
import { Stats } from 'fs';
|
||||
|
||||
interface ExtendedInformation {
|
||||
fileCount: number;
|
||||
totalFileCount: number;
|
||||
folderCount: number;
|
||||
totalFolderCount: number;
|
||||
totalSize: number;
|
||||
lastModified: number;
|
||||
}
|
||||
|
||||
interface ListDir {
|
||||
href: string;
|
||||
name: string;
|
||||
stats: Stats;
|
||||
extendedInfo?: ExtendedInformation;
|
||||
}
|
||||
|
||||
interface ListFile {
|
||||
href: string;
|
||||
name: string;
|
||||
stats: Stats;
|
||||
}
|
||||
|
||||
interface ListRender {
|
||||
@@ -21,6 +34,8 @@ interface ListOptions {
|
||||
format: 'json' | 'html';
|
||||
names: string[];
|
||||
render: ListRender;
|
||||
extendedFolderInfo?: boolean;
|
||||
jsonFormat?: 'names' | 'extended';
|
||||
}
|
||||
|
||||
// Passed on to `send`
|
||||
@@ -48,6 +63,11 @@ export interface FastifyStaticOptions extends SendOptions {
|
||||
wildcard?: boolean;
|
||||
list?: boolean | ListOptions;
|
||||
allowedPath?: (pathName: string, root?: string) => boolean;
|
||||
/**
|
||||
* @description
|
||||
* Opt-in to looking for pre-compressed files
|
||||
*/
|
||||
preCompressed?: boolean;
|
||||
|
||||
// Passed on to `send`
|
||||
acceptRanges?: boolean;
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface PointOfViewOptions {
|
||||
nunjucks?: any;
|
||||
pug?: any;
|
||||
handlebars?: any;
|
||||
marko?: any;
|
||||
mustache?: any;
|
||||
'art-template'?: any;
|
||||
twig?: any;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { FastifyStaticOptions, PointOfViewOptions } from './external';
|
||||
export interface NestFastifyApplication extends INestApplication {
|
||||
/**
|
||||
* A wrapper function around native `fastify.register()` method.
|
||||
* Example `app.register(require('fastify-formbody'))
|
||||
* Example `app.register(require('@fastify/formbody'))
|
||||
* @returns {Promise<FastifyInstance>}
|
||||
*/
|
||||
register<Options extends FastifyPluginOptions = any>(
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "3.29.0",
|
||||
"fastify-cors": "6.1.0",
|
||||
"fastify-formbody": "5.3.0",
|
||||
"@fastify/cors": "7.0.0",
|
||||
"@fastify/formbody": "6.0.0",
|
||||
"light-my-request": "4.10.1",
|
||||
"middie": "6.1.0",
|
||||
"path-to-regexp": "3.2.0",
|
||||
@@ -27,6 +27,16 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^8.0.0",
|
||||
"@nestjs/core": "^8.0.0"
|
||||
"@nestjs/core": "^8.0.0",
|
||||
"@fastify/static": "^5.0.0",
|
||||
"point-of-view": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@fastify/static": {
|
||||
"optional": true
|
||||
},
|
||||
"point-of-view": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ describe('WebSocketsController', () => {
|
||||
Promise.resolve(Promise.resolve(value)),
|
||||
),
|
||||
),
|
||||
).to.be.eq(100);
|
||||
).to.be.eq(value);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -356,18 +356,29 @@ describe('WebSocketsController', () => {
|
||||
await lastValueFrom(
|
||||
await instance.pickResult(Promise.resolve(of(value))),
|
||||
),
|
||||
).to.be.eq(100);
|
||||
).to.be.eq(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is a value', () => {
|
||||
describe('is an object that has the method `subscribe`', () => {
|
||||
it('should return Promise<Observable>', async () => {
|
||||
const value = { subscribe() {} };
|
||||
expect(
|
||||
await lastValueFrom(
|
||||
await instance.pickResult(Promise.resolve(value)),
|
||||
),
|
||||
).to.equal(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is an ordinary value', () => {
|
||||
it('should return Promise<Observable>', async () => {
|
||||
const value = 100;
|
||||
expect(
|
||||
await lastValueFrom(
|
||||
await instance.pickResult(Promise.resolve(value)),
|
||||
),
|
||||
).to.be.eq(100);
|
||||
).to.be.eq(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils';
|
||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||
import { from as fromPromise, Observable, of, Subject } from 'rxjs';
|
||||
import {
|
||||
from as fromPromise,
|
||||
Observable,
|
||||
isObservable,
|
||||
of,
|
||||
Subject,
|
||||
} from 'rxjs';
|
||||
import { distinctUntilChanged, mergeAll } from 'rxjs/operators';
|
||||
import { GATEWAY_OPTIONS, PORT_METADATA } from './constants';
|
||||
import { WsContextCreator } from './context/ws-context-creator';
|
||||
@@ -158,7 +163,7 @@ export class WebSocketsController {
|
||||
deferredResult: Promise<any>,
|
||||
): Promise<Observable<any>> {
|
||||
const result = await deferredResult;
|
||||
if (result && isFunction(result.subscribe)) {
|
||||
if (isObservable(result)) {
|
||||
return result;
|
||||
}
|
||||
if (result instanceof Promise) {
|
||||
|
||||
48
sample/17-mvc-fastify/package-lock.json
generated
48
sample/17-mvc-fastify/package-lock.json
generated
@@ -845,6 +845,30 @@
|
||||
"ajv": "^6.12.6"
|
||||
}
|
||||
},
|
||||
"@fastify/static": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-5.0.0.tgz",
|
||||
"integrity": "sha512-GGltJkO0idXa7yCZ0PfdTZ6qokWDX/vigCvmRpjOU2A3jc93c9p+oHDvHmwHK60hwWoBEGqHjGofVyn3H1CjZg==",
|
||||
"requires": {
|
||||
"content-disposition": "^0.5.3",
|
||||
"encoding-negotiator": "^2.0.1",
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"p-limit": "^3.1.0",
|
||||
"readable-stream": "^3.4.0",
|
||||
"send": "^0.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"requires": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
|
||||
@@ -4255,30 +4279,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz",
|
||||
"integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w=="
|
||||
},
|
||||
"fastify-static": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.5.0.tgz",
|
||||
"integrity": "sha512-Q7Tgl55AjsmBwiO4hKYib2BUCt+XTWLJ6Xp8YPPHU3EsrKNpevJ4cz8pjf1Ey1QhHw9O8Y2FDKdu+IC74oHvqw==",
|
||||
"requires": {
|
||||
"content-disposition": "^0.5.3",
|
||||
"encoding-negotiator": "^2.0.1",
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"p-limit": "^3.1.0",
|
||||
"readable-stream": "^3.4.0",
|
||||
"send": "^0.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"requires": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastify-warning": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@nestjs/common": "8.2.3",
|
||||
"@nestjs/core": "8.2.3",
|
||||
"@nestjs/platform-fastify": "8.2.3",
|
||||
"fastify-static": "4.5.0",
|
||||
"@fastify/static": "5.0.0",
|
||||
"handlebars": "4.7.7",
|
||||
"point-of-view": "4.15.3",
|
||||
"reflect-metadata": "0.1.13",
|
||||
|
||||
Reference in New Issue
Block a user