Skip to content

Commit 22941b1

Browse files
authored
Merge pull request #67 from nirukk52/feat/m5-awilix-di-container
feat(m5): integrate awilix di container + enable parallel tests
2 parents 91b749e + 1450a1b commit 22941b1

File tree

10 files changed

+151
-106
lines changed

10 files changed

+151
-106
lines changed

.cursor/commands/open-pr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
1. Commit the code.
2-
2. Push the code.
3-
3. Create a Pr.
2+
3. Push the code.
3+
4. Create a Pr.

packages/database/prisma/zod/.prisma-zod-generator-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"version": "1.0",
33
"generatorVersion": "0.0.0",
4-
"generatedAt": "2025-10-18T21:38:31.658Z",
4+
"generatedAt": "2025-10-18T23:48:22.184Z",
55
"outputPath": "/Users/priyankalalge/RealSaas/Screengraph/base/packages/database/prisma/zod",
66
"files": [
77
"index.ts"

packages/features/agents-run/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@
2424
"dev:feature": "node ./src/infra/workers/run-worker.js"
2525
},
2626
"dependencies": {
27+
"@orpc/server": "^1.9.0",
28+
"@repo/agents-core": "workspace:*",
2729
"@repo/database": "workspace:*",
2830
"@repo/logs": "workspace:*",
29-
"@repo/agents-core": "workspace:*",
3031
"@sg/agents-contracts": "workspace:*",
3132
"@sg/eventbus": "workspace:*",
3233
"@sg/eventbus-inmemory": "workspace:*",
3334
"@sg/queue": "workspace:*",
3435
"@sg/queue-inmemory": "workspace:*",
35-
"@orpc/server": "^1.9.0"
36+
"awilix": "^12.0.5"
37+
},
38+
"devDependencies": {
39+
"@types/node": "^24.8.1"
3640
}
3741
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { InMemoryEventBus } from "@sg/eventbus-inmemory";
2+
import { InMemoryQueue } from "@sg/queue-inmemory";
3+
import { asClass, createContainer } from "awilix";
4+
5+
/**
6+
* Create a new Awilix container for agents-run with default in-memory registrations.
7+
*
8+
* Default scope: singleton (one instance per container).
9+
* Tests create a fresh container per test for isolation.
10+
* Production uses a single container with optional Redis/BullMQ registrations.
11+
*/
12+
export function createAgentsRunContainer() {
13+
const container = createContainer();
14+
15+
container.register({
16+
bus: asClass(InMemoryEventBus).singleton(),
17+
queue: asClass(InMemoryQueue).singleton(),
18+
});
19+
20+
return container;
21+
}
Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
11
import type { EventBusPort } from "@sg/eventbus";
2-
import { InMemoryEventBus } from "@sg/eventbus-inmemory";
32
import type { QueuePort } from "@sg/queue";
4-
import { InMemoryQueue } from "@sg/queue-inmemory";
3+
import { asValue } from "awilix";
4+
import { createAgentsRunContainer } from "./container";
55

66
export interface Infra {
77
bus: EventBusPort;
88
queue: QueuePort;
99
}
1010

11-
let current: Infra | null = null;
11+
let currentContainer = createAgentsRunContainer();
1212

13-
function createDefaultInfra(): Infra {
13+
export function getInfra(): Infra {
1414
return {
15-
bus: new InMemoryEventBus(),
16-
queue: new InMemoryQueue(),
15+
bus: currentContainer.resolve("bus"),
16+
queue: currentContainer.resolve("queue"),
1717
};
1818
}
1919

20-
export function getInfra(): Infra {
21-
if (!current) {
22-
current = createDefaultInfra();
23-
}
24-
return current;
25-
}
26-
2720
export function setInfra(next: Infra): void {
28-
current = next;
21+
currentContainer = createAgentsRunContainer();
22+
currentContainer.register({
23+
bus: asValue(next.bus),
24+
queue: asValue(next.queue),
25+
});
2926
}
3027

3128
export function resetInfra(): void {
32-
const i = getInfra();
33-
(i.bus as { reset?: () => void }).reset?.();
34-
(i.queue as { reset?: () => void }).reset?.();
29+
const infra = getInfra();
30+
(infra.bus as { reset?: () => void }).reset?.();
31+
(infra.queue as { reset?: () => void }).reset?.();
3532
}

packages/features/agents-run/tests/integration/debug-stream.spec.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,55 @@ import {
55
awaitStreamCompletion,
66
waitForRunCompletion,
77
} from "./helpers/await-outbox";
8+
import { processRunDeterministically } from "./helpers/process-run";
89
import { runAgentsRunTest } from "./helpers/test-harness";
910

1011
describe.sequential("M3 Debug Stream Inspection", () => {
1112
it.skip("prints full event stream with all fields", async () => {
12-
await runAgentsRunTest(async () => {
13-
// Arrange
14-
const runId = `debug-${Math.random().toString(36).slice(2)}`;
15-
const iter = streamRun(runId);
13+
await runAgentsRunTest(
14+
async () => {
15+
// Arrange
16+
const runId = `debug-${Math.random().toString(36).slice(2)}`;
17+
const iter = streamRun(runId);
1618

17-
// Act: start run and collect all events
18-
await startRun(runId);
19-
await waitForRunCompletion(runId);
20-
const events = await awaitStreamCompletion(iter);
19+
// Act: start run and collect all events
20+
await startRun(runId);
21+
await processRunDeterministically(runId);
22+
await waitForRunCompletion(runId);
23+
const events = await awaitStreamCompletion(iter);
2124

22-
// Observable: print event stream for inspection
23-
console.log("\n========== M3 EVENT STREAM DEBUG =========\n");
24-
console.log(`RunId: ${runId}`);
25-
console.log(`Total Events: ${events.length}\n`);
25+
// Observable: print event stream for inspection
26+
console.log("\n========== M3 EVENT STREAM DEBUG =========\n");
27+
console.log(`RunId: ${runId}`);
28+
console.log(`Total Events: ${events.length}\n`);
2629

27-
events.forEach((e, idx) => {
28-
const eventNum = `Event ${idx + 1}/${events.length}`;
29-
const eventType = e.type.padEnd(14);
30-
const seq = `seq=${e.seq}`;
30+
events.forEach((e, idx) => {
31+
const eventNum = `Event ${idx + 1}/${events.length}`;
32+
const eventType = e.type.padEnd(14);
33+
const seq = `seq=${e.seq}`;
3134

32-
if (e.type === "NodeStarted" || e.type === "NodeFinished") {
33-
console.log(
34-
`${eventNum}: ${eventType} ${seq} name="${e.name}"`,
35-
);
36-
} else if (e.type === "DebugTrace") {
37-
console.log(
38-
`${eventNum}: ${eventType} ${seq} fn="${e.fn}"`,
39-
);
40-
} else {
41-
console.log(`${eventNum}: ${eventType} ${seq}`);
42-
}
43-
});
35+
if (e.type === "NodeStarted" || e.type === "NodeFinished") {
36+
console.log(
37+
`${eventNum}: ${eventType} ${seq} name="${e.name}"`,
38+
);
39+
} else if (e.type === "DebugTrace") {
40+
console.log(
41+
`${eventNum}: ${eventType} ${seq} fn="${e.fn}"`,
42+
);
43+
} else {
44+
console.log(`${eventNum}: ${eventType} ${seq}`);
45+
}
46+
});
4447

45-
console.log("\n========== DETAILED JSON =========\n");
46-
events.forEach((e, idx) => {
47-
console.log(`--- Event ${idx + 1}: ${e.type} ---`);
48-
console.log(JSON.stringify(e, null, 2));
49-
});
48+
console.log("\n========== DETAILED JSON =========\n");
49+
events.forEach((e, idx) => {
50+
console.log(`--- Event ${idx + 1}: ${e.type} ---`);
51+
console.log(JSON.stringify(e, null, 2));
52+
});
5053

51-
console.log("\n========== END DEBUG STREAM =========\n");
52-
});
54+
console.log("\n========== END DEBUG STREAM =========\n");
55+
},
56+
{ startWorker: false },
57+
);
5358
}, 30000);
5459
});

packages/features/agents-run/tests/integration/helpers/process-run.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@ export async function processRunDeterministically(
1616
cancelToken: new StubCancellationToken(),
1717
});
1818

19+
// Wait for async append chain to complete
20+
await new Promise((resolve) => setTimeout(resolve, 100));
21+
1922
await drainOutboxForRun(runId);
2023
}

packages/features/agents-run/tests/integration/stream-backfill.spec.ts

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,60 +16,66 @@ async function collect<T>(iter: AsyncIterable<T>): Promise<T[]> {
1616

1717
describe.sequential("SSE stream backfill", () => {
1818
it.skip("backfills from fromSeq and de-dupes live", async () => {
19-
await runAgentsRunTest(async () => {
20-
// Arrange
21-
const runId = `r-stream-${Math.random().toString(36).slice(2)}`;
19+
await runAgentsRunTest(
20+
async () => {
21+
// Arrange
22+
const runId = `r-stream-${Math.random().toString(36).slice(2)}`;
2223

23-
// Act: start run and wait for completion
24-
await startRun(runId);
25-
await processRunDeterministically(runId);
26-
await waitForRunCompletion(runId);
24+
// Act: start run and wait for completion
25+
await startRun(runId);
26+
await processRunDeterministically(runId);
27+
await waitForRunCompletion(runId);
2728

28-
// Assert: full stream contains all events
29-
const recorded = await collect(streamRun(runId));
30-
expect(recorded.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
29+
// Assert: full stream contains all events
30+
const recorded = await collect(streamRun(runId));
31+
expect(recorded.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
3132

32-
// Assert: backfill from midpoint returns remaining events
33-
const startIndex = Math.max(recorded.length - 3, 0);
34-
const backfilled = await collect(
35-
streamRun(runId, recorded[startIndex]?.seq ?? 0),
36-
);
37-
expect(backfilled.length).toBeGreaterThan(0);
38-
expect(backfilled.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
39-
});
33+
// Assert: backfill from midpoint returns remaining events
34+
const startIndex = Math.max(recorded.length - 3, 0);
35+
const backfilled = await collect(
36+
streamRun(runId, recorded[startIndex]?.seq ?? 0),
37+
);
38+
expect(backfilled.length).toBeGreaterThan(0);
39+
expect(backfilled.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
40+
},
41+
{ startWorker: false },
42+
);
4043
}, 20000);
4144

4245
it.skip("subscribes for live events after backfill", async () => {
43-
await runAgentsRunTest(async () => {
44-
// Arrange
45-
const runId = `r-stream-live-${Math.random().toString(36).slice(2)}`;
46+
await runAgentsRunTest(
47+
async () => {
48+
// Arrange
49+
const runId = `r-stream-live-${Math.random().toString(36).slice(2)}`;
4650

47-
// Act: start run and wait for completion
48-
await startRun(runId);
49-
await processRunDeterministically(runId);
50-
await waitForRunCompletion(runId);
51+
// Act: start run and wait for completion
52+
await startRun(runId);
53+
await processRunDeterministically(runId);
54+
await waitForRunCompletion(runId);
5155

52-
// Assert: stream delivers all events
53-
const iter = streamRun(runId);
54-
const events: any[] = [];
55-
const collector = (async () => {
56-
for await (const evt of iter) {
57-
events.push(evt);
58-
if (evt.type === EVENT_TYPES.RunFinished) {
59-
break;
56+
// Assert: stream delivers all events
57+
const iter = streamRun(runId);
58+
const events: any[] = [];
59+
const collector = (async () => {
60+
for await (const evt of iter) {
61+
events.push(evt);
62+
if (evt.type === EVENT_TYPES.RunFinished) {
63+
break;
64+
}
6065
}
61-
}
62-
})();
63-
await collector;
66+
})();
67+
await collector;
6468

65-
expect(events.length).toBeGreaterThan(0);
66-
expect(events.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
69+
expect(events.length).toBeGreaterThan(0);
70+
expect(events.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
6771

68-
// Assert: subscribing from near-end returns only final event
69-
const lastSeq = events.at(-1)?.seq ?? 0;
70-
const liveTail = await collect(streamRun(runId, lastSeq - 1));
71-
expect(liveTail.map((event) => event.seq)).toEqual([lastSeq]);
72-
expect(liveTail.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
73-
});
72+
// Assert: subscribing from near-end returns only final event
73+
const lastSeq = events.at(-1)?.seq ?? 0;
74+
const liveTail = await collect(streamRun(runId, lastSeq - 1));
75+
expect(liveTail.map((event) => event.seq)).toEqual([lastSeq]);
76+
expect(liveTail.at(-1)?.type).toBe(EVENT_TYPES.RunFinished);
77+
},
78+
{ startWorker: false },
79+
);
7480
}, 20000);
7581
});

pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vitest.config.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ export default defineConfig({
1616
],
1717
globalSetup: "packages/database/prisma/test/setup.ts",
1818
globalTeardown: "packages/database/prisma/test/teardown.ts",
19-
poolOptions: {
20-
threads: {
21-
singleThread: true,
22-
},
23-
},
2419
},
2520
coverage: {
2621
reporter: ["text", "json", "lcov"],

0 commit comments

Comments
 (0)