Skip to content

Commit a2a38c5

Browse files
chore: implements PR feedback
1. SessionExportManager directly injected in Session same as ConnectionManager 2. sessionId is not a property anymore on SessionExportManager 3. logs are enriched with export information 4. nested try/catch removed from SessionExportManager.startExport 5. Promise continuation removed by delegating to SessionExportManager.silentyRemoveExport 6. switch/case for getEJSONOptionsForFormat
1 parent cf3cb48 commit a2a38c5

File tree

8 files changed

+112
-67
lines changed

8 files changed

+112
-67
lines changed

src/common/session.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ import {
1010
} from "./connectionManager.js";
1111
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
1212
import { ErrorCodes, MongoDBError } from "./errors.js";
13-
import { ObjectId } from "bson";
14-
import { SessionExportsManager, SessionExportsManagerConfig } from "./sessionExportsManager.js";
15-
import { config } from "./config.js";
13+
import { SessionExportsManager } from "./sessionExportsManager.js";
1614

1715
export interface SessionOptions {
1816
apiBaseUrl: string;
1917
apiClientId?: string;
2018
apiClientSecret?: string;
21-
connectionManager?: ConnectionManager;
22-
exportsManagerConfig?: SessionExportsManagerConfig;
2319
logger: CompositeLogger;
20+
sessionId: string;
21+
exportsManager: SessionExportsManager;
22+
connectionManager: ConnectionManager;
2423
}
2524

2625
export type SessionEvents = {
@@ -31,10 +30,10 @@ export type SessionEvents = {
3130
};
3231

3332
export class Session extends EventEmitter<SessionEvents> {
34-
readonly sessionId = new ObjectId().toString();
33+
readonly sessionId: string;
3534
readonly exportsManager: SessionExportsManager;
36-
connectionManager: ConnectionManager;
37-
apiClient: ApiClient;
35+
readonly connectionManager: ConnectionManager;
36+
readonly apiClient: ApiClient;
3837
agentRunner?: {
3938
name: string;
4039
version: string;
@@ -46,14 +45,14 @@ export class Session extends EventEmitter<SessionEvents> {
4645
apiBaseUrl,
4746
apiClientId,
4847
apiClientSecret,
49-
connectionManager,
5048
logger,
51-
exportsManagerConfig,
49+
sessionId,
50+
connectionManager,
51+
exportsManager,
5252
}: SessionOptions) {
5353
super();
5454

5555
this.logger = logger;
56-
5756
const credentials: ApiClientCredentials | undefined =
5857
apiClientId && apiClientSecret
5958
? {
@@ -63,9 +62,10 @@ export class Session extends EventEmitter<SessionEvents> {
6362
: undefined;
6463

6564
this.apiClient = new ApiClient({ baseUrl: apiBaseUrl, credentials }, logger);
66-
this.exportsManager = new SessionExportsManager(this.sessionId, exportsManagerConfig ?? config, logger);
6765

68-
this.connectionManager = connectionManager ?? new ConnectionManager();
66+
this.sessionId = sessionId;
67+
this.exportsManager = exportsManager;
68+
this.connectionManager = connectionManager;
6969
this.connectionManager.on("connection-succeeded", () => this.emit("connect"));
7070
this.connectionManager.on("connection-timed-out", (error) => this.emit("connection-error", error.errorReason));
7171
this.connectionManager.on("connection-closed", () => this.emit("disconnect"));

src/common/sessionExportsManager.ts

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { pipeline } from "stream/promises";
1010

1111
import { UserConfig } from "./config.js";
1212
import { LoggerBase, LogId } from "./logger.js";
13+
import { MongoLogId } from "mongodb-log-writer";
1314

1415
export const jsonExportFormat = z.enum(["relaxed", "canonical"]);
1516
export type JSONExportFormat = z.infer<typeof jsonExportFormat>;
@@ -71,7 +72,7 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
7172
private exportsDirectoryPath: string;
7273

7374
constructor(
74-
readonly sessionId: string,
75+
sessionId: string,
7576
private readonly config: SessionExportsManagerConfig,
7677
private readonly logger: LoggerBase
7778
) {
@@ -133,7 +134,7 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
133134
} catch (error) {
134135
this.logger.error({
135136
id: LogId.exportReadError,
136-
context: "Error when reading export",
137+
context: `Error when reading export - ${exportName}`,
137138
message: error instanceof Error ? error.message : String(error),
138139
});
139140
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -184,61 +185,54 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
184185
jsonExportFormat: JSONExportFormat;
185186
inProgressExport: InProgressExport;
186187
}): Promise<void> {
188+
let pipeSuccessful = false;
187189
try {
188190
await fs.mkdir(this.exportsDirectoryPath, { recursive: true });
189-
const inputStream = input.stream();
190-
const ejsonDocStream = this.docToEJSONStream(this.getEJSONOptionsForFormat(jsonExportFormat));
191191
const outputStream = createWriteStream(inProgressExport.exportPath);
192192
outputStream.write("[");
193-
let pipeSuccessful = false;
194-
try {
195-
await pipeline([inputStream, ejsonDocStream, outputStream]);
196-
pipeSuccessful = true;
197-
} catch (pipelineError) {
198-
// If the pipeline errors out then we might end up with
199-
// partial and incorrect export so we remove it entirely.
200-
await fs.unlink(inProgressExport.exportPath).catch((error) => {
201-
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
202-
this.logger.error({
203-
id: LogId.exportCreationCleanupError,
204-
context: "Error when removing partial export",
205-
message: error instanceof Error ? error.message : String(error),
206-
});
207-
}
208-
});
209-
delete this.sessionExports[inProgressExport.exportName];
210-
throw pipelineError;
211-
} finally {
212-
if (pipeSuccessful) {
213-
this.sessionExports[inProgressExport.exportName] = {
214-
...inProgressExport,
215-
exportCreatedAt: Date.now(),
216-
exportStatus: "ready",
217-
};
218-
this.emit("export-available", inProgressExport.exportURI);
219-
}
220-
void input.close();
221-
}
193+
await pipeline([
194+
input.stream(),
195+
this.docToEJSONStream(this.getEJSONOptionsForFormat(jsonExportFormat)),
196+
outputStream,
197+
]);
198+
pipeSuccessful = true;
222199
} catch (error) {
223200
this.logger.error({
224201
id: LogId.exportCreationError,
225-
context: "Error when generating JSON export",
202+
context: `Error when generating JSON export for ${inProgressExport.exportName}`,
226203
message: error instanceof Error ? error.message : String(error),
227204
});
205+
206+
// If the pipeline errors out then we might end up with
207+
// partial and incorrect export so we remove it entirely.
208+
await this.silentlyRemoveExport(
209+
inProgressExport.exportPath,
210+
LogId.exportCreationCleanupError,
211+
`Error when removing incomplete export ${inProgressExport.exportName}`
212+
);
213+
delete this.sessionExports[inProgressExport.exportName];
214+
} finally {
215+
if (pipeSuccessful) {
216+
this.sessionExports[inProgressExport.exportName] = {
217+
...inProgressExport,
218+
exportCreatedAt: Date.now(),
219+
exportStatus: "ready",
220+
};
221+
this.emit("export-available", inProgressExport.exportURI);
222+
}
223+
void input.close();
228224
}
229225
}
230226

231227
private getEJSONOptionsForFormat(format: JSONExportFormat): EJSONOptions | undefined {
232-
if (format === "relaxed") {
233-
return {
234-
relaxed: true,
235-
};
228+
switch (format) {
229+
case "relaxed":
230+
return { relaxed: true };
231+
case "canonical":
232+
return { relaxed: false };
233+
default:
234+
return undefined;
236235
}
237-
return format === "canonical"
238-
? {
239-
relaxed: false,
240-
}
241-
: undefined;
242236
}
243237

244238
private docToEJSONStream(ejsonOptions: EJSONOptions | undefined): Transform {
@@ -276,7 +270,11 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
276270
for (const { exportPath, exportCreatedAt, exportURI, exportName } of exportsForCleanup) {
277271
if (isExportExpired(exportCreatedAt, this.config.exportTimeoutMs)) {
278272
delete this.sessionExports[exportName];
279-
await this.silentlyRemoveExport(exportPath);
273+
await this.silentlyRemoveExport(
274+
exportPath,
275+
LogId.exportCleanupError,
276+
`Considerable error when removing export ${exportName}`
277+
);
280278
this.emit("export-expired", exportURI);
281279
}
282280
}
@@ -291,7 +289,7 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
291289
}
292290
}
293291

294-
private async silentlyRemoveExport(exportPath: string): Promise<void> {
292+
private async silentlyRemoveExport(exportPath: string, logId: MongoLogId, logContext: string): Promise<void> {
295293
try {
296294
await fs.unlink(exportPath);
297295
} catch (error) {
@@ -300,8 +298,8 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
300298
// we need to flag.
301299
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
302300
this.logger.error({
303-
id: LogId.exportCleanupError,
304-
context: "Considerable error when removing export file",
301+
id: logId,
302+
context: logContext,
305303
message: error instanceof Error ? error.message : String(error),
306304
});
307305
}

src/transports/base.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { Session } from "../common/session.js";
55
import { Telemetry } from "../telemetry/telemetry.js";
66
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
77
import { CompositeLogger, ConsoleLogger, DiskLogger, LoggerBase, McpLogger } from "../common/logger.js";
8+
import { ObjectId } from "bson";
9+
import { SessionExportsManager } from "../common/sessionExportsManager.js";
10+
import { ConnectionManager } from "../common/connectionManager.js";
811

912
export abstract class TransportRunnerBase {
1013
public logger: LoggerBase;
@@ -39,11 +42,19 @@ export abstract class TransportRunnerBase {
3942
loggers.push(new McpLogger(mcpServer));
4043
}
4144

45+
const logger = new CompositeLogger(...loggers);
46+
const sessionId = new ObjectId().toString();
47+
const exportsManager = new SessionExportsManager(sessionId, userConfig, logger);
48+
const connectionManager = new ConnectionManager();
49+
4250
const session = new Session({
4351
apiBaseUrl: userConfig.apiBaseUrl,
4452
apiClientId: userConfig.apiClientId,
4553
apiClientSecret: userConfig.apiClientSecret,
46-
logger: new CompositeLogger(...loggers),
54+
logger,
55+
sessionId,
56+
exportsManager,
57+
connectionManager,
4758
});
4859

4960
const telemetry = Telemetry.create(session, userConfig);

tests/integration/helpers.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ObjectId } from "bson";
12
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
23
import { InMemoryTransport } from "./inMemoryTransport.js";
34
import { Server } from "../../src/server.js";
@@ -10,6 +11,7 @@ import { config } from "../../src/common/config.js";
1011
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
1112
import { ConnectionManager } from "../../src/common/connectionManager.js";
1213
import { CompositeLogger } from "../../src/common/logger.js";
14+
import { SessionExportsManager } from "../../src/common/sessionExportsManager.js";
1315

1416
interface ParameterInfo {
1517
name: string;
@@ -55,15 +57,19 @@ export function setupIntegrationTest(getUserConfig: () => UserConfig): Integrati
5557
}
5658
);
5759

60+
const logger = new CompositeLogger();
61+
const sessionId = new ObjectId().toString();
62+
const exportsManager = new SessionExportsManager(sessionId, userConfig, logger);
5863
const connectionManager = new ConnectionManager();
5964

6065
const session = new Session({
6166
apiBaseUrl: userConfig.apiBaseUrl,
6267
apiClientId: userConfig.apiClientId,
6368
apiClientSecret: userConfig.apiClientSecret,
69+
logger,
70+
sessionId,
71+
exportsManager,
6472
connectionManager,
65-
logger: new CompositeLogger(),
66-
exportsManagerConfig: userConfig,
6773
});
6874

6975
// Mock hasValidAccessToken for tests

tests/integration/telemetry.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ import { config } from "../../src/common/config.js";
55
import nodeMachineId from "node-machine-id";
66
import { describe, expect, it } from "vitest";
77
import { CompositeLogger } from "../../src/common/logger.js";
8+
import { ConnectionManager } from "../../src/common/connectionManager.js";
9+
import { SessionExportsManager } from "../../src/common/sessionExportsManager.js";
810

911
describe("Telemetry", () => {
1012
it("should resolve the actual machine ID", async () => {
1113
const actualId: string = await nodeMachineId.machineId(true);
1214

1315
const actualHashedId = createHmac("sha256", actualId.toUpperCase()).update("atlascli").digest("hex");
1416

17+
const logger = new CompositeLogger();
1518
const telemetry = Telemetry.create(
1619
new Session({
1720
apiBaseUrl: "",
1821
logger: new CompositeLogger(),
22+
sessionId: "1FOO",
23+
exportsManager: new SessionExportsManager("1FOO", config, logger),
24+
connectionManager: new ConnectionManager(),
1925
}),
2026
config
2127
);

tests/unit/common/session.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@ import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver
33
import { Session } from "../../../src/common/session.js";
44
import { config } from "../../../src/common/config.js";
55
import { CompositeLogger } from "../../../src/common/logger.js";
6+
import { ConnectionManager } from "../../../src/common/connectionManager.js";
7+
import { SessionExportsManager } from "../../../src/common/sessionExportsManager.js";
68

79
vi.mock("@mongosh/service-provider-node-driver");
810
const MockNodeDriverServiceProvider = vi.mocked(NodeDriverServiceProvider);
911

1012
describe("Session", () => {
1113
let session: Session;
1214
beforeEach(() => {
15+
const logger = new CompositeLogger();
1316
session = new Session({
1417
apiClientId: "test-client-id",
1518
apiBaseUrl: "https://api.test.com",
16-
logger: new CompositeLogger(),
19+
logger,
20+
sessionId: "1FOO",
21+
exportsManager: new SessionExportsManager("1FOO", config, logger),
22+
connectionManager: new ConnectionManager(),
1723
});
1824

1925
MockNodeDriverServiceProvider.connect = vi.fn().mockResolvedValue({} as unknown as NodeDriverServiceProvider);

tests/unit/common/sessionExportsManager.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import { config } from "../../../src/common/config.js";
1515
import { Session } from "../../../src/common/session.js";
1616
import { ROOT_DIR } from "../../accuracy/sdk/constants.js";
1717
import { timeout } from "../../integration/helpers.js";
18-
import { EJSON, EJSONOptions } from "bson";
18+
import { EJSON, EJSONOptions, ObjectId } from "bson";
1919
import { CompositeLogger } from "../../../src/common/logger.js";
20+
import { ConnectionManager } from "../../../src/common/connectionManager.js";
2021

2122
const exportsPath = path.join(ROOT_DIR, "tests", "tmp", "exports");
2223
const exportsManagerConfig: SessionExportsManagerConfig = {
@@ -106,7 +107,15 @@ describe("SessionExportsManager unit test", () => {
106107
await manager?.close();
107108
await fs.rm(exportsManagerConfig.exportsPath, { recursive: true, force: true });
108109
await fs.mkdir(exportsManagerConfig.exportsPath, { recursive: true });
109-
session = new Session({ apiBaseUrl: "", logger: new CompositeLogger() });
110+
const logger = new CompositeLogger();
111+
const sessionId = new ObjectId().toString();
112+
session = new Session({
113+
apiBaseUrl: "",
114+
logger,
115+
sessionId,
116+
exportsManager: new SessionExportsManager(sessionId, config, logger),
117+
connectionManager: new ConnectionManager(),
118+
});
110119
manager = session.exportsManager;
111120
});
112121

tests/unit/resources/common/debug.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@ import { Session } from "../../../../src/common/session.js";
44
import { Telemetry } from "../../../../src/telemetry/telemetry.js";
55
import { config } from "../../../../src/common/config.js";
66
import { CompositeLogger } from "../../../../src/common/logger.js";
7+
import { ConnectionManager } from "../../../../src/common/connectionManager.js";
8+
import { SessionExportsManager } from "../../../../src/common/sessionExportsManager.js";
79

810
describe("debug resource", () => {
9-
const session = new Session({ apiBaseUrl: "", logger: new CompositeLogger() });
11+
const logger = new CompositeLogger();
12+
const session = new Session({
13+
apiBaseUrl: "",
14+
logger,
15+
sessionId: "1FOO",
16+
exportsManager: new SessionExportsManager("1FOO", config, logger),
17+
connectionManager: new ConnectionManager(),
18+
});
1019
const telemetry = Telemetry.create(session, { ...config, telemetry: "disabled" });
1120

1221
let debugResource: DebugResource = new DebugResource(session, config, telemetry);

0 commit comments

Comments
 (0)