bunqueue-dashboard 0.0.41 → 0.0.42

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 (64) hide show
  1. package/README.md +10 -8
  2. package/agent/backup/resultValidation.ts +1 -1
  3. package/agent/manager/config.ts +1 -1
  4. package/agent/queue/routes.ts +33 -3
  5. package/agent/queue/runtime.ts +49 -3
  6. package/agent/queue/types.ts +16 -1
  7. package/agent/queue/validation.ts +8 -0
  8. package/agent/workflows.ts +1 -1
  9. package/dist/.vite/manifest.json +122 -120
  10. package/dist/assets/AddJob-FNaPbs_V.js +3 -0
  11. package/dist/assets/{Alerts-CYwfGLSQ.js → Alerts-BvWBv-z7.js} +1 -1
  12. package/dist/assets/{Benchmark-BINaJvak.js → Benchmark-BywalSmU.js} +1 -1
  13. package/dist/assets/BulkAddJobs-IiL1uud0.js +5 -0
  14. package/dist/assets/{CopilotPanel-DjHfX9ta.js → CopilotPanel-CH2jbbYA.js} +1 -1
  15. package/dist/assets/{Cron-CSoEPVeq.js → Cron-1DZTZWXO.js} +1 -1
  16. package/dist/assets/{CronManager-s4xjNhGD.js → CronManager-B6xLLgI5.js} +1 -1
  17. package/dist/assets/{Database-x6iWCu3-.js → Database-BzEkXws0.js} +1 -1
  18. package/dist/assets/{Diagnostics-DFshXeHl.js → Diagnostics-CPqhV3er.js} +1 -1
  19. package/dist/assets/{Dlq-Cayt1PEN.js → Dlq-CbLN0zQ5.js} +1 -1
  20. package/dist/assets/{DlqControl-BBQc6uu9.js → DlqControl-C5Bgzmk0.js} +1 -1
  21. package/dist/assets/{DlqPro-BmrnGQ-3.js → DlqPro-Cgaxtuwh.js} +1 -1
  22. package/dist/assets/{Flows-CcNdf_jS.js → Flows-BtUGOaif.js} +2 -2
  23. package/dist/assets/{JobInspector-BMjVR96H.js → JobInspector-DsOt4mLJ.js} +2 -2
  24. package/dist/assets/{Jobs-NN0fJ3BQ.js → Jobs-BTMpbAHD.js} +1 -1
  25. package/dist/assets/{JobsPro-D_l1c80Q.js → JobsPro-BfXTm9gH.js} +1 -1
  26. package/dist/assets/{Logs-BGeTppXO.js → Logs-uuzU5ohI.js} +1 -1
  27. package/dist/assets/{LogsPro-D7vvgZmb.js → LogsPro-DHWbhWyq.js} +1 -1
  28. package/dist/assets/{McpServer-AsaFGnYZ.js → McpServer-BqmbhrKh.js} +2 -2
  29. package/dist/assets/{Metrics-CvoY4uhX.js → Metrics-DKyBZoFa.js} +1 -1
  30. package/dist/assets/{MetricsPro-C01FjqvR.js → MetricsPro-BR2ULqIc.js} +1 -1
  31. package/dist/assets/{Overview-WStfS5ee.js → Overview-ClsyxH2e.js} +1 -1
  32. package/dist/assets/{QueueActions-D51CYOjo.js → QueueActions-BWnmNEqV.js} +1 -1
  33. package/dist/assets/QueueControl-BW0XXd0B.js +1 -0
  34. package/dist/assets/{QueueDetail-D5TKm4bv.js → QueueDetail-C1dXXN70.js} +1 -1
  35. package/dist/assets/{QueueDetailPro-CFNQcKho.js → QueueDetailPro-DDGxs2J2.js} +1 -1
  36. package/dist/assets/{Queues-DlznHsfh.js → Queues-DkPuIy1C.js} +1 -1
  37. package/dist/assets/{QueuesOverview-DGtapyV0.js → QueuesOverview-COo-V8D_.js} +1 -1
  38. package/dist/assets/{S3Backup-CxwbLJGK.js → S3Backup-DNABHR2Q.js} +1 -1
  39. package/dist/assets/{S3BackupPro-8y0J0-3_.js → S3BackupPro-DxNVFvY1.js} +3 -3
  40. package/dist/assets/{ServerControl-DpE7t-OF.js → ServerControl-B0Po974t.js} +1 -1
  41. package/dist/assets/{Settings-Bw5MtDTF.js → Settings-z9k4EVa_.js} +1 -1
  42. package/dist/assets/{Usage-DpShi3FE.js → Usage-cdWDQUaq.js} +1 -1
  43. package/dist/assets/{UsagePro-D4cGbozz.js → UsagePro-DLuwnM8n.js} +1 -1
  44. package/dist/assets/{Webhooks-UYiTg6hn.js → Webhooks-DT2LK17V.js} +1 -1
  45. package/dist/assets/{Workers-D70B_uAI.js → Workers-CVo9rJ_V.js} +1 -1
  46. package/dist/assets/{WorkersPro-YycNhm9w.js → WorkersPro-BYaredE9.js} +1 -1
  47. package/dist/assets/{Workflows-BxlrgGEB.js → Workflows-DZZA6aGW.js} +1 -1
  48. package/dist/assets/{api-BAGaBh6R.js → api-BgWE-705.js} +1 -1
  49. package/dist/assets/bq-F5BbTG1T.js +1 -0
  50. package/dist/assets/{flowMutationSafety-DSd3JLAV.js → flowMutationSafety-YKTHxqKv.js} +1 -1
  51. package/dist/assets/{index-CxhXRVH_.css → index-C9yGKhQ8.css} +1 -1
  52. package/dist/assets/{index-ByvrmBif.js → index-mID5W7vq.js} +3 -3
  53. package/dist/assets/{install-C5y2ohOo.js → install-D4uRZ7XA.js} +2 -2
  54. package/dist/assets/responses-BkR2JTH9.js +1 -0
  55. package/dist/assets/{upstreamPaths-B_cyinGI.js → upstreamPaths-DQeKSxGc.js} +1 -1
  56. package/dist/assets/{useThroughputSeries-Cagm3qzB.js → useThroughputSeries-DgO8uicf.js} +1 -1
  57. package/dist/index.html +5 -5
  58. package/package.json +6 -3
  59. package/scripts/embedded.gen.ts +152 -152
  60. package/dist/assets/AddJob-CFnESFEU.js +0 -3
  61. package/dist/assets/BulkAddJobs-vyGFvlO3.js +0 -5
  62. package/dist/assets/QueueControl-Ce-O1L4p.js +0 -1
  63. package/dist/assets/bq-Qh0VFLKV.js +0 -1
  64. package/dist/assets/responses-DiIfPXXq.js +0 -1
package/README.md CHANGED
@@ -90,13 +90,13 @@ The full dashboard running on sample data, no server needed.
90
90
  bunqueue exposes a rich HTTP API, but operating it by hand (curl, ad-hoc scripts) is slow and
91
91
  error-prone. This dashboard is a production-oriented operator console: it exposes the verified,
92
92
  safe subset of the API, fails closed where
93
- [Bunqueue 2.9.x](https://github.com/egeominotti/bunqueue/releases/tag/v2.9.2) lacks atomic
93
+ [Bunqueue 2.9.x](https://github.com/egeominotti/bunqueue/releases/tag/v2.9.3) lacks atomic
94
94
  flow-safety guarantees, and also manages the
95
95
  server *process* through a separate guarded agent.
96
96
 
97
97
  It uses Bunqueue's public HTTP API (`:6790`) for ordinary remote observability and a small local
98
98
  **control agent** for process, FlowProducer, Workflow Engine, database, and backup operations. The
99
- agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.2 npm client
99
+ agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.3 npm client
100
100
  contracts; it never patches Bunqueue internals.
101
101
 
102
102
  ## Features
@@ -108,7 +108,7 @@ contracts; it never patches Bunqueue internals.
108
108
  | **Server** | Control ▸ Server | **Start / stop / restart** the server process, edit its config, tail process logs |
109
109
  | **Enqueue** | Control ▸ Add Job | Add jobs (single or bulk) with every option |
110
110
  | **Inspect** | Control ▸ Job Inspector | Look up any job; promote / re-prioritize / delay; view data & result |
111
- | **Queues** | Control ▸ Queue Control | Pause/resume/promote; desired-state writes plus live SDK limits, deduplication, metrics and journal retention |
111
+ | **Queues** | Control ▸ Queue Control | Pause/resume/promote; desired-state writes plus live SDK limits, group jobs/priorities/pause/policies, deduplication, metrics and journal retention |
112
112
  | **Cron** | Control ▸ Cron Manager | Submit acknowledged cron/interval upserts and delete schedules |
113
113
  | **Workflow Engine** | Workflow ▸ Overview / Executions / Waiting / Compensation / Archive | Start, recover, signal, inspect, compensate, archive and clean durable executions |
114
114
  | **Job Flows** | Workflow ▸ Job Flows | Explore DAGs, run all FlowProducer create modes and operate safe Flow Job methods |
@@ -118,7 +118,7 @@ contracts; it never patches Bunqueue internals.
118
118
  | **S3 backup** | Management ▸ S3 Backup | Configure, inspect, list, create and guarded-restore official snapshots |
119
119
  | **Browse** | Queues / Jobs / DLQ / Cron / Metrics / Workers / Logs | Read-only browsing with basic actions |
120
120
 
121
- > Job actions are gated by the v2.9.2 flow contract. Every DLQ retry and completed-job requeue is
121
+ > Job actions are gated by the v2.9.3 flow contract. Every DLQ retry and completed-job requeue is
122
122
  > unavailable: the DLQ GET + POST sequence has no atomic generation/state/topology precondition and
123
123
  > can target a job recreated under the same ID, while `retryCompleted` does not rebuild dependency
124
124
  > registration or flow order. Cancel, Discard, Drain, Clean, Obliterate and DLQ Purge also fail
@@ -262,8 +262,9 @@ target-pinned Flow, Workflow, Queue and Backup agent operations.
262
262
  | `bun run check` | Oxlint + Oxfmt validation (the CI gate) |
263
263
  | `bun run check:fix` | Apply safe Oxlint fixes, then format with Oxfmt |
264
264
  | `bun test` | Unit + agent-lifecycle tests |
265
+ | `bun run test:e2e:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.3 schema-37 migration and retention semantics |
265
266
  | `bun run test:e2e` | Real Flow, Workflow, Queue SDK, and three-broker PostgreSQL runtime tests |
266
- | `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.2 brokers + three agents sharing disposable PostgreSQL 18.6 |
267
+ | `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.3 brokers + three agents sharing disposable PostgreSQL 18.6 |
267
268
 
268
269
  ## Docker
269
270
 
@@ -310,11 +311,12 @@ The canonical gate must be green before a change is considered done; CI runs the
310
311
  bun run quality # architecture, lint/build, size, docs, coverage, real E2E, packed-bin smoke, audit
311
312
  ```
312
313
 
313
- The E2E stage starts disposable Bunqueue 2.9.2 processes and exercises the complete FlowProducer,
314
+ The E2E stage starts disposable Bunqueue 2.9.3 processes and exercises the complete FlowProducer,
314
315
  Workflow Engine, and Queue SDK operator bridges. It also starts PostgreSQL 18.6 in Docker with
315
316
  three authenticated brokers and paired agents, proving cross-broker enqueue/inspect/leased
316
- pull+ack, pause/resume, cron, and rate-limit behavior. Run it alone with `bun run test:e2e` (Docker
317
- is required for the PostgreSQL stage).
317
+ pull+ack, pause/resume, cron, rate limits, group admission, group priority and group pause/resume
318
+ on PostgreSQL schema 20. Run it alone with `bun run test:e2e` (Docker is required for the
319
+ PostgreSQL stage).
318
320
 
319
321
  The blocking browser job builds the production bundle under `/e2e/dashboard`, starts an
320
322
  authenticated disposable Bunqueue server, and drives the UI with Playwright on Chromium, Firefox,
@@ -81,5 +81,5 @@ function validIsoDate(value: string): boolean {
81
81
  }
82
82
 
83
83
  function invalid(label: string): never {
84
- throw new Error(`Bunqueue ${label} has an invalid 2.9.2 contract`);
84
+ throw new Error(`Bunqueue ${label} has an invalid 2.9.3 contract`);
85
85
  }
@@ -10,7 +10,7 @@ const CONFIG_KEYS = new Set<keyof ServerConfig>([
10
10
 
11
11
  export function defaultConfig(): ServerConfig {
12
12
  return {
13
- command: process.env.BUNQUEUE_START_CMD || 'bunx bunqueue@2.9.2 start',
13
+ command: process.env.BUNQUEUE_START_CMD || 'bunx bunqueue@2.9.3 start',
14
14
  httpPort: Number(process.env.HTTP_PORT) || 6790,
15
15
  tcpPort: Number(process.env.TCP_PORT) || 6789,
16
16
  dataPath: process.env.BUNQUEUE_DATA_PATH || './data/bunq.db',
@@ -12,6 +12,7 @@ import {
12
12
  eventRetention,
13
13
  exactJsonBody,
14
14
  exactQuery,
15
+ groupJobsRange,
15
16
  metricsRange,
16
17
  optionalMaxCount,
17
18
  optionalMaxJobs,
@@ -23,7 +24,7 @@ import {
23
24
 
24
25
  const READ_ROUTE = /^\/queue-operations\/([^/]+)\/(limits|groups|deduplication|metrics)$/;
25
26
  const MUTATION_ROUTE =
26
- /^\/queue-operations\/([^/]+)\/(groups\/(?:rate-limit|concurrency)(?:\/remove)?|deduplication\/remove|events\/trim)$/;
27
+ /^\/queue-operations\/([^/]+)\/(groups\/(?:(?:rate-limit|concurrency)(?:\/remove)?|pause|resume)|deduplication\/remove|events\/trim)$/;
27
28
 
28
29
  export async function routeQueueOperationsRequest(
29
30
  request: Request,
@@ -58,15 +59,26 @@ export async function routeQueueOperationsRequest(
58
59
  const query = pinnedQuery(
59
60
  request,
60
61
  config,
61
- ['target', 'groupId', 'maxJobs', 'maxCount'],
62
+ ['target', 'groupId', 'maxJobs', 'maxCount', 'start', 'end'],
62
63
  targetPolicy
63
64
  );
64
65
  const groupId = requiredGroupId(query.get('groupId'));
65
66
  const maxJobs = optionalMaxJobs(query);
66
67
  const maxCount = optionalMaxCount(query);
68
+ const range = groupJobsRange(query);
67
69
  assertRunning(running);
68
70
  return admitted(query, config, running, admission, targetPolicy, async ({ config: current }) =>
69
- ok({ group: await runtime.group(current, queue, groupId, maxJobs, maxCount) })
71
+ ok({
72
+ group: await runtime.group(
73
+ current,
74
+ queue,
75
+ groupId,
76
+ maxJobs,
77
+ maxCount,
78
+ range.start,
79
+ range.end
80
+ ),
81
+ })
70
82
  );
71
83
  }
72
84
  const query = pinnedQuery(request, config, ['target', 'type', 'start', 'end'], targetPolicy);
@@ -93,6 +105,24 @@ export async function routeQueueOperationsRequest(
93
105
  );
94
106
  }
95
107
  if (mutation[2].startsWith('groups/')) {
108
+ if (mutation[2] === 'groups/pause' || mutation[2] === 'groups/resume') {
109
+ const body = await exactJsonBody(request, ['groupId']);
110
+ const groupId = requiredGroupId(body.groupId);
111
+ return admitted(
112
+ query,
113
+ config,
114
+ running,
115
+ admission,
116
+ targetPolicy,
117
+ async ({ config: current }) =>
118
+ ok({
119
+ changed:
120
+ mutation[2] === 'groups/pause'
121
+ ? await runtime.pauseGroup(current, queue, groupId)
122
+ : await runtime.resumeGroup(current, queue, groupId),
123
+ })
124
+ );
125
+ }
96
126
  const remove = mutation[2].endsWith('/remove');
97
127
  const rateLimit = mutation[2].includes('/rate-limit');
98
128
  const allowed = remove
@@ -21,6 +21,11 @@ export type QueueOperationsClient = Pick<
21
21
  | 'setGroupConcurrency'
22
22
  | 'getGroupConcurrency'
23
23
  | 'removeGroupConcurrency'
24
+ | 'pauseGroup'
25
+ | 'resumeGroup'
26
+ | 'isGroupPaused'
27
+ | 'getGroupJobs'
28
+ | 'getCountsPerPriorityForGroup'
24
29
  | 'getDeduplicationJobId'
25
30
  | 'removeDeduplicationKey'
26
31
  | 'removeDlqJob'
@@ -57,21 +62,62 @@ export class QueueOperationsRuntime implements QueueOperationsPort {
57
62
  queue: string,
58
63
  groupId: string,
59
64
  maxJobs?: number,
60
- maxCount?: number
65
+ maxCount?: number,
66
+ start?: number,
67
+ end?: number
61
68
  ): Promise<QueueGroupSnapshot> {
62
69
  return this.withQueue(config, queue, async (client) => {
63
- const [jobs, active, totalGrouped, rateLimit, rateLimitTtl, concurrency] = await Promise.all([
70
+ const [
71
+ jobs,
72
+ active,
73
+ totalGrouped,
74
+ rateLimit,
75
+ rateLimitTtl,
76
+ concurrency,
77
+ paused,
78
+ groupJobs,
79
+ priorityCounts,
80
+ ] = await Promise.all([
64
81
  client.getGroupJobsCount(groupId),
65
82
  client.getGroupActiveCount(groupId),
66
83
  client.getGroupsJobsCount(maxCount),
67
84
  client.getGroupRateLimit(groupId),
68
85
  client.getGroupRateLimitTtl(groupId, maxJobs),
69
86
  client.getGroupConcurrency(groupId),
87
+ client.isGroupPaused(groupId),
88
+ client.getGroupJobs(groupId, start, end),
89
+ client.getCountsPerPriorityForGroup(groupId),
70
90
  ]);
71
- return { jobs, active, totalGrouped, rateLimit, rateLimitTtl, concurrency };
91
+ return {
92
+ jobs,
93
+ active,
94
+ totalGrouped,
95
+ rateLimit,
96
+ rateLimitTtl,
97
+ concurrency,
98
+ paused,
99
+ entries: groupJobs.map((job) => ({
100
+ id: job.id,
101
+ name: job.name,
102
+ priority: job.priority,
103
+ delay: job.delay,
104
+ timestamp: job.timestamp,
105
+ })),
106
+ priorityCounts: Object.fromEntries(
107
+ Object.entries(priorityCounts).map(([priority, count]) => [String(priority), count])
108
+ ),
109
+ };
72
110
  });
73
111
  }
74
112
 
113
+ pauseGroup(config: ServerConfig, queue: string, groupId: string): Promise<boolean> {
114
+ return this.withQueue(config, queue, (client) => client.pauseGroup(groupId));
115
+ }
116
+
117
+ resumeGroup(config: ServerConfig, queue: string, groupId: string): Promise<boolean> {
118
+ return this.withQueue(config, queue, (client) => client.resumeGroup(groupId));
119
+ }
120
+
75
121
  setGroupRateLimit(
76
122
  config: ServerConfig,
77
123
  queue: string,
@@ -12,11 +12,22 @@ export interface QueueGroupSnapshot {
12
12
  jobs: number;
13
13
  active: number;
14
14
  totalGrouped: number;
15
+ paused: boolean;
16
+ entries: QueueGroupJobSummary[];
17
+ priorityCounts: Record<string, number>;
15
18
  rateLimit: { max: number; duration: number } | null;
16
19
  rateLimitTtl: number;
17
20
  concurrency: number | null;
18
21
  }
19
22
 
23
+ export interface QueueGroupJobSummary {
24
+ id: string;
25
+ name: string;
26
+ priority: number;
27
+ delay: number;
28
+ timestamp: number;
29
+ }
30
+
20
31
  export interface QueueOperationsPort {
21
32
  limits(config: ServerConfig, queue: string, maxJobs?: number): Promise<QueueLimitSnapshot>;
22
33
  group(
@@ -24,8 +35,12 @@ export interface QueueOperationsPort {
24
35
  queue: string,
25
36
  groupId: string,
26
37
  maxJobs?: number,
27
- maxCount?: number
38
+ maxCount?: number,
39
+ start?: number,
40
+ end?: number
28
41
  ): Promise<QueueGroupSnapshot>;
42
+ pauseGroup(config: ServerConfig, queue: string, groupId: string): Promise<boolean>;
43
+ resumeGroup(config: ServerConfig, queue: string, groupId: string): Promise<boolean>;
29
44
  setGroupRateLimit(
30
45
  config: ServerConfig,
31
46
  queue: string,
@@ -45,6 +45,14 @@ export function optionalMaxCount(query: URLSearchParams): number | undefined {
45
45
  return raw === null ? undefined : boundedInteger(raw, 'maxCount', 1, 1_000_000);
46
46
  }
47
47
 
48
+ export function groupJobsRange(query: URLSearchParams): { start: number; end: number } {
49
+ const start = optionalInteger(query.get('start'), 'start', 0, 1_000_000, 0);
50
+ const end = optionalInteger(query.get('end'), 'end', 0, 1_000_000, start + 24);
51
+ if (end < start) throw new Error('Group jobs end must be at least start');
52
+ if (end - start >= 100) throw new Error('Group jobs page must contain at most 100 jobs');
53
+ return { start, end };
54
+ }
55
+
48
56
  export function requiredGroupId(value: unknown): string {
49
57
  if (typeof value !== 'string' || !value || value.length > 256 || value.includes('\0')) {
50
58
  throw new Error('groupId must be a non-empty string of at most 256 characters without NUL');
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Read-only facade for Bunqueue 2.9.2 Workflow Engine observability.
2
+ * Read-only facade for Bunqueue 2.9.3 Workflow Engine observability.
3
3
  * Persistence, decoding and validation live behind this stable public module.
4
4
  */
5
5
  export { workflowExecution } from './workflow/detail';