@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 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
@@ -0,0 +1,2 @@
1
+ export * from './main';
2
+ export * from './resolver';
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,2 @@
1
+ export * from './event/event';
2
+ export * from './rule';
@@ -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,2 @@
1
+ export * from './rule';
2
+ export * from './rule.types';
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+ }