@tstdl/base 0.93.142 → 0.93.144

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.
@@ -31,6 +31,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
31
31
  private clockOffset;
32
32
  private initialized;
33
33
  private refreshLoopPromise;
34
+ private loggingOut;
34
35
  /**
35
36
  * Observable for authentication errors.
36
37
  * Emits when a refresh fails.
@@ -74,6 +74,7 @@ let AuthenticationClientService = class AuthenticationClientService {
74
74
  clockOffset = 0;
75
75
  initialized = false;
76
76
  refreshLoopPromise;
77
+ loggingOut;
77
78
  /**
78
79
  * Observable for authentication errors.
79
80
  * Emits when a refresh fails.
@@ -233,18 +234,26 @@ let AuthenticationClientService = class AuthenticationClientService {
233
234
  * This will attempt to end the session on the server and then clear local credentials.
234
235
  */
235
236
  async logout() {
236
- try {
237
- await Promise.race([
238
- this.client.endSession(),
239
- timeout(logoutTimeout),
240
- ]).catch((error) => this.logger.error(error));
241
- }
242
- finally {
243
- // Always clear the local token, even if the server call fails.
244
- this.forceRefreshRequested.set(false);
245
- this.setNewToken(undefined);
246
- this.loggedOutBus.publishAndForget();
237
+ if (isDefined(this.loggingOut)) {
238
+ await this.loggingOut;
239
+ return;
247
240
  }
241
+ this.loggingOut = (async () => {
242
+ try {
243
+ await Promise.race([
244
+ this.client.endSession(),
245
+ timeout(logoutTimeout),
246
+ ]).catch((error) => this.logger.error(error));
247
+ }
248
+ finally {
249
+ // Always clear the local token, even if the server call fails.
250
+ this.forceRefreshRequested.set(false);
251
+ this.setNewToken(undefined);
252
+ this.loggedOutBus.publishAndForget();
253
+ this.loggingOut = undefined;
254
+ }
255
+ })();
256
+ await this.loggingOut;
248
257
  }
249
258
  /**
250
259
  * Force an immediate refresh of the token.
@@ -36,14 +36,17 @@ export function waitForAuthenticationCredentialsMiddleware(authenticationService
36
36
  */
37
37
  export function logoutOnUnauthorizedMiddleware(authenticationServiceOrProvider) {
38
38
  const getAuthenticationService = cacheValueOrAsyncProvider(authenticationServiceOrProvider);
39
- async function logoutOnUnauthorizedMiddleware(_context, next) {
39
+ async function logoutOnUnauthorizedMiddleware({ request }, next) {
40
40
  try {
41
41
  await next();
42
42
  }
43
43
  catch (error) {
44
44
  if ((error instanceof HttpError) && (error.response?.statusCode == 401)) {
45
- const authenticationService = await getAuthenticationService();
46
- await authenticationService.logout();
45
+ const endpoint = request.context?.endpoint;
46
+ if (endpoint?.data?.[dontWaitForValidToken] != true) {
47
+ const authenticationService = await getAuthenticationService();
48
+ await authenticationService.logout();
49
+ }
47
50
  }
48
51
  throw error;
49
52
  }
@@ -1,6 +1,7 @@
1
1
  import { of } from 'rxjs';
2
2
  import { describe, expect, test, vi } from 'vitest';
3
3
  import { HttpClientRequest, HttpClientResponse, HttpError, HttpErrorReason } from '../../http/index.js';
4
+ import { dontWaitForValidToken } from '../authentication.api.js';
4
5
  import { logoutOnUnauthorizedMiddleware, waitForAuthenticationCredentialsMiddleware } from '../client/http-client.middleware.js';
5
6
  describe('waitForAuthenticationCredentialsMiddleware', () => {
6
7
  test('should wait for token and call next', async () => {
@@ -41,6 +42,33 @@ describe('logoutOnUnauthorizedMiddleware', () => {
41
42
  await expect(middleware({ request }, next)).rejects.toThrow(HttpError);
42
43
  expect(authenticationServiceMock.logout).toHaveBeenCalled();
43
44
  });
45
+ test('should NOT call logout on 401 error if endpoint has dontWaitForValidToken', async () => {
46
+ const authenticationServiceMock = {
47
+ logout: vi.fn().mockResolvedValue(undefined),
48
+ };
49
+ const middleware = logoutOnUnauthorizedMiddleware(authenticationServiceMock);
50
+ const request = new HttpClientRequest('http://localhost');
51
+ request.context = {
52
+ endpoint: {
53
+ resource: 'end-session',
54
+ data: {
55
+ [dontWaitForValidToken]: true,
56
+ },
57
+ },
58
+ };
59
+ const response = new HttpClientResponse({
60
+ request,
61
+ statusCode: 401,
62
+ statusMessage: 'Unauthorized',
63
+ headers: {},
64
+ body: undefined,
65
+ closeHandler: () => { }
66
+ });
67
+ const error = new HttpError(HttpErrorReason.StatusCode, request, { response });
68
+ const next = vi.fn().mockRejectedValue(error);
69
+ await expect(middleware({ request }, next)).rejects.toThrow(HttpError);
70
+ expect(authenticationServiceMock.logout).not.toHaveBeenCalled();
71
+ });
44
72
  test('should not call logout on other errors', async () => {
45
73
  const authenticationServiceMock = {
46
74
  logout: vi.fn().mockResolvedValue(undefined),
@@ -1,19 +1,20 @@
1
- import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest';
2
1
  import { Subject } from 'rxjs';
2
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
3
3
  import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
4
4
  import { AUTHENTICATION_API_CLIENT } from '../../authentication/client/tokens.js';
5
+ import { CancellationSignal, CancellationToken } from '../../cancellation/token.js';
6
+ import { Injector } from '../../injector/index.js';
5
7
  import { Lock } from '../../lock/index.js';
6
8
  import { Logger } from '../../logger/index.js';
7
9
  import { MessageBus } from '../../message-bus/index.js';
8
- import { Injector } from '../../injector/index.js';
9
- import { CancellationSignal, CancellationToken } from '../../cancellation/token.js';
10
10
  import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
11
11
  describe('AuthenticationClientService Methods', () => {
12
12
  let injector;
13
13
  let service;
14
14
  let mockApiClient;
15
15
  let mockLock;
16
- let mockMessageBus;
16
+ let mockTokenUpdateBus;
17
+ let mockLoggedOutBus;
17
18
  let mockLogger;
18
19
  beforeEach(() => {
19
20
  const storage = new Map();
@@ -46,9 +47,16 @@ describe('AuthenticationClientService Methods', () => {
46
47
  return await callback({ lost: false });
47
48
  }),
48
49
  };
49
- mockMessageBus = {
50
+ mockTokenUpdateBus = {
50
51
  publishAndForget: vi.fn(),
51
52
  messages$: new Subject(),
53
+ allMessages$: new Subject(),
54
+ dispose: vi.fn(),
55
+ };
56
+ mockLoggedOutBus = {
57
+ publishAndForget: vi.fn(),
58
+ messages$: new Subject(),
59
+ allMessages$: new Subject(),
52
60
  dispose: vi.fn(),
53
61
  };
54
62
  mockLogger = {
@@ -59,7 +67,15 @@ describe('AuthenticationClientService Methods', () => {
59
67
  };
60
68
  injector.register(AUTHENTICATION_API_CLIENT, { useValue: mockApiClient });
61
69
  injector.register(Lock, { useValue: mockLock });
62
- injector.register(MessageBus, { useValue: mockMessageBus });
70
+ injector.register(MessageBus, {
71
+ useFactory: (argument) => {
72
+ if (argument === 'AuthenticationService:tokenUpdate')
73
+ return mockTokenUpdateBus;
74
+ if (argument === 'AuthenticationService:loggedOut')
75
+ return mockLoggedOutBus;
76
+ return undefined;
77
+ },
78
+ });
63
79
  injector.register(Logger, { useValue: mockLogger });
64
80
  const disposeToken = new CancellationToken();
65
81
  injector.register(CancellationSignal, { useValue: disposeToken.signal });
@@ -122,6 +138,22 @@ describe('AuthenticationClientService Methods', () => {
122
138
  mockApiClient.unimpersonate.mockRejectedValue(new Error('Unimpersonation failed'));
123
139
  await expect(service.unimpersonate()).rejects.toThrow('Unimpersonation failed');
124
140
  });
141
+ test('logout should handle concurrent calls and avoid multiple api requests', async () => {
142
+ let resolveEndSession;
143
+ const endSessionPromise = new Promise((resolve) => {
144
+ resolveEndSession = resolve;
145
+ });
146
+ mockApiClient.endSession.mockReturnValue(endSessionPromise);
147
+ const logout1 = service.logout();
148
+ const logout2 = service.logout();
149
+ // logout1 and logout2 will be different promises because the method is async
150
+ expect(mockApiClient.endSession).toHaveBeenCalledTimes(1);
151
+ resolveEndSession(undefined);
152
+ await Promise.all([logout1, logout2]);
153
+ expect(service.isLoggedIn()).toBe(false);
154
+ expect(mockTokenUpdateBus.publishAndForget).toHaveBeenCalledWith(undefined);
155
+ expect(mockLoggedOutBus.publishAndForget).toHaveBeenCalled();
156
+ });
125
157
  test('syncClock should handle errors gracefully', async () => {
126
158
  mockApiClient.timestamp.mockRejectedValue(new Error('Time sync failed'));
127
159
  await service.syncClock();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.142",
3
+ "version": "0.93.144",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -152,8 +152,8 @@
152
152
  "type-fest": "^5.4"
153
153
  },
154
154
  "peerDependencies": {
155
- "@aws-sdk/client-s3": "^3.997",
156
- "@aws-sdk/s3-request-presigner": "^3.997",
155
+ "@aws-sdk/client-s3": "^3.999",
156
+ "@aws-sdk/s3-request-presigner": "^3.999",
157
157
  "@genkit-ai/google-genai": "^1.29",
158
158
  "@google-cloud/storage": "^7.19",
159
159
  "@toon-format/toon": "^2.1.0",
@@ -9,7 +9,7 @@ CREATE TABLE "task_queue"."task" (
9
9
  "trace_id" text,
10
10
  "parent_id" uuid,
11
11
  "tags" text[] NOT NULL,
12
- "fail_fast" boolean NOT NULL,
12
+ "abort_on_dependency_failure" boolean NOT NULL,
13
13
  "priority" integer NOT NULL,
14
14
  "unresolved_schedule_dependencies" integer NOT NULL,
15
15
  "unresolved_complete_dependencies" integer NOT NULL,
@@ -39,7 +39,7 @@ CREATE TABLE "task_queue"."task_archive" (
39
39
  "trace_id" text,
40
40
  "parent_id" uuid,
41
41
  "tags" text[] NOT NULL,
42
- "fail_fast" boolean NOT NULL,
42
+ "abort_on_dependency_failure" boolean NOT NULL,
43
43
  "priority" integer NOT NULL,
44
44
  "unresolved_schedule_dependencies" integer NOT NULL,
45
45
  "unresolved_complete_dependencies" integer NOT NULL,
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "aaf1f877-e250-4660-bd3d-9ba1bfacd10c",
2
+ "id": "f8a5fa74-ffdf-4135-a006-8c8f958ddefa",
3
3
  "prevId": "00000000-0000-0000-0000-000000000000",
4
4
  "version": "7",
5
5
  "dialect": "postgresql",
@@ -58,8 +58,8 @@
58
58
  "primaryKey": false,
59
59
  "notNull": true
60
60
  },
61
- "fail_fast": {
62
- "name": "fail_fast",
61
+ "abort_on_dependency_failure": {
62
+ "name": "abort_on_dependency_failure",
63
63
  "type": "boolean",
64
64
  "primaryKey": false,
65
65
  "notNull": true
@@ -463,8 +463,8 @@
463
463
  "primaryKey": false,
464
464
  "notNull": true
465
465
  },
466
- "fail_fast": {
467
- "name": "fail_fast",
466
+ "abort_on_dependency_failure": {
467
+ "name": "abort_on_dependency_failure",
468
468
  "type": "boolean",
469
469
  "primaryKey": false,
470
470
  "notNull": true
@@ -5,8 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1772048658458,
9
- "tag": "0000_great_gwen_stacy",
8
+ "when": 1772055508571,
9
+ "tag": "0000_faithful_daimon_hellstrom",
10
10
  "breakpoints": true
11
11
  }
12
12
  ]
@@ -71,12 +71,12 @@ import { distinct, toArray } from '../../utils/array/array.js';
71
71
  import { currentTimestamp } from '../../utils/date-time.js';
72
72
  import { Timer } from '../../utils/timer.js';
73
73
  import { cancelableTimeout } from '../../utils/timing.js';
74
- import { isDefined, isNotNull, isNull, isNumber, isString, isUndefined } from '../../utils/type-guards.js';
74
+ import { isArray, isDefined, isNotNull, isNull, isNumber, isString, isUndefined } from '../../utils/type-guards.js';
75
75
  import { millisecondsPerMinute, millisecondsPerSecond } from '../../utils/units.js';
76
- import { defaultQueueConfig, TaskDependencyType, TaskQueue, TaskStatus } from '../task-queue.js';
76
+ import { defaultQueueConfig, queueableOrWaitableStatuses, queueableStatuses, TaskDependencyType, TaskQueue, TaskStatus, terminalStatuses } from '../task-queue.js';
77
77
  import { PostgresTaskQueueModuleConfig } from './module.js';
78
78
  import { taskArchive as taskArchiveTable, taskDependency as taskDependencyTable, taskDependencyType, taskStatus, task as taskTable } from './schemas.js';
79
- import { finalizedStatuses, PostgresTask, PostgresTaskArchive, queueableOrWaitableStatuses, queueableStatuses, terminalStatuses } from './task.model.js';
79
+ import { PostgresTask, PostgresTaskArchive } from './task.model.js';
80
80
  let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
81
81
  #database = inject(Database);
82
82
  #repository = injectRepository(PostgresTask);
@@ -122,7 +122,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
122
122
  tags: sql `excluded.tags`,
123
123
  unresolvedScheduleDependencies: taskTable.unresolvedScheduleDependencies,
124
124
  unresolvedCompleteDependencies: taskTable.unresolvedCompleteDependencies,
125
- failFast: sql `excluded.fail_fast`,
125
+ abortOnDependencyFailure: sql `excluded.abort_on_dependency_failure`,
126
126
  tries: 0,
127
127
  creationTimestamp: TRANSACTION_TIMESTAMP,
128
128
  priorityAgeTimestamp: TRANSACTION_TIMESTAMP,
@@ -157,41 +157,39 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
157
157
  scheduleAfter: isDefined(item.scheduleAfter) ? Array.from(new Map(item.scheduleAfter.map((s) => [isNumber(s) || isString(s) ? s : JSON.stringify(s), s])).values()) : undefined,
158
158
  completeAfter: isDefined(item.completeAfter) ? Array.from(new Map(item.completeAfter.map((s) => [isNumber(s) || isString(s) ? s : JSON.stringify(s), s])).values()) : undefined,
159
159
  }));
160
- const entitiesWithIndex = itemsWithDistinctDependencies.map((item, index) => {
161
- return {
162
- index,
163
- entity: {
164
- namespace: this.#namespace,
165
- type: item.type,
166
- status: TaskStatus.Pending,
167
- token: null,
168
- priority: item.priority ?? 1000,
169
- idempotencyKey: item.idempotencyKey ?? null,
170
- traceId: null,
171
- tags: item.tags ?? [],
172
- unresolvedScheduleDependencies: 0,
173
- unresolvedCompleteDependencies: 0,
174
- failFast: item.failFast ?? false,
175
- parentId: item.parentId ?? null,
176
- tries: 0,
177
- progress: 0,
178
- creationTimestamp: TRANSACTION_TIMESTAMP,
179
- priorityAgeTimestamp: TRANSACTION_TIMESTAMP,
180
- scheduleTimestamp: item.scheduleTimestamp ?? TRANSACTION_TIMESTAMP,
181
- startTimestamp: null,
182
- timeToLive: item.timeToLive ?? sql `${TRANSACTION_TIMESTAMP} + ${interval(this.defaultTimeToLive, 'milliseconds')}`,
183
- visibilityDeadline: null,
184
- completeTimestamp: null,
185
- data: item.data,
186
- state: null,
187
- result: null,
188
- error: null,
189
- },
190
- };
191
- });
160
+ const entitiesWithIndex = itemsWithDistinctDependencies.map((item, index) => ({
161
+ index,
162
+ entity: {
163
+ namespace: this.#namespace,
164
+ type: item.type,
165
+ status: TaskStatus.Pending,
166
+ token: null,
167
+ priority: item.priority ?? 1000,
168
+ idempotencyKey: item.idempotencyKey ?? null,
169
+ traceId: null,
170
+ tags: item.tags ?? [],
171
+ unresolvedScheduleDependencies: 0,
172
+ unresolvedCompleteDependencies: 0,
173
+ abortOnDependencyFailure: item.abortOnDependencyFailure ?? false,
174
+ parentId: item.parentId ?? null,
175
+ tries: 0,
176
+ progress: 0,
177
+ creationTimestamp: TRANSACTION_TIMESTAMP,
178
+ priorityAgeTimestamp: TRANSACTION_TIMESTAMP,
179
+ scheduleTimestamp: item.scheduleTimestamp ?? TRANSACTION_TIMESTAMP,
180
+ startTimestamp: null,
181
+ timeToLive: item.timeToLive ?? sql `${TRANSACTION_TIMESTAMP} + ${interval(this.defaultTimeToLive, 'milliseconds')}`,
182
+ visibilityDeadline: null,
183
+ completeTimestamp: null,
184
+ data: item.data,
185
+ state: null,
186
+ result: null,
187
+ error: null,
188
+ },
189
+ }));
192
190
  const itemsWithIdempotency = entitiesWithIndex.filter((e) => isNotNull(e.entity.idempotencyKey));
193
191
  const itemsWithoutIdempotency = entitiesWithIndex.filter((e) => isNull(e.entity.idempotencyKey));
194
- const hasDependencies = itemsWithDistinctDependencies.some((item) => ((item.scheduleAfter?.length ?? 0) > 0) || ((item.completeAfter?.length ?? 0) > 0) || (isDefined(item.parentId) && (item.waitForCompletion ?? true)));
192
+ const hasDependencies = itemsWithDistinctDependencies.some((item) => ((item.scheduleAfter?.length ?? 0) > 0) || ((item.completeAfter?.length ?? 0) > 0) || (isDefined(item.parentId) && (item.parentRequires != false) && !(isArray(item.parentRequires) && (item.parentRequires.length == 0))));
195
193
  const mustUseTransaction = (entitiesWithIndex.length > 1) || hasDependencies;
196
194
  const newTransaction = __addDisposableResource(env_1, (mustUseTransaction && isUndefined(options?.transaction)) ? await this.#repository.startTransaction() : undefined, true);
197
195
  const transaction = newTransaction ?? options?.transaction;
@@ -260,12 +258,14 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
260
258
  if (!processedTaskIds.has(task.id)) {
261
259
  continue;
262
260
  }
263
- if (isDefined(item.parentId) && (item.waitForCompletion ?? true)) {
261
+ if (isDefined(item.parentId) && (item.parentRequires != false) && !(isArray(item.parentRequires) && (item.parentRequires.length == 0))) {
264
262
  dependencies.push({
265
263
  taskId: item.parentId,
266
264
  dependencyTaskId: task.id,
267
265
  type: TaskDependencyType.Child,
268
- requiredStatuses: [TaskStatus.Completed],
266
+ requiredStatuses: isArray(item.parentRequires)
267
+ ? item.parentRequires
268
+ : [TaskStatus.Completed],
269
269
  });
270
270
  }
271
271
  if (isDefined(item.scheduleAfter)) {
@@ -335,7 +335,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
335
335
  .from(taskArchiveTable)
336
336
  .where(inArray(taskArchiveTable.id, distinctDependencyIds)));
337
337
  if (dependencyStatuses.length > 0) {
338
- await this.resolveDependenciesMany(dependencyStatuses.map((s) => ({ id: s.id, status: s.status })), { transaction: transaction });
338
+ await this.resolveDependenciesMany(dependencyStatuses.map((s) => ({ id: s.id, status: s.status })), { transaction });
339
339
  }
340
340
  }
341
341
  }
@@ -506,7 +506,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
506
506
  const timeout = options?.timeout ?? Infinity;
507
507
  const interval = options?.interval ?? 1000;
508
508
  const cancellationSignal = this.#cancellationSignal.optionallyInherit(options?.cancellationSignal);
509
- const waitStatuses = options?.statuses ?? finalizedStatuses;
509
+ const waitStatuses = options?.statuses ?? terminalStatuses;
510
510
  const messageBus$ = this.#messageBus.allMessages$.pipe(filter((namespace) => namespace == this.#namespace), throttleTime(500, undefined, { leading: true, trailing: true }));
511
511
  const continue$ = merge(messageBus$, cancellationSignal);
512
512
  const timer = Timer.startNew();
@@ -566,7 +566,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
566
566
  const nonFinalizedTasks = await tx.pgTransaction
567
567
  .select({ id: taskTable.id, namespace: taskTable.namespace })
568
568
  .from(taskTable)
569
- .where(and(eq(taskTable.namespace, this.#namespace), notInArray(taskTable.status, finalizedStatuses)))
569
+ .where(and(eq(taskTable.namespace, this.#namespace), notInArray(taskTable.status, terminalStatuses)))
570
570
  .for('update');
571
571
  if (nonFinalizedTasks.length > 0) {
572
572
  const ids = nonFinalizedTasks.map((t) => t.id);
@@ -932,7 +932,6 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
932
932
  await this.resolveDependenciesMany([{ id, status, namespace: options?.namespace }], options);
933
933
  }
934
934
  async resolveDependenciesMany(tasks, options) {
935
- this.#logger.debug(`Resolving dependencies for ${tasks.length} tasks`);
936
935
  if (tasks.length == 0) {
937
936
  return;
938
937
  }
@@ -953,7 +952,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
953
952
  return;
954
953
  }
955
954
  const resolvedEdges = [];
956
- const failFastTaskIds = new Set();
955
+ const abortOnDependencyFailureTaskIds = new Set();
957
956
  for (const dep of dependents) {
958
957
  const status = taskStatusMap.get(dep.dependencyTaskId);
959
958
  const isMatched = dep.requiredStatuses.includes(status);
@@ -961,7 +960,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
961
960
  if (isMatched || isTerminal) {
962
961
  resolvedEdges.push(dep);
963
962
  if (!isMatched) {
964
- failFastTaskIds.add(dep.taskId);
963
+ abortOnDependencyFailureTaskIds.add(dep.taskId);
965
964
  }
966
965
  }
967
966
  }
@@ -989,17 +988,17 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
989
988
  `);
990
989
  const terminalTasks = [];
991
990
  const skippedTaskIds = new Set();
992
- if (failFastTaskIds.size > 0) {
993
- const sortedFailFastIds = [...failFastTaskIds].toSorted();
991
+ if (abortOnDependencyFailureTaskIds.size > 0) {
992
+ const sortedAbortIds = [...abortOnDependencyFailureTaskIds].toSorted();
994
993
  const dependentTasks = await tx.pgTransaction
995
- .select({ id: taskTable.id, namespace: taskTable.namespace, failFast: taskTable.failFast, status: taskTable.status })
994
+ .select({ id: taskTable.id, namespace: taskTable.namespace, abortOnDependencyFailure: taskTable.abortOnDependencyFailure, status: taskTable.status })
996
995
  .from(taskTable)
997
- .where(inArray(taskTable.id, sortedFailFastIds))
996
+ .where(inArray(taskTable.id, sortedAbortIds))
998
997
  .orderBy(asc(taskTable.id))
999
998
  .for('update');
1000
999
  const tasksToSkip = [];
1001
1000
  for (const task of dependentTasks) {
1002
- if (task.failFast && !terminalStatuses.includes(task.status)) {
1001
+ if (task.abortOnDependencyFailure && !terminalStatuses.includes(task.status)) {
1003
1002
  tasksToSkip.push(task.id);
1004
1003
  skippedTaskIds.add(task.id);
1005
1004
  }
@@ -1010,7 +1009,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1010
1009
  .set({
1011
1010
  status: TaskStatus.Skipped,
1012
1011
  token: null,
1013
- error: jsonbBuildObject({ code: 'DependencyFailed', message: 'One or more dependencies failed and failFast is enabled' }),
1012
+ error: jsonbBuildObject({ code: 'DependencyFailed', message: 'One or more dependencies failed and abortOnDependencyFailure is enabled' }),
1014
1013
  completeTimestamp: TRANSACTION_TIMESTAMP,
1015
1014
  })
1016
1015
  .where(inArray(taskTable.id, tasksToSkip))
@@ -1066,7 +1065,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1066
1065
  status: taskTable.status,
1067
1066
  });
1068
1067
  for (const row of updatedRows) {
1069
- if (finalizedStatuses.includes(row.status)) {
1068
+ if (terminalStatuses.includes(row.status)) {
1070
1069
  terminalTasks.push({ id: row.id, status: row.status, namespace: row.namespace });
1071
1070
  }
1072
1071
  notifiedNamespaces.add(row.namespace);
@@ -1102,7 +1101,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1102
1101
  const rowsToArchive = await tx.pgTransaction
1103
1102
  .select()
1104
1103
  .from(taskTable)
1105
- .where(and(eq(taskTable.namespace, this.#namespace), inArray(taskTable.status, finalizedStatuses), lte(taskTable.completeTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.retention, 'milliseconds')}`), notExists(tx.pgTransaction
1104
+ .where(and(eq(taskTable.namespace, this.#namespace), inArray(taskTable.status, terminalStatuses), lte(taskTable.completeTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.retention, 'milliseconds')}`), notExists(tx.pgTransaction
1106
1105
  .select({ id: childTaskTable.id })
1107
1106
  .from(childTaskTable)
1108
1107
  .where(eq(childTaskTable.parentId, taskTable.id))), notExists(tx.pgTransaction
@@ -1300,7 +1299,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1300
1299
  priorityAgeTimestamp: TRANSACTION_TIMESTAMP,
1301
1300
  state: (options?.resetState == true) ? null : undefined,
1302
1301
  })
1303
- .where(and(eq(taskTable.id, id), or(inArray(taskTable.status, queueableStatuses), inArray(taskTable.status, finalizedStatuses), lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP))));
1302
+ .where(and(eq(taskTable.id, id), or(inArray(taskTable.status, queueableStatuses), inArray(taskTable.status, terminalStatuses), lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP))));
1304
1303
  }
1305
1304
  notify(namespace = this.#namespace) {
1306
1305
  this.#messageBus.publishAndForget(namespace);
@@ -2,12 +2,6 @@
2
2
  import { BaseEntity, type Json, type Timestamp } from '../../orm/index.js';
3
3
  import type { ObjectLiteral } from '../../types/types.js';
4
4
  import { type Task, TaskDependencyType, TaskStatus } from '../task-queue.js';
5
- export declare const terminalStatuses: TaskStatus[];
6
- export declare const finalizedStatuses: TaskStatus[];
7
- export declare const nonFinalizedStatuses: TaskStatus[];
8
- export declare const queueableStatuses: TaskStatus[];
9
- export declare const waitableStatuses: TaskStatus[];
10
- export declare const queueableOrWaitableStatuses: TaskStatus[];
11
5
  export declare abstract class PostgresTaskBase<Data extends ObjectLiteral = ObjectLiteral, State extends ObjectLiteral = ObjectLiteral, Result extends ObjectLiteral = ObjectLiteral> extends BaseEntity implements Task {
12
6
  namespace: string;
13
7
  type: string;
@@ -16,7 +10,7 @@ export declare abstract class PostgresTaskBase<Data extends ObjectLiteral = Obje
16
10
  traceId: string | null;
17
11
  parentId: string | null;
18
12
  tags: string[];
19
- failFast: boolean;
13
+ abortOnDependencyFailure: boolean;
20
14
  priority: number;
21
15
  unresolvedScheduleDependencies: number;
22
16
  unresolvedCompleteDependencies: number;
@@ -11,13 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  import { BaseEntity, ForeignKey, Index, JsonProperty, Table, TimestampProperty, Unique, UuidProperty } from '../../orm/index.js';
12
12
  import { Array as ArrayProperty, BooleanProperty, Enumeration, Integer, NumberProperty, StringProperty } from '../../schema/index.js';
13
13
  import { isNotNull } from 'drizzle-orm';
14
- import { TaskDependencyType, TaskStatus } from '../task-queue.js';
15
- export const terminalStatuses = [TaskStatus.Completed, TaskStatus.Cancelled, TaskStatus.Dead, TaskStatus.TimedOut, TaskStatus.Expired, TaskStatus.Skipped, TaskStatus.Orphaned];
16
- export const finalizedStatuses = terminalStatuses;
17
- export const nonFinalizedStatuses = [TaskStatus.Pending, TaskStatus.Retrying, TaskStatus.Waiting, TaskStatus.WaitingChildren, TaskStatus.Running];
18
- export const queueableStatuses = [TaskStatus.Pending, TaskStatus.Retrying];
19
- export const waitableStatuses = [TaskStatus.Waiting, TaskStatus.WaitingChildren];
20
- export const queueableOrWaitableStatuses = [...queueableStatuses, ...waitableStatuses];
14
+ import { queueableOrWaitableStatuses, queueableStatuses, TaskDependencyType, TaskStatus, terminalStatuses } from '../task-queue.js';
21
15
  export class PostgresTaskBase extends BaseEntity {
22
16
  namespace;
23
17
  type;
@@ -26,7 +20,7 @@ export class PostgresTaskBase extends BaseEntity {
26
20
  traceId;
27
21
  parentId;
28
22
  tags;
29
- failFast;
23
+ abortOnDependencyFailure;
30
24
  priority;
31
25
  unresolvedScheduleDependencies;
32
26
  unresolvedCompleteDependencies;
@@ -76,7 +70,7 @@ __decorate([
76
70
  __decorate([
77
71
  BooleanProperty(),
78
72
  __metadata("design:type", Boolean)
79
- ], PostgresTaskBase.prototype, "failFast", void 0);
73
+ ], PostgresTaskBase.prototype, "abortOnDependencyFailure", void 0);
80
74
  __decorate([
81
75
  Integer(),
82
76
  __metadata("design:type", Number)
@@ -95,10 +95,20 @@ export declare const TaskStatus: {
95
95
  */
96
96
  readonly Orphaned: "orphaned";
97
97
  };
98
+ export declare const terminalStatuses: ["completed", "cancelled", "dead", "timed-out", "expired", "skipped", "orphaned"];
99
+ export declare const nonTerminalStatuses: ["pending", "retrying", "waiting", "waiting-children", "running"];
100
+ export declare const queueableStatuses: ["pending", "retrying"];
101
+ export declare const waitableStatuses: ["waiting", "waiting-children"];
102
+ export declare const queueableOrWaitableStatuses: ["pending", "retrying", "waiting", "waiting-children"];
98
103
  /**
99
104
  * Type of task status.
100
105
  */
101
106
  export type TaskStatus = EnumType<typeof TaskStatus>;
107
+ export type TerminalTaskStatus = typeof terminalStatuses[number];
108
+ export type NonTerminalTaskStatus = typeof nonTerminalStatuses[number];
109
+ export type QueueableTaskStatus = typeof queueableStatuses[number];
110
+ export type WaitableTaskStatus = typeof waitableStatuses[number];
111
+ export type QueueableOrWaitableTaskStatus = typeof queueableOrWaitableStatuses[number];
102
112
  /**
103
113
  * Represents the type of dependency between tasks.
104
114
  */
@@ -158,8 +168,8 @@ export type Task<Definitions extends TaskDefinitionMap = Record<string, {
158
168
  unresolvedScheduleDependencies: number;
159
169
  /** The number of unresolved completion dependencies. */
160
170
  unresolvedCompleteDependencies: number;
161
- /** Whether to skip the task if any of its dependencies fail. */
162
- failFast: boolean;
171
+ /** Whether to skip the task if any of its dependencies fail (dependency finalized with a status not in `parentRequires`). */
172
+ abortOnDependencyFailure: boolean;
163
173
  /** The data associated with the task. */
164
174
  data: TaskData<Definitions, Type>;
165
175
  /** The ID of the parent task, if any. */
@@ -240,9 +250,9 @@ export type EnqueueOptions = {
240
250
  */
241
251
  completeAfter?: TaskDependencySpecification[];
242
252
  /** Whether to skip the task if any of its dependencies fail. */
243
- failFast?: boolean;
244
- /** Whether the parent task should wait for this task to complete. */
245
- waitForCompletion?: boolean;
253
+ abortOnDependencyFailure?: boolean;
254
+ /** The statuses the parent task should wait for this task to reach. */
255
+ parentRequires?: boolean | TerminalTaskStatus[];
246
256
  /** The timestamp when the task should be processed. */
247
257
  scheduleTimestamp?: number;
248
258
  /** The duration (ms) before the task is considered expired. */
@@ -157,6 +157,11 @@ export const TaskStatus = defineEnum('TaskStatus', {
157
157
  */
158
158
  Orphaned: 'orphaned',
159
159
  });
160
+ export const terminalStatuses = [TaskStatus.Completed, TaskStatus.Cancelled, TaskStatus.Dead, TaskStatus.TimedOut, TaskStatus.Expired, TaskStatus.Skipped, TaskStatus.Orphaned];
161
+ export const nonTerminalStatuses = [TaskStatus.Pending, TaskStatus.Retrying, TaskStatus.Waiting, TaskStatus.WaitingChildren, TaskStatus.Running];
162
+ export const queueableStatuses = [TaskStatus.Pending, TaskStatus.Retrying];
163
+ export const waitableStatuses = [TaskStatus.Waiting, TaskStatus.WaitingChildren];
164
+ export const queueableOrWaitableStatuses = [...queueableStatuses, ...waitableStatuses];
160
165
  /**
161
166
  * Represents the type of dependency between tasks.
162
167
  */
@@ -305,9 +305,9 @@ describe('Task Queue Branch Coverage Enhancement', () => {
305
305
  const updated = await pruneQueue.getTask(task.id);
306
306
  expect(updated?.status).toBe(TaskStatus.TimedOut);
307
307
  });
308
- it('should handle enqueueMany with parentId and waitForCompletion false', async () => {
308
+ it('should handle enqueueMany with parentId and parentRequires false', async () => {
309
309
  const parent = await queue.enqueue('p', {});
310
- const tasks = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, waitForCompletion: false }], { returnTasks: true });
310
+ const tasks = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, parentRequires: false }], { returnTasks: true });
311
311
  expect(tasks).toHaveLength(1);
312
312
  expect(tasks[0]?.parentId).toBe(parent.id);
313
313
  const updatedParent = await queue.getTask(parent.id);
@@ -64,14 +64,14 @@ describe('Extensive Task Queue Dependency Tests', () => {
64
64
  await completeTask('E');
65
65
  await queue.waitForTasks([taskF.id], { statuses: [TaskStatus.Pending] });
66
66
  });
67
- it('should respect failFast = false (continue other branches)', async () => {
67
+ it('should respect abortOnDependencyFailure = false (continue other branches)', async () => {
68
68
  const taskA = await queue.enqueue('A', {});
69
- const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id], failFast: false });
69
+ const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id], abortOnDependencyFailure: false });
70
70
  const taskC = await queue.enqueue('C', {}); // Independent
71
71
  const dA = await queue.dequeue({ types: ['A'] });
72
72
  await queue.fail(dA, new Error('fatal'), { fatal: true });
73
73
  // taskB should stay Waiting (default requiredStatus is Completed)
74
- // If failFast is false, it should transition to Pending once the dependency is terminal, even if it failed.
74
+ // If abortOnDependencyFailure is false, it should transition to Pending once the dependency is terminal, even if it failed.
75
75
  await timeout(100);
76
76
  const uB = await queue.getTask(taskB.id);
77
77
  expect(uB?.status).toBe(TaskStatus.Pending);
@@ -163,12 +163,12 @@ describe('Extensive Task Queue Dependency Tests', () => {
163
163
  await completeTask('D2');
164
164
  await queue.waitForTasks([taskE.id], { statuses: [TaskStatus.Pending] });
165
165
  });
166
- it('should handle fail-fast cascade', async () => {
166
+ it('should handle abort cascade', async () => {
167
167
  const taskA = await queue.enqueue('A', {});
168
- const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id], failFast: true });
169
- const taskC = await queue.enqueue('C', {}, { scheduleAfter: [taskB.id], failFast: true });
170
- const taskD = await queue.enqueue('D', {}, { scheduleAfter: [taskA.id], failFast: true });
171
- const taskE = await queue.enqueue('E', {}, { scheduleAfter: [taskC.id, taskD.id], failFast: true });
168
+ const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id], abortOnDependencyFailure: true });
169
+ const taskC = await queue.enqueue('C', {}, { scheduleAfter: [taskB.id], abortOnDependencyFailure: true });
170
+ const taskD = await queue.enqueue('D', {}, { scheduleAfter: [taskA.id], abortOnDependencyFailure: true });
171
+ const taskE = await queue.enqueue('E', {}, { scheduleAfter: [taskC.id, taskD.id], abortOnDependencyFailure: true });
172
172
  const dA = await queue.dequeue({ types: ['A'] });
173
173
  await queue.fail(dA, new Error('fatal'), { fatal: true });
174
174
  await queue.waitForTasks([taskB.id, taskC.id, taskD.id, taskE.id], { statuses: [TaskStatus.Skipped] });
@@ -61,11 +61,11 @@ describe('Queue Dependencies & Tree Tests', () => {
61
61
  const d2 = await queue.dequeue({ types: ['dependent'] });
62
62
  expect(d2?.id).toBe(dependent.id);
63
63
  });
64
- it('should fail-fast if dependency fails', async () => {
64
+ it('should abort if dependency fails', async () => {
65
65
  const prereq = await queue.enqueue('prereq', {});
66
66
  const dependent = await queue.enqueue('dependent', {}, {
67
67
  scheduleAfter: [prereq.id],
68
- failFast: true
68
+ abortOnDependencyFailure: true,
69
69
  });
70
70
  const dequeued = await queue.dequeue({ types: ['prereq'] });
71
71
  // Fail fatally
@@ -75,10 +75,10 @@ describe('Queue Dependencies & Tree Tests', () => {
75
75
  expect(updatedDependent?.status).toBe(TaskStatus.Skipped);
76
76
  expect(updatedDependent?.error?.code).toBe('DependencyFailed');
77
77
  });
78
- it('should NOT overwrite terminal states during cancellation (failFast + complete)', async () => {
78
+ it('should NOT overwrite terminal states during cancellation (abortOnDependencyFailure + complete)', async () => {
79
79
  const dep = await queue.enqueue('dep', {});
80
80
  // completeAfter allows 'main' to be Running while 'dep' is not finished
81
- const main = await queue.enqueue('main', {}, { completeAfter: [dep.id], failFast: true });
81
+ const main = await queue.enqueue('main', {}, { completeAfter: [dep.id], abortOnDependencyFailure: true });
82
82
  const runningDep = await queue.dequeue();
83
83
  expect(runningDep?.id).toBe(dep.id);
84
84
  const runningMain = await queue.dequeue();
@@ -110,13 +110,13 @@ describe('Queue Dependencies & Tree Tests', () => {
110
110
  await queueA.clear();
111
111
  await queueB.clear();
112
112
  });
113
- it('should correctly skip dependent tasks across namespaces when failFast is triggered', async () => {
113
+ it('should correctly skip dependent tasks across namespaces when abortOnDependencyFailure is triggered', async () => {
114
114
  const queueProvider = injector.resolve(TaskQueueProvider);
115
115
  const queueA = queueProvider.get(`QueueA-${crypto.randomUUID()}`);
116
116
  const queueB = queueProvider.get(`QueueB-${crypto.randomUUID()}`);
117
117
  const taskA = await queueA.enqueue('test', { value: 'A' });
118
118
  const taskB = await queueB.enqueue('test', { value: 'B' }, {
119
- failFast: true,
119
+ abortOnDependencyFailure: true,
120
120
  scheduleAfter: [{ id: taskA.id }],
121
121
  });
122
122
  const dequeuedA = await queueA.dequeue();
@@ -132,7 +132,7 @@ describe('Queue Dependencies & Tree Tests', () => {
132
132
  const prereq = await queue.enqueue('prereq', {});
133
133
  // Wait for prereq to be Dead or Completed
134
134
  const dependent = await queue.enqueue('dependent', {}, {
135
- scheduleAfter: [{ id: prereq.id, requiredStatuses: [TaskStatus.Dead, TaskStatus.Completed] }]
135
+ scheduleAfter: [{ id: prereq.id, requiredStatuses: [TaskStatus.Dead, TaskStatus.Completed] }],
136
136
  });
137
137
  // Fail fatally -> Dead
138
138
  const d1 = await queue.dequeue({ types: ['prereq'] });
@@ -234,7 +234,7 @@ describe('Queue Dependencies & Tree Tests', () => {
234
234
  });
235
235
  it('should NOT resolve completed tasks as Cancelled when calling cancelMany (Issue 3)', async () => {
236
236
  const taskA = await queue.enqueue('test', { name: 'A' });
237
- const taskB = await queue.enqueue('test', { name: 'B' }, { scheduleAfter: [taskA.id], failFast: true });
237
+ const taskB = await queue.enqueue('test', { name: 'B' }, { scheduleAfter: [taskA.id], abortOnDependencyFailure: true });
238
238
  const dequeuedA = await queue.dequeue();
239
239
  await queue.complete(dequeuedA);
240
240
  // B should have unresolvedScheduleDependencies = 0 and be Pending because A completed
@@ -243,7 +243,7 @@ describe('Queue Dependencies & Tree Tests', () => {
243
243
  expect(freshB?.status).toBe(TaskStatus.Pending);
244
244
  // Cancel A. Since A is Completed, it shouldn't be touched in DB.
245
245
  // Importantly, it shouldn't trigger dependency resolution for B as 'Cancelled'.
246
- // In the old code, this would have caused B to be transitioned to 'Skipped' because A was resolved as 'Cancelled' but B required 'Completed' (and failFast is true).
246
+ // In the old code, this would have caused B to be transitioned to 'Skipped' because A was resolved as 'Cancelled' but B required 'Completed' (and abortOnDependencyFailure is true).
247
247
  await queue.cancelMany([taskA.id]);
248
248
  const stillFreshA = await queue.getTask(taskA.id);
249
249
  expect(stillFreshA?.status).toBe(TaskStatus.Completed);
@@ -280,7 +280,7 @@ describe('Queue Dependencies & Tree Tests', () => {
280
280
  await queue.enqueueMany([
281
281
  { type: 'b', data: { val: 1 } },
282
282
  { type: 'b', data: { val: 2 } },
283
- { type: 'b', data: { val: 3 } }
283
+ { type: 'b', data: { val: 3 } },
284
284
  ]);
285
285
  const token = new CancellationToken();
286
286
  const batchConsumer = queue.getBatchConsumer(2, token);
@@ -45,11 +45,11 @@ describe('Fan-Out Spawning', () => {
45
45
  const fParent = await queue.getTask(parent.id);
46
46
  expect(fParent?.status).toBe(TaskStatus.Completed);
47
47
  });
48
- it('should NOT transition parent to Waiting if waitForCompletion is false', async () => {
48
+ it('should NOT transition parent to Waiting if parentRequires is false', async () => {
49
49
  const parent = await queue.enqueue('parent', {});
50
50
  const dParent = await queue.dequeue();
51
- // Spawn child with waitForCompletion: false
52
- await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, waitForCompletion: false }], { returnTasks: true });
51
+ // Spawn child with parentRequires: false
52
+ await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, parentRequires: false }], { returnTasks: true });
53
53
  await queue.complete(dParent);
54
54
  const uParent = await queue.getTask(parent.id);
55
55
  expect(uParent?.status).toBe(TaskStatus.Completed); // Finished immediately
@@ -195,7 +195,7 @@ describe('PostgresQueue (Distributed Task Orchestration)', () => {
195
195
  const queueB = queueProvider.get(nameB);
196
196
  const parent = await queueA.enqueue('test', { value: 'parent' });
197
197
  expect(parent.unresolvedCompleteDependencies).toBe(0);
198
- await queueB.enqueue('test', { value: 'child' }, { parentId: parent.id, waitForCompletion: true });
198
+ await queueB.enqueue('test', { value: 'child' }, { parentId: parent.id, parentRequires: true });
199
199
  const updatedParent = await queueA.getTask(parent.id);
200
200
  expect(updatedParent?.unresolvedCompleteDependencies).toBe(1);
201
201
  await database.update(taskTable).set({ parentId: null }).where(or(eq(taskTable.namespace, nameA), eq(taskTable.namespace, nameB)));
@@ -254,7 +254,7 @@ describe('PostgresQueue (Distributed Task Orchestration)', () => {
254
254
  await q.enqueue('test', {}, {
255
255
  idempotencyKey,
256
256
  parentId: parent.id,
257
- waitForCompletion: true,
257
+ parentRequires: true,
258
258
  });
259
259
  // Dequeue and complete parent
260
260
  const dequeuedParent = await q.dequeue();
@@ -263,10 +263,10 @@ describe('PostgresQueue (Distributed Task Orchestration)', () => {
263
263
  expect(updatedParent?.status).toBe(TaskStatus.Completed);
264
264
  await q.clear();
265
265
  });
266
- it('should increment unresolvedCompleteDependencies for children with waitForCompletion: true (Bug 6-2)', async () => {
266
+ it('should increment unresolvedCompleteDependencies for children with parentRequires: true (Bug 6-2)', async () => {
267
267
  const parent = await queue.enqueue('parent', {});
268
268
  expect(parent.unresolvedCompleteDependencies).toBe(0);
269
- await queue.enqueue('child', {}, { parentId: parent.id, waitForCompletion: true });
269
+ await queue.enqueue('child', {}, { parentId: parent.id, parentRequires: true });
270
270
  const updatedParent = await queue.getTask(parent.id);
271
271
  expect(updatedParent?.unresolvedCompleteDependencies).toBe(1);
272
272
  });
@@ -21,11 +21,11 @@ describe('Zombie Parent Deadlock', () => {
21
21
  afterAll(async () => {
22
22
  await injector?.dispose();
23
23
  });
24
- it('should resolve parent even if child fails (failFast: false)', async () => {
24
+ it('should resolve parent even if child fails (abortOnDependencyFailure: false)', async () => {
25
25
  const parent = await queue.enqueue('parent', {});
26
26
  const dParent = await queue.dequeue();
27
- // Spawn a child that will fail. Parent has failFast: false by default.
28
- const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, failFast: false }], { returnTasks: true });
27
+ // Spawn a child that will fail. Parent has abortOnDependencyFailure: false by default.
28
+ const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, abortOnDependencyFailure: false }], { returnTasks: true });
29
29
  await queue.complete(dParent);
30
30
  // Parent should be WaitingChildren
31
31
  const uParent = await queue.getTask(parent.id);