Skip to content

Commit 9b8d8b8

Browse files
authored
Release v7.13.1 (#668)
* Release v7.13.1 * 17.13.1 * Added missing cjs-wrapper to the published builds * Updated changelog date * feat: add dual build system with dynamic fetch wrapper - Add fetchWrapper system with separate CJS/ESM implementations - Add setupFetchWrapper.js script for build-time wrapper selection - Update TypeScript configs to support dual module builds - Modify apiClient to use new fetch wrapper system - Update tests for new fetch handling approach This improves compatibility between CommonJS and ES modules by using dynamic imports for node-fetch in CJS builds while maintaining native fetch support in ESM builds. * Added tests * Fixed package.json version
1 parent cd02642 commit 9b8d8b8

16 files changed

+821
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [7.13.1] - 2025-09-18
99

1010
### Fixed
1111
- Broken CJS build outputs resulted in a "TypeError: Nylas is not a constructor" error

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"name": "nylas",
3-
"version": "7.13.0",
3+
"version": "7.13.1",
44
"description": "A NodeJS wrapper for the Nylas REST API for email, contacts, and calendar.",
55
"main": "lib/cjs/nylas.js",
66
"types": "lib/types/nylas.d.ts",
77
"module": "lib/esm/nylas.js",
88
"files": [
9-
"lib"
9+
"lib",
10+
"cjs-wrapper.js",
11+
"cjs-wrapper.d.ts"
1012
],
1113
"engines": {
1214
"node": ">=16"
@@ -24,8 +26,8 @@
2426
"generate-model-index": "node scripts/generateModelIndex.js",
2527
"prebuild": "npm run export-version && npm run generate-model-index",
2628
"build": "rm -rf lib && npm run build-esm && npm run build-cjs && npm run generate-lib-package-json",
27-
"build-esm": "tsc -p tsconfig.esm.json",
28-
"build-cjs": "tsc -p tsconfig.cjs.json",
29+
"build-esm": "node scripts/setupFetchWrapper.js esm && tsc -p tsconfig.esm.json",
30+
"build-cjs": "node scripts/setupFetchWrapper.js cjs && tsc -p tsconfig.cjs.json",
2931
"prepare": "npm run build",
3032
"build:docs": "typedoc --out docs",
3133
"version": "npm run export-version && git add src/version.ts"

scripts/setupFetchWrapper.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Script to copy the appropriate fetchWrapper implementation based on build type
3+
*/
4+
5+
const fs = require('fs');
6+
const path = require('path');
7+
8+
const buildType = process.argv[2]; // 'esm' or 'cjs'
9+
10+
if (!buildType || !['esm', 'cjs'].includes(buildType)) {
11+
console.error('Usage: node setupFetchWrapper.js <esm|cjs>');
12+
process.exit(1);
13+
}
14+
15+
const srcDir = path.join(__dirname, '..', 'src', 'utils');
16+
const sourceFile = path.join(srcDir, `fetchWrapper-${buildType}.ts`);
17+
const targetFile = path.join(srcDir, 'fetchWrapper.ts');
18+
19+
// Ensure source file exists
20+
if (!fs.existsSync(sourceFile)) {
21+
console.error(`Source file ${sourceFile} does not exist`);
22+
process.exit(1);
23+
}
24+
25+
// Copy the appropriate implementation
26+
fs.copyFileSync(sourceFile, targetFile);
27+
/* eslint-disable no-console */
28+
console.log(`✅ Copied fetchWrapper-${buildType}.ts to fetchWrapper.ts`);
29+
/* eslint-enable no-console */

src/apiClient.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import fetch, { Request, Response } from 'node-fetch';
21
import { Readable as _Readable } from 'node:stream';
32
import { NylasConfig, OverridableNylasConfig } from './config.js';
43
import {
@@ -11,6 +10,8 @@ import { SDK_VERSION } from './version.js';
1110
import { FormData } from 'formdata-node';
1211
import { FormDataEncoder as _FormDataEncoder } from 'form-data-encoder';
1312
import { snakeCase } from 'change-case';
13+
import { getFetch, getRequest } from './utils/fetchWrapper.js';
14+
import type { Request, Response } from './utils/fetchWrapper.js';
1415

1516
/**
1617
* The header key for the debugging flow ID
@@ -155,7 +156,7 @@ export default class APIClient {
155156
}
156157

157158
private async sendRequest(options: RequestOptionsParams): Promise<Response> {
158-
const req = this.newRequest(options);
159+
const req = await this.newRequest(options);
159160
const controller: AbortController = new AbortController();
160161

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

179180
try {
181+
const fetch = await getFetch();
180182
const response = await fetch(req, {
181183
signal: controller.signal as AbortSignal,
182184
});
@@ -271,9 +273,10 @@ export default class APIClient {
271273
return requestOptions;
272274
}
273275

274-
newRequest(options: RequestOptionsParams): Request {
276+
async newRequest(options: RequestOptionsParams): Promise<Request> {
275277
const newOptions = this.requestOptions(options);
276-
return new Request(newOptions.url, {
278+
const RequestConstructor = await getRequest();
279+
return new RequestConstructor(newOptions.url, {
277280
method: newOptions.method,
278281
headers: newOptions.headers,
279282
body: newOptions.body,

src/utils/fetchWrapper-cjs.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Fetch wrapper for CJS builds - uses dynamic imports for node-fetch compatibility
3+
*/
4+
5+
// Types for the dynamic import result
6+
interface NodeFetchModule {
7+
default: any; // fetch function
8+
Request: any; // Request constructor
9+
Response: any; // Response constructor
10+
}
11+
12+
// Cache for the dynamically imported module
13+
let nodeFetchModule: NodeFetchModule | null = null;
14+
15+
/**
16+
* Get fetch function - uses dynamic import for CJS
17+
*/
18+
export async function getFetch(): Promise<any> {
19+
// In test environment, use global fetch (mocked by jest-fetch-mock)
20+
if (typeof global !== 'undefined' && global.fetch) {
21+
return global.fetch;
22+
}
23+
24+
if (!nodeFetchModule) {
25+
// Use Function constructor to prevent TypeScript from converting to require()
26+
const dynamicImport = new Function('specifier', 'return import(specifier)');
27+
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
28+
}
29+
30+
return nodeFetchModule.default;
31+
}
32+
33+
/**
34+
* Get Request constructor - uses dynamic import for CJS
35+
*/
36+
export async function getRequest(): Promise<any> {
37+
// In test environment, use global Request or a mock
38+
if (typeof global !== 'undefined' && global.Request) {
39+
return global.Request;
40+
}
41+
42+
if (!nodeFetchModule) {
43+
// Use Function constructor to prevent TypeScript from converting to require()
44+
const dynamicImport = new Function('specifier', 'return import(specifier)');
45+
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
46+
}
47+
48+
return nodeFetchModule.Request;
49+
}
50+
51+
/**
52+
* Get Response constructor - uses dynamic import for CJS
53+
*/
54+
export async function getResponse(): Promise<any> {
55+
// In test environment, use global Response or a mock
56+
if (typeof global !== 'undefined' && global.Response) {
57+
return global.Response;
58+
}
59+
60+
if (!nodeFetchModule) {
61+
// Use Function constructor to prevent TypeScript from converting to require()
62+
const dynamicImport = new Function('specifier', 'return import(specifier)');
63+
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
64+
}
65+
66+
return nodeFetchModule.Response;
67+
}
68+
69+
// Export types as any for CJS compatibility
70+
export type RequestInit = any;
71+
export type HeadersInit = any;
72+
export type Request = any;
73+
export type Response = any;

src/utils/fetchWrapper-esm.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Fetch wrapper for ESM builds - uses static imports for optimal performance
3+
*/
4+
5+
import fetch, { Request, Response } from 'node-fetch';
6+
import type { RequestInit, HeadersInit } from 'node-fetch';
7+
8+
/**
9+
* Get fetch function - uses static import for ESM
10+
*/
11+
export async function getFetch(): Promise<typeof fetch> {
12+
return fetch;
13+
}
14+
15+
/**
16+
* Get Request constructor - uses static import for ESM
17+
*/
18+
export async function getRequest(): Promise<typeof Request> {
19+
return Request;
20+
}
21+
22+
/**
23+
* Get Response constructor - uses static import for ESM
24+
*/
25+
export async function getResponse(): Promise<typeof Response> {
26+
return Response;
27+
}
28+
29+
// Export types directly
30+
export type { RequestInit, HeadersInit, Request, Response };

src/utils/fetchWrapper.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Fetch wrapper for CJS builds - uses dynamic imports for node-fetch compatibility
3+
*/
4+
5+
// Types for the dynamic import result
6+
interface NodeFetchModule {
7+
default: any; // fetch function
8+
Request: any; // Request constructor
9+
Response: any; // Response constructor
10+
}
11+
12+
// Cache for the dynamically imported module
13+
let nodeFetchModule: NodeFetchModule | null = null;
14+
15+
/**
16+
* Get fetch function - uses dynamic import for CJS
17+
*/
18+
export async function getFetch(): Promise<any> {
19+
// In test environment, use global fetch (mocked by jest-fetch-mock)
20+
if (typeof global !== 'undefined' && global.fetch) {
21+
return global.fetch;
22+
}
23+
24+
if (!nodeFetchModule) {
25+
// Use Function constructor to prevent TypeScript from converting to require()
26+
const dynamicImport = new Function('specifier', 'return import(specifier)');
27+
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
28+
}
29+
30+
return nodeFetchModule.default;
31+
}
32+
33+
/**
34+
* Get Request constructor - uses dynamic import for CJS
35+
*/
36+
export async function getRequest(): Promise<any> {
37+
// In test environment, use global Request or a mock
38+
if (typeof global !== 'undefined' && global.Request) {
39+
return global.Request;
40+
}
41+
42+
if (!nodeFetchModule) {
43+
// Use Function constructor to prevent TypeScript from converting to require()
44+
const dynamicImport = new Function('specifier', 'return import(specifier)');
45+
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
46+
}
47+
48+
return nodeFetchModule.Request;
49+
}
50+
51+
/**
52+
* Get Response constructor - uses dynamic import for CJS
53+
*/
54+
export async function getResponse(): Promise<any> {
55+
// In test environment, use global Response or a mock
56+
if (typeof global !== 'undefined' && global.Response) {
57+
return global.Response;
58+
}
59+
60+
if (!nodeFetchModule) {
61+
// Use Function constructor to prevent TypeScript from converting to require()
62+
const dynamicImport = new Function('specifier', 'return import(specifier)');
63+
nodeFetchModule = (await dynamicImport('node-fetch')) as NodeFetchModule;
64+
}
65+
66+
return nodeFetchModule.Response;
67+
}
68+
69+
// Export types as any for CJS compatibility
70+
export type RequestInit = any;
71+
export type HeadersInit = any;
72+
export type Request = any;
73+
export type Response = any;

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// This file is generated by scripts/exportVersion.js
2-
export const SDK_VERSION = '7.13.0';
2+
export const SDK_VERSION = '7.13.1';

tests/apiClient.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('APIClient', () => {
162162
});
163163

164164
describe('newRequest', () => {
165-
it('should set all the fields properly', () => {
165+
it('should set all the fields properly', async () => {
166166
client.headers = {
167167
'global-header': 'global-value',
168168
};
@@ -178,7 +178,7 @@ describe('APIClient', () => {
178178
headers: { override: 'bar' },
179179
},
180180
};
181-
const newRequest = client.newRequest(options);
181+
const newRequest = await client.newRequest(options);
182182

183183
expect(newRequest.method).toBe('POST');
184184
expect(newRequest.headers.raw()).toEqual({

0 commit comments

Comments
 (0)