@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
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
2
|
import { CancellationToken } from '../../cancellation/index.js';
|
|
3
|
+
import { runInInjectionContext } from '../../injector/index.js';
|
|
4
|
+
import { injectRepository } from '../../orm/server/index.js';
|
|
3
5
|
import { TaskProcessResult, TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
6
|
+
import { PostgresTask } from '../../task-queue/postgres/index.js';
|
|
4
7
|
import { setupIntegrationTest } from '../../testing/index.js';
|
|
5
8
|
import { timeout } from '../../utils/timing.js';
|
|
6
9
|
describe('Worker & Base Class Tests', () => {
|
|
7
10
|
let injector;
|
|
8
11
|
let queue;
|
|
9
12
|
let token;
|
|
13
|
+
let otherQueueName;
|
|
10
14
|
beforeAll(async () => {
|
|
11
15
|
({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
|
|
16
|
+
otherQueueName = `other-queue-${crypto.randomUUID()}`;
|
|
12
17
|
});
|
|
13
18
|
beforeEach(() => {
|
|
14
19
|
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
15
|
-
const queueName = `worker-queue-${
|
|
20
|
+
const queueName = `worker-queue-${crypto.randomUUID()}`;
|
|
16
21
|
queue = queueProvider.get(queueName, {
|
|
17
22
|
visibilityTimeout: 200, // Short visibility for testing lease loss
|
|
18
23
|
});
|
|
@@ -20,9 +25,14 @@ describe('Worker & Base Class Tests', () => {
|
|
|
20
25
|
});
|
|
21
26
|
afterEach(async () => {
|
|
22
27
|
token.set();
|
|
28
|
+
const repository = runInInjectionContext(injector, () => injectRepository(PostgresTask));
|
|
29
|
+
const namespace = queue.getTransactionalContextData().namespace;
|
|
30
|
+
// Clear foreign keys
|
|
31
|
+
await repository.updateManyByQuery({ namespace }, { parentId: null });
|
|
32
|
+
await repository.updateManyByQuery({ namespace: otherQueueName }, { parentId: null });
|
|
23
33
|
await queue.clear();
|
|
24
34
|
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
25
|
-
await queueProvider.get(
|
|
35
|
+
await queueProvider.get(otherQueueName).clear();
|
|
26
36
|
});
|
|
27
37
|
afterAll(async () => {
|
|
28
38
|
await injector?.dispose();
|
|
@@ -39,6 +49,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
39
49
|
for (let i = 0; i < 50; i++) {
|
|
40
50
|
if (processed.length == 2)
|
|
41
51
|
break;
|
|
52
|
+
queue.notify();
|
|
42
53
|
await timeout(20);
|
|
43
54
|
}
|
|
44
55
|
token.set(); // Stop worker
|
|
@@ -56,17 +67,18 @@ describe('Worker & Base Class Tests', () => {
|
|
|
56
67
|
queue.process({ cancellationSignal: token }, async () => {
|
|
57
68
|
throw new Error('worker error');
|
|
58
69
|
});
|
|
59
|
-
// Wait until task is processed (error recorded and status is
|
|
70
|
+
// Wait until task is processed (error recorded and status is Retrying)
|
|
60
71
|
for (let i = 0; i < 50; i++) {
|
|
61
72
|
const updated = await queue.getTask(task.id);
|
|
62
|
-
if (updated?.tries == 1) {
|
|
73
|
+
if (updated?.tries == 1 && updated.status == TaskStatus.Retrying) {
|
|
63
74
|
break;
|
|
64
75
|
}
|
|
76
|
+
queue.notify();
|
|
65
77
|
await timeout(20);
|
|
66
78
|
}
|
|
67
79
|
token.set();
|
|
68
80
|
const updated = await queue.getTask(task.id);
|
|
69
|
-
expect(updated?.status).toBe(TaskStatus.
|
|
81
|
+
expect(updated?.status).toBe(TaskStatus.Retrying); // Should retry
|
|
70
82
|
expect(updated?.tries).toBe(1);
|
|
71
83
|
expect(updated?.error?.message).toBe('worker error');
|
|
72
84
|
});
|
|
@@ -79,6 +91,7 @@ describe('Worker & Base Class Tests', () => {
|
|
|
79
91
|
executed = true;
|
|
80
92
|
return TaskProcessResult.Complete();
|
|
81
93
|
});
|
|
94
|
+
queue.notify();
|
|
82
95
|
await queue.waitForTasks([task.id], { timeout: 5000 });
|
|
83
96
|
token.set();
|
|
84
97
|
expect(executed).toBe(true);
|
|
@@ -99,18 +112,19 @@ describe('Worker & Base Class Tests', () => {
|
|
|
99
112
|
}
|
|
100
113
|
return TaskProcessResult.Complete();
|
|
101
114
|
});
|
|
102
|
-
// Wait until tasks are processed (error/reschedule recorded and status is Pending)
|
|
115
|
+
// Wait until tasks are processed (error/reschedule recorded and status is Retrying/Pending)
|
|
103
116
|
for (let i = 0; i < 50; i++) {
|
|
104
117
|
const uFail = await queue.getTask(tFail.id);
|
|
105
118
|
const uResched = await queue.getTask(tResched.id);
|
|
106
|
-
if (uFail?.tries == 1 && uResched?.status == TaskStatus.Pending && (uResched?.scheduleTimestamp ?? 0) > Date.now()) {
|
|
119
|
+
if (uFail?.tries == 1 && uFail.status == TaskStatus.Retrying && uResched?.status == TaskStatus.Pending && (uResched?.scheduleTimestamp ?? 0) > Date.now()) {
|
|
107
120
|
break;
|
|
108
121
|
}
|
|
122
|
+
queue.notify();
|
|
109
123
|
await timeout(20);
|
|
110
124
|
}
|
|
111
125
|
token.set();
|
|
112
126
|
const uFail = await queue.getTask(tFail.id);
|
|
113
|
-
expect(uFail?.status).toBe(TaskStatus.
|
|
127
|
+
expect(uFail?.status).toBe(TaskStatus.Retrying); // Retry
|
|
114
128
|
expect(uFail?.error?.message).toBe('explicit fail');
|
|
115
129
|
const uResched = await queue.getTask(tResched.id);
|
|
116
130
|
expect(uResched?.status).toBe(TaskStatus.Pending);
|
|
@@ -132,22 +146,48 @@ describe('Worker & Base Class Tests', () => {
|
|
|
132
146
|
const children = await context.spawnMany([{ type: 'child', data: { c: 2 } }]);
|
|
133
147
|
expect(children[0]?.parentId).toBe(task.id);
|
|
134
148
|
// Other queue spawn
|
|
135
|
-
const otherQueue = injector.resolve(TaskQueueProvider).get(
|
|
149
|
+
const otherQueue = injector.resolve(TaskQueueProvider).get(otherQueueName);
|
|
136
150
|
const otherChild = await context.spawn(otherQueue, 'other', { x: 1 });
|
|
137
|
-
expect(otherChild.
|
|
138
|
-
|
|
139
|
-
const otherChildren = await context.spawnMany(otherQueue, [{ type: 'other', data: { x: 2 } }]);
|
|
140
|
-
expect(otherChildren[0]?.parentId).toBe(task.id);
|
|
151
|
+
expect(otherChild.namespace).toBe(otherQueueName);
|
|
152
|
+
await context.spawnMany(otherQueue, [{ type: 'other', data: { x: 2 } }]);
|
|
141
153
|
executed = true;
|
|
142
154
|
return TaskProcessResult.Complete();
|
|
143
155
|
});
|
|
144
|
-
|
|
156
|
+
// Complete children so parent can finalize
|
|
157
|
+
void (async () => {
|
|
158
|
+
while (!executed) {
|
|
159
|
+
await timeout(50);
|
|
160
|
+
queue.notify();
|
|
161
|
+
}
|
|
162
|
+
// At this point parent should be WaitingChildren if children are not done
|
|
163
|
+
await queue.waitForTasks([task.id], { statuses: [TaskStatus.WaitingChildren], timeout: 2000 });
|
|
164
|
+
const midTask = await queue.getTask(task.id);
|
|
165
|
+
expect(midTask?.status).toBe(TaskStatus.WaitingChildren);
|
|
166
|
+
while (true) {
|
|
167
|
+
const dChild = await queue.dequeue({ types: ['child'] });
|
|
168
|
+
if (!dChild) {
|
|
169
|
+
// Check other-queue as well
|
|
170
|
+
const otherQueue = injector.resolve(TaskQueueProvider).get(otherQueueName);
|
|
171
|
+
const dOther = await otherQueue.dequeue();
|
|
172
|
+
if (dOther) {
|
|
173
|
+
await otherQueue.complete(dOther);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
await queue.complete(dChild);
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
queue.notify();
|
|
182
|
+
await queue.waitForTasks([task.id], { interval: 50, timeout: 2000 });
|
|
145
183
|
token.set();
|
|
146
184
|
expect(executed).toBe(true);
|
|
185
|
+
const finalTask = await queue.getTask(task.id);
|
|
186
|
+
expect(finalTask?.status).toBe(TaskStatus.Completed);
|
|
147
187
|
});
|
|
148
188
|
it('should correctly report isFinalAttempt in TaskContext', async () => {
|
|
149
189
|
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
150
|
-
const queueName = `final-try-test-${
|
|
190
|
+
const queueName = `final-try-test-${crypto.randomUUID()}`;
|
|
151
191
|
const testQueue = queueProvider.get(queueName, {
|
|
152
192
|
maxTries: 2,
|
|
153
193
|
retryDelayMinimum: 0,
|
|
@@ -173,4 +213,12 @@ describe('Worker & Base Class Tests', () => {
|
|
|
173
213
|
expect(finalAttemptValues).toEqual([false, true]);
|
|
174
214
|
await testQueue.clear();
|
|
175
215
|
});
|
|
216
|
+
it('should handle unsupported task result action', async () => {
|
|
217
|
+
const task = await queue.enqueue('unsupported-action', {});
|
|
218
|
+
const workerPromise = queue.processWorker(token, () => ({ payload: { action: 'magic' } }));
|
|
219
|
+
await expect(workerPromise).rejects.toThrow('Unsupported task result action');
|
|
220
|
+
const updated = await queue.getTask(task.id);
|
|
221
|
+
expect(updated?.status).toBe(TaskStatus.Retrying);
|
|
222
|
+
expect(updated?.error?.message).toContain('Unsupported task result action');
|
|
223
|
+
});
|
|
176
224
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
3
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
4
|
+
import { timeout } from '../../utils/timing.js';
|
|
5
|
+
describe('Zombie Parent Deadlock', () => {
|
|
6
|
+
let injector;
|
|
7
|
+
let queue;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
|
|
10
|
+
});
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
13
|
+
const queueName = `zombie-queue-${crypto.randomUUID()}`;
|
|
14
|
+
queue = queueProvider.get(queueName, {
|
|
15
|
+
visibilityTimeout: 1000,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await queue.clear();
|
|
20
|
+
});
|
|
21
|
+
afterAll(async () => {
|
|
22
|
+
await injector?.dispose();
|
|
23
|
+
});
|
|
24
|
+
it('should resolve parent even if child fails (failFast: false)', async () => {
|
|
25
|
+
const parent = await queue.enqueue('parent', {});
|
|
26
|
+
const dParent = await queue.dequeue();
|
|
27
|
+
// Spawn a child that will fail. Parent has failFast: false by default.
|
|
28
|
+
const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, failFast: false }], { returnTasks: true });
|
|
29
|
+
await queue.complete(dParent);
|
|
30
|
+
// Parent should be WaitingChildren
|
|
31
|
+
const uParent = await queue.getTask(parent.id);
|
|
32
|
+
expect(uParent?.status).toBe(TaskStatus.WaitingChildren);
|
|
33
|
+
// Fail the child fatally
|
|
34
|
+
const dChild = await queue.dequeue();
|
|
35
|
+
await queue.fail(dChild, new Error('child failed'), { fatal: true });
|
|
36
|
+
// Verify child is Dead
|
|
37
|
+
const uChild = await queue.getTask(child.id);
|
|
38
|
+
expect(uChild?.status).toBe(TaskStatus.Dead);
|
|
39
|
+
// Wait a bit for dependency resolution
|
|
40
|
+
await timeout(200);
|
|
41
|
+
// Parent should NOT be stuck. It should transition to Completed because the child is terminal.
|
|
42
|
+
const fParent = await queue.getTask(parent.id);
|
|
43
|
+
expect(fParent?.status).toBe(TaskStatus.Completed);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
3
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
4
|
+
import { timeout } from '../../utils/timing.js';
|
|
5
|
+
describe('Zombie Recovery Race Condition', () => {
|
|
6
|
+
let injector;
|
|
7
|
+
let queue;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
|
|
10
|
+
});
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
13
|
+
const queueName = `zombie-recovery-queue-${crypto.randomUUID()}`;
|
|
14
|
+
queue = queueProvider.get(queueName, {
|
|
15
|
+
visibilityTimeout: 100, // Very short visibility timeout
|
|
16
|
+
retryDelayMinimum: 0,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await queue.clear();
|
|
21
|
+
});
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await injector?.dispose();
|
|
24
|
+
});
|
|
25
|
+
it('should NOT prematurely complete a recovered parent when children finish', async () => {
|
|
26
|
+
// 1. Enqueue parent
|
|
27
|
+
const parent = await queue.enqueue('parent', {});
|
|
28
|
+
// 2. Dequeue parent (it is now Running, startTimestamp is set)
|
|
29
|
+
const dParent = await queue.dequeue();
|
|
30
|
+
expect(dParent?.id).toBe(parent.id);
|
|
31
|
+
expect(dParent?.startTimestamp).not.toBeNull();
|
|
32
|
+
// 3. Parent spawns a child that it waits for
|
|
33
|
+
const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id }], { returnTasks: true });
|
|
34
|
+
// 4. Simulate crash: Wait for visibility timeout so it becomes a zombie
|
|
35
|
+
await timeout(200);
|
|
36
|
+
// 5. Run maintenance to recover the zombie parent
|
|
37
|
+
await queue.maintenance();
|
|
38
|
+
// Verify it was recovered to Pending
|
|
39
|
+
const recoveredParent = await queue.getTask(parent.id);
|
|
40
|
+
expect(recoveredParent?.status).toBe(TaskStatus.Retrying);
|
|
41
|
+
// If the bug exists, startTimestamp is still set here.
|
|
42
|
+
// 6. Complete the child while parent is still Pending
|
|
43
|
+
const dChild = await queue.dequeue({ types: ['child'] });
|
|
44
|
+
await queue.complete(dChild);
|
|
45
|
+
// 7. Verify parent status
|
|
46
|
+
// BUG: evaluateTaskStatus will see unresolvedCompleteDependencies=0 and startTimestamp != null,
|
|
47
|
+
// and incorrectly transition the Pending parent to Completed.
|
|
48
|
+
const finalParent = await queue.getTask(parent.id);
|
|
49
|
+
expect(finalParent?.status).toBe(TaskStatus.Retrying); // It should still be pending execution!
|
|
50
|
+
});
|
|
51
|
+
});
|
package/test5.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import './polyfills.js';
|
|
2
|
-
import { promptBuilder } from './ai/index.js';
|
|
3
2
|
import { Application } from './application/application.js';
|
|
4
3
|
import { provideModule, provideSignalHandler } from './application/index.js';
|
|
5
4
|
import { PrettyPrintLogFormatter } from './logger/index.js';
|
|
6
5
|
import { provideConsoleLogTransport } from './logger/transports/console.js';
|
|
7
6
|
async function main(_cancellationSignal) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const arr = [1, 2, 3, 4, 5];
|
|
8
|
+
for (const item of arr) {
|
|
9
|
+
console.log(item);
|
|
10
|
+
arr.push(arr.at(-1) + 1);
|
|
11
|
+
}
|
|
12
12
|
}
|
|
13
13
|
Application.run('Test', [
|
|
14
14
|
provideConsoleLogTransport(PrettyPrintLogFormatter),
|
|
@@ -3,6 +3,7 @@ import { type AuthenticationAncillaryService } from '../authentication/server/in
|
|
|
3
3
|
import { Injector } from '../injector/index.js';
|
|
4
4
|
import { LogLevel } from '../logger/index.js';
|
|
5
5
|
import { type S3ObjectStorageProviderConfig } from '../object-storage/s3/index.js';
|
|
6
|
+
import type { EntityType } from '../orm/entity.js';
|
|
6
7
|
import { Database } from '../orm/server/index.js';
|
|
7
8
|
import type { Type } from '../types/index.js';
|
|
8
9
|
export type IntegrationTestOptions = {
|
|
@@ -30,7 +31,6 @@ export type IntegrationTestOptions = {
|
|
|
30
31
|
rateLimiter?: boolean;
|
|
31
32
|
signals?: boolean;
|
|
32
33
|
taskQueue?: boolean;
|
|
33
|
-
test?: boolean;
|
|
34
34
|
webServer?: boolean;
|
|
35
35
|
};
|
|
36
36
|
authenticationAncillaryService?: Type<AuthenticationAncillaryService>;
|
|
@@ -47,14 +47,14 @@ export declare function setupIntegrationTest(options?: IntegrationTestOptions):
|
|
|
47
47
|
* Helper to truncate specific tables in a schema.
|
|
48
48
|
* Useful in beforeEach() to reset state.
|
|
49
49
|
*/
|
|
50
|
-
export declare function truncateTables(database: Database, schema: string, tables: string[]): Promise<void>;
|
|
50
|
+
export declare function truncateTables(database: Database, schema: string, tables: (string | EntityType)[]): Promise<void>;
|
|
51
51
|
/**
|
|
52
52
|
* Helper to delete data for a specific tenant from specific tables.
|
|
53
53
|
* Useful for parallel tests to avoid interference.
|
|
54
54
|
*/
|
|
55
|
-
export declare function clearTenantData(database: Database, schema: string, tables: string[], tenantId: string): Promise<void>;
|
|
55
|
+
export declare function clearTenantData(database: Database, schema: string | undefined, tables: (string | EntityType)[], tenantId: string): Promise<void>;
|
|
56
56
|
/**
|
|
57
57
|
* Helper to drop specific tables.
|
|
58
58
|
* Useful in beforeAll() cleanups.
|
|
59
59
|
*/
|
|
60
|
-
export declare function dropTables(database: Database, schema: string, tables: string[]): Promise<void>;
|
|
60
|
+
export declare function dropTables(database: Database, schema: string | undefined, tables: (string | EntityType)[]): Promise<void>;
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { afterAll } from 'vitest';
|
|
2
3
|
import { configureApiServer } from '../api/server/index.js';
|
|
3
|
-
import { configureAudit
|
|
4
|
+
import { configureAudit } from '../audit/index.js';
|
|
4
5
|
import { AuthenticationApiClient } from '../authentication/client/api.client.js';
|
|
5
6
|
import { configureAuthenticationClient } from '../authentication/client/index.js';
|
|
6
|
-
import { AuthenticationApiController, configureAuthenticationServer
|
|
7
|
-
import { configurePostgresCircuitBreaker
|
|
8
|
-
import { configureDocumentManagement
|
|
7
|
+
import { AuthenticationApiController, configureAuthenticationServer } from '../authentication/server/index.js';
|
|
8
|
+
import { configurePostgresCircuitBreaker } from '../circuit-breaker/postgres/module.js';
|
|
9
|
+
import { configureDocumentManagement } from '../document-management/server/index.js';
|
|
9
10
|
import { configureUndiciHttpClientAdapter } from '../http/client/adapters/undici.adapter.js';
|
|
10
11
|
import { configureHttpClient } from '../http/client/index.js';
|
|
11
12
|
import { HttpServer } from '../http/server/index.js';
|
|
12
13
|
import { configureNodeHttpServer } from '../http/server/node/module.js';
|
|
13
14
|
import { Injector, runInInjectionContext } from '../injector/index.js';
|
|
14
|
-
import { configurePostgresKeyValueStore
|
|
15
|
-
import { configurePostgresLock
|
|
15
|
+
import { configurePostgresKeyValueStore } from '../key-value-store/postgres/module.js';
|
|
16
|
+
import { configurePostgresLock } from '../lock/postgres/module.js';
|
|
16
17
|
import { ConsoleLogTransport, DEFAULT_LOG_LEVEL, LogFormatter, LogLevel, LogManager, LogTransport, PrettyPrintLogFormatter } from '../logger/index.js';
|
|
17
18
|
import { configureLocalMessageBus } from '../message-bus/index.js';
|
|
18
19
|
import { configureWebServerModule, WebServerModule } from '../module/modules/web-server.module.js';
|
|
19
|
-
import { configureNotification
|
|
20
|
+
import { configureNotification } from '../notification/server/index.js';
|
|
20
21
|
import { configureS3ObjectStorage } from '../object-storage/s3/index.js';
|
|
21
|
-
import { configureOrm, Database } from '../orm/server/index.js';
|
|
22
|
-
import {
|
|
22
|
+
import { bootstrapOrm, configureOrm, Database } from '../orm/server/index.js';
|
|
23
|
+
import { getEntitySchema, getEntityTableName } from '../orm/utils.js';
|
|
24
|
+
import { configurePostgresRateLimiter } from '../rate-limit/postgres/module.js';
|
|
23
25
|
import { configureDefaultSignalsImplementation } from '../signals/implementation/configure.js';
|
|
24
|
-
import { configurePostgresTaskQueue
|
|
26
|
+
import { configurePostgresTaskQueue } from '../task-queue/postgres/index.js';
|
|
25
27
|
import * as configParser from '../utils/config-parser.js';
|
|
26
28
|
import { objectEntries } from '../utils/object/object.js';
|
|
27
|
-
import { isDefined, isNotNull } from '../utils/type-guards.js';
|
|
29
|
+
import { assertDefinedPass, isDefined, isNotNull, isString } from '../utils/type-guards.js';
|
|
28
30
|
/**
|
|
29
31
|
* Standard setup for integration tests.
|
|
30
32
|
*/
|
|
@@ -51,6 +53,8 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
51
53
|
...options.dbConfig,
|
|
52
54
|
};
|
|
53
55
|
// 4. Configure ORM
|
|
56
|
+
// We disable autoMigrate here because APPLICATION_INITIALIZER is not used in integration tests
|
|
57
|
+
// We manually run migrations via bootstrapOrm below
|
|
54
58
|
configureOrm({
|
|
55
59
|
repositoryConfig: { schema: options.orm?.schema ?? 'test' },
|
|
56
60
|
connection: dbConfig,
|
|
@@ -66,35 +70,29 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
66
70
|
await database.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(options.orm.schema)}`);
|
|
67
71
|
}
|
|
68
72
|
// 7. Optional Modules
|
|
69
|
-
if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.
|
|
73
|
+
if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.notification) {
|
|
70
74
|
configureLocalMessageBus({ injector });
|
|
71
75
|
}
|
|
72
76
|
if (options.modules?.taskQueue) {
|
|
73
77
|
configurePostgresTaskQueue({ injector });
|
|
74
|
-
await runInInjectionContext(injector, migratePostgresTaskQueueSchema);
|
|
75
78
|
}
|
|
76
79
|
if (options.modules?.circuitBreaker ?? options.modules?.taskQueue) {
|
|
77
80
|
configurePostgresCircuitBreaker({ injector });
|
|
78
|
-
await runInInjectionContext(injector, migratePostgresCircuitBreaker);
|
|
79
81
|
}
|
|
80
82
|
if (options.modules?.rateLimiter ?? options.modules?.taskQueue) {
|
|
81
83
|
configurePostgresRateLimiter({ injector });
|
|
82
|
-
await runInInjectionContext(injector, migratePostgresRateLimiterSchema);
|
|
83
84
|
}
|
|
84
85
|
if (options.modules?.keyValueStore ?? options.modules?.authentication) {
|
|
85
86
|
configurePostgresKeyValueStore({ injector });
|
|
86
|
-
await runInInjectionContext(injector, migratePostgresKeyValueStoreSchema);
|
|
87
87
|
}
|
|
88
88
|
if (options.modules?.lock ?? options.modules?.authentication) {
|
|
89
89
|
configurePostgresLock({ injector });
|
|
90
|
-
await runInInjectionContext(injector, migratePostgresLockSchema);
|
|
91
90
|
}
|
|
92
|
-
if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.
|
|
91
|
+
if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.notification) {
|
|
93
92
|
configureDefaultSignalsImplementation();
|
|
94
93
|
}
|
|
95
94
|
if (options.modules?.audit ?? options.modules?.authentication) {
|
|
96
95
|
configureAudit({ injector });
|
|
97
|
-
await runInInjectionContext(injector, migrateAuditSchema);
|
|
98
96
|
}
|
|
99
97
|
if (options.modules?.authentication) {
|
|
100
98
|
configureAuthenticationServer({
|
|
@@ -106,11 +104,9 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
106
104
|
authenticationAncillaryService: options.authenticationAncillaryService,
|
|
107
105
|
injector,
|
|
108
106
|
});
|
|
109
|
-
await runInInjectionContext(injector, migrateAuthenticationSchema);
|
|
110
107
|
}
|
|
111
108
|
if (options.modules?.notification) {
|
|
112
109
|
configureNotification({ injector });
|
|
113
|
-
await runInInjectionContext(injector, migrateNotificationSchema);
|
|
114
110
|
}
|
|
115
111
|
if (options.modules?.documentManagement) {
|
|
116
112
|
configureDocumentManagement({
|
|
@@ -122,8 +118,8 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
122
118
|
skipAi: true,
|
|
123
119
|
injector,
|
|
124
120
|
});
|
|
125
|
-
await runInInjectionContext(injector, migrateDocumentManagementSchema);
|
|
126
121
|
}
|
|
122
|
+
await runInInjectionContext(injector, bootstrapOrm);
|
|
127
123
|
if (options.modules?.objectStorage) {
|
|
128
124
|
const bucketPerModule = options.s3?.bucketPerModule ?? configParser.boolean('S3_BUCKET_PER_MODULE', true);
|
|
129
125
|
configureS3ObjectStorage({
|
|
@@ -167,6 +163,7 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
167
163
|
}
|
|
168
164
|
}
|
|
169
165
|
});
|
|
166
|
+
afterAll(async () => await injector.dispose());
|
|
170
167
|
return { injector, database };
|
|
171
168
|
}
|
|
172
169
|
/**
|
|
@@ -190,9 +187,18 @@ export async function truncateTables(database, schema, tables) {
|
|
|
190
187
|
if (tables.length == 0) {
|
|
191
188
|
return;
|
|
192
189
|
}
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
const lockId = 987654321; // Different lock ID for table maintenance
|
|
191
|
+
await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
|
|
192
|
+
try {
|
|
193
|
+
// Using CASCADE to handle foreign keys automatically
|
|
194
|
+
for (const table of tables) {
|
|
195
|
+
const tableName = isString(table) ? table : getEntityTableName(table);
|
|
196
|
+
const tableSchema = isString(table) ? schema : getEntitySchema(table, schema);
|
|
197
|
+
await database.execute(sql `TRUNCATE TABLE ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} CASCADE`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
|
|
196
202
|
}
|
|
197
203
|
}
|
|
198
204
|
/**
|
|
@@ -203,8 +209,17 @@ export async function clearTenantData(database, schema, tables, tenantId) {
|
|
|
203
209
|
if (tables.length == 0) {
|
|
204
210
|
return;
|
|
205
211
|
}
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
const lockId = 987654321;
|
|
213
|
+
await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
|
|
214
|
+
try {
|
|
215
|
+
for (const table of tables) {
|
|
216
|
+
const tableName = isString(table) ? table : getEntityTableName(table);
|
|
217
|
+
const tableSchema = isString(table) ? assertDefinedPass(schema, 'Schema not provided') : getEntitySchema(table, schema);
|
|
218
|
+
await database.execute(sql `DELETE FROM ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} WHERE tenant_id = ${tenantId}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
/**
|
|
@@ -212,7 +227,19 @@ export async function clearTenantData(database, schema, tables, tenantId) {
|
|
|
212
227
|
* Useful in beforeAll() cleanups.
|
|
213
228
|
*/
|
|
214
229
|
export async function dropTables(database, schema, tables) {
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
if (tables.length == 0) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const lockId = 987654321;
|
|
234
|
+
await database.execute(sql `SELECT pg_advisory_lock(${lockId})`);
|
|
235
|
+
try {
|
|
236
|
+
for (const table of tables) {
|
|
237
|
+
const tableName = isString(table) ? table : getEntityTableName(table);
|
|
238
|
+
const tableSchema = isString(table) ? assertDefinedPass(schema, 'Schema not provided') : getEntitySchema(table, schema);
|
|
239
|
+
await database.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} CASCADE`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
|
|
217
244
|
}
|
|
218
245
|
}
|
|
@@ -192,7 +192,7 @@ function buildMappedLocalization({ language, keys, enums }) {
|
|
|
192
192
|
const mappedLocalization = {
|
|
193
193
|
language,
|
|
194
194
|
keys: new Map(deepObjectEntries(keys)),
|
|
195
|
-
enums: new Map(enumsEntries)
|
|
195
|
+
enums: new Map(enumsEntries),
|
|
196
196
|
};
|
|
197
197
|
return mappedLocalization;
|
|
198
198
|
}
|
|
@@ -215,7 +215,7 @@ function mergeMappedLocalization(a, b, force = false) {
|
|
|
215
215
|
return {
|
|
216
216
|
language: b.language,
|
|
217
217
|
keys: new Map([...a.keys, ...b.keys]),
|
|
218
|
-
enums: new Map([...a.enums, ...b.enums])
|
|
218
|
+
enums: new Map([...a.enums, ...b.enums]),
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
221
|
export function isLocalizeItem(value) {
|
package/utils/file-reader.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DetailsError } from '../errors/details.error.js';
|
|
2
1
|
export async function readAsText(blob, encoding) {
|
|
3
2
|
return await setup((reader) => reader.readAsText(blob, encoding));
|
|
4
3
|
}
|
|
@@ -12,7 +11,7 @@ async function setup(dispatcher) {
|
|
|
12
11
|
return await new Promise((resolve, reject) => {
|
|
13
12
|
const reader = new FileReader();
|
|
14
13
|
reader.onload = () => resolve(reader.result);
|
|
15
|
-
reader.onerror = () => reject(new
|
|
14
|
+
reader.onerror = () => reject(new Error('FileReader error', { cause: reader.error }));
|
|
16
15
|
dispatcher(reader);
|
|
17
16
|
});
|
|
18
17
|
}
|
package/utils/timing.d.ts
CHANGED
|
@@ -8,9 +8,9 @@ export declare function timeout(milliseconds?: number, options?: {
|
|
|
8
8
|
/** Timeout until specified time */
|
|
9
9
|
export declare function timeoutUntil(timestamp: number | Date): Promise<void>;
|
|
10
10
|
/** Timeout for specified duration */
|
|
11
|
-
export declare function cancelableTimeout(milliseconds: number, cancelSignal: Observable<
|
|
11
|
+
export declare function cancelableTimeout(milliseconds: number, cancelSignal: Observable<unknown> | CancellationSignal): Promise<'timeout' | 'canceled'>;
|
|
12
12
|
/** Timeout until specified time */
|
|
13
|
-
export declare function cancelableTimeoutUntil(timestamp: number | Date, cancelSignal: Observable<
|
|
13
|
+
export declare function cancelableTimeoutUntil(timestamp: number | Date, cancelSignal: Observable<unknown> | CancellationSignal): Promise<'timeout' | 'canceled'>;
|
|
14
14
|
export declare function withTimeout<T>(milliseconds: number, promiseOrProvider: ValueOrProvider<Promise<T>>, options?: {
|
|
15
15
|
errorMessage?: string;
|
|
16
16
|
}): Promise<T>;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
CREATE TYPE "task_queue"."dependency_join_mode" AS ENUM('and', 'or');--> statement-breakpoint
|
|
2
|
-
CREATE TYPE "task_queue"."task_status" AS ENUM('pending', 'running', 'completed', 'cancelled', 'waiting', 'dead');--> statement-breakpoint
|
|
3
|
-
CREATE TABLE "task_queue"."task" (
|
|
4
|
-
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
5
|
-
"namespace" text NOT NULL,
|
|
6
|
-
"type" text NOT NULL,
|
|
7
|
-
"status" "task_queue"."task_status" NOT NULL,
|
|
8
|
-
"idempotency_key" text,
|
|
9
|
-
"trace_id" text,
|
|
10
|
-
"tags" text[] NOT NULL,
|
|
11
|
-
"complete_after_tags" text[] NOT NULL,
|
|
12
|
-
"schedule_after_tags" text[] NOT NULL,
|
|
13
|
-
"fail_fast" boolean NOT NULL,
|
|
14
|
-
"dependency_join_mode" "task_queue"."dependency_join_mode" NOT NULL,
|
|
15
|
-
"dependency_trigger_statuses" "task_queue"."task_status"[] NOT NULL,
|
|
16
|
-
"priority" integer NOT NULL,
|
|
17
|
-
"token" uuid,
|
|
18
|
-
"creation_timestamp" timestamp with time zone NOT NULL,
|
|
19
|
-
"priority_age_timestamp" timestamp with time zone NOT NULL,
|
|
20
|
-
"schedule_timestamp" timestamp with time zone NOT NULL,
|
|
21
|
-
"start_timestamp" timestamp with time zone,
|
|
22
|
-
"time_to_live" timestamp with time zone,
|
|
23
|
-
"visibility_deadline" timestamp with time zone,
|
|
24
|
-
"complete_timestamp" timestamp with time zone,
|
|
25
|
-
"tries" integer NOT NULL,
|
|
26
|
-
"progress" double precision NOT NULL,
|
|
27
|
-
"data" jsonb,
|
|
28
|
-
"state" jsonb,
|
|
29
|
-
"result" jsonb,
|
|
30
|
-
"error" jsonb,
|
|
31
|
-
"parent_id" uuid,
|
|
32
|
-
CONSTRAINT "task_namespace_idempotency_key_unique" UNIQUE("namespace","idempotency_key")
|
|
33
|
-
);
|
|
34
|
-
--> statement-breakpoint
|
|
35
|
-
CREATE TABLE "task_queue"."task_archive" (
|
|
36
|
-
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
37
|
-
"namespace" text NOT NULL,
|
|
38
|
-
"type" text NOT NULL,
|
|
39
|
-
"status" "task_queue"."task_status" NOT NULL,
|
|
40
|
-
"idempotency_key" text,
|
|
41
|
-
"trace_id" text,
|
|
42
|
-
"tags" text[] NOT NULL,
|
|
43
|
-
"complete_after_tags" text[] NOT NULL,
|
|
44
|
-
"schedule_after_tags" text[] NOT NULL,
|
|
45
|
-
"fail_fast" boolean NOT NULL,
|
|
46
|
-
"dependency_join_mode" "task_queue"."dependency_join_mode" NOT NULL,
|
|
47
|
-
"dependency_trigger_statuses" "task_queue"."task_status"[] NOT NULL,
|
|
48
|
-
"priority" integer NOT NULL,
|
|
49
|
-
"token" uuid,
|
|
50
|
-
"creation_timestamp" timestamp with time zone NOT NULL,
|
|
51
|
-
"priority_age_timestamp" timestamp with time zone NOT NULL,
|
|
52
|
-
"schedule_timestamp" timestamp with time zone NOT NULL,
|
|
53
|
-
"start_timestamp" timestamp with time zone,
|
|
54
|
-
"time_to_live" timestamp with time zone,
|
|
55
|
-
"visibility_deadline" timestamp with time zone,
|
|
56
|
-
"complete_timestamp" timestamp with time zone,
|
|
57
|
-
"tries" integer NOT NULL,
|
|
58
|
-
"progress" double precision NOT NULL,
|
|
59
|
-
"data" jsonb,
|
|
60
|
-
"state" jsonb,
|
|
61
|
-
"result" jsonb,
|
|
62
|
-
"error" jsonb,
|
|
63
|
-
"parent_id" uuid
|
|
64
|
-
);
|
|
65
|
-
--> statement-breakpoint
|
|
66
|
-
ALTER TABLE "task_queue"."task" ADD CONSTRAINT "task_parent_id_task_id_fk" FOREIGN KEY ("parent_id") REFERENCES "task_queue"."task"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
67
|
-
CREATE INDEX "task_parent_id_idx" ON "task_queue"."task" USING btree ("parent_id");--> statement-breakpoint
|
|
68
|
-
CREATE INDEX "task_status_visibility_deadline_idx" ON "task_queue"."task" USING btree ("status","visibility_deadline");--> statement-breakpoint
|
|
69
|
-
CREATE INDEX "task_status_complete_timestamp_idx" ON "task_queue"."task" USING btree ("status","complete_timestamp");--> statement-breakpoint
|
|
70
|
-
CREATE INDEX "task_schedule_after_tags_idx" ON "task_queue"."task" USING gin ("schedule_after_tags");--> statement-breakpoint
|
|
71
|
-
CREATE INDEX "task_complete_after_tags_idx" ON "task_queue"."task" USING gin ("complete_after_tags");--> statement-breakpoint
|
|
72
|
-
CREATE INDEX "task_tags_idx" ON "task_queue"."task" USING gin ("tags");--> statement-breakpoint
|
|
73
|
-
CREATE INDEX "task_namespace_status_schedule_timestamp_priority_idx" ON "task_queue"."task" USING btree ("namespace","status","schedule_timestamp","priority");--> statement-breakpoint
|
|
74
|
-
CREATE INDEX "task_archive_namespace_complete_timestamp_idx" ON "task_queue"."task_archive" USING btree ("namespace","complete_timestamp");
|