@friggframework/core 2.0.0--canary.651.dadf4d6.0 → 2.0.0--canary.654.5821f44.0
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 +14 -62
- package/README.md +19 -22
- package/core/CLAUDE.md +25 -12
- package/core/Worker.js +31 -37
- package/core/create-handler.js +96 -197
- package/core/index.js +8 -1
- package/core/invocation-scope.js +224 -0
- package/core/secrets-to-env.js +7 -6
- package/database/config.js +0 -7
- package/database/documentdb-encryption-service.js +2 -6
- package/database/documentdb-utils.js +1 -2
- package/database/encryption/README.md +5 -21
- package/database/encryption/encryption-schema-registry.js +3 -0
- package/database/encryption/prisma-encryption-extension.js +9 -20
- package/errors/fetch-error.js +48 -65
- package/handlers/app-definition-loader.js +53 -2
- package/handlers/app-handler-helpers.js +18 -4
- package/handlers/backend-utils.js +0 -5
- package/handlers/database-migration-handler.js +10 -1
- package/handlers/routers/websocket.js +26 -6
- 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/repositories/integration-mapping-repository-documentdb.js +0 -53
- package/integrations/repositories/integration-mapping-repository-interface.js +0 -48
- package/integrations/repositories/integration-mapping-repository-mongo.js +0 -72
- package/integrations/repositories/integration-mapping-repository-postgres.js +0 -118
- 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 +92 -0
- package/logs/debug-shims.js +46 -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 +142 -0
- package/logs/logger.js +132 -42
- package/logs/record.js +231 -0
- package/logs/redact.js +333 -0
- package/logs/serialize.js +322 -0
- package/logs/sinks.js +99 -0
- package/logs/summarize-event.js +162 -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/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 +8 -40
- package/database/encryption/integration-mapping-encryption.js +0 -97
- package/integrations/repositories/integration-mapping-query-pipeline.js +0 -152
- package/integrations/repositories/integration-mapping-query.js +0 -193
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
|
@@ -211,56 +211,7 @@ packages/core/
|
|
|
211
211
|
- `integration-repository-factory.js` - Creates database-specific repositories
|
|
212
212
|
- `integration-repository-mongo.js` - MongoDB implementation
|
|
213
213
|
- `integration-repository-postgres.js` - PostgreSQL implementation
|
|
214
|
-
- `integration-mapping-repository-*.js` - Mapping data persistence
|
|
215
|
-
`queryMappings(integrationId, { where, orderBy, skip, take, omit })`
|
|
216
|
-
returns `{ mappings, total }`: one filtered, ordered page of an
|
|
217
|
-
integration's mappings, for callers that cannot load every row through
|
|
218
|
-
`findMappingsByIntegration`. `where` is an array of ANDed conditions
|
|
219
|
-
(at most 20); an entry may be `{ anyOf: [...] }`, ORed, one level deep.
|
|
220
|
-
Conditions are `{ path: 'mapping.<segment>...', op: 'exists' | 'notExists' }`
|
|
221
|
-
(JSON null counts as absent), `{ path: 'mapping.…', op: 'in', value: string[] }`
|
|
222
|
-
(1–500 strings) and `{ path: 'sourceId', op: 'notStartsWith', value }`
|
|
223
|
-
(a NULL sourceId matches). `orderBy` is `{ path: 'mapping.…', direction:
|
|
224
|
-
'asc' | 'desc' }`, nulls last, ties broken by id in the same direction;
|
|
225
|
-
without it rows come in id order. `take` is 1–500. `omit` lists top-level
|
|
226
|
-
mapping keys to leave out of the rows; never write such rows back. Path
|
|
227
|
-
segments must match `^[A-Za-z_][A-Za-z0-9_]*$`. The PostgreSQL, MongoDB and
|
|
228
|
-
DocumentDB adapters give the same pages and totals;
|
|
229
|
-
`integration-mapping-repository-query-parity.test.js` checks that against
|
|
230
|
-
real databases when `QUERY_MAPPINGS_PARITY_MONGO_URL` /
|
|
231
|
-
`QUERY_MAPPINGS_PARITY_POSTGRES_URL` are set. Two orderings still differ:
|
|
232
|
-
strings compare by the database collation on PostgreSQL and by code point
|
|
233
|
-
on MongoDB and DocumentDB, and arrays or objects at the sort path order
|
|
234
|
-
among themselves only on PostgreSQL (the other adapters fall back to id).
|
|
235
|
-
The legacy `IntegrationMappingRepository` inherits the port's
|
|
236
|
-
"not supported by this database adapter yet" error. Every adapter refuses
|
|
237
|
-
to run while field-level encryption still encrypts
|
|
238
|
-
`IntegrationMapping.mapping` on write (see `database/encryption/README.md`),
|
|
239
|
-
and returns rows decrypted like `findMappingsByIntegration`. PostgreSQL and
|
|
240
|
-
MongoDB decrypt through `decryptQueriedMappings`
|
|
241
|
-
(`database/encryption/integration-mapping-encryption.js`), a no-op while
|
|
242
|
-
encryption is off or lists no `IntegrationMapping` field besides `mapping`.
|
|
243
|
-
Validation lives in `integration-mapping-query.js`; a new operator is one
|
|
244
|
-
entry in its `OPERATORS` table, one in the Postgres adapter's
|
|
245
|
-
`CONDITION_SQL` and one in `CONDITION_EXPRESSIONS` in
|
|
246
|
-
`integration-mapping-query-pipeline.js`, the aggregation stages both
|
|
247
|
-
MongoDB-protocol adapters share. Those stages may only use what Amazon
|
|
248
|
-
DocumentDB 4.0 and 5.0 support (no `$facet`, `$getField`, `$set` or
|
|
249
|
-
`$unset`).
|
|
250
|
-
**Cost**: PostgreSQL answers in one SQL statement, which reads every row
|
|
251
|
-
of the integration and evaluates the JSON paths per row, because no JSON
|
|
252
|
-
index exists. With ~4 KB mappings on PostgreSQL 16 that is about 0.3 s per
|
|
253
|
-
call at 10⁴ rows per integration and 2.5–3 s at 10⁵. MongoDB answers in one
|
|
254
|
-
aggregate: the `integrationId` index narrows `$match` to the integration,
|
|
255
|
-
the `$expr` is then evaluated per document, and `$facet` returns the page
|
|
256
|
-
with its `$count`, so a page (after `omit`) must fit the 16 MB document
|
|
257
|
-
limit. DocumentDB has no `$facet`: the page and the count are two
|
|
258
|
-
concurrent aggregates, so the total does not come from the same snapshot
|
|
259
|
-
as the page. Both sort in memory on a computed key (`allowDiskUse`). With
|
|
260
|
-
~4 KB mappings on MongoDB 7 that is about 0.05–0.1 s per call at 10⁴ rows
|
|
261
|
-
per integration and 0.5–0.7 s at 10⁵ (the DocumentDB pipeline, run on
|
|
262
|
-
MongoDB, 0.6–0.8 s). On every adapter a deep offset or an empty page past
|
|
263
|
-
the end costs about the same as the first page.
|
|
214
|
+
- `integration-mapping-repository-*.js` - Mapping data persistence
|
|
264
215
|
- `process-repository-*.js` - Process (long-running job) persistence.
|
|
265
216
|
Implements `applyProcessUpdate(processId, ops)` — a race-safe alternative
|
|
266
217
|
to `update(id, patch)` that routes increments, sets, and bounded-array
|
|
@@ -486,22 +437,24 @@ if (!userId) {
|
|
|
486
437
|
|
|
487
438
|
### 9. Logging System (`/logs`)
|
|
488
439
|
|
|
489
|
-
**Purpose**:
|
|
440
|
+
**Purpose**: One redacted JSON record per line to stdout (ADR-048). See `docs/guides/LOGGING.md`.
|
|
490
441
|
|
|
491
|
-
**
|
|
492
|
-
- `
|
|
493
|
-
- `
|
|
494
|
-
- `
|
|
442
|
+
**Rules**:
|
|
443
|
+
- `logs/` is a leaf: it requires only Node built-ins and sibling `logs/` files.
|
|
444
|
+
- Integrations and API modules use `this.logger`; core uses one `getLogger('frigg.<area>')` per module.
|
|
445
|
+
- 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.
|
|
446
|
+
- Log an error one time, at the boundary (`createHandler`, the express middleware, `Worker.run`, the DLQ processor). Inner code throws with `cause`.
|
|
447
|
+
- Use `statusCode`, not `status` (reserved key). `body`, `payload`, `response` are dropped at INFO and above.
|
|
448
|
+
- Tests assert on `createMemorySink()` records by `eventName`, not on console spies.
|
|
449
|
+
- `debug`, `initDebugLog`, `flushDebugLog` are deprecated shims.
|
|
495
450
|
|
|
496
451
|
**Usage**:
|
|
497
452
|
|
|
498
453
|
```javascript
|
|
499
|
-
const {
|
|
454
|
+
const { getLogger } = require('../logs');
|
|
455
|
+
const log = getLogger('frigg.core.sync');
|
|
500
456
|
|
|
501
|
-
|
|
502
|
-
debug('Processing request', { userId, action });
|
|
503
|
-
// ... your code ...
|
|
504
|
-
flushDebugLog(); // On error
|
|
457
|
+
log.warn('Sync skipped', { eventName: 'frigg.core.sync.skipped', processId });
|
|
505
458
|
```
|
|
506
459
|
|
|
507
460
|
### 10. Lambda Utilities (`/lambda`)
|
|
@@ -709,8 +662,7 @@ Use test doubles from `@friggframework/test` package for consistent mocking.
|
|
|
709
662
|
### Optional
|
|
710
663
|
|
|
711
664
|
- `SECRET_ARN` - AWS Secrets Manager ARN for auto-injection
|
|
712
|
-
- `
|
|
713
|
-
- `LOG_LEVEL` - Logging level (debug, info, warn, error)
|
|
665
|
+
- `FRIGG_LOG_LEVEL` - Minimum log level (`TRACE` … `FATAL`, default `INFO`; `DEBUG` on local runs)
|
|
714
666
|
|
|
715
667
|
## Version Information
|
|
716
668
|
|
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
|
|
@@ -129,7 +129,8 @@ class MyIntegration extends Delegate {
|
|
|
129
129
|
### Lambda Handler Lifecycle
|
|
130
130
|
1. **Pre-Execution Setup**:
|
|
131
131
|
```javascript
|
|
132
|
-
|
|
132
|
+
runInvocationScope({ requestId, handlerName, method, route, invocation }, …); // Logger scope for the invocation
|
|
133
|
+
log.info('Handler invoked', { eventName: 'frigg.handler.invoked' });
|
|
133
134
|
await secretsToEnv(); // Secrets Manager injection
|
|
134
135
|
await parametersToEnv(); // SSM Parameter Store fetch (only when SSM_PARAMETER_PREFIX + FRIGG_SSM_OFFLOADED_KEYS are set)
|
|
135
136
|
context.callbackWaitsForEmptyEventLoop = false; // Connection pooling
|
|
@@ -149,12 +150,20 @@ class MyIntegration extends Delegate {
|
|
|
149
150
|
|
|
150
151
|
4. **Error Handling & Cleanup**:
|
|
151
152
|
```javascript
|
|
152
|
-
|
|
153
|
-
//
|
|
153
|
+
// One record at the boundary: WARN frigg.handler.rejected (client-safe),
|
|
154
|
+
// ERROR frigg.handler.failed, or ERROR frigg.handler.halted (halt, no retry).
|
|
155
|
+
// Sanitized error response for user-facing endpoints; server-to-server
|
|
156
|
+
// errors are rethrown as a sanitized surrogate (toSanitizedSurrogate).
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
5. **Flush** (in `runInvocationScope`'s `finally`):
|
|
160
|
+
```javascript
|
|
161
|
+
// Usage rollup first (unbounded), then telemetry + log sinks in parallel
|
|
162
|
+
// under one deadline: min(flushTimeoutMs, remaining time - 50 ms).
|
|
154
163
|
```
|
|
155
164
|
|
|
156
165
|
### SQS Job Processing Lifecycle
|
|
157
|
-
1. **Batch Processing**: Process all records in `event.Records` sequentially
|
|
166
|
+
1. **Batch Processing**: Process all records in `event.Records` sequentially, each inside `runMessageScope` (adds `messageId`, `receiveCount`, `processId`, `integrationId`, `integrationEvent` to its records)
|
|
158
167
|
2. **Message Parsing**: JSON.parse message body for parameters
|
|
159
168
|
3. **Validation**: Run custom validation on parsed parameters
|
|
160
169
|
4. **Execution**: Call `_run()` method with validated parameters
|
|
@@ -205,7 +214,9 @@ const handler = createHandler({
|
|
|
205
214
|
- Logs full error details internally
|
|
206
215
|
|
|
207
216
|
2. **Server-to-Server Errors**: `isUserFacingResponse: false`
|
|
208
|
-
-
|
|
217
|
+
- Logs one ERROR, then rethrows a sanitized surrogate for AWS handling:
|
|
218
|
+
a fresh `Error` with the sanitized `name`, `message`, `stack`, plus
|
|
219
|
+
`statusCode` and `code`. `instanceof` checks and custom properties are gone
|
|
209
220
|
- Used for SQS, SNS, and internal API calls
|
|
210
221
|
- Enables proper retry mechanisms
|
|
211
222
|
|
|
@@ -214,12 +225,14 @@ const handler = createHandler({
|
|
|
214
225
|
- Prevents infinite retries for known issues
|
|
215
226
|
- Used for graceful degradation scenarios
|
|
216
227
|
|
|
217
|
-
###
|
|
228
|
+
### Logging Strategy
|
|
218
229
|
```javascript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
230
|
+
const { getLogger } = require('../logs');
|
|
231
|
+
const log = getLogger('frigg.core.sync');
|
|
232
|
+
log.info('Sync started', { eventName: 'frigg.core.sync.started', processId });
|
|
233
|
+
// Throw with cause; the boundary logs the error one time.
|
|
222
234
|
```
|
|
235
|
+
See `docs/guides/LOGGING.md`.
|
|
223
236
|
|
|
224
237
|
## Integration Development Patterns
|
|
225
238
|
|
|
@@ -646,7 +659,7 @@ describe('Health Handler', () => {
|
|
|
646
659
|
const { createHandler } = require('@friggframework/core/core');
|
|
647
660
|
|
|
648
661
|
const testHandler = createHandler({
|
|
649
|
-
isUserFacingResponse: false, //
|
|
662
|
+
isUserFacingResponse: false, // Rethrows a sanitized surrogate (name, message, statusCode, code; not the original instance)
|
|
650
663
|
shouldUseDatabase: false, // Mock/skip DB in tests
|
|
651
664
|
method: yourTestMethod
|
|
652
665
|
});
|
package/core/Worker.js
CHANGED
|
@@ -2,8 +2,11 @@ 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 { runMessageScope } = require('./invocation-scope');
|
|
6
|
+
const { getLogger } = require('../logs');
|
|
5
7
|
|
|
6
8
|
const sqs = new SQSClient({ region: process.env.AWS_REGION });
|
|
9
|
+
const log = getLogger('frigg.worker');
|
|
7
10
|
|
|
8
11
|
class Worker {
|
|
9
12
|
async getQueueURL(params) {
|
|
@@ -25,45 +28,36 @@ class Worker {
|
|
|
25
28
|
);
|
|
26
29
|
|
|
27
30
|
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,
|
|
31
|
+
await runMessageScope(record, async () => {
|
|
32
|
+
// messageId, receiveCount and the event come from the scope.
|
|
33
|
+
log.debug('Record started', { eventName: 'frigg.worker.record_started' });
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const runParams = JSON.parse(record.body);
|
|
37
|
+
this._validateParams(runParams);
|
|
38
|
+
await this._run(runParams, context);
|
|
39
|
+
log.debug('Record succeeded', { eventName: 'frigg.worker.record_succeeded' });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.isHaltError) {
|
|
42
|
+
// HaltError means "discard this message, don't retry".
|
|
43
|
+
// Treat as success so SQS deletes it from the queue.
|
|
44
|
+
// Logged explicitly — silent discards made prod debugging
|
|
45
|
+
// extremely hard; keep this visible.
|
|
46
|
+
log.error('Record halted (discarded, no retry)', {
|
|
47
|
+
eventName: 'frigg.worker.record_halted',
|
|
48
|
+
statusCode: error.statusCode,
|
|
49
|
+
error,
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// The message goes back to SQS, so WARN (ADR-048 §4).
|
|
54
|
+
log.warn('Record failed, returned for retry', {
|
|
55
|
+
eventName: 'frigg.worker.record_failed',
|
|
56
|
+
error,
|
|
61
57
|
});
|
|
62
|
-
|
|
58
|
+
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
63
59
|
}
|
|
64
|
-
|
|
65
|
-
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
66
|
-
}
|
|
60
|
+
});
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
if (batchItemFailures.length > 0) {
|