@lafken/event 0.10.1
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/LICENCE +21 -0
- package/README.md +288 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +18 -0
- package/lib/main/event/event.d.ts +19 -0
- package/lib/main/event/event.js +26 -0
- package/lib/main/index.d.ts +2 -0
- package/lib/main/index.js +18 -0
- package/lib/main/rule/index.d.ts +2 -0
- package/lib/main/rule/index.js +18 -0
- package/lib/main/rule/rule.d.ts +50 -0
- package/lib/main/rule/rule.js +63 -0
- package/lib/main/rule/rule.types.d.ts +229 -0
- package/lib/main/rule/rule.types.js +2 -0
- package/lib/resolver/index.d.ts +1 -0
- package/lib/resolver/index.js +17 -0
- package/lib/resolver/resolver.d.ts +12 -0
- package/lib/resolver/resolver.js +84 -0
- package/lib/resolver/resolver.types.d.ts +87 -0
- package/lib/resolver/resolver.types.js +2 -0
- package/lib/resolver/rule/rule.d.ts +19 -0
- package/lib/resolver/rule/rule.js +126 -0
- package/lib/resolver/rule/rule.types.d.ts +9 -0
- package/lib/resolver/rule/rule.types.js +2 -0
- package/package.json +86 -0
package/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aníbal Emilio Jorquera Cornejo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# @lafken/event
|
|
2
|
+
|
|
3
|
+
Listen and react to Amazon EventBridge events using TypeScript decorators. `@lafken/event` lets you define event rules with pattern-based filtering and automatically connects them to Lambda functions. Supports custom events, S3 notifications, and DynamoDB stream events.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lafken/event
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
Define an event rule class with `@EventRule`, add `@Rule` methods with event patterns, and register everything through `EventRuleResolver`:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createApp, createModule } from '@lafken/main';
|
|
17
|
+
import { EventRuleResolver } from '@lafken/event/resolver';
|
|
18
|
+
import { EventRule, Rule, Event } from '@lafken/event/main';
|
|
19
|
+
|
|
20
|
+
// 1. Define event handlers
|
|
21
|
+
@EventRule()
|
|
22
|
+
export class OrderEvents {
|
|
23
|
+
@Rule({
|
|
24
|
+
pattern: {
|
|
25
|
+
source: 'orders',
|
|
26
|
+
detailType: ['order.created'],
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
onOrderCreated(@Event() event: any) {
|
|
30
|
+
console.log('New order:', event);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. Register in a module
|
|
35
|
+
const orderModule = createModule({
|
|
36
|
+
name: 'order',
|
|
37
|
+
resources: [OrderEvents],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 3. Add the resolver to the app
|
|
41
|
+
createApp({
|
|
42
|
+
name: 'my-app',
|
|
43
|
+
resolvers: [new EventRuleResolver({ busName: 'my-event-bus' })],
|
|
44
|
+
modules: [orderModule],
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Each `@Rule` method becomes an independent Lambda function triggered by matching EventBridge events. The event payload is automatically passed through `$.detail`.
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
### Event Rule Class
|
|
53
|
+
|
|
54
|
+
Use the `@EventRule` decorator to group related event handlers in a single class:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { EventRule, Rule, Event } from '@lafken/event/main';
|
|
58
|
+
|
|
59
|
+
@EventRule()
|
|
60
|
+
export class InventoryEvents {
|
|
61
|
+
@Rule({
|
|
62
|
+
pattern: {
|
|
63
|
+
source: 'inventory',
|
|
64
|
+
detailType: ['stock.updated'],
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
onStockUpdate(@Event() event: any) { }
|
|
68
|
+
|
|
69
|
+
@Rule({
|
|
70
|
+
pattern: {
|
|
71
|
+
source: 'inventory',
|
|
72
|
+
detailType: ['stock.depleted'],
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
onStockDepleted(@Event() event: any) { }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Custom Events
|
|
80
|
+
|
|
81
|
+
Define rules that match custom events by specifying `source`, `detailType`, and `detail` filters:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
@Rule({
|
|
85
|
+
pattern: {
|
|
86
|
+
source: 'payments',
|
|
87
|
+
detailType: ['payment.completed', 'payment.refunded'],
|
|
88
|
+
detail: {
|
|
89
|
+
currency: ['USD', 'EUR'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
onPaymentEvent(@Event() event: any) {
|
|
94
|
+
// Triggered only for USD or EUR payments
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Pattern Fields
|
|
99
|
+
|
|
100
|
+
| Field | Type | Description |
|
|
101
|
+
| ------------ | ---------- | ---------------------------------------------------- |
|
|
102
|
+
| `source` | `string` | Event source identifier (required) |
|
|
103
|
+
| `detailType` | `string[]` | Event type names to match |
|
|
104
|
+
| `detail` | `object` | Attribute-level filtering on the event payload |
|
|
105
|
+
|
|
106
|
+
### S3 Integration
|
|
107
|
+
|
|
108
|
+
Listen for S3 bucket notifications delivered through EventBridge. Set `integration: 's3'` and define a pattern matching the S3 event structure:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
@Rule({
|
|
112
|
+
integration: 's3',
|
|
113
|
+
pattern: {
|
|
114
|
+
detailType: ['Object Created'],
|
|
115
|
+
detail: {
|
|
116
|
+
bucket: {
|
|
117
|
+
name: ['uploads-bucket'],
|
|
118
|
+
},
|
|
119
|
+
object: {
|
|
120
|
+
key: [{ prefix: 'images/' }],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
onImageUploaded(@Event() event: any) {
|
|
126
|
+
// Triggered when a new object is created under images/
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### S3 Pattern Options
|
|
131
|
+
|
|
132
|
+
| Field | Type | Description |
|
|
133
|
+
| --------------------- | ---------------------------------- | -------------------------------------------- |
|
|
134
|
+
| `detailType` | `S3DetailType[]` | `'Object Created'` or `'Object Deleted'` |
|
|
135
|
+
| `detail.bucket.name` | `string[]` | Bucket names to match |
|
|
136
|
+
| `detail.object.key` | `(string \| S3ObjectKey)[]` | Object keys or prefix/suffix patterns |
|
|
137
|
+
|
|
138
|
+
Object key filters support `prefix` and `suffix` matching:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
detail: {
|
|
142
|
+
object: {
|
|
143
|
+
key: [
|
|
144
|
+
{ prefix: 'uploads/' },
|
|
145
|
+
{ suffix: '.pdf' },
|
|
146
|
+
'exact-filename.txt',
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### DynamoDB Integration
|
|
153
|
+
|
|
154
|
+
Consume events from DynamoDB Streams routed through EventBridge. The target table must have streams enabled (see `@lafken/dynamo` stream configuration). Set `integration: 'dynamodb'` and use `source` to specify the table name:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
@Rule({
|
|
158
|
+
integration: 'dynamodb',
|
|
159
|
+
pattern: {
|
|
160
|
+
source: 'customers',
|
|
161
|
+
detail: {
|
|
162
|
+
eventName: ['INSERT', 'MODIFY'],
|
|
163
|
+
newImage: {
|
|
164
|
+
status: ['active'],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
onCustomerChange(@Event() event: any) {
|
|
170
|
+
// Triggered on INSERT or MODIFY where status is 'active'
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### DynamoDB Pattern Options
|
|
175
|
+
|
|
176
|
+
| Field | Type | Description |
|
|
177
|
+
| ------------------ | --------------------------------------- | --------------------------------------------- |
|
|
178
|
+
| `source` | `string` | DynamoDB table name |
|
|
179
|
+
| `detail.eventName` | `('INSERT' \| 'MODIFY' \| 'REMOVE')[]` | Stream event types to match |
|
|
180
|
+
| `detail.keys` | `DynamoAttributeFilters` | Filter by primary key values |
|
|
181
|
+
| `detail.newImage` | `DynamoAttributeFilters` | Filter by new item attributes (after change) |
|
|
182
|
+
| `detail.oldImage` | `DynamoAttributeFilters` | Filter by old item attributes (before change) |
|
|
183
|
+
|
|
184
|
+
Attribute filters support EventBridge content-based filtering patterns:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
detail: {
|
|
188
|
+
keys: {
|
|
189
|
+
email: ['user@example.com'],
|
|
190
|
+
age: [{ numeric: ['>', 18] }],
|
|
191
|
+
},
|
|
192
|
+
newImage: {
|
|
193
|
+
name: [{ prefix: 'A' }],
|
|
194
|
+
role: [{ 'anything-but': 'admin' }],
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Available Filter Patterns
|
|
200
|
+
|
|
201
|
+
| Pattern | Example | Description |
|
|
202
|
+
| --------------------- | ----------------------------------------- | ---------------------------------- |
|
|
203
|
+
| Exact match | `'value'` | Matches exact string or number |
|
|
204
|
+
| `prefix` | `{ prefix: 'usr_' }` | Starts with |
|
|
205
|
+
| `suffix` | `{ suffix: '.com' }` | Ends with |
|
|
206
|
+
| `anything-but` | `{ 'anything-but': 'admin' }` | Matches everything except |
|
|
207
|
+
| `numeric` | `{ numeric: ['>', 100] }` | Numeric comparison |
|
|
208
|
+
| `numeric` (range) | `{ numeric: ['>=', 0, '<', 100] }` | Numeric range |
|
|
209
|
+
| `exists` | `{ exists: true }` | Field exists or does not exist |
|
|
210
|
+
| `equals-ignore-case` | `{ 'equals-ignore-case': 'active' }` | Case-insensitive string match |
|
|
211
|
+
|
|
212
|
+
### Receiving Events
|
|
213
|
+
|
|
214
|
+
Use the `@Event` parameter decorator to inject the EventBridge event payload into a handler method. The payload is automatically extracted from `$.detail`:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
@Rule({
|
|
218
|
+
pattern: {
|
|
219
|
+
source: 'notifications',
|
|
220
|
+
detailType: ['notification.sent'],
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
onNotification(@Event() event: any) {
|
|
224
|
+
// event contains the detail object, not the full EventBridge envelope
|
|
225
|
+
console.log(event.recipientId, event.channel);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Event Buses
|
|
230
|
+
|
|
231
|
+
Configure one or more custom event buses when initializing `EventRuleResolver`. Each `@Rule` can target a specific bus via the `bus` option. If omitted, the default EventBridge bus is used:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { EventRuleResolver } from '@lafken/event/resolver';
|
|
235
|
+
|
|
236
|
+
createApp({
|
|
237
|
+
name: 'my-app',
|
|
238
|
+
resolvers: [
|
|
239
|
+
new EventRuleResolver(
|
|
240
|
+
{
|
|
241
|
+
busName: 'orders-bus',
|
|
242
|
+
extend: ({ eventBus, scope }) => {
|
|
243
|
+
// Apply additional CDKTN configuration
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
busName: 'notifications-bus',
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Reference a specific bus from a rule:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
@Rule({
|
|
258
|
+
bus: 'orders-bus',
|
|
259
|
+
pattern: {
|
|
260
|
+
source: 'checkout',
|
|
261
|
+
detailType: ['checkout.completed'],
|
|
262
|
+
},
|
|
263
|
+
})
|
|
264
|
+
onCheckout(@Event() event: any) { }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Retry Policy
|
|
268
|
+
|
|
269
|
+
Configure how EventBridge handles failed target invocations using `retryAttempts` and `maxEventAge`:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
@Rule({
|
|
273
|
+
retryAttempts: 3,
|
|
274
|
+
maxEventAge: 7200,
|
|
275
|
+
pattern: {
|
|
276
|
+
source: 'billing',
|
|
277
|
+
detailType: ['invoice.generated'],
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
onInvoice(@Event() event: any) {
|
|
281
|
+
// Retries up to 3 times, discards events older than 2 hours
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
| Option | Type | Description |
|
|
286
|
+
| --------------- | -------- | ---------------------------------------------------------- |
|
|
287
|
+
| `retryAttempts` | `number` | Maximum retry attempts if the target invocation fails |
|
|
288
|
+
| `maxEventAge` | `number` | Maximum event age in seconds before the event is discarded |
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./main"), exports);
|
|
18
|
+
__exportStar(require("./resolver"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameter decorator that injects the raw EventBridge event into a
|
|
3
|
+
* handler method argument.
|
|
4
|
+
*
|
|
5
|
+
* Use it on a parameter of an `@EventHandler` method so the framework
|
|
6
|
+
* passes the incoming EventBridge event payload at runtime.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* @EventRule({})
|
|
11
|
+
* export class OrderEvents {
|
|
12
|
+
* @EventHandler({ source: 'orders', detailType: 'order.created' })
|
|
13
|
+
* onOrderCreated(@Event() event: any) {
|
|
14
|
+
* // event contains the full EventBridge event payload
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare const Event: () => (target: any, methodName: string, _number: number) => void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Event = void 0;
|
|
4
|
+
const common_1 = require("@lafken/common");
|
|
5
|
+
/**
|
|
6
|
+
* Parameter decorator that injects the raw EventBridge event into a
|
|
7
|
+
* handler method argument.
|
|
8
|
+
*
|
|
9
|
+
* Use it on a parameter of an `@EventHandler` method so the framework
|
|
10
|
+
* passes the incoming EventBridge event payload at runtime.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* @EventRule({})
|
|
15
|
+
* export class OrderEvents {
|
|
16
|
+
* @EventHandler({ source: 'orders', detailType: 'order.created' })
|
|
17
|
+
* onOrderCreated(@Event() event: any) {
|
|
18
|
+
* // event contains the full EventBridge event payload
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
const Event = () => (target, methodName, _number) => {
|
|
24
|
+
(0, common_1.reflectArgumentMethod)(target, methodName, common_1.LambdaArgumentTypes.event);
|
|
25
|
+
};
|
|
26
|
+
exports.Event = Event;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./event/event"), exports);
|
|
18
|
+
__exportStar(require("./rule"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./rule"), exports);
|
|
18
|
+
__exportStar(require("./rule.types"), exports);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { EventRuleProps } from './rule.types';
|
|
2
|
+
export declare const RESOURCE_TYPE: "EVENT";
|
|
3
|
+
/**
|
|
4
|
+
* Class decorator that registers a class as an EventBridge rule resource.
|
|
5
|
+
*
|
|
6
|
+
* The decorated class groups one or more `@Rule` handler methods that
|
|
7
|
+
* react to events published on an EventBridge bus. Each handler inside
|
|
8
|
+
* the class defines its own event pattern and integration.
|
|
9
|
+
*
|
|
10
|
+
* @param props - Optional resource configuration (e.g. a custom `name`).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* @EventRule()
|
|
15
|
+
* export class OrderEvents {
|
|
16
|
+
* @Rule({ pattern: { source: 'orders', detailType: ['order.created'] } })
|
|
17
|
+
* onCreated(@Event() event) { }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const EventRule: (props?: import("@lafken/common").ResourceProps | undefined) => (constructor: Function) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Method decorator that registers a handler for a specific EventBridge
|
|
24
|
+
* rule pattern.
|
|
25
|
+
*
|
|
26
|
+
* The decorated method becomes a Lambda function that is invoked when
|
|
27
|
+
* an event matching the configured pattern is published on the
|
|
28
|
+
* EventBridge bus. Supports default custom events, S3 events, and
|
|
29
|
+
* DynamoDB stream events through the `integration` option.
|
|
30
|
+
*
|
|
31
|
+
* @param props - Rule configuration including the event pattern, optional
|
|
32
|
+
* integration source, retry attempts, max event age, and bus name.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // Custom event
|
|
37
|
+
* @Rule({
|
|
38
|
+
* pattern: { source: 'payments', detailType: ['payment.completed'] },
|
|
39
|
+
* })
|
|
40
|
+
* onPayment(@Event() event) { }
|
|
41
|
+
*
|
|
42
|
+
* // S3 integration
|
|
43
|
+
* @Rule({
|
|
44
|
+
* integration: 's3',
|
|
45
|
+
* pattern: { detailType: ['Object Created'], detail: { bucket: { name: ['uploads'] } } },
|
|
46
|
+
* })
|
|
47
|
+
* onUpload(@Event() event) { }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare const Rule: (props: EventRuleProps) => (target: any, methodName: string, descriptor: PropertyDescriptor) => any;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Rule = exports.EventRule = exports.RESOURCE_TYPE = void 0;
|
|
4
|
+
const common_1 = require("@lafken/common");
|
|
5
|
+
exports.RESOURCE_TYPE = 'EVENT';
|
|
6
|
+
/**
|
|
7
|
+
* Class decorator that registers a class as an EventBridge rule resource.
|
|
8
|
+
*
|
|
9
|
+
* The decorated class groups one or more `@Rule` handler methods that
|
|
10
|
+
* react to events published on an EventBridge bus. Each handler inside
|
|
11
|
+
* the class defines its own event pattern and integration.
|
|
12
|
+
*
|
|
13
|
+
* @param props - Optional resource configuration (e.g. a custom `name`).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* @EventRule()
|
|
18
|
+
* export class OrderEvents {
|
|
19
|
+
* @Rule({ pattern: { source: 'orders', detailType: ['order.created'] } })
|
|
20
|
+
* onCreated(@Event() event) { }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
exports.EventRule = (0, common_1.createResourceDecorator)({
|
|
25
|
+
type: exports.RESOURCE_TYPE,
|
|
26
|
+
callerFileIndex: 5,
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Method decorator that registers a handler for a specific EventBridge
|
|
30
|
+
* rule pattern.
|
|
31
|
+
*
|
|
32
|
+
* The decorated method becomes a Lambda function that is invoked when
|
|
33
|
+
* an event matching the configured pattern is published on the
|
|
34
|
+
* EventBridge bus. Supports default custom events, S3 events, and
|
|
35
|
+
* DynamoDB stream events through the `integration` option.
|
|
36
|
+
*
|
|
37
|
+
* @param props - Rule configuration including the event pattern, optional
|
|
38
|
+
* integration source, retry attempts, max event age, and bus name.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* // Custom event
|
|
43
|
+
* @Rule({
|
|
44
|
+
* pattern: { source: 'payments', detailType: ['payment.completed'] },
|
|
45
|
+
* })
|
|
46
|
+
* onPayment(@Event() event) { }
|
|
47
|
+
*
|
|
48
|
+
* // S3 integration
|
|
49
|
+
* @Rule({
|
|
50
|
+
* integration: 's3',
|
|
51
|
+
* pattern: { detailType: ['Object Created'], detail: { bucket: { name: ['uploads'] } } },
|
|
52
|
+
* })
|
|
53
|
+
* onUpload(@Event() event) { }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
const Rule = (props) => (0, common_1.createLambdaDecorator)({
|
|
57
|
+
getLambdaMetadata: (props, methodName) => ({
|
|
58
|
+
...props,
|
|
59
|
+
name: methodName,
|
|
60
|
+
eventType: 'rule',
|
|
61
|
+
}),
|
|
62
|
+
})(props);
|
|
63
|
+
exports.Rule = Rule;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { BucketNames, DynamoTableNames, EventBusNames, LambdaMetadata, LambdaProps } from '@lafken/common';
|
|
2
|
+
export interface EventRuleBaseProps {
|
|
3
|
+
/**
|
|
4
|
+
* Maximum event age.
|
|
5
|
+
*
|
|
6
|
+
* Specifies the maximum age of an event that can be sent to the rule's targets.
|
|
7
|
+
* Events older than this duration will be discarded.
|
|
8
|
+
*/
|
|
9
|
+
maxEventAge?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Retry attempts for failed events.
|
|
12
|
+
*
|
|
13
|
+
* Specifies the maximum number of times EventBridge will retry sending
|
|
14
|
+
* an event to the target if the initial attempt fails.
|
|
15
|
+
*/
|
|
16
|
+
retryAttempts?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Event bus name.
|
|
19
|
+
*
|
|
20
|
+
* Specifies the EventBridge bus where the rule will be created.
|
|
21
|
+
* If not provided, the default event bus is used.
|
|
22
|
+
*/
|
|
23
|
+
bus?: EventBusNames;
|
|
24
|
+
/**
|
|
25
|
+
* Lambda configuration for the method.
|
|
26
|
+
*
|
|
27
|
+
* Specifies the properties and settings of the Lambda function
|
|
28
|
+
* associated with this API method. This allows you to customize
|
|
29
|
+
* aspects such as timeout, memory, runtime, environment variables,
|
|
30
|
+
* services, and tracing on a per-method basis.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* {
|
|
34
|
+
* timeout: 300,
|
|
35
|
+
* memory: 1024,
|
|
36
|
+
* runtime: 22,
|
|
37
|
+
* services: ['sqs'],
|
|
38
|
+
* enableTrace: booleam
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
41
|
+
lambda?: LambdaProps;
|
|
42
|
+
}
|
|
43
|
+
export type S3DetailType = 'Object Created' | 'Object Deleted';
|
|
44
|
+
export type S3ObjectKey = {
|
|
45
|
+
prefix?: string;
|
|
46
|
+
suffix?: string;
|
|
47
|
+
};
|
|
48
|
+
export interface S3Detail {
|
|
49
|
+
bucket?: {
|
|
50
|
+
name: BucketNames[];
|
|
51
|
+
};
|
|
52
|
+
object?: {
|
|
53
|
+
key?: (S3ObjectKey | string)[];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface EventDefaultRuleProps extends EventRuleBaseProps {
|
|
57
|
+
/**
|
|
58
|
+
* Integration source for the EventBridge rule.
|
|
59
|
+
*
|
|
60
|
+
* Specifies the AWS service that will emit events for this rule.
|
|
61
|
+
* Common examples include:
|
|
62
|
+
* - `'dynamodb'` – captures events from a DynamoDB table.
|
|
63
|
+
* - `'s3'` – captures events from an S3 bucket.
|
|
64
|
+
*/
|
|
65
|
+
integration?: never;
|
|
66
|
+
/**
|
|
67
|
+
* Event pattern for the EventBridge rule.
|
|
68
|
+
*
|
|
69
|
+
* Defines the filtering criteria that determine which events
|
|
70
|
+
* trigger the rule. Events are matched against the specified
|
|
71
|
+
* pattern fields.
|
|
72
|
+
* @example
|
|
73
|
+
* {
|
|
74
|
+
* pattern: {
|
|
75
|
+
* source: "<event_source>",
|
|
76
|
+
* detailType: ['foo'],
|
|
77
|
+
* detail: {
|
|
78
|
+
* foo: 'bar'
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
83
|
+
pattern: {
|
|
84
|
+
/**
|
|
85
|
+
* Event source.
|
|
86
|
+
*
|
|
87
|
+
* Specifies the AWS service or custom source that emits the events
|
|
88
|
+
* to be captured by this EventBridge rule.
|
|
89
|
+
*/
|
|
90
|
+
source: string;
|
|
91
|
+
/**
|
|
92
|
+
* Event types to match.
|
|
93
|
+
*
|
|
94
|
+
* Optional array of event types (detailType) that should trigger the rule.
|
|
95
|
+
* If not specified, all event types from the source are captured.
|
|
96
|
+
*/
|
|
97
|
+
detailType?: string[];
|
|
98
|
+
/**
|
|
99
|
+
* Additional filtering criteria on the event payload.
|
|
100
|
+
*
|
|
101
|
+
* Optional object specifying conditions on event attributes to further
|
|
102
|
+
* filter which events trigger the rule.
|
|
103
|
+
*/
|
|
104
|
+
detail?: any;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export interface EventS3RuleProps extends EventRuleBaseProps {
|
|
108
|
+
/**
|
|
109
|
+
* Integration source for the EventBridge rule.
|
|
110
|
+
*
|
|
111
|
+
* Specifies the AWS service that will emit events for this rule.
|
|
112
|
+
* Common examples include:
|
|
113
|
+
* - `'dynamodb'` – captures events from a DynamoDB table.
|
|
114
|
+
* - `'s3'` – captures events from an S3 bucket.
|
|
115
|
+
*/
|
|
116
|
+
integration: 's3';
|
|
117
|
+
/**
|
|
118
|
+
* Event pattern for the EventBridge rule.
|
|
119
|
+
*
|
|
120
|
+
* Defines the filtering criteria that determine which events
|
|
121
|
+
* trigger the rule. Events are matched against the specified
|
|
122
|
+
* pattern fields.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* {
|
|
126
|
+
* pattern: {
|
|
127
|
+
* detailType: ['Object Created'],
|
|
128
|
+
* detail: {
|
|
129
|
+
* bucket: {
|
|
130
|
+
* name: 'bucket_name'
|
|
131
|
+
* }
|
|
132
|
+
* }
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
*/
|
|
136
|
+
pattern: {
|
|
137
|
+
/**
|
|
138
|
+
* Event types to match.
|
|
139
|
+
*
|
|
140
|
+
* Optional array of event types (detailType) that should trigger the rule.
|
|
141
|
+
* If not specified, all event types from the source are captured.
|
|
142
|
+
*/
|
|
143
|
+
detailType: S3DetailType[];
|
|
144
|
+
/**
|
|
145
|
+
* Additional filtering criteria on the event payload.
|
|
146
|
+
*
|
|
147
|
+
* Optional object specifying conditions on event attributes to further
|
|
148
|
+
* filter which events trigger the rule.
|
|
149
|
+
*/
|
|
150
|
+
detail: S3Detail;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
type PrefixPattern = {
|
|
154
|
+
prefix: string;
|
|
155
|
+
};
|
|
156
|
+
type SuffixPattern = {
|
|
157
|
+
suffix: string;
|
|
158
|
+
};
|
|
159
|
+
type AnythingButPattern = {
|
|
160
|
+
'anything-but': string | string[];
|
|
161
|
+
};
|
|
162
|
+
type NumericPattern = {
|
|
163
|
+
numeric: ['=' | '>' | '>=' | '<' | '<=', number] | ['>' | '>=', number, '<' | '<=', number];
|
|
164
|
+
};
|
|
165
|
+
type ExistsPattern = {
|
|
166
|
+
exists: boolean;
|
|
167
|
+
};
|
|
168
|
+
type EqualsIgnoreCasePattern = {
|
|
169
|
+
'equals-ignore-case': string;
|
|
170
|
+
};
|
|
171
|
+
export type EventBridgePattern = string | number | boolean | PrefixPattern | SuffixPattern | AnythingButPattern | NumericPattern | ExistsPattern | EqualsIgnoreCasePattern;
|
|
172
|
+
export type DynamoAttributeFilter = EventBridgePattern | EventBridgePattern[];
|
|
173
|
+
export type DynamoAttributeFilters = Record<string, DynamoAttributeFilter>;
|
|
174
|
+
interface DynamoDetail {
|
|
175
|
+
eventName?: ('INSERT' | 'MODIFY' | 'REMOVE')[];
|
|
176
|
+
keys?: DynamoAttributeFilters;
|
|
177
|
+
newImage?: DynamoAttributeFilters;
|
|
178
|
+
oldImage?: DynamoAttributeFilters;
|
|
179
|
+
}
|
|
180
|
+
export interface DynamoRuleProps extends EventRuleBaseProps {
|
|
181
|
+
/**
|
|
182
|
+
* Integration source for the EventBridge rule.
|
|
183
|
+
*
|
|
184
|
+
* Specifies the AWS service that will emit events for this rule.
|
|
185
|
+
* Common examples include:
|
|
186
|
+
* - `'dynamodb'` – captures events from a DynamoDB table.
|
|
187
|
+
* - `'s3'` – captures events from an S3 bucket.
|
|
188
|
+
*/
|
|
189
|
+
integration: 'dynamodb';
|
|
190
|
+
/**
|
|
191
|
+
* Event pattern for the EventBridge rule.
|
|
192
|
+
*
|
|
193
|
+
* Defines the filtering criteria that determine which events
|
|
194
|
+
* trigger the rule. Events are matched against the specified
|
|
195
|
+
* pattern fields.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* {
|
|
199
|
+
* pattern: {
|
|
200
|
+
* source: 'dynamo_table_name',
|
|
201
|
+
* detail: {
|
|
202
|
+
* eventname: ['INSERT'],
|
|
203
|
+
* keys: {
|
|
204
|
+
* PK: ['a', 'b']
|
|
205
|
+
* }
|
|
206
|
+
* }
|
|
207
|
+
* }
|
|
208
|
+
* }
|
|
209
|
+
*/
|
|
210
|
+
pattern: {
|
|
211
|
+
/**
|
|
212
|
+
* Event source.
|
|
213
|
+
*
|
|
214
|
+
* Specifies the AWS service or custom source that emits the events
|
|
215
|
+
* to be captured by this EventBridge rule.
|
|
216
|
+
*/
|
|
217
|
+
source: DynamoTableNames;
|
|
218
|
+
/**
|
|
219
|
+
* Additional filtering criteria on the event payload.
|
|
220
|
+
*
|
|
221
|
+
* Optional object specifying conditions on event attributes to further
|
|
222
|
+
* filter which events trigger the rule.
|
|
223
|
+
*/
|
|
224
|
+
detail?: DynamoDetail;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export type EventRuleProps = EventDefaultRuleProps | EventS3RuleProps | DynamoRuleProps;
|
|
228
|
+
export type EventRuleMetadata = LambdaMetadata & EventRuleProps;
|
|
229
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resolver';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./resolver"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ClassResource } from '@lafken/common';
|
|
2
|
+
import { type AppModule, type AppStack, type ResolverType } from '@lafken/resolver';
|
|
3
|
+
import type { EventRuleResolverProps } from './resolver.types';
|
|
4
|
+
export declare class EventRuleResolver implements ResolverType {
|
|
5
|
+
type: "EVENT";
|
|
6
|
+
private eventBuses;
|
|
7
|
+
private props;
|
|
8
|
+
constructor(...props: EventRuleResolverProps[]);
|
|
9
|
+
beforeCreate(scope: AppStack): Promise<void>;
|
|
10
|
+
create(module: AppModule, resource: ClassResource): void;
|
|
11
|
+
afterCreate(scope: AppStack): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventRuleResolver = void 0;
|
|
4
|
+
const cloudwatch_event_bus_1 = require("@cdktn/provider-aws/lib/cloudwatch-event-bus");
|
|
5
|
+
const data_aws_cloudwatch_event_bus_1 = require("@cdktn/provider-aws/lib/data-aws-cloudwatch-event-bus");
|
|
6
|
+
const common_1 = require("@lafken/common");
|
|
7
|
+
const resolver_1 = require("@lafken/resolver");
|
|
8
|
+
const main_1 = require("../main");
|
|
9
|
+
const rule_1 = require("./rule/rule");
|
|
10
|
+
const LafkenEventBus = resolver_1.lafkenResource.make(cloudwatch_event_bus_1.CloudwatchEventBus);
|
|
11
|
+
const LafkenDataEventBus = resolver_1.lafkenResource.make(data_aws_cloudwatch_event_bus_1.DataAwsCloudwatchEventBus);
|
|
12
|
+
class EventRuleResolver {
|
|
13
|
+
type = main_1.RESOURCE_TYPE;
|
|
14
|
+
eventBuses = {};
|
|
15
|
+
props = [];
|
|
16
|
+
constructor(...props) {
|
|
17
|
+
if (props) {
|
|
18
|
+
this.props = props;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async beforeCreate(scope) {
|
|
22
|
+
const defaultBus = new data_aws_cloudwatch_event_bus_1.DataAwsCloudwatchEventBus(scope, 'EventDefaultBus', {
|
|
23
|
+
name: 'default',
|
|
24
|
+
});
|
|
25
|
+
this.eventBuses.default = {
|
|
26
|
+
eventBus: defaultBus,
|
|
27
|
+
};
|
|
28
|
+
for (const eventBusProps of this.props) {
|
|
29
|
+
if (eventBusProps.busName === 'default') {
|
|
30
|
+
throw new Error('Event bus default already exist');
|
|
31
|
+
}
|
|
32
|
+
let eventBus;
|
|
33
|
+
if (eventBusProps.isExternal) {
|
|
34
|
+
eventBus = new LafkenDataEventBus(scope, `${eventBusProps.busName}-bus`, {
|
|
35
|
+
name: eventBusProps.busName,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
eventBus = new LafkenEventBus(scope, `${eventBusProps.busName}-bus`, {
|
|
40
|
+
name: eventBusProps.busName,
|
|
41
|
+
});
|
|
42
|
+
new resolver_1.ResourceOutput(eventBus, eventBusProps.outputs);
|
|
43
|
+
}
|
|
44
|
+
eventBus.isGlobal('event-bus', eventBusProps.busName);
|
|
45
|
+
this.eventBuses[eventBusProps.busName] = {
|
|
46
|
+
eventBus: eventBus,
|
|
47
|
+
extend: eventBusProps.extend,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
create(module, resource) {
|
|
52
|
+
const metadata = (0, common_1.getResourceMetadata)(resource);
|
|
53
|
+
const handlers = (0, common_1.getResourceHandlerMetadata)(resource);
|
|
54
|
+
resolver_1.lambdaAssets.initializeMetadata({
|
|
55
|
+
foldername: metadata.foldername,
|
|
56
|
+
filename: metadata.filename,
|
|
57
|
+
minify: metadata.minify,
|
|
58
|
+
className: metadata.originalName,
|
|
59
|
+
methods: handlers.map((handler) => handler.name),
|
|
60
|
+
});
|
|
61
|
+
for (const handler of handlers) {
|
|
62
|
+
const id = `${handler.name}-${metadata.name}`;
|
|
63
|
+
const bus = this.eventBuses[handler.bus || 'default'];
|
|
64
|
+
new rule_1.Rule(module, id, {
|
|
65
|
+
bus: bus.eventBus,
|
|
66
|
+
handler,
|
|
67
|
+
resourceMetadata: metadata,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
afterCreate(scope) {
|
|
72
|
+
for (const key in this.eventBuses) {
|
|
73
|
+
const { extend, eventBus } = this.eventBuses[key];
|
|
74
|
+
if (!extend) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
extend({
|
|
78
|
+
scope,
|
|
79
|
+
eventBus,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.EventRuleResolver = EventRuleResolver;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CloudwatchEventBus } from '@cdktn/provider-aws/lib/cloudwatch-event-bus';
|
|
2
|
+
import type { DataAwsCloudwatchEventBus } from '@cdktn/provider-aws/lib/data-aws-cloudwatch-event-bus';
|
|
3
|
+
import type { EventBusNames, ResourceOutputType } from '@lafken/common';
|
|
4
|
+
import type { AppStack } from '@lafken/resolver';
|
|
5
|
+
export type BusOutputAttributes = 'arn' | 'id';
|
|
6
|
+
interface ExtendProps<T> {
|
|
7
|
+
/**
|
|
8
|
+
* The CDKTN application stack scope.
|
|
9
|
+
*/
|
|
10
|
+
scope: AppStack;
|
|
11
|
+
/**
|
|
12
|
+
* The underlying CloudWatch Event Bus construct.
|
|
13
|
+
* Use this to apply additional CDKTN configuration beyond what
|
|
14
|
+
* `EventRuleResolverProps` exposes directly.
|
|
15
|
+
*/
|
|
16
|
+
eventBus: T;
|
|
17
|
+
}
|
|
18
|
+
export interface EventBusList {
|
|
19
|
+
eventBus: CloudwatchEventBus | DataAwsCloudwatchEventBus;
|
|
20
|
+
extend?: (props: ExtendProps<CloudwatchEventBus | DataAwsCloudwatchEventBus>) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface EventRuleResolverBaseProps {
|
|
23
|
+
/**
|
|
24
|
+
* Defines the name of the custom EventBridge event bus to create.
|
|
25
|
+
*
|
|
26
|
+
* The value must match one of the registered `EventBusNames` in your application.
|
|
27
|
+
* The reserved name `'default'` cannot be used here — the default EventBridge bus
|
|
28
|
+
* is always provisioned automatically.
|
|
29
|
+
*/
|
|
30
|
+
busName: EventBusNames;
|
|
31
|
+
}
|
|
32
|
+
export interface InternalEventRuleResolverProps extends EventRuleResolverBaseProps {
|
|
33
|
+
isExternal?: never;
|
|
34
|
+
/**
|
|
35
|
+
* Defines which EventBridge event bus attributes should be exported.
|
|
36
|
+
*
|
|
37
|
+
* Supported attributes are based on Terraform `aws_cloudwatch_event_bus`
|
|
38
|
+
* attribute reference:
|
|
39
|
+
* - `arn`: ARN of the event bus.
|
|
40
|
+
* - `id`: Name of the event bus.
|
|
41
|
+
*
|
|
42
|
+
* Each selected attribute can be exported through SSM Parameter Store (`type: 'ssm'`)
|
|
43
|
+
* or Terraform outputs (`type: 'output'`).
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* {
|
|
47
|
+
* outputs: [
|
|
48
|
+
* { type: 'ssm', name: '/my-app/orders-bus-arn', value: 'arn' },
|
|
49
|
+
* { type: 'output', name: 'orders_bus_id', value: 'id' }
|
|
50
|
+
* ]
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
outputs?: ResourceOutputType<BusOutputAttributes>;
|
|
54
|
+
/**
|
|
55
|
+
* Allows extending the event bus with custom configurations or resources.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* {
|
|
59
|
+
* extend: ({ eventBus, scope }) => {
|
|
60
|
+
* // Apply additional CDKTN configuration
|
|
61
|
+
* },
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
extend?: (props: ExtendProps<CloudwatchEventBus>) => void;
|
|
65
|
+
}
|
|
66
|
+
export interface ExternalEventRuleResolverProps extends EventRuleResolverBaseProps {
|
|
67
|
+
/**
|
|
68
|
+
* Marks the EventBridge event bus as an external resource.
|
|
69
|
+
*
|
|
70
|
+
* When set to `true`, the event bus is not created by the framework.
|
|
71
|
+
* Instead, it references an existing EventBridge event bus using the provided `busName`.
|
|
72
|
+
*/
|
|
73
|
+
isExternal: true;
|
|
74
|
+
/**
|
|
75
|
+
* Allows extending the event bus with custom configurations or resources.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* {
|
|
79
|
+
* extend: ({ eventBus, scope }) => {
|
|
80
|
+
* // Apply additional CDKTN configuration
|
|
81
|
+
* },
|
|
82
|
+
* }
|
|
83
|
+
*/
|
|
84
|
+
extend?: (props: ExtendProps<DataAwsCloudwatchEventBus>) => void;
|
|
85
|
+
}
|
|
86
|
+
export type EventRuleResolverProps = InternalEventRuleResolverProps | ExternalEventRuleResolverProps;
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CloudwatchEventRule } from '@cdktn/provider-aws/lib/cloudwatch-event-rule';
|
|
2
|
+
import { type AppModule } from '@lafken/resolver';
|
|
3
|
+
import type { RuleProps } from './rule.types';
|
|
4
|
+
declare const Rule_base: (new (...args: any[]) => {
|
|
5
|
+
isGlobal(module: import("@lafken/common").ModuleGlobalReferenceNames | (string & {}), id: string): void;
|
|
6
|
+
isDependent(resolveDependency: () => void): void;
|
|
7
|
+
readonly node: import("constructs").Node;
|
|
8
|
+
with(...mixins: import("constructs").IMixin[]): import("constructs").IConstruct;
|
|
9
|
+
toString(): string;
|
|
10
|
+
}) & typeof CloudwatchEventRule;
|
|
11
|
+
export declare class Rule extends Rule_base {
|
|
12
|
+
private props;
|
|
13
|
+
constructor(scope: AppModule, id: string, props: RuleProps);
|
|
14
|
+
private addEventTarget;
|
|
15
|
+
private static getEvent;
|
|
16
|
+
private static inferDynamoDBType;
|
|
17
|
+
private static transformAttributes;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Rule = void 0;
|
|
4
|
+
const cloudwatch_event_rule_1 = require("@cdktn/provider-aws/lib/cloudwatch-event-rule");
|
|
5
|
+
const cloudwatch_event_target_1 = require("@cdktn/provider-aws/lib/cloudwatch-event-target");
|
|
6
|
+
const resolver_1 = require("@lafken/resolver");
|
|
7
|
+
const cdktn_1 = require("cdktn");
|
|
8
|
+
class Rule extends resolver_1.lafkenResource.make(cloudwatch_event_rule_1.CloudwatchEventRule) {
|
|
9
|
+
props;
|
|
10
|
+
constructor(scope, id, props) {
|
|
11
|
+
const { handler, bus } = props;
|
|
12
|
+
super(scope, `${id}-rule`, {
|
|
13
|
+
name: id,
|
|
14
|
+
eventBusName: bus.name,
|
|
15
|
+
eventPattern: cdktn_1.Fn.jsonencode(Rule.getEvent(handler)),
|
|
16
|
+
});
|
|
17
|
+
this.props = props;
|
|
18
|
+
this.isGlobal(scope.id, id);
|
|
19
|
+
this.addEventTarget(id);
|
|
20
|
+
}
|
|
21
|
+
addEventTarget(id) {
|
|
22
|
+
const { resourceMetadata, handler } = this.props;
|
|
23
|
+
const lambdaHandler = new resolver_1.LambdaHandler(this, `${handler.name}-${resourceMetadata.name}`, {
|
|
24
|
+
...handler,
|
|
25
|
+
originalName: resourceMetadata.originalName,
|
|
26
|
+
filename: resourceMetadata.filename,
|
|
27
|
+
foldername: resourceMetadata.foldername,
|
|
28
|
+
suffix: 'event',
|
|
29
|
+
principal: 'events.amazonaws.com',
|
|
30
|
+
sourceArn: this.arn,
|
|
31
|
+
});
|
|
32
|
+
new cloudwatch_event_target_1.CloudwatchEventTarget(this, `${id}-event-target`, {
|
|
33
|
+
rule: this.name,
|
|
34
|
+
arn: lambdaHandler.arn,
|
|
35
|
+
retryPolicy: {
|
|
36
|
+
maximumRetryAttempts: handler.retryAttempts,
|
|
37
|
+
maximumEventAgeInSeconds: handler.maxEventAge,
|
|
38
|
+
},
|
|
39
|
+
inputPath: '$.detail',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
static getEvent(handler) {
|
|
43
|
+
if (!handler.integration) {
|
|
44
|
+
return {
|
|
45
|
+
source: [handler.pattern.source],
|
|
46
|
+
detail: handler.pattern.detail,
|
|
47
|
+
'detail-type': handler.pattern.detailType,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
switch (handler.integration) {
|
|
51
|
+
case 's3': {
|
|
52
|
+
return {
|
|
53
|
+
source: ['aws.s3'],
|
|
54
|
+
'detail-type': handler.pattern.detailType,
|
|
55
|
+
detail: handler.pattern.detail,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
case 'dynamodb': {
|
|
59
|
+
const dynamoDetail = {
|
|
60
|
+
...(handler.pattern.detail?.keys
|
|
61
|
+
? {
|
|
62
|
+
Keys: Rule.transformAttributes(handler.pattern.detail?.keys),
|
|
63
|
+
}
|
|
64
|
+
: {}),
|
|
65
|
+
...(handler.pattern.detail?.newImage
|
|
66
|
+
? {
|
|
67
|
+
NewImage: Rule.transformAttributes(handler.pattern.detail?.newImage),
|
|
68
|
+
}
|
|
69
|
+
: {}),
|
|
70
|
+
...(handler.pattern.detail?.oldImage
|
|
71
|
+
? {
|
|
72
|
+
OldImage: Rule.transformAttributes(handler.pattern.detail?.oldImage),
|
|
73
|
+
}
|
|
74
|
+
: {}),
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
source: [`dynamodb.${handler.pattern.source}`],
|
|
78
|
+
'detail-type': ['db:stream'],
|
|
79
|
+
...(Object.keys(dynamoDetail).length > 0 || handler.pattern.detail?.eventName
|
|
80
|
+
? {
|
|
81
|
+
detail: {
|
|
82
|
+
...(handler.pattern.detail?.eventName
|
|
83
|
+
? {
|
|
84
|
+
eventName: handler.pattern.detail?.eventName,
|
|
85
|
+
}
|
|
86
|
+
: {}),
|
|
87
|
+
dynamodb: dynamoDetail,
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
: {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
default:
|
|
94
|
+
throw new Error('Unsupported integration type');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
static inferDynamoDBType(value) {
|
|
98
|
+
if (typeof value === 'string')
|
|
99
|
+
return 'S';
|
|
100
|
+
if (typeof value === 'number')
|
|
101
|
+
return 'N';
|
|
102
|
+
if (typeof value === 'boolean')
|
|
103
|
+
return 'BOOL';
|
|
104
|
+
if (typeof value === 'object') {
|
|
105
|
+
if ('numeric' in value)
|
|
106
|
+
return 'N';
|
|
107
|
+
if ('prefix' in value || 'suffix' in value || 'equals-ignore-case' in value)
|
|
108
|
+
return 'S';
|
|
109
|
+
}
|
|
110
|
+
return 'S';
|
|
111
|
+
}
|
|
112
|
+
static transformAttributes(attrs) {
|
|
113
|
+
if (!attrs)
|
|
114
|
+
return undefined;
|
|
115
|
+
const transformed = {};
|
|
116
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
117
|
+
const values = Array.isArray(value) ? value : [value];
|
|
118
|
+
const type = Rule.inferDynamoDBType(values[0]);
|
|
119
|
+
transformed[key] = {
|
|
120
|
+
[type]: values,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return transformed;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.Rule = Rule;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CloudwatchEventBus } from '@cdktn/provider-aws/lib/cloudwatch-event-bus';
|
|
2
|
+
import type { DataAwsCloudwatchEventBus } from '@cdktn/provider-aws/lib/data-aws-cloudwatch-event-bus';
|
|
3
|
+
import type { ResourceMetadata } from '@lafken/common';
|
|
4
|
+
import type { EventRuleMetadata } from '../../main';
|
|
5
|
+
export interface RuleProps {
|
|
6
|
+
resourceMetadata: ResourceMetadata;
|
|
7
|
+
handler: EventRuleMetadata;
|
|
8
|
+
bus: CloudwatchEventBus | DataAwsCloudwatchEventBus;
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lafken/event",
|
|
3
|
+
"version": "0.10.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Define EventBridge event listeners using TypeScript decorators - serverless event-driven infrastructure",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"aws",
|
|
8
|
+
"eventbridge",
|
|
9
|
+
"event",
|
|
10
|
+
"event-driven",
|
|
11
|
+
"serverless",
|
|
12
|
+
"typescript",
|
|
13
|
+
"decorators",
|
|
14
|
+
"lafken"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/Hero64/lafken#readme",
|
|
17
|
+
"bugs": "https://github.com/Hero64/lafken/issues",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/Hero64/lafken",
|
|
21
|
+
"directory": "packages/event"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Aníbal Jorquera",
|
|
25
|
+
"exports": {
|
|
26
|
+
"./main": {
|
|
27
|
+
"import": "./lib/main/index.js",
|
|
28
|
+
"types": "./lib/main/index.d.ts",
|
|
29
|
+
"require": "./lib/main/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./resolver": {
|
|
32
|
+
"import": "./lib/resolver/index.js",
|
|
33
|
+
"types": "./lib/resolver/index.d.ts",
|
|
34
|
+
"require": "./lib/resolver/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"typesVersions": {
|
|
38
|
+
"*": {
|
|
39
|
+
"main": [
|
|
40
|
+
"./lib/main/index.d.ts"
|
|
41
|
+
],
|
|
42
|
+
"resolver": [
|
|
43
|
+
"./lib/resolver/index.d.ts"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"lib"
|
|
49
|
+
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"reflect-metadata": "^0.2.2",
|
|
52
|
+
"@lafken/resolver": "0.10.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@cdktn/provider-aws": "^23.5.0",
|
|
56
|
+
"@swc/core": "^1.15.21",
|
|
57
|
+
"@swc/helpers": "^0.5.20",
|
|
58
|
+
"@vitest/runner": "^4.1.2",
|
|
59
|
+
"cdktn": "^0.22.1",
|
|
60
|
+
"cdktn-vitest": "^1.0.0",
|
|
61
|
+
"constructs": "^10.6.0",
|
|
62
|
+
"typescript": "6.0.2",
|
|
63
|
+
"unplugin-swc": "^1.5.9",
|
|
64
|
+
"vitest": "^4.1.2",
|
|
65
|
+
"@lafken/common": "0.10.1"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@cdktn/provider-aws": ">=23.0.0",
|
|
69
|
+
"cdktn": ">=0.22.0",
|
|
70
|
+
"constructs": "^10.4.5",
|
|
71
|
+
"@lafken/common": "0.10.1"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=20.19"
|
|
75
|
+
},
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
78
|
+
},
|
|
79
|
+
"scripts": {
|
|
80
|
+
"build": "pnpm clean && tsc -p ./tsconfig.build.json",
|
|
81
|
+
"check-types": "tsc --noEmit -p ./tsconfig.build.json",
|
|
82
|
+
"clean": "rm -rf ./lib",
|
|
83
|
+
"dev": "tsc -w",
|
|
84
|
+
"test": "vitest"
|
|
85
|
+
}
|
|
86
|
+
}
|