bunqueue-client 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/README.md +44 -1
- package/dist/ack-batcher.d.ts +28 -0
- package/dist/ack-batcher.js +67 -0
- package/dist/backpressure.d.ts +22 -0
- package/dist/backpressure.js +39 -0
- package/dist/connection-pool.d.ts +30 -0
- package/dist/connection-pool.js +60 -0
- package/dist/connection-types.d.ts +21 -1
- package/dist/connection.d.ts +20 -3
- package/dist/connection.js +70 -3
- package/dist/flow-types.d.ts +2 -1
- package/dist/flow.js +33 -7
- package/dist/index.d.ts +7 -2
- package/dist/index.js +3 -1
- package/dist/job.d.ts +2 -2
- package/dist/observability.d.ts +95 -0
- package/dist/observability.js +110 -0
- package/dist/queue-control.js +6 -5
- package/dist/queue-query.d.ts +9 -2
- package/dist/queue-query.js +44 -26
- package/dist/queue.d.ts +13 -4
- package/dist/queue.js +26 -11
- package/dist/responses.d.ts +79 -0
- package/dist/responses.js +10 -0
- package/dist/worker-base.d.ts +2 -0
- package/dist/worker-base.js +5 -0
- package/dist/worker-types.d.ts +15 -1
- package/dist/worker.d.ts +4 -0
- package/dist/worker.js +46 -7
- package/package.json +2 -1
- package/src/ack-batcher.ts +76 -0
- package/src/backpressure.ts +40 -0
- package/src/connection-pool.ts +71 -0
- package/src/connection-types.ts +23 -1
- package/src/connection.ts +72 -6
- package/src/flow-types.ts +2 -1
- package/src/flow.ts +46 -18
- package/src/index.ts +28 -2
- package/src/job.ts +3 -3
- package/src/observability.ts +158 -0
- package/src/queue-control.ts +9 -11
- package/src/queue-query.ts +73 -35
- package/src/queue.ts +42 -15
- package/src/responses.ts +96 -0
- package/src/worker-base.ts +6 -0
- package/src/worker-types.ts +16 -1
- package/src/worker.ts +48 -7
package/dist/flow.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* UpdateParent fix-up, rollback via Cancel on failure).
|
|
5
5
|
*/
|
|
6
6
|
import { Connection } from './connection.js';
|
|
7
|
+
import { CommandError } from './errors.js';
|
|
7
8
|
import { compact } from './frame.js';
|
|
8
9
|
import { Job } from './job.js';
|
|
9
10
|
import { jobPayload, wireJobOptions } from './types.js';
|
|
@@ -13,7 +14,14 @@ export class FlowProducer {
|
|
|
13
14
|
constructor(opts = {}) {
|
|
14
15
|
this.connection =
|
|
15
16
|
opts.connection ??
|
|
16
|
-
new Connection({
|
|
17
|
+
new Connection({
|
|
18
|
+
host: opts.host,
|
|
19
|
+
port: opts.port,
|
|
20
|
+
token: opts.token,
|
|
21
|
+
tls: opts.tls,
|
|
22
|
+
logger: opts.logger,
|
|
23
|
+
onTelemetry: opts.onTelemetry,
|
|
24
|
+
});
|
|
17
25
|
this.ownsConnection = opts.connection === undefined;
|
|
18
26
|
}
|
|
19
27
|
/** Add a flow tree. Children are created (and processed) BEFORE their parent. */
|
|
@@ -43,7 +51,7 @@ export class FlowProducer {
|
|
|
43
51
|
}
|
|
44
52
|
/** Fetch a flow tree starting from a job id (recursive over childrenIds). */
|
|
45
53
|
getFlow(opts) {
|
|
46
|
-
return this.fetchNode(opts.id, opts.depth ?? Number.POSITIVE_INFINITY, opts.maxChildren);
|
|
54
|
+
return this.fetchNode(opts.id, opts.depth ?? Number.POSITIVE_INFINITY, opts.maxChildren, new Set());
|
|
47
55
|
}
|
|
48
56
|
/** Add a sequential chain: step[0] → step[1] → ... via dependsOn. */
|
|
49
57
|
async addChain(steps) {
|
|
@@ -57,13 +65,15 @@ export class FlowProducer {
|
|
|
57
65
|
...jobPayload(step.name, step.data),
|
|
58
66
|
__flowParentId: prevId ?? undefined,
|
|
59
67
|
});
|
|
60
|
-
|
|
68
|
+
// Connection.call compacts internally, so no outer compact() is needed
|
|
69
|
+
// (nesting it confused generic inference of `data`/`response`).
|
|
70
|
+
const response = await this.connection.call({
|
|
61
71
|
cmd: 'PUSH',
|
|
62
72
|
queue: step.queueName,
|
|
63
73
|
data,
|
|
64
74
|
...wireJobOptions(step.opts),
|
|
65
75
|
dependsOn: prevId ? [prevId] : undefined,
|
|
66
|
-
})
|
|
76
|
+
});
|
|
67
77
|
const id = String(response.id);
|
|
68
78
|
jobIds.push(id);
|
|
69
79
|
prevId = id;
|
|
@@ -156,8 +166,24 @@ export class FlowProducer {
|
|
|
156
166
|
}
|
|
157
167
|
return parentJobId;
|
|
158
168
|
}
|
|
159
|
-
async fetchNode(id, depth, maxChildren) {
|
|
160
|
-
|
|
169
|
+
async fetchNode(id, depth, maxChildren, visited) {
|
|
170
|
+
if (visited.has(id))
|
|
171
|
+
return null; // cycle guard: id already on the current path
|
|
172
|
+
visited.add(id);
|
|
173
|
+
// A missing job — the root, or a child removed via removeOnComplete/cancel
|
|
174
|
+
// (childrenIds is a static push-time list, never pruned) — yields null and
|
|
175
|
+
// is skipped, returning the surviving partial tree instead of throwing.
|
|
176
|
+
let response;
|
|
177
|
+
try {
|
|
178
|
+
response = await this.connection.call({ cmd: 'GetJob', id });
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
// Only 'Job not found' means a removed node; a real server error must not
|
|
182
|
+
// masquerade as a missing child and yield a misleading partial tree.
|
|
183
|
+
if (err instanceof CommandError && /not found/i.test(err.message))
|
|
184
|
+
return null;
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
161
187
|
const raw = response.job;
|
|
162
188
|
if (!raw)
|
|
163
189
|
return null;
|
|
@@ -167,7 +193,7 @@ export class FlowProducer {
|
|
|
167
193
|
const limit = maxChildren ?? job.childrenIds.length;
|
|
168
194
|
const children = [];
|
|
169
195
|
for (const childId of job.childrenIds.slice(0, limit)) {
|
|
170
|
-
const child = await this.fetchNode(childId, depth - 1, maxChildren);
|
|
196
|
+
const child = await this.fetchNode(childId, depth - 1, maxChildren, visited);
|
|
171
197
|
if (child)
|
|
172
198
|
children.push(child);
|
|
173
199
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,16 +7,21 @@ export type { DlqFilter, DlqStats } from './bunqueue/dlq-rate-limit.js';
|
|
|
7
7
|
export type { BatchConfig, BatchProcessor, BunqueueConnection, BunqueueDebounceConfig, BunqueueDeduplicationConfig, BunqueueDlqConfig, BunqueueMiddleware, BunqueueOptions, CircuitBreakerConfig, CircuitState, JobTtlConfig, PriorityAgingConfig, RateLimiterOptions, RetryConfig, RetryStrategy, TriggerRule, } from './bunqueue/types.js';
|
|
8
8
|
export type { Command, ConnectionOptions, Response, TlsOption } from './connection.js';
|
|
9
9
|
export { Connection } from './connection.js';
|
|
10
|
+
export { ConnectionPool } from './connection-pool.js';
|
|
11
|
+
export type { ConnectionLike } from './connection-types.js';
|
|
10
12
|
export { AuthError, BunqueueError, CommandError, CommandTimeoutError, ConnectionClosedError, UnrecoverableError, } from './errors.js';
|
|
11
13
|
export { FlowProducer } from './flow.js';
|
|
12
14
|
export type { FlowJob, FlowProducerOptions, FlowStep, GetFlowOptions, JobNode, } from './flow-types.js';
|
|
13
15
|
export { MAX_FRAME_SIZE, PROTOCOL_VERSION } from './frame.js';
|
|
14
16
|
export type { JobRaw } from './job.js';
|
|
15
17
|
export { Job } from './job.js';
|
|
18
|
+
export type { Logger, LogLevel, Observability, TelemetryEvent, TelemetryHandler, } from './observability.js';
|
|
19
|
+
export { consoleLogger, noopLogger } from './observability.js';
|
|
16
20
|
export type { BulkJobEntry, QueueOptions } from './queue.js';
|
|
17
21
|
export { Queue } from './queue.js';
|
|
18
22
|
export type { SchedulerOptions } from './queue-admin.js';
|
|
23
|
+
export type { BatchResponse, CountResponse, DataResponse, JobCountsResponse, JobResponse, JobsResponse, OkResponse, PausedResponse, ProgressResponse, PulledJobResponse, PulledJobsResponse, ResultResponse, StateResponse, WaitJobResponse, } from './responses.js';
|
|
19
24
|
export type { BackoffOptions, DeduplicationOptions, JobCounts, JobOptions, JobStateName, RepeatOptions, } from './types.js';
|
|
20
25
|
export { Worker } from './worker.js';
|
|
21
|
-
export type { Processor, WorkerOptions } from './worker-types.js';
|
|
22
|
-
export declare const __version__ = "0.1.
|
|
26
|
+
export type { AckBatchOptions, Processor, WorkerOptions } from './worker-types.js';
|
|
27
|
+
export declare const __version__ = "0.1.6";
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
// Simple Mode (Bunqueue): all-in-one Queue + Worker, 1:1 with the official client
|
|
6
6
|
export { Bunqueue } from './bunqueue/bunqueue.js';
|
|
7
7
|
export { Connection } from './connection.js';
|
|
8
|
+
export { ConnectionPool } from './connection-pool.js';
|
|
8
9
|
export { AuthError, BunqueueError, CommandError, CommandTimeoutError, ConnectionClosedError, UnrecoverableError, } from './errors.js';
|
|
9
10
|
export { FlowProducer } from './flow.js';
|
|
10
11
|
export { MAX_FRAME_SIZE, PROTOCOL_VERSION } from './frame.js';
|
|
11
12
|
export { Job } from './job.js';
|
|
13
|
+
export { consoleLogger, noopLogger } from './observability.js';
|
|
12
14
|
export { Queue } from './queue.js';
|
|
13
15
|
export { Worker } from './worker.js';
|
|
14
|
-
export const __version__ = '0.1.
|
|
16
|
+
export const __version__ = '0.1.6';
|
package/dist/job.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Job wrapper: read view over the server job record + per-job operations.
|
|
3
3
|
* Mirrors the official TS client's Job surface (TCP mode).
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
5
|
+
import type { ConnectionLike } from './connection-types.js';
|
|
6
6
|
export type JobRaw = Record<string, unknown>;
|
|
7
7
|
export type ProgressHook = (job: Job, progress: number) => void;
|
|
8
8
|
export declare class Job<T = unknown> {
|
|
@@ -10,7 +10,7 @@ export declare class Job<T = unknown> {
|
|
|
10
10
|
readonly token: string | undefined;
|
|
11
11
|
private readonly conn;
|
|
12
12
|
private readonly onProgress;
|
|
13
|
-
constructor(raw: JobRaw, connection?:
|
|
13
|
+
constructor(raw: JobRaw, connection?: ConnectionLike, token?: string, onProgress?: ProgressHook);
|
|
14
14
|
get id(): string;
|
|
15
15
|
get queue(): string;
|
|
16
16
|
get data(): T;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability primitives: a pluggable structured logger and a telemetry
|
|
3
|
+
* sink. Zero hard dependencies — wire your own OpenTelemetry / Prometheus /
|
|
4
|
+
* logging stack through these interfaces. Defaults are no-ops, so the SDK is
|
|
5
|
+
* silent unless you opt in.
|
|
6
|
+
*/
|
|
7
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
export interface Logger {
|
|
9
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
10
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
11
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
12
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
13
|
+
}
|
|
14
|
+
/** Silent logger (the default). */
|
|
15
|
+
export declare const noopLogger: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* A console-backed logger. Levels below `min` are dropped. Opt-in — pass it as
|
|
18
|
+
* `logger` when you want the SDK to log to the console.
|
|
19
|
+
*/
|
|
20
|
+
export declare function consoleLogger(min?: LogLevel): Logger;
|
|
21
|
+
/** A single telemetry data point. A discriminated union keyed on `type`. */
|
|
22
|
+
export type TelemetryEvent = {
|
|
23
|
+
type: 'command';
|
|
24
|
+
cmd: string;
|
|
25
|
+
reqId: string;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
ok: boolean;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'command_timeout';
|
|
30
|
+
cmd: string;
|
|
31
|
+
reqId: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'connect';
|
|
34
|
+
host: string;
|
|
35
|
+
port: number;
|
|
36
|
+
generation: number;
|
|
37
|
+
durationMs: number;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'disconnect';
|
|
40
|
+
host: string;
|
|
41
|
+
port: number;
|
|
42
|
+
generation: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'reconnect_scheduled';
|
|
45
|
+
host: string;
|
|
46
|
+
port: number;
|
|
47
|
+
attempt: number;
|
|
48
|
+
delayMs: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'auth';
|
|
51
|
+
ok: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'backpressure';
|
|
54
|
+
inFlight: number;
|
|
55
|
+
maxInFlight: number;
|
|
56
|
+
};
|
|
57
|
+
export type TelemetryHandler = (event: TelemetryEvent) => void;
|
|
58
|
+
/** Lifecycle telemetry types that are ALSO emitted as EventEmitter events. */
|
|
59
|
+
export declare const LIFECYCLE_EVENTS: readonly ["connect", "disconnect", "reconnect_scheduled"];
|
|
60
|
+
export interface Observability {
|
|
61
|
+
/** Structured logger; defaults to {@link noopLogger}. */
|
|
62
|
+
logger?: Logger;
|
|
63
|
+
/**
|
|
64
|
+
* Telemetry sink for metrics and tracing. Receives every command's latency,
|
|
65
|
+
* connect/disconnect/reconnect, auth and backpressure events. Bridge it to
|
|
66
|
+
* OpenTelemetry spans or Prometheus counters without the SDK depending on
|
|
67
|
+
* either.
|
|
68
|
+
*/
|
|
69
|
+
onTelemetry?: TelemetryHandler;
|
|
70
|
+
}
|
|
71
|
+
/** High-resolution monotonic clock (Node/Bun/Deno all expose `performance`). */
|
|
72
|
+
export declare const nowMs: () => number;
|
|
73
|
+
/**
|
|
74
|
+
* Fans a telemetry event out to (1) the telemetry sink, (2) the logger, and
|
|
75
|
+
* (3) — for lifecycle events — the owning EventEmitter, so users can both
|
|
76
|
+
* scrape metrics and attach imperative `connection.on('connect', …)` handlers.
|
|
77
|
+
*/
|
|
78
|
+
export declare class Telemetry {
|
|
79
|
+
private readonly logger;
|
|
80
|
+
private readonly sink;
|
|
81
|
+
private readonly emit;
|
|
82
|
+
constructor(obs: Observability | undefined, emit: (event: string, payload: unknown) => void);
|
|
83
|
+
/** Observability must never break the client: swallow consumer errors. */
|
|
84
|
+
private static safely;
|
|
85
|
+
get log(): Logger;
|
|
86
|
+
/** Record a completed command's latency and outcome. */
|
|
87
|
+
command(cmd: string, reqId: string, startMs: number, ok: boolean): void;
|
|
88
|
+
timeout(cmd: string, reqId: string): void;
|
|
89
|
+
connected(host: string, port: number, generation: number, startMs: number): void;
|
|
90
|
+
disconnected(host: string, port: number, generation: number): void;
|
|
91
|
+
reconnectScheduled(host: string, port: number, attempt: number, delayMs: number): void;
|
|
92
|
+
auth(ok: boolean): void;
|
|
93
|
+
backpressure(inFlight: number, maxInFlight: number): void;
|
|
94
|
+
private dispatch;
|
|
95
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability primitives: a pluggable structured logger and a telemetry
|
|
3
|
+
* sink. Zero hard dependencies — wire your own OpenTelemetry / Prometheus /
|
|
4
|
+
* logging stack through these interfaces. Defaults are no-ops, so the SDK is
|
|
5
|
+
* silent unless you opt in.
|
|
6
|
+
*/
|
|
7
|
+
/** Silent logger (the default). */
|
|
8
|
+
export const noopLogger = {
|
|
9
|
+
debug() { },
|
|
10
|
+
info() { },
|
|
11
|
+
warn() { },
|
|
12
|
+
error() { },
|
|
13
|
+
};
|
|
14
|
+
const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
15
|
+
/**
|
|
16
|
+
* A console-backed logger. Levels below `min` are dropped. Opt-in — pass it as
|
|
17
|
+
* `logger` when you want the SDK to log to the console.
|
|
18
|
+
*/
|
|
19
|
+
export function consoleLogger(min = 'info') {
|
|
20
|
+
const floor = LEVELS[min];
|
|
21
|
+
const line = (lvl, msg, meta) => {
|
|
22
|
+
if (LEVELS[lvl] < floor)
|
|
23
|
+
return;
|
|
24
|
+
const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
|
|
25
|
+
const sink = lvl === 'debug' ? console.log : console[lvl];
|
|
26
|
+
sink(`[bunqueue] ${lvl} ${msg}${suffix}`);
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
debug: (m, meta) => line('debug', m, meta),
|
|
30
|
+
info: (m, meta) => line('info', m, meta),
|
|
31
|
+
warn: (m, meta) => line('warn', m, meta),
|
|
32
|
+
error: (m, meta) => line('error', m, meta),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Lifecycle telemetry types that are ALSO emitted as EventEmitter events. */
|
|
36
|
+
export const LIFECYCLE_EVENTS = ['connect', 'disconnect', 'reconnect_scheduled'];
|
|
37
|
+
/** High-resolution monotonic clock (Node/Bun/Deno all expose `performance`). */
|
|
38
|
+
export const nowMs = () => performance.now();
|
|
39
|
+
/**
|
|
40
|
+
* Fans a telemetry event out to (1) the telemetry sink, (2) the logger, and
|
|
41
|
+
* (3) — for lifecycle events — the owning EventEmitter, so users can both
|
|
42
|
+
* scrape metrics and attach imperative `connection.on('connect', …)` handlers.
|
|
43
|
+
*/
|
|
44
|
+
export class Telemetry {
|
|
45
|
+
logger;
|
|
46
|
+
sink;
|
|
47
|
+
emit;
|
|
48
|
+
constructor(obs, emit) {
|
|
49
|
+
// Wrap the user logger so a throwing logger can never break the transport
|
|
50
|
+
// hot path (dispatch runs inside socket 'data' handlers and connect).
|
|
51
|
+
const inner = obs?.logger ?? noopLogger;
|
|
52
|
+
this.logger =
|
|
53
|
+
inner === noopLogger
|
|
54
|
+
? inner
|
|
55
|
+
: {
|
|
56
|
+
debug: (m, meta) => Telemetry.safely(() => inner.debug(m, meta)),
|
|
57
|
+
info: (m, meta) => Telemetry.safely(() => inner.info(m, meta)),
|
|
58
|
+
warn: (m, meta) => Telemetry.safely(() => inner.warn(m, meta)),
|
|
59
|
+
error: (m, meta) => Telemetry.safely(() => inner.error(m, meta)),
|
|
60
|
+
};
|
|
61
|
+
this.sink = obs?.onTelemetry;
|
|
62
|
+
this.emit = emit;
|
|
63
|
+
}
|
|
64
|
+
/** Observability must never break the client: swallow consumer errors. */
|
|
65
|
+
static safely(fn) {
|
|
66
|
+
try {
|
|
67
|
+
fn();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* consumer logging/telemetry error — intentionally ignored */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
get log() {
|
|
74
|
+
return this.logger;
|
|
75
|
+
}
|
|
76
|
+
/** Record a completed command's latency and outcome. */
|
|
77
|
+
command(cmd, reqId, startMs, ok) {
|
|
78
|
+
this.dispatch({ type: 'command', cmd, reqId, durationMs: nowMs() - startMs, ok });
|
|
79
|
+
}
|
|
80
|
+
timeout(cmd, reqId) {
|
|
81
|
+
this.logger.warn('command timed out', { cmd, reqId });
|
|
82
|
+
this.dispatch({ type: 'command_timeout', cmd, reqId });
|
|
83
|
+
}
|
|
84
|
+
connected(host, port, generation, startMs) {
|
|
85
|
+
this.logger.info('connected', { host, port, generation });
|
|
86
|
+
this.dispatch({ type: 'connect', host, port, generation, durationMs: nowMs() - startMs });
|
|
87
|
+
}
|
|
88
|
+
disconnected(host, port, generation) {
|
|
89
|
+
this.logger.info('disconnected', { host, port, generation });
|
|
90
|
+
this.dispatch({ type: 'disconnect', host, port, generation });
|
|
91
|
+
}
|
|
92
|
+
reconnectScheduled(host, port, attempt, delayMs) {
|
|
93
|
+
this.logger.warn('reconnect scheduled', { host, port, attempt, delayMs });
|
|
94
|
+
this.dispatch({ type: 'reconnect_scheduled', host, port, attempt, delayMs });
|
|
95
|
+
}
|
|
96
|
+
auth(ok) {
|
|
97
|
+
this.dispatch({ type: 'auth', ok });
|
|
98
|
+
}
|
|
99
|
+
backpressure(inFlight, maxInFlight) {
|
|
100
|
+
this.dispatch({ type: 'backpressure', inFlight, maxInFlight });
|
|
101
|
+
}
|
|
102
|
+
dispatch(event) {
|
|
103
|
+
// A throwing sink or lifecycle listener must never break the transport:
|
|
104
|
+
// dispatch runs inside socket 'data' handlers and the connect path.
|
|
105
|
+
Telemetry.safely(() => this.sink?.(event));
|
|
106
|
+
if (LIFECYCLE_EVENTS.includes(event.type)) {
|
|
107
|
+
Telemetry.safely(() => this.emit(event.type, event));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/queue-control.js
CHANGED
|
@@ -11,8 +11,7 @@ export const controlMethods = {
|
|
|
11
11
|
await this.call({ cmd: 'Resume', queue: this.name });
|
|
12
12
|
},
|
|
13
13
|
async isPaused() {
|
|
14
|
-
|
|
15
|
-
return response.paused === true;
|
|
14
|
+
return (await this.call({ cmd: 'IsPaused', queue: this.name })).paused === true;
|
|
16
15
|
},
|
|
17
16
|
async drain() {
|
|
18
17
|
await this.call({ cmd: 'Drain', queue: this.name });
|
|
@@ -23,7 +22,7 @@ export const controlMethods = {
|
|
|
23
22
|
/** Remove old jobs; returns how many were removed. */
|
|
24
23
|
async clean(graceMs, limit, state) {
|
|
25
24
|
const response = await this.call(compact({ cmd: 'Clean', queue: this.name, grace: graceMs, limit, state }));
|
|
26
|
-
return
|
|
25
|
+
return response.count ?? 0;
|
|
27
26
|
},
|
|
28
27
|
async remove(id) {
|
|
29
28
|
await this.call({ cmd: 'Cancel', id });
|
|
@@ -37,7 +36,7 @@ export const controlMethods = {
|
|
|
37
36
|
/** Promote delayed jobs to waiting; returns how many were promoted. */
|
|
38
37
|
async promoteJobs(opts = {}) {
|
|
39
38
|
const response = await this.call(compact({ cmd: 'PromoteJobs', queue: this.name, count: opts.count }));
|
|
40
|
-
return
|
|
39
|
+
return response.count ?? 0;
|
|
41
40
|
},
|
|
42
41
|
/** BullMQ contract: failed → waiting. */
|
|
43
42
|
async retryJob(id) {
|
|
@@ -48,7 +47,9 @@ export const controlMethods = {
|
|
|
48
47
|
await this.call({ cmd: 'RetryCompleted', queue: this.name });
|
|
49
48
|
return;
|
|
50
49
|
}
|
|
51
|
-
|
|
50
|
+
// `count` is accepted for API parity but not sent: the server has no
|
|
51
|
+
// partial RetryDlq — it retries the whole DLQ (BullMQ semantics).
|
|
52
|
+
await this.call({ cmd: 'RetryDlq', queue: this.name });
|
|
52
53
|
},
|
|
53
54
|
async retryCompleted(id) {
|
|
54
55
|
await this.call(compact({ cmd: 'RetryCompleted', queue: this.name, id }));
|
package/dist/queue-query.d.ts
CHANGED
|
@@ -32,7 +32,14 @@ export declare const queryMethods: {
|
|
|
32
32
|
removeChildDependency(this: Ctx, id: string): Promise<void>;
|
|
33
33
|
/** Remove all still-unprocessed children of a parent job. */
|
|
34
34
|
removeUnprocessedChildren(this: Ctx, id: string): Promise<void>;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Block until the job completes; returns its result.
|
|
37
|
+
* The server's WaitJob waiter resolves only on completion, replying
|
|
38
|
+
* `{ok:true, completed:false}` (no result) otherwise — so returning undefined
|
|
39
|
+
* would be indistinguishable from a genuine undefined result. On
|
|
40
|
+
* non-completion we probe the state: a `failed` job throws CommandError (it
|
|
41
|
+
* will not complete), everything else throws CommandTimeoutError.
|
|
42
|
+
*/
|
|
36
43
|
waitForJob<R = unknown>(this: Ctx, id: string, ttlMs?: number): Promise<R>;
|
|
37
44
|
/** BullMQ v5 alias for waitForJob (queueEvents param unused over TCP). */
|
|
38
45
|
waitJobUntilFinished<R = unknown>(this: Ctx, id: string, _queueEvents?: unknown, ttlMs?: number): Promise<R>;
|
|
@@ -50,7 +57,7 @@ export declare const queryMethods: {
|
|
|
50
57
|
getWaitingChildrenCount(this: Ctx): Promise<number>;
|
|
51
58
|
count(this: Ctx): Promise<number>;
|
|
52
59
|
getCountsPerPriority(this: Ctx): Promise<Record<string, number>>;
|
|
53
|
-
addJobLog(this: Ctx, id: string, message: string): Promise<void>;
|
|
60
|
+
addJobLog(this: Ctx, id: string, message: string, level?: "info" | "warn" | "error"): Promise<void>;
|
|
54
61
|
getJobLogs(this: Ctx, id: string, start?: number, end?: number): Promise<string[]>;
|
|
55
62
|
clearJobLogs(this: Ctx, id: string, keepLogs?: number): Promise<void>;
|
|
56
63
|
};
|
package/dist/queue-query.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Queue query surface: job lookup, state, results, counts, logs, children.
|
|
3
3
|
* Methods are merged onto Queue.prototype by queue.ts.
|
|
4
4
|
*/
|
|
5
|
-
import { CommandError } from './errors.js';
|
|
5
|
+
import { CommandError, CommandTimeoutError } from './errors.js';
|
|
6
6
|
import { compact } from './frame.js';
|
|
7
7
|
import { Job } from './job.js';
|
|
8
8
|
function unwrapValues(response) {
|
|
@@ -13,8 +13,7 @@ export const queryMethods = {
|
|
|
13
13
|
async getJob(id) {
|
|
14
14
|
try {
|
|
15
15
|
const response = await this.call({ cmd: 'GetJob', id });
|
|
16
|
-
|
|
17
|
-
return raw ? new Job(raw, this.connection) : null;
|
|
16
|
+
return response.job ? new Job(response.job, this.connection) : null;
|
|
18
17
|
}
|
|
19
18
|
catch (err) {
|
|
20
19
|
if (err instanceof CommandError)
|
|
@@ -25,8 +24,7 @@ export const queryMethods = {
|
|
|
25
24
|
async getJobByCustomId(customId) {
|
|
26
25
|
try {
|
|
27
26
|
const response = await this.call({ cmd: 'GetJobByCustomId', customId });
|
|
28
|
-
|
|
29
|
-
return raw ? new Job(raw, this.connection) : null;
|
|
27
|
+
return response.job ? new Job(response.job, this.connection) : null;
|
|
30
28
|
}
|
|
31
29
|
catch (err) {
|
|
32
30
|
if (err instanceof CommandError)
|
|
@@ -49,11 +47,12 @@ export const queryMethods = {
|
|
|
49
47
|
offset: start,
|
|
50
48
|
limit: end - start,
|
|
51
49
|
}));
|
|
52
|
-
|
|
53
|
-
return jobs.map((raw) => new Job(raw, this.connection));
|
|
50
|
+
return (response.jobs ?? []).map((raw) => new Job(raw, this.connection));
|
|
54
51
|
},
|
|
55
52
|
getWaiting(start, end) {
|
|
56
|
-
|
|
53
|
+
// Only the 'waiting' bucket — prioritized jobs live in a separate bucket
|
|
54
|
+
// (getPrioritized), matching BullMQ and the Python SDK / reference client.
|
|
55
|
+
return this.getJobs({ state: 'waiting', start, end });
|
|
57
56
|
},
|
|
58
57
|
getDelayed(start, end) {
|
|
59
58
|
return this.getJobs({ state: 'delayed', start, end });
|
|
@@ -74,12 +73,10 @@ export const queryMethods = {
|
|
|
74
73
|
return this.getJobs({ state: 'waiting-children', start, end });
|
|
75
74
|
},
|
|
76
75
|
async getJobState(id) {
|
|
77
|
-
|
|
78
|
-
return String(response.state);
|
|
76
|
+
return (await this.call({ cmd: 'GetState', id })).state;
|
|
79
77
|
},
|
|
80
78
|
async getResult(id) {
|
|
81
|
-
|
|
82
|
-
return response.result;
|
|
79
|
+
return (await this.call({ cmd: 'GetResult', id })).result;
|
|
83
80
|
},
|
|
84
81
|
async getChildrenValues(id) {
|
|
85
82
|
return unwrapValues(await this.call({ cmd: 'GetChildrenValues', id }));
|
|
@@ -98,9 +95,28 @@ export const queryMethods = {
|
|
|
98
95
|
async removeUnprocessedChildren(id) {
|
|
99
96
|
await this.call({ cmd: 'RemoveUnprocessedChildren', id });
|
|
100
97
|
},
|
|
101
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Block until the job completes; returns its result.
|
|
100
|
+
* The server's WaitJob waiter resolves only on completion, replying
|
|
101
|
+
* `{ok:true, completed:false}` (no result) otherwise — so returning undefined
|
|
102
|
+
* would be indistinguishable from a genuine undefined result. On
|
|
103
|
+
* non-completion we probe the state: a `failed` job throws CommandError (it
|
|
104
|
+
* will not complete), everything else throws CommandTimeoutError.
|
|
105
|
+
*/
|
|
102
106
|
async waitForJob(id, ttlMs = 30_000) {
|
|
103
107
|
const response = await this.call({ cmd: 'WaitJob', id, timeout: ttlMs }, ttlMs + 5000);
|
|
108
|
+
if (response.completed !== true) {
|
|
109
|
+
let state;
|
|
110
|
+
try {
|
|
111
|
+
state = await this.getJobState(id);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* ignore probe failure; fall through to timeout */
|
|
115
|
+
}
|
|
116
|
+
if (state === 'failed')
|
|
117
|
+
throw new CommandError(`job ${id} failed before completion`);
|
|
118
|
+
throw new CommandTimeoutError(`waitUntilFinished timed out after ${ttlMs}ms`);
|
|
119
|
+
}
|
|
104
120
|
return response.result;
|
|
105
121
|
},
|
|
106
122
|
/** BullMQ v5 alias for waitForJob (queueEvents param unused over TCP). */
|
|
@@ -109,19 +125,16 @@ export const queryMethods = {
|
|
|
109
125
|
},
|
|
110
126
|
async getProgress(id) {
|
|
111
127
|
const response = await this.call({ cmd: 'GetProgress', id });
|
|
112
|
-
return {
|
|
113
|
-
progress: Number(response.progress ?? 0),
|
|
114
|
-
message: response.message ?? null,
|
|
115
|
-
};
|
|
128
|
+
return { progress: response.progress ?? 0, message: response.message ?? null };
|
|
116
129
|
},
|
|
117
130
|
// ------------------------------------------------------------------- counts
|
|
118
131
|
async getJobCounts() {
|
|
119
|
-
|
|
120
|
-
return response.counts;
|
|
132
|
+
return (await this.call({ cmd: 'GetJobCounts', queue: this.name })).counts;
|
|
121
133
|
},
|
|
122
134
|
async getWaitingCount() {
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
// 'waiting' only — prioritized jobs are counted by getPrioritizedCount,
|
|
136
|
+
// matching BullMQ and the Python SDK / reference client.
|
|
137
|
+
return (await this.getJobCounts()).waiting;
|
|
125
138
|
},
|
|
126
139
|
async getActiveCount() {
|
|
127
140
|
return (await this.getJobCounts()).active;
|
|
@@ -142,22 +155,27 @@ export const queryMethods = {
|
|
|
142
155
|
return (await this.getJobCounts())['waiting-children'];
|
|
143
156
|
},
|
|
144
157
|
async count() {
|
|
145
|
-
|
|
146
|
-
return Number(response.count ?? 0);
|
|
158
|
+
return (await this.call({ cmd: 'Count', queue: this.name })).count ?? 0;
|
|
147
159
|
},
|
|
148
160
|
async getCountsPerPriority() {
|
|
149
161
|
const response = await this.call({ cmd: 'GetCountsPerPriority', queue: this.name });
|
|
150
162
|
return (response.counts ?? response.data ?? {});
|
|
151
163
|
},
|
|
152
164
|
// --------------------------------------------------------------------- logs
|
|
153
|
-
async addJobLog(id, message) {
|
|
154
|
-
await this.call({ cmd: 'AddLog', id, message });
|
|
165
|
+
async addJobLog(id, message, level) {
|
|
166
|
+
await this.call(compact({ cmd: 'AddLog', id, message, level }));
|
|
155
167
|
},
|
|
156
168
|
async getJobLogs(id, start, end) {
|
|
157
169
|
const response = await this.call(compact({ cmd: 'GetLogs', id, start, end }));
|
|
158
170
|
const data = (response.data ?? {});
|
|
159
171
|
const logs = (data.logs ?? response.logs ?? []);
|
|
160
|
-
|
|
172
|
+
// Format as `[level] message` (reference client parity); never drop level.
|
|
173
|
+
return logs.map((row) => {
|
|
174
|
+
if (typeof row === 'string')
|
|
175
|
+
return row;
|
|
176
|
+
const r = row;
|
|
177
|
+
return r.level ? `[${r.level}] ${r.message}` : String(r.message ?? row);
|
|
178
|
+
});
|
|
161
179
|
},
|
|
162
180
|
async clearJobLogs(id, keepLogs) {
|
|
163
181
|
await this.call(compact({ cmd: 'ClearLogs', id, keepLogs }));
|
package/dist/queue.d.ts
CHANGED
|
@@ -9,18 +9,27 @@
|
|
|
9
9
|
* exposes them on the type.
|
|
10
10
|
*/
|
|
11
11
|
import { Connection, type Response, type TlsOption } from './connection.js';
|
|
12
|
+
import type { ConnectionLike } from './connection-types.js';
|
|
12
13
|
import { Job } from './job.js';
|
|
14
|
+
import type { Observability } from './observability.js';
|
|
13
15
|
import { type QueueAdminApi } from './queue-admin.js';
|
|
14
16
|
import { type QueueControlApi } from './queue-control.js';
|
|
15
17
|
import { type QueueQueryApi } from './queue-query.js';
|
|
16
18
|
import { type JobOptions } from './types.js';
|
|
17
|
-
export interface QueueOptions {
|
|
19
|
+
export interface QueueOptions extends Observability {
|
|
18
20
|
host?: string;
|
|
19
21
|
port?: number;
|
|
20
22
|
token?: string;
|
|
21
23
|
tls?: TlsOption;
|
|
22
24
|
connection?: Connection;
|
|
23
25
|
commandTimeoutMs?: number;
|
|
26
|
+
/** Max in-flight commands before backpressure kicks in (0 = unbounded). */
|
|
27
|
+
maxInFlight?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Fan producer commands across N connections (round-robin) for throughput.
|
|
30
|
+
* >1 builds a ConnectionPool; default 1 = a single connection.
|
|
31
|
+
*/
|
|
32
|
+
poolSize?: number;
|
|
24
33
|
}
|
|
25
34
|
export interface BulkJobEntry<T = unknown> {
|
|
26
35
|
name: string;
|
|
@@ -29,13 +38,13 @@ export interface BulkJobEntry<T = unknown> {
|
|
|
29
38
|
}
|
|
30
39
|
export declare class Queue<T = unknown> {
|
|
31
40
|
readonly name: string;
|
|
32
|
-
readonly connection:
|
|
41
|
+
readonly connection: ConnectionLike;
|
|
33
42
|
private readonly ownsConnection;
|
|
34
43
|
constructor(name: string, opts?: QueueOptions);
|
|
35
44
|
/** Send a raw command on this queue's connection (used by area modules). */
|
|
36
|
-
call(command: Record<string, unknown> & {
|
|
45
|
+
call<R = Response>(command: Record<string, unknown> & {
|
|
37
46
|
cmd: string;
|
|
38
|
-
}, timeoutMs?: number): Promise<
|
|
47
|
+
}, timeoutMs?: number): Promise<R>;
|
|
39
48
|
/** Add a job; returns a Job stub carrying the assigned id. */
|
|
40
49
|
add(name: string, data: T, opts?: JobOptions): Promise<Job<T>>;
|
|
41
50
|
/** Add many jobs in one round-trip; returns Job stubs. */
|