@tstdl/base 0.93.140 → 0.93.142
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/application/application.d.ts +1 -1
- package/application/application.js +1 -1
- package/application/providers.d.ts +20 -2
- package/application/providers.js +34 -7
- package/audit/module.d.ts +5 -0
- package/audit/module.js +9 -1
- package/authentication/client/authentication.service.d.ts +1 -0
- package/authentication/client/authentication.service.js +3 -2
- package/authentication/server/module.d.ts +5 -0
- package/authentication/server/module.js +9 -1
- package/authentication/tests/authentication.api-controller.test.js +1 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/circuit-breaker/circuit-breaker.d.ts +6 -4
- package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
- package/circuit-breaker/postgres/circuit-breaker.js +8 -5
- package/circuit-breaker/postgres/module.d.ts +1 -0
- package/circuit-breaker/postgres/module.js +5 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
- package/document-management/server/configure.js +5 -1
- package/document-management/server/module.d.ts +1 -1
- package/document-management/server/module.js +1 -1
- package/document-management/server/services/document-management-ancillary.service.js +1 -1
- package/document-management/tests/ai-config-hierarchy.test.js +0 -5
- package/document-management/tests/document-management-ai-overrides.test.js +0 -1
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
- package/examples/document-management/main.d.ts +1 -0
- package/examples/document-management/main.js +14 -11
- package/key-value-store/postgres/module.d.ts +1 -0
- package/key-value-store/postgres/module.js +5 -1
- package/lock/postgres/module.d.ts +1 -0
- package/lock/postgres/module.js +5 -1
- package/mail/module.d.ts +5 -1
- package/mail/module.js +11 -6
- package/module/modules/web-server.module.js +2 -3
- package/notification/server/module.d.ts +1 -0
- package/notification/server/module.js +5 -1
- package/notification/tests/notification-api.test.js +5 -1
- package/notification/tests/notification-flow.test.js +8 -5
- package/orm/decorators.d.ts +22 -5
- package/orm/decorators.js +10 -1
- package/orm/server/bootstrap.d.ts +11 -0
- package/orm/server/bootstrap.js +31 -0
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +85 -56
- package/orm/server/encryption.d.ts +0 -1
- package/orm/server/encryption.js +1 -4
- package/orm/server/extension.d.ts +14 -0
- package/orm/server/extension.js +27 -0
- package/orm/server/index.d.ts +3 -6
- package/orm/server/index.js +3 -6
- package/orm/server/migration.d.ts +18 -0
- package/orm/server/migration.js +58 -0
- package/orm/server/repository.d.ts +2 -1
- package/orm/server/repository.js +19 -9
- package/orm/server/transaction.d.ts +6 -10
- package/orm/server/transaction.js +25 -26
- package/orm/server/transactional.js +3 -3
- package/orm/tests/database-extension.test.js +63 -0
- package/orm/tests/database-migration.test.js +83 -0
- package/orm/tests/encryption.test.js +3 -4
- package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
- package/orm/tests/repository-compound-primary-key.test.js +234 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +52 -5
- package/orm/utils.d.ts +17 -2
- package/orm/utils.js +49 -1
- package/package.json +5 -4
- package/rate-limit/postgres/module.d.ts +1 -0
- package/rate-limit/postgres/module.js +5 -1
- package/reflection/decorator-data.js +11 -12
- package/task-queue/README.md +2 -10
- package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +250 -89
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.d.ts +1 -0
- package/task-queue/postgres/module.js +6 -1
- package/task-queue/postgres/schemas.d.ts +15 -6
- package/task-queue/postgres/schemas.js +4 -3
- package/task-queue/postgres/task-queue.d.ts +18 -15
- package/task-queue/postgres/task-queue.js +797 -499
- package/task-queue/postgres/task.model.d.ts +20 -9
- package/task-queue/postgres/task.model.js +65 -39
- package/task-queue/task-context.d.ts +12 -7
- package/task-queue/task-context.js +8 -6
- package/task-queue/task-queue.d.ts +364 -43
- package/task-queue/task-queue.js +153 -41
- package/task-queue/tests/coverage-branch.test.d.ts +1 -0
- package/task-queue/tests/coverage-branch.test.js +395 -0
- package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
- package/task-queue/tests/coverage-enhancement.test.js +150 -0
- package/task-queue/tests/dag.test.d.ts +1 -0
- package/task-queue/tests/dag.test.js +188 -0
- package/task-queue/tests/dependencies.test.js +165 -47
- package/task-queue/tests/enqueue-batch.test.d.ts +1 -0
- package/task-queue/tests/enqueue-batch.test.js +125 -0
- package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
- package/task-queue/tests/fan-out-spawning.test.js +94 -0
- package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
- package/task-queue/tests/idempotent-replacement.test.js +114 -0
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
- package/task-queue/tests/missing-idempotent-tasks.test.js +39 -0
- package/task-queue/tests/queue.test.js +294 -49
- package/task-queue/tests/shutdown.test.d.ts +1 -0
- package/task-queue/tests/shutdown.test.js +41 -0
- package/task-queue/tests/transactions.test.d.ts +1 -0
- package/task-queue/tests/transactions.test.js +47 -0
- package/task-queue/tests/worker.test.js +63 -15
- package/task-queue/tests/zombie-parent.test.d.ts +1 -0
- package/task-queue/tests/zombie-parent.test.js +45 -0
- package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
- package/task-queue/tests/zombie-recovery.test.js +51 -0
- package/test5.js +5 -5
- package/testing/integration-setup.d.ts +4 -4
- package/testing/integration-setup.js +56 -29
- package/text/localization.service.js +2 -2
- package/utils/file-reader.js +1 -2
- package/utils/timing.d.ts +2 -2
- package/task-queue/postgres/drizzle/0000_simple_invisible_woman.sql +0 -74
- package/task-queue/tests/complex.test.js +0 -306
- package/task-queue/tests/extensive-dependencies.test.js +0 -234
- /package/{task-queue/tests/complex.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
- /package/{task-queue/tests/extensive-dependencies.test.d.ts → orm/tests/database-migration.test.d.ts} +0 -0
package/task-queue/task-queue.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
|
|
1
2
|
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
3
|
if (value !== null && value !== void 0) {
|
|
3
4
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
@@ -55,67 +56,131 @@ import { inject, injectArgument, Injector } from '../injector/index.js';
|
|
|
55
56
|
import { Logger } from '../logger/logger.js';
|
|
56
57
|
import { Transactional } from '../orm/server/transactional.js';
|
|
57
58
|
import { currentTimestamp } from '../utils/date-time.js';
|
|
58
|
-
import {
|
|
59
|
-
import { isDefined, isString, isUndefined } from '../utils/type-guards.js';
|
|
59
|
+
import { isDefined, isError, isString } from '../utils/type-guards.js';
|
|
60
60
|
import { millisecondsPerDay, millisecondsPerMinute, millisecondsPerSecond } from '../utils/units.js';
|
|
61
61
|
import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
|
|
62
62
|
import { TaskContext } from './task-context.js';
|
|
63
|
+
/**
|
|
64
|
+
* Represents the result of processing a task.
|
|
65
|
+
* @template Result The type of the result data.
|
|
66
|
+
*/
|
|
63
67
|
export class TaskProcessResult {
|
|
64
68
|
payload;
|
|
65
69
|
constructor(payload) {
|
|
66
70
|
this.payload = payload;
|
|
67
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a successful process result.
|
|
74
|
+
* @param result The optional result data.
|
|
75
|
+
*/
|
|
68
76
|
static Complete(result) {
|
|
69
77
|
return new TaskProcessResult({ action: 'complete', result });
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Creates a failed process result.
|
|
81
|
+
* @param error The error that occurred.
|
|
82
|
+
* @param fatal Whether the error is fatal and the task should not be retried.
|
|
83
|
+
*/
|
|
71
84
|
static Fail(error, fatal = false) {
|
|
72
85
|
return new TaskProcessResult({ action: 'fail', error, fatal });
|
|
73
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates a result that reschedules the task to a specific timestamp.
|
|
89
|
+
* @param timestamp The timestamp to reschedule to.
|
|
90
|
+
*/
|
|
74
91
|
static RescheduleTo(timestamp) {
|
|
75
92
|
return new TaskProcessResult({ action: 'reschedule', timestamp });
|
|
76
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Creates a result that reschedules the task by a specific number of milliseconds.
|
|
96
|
+
* @param milliseconds The number of milliseconds to reschedule by.
|
|
97
|
+
*/
|
|
77
98
|
static RescheduleBy(milliseconds) {
|
|
78
99
|
const timestamp = currentTimestamp() + milliseconds;
|
|
79
100
|
return this.RescheduleTo(timestamp);
|
|
80
101
|
}
|
|
81
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Represents the status of a task in the queue.
|
|
105
|
+
*/
|
|
82
106
|
export const TaskStatus = defineEnum('TaskStatus', {
|
|
83
107
|
/**
|
|
84
|
-
* The task is
|
|
108
|
+
* The task is ready to be processed and is waiting for a worker.
|
|
85
109
|
*/
|
|
86
110
|
Pending: 'pending',
|
|
87
111
|
/**
|
|
88
|
-
* The task is currently being processed.
|
|
112
|
+
* The task is currently being processed by a worker.
|
|
89
113
|
*/
|
|
90
114
|
Running: 'running',
|
|
91
115
|
/**
|
|
92
|
-
* The task has been completed successfully.
|
|
116
|
+
* The task has been completed successfully and all its lifecycle requirements met.
|
|
93
117
|
*/
|
|
94
118
|
Completed: 'completed',
|
|
95
119
|
/**
|
|
96
|
-
* The task has been cancelled and will not be processed.
|
|
120
|
+
* The task has been cancelled and will not be processed further.
|
|
97
121
|
*/
|
|
98
122
|
Cancelled: 'cancelled',
|
|
99
123
|
/**
|
|
100
|
-
* The task
|
|
124
|
+
* The task has failed and will not be retried.
|
|
125
|
+
*/
|
|
126
|
+
Dead: 'dead',
|
|
127
|
+
/**
|
|
128
|
+
* The task is waiting for its pre-execution (schedule) dependencies to be met.
|
|
101
129
|
*/
|
|
102
130
|
Waiting: 'waiting',
|
|
103
131
|
/**
|
|
104
|
-
* The task has
|
|
132
|
+
* The task has finished execution but is waiting for its completion dependencies (children) to finish.
|
|
105
133
|
*/
|
|
106
|
-
|
|
134
|
+
WaitingChildren: 'waiting-children',
|
|
135
|
+
/**
|
|
136
|
+
* The task has been manually paused and will not be dequeued until resumed.
|
|
137
|
+
*/
|
|
138
|
+
Paused: 'paused',
|
|
139
|
+
/**
|
|
140
|
+
* The task has failed but has remaining attempts and is waiting for its next scheduled attempt.
|
|
141
|
+
*/
|
|
142
|
+
Retrying: 'retrying',
|
|
143
|
+
/**
|
|
144
|
+
* The task was forcefully terminated because its execution time exceeded maxExecutionTime.
|
|
145
|
+
*/
|
|
146
|
+
TimedOut: 'timed-out',
|
|
147
|
+
/**
|
|
148
|
+
* The task expired in the queue before it could be picked up by a worker.
|
|
149
|
+
*/
|
|
150
|
+
Expired: 'expired',
|
|
151
|
+
/**
|
|
152
|
+
* The task was never attempted because one of its prerequisites failed or was cancelled.
|
|
153
|
+
*/
|
|
154
|
+
Skipped: 'skipped',
|
|
155
|
+
/**
|
|
156
|
+
* The task was abandoned by a worker and has exhausted all retry attempts.
|
|
157
|
+
*/
|
|
158
|
+
Orphaned: 'orphaned',
|
|
107
159
|
});
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Represents the type of dependency between tasks.
|
|
162
|
+
*/
|
|
163
|
+
export const TaskDependencyType = defineEnum('TaskDependencyType', {
|
|
164
|
+
/**
|
|
165
|
+
* The task should only be scheduled after the dependency has reached a specific status.
|
|
166
|
+
*/
|
|
167
|
+
Schedule: 'schedule',
|
|
168
|
+
/**
|
|
169
|
+
* The task is only considered complete after the dependency has reached a specific status.
|
|
170
|
+
*/
|
|
171
|
+
Complete: 'complete',
|
|
172
|
+
/**
|
|
173
|
+
* The task is only considered complete after the child task has reached a specific status.
|
|
174
|
+
*/
|
|
175
|
+
Child: 'child',
|
|
111
176
|
});
|
|
177
|
+
/** Default priority for tasks. */
|
|
112
178
|
export const defaultTaskPriority = 1000;
|
|
113
179
|
export const defaultQueueConfig = {
|
|
114
180
|
visibilityTimeout: millisecondsPerMinute * 5,
|
|
115
181
|
maxExecutionTime: millisecondsPerMinute * 60,
|
|
116
182
|
maxTries: 3,
|
|
117
183
|
retention: 30 * millisecondsPerDay,
|
|
118
|
-
globalConcurrency: null,
|
|
119
184
|
circuitBreakerThreshold: 5,
|
|
120
185
|
circuitBreakerResetTimeout: 30 * millisecondsPerSecond,
|
|
121
186
|
retryDelayMinimum: 5 * millisecondsPerSecond,
|
|
@@ -129,15 +194,30 @@ export const defaultQueueConfig = {
|
|
|
129
194
|
rateInterval: 1000,
|
|
130
195
|
idempotencyWindow: millisecondsPerMinute * 60,
|
|
131
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* Abstract base class for task queues.
|
|
199
|
+
* @template Definitions The type map of task definitions.
|
|
200
|
+
*/
|
|
132
201
|
export class TaskQueue extends Transactional {
|
|
202
|
+
#activeTasks = new Map();
|
|
203
|
+
#heartbeatLoopRunning = false;
|
|
133
204
|
injector = inject(Injector);
|
|
134
205
|
config = this.transactionalContextData ?? (() => { const arg = injectArgument(this); return isString(arg) ? { namespace: arg } : arg; })();
|
|
135
206
|
logger = inject(Logger, TaskQueue.name).with({ namespace: this.config.namespace });
|
|
207
|
+
get namespace() {
|
|
208
|
+
return this.config.namespace;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Starts a new batch operation for enqueuing multiple tasks.
|
|
212
|
+
* @returns A TaskQueueEnqueueBatch instance.
|
|
213
|
+
*/
|
|
136
214
|
batch() {
|
|
137
215
|
return new TaskQueueEnqueueBatch(this);
|
|
138
216
|
}
|
|
139
217
|
/**
|
|
140
|
-
* Starts processing tasks with the provided worker function
|
|
218
|
+
* Starts processing tasks with the provided worker function.
|
|
219
|
+
* @param options Concurrency, cancellation signal, task types, and forceDequeue flag.
|
|
220
|
+
* @param handler The worker function to process tasks.
|
|
141
221
|
*/
|
|
142
222
|
process({ concurrency = 1, cancellationSignal, types, forceDequeue }, handler) {
|
|
143
223
|
const promises = [];
|
|
@@ -151,7 +231,10 @@ export class TaskQueue extends Transactional {
|
|
|
151
231
|
return this.config;
|
|
152
232
|
}
|
|
153
233
|
/**
|
|
154
|
-
*
|
|
234
|
+
* Internal method to process tasks using a worker function.
|
|
235
|
+
* @param cancellationSignal A signal to stop processing tasks.
|
|
236
|
+
* @param handler The worker function.
|
|
237
|
+
* @param options Dequeue options.
|
|
155
238
|
*/
|
|
156
239
|
async processWorker(cancellationSignal, handler, options) {
|
|
157
240
|
for await (const task of this.getConsumer(cancellationSignal, options)) {
|
|
@@ -159,54 +242,50 @@ export class TaskQueue extends Transactional {
|
|
|
159
242
|
try {
|
|
160
243
|
const taskToken = __addDisposableResource(env_1, cancellationSignal.fork(), false);
|
|
161
244
|
const context = new TaskContext(this, task, taskToken, this.logger.with({ type: task.type }));
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
})();
|
|
245
|
+
const isTaskActiveObj = { value: true };
|
|
246
|
+
this.#activeTasks.set(task.id, {
|
|
247
|
+
task: task,
|
|
248
|
+
taskToken,
|
|
249
|
+
logger: context.logger,
|
|
250
|
+
isTaskActive: isTaskActiveObj,
|
|
251
|
+
});
|
|
252
|
+
if (!this.#heartbeatLoopRunning) {
|
|
253
|
+
void this.#runHeartbeatLoop();
|
|
254
|
+
}
|
|
255
|
+
context.logger.verbose('Processing task');
|
|
183
256
|
try {
|
|
184
257
|
if (taskToken.isSet) {
|
|
185
258
|
throw new Error('Task cancelled before start');
|
|
186
259
|
}
|
|
187
260
|
const result = await handler(context);
|
|
188
|
-
if (isDefined(result) &&
|
|
261
|
+
if (isDefined(result) && isTaskActiveObj.value) {
|
|
189
262
|
switch (result.payload.action) {
|
|
190
263
|
case 'complete':
|
|
191
|
-
context.logger.verbose(
|
|
264
|
+
context.logger.verbose('Completing task');
|
|
192
265
|
await this.complete(task, { result: result.payload.result });
|
|
193
266
|
break;
|
|
194
267
|
case 'fail':
|
|
195
|
-
context.logger.verbose(
|
|
268
|
+
context.logger.verbose('Failing task');
|
|
196
269
|
await this.fail(task, result.payload.error, { fatal: result.payload.fatal });
|
|
197
270
|
break;
|
|
198
271
|
case 'reschedule':
|
|
199
|
-
context.logger.verbose(
|
|
272
|
+
context.logger.verbose('Rescheduling task');
|
|
200
273
|
await this.reschedule(task.id, result.payload.timestamp);
|
|
201
274
|
break;
|
|
202
275
|
default:
|
|
203
|
-
throw new Error(
|
|
276
|
+
throw new Error('Unsupported task result action.');
|
|
204
277
|
}
|
|
205
278
|
}
|
|
206
279
|
}
|
|
207
280
|
catch (error) {
|
|
208
281
|
context.logger.error('Error processing task', error);
|
|
209
282
|
await this.fail(task, error);
|
|
283
|
+
if (isError(error) && ((error.message === 'Task cancelled before start') || (error.message == 'Unsupported task result action.'))) {
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
this.#activeTasks.delete(task.id);
|
|
210
289
|
}
|
|
211
290
|
}
|
|
212
291
|
catch (e_1) {
|
|
@@ -218,4 +297,37 @@ export class TaskQueue extends Transactional {
|
|
|
218
297
|
}
|
|
219
298
|
}
|
|
220
299
|
}
|
|
300
|
+
async #runHeartbeatLoop() {
|
|
301
|
+
if (this.#heartbeatLoopRunning) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.#heartbeatLoopRunning = true;
|
|
305
|
+
try {
|
|
306
|
+
while (this.#activeTasks.size > 0) {
|
|
307
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(this.visibilityTimeout / 2, 5000)));
|
|
308
|
+
if (this.#activeTasks.size === 0) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
const entries = Array.from(this.#activeTasks.values());
|
|
312
|
+
const tasks = entries.map((e) => e.task);
|
|
313
|
+
try {
|
|
314
|
+
const touchedIds = await this.touchMany(tasks);
|
|
315
|
+
const touchedSet = new Set(touchedIds);
|
|
316
|
+
for (const entry of entries) {
|
|
317
|
+
if (entry.taskToken.isUnset && !touchedSet.has(entry.task.id)) {
|
|
318
|
+
entry.logger.warn(`Task lost lease. Aborting.`);
|
|
319
|
+
entry.isTaskActive.value = false;
|
|
320
|
+
entry.taskToken.set();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
this.logger.error('Error touching tasks', error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
this.#heartbeatLoopRunning = false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
221
333
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|