@yourrentals/cloudevent-receiver-nestjs 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -31
- package/index.cjs.js +12 -14
- package/index.esm.js +12 -9
- package/package.json +3 -3
- package/src/index.d.ts +0 -1
- package/src/lib/cloud-event-receiver.constants.d.ts +1 -1
- package/src/lib/cloud-event-receiver.controller.d.ts +3 -2
- package/src/lib/cloud-event-receiver.decorators.d.ts +1 -1
- package/src/lib/cloud-event-receiver.types.d.ts +11 -1
package/README.md
CHANGED
|
@@ -10,26 +10,57 @@ yarn add @yourrentals/cloudevent-receiver-nestjs @golevelup/nestjs-discovery
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
+
This plugin requires [NestJS Raw Body parsing](https://docs.nestjs.com/faq/raw-body) for signature verification, and
|
|
14
|
+
CloudEvent Body Parser for CloudEvent parsing (in structured mode)
|
|
15
|
+
|
|
13
16
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
17
|
+
import { NestFactory } from '@nestjs/core';
|
|
18
|
+
import type { NestExpressApplication } from '@nestjs/platform-express';
|
|
19
|
+
import { AppModule } from './app.module';
|
|
20
|
+
|
|
21
|
+
// in the "bootstrap" function
|
|
22
|
+
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
|
23
|
+
rawBody: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Support CloudEvent - Structured mode parsing
|
|
27
|
+
app.useBodyParser('json', {
|
|
28
|
+
type: 'application/cloudevents+json',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await app.listen(3000);
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
CloudEventReceiverModule,
|
|
38
|
+
EventHandler,
|
|
39
|
+
WebhookSignatureV1HmacPlugin,
|
|
40
|
+
CloudEvent
|
|
41
|
+
} from '@yourrentals/cloudevent-receiver-nestjs'
|
|
16
42
|
|
|
17
43
|
@Injectable()
|
|
18
44
|
class MessagebusHandlerService {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
45
|
+
@EventHandler('my-queue')
|
|
46
|
+
async handleMyQueue(event: CloudEvent) {
|
|
47
|
+
// ...
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@EventHandler('my-other-queue', false /* event is not CloudEvent */)
|
|
51
|
+
async handleSqsQueue(event: Message) {
|
|
52
|
+
// ...
|
|
53
|
+
}
|
|
23
54
|
}
|
|
24
55
|
|
|
25
56
|
@Module({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
57
|
+
imports: [
|
|
58
|
+
CloudEventReceiverModule.forRoot({
|
|
59
|
+
pathPrefix: '/message-bus',
|
|
60
|
+
verifierPlugins: [new WebhookSignatureV1HmacPlugin('secret')],
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
providers: [MessagebusHandlerService],
|
|
33
64
|
})
|
|
34
65
|
|
|
35
66
|
```
|
|
@@ -38,32 +69,84 @@ class MessagebusHandlerService {
|
|
|
38
69
|
import Axios from 'axios';
|
|
39
70
|
|
|
40
71
|
const main = async () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
// Queue name defined in the receiver are automatically registered
|
|
73
|
+
await Axios.post('http://localhost:3000/message-bus/my-queue', {
|
|
74
|
+
headers: {
|
|
75
|
+
'ce-specversion': '1.0',
|
|
76
|
+
'ce-type': 'my-event',
|
|
77
|
+
'ce-source': 'my-source',
|
|
78
|
+
'ce-id': 'my-id',
|
|
79
|
+
'ce-time': '2020-01-01T00:00:00Z',
|
|
80
|
+
'ce-datacontenttype': 'application/json',
|
|
81
|
+
'ce-dataschema': 'http://myschema.com',
|
|
82
|
+
'ce-subject': 'my-subject',
|
|
83
|
+
'ce-signature': 'my-signature',
|
|
84
|
+
},
|
|
85
|
+
body: {
|
|
86
|
+
foo: 'bar',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
58
89
|
};
|
|
59
90
|
```
|
|
60
91
|
|
|
92
|
+
## Authentication
|
|
93
|
+
|
|
94
|
+
NestJS usually have global authentication that will impede operations of this module, as the routes are considered public route. To disable authentication for the routes, you can use the `routeDecorator` module option:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { Module, SetMetadata } from '@nestjs/common';
|
|
98
|
+
import { AppController } from './app.controller';
|
|
99
|
+
import { AppService } from './app.service';
|
|
100
|
+
import {
|
|
101
|
+
CloudEventReceiverModule,
|
|
102
|
+
WebhookSignatureV1HmacPlugin,
|
|
103
|
+
} from '@yourrentals/cloudevent-receiver-nestjs';
|
|
104
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
105
|
+
import { AuthGuardGuard } from './auth-guard/auth-guard.guard';
|
|
106
|
+
|
|
107
|
+
@Module({
|
|
108
|
+
imports: [
|
|
109
|
+
CloudEventReceiverModule.forRoot({
|
|
110
|
+
// Set the route decorator so that your authentication guard will treat the route as public
|
|
111
|
+
routeDecorator: SetMetadata('isPublic', true),
|
|
112
|
+
verifierPlugins: [new WebhookSignatureV1HmacPlugin('secret2')],
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
controllers: [AppController],
|
|
116
|
+
providers: [AppService, { provide: APP_GUARD, useClass: AuthGuardGuard }],
|
|
117
|
+
})
|
|
118
|
+
export class AppModule {}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Raw Mode
|
|
122
|
+
|
|
123
|
+
The library can receive messages from any sources so long that it implements the Webhook Signature.
|
|
124
|
+
To disable parsing as cloudevent, set the `isCloudEvent` option in decorator to `false`.
|
|
125
|
+
|
|
126
|
+
By default, the event type is assumed to be a SQS Message
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { Injectable } from '@nestjs/common';
|
|
130
|
+
import {
|
|
131
|
+
EventHandler,
|
|
132
|
+
SqsMessage,
|
|
133
|
+
} from '@yourrentals/cloudevent-receiver-nestjs';
|
|
134
|
+
|
|
135
|
+
@Injectable()
|
|
136
|
+
export class AppService {
|
|
137
|
+
@EventHandler('test', /* isCloudEvent */ false)
|
|
138
|
+
async handle(event: SqsMessage) {
|
|
139
|
+
console.log(event);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
61
144
|
## Errors
|
|
62
145
|
|
|
63
146
|
The library defines the following errors:
|
|
64
147
|
|
|
65
148
|
| Error | Description | Expected HTTP status code | Retryable |
|
|
66
|
-
|
|
149
|
+
|------------------------|-----------------------------------------|---------------------------|-----------|
|
|
67
150
|
| InvalidSignatureError | The signature of the message is invalid | 401 | No |
|
|
68
151
|
| UnparsableMessageError | The message is not a valid CloudEvent | 400 | No |
|
|
69
152
|
| NotFoundError | The queue name is not registered | 404 | Yes |
|
package/index.cjs.js
CHANGED
|
@@ -52,7 +52,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
52
52
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const EVENT_HANDLER = Symbol.for('EVENT_HANDLER');
|
|
56
56
|
const CLOUD_EVENT_RECEIVER_OPTIONS = Symbol.for('CLOUD_EVENT_RECEIVER_OPTIONS');
|
|
57
57
|
|
|
58
58
|
let CloudEventReceiverService = class CloudEventReceiverService {
|
|
@@ -62,13 +62,14 @@ let CloudEventReceiverService = class CloudEventReceiverService {
|
|
|
62
62
|
}
|
|
63
63
|
onModuleInit() {
|
|
64
64
|
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
-
const providers = yield this.discovery.providerMethodsWithMetaAtKey(
|
|
65
|
+
const providers = yield this.discovery.providerMethodsWithMetaAtKey(EVENT_HANDLER);
|
|
66
66
|
this.receiver = new cloudeventReceiverCore.CloudEventReceiver({
|
|
67
67
|
queues: providers.map((provider) => ({
|
|
68
68
|
name: provider.meta.name,
|
|
69
|
+
isCloudEvent: provider.meta.isCloudEvent,
|
|
69
70
|
handler: provider.discoveredMethod.handler.bind(provider.discoveredMethod.parentClass.instance),
|
|
70
71
|
})),
|
|
71
|
-
|
|
72
|
+
verifierPlugins: this.options.verifierPlugins || [],
|
|
72
73
|
});
|
|
73
74
|
});
|
|
74
75
|
}
|
|
@@ -84,7 +85,7 @@ CloudEventReceiverService = __decorate([
|
|
|
84
85
|
__metadata("design:paramtypes", [Object, nestjsDiscovery.DiscoveryService])
|
|
85
86
|
], CloudEventReceiverService);
|
|
86
87
|
|
|
87
|
-
const createController = (pathPrefix = '/message-bus') => {
|
|
88
|
+
const createController = (pathPrefix = '/message-bus', routeDecorator = (target, propertyKey, descriptor) => descriptor) => {
|
|
88
89
|
let CloudEventReceiverController = class CloudEventReceiverController {
|
|
89
90
|
constructor(service) {
|
|
90
91
|
this.service = service;
|
|
@@ -93,7 +94,7 @@ const createController = (pathPrefix = '/message-bus') => {
|
|
|
93
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
94
95
|
try {
|
|
95
96
|
const queueName = request.params.queueName;
|
|
96
|
-
yield this.service.handle(queueName, request.headers, request.
|
|
97
|
+
yield this.service.handle(queueName, request.headers, request.rawBody);
|
|
97
98
|
}
|
|
98
99
|
catch (e) {
|
|
99
100
|
if (utilReceiverError.isReceiverError(e)) {
|
|
@@ -105,6 +106,7 @@ const createController = (pathPrefix = '/message-bus') => {
|
|
|
105
106
|
}
|
|
106
107
|
};
|
|
107
108
|
__decorate([
|
|
109
|
+
routeDecorator,
|
|
108
110
|
common.Post('/:queueName'),
|
|
109
111
|
common.HttpCode(200),
|
|
110
112
|
__param(0, common.Req()),
|
|
@@ -138,7 +140,9 @@ exports.CloudEventReceiverModule = CloudEventReceiverModule_1 = class CloudEvent
|
|
|
138
140
|
inject: [CLOUD_EVENT_RECEIVER_OPTIONS, nestjsDiscovery.DiscoveryService],
|
|
139
141
|
},
|
|
140
142
|
],
|
|
141
|
-
controllers: [
|
|
143
|
+
controllers: [
|
|
144
|
+
createController(options.pathPrefix, options.routeDecorator),
|
|
145
|
+
],
|
|
142
146
|
};
|
|
143
147
|
}
|
|
144
148
|
};
|
|
@@ -146,15 +150,9 @@ exports.CloudEventReceiverModule = CloudEventReceiverModule_1 = __decorate([
|
|
|
146
150
|
common.Module({})
|
|
147
151
|
], exports.CloudEventReceiverModule);
|
|
148
152
|
|
|
149
|
-
const
|
|
153
|
+
const EventHandler = (queueName, isCloudEvent = true) => common.SetMetadata(EVENT_HANDLER, { name: queueName, isCloudEvent });
|
|
150
154
|
|
|
151
|
-
exports.
|
|
152
|
-
Object.keys(utilReceiverError).forEach(function (k) {
|
|
153
|
-
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
|
|
154
|
-
enumerable: true,
|
|
155
|
-
get: function () { return utilReceiverError[k]; }
|
|
156
|
-
});
|
|
157
|
-
});
|
|
155
|
+
exports.EventHandler = EventHandler;
|
|
158
156
|
Object.keys(cloudeventReceiverCore).forEach(function (k) {
|
|
159
157
|
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
|
|
160
158
|
enumerable: true,
|
package/index.esm.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Injectable, Inject, Post, HttpCode, Req, Controller, HttpException, InternalServerErrorException, Module, SetMetadata } from '@nestjs/common';
|
|
2
2
|
import { isReceiverError } from '@yourrentals/util-receiver-error';
|
|
3
|
-
export * from '@yourrentals/util-receiver-error';
|
|
4
3
|
import { DiscoveryService, DiscoveryModule } from '@golevelup/nestjs-discovery';
|
|
5
4
|
import { CloudEventReceiver } from '@yourrentals/cloudevent-receiver-core';
|
|
6
5
|
export * from '@yourrentals/cloudevent-receiver-core';
|
|
@@ -50,7 +49,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
50
49
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
51
50
|
};
|
|
52
51
|
|
|
53
|
-
const
|
|
52
|
+
const EVENT_HANDLER = Symbol.for('EVENT_HANDLER');
|
|
54
53
|
const CLOUD_EVENT_RECEIVER_OPTIONS = Symbol.for('CLOUD_EVENT_RECEIVER_OPTIONS');
|
|
55
54
|
|
|
56
55
|
let CloudEventReceiverService = class CloudEventReceiverService {
|
|
@@ -60,13 +59,14 @@ let CloudEventReceiverService = class CloudEventReceiverService {
|
|
|
60
59
|
}
|
|
61
60
|
onModuleInit() {
|
|
62
61
|
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
-
const providers = yield this.discovery.providerMethodsWithMetaAtKey(
|
|
62
|
+
const providers = yield this.discovery.providerMethodsWithMetaAtKey(EVENT_HANDLER);
|
|
64
63
|
this.receiver = new CloudEventReceiver({
|
|
65
64
|
queues: providers.map((provider) => ({
|
|
66
65
|
name: provider.meta.name,
|
|
66
|
+
isCloudEvent: provider.meta.isCloudEvent,
|
|
67
67
|
handler: provider.discoveredMethod.handler.bind(provider.discoveredMethod.parentClass.instance),
|
|
68
68
|
})),
|
|
69
|
-
|
|
69
|
+
verifierPlugins: this.options.verifierPlugins || [],
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
}
|
|
@@ -82,7 +82,7 @@ CloudEventReceiverService = __decorate([
|
|
|
82
82
|
__metadata("design:paramtypes", [Object, DiscoveryService])
|
|
83
83
|
], CloudEventReceiverService);
|
|
84
84
|
|
|
85
|
-
const createController = (pathPrefix = '/message-bus') => {
|
|
85
|
+
const createController = (pathPrefix = '/message-bus', routeDecorator = (target, propertyKey, descriptor) => descriptor) => {
|
|
86
86
|
let CloudEventReceiverController = class CloudEventReceiverController {
|
|
87
87
|
constructor(service) {
|
|
88
88
|
this.service = service;
|
|
@@ -91,7 +91,7 @@ const createController = (pathPrefix = '/message-bus') => {
|
|
|
91
91
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
92
|
try {
|
|
93
93
|
const queueName = request.params.queueName;
|
|
94
|
-
yield this.service.handle(queueName, request.headers, request.
|
|
94
|
+
yield this.service.handle(queueName, request.headers, request.rawBody);
|
|
95
95
|
}
|
|
96
96
|
catch (e) {
|
|
97
97
|
if (isReceiverError(e)) {
|
|
@@ -103,6 +103,7 @@ const createController = (pathPrefix = '/message-bus') => {
|
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
__decorate([
|
|
106
|
+
routeDecorator,
|
|
106
107
|
Post('/:queueName'),
|
|
107
108
|
HttpCode(200),
|
|
108
109
|
__param(0, Req()),
|
|
@@ -136,7 +137,9 @@ let CloudEventReceiverModule = CloudEventReceiverModule_1 = class CloudEventRece
|
|
|
136
137
|
inject: [CLOUD_EVENT_RECEIVER_OPTIONS, DiscoveryService],
|
|
137
138
|
},
|
|
138
139
|
],
|
|
139
|
-
controllers: [
|
|
140
|
+
controllers: [
|
|
141
|
+
createController(options.pathPrefix, options.routeDecorator),
|
|
142
|
+
],
|
|
140
143
|
};
|
|
141
144
|
}
|
|
142
145
|
};
|
|
@@ -144,6 +147,6 @@ CloudEventReceiverModule = CloudEventReceiverModule_1 = __decorate([
|
|
|
144
147
|
Module({})
|
|
145
148
|
], CloudEventReceiverModule);
|
|
146
149
|
|
|
147
|
-
const
|
|
150
|
+
const EventHandler = (queueName, isCloudEvent = true) => SetMetadata(EVENT_HANDLER, { name: queueName, isCloudEvent });
|
|
148
151
|
|
|
149
|
-
export {
|
|
152
|
+
export { CloudEventReceiverModule, EventHandler };
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yourrentals/cloudevent-receiver-nestjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"@yourrentals/cloudevent-receiver-core": "0.
|
|
6
|
-
"@yourrentals/util-receiver-error": "0.
|
|
5
|
+
"@yourrentals/cloudevent-receiver-core": "0.5.0",
|
|
6
|
+
"@yourrentals/util-receiver-error": "0.5.0"
|
|
7
7
|
},
|
|
8
8
|
"peerDependencies": {
|
|
9
9
|
"@nestjs/common": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
|
package/src/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const EVENT_HANDLER: unique symbol;
|
|
2
2
|
export declare const CLOUD_EVENT_RECEIVER_OPTIONS: unique symbol;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { RawBodyRequest } from '@nestjs/common';
|
|
1
2
|
import { CloudEventReceiverService } from './cloud-event-receiver.service';
|
|
2
|
-
export declare const createController: (pathPrefix?: string) => {
|
|
3
|
+
export declare const createController: (pathPrefix?: string, routeDecorator?: MethodDecorator) => {
|
|
3
4
|
new (service: CloudEventReceiverService): {
|
|
4
5
|
readonly service: CloudEventReceiverService;
|
|
5
|
-
receiveMessage(request: any): Promise<void>;
|
|
6
|
+
receiveMessage(request: RawBodyRequest<any>): Promise<void>;
|
|
6
7
|
};
|
|
7
8
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const EventHandler: (queueName: string, isCloudEvent?: boolean) => MethodDecorator;
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { CloudEventReceiverOptions } from '@yourrentals/cloudevent-receiver-core';
|
|
2
2
|
export type CloudEventReceiverModuleOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Path prefix. Used to prefix the route path
|
|
5
|
+
*/
|
|
3
6
|
pathPrefix?: string;
|
|
4
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Verifier plugins. Used to verify the authenticity of the incoming CloudEvent
|
|
9
|
+
*/
|
|
10
|
+
verifierPlugins?: CloudEventReceiverOptions['verifierPlugins'];
|
|
11
|
+
/**
|
|
12
|
+
* Route Decorator. Used to annotate the route controller method, usually for bypassing authentication
|
|
13
|
+
*/
|
|
14
|
+
routeDecorator?: MethodDecorator;
|
|
5
15
|
};
|