bunqueue 2.9.0 → 2.9.2

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 (150) hide show
  1. package/README.md +7 -3
  2. package/dist/application/background/recovery/active.d.ts +1 -0
  3. package/dist/application/background/recovery/active.js +12 -0
  4. package/dist/application/background/recovery/index.js +3 -1
  5. package/dist/application/background/recovery/pending.js +1 -0
  6. package/dist/application/cleanupTasks.js +4 -16
  7. package/dist/application/contextFactory.js +3 -0
  8. package/dist/application/dlqManager.js +1 -0
  9. package/dist/application/dlqRetry.js +1 -0
  10. package/dist/application/eventsManager.d.ts +8 -1
  11. package/dist/application/eventsManager.js +76 -29
  12. package/dist/application/operations/ackHelpers.d.ts +1 -4
  13. package/dist/application/operations/ackHelpers.js +39 -14
  14. package/dist/application/operations/flowPush.js +1 -0
  15. package/dist/application/operations/flowValidation.js +4 -4
  16. package/dist/application/operations/pull.d.ts +7 -2
  17. package/dist/application/operations/pull.js +15 -57
  18. package/dist/application/operations/pullFinalization.d.ts +6 -0
  19. package/dist/application/operations/pullFinalization.js +69 -0
  20. package/dist/application/operations/pullStateTransition.d.ts +8 -7
  21. package/dist/application/operations/pullStateTransition.js +25 -18
  22. package/dist/application/operations/push.js +3 -0
  23. package/dist/application/operations/pushBatch.js +14 -7
  24. package/dist/application/operations/pushContext.d.ts +2 -1
  25. package/dist/application/postgres-queue-manager/control.d.ts +4 -0
  26. package/dist/application/postgres-queue-manager/control.js +24 -0
  27. package/dist/application/postgres-queue-manager/delivery.d.ts +5 -4
  28. package/dist/application/postgres-queue-manager/delivery.js +13 -10
  29. package/dist/application/postgres-queue-manager/groupQueries.d.ts +10 -0
  30. package/dist/application/postgres-queue-manager/groupQueries.js +40 -0
  31. package/dist/application/postgres-queue-manager/queries.d.ts +2 -2
  32. package/dist/application/postgres-queue-manager/queries.js +2 -2
  33. package/dist/application/queue-manager/control.js +1 -0
  34. package/dist/application/queue-manager/delivery.d.ts +5 -4
  35. package/dist/application/queue-manager/delivery.js +15 -8
  36. package/dist/application/queue-manager/limits.d.ts +11 -0
  37. package/dist/application/queue-manager/limits.js +61 -0
  38. package/dist/application/queue-manager/state.js +1 -1
  39. package/dist/application/queueTelemetryJournal.d.ts +2 -0
  40. package/dist/application/queueTelemetryJournal.js +23 -0
  41. package/dist/application/types/ack.d.ts +3 -1
  42. package/dist/cli/commandRouter.d.ts +1 -1
  43. package/dist/client/flowPush.js +6 -0
  44. package/dist/client/groupId.d.ts +1 -0
  45. package/dist/client/groupId.js +1 -0
  46. package/dist/client/index.d.ts +1 -1
  47. package/dist/client/jobHelpers.js +1 -0
  48. package/dist/client/queue/operations/add/bulk.js +4 -4
  49. package/dist/client/queue/operations/add/payload.d.ts +1 -0
  50. package/dist/client/queue/operations/add/payload.js +7 -1
  51. package/dist/client/queue/operations/add/single.js +3 -3
  52. package/dist/client/queue/operations/groups.d.ts +21 -0
  53. package/dist/client/queue/operations/groups.js +88 -0
  54. package/dist/client/queue/runtime/queries.d.ts +13 -0
  55. package/dist/client/queue/runtime/queries.js +31 -0
  56. package/dist/client/queue/runtime/state.d.ts +4 -0
  57. package/dist/client/queue/runtime/state.js +1 -0
  58. package/dist/client/types/index.d.ts +2 -2
  59. package/dist/client/types/options.d.ts +4 -0
  60. package/dist/client/types/worker.d.ts +8 -0
  61. package/dist/client/worker/runtime/execution.js +1 -0
  62. package/dist/client/worker/runtime/options.js +3 -0
  63. package/dist/client/worker/types/options.d.ts +2 -1
  64. package/dist/client/worker/workerPull.d.ts +2 -0
  65. package/dist/client/worker/workerPull.js +6 -4
  66. package/dist/domain/job/groupFifoOrder.d.ts +9 -0
  67. package/dist/domain/job/groupFifoOrder.js +26 -0
  68. package/dist/domain/queue/groupLimiterManager.d.ts +23 -0
  69. package/dist/domain/queue/groupLimiterManager.js +107 -0
  70. package/dist/domain/queue/groupScheduler.d.ts +31 -0
  71. package/dist/domain/queue/groupScheduler.js +256 -0
  72. package/dist/domain/queue/groupSchedulerOrder.d.ts +6 -0
  73. package/dist/domain/queue/groupSchedulerOrder.js +30 -0
  74. package/dist/domain/queue/priorityQueue.d.ts +13 -23
  75. package/dist/domain/queue/priorityQueue.js +53 -42
  76. package/dist/domain/queue/shard/keys.d.ts +1 -0
  77. package/dist/domain/queue/shard/keys.js +25 -2
  78. package/dist/domain/queue/shard/lifecycle.js +4 -0
  79. package/dist/domain/queue/shard/limits.d.ts +20 -1
  80. package/dist/domain/queue/shard/limits.js +56 -0
  81. package/dist/domain/queue/shard/state.d.ts +5 -0
  82. package/dist/domain/queue/shard/state.js +9 -1
  83. package/dist/domain/types/commands/core.d.ts +3 -0
  84. package/dist/domain/types/commands/limits.d.ts +41 -0
  85. package/dist/domain/types/commands/union.d.ts +1 -1
  86. package/dist/domain/types/group.d.ts +21 -0
  87. package/dist/domain/types/group.js +78 -0
  88. package/dist/domain/types/priorityQueue.d.ts +1 -0
  89. package/dist/infrastructure/persistence/jobOptionsBlob.d.ts +1 -0
  90. package/dist/infrastructure/persistence/jobOptionsBlob.js +10 -2
  91. package/dist/infrastructure/persistence/migrations.js +11 -0
  92. package/dist/infrastructure/persistence/postgres/admission.js +5 -2
  93. package/dist/infrastructure/persistence/postgres/batchAdmission.js +22 -4
  94. package/dist/infrastructure/persistence/postgres/claimBatch.d.ts +3 -0
  95. package/dist/infrastructure/persistence/postgres/claimBatch.js +16 -3
  96. package/dist/infrastructure/persistence/postgres/claimSelection.d.ts +8 -1
  97. package/dist/infrastructure/persistence/postgres/claimSelection.js +73 -131
  98. package/dist/infrastructure/persistence/postgres/claims.d.ts +8 -1
  99. package/dist/infrastructure/persistence/postgres/claims.js +19 -3
  100. package/dist/infrastructure/persistence/postgres/eventCatchupCursors.d.ts +32 -0
  101. package/dist/infrastructure/persistence/postgres/eventCatchupCursors.js +66 -0
  102. package/dist/infrastructure/persistence/postgres/eventJournalSchema.d.ts +2 -2
  103. package/dist/infrastructure/persistence/postgres/eventJournalSchema.js +16 -2
  104. package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.d.ts +2 -0
  105. package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.js +21 -3
  106. package/dist/infrastructure/persistence/postgres/events.d.ts +3 -3
  107. package/dist/infrastructure/persistence/postgres/events.js +44 -17
  108. package/dist/infrastructure/persistence/postgres/groupClaims.d.ts +15 -0
  109. package/dist/infrastructure/persistence/postgres/groupClaims.js +95 -0
  110. package/dist/infrastructure/persistence/postgres/groupSchema.d.ts +2 -0
  111. package/dist/infrastructure/persistence/postgres/groupSchema.js +155 -0
  112. package/dist/infrastructure/persistence/postgres/groupSchemaFingerprint.d.ts +3 -0
  113. package/dist/infrastructure/persistence/postgres/groupSchemaFingerprint.js +127 -0
  114. package/dist/infrastructure/persistence/postgres/groupStateRetention.d.ts +3 -0
  115. package/dist/infrastructure/persistence/postgres/groupStateRetention.js +93 -0
  116. package/dist/infrastructure/persistence/postgres/groups.d.ts +11 -0
  117. package/dist/infrastructure/persistence/postgres/groups.js +117 -0
  118. package/dist/infrastructure/persistence/postgres/queueDestruction.js +4 -0
  119. package/dist/infrastructure/persistence/postgres/runtime.d.ts +0 -1
  120. package/dist/infrastructure/persistence/postgres/runtime.js +3 -1
  121. package/dist/infrastructure/persistence/postgres/schema.d.ts +2 -2
  122. package/dist/infrastructure/persistence/postgres/schema.js +8 -4
  123. package/dist/infrastructure/persistence/postgres/schemaInitialization.js +6 -1
  124. package/dist/infrastructure/persistence/postgres/store.d.ts +11 -1
  125. package/dist/infrastructure/persistence/postgres/store.js +47 -3
  126. package/dist/infrastructure/persistence/postgres/telemetry.d.ts +2 -0
  127. package/dist/infrastructure/persistence/postgres/telemetry.js +33 -21
  128. package/dist/infrastructure/persistence/postgres/types.d.ts +1 -0
  129. package/dist/infrastructure/persistence/schema.d.ts +2 -2
  130. package/dist/infrastructure/persistence/schema.js +12 -1
  131. package/dist/infrastructure/persistence/sqlite/control.d.ts +14 -0
  132. package/dist/infrastructure/persistence/sqlite/control.js +69 -0
  133. package/dist/infrastructure/persistence/sqlite/jobs.d.ts +12 -0
  134. package/dist/infrastructure/persistence/sqlite/jobs.js +36 -0
  135. package/dist/infrastructure/persistence/sqlite/telemetry.d.ts +2 -1
  136. package/dist/infrastructure/persistence/sqlite/telemetry.js +5 -64
  137. package/dist/infrastructure/persistence/sqlite/telemetryWrites.d.ts +4 -0
  138. package/dist/infrastructure/persistence/sqlite/telemetryWrites.js +123 -0
  139. package/dist/infrastructure/persistence/sqliteSerializer.js +2 -0
  140. package/dist/infrastructure/server/handler-routes/control.js +13 -0
  141. package/dist/infrastructure/server/handlers/core.d.ts +0 -5
  142. package/dist/infrastructure/server/handlers/core.js +15 -18
  143. package/dist/infrastructure/server/handlers/groups.d.ts +11 -0
  144. package/dist/infrastructure/server/handlers/groups.js +55 -0
  145. package/dist/infrastructure/server/handlers/pushBatchValidation.js +4 -1
  146. package/dist/infrastructure/server/protocol/validation.d.ts +1 -0
  147. package/dist/infrastructure/server/protocol/validation.js +1 -0
  148. package/dist/infrastructure/server/protocol.d.ts +1 -1
  149. package/dist/infrastructure/server/protocol.js +1 -1
  150. package/package.json +2 -1
@@ -6,6 +6,7 @@
6
6
  import { jobId } from '../../domain/types/job';
7
7
  import { setDlqRetryState } from '../../domain/types/dlq';
8
8
  import { normalizeLegacyJobPayload } from '../../domain/job/payload';
9
+ import { restoreGroupFifoOrder } from '../../domain/job/groupFifoOrder';
9
10
  import { decodeJobOptions } from './jobOptionsBlob';
10
11
  import { storageLog } from '../../shared/logger';
11
12
  import { decodeMessagePack as msgpackDecode, encodeMessagePack as msgpackEncode, } from '../../shared/msgpack';
@@ -166,6 +167,7 @@ export function rowToJob(row) {
166
167
  ? unpack(row.stacktrace, null, `${jobContext}:stacktrace`)
167
168
  : null,
168
169
  };
170
+ restoreGroupFifoOrder(job, extended.groupFifoOrder);
169
171
  const dlqRetryState = row.dlq_retry_state
170
172
  ? unpack(row.dlq_retry_state, null, `${jobContext}:dlqRetryState`)
171
173
  : null;
@@ -2,6 +2,7 @@ import { handleClean, handleClearConcurrency, handleGetDlqConfig, handleGetStall
2
2
  import { handleCron, handleCronDelete, handleCronGet, handleCronList } from '../handlers/cron';
3
3
  import { handleDlq, handleGetDlqStats, handlePurgeDlq, handleRemoveDlqJob, handleRetryCompleted, handleRetryDlq, } from '../handlers/dlq';
4
4
  import { handleDrain, handlePause, handleResume } from '../handlers/management';
5
+ import { handleGroupCommand, handleGroupsJobsCount } from '../handlers/groups';
5
6
  export function routeQueueControlCommand(command, context, requestId) {
6
7
  switch (command.cmd) {
7
8
  case 'Pause':
@@ -42,6 +43,18 @@ export function routeDlqCommand(command, context, requestId) {
42
43
  }
43
44
  export function routeRateLimitCommand(command, context, requestId) {
44
45
  switch (command.cmd) {
46
+ case 'GetGroupsJobsCount':
47
+ return handleGroupsJobsCount(command, context, requestId);
48
+ case 'GetGroupJobsCount':
49
+ case 'GetGroupActiveCount':
50
+ case 'SetGroupRateLimit':
51
+ case 'GetGroupRateLimit':
52
+ case 'RemoveGroupRateLimit':
53
+ case 'GetGroupRateLimitTtl':
54
+ case 'SetGroupConcurrency':
55
+ case 'GetGroupConcurrency':
56
+ case 'RemoveGroupConcurrency':
57
+ return handleGroupCommand(command, context, requestId);
45
58
  case 'RateLimit':
46
59
  return handleRateLimit(command, context, requestId);
47
60
  case 'RateLimitClear':
@@ -1,20 +1,15 @@
1
- /** Core command handlers for push, pull, acknowledgement, and failure. */
2
1
  import type { Command } from '../../../domain/types/command';
3
2
  import type { Response } from '../../../domain/types/response';
4
3
  import type { HandlerContext } from '../types';
5
- /** Handle PUSH command */
6
4
  export declare function handlePush(cmd: Extract<Command, {
7
5
  cmd: 'PUSH';
8
6
  }>, ctx: HandlerContext, reqId?: string): Promise<Response>;
9
- /** Handle PUSHB (batch push) command */
10
7
  export declare function handlePushBatch(cmd: Extract<Command, {
11
8
  cmd: 'PUSHB';
12
9
  }>, ctx: HandlerContext, reqId?: string): Promise<Response>;
13
- /** Handle PULL command */
14
10
  export declare function handlePull(cmd: Extract<Command, {
15
11
  cmd: 'PULL';
16
12
  }>, ctx: HandlerContext, reqId?: string): Promise<Response>;
17
- /** Handle PULLB (batch pull) command - uses optimized single-lock batch pull */
18
13
  export declare function handlePullBatch(cmd: Extract<Command, {
19
14
  cmd: 'PULLB';
20
15
  }>, ctx: HandlerContext, reqId?: string): Promise<Response>;
@@ -1,11 +1,10 @@
1
- /** Core command handlers for push, pull, acknowledgement, and failure. */
2
1
  import * as resp from '../../../domain/types/response';
2
+ import { validateGroupPullOptions } from '../../../domain/types/group';
3
3
  import { jobId, normalizeLegacyJobPayload } from '../../../domain/types/job';
4
4
  import { tcpLog } from '../../../shared/logger';
5
5
  import { sanitizeServerError, serverErrorDiagnostics } from '../errors';
6
- import { validateQueueName, validateJobData, validateJobOptions, validateNumericField, } from '../protocol';
6
+ import { validateQueueName, validateGroupId, validateJobData, validateJobOptions, validateNumericField, } from '../protocol';
7
7
  import { validatePushBatchJobs, validatePushDependencies } from './pushBatchValidation';
8
- /** Handle PUSH command */
9
8
  export async function handlePush(cmd, ctx, reqId) {
10
9
  const queueError = validateQueueName(cmd.queue);
11
10
  if (queueError)
@@ -13,7 +12,9 @@ export async function handlePush(cmd, ctx, reqId) {
13
12
  const dataError = validateJobData(cmd.data);
14
13
  if (dataError)
15
14
  return resp.error(dataError, reqId);
16
- // Validate numeric fields
15
+ const groupError = validateGroupId(cmd.groupId);
16
+ if (groupError)
17
+ return resp.error(groupError, reqId);
17
18
  const optionsError = validateJobOptions({
18
19
  priority: cmd.priority,
19
20
  delay: cmd.delay,
@@ -69,7 +70,6 @@ export async function handlePush(cmd, ctx, reqId) {
69
70
  return resp.error(sanitizeServerError(err), reqId);
70
71
  }
71
72
  }
72
- /** Handle PUSHB (batch push) command */
73
73
  export async function handlePushBatch(cmd, ctx, reqId) {
74
74
  const queueError = validateQueueName(cmd.queue);
75
75
  if (queueError)
@@ -91,48 +91,45 @@ export async function handlePushBatch(cmd, ctx, reqId) {
91
91
  return resp.error(sanitizeServerError(error), reqId);
92
92
  }
93
93
  }
94
- /** Handle PULL command */
95
94
  export async function handlePull(cmd, ctx, reqId) {
96
95
  const queueError = validateQueueName(cmd.queue);
97
96
  if (queueError)
98
97
  return resp.error(queueError, reqId);
99
- // Validate timeout
100
98
  const timeoutError = validateNumericField(cmd.timeout, 'timeout', { min: 0, max: 60000 });
101
99
  if (timeoutError)
102
100
  return resp.error(timeoutError, reqId);
103
- // If owner is provided, use lock-based pull
101
+ const groupError = validateGroupPullOptions(cmd.group);
102
+ if (groupError)
103
+ return resp.error(groupError, reqId);
104
104
  if (cmd.owner) {
105
- const { job, token } = await ctx.queueManager.pullWithLock(cmd.queue, cmd.owner, cmd.timeout, cmd.lockTtl, ctx.signal);
106
- // Register job with client for connection-based release
105
+ const { job, token } = await ctx.queueManager.pullWithLock(cmd.queue, cmd.owner, cmd.timeout, cmd.lockTtl, ctx.signal, cmd.group);
107
106
  if (job && ctx.clientId) {
108
107
  ctx.queueManager.registerClientJob(ctx.clientId, job.id);
109
108
  }
110
109
  return resp.pulledJob(job, token, reqId);
111
110
  }
112
111
  // Standard pull (no lock, but still track for client release unless detached)
113
- const job = await ctx.queueManager.pull(cmd.queue, cmd.timeout, ctx.signal);
112
+ const job = await ctx.queueManager.pull(cmd.queue, cmd.timeout, ctx.signal, cmd.group);
114
113
  if (job && ctx.clientId && !cmd.detach) {
115
114
  ctx.queueManager.registerClientJob(ctx.clientId, job.id);
116
115
  }
117
116
  return resp.nullableJob(job, reqId);
118
117
  }
119
- /** Handle PULLB (batch pull) command - uses optimized single-lock batch pull */
120
118
  export async function handlePullBatch(cmd, ctx, reqId) {
121
119
  const queueError = validateQueueName(cmd.queue);
122
120
  if (queueError)
123
121
  return resp.error(queueError, reqId);
124
- // Validate count
125
122
  const countError = validateNumericField(cmd.count, 'count', { min: 1, max: 1000 });
126
123
  if (countError)
127
124
  return resp.error(countError, reqId);
128
- // Validate timeout (same bound as PULL)
129
125
  const timeoutError = validateNumericField(cmd.timeout, 'timeout', { min: 0, max: 60000 });
130
126
  if (timeoutError)
131
127
  return resp.error(timeoutError, reqId);
132
- // If owner is provided, use lock-based pull
128
+ const groupError = validateGroupPullOptions(cmd.group);
129
+ if (groupError)
130
+ return resp.error(groupError, reqId);
133
131
  if (cmd.owner) {
134
- const { jobs, tokens } = await ctx.queueManager.pullBatchWithLock(cmd.queue, cmd.count, cmd.owner, cmd.timeout ?? 0, cmd.lockTtl, ctx.signal);
135
- // Register all jobs with client for connection-based release
132
+ const { jobs, tokens } = await ctx.queueManager.pullBatchWithLock(cmd.queue, cmd.count, cmd.owner, cmd.timeout ?? 0, cmd.lockTtl, ctx.signal, cmd.group);
136
133
  if (ctx.clientId) {
137
134
  for (const job of jobs) {
138
135
  ctx.queueManager.registerClientJob(ctx.clientId, job.id);
@@ -142,7 +139,7 @@ export async function handlePullBatch(cmd, ctx, reqId) {
142
139
  }
143
140
  // Standard pull (no locks, but still track for client release) — the
144
141
  // non-owner branch honors cmd.timeout exactly like the owner branch and PULL.
145
- const jobs = await ctx.queueManager.pullBatch(cmd.queue, cmd.count, cmd.timeout ?? 0, ctx.signal);
142
+ const jobs = await ctx.queueManager.pullBatch(cmd.queue, cmd.count, cmd.timeout ?? 0, ctx.signal, cmd.group);
146
143
  if (ctx.clientId) {
147
144
  for (const job of jobs) {
148
145
  ctx.queueManager.registerClientJob(ctx.clientId, job.id);
@@ -0,0 +1,11 @@
1
+ import type { Command } from '../../../domain/types/command';
2
+ import type { Response } from '../../../domain/types/response';
3
+ import type { HandlerContext } from '../types';
4
+ type GroupCommand = Extract<Command, {
5
+ groupId: string;
6
+ }>;
7
+ export declare function handleGroupCommand(command: GroupCommand, context: HandlerContext, requestId?: string): Promise<Response>;
8
+ export declare function handleGroupsJobsCount(command: Extract<Command, {
9
+ cmd: 'GetGroupsJobsCount';
10
+ }>, context: HandlerContext, requestId?: string): Promise<Response>;
11
+ export {};
@@ -0,0 +1,55 @@
1
+ import { validatePositiveSafeInteger } from '../../../domain/types/group';
2
+ import * as resp from '../../../domain/types/response';
3
+ import { validateGroupId, validateQueueName } from '../protocol';
4
+ function validate(command) {
5
+ const queueError = validateQueueName(command.queue);
6
+ if (queueError)
7
+ return queueError;
8
+ return validateGroupId(command.groupId, true);
9
+ }
10
+ export async function handleGroupCommand(command, context, requestId) {
11
+ const error = validate(command);
12
+ if (error)
13
+ return resp.error(error, requestId);
14
+ const manager = context.queueManager;
15
+ switch (command.cmd) {
16
+ case 'GetGroupJobsCount':
17
+ return resp.data({ count: await manager.getGroupJobsCount(command.queue, command.groupId) }, requestId);
18
+ case 'GetGroupActiveCount':
19
+ return resp.data({ count: await manager.getGroupActiveCount(command.queue, command.groupId) }, requestId);
20
+ case 'SetGroupRateLimit': {
21
+ const maxError = validatePositiveSafeInteger(command.max, 'max');
22
+ const durationError = validatePositiveSafeInteger(command.duration, 'duration');
23
+ const numericError = maxError ?? durationError;
24
+ if (numericError)
25
+ return resp.error(numericError, requestId);
26
+ await manager.setGroupRateLimit(command.queue, command.groupId, command.max, command.duration);
27
+ return resp.ok(undefined, requestId);
28
+ }
29
+ case 'GetGroupRateLimit':
30
+ return resp.data({ limit: await manager.getGroupRateLimit(command.queue, command.groupId) }, requestId);
31
+ case 'RemoveGroupRateLimit':
32
+ return resp.data({ removed: await manager.removeGroupRateLimit(command.queue, command.groupId) }, requestId);
33
+ case 'GetGroupRateLimitTtl':
34
+ return resp.data({
35
+ ttl: await manager.getGroupRateLimitTtl(command.queue, command.groupId, command.maxJobs),
36
+ }, requestId);
37
+ case 'SetGroupConcurrency': {
38
+ const concurrencyError = validatePositiveSafeInteger(command.concurrency, 'concurrency');
39
+ if (concurrencyError)
40
+ return resp.error(concurrencyError, requestId);
41
+ await manager.setGroupConcurrency(command.queue, command.groupId, command.concurrency);
42
+ return resp.ok(undefined, requestId);
43
+ }
44
+ case 'GetGroupConcurrency':
45
+ return resp.data({ concurrency: await manager.getGroupConcurrency(command.queue, command.groupId) }, requestId);
46
+ case 'RemoveGroupConcurrency':
47
+ return resp.data({ removed: await manager.removeGroupConcurrency(command.queue, command.groupId) }, requestId);
48
+ }
49
+ }
50
+ export async function handleGroupsJobsCount(command, context, requestId) {
51
+ const queueError = validateQueueName(command.queue);
52
+ if (queueError)
53
+ return resp.error(queueError, requestId);
54
+ return resp.data({ count: await context.queueManager.getGroupsJobsCount(command.queue) }, requestId);
55
+ }
@@ -5,7 +5,7 @@
5
5
  * existence gate handlePush enforces (core.ts).
6
6
  */
7
7
  import { jobId, normalizeLegacyJobPayload, } from '../../../domain/types/job';
8
- import { validateJobData, validateJobOptions } from '../protocol';
8
+ import { validateGroupId, validateJobData, validateJobOptions } from '../protocol';
9
9
  function dependencyExistsLocally(id, ctx) {
10
10
  return (ctx.queueManager.getJobIndex().has(id) ||
11
11
  ctx.queueManager.getCompletedJobs().has(id) ||
@@ -88,6 +88,9 @@ function validateJobs(jobs, state) {
88
88
  const dataError = validateJobData(job.data);
89
89
  if (dataError)
90
90
  return `jobs[${i}]: ${dataError}`;
91
+ const groupError = validateGroupId(job.groupId);
92
+ if (groupError)
93
+ return `jobs[${i}]: ${groupError}`;
91
94
  const optionsError = validateJobOptions({
92
95
  priority: job.priority,
93
96
  delay: job.delay,
@@ -1,4 +1,5 @@
1
1
  export declare function validateQueueName(name: string): string | null;
2
+ export { validateGroupId } from '../../../domain/types/group';
2
3
  export declare function validateJobData(data: unknown): string | null;
3
4
  export declare function validateNumericField(value: unknown, name: string, options?: {
4
5
  min?: number;
@@ -7,6 +7,7 @@ export function validateQueueName(name) {
7
7
  return 'Queue name contains invalid characters';
8
8
  return null;
9
9
  }
10
+ export { validateGroupId } from '../../../domain/types/group';
10
11
  export function validateJobData(data) {
11
12
  let json;
12
13
  try {
@@ -1,6 +1,6 @@
1
1
  export { createConnectionState, errorResponse, parseCommand, parseCommands, serializeResponse, } from './protocol/commands';
2
2
  export { FrameParser, FrameSizeError, MAX_FRAME_SIZE } from './protocol/frameParser';
3
3
  export { LineBuffer } from './protocol/lineBuffer';
4
- export { validateBackoffField, validateJobData, validateJobOptions, validateNumericField, validateQueueName, } from './protocol/validation';
4
+ export { validateBackoffField, validateGroupId, validateJobData, validateJobOptions, validateNumericField, validateQueueName, } from './protocol/validation';
5
5
  export type { ConnectionState } from './types/protocol';
6
6
  export { validateWebhookUrl } from '../../shared/webhookValidation';
@@ -1,5 +1,5 @@
1
1
  export { createConnectionState, errorResponse, parseCommand, parseCommands, serializeResponse, } from './protocol/commands';
2
2
  export { FrameParser, FrameSizeError, MAX_FRAME_SIZE } from './protocol/frameParser';
3
3
  export { LineBuffer } from './protocol/lineBuffer';
4
- export { validateBackoffField, validateJobData, validateJobOptions, validateNumericField, validateQueueName, } from './protocol/validation';
4
+ export { validateBackoffField, validateGroupId, validateJobData, validateJobOptions, validateNumericField, validateQueueName, } from './protocol/validation';
5
5
  export { validateWebhookUrl } from '../../shared/webhookValidation';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunqueue",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "High-performance job queue for Bun and AI agents. Memory or one-file SQLite, optional PostgreSQL 15–18 multi-broker persistence, cron, retries, DLQ, and MCP.",
5
5
  "keywords": [
6
6
  "agentic",
@@ -127,6 +127,7 @@
127
127
  "bench:autobatch": "bun run bench/local-autobatch.ts",
128
128
  "bench:postgres:versions": "bun run bench/postgres-versions.ts",
129
129
  "bench:pushbulk": "bun run bench/pushbulk-delta.ts",
130
+ "bench:sqlite-lifecycle": "bun run bench/sqlite-lifecycle-batching.ts",
130
131
  "bench:workflow": "bun run bench/workflow-engine.ts",
131
132
  "bench:workflow:scale": "bun run bench/workflow-engine/scale.ts",
132
133
  "bench:joblist": "bun run bench/job-list-perf.ts",