@checkstack/queue-memory-backend 0.4.20 → 0.4.21
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/CHANGELOG.md +16 -0
- package/package.json +6 -6
- package/src/benchmark.ts +7 -0
- package/src/recurring-jobs.test.ts +68 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @checkstack/queue-memory-backend
|
|
2
2
|
|
|
3
|
+
## 0.4.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [8cad340]
|
|
8
|
+
- Updated dependencies [8cad340]
|
|
9
|
+
- Updated dependencies [8cad340]
|
|
10
|
+
- Updated dependencies [8cad340]
|
|
11
|
+
- Updated dependencies [8cad340]
|
|
12
|
+
- Updated dependencies [8cad340]
|
|
13
|
+
- Updated dependencies [8cad340]
|
|
14
|
+
- @checkstack/backend-api@0.25.0
|
|
15
|
+
- @checkstack/common@0.17.0
|
|
16
|
+
- @checkstack/queue-api@0.3.14
|
|
17
|
+
- @checkstack/queue-memory-common@0.1.21
|
|
18
|
+
|
|
3
19
|
## 0.4.20
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/queue-memory-backend",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"checkstack": {
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"pack": "bunx @checkstack/scripts plugin-pack"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@checkstack/backend-api": "0.
|
|
21
|
-
"@checkstack/queue-api": "0.3.
|
|
22
|
-
"@checkstack/queue-memory-common": "0.1.
|
|
23
|
-
"@checkstack/common": "0.
|
|
20
|
+
"@checkstack/backend-api": "0.25.0",
|
|
21
|
+
"@checkstack/queue-api": "0.3.14",
|
|
22
|
+
"@checkstack/queue-memory-common": "0.1.21",
|
|
23
|
+
"@checkstack/common": "0.17.0",
|
|
24
24
|
"cron-parser": "^4.9.0",
|
|
25
25
|
"zod": "^4.2.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/bun": "latest",
|
|
29
29
|
"@checkstack/tsconfig": "0.0.7",
|
|
30
|
-
"@checkstack/scripts": "0.6.
|
|
30
|
+
"@checkstack/scripts": "0.6.3"
|
|
31
31
|
},
|
|
32
32
|
"description": "Checkstack queue-memory-backend plugin",
|
|
33
33
|
"author": {
|
package/src/benchmark.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* bun run src/benchmark.ts
|
|
9
9
|
*
|
|
10
10
|
* This is NOT a test file - it runs benchmarks and prints results.
|
|
11
|
+
*
|
|
12
|
+
* INTENTIONAL console.* usage: this is a standalone CLI report tool whose
|
|
13
|
+
* entire purpose is to print a human-readable benchmark table to stdout.
|
|
14
|
+
* `console.*` IS the output here, not stray debug logging, so the structured
|
|
15
|
+
* `Logger` abstraction used by the rest of the backend does not apply. The
|
|
16
|
+
* `no-console` lint rule is scoped to frontend packages and does not flag this
|
|
17
|
+
* backend script; the console calls below are deliberate and must stay.
|
|
11
18
|
*/
|
|
12
19
|
|
|
13
20
|
import { InMemoryQueue } from "./memory-queue";
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Recurring Job Tests for InMemoryQueue
|
|
3
|
+
*
|
|
4
|
+
* Uses Bun's Jest-compatible fake timers (`jest.useFakeTimers`,
|
|
5
|
+
* `jest.advanceTimersByTime`) so interval-based recurring scheduling is driven
|
|
6
|
+
* deterministically instead of via real `Bun.sleep` waits. Real-time waits in
|
|
7
|
+
* the unit lane are a known flake source under the parallel runner; advancing a
|
|
8
|
+
* fake clock makes each "after N intervals" assertion exact and fast.
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
describe,
|
|
13
|
+
it,
|
|
14
|
+
expect,
|
|
15
|
+
afterEach,
|
|
16
|
+
beforeEach,
|
|
17
|
+
jest,
|
|
18
|
+
setSystemTime,
|
|
19
|
+
} from "bun:test";
|
|
6
20
|
import { InMemoryQueue } from "./memory-queue";
|
|
7
21
|
import type { Logger } from "@checkstack/backend-api";
|
|
8
22
|
|
|
@@ -19,17 +33,38 @@ function createTestQueue(name: string) {
|
|
|
19
33
|
{
|
|
20
34
|
concurrency: 10,
|
|
21
35
|
maxQueueSize: 100,
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
// Real timing (multiplier 1) so advancing the fake clock by the interval
|
|
37
|
+
// in ms maps 1:1 to a scheduled fire.
|
|
38
|
+
delayMultiplier: 1,
|
|
39
|
+
heartbeatIntervalMs: 100,
|
|
24
40
|
},
|
|
25
41
|
testLogger,
|
|
26
42
|
);
|
|
27
43
|
}
|
|
28
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Advance the fake clock by `ms` and let the queue's async processing
|
|
47
|
+
* (enqueue -> processNext -> async consumer) drain. Several microtask flushes
|
|
48
|
+
* cover the multi-hop async path between a timer firing and the handler
|
|
49
|
+
* completing.
|
|
50
|
+
*/
|
|
51
|
+
async function advanceAndDrain(ms: number): Promise<void> {
|
|
52
|
+
jest.advanceTimersByTime(ms);
|
|
53
|
+
for (let i = 0; i < 10; i++) {
|
|
54
|
+
await Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
29
58
|
describe("InMemoryQueue Recurring Jobs", () => {
|
|
30
59
|
let queue: InMemoryQueue<string> | undefined;
|
|
31
60
|
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
jest.useFakeTimers();
|
|
63
|
+
setSystemTime(new Date("2026-01-18T10:00:00Z"));
|
|
64
|
+
});
|
|
65
|
+
|
|
32
66
|
afterEach(async () => {
|
|
67
|
+
jest.useRealTimers();
|
|
33
68
|
if (queue) {
|
|
34
69
|
await queue.stop();
|
|
35
70
|
queue = undefined;
|
|
@@ -49,11 +84,12 @@ describe("InMemoryQueue Recurring Jobs", () => {
|
|
|
49
84
|
|
|
50
85
|
await queue.scheduleRecurring("payload", {
|
|
51
86
|
jobId: "recurring-success",
|
|
52
|
-
intervalSeconds:
|
|
87
|
+
intervalSeconds: 1,
|
|
53
88
|
});
|
|
54
89
|
|
|
55
|
-
//
|
|
56
|
-
await
|
|
90
|
+
// Two intervals -> at least two executions (re-scheduled after each run).
|
|
91
|
+
await advanceAndDrain(1000);
|
|
92
|
+
await advanceAndDrain(1000);
|
|
57
93
|
|
|
58
94
|
expect(executionCount).toBeGreaterThanOrEqual(2);
|
|
59
95
|
});
|
|
@@ -72,10 +108,11 @@ describe("InMemoryQueue Recurring Jobs", () => {
|
|
|
72
108
|
|
|
73
109
|
await queue.scheduleRecurring("payload", {
|
|
74
110
|
jobId: "recurring-failure",
|
|
75
|
-
intervalSeconds:
|
|
111
|
+
intervalSeconds: 1,
|
|
76
112
|
});
|
|
77
113
|
|
|
78
|
-
await
|
|
114
|
+
await advanceAndDrain(1000);
|
|
115
|
+
await advanceAndDrain(1000);
|
|
79
116
|
|
|
80
117
|
// Should still reschedule despite failures
|
|
81
118
|
expect(executionCount).toBeGreaterThanOrEqual(2);
|
|
@@ -103,7 +140,11 @@ describe("InMemoryQueue Recurring Jobs", () => {
|
|
|
103
140
|
intervalSeconds: 60, // Long interval so only retries should execute
|
|
104
141
|
});
|
|
105
142
|
|
|
106
|
-
|
|
143
|
+
// Retries use exponential backoff (2s, then 4s) on their own delayed
|
|
144
|
+
// schedule. Drain past the full backoff window (~6s) but stay well under
|
|
145
|
+
// the 60s recurring interval, so only retries — not a reschedule — run.
|
|
146
|
+
await advanceAndDrain(3000);
|
|
147
|
+
await advanceAndDrain(5000);
|
|
107
148
|
|
|
108
149
|
// Should complete after retries, not reschedule
|
|
109
150
|
expect(completed).toBe(true);
|
|
@@ -123,14 +164,14 @@ describe("InMemoryQueue Recurring Jobs", () => {
|
|
|
123
164
|
|
|
124
165
|
await queue.scheduleRecurring("payload", {
|
|
125
166
|
jobId: "recurring-cancel",
|
|
126
|
-
intervalSeconds:
|
|
167
|
+
intervalSeconds: 1,
|
|
127
168
|
});
|
|
128
169
|
|
|
129
|
-
await
|
|
170
|
+
await advanceAndDrain(1000);
|
|
130
171
|
const countBeforeCancel = executionCount;
|
|
131
172
|
|
|
132
173
|
await queue.cancelRecurring("recurring-cancel");
|
|
133
|
-
await
|
|
174
|
+
await advanceAndDrain(2000);
|
|
134
175
|
|
|
135
176
|
// Should not have executed more after cancellation
|
|
136
177
|
expect(countBeforeCancel).toBeGreaterThanOrEqual(1);
|
|
@@ -148,23 +189,24 @@ describe("InMemoryQueue Recurring Jobs", () => {
|
|
|
148
189
|
{ consumerGroup: "test", maxRetries: 0 },
|
|
149
190
|
);
|
|
150
191
|
|
|
151
|
-
// Schedule with original payload
|
|
192
|
+
// Schedule with original payload on a slow interval.
|
|
152
193
|
await queue.scheduleRecurring("original-payload", {
|
|
153
194
|
jobId: "recurring-update",
|
|
154
|
-
intervalSeconds:
|
|
195
|
+
intervalSeconds: 10,
|
|
155
196
|
});
|
|
156
197
|
|
|
157
|
-
await
|
|
198
|
+
await advanceAndDrain(2000);
|
|
158
199
|
|
|
159
|
-
// Update to new payload and faster interval
|
|
200
|
+
// Update to new payload and a faster interval.
|
|
160
201
|
await queue.scheduleRecurring("updated-payload", {
|
|
161
202
|
jobId: "recurring-update",
|
|
162
|
-
intervalSeconds:
|
|
203
|
+
intervalSeconds: 1,
|
|
163
204
|
});
|
|
164
205
|
|
|
165
|
-
await
|
|
206
|
+
await advanceAndDrain(1000);
|
|
207
|
+
await advanceAndDrain(1000);
|
|
166
208
|
|
|
167
|
-
// Should have the updated payload multiple times
|
|
209
|
+
// Should have the updated payload multiple times.
|
|
168
210
|
const updatedPayloads = payloads.filter((p) => p === "updated-payload");
|
|
169
211
|
expect(updatedPayloads.length).toBeGreaterThanOrEqual(2);
|
|
170
212
|
});
|
|
@@ -180,24 +222,25 @@ describe("InMemoryQueue Recurring Jobs", () => {
|
|
|
180
222
|
{ consumerGroup: "test", maxRetries: 0 },
|
|
181
223
|
);
|
|
182
224
|
|
|
183
|
-
// Schedule with a long interval (won't fire again during test)
|
|
225
|
+
// Schedule with a long interval (won't fire again during the test).
|
|
184
226
|
await queue.scheduleRecurring("payload", {
|
|
185
227
|
jobId: "recurring-test",
|
|
186
228
|
intervalSeconds: 100, // Very long, should only execute once initially
|
|
187
229
|
});
|
|
188
230
|
|
|
189
|
-
await
|
|
231
|
+
await advanceAndDrain(2000);
|
|
190
232
|
const countAfterFirst = executionCount;
|
|
191
233
|
|
|
192
|
-
// Update to a short interval
|
|
234
|
+
// Update to a short interval.
|
|
193
235
|
await queue.scheduleRecurring("payload", {
|
|
194
236
|
jobId: "recurring-test",
|
|
195
|
-
intervalSeconds:
|
|
237
|
+
intervalSeconds: 1,
|
|
196
238
|
});
|
|
197
239
|
|
|
198
|
-
await
|
|
240
|
+
await advanceAndDrain(1000);
|
|
241
|
+
await advanceAndDrain(1000);
|
|
199
242
|
|
|
200
|
-
// Should have executed multiple times with new interval
|
|
243
|
+
// Should have executed multiple times with the new interval.
|
|
201
244
|
expect(executionCount).toBeGreaterThan(countAfterFirst);
|
|
202
245
|
expect(executionCount - countAfterFirst).toBeGreaterThanOrEqual(2);
|
|
203
246
|
});
|