@friggframework/core 2.0.0-next.112 → 2.0.0-next.114
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/.eslintrc.json +18 -1
- package/CLAUDE.md +51 -12
- package/README.md +19 -22
- package/core/CLAUDE.md +28 -13
- package/core/Worker.js +32 -37
- package/core/create-handler.js +97 -197
- package/core/index.js +8 -1
- package/core/invocation-scope.js +225 -0
- package/core/secrets-to-env.js +7 -6
- package/database/config.js +0 -7
- package/database/encryption/README.md +5 -1
- package/database/encryption/encryption-schema-registry.js +3 -0
- package/database/encryption/logger.js +5 -2
- package/errors/fetch-error.js +58 -64
- package/handlers/app-definition-loader.js +38 -2
- package/handlers/app-handler-helpers.js +18 -4
- package/handlers/backend-utils.js +10 -13
- package/handlers/database-migration-handler.js +10 -1
- package/handlers/integration-event-dispatcher.js +2 -2
- package/handlers/routers/integration-webhook-routers.js +13 -6
- package/handlers/routers/websocket.js +27 -7
- package/handlers/workers/db-migration.js +12 -1
- package/handlers/workers/dlq-processor.js +64 -38
- package/index.js +19 -1
- package/integrations/integration-base.js +56 -38
- package/integrations/integration-router.js +21 -20
- package/integrations/use-cases/delete-integration-for-user.js +24 -13
- package/jest.config.js +2 -0
- package/logs/.eslintrc.json +26 -1
- package/logs/__fixtures__/events.js +82 -0
- package/logs/__fixtures__/matchers.js +45 -0
- package/logs/__fixtures__/secrets.js +59 -0
- package/logs/__fixtures__/vectors.js +329 -0
- package/logs/__fixtures__/with-env.js +45 -0
- package/logs/context.js +109 -0
- package/logs/contract-keys.js +67 -0
- package/logs/debug-shims.js +46 -0
- package/logs/denied-keys.js +21 -0
- package/logs/index.js +14 -3
- package/logs/jest-logger-setup.js +25 -0
- package/logs/levels.js +71 -0
- package/logs/logger-runtime.js +137 -0
- package/logs/logger.js +134 -42
- package/logs/record.js +276 -0
- package/logs/redact.js +328 -0
- package/logs/serialize.js +329 -0
- package/logs/sinks.js +93 -0
- package/logs/summarize-event.js +163 -0
- package/modules/module.js +32 -19
- package/modules/requester/oauth-2.js +28 -22
- package/modules/requester/requester.js +69 -6
- package/package.json +5 -6
- package/queues/queue-delivery.js +41 -0
- package/queues/queuer-util.js +1 -15
- package/telemetry/bind-telemetry-context.js +4 -0
- package/telemetry/instrument-handler.js +19 -2
- package/telemetry/no-op-telemetry.js +4 -0
- package/telemetry/otel-telemetry.js +25 -6
- package/telemetry/telemetry-context.js +19 -8
- package/telemetry/telemetry-runtime.js +17 -0
- package/telemetry/telemetry-service.js +1 -0
- package/types/errors/index.d.ts +24 -10
- package/types/logs/index.d.ts +61 -2
- package/user/repositories/user-repository-documentdb.js +10 -42
package/.eslintrc.json
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "@friggframework/eslint-config"
|
|
2
|
+
"extends": "@friggframework/eslint-config",
|
|
3
|
+
"rules": {
|
|
4
|
+
"no-restricted-syntax": [
|
|
5
|
+
"warn",
|
|
6
|
+
{
|
|
7
|
+
"selector": "CallExpression[callee.object.object.name='process'][callee.object.property.name=/^(stdout|stderr)$/][callee.property.name='write']",
|
|
8
|
+
"message": "Write through the logger; only logs/sinks.js writes to fd 1 (ADR-048 §14)"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"overrides": [
|
|
13
|
+
{
|
|
14
|
+
"files": ["*.test.js", "database/utils/prisma-runner.js"],
|
|
15
|
+
"rules": {
|
|
16
|
+
"no-restricted-syntax": "off"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
3
20
|
}
|
package/CLAUDE.md
CHANGED
|
@@ -463,6 +463,43 @@ const decrypted = await cryptor.decrypt(encrypted);
|
|
|
463
463
|
- `integration-event-dispatcher.js` - Routes events to integration handlers
|
|
464
464
|
- Supports lifecycle events and user actions
|
|
465
465
|
|
|
466
|
+
**Queue handler delivery**: a handler dispatched from the integration queue
|
|
467
|
+
(any `this.events` entry reached through SQS, including `ON_WEBHOOK` and
|
|
468
|
+
scheduled jobs) receives `{ data, context, delivery }`. HTTP-dispatched
|
|
469
|
+
handlers (`{ req, res, next }`) and `this.on` events do not.
|
|
470
|
+
|
|
471
|
+
| Field | Type | Value |
|
|
472
|
+
|---|---|---|
|
|
473
|
+
| `delivery.receiveCount` | `number \| undefined` | SQS `ApproximateReceiveCount` of this delivery |
|
|
474
|
+
| `delivery.maxReceiveCount` | `number \| undefined` | Receives allowed before SQS moves the message to the DLQ (`FRIGG_QUEUE_MAX_RECEIVE_COUNT`) |
|
|
475
|
+
| `delivery.isLastAttempt` | `boolean` | `true` only when both counts are known and `receiveCount >= maxReceiveCount` |
|
|
476
|
+
|
|
477
|
+
`isLastAttempt` is `false` when either count is unknown: a local or non-SQS
|
|
478
|
+
invocation, or a queue whose redrive policy the stack does not own
|
|
479
|
+
(`ownership.queue: 'external'`). The value is information only: core still
|
|
480
|
+
rethrows retryable errors and discards halt errors (4xx except 408/429).
|
|
481
|
+
|
|
482
|
+
Use it to end a run or count lost work on the final try: when a retryable
|
|
483
|
+
error (429, 5xx, network) is about to be rethrown and `isLastAttempt` is
|
|
484
|
+
`true`, the message goes to the DLQ next, so mark the run failed or count the
|
|
485
|
+
message's records as failed before rethrowing.
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
async processBatch({ data, delivery }) {
|
|
489
|
+
try {
|
|
490
|
+
await this.syncPage(data);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
if (delivery?.isLastAttempt) await this.failRun(data.processId, error);
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
The single source of the max receive count is
|
|
499
|
+
`INTEGRATION_QUEUE_MAX_RECEIVE_COUNT` in `queues/queue-delivery.js`. The
|
|
500
|
+
devtools integration builder uses it for the queue's `RedrivePolicy` and sets
|
|
501
|
+
it as `FRIGG_QUEUE_MAX_RECEIVE_COUNT` on the queue worker function.
|
|
502
|
+
|
|
466
503
|
### 8. Error Handling (`/errors`)
|
|
467
504
|
|
|
468
505
|
**Purpose**: Standardized error types with proper HTTP semantics.
|
|
@@ -486,22 +523,24 @@ if (!userId) {
|
|
|
486
523
|
|
|
487
524
|
### 9. Logging System (`/logs`)
|
|
488
525
|
|
|
489
|
-
**Purpose**:
|
|
526
|
+
**Purpose**: One redacted JSON record per line to stdout (ADR-048). See `docs/guides/LOGGING.md`.
|
|
490
527
|
|
|
491
|
-
**
|
|
492
|
-
- `
|
|
493
|
-
- `
|
|
494
|
-
- `
|
|
528
|
+
**Rules**:
|
|
529
|
+
- `logs/` is a leaf: it requires only Node built-ins and sibling `logs/` files.
|
|
530
|
+
- Integrations and API modules use `this.logger`; core uses one `getLogger('frigg.<area>')` per module.
|
|
531
|
+
- Fixed message text; ids and counts go into fields. `frigg.*` records at WARN and above need an `eventName` (`<logger name>.<action>`); tests fail without it.
|
|
532
|
+
- Log an error one time, at the boundary (`createHandler`, the express middleware, `Worker.run`, the DLQ processor). Inner code throws with `cause`.
|
|
533
|
+
- Use `statusCode`, not `status` (reserved key). `body`, `payload`, `response` are dropped at INFO and above.
|
|
534
|
+
- Tests assert on `createMemorySink()` records by `eventName`, not on console spies.
|
|
535
|
+
- `debug`, `initDebugLog`, `flushDebugLog` are deprecated shims.
|
|
495
536
|
|
|
496
537
|
**Usage**:
|
|
497
538
|
|
|
498
539
|
```javascript
|
|
499
|
-
const {
|
|
540
|
+
const { getLogger } = require('../logs');
|
|
541
|
+
const log = getLogger('frigg.core.sync');
|
|
500
542
|
|
|
501
|
-
|
|
502
|
-
debug('Processing request', { userId, action });
|
|
503
|
-
// ... your code ...
|
|
504
|
-
flushDebugLog(); // On error
|
|
543
|
+
log.warn('Sync skipped', { eventName: 'frigg.core.sync.skipped', processId });
|
|
505
544
|
```
|
|
506
545
|
|
|
507
546
|
### 10. Lambda Utilities (`/lambda`)
|
|
@@ -709,8 +748,8 @@ Use test doubles from `@friggframework/test` package for consistent mocking.
|
|
|
709
748
|
### Optional
|
|
710
749
|
|
|
711
750
|
- `SECRET_ARN` - AWS Secrets Manager ARN for auto-injection
|
|
712
|
-
- `
|
|
713
|
-
- `
|
|
751
|
+
- `FRIGG_LOG_LEVEL` - Minimum log level (`TRACE` … `FATAL`, default `INFO`; `DEBUG` on local runs)
|
|
752
|
+
- `FRIGG_QUEUE_MAX_RECEIVE_COUNT` - Set by devtools on integration queue workers whose queue the stack owns; feeds `delivery.maxReceiveCount`
|
|
714
753
|
|
|
715
754
|
## Version Information
|
|
716
755
|
|
package/README.md
CHANGED
|
@@ -107,10 +107,6 @@ FRIGG_ENCRYPTION_KEY=your-256-bit-encryption-key
|
|
|
107
107
|
AWS_REGION=us-east-1
|
|
108
108
|
AWS_ACCESS_KEY_ID=your-access-key
|
|
109
109
|
AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
110
|
-
|
|
111
|
-
# Logging
|
|
112
|
-
DEBUG=frigg:*
|
|
113
|
-
LOG_LEVEL=info
|
|
114
110
|
```
|
|
115
111
|
|
|
116
112
|
## Core Components
|
|
@@ -255,10 +251,12 @@ const {
|
|
|
255
251
|
// Custom business logic error
|
|
256
252
|
throw new RequiredPropertyError('userId is required');
|
|
257
253
|
|
|
258
|
-
// API communication error
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
254
|
+
// API communication error. The message is built from the method, the
|
|
255
|
+
// sanitized URL and the status; the response body stays off the message.
|
|
256
|
+
throw await FetchError.create({
|
|
257
|
+
resource: 'https://api.example.com/contacts',
|
|
258
|
+
init: { method: 'GET' },
|
|
259
|
+
response,
|
|
262
260
|
});
|
|
263
261
|
|
|
264
262
|
// Base error with custom properties
|
|
@@ -270,25 +268,25 @@ throw new BaseError('Integration failed', {
|
|
|
270
268
|
|
|
271
269
|
### 6. Logging (`/logs`)
|
|
272
270
|
|
|
273
|
-
|
|
271
|
+
One redacted JSON record per line to stdout, with the correlation ids of
|
|
272
|
+
ADR-011. See the [Logging guide](../../docs/guides/LOGGING.md).
|
|
274
273
|
|
|
275
274
|
**Usage:**
|
|
276
275
|
```javascript
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// Log debug information
|
|
283
|
-
debug('Processing webhook payload', {
|
|
284
|
-
eventType: 'contact.created',
|
|
285
|
-
payload: webhookData
|
|
276
|
+
// In an integration or an API module
|
|
277
|
+
this.logger.info('Contact batch started', {
|
|
278
|
+
eventName: 'integration.hubspot.batch_started',
|
|
279
|
+
batchSize: contacts.length,
|
|
286
280
|
});
|
|
287
281
|
|
|
288
|
-
//
|
|
289
|
-
|
|
282
|
+
// In core code
|
|
283
|
+
const { getLogger } = require('@friggframework/core');
|
|
284
|
+
const log = getLogger('frigg.core.sync');
|
|
285
|
+
log.warn('Sync skipped', { eventName: 'frigg.core.sync.skipped', processId });
|
|
290
286
|
```
|
|
291
287
|
|
|
288
|
+
`debug`, `initDebugLog` and `flushDebugLog` are deprecated shims.
|
|
289
|
+
|
|
292
290
|
### 7. User Management (`/user`)
|
|
293
291
|
|
|
294
292
|
Comprehensive user authentication and authorization system supporting both individual and organizational users.
|
|
@@ -1013,8 +1011,7 @@ const {
|
|
|
1013
1011
|
| `MONGO_URI` | Yes | MongoDB connection string |
|
|
1014
1012
|
| `FRIGG_ENCRYPTION_KEY` | Yes | 256-bit encryption key |
|
|
1015
1013
|
| `AWS_REGION` | No | AWS region for services |
|
|
1016
|
-
| `
|
|
1017
|
-
| `LOG_LEVEL` | No | Logging level (debug, info, warn, error) |
|
|
1014
|
+
| `FRIGG_LOG_LEVEL` | No | Minimum log level: `TRACE`, `DEBUG`, `INFO` (default), `WARN`, `ERROR`, `FATAL`. Local runs default to `DEBUG` |
|
|
1018
1015
|
|
|
1019
1016
|
## License
|
|
1020
1017
|
|
package/core/CLAUDE.md
CHANGED
|
@@ -21,13 +21,13 @@ This file provides guidance to Claude Code when working with the Frigg Framework
|
|
|
21
21
|
- **Database Connection Management**: Automatic MongoDB connection with pooling
|
|
22
22
|
- **Secrets Management**: AWS Secrets Manager integration via `SECRET_ARN` env var
|
|
23
23
|
- **Error Sanitization**: Prevents internal details from leaking to end users
|
|
24
|
-
- **
|
|
24
|
+
- **Invocation Scope**: `runInvocationScope` puts `requestId`, `route` and the redacted event summary on every record, then flushes usage, telemetry and sinks
|
|
25
25
|
- **Connection Optimization**: `context.callbackWaitsForEmptyEventLoop = false` for reuse
|
|
26
26
|
|
|
27
27
|
**Handler Configuration Options**:
|
|
28
28
|
```javascript
|
|
29
29
|
const handler = createHandler({
|
|
30
|
-
eventName: 'MyIntegration', //
|
|
30
|
+
eventName: 'MyIntegration', // Logged as handlerName
|
|
31
31
|
isUserFacingResponse: true, // true = sanitize errors, false = pass through
|
|
32
32
|
method: async (event, context) => {}, // Your Lambda function logic
|
|
33
33
|
shouldUseDatabase: true // false = skip MongoDB connection
|
|
@@ -52,9 +52,10 @@ const handler = createHandler({
|
|
|
52
52
|
**Usage Pattern**:
|
|
53
53
|
```javascript
|
|
54
54
|
class MyWorker extends Worker {
|
|
55
|
-
async _run(params, context = {}) {
|
|
55
|
+
async _run(params, context = {}, delivery) {
|
|
56
56
|
// Your job processing logic here
|
|
57
57
|
// params are already JSON.parsed from SQS message body
|
|
58
|
+
// delivery is { receiveCount, maxReceiveCount, isLastAttempt }
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
_validateParams(params) {
|
|
@@ -129,7 +130,8 @@ class MyIntegration extends Delegate {
|
|
|
129
130
|
### Lambda Handler Lifecycle
|
|
130
131
|
1. **Pre-Execution Setup**:
|
|
131
132
|
```javascript
|
|
132
|
-
|
|
133
|
+
runInvocationScope({ requestId, handlerName, method, route, invocation }, …); // Logger scope for the invocation
|
|
134
|
+
log.info('Handler invoked', { eventName: 'frigg.handler.invoked' });
|
|
133
135
|
await secretsToEnv(); // Secrets Manager injection
|
|
134
136
|
await parametersToEnv(); // SSM Parameter Store fetch (only when SSM_PARAMETER_PREFIX + FRIGG_SSM_OFFLOADED_KEYS are set)
|
|
135
137
|
context.callbackWaitsForEmptyEventLoop = false; // Connection pooling
|
|
@@ -149,12 +151,21 @@ class MyIntegration extends Delegate {
|
|
|
149
151
|
|
|
150
152
|
4. **Error Handling & Cleanup**:
|
|
151
153
|
```javascript
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
+
// One record at the boundary: WARN frigg.handler.rejected (client-safe),
|
|
155
|
+
// ERROR frigg.handler.failed, or ERROR frigg.handler.halted (halt, no retry).
|
|
156
|
+
// Sanitized error response for user-facing endpoints; server-to-server
|
|
157
|
+
// errors are rethrown as a sanitized surrogate (toSanitizedSurrogate).
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
5. **Flush** (in `runInvocationScope`'s `finally`):
|
|
161
|
+
```javascript
|
|
162
|
+
// Telemetry + log sinks under one deadline, fixed at flush start:
|
|
163
|
+
// min(flushTimeoutMs, remaining time - 50 ms). The usage rollup runs in
|
|
164
|
+
// parallel with no bound of its own.
|
|
154
165
|
```
|
|
155
166
|
|
|
156
167
|
### SQS Job Processing Lifecycle
|
|
157
|
-
1. **Batch Processing**: Process all records in `event.Records` sequentially
|
|
168
|
+
1. **Batch Processing**: Process all records in `event.Records` sequentially, each inside `runMessageScope` (adds `messageId`, `receiveCount`, `processId`, `integrationId`, `integrationEvent` to its records)
|
|
158
169
|
2. **Message Parsing**: JSON.parse message body for parameters
|
|
159
170
|
3. **Validation**: Run custom validation on parsed parameters
|
|
160
171
|
4. **Execution**: Call `_run()` method with validated parameters
|
|
@@ -205,7 +216,9 @@ const handler = createHandler({
|
|
|
205
216
|
- Logs full error details internally
|
|
206
217
|
|
|
207
218
|
2. **Server-to-Server Errors**: `isUserFacingResponse: false`
|
|
208
|
-
-
|
|
219
|
+
- Logs one ERROR, then rethrows a sanitized surrogate for AWS handling:
|
|
220
|
+
a fresh `Error` with the sanitized `name`, `message`, `stack`, plus
|
|
221
|
+
`statusCode` and `code`. `instanceof` checks and custom properties are gone
|
|
209
222
|
- Used for SQS, SNS, and internal API calls
|
|
210
223
|
- Enables proper retry mechanisms
|
|
211
224
|
|
|
@@ -214,12 +227,14 @@ const handler = createHandler({
|
|
|
214
227
|
- Prevents infinite retries for known issues
|
|
215
228
|
- Used for graceful degradation scenarios
|
|
216
229
|
|
|
217
|
-
###
|
|
230
|
+
### Logging Strategy
|
|
218
231
|
```javascript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
232
|
+
const { getLogger } = require('../logs');
|
|
233
|
+
const log = getLogger('frigg.core.sync');
|
|
234
|
+
log.info('Sync started', { eventName: 'frigg.core.sync.started', processId });
|
|
235
|
+
// Throw with cause; the boundary logs the error one time.
|
|
222
236
|
```
|
|
237
|
+
See `docs/guides/LOGGING.md`.
|
|
223
238
|
|
|
224
239
|
## Integration Development Patterns
|
|
225
240
|
|
|
@@ -646,7 +661,7 @@ describe('Health Handler', () => {
|
|
|
646
661
|
const { createHandler } = require('@friggframework/core/core');
|
|
647
662
|
|
|
648
663
|
const testHandler = createHandler({
|
|
649
|
-
isUserFacingResponse: false, //
|
|
664
|
+
isUserFacingResponse: false, // Rethrows a sanitized surrogate (name, message, statusCode, code; not the original instance)
|
|
650
665
|
shouldUseDatabase: false, // Mock/skip DB in tests
|
|
651
666
|
method: yourTestMethod
|
|
652
667
|
});
|
package/core/Worker.js
CHANGED
|
@@ -2,8 +2,12 @@ const { SQSClient, GetQueueUrlCommand, SendMessageCommand } = require('@aws-sdk/
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const { RequiredPropertyError } = require('../errors');
|
|
4
4
|
const { get } = require('../assertions');
|
|
5
|
+
const { readQueueDelivery } = require('../queues/queue-delivery');
|
|
6
|
+
const { runMessageScope } = require('./invocation-scope');
|
|
7
|
+
const { getLogger } = require('../logs');
|
|
5
8
|
|
|
6
9
|
const sqs = new SQSClient({ region: process.env.AWS_REGION });
|
|
10
|
+
const log = getLogger('frigg.worker');
|
|
7
11
|
|
|
8
12
|
class Worker {
|
|
9
13
|
async getQueueURL(params) {
|
|
@@ -25,45 +29,36 @@ class Worker {
|
|
|
25
29
|
);
|
|
26
30
|
|
|
27
31
|
for (const record of records) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Logged explicitly — silent discards made prod debugging
|
|
55
|
-
// extremely hard; keep this visible.
|
|
56
|
-
console.warn(`[Worker] record halted (discarded, no retry)`, {
|
|
57
|
-
messageId: record.messageId,
|
|
58
|
-
event: parsedEvent,
|
|
59
|
-
reason: error.message,
|
|
60
|
-
statusCode: error.statusCode,
|
|
32
|
+
await runMessageScope(record, async () => {
|
|
33
|
+
// messageId, receiveCount and the event come from the scope.
|
|
34
|
+
log.debug('Record started', { eventName: 'frigg.worker.record_started' });
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const runParams = JSON.parse(record.body);
|
|
38
|
+
this._validateParams(runParams);
|
|
39
|
+
await this._run(runParams, context, readQueueDelivery(record));
|
|
40
|
+
log.debug('Record succeeded', { eventName: 'frigg.worker.record_succeeded' });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.isHaltError) {
|
|
43
|
+
// HaltError means "discard this message, don't retry".
|
|
44
|
+
// Treat as success so SQS deletes it from the queue.
|
|
45
|
+
// Logged explicitly — silent discards made prod debugging
|
|
46
|
+
// extremely hard; keep this visible.
|
|
47
|
+
log.error('Record halted (discarded, no retry)', {
|
|
48
|
+
eventName: 'frigg.worker.record_halted',
|
|
49
|
+
statusCode: error.statusCode,
|
|
50
|
+
error,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// The message goes back to SQS, so WARN (ADR-048 §4).
|
|
55
|
+
log.warn('Record failed, returned for retry', {
|
|
56
|
+
eventName: 'frigg.worker.record_failed',
|
|
57
|
+
error,
|
|
61
58
|
});
|
|
62
|
-
|
|
59
|
+
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
66
|
-
}
|
|
61
|
+
});
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
if (batchItemFailures.length > 0) {
|