@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.
Files changed (42) hide show
  1. package/authentication/client/authentication.service.js +3 -2
  2. package/document-management/server/services/document-validation.service.js +5 -5
  3. package/document-management/server/services/document-workflow.service.js +2 -2
  4. package/orm/sqls/sqls.d.ts +6 -6
  5. package/package.json +2 -2
  6. package/task-queue/enqueue-batch.d.ts +16 -11
  7. package/task-queue/enqueue-batch.js +2 -2
  8. package/task-queue/index.d.ts +2 -1
  9. package/task-queue/index.js +2 -1
  10. package/task-queue/postgres/drizzle/{0000_thin_black_panther.sql → 0000_simple_invisible_woman.sql} +5 -5
  11. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +11 -11
  12. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  13. package/task-queue/postgres/module.js +2 -2
  14. package/task-queue/postgres/schemas.d.ts +1 -1
  15. package/task-queue/postgres/schemas.js +2 -2
  16. package/task-queue/postgres/task-queue.d.ts +101 -47
  17. package/task-queue/postgres/task-queue.js +149 -139
  18. package/task-queue/postgres/task-queue.provider.d.ts +3 -4
  19. package/task-queue/postgres/task-queue.provider.js +2 -2
  20. package/task-queue/postgres/task.model.d.ts +5 -5
  21. package/task-queue/postgres/task.model.js +5 -5
  22. package/task-queue/provider.d.ts +2 -2
  23. package/task-queue/task-context.d.ts +38 -18
  24. package/task-queue/task-context.js +35 -13
  25. package/task-queue/task-queue.d.ts +160 -132
  26. package/task-queue/task-queue.js +8 -8
  27. package/task-queue/tests/complex.test.js +36 -29
  28. package/task-queue/tests/dependencies.test.js +17 -17
  29. package/task-queue/tests/enqueue-item.test.d.ts +1 -0
  30. package/task-queue/tests/enqueue-item.test.js +12 -0
  31. package/task-queue/tests/queue-generic.test.d.ts +1 -0
  32. package/task-queue/tests/queue-generic.test.js +8 -0
  33. package/task-queue/tests/queue.test.js +50 -50
  34. package/task-queue/tests/task-context.test.d.ts +1 -0
  35. package/task-queue/tests/task-context.test.js +7 -0
  36. package/task-queue/tests/task-union.test.d.ts +1 -0
  37. package/task-queue/tests/task-union.test.js +18 -0
  38. package/task-queue/tests/typing.test.d.ts +1 -0
  39. package/task-queue/tests/typing.test.js +9 -0
  40. package/task-queue/tests/worker.test.js +16 -16
  41. package/task-queue/types.d.ts +48 -0
  42. 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 { BatchTaskContext, type TaskContext } from './task-context.js';
10
- type TaskResultPayload<Result> = {
11
- action: 'complete';
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): TaskResult<Result>;
25
- static Fail(error: any, fatal?: boolean): TaskResult;
26
- static RescheduleTo(timestamp: number): TaskResult;
27
- static RescheduleBy(milliseconds: number): TaskResult;
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 TaskState: {
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 TaskState = EnumType<typeof TaskState>;
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<Data = unknown, State = unknown, Result = unknown> = {
76
- id: string;
77
- namespace: string;
78
- type: string;
79
- status: TaskState;
80
- token: string | null;
81
- /**
82
- * The lower the number, the higher the priority.
83
- * @default 1000
84
- */
85
- priority: number;
86
- idempotencyKey: string | null;
87
- traceId: string | null;
88
- tags: string[];
89
- completeAfterTags: string[];
90
- scheduleAfterTags: string[];
91
- failFast: boolean;
92
- dependencyJoinMode: DependencyJoinMode;
93
- dependencyTriggerStates: TaskState[];
94
- data: Data | null;
95
- parentId: string | null;
96
- tries: number;
97
- creationTimestamp: number;
98
- priorityAgeTimestamp: number;
99
- scheduleTimestamp: number;
100
- /**
101
- * Timestamp when the task most recently switched to Running state.
102
- * Used for Hard Execution Timeouts.
103
- */
104
- startTimestamp: number | null;
105
- /**
106
- * Timestamp after which the task is considered expired if it hasn't started Running.
107
- * If null, the task never expires in the queue.
108
- */
109
- timeToLive: number | null;
110
- /**
111
- * Token expiration (Soft Timeout).
112
- */
113
- visibilityDeadline: number | null;
114
- completeTimestamp: number | null;
115
- /** A number between 0 and 1 indicating the progress of the task. */
116
- progress: number;
117
- /** A snapshot of the current state of the task. */
118
- state: State | null;
119
- result: Result | null;
120
- error: UndefinableJson | null;
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
- dependencyTriggerStates?: TaskState[];
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<T> = EnqueueOptions & {
140
- type: string;
141
- data: T;
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<Data, State = unknown, Result = unknown> extends Transactional implements Resolvable<TaskQueueArgument> {
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<Data, State, Result>;
204
- abstract enqueue(type: string, data: Data, options?: EnqueueOneOptions): Promise<Task<Data, State, Result>>;
205
- abstract enqueueMany(items: EnqueueManyItem<Data>[], options?: EnqueueManyOptions & {
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<Data>[], options: EnqueueManyOptions & {
191
+ abstract enqueueMany<Type extends TaskTypes<Definitions>>(items: EnqueueManyItem<Definitions, Type>[], options: EnqueueManyOptions & {
209
192
  returnTasks: true;
210
- }): Promise<Task<Data, State, Result>[]>;
211
- abstract enqueueMany(items: EnqueueManyItem<Data>[], options?: EnqueueManyOptions): Promise<Task<Data, State, Result>[] | undefined>;
212
- abstract has(id: string): Promise<boolean>;
213
- abstract getTask(id: string): Promise<Task<Data, State, Result> | undefined>;
214
- abstract getManyByTags(tags: OneOrMany<string>): Promise<Task<Data, State, Result>[]>;
215
- abstract countByTags(tags: OneOrMany<string>): Promise<number>;
216
- abstract getTree(rootId: string | string[]): Promise<Task<unknown, unknown, unknown>[]>;
217
- abstract cancel(id: string): Promise<void>;
218
- abstract cancelMany(ids: string[]): Promise<void>;
219
- abstract cancelManyByTags(tags: OneOrMany<string>): Promise<void>;
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(): Promise<void>;
222
- abstract dequeue(): Promise<Task<Data, State, Result> | undefined>;
223
- abstract dequeueMany(count: number, options?: {
220
+ abstract clear(options?: {
221
+ transaction?: Transaction;
222
+ }): Promise<void>;
223
+ abstract dequeue<Type extends TaskTypes<Definitions>>(options?: {
224
224
  forceDequeue?: boolean;
225
- types?: string[];
226
- }): Promise<Task<Data, State, Result>[]>;
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, transaction?: Transaction): Promise<void>;
232
- abstract rescheduleMany(ids: string[], timestamp: number, transaction?: Transaction): Promise<void>;
233
- abstract rescheduleManyByTags(tags: OneOrMany<string>, timestamp: number, transaction?: Transaction): Promise<void>;
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: Task, options?: {
250
+ abstract touch<Type extends TaskTypes<Definitions>>(task: TaskOfType<Definitions, Type>, options?: {
239
251
  progress?: number;
240
- state?: State;
252
+ state?: TaskState<Definitions, Type>;
241
253
  transaction?: Transaction;
242
- }): Promise<Task<Data, State, Result> | undefined>;
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(tasks: Task[], progresses?: number[], states?: State[], transaction?: Transaction): Promise<string[]>;
248
- abstract complete<R>(task: Task<any, any, R>, result?: R, transaction?: Transaction): Promise<void>;
249
- abstract completeMany<R>(tasks: Task<any, any, R>[], results?: R[], transaction?: Transaction): Promise<void>;
250
- abstract fail(task: Task, error: any, fatal?: boolean, transaction?: Transaction): Promise<void>;
251
- abstract failMany(tasks: Task[], error: any, transaction?: Transaction): Promise<void>;
252
- abstract maintenance(): Promise<void>;
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?: string[];
260
- }): AsyncIterableIterator<Task<Data, State, Result>>;
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?: string[];
264
- }): AsyncIterableIterator<Task<Data, State, Result>[]>;
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?: string[];
297
+ types?: Type[];
269
298
  forceDequeue?: boolean;
270
- }, handler: ProcessWorker<Data, State, Result>): void;
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?: string[];
304
+ types?: Type[];
276
305
  forceDequeue?: boolean;
277
- }, handler: ProcessBatchWorker<Data, State, Result>): void;
306
+ }, handler: ProcessBatchWorker<Definitions, Type>): void;
278
307
  private processWorker;
279
308
  private processBatchWorker;
280
309
  }
281
- export {};
@@ -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 TaskResult {
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 TaskResult({ action: 'complete', result });
18
+ return new TaskProcessResult({ action: 'complete', result });
19
19
  }
20
20
  static Fail(error, fatal = false) {
21
- return new TaskResult({ action: 'fail', error, fatal });
21
+ return new TaskProcessResult({ action: 'fail', error, fatal });
22
22
  }
23
23
  static RescheduleTo(timestamp) {
24
- return new TaskResult({ action: 'reschedule', timestamp });
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 TaskState = defineEnum('TaskState', {
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, TaskState } from '../../task-queue/index.js';
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(TaskState.Waiting);
54
- expect(taskB.status).toBe(TaskState.Waiting);
55
- expect(taskC.status).toBe(TaskState.Waiting);
56
- expect(taskA.status).toBe(TaskState.Pending);
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, TaskState.Pending);
61
- await waitForStatus(taskC.id, TaskState.Pending);
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(TaskState.Waiting);
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, TaskState.Pending);
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(TaskState.Pending);
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, TaskState.Pending);
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, TaskState.Pending);
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, TaskState.Pending);
92
- expect((await queue.getTask(D.id))?.status).toBe(TaskState.Pending);
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(TaskState.Cancelled);
101
- expect((await queue.getTask(child.id))?.status).toBe(TaskState.Cancelled);
102
- expect((await queue.getTask(grandChild.id))?.status).toBe(TaskState.Cancelled);
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).toBeGreaterThan(now + 300);
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(TaskState.Completed);
181
- expect(before?.completeTimestamp).toBeGreaterThan(0);
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(TaskState.Completed);
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(TaskState.Dead);
198
- expect(updated?.error?.code).toBe('Expired');
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(TaskState.Completed);
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.v).toBe(3);
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
- const u = await queue.getTask(dependent.id);
281
- expect(u?.status).toBe(TaskState.Waiting); // Should still be waiting because dependency didn't Complete
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, TaskState.Pending);
291
- expect((await queue.getTask(dep.id))?.status).toBe(TaskState.Pending);
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', {});