@tstdl/base 0.93.175 → 0.93.177
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/api/server/middlewares/response-time.middleware.js +2 -5
- package/audit/auditor.js +1 -1
- package/authentication/server/authentication.service.js +1 -1
- package/circuit-breaker/postgres/circuit-breaker.js +1 -1
- package/document-management/server/services/singleton.js +1 -1
- package/errors/format.d.ts +6 -1
- package/errors/format.js +1 -1
- package/http/index.d.ts +1 -0
- package/http/index.js +1 -0
- package/http/server/http-server-response.d.ts +2 -0
- package/http/server/http-server-response.js +2 -0
- package/http/server/node/node-http-server.js +3 -0
- package/http/server-timing.d.ts +31 -0
- package/http/server-timing.js +47 -0
- package/http/tests/server-timing.test.d.ts +1 -0
- package/http/tests/server-timing.test.js +42 -0
- package/key-value-store/postgres/key-value-store.service.js +1 -1
- package/lock/postgres/provider.js +1 -1
- package/mail/README.md +1 -1
- package/mail/drizzle/0001_married_tarantula.sql +12 -0
- package/mail/drizzle/meta/0001_snapshot.json +69 -0
- package/mail/drizzle/meta/_journal.json +7 -0
- package/mail/index.d.ts +1 -0
- package/mail/index.js +1 -0
- package/mail/mail.service.d.ts +21 -4
- package/mail/mail.service.js +44 -13
- package/mail/models/mail-log.model.d.ts +2 -1
- package/mail/models/mail-log.model.js +1 -1
- package/mail/task-definitions.d.ts +20 -0
- package/mail/task-definitions.js +1 -0
- package/package.json +3 -3
- package/rate-limit/postgres/postgres-rate-limiter.js +1 -1
- package/task-queue/postgres/drizzle/0001_rapid_infant_terrible.sql +16 -0
- package/task-queue/postgres/drizzle/meta/0001_snapshot.json +753 -0
- package/task-queue/postgres/drizzle/meta/_journal.json +7 -0
- package/task-queue/postgres/task-queue.js +13 -13
- package/task-queue/postgres/task.model.d.ts +2 -1
- package/task-queue/postgres/task.model.js +3 -3
- package/task-queue/task-queue.d.ts +4 -3
- package/task-queue/task-queue.js +12 -2
- package/task-queue/tests/coverage-branch.test.js +1 -1
- package/task-queue/tests/dependencies.test.js +2 -2
- package/task-queue/tests/queue.test.js +2 -2
- package/task-queue/tests/worker.test.js +39 -5
- package/tsconfig.json +0 -2
|
@@ -64,7 +64,7 @@ import { NotFoundError, serializeError, TimeoutError } from '../../errors/index.
|
|
|
64
64
|
import { afterResolve, inject, provide, Singleton } from '../../injector/index.js';
|
|
65
65
|
import { Logger } from '../../logger/index.js';
|
|
66
66
|
import { MessageBus } from '../../message-bus/index.js';
|
|
67
|
-
import { arrayOverlaps, caseWhen, coalesce, enumValue, greatest, interval,
|
|
67
|
+
import { arrayOverlaps, buildJsonb, caseWhen, coalesce, enumValue, greatest, interval, least, power, RANDOM_UUID_V4, TRANSACTION_TIMESTAMP } from '../../orm/index.js';
|
|
68
68
|
import { DatabaseConfig, injectRepository } from '../../orm/server/index.js';
|
|
69
69
|
import { RateLimiter } from '../../rate-limit/index.js';
|
|
70
70
|
import { distinct, toArray } from '../../utils/array/array.js';
|
|
@@ -133,7 +133,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
133
133
|
progress: 0,
|
|
134
134
|
data: sql `excluded.data`,
|
|
135
135
|
state: null,
|
|
136
|
-
|
|
136
|
+
errors: buildJsonb([]),
|
|
137
137
|
result: null,
|
|
138
138
|
};
|
|
139
139
|
[afterResolve]() {
|
|
@@ -183,7 +183,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
183
183
|
data: item.data,
|
|
184
184
|
state: null,
|
|
185
185
|
result: null,
|
|
186
|
-
|
|
186
|
+
errors: buildJsonb([]),
|
|
187
187
|
},
|
|
188
188
|
}));
|
|
189
189
|
const itemsWithIdempotency = entitiesWithIndex.filter((e) => isNotNull(e.entity.idempotencyKey));
|
|
@@ -721,7 +721,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
721
721
|
status: caseWhen(exceededMaxExecutionTime, enumValue(TaskStatus, taskStatus, TaskStatus.TimedOut)).else(taskTable.status),
|
|
722
722
|
visibilityDeadline: caseWhen(exceededMaxExecutionTime, null).else(sql `${TRANSACTION_TIMESTAMP} + ${interval(this.visibilityTimeout, 'milliseconds')}`),
|
|
723
723
|
completeTimestamp: caseWhen(exceededMaxExecutionTime, TRANSACTION_TIMESTAMP).else(taskTable.completeTimestamp),
|
|
724
|
-
|
|
724
|
+
errors: caseWhen(exceededMaxExecutionTime, sql `${taskTable.errors} || ${buildJsonb([{ code: 'MaxTimeExceeded', message: 'Hard Execution Timeout' }])}`).else(taskTable.errors),
|
|
725
725
|
progress: caseWhen(exceededMaxExecutionTime, taskTable.progress).else(isDefined(options?.progress) ? options.progress : taskTable.progress),
|
|
726
726
|
state: caseWhen(exceededMaxExecutionTime, taskTable.state).else(isDefined(options?.state) ? options.state : taskTable.state),
|
|
727
727
|
})
|
|
@@ -844,7 +844,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
844
844
|
.set({
|
|
845
845
|
status: update.status,
|
|
846
846
|
token: null,
|
|
847
|
-
|
|
847
|
+
errors: sql `${taskTable.errors} || ${buildJsonb([update.error])}`,
|
|
848
848
|
visibilityDeadline: null,
|
|
849
849
|
scheduleTimestamp: (update.status == TaskStatus.Retrying) ? update.scheduleTimestamp : taskTable.scheduleTimestamp,
|
|
850
850
|
completeTimestamp: update.completeTimestamp,
|
|
@@ -866,7 +866,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
866
866
|
const rows = tasks.map((task, index) => {
|
|
867
867
|
const error = ensureTaskError(errors[index]);
|
|
868
868
|
const update = this.getFailureUpdate(task.tries, error);
|
|
869
|
-
return sql `(${task.id}::uuid, ${task.token}::uuid, ${task.tries}::int, ${update.status}::${taskStatus}, ${update.error}
|
|
869
|
+
return sql `(${task.id}::uuid, ${task.token}::uuid, ${task.tries}::int, ${update.status}::${taskStatus}, ${buildJsonb([update.error])}, ${update.scheduleTimestamp}::timestamptz, ${update.completeTimestamp}::timestamptz)`;
|
|
870
870
|
});
|
|
871
871
|
const updates = tx.pgTransaction.$with('updates').as((qb) => qb
|
|
872
872
|
.select({
|
|
@@ -874,7 +874,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
874
874
|
updateToken: sql `(token)`.as('update_token'),
|
|
875
875
|
updateTries: sql `(tries)`.as('update_tries'),
|
|
876
876
|
updateStatus: sql `(status)`.as('update_status'),
|
|
877
|
-
|
|
877
|
+
updateErrors: sql `(error)`.as('update_errors'),
|
|
878
878
|
updateSchedule: sql `(schedule_timestamp)`.as('update_schedule'),
|
|
879
879
|
updateComplete: sql `(complete_timestamp)`.as('update_complete'),
|
|
880
880
|
})
|
|
@@ -884,7 +884,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
884
884
|
.set({
|
|
885
885
|
status: sql `${updates.updateStatus}`,
|
|
886
886
|
token: null,
|
|
887
|
-
|
|
887
|
+
errors: sql `${taskTable.errors} || ${updates.updateErrors}`,
|
|
888
888
|
visibilityDeadline: null,
|
|
889
889
|
scheduleTimestamp: caseWhen(eq(updates.updateStatus, TaskStatus.Retrying), updates.updateSchedule).else(taskTable.scheduleTimestamp),
|
|
890
890
|
completeTimestamp: sql `${updates.updateComplete}`,
|
|
@@ -985,7 +985,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
985
985
|
.set({
|
|
986
986
|
status: TaskStatus.Skipped,
|
|
987
987
|
token: null,
|
|
988
|
-
|
|
988
|
+
errors: sql `${taskTable.errors} || ${buildJsonb([{ code: 'DependencyFailed', message: 'One or more dependencies failed and abortOnDependencyFailure is enabled' }])}`,
|
|
989
989
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
990
990
|
})
|
|
991
991
|
.where(inArray(taskTable.id, tasksToSkip))
|
|
@@ -1132,7 +1132,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1132
1132
|
.set({
|
|
1133
1133
|
status: TaskStatus.Expired,
|
|
1134
1134
|
token: null,
|
|
1135
|
-
|
|
1135
|
+
errors: sql `${taskTable.errors} || ${buildJsonb([{ code: 'Expired', message: 'Task expired before processing' }])}`,
|
|
1136
1136
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
1137
1137
|
})
|
|
1138
1138
|
.where(inArray(taskTable.id, tx.pgTransaction.select().from(expiredSelection)))
|
|
@@ -1166,7 +1166,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1166
1166
|
visibilityDeadline: null,
|
|
1167
1167
|
completeTimestamp: caseWhen(or(lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`), gte(taskTable.tries, this.maxTries)), TRANSACTION_TIMESTAMP).else(null),
|
|
1168
1168
|
scheduleTimestamp: caseWhen(and(lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP), lt(taskTable.tries, this.maxTries), not(lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`))), sql `${TRANSACTION_TIMESTAMP} + ${interval(least(this.retryDelayMaximum, sql `${this.retryDelayMinimum} * ${power(this.retryDelayGrowth, sql `${taskTable.tries} - 1`)}`), 'milliseconds')}`).else(taskTable.scheduleTimestamp),
|
|
1169
|
-
|
|
1169
|
+
errors: caseWhen(lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`), sql `${taskTable.errors} || ${buildJsonb([{ code: 'MaxTimeExceeded', message: sql `'Hard Execution Timeout: Task ran longer than ' || ${this.maxExecutionTime} || 'ms'` }])}`).else(caseWhen(lt(taskTable.tries, this.maxTries), sql `${taskTable.errors} || ${buildJsonb([{ code: 'VisibilityTimeout', message: 'Worker Lost' }])}`).else(sql `${taskTable.errors} || ${buildJsonb([{ code: 'ZombieExhausted', message: 'Exceeded max retries after repeated crashes' }])}`)),
|
|
1170
1170
|
})
|
|
1171
1171
|
.where(inArray(taskTable.id, tx.pgTransaction.select().from(selection)))
|
|
1172
1172
|
.returning({ id: taskTable.id, status: taskTable.status });
|
|
@@ -1211,7 +1211,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1211
1211
|
.set({
|
|
1212
1212
|
status: TaskStatus.Pending,
|
|
1213
1213
|
token: null,
|
|
1214
|
-
|
|
1214
|
+
errors: [],
|
|
1215
1215
|
result: null,
|
|
1216
1216
|
scheduleTimestamp: TRANSACTION_TIMESTAMP,
|
|
1217
1217
|
completeTimestamp: null,
|
|
@@ -1319,7 +1319,7 @@ PostgresTaskQueue = __decorate([
|
|
|
1319
1319
|
Singleton({
|
|
1320
1320
|
argumentIdentityProvider: JSON.stringify,
|
|
1321
1321
|
providers: [
|
|
1322
|
-
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresTaskQueueModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf:
|
|
1322
|
+
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresTaskQueueModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) }),
|
|
1323
1323
|
],
|
|
1324
1324
|
})
|
|
1325
1325
|
], PostgresTaskQueue);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
|
|
2
|
+
import type { NonRecursiveSerializedError } from '../../errors/index.js';
|
|
2
3
|
import { BaseEntity, type Json, type Timestamp } from '../../orm/index.js';
|
|
3
4
|
import type { ObjectLiteral } from '../../types/types.js';
|
|
4
5
|
import { type Task, TaskDependencyType, TaskStatus } from '../task-queue.js';
|
|
@@ -27,7 +28,7 @@ export declare abstract class PostgresTaskBase<Data extends ObjectLiteral = Obje
|
|
|
27
28
|
data: Json<Data> | null;
|
|
28
29
|
state: Json<State> | null;
|
|
29
30
|
result: Json<Result> | null;
|
|
30
|
-
|
|
31
|
+
errors: Json<NonRecursiveSerializedError[]>;
|
|
31
32
|
}
|
|
32
33
|
export declare class PostgresTask<Data extends ObjectLiteral = ObjectLiteral, State extends ObjectLiteral = ObjectLiteral, Result extends ObjectLiteral = ObjectLiteral> extends PostgresTaskBase<Data, State, Result> implements Task {
|
|
33
34
|
static readonly entityName = "Task";
|
|
@@ -37,7 +37,7 @@ export class PostgresTaskBase extends BaseEntity {
|
|
|
37
37
|
data;
|
|
38
38
|
state;
|
|
39
39
|
result;
|
|
40
|
-
|
|
40
|
+
errors;
|
|
41
41
|
}
|
|
42
42
|
__decorate([
|
|
43
43
|
StringProperty(),
|
|
@@ -136,9 +136,9 @@ __decorate([
|
|
|
136
136
|
__metadata("design:type", Object)
|
|
137
137
|
], PostgresTaskBase.prototype, "result", void 0);
|
|
138
138
|
__decorate([
|
|
139
|
-
JsonProperty(
|
|
139
|
+
JsonProperty(),
|
|
140
140
|
__metadata("design:type", Object)
|
|
141
|
-
], PostgresTaskBase.prototype, "
|
|
141
|
+
], PostgresTaskBase.prototype, "errors", void 0);
|
|
142
142
|
let PostgresTask = class PostgresTask extends PostgresTaskBase {
|
|
143
143
|
static entityName = 'Task';
|
|
144
144
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
|
|
2
2
|
import type { CancellationSignal } from '../cancellation/index.js';
|
|
3
3
|
import { type EnumType } from '../enumeration/enumeration.js';
|
|
4
|
+
import type { NonRecursiveSerializedError } from '../errors/index.js';
|
|
4
5
|
import { Injector } from '../injector/index.js';
|
|
5
6
|
import type { Resolvable, resolveArgumentType } from '../injector/interfaces.js';
|
|
6
7
|
import { Logger } from '../logger/logger.js';
|
|
7
8
|
import type { Transaction } from '../orm/server/transaction.js';
|
|
8
9
|
import { Transactional } from '../orm/server/transactional.js';
|
|
9
|
-
import type { OneOrMany, Record
|
|
10
|
+
import type { OneOrMany, Record } from '../types/types.js';
|
|
10
11
|
import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
|
|
11
12
|
import type { ProcessWorker, TaskData, TaskDefinitionMap, TaskOfType, TaskProcessResultPayload, TaskResult, TasksResults, TasksStates, TaskState, TaskTypes } from './types.js';
|
|
12
13
|
/**
|
|
@@ -204,8 +205,8 @@ export type Task<Definitions extends TaskDefinitionMap = Record<string, {
|
|
|
204
205
|
state: TaskState<Definitions, Type> | null;
|
|
205
206
|
/** The result of the task, if successful. */
|
|
206
207
|
result: TaskResult<Definitions, Type> | null;
|
|
207
|
-
/** The
|
|
208
|
-
|
|
208
|
+
/** The errors that occurred, if the task failed. */
|
|
209
|
+
errors: NonRecursiveSerializedError[];
|
|
209
210
|
};
|
|
210
211
|
}[Extract<keyof Definitions, string>];
|
|
211
212
|
/** Default priority for tasks. */
|
package/task-queue/task-queue.js
CHANGED
|
@@ -283,9 +283,19 @@ export class TaskQueue extends Transactional {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
catch (error) {
|
|
286
|
+
const isUnsupportedAction = isError(error) && (error.message == 'Unsupported task result action.');
|
|
287
|
+
const isCancelledBeforeStart = isError(error) && (error.message == 'Task cancelled before start');
|
|
288
|
+
if (isCancelledBeforeStart) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
286
291
|
context.logger.error('Error processing task', error);
|
|
287
|
-
|
|
288
|
-
|
|
292
|
+
try {
|
|
293
|
+
await this.fail(task, error, { fatal: isUnsupportedAction });
|
|
294
|
+
}
|
|
295
|
+
catch (failError) {
|
|
296
|
+
context.logger.error('Error failing task after processing error', failError);
|
|
297
|
+
}
|
|
298
|
+
if (isUnsupportedAction) {
|
|
289
299
|
throw error;
|
|
290
300
|
}
|
|
291
301
|
}
|
|
@@ -212,7 +212,7 @@ describe('Task Queue Branch Coverage Enhancement', () => {
|
|
|
212
212
|
await ttlQueue.maintenance();
|
|
213
213
|
const updated = await ttlQueue.getTask(task.id);
|
|
214
214
|
expect(updated?.status).toBe(TaskStatus.Expired);
|
|
215
|
-
expect(updated?.
|
|
215
|
+
expect(updated?.errors[0]?.message).toContain('Task expired');
|
|
216
216
|
});
|
|
217
217
|
it('should handle cancelMany with multiple valid IDs', async () => {
|
|
218
218
|
const t1 = await queue.enqueue('c1', {});
|
|
@@ -73,7 +73,7 @@ describe('Queue Dependencies & Tree Tests', () => {
|
|
|
73
73
|
await queue.waitForTasks([dependent.id]);
|
|
74
74
|
const updatedDependent = await queue.getTask(dependent.id);
|
|
75
75
|
expect(updatedDependent?.status).toBe(TaskStatus.Skipped);
|
|
76
|
-
expect(updatedDependent?.
|
|
76
|
+
expect(updatedDependent?.errors[0]?.code).toBe('DependencyFailed');
|
|
77
77
|
});
|
|
78
78
|
it('should NOT overwrite terminal states during cancellation (abortOnDependencyFailure + complete)', async () => {
|
|
79
79
|
const dep = await queue.enqueue('dep', {});
|
|
@@ -261,7 +261,7 @@ describe('Queue Dependencies & Tree Tests', () => {
|
|
|
261
261
|
const updated = await queue.getTask(task.id);
|
|
262
262
|
expect(updated?.status).toBe(TaskStatus.Pending);
|
|
263
263
|
expect(updated?.tries).toBe(0);
|
|
264
|
-
expect(updated?.
|
|
264
|
+
expect(updated?.errors).toEqual([]);
|
|
265
265
|
});
|
|
266
266
|
it('should consume tasks via async iterator (getConsumer)', async () => {
|
|
267
267
|
await queue.enqueue('c1', { val: 1 });
|
|
@@ -184,7 +184,7 @@ describe('PostgresQueue (Distributed Task Orchestration)', () => {
|
|
|
184
184
|
const updated = await queue.getTask(task.id);
|
|
185
185
|
expect(updated?.status).toBe(TaskStatus.Retrying);
|
|
186
186
|
expect(updated?.tries).toBe(1);
|
|
187
|
-
expect(updated?.
|
|
187
|
+
expect(updated?.errors).toHaveLength(1);
|
|
188
188
|
});
|
|
189
189
|
it('should NOT clear startTimestamp when transitioning to terminal or retry states', async () => {
|
|
190
190
|
// Re-create queue with high circuit breaker threshold for this test
|
|
@@ -423,7 +423,7 @@ describe('PostgresQueue (Distributed Task Orchestration)', () => {
|
|
|
423
423
|
await shortQueue.maintenance();
|
|
424
424
|
const updated = await shortQueue.getTask(task.id);
|
|
425
425
|
expect(updated?.status).toBe(TaskStatus.TimedOut);
|
|
426
|
-
expect(updated?.
|
|
426
|
+
expect(updated?.errors[0]?.code).toBe('MaxTimeExceeded');
|
|
427
427
|
await shortQueue.clear();
|
|
428
428
|
});
|
|
429
429
|
it('should touch a task to extend token', async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { CancellationToken } from '../../cancellation/index.js';
|
|
3
3
|
import { getRepository } from '../../orm/server/index.js';
|
|
4
4
|
import { TaskProcessResult, TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
@@ -33,6 +33,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
33
33
|
await queue.clear();
|
|
34
34
|
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
35
35
|
await queueProvider.get(otherQueueName).clear();
|
|
36
|
+
vi.restoreAllMocks();
|
|
36
37
|
});
|
|
37
38
|
afterAll(async () => {
|
|
38
39
|
await injector?.dispose();
|
|
@@ -80,7 +81,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
80
81
|
const updated = await queue.getTask(task.id);
|
|
81
82
|
expect(updated?.status).toBe(TaskStatus.Retrying); // Should retry
|
|
82
83
|
expect(updated?.tries).toBe(1);
|
|
83
|
-
expect(updated?.
|
|
84
|
+
expect(updated?.errors[0]?.message).toBe('worker error');
|
|
84
85
|
});
|
|
85
86
|
it('should extend lease (heartbeat) during long processing', async () => {
|
|
86
87
|
const task = await queue.enqueue('long', {});
|
|
@@ -125,7 +126,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
125
126
|
token.set();
|
|
126
127
|
const uFail = await queue.getTask(tFail.id);
|
|
127
128
|
expect(uFail?.status).toBe(TaskStatus.Retrying); // Retry
|
|
128
|
-
expect(uFail?.
|
|
129
|
+
expect(uFail?.errors[0]?.message).toBe('explicit fail');
|
|
129
130
|
const uResched = await queue.getTask(tResched.id);
|
|
130
131
|
expect(uResched?.status).toBe(TaskStatus.Pending);
|
|
131
132
|
expect(uResched?.scheduleTimestamp).toBeGreaterThan(Date.now());
|
|
@@ -218,7 +219,40 @@ describe('Worker & Base Class Tests', () => {
|
|
|
218
219
|
const workerPromise = queue.processWorker(token, () => ({ payload: { action: 'magic' } }));
|
|
219
220
|
await expect(workerPromise).rejects.toThrow('Unsupported task result action');
|
|
220
221
|
const updated = await queue.getTask(task.id);
|
|
221
|
-
expect(updated?.status).toBe(TaskStatus.
|
|
222
|
-
expect(updated?.
|
|
222
|
+
expect(updated?.status).toBe(TaskStatus.Dead); // Fatal now
|
|
223
|
+
expect(updated?.errors[0]?.message).toContain('Unsupported task result action');
|
|
224
|
+
});
|
|
225
|
+
it('should not crash worker lane if fail() throws', async () => {
|
|
226
|
+
const t1 = await queue.enqueue('task1', {});
|
|
227
|
+
const t2 = await queue.enqueue('task2', {});
|
|
228
|
+
let t1Processed = false;
|
|
229
|
+
let t2Processed = false;
|
|
230
|
+
// Mock fail to throw once
|
|
231
|
+
const failSpy = vi.spyOn(queue, 'fail').mockRejectedValueOnce(new Error('DB DOWN'));
|
|
232
|
+
queue.process({ cancellationSignal: token }, async (context) => {
|
|
233
|
+
if (context.id === t1.id) {
|
|
234
|
+
t1Processed = true;
|
|
235
|
+
throw new Error('Task 1 failed');
|
|
236
|
+
}
|
|
237
|
+
if (context.id === t2.id) {
|
|
238
|
+
t2Processed = true;
|
|
239
|
+
return TaskProcessResult.Complete();
|
|
240
|
+
}
|
|
241
|
+
return TaskProcessResult.Complete();
|
|
242
|
+
});
|
|
243
|
+
// Wait until both tasks are processed
|
|
244
|
+
// If the bug exists, the worker lane will crash after t1 and t2 will never be processed
|
|
245
|
+
for (let i = 0; i < 40; i++) {
|
|
246
|
+
if (t1Processed && t2Processed)
|
|
247
|
+
break;
|
|
248
|
+
queue.notify();
|
|
249
|
+
await timeout(50);
|
|
250
|
+
}
|
|
251
|
+
token.set();
|
|
252
|
+
expect(t1Processed).toBe(true);
|
|
253
|
+
expect(t2Processed).toBe(true);
|
|
254
|
+
expect(failSpy).toHaveBeenCalled();
|
|
255
|
+
const check2 = await queue.getTask(t2.id);
|
|
256
|
+
expect(check2?.status).toBe(TaskStatus.Completed);
|
|
223
257
|
});
|
|
224
258
|
});
|
package/tsconfig.json
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"moduleResolution": "nodenext",
|
|
6
6
|
"lib": ["esnext", "dom"],
|
|
7
7
|
"target": "es2024",
|
|
8
|
-
"baseUrl": "source/",
|
|
9
8
|
"rootDir": "source/",
|
|
10
9
|
"outDir": "dist/",
|
|
11
10
|
"declaration": true,
|
|
@@ -15,7 +14,6 @@
|
|
|
15
14
|
"removeComments": false,
|
|
16
15
|
"resolveJsonModule": true,
|
|
17
16
|
"sourceMap": false,
|
|
18
|
-
"downlevelIteration": true,
|
|
19
17
|
"emitDecoratorMetadata": true,
|
|
20
18
|
"experimentalDecorators": true,
|
|
21
19
|
|