@friggframework/core 2.0.0--canary.652.60de836.0 → 2.0.0--canary.653.af34bd7.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/CLAUDE.md +38 -0
- package/core/CLAUDE.md +2 -1
- package/core/Worker.js +2 -1
- package/handlers/backend-utils.js +2 -1
- package/handlers/integration-event-dispatcher.js +2 -2
- package/package.json +5 -5
- package/queues/queue-delivery.js +41 -0
package/CLAUDE.md
CHANGED
|
@@ -414,6 +414,43 @@ const decrypted = await cryptor.decrypt(encrypted);
|
|
|
414
414
|
- `integration-event-dispatcher.js` - Routes events to integration handlers
|
|
415
415
|
- Supports lifecycle events and user actions
|
|
416
416
|
|
|
417
|
+
**Queue handler delivery**: a handler dispatched from the integration queue
|
|
418
|
+
(any `this.events` entry reached through SQS, including `ON_WEBHOOK` and
|
|
419
|
+
scheduled jobs) receives `{ data, context, delivery }`. HTTP-dispatched
|
|
420
|
+
handlers (`{ req, res, next }`) and `this.on` events do not.
|
|
421
|
+
|
|
422
|
+
| Field | Type | Value |
|
|
423
|
+
|---|---|---|
|
|
424
|
+
| `delivery.receiveCount` | `number \| undefined` | SQS `ApproximateReceiveCount` of this delivery |
|
|
425
|
+
| `delivery.maxReceiveCount` | `number \| undefined` | Receives allowed before SQS moves the message to the DLQ (`FRIGG_QUEUE_MAX_RECEIVE_COUNT`) |
|
|
426
|
+
| `delivery.isLastAttempt` | `boolean` | `true` only when both counts are known and `receiveCount >= maxReceiveCount` |
|
|
427
|
+
|
|
428
|
+
`isLastAttempt` is `false` when either count is unknown: a local or non-SQS
|
|
429
|
+
invocation, or a queue whose redrive policy the stack does not own
|
|
430
|
+
(`ownership.queue: 'external'`). The value is information only: core still
|
|
431
|
+
rethrows retryable errors and discards halt errors (4xx except 408/429).
|
|
432
|
+
|
|
433
|
+
Use it to end a run or count lost work on the final try: when a retryable
|
|
434
|
+
error (429, 5xx, network) is about to be rethrown and `isLastAttempt` is
|
|
435
|
+
`true`, the message goes to the DLQ next, so mark the run failed or count the
|
|
436
|
+
message's records as failed before rethrowing.
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
async processBatch({ data, delivery }) {
|
|
440
|
+
try {
|
|
441
|
+
await this.syncPage(data);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (delivery?.isLastAttempt) await this.failRun(data.processId, error);
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
The single source of the max receive count is
|
|
450
|
+
`INTEGRATION_QUEUE_MAX_RECEIVE_COUNT` in `queues/queue-delivery.js`. The
|
|
451
|
+
devtools integration builder uses it for the queue's `RedrivePolicy` and sets
|
|
452
|
+
it as `FRIGG_QUEUE_MAX_RECEIVE_COUNT` on the queue worker function.
|
|
453
|
+
|
|
417
454
|
### 8. Error Handling (`/errors`)
|
|
418
455
|
|
|
419
456
|
**Purpose**: Standardized error types with proper HTTP semantics.
|
|
@@ -662,6 +699,7 @@ Use test doubles from `@friggframework/test` package for consistent mocking.
|
|
|
662
699
|
- `SECRET_ARN` - AWS Secrets Manager ARN for auto-injection
|
|
663
700
|
- `DEBUG` - Debug logging pattern
|
|
664
701
|
- `LOG_LEVEL` - Logging level (debug, info, warn, error)
|
|
702
|
+
- `FRIGG_QUEUE_MAX_RECEIVE_COUNT` - Set by devtools on integration queue workers whose queue the stack owns; feeds `delivery.maxReceiveCount`
|
|
665
703
|
|
|
666
704
|
## Version Information
|
|
667
705
|
|
package/core/CLAUDE.md
CHANGED
|
@@ -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 } (see ../CLAUDE.md "Queue handler delivery")
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
_validateParams(params) {
|
package/core/Worker.js
CHANGED
|
@@ -2,6 +2,7 @@ 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');
|
|
5
6
|
|
|
6
7
|
const sqs = new SQSClient({ region: process.env.AWS_REGION });
|
|
7
8
|
|
|
@@ -42,7 +43,7 @@ class Worker {
|
|
|
42
43
|
try {
|
|
43
44
|
const runParams = JSON.parse(record.body);
|
|
44
45
|
this._validateParams(runParams);
|
|
45
|
-
await this._run(runParams, context);
|
|
46
|
+
await this._run(runParams, context, readQueueDelivery(record));
|
|
46
47
|
console.log(`[Worker] record success`, {
|
|
47
48
|
messageId: record.messageId,
|
|
48
49
|
event: runParams?.event,
|
|
@@ -164,7 +164,7 @@ const createQueueWorker = (integrationClass) => {
|
|
|
164
164
|
const integrationName = integrationClass.Definition.name;
|
|
165
165
|
|
|
166
166
|
class QueueWorker extends Worker {
|
|
167
|
-
async _run(params, context) {
|
|
167
|
+
async _run(params, context, delivery) {
|
|
168
168
|
const logCtx = {
|
|
169
169
|
integration: integrationName,
|
|
170
170
|
event: params.event,
|
|
@@ -254,6 +254,7 @@ const createQueueWorker = (integrationClass) => {
|
|
|
254
254
|
event: params.event,
|
|
255
255
|
data: params.data,
|
|
256
256
|
context: context,
|
|
257
|
+
delivery,
|
|
257
258
|
});
|
|
258
259
|
console.log(
|
|
259
260
|
`[QueueWorker] ${params.event} dispatched ok`,
|
|
@@ -18,9 +18,9 @@ class IntegrationEventDispatcher {
|
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async dispatchJob({ event, data, context }) {
|
|
21
|
+
async dispatchJob({ event, data, context, delivery }) {
|
|
22
22
|
return this._dispatch(event, (instance, handler) =>
|
|
23
|
-
handler.call(instance, { data, context })
|
|
23
|
+
handler.call(instance, { data, context, delivery })
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
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--canary.
|
|
4
|
+
"version": "2.0.0--canary.653.af34bd7.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
52
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
53
|
-
"@friggframework/test": "2.0.0--canary.
|
|
51
|
+
"@friggframework/eslint-config": "2.0.0--canary.653.af34bd7.0",
|
|
52
|
+
"@friggframework/prettier-config": "2.0.0--canary.653.af34bd7.0",
|
|
53
|
+
"@friggframework/test": "2.0.0--canary.653.af34bd7.0",
|
|
54
54
|
"@prisma/client": "^6.19.3",
|
|
55
55
|
"@types/lodash": "4.17.15",
|
|
56
56
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"publishConfig": {
|
|
91
91
|
"access": "public"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "af34bd7c677948dcd9f4a7a8dc0a59b038c35c48"
|
|
94
94
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const INTEGRATION_QUEUE_MAX_RECEIVE_COUNT = 3;
|
|
2
|
+
const QUEUE_MAX_RECEIVE_COUNT_ENV = 'FRIGG_QUEUE_MAX_RECEIVE_COUNT';
|
|
3
|
+
|
|
4
|
+
const toPositiveInteger = (value) => {
|
|
5
|
+
const number = Number(value);
|
|
6
|
+
return Number.isInteger(number) && number > 0 ? number : undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} QueueDelivery
|
|
11
|
+
* @property {number|undefined} receiveCount SQS ApproximateReceiveCount of this delivery.
|
|
12
|
+
* @property {number|undefined} maxReceiveCount Receives allowed before the queue dead-letters the message.
|
|
13
|
+
* @property {boolean} isLastAttempt True only when both counts are known and receiveCount >= maxReceiveCount.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Object} record SQS event record
|
|
18
|
+
* @param {Object} [env=process.env]
|
|
19
|
+
* @returns {QueueDelivery}
|
|
20
|
+
*/
|
|
21
|
+
const readQueueDelivery = (record, env = process.env) => {
|
|
22
|
+
const receiveCount = toPositiveInteger(
|
|
23
|
+
record?.attributes?.ApproximateReceiveCount
|
|
24
|
+
);
|
|
25
|
+
const maxReceiveCount = toPositiveInteger(env[QUEUE_MAX_RECEIVE_COUNT_ENV]);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
receiveCount,
|
|
29
|
+
maxReceiveCount,
|
|
30
|
+
isLastAttempt:
|
|
31
|
+
receiveCount !== undefined &&
|
|
32
|
+
maxReceiveCount !== undefined &&
|
|
33
|
+
receiveCount >= maxReceiveCount,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
INTEGRATION_QUEUE_MAX_RECEIVE_COUNT,
|
|
39
|
+
QUEUE_MAX_RECEIVE_COUNT_ENV,
|
|
40
|
+
readQueueDelivery,
|
|
41
|
+
};
|