@tstdl/base 0.93.141 → 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 (71) hide show
  1. package/authentication/client/authentication.service.d.ts +1 -0
  2. package/authentication/client/authentication.service.js +3 -2
  3. package/circuit-breaker/circuit-breaker.d.ts +6 -4
  4. package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
  5. package/circuit-breaker/postgres/circuit-breaker.js +8 -5
  6. package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
  7. package/examples/document-management/main.js +2 -2
  8. package/notification/tests/notification-api.test.js +5 -1
  9. package/notification/tests/notification-flow.test.js +9 -6
  10. package/orm/decorators.d.ts +17 -4
  11. package/orm/decorators.js +9 -0
  12. package/orm/server/bootstrap.d.ts +11 -0
  13. package/orm/server/bootstrap.js +31 -0
  14. package/orm/server/drizzle/schema-converter.d.ts +3 -1
  15. package/orm/server/drizzle/schema-converter.js +71 -29
  16. package/orm/server/extension.d.ts +14 -0
  17. package/orm/server/extension.js +27 -0
  18. package/orm/server/index.d.ts +2 -0
  19. package/orm/server/index.js +2 -0
  20. package/orm/server/migration.d.ts +2 -3
  21. package/orm/server/migration.js +7 -21
  22. package/orm/server/repository.d.ts +1 -0
  23. package/orm/server/repository.js +19 -9
  24. package/orm/server/transaction.d.ts +1 -0
  25. package/orm/server/transaction.js +3 -0
  26. package/orm/tests/database-extension.test.js +63 -0
  27. package/orm/tests/database-migration.test.js +7 -6
  28. package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
  29. package/orm/tests/repository-compound-primary-key.test.js +234 -0
  30. package/orm/tests/schema-generation.test.d.ts +1 -0
  31. package/orm/tests/schema-generation.test.js +52 -5
  32. package/package.json +4 -4
  33. package/task-queue/README.md +0 -1
  34. package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
  35. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +151 -68
  36. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  37. package/task-queue/postgres/module.js +2 -1
  38. package/task-queue/postgres/schemas.d.ts +6 -0
  39. package/task-queue/postgres/task-queue.d.ts +18 -5
  40. package/task-queue/postgres/task-queue.js +593 -372
  41. package/task-queue/postgres/task.model.d.ts +9 -5
  42. package/task-queue/postgres/task.model.js +26 -26
  43. package/task-queue/task-context.d.ts +10 -5
  44. package/task-queue/task-context.js +5 -3
  45. package/task-queue/task-queue.d.ts +339 -35
  46. package/task-queue/task-queue.js +135 -31
  47. package/task-queue/tests/coverage-branch.test.js +45 -57
  48. package/task-queue/tests/coverage-enhancement.test.js +123 -117
  49. package/task-queue/tests/{extensive-dependencies.test.js → dag.test.js} +61 -32
  50. package/task-queue/tests/dependencies.test.js +139 -21
  51. package/task-queue/tests/enqueue-batch.test.js +125 -0
  52. package/task-queue/tests/fan-out-spawning.test.js +43 -2
  53. package/task-queue/tests/idempotent-replacement.test.js +54 -1
  54. package/task-queue/tests/missing-idempotent-tasks.test.js +9 -8
  55. package/task-queue/tests/queue.test.js +261 -25
  56. package/task-queue/tests/shutdown.test.js +41 -0
  57. package/task-queue/tests/transactions.test.d.ts +1 -0
  58. package/task-queue/tests/transactions.test.js +47 -0
  59. package/task-queue/tests/worker.test.js +46 -13
  60. package/task-queue/tests/zombie-parent.test.js +1 -1
  61. package/task-queue/tests/zombie-recovery.test.js +3 -3
  62. package/testing/integration-setup.js +5 -3
  63. package/utils/timing.d.ts +2 -2
  64. package/task-queue/postgres/drizzle/0000_wakeful_sunspot.sql +0 -82
  65. package/task-queue/tests/cascading-cancellations.test.js +0 -38
  66. package/task-queue/tests/complex.test.js +0 -122
  67. package/task-queue/tests/dag-dependencies.test.js +0 -41
  68. /package/{task-queue/tests/cascading-cancellations.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
  69. /package/task-queue/tests/{complex.test.d.ts → dag.test.d.ts} +0 -0
  70. /package/task-queue/tests/{dag-dependencies.test.d.ts → enqueue-batch.test.d.ts} +0 -0
  71. /package/task-queue/tests/{extensive-dependencies.test.d.ts → shutdown.test.d.ts} +0 -0
@@ -10,7 +10,7 @@ describe('Zombie Recovery Race Condition', () => {
10
10
  });
11
11
  beforeEach(() => {
12
12
  const queueProvider = injector.resolve(TaskQueueProvider);
13
- const queueName = `zombie-recovery-queue-${Date.now()}-${Math.random()}`;
13
+ const queueName = `zombie-recovery-queue-${crypto.randomUUID()}`;
14
14
  queue = queueProvider.get(queueName, {
15
15
  visibilityTimeout: 100, // Very short visibility timeout
16
16
  retryDelayMinimum: 0,
@@ -37,7 +37,7 @@ describe('Zombie Recovery Race Condition', () => {
37
37
  await queue.maintenance();
38
38
  // Verify it was recovered to Pending
39
39
  const recoveredParent = await queue.getTask(parent.id);
40
- expect(recoveredParent?.status).toBe(TaskStatus.Pending);
40
+ expect(recoveredParent?.status).toBe(TaskStatus.Retrying);
41
41
  // If the bug exists, startTimestamp is still set here.
42
42
  // 6. Complete the child while parent is still Pending
43
43
  const dChild = await queue.dequeue({ types: ['child'] });
@@ -46,6 +46,6 @@ describe('Zombie Recovery Race Condition', () => {
46
46
  // BUG: evaluateTaskStatus will see unresolvedCompleteDependencies=0 and startTimestamp != null,
47
47
  // and incorrectly transition the Pending parent to Completed.
48
48
  const finalParent = await queue.getTask(parent.id);
49
- expect(finalParent?.status).toBe(TaskStatus.Pending); // It should still be pending execution!
49
+ expect(finalParent?.status).toBe(TaskStatus.Retrying); // It should still be pending execution!
50
50
  });
51
51
  });
@@ -1,4 +1,5 @@
1
1
  import { sql } from 'drizzle-orm';
2
+ import { afterAll } from 'vitest';
2
3
  import { configureApiServer } from '../api/server/index.js';
3
4
  import { configureAudit } from '../audit/index.js';
4
5
  import { AuthenticationApiClient } from '../authentication/client/api.client.js';
@@ -18,7 +19,7 @@ import { configureLocalMessageBus } from '../message-bus/index.js';
18
19
  import { configureWebServerModule, WebServerModule } from '../module/modules/web-server.module.js';
19
20
  import { configureNotification } from '../notification/server/index.js';
20
21
  import { configureS3ObjectStorage } from '../object-storage/s3/index.js';
21
- import { configureOrm, Database, runDatabaseMigrations } from '../orm/server/index.js';
22
+ import { bootstrapOrm, configureOrm, Database } from '../orm/server/index.js';
22
23
  import { getEntitySchema, getEntityTableName } from '../orm/utils.js';
23
24
  import { configurePostgresRateLimiter } from '../rate-limit/postgres/module.js';
24
25
  import { configureDefaultSignalsImplementation } from '../signals/implementation/configure.js';
@@ -53,7 +54,7 @@ export async function setupIntegrationTest(options = {}) {
53
54
  };
54
55
  // 4. Configure ORM
55
56
  // We disable autoMigrate here because APPLICATION_INITIALIZER is not used in integration tests
56
- // We manually run migrations via runDatabaseMigrations below
57
+ // We manually run migrations via bootstrapOrm below
57
58
  configureOrm({
58
59
  repositoryConfig: { schema: options.orm?.schema ?? 'test' },
59
60
  connection: dbConfig,
@@ -118,7 +119,7 @@ export async function setupIntegrationTest(options = {}) {
118
119
  injector,
119
120
  });
120
121
  }
121
- await runInInjectionContext(injector, runDatabaseMigrations);
122
+ await runInInjectionContext(injector, bootstrapOrm);
122
123
  if (options.modules?.objectStorage) {
123
124
  const bucketPerModule = options.s3?.bucketPerModule ?? configParser.boolean('S3_BUCKET_PER_MODULE', true);
124
125
  configureS3ObjectStorage({
@@ -162,6 +163,7 @@ export async function setupIntegrationTest(options = {}) {
162
163
  }
163
164
  }
164
165
  });
166
+ afterAll(async () => await injector.dispose());
165
167
  return { injector, database };
166
168
  }
167
169
  /**
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<void> | CancellationSignal): Promise<'timeout' | 'canceled'>;
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<void> | CancellationSignal): Promise<'timeout' | 'canceled'>;
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,82 +0,0 @@
1
- CREATE TYPE "task_queue"."task_dependency_type" AS ENUM('schedule', 'complete');--> statement-breakpoint
2
- CREATE TYPE "task_queue"."task_status" AS ENUM('pending', 'running', 'completed', 'cancelled', 'dead', 'waiting', 'waiting-children', 'paused');--> 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
- "fail_fast" boolean NOT NULL,
12
- "priority" integer NOT NULL,
13
- "unresolved_schedule_dependencies" integer NOT NULL,
14
- "unresolved_complete_dependencies" integer NOT NULL,
15
- "token" uuid,
16
- "creation_timestamp" timestamp with time zone NOT NULL,
17
- "priority_age_timestamp" timestamp with time zone NOT NULL,
18
- "schedule_timestamp" timestamp with time zone NOT NULL,
19
- "start_timestamp" timestamp with time zone,
20
- "time_to_live" timestamp with time zone,
21
- "visibility_deadline" timestamp with time zone,
22
- "complete_timestamp" timestamp with time zone,
23
- "tries" integer NOT NULL,
24
- "progress" double precision NOT NULL,
25
- "data" jsonb,
26
- "state" jsonb,
27
- "result" jsonb,
28
- "error" jsonb,
29
- "parent_id" uuid,
30
- CONSTRAINT "task_namespace_id_unique" UNIQUE("namespace","id"),
31
- CONSTRAINT "task_namespace_idempotency_key_unique" UNIQUE("namespace","idempotency_key")
32
- );
33
- --> statement-breakpoint
34
- CREATE TABLE "task_queue"."task_archive" (
35
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
36
- "namespace" text NOT NULL,
37
- "type" text NOT NULL,
38
- "status" "task_queue"."task_status" NOT NULL,
39
- "idempotency_key" text,
40
- "trace_id" text,
41
- "tags" text[] NOT NULL,
42
- "fail_fast" boolean NOT NULL,
43
- "priority" integer NOT NULL,
44
- "unresolved_schedule_dependencies" integer NOT NULL,
45
- "unresolved_complete_dependencies" integer NOT NULL,
46
- "token" uuid,
47
- "creation_timestamp" timestamp with time zone NOT NULL,
48
- "priority_age_timestamp" timestamp with time zone NOT NULL,
49
- "schedule_timestamp" timestamp with time zone NOT NULL,
50
- "start_timestamp" timestamp with time zone,
51
- "time_to_live" timestamp with time zone,
52
- "visibility_deadline" timestamp with time zone,
53
- "complete_timestamp" timestamp with time zone,
54
- "tries" integer NOT NULL,
55
- "progress" double precision NOT NULL,
56
- "data" jsonb,
57
- "state" jsonb,
58
- "result" jsonb,
59
- "error" jsonb,
60
- "parent_id" uuid
61
- );
62
- --> statement-breakpoint
63
- CREATE TABLE "task_queue"."task_dependency" (
64
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
65
- "namespace" text NOT NULL,
66
- "task_id" uuid NOT NULL,
67
- "dependency_task_id" uuid NOT NULL,
68
- "type" "task_queue"."task_dependency_type" NOT NULL,
69
- "required_statuses" "task_queue"."task_status"[] NOT NULL,
70
- CONSTRAINT "td_namespace_task_id_dependency_task_id_type_unique" UNIQUE("namespace","task_id","dependency_task_id","type")
71
- );
72
- --> statement-breakpoint
73
- ALTER TABLE "task_queue"."task" ADD CONSTRAINT "task_namespace_parentId_task_fkey" FOREIGN KEY ("namespace","parent_id") REFERENCES "task_queue"."task"("namespace","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
74
- ALTER TABLE "task_queue"."task_dependency" ADD CONSTRAINT "task_dependency_namespace_dependencyTaskId_task_fkey" FOREIGN KEY ("namespace","dependency_task_id") REFERENCES "task_queue"."task"("namespace","id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
75
- ALTER TABLE "task_queue"."task_dependency" ADD CONSTRAINT "task_dependency_namespace_taskId_task_fkey" FOREIGN KEY ("namespace","task_id") REFERENCES "task_queue"."task"("namespace","id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
76
- CREATE INDEX "task_parent_id_idx" ON "task_queue"."task" USING btree ("parent_id");--> statement-breakpoint
77
- CREATE INDEX "task_status_visibility_deadline_idx" ON "task_queue"."task" USING btree ("status","visibility_deadline");--> statement-breakpoint
78
- CREATE INDEX "task_status_complete_timestamp_idx" ON "task_queue"."task" USING btree ("status","complete_timestamp");--> statement-breakpoint
79
- CREATE INDEX "task_tags_idx" ON "task_queue"."task" USING gin ("tags");--> statement-breakpoint
80
- CREATE INDEX "task_namespace_status_schedule_timestamp_priority_idx" ON "task_queue"."task" USING btree ("namespace","status","schedule_timestamp","priority");--> statement-breakpoint
81
- CREATE INDEX "task_archive_namespace_complete_timestamp_idx" ON "task_queue"."task_archive" USING btree ("namespace","complete_timestamp");--> statement-breakpoint
82
- CREATE INDEX "task_dependency_namespace_dependency_task_id_type_idx" ON "task_queue"."task_dependency" USING btree ("namespace","dependency_task_id","type");
@@ -1,38 +0,0 @@
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
- describe('Cascading Cancellations', () => {
5
- let injector;
6
- let queue;
7
- beforeAll(async () => {
8
- ({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
9
- });
10
- beforeEach(() => {
11
- const queueProvider = injector.resolve(TaskQueueProvider);
12
- const queueName = `cancellation-queue-${Date.now()}-${Math.random()}`;
13
- queue = queueProvider.get(queueName, {
14
- visibilityTimeout: 1000,
15
- });
16
- });
17
- afterEach(async () => {
18
- await queue.clear();
19
- });
20
- afterAll(async () => {
21
- await injector?.dispose();
22
- });
23
- it('should recursively cancel children in ownership tree', async () => {
24
- const root = await queue.enqueue('root', {});
25
- const child1 = await queue.enqueue('child1', {}, { parentId: root.id });
26
- const child2 = await queue.enqueue('child2', {}, { parentId: root.id });
27
- const grandchild = await queue.enqueue('grandchild', {}, { parentId: child1.id });
28
- await queue.cancel(root.id);
29
- const uRoot = await queue.getTask(root.id);
30
- const uChild1 = await queue.getTask(child1.id);
31
- const uChild2 = await queue.getTask(child2.id);
32
- const uGrandchild = await queue.getTask(grandchild.id);
33
- expect(uRoot?.status).toBe(TaskStatus.Cancelled);
34
- expect(uChild1?.status).toBe(TaskStatus.Cancelled);
35
- expect(uChild2?.status).toBe(TaskStatus.Cancelled);
36
- expect(uGrandchild?.status).toBe(TaskStatus.Cancelled);
37
- });
38
- });
@@ -1,122 +0,0 @@
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('Complex Queue Scenarios', () => {
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 = `complex-queue-${Date.now()}-${Math.random()}`;
14
- // Configure with specific settings for testing logic
15
- queue = queueProvider.get(queueName, {
16
- visibilityTimeout: 1000,
17
- priorityAgingInterval: 50, // Fast aging
18
- priorityAgingStep: 10,
19
- rateLimit: 5,
20
- rateInterval: 200,
21
- retryDelayMinimum: 50,
22
- retryDelayGrowth: 2,
23
- retention: 50, // Fast retention for archive test
24
- });
25
- });
26
- afterEach(async () => {
27
- await queue.clear();
28
- const queueProvider = injector.resolve(TaskQueueProvider);
29
- await queueProvider.get('other-queue').clear();
30
- });
31
- afterAll(async () => {
32
- await injector?.dispose();
33
- });
34
- async function waitForStatus(id, status) {
35
- for (let i = 0; i < 50; i++) {
36
- const task = await queue.getTask(id);
37
- if (task?.status === status)
38
- return;
39
- await timeout(10);
40
- }
41
- }
42
- describe('Complex Dependencies', () => {
43
- it('should handle Diamond Dependency (A -> B, A -> C, (B&C) -> D)', async () => {
44
- const taskA = await queue.enqueue('A', {});
45
- const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id] });
46
- const taskC = await queue.enqueue('C', {}, { scheduleAfter: [taskA.id] });
47
- const taskD = await queue.enqueue('D', {}, { scheduleAfter: [taskB.id, taskC.id] });
48
- expect(taskD.status).toBe(TaskStatus.Waiting);
49
- expect(taskB.status).toBe(TaskStatus.Waiting);
50
- expect(taskC.status).toBe(TaskStatus.Waiting);
51
- expect(taskA.status).toBe(TaskStatus.Pending);
52
- // Process A
53
- const dA = await queue.dequeue({ types: ['A'] });
54
- await queue.complete(dA);
55
- await waitForStatus(taskB.id, TaskStatus.Pending);
56
- await waitForStatus(taskC.id, TaskStatus.Pending);
57
- // Process B
58
- const dB = await queue.dequeue({ types: ['B'] });
59
- await queue.complete(dB);
60
- // D still waiting (needs C)
61
- const uD2 = await queue.getTask(taskD.id);
62
- expect(uD2?.status).toBe(TaskStatus.Waiting);
63
- // Process C
64
- const dC = await queue.dequeue({ types: ['C'] });
65
- await queue.complete(dC);
66
- await waitForStatus(taskD.id, TaskStatus.Pending);
67
- // D should be Pending
68
- const uD3 = await queue.getTask(taskD.id);
69
- expect(uD3?.status).toBe(TaskStatus.Pending);
70
- });
71
- it('should handle Deep Chain (A -> B -> C -> D)', async () => {
72
- const taskA = await queue.enqueue('A', {});
73
- const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id] });
74
- const taskC = await queue.enqueue('C', {}, { scheduleAfter: [taskB.id] });
75
- const taskD = await queue.enqueue('D', {}, { scheduleAfter: [taskC.id] });
76
- await queue.complete((await queue.dequeue({ types: ['A'] })));
77
- await waitForStatus(taskB.id, TaskStatus.Pending);
78
- await queue.complete((await queue.dequeue({ types: ['B'] })));
79
- await waitForStatus(taskC.id, TaskStatus.Pending);
80
- await queue.complete((await queue.dequeue({ types: ['C'] })));
81
- await waitForStatus(taskD.id, TaskStatus.Pending);
82
- });
83
- it('should respect failFast = false (continue other branches)', async () => {
84
- const taskA = await queue.enqueue('A', {});
85
- const taskB = await queue.enqueue('B', {}, { scheduleAfter: [taskA.id], failFast: false });
86
- const taskC = await queue.enqueue('C', {}); // Independent
87
- const dA = await queue.dequeue({ types: ['A'] });
88
- await queue.fail(dA, new Error('fatal'), { fatal: true });
89
- // taskB should stay Waiting (default requiredStatus is Completed)
90
- // If failFast is false, it should transition to Pending once the dependency is terminal, even if it failed.
91
- await timeout(100);
92
- const uB = await queue.getTask(taskB.id);
93
- expect(uB?.status).toBe(TaskStatus.Pending);
94
- const dC = await queue.dequeue({ types: ['C'] });
95
- expect(dC?.id).toBe(taskC.id);
96
- });
97
- });
98
- describe('Other Scenarios', () => {
99
- it('should handle priority aging', async () => {
100
- const t1 = await queue.enqueue('low', {}, { priority: 2000 });
101
- const t2 = await queue.enqueue('high', {}, { priority: 1000 });
102
- // Wait for aging to trigger
103
- await timeout(100);
104
- await queue.maintenance();
105
- const u1 = await queue.getTask(t1.id);
106
- const u2 = await queue.getTask(t2.id);
107
- expect(u1.priority).toBeLessThan(2000);
108
- expect(u2.priority).toBeLessThan(1000);
109
- });
110
- it('should handle rate limiting', async () => {
111
- // Limit is 5 per 200ms
112
- for (let i = 0; i < 10; i++) {
113
- await queue.enqueue('task', { i });
114
- }
115
- const batch1 = await queue.dequeueMany(10);
116
- expect(batch1.length).toBe(5);
117
- await timeout(500); // Increased from 250
118
- const batch2 = await queue.dequeueMany(10);
119
- expect(batch2.length).toBe(5);
120
- });
121
- });
122
- });
@@ -1,41 +0,0 @@
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
- describe('DAG Dependencies', () => {
5
- let injector;
6
- let queue;
7
- beforeAll(async () => {
8
- ({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
9
- });
10
- beforeEach(() => {
11
- const queueProvider = injector.resolve(TaskQueueProvider);
12
- const queueName = `dag-queue-${Date.now()}-${Math.random()}`;
13
- queue = queueProvider.get(queueName, {
14
- visibilityTimeout: 1000,
15
- });
16
- });
17
- afterEach(async () => {
18
- await queue.clear();
19
- });
20
- afterAll(async () => {
21
- await injector?.dispose();
22
- });
23
- it('should schedule a task only after dependency reaches required status (scheduleAfter)', async () => {
24
- const prereq = await queue.enqueue('prereq', { val: 1 });
25
- const dependent = await queue.enqueue('dependent', { foo: 'bar' }, {
26
- scheduleAfter: [prereq.id],
27
- });
28
- expect(dependent.status).toBe(TaskStatus.Waiting);
29
- const dequeued = await queue.dequeue({ types: ['prereq'] });
30
- expect(dequeued?.id).toBe(prereq.id);
31
- await queue.complete(dequeued);
32
- // Dependent should transition to Pending.
33
- // Let's dequeue and complete it to make it reaches a finalized state for waitForTasks
34
- const dDependent = await queue.dequeue({ types: ['dependent'] });
35
- expect(dDependent?.id).toBe(dependent.id);
36
- await queue.complete(dDependent);
37
- await queue.waitForTasks([dependent.id]);
38
- const updatedDependent = await queue.getTask(dependent.id);
39
- expect(updatedDependent?.status).toBe(TaskStatus.Completed);
40
- });
41
- });