@lafken/queue 0.12.8 → 0.12.10

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 CHANGED
@@ -20,7 +20,7 @@ import { Queue, Standard, Payload, Param, Event } from '@lafken/queue/main';
20
20
  // 1. Define the message payload
21
21
  @Payload()
22
22
  export class OrderMessage {
23
- @Param({ source: 'body', parse: true })
23
+ @Param({ source: 'attribute' })
24
24
  orderId: string;
25
25
 
26
26
  @Param({ source: 'body', parse: true })
@@ -31,8 +31,10 @@ export class OrderMessage {
31
31
  @Queue()
32
32
  export class OrderQueue {
33
33
  @Standard({ batchSize: 5, visibilityTimeout: 60 })
34
- processOrder(@Event(OrderMessage) message: OrderMessage) {
35
- console.log(`Processing order ${message.orderId}`);
34
+ processOrder(@Event(OrderMessage) messages: OrderMessage[]) {
35
+ for (const message of messages) {
36
+ console.log(`Processing order ${message.orderId}`);
37
+ }
36
38
  }
37
39
  }
38
40
 
@@ -50,7 +52,7 @@ createApp({
50
52
  });
51
53
  ```
52
54
 
53
- Each `@Standard` or `@Fifo` method becomes an independent Lambda function with its own SQS queue and event source mapping.
55
+ Each `@Standard` or `@Fifo` method becomes an independent Lambda function with its own SQS queue and event source mapping. The handler always receives an **array** of mapped payload objects — one per SQS record in the batch.
54
56
 
55
57
  ## Features
56
58
 
@@ -82,8 +84,9 @@ Use the `@Standard` decorator to create a standard (non-FIFO) SQS queue consumer
82
84
  visibilityTimeout: 120,
83
85
  maxConcurrency: 5,
84
86
  })
85
- generateReport(@Event(ReportMessage) message: ReportMessage) {
87
+ generateReport(@Event(ReportMessage) messages: ReportMessage[]) {
86
88
  // Process up to 10 messages per invocation
89
+ for (const message of messages) { ... }
87
90
  }
88
91
  ```
89
92
 
@@ -113,8 +116,9 @@ Use the `@Fifo` decorator for FIFO (First-In-First-Out) queues. FIFO queues guar
113
116
  contentBasedDeduplication: true,
114
117
  batchSize: 1,
115
118
  })
116
- processPayment(@Event(PaymentMessage) message: PaymentMessage) {
119
+ processPayment(@Event(PaymentMessage) messages: PaymentMessage[]) {
117
120
  // Messages are processed in exact send order
121
+ for (const message of messages) { ... }
118
122
  }
119
123
  ```
120
124
 
@@ -136,31 +140,63 @@ import { Payload, Param } from '@lafken/queue/main';
136
140
 
137
141
  @Payload()
138
142
  export class TaskMessage {
139
- @Param({ source: 'attribute', type: String })
143
+ @Param({ source: 'attribute' })
140
144
  correlationId: string;
141
145
 
142
- @Param({ source: 'body', parse: true })
143
- taskName: string;
146
+ @Param({ source: 'attribute', type: Number })
147
+ priority: number;
144
148
 
145
149
  @Param({ source: 'body', parse: true })
146
- priority: number;
150
+ taskName: string;
147
151
  }
148
152
  ```
149
153
 
150
154
  #### @Param Options
151
155
 
152
- | Option | Type | Default | Description |
153
- | -------- | ------------------------- | ------------- | ------------------------------------------------ |
154
- | `source` | `'attribute' \| 'body'` | `'attribute'` | Where to extract the value from the SQS record |
155
- | `parse` | `boolean` | `false` | JSON-parse the message body before extraction |
156
- | `type` | `String \| Number \| ...` | inferred | Data type of the extracted value |
157
- | `name` | `string` | property name | Override the field name used for extraction |
156
+ | Option | Type | Default | Description |
157
+ | -------- | ------------------------------------------ | ------------- | -------------------------------------------------------- |
158
+ | `source` | `'attribute' \| 'body' \| 'record'` | `'attribute'` | Where to extract the value from the SQS record |
159
+ | `parse` | `boolean` | `false` | JSON-parse the message body before extraction (`'body'` only) |
160
+ | `type` | `String \| Number \| ...` | inferred | Data type of the extracted value |
161
+ | `name` | `string` | property name | Override the source field name used for extraction |
158
162
 
159
- - **`source: 'attribute'`** — reads from SQS message attributes (supports `String` and `Number` types)
160
- - **`source: 'body'`** — reads from the message body. Set `parse: true` to JSON-parse the body and extract a specific field
163
+ - **`source: 'attribute'`** — reads from SQS message attributes (supports `String` and `Number` types).
164
+ - **`source: 'body'`** — reads from the message body. Set `parse: true` to JSON-parse the body and extract a specific key by property name.
165
+ - **`source: 'record'`** — reads a top-level SQS record field such as `messageId`, `receiptHandle`, `awsRegion`, etc. The `name` option accepts only valid `SQSRecordField` values and defaults to the property name.
161
166
 
162
- > [!NOTE]
163
- > Only one `@Param` with `source: 'body'` and `parse: false` (raw body) is allowed per handler.
167
+ > [!IMPORTANT]
168
+ > Only **one** `@Param` with `source: 'body'` is allowed per payload class.
169
+
170
+ #### `source: 'record'` — SQS Record Fields
171
+
172
+ Use `source: 'record'` to bind SQS envelope metadata (e.g. the message identifier or region) directly to a payload property. The available fields are:
173
+
174
+ | Field | Description |
175
+ | ---------------- | --------------------------------------------------- |
176
+ | `messageId` | Unique identifier assigned by SQS to the message |
177
+ | `receiptHandle` | Token used to delete or extend message visibility |
178
+ | `md5OfBody` | MD5 digest of the message body |
179
+ | `eventSource` | Always `"aws:sqs"` |
180
+ | `eventSourceARN` | ARN of the source SQS queue |
181
+ | `awsRegion` | AWS region where the queue resides |
182
+
183
+ ```typescript
184
+ import { Payload, Param } from '@lafken/queue/main';
185
+
186
+ @Payload()
187
+ export class AuditMessage {
188
+ // property name matches record field → no 'name' needed
189
+ @Param({ source: 'record' })
190
+ messageId: string;
191
+
192
+ // alias: property 'region' extracts record.awsRegion
193
+ @Param({ source: 'record', name: 'awsRegion' })
194
+ region: string;
195
+
196
+ @Param({ source: 'attribute' })
197
+ userId: string;
198
+ }
199
+ ```
164
200
 
165
201
  #### @Field Decorator
166
202
 
@@ -181,7 +217,7 @@ export class SimpleMessage {
181
217
 
182
218
  ### Consuming Messages
183
219
 
184
- Bind a typed payload to a handler method using the `@Event` parameter decorator. Pass the payload class so the framework can automatically extract and map fields from the SQS record at runtime:
220
+ Bind a typed payload to a handler method using the `@Event` parameter decorator. Pass the payload class so the framework can automatically extract and map fields from every SQS record in the batch. The handler always receives an **array** of mapped objects:
185
221
 
186
222
  ```typescript
187
223
  import { Queue, Standard, Event } from '@lafken/queue/main';
@@ -189,8 +225,10 @@ import { Queue, Standard, Event } from '@lafken/queue/main';
189
225
  @Queue()
190
226
  export class AlertQueue {
191
227
  @Standard({ queueName: 'alerts', batchSize: 5 })
192
- processAlert(@Event(AlertMessage) alert: AlertMessage) {
193
- console.log(`Alert from ${alert.source}: ${alert.message}`);
228
+ processAlert(@Event(AlertMessage) alerts: AlertMessage[]) {
229
+ for (const alert of alerts) {
230
+ console.log(`Alert from ${alert.source}: ${alert.message}`);
231
+ }
194
232
  }
195
233
  }
196
234
  ```
@@ -224,7 +262,7 @@ await QueueService.sendMessage({
224
262
  | ------------------- | ---------------------------------- | -------------------------------------------------- |
225
263
  | `url` | `string` | Full SQS queue URL |
226
264
  | `body` | `any` | Message body (automatically JSON-stringified) |
227
- | `attributes` | `Record<string, string \| number>` | SQS message attributes |
265
+ | `attributes` | `Record<string, string \| number>` | SQS message attributes |
228
266
  | `delay` | `number` | Delay in seconds before the message becomes visible |
229
267
  | `groupId` | `string` | Message group ID (FIFO queues only) |
230
268
  | `deduplicationId` | `string` | Deduplication ID (FIFO queues only) |
@@ -31,12 +31,15 @@ export declare const Payload: (props?: import("@lafken/common").PayloadProps | u
31
31
  * Property decorator that maps a class field to a value extracted
32
32
  * from an SQS message.
33
33
  *
34
- * By default the value is read from a **message attribute** whose name
35
- * matches the property. Set `source: 'body'` to extract it from the
36
- * message body instead, and optionally enable `parse: true` to
37
- * JSON-parse the body before extraction.
34
+ * - `source: 'attribute'` (default) reads a **message attribute** whose
35
+ * name matches the property (or the `name` option).
36
+ * - `source: 'body'` reads from the raw message body string; set
37
+ * `parse: true` to JSON-parse it and pick the matching key.
38
+ * - `source: 'record'` — reads a top-level SQS record field such as
39
+ * `messageId`, `receiptHandle`, `awsRegion`, etc. The `name` option
40
+ * can override which record field is read.
38
41
  *
39
- * @param props - Optional extraction configuration (source, type, parse).
42
+ * @param props - Optional extraction configuration (source, type, parse, name).
40
43
  *
41
44
  * @example
42
45
  * ```ts
@@ -47,6 +50,12 @@ export declare const Payload: (props?: import("@lafken/common").PayloadProps | u
47
50
  *
48
51
  * @Param({ source: 'body', parse: true })
49
52
  * content: NotificationContent;
53
+ *
54
+ * @Param({ source: 'record' })
55
+ * messageId: string;
56
+ *
57
+ * @Param({ source: 'record', name: 'messageId' })
58
+ * sqsId: string;
50
59
  * }
51
60
  * ```
52
61
  */
@@ -39,12 +39,15 @@ exports.Payload = (0, common_1.createPayloadDecorator)({
39
39
  * Property decorator that maps a class field to a value extracted
40
40
  * from an SQS message.
41
41
  *
42
- * By default the value is read from a **message attribute** whose name
43
- * matches the property. Set `source: 'body'` to extract it from the
44
- * message body instead, and optionally enable `parse: true` to
45
- * JSON-parse the body before extraction.
42
+ * - `source: 'attribute'` (default) reads a **message attribute** whose
43
+ * name matches the property (or the `name` option).
44
+ * - `source: 'body'` reads from the raw message body string; set
45
+ * `parse: true` to JSON-parse it and pick the matching key.
46
+ * - `source: 'record'` — reads a top-level SQS record field such as
47
+ * `messageId`, `receiptHandle`, `awsRegion`, etc. The `name` option
48
+ * can override which record field is read.
46
49
  *
47
- * @param props - Optional extraction configuration (source, type, parse).
50
+ * @param props - Optional extraction configuration (source, type, parse, name).
48
51
  *
49
52
  * @example
50
53
  * ```ts
@@ -55,6 +58,12 @@ exports.Payload = (0, common_1.createPayloadDecorator)({
55
58
  *
56
59
  * @Param({ source: 'body', parse: true })
57
60
  * content: NotificationContent;
61
+ *
62
+ * @Param({ source: 'record' })
63
+ * messageId: string;
64
+ *
65
+ * @Param({ source: 'record', name: 'messageId' })
66
+ * sqsId: string;
58
67
  * }
59
68
  * ```
60
69
  */
@@ -1,4 +1,9 @@
1
1
  import type { ArrayField, BooleanField, FieldProps, NumberField, ObjectField, StringField } from '@lafken/common';
2
+ /**
3
+ * Top-level string fields available on every SQS record that can be
4
+ * extracted with `@Param({ source: 'record' })`.
5
+ */
6
+ export type SQSRecordField = 'messageId' | 'receiptHandle' | 'md5OfBody' | 'eventSource' | 'eventSourceARN' | 'awsRegion';
2
7
  export interface ParamAttributeProps extends Omit<FieldProps, 'type'> {
3
8
  /**
4
9
  * Attribute source.
@@ -67,7 +72,30 @@ export interface ParamBodyParsedProps extends Omit<FieldProps, 'type'> {
67
72
  */
68
73
  type?: StringConstructor | Function | [Function];
69
74
  }
70
- export type ParamProps = ParamAttributeProps | ParamBodyUnparsedProps | ParamBodyParsedProps;
75
+ export interface ParamRecordProps extends Omit<FieldProps, 'type' | 'name'> {
76
+ /**
77
+ * Record source.
78
+ *
79
+ * Extracts a top-level field directly from the raw SQS record object
80
+ * (e.g. `messageId`, `receiptHandle`, `awsRegion`).
81
+ */
82
+ source: 'record';
83
+ /**
84
+ * SQS record field name.
85
+ *
86
+ * Specifies which top-level property of the SQS record to extract.
87
+ * Defaults to the decorated property name when omitted.
88
+ */
89
+ name?: SQSRecordField;
90
+ /**
91
+ * Attribute type.
92
+ *
93
+ * All SQS record metadata fields are strings, so only `String`
94
+ * is accepted here.
95
+ */
96
+ type?: StringConstructor;
97
+ }
98
+ export type ParamProps = ParamAttributeProps | ParamBodyUnparsedProps | ParamBodyParsedProps | ParamRecordProps;
71
99
  export type Source = Exclude<ParamProps['source'], undefined>;
72
100
  interface QueueParamBase {
73
101
  source: Source;
@@ -85,5 +113,8 @@ export interface QueueObjectParam extends Omit<ObjectField, 'properties'>, Queue
85
113
  export interface QueueArrayParam extends Omit<ArrayField, 'items'>, QueueParamBase {
86
114
  items: QueueParamMetadata;
87
115
  }
88
- export type QueueParamMetadata = QueueStringParam | QueueNumberParam | QueueBooleanParam | QueueObjectParam | QueueArrayParam;
116
+ export interface QueueRecordParam extends StringField, QueueParamBase {
117
+ source: 'record';
118
+ }
119
+ export type QueueParamMetadata = QueueStringParam | QueueNumberParam | QueueBooleanParam | QueueObjectParam | QueueArrayParam | QueueRecordParam;
89
120
  export {};
@@ -42,6 +42,9 @@ const getValueFormBody = (param, record) => {
42
42
  }
43
43
  return JSON.parse(String(value))?.[param.name];
44
44
  };
45
+ const getValueFromRecord = (param, record) => {
46
+ return record[param.name];
47
+ };
45
48
  const argumentParser = {
46
49
  [common_1.LambdaArgumentTypes.event]: ({ event, methodName, target }) => {
47
50
  const queueEvent = event;
@@ -57,10 +60,15 @@ const argumentParser = {
57
60
  continue;
58
61
  }
59
62
  for (const param of paramsByHandler.properties) {
60
- attributes[param.destinationName] =
61
- param.source === 'attribute'
62
- ? getValueFromAttribute(param, record)
63
- : getValueFormBody(param, record);
63
+ if (param.source === 'attribute') {
64
+ attributes[param.destinationName] = getValueFromAttribute(param, record);
65
+ }
66
+ else if (param.source === 'record') {
67
+ attributes[param.destinationName] = getValueFromRecord(param, record);
68
+ }
69
+ else {
70
+ attributes[param.destinationName] = getValueFormBody(param, record);
71
+ }
64
72
  }
65
73
  data.push(attributes);
66
74
  }
@@ -138,6 +138,10 @@ export interface ExternalQueueProps extends SourceMappingProps {
138
138
  *
139
139
  */
140
140
  queueName: string | ((props: GetResourceProps) => string);
141
+ /**
142
+ * Lambda configuration for processing messages from this queue.
143
+ */
144
+ lambda?: LambdaProps;
141
145
  }
142
146
  export interface InternalFifoProps extends InternalStandardProps {
143
147
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lafken/queue",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
4
  "private": false,
5
5
  "description": "Define SQS queues and consumers using TypeScript decorators - automatic infrastructure generation with Lafken",
6
6
  "keywords": [
@@ -54,7 +54,7 @@
54
54
  "dependencies": {
55
55
  "@aws-sdk/client-sqs": "^3.1045.0",
56
56
  "reflect-metadata": "^0.2.2",
57
- "@lafken/resolver": "0.12.8"
57
+ "@lafken/resolver": "0.12.10"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@cdktn/provider-aws": "^24.0.0",
@@ -68,13 +68,13 @@
68
68
  "typescript": "6.0.3",
69
69
  "unplugin-swc": "^1.5.9",
70
70
  "vitest": "^4.1.5",
71
- "@lafken/common": "0.12.8"
71
+ "@lafken/common": "0.12.10"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@cdktn/provider-aws": ">=23.0.0",
75
75
  "cdktn": ">=0.22.0",
76
76
  "constructs": "^10.4.5",
77
- "@lafken/common": "0.12.8"
77
+ "@lafken/common": "0.12.10"
78
78
  },
79
79
  "engines": {
80
80
  "node": ">=20.19"