@checkstack/queue-memory-backend 0.4.19 → 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 CHANGED
@@ -1,5 +1,28 @@
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
+
19
+ ## 0.4.20
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [2ec8f64]
24
+ - @checkstack/backend-api@0.24.1
25
+
3
26
  ## 0.4.19
4
27
 
5
28
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/queue-memory-backend",
3
- "version": "0.4.19",
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.24.0",
21
- "@checkstack/queue-api": "0.3.13",
22
- "@checkstack/queue-memory-common": "0.1.20",
23
- "@checkstack/common": "0.16.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.2"
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 { describe, it, expect, afterEach } from "bun:test";
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
- delayMultiplier: 0.01, // Speed up delays for testing
23
- heartbeatIntervalMs: 5,
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: 0.5, // 5ms with multiplier
87
+ intervalSeconds: 1,
53
88
  });
54
89
 
55
- // Wait for multiple executions
56
- await Bun.sleep(100);
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: 0.5,
111
+ intervalSeconds: 1,
76
112
  });
77
113
 
78
- await Bun.sleep(100);
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
- await Bun.sleep(200);
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: 0.5,
167
+ intervalSeconds: 1,
127
168
  });
128
169
 
129
- await Bun.sleep(50);
170
+ await advanceAndDrain(1000);
130
171
  const countBeforeCancel = executionCount;
131
172
 
132
173
  await queue.cancelRecurring("recurring-cancel");
133
- await Bun.sleep(100);
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: 5, // 50ms with multiplier - slow interval
195
+ intervalSeconds: 10,
155
196
  });
156
197
 
157
- await Bun.sleep(20);
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: 0.5, // 5ms with multiplier
203
+ intervalSeconds: 1,
163
204
  });
164
205
 
165
- await Bun.sleep(80);
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 Bun.sleep(30);
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: 0.5, // 5ms with multiplier
237
+ intervalSeconds: 1,
196
238
  });
197
239
 
198
- await Bun.sleep(80);
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
  });