@friggframework/core 2.0.0--canary.657.07aea31.0 → 2.0.0--canary.657.e372ef6.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 +13 -8
- package/core/CLAUDE.md +4 -2
- package/core/Worker.js +11 -5
- package/handlers/backend-utils.js +2 -2
- package/integrations/use-cases/clear-rate-limit-wait.js +8 -3
- package/integrations/use-cases/record-rate-limit-wait.js +1 -0
- package/package.json +5 -5
- package/queues/queue-deferral.js +7 -2
- package/types/core/index.d.ts +5 -1
package/CLAUDE.md
CHANGED
|
@@ -521,8 +521,9 @@ minute redelivery. It puts the message back so it runs at `retryAt`:
|
|
|
521
521
|
| Over 900 s, no scheduler | Extends the message's visibility timeout to `retryAt` (12 h less 20 minutes at most). This uses one receive | reported failed |
|
|
522
522
|
|
|
523
523
|
- The new message starts at receive count 1. A deferred body carries
|
|
524
|
-
`_frigg: { deferrals, firstDeferredAt }`; `_frigg` is reserved and
|
|
525
|
-
never sees it.
|
|
524
|
+
`_frigg: { deferrals, firstDeferredAt, deferralId }`; `_frigg` is reserved and
|
|
525
|
+
the handler never sees it. `deferralId` is the SQS message id of the first
|
|
526
|
+
message that was put back, and every later deferral of that work keeps it.
|
|
526
527
|
- The visibility tier sets a timeout of at most 12 h less 20 minutes
|
|
527
528
|
(42,000 s). SQS counts its 12 h maximum from the receive, and an extension
|
|
528
529
|
does not reset it. The margin covers the longest Lambda run (900 s) plus the
|
|
@@ -537,12 +538,16 @@ minute redelivery. It puts the message back so it runs at `retryAt`:
|
|
|
537
538
|
would be lost with the acknowledged message.
|
|
538
539
|
- With a `processId` in the message, the worker writes
|
|
539
540
|
`Process.context.rateLimit = { status: 'WAITING' | 'EXHAUSTED', mechanism,
|
|
540
|
-
retryAt, reason, module, deferrals, updatedAt }`. `EXHAUSTED` means
|
|
541
|
-
the deferrals, or a visibility change came on the last delivery.
|
|
542
|
-
one per process, so it holds the latest known wait: a new wait
|
|
543
|
-
when its `retryAt` is later, a `WAITING` value never hides
|
|
544
|
-
|
|
545
|
-
|
|
541
|
+
retryAt, reason, module, deferrals, deferralId, updatedAt }`. `EXHAUSTED` means
|
|
542
|
+
a cap ended the deferrals, or a visibility change came on the last delivery.
|
|
543
|
+
The value is one per process, so it holds the latest known wait: a new wait
|
|
544
|
+
replaces it only when its `retryAt` is later, and a `WAITING` value never hides
|
|
545
|
+
an `EXHAUSTED` one. After a deferred or redelivered message ran, the worker
|
|
546
|
+
sets a `WAITING` value to `null` at once when that message set it (same
|
|
547
|
+
`deferralId`, or the message's own id for a visibility change), and for any
|
|
548
|
+
other message only when its `retryAt` is past or within 5 s. So a job that
|
|
549
|
+
resumes before its `retryAt` clears its own wait, and cannot clear the wait of
|
|
550
|
+
another message.
|
|
546
551
|
- Delivery stays at least once. The new message is sent before the old one is
|
|
547
552
|
acknowledged, so a crash between the two can run the work twice.
|
|
548
553
|
- Handlers check `delivery.isLastAttempt` first, then rethrow. The delay and
|
package/core/CLAUDE.md
CHANGED
|
@@ -98,8 +98,10 @@ the last attempt they write one ERROR, `record_lost_rate_limited`, with the
|
|
|
98
98
|
details, and no WARN.
|
|
99
99
|
|
|
100
100
|
Subclasses override two no-op hooks to keep run state: `recordRateLimitWait(body,
|
|
101
|
-
error, state)` runs after the message was put back, and `clearRateLimitWait(body
|
|
102
|
-
runs after a deferred or redelivered message succeeded.
|
|
101
|
+
error, state)` runs after the message was put back, and `clearRateLimitWait(body,
|
|
102
|
+
{ deferralId })` runs after a deferred or redelivered message succeeded.
|
|
103
|
+
`state.deferralId` and `deferralId` name the message that was first put back
|
|
104
|
+
(`_frigg.deferralId`, or the message's own id). A failing hook logs
|
|
103
105
|
`frigg.worker.rate_limit_state_failed` and never changes the outcome. The queue
|
|
104
106
|
worker of an integration (`createQueueWorker`) writes `Process.context.rateLimit`
|
|
105
107
|
in them. See `packages/core/CLAUDE.md` section 7.
|
package/core/Worker.js
CHANGED
|
@@ -63,7 +63,7 @@ class Worker {
|
|
|
63
63
|
runParams = JSON.parse(record.body);
|
|
64
64
|
this._validateParams(runParams);
|
|
65
65
|
await this._run(runParams, context, delivery);
|
|
66
|
-
await this._clearDeferredState(runParams, delivery);
|
|
66
|
+
await this._clearDeferredState(record, runParams, delivery);
|
|
67
67
|
log.debug('Record succeeded', { eventName: 'frigg.worker.record_succeeded' });
|
|
68
68
|
} catch (error) {
|
|
69
69
|
let deferralResult;
|
|
@@ -146,7 +146,7 @@ class Worker {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
const now = Date.now();
|
|
149
|
-
const counters = nextDeferral(body, now);
|
|
149
|
+
const counters = nextDeferral(body, now, record.messageId);
|
|
150
150
|
const deferral = {
|
|
151
151
|
record,
|
|
152
152
|
queueName,
|
|
@@ -312,15 +312,21 @@ class Worker {
|
|
|
312
312
|
status,
|
|
313
313
|
mechanism,
|
|
314
314
|
deferrals: counters.deferrals,
|
|
315
|
+
deferralId: counters.deferralId,
|
|
315
316
|
retryAt,
|
|
316
317
|
})
|
|
317
318
|
);
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
async _clearDeferredState(body, delivery) {
|
|
321
|
+
async _clearDeferredState(record, body, delivery) {
|
|
321
322
|
const redelivered = delivery.receiveCount > 1;
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
const { deferrals, deferralId } = readDeferral(body);
|
|
324
|
+
if (deferrals === 0 && !redelivered) return;
|
|
325
|
+
await this._runStateHook('clear', () =>
|
|
326
|
+
this.clearRateLimitWait(body, {
|
|
327
|
+
deferralId: deferralId ?? record.messageId,
|
|
328
|
+
})
|
|
329
|
+
);
|
|
324
330
|
}
|
|
325
331
|
|
|
326
332
|
async _runStateHook(operation, hook) {
|
|
@@ -184,13 +184,13 @@ const createQueueWorker = (integrationClass) => {
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
async clearRateLimitWait(body) {
|
|
187
|
+
async clearRateLimitWait(body, { deferralId } = {}) {
|
|
188
188
|
const processId = body?.data?.processId;
|
|
189
189
|
if (!processId) return;
|
|
190
190
|
const clearRateLimitWait = new ClearRateLimitWait({
|
|
191
191
|
processRepository: createProcessRepository(),
|
|
192
192
|
});
|
|
193
|
-
await clearRateLimitWait.execute(processId);
|
|
193
|
+
await clearRateLimitWait.execute(processId, { deferralId });
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
async _run(params, context, delivery) {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
const CLEAR_RATE_LIMIT_TOLERANCE_MS = 5_000;
|
|
2
2
|
|
|
3
|
+
const isOwnWait = (current, deferralId) =>
|
|
4
|
+
Boolean(deferralId) && current.deferralId === deferralId;
|
|
5
|
+
|
|
6
|
+
const isOver = (current) =>
|
|
7
|
+
Date.parse(current.retryAt) - Date.now() <= CLEAR_RATE_LIMIT_TOLERANCE_MS;
|
|
8
|
+
|
|
3
9
|
class ClearRateLimitWait {
|
|
4
10
|
constructor({ processRepository }) {
|
|
5
11
|
if (!processRepository) {
|
|
@@ -8,12 +14,11 @@ class ClearRateLimitWait {
|
|
|
8
14
|
this.processRepository = processRepository;
|
|
9
15
|
}
|
|
10
16
|
|
|
11
|
-
async execute(processId) {
|
|
17
|
+
async execute(processId, { deferralId } = {}) {
|
|
12
18
|
const process = await this.processRepository.findById(processId);
|
|
13
19
|
const current = process?.context?.rateLimit;
|
|
14
20
|
if (current?.status !== 'WAITING') return;
|
|
15
|
-
|
|
16
|
-
if (remainingMs > CLEAR_RATE_LIMIT_TOLERANCE_MS) return;
|
|
21
|
+
if (!isOwnWait(current, deferralId) && !isOver(current)) return;
|
|
17
22
|
await this.processRepository.applyProcessUpdate(processId, {
|
|
18
23
|
set: { 'context.rateLimit': null },
|
|
19
24
|
});
|
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.657.
|
|
4
|
+
"version": "2.0.0--canary.657.e372ef6.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.657.
|
|
52
|
-
"@friggframework/prettier-config": "2.0.0--canary.657.
|
|
53
|
-
"@friggframework/test": "2.0.0--canary.657.
|
|
51
|
+
"@friggframework/eslint-config": "2.0.0--canary.657.e372ef6.0",
|
|
52
|
+
"@friggframework/prettier-config": "2.0.0--canary.657.e372ef6.0",
|
|
53
|
+
"@friggframework/test": "2.0.0--canary.657.e372ef6.0",
|
|
54
54
|
"@prisma/client": "^6.19.3",
|
|
55
55
|
"@types/lodash": "4.17.15",
|
|
56
56
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -91,5 +91,5 @@
|
|
|
91
91
|
"publishConfig": {
|
|
92
92
|
"access": "public"
|
|
93
93
|
},
|
|
94
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "e372ef6b74e5bb76673a99181fed6d54d097dc99"
|
|
95
95
|
}
|
package/queues/queue-deferral.js
CHANGED
|
@@ -30,14 +30,19 @@ const readDeferral = (body) => {
|
|
|
30
30
|
!Number.isNaN(Date.parse(frigg.firstDeferredAt))
|
|
31
31
|
? frigg.firstDeferredAt
|
|
32
32
|
: undefined;
|
|
33
|
-
|
|
33
|
+
const deferralId =
|
|
34
|
+
typeof frigg?.deferralId === 'string' && frigg.deferralId !== ''
|
|
35
|
+
? frigg.deferralId
|
|
36
|
+
: undefined;
|
|
37
|
+
return { deferrals, firstDeferredAt, deferralId };
|
|
34
38
|
};
|
|
35
39
|
|
|
36
|
-
const nextDeferral = (body, now = Date.now()) => {
|
|
40
|
+
const nextDeferral = (body, now = Date.now(), messageId) => {
|
|
37
41
|
const current = readDeferral(body);
|
|
38
42
|
return {
|
|
39
43
|
deferrals: current.deferrals + 1,
|
|
40
44
|
firstDeferredAt: current.firstDeferredAt ?? new Date(now).toISOString(),
|
|
45
|
+
deferralId: current.deferralId ?? messageId,
|
|
41
46
|
};
|
|
42
47
|
};
|
|
43
48
|
|
package/types/core/index.d.ts
CHANGED
|
@@ -54,13 +54,17 @@ declare module "@friggframework/core" {
|
|
|
54
54
|
state: RateLimitWaitState
|
|
55
55
|
): Promise<void>;
|
|
56
56
|
|
|
57
|
-
clearRateLimitWait(
|
|
57
|
+
clearRateLimitWait(
|
|
58
|
+
body: object,
|
|
59
|
+
options?: { deferralId?: string }
|
|
60
|
+
): Promise<void>;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
export type RateLimitWaitState = {
|
|
61
64
|
status: "WAITING" | "EXHAUSTED";
|
|
62
65
|
mechanism: "delay" | "schedule" | "visibility" | "none";
|
|
63
66
|
deferrals: number;
|
|
67
|
+
deferralId?: string;
|
|
64
68
|
retryAt: Date;
|
|
65
69
|
};
|
|
66
70
|
|