From ed3bcea755bf66d5ca0cdad22496d2c69c667fd1 Mon Sep 17 00:00:00 2001 From: Felipe Lahti Date: Mon, 27 Apr 2026 12:34:52 -0300 Subject: [PATCH 1/2] fix: make types compile under TypeScript 7.0 Both `@sendgrid/client` and `@sendgrid/mail` ship .d.ts files that mix `export = instance` with `export {Class}` and silence the diagnostic with `// @ts-ignore`. Pre-7.0, downstream consumers paid no cost for this since named imports of the class still resolved. TS 7.0 (the Go-port `tsgo` beta) tightens the rule and surfaces TS2616 in every consumer that imports `Client` or `MailService`: error TS2616: 'Client' can only be imported by using 'import Client = require("@sendgrid/client")' or a default import. The fix mirrors the runtime: `module.exports = new Client(); module.exports.Client = Client;`. Modeling that as a `Client` instance with a `Client: typeof Client` instance member lets TS expose the class through `client.Client` AND through the named import `import { Client } from "@sendgrid/client"` without any escape hatches. Same pattern for MailService. Existing TS test fixtures (incl. `new Client()` and `setClient(client: Client)`) keep passing under both tsc 5.9 and tsgo 7.0 unchanged. Also bring tsconfig.json up to TS 7.0 minimums so the test fixtures type-check under tsgo: drop the removed `baseUrl`, add the now- required leading `./` to path mappings, and declare `types: ["node"]` explicitly (auto-load of all @types is gone in 7.0). Verified with tsc@5.9.3 and @typescript/native-preview@7.0.0-dev. --- packages/client/src/client.d.ts | 12 ++++++++---- packages/mail/src/mail.d.ts | 15 ++++++++------- tsconfig.json | 4 ++-- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/client/src/client.d.ts b/packages/client/src/client.d.ts index 84075d1f9..c1696cf78 100644 --- a/packages/client/src/client.d.ts +++ b/packages/client/src/client.d.ts @@ -5,6 +5,13 @@ import {ClientResponse} from "@sendgrid/client/src/response"; declare class Client { constructor(); + /** + * Class itself, attached at runtime via `module.exports.Client = Client`. + * Lets `import client = require("@sendgrid/client")` consumers reach the + * class via `client.Client`. + */ + Client: typeof Client; + /** * Set the SendGrid API key. */ @@ -52,7 +59,4 @@ declare class Client { } declare const client: Client; -// @ts-ignore -export = client - -export {Client}; +export = client; diff --git a/packages/mail/src/mail.d.ts b/packages/mail/src/mail.d.ts index fa827498d..0fc8511d1 100644 --- a/packages/mail/src/mail.d.ts +++ b/packages/mail/src/mail.d.ts @@ -1,9 +1,16 @@ -import {Client} from "@sendgrid/client"; +import sgClient = require("@sendgrid/client"); import {ClientResponse} from "@sendgrid/client/src/response"; import {ResponseError} from "@sendgrid/helpers/classes"; import {MailDataRequired} from "@sendgrid/helpers/classes/mail"; +type Client = InstanceType; + declare class MailService { + /** + * Class itself, attached at runtime via `module.exports.MailService = MailService`. + */ + MailService: typeof MailService; + /** * SendGrid API key passthrough for convenience. */ @@ -41,10 +48,4 @@ declare class MailService { } declare const mail: MailService; -// @ts-ignore export = mail; - -export {MailService}; -export {MailDataRequired}; -export {ClientResponse}; -export {ResponseError}; diff --git a/tsconfig.json b/tsconfig.json index b8d1ada33..f61d45bf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,9 +3,9 @@ "noEmit": true, "module": "commonjs", "target": "es6", - "baseUrl": ".", + "types": ["node"], "paths": { - "@sendgrid/*": ["packages/*"] + "@sendgrid/*": ["./packages/*"] } }, "include": [ From e13aa1782cfe3982e9c18797a985e4c6645b0d77 Mon Sep 17 00:00:00 2001 From: Felipe Lahti Date: Thu, 10 Sep 2026 19:15:59 -0300 Subject: [PATCH 2/2] fix: model the CJS export as a namespace instead of a class member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attaching `Client` as an instance member made it a value-only export, so `function f(c: Client)` stopped type-checking, and it could not carry the type-only `MailDataRequired`, `ClientResponse` and `ResponseError` re-exports at all. Declaring an ambient namespace merged with the `export =` target models `module.exports` exactly — the singleton's methods plus the class attached in index.js — and keeps both meanings of `Client` and `MailService` importable by name. No `@ts-ignore`, no runtime change. Fixtures now cover `client.Client`, `Client` in type position, and the types re-exported from the package root; each of those failed to compile under the previous shape. Co-Authored-By: Claude Opus 5 (1M context) --- packages/client/src/client.d.ts | 27 +++++++++++++++++++-------- packages/mail/src/mail.d.ts | 29 ++++++++++++++++++++--------- test/typescript/client.ts | 4 ++++ test/typescript/mail.ts | 10 ++++++++++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/packages/client/src/client.d.ts b/packages/client/src/client.d.ts index c1696cf78..6845ea9ec 100644 --- a/packages/client/src/client.d.ts +++ b/packages/client/src/client.d.ts @@ -5,13 +5,6 @@ import {ClientResponse} from "@sendgrid/client/src/response"; declare class Client { constructor(); - /** - * Class itself, attached at runtime via `module.exports.Client = Client`. - * Lets `import client = require("@sendgrid/client")` consumers reach the - * class via `client.Client`. - */ - Client: typeof Client; - /** * Set the SendGrid API key. */ @@ -58,5 +51,23 @@ declare class Client { request(data: ClientRequest, cb?: (err: ResponseError, response: [ClientResponse, any]) => void): Promise<[ClientResponse, any]>; } -declare const client: Client; +/** + * The module exports the singleton, with the class attached as + * `module.exports.Client` (see index.js), so both meanings of `Client` — + * the value and the instance type — stay importable by name. + */ +declare namespace client { + export {Client}; + + export function setApiKey(apiKey: string): void; + export function setTwilioEmailAuth(username: string, password: string): void; + export function setImpersonateSubuser(subuser: string): void; + export function setDefaultHeader(key: string | { [s: string]: string }, value ?: string): Client; + export function setDefaultRequest(key: K | ClientRequest, value ?: ClientRequest[K]): Client; + export function setDataResidency(region: string): Client; + export function createHeaders(data: { [key: string]: string }): { [key: string]: string }; + export function createRequest(data: ClientRequest): ClientRequest; + export function request(data: ClientRequest, cb?: (err: ResponseError, response: [ClientResponse, any]) => void): Promise<[ClientResponse, any]>; +} + export = client; diff --git a/packages/mail/src/mail.d.ts b/packages/mail/src/mail.d.ts index 0fc8511d1..f223e037f 100644 --- a/packages/mail/src/mail.d.ts +++ b/packages/mail/src/mail.d.ts @@ -1,16 +1,9 @@ -import sgClient = require("@sendgrid/client"); +import {Client} from "@sendgrid/client"; import {ClientResponse} from "@sendgrid/client/src/response"; import {ResponseError} from "@sendgrid/helpers/classes"; import {MailDataRequired} from "@sendgrid/helpers/classes/mail"; -type Client = InstanceType; - declare class MailService { - /** - * Class itself, attached at runtime via `module.exports.MailService = MailService`. - */ - MailService: typeof MailService; - /** * SendGrid API key passthrough for convenience. */ @@ -47,5 +40,23 @@ declare class MailService { sendMultiple(data: MailDataRequired, cb?: (error: Error | ResponseError, result: [ClientResponse, {}]) => void): Promise<[ClientResponse, {}]>; } -declare const mail: MailService; +/** + * The module exports the singleton, with the class attached as + * `module.exports.MailService` (see index.js). + */ +declare namespace mail { + export {MailService}; + export {MailDataRequired}; + export {ClientResponse}; + export {ResponseError}; + + export function setApiKey(apiKey: string): void; + export function setClient(client: Client): void; + export function setTwilioEmailAuth(username: string, password: string): void; + export function setTimeout(timeout: number): void; + export function setSubstitutionWrappers(left: string, right: string): void; + export function send(data: MailDataRequired | MailDataRequired[], isMultiple?: boolean, cb?: (err: Error | ResponseError, result: [ClientResponse, {}]) => void): Promise<[ClientResponse, {}]>; + export function sendMultiple(data: MailDataRequired, cb?: (error: Error | ResponseError, result: [ClientResponse, {}]) => void): Promise<[ClientResponse, {}]>; +} + export = mail; diff --git a/test/typescript/client.ts b/test/typescript/client.ts index c0fb0d781..db472daf6 100644 --- a/test/typescript/client.ts +++ b/test/typescript/client.ts @@ -31,3 +31,7 @@ Client.request({ }).then(res => { res[0].statusCode; }); + +// Test Client class attached to the module +const instance: Client.Client = new Client.Client(); +instance.setApiKey("MY_SENDGRID_API_KEY"); diff --git a/test/typescript/mail.ts b/test/typescript/mail.ts index fb8199860..cca8c448d 100644 --- a/test/typescript/mail.ts +++ b/test/typescript/mail.ts @@ -1,9 +1,19 @@ import { Client } from "@sendgrid/client"; +import { MailDataRequired, ClientResponse, ResponseError } from "@sendgrid/mail"; import sgMail = require("@sendgrid/mail"); // Test setClient() method sgMail.setClient(new Client()); +// Test Client used as a type +const client: Client = new Client(); +sgMail.setClient(client); + +// Test types re-exported from the package root +declare const data: MailDataRequired; +declare const onSent: (err: Error | ResponseError, result: [ClientResponse, {}]) => void; +sgMail.send(data, false, onSent); + // Test setApiKey() method sgMail.setApiKey("MY_SENDGRID_API_KEY");