@tstdl/base 0.93.101 → 0.93.103

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.
@@ -16,6 +16,7 @@ let InAppNotification = class InAppNotification extends TenantBaseEntity {
16
16
  static entityName = 'InAppNotification';
17
17
  userId;
18
18
  logId;
19
+ // TODO: anpinnen?
19
20
  readTimestamp;
20
21
  archiveTimestamp;
21
22
  };
@@ -5,6 +5,10 @@ export declare class S3ObjectStorageProviderConfig {
5
5
  * S3 endpoint
6
6
  */
7
7
  endpoint: string;
8
+ /**
9
+ * S3 Region
10
+ */
11
+ region?: string;
8
12
  /**
9
13
  * S3 bucket, use a single bucket for all modules (which will become transparent key prefixes)
10
14
  *
@@ -18,6 +18,10 @@ export class S3ObjectStorageProviderConfig {
18
18
  * S3 endpoint
19
19
  */
20
20
  endpoint;
21
+ /**
22
+ * S3 Region
23
+ */
24
+ region;
21
25
  /**
22
26
  * S3 bucket, use a single bucket for all modules (which will become transparent key prefixes)
23
27
  *
@@ -50,6 +54,7 @@ let S3ObjectStorageProvider = class S3ObjectStorageProvider extends ObjectStorag
50
54
  }
51
55
  this.client = new Client({
52
56
  endPoint: hostname,
57
+ region: config.region,
53
58
  port: (port.length > 0) ? parseInt(port, 10) : undefined,
54
59
  useSSL: protocol == 'https:',
55
60
  accessKey: config.accessKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.101",
3
+ "version": "0.93.103",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -2,7 +2,7 @@ import type { CancellationSignal, CancellationToken } from '../cancellation/inde
2
2
  import type { Logger } from '../logger/index.js';
3
3
  import type { Transaction } from '../orm/server/index.js';
4
4
  import { TaskQueue, type EnqueueManyItem, type EnqueueOptions } from './task-queue.js';
5
- import type { TaskData, TaskDefinitionMap, TaskOfType, TaskResult, TaskState, TaskTypes, TasksStates } from './types.js';
5
+ import type { TaskData, TaskDefinitionMap, TaskOfType, TaskResult, TaskState, TaskTypes } from './types.js';
6
6
  export declare class TaskContext<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> {
7
7
  #private;
8
8
  constructor(queue: TaskQueue<Definitions>, task: TaskOfType<Definitions, Type>, signal: CancellationToken, logger: Logger);
@@ -43,16 +43,3 @@ export declare class TaskContext<Definitions extends TaskDefinitionMap, Type ext
43
43
  transaction?: Transaction;
44
44
  }): Promise<void>;
45
45
  }
46
- export declare class BatchTaskContext<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> {
47
- #private;
48
- constructor(queue: TaskQueue<Definitions>, tasks: TaskOfType<Definitions, Type>[], signal: CancellationToken, logger: Logger);
49
- get tasks(): TaskOfType<Definitions, Type>[];
50
- get signal(): CancellationSignal;
51
- get logger(): Logger;
52
- for<Type extends TaskTypes<Definitions>>(task: TaskOfType<Definitions, Type>): TaskContext<Definitions, Type>;
53
- checkpointAll(options?: {
54
- progresses?: number[];
55
- states?: TasksStates<TaskOfType<Definitions, Type>[]>;
56
- transaction?: Transaction;
57
- }): Promise<void>;
58
- }
@@ -92,33 +92,3 @@ export class TaskContext {
92
92
  this.#signal.set();
93
93
  }
94
94
  }
95
- export class BatchTaskContext {
96
- #queue;
97
- #tasks;
98
- #logger;
99
- #signal;
100
- constructor(queue, tasks, signal, logger) {
101
- this.#queue = queue;
102
- this.#tasks = tasks;
103
- this.#signal = signal;
104
- this.#logger = logger.fork('Batch');
105
- }
106
- get tasks() {
107
- return this.#tasks;
108
- }
109
- get signal() {
110
- return this.#signal.signal;
111
- }
112
- get logger() {
113
- return this.#logger;
114
- }
115
- for(task) {
116
- return new TaskContext(this.#queue, task, this.#signal, this.#logger);
117
- }
118
- async checkpointAll(options) {
119
- const validIds = await this.#queue.touchMany(this.#tasks, options);
120
- if (validIds.length < this.#tasks.length) {
121
- this.#logger.warn(`${this.#tasks.length - validIds.length} tasks in batch lost their lease during checkpoint`);
122
- }
123
- }
124
- }
@@ -6,7 +6,7 @@ import type { Transaction } from '../orm/server/transaction.js';
6
6
  import { Transactional } from '../orm/server/transactional.js';
7
7
  import type { OneOrMany, Record, UndefinableJson } from '../types/types.js';
8
8
  import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
9
- import type { ProcessBatchWorker, ProcessWorker, TaskData, TaskDefinitionMap, TaskOfType, TaskProcessResultPayload, TaskResult, TasksResults, TasksStates, TaskState, TaskTypes } from './types.js';
9
+ import type { ProcessWorker, TaskData, TaskDefinitionMap, TaskOfType, TaskProcessResultPayload, TaskResult, TasksResults, TasksStates, TaskState, TaskTypes } from './types.js';
10
10
  export declare class TaskProcessResult<Result = unknown> {
11
11
  readonly payload: TaskProcessResultPayload<Result>;
12
12
  private constructor();
@@ -299,16 +299,8 @@ export declare abstract class TaskQueue<Definitions extends TaskDefinitionMap =
299
299
  types?: Type[];
300
300
  forceDequeue?: boolean;
301
301
  }, handler: ProcessWorker<Definitions, Type>): void;
302
- processBatch<Type extends TaskTypes<Definitions>>({ batchSize, concurrency, cancellationSignal, types, forceDequeue }: {
303
- batchSize?: number;
304
- concurrency?: number;
305
- cancellationSignal: CancellationSignal;
306
- types?: Type[];
307
- forceDequeue?: boolean;
308
- }, handler: ProcessBatchWorker<Definitions, Type>): void;
309
302
  protected getTransactionalContextData(): QueueConfig & {
310
303
  namespace: string;
311
304
  };
312
305
  private processWorker;
313
- private processBatchWorker;
314
306
  }
@@ -2,13 +2,12 @@ import { defineEnum } from '../enumeration/enumeration.js';
2
2
  import { inject, injectArgument } from '../injector/index.js';
3
3
  import { Logger } from '../logger/logger.js';
4
4
  import { Transactional } from '../orm/server/transactional.js';
5
- import { createArray } from '../utils/array/array.js';
6
5
  import { currentTimestamp } from '../utils/date-time.js';
7
6
  import { cancelableTimeout } from '../utils/timing.js';
8
- import { isDefined, isString } from '../utils/type-guards.js';
7
+ import { isDefined, isString, isUndefined } from '../utils/type-guards.js';
9
8
  import { millisecondsPerDay, millisecondsPerMinute, millisecondsPerSecond } from '../utils/units.js';
10
9
  import { TaskQueueEnqueueBatch } from './enqueue-batch.js';
11
- import { BatchTaskContext } from './task-context.js';
10
+ import { TaskContext } from './task-context.js';
12
11
  export class TaskProcessResult {
13
12
  payload;
14
13
  constructor(payload) {
@@ -89,114 +88,64 @@ export class TaskQueue extends Transactional {
89
88
  void this.processWorker(cancellationSignal, handler, { types, forceDequeue });
90
89
  }
91
90
  }
92
- processBatch({ batchSize = 10, concurrency = 1, cancellationSignal, types, forceDequeue }, handler) {
93
- for (let i = 0; i < concurrency; i++) {
94
- void this.processBatchWorker(batchSize, cancellationSignal, handler, { types, forceDequeue });
95
- }
96
- }
97
91
  getTransactionalContextData() {
98
92
  return this.config;
99
93
  }
100
94
  async processWorker(cancellationSignal, handler, options) {
101
- await this.processBatchWorker(1, cancellationSignal, async (batchContext) => {
102
- const task = batchContext.tasks[0];
103
- const context = batchContext.for(task);
104
- const result = await handler(context);
105
- return [result];
106
- }, options);
107
- }
108
- async processBatchWorker(size, cancellationSignal, handler, options) {
109
- for await (const tasks of this.getBatchConsumer(size, cancellationSignal, options)) {
110
- const batchToken = cancellationSignal.createChild();
111
- const context = new BatchTaskContext(this, tasks, batchToken, this.logger);
112
- let activeTaskIds = new Set(tasks.map((t) => t.id));
113
- context.logger.verbose(`Processing batch of ${tasks.length}`);
95
+ for await (const task of this.getConsumer(cancellationSignal, options)) {
96
+ const taskToken = cancellationSignal.createChild();
97
+ const context = new TaskContext(this, task, taskToken, this.logger);
98
+ let isTaskActive = true;
99
+ context.logger.verbose(`Processing task`);
114
100
  void (async () => {
115
- while (batchToken.isUnset) {
116
- await cancelableTimeout(Math.min(this.visibilityTimeout / 2, 5000), batchToken);
117
- if (batchToken.isSet) {
101
+ while (taskToken.isUnset) {
102
+ await cancelableTimeout(Math.min(this.visibilityTimeout / 2, 5000), taskToken);
103
+ if (taskToken.isSet) {
118
104
  break;
119
105
  }
120
106
  try {
121
- const tasksToTouch = tasks.filter((t) => activeTaskIds.has(t.id));
122
- if (tasksToTouch.length > 0) {
123
- const touchedIds = await this.touchMany(tasksToTouch);
124
- if (touchedIds.length != tasksToTouch.length) {
125
- const lostCount = tasksToTouch.length - touchedIds.length;
126
- context.logger.warn(`Batch integrity compromised: ${lostCount} tasks lost lease. Aborting batch.`);
127
- activeTaskIds = new Set(touchedIds);
128
- batchToken.set();
129
- }
130
- else {
131
- activeTaskIds = new Set(touchedIds);
132
- }
133
- }
134
- if (activeTaskIds.size == 0 && batchToken.isUnset) {
135
- context.logger.warn(`All tasks in batch lost lease. Stopping worker.`);
136
- batchToken.set();
107
+ const touchedTask = await this.touch(task);
108
+ if (isUndefined(touchedTask)) {
109
+ context.logger.warn(`Task lost lease. Aborting.`);
110
+ isTaskActive = false;
111
+ taskToken.set();
137
112
  }
138
113
  }
139
114
  catch (error) {
140
- context.logger.error('Error touching tasks', error);
115
+ context.logger.error('Error touching task', error);
141
116
  }
142
117
  }
143
118
  })();
144
119
  try {
145
- if (batchToken.isSet) {
146
- throw new Error('Tasks cancelled before start');
120
+ if (taskToken.isSet) {
121
+ throw new Error('Task cancelled before start');
147
122
  }
148
- const results = await handler(context);
149
- if (isDefined(results)) {
150
- const completions = [];
151
- const failures = [];
152
- const reschedules = [];
153
- for (let i = 0; i < tasks.length; i++) {
154
- const task = tasks[i];
155
- const result = results[i];
156
- switch (result.payload.action) {
157
- case 'complete':
158
- completions.push({ task, result: result.payload.result });
159
- break;
160
- case 'fail':
161
- failures.push({ task, error: result.payload.error, fatal: result.payload.fatal });
162
- break;
163
- case 'reschedule':
164
- reschedules.push({ task, timestamp: result.payload.timestamp });
165
- break;
166
- default:
167
- throw new Error(`Unsupported task result action.`);
168
- }
169
- }
170
- if (completions.length > 0) {
171
- context.logger.verbose(`Completing ${completions.length} tasks`);
172
- await this.completeMany(completions.map((c) => c.task), { results: completions.map((c) => c.result) });
173
- }
174
- if (failures.length > 0) {
175
- context.logger.verbose(`Failing ${failures.length} tasks`);
176
- for (const item of failures) {
177
- await this.fail(item.task, item.error, { fatal: item.fatal });
178
- }
179
- }
180
- if (reschedules.length > 0) {
181
- context.logger.verbose(`Rescheduling ${reschedules.length} tasks`);
182
- const reschedulesByTimestamp = new Map();
183
- for (const item of reschedules) {
184
- const ids = reschedulesByTimestamp.get(item.timestamp) ?? [];
185
- ids.push(item.task.id);
186
- reschedulesByTimestamp.set(item.timestamp, ids);
187
- }
188
- for (const [timestamp, ids] of reschedulesByTimestamp) {
189
- await this.rescheduleMany(ids, timestamp);
190
- }
123
+ const result = await handler(context);
124
+ if (isDefined(result) && isTaskActive) {
125
+ switch (result.payload.action) {
126
+ case 'complete':
127
+ context.logger.verbose(`Completing task`);
128
+ await this.complete(task, { result: result.payload.result });
129
+ break;
130
+ case 'fail':
131
+ context.logger.verbose(`Failing task`);
132
+ await this.fail(task, result.payload.error, { fatal: result.payload.fatal });
133
+ break;
134
+ case 'reschedule':
135
+ context.logger.verbose(`Rescheduling task`);
136
+ await this.reschedule(task.id, result.payload.timestamp);
137
+ break;
138
+ default:
139
+ throw new Error(`Unsupported task result action.`);
191
140
  }
192
141
  }
193
142
  }
194
143
  catch (error) {
195
- context.logger.error('Error processing tasks', error);
196
- await this.failMany(tasks, createArray(tasks.length, () => error));
144
+ context.logger.error('Error processing task', error);
145
+ await this.fail(task, error);
197
146
  }
198
147
  finally {
199
- batchToken.set();
148
+ taskToken.set();
200
149
  }
201
150
  }
202
151
  }
@@ -61,27 +61,6 @@ describe('Worker & Base Class Tests', () => {
61
61
  expect(updated?.tries).toBe(1);
62
62
  expect(updated?.error?.message).toBe('worker error');
63
63
  });
64
- it('should process batch of tasks', async () => {
65
- await queue.enqueueMany([
66
- { type: 'batch', data: { v: 1 } },
67
- { type: 'batch', data: { v: 2 } },
68
- { type: 'batch', data: { v: 3 } },
69
- ]);
70
- const processedBatch = [];
71
- const token = new CancellationToken();
72
- queue.processBatch({ batchSize: 2, cancellationSignal: token }, async (context) => {
73
- expect(context.tasks.length).toBeLessThanOrEqual(2);
74
- context.tasks.forEach(t => processedBatch.push(t.data['v']));
75
- return context.tasks.map(() => TaskProcessResult.Complete());
76
- });
77
- for (let i = 0; i < 20; i++) {
78
- if (processedBatch.length === 3)
79
- break;
80
- await timeout(100);
81
- }
82
- token.set();
83
- expect(processedBatch.sort()).toEqual([1, 2, 3]);
84
- });
85
64
  it('should extend lease (heartbeat) during long processing', async () => {
86
65
  const task = await queue.enqueue('long', {});
87
66
  const token = new CancellationToken();
@@ -1,4 +1,4 @@
1
- import type { BatchTaskContext, TaskContext } from './task-context.js';
1
+ import type { TaskContext } from './task-context.js';
2
2
  import type { Task, TaskProcessResult } from './task-queue.js';
3
3
  export type TaskDefinition<Data = unknown, State = unknown, Result = unknown> = {
4
4
  data: Data;
@@ -39,10 +39,3 @@ export interface ProcessWorker<Definitions extends TaskDefinitionMap, Type exten
39
39
  */
40
40
  (context: TaskContext<Definitions, Type>): TaskProcessResult<Definitions[Type]['result']> | Promise<TaskProcessResult<Definitions[Type]['result']>>;
41
41
  }
42
- export interface ProcessBatchWorker<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> {
43
- /**
44
- * A worker function that processes a batch of tasks.
45
- * @param context The batch context providing tasks and helpers.
46
- */
47
- (context: BatchTaskContext<Definitions, Type>): TaskProcessResult<Definitions[Type]['result']>[] | Promise<TaskProcessResult<Definitions[Type]['result']>[]>;
48
- }