@tstdl/base 0.93.92 → 0.93.94
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/authentication/client/authentication.service.js +3 -2
- package/document-management/server/services/document-validation.service.js +5 -5
- package/document-management/server/services/document-workflow.service.js +2 -2
- package/orm/sqls/sqls.d.ts +6 -6
- package/package.json +2 -2
- package/task-queue/enqueue-batch.d.ts +16 -11
- package/task-queue/enqueue-batch.js +2 -2
- package/task-queue/index.d.ts +2 -1
- package/task-queue/index.js +2 -1
- package/task-queue/postgres/drizzle/{0000_thin_black_panther.sql → 0000_simple_invisible_woman.sql} +5 -5
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +11 -11
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.js +2 -2
- package/task-queue/postgres/schemas.d.ts +1 -1
- package/task-queue/postgres/schemas.js +2 -2
- package/task-queue/postgres/task-queue.d.ts +101 -47
- package/task-queue/postgres/task-queue.js +149 -139
- package/task-queue/postgres/task-queue.provider.d.ts +3 -4
- package/task-queue/postgres/task-queue.provider.js +2 -2
- package/task-queue/postgres/task.model.d.ts +5 -5
- package/task-queue/postgres/task.model.js +5 -5
- package/task-queue/provider.d.ts +2 -2
- package/task-queue/task-context.d.ts +38 -18
- package/task-queue/task-context.js +35 -13
- package/task-queue/task-queue.d.ts +160 -132
- package/task-queue/task-queue.js +8 -8
- package/task-queue/tests/complex.test.js +36 -29
- package/task-queue/tests/dependencies.test.js +17 -17
- package/task-queue/tests/enqueue-item.test.d.ts +1 -0
- package/task-queue/tests/enqueue-item.test.js +12 -0
- package/task-queue/tests/queue-generic.test.d.ts +1 -0
- package/task-queue/tests/queue-generic.test.js +8 -0
- package/task-queue/tests/queue.test.js +50 -50
- package/task-queue/tests/task-context.test.d.ts +1 -0
- package/task-queue/tests/task-context.test.js +7 -0
- package/task-queue/tests/task-union.test.d.ts +1 -0
- package/task-queue/tests/task-union.test.js +18 -0
- package/task-queue/tests/typing.test.d.ts +1 -0
- package/task-queue/tests/typing.test.js +9 -0
- package/task-queue/tests/worker.test.js +16 -16
- package/task-queue/types.d.ts +48 -0
- package/task-queue/types.js +1 -0
|
@@ -4,43 +4,18 @@ import type { Resolvable, resolveArgumentType } from '../injector/interfaces.js'
|
|
|
4
4
|
import { Logger } from '../logger/logger.js';
|
|
5
5
|
import type { Transaction } from '../orm/server/transaction.js';
|
|
6
6
|
import { Transactional } from '../orm/server/transactional.js';
|
|
7
|
-
import type { OneOrMany, UndefinableJson } from '../types/types.js';
|
|
7
|
+
import type { OneOrMany, Record, UndefinableJson } from '../types/types.js';
|
|
8
8
|
import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
result: Result | undefined;
|
|
13
|
-
} | {
|
|
14
|
-
action: 'fail';
|
|
15
|
-
error: any;
|
|
16
|
-
fatal: boolean;
|
|
17
|
-
} | {
|
|
18
|
-
action: 'reschedule';
|
|
19
|
-
timestamp: number;
|
|
20
|
-
};
|
|
21
|
-
export declare class TaskResult<Result = unknown> {
|
|
22
|
-
readonly payload: TaskResultPayload<Result>;
|
|
9
|
+
import type { ProcessBatchWorker, ProcessWorker, TaskData, TaskDefinitionMap, TaskOfType, TaskProcessResultPayload, TaskResult, TaskState, TaskTypes, TasksResults, TasksStates } from './types.js';
|
|
10
|
+
export declare class TaskProcessResult<Result = unknown> {
|
|
11
|
+
readonly payload: TaskProcessResultPayload<Result>;
|
|
23
12
|
private constructor();
|
|
24
|
-
static Complete<Result>(result?: Result):
|
|
25
|
-
static Fail(error:
|
|
26
|
-
static RescheduleTo(timestamp: number):
|
|
27
|
-
static RescheduleBy(milliseconds: number):
|
|
28
|
-
}
|
|
29
|
-
export interface ProcessWorker<Data, State, Result> {
|
|
30
|
-
/**
|
|
31
|
-
* A worker function that processes a single task.
|
|
32
|
-
* @param context The task context providing data, logger, and orchestration helpers.
|
|
33
|
-
*/
|
|
34
|
-
(context: TaskContext<Data, State, Result>): TaskResult<Result> | Promise<TaskResult<Result>>;
|
|
35
|
-
}
|
|
36
|
-
export interface ProcessBatchWorker<Data, State, Result> {
|
|
37
|
-
/**
|
|
38
|
-
* A worker function that processes a batch of tasks.
|
|
39
|
-
* @param context The batch context providing tasks and helpers.
|
|
40
|
-
*/
|
|
41
|
-
(context: BatchTaskContext<Data, State, Result>): TaskResult<Result>[] | Promise<TaskResult<Result>[]>;
|
|
13
|
+
static Complete<Result>(result?: Result): TaskProcessResult<Result>;
|
|
14
|
+
static Fail(error: unknown, fatal?: boolean): TaskProcessResult;
|
|
15
|
+
static RescheduleTo(timestamp: number): TaskProcessResult;
|
|
16
|
+
static RescheduleBy(milliseconds: number): TaskProcessResult;
|
|
42
17
|
}
|
|
43
|
-
export declare const
|
|
18
|
+
export declare const TaskStatus: {
|
|
44
19
|
/**
|
|
45
20
|
* The task is waiting to be processed.
|
|
46
21
|
*/
|
|
@@ -66,59 +41,65 @@ export declare const TaskState: {
|
|
|
66
41
|
*/
|
|
67
42
|
readonly Dead: "dead";
|
|
68
43
|
};
|
|
69
|
-
export type
|
|
44
|
+
export type TaskStatus = EnumType<typeof TaskStatus>;
|
|
70
45
|
export declare const DependencyJoinMode: {
|
|
71
46
|
readonly And: "and";
|
|
72
47
|
readonly Or: "or";
|
|
73
48
|
};
|
|
74
49
|
export type DependencyJoinMode = EnumType<typeof DependencyJoinMode>;
|
|
75
|
-
export type Task<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
50
|
+
export type Task<Definitions extends TaskDefinitionMap = Record<string, {
|
|
51
|
+
data: unknown;
|
|
52
|
+
state: unknown;
|
|
53
|
+
result: unknown;
|
|
54
|
+
}>> = {
|
|
55
|
+
[Type in TaskTypes<Definitions>]: {
|
|
56
|
+
id: string;
|
|
57
|
+
namespace: string;
|
|
58
|
+
type: Type;
|
|
59
|
+
status: TaskStatus;
|
|
60
|
+
token: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* The lower the number, the higher the priority.
|
|
63
|
+
* @default 1000
|
|
64
|
+
*/
|
|
65
|
+
priority: number;
|
|
66
|
+
idempotencyKey: string | null;
|
|
67
|
+
traceId: string | null;
|
|
68
|
+
tags: string[];
|
|
69
|
+
completeAfterTags: string[];
|
|
70
|
+
scheduleAfterTags: string[];
|
|
71
|
+
failFast: boolean;
|
|
72
|
+
dependencyJoinMode: DependencyJoinMode;
|
|
73
|
+
dependencyTriggerStatuses: TaskStatus[];
|
|
74
|
+
data: TaskData<Definitions, Type>;
|
|
75
|
+
parentId: string | null;
|
|
76
|
+
tries: number;
|
|
77
|
+
creationTimestamp: number;
|
|
78
|
+
priorityAgeTimestamp: number;
|
|
79
|
+
scheduleTimestamp: number;
|
|
80
|
+
/**
|
|
81
|
+
* Timestamp when the task most recently switched to Running state.
|
|
82
|
+
* Used for Hard Execution Timeouts.
|
|
83
|
+
*/
|
|
84
|
+
startTimestamp: number | null;
|
|
85
|
+
/**
|
|
86
|
+
* Timestamp after which the task is considered expired if it hasn't started Running.
|
|
87
|
+
* If null, the task never expires in the queue.
|
|
88
|
+
*/
|
|
89
|
+
timeToLive: number | null;
|
|
90
|
+
/**
|
|
91
|
+
* Token expiration (Soft Timeout).
|
|
92
|
+
*/
|
|
93
|
+
visibilityDeadline: number | null;
|
|
94
|
+
completeTimestamp: number | null;
|
|
95
|
+
/** A number between 0 and 1 indicating the progress of the task. */
|
|
96
|
+
progress: number;
|
|
97
|
+
/** A snapshot of the current state of the task. */
|
|
98
|
+
state: TaskState<Definitions, Type> | null;
|
|
99
|
+
result: TaskResult<Definitions, Type> | null;
|
|
100
|
+
error: UndefinableJson | null;
|
|
101
|
+
};
|
|
102
|
+
}[Extract<keyof Definitions, string>];
|
|
122
103
|
export declare const defaultTaskPriority = 1000;
|
|
123
104
|
export type EnqueueOptions = {
|
|
124
105
|
priority?: number;
|
|
@@ -130,16 +111,18 @@ export type EnqueueOptions = {
|
|
|
130
111
|
scheduleAfterTags?: string[];
|
|
131
112
|
failFast?: boolean;
|
|
132
113
|
dependencyJoinMode?: DependencyJoinMode;
|
|
133
|
-
|
|
114
|
+
dependencyTriggerStatuses?: TaskStatus[];
|
|
134
115
|
scheduleTimestamp?: number;
|
|
135
116
|
timeToLive?: number;
|
|
136
117
|
transaction?: Transaction;
|
|
137
118
|
};
|
|
138
119
|
export type EnqueueOneOptions = EnqueueOptions;
|
|
139
|
-
export type EnqueueManyItem<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
120
|
+
export type EnqueueManyItem<Definitions extends TaskDefinitionMap = TaskDefinitionMap, Type extends TaskTypes<Definitions> = TaskTypes<Definitions>> = {
|
|
121
|
+
[Type in TaskTypes<Definitions>]: {
|
|
122
|
+
type: Type;
|
|
123
|
+
data: TaskData<Definitions, Type>;
|
|
124
|
+
} & EnqueueOptions;
|
|
125
|
+
}[Type];
|
|
143
126
|
export type EnqueueManyOptions = {
|
|
144
127
|
replace?: boolean;
|
|
145
128
|
returnTasks?: boolean;
|
|
@@ -192,7 +175,7 @@ export type TaskQueueArgument = string | (QueueConfig & {
|
|
|
192
175
|
namespace: string;
|
|
193
176
|
});
|
|
194
177
|
export declare const defaultQueueConfig: Required<QueueConfig>;
|
|
195
|
-
export declare abstract class TaskQueue<
|
|
178
|
+
export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap = TaskDefinitionMap> extends Transactional implements Resolvable<TaskQueueArgument> {
|
|
196
179
|
readonly [resolveArgumentType]: TaskQueueArgument;
|
|
197
180
|
protected readonly config: QueueConfig & {
|
|
198
181
|
namespace: string;
|
|
@@ -200,82 +183,127 @@ export declare abstract class TaskQueue<Data, State = unknown, Result = unknown>
|
|
|
200
183
|
protected readonly logger: Logger;
|
|
201
184
|
abstract readonly visibilityTimeout: number;
|
|
202
185
|
abstract readonly maxTries: number;
|
|
203
|
-
batch(): TaskQueueEnqueueBatch<
|
|
204
|
-
abstract enqueue(type:
|
|
205
|
-
abstract enqueueMany(items: EnqueueManyItem<
|
|
186
|
+
batch(): TaskQueueEnqueueBatch<Definitions>;
|
|
187
|
+
abstract enqueue<Type extends TaskTypes<Definitions>>(type: Type, data: TaskData<Definitions, Type>, options?: EnqueueOneOptions): Promise<TaskOfType<Definitions, Type>>;
|
|
188
|
+
abstract enqueueMany<Type extends TaskTypes<Definitions>>(items: EnqueueManyItem<Definitions, Type>[], options?: EnqueueManyOptions & {
|
|
206
189
|
returnTasks?: false;
|
|
207
190
|
}): Promise<void>;
|
|
208
|
-
abstract enqueueMany(items: EnqueueManyItem<
|
|
191
|
+
abstract enqueueMany<Type extends TaskTypes<Definitions>>(items: EnqueueManyItem<Definitions, Type>[], options: EnqueueManyOptions & {
|
|
209
192
|
returnTasks: true;
|
|
210
|
-
}): Promise<
|
|
211
|
-
abstract enqueueMany(items: EnqueueManyItem<
|
|
212
|
-
abstract has(id: string
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
abstract
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
abstract
|
|
219
|
-
|
|
193
|
+
}): Promise<TaskOfType<Definitions, Type>[]>;
|
|
194
|
+
abstract enqueueMany<Type extends TaskTypes<Definitions>>(items: EnqueueManyItem<Definitions, Type>[], options?: EnqueueManyOptions): Promise<TaskOfType<Definitions, Type>[] | undefined>;
|
|
195
|
+
abstract has(id: string, options?: {
|
|
196
|
+
transaction?: Transaction;
|
|
197
|
+
}): Promise<boolean>;
|
|
198
|
+
abstract getTask(id: string, options?: {
|
|
199
|
+
transaction?: Transaction;
|
|
200
|
+
}): Promise<Task<Definitions> | undefined>;
|
|
201
|
+
abstract getManyByTags(tags: OneOrMany<string>, options?: {
|
|
202
|
+
transaction?: Transaction;
|
|
203
|
+
}): Promise<Task<Definitions>[]>;
|
|
204
|
+
abstract countByTags(tags: OneOrMany<string>, options?: {
|
|
205
|
+
transaction?: Transaction;
|
|
206
|
+
}): Promise<number>;
|
|
207
|
+
abstract getTree(rootId: string | string[], options?: {
|
|
208
|
+
transaction?: Transaction;
|
|
209
|
+
}): Promise<Task[]>;
|
|
210
|
+
abstract cancel(id: string, options?: {
|
|
211
|
+
transaction?: Transaction;
|
|
212
|
+
}): Promise<void>;
|
|
213
|
+
abstract cancelMany(ids: string[], options?: {
|
|
214
|
+
transaction?: Transaction;
|
|
215
|
+
}): Promise<void>;
|
|
216
|
+
abstract cancelManyByTags(tags: OneOrMany<string>, options?: {
|
|
217
|
+
transaction?: Transaction;
|
|
218
|
+
}): Promise<void>;
|
|
220
219
|
/** Clears all tasks from the queue. Use with caution! */
|
|
221
|
-
abstract clear(
|
|
222
|
-
|
|
223
|
-
|
|
220
|
+
abstract clear(options?: {
|
|
221
|
+
transaction?: Transaction;
|
|
222
|
+
}): Promise<void>;
|
|
223
|
+
abstract dequeue<Type extends TaskTypes<Definitions>>(options?: {
|
|
224
224
|
forceDequeue?: boolean;
|
|
225
|
-
types?:
|
|
226
|
-
|
|
225
|
+
types?: Type[];
|
|
226
|
+
transaction?: Transaction;
|
|
227
|
+
}): Promise<TaskOfType<Definitions, Type> | undefined>;
|
|
228
|
+
abstract dequeueMany<Type extends TaskTypes<Definitions>>(count: number, options?: {
|
|
229
|
+
forceDequeue?: boolean;
|
|
230
|
+
types?: Type[];
|
|
231
|
+
transaction?: Transaction;
|
|
232
|
+
}): Promise<TaskOfType<Definitions, Type>[]>;
|
|
227
233
|
/**
|
|
228
234
|
* Reschedules a task to run at a specific time.
|
|
229
235
|
* NOTE: If the task is currently running, its retry count is decremented (refunded) so this attempt doesn't count against maxTries.
|
|
230
236
|
*/
|
|
231
|
-
abstract reschedule(id: string, timestamp: number,
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
abstract reschedule(id: string, timestamp: number, options?: {
|
|
238
|
+
transaction?: Transaction;
|
|
239
|
+
}): Promise<void>;
|
|
240
|
+
abstract rescheduleMany(ids: string[], timestamp: number, options?: {
|
|
241
|
+
transaction?: Transaction;
|
|
242
|
+
}): Promise<void>;
|
|
243
|
+
abstract rescheduleManyByTags(tags: OneOrMany<string>, timestamp: number, options?: {
|
|
244
|
+
transaction?: Transaction;
|
|
245
|
+
}): Promise<void>;
|
|
234
246
|
/**
|
|
235
247
|
* Updates task progress, state and lock.
|
|
236
248
|
* Returns the updated task if successful, `undefined` if task is lost/cancelled/timed out.
|
|
237
249
|
*/
|
|
238
|
-
abstract touch(task:
|
|
250
|
+
abstract touch<Type extends TaskTypes<Definitions>>(task: TaskOfType<Definitions, Type>, options?: {
|
|
239
251
|
progress?: number;
|
|
240
|
-
state?:
|
|
252
|
+
state?: TaskState<Definitions, Type>;
|
|
241
253
|
transaction?: Transaction;
|
|
242
|
-
}): Promise<
|
|
254
|
+
}): Promise<TaskOfType<Definitions, Type> | undefined>;
|
|
243
255
|
/**
|
|
244
256
|
* Updates multiple tasks' progress, state and lock.
|
|
245
257
|
* Returns the IDs of the successfully updated tasks.
|
|
246
258
|
*/
|
|
247
|
-
abstract touchMany
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
abstract
|
|
259
|
+
abstract touchMany<Tasks extends Task<Definitions>[]>(tasks: Tasks, options?: {
|
|
260
|
+
progresses?: number[];
|
|
261
|
+
states?: TasksStates<Tasks>;
|
|
262
|
+
transaction?: Transaction;
|
|
263
|
+
}): Promise<string[]>;
|
|
264
|
+
abstract complete<Type extends TaskTypes<Definitions>>(task: TaskOfType<Definitions, Type>, options?: {
|
|
265
|
+
result?: TaskResult<Definitions, Type>;
|
|
266
|
+
transaction?: Transaction;
|
|
267
|
+
}): Promise<void>;
|
|
268
|
+
abstract completeMany<Tasks extends Task<Definitions>[]>(tasks: Tasks, options?: {
|
|
269
|
+
results?: TasksResults<Tasks>;
|
|
270
|
+
transaction?: Transaction;
|
|
271
|
+
}): Promise<void>;
|
|
272
|
+
abstract fail(task: Task<Definitions>, error: unknown, options?: {
|
|
273
|
+
fatal?: boolean;
|
|
274
|
+
transaction?: Transaction;
|
|
275
|
+
}): Promise<void>;
|
|
276
|
+
abstract failMany(tasks: Task<Definitions>[], errors: unknown[], options?: {
|
|
277
|
+
transaction?: Transaction;
|
|
278
|
+
}): Promise<void>;
|
|
279
|
+
abstract maintenance(options?: {
|
|
280
|
+
transaction?: Transaction;
|
|
281
|
+
}): Promise<void>;
|
|
253
282
|
abstract restart(id: string, options?: {
|
|
254
283
|
resetState?: boolean;
|
|
255
284
|
transaction?: Transaction;
|
|
256
285
|
}): Promise<void>;
|
|
257
|
-
abstract getConsumer(cancellationSignal: CancellationSignal, options?: {
|
|
286
|
+
abstract getConsumer<Type extends TaskTypes<Definitions>>(cancellationSignal: CancellationSignal, options?: {
|
|
258
287
|
forceDequeue?: boolean;
|
|
259
|
-
types?:
|
|
260
|
-
}): AsyncIterableIterator<
|
|
261
|
-
abstract getBatchConsumer(size: number, cancellationSignal: CancellationSignal, options?: {
|
|
288
|
+
types?: Type[];
|
|
289
|
+
}): AsyncIterableIterator<TaskOfType<Definitions, Type>>;
|
|
290
|
+
abstract getBatchConsumer<Type extends TaskTypes<Definitions>>(size: number, cancellationSignal: CancellationSignal, options?: {
|
|
262
291
|
forceDequeue?: boolean;
|
|
263
|
-
types?:
|
|
264
|
-
}): AsyncIterableIterator<
|
|
265
|
-
process({ concurrency, cancellationSignal, types, forceDequeue }: {
|
|
292
|
+
types?: Type[];
|
|
293
|
+
}): AsyncIterableIterator<TaskOfType<Definitions, Type>[]>;
|
|
294
|
+
process<Type extends TaskTypes<Definitions>>({ concurrency, cancellationSignal, types, forceDequeue }: {
|
|
266
295
|
concurrency?: number;
|
|
267
296
|
cancellationSignal: CancellationSignal;
|
|
268
|
-
types?:
|
|
297
|
+
types?: Type[];
|
|
269
298
|
forceDequeue?: boolean;
|
|
270
|
-
}, handler: ProcessWorker<
|
|
271
|
-
processBatch({ batchSize, concurrency, cancellationSignal, types, forceDequeue }: {
|
|
299
|
+
}, handler: ProcessWorker<Definitions, Type>): void;
|
|
300
|
+
processBatch<Type extends TaskTypes<Definitions>>({ batchSize, concurrency, cancellationSignal, types, forceDequeue }: {
|
|
272
301
|
batchSize?: number;
|
|
273
302
|
concurrency?: number;
|
|
274
303
|
cancellationSignal: CancellationSignal;
|
|
275
|
-
types?:
|
|
304
|
+
types?: Type[];
|
|
276
305
|
forceDequeue?: boolean;
|
|
277
|
-
}, handler: ProcessBatchWorker<
|
|
306
|
+
}, handler: ProcessBatchWorker<Definitions, Type>): void;
|
|
278
307
|
private processWorker;
|
|
279
308
|
private processBatchWorker;
|
|
280
309
|
}
|
|
281
|
-
export {};
|
package/task-queue/task-queue.js
CHANGED
|
@@ -9,26 +9,26 @@ import { isDefined, isString } from '../utils/type-guards.js';
|
|
|
9
9
|
import { millisecondsPerDay, millisecondsPerMinute, millisecondsPerSecond } from '../utils/units.js';
|
|
10
10
|
import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
|
|
11
11
|
import { BatchTaskContext } from './task-context.js';
|
|
12
|
-
export class
|
|
12
|
+
export class TaskProcessResult {
|
|
13
13
|
payload;
|
|
14
14
|
constructor(payload) {
|
|
15
15
|
this.payload = payload;
|
|
16
16
|
}
|
|
17
17
|
static Complete(result) {
|
|
18
|
-
return new
|
|
18
|
+
return new TaskProcessResult({ action: 'complete', result });
|
|
19
19
|
}
|
|
20
20
|
static Fail(error, fatal = false) {
|
|
21
|
-
return new
|
|
21
|
+
return new TaskProcessResult({ action: 'fail', error, fatal });
|
|
22
22
|
}
|
|
23
23
|
static RescheduleTo(timestamp) {
|
|
24
|
-
return new
|
|
24
|
+
return new TaskProcessResult({ action: 'reschedule', timestamp });
|
|
25
25
|
}
|
|
26
26
|
static RescheduleBy(milliseconds) {
|
|
27
27
|
const timestamp = currentTimestamp() + milliseconds;
|
|
28
28
|
return this.RescheduleTo(timestamp);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
export const
|
|
31
|
+
export const TaskStatus = defineEnum('TaskStatus', {
|
|
32
32
|
/**
|
|
33
33
|
* The task is waiting to be processed.
|
|
34
34
|
*/
|
|
@@ -152,7 +152,7 @@ export class TaskQueue extends Transactional {
|
|
|
152
152
|
const result = results[i];
|
|
153
153
|
switch (result.payload.action) {
|
|
154
154
|
case 'complete':
|
|
155
|
-
completions.push({ task, result: result.payload });
|
|
155
|
+
completions.push({ task, result: result.payload.result });
|
|
156
156
|
break;
|
|
157
157
|
case 'fail':
|
|
158
158
|
failures.push({ task, error: result.payload.error, fatal: result.payload.fatal });
|
|
@@ -166,12 +166,12 @@ export class TaskQueue extends Transactional {
|
|
|
166
166
|
}
|
|
167
167
|
if (completions.length > 0) {
|
|
168
168
|
context.logger.verbose(`Completing ${completions.length} tasks`);
|
|
169
|
-
await this.completeMany(completions.map((c) => c.task), completions.map((c) => c.result));
|
|
169
|
+
await this.completeMany(completions.map((c) => c.task), { results: completions.map((c) => c.result) });
|
|
170
170
|
}
|
|
171
171
|
if (failures.length > 0) {
|
|
172
172
|
context.logger.verbose(`Failing ${failures.length} tasks`);
|
|
173
173
|
for (const item of failures) {
|
|
174
|
-
await this.fail(item.task, item.error, item.fatal);
|
|
174
|
+
await this.fail(item.task, item.error, { fatal: item.fatal });
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
if (reschedules.length > 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { DependencyJoinMode, TaskQueueProvider,
|
|
2
|
+
import { DependencyJoinMode, TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
3
3
|
import { setupIntegrationTest } from '../../unit-test/index.js';
|
|
4
4
|
import { currentTimestamp } from '../../utils/date-time.js';
|
|
5
5
|
import { timeout } from '../../utils/timing.js';
|
|
@@ -50,15 +50,15 @@ describe('Complex Queue Scenarios', () => {
|
|
|
50
50
|
const taskC = await queue.enqueue('C', {}, { tags: ['tag-c'], scheduleAfterTags: ['tag-a'] });
|
|
51
51
|
// A runs first
|
|
52
52
|
const taskA = await queue.enqueue('A', {}, { tags: ['tag-a'] });
|
|
53
|
-
expect(taskD.status).toBe(
|
|
54
|
-
expect(taskB.status).toBe(
|
|
55
|
-
expect(taskC.status).toBe(
|
|
56
|
-
expect(taskA.status).toBe(
|
|
53
|
+
expect(taskD.status).toBe(TaskStatus.Waiting);
|
|
54
|
+
expect(taskB.status).toBe(TaskStatus.Waiting);
|
|
55
|
+
expect(taskC.status).toBe(TaskStatus.Waiting);
|
|
56
|
+
expect(taskA.status).toBe(TaskStatus.Pending);
|
|
57
57
|
// Process A
|
|
58
58
|
const dA = await queue.dequeue({ types: ['A'] });
|
|
59
59
|
await queue.complete(dA);
|
|
60
|
-
await waitForStatus(taskB.id,
|
|
61
|
-
await waitForStatus(taskC.id,
|
|
60
|
+
await waitForStatus(taskB.id, TaskStatus.Pending);
|
|
61
|
+
await waitForStatus(taskC.id, TaskStatus.Pending);
|
|
62
62
|
// Process B
|
|
63
63
|
const dB = await queue.dequeue({ types: ['B'] });
|
|
64
64
|
await queue.complete(dB);
|
|
@@ -66,14 +66,14 @@ describe('Complex Queue Scenarios', () => {
|
|
|
66
66
|
await queue.processPendingFanIn();
|
|
67
67
|
// D still waiting (needs C)
|
|
68
68
|
const uD2 = await queue.getTask(taskD.id);
|
|
69
|
-
expect(uD2?.status).toBe(
|
|
69
|
+
expect(uD2?.status).toBe(TaskStatus.Waiting);
|
|
70
70
|
// Process C
|
|
71
71
|
const dC = await queue.dequeue({ types: ['C'] });
|
|
72
72
|
await queue.complete(dC);
|
|
73
|
-
await waitForStatus(taskD.id,
|
|
73
|
+
await waitForStatus(taskD.id, TaskStatus.Pending);
|
|
74
74
|
// D should be Pending
|
|
75
75
|
const uD3 = await queue.getTask(taskD.id);
|
|
76
|
-
expect(uD3?.status).toBe(
|
|
76
|
+
expect(uD3?.status).toBe(TaskStatus.Pending);
|
|
77
77
|
});
|
|
78
78
|
it('should handle Deep Chain (A -> B -> C -> D)', async () => {
|
|
79
79
|
const D = await queue.enqueue('D', {}, { scheduleAfterTags: ['C'] });
|
|
@@ -82,14 +82,14 @@ describe('Complex Queue Scenarios', () => {
|
|
|
82
82
|
const A = await queue.enqueue('A', {}, { tags: ['A'] });
|
|
83
83
|
// Run A
|
|
84
84
|
await queue.complete((await queue.dequeue({ types: ['A'] })));
|
|
85
|
-
await waitForStatus(B.id,
|
|
85
|
+
await waitForStatus(B.id, TaskStatus.Pending);
|
|
86
86
|
// Run B
|
|
87
87
|
await queue.complete((await queue.dequeue({ types: ['B'] })));
|
|
88
|
-
await waitForStatus(C.id,
|
|
88
|
+
await waitForStatus(C.id, TaskStatus.Pending);
|
|
89
89
|
// Run C
|
|
90
90
|
await queue.complete((await queue.dequeue({ types: ['C'] })));
|
|
91
|
-
await waitForStatus(D.id,
|
|
92
|
-
expect((await queue.getTask(D.id))?.status).toBe(
|
|
91
|
+
await waitForStatus(D.id, TaskStatus.Pending);
|
|
92
|
+
expect((await queue.getTask(D.id))?.status).toBe(TaskStatus.Pending);
|
|
93
93
|
});
|
|
94
94
|
it('should propagate cancellation down the dependency tree', async () => {
|
|
95
95
|
// Use parentId for explicit tree structure which `cancel` supports
|
|
@@ -97,9 +97,9 @@ describe('Complex Queue Scenarios', () => {
|
|
|
97
97
|
const child = await queue.enqueue('child', {}, { parentId: root.id });
|
|
98
98
|
const grandChild = await queue.enqueue('grand', {}, { parentId: child.id });
|
|
99
99
|
await queue.cancel(root.id);
|
|
100
|
-
expect((await queue.getTask(root.id))?.status).toBe(
|
|
101
|
-
expect((await queue.getTask(child.id))?.status).toBe(
|
|
102
|
-
expect((await queue.getTask(grandChild.id))?.status).toBe(
|
|
100
|
+
expect((await queue.getTask(root.id))?.status).toBe(TaskStatus.Cancelled);
|
|
101
|
+
expect((await queue.getTask(child.id))?.status).toBe(TaskStatus.Cancelled);
|
|
102
|
+
expect((await queue.getTask(grandChild.id))?.status).toBe(TaskStatus.Cancelled);
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
describe('Scheduling & Priorities', () => {
|
|
@@ -129,7 +129,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
129
129
|
const u2 = await queue.getTask(task.id);
|
|
130
130
|
expect(u2?.tries).toBe(2);
|
|
131
131
|
const now = currentTimestamp();
|
|
132
|
-
expect(u2.scheduleTimestamp
|
|
132
|
+
expect(u2.scheduleTimestamp > now + 300).toBe(true);
|
|
133
133
|
});
|
|
134
134
|
});
|
|
135
135
|
describe('Rate Limiting & Concurrency', () => {
|
|
@@ -177,15 +177,15 @@ describe('Complex Queue Scenarios', () => {
|
|
|
177
177
|
// Verify it is in main table
|
|
178
178
|
const before = await archiveQueue.getTask(task.id);
|
|
179
179
|
expect(before).toBeDefined();
|
|
180
|
-
expect(before?.status).toBe(
|
|
181
|
-
expect(before
|
|
180
|
+
expect(before?.status).toBe(TaskStatus.Completed);
|
|
181
|
+
expect(before.completeTimestamp > 0).toBe(true);
|
|
182
182
|
// Wait for retention (100ms).
|
|
183
183
|
await timeout(500);
|
|
184
184
|
await archiveQueue.maintenance();
|
|
185
185
|
// Should move from main table to archive
|
|
186
186
|
const loaded = await archiveQueue.getTask(task.id);
|
|
187
187
|
expect(loaded).toBeDefined();
|
|
188
|
-
expect(loaded?.status).toBe(
|
|
188
|
+
expect(loaded?.status).toBe(TaskStatus.Completed);
|
|
189
189
|
await archiveQueue.clear();
|
|
190
190
|
});
|
|
191
191
|
it('should prune expired pending tasks', async () => {
|
|
@@ -194,8 +194,8 @@ describe('Complex Queue Scenarios', () => {
|
|
|
194
194
|
await timeout(150);
|
|
195
195
|
await queue.maintenance();
|
|
196
196
|
const updated = await queue.getTask(task.id);
|
|
197
|
-
expect(updated?.status).toBe(
|
|
198
|
-
expect(updated?.error
|
|
197
|
+
expect(updated?.status).toBe(TaskStatus.Dead);
|
|
198
|
+
expect((updated?.error)['code']).toBe('Expired');
|
|
199
199
|
});
|
|
200
200
|
it('should retrieve task from archive', async () => {
|
|
201
201
|
// Manually insert into archive? We can't access archiveRepository directly.
|
|
@@ -211,7 +211,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
211
211
|
// Verify retrieval
|
|
212
212
|
const fromArchive = await queue.getTask(task.id);
|
|
213
213
|
expect(fromArchive).toBeDefined();
|
|
214
|
-
expect(fromArchive?.status).toBe(
|
|
214
|
+
expect(fromArchive?.status).toBe(TaskStatus.Completed);
|
|
215
215
|
});
|
|
216
216
|
it('should defer archival of parent tasks until children are archived', async () => {
|
|
217
217
|
const qProvider = injector.resolve(TaskQueueProvider);
|
|
@@ -267,7 +267,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
267
267
|
expect(t1.id).toBe(t2.id); // Deduplicated
|
|
268
268
|
const t3 = await queue.enqueue('t', { v: 3 }, { idempotencyKey: key, replace: true });
|
|
269
269
|
expect(t3.id).not.toBe(t1.id); // Replaced
|
|
270
|
-
expect(t3.data
|
|
270
|
+
expect(t3.data['v']).toBe(3);
|
|
271
271
|
});
|
|
272
272
|
});
|
|
273
273
|
describe('Edge Cases', () => {
|
|
@@ -277,8 +277,15 @@ describe('Complex Queue Scenarios', () => {
|
|
|
277
277
|
const d = await queue.dequeue({ types: ['pre'] });
|
|
278
278
|
await queue.fail(d, new Error('fail'));
|
|
279
279
|
await queue.processPendingFanIn();
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
// Retry check because fan-in might be processed by background worker asynchronously
|
|
281
|
+
let u = await queue.getTask(dependent.id);
|
|
282
|
+
for (let i = 0; i < 5; i++) {
|
|
283
|
+
if (u?.status == TaskStatus.Waiting)
|
|
284
|
+
break;
|
|
285
|
+
await timeout(50);
|
|
286
|
+
u = await queue.getTask(dependent.id);
|
|
287
|
+
}
|
|
288
|
+
expect(u?.status).toBe(TaskStatus.Waiting); // Should still be waiting because dependency didn't Complete
|
|
282
289
|
});
|
|
283
290
|
it('should handle mixed AND/OR dependencies', async () => {
|
|
284
291
|
const dep = await queue.enqueue('dep', {}, {
|
|
@@ -287,8 +294,8 @@ describe('Complex Queue Scenarios', () => {
|
|
|
287
294
|
});
|
|
288
295
|
const A = await queue.enqueue('A', {}, { tags: ['A'] });
|
|
289
296
|
await queue.complete((await queue.dequeue({ types: ['A'] })));
|
|
290
|
-
await waitForStatus(dep.id,
|
|
291
|
-
expect((await queue.getTask(dep.id))?.status).toBe(
|
|
297
|
+
await waitForStatus(dep.id, TaskStatus.Pending);
|
|
298
|
+
expect((await queue.getTask(dep.id))?.status).toBe(TaskStatus.Pending);
|
|
292
299
|
});
|
|
293
300
|
it('should not reschedule if task is not running', async () => {
|
|
294
301
|
const task = await queue.enqueue('t', {});
|