@powerhousedao/reactor 4.1.0-staging.1 → 5.0.0-staging.2

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 (126) hide show
  1. package/dist/bench/end-to-end-flow.bench.js +64 -36
  2. package/dist/bench/end-to-end-flow.bench.js.map +1 -1
  3. package/dist/bench/event-bus.bench.js.map +1 -1
  4. package/dist/bench/queue-only.bench.js +8 -2
  5. package/dist/bench/queue-only.bench.js.map +1 -1
  6. package/dist/bench/reactor-throughput.bench.js +43 -16
  7. package/dist/bench/reactor-throughput.bench.js.map +1 -1
  8. package/dist/src/events/event-bus.d.ts +2 -2
  9. package/dist/src/events/event-bus.d.ts.map +1 -1
  10. package/dist/src/events/event-bus.js +1 -1
  11. package/dist/src/events/event-bus.js.map +1 -1
  12. package/dist/src/events/interfaces.d.ts +1 -1
  13. package/dist/src/events/interfaces.d.ts.map +1 -1
  14. package/dist/src/events/types.d.ts +1 -1
  15. package/dist/src/events/types.d.ts.map +1 -1
  16. package/dist/src/events/types.js.map +1 -1
  17. package/dist/src/executor/interfaces.d.ts +31 -54
  18. package/dist/src/executor/interfaces.d.ts.map +1 -1
  19. package/dist/src/executor/simple-job-executor-manager.d.ts +27 -0
  20. package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -0
  21. package/dist/src/executor/simple-job-executor-manager.js +128 -0
  22. package/dist/src/executor/simple-job-executor-manager.js.map +1 -0
  23. package/dist/src/executor/simple-job-executor.d.ts +19 -0
  24. package/dist/src/executor/simple-job-executor.d.ts.map +1 -0
  25. package/dist/src/executor/simple-job-executor.js +69 -0
  26. package/dist/src/executor/simple-job-executor.js.map +1 -0
  27. package/dist/src/executor/types.d.ts +23 -8
  28. package/dist/src/executor/types.d.ts.map +1 -1
  29. package/dist/src/executor/types.js.map +1 -1
  30. package/dist/src/index.d.ts +9 -2
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/index.js +8 -2
  33. package/dist/src/index.js.map +1 -1
  34. package/dist/src/interfaces/reactor.d.ts +121 -0
  35. package/dist/src/interfaces/reactor.d.ts.map +1 -0
  36. package/dist/src/interfaces/reactor.js +2 -0
  37. package/dist/src/interfaces/reactor.js.map +1 -0
  38. package/dist/src/queue/interfaces.d.ts +26 -1
  39. package/dist/src/queue/interfaces.d.ts.map +1 -1
  40. package/dist/src/queue/queue.d.ts +33 -3
  41. package/dist/src/queue/queue.d.ts.map +1 -1
  42. package/dist/src/queue/queue.js +164 -17
  43. package/dist/src/queue/queue.js.map +1 -1
  44. package/dist/src/queue/types.d.ts +5 -1
  45. package/dist/src/queue/types.d.ts.map +1 -1
  46. package/dist/src/queue/types.js.map +1 -1
  47. package/dist/src/reactor.d.ts +109 -0
  48. package/dist/src/reactor.d.ts.map +1 -0
  49. package/dist/src/reactor.js +602 -0
  50. package/dist/src/reactor.js.map +1 -0
  51. package/dist/src/registry/implementation.d.ts +62 -0
  52. package/dist/src/registry/implementation.d.ts.map +1 -0
  53. package/dist/src/registry/implementation.js +96 -0
  54. package/dist/src/registry/implementation.js.map +1 -0
  55. package/dist/src/registry/index.d.ts +3 -0
  56. package/dist/src/registry/index.d.ts.map +1 -0
  57. package/dist/src/registry/index.js +2 -0
  58. package/dist/src/registry/index.js.map +1 -0
  59. package/dist/src/registry/interfaces.d.ts +39 -0
  60. package/dist/src/registry/interfaces.d.ts.map +1 -0
  61. package/dist/src/registry/interfaces.js +2 -0
  62. package/dist/src/registry/interfaces.js.map +1 -0
  63. package/dist/src/shared/factories.d.ts +16 -0
  64. package/dist/src/shared/factories.d.ts.map +1 -0
  65. package/dist/src/shared/factories.js +33 -0
  66. package/dist/src/shared/factories.js.map +1 -0
  67. package/dist/src/shared/types.d.ts +83 -19
  68. package/dist/src/shared/types.d.ts.map +1 -1
  69. package/dist/src/shared/types.js +30 -1
  70. package/dist/src/shared/types.js.map +1 -1
  71. package/dist/src/shared/utils.d.ts +3 -0
  72. package/dist/src/shared/utils.d.ts.map +1 -0
  73. package/dist/src/shared/utils.js +8 -0
  74. package/dist/src/shared/utils.js.map +1 -0
  75. package/dist/src/utils.d.ts +11 -0
  76. package/dist/src/utils.d.ts.map +1 -0
  77. package/dist/src/utils.js +29 -0
  78. package/dist/src/utils.js.map +1 -0
  79. package/dist/test/executor-integration.test.d.ts +2 -0
  80. package/dist/test/executor-integration.test.d.ts.map +1 -0
  81. package/dist/test/executor-integration.test.js +290 -0
  82. package/dist/test/executor-integration.test.js.map +1 -0
  83. package/dist/test/factories.d.ts +131 -0
  84. package/dist/test/factories.d.ts.map +1 -0
  85. package/dist/test/factories.js +347 -0
  86. package/dist/test/factories.js.map +1 -0
  87. package/dist/test/queue.test.js +219 -28
  88. package/dist/test/queue.test.js.map +1 -1
  89. package/dist/test/reactor-queue.test.d.ts +2 -0
  90. package/dist/test/reactor-queue.test.d.ts.map +1 -0
  91. package/dist/test/reactor-queue.test.js +328 -0
  92. package/dist/test/reactor-queue.test.js.map +1 -0
  93. package/dist/test/reactor-read.test.d.ts +2 -0
  94. package/dist/test/reactor-read.test.d.ts.map +1 -0
  95. package/dist/test/reactor-read.test.js +355 -0
  96. package/dist/test/reactor-read.test.js.map +1 -0
  97. package/dist/test/registry.test.d.ts +2 -0
  98. package/dist/test/registry.test.d.ts.map +1 -0
  99. package/dist/test/registry.test.js +182 -0
  100. package/dist/test/registry.test.js.map +1 -0
  101. package/dist/test/simple-job-executor-manager.test.d.ts +2 -0
  102. package/dist/test/simple-job-executor-manager.test.d.ts.map +1 -0
  103. package/dist/test/simple-job-executor-manager.test.js +132 -0
  104. package/dist/test/simple-job-executor-manager.test.js.map +1 -0
  105. package/dist/test/simple-job-executor.test.d.ts +2 -0
  106. package/dist/test/simple-job-executor.test.d.ts.map +1 -0
  107. package/dist/test/simple-job-executor.test.js +139 -0
  108. package/dist/test/simple-job-executor.test.js.map +1 -0
  109. package/dist/test/utils.test.d.ts +2 -0
  110. package/dist/test/utils.test.d.ts.map +1 -0
  111. package/dist/test/utils.test.js +66 -0
  112. package/dist/test/utils.test.js.map +1 -0
  113. package/dist/tsconfig.tsbuildinfo +1 -1
  114. package/dist/vitest.config.d.ts +3 -0
  115. package/dist/vitest.config.d.ts.map +1 -0
  116. package/dist/vitest.config.js +11 -0
  117. package/dist/vitest.config.js.map +1 -0
  118. package/package.json +6 -1
  119. package/dist/src/executor/job-executor.d.ts +0 -62
  120. package/dist/src/executor/job-executor.d.ts.map +0 -1
  121. package/dist/src/executor/job-executor.js +0 -325
  122. package/dist/src/executor/job-executor.js.map +0 -1
  123. package/dist/test/job-executor.test.d.ts +0 -2
  124. package/dist/test/job-executor.test.d.ts.map +0 -1
  125. package/dist/test/job-executor.test.js +0 -581
  126. package/dist/test/job-executor.test.js.map +0 -1
@@ -0,0 +1,347 @@
1
+ import { MemoryStorage, ReactorBuilder, driveDocumentModelModule, } from "document-drive";
2
+ import { documentModelDocumentModelModule, } from "document-model";
3
+ import { v4 as uuidv4 } from "uuid";
4
+ import { vi } from "vitest";
5
+ import { EventBus } from "../src/events/event-bus.js";
6
+ import { SimpleJobExecutor } from "../src/executor/simple-job-executor.js";
7
+ import { SimpleJobExecutorManager } from "../src/executor/simple-job-executor-manager.js";
8
+ import { InMemoryQueue } from "../src/queue/queue.js";
9
+ import { Reactor } from "../src/reactor.js";
10
+ import { DocumentModelRegistry } from "../src/registry/implementation.js";
11
+ /**
12
+ * Factory for creating test Job objects
13
+ */
14
+ export function createTestJob(overrides = {}) {
15
+ const defaultJob = {
16
+ id: overrides.id || `job-${uuidv4()}`,
17
+ documentId: "doc-1",
18
+ scope: "global",
19
+ branch: "main",
20
+ operation: createTestOperation(overrides.operation ? { ...overrides.operation } : undefined),
21
+ createdAt: new Date().toISOString(),
22
+ queueHint: [],
23
+ retryCount: 0,
24
+ maxRetries: 3,
25
+ };
26
+ return {
27
+ ...defaultJob,
28
+ ...overrides,
29
+ };
30
+ }
31
+ /**
32
+ * Factory for creating minimal Job objects (useful for performance tests)
33
+ */
34
+ export function createMinimalJob(overrides = {}) {
35
+ return {
36
+ id: overrides.id || `job-${uuidv4()}`,
37
+ documentId: overrides.documentId || "doc-1",
38
+ scope: overrides.scope || "global",
39
+ branch: overrides.branch || "main",
40
+ operation: createMinimalOperation(),
41
+ createdAt: overrides.createdAt || "2023-01-01T00:00:00.000Z",
42
+ queueHint: overrides.queueHint || [],
43
+ ...overrides,
44
+ };
45
+ }
46
+ /**
47
+ * Factory for creating test Operation objects
48
+ */
49
+ export function createTestOperation(overrides = {}) {
50
+ const defaultOperation = {
51
+ index: 1,
52
+ timestampUtcMs: new Date().toISOString(),
53
+ hash: "test-hash",
54
+ skip: 0,
55
+ action: createTestAction(overrides.action ? { ...overrides.action } : undefined),
56
+ id: "op-1",
57
+ };
58
+ return {
59
+ ...defaultOperation,
60
+ ...overrides,
61
+ };
62
+ }
63
+ /**
64
+ * Factory for creating minimal Operation objects
65
+ */
66
+ export function createMinimalOperation(overrides = {}) {
67
+ return {
68
+ index: overrides.index ?? 0,
69
+ timestampUtcMs: overrides.timestampUtcMs || "2023-01-01T00:00:00.000Z",
70
+ hash: overrides.hash || "hash-123",
71
+ skip: overrides.skip ?? 0,
72
+ action: overrides.action || createMinimalAction(),
73
+ id: overrides.id || `op-${uuidv4()}`,
74
+ };
75
+ }
76
+ /**
77
+ * Factory for creating test Action objects
78
+ */
79
+ export function createTestAction(overrides = {}) {
80
+ const defaultAction = {
81
+ id: uuidv4(),
82
+ type: "test-operation",
83
+ timestampUtcMs: new Date().toISOString(),
84
+ input: { test: "data" },
85
+ scope: "global",
86
+ };
87
+ return {
88
+ ...defaultAction,
89
+ ...overrides,
90
+ };
91
+ }
92
+ /**
93
+ * Factory for creating minimal Action objects
94
+ */
95
+ export function createMinimalAction(overrides = {}) {
96
+ return {
97
+ id: overrides.id || `action-${uuidv4()}`,
98
+ type: overrides.type || "CREATE",
99
+ timestampUtcMs: overrides.timestampUtcMs || "2023-01-01T00:00:00.000Z",
100
+ input: overrides.input || { data: "test" },
101
+ scope: overrides.scope || "global",
102
+ };
103
+ }
104
+ /**
105
+ * Factory for creating specific document model actions
106
+ */
107
+ export function createDocumentModelAction(type, overrides = {}) {
108
+ const actionTypes = {
109
+ SET_NAME: { name: "Test Name" },
110
+ SET_DESCRIPTION: { description: "Test Description" },
111
+ CREATE: { name: "New Document" },
112
+ UPDATE: { data: "Updated Data" },
113
+ };
114
+ return {
115
+ id: uuidv4(),
116
+ type,
117
+ scope: "global",
118
+ timestampUtcMs: String(Date.now()),
119
+ input: actionTypes[type] || {},
120
+ ...overrides,
121
+ };
122
+ }
123
+ /**
124
+ * Factory for creating mock PHDocument objects
125
+ */
126
+ export function createMockDocument(overrides = {}) {
127
+ const baseDocument = documentModelDocumentModelModule.utils.createDocument();
128
+ if (overrides.id) {
129
+ baseDocument.header.id = overrides.id;
130
+ }
131
+ if (overrides.slug) {
132
+ baseDocument.header.slug = overrides.slug;
133
+ }
134
+ if (overrides.documentType) {
135
+ baseDocument.header.documentType = overrides.documentType;
136
+ }
137
+ if (overrides.state) {
138
+ baseDocument.state = {
139
+ ...baseDocument.state,
140
+ ...overrides.state,
141
+ };
142
+ }
143
+ return baseDocument;
144
+ }
145
+ /**
146
+ * Factory for creating test EventBus instances
147
+ */
148
+ export function createTestEventBus() {
149
+ return new EventBus();
150
+ }
151
+ /**
152
+ * Factory for creating test EventBus with mock emit
153
+ */
154
+ export function createMockEventBus() {
155
+ const eventBus = new EventBus();
156
+ const mockEmit = vi.fn().mockResolvedValue(undefined);
157
+ eventBus.emit = mockEmit;
158
+ return {
159
+ eventBus,
160
+ mockEmit,
161
+ };
162
+ }
163
+ /**
164
+ * Factory for creating test Queue instances
165
+ */
166
+ export function createTestQueue(eventBus) {
167
+ return new InMemoryQueue(eventBus || createTestEventBus());
168
+ }
169
+ /**
170
+ * Factory for creating test Registry instances
171
+ */
172
+ export function createTestRegistry(modules) {
173
+ const registry = new DocumentModelRegistry();
174
+ if (modules) {
175
+ modules.forEach((module) => registry.registerModules(module));
176
+ }
177
+ else {
178
+ // Register default module
179
+ registry.registerModules(documentModelDocumentModelModule);
180
+ }
181
+ return registry;
182
+ }
183
+ /**
184
+ * Factory for creating mock JobExecutor
185
+ */
186
+ export function createMockJobExecutor(overrides = {}) {
187
+ return {
188
+ executeJob: vi.fn().mockResolvedValue({ success: true }),
189
+ ...overrides,
190
+ };
191
+ }
192
+ /**
193
+ * Factory for creating mock SimpleJobExecutor with spy functions
194
+ */
195
+ export function createMockSimpleJobExecutor() {
196
+ const executeJobSpy = vi.fn().mockResolvedValue({
197
+ success: true,
198
+ result: {},
199
+ });
200
+ const executor = {
201
+ executeJob: executeJobSpy,
202
+ };
203
+ return {
204
+ executor,
205
+ executeJobSpy,
206
+ };
207
+ }
208
+ /**
209
+ * Factory for creating mock IDocumentStorage
210
+ */
211
+ export function createMockDocumentStorage(overrides = {}) {
212
+ return {
213
+ get: vi.fn().mockResolvedValue({
214
+ header: {
215
+ id: "doc-1",
216
+ documentType: "powerhouse/document-model",
217
+ },
218
+ operations: { global: [] },
219
+ state: {},
220
+ }),
221
+ set: vi.fn().mockResolvedValue(undefined),
222
+ delete: vi.fn().mockResolvedValue(undefined),
223
+ exists: vi.fn().mockResolvedValue(false),
224
+ getChildren: vi.fn().mockResolvedValue([]),
225
+ findByType: vi.fn().mockResolvedValue([]),
226
+ resolveIds: vi.fn().mockResolvedValue([]),
227
+ ...overrides,
228
+ };
229
+ }
230
+ /**
231
+ * Factory for creating mock IDocumentOperationStorage
232
+ */
233
+ export function createMockOperationStorage(overrides = {}) {
234
+ return {
235
+ addDocumentOperations: vi.fn().mockResolvedValue(undefined),
236
+ ...overrides,
237
+ };
238
+ }
239
+ /**
240
+ * Factory for creating a complete test reactor setup
241
+ */
242
+ export async function createTestReactorSetup(documentModels = [
243
+ documentModelDocumentModelModule,
244
+ driveDocumentModelModule,
245
+ ]) {
246
+ const storage = new MemoryStorage();
247
+ const eventBus = new EventBus();
248
+ const queue = new InMemoryQueue(eventBus);
249
+ // Create real drive server
250
+ const builder = new ReactorBuilder(documentModels).withStorage(storage);
251
+ const driveServer = builder.build();
252
+ await driveServer.initialize();
253
+ // Create registry and register modules
254
+ const registry = new DocumentModelRegistry();
255
+ registry.registerModules(documentModelDocumentModelModule);
256
+ // Create job executor
257
+ const jobExecutor = new SimpleJobExecutor(registry, storage, storage);
258
+ // Create reactor
259
+ const reactor = new Reactor(driveServer, storage, eventBus, queue, jobExecutor);
260
+ return {
261
+ reactor,
262
+ driveServer,
263
+ storage,
264
+ eventBus,
265
+ queue,
266
+ jobExecutor,
267
+ registry,
268
+ };
269
+ }
270
+ /**
271
+ * Factory for creating SimpleJobExecutorManager with dependencies
272
+ */
273
+ export function createTestJobExecutorManager(queue, eventBus, executor) {
274
+ const actualQueue = queue || createTestQueue();
275
+ const actualEventBus = eventBus || createTestEventBus();
276
+ const actualExecutor = executor || createMockJobExecutor();
277
+ const manager = new SimpleJobExecutorManager(() => actualExecutor, actualEventBus, actualQueue);
278
+ return {
279
+ manager,
280
+ queue: actualQueue,
281
+ eventBus: actualEventBus,
282
+ executor: actualExecutor,
283
+ };
284
+ }
285
+ /**
286
+ * Factory for creating test data sets
287
+ */
288
+ export function createTestJobSet(count, baseOverrides = {}) {
289
+ return Array.from({ length: count }, (_, i) => createTestJob({
290
+ id: `job-${i + 1}`,
291
+ ...baseOverrides,
292
+ operation: createTestOperation({
293
+ index: i,
294
+ action: createTestAction({
295
+ input: { name: `Action ${i + 1}` },
296
+ }),
297
+ }),
298
+ }));
299
+ }
300
+ /**
301
+ * Factory for creating jobs with dependencies
302
+ */
303
+ export function createJobWithDependencies(id, dependencies, overrides = {}) {
304
+ return createTestJob({
305
+ id,
306
+ queueHint: dependencies,
307
+ ...overrides,
308
+ });
309
+ }
310
+ /**
311
+ * Factory for creating a dependency chain of jobs
312
+ */
313
+ export function createJobDependencyChain(length) {
314
+ const jobs = [];
315
+ for (let i = 0; i < length; i++) {
316
+ const dependencies = i === 0 ? [] : [`job-${i}`];
317
+ jobs.push(createJobWithDependencies(`job-${i + 1}`, dependencies, {
318
+ operation: createTestOperation({
319
+ index: i,
320
+ action: createTestAction({
321
+ input: { name: `Chain Action ${i + 1}` },
322
+ }),
323
+ }),
324
+ }));
325
+ }
326
+ return jobs;
327
+ }
328
+ /**
329
+ * Factory for creating multiple actions for testing
330
+ */
331
+ export function createTestActions(count, type = "SET_NAME") {
332
+ return Array.from({ length: count }, (_, i) => createDocumentModelAction(type, {
333
+ input: { name: `Action ${i + 1}` },
334
+ timestampUtcMs: String(Date.now() + i * 1000),
335
+ }));
336
+ }
337
+ /**
338
+ * Factory for creating mock documents in bulk
339
+ */
340
+ export function createTestDocuments(count, baseOverrides = {}) {
341
+ return Array.from({ length: count }, (_, i) => createMockDocument({
342
+ id: `doc-${i + 1}`,
343
+ slug: `doc-${i + 1}`,
344
+ ...baseOverrides,
345
+ }));
346
+ }
347
+ //# sourceMappingURL=factories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factories.js","sourceRoot":"","sources":["../../test/factories.ts"],"names":[],"mappings":"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"}
@@ -1,36 +1,15 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { EventBus } from "../src/events/event-bus.js";
1
+ import { beforeEach, describe, expect, it } from "vitest";
3
2
  import { InMemoryQueue } from "../src/queue/queue.js";
4
3
  import { QueueEventTypes, } from "../src/queue/types.js";
4
+ import { createTestJob, createMockEventBus, createTestOperation, createJobWithDependencies, createJobDependencyChain, } from "./factories.js";
5
5
  describe("InMemoryQueue", () => {
6
6
  let queue;
7
7
  let eventBus;
8
8
  let mockEventBusEmit;
9
- const createTestOperation = (overrides = {}) => ({
10
- index: 1,
11
- timestampUtcMs: new Date().toISOString(),
12
- hash: "test-hash",
13
- skip: 0,
14
- type: "test-operation",
15
- input: { test: "data" },
16
- id: "op-1",
17
- ...overrides,
18
- });
19
- const createTestJob = (overrides = {}) => ({
20
- id: "job-1",
21
- documentId: "doc-1",
22
- scope: "global",
23
- branch: "main",
24
- operation: createTestOperation(),
25
- createdAt: new Date().toISOString(),
26
- retryCount: 0,
27
- maxRetries: 3,
28
- ...overrides,
29
- });
30
9
  beforeEach(() => {
31
- eventBus = new EventBus();
32
- mockEventBusEmit = vi.fn().mockResolvedValue(undefined);
33
- eventBus.emit = mockEventBusEmit;
10
+ const mocks = createMockEventBus();
11
+ eventBus = mocks.eventBus;
12
+ mockEventBusEmit = mocks.mockEmit;
34
13
  queue = new InMemoryQueue(eventBus);
35
14
  });
36
15
  describe("enqueue", () => {
@@ -349,6 +328,212 @@ describe("InMemoryQueue", () => {
349
328
  expect(await queue.size("doc:with:colons", "scope-with-dashes", "branch_with_underscores")).toBe(1);
350
329
  });
351
330
  });
331
+ describe("dependency management", () => {
332
+ it("should dequeue job with no dependencies immediately", async () => {
333
+ const job = createTestJob({ id: "job-1", queueHint: [] });
334
+ await queue.enqueue(job);
335
+ const dequeuedJob = await queue.dequeue(job.documentId, job.scope, job.branch);
336
+ expect(dequeuedJob?.id).toBe("job-1");
337
+ });
338
+ it("should block job with unmet dependencies", async () => {
339
+ const job1 = createTestJob({ id: "job-1", queueHint: ["job-0"] });
340
+ await queue.enqueue(job1);
341
+ const dequeuedJob = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
342
+ expect(dequeuedJob).toBeNull();
343
+ expect(await queue.size(job1.documentId, job1.scope, job1.branch)).toBe(1);
344
+ });
345
+ it("should dequeue job after dependencies are completed", async () => {
346
+ const job1 = createTestJob({ id: "job-1", queueHint: [] });
347
+ const job2 = createTestJob({ id: "job-2", queueHint: ["job-1"] });
348
+ await queue.enqueue(job1);
349
+ await queue.enqueue(job2);
350
+ // First job should be dequeued
351
+ const dequeuedJob1 = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
352
+ expect(dequeuedJob1?.id).toBe("job-1");
353
+ // Second job should be blocked
354
+ const blockedJob = await queue.dequeue(job2.documentId, job2.scope, job2.branch);
355
+ expect(blockedJob).toBeNull();
356
+ // Complete first job
357
+ await queue.completeJob("job-1");
358
+ // Now second job should be available
359
+ const dequeuedJob2 = await queue.dequeue(job2.documentId, job2.scope, job2.branch);
360
+ expect(dequeuedJob2?.id).toBe("job-2");
361
+ });
362
+ it("should handle multiple dependencies", async () => {
363
+ const job1 = createTestJob({ id: "job-1", queueHint: [] });
364
+ const job2 = createTestJob({ id: "job-2", queueHint: [] });
365
+ const job3 = createTestJob({
366
+ id: "job-3",
367
+ queueHint: ["job-1", "job-2"],
368
+ });
369
+ await queue.enqueue(job1);
370
+ await queue.enqueue(job2);
371
+ await queue.enqueue(job3);
372
+ // Dequeue and complete first job
373
+ const dequeuedJob1 = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
374
+ expect(dequeuedJob1?.id).toBe("job-1");
375
+ await queue.completeJob("job-1");
376
+ // Next dequeue should return job-2 (not blocked), not job-3 (still blocked)
377
+ const nextJob = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
378
+ expect(nextJob?.id).toBe("job-2");
379
+ await queue.completeJob("job-2");
380
+ // Now job 3 should be available
381
+ const dequeuedJob3 = await queue.dequeue(job3.documentId, job3.scope, job3.branch);
382
+ expect(dequeuedJob3?.id).toBe("job-3");
383
+ });
384
+ it("should handle dependency chains", async () => {
385
+ const jobs = createJobDependencyChain(4);
386
+ for (const job of jobs) {
387
+ await queue.enqueue(job);
388
+ }
389
+ // Process in order
390
+ const d1 = await queue.dequeue(jobs[0].documentId, jobs[0].scope, jobs[0].branch);
391
+ expect(d1?.id).toBe("job-1");
392
+ // Others should be blocked
393
+ expect(await queue.dequeue(jobs[1].documentId, jobs[1].scope, jobs[1].branch)).toBeNull();
394
+ expect(await queue.dequeue(jobs[2].documentId, jobs[2].scope, jobs[2].branch)).toBeNull();
395
+ expect(await queue.dequeue(jobs[3].documentId, jobs[3].scope, jobs[3].branch)).toBeNull();
396
+ await queue.completeJob("job-1");
397
+ const d2 = await queue.dequeue(jobs[1].documentId, jobs[1].scope, jobs[1].branch);
398
+ expect(d2?.id).toBe("job-2");
399
+ // job-3 and job-4 still blocked
400
+ expect(await queue.dequeue(jobs[2].documentId, jobs[2].scope, jobs[2].branch)).toBeNull();
401
+ expect(await queue.dequeue(jobs[3].documentId, jobs[3].scope, jobs[3].branch)).toBeNull();
402
+ await queue.completeJob("job-2");
403
+ const d3 = await queue.dequeue(jobs[2].documentId, jobs[2].scope, jobs[2].branch);
404
+ expect(d3?.id).toBe("job-3");
405
+ // job-4 still blocked
406
+ expect(await queue.dequeue(jobs[3].documentId, jobs[3].scope, jobs[3].branch)).toBeNull();
407
+ await queue.completeJob("job-3");
408
+ const d4 = await queue.dequeue(jobs[3].documentId, jobs[3].scope, jobs[3].branch);
409
+ expect(d4?.id).toBe("job-4");
410
+ });
411
+ it("should dequeue jobs out of order based on dependencies", async () => {
412
+ const job1 = createTestJob({ id: "job-1", queueHint: ["job-0"] });
413
+ const job2 = createTestJob({ id: "job-2", queueHint: [] });
414
+ const job3 = createTestJob({ id: "job-3", queueHint: [] });
415
+ await queue.enqueue(job1);
416
+ await queue.enqueue(job2);
417
+ await queue.enqueue(job3);
418
+ // Job 1 is blocked, so job 2 should be dequeued
419
+ const dequeuedJob = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
420
+ expect(dequeuedJob?.id).toBe("job-2");
421
+ });
422
+ it("should handle dependencies across different queues", async () => {
423
+ const job1 = createTestJob({
424
+ id: "job-1",
425
+ documentId: "doc-1",
426
+ queueHint: [],
427
+ });
428
+ const job2 = createTestJob({
429
+ id: "job-2",
430
+ documentId: "doc-2",
431
+ queueHint: ["job-1"],
432
+ });
433
+ await queue.enqueue(job1);
434
+ await queue.enqueue(job2);
435
+ // Job 2 should be blocked even though it's in a different queue
436
+ const blockedJob = await queue.dequeue("doc-2", "global", "main");
437
+ expect(blockedJob).toBeNull();
438
+ // Dequeue and complete job 1
439
+ const dequeuedJob1 = await queue.dequeue("doc-1", "global", "main");
440
+ expect(dequeuedJob1?.id).toBe("job-1");
441
+ await queue.completeJob("job-1");
442
+ // Now job 2 should be available
443
+ const dequeuedJob2 = await queue.dequeue("doc-2", "global", "main");
444
+ expect(dequeuedJob2?.id).toBe("job-2");
445
+ });
446
+ it("should work with dequeueNext respecting dependencies", async () => {
447
+ const job1 = createTestJob({
448
+ id: "job-1",
449
+ documentId: "doc-1",
450
+ queueHint: ["job-0"],
451
+ });
452
+ const job2 = createTestJob({
453
+ id: "job-2",
454
+ documentId: "doc-2",
455
+ queueHint: [],
456
+ });
457
+ await queue.enqueue(job1);
458
+ await queue.enqueue(job2);
459
+ // Should dequeue job-2 since job-1 is blocked
460
+ const dequeuedJob = await queue.dequeueNext();
461
+ expect(dequeuedJob?.id).toBe("job-2");
462
+ });
463
+ it("should handle circular dependencies by blocking all involved jobs", async () => {
464
+ const job1 = createJobWithDependencies("job-1", ["job-2"]);
465
+ const job2 = createJobWithDependencies("job-2", ["job-1"]);
466
+ await queue.enqueue(job1);
467
+ await queue.enqueue(job2);
468
+ // Both jobs should be blocked
469
+ const dequeuedJob1 = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
470
+ expect(dequeuedJob1).toBeNull();
471
+ const dequeuedJob2 = await queue.dequeue(job2.documentId, job2.scope, job2.branch);
472
+ expect(dequeuedJob2).toBeNull();
473
+ });
474
+ it("should handle self-dependencies by blocking the job", async () => {
475
+ const job = createJobWithDependencies("job-1", ["job-1"]);
476
+ await queue.enqueue(job);
477
+ const dequeuedJob = await queue.dequeue(job.documentId, job.scope, job.branch);
478
+ expect(dequeuedJob).toBeNull();
479
+ });
480
+ it("should clear completed jobs tracking when clearAll is called", async () => {
481
+ const job1 = createTestJob({ id: "job-1", queueHint: [] });
482
+ const job2 = createTestJob({ id: "job-2", queueHint: ["job-1"] });
483
+ await queue.enqueue(job1);
484
+ // Complete job-1
485
+ await queue.dequeue(job1.documentId, job1.scope, job1.branch);
486
+ await queue.completeJob("job-1");
487
+ // Clear all
488
+ await queue.clearAll();
489
+ // Re-enqueue job-2 - it should be blocked again since completed jobs were cleared
490
+ await queue.enqueue(job2);
491
+ const dequeuedJob = await queue.dequeue(job2.documentId, job2.scope, job2.branch);
492
+ expect(dequeuedJob).toBeNull();
493
+ });
494
+ it("should handle already completed dependencies", async () => {
495
+ // Complete a job that doesn't exist in queue
496
+ await queue.completeJob("job-0");
497
+ // Enqueue a job that depends on it
498
+ const job1 = createJobWithDependencies("job-1", ["job-0"]);
499
+ await queue.enqueue(job1);
500
+ // Should be able to dequeue since dependency is already complete
501
+ const dequeuedJob = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
502
+ expect(dequeuedJob?.id).toBe("job-1");
503
+ });
504
+ it("should handle mixed dependencies (some met, some unmet)", async () => {
505
+ // Complete one dependency
506
+ await queue.completeJob("job-0");
507
+ // Enqueue a job with mixed dependencies
508
+ const job = createTestJob({
509
+ id: "job-3",
510
+ queueHint: ["job-0", "job-1", "job-2"],
511
+ });
512
+ await queue.enqueue(job);
513
+ // Should be blocked (job-1 and job-2 not complete)
514
+ let dequeuedJob = await queue.dequeue(job.documentId, job.scope, job.branch);
515
+ expect(dequeuedJob).toBeNull();
516
+ // Complete remaining dependencies
517
+ await queue.completeJob("job-1");
518
+ await queue.completeJob("job-2");
519
+ // Now should be available
520
+ dequeuedJob = await queue.dequeue(job.documentId, job.scope, job.branch);
521
+ expect(dequeuedJob?.id).toBe("job-3");
522
+ });
523
+ it("should handle failJob without marking as completed", async () => {
524
+ const job1 = createTestJob({ id: "job-1", queueHint: [] });
525
+ const job2 = createTestJob({ id: "job-2", queueHint: ["job-1"] });
526
+ await queue.enqueue(job1);
527
+ await queue.enqueue(job2);
528
+ // Dequeue and fail job 1
529
+ const d1 = await queue.dequeue(job1.documentId, job1.scope, job1.branch);
530
+ expect(d1?.id).toBe("job-1");
531
+ await queue.failJob("job-1", "Test error");
532
+ // Job 2 should still be blocked since job 1 wasn't completed
533
+ const dequeuedJob = await queue.dequeue(job2.documentId, job2.scope, job2.branch);
534
+ expect(dequeuedJob).toBeNull();
535
+ });
536
+ });
352
537
  describe("job properties", () => {
353
538
  it("should preserve all job properties", async () => {
354
539
  const operation = createTestOperation({
@@ -356,8 +541,13 @@ describe("InMemoryQueue", () => {
356
541
  timestampUtcMs: "2023-01-01T00:00:00.000Z",
357
542
  hash: "custom-hash",
358
543
  skip: 5,
359
- type: "custom-operation",
360
- input: { custom: "input" },
544
+ action: {
545
+ id: "action-1",
546
+ type: "custom-operation",
547
+ timestampUtcMs: "2023-01-01T00:00:00.000Z",
548
+ input: { custom: "input" },
549
+ scope: "global",
550
+ },
361
551
  error: "test error",
362
552
  id: "custom-op-id",
363
553
  });
@@ -383,6 +573,7 @@ describe("InMemoryQueue", () => {
383
573
  branch: "main",
384
574
  operation: createTestOperation(),
385
575
  createdAt: new Date().toISOString(),
576
+ queueHint: [],
386
577
  // retryCount and maxRetries are optional
387
578
  };
388
579
  await queue.enqueue(job);