bunqueue 2.8.31 → 2.8.33

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.
Files changed (44) hide show
  1. package/README.md +29 -26
  2. package/dist/application/backgroundTasks.js +17 -9
  3. package/dist/application/dependencyProcessor.js +6 -1
  4. package/dist/application/dlqManager.js +1 -1
  5. package/dist/application/lockManager.js +1 -1
  6. package/dist/application/operations/ack.js +23 -1
  7. package/dist/application/operations/jobManagement.js +29 -1
  8. package/dist/application/operations/jobStateTransitions.js +1 -1
  9. package/dist/application/operations/pull.js +125 -96
  10. package/dist/application/operations/push.js +2 -2
  11. package/dist/application/operations/queryOperations.js +74 -70
  12. package/dist/application/queueManager.d.ts +6 -11
  13. package/dist/application/queueManager.js +16 -3
  14. package/dist/application/queueStatsAggregator.d.ts +23 -0
  15. package/dist/application/queueStatsAggregator.js +80 -0
  16. package/dist/application/statsManager.d.ts +2 -26
  17. package/dist/application/statsManager.js +8 -124
  18. package/dist/client/tcpPool.js +8 -1
  19. package/dist/domain/queue/shard.d.ts +3 -1
  20. package/dist/domain/queue/shard.js +15 -7
  21. package/dist/domain/queue/temporalIndex.d.ts +24 -0
  22. package/dist/domain/queue/temporalIndex.js +100 -0
  23. package/dist/domain/queue/temporalManager.d.ts +2 -11
  24. package/dist/domain/queue/temporalManager.js +27 -44
  25. package/dist/domain/queue/waiterManager.d.ts +11 -13
  26. package/dist/domain/queue/waiterManager.js +80 -49
  27. package/dist/infrastructure/cloud/commands.js +5 -2
  28. package/dist/infrastructure/cloud/snapshotCollector.js +5 -2
  29. package/dist/infrastructure/cloud/statsRefresh.js +5 -2
  30. package/dist/infrastructure/cloud/statsUpdate.js +5 -2
  31. package/dist/infrastructure/persistence/schema.d.ts +2 -2
  32. package/dist/infrastructure/persistence/schema.js +13 -1
  33. package/dist/infrastructure/persistence/sqlite.d.ts +18 -2
  34. package/dist/infrastructure/persistence/sqlite.js +49 -6
  35. package/dist/infrastructure/server/handlers/core.js +13 -7
  36. package/dist/infrastructure/server/handlers/pushBatchValidation.d.ts +23 -0
  37. package/dist/infrastructure/server/handlers/pushBatchValidation.js +70 -0
  38. package/dist/infrastructure/server/queueCountsScheduler.d.ts +13 -0
  39. package/dist/infrastructure/server/queueCountsScheduler.js +35 -0
  40. package/dist/infrastructure/server/sseHandler.d.ts +1 -0
  41. package/dist/infrastructure/server/sseHandler.js +18 -13
  42. package/dist/infrastructure/server/wsHandler.d.ts +1 -1
  43. package/dist/infrastructure/server/wsHandler.js +18 -15
  44. package/package.json +3 -3
@@ -1321,7 +1321,7 @@ export class QueueManager {
1321
1321
  queue.push(parentJob);
1322
1322
  shard.incrementQueued(parentId, false, parentJob.createdAt, parentJob.queue, now);
1323
1323
  this.jobIndex.set(parentId, { type: 'queue', shardIdx: idx, queueName: parentJob.queue });
1324
- shard.notify();
1324
+ shard.notify(parentJob.queue);
1325
1325
  promoted = true;
1326
1326
  }
1327
1327
  });
@@ -1435,7 +1435,7 @@ export class QueueManager {
1435
1435
  shard.getQueue(parentJob.queue).push(parentJob);
1436
1436
  shard.incrementQueued(parentId, false, parentJob.createdAt, parentJob.queue, now);
1437
1437
  this.jobIndex.set(parentId, { type: 'queue', shardIdx: idx, queueName: parentJob.queue });
1438
- shard.notify();
1438
+ shard.notify(parentJob.queue);
1439
1439
  promoted = true;
1440
1440
  }
1441
1441
  });
@@ -1529,13 +1529,17 @@ export class QueueManager {
1529
1529
  const ctx = this.contextFactory.getStatsContext();
1530
1530
  const queues = this.listQueues();
1531
1531
  const result = [];
1532
+ const countsByQueue = statsMgr.getAllQueueJobCounts(queues, ctx);
1532
1533
  for (const name of queues) {
1533
- const c = statsMgr.getQueueJobCounts(name, ctx);
1534
+ const c = countsByQueue.get(name);
1535
+ if (!c)
1536
+ continue;
1534
1537
  result.push({
1535
1538
  name,
1536
1539
  paused: this.isPaused(name),
1537
1540
  counts: {
1538
1541
  waiting: c.waiting,
1542
+ prioritized: c.prioritized,
1539
1543
  active: c.active,
1540
1544
  completed: c.completed,
1541
1545
  failed: c.failed,
@@ -1545,6 +1549,15 @@ export class QueueManager {
1545
1549
  }
1546
1550
  return result;
1547
1551
  }
1552
+ /** Get counts for every registered queue with one global aggregation pass. */
1553
+ getAllQueueJobCounts() {
1554
+ return this.getQueueJobCountsBatch(this.queueNamesCache);
1555
+ }
1556
+ /** Aggregate a selected group of queues without repeating global scans. */
1557
+ getQueueJobCountsBatch(queueNames) {
1558
+ const ctx = this.contextFactory.getStatsContext();
1559
+ return statsMgr.getAllQueueJobCounts(queueNames, ctx);
1560
+ }
1548
1561
  /** Get job counts for a specific queue */
1549
1562
  getQueueJobCounts(queueName) {
1550
1563
  return statsMgr.getQueueJobCounts(queueName, this.contextFactory.getStatsContext());
@@ -0,0 +1,23 @@
1
+ import type { StatsContext } from './types';
2
+ export interface PerQueueStats {
3
+ waiting: number;
4
+ prioritized: number;
5
+ delayed: number;
6
+ active: number;
7
+ dlq: number;
8
+ }
9
+ export interface QueueJobCounts {
10
+ waiting: number;
11
+ prioritized: number;
12
+ delayed: number;
13
+ active: number;
14
+ completed: number;
15
+ failed: number;
16
+ 'waiting-children': number;
17
+ totalCompleted: number;
18
+ totalFailed: number;
19
+ }
20
+ /** Aggregate all requested queues in one pass over every global collection. */
21
+ export declare function getAllQueueJobCounts(queueNames: Iterable<string>, ctx: StatsContext): Map<string, QueueJobCounts>;
22
+ export declare function getPerQueueStats(ctx: StatsContext, queueNames: Set<string>): Map<string, PerQueueStats>;
23
+ export declare function getQueueJobCounts(queueName: string, ctx: StatsContext): QueueJobCounts;
@@ -0,0 +1,80 @@
1
+ import { SHARD_COUNT, shardIndex } from '../shared/hash';
2
+ function createCounts(queue, ctx) {
3
+ const metrics = ctx.perQueueMetrics?.get(queue);
4
+ return {
5
+ waiting: 0,
6
+ prioritized: 0,
7
+ delayed: 0,
8
+ active: 0,
9
+ completed: 0,
10
+ failed: ctx.shards[shardIndex(queue)].getDlqCount(queue),
11
+ 'waiting-children': 0,
12
+ totalCompleted: Number(metrics?.totalCompleted ?? 0n),
13
+ totalFailed: Number(metrics?.totalFailed ?? 0n),
14
+ };
15
+ }
16
+ /** Aggregate all requested queues in one pass over every global collection. */
17
+ export function getAllQueueJobCounts(queueNames, ctx) {
18
+ const result = new Map();
19
+ const now = Date.now();
20
+ for (const name of queueNames) {
21
+ const counts = createCounts(name, ctx);
22
+ const queue = ctx.shards[shardIndex(name)].queues.get(name);
23
+ if (queue) {
24
+ for (const job of queue.values()) {
25
+ if (job.runAt > now)
26
+ counts.delayed++;
27
+ else if (job.priority > 0)
28
+ counts.prioritized++;
29
+ else
30
+ counts.waiting++;
31
+ }
32
+ }
33
+ result.set(name, counts);
34
+ }
35
+ for (const processing of ctx.processingShards) {
36
+ for (const job of processing.values()) {
37
+ const counts = result.get(job.queue);
38
+ if (counts)
39
+ counts.active++;
40
+ }
41
+ }
42
+ for (const [jobId, location] of ctx.jobIndex) {
43
+ if (location.type !== 'completed' || !ctx.completedJobs.has(jobId))
44
+ continue;
45
+ const counts = result.get(location.queueName);
46
+ if (counts)
47
+ counts.completed++;
48
+ }
49
+ for (let i = 0; i < SHARD_COUNT; i++) {
50
+ const shard = ctx.shards[i];
51
+ for (const job of shard.waitingChildren.values()) {
52
+ const counts = result.get(job.queue);
53
+ if (counts)
54
+ counts['waiting-children']++;
55
+ }
56
+ for (const job of shard.waitingDeps.values()) {
57
+ const counts = result.get(job.queue);
58
+ if (counts)
59
+ counts['waiting-children']++;
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ export function getPerQueueStats(ctx, queueNames) {
65
+ const allCounts = getAllQueueJobCounts(queueNames, ctx);
66
+ const result = new Map();
67
+ for (const [name, counts] of allCounts) {
68
+ result.set(name, {
69
+ waiting: counts.waiting,
70
+ prioritized: counts.prioritized,
71
+ delayed: counts.delayed,
72
+ active: counts.active,
73
+ dlq: counts.failed,
74
+ });
75
+ }
76
+ return result;
77
+ }
78
+ export function getQueueJobCounts(queueName, ctx) {
79
+ return getAllQueueJobCounts([queueName], ctx).get(queueName) ?? createCounts(queueName, ctx);
80
+ }
@@ -3,6 +3,8 @@
3
3
  * Provides system metrics and memory compaction utilities
4
4
  */
5
5
  import type { StatsContext } from './types';
6
+ export { getAllQueueJobCounts, getPerQueueStats, getQueueJobCounts, } from './queueStatsAggregator';
7
+ export type { PerQueueStats, QueueJobCounts } from './queueStatsAggregator';
6
8
  export interface QueueStats {
7
9
  waiting: number;
8
10
  prioritized: number;
@@ -19,13 +21,6 @@ export interface QueueStats {
19
21
  cronJobs: number;
20
22
  cronPending: number;
21
23
  }
22
- export interface PerQueueStats {
23
- waiting: number;
24
- prioritized: number;
25
- delayed: number;
26
- active: number;
27
- dlq: number;
28
- }
29
24
  export interface MemoryStats {
30
25
  jobIndex: number;
31
26
  completedJobs: number;
@@ -57,25 +52,6 @@ export declare function getStats(ctx: StatsContext, cronScheduler: {
57
52
  * Returns counts of entries in all major collections.
58
53
  */
59
54
  export declare function getMemoryStats(ctx: StatsContext): MemoryStats;
60
- /**
61
- * Get per-queue statistics by iterating shards to aggregate per queue name.
62
- * Uses shard hashing to look up each queue in its designated shard.
63
- */
64
- export declare function getPerQueueStats(ctx: StatsContext, queueNames: Set<string>): Map<string, PerQueueStats>;
65
- /**
66
- * Get job counts for a specific queue
67
- */
68
- export declare function getQueueJobCounts(queueName: string, ctx: StatsContext): {
69
- waiting: number;
70
- prioritized: number;
71
- delayed: number;
72
- active: number;
73
- completed: number;
74
- failed: number;
75
- 'waiting-children': number;
76
- totalCompleted: number;
77
- totalFailed: number;
78
- };
79
55
  /**
80
56
  * Force compact all collections to reduce memory usage.
81
57
  * Use after large batch operations or when memory pressure is high.
@@ -2,17 +2,8 @@
2
2
  * Stats Manager - Queue statistics and memory management
3
3
  * Provides system metrics and memory compaction utilities
4
4
  */
5
- import { SHARD_COUNT, shardIndex } from '../shared/hash';
6
- /** Count jobs belonging to `queueName` across one or more job iterables. */
7
- function countByQueue(sources, queueName) {
8
- let count = 0;
9
- for (const src of sources) {
10
- for (const job of src)
11
- if (job.queue === queueName)
12
- count++;
13
- }
14
- return count;
15
- }
5
+ import { SHARD_COUNT } from '../shared/hash';
6
+ export { getAllQueueJobCounts, getPerQueueStats, getQueueJobCounts, } from './queueStatsAggregator';
16
7
  /**
17
8
  * Get queue statistics - uses running counters + priority scan
18
9
  */
@@ -21,7 +12,6 @@ export function getStats(ctx, cronScheduler) {
21
12
  const now = Date.now();
22
13
  for (let i = 0; i < SHARD_COUNT; i++) {
23
14
  const shardStats = ctx.shards[i].getStats();
24
- delayed += shardStats.delayedJobs;
25
15
  dlq += shardStats.dlqJobs;
26
16
  active += ctx.processingShards[i].size;
27
17
  // getJobState reports BOTH waitingChildren (flow parents) and waitingDeps
@@ -31,14 +21,12 @@ export function getStats(ctx, cronScheduler) {
31
21
  // Scan queues to split waiting vs prioritized (BullMQ v5 compat)
32
22
  for (const queue of ctx.shards[i].queues.values()) {
33
23
  for (const job of queue.values()) {
34
- if (job.runAt <= now) {
35
- if (job.priority > 0) {
36
- prioritized++;
37
- }
38
- else {
39
- waiting++;
40
- }
41
- }
24
+ if (job.runAt > now)
25
+ delayed++;
26
+ else if (job.priority > 0)
27
+ prioritized++;
28
+ else
29
+ waiting++;
42
30
  }
43
31
  }
44
32
  }
@@ -103,110 +91,6 @@ export function getMemoryStats(ctx) {
103
91
  delayedHeapTotal,
104
92
  };
105
93
  }
106
- /**
107
- * Get per-queue statistics by iterating shards to aggregate per queue name.
108
- * Uses shard hashing to look up each queue in its designated shard.
109
- */
110
- export function getPerQueueStats(ctx, queueNames) {
111
- const result = new Map();
112
- const now = Date.now();
113
- for (const name of queueNames) {
114
- const idx = shardIndex(name);
115
- const shard = ctx.shards[idx];
116
- const queue = shard.queues.get(name);
117
- let waiting = 0;
118
- let prioritized = 0;
119
- let delayed = 0;
120
- if (queue) {
121
- for (const job of queue.values()) {
122
- if (job.runAt > now) {
123
- delayed++;
124
- }
125
- else if (job.priority > 0) {
126
- prioritized++;
127
- }
128
- else {
129
- waiting++;
130
- }
131
- }
132
- }
133
- const dlq = shard.getDlqCount(name);
134
- result.set(name, { waiting, prioritized, delayed, active: 0, dlq });
135
- }
136
- // Count active jobs per queue from all processing shards
137
- for (let i = 0; i < SHARD_COUNT; i++) {
138
- for (const job of ctx.processingShards[i].values()) {
139
- const entry = result.get(job.queue);
140
- if (entry) {
141
- entry.active++;
142
- }
143
- }
144
- }
145
- return result;
146
- }
147
- /**
148
- * Get job counts for a specific queue
149
- */
150
- export function getQueueJobCounts(queueName, ctx) {
151
- const idx = shardIndex(queueName);
152
- const shard = ctx.shards[idx];
153
- const queue = shard.queues.get(queueName);
154
- const now = Date.now();
155
- // Count waiting vs prioritized vs delayed jobs in the queue
156
- let waiting = 0;
157
- let prioritized = 0;
158
- let delayed = 0;
159
- if (queue) {
160
- for (const job of queue.values()) {
161
- if (job.runAt > now) {
162
- delayed++;
163
- }
164
- else if (job.priority > 0) {
165
- prioritized++;
166
- }
167
- else {
168
- waiting++;
169
- }
170
- }
171
- }
172
- // Count active jobs (processing) for this queue
173
- let active = 0;
174
- for (const procShard of ctx.processingShards) {
175
- for (const job of procShard.values()) {
176
- if (job.queue === queueName) {
177
- active++;
178
- }
179
- }
180
- }
181
- // Count completed jobs for this queue
182
- let completed = 0;
183
- for (const [jobId, loc] of ctx.jobIndex) {
184
- if (loc.type === 'completed' && loc.queueName === queueName && ctx.completedJobs.has(jobId)) {
185
- completed++;
186
- }
187
- }
188
- // Count failed (DLQ) jobs for this queue
189
- const failed = shard.getDlq(queueName).length;
190
- // Count waiting-children jobs. getJobState/getJobs treat BOTH waitingChildren
191
- // (flow parents) and waitingDeps (jobs blocked on dependsOn) as 'waiting-children',
192
- // so count both to stay consistent with state/list (#95 class).
193
- const waitingChildrenCount = countByQueue([shard.waitingChildren.values(), shard.waitingDeps.values()], queueName);
194
- // Per-queue cumulative counters
195
- const perQueue = ctx.perQueueMetrics?.get(queueName);
196
- const totalCompleted = Number(perQueue?.totalCompleted ?? 0n);
197
- const totalFailed = Number(perQueue?.totalFailed ?? 0n);
198
- return {
199
- waiting,
200
- prioritized,
201
- delayed,
202
- active,
203
- completed,
204
- failed,
205
- 'waiting-children': waitingChildrenCount,
206
- totalCompleted,
207
- totalFailed,
208
- };
209
- }
210
94
  /**
211
95
  * Force compact all collections to reduce memory usage.
212
96
  * Use after large batch operations or when memory pressure is high.
@@ -50,6 +50,8 @@ export class TcpConnectionPool {
50
50
  pingInterval: this.options.pingInterval,
51
51
  maxPingFailures: this.options.maxPingFailures,
52
52
  maxCommandTimeouts: this.options.maxCommandTimeouts,
53
+ pipelining: this.options.pipelining,
54
+ maxInFlight: this.options.maxInFlight,
53
55
  });
54
56
  // Socket-level errors (e.g. TLS handshake failures, protocol garbage) are
55
57
  // emitted as 'error' events; without a listener EventEmitter would throw
@@ -190,7 +192,12 @@ function getPoolKey(options) {
190
192
  // TLS config must differentiate pools too: a TLS pool and a plaintext pool
191
193
  // to the same host:port are NOT interchangeable.
192
194
  const tlsKey = options?.tls ? JSON.stringify(options.tls) : '0';
193
- return `${host}:${port}:${poolSize}:${tokenHash}:${tlsKey}`;
195
+ // pipelining/maxInFlight shape per-connection behavior: without them in the
196
+ // key, two Queues with different windows would silently share whichever
197
+ // pool was created first.
198
+ const pipelining = (options?.pipelining ?? true) ? '1' : '0';
199
+ const maxInFlight = options?.maxInFlight ?? 100;
200
+ return `${host}:${port}:${poolSize}:${tokenHash}:${tlsKey}:${pipelining}:${maxInFlight}`;
194
201
  }
195
202
  /** Get or create shared connection pool */
196
203
  export function getSharedPool(options) {
@@ -48,9 +48,11 @@ export declare class Shard {
48
48
  /** Active FIFO groups per queue */
49
49
  readonly activeGroups: Map<string, Set<string>>;
50
50
  constructor();
51
- notify(): void;
51
+ notify(queue?: string): void;
52
52
  notifyBatch(count: number): void;
53
+ notifyBatch(queue: string, count: number): void;
53
54
  waitForJob(timeoutMs: number): Promise<void>;
55
+ waitForJob(queue: string, timeoutMs: number): Promise<void>;
54
56
  getQueue(name: string): IndexedPriorityQueue;
55
57
  getState(name: string): QueueState;
56
58
  isPaused(name: string): boolean;
@@ -58,14 +58,22 @@ export class Shard {
58
58
  });
59
59
  }
60
60
  // ============ Waiter Management (delegated) ============
61
- notify() {
62
- this.waiterManager.notify();
61
+ notify(queue) {
62
+ this.waiterManager.notify(queue);
63
63
  }
64
- notifyBatch(count) {
65
- this.waiterManager.notifyBatch(count);
64
+ notifyBatch(queueOrCount, maybeCount) {
65
+ if (typeof queueOrCount === 'number') {
66
+ this.waiterManager.notifyBatch(queueOrCount);
67
+ }
68
+ else {
69
+ this.waiterManager.notifyBatch(queueOrCount, maybeCount ?? 0);
70
+ }
66
71
  }
67
- waitForJob(timeoutMs) {
68
- return this.waiterManager.waitForJob(timeoutMs);
72
+ waitForJob(queueOrTimeout, maybeTimeout) {
73
+ if (typeof queueOrTimeout === 'number') {
74
+ return this.waiterManager.waitForJob(queueOrTimeout);
75
+ }
76
+ return this.waiterManager.waitForJob(queueOrTimeout, maybeTimeout ?? 0);
69
77
  }
70
78
  // ============ Queue Operations ============
71
79
  getQueue(name) {
@@ -87,7 +95,7 @@ export class Shard {
87
95
  }
88
96
  resume(name) {
89
97
  this.limiterManager.resume(name);
90
- this.waiterManager.notify();
98
+ this.waiterManager.notify(name);
91
99
  }
92
100
  // ============ Unique Key Management (delegated) ============
93
101
  isUniqueAvailable(queue, key) {
@@ -0,0 +1,24 @@
1
+ import type { JobId } from '../types/job';
2
+ export interface TemporalEntry {
3
+ createdAt: number;
4
+ jobId: JobId;
5
+ queue: string;
6
+ }
7
+ /** Queue-local temporal indexes with direct lookup by job ID. */
8
+ export declare class TemporalIndex {
9
+ private readonly indexesByQueue;
10
+ private readonly entriesByJobId;
11
+ private entryCount;
12
+ get size(): number;
13
+ add(createdAt: number, jobId: JobId, queue: string): void;
14
+ getOldJobs(queue: string, threshold: number, limit: number): Array<{
15
+ jobId: JobId;
16
+ createdAt: number;
17
+ }>;
18
+ remove(jobId: JobId): void;
19
+ clearQueue(queue: string): void;
20
+ cleanOrphaned(validJobIds: Set<JobId>): number;
21
+ clear(): void;
22
+ private removeFromQueueIndex;
23
+ private detachJobEntry;
24
+ }
@@ -0,0 +1,100 @@
1
+ import { SkipList } from '../../shared/skipList';
2
+ const compareEntries = (a, b) => a.createdAt - b.createdAt || (a.jobId < b.jobId ? -1 : a.jobId > b.jobId ? 1 : 0);
3
+ const createQueueIndex = () => new SkipList(compareEntries);
4
+ /** Queue-local temporal indexes with direct lookup by job ID. */
5
+ export class TemporalIndex {
6
+ indexesByQueue = new Map();
7
+ entriesByJobId = new Map();
8
+ entryCount = 0;
9
+ get size() {
10
+ return this.entryCount;
11
+ }
12
+ add(createdAt, jobId, queue) {
13
+ const jobEntries = this.entriesByJobId.get(jobId);
14
+ if (jobEntries?.some((entry) => entry.createdAt === createdAt))
15
+ return;
16
+ const entry = { createdAt, jobId, queue };
17
+ let queueIndex = this.indexesByQueue.get(queue);
18
+ if (!queueIndex) {
19
+ queueIndex = createQueueIndex();
20
+ this.indexesByQueue.set(queue, queueIndex);
21
+ }
22
+ queueIndex.insert(entry);
23
+ if (jobEntries)
24
+ jobEntries.push(entry);
25
+ else
26
+ this.entriesByJobId.set(jobId, [entry]);
27
+ this.entryCount++;
28
+ }
29
+ getOldJobs(queue, threshold, limit) {
30
+ if (limit <= 0)
31
+ return [];
32
+ const queueIndex = this.indexesByQueue.get(queue);
33
+ if (!queueIndex)
34
+ return [];
35
+ return queueIndex
36
+ .takeWhile((entry) => entry.createdAt <= threshold, limit)
37
+ .map(({ jobId, createdAt }) => ({ jobId, createdAt }));
38
+ }
39
+ remove(jobId) {
40
+ const jobEntries = this.entriesByJobId.get(jobId);
41
+ if (!jobEntries?.length)
42
+ return;
43
+ let targetIndex = 0;
44
+ for (let i = 1; i < jobEntries.length; i++) {
45
+ if (compareEntries(jobEntries[i], jobEntries[targetIndex]) < 0)
46
+ targetIndex = i;
47
+ }
48
+ const [entry] = jobEntries.splice(targetIndex, 1);
49
+ this.removeFromQueueIndex(entry);
50
+ if (jobEntries.length === 0)
51
+ this.entriesByJobId.delete(jobId);
52
+ this.entryCount--;
53
+ }
54
+ clearQueue(queue) {
55
+ const queueIndex = this.indexesByQueue.get(queue);
56
+ if (!queueIndex)
57
+ return;
58
+ for (const entry of queueIndex.values())
59
+ this.detachJobEntry(entry);
60
+ this.entryCount -= queueIndex.size;
61
+ this.indexesByQueue.delete(queue);
62
+ }
63
+ cleanOrphaned(validJobIds) {
64
+ let removed = 0;
65
+ for (const [jobId, entries] of this.entriesByJobId) {
66
+ if (validJobIds.has(jobId))
67
+ continue;
68
+ for (const entry of entries) {
69
+ this.removeFromQueueIndex(entry);
70
+ removed++;
71
+ }
72
+ this.entriesByJobId.delete(jobId);
73
+ }
74
+ this.entryCount -= removed;
75
+ return removed;
76
+ }
77
+ clear() {
78
+ this.indexesByQueue.clear();
79
+ this.entriesByJobId.clear();
80
+ this.entryCount = 0;
81
+ }
82
+ removeFromQueueIndex(entry) {
83
+ const queueIndex = this.indexesByQueue.get(entry.queue);
84
+ if (!queueIndex)
85
+ return;
86
+ queueIndex.delete(entry);
87
+ if (queueIndex.isEmpty)
88
+ this.indexesByQueue.delete(entry.queue);
89
+ }
90
+ detachJobEntry(entry) {
91
+ const jobEntries = this.entriesByJobId.get(entry.jobId);
92
+ if (!jobEntries)
93
+ return;
94
+ const index = jobEntries.indexOf(entry);
95
+ if (index !== -1)
96
+ jobEntries.splice(index, 1);
97
+ if (jobEntries.length === 0)
98
+ this.entriesByJobId.delete(entry.jobId);
99
+ }
100
+ }
@@ -6,22 +6,12 @@
6
6
  * - Delayed heap (MinHeap) for O(k) delayed job refresh
7
7
  */
8
8
  import type { JobId } from '../types/job';
9
- /** Entry in the temporal index */
10
- export interface TemporalEntry {
11
- createdAt: number;
12
- jobId: JobId;
13
- queue: string;
14
- }
9
+ export type { TemporalEntry } from './temporalIndex';
15
10
  /**
16
11
  * Manages temporal indexing for efficient job cleanup
17
12
  * and delayed job tracking for O(k) refresh
18
13
  */
19
14
  export declare class TemporalManager {
20
- /**
21
- * Temporal index: Skip List for O(log n) insert/delete
22
- * Ordered by createdAt for efficient cleanQueue range queries
23
- * Uses equality check on jobId to prevent duplicate entries for the same job
24
- */
25
15
  private readonly temporalIndex;
26
16
  /** Set of delayed job IDs for tracking when they become ready */
27
17
  private readonly delayedJobIds;
@@ -65,6 +55,7 @@ export declare class TemporalManager {
65
55
  * Returns number of jobs that became ready
66
56
  */
67
57
  refreshDelayed(now: number): number;
58
+ private maybeCompactDelayedHeap;
68
59
  /** Get delayed job count */
69
60
  get delayedCount(): number;
70
61
  /** Clear all delayed tracking */