@tstdl/base 0.93.138 → 0.93.140
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/README.md +166 -0
- package/ai/genkit/multi-region.plugin.js +5 -3
- package/ai/genkit/tests/multi-region.test.d.ts +1 -0
- package/ai/genkit/tests/multi-region.test.js +5 -2
- package/ai/parser/parser.js +2 -2
- package/ai/prompts/build.js +1 -0
- package/ai/prompts/instructions-formatter.d.ts +15 -2
- package/ai/prompts/instructions-formatter.js +36 -31
- package/ai/prompts/prompt-builder.js +5 -5
- package/ai/prompts/steering.d.ts +3 -2
- package/ai/prompts/steering.js +3 -1
- package/ai/tests/instructions-formatter.test.js +1 -0
- package/api/README.md +403 -0
- package/api/client/client.js +7 -13
- package/api/client/tests/api-client.test.js +10 -10
- package/api/default-error-handlers.js +1 -1
- package/api/response.d.ts +2 -2
- package/api/response.js +22 -33
- package/api/server/api-controller.d.ts +1 -1
- package/api/server/api-controller.js +3 -3
- package/api/server/api-request-token.provider.d.ts +1 -0
- package/api/server/api-request-token.provider.js +1 -0
- package/api/server/middlewares/allowed-methods.middleware.js +2 -1
- package/api/server/middlewares/content-type.middleware.js +2 -1
- package/api/types.d.ts +3 -2
- package/application/README.md +240 -0
- package/application/application.js +2 -2
- package/audit/README.md +267 -0
- package/authentication/README.md +288 -0
- package/authentication/client/authentication.service.d.ts +12 -11
- package/authentication/client/authentication.service.js +21 -21
- package/authentication/client/http-client.middleware.js +2 -2
- package/authentication/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- package/browser/README.md +401 -0
- package/cancellation/README.md +156 -0
- package/cancellation/tests/coverage.test.d.ts +1 -0
- package/cancellation/tests/coverage.test.js +49 -0
- package/cancellation/tests/leak.test.d.ts +1 -0
- package/cancellation/tests/leak.test.js +35 -0
- package/cancellation/tests/token.test.d.ts +1 -0
- package/cancellation/tests/token.test.js +136 -0
- package/cancellation/token.d.ts +53 -177
- package/cancellation/token.js +132 -201
- package/context/README.md +174 -0
- package/cookie/README.md +161 -0
- package/css/README.md +157 -0
- package/data-structures/README.md +320 -0
- package/decorators/README.md +140 -0
- package/distributed-loop/README.md +231 -0
- package/distributed-loop/distributed-loop.js +1 -1
- package/document-management/README.md +403 -0
- package/document-management/server/services/document-management.service.js +9 -7
- package/document-management/tests/document-management-core.test.js +2 -7
- package/document-management/tests/document-management.api.test.js +6 -7
- package/document-management/tests/document-statistics.service.test.js +11 -12
- package/document-management/tests/document.service.test.js +3 -3
- package/document-management/tests/enum-helpers.test.js +2 -3
- package/dom/README.md +213 -0
- package/enumerable/README.md +259 -0
- package/enumeration/README.md +121 -0
- package/errors/README.md +267 -0
- package/file/README.md +191 -0
- package/formats/README.md +210 -0
- package/function/README.md +144 -0
- package/http/README.md +318 -0
- package/http/client/adapters/undici.adapter.js +1 -1
- package/http/client/http-client-request.d.ts +6 -5
- package/http/client/http-client-request.js +8 -9
- package/http/server/node/node-http-server.js +1 -2
- package/image-service/README.md +137 -0
- package/injector/README.md +491 -0
- package/injector/injector.d.ts +1 -0
- package/injector/injector.js +17 -5
- package/injector/tests/leak.test.d.ts +1 -0
- package/injector/tests/leak.test.js +45 -0
- package/intl/README.md +113 -0
- package/json-path/README.md +182 -0
- package/jsx/README.md +154 -0
- package/key-value-store/README.md +191 -0
- package/lock/README.md +249 -0
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- package/memory/README.md +144 -0
- package/message-bus/README.md +244 -0
- package/message-bus/message-bus-base.js +1 -1
- package/module/README.md +182 -0
- package/module/module.d.ts +1 -1
- package/module/module.js +77 -17
- package/module/modules/web-server.module.js +1 -1
- package/notification/tests/notification-type.service.test.js +24 -15
- package/object-storage/README.md +300 -0
- package/openid-connect/README.md +274 -0
- package/orm/README.md +423 -0
- package/package.json +8 -6
- package/password/README.md +164 -0
- package/pdf/README.md +246 -0
- package/polyfills.js +1 -0
- package/pool/README.md +198 -0
- package/process/README.md +237 -0
- package/promise/README.md +252 -0
- package/promise/cancelable-promise.js +1 -1
- package/random/README.md +193 -0
- package/reflection/README.md +305 -0
- package/rpc/README.md +386 -0
- package/rxjs-utils/README.md +262 -0
- package/schema/README.md +342 -0
- package/serializer/README.md +342 -0
- package/signals/implementation/README.md +134 -0
- package/sse/README.md +278 -0
- package/task-queue/README.md +300 -0
- package/task-queue/postgres/task-queue.d.ts +2 -1
- package/task-queue/postgres/task-queue.js +32 -2
- package/task-queue/task-context.js +1 -1
- package/task-queue/task-queue.d.ts +17 -0
- package/task-queue/task-queue.js +103 -44
- package/task-queue/tests/complex.test.js +4 -4
- package/task-queue/tests/dependencies.test.js +4 -2
- package/task-queue/tests/queue.test.js +111 -0
- package/task-queue/tests/worker.test.js +21 -13
- package/templates/README.md +287 -0
- package/testing/README.md +157 -0
- package/text/README.md +346 -0
- package/threading/README.md +238 -0
- package/types/README.md +311 -0
- package/utils/README.md +322 -0
- package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
- package/utils/async-iterable-helpers/observable-iterable.js +4 -8
- package/utils/async-iterable-helpers/take-until.js +4 -4
- package/utils/backoff.js +89 -30
- package/utils/retry-with-backoff.js +1 -1
- package/utils/timer.d.ts +1 -1
- package/utils/timer.js +5 -7
- package/utils/timing.d.ts +1 -1
- package/utils/timing.js +2 -4
- package/utils/z-base32.d.ts +1 -0
- package/utils/z-base32.js +1 -0
|
@@ -175,6 +175,14 @@ export type QueueConfig = {
|
|
|
175
175
|
export type TaskQueueArgument = string | (QueueConfig & {
|
|
176
176
|
namespace: string;
|
|
177
177
|
});
|
|
178
|
+
export type TaskQueueWaitOptions = {
|
|
179
|
+
interval?: number;
|
|
180
|
+
timeout?: number;
|
|
181
|
+
cancellationSignal?: CancellationSignal;
|
|
182
|
+
};
|
|
183
|
+
export type TaskQueueWaitResult = {
|
|
184
|
+
cancelled: boolean;
|
|
185
|
+
};
|
|
178
186
|
export declare const defaultQueueConfig: Required<QueueConfig>;
|
|
179
187
|
export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap = TaskDefinitionMap> extends Transactional<QueueConfig & {
|
|
180
188
|
namespace: string;
|
|
@@ -211,6 +219,15 @@ export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap =
|
|
|
211
219
|
abstract getTree(rootId: string | string[], options?: {
|
|
212
220
|
transaction?: Transaction;
|
|
213
221
|
}): Promise<Task[]>;
|
|
222
|
+
/**
|
|
223
|
+
* Waits for the specified tasks to reach a finalized state (Completed, Cancelled, or Dead) or be removed/archived from queue (non-existent).
|
|
224
|
+
* @param ids The IDs of the tasks to wait for.
|
|
225
|
+
* @param options Timeout and cancellation options.
|
|
226
|
+
*/
|
|
227
|
+
abstract waitForTasks(ids: string[], options?: {
|
|
228
|
+
timeout?: number;
|
|
229
|
+
cancellationSignal?: CancellationSignal;
|
|
230
|
+
}): Promise<TaskQueueWaitResult>;
|
|
214
231
|
abstract cancel(id: string, options?: {
|
|
215
232
|
transaction?: Transaction;
|
|
216
233
|
}): Promise<void>;
|
package/task-queue/task-queue.js
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import { defineEnum } from '../enumeration/enumeration.js';
|
|
2
54
|
import { inject, injectArgument, Injector } from '../injector/index.js';
|
|
3
55
|
import { Logger } from '../logger/logger.js';
|
|
@@ -103,59 +155,66 @@ export class TaskQueue extends Transactional {
|
|
|
103
155
|
*/
|
|
104
156
|
async processWorker(cancellationSignal, handler, options) {
|
|
105
157
|
for await (const task of this.getConsumer(cancellationSignal, options)) {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
158
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
159
|
+
try {
|
|
160
|
+
const taskToken = __addDisposableResource(env_1, cancellationSignal.fork(), false);
|
|
161
|
+
const context = new TaskContext(this, task, taskToken, this.logger.with({ type: task.type }));
|
|
162
|
+
let isTaskActive = true;
|
|
163
|
+
context.logger.verbose(`Processing task`);
|
|
164
|
+
void (async () => {
|
|
165
|
+
while (taskToken.isUnset) {
|
|
166
|
+
await cancelableTimeout(Math.min(this.visibilityTimeout / 2, 5000), taskToken);
|
|
167
|
+
if (taskToken.isSet) {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const touchedTask = await this.touch(task);
|
|
172
|
+
if (isUndefined(touchedTask) && taskToken.isUnset) {
|
|
173
|
+
context.logger.warn(`Task lost lease. Aborting.`);
|
|
174
|
+
isTaskActive = false;
|
|
175
|
+
taskToken.set();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
context.logger.error('Error touching task', error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})();
|
|
183
|
+
try {
|
|
113
184
|
if (taskToken.isSet) {
|
|
114
|
-
|
|
185
|
+
throw new Error('Task cancelled before start');
|
|
115
186
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
187
|
+
const result = await handler(context);
|
|
188
|
+
if (isDefined(result) && isTaskActive) {
|
|
189
|
+
switch (result.payload.action) {
|
|
190
|
+
case 'complete':
|
|
191
|
+
context.logger.verbose(`Completing task`);
|
|
192
|
+
await this.complete(task, { result: result.payload.result });
|
|
193
|
+
break;
|
|
194
|
+
case 'fail':
|
|
195
|
+
context.logger.verbose(`Failing task`);
|
|
196
|
+
await this.fail(task, result.payload.error, { fatal: result.payload.fatal });
|
|
197
|
+
break;
|
|
198
|
+
case 'reschedule':
|
|
199
|
+
context.logger.verbose(`Rescheduling task`);
|
|
200
|
+
await this.reschedule(task.id, result.payload.timestamp);
|
|
201
|
+
break;
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`Unsupported task result action.`);
|
|
122
204
|
}
|
|
123
205
|
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
context.logger.error('Error touching task', error);
|
|
126
|
-
}
|
|
127
206
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
throw new Error('Task cancelled before start');
|
|
132
|
-
}
|
|
133
|
-
const result = await handler(context);
|
|
134
|
-
if (isDefined(result) && isTaskActive) {
|
|
135
|
-
switch (result.payload.action) {
|
|
136
|
-
case 'complete':
|
|
137
|
-
context.logger.verbose(`Completing task`);
|
|
138
|
-
await this.complete(task, { result: result.payload.result });
|
|
139
|
-
break;
|
|
140
|
-
case 'fail':
|
|
141
|
-
context.logger.verbose(`Failing task`);
|
|
142
|
-
await this.fail(task, result.payload.error, { fatal: result.payload.fatal });
|
|
143
|
-
break;
|
|
144
|
-
case 'reschedule':
|
|
145
|
-
context.logger.verbose(`Rescheduling task`);
|
|
146
|
-
await this.reschedule(task.id, result.payload.timestamp);
|
|
147
|
-
break;
|
|
148
|
-
default:
|
|
149
|
-
throw new Error(`Unsupported task result action.`);
|
|
150
|
-
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
context.logger.error('Error processing task', error);
|
|
209
|
+
await this.fail(task, error);
|
|
151
210
|
}
|
|
152
211
|
}
|
|
153
|
-
catch (
|
|
154
|
-
|
|
155
|
-
|
|
212
|
+
catch (e_1) {
|
|
213
|
+
env_1.error = e_1;
|
|
214
|
+
env_1.hasError = true;
|
|
156
215
|
}
|
|
157
216
|
finally {
|
|
158
|
-
|
|
217
|
+
__disposeResources(env_1);
|
|
159
218
|
}
|
|
160
219
|
}
|
|
161
220
|
}
|
|
@@ -18,7 +18,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
18
18
|
priorityAgingInterval: 50, // Fast aging
|
|
19
19
|
priorityAgingStep: 10,
|
|
20
20
|
rateLimit: 5,
|
|
21
|
-
rateInterval:
|
|
21
|
+
rateInterval: 200,
|
|
22
22
|
retryDelayMinimum: 50,
|
|
23
23
|
retryDelayGrowth: 2,
|
|
24
24
|
retention: 50, // Fast retention for archive test
|
|
@@ -143,7 +143,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
143
143
|
const batch2 = await queue.dequeueMany(1);
|
|
144
144
|
expect(batch2.length).toBe(0); // Rate limited
|
|
145
145
|
// Wait for refill
|
|
146
|
-
await timeout(
|
|
146
|
+
await timeout(300);
|
|
147
147
|
const batch3 = await queue.dequeueMany(5);
|
|
148
148
|
expect(batch3.length).toBe(5); // Refilled
|
|
149
149
|
});
|
|
@@ -153,7 +153,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
153
153
|
await limitQueue.enqueueMany([
|
|
154
154
|
{ type: 'c', data: {} },
|
|
155
155
|
{ type: 'c', data: {} },
|
|
156
|
-
{ type: 'c', data: {} }
|
|
156
|
+
{ type: 'c', data: {} },
|
|
157
157
|
]);
|
|
158
158
|
const t1 = await limitQueue.dequeue();
|
|
159
159
|
const t2 = await limitQueue.dequeue();
|
|
@@ -290,7 +290,7 @@ describe('Complex Queue Scenarios', () => {
|
|
|
290
290
|
it('should handle mixed AND/OR dependencies', async () => {
|
|
291
291
|
const dep = await queue.enqueue('dep', {}, {
|
|
292
292
|
scheduleAfterTags: ['A', 'B'],
|
|
293
|
-
dependencyJoinMode: DependencyJoinMode.Or
|
|
293
|
+
dependencyJoinMode: DependencyJoinMode.Or,
|
|
294
294
|
});
|
|
295
295
|
const A = await queue.enqueue('A', {}, { tags: ['A'] });
|
|
296
296
|
await queue.complete((await queue.dequeue({ types: ['A'] })));
|
|
@@ -45,7 +45,8 @@ describe('Queue Dependencies & Tree Tests', () => {
|
|
|
45
45
|
const dequeued = await queue.dequeue({ types: ['prereq'] });
|
|
46
46
|
expect(dequeued?.id).toBe(prereq.id);
|
|
47
47
|
await queue.complete(dequeued);
|
|
48
|
-
await
|
|
48
|
+
await queue.processPendingFanIn();
|
|
49
|
+
await queue.waitForTasks([dependent.id]);
|
|
49
50
|
const updatedDependent = await queue.getTask(dependent.id);
|
|
50
51
|
expect(updatedDependent?.status).toBe(TaskStatus.Completed);
|
|
51
52
|
});
|
|
@@ -77,7 +78,8 @@ describe('Queue Dependencies & Tree Tests', () => {
|
|
|
77
78
|
const dequeued = await queue.dequeue({ types: ['prereq'] });
|
|
78
79
|
// Fail fatally
|
|
79
80
|
await queue.fail(dequeued, new Error('boom'), { fatal: true });
|
|
80
|
-
await
|
|
81
|
+
await queue.processPendingFanIn();
|
|
82
|
+
await queue.waitForTasks([dependent.id]);
|
|
81
83
|
const updatedDependent = await queue.getTask(dependent.id);
|
|
82
84
|
expect(updatedDependent?.status).toBe(TaskStatus.Dead);
|
|
83
85
|
expect(updatedDependent?.error?.code).toBe('DependencyFailed');
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { CancellationToken } from '../../cancellation/index.js';
|
|
2
3
|
import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
3
4
|
import { setupIntegrationTest } from '../../testing/index.js';
|
|
4
5
|
import { currentTimestamp } from '../../utils/date-time.js';
|
|
6
|
+
import { Timer } from '../../utils/timer.js';
|
|
5
7
|
import { timeout } from '../../utils/timing.js';
|
|
6
8
|
describe('Queue Integration Tests', () => {
|
|
7
9
|
let injector;
|
|
@@ -331,4 +333,113 @@ describe('PostgresQueue (Distributed Task Orchestration)', () => {
|
|
|
331
333
|
expect(updated?.state).toEqual({ step: 1 });
|
|
332
334
|
});
|
|
333
335
|
});
|
|
336
|
+
describe('waitForTasks', () => {
|
|
337
|
+
it('should wait for multiple tasks to reach finalized state', async () => {
|
|
338
|
+
const t1 = await queue.enqueue('foo', { foo: 'wait-1' });
|
|
339
|
+
const t2 = await queue.enqueue('foo', { foo: 'wait-2' });
|
|
340
|
+
void (async () => {
|
|
341
|
+
await timeout(100);
|
|
342
|
+
const d1 = await queue.dequeue();
|
|
343
|
+
await queue.complete(d1);
|
|
344
|
+
await timeout(100);
|
|
345
|
+
const d2 = await queue.dequeue();
|
|
346
|
+
await queue.complete(d2);
|
|
347
|
+
})();
|
|
348
|
+
const result = await queue.waitForTasks([t1.id, t2.id], { timeout: 2000 });
|
|
349
|
+
expect(result.cancelled).toBe(false);
|
|
350
|
+
const check1 = await queue.getTask(t1.id);
|
|
351
|
+
const check2 = await queue.getTask(t2.id);
|
|
352
|
+
expect(check1?.status).toBe(TaskStatus.Completed);
|
|
353
|
+
expect(check2?.status).toBe(TaskStatus.Completed);
|
|
354
|
+
});
|
|
355
|
+
it('should throw TimeoutError on timeout', async () => {
|
|
356
|
+
const t1 = await queue.enqueue('foo', { foo: 'timeout' });
|
|
357
|
+
await expect(queue.waitForTasks([t1.id], { timeout: 100 })).rejects.toThrow('Timeout while waiting for tasks to complete');
|
|
358
|
+
});
|
|
359
|
+
it('should wait for Cancelled and Dead states', async () => {
|
|
360
|
+
const t1 = await queue.enqueue('foo', { foo: 'cancel' });
|
|
361
|
+
const t2 = await queue.enqueue('foo', { foo: 'dead' });
|
|
362
|
+
void (async () => {
|
|
363
|
+
await timeout(50);
|
|
364
|
+
const d1 = await queue.dequeue();
|
|
365
|
+
if (d1)
|
|
366
|
+
await queue.cancel(d1.id);
|
|
367
|
+
const d2 = await queue.dequeue();
|
|
368
|
+
if (d2)
|
|
369
|
+
await queue.fail(d2, new Error('fatal'), { fatal: true });
|
|
370
|
+
queue.notify();
|
|
371
|
+
})();
|
|
372
|
+
const result = await queue.waitForTasks([t1.id, t2.id], { timeout: 2000 });
|
|
373
|
+
expect(result.cancelled).toBe(false);
|
|
374
|
+
const c1 = await queue.getTask(t1.id);
|
|
375
|
+
const c2 = await queue.getTask(t2.id);
|
|
376
|
+
expect(c1?.status).toBe(TaskStatus.Cancelled);
|
|
377
|
+
expect(c2?.status).toBe(TaskStatus.Dead);
|
|
378
|
+
});
|
|
379
|
+
it('should handle cancellationSignal', async () => {
|
|
380
|
+
const t1 = await queue.enqueue('foo', { foo: 'long' });
|
|
381
|
+
const signal = new CancellationToken();
|
|
382
|
+
void timeout(100).then(() => signal.set());
|
|
383
|
+
const result = await queue.waitForTasks([t1.id], { cancellationSignal: signal, timeout: 5000 });
|
|
384
|
+
expect(result.cancelled).toBe(true);
|
|
385
|
+
});
|
|
386
|
+
it('should return immediately for non-existent tasks', async () => {
|
|
387
|
+
const result = await queue.waitForTasks([crypto.randomUUID()], { timeout: 1000 });
|
|
388
|
+
expect(result.cancelled).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
it('should return immediately if all tasks are already finalized', async () => {
|
|
391
|
+
const t1 = await queue.enqueue('foo', { foo: 'immediate' });
|
|
392
|
+
const d1 = await queue.dequeue();
|
|
393
|
+
await queue.complete(d1);
|
|
394
|
+
const timer = Timer.startNew();
|
|
395
|
+
const result = await queue.waitForTasks([t1.id], { timeout: 1000 });
|
|
396
|
+
expect(result.cancelled).toBe(false);
|
|
397
|
+
expect(timer.milliseconds).toBeLessThan(100);
|
|
398
|
+
});
|
|
399
|
+
it('should handle a mix of active and archived tasks', async () => {
|
|
400
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
401
|
+
const archiveQueue = queueProvider.get(`archive-test-${crypto.randomUUID()}`, {
|
|
402
|
+
retention: 0, // Archive immediately
|
|
403
|
+
});
|
|
404
|
+
const t1 = await archiveQueue.enqueue('foo', { foo: 'archived' });
|
|
405
|
+
const d1 = await archiveQueue.dequeue();
|
|
406
|
+
await archiveQueue.complete(d1);
|
|
407
|
+
// Run maintenance to move to archive
|
|
408
|
+
await archiveQueue.maintenance();
|
|
409
|
+
const t2 = await archiveQueue.enqueue('foo', { foo: 'active' });
|
|
410
|
+
const d2 = await archiveQueue.dequeue();
|
|
411
|
+
await archiveQueue.complete(d2);
|
|
412
|
+
// t1 is archived, t2 is completed (active)
|
|
413
|
+
const result = await archiveQueue.waitForTasks([t1.id, t2.id], { timeout: 1000 });
|
|
414
|
+
expect(result.cancelled).toBe(false);
|
|
415
|
+
await archiveQueue.clear();
|
|
416
|
+
});
|
|
417
|
+
it('should return immediately for empty ids array', async () => {
|
|
418
|
+
const result = await queue.waitForTasks([], { timeout: 1000 });
|
|
419
|
+
expect(result.cancelled).toBe(false);
|
|
420
|
+
});
|
|
421
|
+
it('should wait for parent task to reach finalized state after child completion', async () => {
|
|
422
|
+
const childTag = `child-of-${crypto.randomUUID()}`;
|
|
423
|
+
const parent = await queue.enqueue('test', { value: 'parent' }, { completeAfterTags: [childTag] });
|
|
424
|
+
const dParent = await queue.dequeue();
|
|
425
|
+
// Spawn a child
|
|
426
|
+
await queue.enqueueMany([{ type: 'test', data: { value: 'child' }, parentId: parent.id, tags: [childTag] }], { transaction: undefined });
|
|
427
|
+
// Complete parent (it will move to Waiting because of completeAfterTags)
|
|
428
|
+
await queue.complete(dParent);
|
|
429
|
+
const checkParent = await queue.getTask(parent.id);
|
|
430
|
+
expect(checkParent?.status).toBe(TaskStatus.Waiting);
|
|
431
|
+
void (async () => {
|
|
432
|
+
await timeout(100);
|
|
433
|
+
const dChild = await queue.dequeue();
|
|
434
|
+
if (dChild) {
|
|
435
|
+
await queue.complete(dChild);
|
|
436
|
+
}
|
|
437
|
+
// Manual fan-in processing since we are in a test environment and might want immediate result
|
|
438
|
+
await queue.processPendingFanIn();
|
|
439
|
+
})();
|
|
440
|
+
await queue.waitForTasks([parent.id], { timeout: 2000 });
|
|
441
|
+
const finalParent = await queue.getTask(parent.id);
|
|
442
|
+
expect(finalParent?.status).toBe(TaskStatus.Completed);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
334
445
|
});
|
|
@@ -37,11 +37,12 @@ describe('Worker & Base Class Tests', () => {
|
|
|
37
37
|
});
|
|
38
38
|
// Wait until 2 tasks are processed
|
|
39
39
|
for (let i = 0; i < 50; i++) {
|
|
40
|
-
if (processed.length
|
|
40
|
+
if (processed.length == 2)
|
|
41
41
|
break;
|
|
42
42
|
await timeout(20);
|
|
43
43
|
}
|
|
44
44
|
token.set(); // Stop worker
|
|
45
|
+
await queue.waitForTasks([t1.id, t2.id], { interval: 50, timeout: 1000 });
|
|
45
46
|
expect(processed).toContain(1);
|
|
46
47
|
expect(processed).toContain(2);
|
|
47
48
|
expect(processed.length).toBe(2);
|
|
@@ -55,7 +56,14 @@ describe('Worker & Base Class Tests', () => {
|
|
|
55
56
|
queue.process({ cancellationSignal: token }, async () => {
|
|
56
57
|
throw new Error('worker error');
|
|
57
58
|
});
|
|
58
|
-
|
|
59
|
+
// Wait until task is processed (error recorded and status is Pending)
|
|
60
|
+
for (let i = 0; i < 50; i++) {
|
|
61
|
+
const updated = await queue.getTask(task.id);
|
|
62
|
+
if (updated?.tries == 1) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
await timeout(20);
|
|
66
|
+
}
|
|
59
67
|
token.set();
|
|
60
68
|
const updated = await queue.getTask(task.id);
|
|
61
69
|
expect(updated?.status).toBe(TaskStatus.Pending); // Should retry
|
|
@@ -71,7 +79,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
71
79
|
executed = true;
|
|
72
80
|
return TaskProcessResult.Complete();
|
|
73
81
|
});
|
|
74
|
-
await timeout
|
|
82
|
+
await queue.waitForTasks([task.id], { timeout: 5000 });
|
|
75
83
|
token.set();
|
|
76
84
|
expect(executed).toBe(true);
|
|
77
85
|
const updated = await queue.getTask(task.id);
|
|
@@ -83,17 +91,21 @@ describe('Worker & Base Class Tests', () => {
|
|
|
83
91
|
const processed = new Set();
|
|
84
92
|
queue.process({ cancellationSignal: token }, async (context) => {
|
|
85
93
|
processed.add(context.id);
|
|
86
|
-
if (context.id
|
|
94
|
+
if (context.id == tFail.id) {
|
|
87
95
|
return TaskProcessResult.Fail(new Error('explicit fail'));
|
|
88
96
|
}
|
|
89
|
-
if (context.id
|
|
97
|
+
if (context.id == tResched.id) {
|
|
90
98
|
return TaskProcessResult.RescheduleBy(1000);
|
|
91
99
|
}
|
|
92
100
|
return TaskProcessResult.Complete();
|
|
93
101
|
});
|
|
102
|
+
// Wait until tasks are processed (error/reschedule recorded and status is Pending)
|
|
94
103
|
for (let i = 0; i < 50; i++) {
|
|
95
|
-
|
|
104
|
+
const uFail = await queue.getTask(tFail.id);
|
|
105
|
+
const uResched = await queue.getTask(tResched.id);
|
|
106
|
+
if (uFail?.tries == 1 && uResched?.status == TaskStatus.Pending && (uResched?.scheduleTimestamp ?? 0) > Date.now()) {
|
|
96
107
|
break;
|
|
108
|
+
}
|
|
97
109
|
await timeout(20);
|
|
98
110
|
}
|
|
99
111
|
token.set();
|
|
@@ -129,11 +141,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
129
141
|
executed = true;
|
|
130
142
|
return TaskProcessResult.Complete();
|
|
131
143
|
});
|
|
132
|
-
|
|
133
|
-
if (executed)
|
|
134
|
-
break;
|
|
135
|
-
await timeout(20);
|
|
136
|
-
}
|
|
144
|
+
await queue.waitForTasks([task.id], { interval: 50, timeout: 1000 });
|
|
137
145
|
token.set();
|
|
138
146
|
expect(executed).toBe(true);
|
|
139
147
|
});
|
|
@@ -150,13 +158,13 @@ describe('Worker & Base Class Tests', () => {
|
|
|
150
158
|
const finalAttemptValues = [];
|
|
151
159
|
testQueue.process({ cancellationSignal: token }, async (context) => {
|
|
152
160
|
finalAttemptValues.push(context.isFinalAttempt);
|
|
153
|
-
if (context.attempt
|
|
161
|
+
if (context.attempt == 1) {
|
|
154
162
|
throw new Error('fail first attempt');
|
|
155
163
|
}
|
|
156
164
|
return TaskProcessResult.Complete();
|
|
157
165
|
});
|
|
158
166
|
for (let i = 0; i < 100; i++) {
|
|
159
|
-
if (finalAttemptValues.length
|
|
167
|
+
if (finalAttemptValues.length == 2)
|
|
160
168
|
break;
|
|
161
169
|
testQueue.notify();
|
|
162
170
|
await timeout(20);
|