@tstdl/base 0.93.92 → 0.93.93
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/authentication/client/authentication.service.js +3 -2
- package/document-management/server/services/document-validation.service.js +5 -5
- package/document-management/server/services/document-workflow.service.js +2 -2
- package/orm/sqls/sqls.d.ts +6 -6
- package/package.json +2 -2
- package/task-queue/enqueue-batch.d.ts +16 -11
- package/task-queue/enqueue-batch.js +2 -2
- package/task-queue/index.d.ts +2 -1
- package/task-queue/index.js +2 -1
- package/task-queue/postgres/drizzle/{0000_thin_black_panther.sql → 0000_simple_invisible_woman.sql} +5 -5
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +11 -11
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.js +2 -2
- package/task-queue/postgres/schemas.d.ts +1 -1
- package/task-queue/postgres/schemas.js +2 -2
- package/task-queue/postgres/task-queue.d.ts +101 -47
- package/task-queue/postgres/task-queue.js +149 -139
- package/task-queue/postgres/task-queue.provider.d.ts +3 -4
- package/task-queue/postgres/task-queue.provider.js +2 -2
- package/task-queue/postgres/task.model.d.ts +5 -5
- package/task-queue/postgres/task.model.js +5 -5
- package/task-queue/provider.d.ts +2 -2
- package/task-queue/task-context.d.ts +34 -18
- package/task-queue/task-context.js +23 -13
- package/task-queue/task-queue.d.ts +160 -132
- package/task-queue/task-queue.js +8 -8
- package/task-queue/tests/complex.test.js +36 -29
- package/task-queue/tests/dependencies.test.js +17 -17
- package/task-queue/tests/enqueue-item.test.d.ts +1 -0
- package/task-queue/tests/enqueue-item.test.js +12 -0
- package/task-queue/tests/queue-generic.test.d.ts +1 -0
- package/task-queue/tests/queue-generic.test.js +8 -0
- package/task-queue/tests/queue.test.js +50 -50
- package/task-queue/tests/task-context.test.d.ts +1 -0
- package/task-queue/tests/task-context.test.js +7 -0
- package/task-queue/tests/task-union.test.d.ts +1 -0
- package/task-queue/tests/task-union.test.js +18 -0
- package/task-queue/tests/typing.test.d.ts +1 -0
- package/task-queue/tests/typing.test.js +9 -0
- package/task-queue/tests/worker.test.js +16 -16
- package/task-queue/types.d.ts +48 -0
- package/task-queue/types.js +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expectTypeOf, test } from 'vitest';
|
|
2
|
+
describe('Task Discriminated Union', () => {
|
|
3
|
+
test('Task should be a discriminated union based on definition map', () => {
|
|
4
|
+
// Should be a union
|
|
5
|
+
expectTypeOf().toBeObject();
|
|
6
|
+
// Discrimination check
|
|
7
|
+
const task = {};
|
|
8
|
+
if (task.type == 'test-task') {
|
|
9
|
+
expectTypeOf(task.data).toEqualTypeOf();
|
|
10
|
+
expectTypeOf(task.state).toEqualTypeOf();
|
|
11
|
+
expectTypeOf(task.result).toEqualTypeOf();
|
|
12
|
+
}
|
|
13
|
+
else if (task.type == 'other-task') {
|
|
14
|
+
expectTypeOf(task.data).toEqualTypeOf();
|
|
15
|
+
expectTypeOf(task.result).toEqualTypeOf();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, expectTypeOf, test } from 'vitest';
|
|
2
|
+
describe('TaskQueue Type Definitions', () => {
|
|
3
|
+
test('TaskDefinition and TaskDefinitionMap should be defined', () => {
|
|
4
|
+
expectTypeOf().toEqualTypeOf();
|
|
5
|
+
expectTypeOf().toEqualTypeOf();
|
|
6
|
+
expectTypeOf().toEqualTypeOf();
|
|
7
|
+
expectTypeOf().toEqualTypeOf();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
2
|
import { CancellationToken } from '../../cancellation/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { TaskProcessResult, TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
4
4
|
import { setupIntegrationTest } from '../../unit-test/index.js';
|
|
5
5
|
import { timeout } from '../../utils/timing.js';
|
|
6
6
|
describe('Worker & Base Class Tests', () => {
|
|
@@ -30,8 +30,8 @@ describe('Worker & Base Class Tests', () => {
|
|
|
30
30
|
const processed = [];
|
|
31
31
|
const token = new CancellationToken();
|
|
32
32
|
queue.process({ cancellationSignal: token }, async (context) => {
|
|
33
|
-
processed.push(context.data
|
|
34
|
-
return
|
|
33
|
+
processed.push(context.data['val']);
|
|
34
|
+
return TaskProcessResult.Complete();
|
|
35
35
|
});
|
|
36
36
|
// Wait until 2 tasks are processed
|
|
37
37
|
for (let i = 0; i < 20; i++) {
|
|
@@ -45,8 +45,8 @@ describe('Worker & Base Class Tests', () => {
|
|
|
45
45
|
expect(processed.length).toBe(2);
|
|
46
46
|
const check1 = await queue.getTask(t1.id);
|
|
47
47
|
const check2 = await queue.getTask(t2.id);
|
|
48
|
-
expect(check1?.status).toBe(
|
|
49
|
-
expect(check2?.status).toBe(
|
|
48
|
+
expect(check1?.status).toBe(TaskStatus.Completed);
|
|
49
|
+
expect(check2?.status).toBe(TaskStatus.Completed);
|
|
50
50
|
});
|
|
51
51
|
it('should handle errors in worker gracefully', async () => {
|
|
52
52
|
const task = await queue.enqueue('fail', {});
|
|
@@ -57,7 +57,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
57
57
|
await timeout(200);
|
|
58
58
|
token.set();
|
|
59
59
|
const updated = await queue.getTask(task.id);
|
|
60
|
-
expect(updated?.status).toBe(
|
|
60
|
+
expect(updated?.status).toBe(TaskStatus.Pending); // Should retry
|
|
61
61
|
expect(updated?.tries).toBe(1);
|
|
62
62
|
expect(updated?.error?.message).toBe('worker error');
|
|
63
63
|
});
|
|
@@ -71,8 +71,8 @@ describe('Worker & Base Class Tests', () => {
|
|
|
71
71
|
const token = new CancellationToken();
|
|
72
72
|
queue.processBatch({ batchSize: 2, cancellationSignal: token }, async (context) => {
|
|
73
73
|
expect(context.tasks.length).toBeLessThanOrEqual(2);
|
|
74
|
-
context.tasks.forEach(t => processedBatch.push(t.data
|
|
75
|
-
return context.tasks.map(() =>
|
|
74
|
+
context.tasks.forEach(t => processedBatch.push(t.data['v']));
|
|
75
|
+
return context.tasks.map(() => TaskProcessResult.Complete());
|
|
76
76
|
});
|
|
77
77
|
for (let i = 0; i < 20; i++) {
|
|
78
78
|
if (processedBatch.length === 3)
|
|
@@ -90,13 +90,13 @@ describe('Worker & Base Class Tests', () => {
|
|
|
90
90
|
// Simulate long work > visibilityTimeout (500ms)
|
|
91
91
|
await timeout(700);
|
|
92
92
|
executed = true;
|
|
93
|
-
return
|
|
93
|
+
return TaskProcessResult.Complete();
|
|
94
94
|
});
|
|
95
95
|
await timeout(1000);
|
|
96
96
|
token.set();
|
|
97
97
|
expect(executed).toBe(true);
|
|
98
98
|
const updated = await queue.getTask(task.id);
|
|
99
|
-
expect(updated?.status).toBe(
|
|
99
|
+
expect(updated?.status).toBe(TaskStatus.Completed);
|
|
100
100
|
});
|
|
101
101
|
it('should handle TaskResult actions (Fail, Reschedule)', async () => {
|
|
102
102
|
const tFail = await queue.enqueue('fail-action', {});
|
|
@@ -106,12 +106,12 @@ describe('Worker & Base Class Tests', () => {
|
|
|
106
106
|
queue.process({ cancellationSignal: token }, async (context) => {
|
|
107
107
|
processed.add(context.id);
|
|
108
108
|
if (context.id === tFail.id) {
|
|
109
|
-
return
|
|
109
|
+
return TaskProcessResult.Fail(new Error('explicit fail'));
|
|
110
110
|
}
|
|
111
111
|
if (context.id === tResched.id) {
|
|
112
|
-
return
|
|
112
|
+
return TaskProcessResult.RescheduleBy(1000);
|
|
113
113
|
}
|
|
114
|
-
return
|
|
114
|
+
return TaskProcessResult.Complete();
|
|
115
115
|
});
|
|
116
116
|
for (let i = 0; i < 20; i++) {
|
|
117
117
|
if (processed.size === 2)
|
|
@@ -120,10 +120,10 @@ describe('Worker & Base Class Tests', () => {
|
|
|
120
120
|
}
|
|
121
121
|
token.set();
|
|
122
122
|
const uFail = await queue.getTask(tFail.id);
|
|
123
|
-
expect(uFail?.status).toBe(
|
|
123
|
+
expect(uFail?.status).toBe(TaskStatus.Pending); // Retry
|
|
124
124
|
expect(uFail?.error?.message).toBe('explicit fail');
|
|
125
125
|
const uResched = await queue.getTask(tResched.id);
|
|
126
|
-
expect(uResched?.status).toBe(
|
|
126
|
+
expect(uResched?.status).toBe(TaskStatus.Pending);
|
|
127
127
|
expect(uResched?.scheduleTimestamp).toBeGreaterThan(Date.now());
|
|
128
128
|
});
|
|
129
129
|
it('should exercise TaskContext methods', async () => {
|
|
@@ -150,7 +150,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
150
150
|
const otherChildren = await context.spawnMany(otherQueue, [{ type: 'other', data: { x: 2 } }]);
|
|
151
151
|
expect(otherChildren[0]?.parentId).toBe(task.id);
|
|
152
152
|
executed = true;
|
|
153
|
-
return
|
|
153
|
+
return TaskProcessResult.Complete();
|
|
154
154
|
});
|
|
155
155
|
for (let i = 0; i < 20; i++) {
|
|
156
156
|
if (executed)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BatchTaskContext, TaskContext } from './task-context.js';
|
|
2
|
+
import type { Task, TaskProcessResult } from './task-queue.js';
|
|
3
|
+
export type TaskDefinition<Data = unknown, State = unknown, Result = unknown> = {
|
|
4
|
+
data: Data;
|
|
5
|
+
state: State;
|
|
6
|
+
result: Result;
|
|
7
|
+
};
|
|
8
|
+
export type TaskDefinitionMap<Definition extends Record<string, TaskDefinition> = Record<string, TaskDefinition>> = Definition;
|
|
9
|
+
export type TaskTypes<Definitions extends TaskDefinitionMap> = Extract<keyof Definitions, string>;
|
|
10
|
+
export type TaskOfType<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> = string extends TaskTypes<Definitions> ? Task<Definitions> & {
|
|
11
|
+
type: Type;
|
|
12
|
+
} : Extract<Task<Definitions>, {
|
|
13
|
+
type: Type;
|
|
14
|
+
}>;
|
|
15
|
+
export type TaskData<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> = Definitions[Type]['data'];
|
|
16
|
+
export type TaskState<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> = Definitions[Type]['state'];
|
|
17
|
+
export type TaskResult<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> = Definitions[Type]['result'];
|
|
18
|
+
export type TasksResults<Tasks extends Task<any>[]> = {
|
|
19
|
+
[I in keyof Tasks]: Tasks[I] extends Task<infer Definitions> ? TaskResult<Definitions, Tasks[I]['type']> : never;
|
|
20
|
+
};
|
|
21
|
+
export type TasksStates<Tasks extends Task<any>[]> = {
|
|
22
|
+
[I in keyof Tasks]: Tasks[I] extends Task<infer Definitions> ? TaskState<Definitions, Tasks[I]['type']> : never;
|
|
23
|
+
};
|
|
24
|
+
export type TaskProcessResultPayload<Result> = {
|
|
25
|
+
action: 'complete';
|
|
26
|
+
result: Result | undefined;
|
|
27
|
+
} | {
|
|
28
|
+
action: 'fail';
|
|
29
|
+
error: unknown;
|
|
30
|
+
fatal: boolean;
|
|
31
|
+
} | {
|
|
32
|
+
action: 'reschedule';
|
|
33
|
+
timestamp: number;
|
|
34
|
+
};
|
|
35
|
+
export interface ProcessWorker<Definitions extends TaskDefinitionMap, Type extends TaskTypes<Definitions>> {
|
|
36
|
+
/**
|
|
37
|
+
* A worker function that processes a single task.
|
|
38
|
+
* @param context The task context providing data, logger, and orchestration helpers.
|
|
39
|
+
*/
|
|
40
|
+
(context: TaskContext<Definitions, Type>): TaskProcessResult<Definitions[Type]['result']> | Promise<TaskProcessResult<Definitions[Type]['result']>>;
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|