bunqueue 2.8.53 → 2.8.54
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/dist/application/backgroundTasks.js +5 -1
- package/dist/application/cleanupTasks.js +1 -1
- package/dist/application/clientTracking.js +1 -1
- package/dist/application/contextFactory.js +1 -0
- package/dist/application/dlqManager.d.ts +7 -1
- package/dist/application/dlqManager.js +16 -5
- package/dist/application/lockManager.js +3 -3
- package/dist/application/operations/ack.js +2 -2
- package/dist/application/operations/ackHelpers.js +1 -1
- package/dist/application/operations/jobManagement.js +6 -4
- package/dist/application/operations/jobMoveOperations.js +3 -3
- package/dist/application/operations/jobStateTransitions.d.ts +1 -1
- package/dist/application/operations/jobStateTransitions.js +16 -2
- package/dist/application/queueManager.d.ts +22 -2
- package/dist/application/queueManager.js +48 -6
- package/dist/application/stallDetection.js +3 -3
- package/dist/client/flowJobDependencies.js +3 -1
- package/dist/client/flowJobMoveMethods.js +7 -2
- package/dist/client/jobConversion.d.ts +3 -3
- package/dist/client/jobConversion.js +10 -6
- package/dist/client/jobConversionHelpers.d.ts +1 -1
- package/dist/client/jobConversionHelpers.js +3 -6
- package/dist/client/jobConversionTypes.d.ts +4 -0
- package/dist/client/jobDeduplication.d.ts +6 -0
- package/dist/client/jobDeduplication.js +14 -0
- package/dist/client/queue/bullmqCompat.d.ts +18 -8
- package/dist/client/queue/bullmqCompat.js +39 -71
- package/dist/client/queue/deduplication.d.ts +2 -0
- package/dist/client/queue/deduplication.js +20 -11
- package/dist/client/queue/dlq.d.ts +11 -6
- package/dist/client/queue/dlq.js +47 -4
- package/dist/client/queue/dlqJobMethods.d.ts +9 -0
- package/dist/client/queue/dlqJobMethods.js +176 -0
- package/dist/client/queue/dlqOps.d.ts +2 -1
- package/dist/client/queue/dlqOps.js +2 -2
- package/dist/client/queue/jobMove.js +6 -1
- package/dist/client/queue/jobProxy.js +10 -5
- package/dist/client/queue/operations/add.js +17 -0
- package/dist/client/queue/operations/index.d.ts +1 -0
- package/dist/client/queue/operations/index.js +1 -0
- package/dist/client/queue/operations/management.d.ts +0 -4
- package/dist/client/queue/operations/management.js +33 -5
- package/dist/client/queue/operations/query.d.ts +1 -20
- package/dist/client/queue/operations/query.js +18 -53
- package/dist/client/queue/operations/queryStates.d.ts +13 -0
- package/dist/client/queue/operations/queryStates.js +32 -0
- package/dist/client/queue/operations/queryTcpPages.d.ts +14 -0
- package/dist/client/queue/operations/queryTcpPages.js +40 -0
- package/dist/client/queue/queue.d.ts +3 -0
- package/dist/client/queue/queue.js +23 -11
- package/dist/client/queue/rateLimit.d.ts +4 -4
- package/dist/client/queue/rateLimit.js +17 -8
- package/dist/client/queue/workers.d.ts +12 -0
- package/dist/client/queue/workers.js +20 -2
- package/dist/client/queueGroup.d.ts +11 -0
- package/dist/client/queueGroup.js +28 -1
- package/dist/client/sandboxed/worker.js +1 -1
- package/dist/client/worker/processor.js +1 -1
- package/dist/client/worker/processorHandlers.d.ts +1 -5
- package/dist/client/worker/processorHandlers.js +5 -7
- package/dist/client/worker/worker.d.ts +1 -1
- package/dist/client/worker/worker.js +37 -13
- package/dist/domain/queue/limiterManager.d.ts +11 -0
- package/dist/domain/queue/limiterManager.js +28 -0
- package/dist/domain/queue/shard.d.ts +10 -2
- package/dist/domain/queue/shard.js +23 -4
- package/dist/domain/queue/uniqueKeyManager.d.ts +3 -1
- package/dist/domain/queue/uniqueKeyManager.js +9 -1
- package/dist/domain/types/command.d.ts +34 -1
- package/dist/domain/types/queue.d.ts +5 -0
- package/dist/domain/types/queue.js +13 -0
- package/dist/infrastructure/persistence/sqlite.d.ts +4 -0
- package/dist/infrastructure/persistence/sqlite.js +16 -0
- package/dist/infrastructure/server/handlerRoutes.js +14 -1
- package/dist/infrastructure/server/handlers/dlq.d.ts +3 -0
- package/dist/infrastructure/server/handlers/dlq.js +18 -4
- package/dist/infrastructure/server/handlers/introspection.d.ts +19 -0
- package/dist/infrastructure/server/handlers/introspection.js +21 -0
- package/dist/infrastructure/server/handlers/query.js +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { TcpConnectionPool } from '../tcpPool';
|
|
5
5
|
import type { Job, JobDependencies, JobDependenciesCount, GetDependenciesOpts } from '../types';
|
|
6
|
-
interface
|
|
6
|
+
interface CompatibilityContext<T = unknown> {
|
|
7
7
|
name: string;
|
|
8
8
|
embedded: boolean;
|
|
9
9
|
tcp: TcpConnectionPool | null;
|
|
@@ -22,26 +22,36 @@ interface BullMQContext<T = unknown> {
|
|
|
22
22
|
clearJobLogs: (id: string, keepLogs?: number) => Promise<void>;
|
|
23
23
|
getJobDependencies: (id: string, opts?: GetDependenciesOpts) => Promise<JobDependencies>;
|
|
24
24
|
getJobDependenciesCount: (id: string, opts?: GetDependenciesOpts) => Promise<JobDependenciesCount>;
|
|
25
|
+
getJobCountsAsync: () => Promise<{
|
|
26
|
+
prioritized: number;
|
|
27
|
+
'waiting-children': number;
|
|
28
|
+
}>;
|
|
25
29
|
getWaitingAsync: (start?: number, end?: number) => Promise<Job<T>[]>;
|
|
26
30
|
getPrioritizedAsync: (start?: number, end?: number) => Promise<Job<T>[]>;
|
|
31
|
+
getJobsAsync: (options: {
|
|
32
|
+
state?: string | string[];
|
|
33
|
+
start?: number;
|
|
34
|
+
end?: number;
|
|
35
|
+
asc?: boolean;
|
|
36
|
+
}) => Promise<Job<T>[]>;
|
|
27
37
|
}
|
|
28
38
|
/** Get jobs in the "prioritized" state (priority > 0, BullMQ v5) */
|
|
29
|
-
export declare function getPrioritized<T>(ctx:
|
|
39
|
+
export declare function getPrioritized<T>(ctx: CompatibilityContext<T>, start?: number, end?: number): Promise<Job<T>[]>;
|
|
30
40
|
/** Get count of prioritized jobs (priority > 0) */
|
|
31
|
-
export declare function getPrioritizedCount<T>(ctx:
|
|
41
|
+
export declare function getPrioritizedCount<T>(ctx: CompatibilityContext<T>): Promise<number>;
|
|
32
42
|
/** Get jobs waiting for parent to complete */
|
|
33
|
-
export declare function getWaitingChildren<T>(ctx:
|
|
43
|
+
export declare function getWaitingChildren<T>(ctx: CompatibilityContext<T>, start?: number, end?: number): Promise<Job<T>[]>;
|
|
34
44
|
/** Get count of jobs waiting for parent */
|
|
35
|
-
export declare function getWaitingChildrenCount<T>(ctx:
|
|
45
|
+
export declare function getWaitingChildrenCount<T>(ctx: CompatibilityContext<T>): Promise<number>;
|
|
36
46
|
/** Get dependencies of a parent job */
|
|
37
|
-
export declare function getDependencies(
|
|
47
|
+
export declare function getDependencies(ctx: CompatibilityContext, parentId: string, type?: 'processed' | 'unprocessed', start?: number, end?: number): Promise<{
|
|
38
48
|
processed?: Record<string, unknown>;
|
|
39
49
|
unprocessed?: string[];
|
|
40
50
|
nextProcessedCursor?: number;
|
|
41
51
|
nextUnprocessedCursor?: number;
|
|
42
52
|
}>;
|
|
43
53
|
/** Get job dependencies (for Job instance method) */
|
|
44
|
-
export declare function getJobDependencies(ctx:
|
|
54
|
+
export declare function getJobDependencies(ctx: CompatibilityContext, id: string, _opts?: GetDependenciesOpts): Promise<JobDependencies>;
|
|
45
55
|
/** Get job dependencies count */
|
|
46
|
-
export declare function getJobDependenciesCount(ctx:
|
|
56
|
+
export declare function getJobDependenciesCount(ctx: CompatibilityContext, id: string, _opts?: GetDependenciesOpts): Promise<JobDependenciesCount>;
|
|
47
57
|
export {};
|
|
@@ -2,95 +2,63 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* BullMQ v5 Compatibility Operations
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { toPublicJob } from '../types';
|
|
7
|
-
import { jobId } from '../../domain/types/job';
|
|
5
|
+
import { getFlowDependencies } from '../flowJobDependencies';
|
|
8
6
|
/** Get jobs in the "prioritized" state (priority > 0, BullMQ v5) */
|
|
9
7
|
export function getPrioritized(ctx, start = 0, end = -1) {
|
|
10
8
|
return ctx.getPrioritizedAsync(start, end);
|
|
11
9
|
}
|
|
12
10
|
/** Get count of prioritized jobs (priority > 0) */
|
|
13
11
|
export async function getPrioritizedCount(ctx) {
|
|
14
|
-
|
|
15
|
-
return jobs.length;
|
|
12
|
+
return (await ctx.getJobCountsAsync()).prioritized;
|
|
16
13
|
}
|
|
17
14
|
/** Get jobs waiting for parent to complete */
|
|
18
15
|
export async function getWaitingChildren(ctx, start = 0, end = -1) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return jobs
|
|
26
|
-
.filter((j) => {
|
|
27
|
-
const data = j.data;
|
|
28
|
-
return data?._waitingParent === true;
|
|
29
|
-
})
|
|
30
|
-
.map((job) => {
|
|
31
|
-
const jobData = job.data;
|
|
32
|
-
return toPublicJob({
|
|
33
|
-
job,
|
|
34
|
-
name: jobData?.name ?? 'default',
|
|
35
|
-
getState: ctx.getJobState,
|
|
36
|
-
remove: ctx.removeAsync,
|
|
37
|
-
retry: ctx.retryJob,
|
|
38
|
-
getChildrenValues: ctx.getChildrenValues,
|
|
39
|
-
updateData: ctx.updateJobData,
|
|
40
|
-
promote: ctx.promoteJob,
|
|
41
|
-
changeDelay: ctx.changeJobDelay,
|
|
42
|
-
changePriority: ctx.changeJobPriority,
|
|
43
|
-
extendLock: ctx.extendJobLock,
|
|
44
|
-
clearLogs: ctx.clearJobLogs,
|
|
45
|
-
getDependencies: ctx.getJobDependencies,
|
|
46
|
-
getDependenciesCount: ctx.getJobDependenciesCount,
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return [];
|
|
16
|
+
return ctx.getJobsAsync({
|
|
17
|
+
state: 'waiting-children',
|
|
18
|
+
start,
|
|
19
|
+
end: end === -1 ? -1 : end + 1,
|
|
20
|
+
asc: true,
|
|
21
|
+
});
|
|
51
22
|
}
|
|
52
23
|
/** Get count of jobs waiting for parent */
|
|
53
24
|
export async function getWaitingChildrenCount(ctx) {
|
|
54
|
-
|
|
55
|
-
return children.length;
|
|
25
|
+
return (await ctx.getJobCountsAsync())['waiting-children'];
|
|
56
26
|
}
|
|
57
27
|
/** Get dependencies of a parent job */
|
|
58
|
-
export function getDependencies(
|
|
59
|
-
return
|
|
28
|
+
export function getDependencies(ctx, parentId, type, start = 0, end = -1) {
|
|
29
|
+
return getFlowDependencies(parentId, ctx.name, ctx.embedded, ctx.tcp).then((dependencies) => {
|
|
30
|
+
const unprocessed = [...dependencies.unprocessed].sort();
|
|
31
|
+
const processedEntries = Object.entries(dependencies.processed).sort(([a], [b]) => a.localeCompare(b));
|
|
32
|
+
const stop = end < 0 ? undefined : end + 1;
|
|
33
|
+
return {
|
|
34
|
+
processed: type === 'unprocessed' ? {} : Object.fromEntries(processedEntries.slice(start, stop)),
|
|
35
|
+
unprocessed: type === 'processed' ? [] : unprocessed.slice(start, stop),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
60
38
|
}
|
|
61
39
|
/** Get job dependencies (for Job instance method) */
|
|
62
40
|
export async function getJobDependencies(ctx, id, _opts) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
else {
|
|
79
|
-
unprocessed.push(String(childId));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return { processed, unprocessed };
|
|
83
|
-
}
|
|
84
|
-
return { processed: {}, unprocessed: [] };
|
|
41
|
+
const dependencies = await getFlowDependencies(id, ctx.name, ctx.embedded, ctx.tcp);
|
|
42
|
+
const processedEntries = Object.entries(dependencies.processed).sort(([a], [b]) => a.localeCompare(b));
|
|
43
|
+
const unprocessed = [...dependencies.unprocessed].sort();
|
|
44
|
+
const processedCursor = _opts?.processed?.cursor ?? 0;
|
|
45
|
+
const processedCount = _opts?.processed?.count ?? processedEntries.length;
|
|
46
|
+
const unprocessedCursor = _opts?.unprocessed?.cursor ?? 0;
|
|
47
|
+
const unprocessedCount = _opts?.unprocessed?.count ?? unprocessed.length;
|
|
48
|
+
const processedEnd = Math.min(processedEntries.length, processedCursor + processedCount);
|
|
49
|
+
const unprocessedEnd = Math.min(unprocessed.length, unprocessedCursor + unprocessedCount);
|
|
50
|
+
return {
|
|
51
|
+
processed: Object.fromEntries(processedEntries.slice(processedCursor, processedEnd)),
|
|
52
|
+
unprocessed: unprocessed.slice(unprocessedCursor, unprocessedEnd),
|
|
53
|
+
nextProcessedCursor: processedEnd < processedEntries.length ? processedEnd : 0,
|
|
54
|
+
nextUnprocessedCursor: unprocessedEnd < unprocessed.length ? unprocessedEnd : 0,
|
|
55
|
+
};
|
|
85
56
|
}
|
|
86
57
|
/** Get job dependencies count */
|
|
87
58
|
export async function getJobDependenciesCount(ctx, id, _opts) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return { processed: 0, unprocessed: 0 };
|
|
59
|
+
const deps = await getFlowDependencies(id, ctx.name, ctx.embedded, ctx.tcp);
|
|
60
|
+
return {
|
|
61
|
+
processed: Object.keys(deps.processed).length,
|
|
62
|
+
unprocessed: deps.unprocessed.length,
|
|
63
|
+
};
|
|
96
64
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Deduplication Operations
|
|
3
3
|
*/
|
|
4
|
+
import type { TcpConnectionPool } from '../tcpPool';
|
|
4
5
|
interface DeduplicationContext {
|
|
5
6
|
name: string;
|
|
6
7
|
embedded: boolean;
|
|
8
|
+
tcp: TcpConnectionPool | null;
|
|
7
9
|
}
|
|
8
10
|
/** Get the job ID associated with a deduplication key */
|
|
9
11
|
export declare function getDeduplicationJobId(ctx: DeduplicationContext, deduplicationId: string): Promise<string | null>;
|
|
@@ -3,21 +3,30 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getSharedManager } from '../manager';
|
|
5
5
|
/** Get the job ID associated with a deduplication key */
|
|
6
|
-
export function getDeduplicationJobId(ctx, deduplicationId) {
|
|
6
|
+
export async function getDeduplicationJobId(ctx, deduplicationId) {
|
|
7
7
|
if (ctx.embedded) {
|
|
8
|
-
|
|
9
|
-
return Promise.resolve(job ? String(job.id) : null);
|
|
8
|
+
return getSharedManager().getDeduplicationJobId(ctx.name, deduplicationId);
|
|
10
9
|
}
|
|
11
|
-
|
|
10
|
+
if (!ctx.tcp)
|
|
11
|
+
return null;
|
|
12
|
+
const response = await ctx.tcp.send({
|
|
13
|
+
cmd: 'GetDeduplicationJobId',
|
|
14
|
+
queue: ctx.name,
|
|
15
|
+
deduplicationId,
|
|
16
|
+
});
|
|
17
|
+
return response.data.jobId;
|
|
12
18
|
}
|
|
13
19
|
/** Remove a deduplication key */
|
|
14
|
-
export function removeDeduplicationKey(ctx, deduplicationId) {
|
|
20
|
+
export async function removeDeduplicationKey(ctx, deduplicationId) {
|
|
15
21
|
if (ctx.embedded) {
|
|
16
|
-
|
|
17
|
-
if (job) {
|
|
18
|
-
return Promise.resolve(1);
|
|
19
|
-
}
|
|
20
|
-
return Promise.resolve(0);
|
|
22
|
+
return getSharedManager().removeDeduplicationKey(ctx.name, deduplicationId);
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
if (!ctx.tcp)
|
|
25
|
+
return 0;
|
|
26
|
+
const response = await ctx.tcp.send({
|
|
27
|
+
cmd: 'RemoveDeduplicationKey',
|
|
28
|
+
queue: ctx.name,
|
|
29
|
+
deduplicationId,
|
|
30
|
+
});
|
|
31
|
+
return response.data.count;
|
|
23
32
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { TcpConnectionPool } from '../tcpPool';
|
|
6
6
|
import type { Job, DlqConfig, DlqEntry, DlqStats, DlqFilter } from '../types';
|
|
7
|
-
import { type
|
|
7
|
+
import { type DlqJobContext } from './dlqJobMethods';
|
|
8
8
|
interface DlqContext {
|
|
9
9
|
name: string;
|
|
10
10
|
embedded: boolean;
|
|
@@ -19,16 +19,19 @@ export declare function getDlqConfig(ctx: DlqContext): DlqConfig;
|
|
|
19
19
|
/** Get DLQ configuration (async, works in TCP mode) */
|
|
20
20
|
export declare function getDlqConfigAsync(ctx: DlqContext): Promise<DlqConfig>;
|
|
21
21
|
/** Get DLQ entries */
|
|
22
|
-
export declare function getDlq<T>(ctx:
|
|
23
|
-
type DlqQueryContext =
|
|
22
|
+
export declare function getDlq<T>(ctx: DlqQueryContext, filter?: DlqFilter): DlqEntry<T>[];
|
|
23
|
+
type DlqQueryContext = DlqJobContext;
|
|
24
|
+
/** Read full DLQ metadata from the selected broker runtime. */
|
|
25
|
+
export declare function getDlqAsync<T>(ctx: DlqQueryContext, filter?: DlqFilter): Promise<DlqEntry<T>[]>;
|
|
24
26
|
/**
|
|
25
|
-
* Get the
|
|
26
|
-
*
|
|
27
|
-
* (reason, enteredAt, attempts) — that stays embedded-only via getDlq().
|
|
27
|
+
* Get only the public jobs from the DLQ. Use getDlqAsync() when the full entry
|
|
28
|
+
* metadata is needed; both views work over TCP through the same command.
|
|
28
29
|
*/
|
|
29
30
|
export declare function getDlqJobsAsync<T>(ctx: DlqQueryContext, count?: number): Promise<Job<T>[]>;
|
|
30
31
|
/** Get DLQ stats */
|
|
31
32
|
export declare function getDlqStats(ctx: DlqContext): DlqStats;
|
|
33
|
+
/** Read authoritative DLQ statistics from the selected broker runtime. */
|
|
34
|
+
export declare function getDlqStatsAsync(ctx: DlqContext): Promise<DlqStats>;
|
|
32
35
|
/** Retry DLQ entries */
|
|
33
36
|
export declare function retryDlq(ctx: DlqContext, id?: string): number;
|
|
34
37
|
/**
|
|
@@ -39,6 +42,8 @@ export declare function retryDlq(ctx: DlqContext, id?: string): number;
|
|
|
39
42
|
export declare function retryDlqAsync(ctx: DlqContext, id?: string): Promise<number>;
|
|
40
43
|
/** Retry DLQ entries by filter */
|
|
41
44
|
export declare function retryDlqByFilter(ctx: DlqContext, filter: DlqFilter): number;
|
|
45
|
+
/** Retry matching DLQ entries and return the applied count. */
|
|
46
|
+
export declare function retryDlqByFilterAsync(ctx: DlqContext, filter: DlqFilter): Promise<number>;
|
|
42
47
|
/** Purge DLQ */
|
|
43
48
|
export declare function purgeDlq(ctx: DlqContext): number;
|
|
44
49
|
/**
|
package/dist/client/queue/dlq.js
CHANGED
|
@@ -6,6 +6,8 @@ import { getSharedManager } from '../manager';
|
|
|
6
6
|
import { jobId } from '../../domain/types/job';
|
|
7
7
|
import { createSimpleJob } from './jobProxy';
|
|
8
8
|
import * as dlqOps from './dlqOps';
|
|
9
|
+
import { toDlqEntry } from '../jobConversion';
|
|
10
|
+
import { createDlqJobMethods } from './dlqJobMethods';
|
|
9
11
|
/** Client-side cache for TCP mode */
|
|
10
12
|
const tcpDlqConfigCache = new Map();
|
|
11
13
|
/** Set DLQ configuration */
|
|
@@ -56,12 +58,31 @@ export async function getDlqConfigAsync(ctx) {
|
|
|
56
58
|
export function getDlq(ctx, filter) {
|
|
57
59
|
if (!ctx.embedded)
|
|
58
60
|
return [];
|
|
59
|
-
|
|
61
|
+
const entries = getSharedManager().getDlqEntries(ctx.name, filter);
|
|
62
|
+
const methods = createDlqJobMethods(ctx);
|
|
63
|
+
return entries.map((entry) => toDlqEntry(entry, methods));
|
|
64
|
+
}
|
|
65
|
+
/** Read full DLQ metadata from the selected broker runtime. */
|
|
66
|
+
export async function getDlqAsync(ctx, filter) {
|
|
67
|
+
let entries;
|
|
68
|
+
if (ctx.embedded) {
|
|
69
|
+
entries = getSharedManager().getDlqEntries(ctx.name, filter);
|
|
70
|
+
}
|
|
71
|
+
else if (ctx.tcp) {
|
|
72
|
+
const response = await ctx.tcp.send({ cmd: 'Dlq', queue: ctx.name, filter });
|
|
73
|
+
if (!response.ok)
|
|
74
|
+
return [];
|
|
75
|
+
entries = (response.entries ?? []);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const methods = createDlqJobMethods(ctx);
|
|
81
|
+
return entries.map((entry) => toDlqEntry(entry, methods));
|
|
60
82
|
}
|
|
61
83
|
/**
|
|
62
|
-
* Get the
|
|
63
|
-
*
|
|
64
|
-
* (reason, enteredAt, attempts) — that stays embedded-only via getDlq().
|
|
84
|
+
* Get only the public jobs from the DLQ. Use getDlqAsync() when the full entry
|
|
85
|
+
* metadata is needed; both views work over TCP through the same command.
|
|
65
86
|
*/
|
|
66
87
|
export async function getDlqJobsAsync(ctx, count) {
|
|
67
88
|
let raw;
|
|
@@ -104,6 +125,17 @@ export function getDlqStats(ctx) {
|
|
|
104
125
|
}
|
|
105
126
|
return dlqOps.getDlqStatsEmbedded(ctx.name);
|
|
106
127
|
}
|
|
128
|
+
/** Read authoritative DLQ statistics from the selected broker runtime. */
|
|
129
|
+
export async function getDlqStatsAsync(ctx) {
|
|
130
|
+
if (ctx.embedded)
|
|
131
|
+
return dlqOps.getDlqStatsEmbedded(ctx.name);
|
|
132
|
+
if (!ctx.tcp)
|
|
133
|
+
return getDlqStats(ctx);
|
|
134
|
+
const response = await ctx.tcp.send({ cmd: 'GetDlqStats', queue: ctx.name });
|
|
135
|
+
if (!response.ok)
|
|
136
|
+
return getDlqStats(ctx);
|
|
137
|
+
return response.data.stats;
|
|
138
|
+
}
|
|
107
139
|
/** Retry DLQ entries */
|
|
108
140
|
export function retryDlq(ctx, id) {
|
|
109
141
|
if (ctx.embedded)
|
|
@@ -133,6 +165,17 @@ export function retryDlqByFilter(ctx, filter) {
|
|
|
133
165
|
return 0;
|
|
134
166
|
return dlqOps.retryDlqByFilterEmbedded(ctx.name, filter);
|
|
135
167
|
}
|
|
168
|
+
/** Retry matching DLQ entries and return the applied count. */
|
|
169
|
+
export async function retryDlqByFilterAsync(ctx, filter) {
|
|
170
|
+
if (ctx.embedded)
|
|
171
|
+
return dlqOps.retryDlqByFilterEmbedded(ctx.name, filter);
|
|
172
|
+
if (!ctx.tcp)
|
|
173
|
+
return 0;
|
|
174
|
+
const response = await ctx.tcp.send({ cmd: 'RetryDlq', queue: ctx.name, filter });
|
|
175
|
+
if (!response.ok)
|
|
176
|
+
return 0;
|
|
177
|
+
return (response.count ?? 0);
|
|
178
|
+
}
|
|
136
179
|
/** Purge DLQ */
|
|
137
180
|
export function purgeDlq(ctx) {
|
|
138
181
|
if (ctx.embedded)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PublicJobMethodContext } from '../jobConversionTypes';
|
|
2
|
+
import type { TcpConnectionPool } from '../tcpPool';
|
|
3
|
+
import type { SimpleJobContext } from './jobProxy';
|
|
4
|
+
export interface DlqJobContext extends Pick<SimpleJobContext, 'getJobState' | 'removeAsync' | 'retryJob' | 'getChildrenValues'> {
|
|
5
|
+
name: string;
|
|
6
|
+
embedded: boolean;
|
|
7
|
+
tcp: TcpConnectionPool | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function createDlqJobMethods(ctx: DlqJobContext): PublicJobMethodContext;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { jobId } from '../../domain/types/job';
|
|
2
|
+
import { getFlowDependencies } from '../flowJobDependencies';
|
|
3
|
+
import { getSharedManager } from '../manager';
|
|
4
|
+
import { removeJobDeduplicationKey } from '../jobDeduplication';
|
|
5
|
+
import { buildFailCommand, failEmbeddedArgs } from './failWire';
|
|
6
|
+
function assertCommand(response, operation) {
|
|
7
|
+
if (response.ok === true)
|
|
8
|
+
return;
|
|
9
|
+
throw new Error(typeof response.error === 'string' ? response.error : `${operation} failed`);
|
|
10
|
+
}
|
|
11
|
+
export function createDlqJobMethods(ctx) {
|
|
12
|
+
const manager = getSharedManager();
|
|
13
|
+
const send = async (command, operation) => {
|
|
14
|
+
if (!ctx.tcp)
|
|
15
|
+
throw new Error(`${operation}: no connection`);
|
|
16
|
+
const response = await ctx.tcp.send(command);
|
|
17
|
+
assertCommand(response, operation);
|
|
18
|
+
return response;
|
|
19
|
+
};
|
|
20
|
+
const dependencies = (id) => getFlowDependencies(id, ctx.name, ctx.embedded, ctx.tcp);
|
|
21
|
+
return {
|
|
22
|
+
updateProgress: async (id, progress, message) => {
|
|
23
|
+
if (ctx.embedded) {
|
|
24
|
+
if (!(await manager.updateProgress(jobId(id), progress, message))) {
|
|
25
|
+
throw new Error(`Job ${id} is not active`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else
|
|
29
|
+
await send({ cmd: 'Progress', id, progress, message }, 'Progress');
|
|
30
|
+
},
|
|
31
|
+
log: async (id, message) => {
|
|
32
|
+
if (ctx.embedded)
|
|
33
|
+
manager.addLog(jobId(id), message);
|
|
34
|
+
else
|
|
35
|
+
await send({ cmd: 'AddLog', id, message }, 'AddLog');
|
|
36
|
+
},
|
|
37
|
+
getState: ctx.getJobState,
|
|
38
|
+
remove: ctx.removeAsync,
|
|
39
|
+
retry: ctx.retryJob,
|
|
40
|
+
getChildrenValues: ctx.getChildrenValues,
|
|
41
|
+
updateData: async (id, data) => {
|
|
42
|
+
if (ctx.embedded)
|
|
43
|
+
await manager.updateJobData(jobId(id), data);
|
|
44
|
+
else
|
|
45
|
+
await send({ cmd: 'Update', id, data }, 'Update');
|
|
46
|
+
},
|
|
47
|
+
promote: async (id) => {
|
|
48
|
+
if (ctx.embedded)
|
|
49
|
+
await manager.promote(jobId(id));
|
|
50
|
+
else
|
|
51
|
+
await send({ cmd: 'Promote', id }, 'Promote');
|
|
52
|
+
},
|
|
53
|
+
changeDelay: async (id, delay) => {
|
|
54
|
+
if (ctx.embedded)
|
|
55
|
+
await manager.changeDelay(jobId(id), delay);
|
|
56
|
+
else
|
|
57
|
+
await send({ cmd: 'ChangeDelay', id, delay }, 'ChangeDelay');
|
|
58
|
+
},
|
|
59
|
+
changePriority: async (id, options) => {
|
|
60
|
+
if (ctx.embedded)
|
|
61
|
+
await manager.changePriority(jobId(id), options.priority, options.lifo);
|
|
62
|
+
else {
|
|
63
|
+
await send({ cmd: 'ChangePriority', id, priority: options.priority, lifo: options.lifo }, 'ChangePriority');
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
extendLock: async (id, token, duration) => {
|
|
67
|
+
if (ctx.embedded)
|
|
68
|
+
return (await manager.extendLock(jobId(id), token, duration)) ? duration : 0;
|
|
69
|
+
const response = await send({ cmd: 'ExtendLock', id, token, duration }, 'ExtendLock');
|
|
70
|
+
return response.ok === true ? duration : 0;
|
|
71
|
+
},
|
|
72
|
+
clearLogs: async (id, keepLogs) => {
|
|
73
|
+
if (ctx.embedded)
|
|
74
|
+
manager.clearLogs(jobId(id), keepLogs);
|
|
75
|
+
else
|
|
76
|
+
await send({ cmd: 'ClearLogs', id, keepLogs }, 'ClearLogs');
|
|
77
|
+
},
|
|
78
|
+
getDependencies: async (id) => dependencies(id),
|
|
79
|
+
getDependenciesCount: async (id) => {
|
|
80
|
+
const value = await dependencies(id);
|
|
81
|
+
return {
|
|
82
|
+
processed: Object.keys(value.processed).length,
|
|
83
|
+
unprocessed: value.unprocessed.length,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
moveToCompleted: async (id, result, token) => {
|
|
87
|
+
if (ctx.embedded)
|
|
88
|
+
await manager.ack(jobId(id), result, token);
|
|
89
|
+
else
|
|
90
|
+
await send({ cmd: 'ACK', id, result, token }, 'ACK');
|
|
91
|
+
return null;
|
|
92
|
+
},
|
|
93
|
+
moveToFailed: async (id, error, token) => {
|
|
94
|
+
if (ctx.embedded)
|
|
95
|
+
await manager.fail(jobId(id), ...failEmbeddedArgs(error, token));
|
|
96
|
+
else
|
|
97
|
+
await send(buildFailCommand(id, error, token), 'FAIL');
|
|
98
|
+
},
|
|
99
|
+
moveToWait: async (id) => {
|
|
100
|
+
if (ctx.embedded) {
|
|
101
|
+
const state = await manager.getJobState(jobId(id));
|
|
102
|
+
if (state === 'failed')
|
|
103
|
+
return manager.retryDlq(ctx.name, jobId(id)) > 0;
|
|
104
|
+
if (state === 'active')
|
|
105
|
+
return manager.moveActiveToWait(jobId(id));
|
|
106
|
+
if (state === 'delayed')
|
|
107
|
+
return manager.promote(jobId(id));
|
|
108
|
+
return state === 'waiting' || state === 'prioritized';
|
|
109
|
+
}
|
|
110
|
+
const response = await send({ cmd: 'MoveToWait', id }, 'MoveToWait');
|
|
111
|
+
return response.ok === true;
|
|
112
|
+
},
|
|
113
|
+
moveToDelayed: async (id, timestamp) => {
|
|
114
|
+
const delay = Math.max(0, timestamp - Date.now());
|
|
115
|
+
if (ctx.embedded)
|
|
116
|
+
await manager.moveToDelayed(jobId(id), delay);
|
|
117
|
+
else
|
|
118
|
+
await send({ cmd: 'MoveToDelayed', id, delay }, 'MoveToDelayed');
|
|
119
|
+
},
|
|
120
|
+
moveToWaitingChildren: async (id) => {
|
|
121
|
+
if (ctx.embedded)
|
|
122
|
+
return manager.moveToWaitingChildren(jobId(id));
|
|
123
|
+
await send({ cmd: 'MoveToWaitingChildren', id }, 'MoveToWaitingChildren');
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
waitUntilFinished: async (id, _events, ttl) => {
|
|
127
|
+
const timeout = ttl ?? 30_000;
|
|
128
|
+
if (ctx.embedded) {
|
|
129
|
+
const current = await manager.getJob(jobId(id));
|
|
130
|
+
if (!current)
|
|
131
|
+
throw new Error(`Job ${id} not found`);
|
|
132
|
+
if (current.completedAt)
|
|
133
|
+
return manager.getResult(jobId(id));
|
|
134
|
+
if (!(await manager.waitForJobCompletion(jobId(id), timeout))) {
|
|
135
|
+
throw new Error(`Job ${id} timed out after ${timeout}ms`);
|
|
136
|
+
}
|
|
137
|
+
return manager.getResult(jobId(id));
|
|
138
|
+
}
|
|
139
|
+
const response = await send({ cmd: 'WaitJob', id, timeout }, 'WaitJob');
|
|
140
|
+
if (!response.completed)
|
|
141
|
+
throw new Error(`Job ${id} timed out after ${timeout}ms`);
|
|
142
|
+
return response.result;
|
|
143
|
+
},
|
|
144
|
+
discard: (id) => {
|
|
145
|
+
if (ctx.embedded)
|
|
146
|
+
void manager.discard(jobId(id));
|
|
147
|
+
else if (ctx.tcp)
|
|
148
|
+
void ctx.tcp.send({ cmd: 'Discard', id });
|
|
149
|
+
},
|
|
150
|
+
getFailedChildrenValues: async (id) => {
|
|
151
|
+
if (ctx.embedded)
|
|
152
|
+
return manager.getFailedChildrenValues(jobId(id));
|
|
153
|
+
const response = await send({ cmd: 'GetFailedChildrenValues', id }, 'GetFailedChildrenValues');
|
|
154
|
+
return response.values ?? {};
|
|
155
|
+
},
|
|
156
|
+
getIgnoredChildrenFailures: async (id) => {
|
|
157
|
+
if (ctx.embedded)
|
|
158
|
+
return manager.getIgnoredChildrenFailures(jobId(id));
|
|
159
|
+
const response = await send({ cmd: 'GetIgnoredChildrenFailures', id }, 'GetIgnoredChildrenFailures');
|
|
160
|
+
return response.values ?? {};
|
|
161
|
+
},
|
|
162
|
+
removeChildDependency: async (id) => {
|
|
163
|
+
if (ctx.embedded)
|
|
164
|
+
return manager.removeChildDependency(jobId(id));
|
|
165
|
+
const response = await send({ cmd: 'RemoveChildDependency', id }, 'RemoveChildDependency');
|
|
166
|
+
return response.removed ?? false;
|
|
167
|
+
},
|
|
168
|
+
removeDeduplicationKey: (id) => removeJobDeduplicationKey(id, ctx.embedded, ctx.tcp),
|
|
169
|
+
removeUnprocessedChildren: async (id) => {
|
|
170
|
+
if (ctx.embedded)
|
|
171
|
+
await manager.removeUnprocessedChildren(jobId(id));
|
|
172
|
+
else
|
|
173
|
+
await send({ cmd: 'RemoveUnprocessedChildren', id }, 'RemoveUnprocessedChildren');
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Dead Letter Queue management for embedded mode
|
|
4
4
|
*/
|
|
5
5
|
import type { DlqConfig, DlqEntry, DlqStats, DlqFilter, StallConfig } from '../types';
|
|
6
|
+
import type { PublicJobMethodContext } from '../jobConversionTypes';
|
|
6
7
|
/** Set DLQ configuration (embedded only) */
|
|
7
8
|
export declare function setDlqConfig(queue: string, config: Partial<DlqConfig>): void;
|
|
8
9
|
/** Get DLQ configuration (embedded only) */
|
|
9
10
|
export declare function getDlqConfigEmbedded(queue: string): DlqConfig;
|
|
10
11
|
/** Get DLQ entries (embedded only) */
|
|
11
|
-
export declare function getDlqEntries<T>(queue: string, filter?: DlqFilter): DlqEntry<T>[];
|
|
12
|
+
export declare function getDlqEntries<T>(queue: string, methods: PublicJobMethodContext, filter?: DlqFilter): DlqEntry<T>[];
|
|
12
13
|
/** Get DLQ statistics (embedded only) */
|
|
13
14
|
export declare function getDlqStatsEmbedded(queue: string): DlqStats;
|
|
14
15
|
/** Retry DLQ jobs by filter (embedded only) */
|
|
@@ -18,11 +18,11 @@ export function getDlqConfigEmbedded(queue) {
|
|
|
18
18
|
return dlqOps.getDlqConfig(queue, ctx);
|
|
19
19
|
}
|
|
20
20
|
/** Get DLQ entries (embedded only) */
|
|
21
|
-
export function getDlqEntries(queue, filter) {
|
|
21
|
+
export function getDlqEntries(queue, methods, filter) {
|
|
22
22
|
const manager = getSharedManager();
|
|
23
23
|
const ctx = getDlqContext(manager);
|
|
24
24
|
const entries = dlqOps.getDlqEntries(queue, ctx, toDomainFilter(filter));
|
|
25
|
-
return entries.map((entry) => toDlqEntry(entry));
|
|
25
|
+
return entries.map((entry) => toDlqEntry(entry, methods));
|
|
26
26
|
}
|
|
27
27
|
/** Get DLQ statistics (embedded only) */
|
|
28
28
|
export function getDlqStatsEmbedded(queue) {
|
|
@@ -104,7 +104,12 @@ export async function moveJobToWaitingChildren(ctx, id, _token, _opts) {
|
|
|
104
104
|
const manager = getSharedManager();
|
|
105
105
|
return manager.moveToWaitingChildren(jobId(id));
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
if (!ctx.tcp)
|
|
108
|
+
return false;
|
|
109
|
+
const response = await ctx.tcp.send({ cmd: 'MoveToWaitingChildren', id });
|
|
110
|
+
if (!response.ok)
|
|
111
|
+
return false;
|
|
112
|
+
return response.data?.moved ?? true;
|
|
108
113
|
}
|
|
109
114
|
/** Wait until job has finished */
|
|
110
115
|
export async function waitJobUntilFinished(ctx, id, queueEvents, ttl) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { getSharedManager } from '../manager';
|
|
6
6
|
import { jobId } from '../../domain/types/job';
|
|
7
7
|
import { buildFailCommand, failEmbeddedArgs } from './failWire';
|
|
8
|
+
import { removeJobDeduplicationKey } from '../jobDeduplication';
|
|
8
9
|
function reflectFields(id, queueName, meta) {
|
|
9
10
|
const opts = meta?.opts ?? {};
|
|
10
11
|
const p = opts.parent;
|
|
@@ -142,8 +143,9 @@ export function createJobProxy(id, name, data, ctx, meta) {
|
|
|
142
143
|
const delay = Math.max(0, timestamp - Date.now());
|
|
143
144
|
await tcp.send({ cmd: 'MoveToDelayed', id, delay });
|
|
144
145
|
},
|
|
145
|
-
moveToWaitingChildren: () => {
|
|
146
|
-
|
|
146
|
+
moveToWaitingChildren: async () => {
|
|
147
|
+
const response = await tcp.send({ cmd: 'MoveToWaitingChildren', id });
|
|
148
|
+
return response.ok === true;
|
|
147
149
|
},
|
|
148
150
|
waitUntilFinished: async (_queueEvents, ttl) => {
|
|
149
151
|
const timeout = ttl ?? 30000;
|
|
@@ -169,7 +171,7 @@ export function createJobProxy(id, name, data, ctx, meta) {
|
|
|
169
171
|
const res = await tcp.send({ cmd: 'RemoveChildDependency', id });
|
|
170
172
|
return res.removed ?? false;
|
|
171
173
|
},
|
|
172
|
-
removeDeduplicationKey: () =>
|
|
174
|
+
removeDeduplicationKey: () => removeJobDeduplicationKey(id, false, tcp),
|
|
173
175
|
removeUnprocessedChildren: async () => {
|
|
174
176
|
await tcp.send({ cmd: 'RemoveUnprocessedChildren', id });
|
|
175
177
|
},
|
|
@@ -398,7 +400,10 @@ export function createSimpleJob(id, name, data, timestamp, ctx) {
|
|
|
398
400
|
if (embedded) {
|
|
399
401
|
return await getSharedManager().moveToWaitingChildren(jobId(id));
|
|
400
402
|
}
|
|
401
|
-
|
|
403
|
+
if (!tcp)
|
|
404
|
+
return false;
|
|
405
|
+
const response = await tcp.send({ cmd: 'MoveToWaitingChildren', id });
|
|
406
|
+
return response.ok === true;
|
|
402
407
|
},
|
|
403
408
|
waitUntilFinished: async (_qe, ttl) => {
|
|
404
409
|
const timeout = ttl ?? 30000;
|
|
@@ -457,7 +462,7 @@ export function createSimpleJob(id, name, data, timestamp, ctx) {
|
|
|
457
462
|
const res = await tcp.send({ cmd: 'RemoveChildDependency', id });
|
|
458
463
|
return res.removed ?? false;
|
|
459
464
|
},
|
|
460
|
-
removeDeduplicationKey: () =>
|
|
465
|
+
removeDeduplicationKey: () => removeJobDeduplicationKey(id, embedded ?? false, tcp),
|
|
461
466
|
removeUnprocessedChildren: async () => {
|
|
462
467
|
if (embedded) {
|
|
463
468
|
await getSharedManager().removeUnprocessedChildren(jobId(id));
|