bunqueue-dashboard 0.0.41 → 0.0.43
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/README.md +11 -8
- package/agent/backup/resultValidation.ts +1 -1
- package/agent/flow/routes.ts +2 -0
- package/agent/flow/service.ts +7 -2
- package/agent/managedConnection.ts +35 -0
- package/agent/manager/config.ts +1 -1
- package/agent/queue/routes.ts +33 -3
- package/agent/queue/runtime.ts +51 -5
- package/agent/queue/types.ts +16 -1
- package/agent/queue/validation.ts +8 -0
- package/agent/workflow/runtime.ts +2 -6
- package/agent/workflow/runtimeConfig.ts +2 -2
- package/agent/workflows.ts +1 -1
- package/dist/.vite/manifest.json +130 -127
- package/dist/assets/AddJob-FNaPbs_V.js +3 -0
- package/dist/assets/{Alerts-CYwfGLSQ.js → Alerts-C961Al5D.js} +1 -1
- package/dist/assets/{Benchmark-BINaJvak.js → Benchmark-DQNZV7dM.js} +1 -1
- package/dist/assets/BulkAddJobs-IiL1uud0.js +5 -0
- package/dist/assets/{CopilotPanel-DjHfX9ta.js → CopilotPanel-Dlm8XwBj.js} +1 -1
- package/dist/assets/{Cron-CSoEPVeq.js → Cron-1DZTZWXO.js} +1 -1
- package/dist/assets/{CronManager-s4xjNhGD.js → CronManager-B6xLLgI5.js} +1 -1
- package/dist/assets/{Database-x6iWCu3-.js → Database-DGM2BIHy.js} +1 -1
- package/dist/assets/{Diagnostics-DFshXeHl.js → Diagnostics-_Sh3UyaT.js} +1 -1
- package/dist/assets/{Dlq-Cayt1PEN.js → Dlq-mtInIoTh.js} +1 -1
- package/dist/assets/{DlqControl-BBQc6uu9.js → DlqControl-Lq9ISb9b.js} +1 -1
- package/dist/assets/{DlqPro-BmrnGQ-3.js → DlqPro-0_75vrd1.js} +1 -1
- package/dist/assets/Flows-CE7KzaFN.js +2 -0
- package/dist/assets/{JobInspector-BMjVR96H.js → JobInspector-DyWZk1eW.js} +2 -2
- package/dist/assets/{Jobs-NN0fJ3BQ.js → Jobs-ELSH1Wbd.js} +1 -1
- package/dist/assets/JobsPro-BlWwDeFN.js +1 -0
- package/dist/assets/{Logs-BGeTppXO.js → Logs-DMukqmHy.js} +1 -1
- package/dist/assets/{LogsPro-D7vvgZmb.js → LogsPro-BfYbmZHi.js} +1 -1
- package/dist/assets/{McpServer-AsaFGnYZ.js → McpServer-DNrOEp9j.js} +2 -2
- package/dist/assets/{Metrics-CvoY4uhX.js → Metrics-Cm5ZvfTo.js} +1 -1
- package/dist/assets/{MetricsPro-C01FjqvR.js → MetricsPro-C9g1sp-u.js} +1 -1
- package/dist/assets/{Overview-WStfS5ee.js → Overview-CKAA3h9f.js} +1 -1
- package/dist/assets/{QueueActions-D51CYOjo.js → QueueActions-BWnmNEqV.js} +1 -1
- package/dist/assets/QueueControl-Bvm8GUQa.js +1 -0
- package/dist/assets/{QueueDetail-D5TKm4bv.js → QueueDetail-BRDATlp0.js} +1 -1
- package/dist/assets/{QueueDetailPro-CFNQcKho.js → QueueDetailPro-Dkz4-1wn.js} +1 -1
- package/dist/assets/{Queues-DlznHsfh.js → Queues-BV99vr68.js} +1 -1
- package/dist/assets/{QueuesOverview-DGtapyV0.js → QueuesOverview-CT6JBaim.js} +1 -1
- package/dist/assets/{S3Backup-CxwbLJGK.js → S3Backup-DNABHR2Q.js} +1 -1
- package/dist/assets/{S3BackupPro-8y0J0-3_.js → S3BackupPro-CjR0y18n.js} +3 -3
- package/dist/assets/{ServerControl-DpE7t-OF.js → ServerControl-BH6Doax0.js} +1 -1
- package/dist/assets/{Settings-Bw5MtDTF.js → Settings-CL7odwoU.js} +1 -1
- package/dist/assets/{Usage-DpShi3FE.js → Usage-BWZkz7Se.js} +1 -1
- package/dist/assets/{UsagePro-D4cGbozz.js → UsagePro-CoBbI-Cl.js} +1 -1
- package/dist/assets/{Webhooks-UYiTg6hn.js → Webhooks-DT2LK17V.js} +1 -1
- package/dist/assets/{Workers-D70B_uAI.js → Workers-CBEDvXm_.js} +1 -1
- package/dist/assets/{WorkersPro-YycNhm9w.js → WorkersPro-Dk4NfplT.js} +1 -1
- package/dist/assets/{Workflows-BxlrgGEB.js → Workflows-DZZA6aGW.js} +1 -1
- package/dist/assets/{api-BAGaBh6R.js → api-BgWE-705.js} +1 -1
- package/dist/assets/bq-F5BbTG1T.js +1 -0
- package/dist/assets/{flowMutationSafety-DSd3JLAV.js → flowMutationSafety-YKTHxqKv.js} +1 -1
- package/dist/assets/flowOperationPolicy-B2Etcb3f.js +1 -0
- package/dist/assets/{index-ByvrmBif.js → index-BFW2_47n.js} +3 -3
- package/dist/assets/{index-CxhXRVH_.css → index-C9yGKhQ8.css} +1 -1
- package/dist/assets/{install-C5y2ohOo.js → install-oydLpc1T.js} +2 -2
- package/dist/assets/responses-BkR2JTH9.js +1 -0
- package/dist/assets/{upstreamPaths-B_cyinGI.js → upstreamPaths-DQeKSxGc.js} +1 -1
- package/dist/assets/{useThroughputSeries-Cagm3qzB.js → useThroughputSeries-DgO8uicf.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +11 -5
- package/scripts/embedded.gen.ts +164 -164
- package/shared/flowOperationPolicy.ts +66 -0
- package/dist/assets/AddJob-CFnESFEU.js +0 -3
- package/dist/assets/BulkAddJobs-vyGFvlO3.js +0 -5
- package/dist/assets/Flows-CcNdf_jS.js +0 -2
- package/dist/assets/JobsPro-D_l1c80Q.js +0 -1
- package/dist/assets/QueueControl-Ce-O1L4p.js +0 -1
- package/dist/assets/bq-Qh0VFLKV.js +0 -1
- package/dist/assets/jobActions-LQD4KfCc.js +0 -1
- 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.
|
|
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.
|
|
99
|
+
agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.4 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.
|
|
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,10 @@ 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:tls` | Queue, Flow and Workflow over verified native TLS, including untrusted-certificate rejection |
|
|
266
|
+
| `bun run test:e2e:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.4 schema-37 migration and retention semantics |
|
|
265
267
|
| `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.
|
|
268
|
+
| `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.4 brokers + three agents sharing disposable PostgreSQL 18.6 |
|
|
267
269
|
|
|
268
270
|
## Docker
|
|
269
271
|
|
|
@@ -310,11 +312,12 @@ The canonical gate must be green before a change is considered done; CI runs the
|
|
|
310
312
|
bun run quality # architecture, lint/build, size, docs, coverage, real E2E, packed-bin smoke, audit
|
|
311
313
|
```
|
|
312
314
|
|
|
313
|
-
The E2E stage starts disposable Bunqueue 2.9.
|
|
315
|
+
The E2E stage starts disposable Bunqueue 2.9.4 processes and exercises the complete FlowProducer,
|
|
314
316
|
Workflow Engine, and Queue SDK operator bridges. It also starts PostgreSQL 18.6 in Docker with
|
|
315
317
|
three authenticated brokers and paired agents, proving cross-broker enqueue/inspect/leased
|
|
316
|
-
pull+ack, pause/resume, cron,
|
|
317
|
-
is required for the
|
|
318
|
+
pull+ack, pause/resume, cron, rate limits, group admission, group priority and group pause/resume
|
|
319
|
+
on PostgreSQL schema 20. Run it alone with `bun run test:e2e` (Docker is required for the
|
|
320
|
+
PostgreSQL stage).
|
|
318
321
|
|
|
319
322
|
The blocking browser job builds the production bundle under `/e2e/dashboard`, starts an
|
|
320
323
|
authenticated disposable Bunqueue server, and drives the UI with Playwright on Chromium, Firefox,
|
package/agent/flow/routes.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertFlowOperationAllowed } from '../../shared/flowOperationPolicy';
|
|
1
2
|
import type { ServerConfig } from '../manager';
|
|
2
3
|
import { assertManagedTarget, type ManagedTargetPolicy } from '../managedTarget';
|
|
3
4
|
import type {
|
|
@@ -160,6 +161,7 @@ export async function routeFlowRequest(
|
|
|
160
161
|
const payload = MUTATIONS_WITH_BODY.has(mutation)
|
|
161
162
|
? await readFlowJsonBody(request)
|
|
162
163
|
: (assertNoFlowBody(request), {});
|
|
164
|
+
assertFlowOperationAllowed(mutation);
|
|
163
165
|
return success(
|
|
164
166
|
await admitted(query, config, serverRunning, dependencies, ({ config: current }) =>
|
|
165
167
|
operations(dependencies).mutate(current, target, mutation, payload)
|
package/agent/flow/service.ts
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
type JobNode,
|
|
8
8
|
} from 'bunqueue/client';
|
|
9
9
|
import type { ServerConfig } from '../manager';
|
|
10
|
-
import {
|
|
10
|
+
import { managedConnection } from '../managedConnection';
|
|
11
|
+
import { assertFlowOperationAllowed, assertFlowOperationState } from '../../shared/flowOperationPolicy';
|
|
11
12
|
import {
|
|
12
13
|
asFlowRecord,
|
|
13
14
|
assertFlowBodySize,
|
|
@@ -119,11 +120,15 @@ export async function mutateFlowJob(
|
|
|
119
120
|
operation: FlowMutationOperation,
|
|
120
121
|
input: unknown
|
|
121
122
|
): Promise<unknown> {
|
|
123
|
+
assertFlowOperationAllowed(operation);
|
|
122
124
|
assertFlowBodySize(input);
|
|
123
125
|
const payload = asFlowRecord(input);
|
|
124
126
|
validateMutationPayload(operation, payload);
|
|
125
127
|
const { producer, job } = await loadJob(config, target);
|
|
126
128
|
try {
|
|
129
|
+
if (['promote', 'changeDelay', 'changePriority'].includes(operation)) {
|
|
130
|
+
assertFlowOperationState(operation, await job.getState());
|
|
131
|
+
}
|
|
127
132
|
switch (operation) {
|
|
128
133
|
case 'removeChildDependency': return { removed: await job.removeChildDependency() };
|
|
129
134
|
case 'removeUnprocessedChildren': await job.removeUnprocessedChildren(); break;
|
|
@@ -173,7 +178,7 @@ function producerFor(
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
function connectionFor(config: ServerConfig) {
|
|
176
|
-
return
|
|
181
|
+
return managedConnection(config);
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
async function loadJob(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
import type { ConnectionOptions } from 'bunqueue/client';
|
|
4
|
+
import type { ServerConfig } from './manager';
|
|
5
|
+
import { managedAuthToken } from './managedTarget';
|
|
6
|
+
|
|
7
|
+
/** Same environment precedence as the managed child; never disables verification. */
|
|
8
|
+
export function managedConnection(config: ServerConfig): ConnectionOptions {
|
|
9
|
+
const env = (key: string) =>
|
|
10
|
+
(Object.hasOwn(config.extraEnv, key) ? config.extraEnv[key] : process.env[key])?.trim();
|
|
11
|
+
const mode = env('BUNQUEUE_AGENT_TCP_TLS');
|
|
12
|
+
if (mode && mode !== 'true' && mode !== 'false') {
|
|
13
|
+
throw new Error('BUNQUEUE_AGENT_TCP_TLS must be true or false.');
|
|
14
|
+
}
|
|
15
|
+
const caFile = env('BUNQUEUE_AGENT_TCP_CA_FILE');
|
|
16
|
+
const serverTls = Boolean(env('TLS_CERT_FILE') || env('TLS_KEY_FILE'));
|
|
17
|
+
if (mode === 'false' && (serverTls || caFile)) {
|
|
18
|
+
throw new Error('Cannot disable agent TCP TLS when server TLS or a CA is configured.');
|
|
19
|
+
}
|
|
20
|
+
if (caFile) {
|
|
21
|
+
if (!isAbsolute(caFile)) throw new Error('BUNQUEUE_AGENT_TCP_CA_FILE must be absolute.');
|
|
22
|
+
try {
|
|
23
|
+
if (!statSync(caFile).isFile()) throw new Error();
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error('BUNQUEUE_AGENT_TCP_CA_FILE must point to a readable CA file.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const tls = mode === 'true' || serverTls || Boolean(caFile);
|
|
29
|
+
return {
|
|
30
|
+
host: '127.0.0.1',
|
|
31
|
+
port: config.tcpPort,
|
|
32
|
+
token: managedAuthToken(config),
|
|
33
|
+
...(tls ? { tls: { rejectUnauthorized: true, ...(caFile ? { caFile } : {}) } } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
package/agent/manager/config.ts
CHANGED
|
@@ -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.
|
|
13
|
+
command: process.env.BUNQUEUE_START_CMD || 'bunx bunqueue@2.9.4 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',
|
package/agent/queue/routes.ts
CHANGED
|
@@ -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({
|
|
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
|
package/agent/queue/runtime.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Queue, type QueueMetricType, type QueueMetrics } from 'bunqueue/client';
|
|
2
|
-
import {
|
|
2
|
+
import { managedConnection } from '../managedConnection';
|
|
3
3
|
import type { ServerConfig } from '../manager';
|
|
4
4
|
import type { QueueGroupSnapshot, QueueLimitSnapshot, QueueOperationsPort } from './types';
|
|
5
5
|
import { QueueOperationsUnavailableError } from './types';
|
|
@@ -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 [
|
|
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 {
|
|
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,
|
|
@@ -174,7 +220,7 @@ export class QueueOperationsRuntime implements QueueOperationsPort {
|
|
|
174
220
|
}
|
|
175
221
|
|
|
176
222
|
function connectionFor(config: ServerConfig) {
|
|
177
|
-
return
|
|
223
|
+
return managedConnection(config);
|
|
178
224
|
}
|
|
179
225
|
|
|
180
226
|
function defaultClient(config: ServerConfig, queue: string): QueueOperationsClient {
|
package/agent/queue/types.ts
CHANGED
|
@@ -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');
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type ExecutionState,
|
|
8
8
|
type Workflow,
|
|
9
9
|
} from 'bunqueue/workflow';
|
|
10
|
-
import {
|
|
10
|
+
import { managedConnection } from '../managedConnection';
|
|
11
11
|
import type { ServerConfig } from '../manager';
|
|
12
12
|
import {
|
|
13
13
|
configuredWorkflowModule,
|
|
@@ -188,11 +188,7 @@ export class WorkflowRuntime implements WorkflowRuntimePort {
|
|
|
188
188
|
dataPath: config.dataPath,
|
|
189
189
|
queueName: options.queueName,
|
|
190
190
|
concurrency: options.concurrency,
|
|
191
|
-
connection:
|
|
192
|
-
host: '127.0.0.1',
|
|
193
|
-
port: config.tcpPort,
|
|
194
|
-
token: managedAuthToken(config),
|
|
195
|
-
},
|
|
191
|
+
connection: managedConnection(config),
|
|
196
192
|
});
|
|
197
193
|
const active: ActiveRuntime = {
|
|
198
194
|
engine,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
2
|
import { safeErrorMessage } from '../errorMessage';
|
|
3
|
-
import {
|
|
3
|
+
import { managedConnection } from '../managedConnection';
|
|
4
4
|
import type { ServerConfig } from '../manager';
|
|
5
5
|
import type { WorkflowRuntimeStatus } from './runtime';
|
|
6
6
|
|
|
@@ -53,7 +53,7 @@ export function workflowRuntimeSignature(
|
|
|
53
53
|
modified,
|
|
54
54
|
config.dataPath,
|
|
55
55
|
config.tcpPort,
|
|
56
|
-
|
|
56
|
+
managedConnection(config),
|
|
57
57
|
options.queueName,
|
|
58
58
|
options.concurrency,
|
|
59
59
|
]);
|
package/agent/workflows.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read-only facade for Bunqueue 2.9.
|
|
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';
|