Skip to content

Commit 504b3de

Browse files
tianzhouclaude
andcommitted
fix: use string literal imports and exact package name matching
- Use load functions with string literal import() paths so the bundler can statically analyze and code-split connector modules - Drop subpath matching — only exact driver package name triggers skip - Add loadConnectors behavior tests: missing driver logs and continues, non-driver errors are rethrown Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 03494ce commit 504b3de

File tree

3 files changed

+76
-17
lines changed

3 files changed

+76
-17
lines changed

src/__tests__/load-connectors.test.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect } from "vitest";
1+
import { describe, it, expect, vi } from "vitest";
22
import { isDriverNotInstalled } from "../utils/module-loader.js";
33

44
describe("isDriverNotInstalled", () => {
@@ -20,13 +20,13 @@ describe("isDriverNotInstalled", () => {
2020
expect(isDriverNotInstalled(err, "mysql2")).toBe(false);
2121
});
2222

23-
it("should return true for driver subpath imports", () => {
23+
it("should return false for driver subpath imports", () => {
2424
const err = new Error(
2525
"Cannot find package 'mysql2/promise' imported from /fake/path"
2626
);
2727
(err as NodeJS.ErrnoException).code = "ERR_MODULE_NOT_FOUND";
2828

29-
expect(isDriverNotInstalled(err, "mysql2")).toBe(true);
29+
expect(isDriverNotInstalled(err, "mysql2")).toBe(false);
3030
});
3131

3232
it("should return false when missing module name only contains driver as a substring", () => {
@@ -59,3 +59,66 @@ describe("isDriverNotInstalled", () => {
5959
expect(isDriverNotInstalled(undefined, "pg")).toBe(false);
6060
});
6161
});
62+
63+
describe("loadConnectors", () => {
64+
it("should log and continue when a driver is missing", async () => {
65+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
66+
67+
// Mock a connector module that throws ERR_MODULE_NOT_FOUND for its driver
68+
const driverErr = new Error("Cannot find package 'pg' imported from /fake/path");
69+
(driverErr as NodeJS.ErrnoException).code = "ERR_MODULE_NOT_FOUND";
70+
71+
const connectorModules = [
72+
{ load: () => Promise.reject(driverErr), name: "PostgreSQL", driver: "pg" },
73+
];
74+
75+
// Inline the loadConnectors logic to test without importing index.ts
76+
// (which triggers side effects)
77+
await Promise.all(
78+
connectorModules.map(async ({ load, name, driver }) => {
79+
try {
80+
await load();
81+
} catch (err) {
82+
if (isDriverNotInstalled(err, driver)) {
83+
console.error(
84+
`Skipping ${name} connector: driver package "${driver}" not installed.`
85+
);
86+
} else {
87+
throw err;
88+
}
89+
}
90+
})
91+
);
92+
93+
expect(errorSpy).toHaveBeenCalledWith(
94+
'Skipping PostgreSQL connector: driver package "pg" not installed.'
95+
);
96+
errorSpy.mockRestore();
97+
});
98+
99+
it("should rethrow non-driver errors", async () => {
100+
const runtimeErr = new Error("Unexpected syntax error");
101+
102+
const connectorModules = [
103+
{ load: () => Promise.reject(runtimeErr), name: "PostgreSQL", driver: "pg" },
104+
];
105+
106+
await expect(
107+
Promise.all(
108+
connectorModules.map(async ({ load, name, driver }) => {
109+
try {
110+
await load();
111+
} catch (err) {
112+
if (isDriverNotInstalled(err, driver)) {
113+
console.error(
114+
`Skipping ${name} connector: driver package "${driver}" not installed.`
115+
);
116+
} else {
117+
throw err;
118+
}
119+
}
120+
})
121+
)
122+
).rejects.toThrow("Unexpected syntax error");
123+
});
124+
});

src/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
#!/usr/bin/env node
22

33
import { main } from "./server.js";
4+
import { isDriverNotInstalled } from "./utils/module-loader.js";
45

56
// Dynamically import connector modules so missing driver packages
67
// (e.g. when only one database type is needed) don't crash the server.
8+
// Each load function uses a string literal so the bundler can resolve it.
79
const connectorModules = [
8-
{ modulePath: "./connectors/postgres/index.js", name: "PostgreSQL", driver: "pg" },
9-
{ modulePath: "./connectors/sqlserver/index.js", name: "SQL Server", driver: "mssql" },
10-
{ modulePath: "./connectors/sqlite/index.js", name: "SQLite", driver: "better-sqlite3" },
11-
{ modulePath: "./connectors/mysql/index.js", name: "MySQL", driver: "mysql2" },
12-
{ modulePath: "./connectors/mariadb/index.js", name: "MariaDB", driver: "mariadb" },
10+
{ load: () => import("./connectors/postgres/index.js"), name: "PostgreSQL", driver: "pg" },
11+
{ load: () => import("./connectors/sqlserver/index.js"), name: "SQL Server", driver: "mssql" },
12+
{ load: () => import("./connectors/sqlite/index.js"), name: "SQLite", driver: "better-sqlite3" },
13+
{ load: () => import("./connectors/mysql/index.js"), name: "MySQL", driver: "mysql2" },
14+
{ load: () => import("./connectors/mariadb/index.js"), name: "MariaDB", driver: "mariadb" },
1315
];
1416

15-
import { isDriverNotInstalled } from "./utils/module-loader.js";
16-
1717
export async function loadConnectors(): Promise<void> {
1818
await Promise.all(
19-
connectorModules.map(async ({ modulePath, name, driver }) => {
19+
connectorModules.map(async ({ load, name, driver }) => {
2020
try {
21-
await import(modulePath);
21+
await load();
2222
} catch (err) {
2323
if (isDriverNotInstalled(err, driver)) {
2424
console.error(

src/utils/module-loader.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,5 @@ export function isDriverNotInstalled(err: unknown, driver: string): boolean {
2323
}
2424

2525
const missingSpecifier = match[1];
26-
// Match the exact driver package or a subpath import (e.g. "mysql2/promise")
27-
return (
28-
missingSpecifier === driver ||
29-
missingSpecifier.startsWith(`${driver}/`)
30-
);
26+
return missingSpecifier === driver;
3127
}

0 commit comments

Comments
 (0)