Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@
"author": "",
"license": "MIT",
"dependencies": {
"@aws-sdk/rds-signer": "^3.1001.0",
"@azure/identity": "^4.8.0",
"@iarna/toml": "^2.2.5",
"@modelcontextprotocol/sdk": "^1.25.1",
"dotenv": "^16.4.7",
Expand All @@ -51,6 +49,8 @@
"zod": "^3.24.2"
},
"optionalDependencies": {
"@aws-sdk/rds-signer": "^3.1001.0",
"@azure/identity": "^4.8.0",
"better-sqlite3": "^11.9.0",
"mariadb": "^3.4.0",
"mssql": "^11.0.1",
Expand Down
13 changes: 12 additions & 1 deletion src/connectors/sqlserver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ExecuteOptions,
ConnectorConfig,
} from "../interface.js";
import { DefaultAzureCredential } from "@azure/identity";
import { isDriverNotInstalled } from "../../utils/module-loader.js";
import { SafeURL } from "../../utils/safe-url.js";
import { obfuscateDSNPassword } from "../../utils/dsn-obfuscate.js";
import { SQLRowLimiter } from "../../utils/sql-row-limiter.js";
Expand Down Expand Up @@ -95,6 +95,17 @@ export class SQLServerDSNParser implements DSNParser {
switch (options.authentication) {
case "azure-active-directory-access-token": {
try {
let DefaultAzureCredential: typeof import("@azure/identity")["DefaultAzureCredential"];
try {
({ DefaultAzureCredential } = await import("@azure/identity"));
} catch (importError) {
if (isDriverNotInstalled(importError, "@azure/identity")) {
throw new Error(
'Azure AD authentication requires the "@azure/identity" package. Install it with: pnpm add @azure/identity'
);
}
throw importError;
Comment thread
tianzhou marked this conversation as resolved.
Outdated
}
const credential = new DefaultAzureCredential();
const token = await credential.getToken("https://database.windows.net/");
Comment thread
tianzhou marked this conversation as resolved.
config.authentication = {
Expand Down
23 changes: 23 additions & 0 deletions src/utils/__tests__/aws-rds-signer-missing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { isDriverNotInstalled } from '../module-loader.js';

describe('generateRdsAuthToken missing package', () => {
it('should throw actionable error when @aws-sdk/rds-signer is not installed', async () => {

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file imports vi and beforeEach but never uses them, and the first it(...) is marked async without awaiting anything. Clean up unused imports / remove the unnecessary async to keep the test suite lint/tsc-friendly.

Suggested change
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { isDriverNotInstalled } from '../module-loader.js';
describe('generateRdsAuthToken missing package', () => {
it('should throw actionable error when @aws-sdk/rds-signer is not installed', async () => {
import { describe, it, expect } from 'vitest';
import { isDriverNotInstalled } from '../module-loader.js';
describe('generateRdsAuthToken missing package', () => {
it('should throw actionable error when @aws-sdk/rds-signer is not installed', () => {

Copilot uses AI. Check for mistakes.
// Simulate ERR_MODULE_NOT_FOUND for @aws-sdk/rds-signer
const err = new Error(
"Cannot find package '@aws-sdk/rds-signer' imported from /fake/path"
);
(err as NodeJS.ErrnoException).code = 'ERR_MODULE_NOT_FOUND';

expect(isDriverNotInstalled(err, '@aws-sdk/rds-signer')).toBe(true);
});

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suite name suggests it’s validating generateRdsAuthToken behavior when @aws-sdk/rds-signer is missing, but the assertions only test isDriverNotInstalled(). This leaves the new user-facing error path in generateRdsAuthToken() untested. Consider either renaming the suite to match what’s tested or adding a test that forces the dynamic import("@aws-sdk/rds-signer") to fail and asserts the thrown install message.

Copilot uses AI. Check for mistakes.

it('should not match unrelated ERR_MODULE_NOT_FOUND errors', () => {
const err = new Error(
"Cannot find package 'some-other-pkg' imported from /fake/path"
);
(err as NodeJS.ErrnoException).code = 'ERR_MODULE_NOT_FOUND';

expect(isDriverNotInstalled(err, '@aws-sdk/rds-signer')).toBe(false);
});
});
14 changes: 13 additions & 1 deletion src/utils/aws-rds-signer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Signer } from "@aws-sdk/rds-signer";
import { isDriverNotInstalled } from "./module-loader.js";

export interface RdsAuthTokenParams {
hostname: string;
Expand All @@ -13,6 +13,18 @@ export interface RdsAuthTokenParams {
* (AWS CLI profile, env vars, instance role, etc.).
*/
export async function generateRdsAuthToken(params: RdsAuthTokenParams): Promise<string> {
let Signer: typeof import("@aws-sdk/rds-signer")["Signer"];
try {
({ Signer } = await import("@aws-sdk/rds-signer"));
} catch (error) {
if (isDriverNotInstalled(error, "@aws-sdk/rds-signer")) {
throw new Error(
'AWS IAM authentication requires the "@aws-sdk/rds-signer" package. Install it with: pnpm add @aws-sdk/rds-signer'
);
}
throw error;
}

const signer = new Signer({
Comment thread
tianzhou marked this conversation as resolved.
hostname: params.hostname,
port: params.port,
Expand Down
11 changes: 7 additions & 4 deletions tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ export default defineConfig({
dts: true,
clean: true,
outDir: 'dist',
// Database drivers are optionalDependencies loaded at runtime via dynamic
// import(). They must be external so tsup does not bundle their CJS code
// into ESM chunks (which causes "Dynamic require of X is not supported").
external: ['pg', 'mysql2', 'mariadb', 'mssql', 'better-sqlite3'],
// Optional runtime-loaded dependencies (database drivers and cloud auth
// packages) are declared as optionalDependencies and loaded via dynamic
// import(). Database drivers must be external so tsup does not bundle their
// CJS code into ESM chunks (which causes "Dynamic require of X is not
// supported"). Cloud auth packages are externalized to keep their large
// dependency trees out of the bundle.
external: ['pg', 'mysql2', 'mariadb', 'mssql', 'better-sqlite3', '@aws-sdk/rds-signer', '@azure/identity'],
// Copy the employee-sqlite demo data to dist
async onSuccess() {
// Create target directory
Expand Down
Loading