Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [7.13.1] - 2025-09-18

### Fixed
- Broken CJS build outputs resulted in a "TypeError: Nylas is not a constructor" error
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"name": "nylas",
"version": "7.13.0",
"version": "7.13.1",
"description": "A NodeJS wrapper for the Nylas REST API for email, contacts, and calendar.",
"main": "lib/cjs/nylas.js",
"types": "lib/types/nylas.d.ts",
"module": "lib/esm/nylas.js",
"files": [
"lib"
"lib",
"cjs-wrapper.js",
"cjs-wrapper.d.ts"
],
"engines": {
"node": ">=16"
Expand All @@ -24,8 +26,8 @@
"generate-model-index": "node scripts/generateModelIndex.js",
"prebuild": "npm run export-version && npm run generate-model-index",
"build": "rm -rf lib && npm run build-esm && npm run build-cjs && npm run generate-lib-package-json",
"build-esm": "tsc -p tsconfig.esm.json",
"build-cjs": "tsc -p tsconfig.cjs.json",
"build-esm": "node scripts/setupFetchWrapper.js esm && tsc -p tsconfig.esm.json",
"build-cjs": "node scripts/setupFetchWrapper.js cjs && tsc -p tsconfig.cjs.json",
"prepare": "npm run build",
"build:docs": "typedoc --out docs",
"version": "npm run export-version && git add src/version.ts"
Expand Down
29 changes: 29 additions & 0 deletions scripts/setupFetchWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Script to copy the appropriate fetchWrapper implementation based on build type
*/

const fs = require('fs');
const path = require('path');

const buildType = process.argv[2]; // 'esm' or 'cjs'

if (!buildType || !['esm', 'cjs'].includes(buildType)) {
console.error('Usage: node setupFetchWrapper.js <esm|cjs>');
process.exit(1);
}

const srcDir = path.join(__dirname, '..', 'src', 'utils');
const sourceFile = path.join(srcDir, `fetchWrapper-${buildType}.ts`);
const targetFile = path.join(srcDir, 'fetchWrapper.ts');

// Ensure source file exists
if (!fs.existsSync(sourceFile)) {
console.error(`Source file ${sourceFile} does not exist`);
process.exit(1);
}

// Copy the appropriate implementation
fs.copyFileSync(sourceFile, targetFile);
/* eslint-disable no-console */
console.log(`✅ Copied fetchWrapper-${buildType}.ts to fetchWrapper.ts`);
/* eslint-enable no-console */
11 changes: 7 additions & 4 deletions src/apiClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import fetch, { Request, Response } from 'node-fetch';
import { Readable as _Readable } from 'node:stream';
import { NylasConfig, OverridableNylasConfig } from './config.js';
import {
Expand All @@ -11,6 +10,8 @@ import { SDK_VERSION } from './version.js';
import { FormData } from 'formdata-node';
import { FormDataEncoder as _FormDataEncoder } from 'form-data-encoder';
import { snakeCase } from 'change-case';
import { getFetch, getRequest } from './utils/fetchWrapper.js';
import type { Request, Response } from './utils/fetchWrapper.js';

/**
* The header key for the debugging flow ID
Expand Down Expand Up @@ -155,7 +156,7 @@ export default class APIClient {
}

private async sendRequest(options: RequestOptionsParams): Promise<Response> {
const req = this.newRequest(options);
const req = await this.newRequest(options);
const controller: AbortController = new AbortController();

// Handle timeout
Expand All @@ -177,6 +178,7 @@ export default class APIClient {
}, timeoutDuration);

try {
const fetch = await getFetch();
const response = await fetch(req, {
signal: controller.signal as AbortSignal,
});
Expand Down Expand Up @@ -271,9 +273,10 @@ export default class APIClient {
return requestOptions;
}

newRequest(options: RequestOptionsParams): Request {
async newRequest(options: RequestOptionsParams): Promise<Request> {
const newOptions = this.requestOptions(options);
return new Request(newOptions.url, {
const RequestConstructor = await getRequest();
return new RequestConstructor(newOptions.url, {
method: newOptions.method,
headers: newOptions.headers,
body: newOptions.body,
Expand Down
73 changes: 73 additions & 0 deletions src/utils/fetchWrapper-cjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Fetch wrapper for CJS builds - uses dynamic imports for node-fetch compatibility
*/

// Types for the dynamic import result
interface NodeFetchModule {
default: any; // fetch function
Request: any; // Request constructor
Response: any; // Response constructor
}

// Cache for the dynamically imported module
let nodeFetchModule: NodeFetchModule | null = null;

/**
* Get fetch function - uses dynamic import for CJS
*/
export async function getFetch(): Promise<any> {
// In test environment, use global fetch (mocked by jest-fetch-mock)
if (typeof global !== 'undefined' && global.fetch) {
return global.fetch;
}

if (!nodeFetchModule) {
// Use Function constructor to prevent TypeScript from converting to require()
const dynamicImport = new Function('specifier', 'return import(specifier)');
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
}

return nodeFetchModule.default;
}

/**
* Get Request constructor - uses dynamic import for CJS
*/
export async function getRequest(): Promise<any> {
// In test environment, use global Request or a mock
if (typeof global !== 'undefined' && global.Request) {
return global.Request;
}

if (!nodeFetchModule) {
// Use Function constructor to prevent TypeScript from converting to require()
const dynamicImport = new Function('specifier', 'return import(specifier)');
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
}

return nodeFetchModule.Request;
}

/**
* Get Response constructor - uses dynamic import for CJS
*/
export async function getResponse(): Promise<any> {
// In test environment, use global Response or a mock
if (typeof global !== 'undefined' && global.Response) {
return global.Response;
}

if (!nodeFetchModule) {
// Use Function constructor to prevent TypeScript from converting to require()
const dynamicImport = new Function('specifier', 'return import(specifier)');
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
}

return nodeFetchModule.Response;
}

// Export types as any for CJS compatibility
export type RequestInit = any;
export type HeadersInit = any;
export type Request = any;
export type Response = any;
30 changes: 30 additions & 0 deletions src/utils/fetchWrapper-esm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Fetch wrapper for ESM builds - uses static imports for optimal performance
*/

import fetch, { Request, Response } from 'node-fetch';
import type { RequestInit, HeadersInit } from 'node-fetch';

/**
* Get fetch function - uses static import for ESM
*/
export async function getFetch(): Promise<typeof fetch> {
return fetch;
}

/**
* Get Request constructor - uses static import for ESM
*/
export async function getRequest(): Promise<typeof Request> {
return Request;
}

/**
* Get Response constructor - uses static import for ESM
*/
export async function getResponse(): Promise<typeof Response> {
return Response;
}

// Export types directly
export type { RequestInit, HeadersInit, Request, Response };
73 changes: 73 additions & 0 deletions src/utils/fetchWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Fetch wrapper for CJS builds - uses dynamic imports for node-fetch compatibility
*/

// Types for the dynamic import result
interface NodeFetchModule {
default: any; // fetch function
Request: any; // Request constructor
Response: any; // Response constructor
}

// Cache for the dynamically imported module
let nodeFetchModule: NodeFetchModule | null = null;

/**
* Get fetch function - uses dynamic import for CJS
*/
export async function getFetch(): Promise<any> {
// In test environment, use global fetch (mocked by jest-fetch-mock)
if (typeof global !== 'undefined' && global.fetch) {
return global.fetch;
}

if (!nodeFetchModule) {
// Use Function constructor to prevent TypeScript from converting to require()
const dynamicImport = new Function('specifier', 'return import(specifier)');
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
}

return nodeFetchModule.default;
}

/**
* Get Request constructor - uses dynamic import for CJS
*/
export async function getRequest(): Promise<any> {
// In test environment, use global Request or a mock
if (typeof global !== 'undefined' && global.Request) {
return global.Request;
}

if (!nodeFetchModule) {
// Use Function constructor to prevent TypeScript from converting to require()
const dynamicImport = new Function('specifier', 'return import(specifier)');
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
}

return nodeFetchModule.Request;
}

/**
* Get Response constructor - uses dynamic import for CJS
*/
export async function getResponse(): Promise<any> {
// In test environment, use global Response or a mock
if (typeof global !== 'undefined' && global.Response) {
return global.Response;
}

if (!nodeFetchModule) {
// Use Function constructor to prevent TypeScript from converting to require()
const dynamicImport = new Function('specifier', 'return import(specifier)');
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
}

return nodeFetchModule.Response;
}

// Export types as any for CJS compatibility
export type RequestInit = any;
export type HeadersInit = any;
export type Request = any;
export type Response = any;
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// This file is generated by scripts/exportVersion.js
export const SDK_VERSION = '7.13.0';
export const SDK_VERSION = '7.13.1';
4 changes: 2 additions & 2 deletions tests/apiClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ describe('APIClient', () => {
});

describe('newRequest', () => {
it('should set all the fields properly', () => {
it('should set all the fields properly', async () => {
client.headers = {
'global-header': 'global-value',
};
Expand All @@ -178,7 +178,7 @@ describe('APIClient', () => {
headers: { override: 'bar' },
},
};
const newRequest = client.newRequest(options);
const newRequest = await client.newRequest(options);

expect(newRequest.method).toBe('POST');
expect(newRequest.headers.raw()).toEqual({
Expand Down
Loading