@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.
Files changed (123) hide show
  1. package/application/application.d.ts +1 -1
  2. package/application/application.js +1 -1
  3. package/application/providers.d.ts +20 -2
  4. package/application/providers.js +34 -7
  5. package/audit/module.d.ts +5 -0
  6. package/audit/module.js +9 -1
  7. package/authentication/client/authentication.service.d.ts +1 -0
  8. package/authentication/client/authentication.service.js +3 -2
  9. package/authentication/server/module.d.ts +5 -0
  10. package/authentication/server/module.js +9 -1
  11. package/authentication/tests/authentication.api-controller.test.js +1 -1
  12. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  13. package/authentication/tests/authentication.client-service.test.js +1 -1
  14. package/circuit-breaker/circuit-breaker.d.ts +6 -4
  15. package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
  16. package/circuit-breaker/postgres/circuit-breaker.js +8 -5
  17. package/circuit-breaker/postgres/module.d.ts +1 -0
  18. package/circuit-breaker/postgres/module.js +5 -1
  19. package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
  20. package/document-management/server/configure.js +5 -1
  21. package/document-management/server/module.d.ts +1 -1
  22. package/document-management/server/module.js +1 -1
  23. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  24. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  25. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  26. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  27. package/examples/document-management/main.d.ts +1 -0
  28. package/examples/document-management/main.js +14 -11
  29. package/key-value-store/postgres/module.d.ts +1 -0
  30. package/key-value-store/postgres/module.js +5 -1
  31. package/lock/postgres/module.d.ts +1 -0
  32. package/lock/postgres/module.js +5 -1
  33. package/mail/module.d.ts +5 -1
  34. package/mail/module.js +11 -6
  35. package/module/modules/web-server.module.js +2 -3
  36. package/notification/server/module.d.ts +1 -0
  37. package/notification/server/module.js +5 -1
  38. package/notification/tests/notification-api.test.js +5 -1
  39. package/notification/tests/notification-flow.test.js +8 -5
  40. package/orm/decorators.d.ts +22 -5
  41. package/orm/decorators.js +10 -1
  42. package/orm/server/bootstrap.d.ts +11 -0
  43. package/orm/server/bootstrap.js +31 -0
  44. package/orm/server/drizzle/schema-converter.d.ts +3 -1
  45. package/orm/server/drizzle/schema-converter.js +85 -56
  46. package/orm/server/encryption.d.ts +0 -1
  47. package/orm/server/encryption.js +1 -4
  48. package/orm/server/extension.d.ts +14 -0
  49. package/orm/server/extension.js +27 -0
  50. package/orm/server/index.d.ts +3 -6
  51. package/orm/server/index.js +3 -6
  52. package/orm/server/migration.d.ts +18 -0
  53. package/orm/server/migration.js +58 -0
  54. package/orm/server/repository.d.ts +2 -1
  55. package/orm/server/repository.js +19 -9
  56. package/orm/server/transaction.d.ts +6 -10
  57. package/orm/server/transaction.js +25 -26
  58. package/orm/server/transactional.js +3 -3
  59. package/orm/tests/database-extension.test.js +63 -0
  60. package/orm/tests/database-migration.test.js +83 -0
  61. package/orm/tests/encryption.test.js +3 -4
  62. package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
  63. package/orm/tests/repository-compound-primary-key.test.js +234 -0
  64. package/orm/tests/schema-generation.test.d.ts +1 -0
  65. package/orm/tests/schema-generation.test.js +52 -5
  66. package/orm/utils.d.ts +17 -2
  67. package/orm/utils.js +49 -1
  68. package/package.json +5 -4
  69. package/rate-limit/postgres/module.d.ts +1 -0
  70. package/rate-limit/postgres/module.js +5 -1
  71. package/reflection/decorator-data.js +11 -12
  72. package/task-queue/README.md +2 -10
  73. package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
  74. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +250 -89
  75. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  76. package/task-queue/postgres/module.d.ts +1 -0
  77. package/task-queue/postgres/module.js +6 -1
  78. package/task-queue/postgres/schemas.d.ts +15 -6
  79. package/task-queue/postgres/schemas.js +4 -3
  80. package/task-queue/postgres/task-queue.d.ts +18 -15
  81. package/task-queue/postgres/task-queue.js +797 -499
  82. package/task-queue/postgres/task.model.d.ts +20 -9
  83. package/task-queue/postgres/task.model.js +65 -39
  84. package/task-queue/task-context.d.ts +12 -7
  85. package/task-queue/task-context.js +8 -6
  86. package/task-queue/task-queue.d.ts +364 -43
  87. package/task-queue/task-queue.js +153 -41
  88. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  89. package/task-queue/tests/coverage-branch.test.js +395 -0
  90. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  91. package/task-queue/tests/coverage-enhancement.test.js +150 -0
  92. package/task-queue/tests/dag.test.d.ts +1 -0
  93. package/task-queue/tests/dag.test.js +188 -0
  94. package/task-queue/tests/dependencies.test.js +165 -47
  95. package/task-queue/tests/enqueue-batch.test.d.ts +1 -0
  96. package/task-queue/tests/enqueue-batch.test.js +125 -0
  97. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  98. package/task-queue/tests/fan-out-spawning.test.js +94 -0
  99. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  100. package/task-queue/tests/idempotent-replacement.test.js +114 -0
  101. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  102. package/task-queue/tests/missing-idempotent-tasks.test.js +39 -0
  103. package/task-queue/tests/queue.test.js +294 -49
  104. package/task-queue/tests/shutdown.test.d.ts +1 -0
  105. package/task-queue/tests/shutdown.test.js +41 -0
  106. package/task-queue/tests/transactions.test.d.ts +1 -0
  107. package/task-queue/tests/transactions.test.js +47 -0
  108. package/task-queue/tests/worker.test.js +63 -15
  109. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  110. package/task-queue/tests/zombie-parent.test.js +45 -0
  111. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  112. package/task-queue/tests/zombie-recovery.test.js +51 -0
  113. package/test5.js +5 -5
  114. package/testing/integration-setup.d.ts +4 -4
  115. package/testing/integration-setup.js +56 -29
  116. package/text/localization.service.js +2 -2
  117. package/utils/file-reader.js +1 -2
  118. package/utils/timing.d.ts +2 -2
  119. package/task-queue/postgres/drizzle/0000_simple_invisible_woman.sql +0 -74
  120. package/task-queue/tests/complex.test.js +0 -306
  121. package/task-queue/tests/extensive-dependencies.test.js +0 -234
  122. /package/{task-queue/tests/complex.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
  123. /package/{task-queue/tests/extensive-dependencies.test.d.ts → orm/tests/database-migration.test.d.ts} +0 -0
@@ -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 { cancelableTimeout } from '../utils/timing.js';
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 waiting to be processed.
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 is scheduled to be processed in the future when all children have completed.
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 failed and will not be retried.
132
+ * The task has finished execution but is waiting for its completion dependencies (children) to finish.
105
133
  */
106
- Dead: 'dead',
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
- export const DependencyJoinMode = defineEnum('DependencyJoinMode', {
109
- And: 'and',
110
- Or: 'or',
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 in the background until the cancellation signal is triggered.
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
- * Starts processing tasks with the provided worker function in the foreground until the cancellation signal is triggered.
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
- 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
- })();
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) && isTaskActive) {
261
+ if (isDefined(result) && isTaskActiveObj.value) {
189
262
  switch (result.payload.action) {
190
263
  case 'complete':
191
- context.logger.verbose(`Completing task`);
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(`Failing task`);
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(`Rescheduling task`);
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(`Unsupported task result action.`);
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 {};