@friggframework/core 2.0.0-next.74 → 2.0.0-next.76

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/core/Worker.js CHANGED
@@ -18,12 +18,25 @@ class Worker {
18
18
 
19
19
  async run(params, context = {}) {
20
20
  const records = get(params, 'Records');
21
+ const batchItemFailures = [];
21
22
 
22
23
  for (const record of records) {
23
- const runParams = JSON.parse(record.body);
24
- this._validateParams(runParams);
25
- await this._run(runParams, context);
24
+ try {
25
+ const runParams = JSON.parse(record.body);
26
+ this._validateParams(runParams);
27
+ await this._run(runParams, context);
28
+ } catch (error) {
29
+ if (error.isHaltError) {
30
+ // HaltError means "discard this message, don't retry".
31
+ // Treat as success so SQS deletes it from the queue.
32
+ continue;
33
+ }
34
+ console.error(`[Worker] Failed to process record ${record.messageId}:`, error);
35
+ batchItemFailures.push({ itemIdentifier: record.messageId });
36
+ }
26
37
  }
38
+
39
+ return { batchItemFailures };
27
40
  }
28
41
 
29
42
  async _run(params, context = {}) {
@@ -88,9 +88,16 @@ const loadIntegrationForWebhook = async (integrationId) => {
88
88
  moduleFactory,
89
89
  });
90
90
 
91
- const integrationRecord = await integrationRepository.findIntegrationById(
92
- integrationId
93
- );
91
+ let integrationRecord;
92
+ try {
93
+ integrationRecord =
94
+ await integrationRepository.findIntegrationById(integrationId);
95
+ } catch (error) {
96
+ if (error.message?.includes('not found')) {
97
+ return null;
98
+ }
99
+ throw error;
100
+ }
94
101
 
95
102
  const instance = await getIntegrationInstance.execute(
96
103
  integrationId,
@@ -151,6 +158,12 @@ const createQueueWorker = (integrationClass) => {
151
158
  integrationInstance = await loadIntegrationForWebhook(
152
159
  params.data.integrationId
153
160
  );
161
+ if (!integrationInstance) {
162
+ console.warn(
163
+ `[${integrationClass.Definition.name}] Integration ${params.data.integrationId} no longer exists. Discarding ${params.event} message.`
164
+ );
165
+ return;
166
+ }
154
167
  } else {
155
168
  // Instantiates a DRY integration class without database records.
156
169
  // There will be cases where we need to use helpers that the api modules can export.
@@ -0,0 +1,63 @@
1
+ /**
2
+ * DLQ Processor — logs failed messages from the InternalErrorQueue
3
+ * with structured context for monitoring and debugging.
4
+ *
5
+ * This handler MUST NOT throw. If it throws, the message goes back to
6
+ * the DLQ and creates an infinite loop. All errors are caught and logged.
7
+ */
8
+
9
+ function extractQueueName(eventSourceARN) {
10
+ if (!eventSourceARN) return 'UNKNOWN';
11
+ const parts = eventSourceARN.split(':');
12
+ return parts[parts.length - 1] || 'UNKNOWN';
13
+ }
14
+
15
+ function parseMessageBody(body) {
16
+ try {
17
+ const parsed = JSON.parse(body);
18
+ return {
19
+ event: parsed.event || 'UNKNOWN',
20
+ integrationId: parsed.data?.integrationId || null,
21
+ processId: parsed.data?.processId || null,
22
+ data: parsed.data,
23
+ };
24
+ } catch (error) {
25
+ console.warn('[DLQ] Failed to parse message body', { error: error.message, body });
26
+ return {
27
+ event: 'UNKNOWN',
28
+ integrationId: null,
29
+ processId: null,
30
+ rawBody: body,
31
+ };
32
+ }
33
+ }
34
+
35
+ async function dlqProcessor(event) {
36
+ if (!event?.Records?.length) return { batchItemFailures: [] };
37
+
38
+ for (const record of event.Records) {
39
+ try {
40
+ const parsed = parseMessageBody(record.body);
41
+
42
+ console.error('[DLQ] Failed message', {
43
+ messageId: record.messageId,
44
+ event: parsed.event,
45
+ integrationId: parsed.integrationId,
46
+ processId: parsed.processId,
47
+ receiveCount: record.attributes?.ApproximateReceiveCount,
48
+ sentTimestamp: record.attributes?.SentTimestamp,
49
+ sourceQueue: extractQueueName(record.eventSourceARN),
50
+ ...(parsed.rawBody !== undefined && { rawBody: parsed.rawBody }),
51
+ });
52
+ } catch (error) {
53
+ console.error('[DLQ] Error processing DLQ record', {
54
+ messageId: record.messageId,
55
+ error: error.message,
56
+ });
57
+ }
58
+ }
59
+
60
+ return { batchItemFailures: [] };
61
+ }
62
+
63
+ module.exports = { dlqProcessor };
@@ -14,11 +14,7 @@ integrationClasses.forEach((IntegrationClass) => {
14
14
  isUserFacingResponse: false,
15
15
  method: async (event, context) => {
16
16
  const worker = new defaultQueueWorker();
17
- await worker.run(event, context);
18
- return {
19
- message: 'Successfully processed the Generic Queue Worker',
20
- input: event,
21
- };
17
+ return await worker.run(event, context);
22
18
  },
23
19
  }),
24
20
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.74",
4
+ "version": "2.0.0-next.76",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -38,9 +38,9 @@
38
38
  }
39
39
  },
40
40
  "devDependencies": {
41
- "@friggframework/eslint-config": "2.0.0-next.74",
42
- "@friggframework/prettier-config": "2.0.0-next.74",
43
- "@friggframework/test": "2.0.0-next.74",
41
+ "@friggframework/eslint-config": "2.0.0-next.76",
42
+ "@friggframework/prettier-config": "2.0.0-next.76",
43
+ "@friggframework/test": "2.0.0-next.76",
44
44
  "@prisma/client": "^6.17.0",
45
45
  "@types/lodash": "4.17.15",
46
46
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -80,5 +80,5 @@
80
80
  "publishConfig": {
81
81
  "access": "public"
82
82
  },
83
- "gitHead": "8f935e0715e46299f5bc720d2ce8551da5d5705a"
83
+ "gitHead": "5f03113234df57301443502f2751f1864dd44e73"
84
84
  }
@@ -26,10 +26,18 @@ declare module "@friggframework/core" {
26
26
  ): Promise<any>;
27
27
  }
28
28
 
29
+ export interface BatchItemFailure {
30
+ itemIdentifier: string;
31
+ }
32
+
33
+ export interface BatchItemFailuresResponse {
34
+ batchItemFailures: BatchItemFailure[];
35
+ }
36
+
29
37
  export class Worker implements IWorker {
30
38
  getQueueURL(params: GetQueueURLParams): Promise<string | undefined>;
31
39
 
32
- run(params: { Records: any }): Promise<void>;
40
+ run(params: { Records: any }, context?: object): Promise<BatchItemFailuresResponse>;
33
41
 
34
42
  send(params: object & { QueueUrl: any }, delay?: number): Promise<string>;
35
43
 
@@ -38,7 +46,7 @@ declare module "@friggframework/core" {
38
46
 
39
47
  interface IWorker {
40
48
  getQueueURL(params: GetQueueURLParams): Promise<string | undefined>;
41
- run(params: { Records: any }): Promise<void>;
49
+ run(params: { Records: any }, context?: object): Promise<BatchItemFailuresResponse>;
42
50
  send(params: object & { QueueUrl: any }, delay?: number): Promise<string>;
43
51
  sendAsyncSQSMessage(params: SendSQSMessageParams): Promise<string>;
44
52
  }